working commit

This commit is contained in:
2026-03-13 19:02:42 +02:00
parent bebbf79c7a
commit 5c1da77f4c
1329 changed files with 314708 additions and 39 deletions
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020-2022 wazero authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+118
View File
@@ -0,0 +1,118 @@
package binary
import (
"bytes"
"fmt"
"io"
"math"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
func decodeCode(r *bytes.Reader) (*wasm.Code, error) {
ss, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get the size of code: %w", err)
}
remaining := int64(ss)
// parse locals
ls, bytesRead, err := leb128.DecodeUint32(r)
remaining -= int64(bytesRead)
if err != nil {
return nil, fmt.Errorf("get the size locals: %v", err)
} else if remaining < 0 {
return nil, io.EOF
}
var nums []uint64
var types []wasm.ValueType
var sum uint64
var n uint32
for i := uint32(0); i < ls; i++ {
n, bytesRead, err = leb128.DecodeUint32(r)
remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte
if err != nil {
return nil, fmt.Errorf("read n of locals: %v", err)
} else if remaining < 0 {
return nil, io.EOF
}
sum += uint64(n)
nums = append(nums, uint64(n))
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read type of local: %v", err)
}
switch vt := b; vt {
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128:
types = append(types, vt)
default:
return nil, fmt.Errorf("invalid local type: 0x%x", vt)
}
}
if sum > math.MaxUint32 {
return nil, fmt.Errorf("too many locals: %d", sum)
}
var localTypes []wasm.ValueType
for i, num := range nums {
t := types[i]
for j := uint64(0); j < num; j++ {
localTypes = append(localTypes, t)
}
}
body := make([]byte, remaining)
if _, err = io.ReadFull(r, body); err != nil {
return nil, fmt.Errorf("read body: %w", err)
}
if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd {
return nil, fmt.Errorf("expr not end with OpcodeEnd")
}
return &wasm.Code{Body: body, LocalTypes: localTypes}, nil
}
// encodeCode returns the wasm.Code encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
func encodeCode(c *wasm.Code) []byte {
// local blocks compress locals while preserving index order by grouping locals of the same type.
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
localBlockCount := uint32(0) // how many blocks of locals with the same type (types can repeat!)
var localBlocks []byte
localTypeLen := len(c.LocalTypes)
if localTypeLen > 0 {
i := localTypeLen - 1
var runCount uint32 // count of the same type
var lastValueType wasm.ValueType // initialize to an invalid type 0
// iterate backwards so it is easier to size prefix
for ; i >= 0; i-- {
vt := c.LocalTypes[i]
if lastValueType != vt {
if runCount != 0 { // Only on the first iteration, this is zero when vt is compared against invalid
localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...)
}
lastValueType = vt
localBlocks = append(leb128.EncodeUint32(uint32(vt)), localBlocks...) // reuse the EncodeUint32 cache
localBlockCount++
runCount = 1
} else {
runCount++
}
}
localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...)
localBlocks = append(leb128.EncodeUint32(localBlockCount), localBlocks...)
} else {
localBlocks = leb128.EncodeUint32(0)
}
code := append(localBlocks, c.Body...)
return append(leb128.EncodeUint32(uint32(len(code))), code...)
}
+102
View File
@@ -0,0 +1,102 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/ieee754"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
func decodeConstantExpression(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.ConstantExpression, error) {
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read opcode: %v", err)
}
remainingBeforeData := int64(r.Len())
offsetAtData := r.Size() - remainingBeforeData
opcode := b
switch opcode {
case wasm.OpcodeI32Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt32(r)
case wasm.OpcodeI64Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt64(r)
case wasm.OpcodeF32Const:
_, err = ieee754.DecodeFloat32(r)
case wasm.OpcodeF64Const:
_, err = ieee754.DecodeFloat64(r)
case wasm.OpcodeGlobalGet:
_, _, err = leb128.DecodeUint32(r)
case wasm.OpcodeRefNull:
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("ref.null is not supported as %w", err)
}
reftype, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read reference type for ref.null: %w", err)
} else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref {
return nil, fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
}
case wasm.OpcodeRefFunc:
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("ref.func is not supported as %w", err)
}
// Parsing index.
_, _, err = leb128.DecodeUint32(r)
case wasm.OpcodeVecPrefix:
if err := features.RequireEnabled(wasm.CoreFeatureSIMD); err != nil {
return nil, fmt.Errorf("vector instructions are not supported as %w", err)
}
opcode, err = r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read vector instruction opcode suffix: %w", err)
}
if opcode != wasm.OpcodeVecV128Const {
return nil, fmt.Errorf("invalid vector opcode for const expression: %#x", opcode)
}
remainingBeforeData = int64(r.Len())
offsetAtData = r.Size() - remainingBeforeData
n, err := r.Read(make([]byte, 16))
if err != nil {
return nil, fmt.Errorf("read vector const instruction immediates: %w", err)
} else if n != 16 {
return nil, fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n)
}
default:
return nil, fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b)
}
if err != nil {
return nil, fmt.Errorf("read value: %v", err)
}
if b, err = r.ReadByte(); err != nil {
return nil, fmt.Errorf("look for end opcode: %v", err)
}
if b != wasm.OpcodeEnd {
return nil, fmt.Errorf("constant expression has been not terminated")
}
data := make([]byte, remainingBeforeData-int64(r.Len())-1)
if _, err := r.ReadAt(data, offsetAtData); err != nil {
return nil, fmt.Errorf("error re-buffering ConstantExpression.Data")
}
return &wasm.ConstantExpression{Opcode: opcode, Data: data}, nil
}
func encodeConstantExpression(expr *wasm.ConstantExpression) (ret []byte) {
ret = append(ret, expr.Opcode)
ret = append(ret, expr.Data...)
ret = append(ret, wasm.OpcodeEnd)
return
}
+22
View File
@@ -0,0 +1,22 @@
package binary
import (
"bytes"
"github.com/tetratelabs/wabin/wasm"
)
// decodeCustomSection deserializes the data **not** associated with the "name" key in SectionIDCustom.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
func decodeCustomSection(r *bytes.Reader, name string, limit uint64) (result *wasm.CustomSection, err error) {
buf := make([]byte, limit)
_, err = r.Read(buf)
result = &wasm.CustomSection{
Name: name,
Data: buf,
}
return
}
+94
View File
@@ -0,0 +1,94 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
// dataSegmentPrefix represents three types of data segments.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
type dataSegmentPrefix = uint32
const (
// dataSegmentPrefixActive is the prefix for the version 1.0 compatible
// data segment, which is classified as "active" in 2.0.
dataSegmentPrefixActive dataSegmentPrefix = 0x0
// dataSegmentPrefixPassive prefixes the "passive" data segment as in
// version 2.0 specification.
dataSegmentPrefixPassive dataSegmentPrefix = 0x1
// dataSegmentPrefixActiveWithMemoryIndex is the active prefix with memory
//index encoded which is defined for future use as of 2.0.
dataSegmentPrefixActiveWithMemoryIndex dataSegmentPrefix = 0x2
)
func decodeDataSegment(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.DataSegment, error) {
dataSegmentPrefix, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read data segment prefix: %w", err)
}
if dataSegmentPrefix != dataSegmentPrefixActive {
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("non-zero prefix for data segment is invalid as %w", err)
}
}
var expr *wasm.ConstantExpression
switch dataSegmentPrefix {
case dataSegmentPrefixActive,
dataSegmentPrefixActiveWithMemoryIndex:
// Active data segment as in
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
if dataSegmentPrefix == 0x2 {
d, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read memory index: %v", err)
} else if d != 0 {
return nil, fmt.Errorf("memory index must be zero but was %d", d)
}
}
expr, err = decodeConstantExpression(r, features)
if err != nil {
return nil, fmt.Errorf("read offset expression: %v", err)
}
case dataSegmentPrefixPassive:
// Passive data segment doesn't need const expr nor memory index encoded.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
default:
return nil, fmt.Errorf("invalid data segment prefix: 0x%x", dataSegmentPrefix)
}
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get the size of vector: %v", err)
}
b := make([]byte, vs)
if _, err := io.ReadFull(r, b); err != nil {
return nil, fmt.Errorf("read bytes for init: %v", err)
}
return &wasm.DataSegment{
OffsetExpression: expr,
Init: b,
}, nil
}
func encodeDataSegment(d *wasm.DataSegment) (ret []byte) {
if d.OffsetExpression == nil {
ret = append(ret, leb128.EncodeInt32(int32(dataSegmentPrefixPassive))...)
} else {
// Currently multiple memories are not supported.
ret = append(ret, leb128.EncodeInt32(int32(dataSegmentPrefixActive))...)
ret = append(ret, encodeConstantExpression(d.OffsetExpression)...)
}
ret = append(ret, leb128.EncodeUint32(uint32(len(d.Init)))...)
ret = append(ret, d.Init...)
return
}
+126
View File
@@ -0,0 +1,126 @@
package binary
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
// DecodeModule implements wasm.DecodeModule for the WebAssembly Binary Format
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
func DecodeModule(binary []byte, features wasm.CoreFeatures) (*wasm.Module, error) {
r := bytes.NewReader(binary)
// Magic number.
buf := make([]byte, 4)
if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) {
return nil, ErrInvalidMagicNumber
}
// Version.
if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) {
return nil, ErrInvalidVersion
}
m := &wasm.Module{}
for {
// TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA
sectionID, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, fmt.Errorf("read section id: %w", err)
}
sectionSize, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
}
sectionContentStart := r.Len()
switch sectionID {
case wasm.SectionIDCustom:
// First, validate the section and determine if the section for this name has already been set
name, nameSize, decodeErr := decodeUTF8(r, "custom section name")
if decodeErr != nil {
err = decodeErr
break
} else if sectionSize < nameSize {
err = fmt.Errorf("malformed custom section %s", name)
break
} else if name == "name" && m.NameSection != nil {
err = fmt.Errorf("redundant custom section %s", name)
break
}
// Now, either decode the NameSection or CustomSection
limit := sectionSize - nameSize
if name == "name" {
m.NameSection, err = decodeNameSection(r, uint64(limit))
} else {
custom, err := decodeCustomSection(r, name, uint64(limit))
if err != nil {
return nil, fmt.Errorf("failed to read custom section name[%s]: %w", name, err)
}
m.CustomSections = append(m.CustomSections, custom)
}
case wasm.SectionIDType:
m.TypeSection, err = decodeTypeSection(features, r)
case wasm.SectionIDImport:
if m.ImportSection, err = decodeImportSection(r, features); err != nil {
return nil, err // avoid re-wrapping the error.
}
case wasm.SectionIDFunction:
m.FunctionSection, err = decodeFunctionSection(r)
case wasm.SectionIDTable:
m.TableSection, err = decodeTableSection(r, features)
case wasm.SectionIDMemory:
m.MemorySection, err = decodeMemorySection(r)
case wasm.SectionIDGlobal:
if m.GlobalSection, err = decodeGlobalSection(r, features); err != nil {
return nil, err // avoid re-wrapping the error.
}
case wasm.SectionIDExport:
m.ExportSection, err = decodeExportSection(r)
case wasm.SectionIDStart:
if m.StartSection != nil {
return nil, errors.New("multiple start sections are invalid")
}
m.StartSection, err = decodeStartSection(r)
case wasm.SectionIDElement:
m.ElementSection, err = decodeElementSection(r, features)
case wasm.SectionIDCode:
m.CodeSection, err = decodeCodeSection(r)
case wasm.SectionIDData:
m.DataSection, err = decodeDataSection(r, features)
case wasm.SectionIDDataCount:
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("data count section not supported as %v", err)
}
m.DataCountSection, err = decodeDataCountSection(r)
default:
err = ErrInvalidSectionID
}
readBytes := sectionContentStart - r.Len()
if err == nil && int(sectionSize) != readBytes {
err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes)
}
if err != nil {
return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err)
}
}
functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode)
if functionCount != codeCount {
return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount)
}
return m, nil
}
+308
View File
@@ -0,0 +1,308 @@
package binary
import (
"bytes"
"errors"
"fmt"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
func ensureElementKindFuncRef(r *bytes.Reader) error {
elemKind, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read element prefix: %w", err)
}
if elemKind != 0x0 { // ElemKind is fixed to 0x0 now: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
return fmt.Errorf("element kind must be zero but was 0x%x", elemKind)
}
return nil
}
func decodeElementInitValueVector(r *bytes.Reader) ([]*wasm.Index, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
vec := make([]*wasm.Index, vs)
for i := range vec {
u32, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read function index: %w", err)
}
vec[i] = &u32
}
return vec, nil
}
func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, features wasm.CoreFeatures) ([]*wasm.Index, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err)
}
vec := make([]*wasm.Index, vs)
for i := range vec {
expr, err := decodeConstantExpression(r, features)
if err != nil {
return nil, err
}
switch expr.Opcode {
case wasm.OpcodeRefFunc:
if elemType != wasm.RefTypeFuncref {
return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType))
}
v, _, _ := leb128.DecodeUint32(bytes.NewReader(expr.Data))
vec[i] = &v
case wasm.OpcodeRefNull:
if elemType != expr.Data[0] {
return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s",
wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0]))
}
// vec[i] is already nil, so nothing to do.
default:
return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode))
}
}
return vec, nil
}
func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) {
ret, err = r.ReadByte()
if err != nil {
err = fmt.Errorf("read element ref type: %w", err)
return
}
if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref {
return 0, errors.New("ref type must be funcref or externref for element as of WebAssembly 2.0")
}
return
}
const (
// The prefix is explained at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
// elementSegmentPrefixLegacy is the legacy prefix and is only valid one
// before FeatureBulkMemoryOperations.
elementSegmentPrefixLegacy = iota
// elementSegmentPrefixPassiveFuncrefValueVector is the passive element
// whose indexes are encoded as vec(varint), and reftype is fixed to funcref.
elementSegmentPrefixPassiveFuncrefValueVector
// elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex is the same
// as elementSegmentPrefixPassiveFuncrefValueVector but active and table
// index is encoded.
elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex
// elementSegmentPrefixDeclarativeFuncrefValueVector is the same as
// elementSegmentPrefixPassiveFuncrefValueVector but declarative.
elementSegmentPrefixDeclarativeFuncrefValueVector
// elementSegmentPrefixActiveFuncrefConstExprVector is active and reftype
// is fixed to funcref and indexes are encoded as vec(const_expr).
elementSegmentPrefixActiveFuncrefConstExprVector
// elementSegmentPrefixPassiveConstExprVector is passive where indexes
// are encoded as vec(const_expr), and reftype is encoded.
elementSegmentPrefixPassiveConstExprVector
// elementSegmentPrefixPassiveConstExprVector is active where indexes are
// encoded as vec(const_expr), and reftype and table index are encoded.
elementSegmentPrefixActiveConstExprVector
// elementSegmentPrefixDeclarativeConstExprVector is declarative where
// indexes are encoded as vec(const_expr), and reftype is encoded.
elementSegmentPrefixDeclarativeConstExprVector
)
func decodeElementSegment(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.ElementSegment, error) {
prefix, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read element prefix: %w", err)
}
if prefix != elementSegmentPrefixLegacy {
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("non-zero prefix for element segment is invalid as %w", err)
}
}
// Encoding depends on the prefix and described at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
switch prefix {
case elementSegmentPrefixLegacy:
// Legacy prefix which is WebAssembly 1.0 compatible.
expr, err := decodeConstantExpression(r, features)
if err != nil {
return nil, fmt.Errorf("read expr for offset: %w", err)
}
init, err := decodeElementInitValueVector(r)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
OffsetExpr: expr,
Init: init,
Type: wasm.RefTypeFuncref,
Mode: wasm.ElementModeActive,
// Legacy prefix has the fixed table index zero.
TableIndex: 0,
}, nil
case elementSegmentPrefixPassiveFuncrefValueVector:
// Prefix 1 requires funcref.
if err = ensureElementKindFuncRef(r); err != nil {
return nil, err
}
init, err := decodeElementInitValueVector(r)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
Init: init,
Type: wasm.RefTypeFuncref,
Mode: wasm.ElementModePassive,
}, nil
case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex:
tableIndex, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
if tableIndex != 0 {
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
}
}
expr, err := decodeConstantExpression(r, features)
if err != nil {
return nil, fmt.Errorf("read expr for offset: %w", err)
}
// Prefix 2 requires funcref.
if err = ensureElementKindFuncRef(r); err != nil {
return nil, err
}
init, err := decodeElementInitValueVector(r)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
OffsetExpr: expr,
Init: init,
Type: wasm.RefTypeFuncref,
Mode: wasm.ElementModeActive,
TableIndex: tableIndex,
}, nil
case elementSegmentPrefixDeclarativeFuncrefValueVector:
// Prefix 3 requires funcref.
if err = ensureElementKindFuncRef(r); err != nil {
return nil, err
}
init, err := decodeElementInitValueVector(r)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
Init: init,
Type: wasm.RefTypeFuncref,
Mode: wasm.ElementModeDeclarative,
}, nil
case elementSegmentPrefixActiveFuncrefConstExprVector:
expr, err := decodeConstantExpression(r, features)
if err != nil {
return nil, fmt.Errorf("read expr for offset: %w", err)
}
init, err := decodeElementConstExprVector(r, wasm.RefTypeFuncref, features)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
OffsetExpr: expr,
Init: init,
Type: wasm.RefTypeFuncref,
Mode: wasm.ElementModeActive,
TableIndex: 0,
}, nil
case elementSegmentPrefixPassiveConstExprVector:
refType, err := decodeElementRefType(r)
if err != nil {
return nil, err
}
init, err := decodeElementConstExprVector(r, refType, features)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
Init: init,
Type: refType,
Mode: wasm.ElementModePassive,
}, nil
case elementSegmentPrefixActiveConstExprVector:
tableIndex, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
if tableIndex != 0 {
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
}
}
expr, err := decodeConstantExpression(r, features)
if err != nil {
return nil, fmt.Errorf("read expr for offset: %w", err)
}
refType, err := decodeElementRefType(r)
if err != nil {
return nil, err
}
init, err := decodeElementConstExprVector(r, refType, features)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
OffsetExpr: expr,
Init: init,
Type: refType,
Mode: wasm.ElementModeActive,
TableIndex: tableIndex,
}, nil
case elementSegmentPrefixDeclarativeConstExprVector:
refType, err := decodeElementRefType(r)
if err != nil {
return nil, err
}
init, err := decodeElementConstExprVector(r, refType, features)
if err != nil {
return nil, err
}
return &wasm.ElementSegment{
Init: init,
Type: refType,
Mode: wasm.ElementModeDeclarative,
}, nil
default:
return nil, fmt.Errorf("invalid element segment prefix: 0x%x", prefix)
}
}
// encodeCode returns the wasm.ElementSegment encoded in WebAssembly Binary Format.
//
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0
func encodeElement(e *wasm.ElementSegment) (ret []byte) {
if e.Mode == wasm.ElementModeActive {
ret = append(ret, leb128.EncodeInt32(int32(e.TableIndex))...)
ret = append(ret, encodeConstantExpression(e.OffsetExpr)...)
ret = append(ret, leb128.EncodeUint32(uint32(len(e.Init)))...)
for _, idx := range e.Init {
ret = append(ret, leb128.EncodeInt32(int32(*idx))...)
}
} else {
panic("TODO: support encoding for non-active elements in bulk-memory-operations proposal")
}
return
}
+59
View File
@@ -0,0 +1,59 @@
package binary
import (
"github.com/tetratelabs/wabin/wasm"
)
var sizePrefixedName = []byte{4, 'n', 'a', 'm', 'e'}
// EncodeModule implements wasm.EncodeModule for the WebAssembly Binary Format.
// Note: If saving to a file, the conventional extension is wasm
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
func EncodeModule(m *wasm.Module) (bytes []byte) {
bytes = append(Magic, version...)
if m.SectionElementCount(wasm.SectionIDType) > 0 {
bytes = append(bytes, encodeTypeSection(m.TypeSection)...)
}
if m.SectionElementCount(wasm.SectionIDImport) > 0 {
bytes = append(bytes, encodeImportSection(m.ImportSection)...)
}
if m.SectionElementCount(wasm.SectionIDFunction) > 0 {
bytes = append(bytes, encodeFunctionSection(m.FunctionSection)...)
}
if m.SectionElementCount(wasm.SectionIDTable) > 0 {
bytes = append(bytes, encodeTableSection(m.TableSection)...)
}
if m.SectionElementCount(wasm.SectionIDMemory) > 0 {
bytes = append(bytes, encodeMemorySection(m.MemorySection)...)
}
if m.SectionElementCount(wasm.SectionIDGlobal) > 0 {
bytes = append(bytes, encodeGlobalSection(m.GlobalSection)...)
}
if m.SectionElementCount(wasm.SectionIDExport) > 0 {
bytes = append(bytes, encodeExportSection(m.ExportSection)...)
}
if m.SectionElementCount(wasm.SectionIDStart) > 0 {
bytes = append(bytes, encodeStartSection(*m.StartSection)...)
}
if m.SectionElementCount(wasm.SectionIDElement) > 0 {
bytes = append(bytes, encodeElementSection(m.ElementSection)...)
}
if m.SectionElementCount(wasm.SectionIDCode) > 0 {
bytes = append(bytes, encodeCodeSection(m.CodeSection)...)
}
if m.SectionElementCount(wasm.SectionIDData) > 0 {
bytes = append(bytes, encodeDataSection(m.DataSection)...)
}
if m.SectionElementCount(wasm.SectionIDCustom) > 0 {
// >> The name section should appear only once in a module, and only after the data section.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
if m.NameSection != nil {
nameSection := append(sizePrefixedName, encodeNameSectionData(m.NameSection)...)
bytes = append(bytes, encodeSection(wasm.SectionIDCustom, nameSection)...)
}
for _, custom := range m.CustomSections {
bytes = append(bytes, encodeSection(wasm.SectionIDCustom, encodeCustomSection(custom))...)
}
}
return
}
+11
View File
@@ -0,0 +1,11 @@
package binary
import "errors"
var (
ErrInvalidByte = errors.New("invalid byte")
ErrInvalidMagicNumber = errors.New("invalid magic number")
ErrInvalidVersion = errors.New("invalid version header")
ErrInvalidSectionID = errors.New("invalid section id")
ErrCustomSectionNotFound = errors.New("custom section not found")
)
+43
View File
@@ -0,0 +1,43 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
func decodeExport(r *bytes.Reader) (i *wasm.Export, err error) {
i = &wasm.Export{}
if i.Name, _, err = decodeUTF8(r, "export name"); err != nil {
return nil, err
}
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("error decoding export kind: %w", err)
}
i.Type = b
switch i.Type {
case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal:
if i.Index, _, err = leb128.DecodeUint32(r); err != nil {
return nil, fmt.Errorf("error decoding export index: %w", err)
}
default:
return nil, fmt.Errorf("%w: invalid byte for exportdesc: %#x", ErrInvalidByte, b)
}
return
}
// encodeExport returns the wasm.Export encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0
func encodeExport(i *wasm.Export) []byte {
data := encodeSizePrefixed([]byte(i.Name))
data = append(data, i.Type)
data = append(data, leb128.EncodeUint32(i.Index)...)
return data
}
+100
View File
@@ -0,0 +1,100 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
var nullary = []byte{0x60, 0, 0}
// encodedOneParam is a cache of wasm.FunctionType values for param length 1 and result length 0
var encodedOneParam = map[wasm.ValueType][]byte{
wasm.ValueTypeI32: {0x60, 1, wasm.ValueTypeI32, 0},
wasm.ValueTypeI64: {0x60, 1, wasm.ValueTypeI64, 0},
wasm.ValueTypeF32: {0x60, 1, wasm.ValueTypeF32, 0},
wasm.ValueTypeF64: {0x60, 1, wasm.ValueTypeF64, 0},
}
// encodedOneResult is a cache of wasm.FunctionType values for param length 0 and result length 1
var encodedOneResult = map[wasm.ValueType][]byte{
wasm.ValueTypeI32: {0x60, 0, 1, wasm.ValueTypeI32},
wasm.ValueTypeI64: {0x60, 0, 1, wasm.ValueTypeI64},
wasm.ValueTypeF32: {0x60, 0, 1, wasm.ValueTypeF32},
wasm.ValueTypeF64: {0x60, 0, 1, wasm.ValueTypeF64},
}
// encodeFunctionType returns the wasm.FunctionType encoded in WebAssembly Binary Format.
//
// Note: Function types are encoded by the byte 0x60 followed by the respective vectors of parameter and result types.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A4
func encodeFunctionType(t *wasm.FunctionType) []byte {
paramCount, resultCount := len(t.Params), len(t.Results)
if paramCount == 0 && resultCount == 0 {
return nullary
}
if resultCount == 0 {
if paramCount == 1 {
if encoded, ok := encodedOneParam[t.Params[0]]; ok {
return encoded
}
}
return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 0)
} else if resultCount == 1 {
if paramCount == 0 {
if encoded, ok := encodedOneResult[t.Results[0]]; ok {
return encoded
}
}
return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 1, t.Results[0])
}
// Only reached when "multi-value" is enabled because WebAssembly supports at most 1 result.
data := append([]byte{0x60}, encodeValTypes(t.Params)...)
return append(data, encodeValTypes(t.Results)...)
}
func decodeFunctionType(features wasm.CoreFeatures, r *bytes.Reader) (*wasm.FunctionType, error) {
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read leading byte: %w", err)
}
if b != 0x60 {
return nil, fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b)
}
paramCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("could not read parameter count: %w", err)
}
paramTypes, err := decodeValueTypes(r, paramCount)
if err != nil {
return nil, fmt.Errorf("could not read parameter types: %w", err)
}
resultCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("could not read result count: %w", err)
}
// Guard >1.0 feature multi-value
if resultCount > 1 {
if err = features.RequireEnabled(wasm.CoreFeatureMultiValue); err != nil {
return nil, fmt.Errorf("multiple result types invalid as %v", err)
}
}
resultTypes, err := decodeValueTypes(r, resultCount)
if err != nil {
return nil, fmt.Errorf("could not read result types: %w", err)
}
ret := &wasm.FunctionType{
Params: paramTypes,
Results: resultTypes,
}
return ret, nil
}
+66
View File
@@ -0,0 +1,66 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/wasm"
)
// decodeGlobal returns the wasm.Global decoded with the WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global
func decodeGlobal(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.Global, error) {
gt, err := decodeGlobalType(r)
if err != nil {
return nil, err
}
init, err := decodeConstantExpression(r, features)
if err != nil {
return nil, err
}
return &wasm.Global{Type: gt, Init: init}, nil
}
// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-globaltype
func decodeGlobalType(r *bytes.Reader) (*wasm.GlobalType, error) {
vt, err := decodeValueTypes(r, 1)
if err != nil {
return nil, fmt.Errorf("read value type: %w", err)
}
ret := &wasm.GlobalType{
ValType: vt[0],
}
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read mutablity: %w", err)
}
switch mut := b; mut {
case 0x00: // not mutable
case 0x01: // mutable
ret.Mutable = true
default:
return nil, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut)
}
return ret, nil
}
// encodeGlobal returns the wasm.Global encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0
func encodeGlobal(g *wasm.Global) (data []byte) {
var mutable byte
if g.Type.Mutable {
mutable = 1
}
data = []byte{g.Type.ValType, mutable}
data = append(data, encodeConstantExpression(g.Init)...)
return
}
+9
View File
@@ -0,0 +1,9 @@
package binary
// Magic is the 4 byte preamble (literally "\0asm") of the binary format
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-magic
var Magic = []byte{0x00, 0x61, 0x73, 0x6D}
// version is format version and doesn't change between known specification versions
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-version
var version = []byte{0x01, 0x00, 0x00, 0x00}
+78
View File
@@ -0,0 +1,78 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
func decodeImport(
r *bytes.Reader,
idx uint32,
features wasm.CoreFeatures,
) (i *wasm.Import, err error) {
i = &wasm.Import{}
if i.Module, _, err = decodeUTF8(r, "import module"); err != nil {
return nil, fmt.Errorf("import[%d] error decoding module: %w", idx, err)
}
if i.Name, _, err = decodeUTF8(r, "import name"); err != nil {
return nil, fmt.Errorf("import[%d] error decoding name: %w", idx, err)
}
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("import[%d] error decoding type: %w", idx, err)
}
i.Type = b
switch i.Type {
case wasm.ExternTypeFunc:
i.DescFunc, _, err = leb128.DecodeUint32(r)
case wasm.ExternTypeTable:
i.DescTable, err = decodeTable(r, features)
case wasm.ExternTypeMemory:
i.DescMem, err = decodeMemory(r)
case wasm.ExternTypeGlobal:
i.DescGlobal, err = decodeGlobalType(r)
default:
err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b)
}
if err != nil {
return nil, fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, wasm.ExternTypeName(i.Type), i.Module, i.Name, err)
}
return
}
// encodeImport returns the wasm.Import encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import
func encodeImport(i *wasm.Import) []byte {
data := encodeSizePrefixed([]byte(i.Module))
data = append(data, encodeSizePrefixed([]byte(i.Name))...)
data = append(data, i.Type)
switch i.Type {
case wasm.ExternTypeFunc:
data = append(data, leb128.EncodeUint32(i.DescFunc)...)
case wasm.ExternTypeTable:
data = append(data, wasm.RefTypeFuncref)
data = append(data, encodeLimitsType(i.DescTable.Min, i.DescTable.Max)...)
case wasm.ExternTypeMemory:
maxPtr := &i.DescMem.Max
if !i.DescMem.IsMaxEncoded {
maxPtr = nil
}
data = append(data, encodeLimitsType(i.DescMem.Min, maxPtr)...)
case wasm.ExternTypeGlobal:
g := i.DescGlobal
var mutable byte
if g.Mutable {
mutable = 1
}
data = append(data, g.ValType, mutable)
default:
panic(fmt.Errorf("invalid externtype: %s", wasm.ExternTypeName(i.Type)))
}
return data
}
+52
View File
@@ -0,0 +1,52 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/leb128"
)
// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, err error) {
var flag byte
if flag, err = r.ReadByte(); err != nil {
err = fmt.Errorf("read leading byte: %v", err)
return
}
switch flag {
case 0x00:
min, _, err = leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("read min of limit: %v", err)
}
case 0x01:
min, _, err = leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("read min of limit: %v", err)
return
}
var m uint32
if m, _, err = leb128.DecodeUint32(r); err != nil {
err = fmt.Errorf("read max of limit: %v", err)
} else {
max = &m
}
default:
err = fmt.Errorf("%v for limits: %#x != 0x00 or 0x01", ErrInvalidByte, flag)
}
return
}
// encodeLimitsType returns the `limitsType` (min, max) encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
func encodeLimitsType(min uint32, max *uint32) []byte {
if max == nil {
return append(leb128.EncodeUint32(0x00), leb128.EncodeUint32(min)...)
}
return append(leb128.EncodeUint32(0x01), append(leb128.EncodeUint32(min), leb128.EncodeUint32(*max)...)...)
}
+43
View File
@@ -0,0 +1,43 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/wasm"
)
// decodeMemory returns the wasm.Memory decoded with the WebAssembly
// Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory
func decodeMemory(r *bytes.Reader) (*wasm.Memory, error) {
min, maxP, err := decodeLimitsType(r)
if err != nil {
return nil, err
}
mem := &wasm.Memory{Min: min}
if maxP != nil {
mem.Max = *maxP
mem.IsMaxEncoded = true
if min > mem.Max {
return nil, fmt.Errorf("min %d pages (%s) > max %d pages (%s)",
min, wasm.PagesToUnitOfBytes(min), mem.Max, wasm.PagesToUnitOfBytes(mem.Max))
}
}
return mem, nil
}
// encodeMemory returns the wasm.Memory encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory
func encodeMemory(i *wasm.Memory) []byte {
maxPtr := &i.Max
if !i.IsMaxEncoded {
maxPtr = nil
}
return encodeLimitsType(i.Min, maxPtr)
}
+228
View File
@@ -0,0 +1,228 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
const (
// subsectionIDModuleName contains only the module name.
subsectionIDModuleName = uint8(0)
// subsectionIDFunctionNames is a map of indices to function names, in ascending order by function index
subsectionIDFunctionNames = uint8(1)
// subsectionIDLocalNames contain a map of function indices to a map of local indices to their names, in ascending
// order by function and local index
subsectionIDLocalNames = uint8(2)
)
// decodeNameSection deserializes the data associated with the "name" key in SectionIDCustom according to the
// standard:
//
// * ModuleName decode from subsection 0
// * FunctionNames decode from subsection 1
// * LocalNames decode from subsection 2
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) {
// TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based
// leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte.
result = &wasm.NameSection{}
// subsectionID is decoded if known, and skipped if not
var subsectionID uint8
// subsectionSize is the length to skip when the subsectionID is unknown
var subsectionSize uint32
var bytesRead uint64
for limit > 0 {
if subsectionID, err = r.ReadByte(); err != nil {
if err == io.EOF {
return result, nil
}
// TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer
return nil, fmt.Errorf("failed to read a subsection ID: %w", err)
}
limit--
if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil {
return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err)
}
limit -= bytesRead
switch subsectionID {
case subsectionIDModuleName:
if result.ModuleName, _, err = decodeUTF8(r, "module name"); err != nil {
return nil, err
}
case subsectionIDFunctionNames:
if result.FunctionNames, err = decodeFunctionNames(r); err != nil {
return nil, err
}
case subsectionIDLocalNames:
if result.LocalNames, err = decodeLocalNames(r); err != nil {
return nil, err
}
default: // Skip other subsections.
// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil {
return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err)
}
}
limit -= uint64(subsectionSize)
}
return
}
func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) {
functionCount, err := decodeFunctionCount(r, subsectionIDFunctionNames)
if err != nil {
return nil, err
}
result := make(wasm.NameMap, functionCount)
for i := uint32(0); i < functionCount; i++ {
functionIndex, err := decodeFunctionIndex(r, subsectionIDFunctionNames)
if err != nil {
return nil, err
}
name, _, err := decodeUTF8(r, "function[%d] name", functionIndex)
if err != nil {
return nil, err
}
result[i] = &wasm.NameAssoc{Index: functionIndex, Name: name}
}
return result, nil
}
func decodeLocalNames(r *bytes.Reader) (wasm.IndirectNameMap, error) {
functionCount, err := decodeFunctionCount(r, subsectionIDLocalNames)
if err != nil {
return nil, err
}
result := make(wasm.IndirectNameMap, functionCount)
for i := uint32(0); i < functionCount; i++ {
functionIndex, err := decodeFunctionIndex(r, subsectionIDLocalNames)
if err != nil {
return nil, err
}
localCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("failed to read the local count for function[%d]: %w", functionIndex, err)
}
locals := make(wasm.NameMap, localCount)
for j := uint32(0); j < localCount; j++ {
localIndex, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("failed to read a local index of function[%d]: %w", functionIndex, err)
}
name, _, err := decodeUTF8(r, "function[%d] local[%d] name", functionIndex, localIndex)
if err != nil {
return nil, err
}
locals[j] = &wasm.NameAssoc{Index: localIndex, Name: name}
}
result[i] = &wasm.NameMapAssoc{Index: functionIndex, NameMap: locals}
}
return result, nil
}
func decodeFunctionIndex(r *bytes.Reader, subsectionID uint8) (uint32, error) {
functionIndex, _, err := leb128.DecodeUint32(r)
if err != nil {
return 0, fmt.Errorf("failed to read a function index in subsection[%d]: %w", subsectionID, err)
}
return functionIndex, nil
}
func decodeFunctionCount(r *bytes.Reader, subsectionID uint8) (uint32, error) {
functionCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return 0, fmt.Errorf("failed to read the function count of subsection[%d]: %w", subsectionID, err)
}
return functionCount, nil
}
// encodeNameSectionData serializes the data for the "name" key in wasm.SectionIDCustom according to the
// standard:
//
// Note: The result can be nil because this does not encode empty subsections
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
func encodeNameSectionData(n *wasm.NameSection) (data []byte) {
if n.ModuleName != "" {
data = append(data, encodeNameSubsection(subsectionIDModuleName, encodeSizePrefixed([]byte(n.ModuleName)))...)
}
if fd := encodeFunctionNameData(n); len(fd) > 0 {
data = append(data, encodeNameSubsection(subsectionIDFunctionNames, fd)...)
}
if ld := encodeLocalNameData(n); len(ld) > 0 {
data = append(data, encodeNameSubsection(subsectionIDLocalNames, ld)...)
}
return
}
// encodeFunctionNameData encodes the data for the function name subsection.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-funcnamesec
func encodeFunctionNameData(n *wasm.NameSection) []byte {
if len(n.FunctionNames) == 0 {
return nil
}
return encodeNameMap(n.FunctionNames)
}
func encodeNameMap(m wasm.NameMap) []byte {
count := uint32(len(m))
data := leb128.EncodeUint32(count)
for _, na := range m {
data = append(data, encodeNameAssoc(na)...)
}
return data
}
// encodeLocalNameData encodes the data for the local name subsection.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-localnamesec
func encodeLocalNameData(n *wasm.NameSection) []byte {
if len(n.LocalNames) == 0 {
return nil
}
funcNameCount := uint32(len(n.LocalNames))
subsection := leb128.EncodeUint32(funcNameCount)
for _, na := range n.LocalNames {
locals := encodeNameMap(na.NameMap)
subsection = append(subsection, append(leb128.EncodeUint32(na.Index), locals...)...)
}
return subsection
}
// encodeNameSubsection returns a buffer encoding the given subsection
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#subsections%E2%91%A0
func encodeNameSubsection(subsectionID uint8, content []byte) []byte {
contentSizeInBytes := leb128.EncodeUint32(uint32(len(content)))
result := []byte{subsectionID}
result = append(result, contentSizeInBytes...)
result = append(result, content...)
return result
}
// encodeNameAssoc encodes the index and data prefixed by their size.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap
func encodeNameAssoc(na *wasm.NameAssoc) []byte {
return append(leb128.EncodeUint32(na.Index), encodeSizePrefixed([]byte(na.Name))...)
}
// encodeSizePrefixed encodes the data prefixed by their size.
func encodeSizePrefixed(data []byte) []byte {
size := leb128.EncodeUint32(uint32(len(data)))
return append(size, data...)
}
+342
View File
@@ -0,0 +1,342 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
func decodeTypeSection(features wasm.CoreFeatures, r *bytes.Reader) ([]*wasm.FunctionType, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]*wasm.FunctionType, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeFunctionType(features, r); err != nil {
return nil, fmt.Errorf("read %d-th type: %v", i, err)
}
}
return result, nil
}
func decodeImportSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Import, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]*wasm.Import, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeImport(r, i, features); err != nil {
return nil, err
}
}
return result, nil
}
func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]uint32, vs)
for i := uint32(0); i < vs; i++ {
if result[i], _, err = leb128.DecodeUint32(r); err != nil {
return nil, fmt.Errorf("get type index: %w", err)
}
}
return result, err
}
func decodeTableSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Table, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("error reading size")
}
if vs > 1 {
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
return nil, fmt.Errorf("at most one table allowed in module as %w", err)
}
}
ret := make([]*wasm.Table, vs)
for i := range ret {
table, err := decodeTable(r, features)
if err != nil {
return nil, err
}
ret[i] = table
}
return ret, nil
}
func decodeMemorySection(r *bytes.Reader) (*wasm.Memory, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("error reading size")
}
if vs > 1 {
return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs)
}
return decodeMemory(r)
}
func decodeGlobalSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Global, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]*wasm.Global, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeGlobal(r, features); err != nil {
return nil, fmt.Errorf("global[%d]: %w", i, err)
}
}
return result, nil
}
func decodeExportSection(r *bytes.Reader) ([]*wasm.Export, error) {
vs, _, sizeErr := leb128.DecodeUint32(r)
if sizeErr != nil {
return nil, fmt.Errorf("get size of vector: %v", sizeErr)
}
usedName := make(map[string]struct{}, vs)
exportSection := make([]*wasm.Export, 0, vs)
for i := wasm.Index(0); i < vs; i++ {
export, err := decodeExport(r)
if err != nil {
return nil, fmt.Errorf("read export: %w", err)
}
if _, ok := usedName[export.Name]; ok {
return nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name)
} else {
usedName[export.Name] = struct{}{}
}
exportSection = append(exportSection, export)
}
return exportSection, nil
}
func decodeStartSection(r *bytes.Reader) (*wasm.Index, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get function index: %w", err)
}
return &vs, nil
}
func decodeElementSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.ElementSegment, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]*wasm.ElementSegment, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeElementSegment(r, features); err != nil {
return nil, fmt.Errorf("read element: %w", err)
}
}
return result, nil
}
func decodeCodeSection(r *bytes.Reader) ([]*wasm.Code, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]*wasm.Code, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeCode(r); err != nil {
return nil, fmt.Errorf("read %d-th code segment: %v", i, err)
}
}
return result, nil
}
func decodeDataSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.DataSegment, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]*wasm.DataSegment, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeDataSegment(r, features); err != nil {
return nil, fmt.Errorf("read data segment: %w", err)
}
}
return result, nil
}
func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) {
v, _, err := leb128.DecodeUint32(r)
if err != nil && err != io.EOF {
// data count is optional, so EOF is fine.
return nil, err
}
return &v, nil
}
// encodeSection encodes the sectionID, the size of its contents in bytes,
// followed by the contents.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
func encodeSection(sectionID wasm.SectionID, contents []byte) []byte {
return append([]byte{sectionID}, encodeSizePrefixed(contents)...)
}
// encodeTypeSection encodes a wasm.SectionIDType for the given imports in
// WebAssembly Binary Format.
//
// See encodeFunctionType
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#type-section%E2%91%A0
func encodeTypeSection(types []*wasm.FunctionType) []byte {
contents := leb128.EncodeUint32(uint32(len(types)))
for _, t := range types {
contents = append(contents, encodeFunctionType(t)...)
}
return encodeSection(wasm.SectionIDType, contents)
}
// encodeImportSection encodes a wasm.SectionIDImport for the given imports in
// WebAssembly Binary Format.
//
// See encodeImport
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0
func encodeImportSection(imports []*wasm.Import) []byte {
contents := leb128.EncodeUint32(uint32(len(imports)))
for _, i := range imports {
contents = append(contents, encodeImport(i)...)
}
return encodeSection(wasm.SectionIDImport, contents)
}
// encodeFunctionSection encodes a wasm.SectionIDFunction for the type indices
// associated with module-defined functions in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0
func encodeFunctionSection(typeIndices []wasm.Index) []byte {
contents := leb128.EncodeUint32(uint32(len(typeIndices)))
for _, index := range typeIndices {
contents = append(contents, leb128.EncodeUint32(index)...)
}
return encodeSection(wasm.SectionIDFunction, contents)
}
// encodeCodeSection encodes a wasm.SectionIDCode for the module-defined
// function in WebAssembly Binary Format.
//
// See encodeCode
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
func encodeCodeSection(code []*wasm.Code) []byte {
contents := leb128.EncodeUint32(uint32(len(code)))
for _, i := range code {
contents = append(contents, encodeCode(i)...)
}
return encodeSection(wasm.SectionIDCode, contents)
}
// encodeTableSection encodes a wasm.SectionIDTable for the module-defined
// function in WebAssembly Binary Format.
//
// See encodeTable
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0
func encodeTableSection(tables []*wasm.Table) []byte {
var contents = leb128.EncodeUint32(uint32(len(tables)))
for _, table := range tables {
contents = append(contents, encodeTable(table)...)
}
return encodeSection(wasm.SectionIDTable, contents)
}
// encodeMemorySection encodes a wasm.SectionIDMemory for the module-defined
// function in WebAssembly Binary Format.
//
// See encodeMemory
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0
func encodeMemorySection(memory *wasm.Memory) []byte {
contents := append([]byte{1}, encodeMemory(memory)...)
return encodeSection(wasm.SectionIDMemory, contents)
}
// encodeGlobalSection encodes a wasm.SectionIDGlobal for the given globals in
// WebAssembly Binary Format.
//
// See encodeGlobal
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0
func encodeGlobalSection(globals []*wasm.Global) []byte {
contents := leb128.EncodeUint32(uint32(len(globals)))
for _, g := range globals {
contents = append(contents, encodeGlobal(g)...)
}
return encodeSection(wasm.SectionIDGlobal, contents)
}
// encodeExportSection encodes a wasm.SectionIDExport for the given exports in
// WebAssembly Binary Format.
//
// See encodeExport
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0
func encodeExportSection(exports []*wasm.Export) []byte {
contents := leb128.EncodeUint32(uint32(len(exports)))
for _, e := range exports {
contents = append(contents, encodeExport(e)...)
}
return encodeSection(wasm.SectionIDExport, contents)
}
// encodeStartSection encodes a wasm.SectionIDStart for the given function
// index in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0
func encodeStartSection(funcidx wasm.Index) []byte {
return encodeSection(wasm.SectionIDStart, leb128.EncodeUint32(funcidx))
}
// encodeElementSection encodes a wasm.SectionIDElement for the elements in
// WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0
func encodeElementSection(elements []*wasm.ElementSegment) []byte {
contents := leb128.EncodeUint32(uint32(len(elements)))
for _, e := range elements {
contents = append(contents, encodeElement(e)...)
}
return encodeSection(wasm.SectionIDElement, contents)
}
// encodeDataSection encodes a wasm.SectionIDData for the data in WebAssembly 1.0 (20191205)
// Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#data-section%E2%91%A0
func encodeDataSection(datum []*wasm.DataSegment) []byte {
contents := leb128.EncodeUint32(uint32(len(datum)))
for _, d := range datum {
contents = append(contents, encodeDataSegment(d)...)
}
return encodeSection(wasm.SectionIDData, contents)
}
// encodeCustomSection encodes a wasm.SectionIDCustom for the data in WebAssembly 1.0 (20191205)
// Binary Format. This is used for custom sections that are **not** associated with the "name" key.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
func encodeCustomSection(c *wasm.CustomSection) (data []byte) {
data = make([]byte, 0, 1+len(c.Name)+len(c.Data))
l := byte(len(c.Name))
data = append(data, l)
data = append(data, []byte(c.Name)...)
data = append(data, c.Data...)
return
}
+45
View File
@@ -0,0 +1,45 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wabin/wasm"
)
// decodeTable returns the wasm.Table decoded with the WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
func decodeTable(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.Table, error) {
tableType, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read leading byte: %v", err)
}
if tableType != wasm.RefTypeFuncref {
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
return nil, fmt.Errorf("table type funcref is invalid: %w", err)
}
}
min, max, err := decodeLimitsType(r)
if err != nil {
return nil, fmt.Errorf("read limits: %v", err)
}
if min > wasm.MaximumFunctionIndex {
return nil, fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex)
}
if max != nil {
if *max < min {
return nil, fmt.Errorf("table size minimum must not be greater than maximum")
}
}
return &wasm.Table{Min: min, Max: max, Type: tableType}, nil
}
// encodeTable returns the wasm.Table encoded in WebAssembly Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
func encodeTable(i *wasm.Table) []byte {
return append([]byte{i.Type}, encodeLimitsType(i.Min, i.Max)...)
}
+89
View File
@@ -0,0 +1,89 @@
package binary
import (
"bytes"
"fmt"
"io"
"unicode/utf8"
"github.com/tetratelabs/wabin/leb128"
"github.com/tetratelabs/wabin/wasm"
)
var noValType = []byte{0}
// encodedValTypes is a cache of size prefixed binary encoding of known val types.
var encodedValTypes = map[wasm.ValueType][]byte{
wasm.ValueTypeI32: {1, wasm.ValueTypeI32},
wasm.ValueTypeI64: {1, wasm.ValueTypeI64},
wasm.ValueTypeF32: {1, wasm.ValueTypeF32},
wasm.ValueTypeF64: {1, wasm.ValueTypeF64},
wasm.ValueTypeExternref: {1, wasm.ValueTypeExternref},
wasm.ValueTypeFuncref: {1, wasm.ValueTypeFuncref},
wasm.ValueTypeV128: {1, wasm.ValueTypeV128},
}
// encodeValTypes fast paths binary encoding of common value type lengths
func encodeValTypes(vt []wasm.ValueType) []byte {
// Special case nullary and parameter lengths of wasi_snapshot_preview1 to avoid excess allocations
switch uint32(len(vt)) {
case 0: // nullary
return noValType
case 1: // ex $wasi.fd_close or any result
if encoded, ok := encodedValTypes[vt[0]]; ok {
return encoded
}
case 2: // ex $wasi.environ_sizes_get
return []byte{2, vt[0], vt[1]}
case 4: // ex $wasi.fd_write
return []byte{4, vt[0], vt[1], vt[2], vt[3]}
case 9: // ex $wasi.fd_write
return []byte{9, vt[0], vt[1], vt[2], vt[3], vt[4], vt[5], vt[6], vt[7], vt[8]}
}
// Slow path others until someone complains with a valid signature
count := leb128.EncodeUint32(uint32(len(vt)))
return append(count, vt...)
}
func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
if num == 0 {
return nil, nil
}
ret := make([]wasm.ValueType, num)
buf := make([]wasm.ValueType, num)
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
}
for i, v := range buf {
switch v {
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128:
ret[i] = v
default:
return nil, fmt.Errorf("invalid value type: %d", v)
}
}
return ret, nil
}
// decodeUTF8 decodes a size prefixed string from the reader, returning it and the count of bytes read.
// contextFormat and contextArgs apply an error format when present
func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{}) (string, uint32, error) {
size, sizeOfSize, err := leb128.DecodeUint32(r)
if err != nil {
return "", 0, fmt.Errorf("failed to read %s size: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
}
buf := make([]byte, size)
if _, err = io.ReadFull(r, buf); err != nil {
return "", 0, fmt.Errorf("failed to read %s: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
}
if !utf8.Valid(buf) {
return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...))
}
return string(buf), size + uint32(sizeOfSize), nil
}
+31
View File
@@ -0,0 +1,31 @@
package ieee754
import (
"encoding/binary"
"io"
"math"
)
// DecodeFloat32 decodes a float32 in IEEE 754 binary representation.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2
func DecodeFloat32(r io.Reader) (float32, error) {
buf := make([]byte, 4)
_, err := io.ReadFull(r, buf)
if err != nil {
return 0, err
}
raw := binary.LittleEndian.Uint32(buf)
return math.Float32frombits(raw), nil
}
// DecodeFloat64 decodes a float64 in IEEE 754 binary representation.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2
func DecodeFloat64(r io.Reader) (float64, error) {
buf := make([]byte, 8)
_, err := io.ReadFull(r, buf)
if err != nil {
return 0, err
}
raw := binary.LittleEndian.Uint64(buf)
return math.Float64frombits(raw), nil
}
+240
View File
@@ -0,0 +1,240 @@
package leb128
import (
"bytes"
"errors"
"fmt"
)
const (
maxVarintLen32 = 5
maxVarintLen64 = 10
)
var (
errOverflow32 = errors.New("overflows a 32-bit integer")
errOverflow33 = errors.New("overflows a 33-bit integer")
errOverflow64 = errors.New("overflows a 64-bit integer")
)
// EncodeInt32 encodes the signed value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer
func EncodeInt32(value int32) []byte {
return EncodeInt64(int64(value))
}
// EncodeInt64 encodes the signed value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer
func EncodeInt64(value int64) (buf []byte) {
for {
// Take 7 remaining low-order bits from the value into b.
b := uint8(value & 0x7f)
// Extract the sign bit.
s := uint8(value & 0x40)
value >>= 7
// The encoding unsigned numbers is simpler as it only needs to check if the value is non-zero to tell if there
// are more bits to encode. Signed is a little more complicated as you have to double-check the sign bit.
// If either case, set the high-order bit to tell the reader there are more bytes in this int.
if (value != -1 || s == 0) && (value != 0 || s != 0) {
b |= 0x80
}
// Append b into the buffer
buf = append(buf, b)
if b&0x80 == 0 {
break
}
}
return buf
}
// EncodeUint32 encodes the value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer
func EncodeUint32(value uint32) []byte {
return EncodeUint64(uint64(value))
}
// EncodeUint64 encodes the value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer
func EncodeUint64(value uint64) (buf []byte) {
// This is effectively a do/while loop where we take 7 bits of the value and encode them until it is zero.
for {
// Take 7 remaining low-order bits from the value into b.
b := uint8(value & 0x7f)
value = value >> 7
// If there are remaining bits, the value won't be zero: Set the high
// order bit to tell the reader there are more bytes in this uint.
if value != 0 {
b |= 0x80
}
// Append b into the buffer
buf = append(buf, b)
if b&0x80 == 0 {
return buf
}
}
}
func DecodeUint32(r *bytes.Reader) (ret uint32, bytesRead uint64, err error) {
// Derived from https://github.com/golang/go/blob/aafad20b617ee63d58fcd4f6e0d98fe27760678c/src/encoding/binary/varint.go
// with the modification on the overflow handling tailored for 32-bits.
var s uint32
for i := 0; i < maxVarintLen32; i++ {
b, err := r.ReadByte()
if err != nil {
return 0, 0, err
}
if b < 0x80 {
// Unused bits must be all zero.
if i == maxVarintLen32-1 && (b&0xf0) > 0 {
return 0, 0, errOverflow32
}
return ret | uint32(b)<<s, uint64(i) + 1, nil
}
ret |= (uint32(b) & 0x7f) << s
s += 7
}
return 0, 0, errOverflow32
}
func DecodeUint64(r *bytes.Reader) (ret uint64, bytesRead uint64, err error) {
// Derived from https://github.com/golang/go/blob/aafad20b617ee63d58fcd4f6e0d98fe27760678c/src/encoding/binary/varint.go
var s uint64
for i := 0; i < maxVarintLen64; i++ {
b, err := r.ReadByte()
if err != nil {
return 0, 0, err
}
if b < 0x80 {
// Unused bits (non first bit) must all be zero.
if i == maxVarintLen64-1 && b > 1 {
return 0, 0, errOverflow64
}
return ret | uint64(b)<<s, uint64(i) + 1, nil
}
ret |= (uint64(b) & 0x7f) << s
s += 7
}
return 0, 0, errOverflow64
}
func DecodeInt32(r *bytes.Reader) (ret int32, bytesRead uint64, err error) {
var shift int
var b byte
for {
b, err = r.ReadByte()
if err != nil {
return 0, 0, fmt.Errorf("readByte failed: %w", err)
}
ret |= (int32(b) & 0x7f) << shift
shift += 7
bytesRead++
if b&0x80 == 0 {
if shift < 32 && (b&0x40) != 0 {
ret |= ^0 << shift
}
// Over flow checks.
// fixme: can be optimized.
if bytesRead > 5 {
return 0, 0, errOverflow32
} else if unused := b & 0b00110000; bytesRead == 5 && ret < 0 && unused != 0b00110000 {
return 0, 0, errOverflow32
} else if bytesRead == 5 && ret >= 0 && unused != 0x00 {
return 0, 0, errOverflow32
}
return
}
}
}
// DecodeInt33AsInt64 is a special cased decoder for wasm.BlockType which is encoded as a positive signed integer, yet
// still needs to fit the 32-bit range of allowed indices. Hence, this is 33, not 32-bit!
//
// See https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
func DecodeInt33AsInt64(r *bytes.Reader) (ret int64, bytesRead uint64, err error) {
const (
int33Mask int64 = 1 << 7
int33Mask2 = ^int33Mask
int33Mask3 = 1 << 6
int33Mask4 = 8589934591 // 2^33-1
int33Mask5 = 1 << 32
int33Mask6 = int33Mask4 + 1 // 2^33
)
var shift int
var b int64
var rb byte
for shift < 35 {
rb, err = r.ReadByte()
if err != nil {
return 0, 0, fmt.Errorf("readByte failed: %w", err)
}
b = int64(rb)
ret |= (b & int33Mask2) << shift
shift += 7
bytesRead++
if b&int33Mask == 0 {
break
}
}
// fixme: can be optimized
if shift < 33 && (b&int33Mask3) == int33Mask3 {
ret |= int33Mask4 << shift
}
ret = ret & int33Mask4
// if 33rd bit == 1, we translate it as a corresponding signed-33bit minus value
if ret&int33Mask5 > 0 {
ret = ret - int33Mask6
}
// Over flow checks.
// fixme: can be optimized.
if bytesRead > 5 {
return 0, 0, errOverflow33
} else if unused := b & 0b00100000; bytesRead == 5 && ret < 0 && unused != 0b00100000 {
return 0, 0, errOverflow33
} else if bytesRead == 5 && ret >= 0 && unused != 0x00 {
return 0, 0, errOverflow33
}
return ret, bytesRead, nil
}
func DecodeInt64(r *bytes.Reader) (ret int64, bytesRead uint64, err error) {
const (
int64Mask3 = 1 << 6
int64Mask4 = ^0
)
var shift int
var b byte
for {
b, err = r.ReadByte()
if err != nil {
return 0, 0, fmt.Errorf("readByte failed: %w", err)
}
ret |= (int64(b) & 0x7f) << shift
shift += 7
bytesRead++
if b&0x80 == 0 {
if shift < 64 && (b&int64Mask3) == int64Mask3 {
ret |= int64Mask4 << shift
}
// Over flow checks.
// fixme: can be optimized.
if bytesRead > 10 {
return 0, 0, errOverflow64
} else if unused := b & 0b00111110; bytesRead == 10 && ret < 0 && unused != 0b00111110 {
return 0, 0, errOverflow64
} else if bytesRead == 10 && ret >= 0 && unused != 0x00 {
return 0, 0, errOverflow64
}
return
}
}
}
+86
View File
@@ -0,0 +1,86 @@
package wasm
import "fmt"
// ImportFuncCount returns the possibly empty count of imported functions. This plus SectionElementCount of
// SectionIDFunction is the size of the function index namespace.
func (m *Module) ImportFuncCount() uint32 {
return m.importCount(ExternTypeFunc)
}
// ImportTableCount returns the possibly empty count of imported tables. This plus SectionElementCount of SectionIDTable
// is the size of the table index namespace.
func (m *Module) ImportTableCount() uint32 {
return m.importCount(ExternTypeTable)
}
// ImportMemoryCount returns the possibly empty count of imported memories. This plus SectionElementCount of
// SectionIDMemory is the size of the memory index namespace.
func (m *Module) ImportMemoryCount() uint32 {
return m.importCount(ExternTypeMemory) // TODO: once validation happens on decode, this is zero or one.
}
// ImportGlobalCount returns the possibly empty count of imported globals. This plus SectionElementCount of
// SectionIDGlobal is the size of the global index namespace.
func (m *Module) ImportGlobalCount() uint32 {
return m.importCount(ExternTypeGlobal)
}
// importCount returns the count of a specific type of import. This is important because it is easy to mistake the
// length of the import section with the count of a specific kind of import.
func (m *Module) importCount(et ExternType) (res uint32) {
for _, im := range m.ImportSection {
if im.Type == et {
res++
}
}
return
}
// SectionElementCount returns the count of elements in a given section ID
//
// For example...
// * SectionIDType returns the count of FunctionType
// * SectionIDCustom returns one if the NameSection is present
// * SectionIDHostFunction returns the count of HostFunctionSection
// * SectionIDExport returns the count of unique export names
func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
switch sectionID {
case SectionIDCustom:
numCustomSections := uint32(len(m.CustomSections))
if m.NameSection != nil {
numCustomSections++
}
return numCustomSections
case SectionIDType:
return uint32(len(m.TypeSection))
case SectionIDImport:
return uint32(len(m.ImportSection))
case SectionIDFunction:
return uint32(len(m.FunctionSection))
case SectionIDTable:
return uint32(len(m.TableSection))
case SectionIDMemory:
if m.MemorySection != nil {
return 1
}
return 0
case SectionIDGlobal:
return uint32(len(m.GlobalSection))
case SectionIDExport:
return uint32(len(m.ExportSection))
case SectionIDStart:
if m.StartSection != nil {
return 1
}
return 0
case SectionIDElement:
return uint32(len(m.ElementSection))
case SectionIDCode:
return uint32(len(m.CodeSection))
case SectionIDData:
return uint32(len(m.DataSection))
default:
panic(fmt.Errorf("BUG: unknown section: %d", sectionID))
}
}
+212
View File
@@ -0,0 +1,212 @@
package wasm
import (
"fmt"
"strings"
)
// CoreFeatures is a bit flag of WebAssembly Core specification features. See
// https://github.com/WebAssembly/proposals for proposals and their status.
//
// Constants define individual features, such as CoreFeatureMultiValue, or
// groups of "finished" features, assigned to a WebAssembly Core Specification
// version, ex. CoreFeaturesV1 or CoreFeaturesV2.
//
// Note: Numeric values are not intended to be interpreted except as bit flags.
type CoreFeatures uint64
// CoreFeaturesV1 are features included in the WebAssembly Core Specification
// 1.0. As of late 2022, this is the only version that is a Web Standard (W3C
// Recommendation).
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
const CoreFeaturesV1 = CoreFeatureMutableGlobal
// CoreFeaturesV2 are features included in the WebAssembly Core Specification
// 2.0 (20220419). As of late 2022, version 2.0 is a W3C working draft, not yet
// a Web Standard (W3C Recommendation).
//
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#release-1-1
const CoreFeaturesV2 = CoreFeaturesV1 |
CoreFeatureBulkMemoryOperations |
CoreFeatureMultiValue |
CoreFeatureNonTrappingFloatToIntConversion |
CoreFeatureReferenceTypes |
CoreFeatureSignExtensionOps |
CoreFeatureSIMD
const (
// CoreFeatureBulkMemoryOperations adds instructions modify ranges of
// memory or table entries ("bulk-memory-operations"). This is included in
// CoreFeaturesV2, but not CoreFeaturesV1.
//
// Here are the notable effects:
// - Adds `memory.fill`, `memory.init`, `memory.copy` and `data.drop`
// instructions.
// - Adds `table.init`, `table.copy` and `elem.drop` instructions.
// - Introduces a "passive" form of element and data segments.
// - Stops checking "active" element and data segment boundaries at
// compile-time, meaning they can error at runtime.
//
// Note: "bulk-memory-operations" is mixed with the "reference-types"
// proposal due to the WebAssembly Working Group merging them
// "mutually dependent". Therefore, enabling this feature requires enabling
// CoreFeatureReferenceTypes, and vice-versa.
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and
// https://github.com/WebAssembly/spec/pull/1287
CoreFeatureBulkMemoryOperations CoreFeatures = 1 << iota
// CoreFeatureMultiValue enables multiple values ("multi-value"). This is
// included in CoreFeaturesV2, but not CoreFeaturesV1.
//
// Here are the notable effects:
// - Function (`func`) types allow more than one result.
// - Block types (`block`, `loop` and `if`) can be arbitrary function
// types.
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
CoreFeatureMultiValue
// CoreFeatureMutableGlobal allows globals to be mutable. This is included
// in both CoreFeaturesV1 and CoreFeaturesV2.
//
// When false, an api.Global can never be cast to an api.MutableGlobal, and
// any wasm that includes global vars will fail to parse.
CoreFeatureMutableGlobal
// CoreFeatureNonTrappingFloatToIntConversion enables non-trapping
// float-to-int conversions ("nontrapping-float-to-int-conversion"). This
// is included in CoreFeaturesV2, but not CoreFeaturesV1.
//
// The only effect of enabling is allowing the following instructions,
// which return 0 on NaN instead of panicking.
// - `i32.trunc_sat_f32_s`
// - `i32.trunc_sat_f32_u`
// - `i32.trunc_sat_f64_s`
// - `i32.trunc_sat_f64_u`
// - `i64.trunc_sat_f32_s`
// - `i64.trunc_sat_f32_u`
// - `i64.trunc_sat_f64_s`
// - `i64.trunc_sat_f64_u`
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
CoreFeatureNonTrappingFloatToIntConversion
// CoreFeatureReferenceTypes enables various instructions and features
// related to table and new reference types. This is included in
// CoreFeaturesV2, but not CoreFeaturesV1.
//
// - Introduction of new value types: `funcref` and `externref`.
// - Support for the following new instructions:
// - `ref.null`
// - `ref.func`
// - `ref.is_null`
// - `table.fill`
// - `table.get`
// - `table.grow`
// - `table.set`
// - `table.size`
// - Support for multiple tables per module:
// - `call_indirect`, `table.init`, `table.copy` and `elem.drop`
// - Support for instructions can take non-zero table index.
// - Element segments can take non-zero table index.
//
// Note: "reference-types" is mixed with the "bulk-memory-operations"
// proposal due to the WebAssembly Working Group merging them
// "mutually dependent". Therefore, enabling this feature requires enabling
// CoreFeatureBulkMemoryOperations, and vice-versa.
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
// https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md and
// https://github.com/WebAssembly/spec/pull/1287
CoreFeatureReferenceTypes
// CoreFeatureSignExtensionOps enables sign extension instructions
// ("sign-extension-ops"). This is included in CoreFeaturesV2, but not
// CoreFeaturesV1.
//
// Adds instructions:
// - `i32.extend8_s`
// - `i32.extend16_s`
// - `i64.extend8_s`
// - `i64.extend16_s`
// - `i64.extend32_s`
//
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
CoreFeatureSignExtensionOps
// CoreFeatureSIMD enables the vector value type and vector instructions
// (aka SIMD). This is included in CoreFeaturesV2, but not CoreFeaturesV1.
//
// Note: The instruction list is too long to enumerate in godoc.
// See https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
CoreFeatureSIMD
)
// SetEnabled enables or disables the feature or group of features.
func (f CoreFeatures) SetEnabled(feature CoreFeatures, val bool) CoreFeatures {
if val {
return f | feature
}
return f &^ feature
}
// IsEnabled returns true if the feature (or group of features) is enabled.
func (f CoreFeatures) IsEnabled(feature CoreFeatures) bool {
return f&feature != 0
}
// RequireEnabled returns an error if the feature (or group of features) is not
// enabled.
func (f CoreFeatures) RequireEnabled(feature CoreFeatures) error {
if f&feature == 0 {
return fmt.Errorf("feature %q is disabled", feature)
}
return nil
}
// String implements fmt.Stringer by returning each enabled feature.
func (f CoreFeatures) String() string {
var builder strings.Builder
for i := 0; i <= 63; i++ { // cycle through all bits to reduce code and maintenance
target := CoreFeatures(1 << i)
if f.IsEnabled(target) {
if name := featureName(target); name != "" {
if builder.Len() > 0 {
builder.WriteByte('|')
}
builder.WriteString(name)
}
}
}
return builder.String()
}
func featureName(f CoreFeatures) string {
switch f {
case CoreFeatureMutableGlobal:
// match https://github.com/WebAssembly/mutable-global
return "mutable-global"
case CoreFeatureSignExtensionOps:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/sign-extension-ops/Overview.md
return "sign-extension-ops"
case CoreFeatureMultiValue:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/multi-value/Overview.md
return "multi-value"
case CoreFeatureNonTrappingFloatToIntConversion:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/nontrapping-float-to-int-conversion/Overview.md
return "nontrapping-float-to-int-conversion"
case CoreFeatureBulkMemoryOperations:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/bulk-memory-operations/Overview.md
return "bulk-memory-operations"
case CoreFeatureReferenceTypes:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/reference-types/Overview.md
return "reference-types"
case CoreFeatureSIMD:
// match https://github.com/WebAssembly/spec/blob/wg-2.0.draft1/proposals/simd/SIMD.md
return "simd"
}
return ""
}
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
package wasm
import (
"fmt"
)
const (
// MemoryPageSize is the unit of memory length in WebAssembly,
// and is defined as 2^16 = 65536.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
MemoryPageSize = uint32(65536)
// MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize".
MemoryPageSizeInBits = 16
)
// MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages.
func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
return uint64(pages) << MemoryPageSizeInBits
}
// PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. Ex. 1 -> "64Ki"
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
func PagesToUnitOfBytes(pages uint32) string {
k := pages * 64
if k < 1024 {
return fmt.Sprintf("%d Ki", k)
}
m := k / 1024
if m < 1024 {
return fmt.Sprintf("%d Mi", m)
}
g := m / 1024
if g < 1024 {
return fmt.Sprintf("%d Gi", g)
}
return fmt.Sprintf("%d Ti", g/1024)
}
// Below are raw functions used to implement the api.Memory API:
// memoryBytesNumToPages converts the given number of bytes into the number of pages.
func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
return uint32(bytesNum >> MemoryPageSizeInBits)
}
+496
View File
@@ -0,0 +1,496 @@
package wasm
import (
"bytes"
"fmt"
)
// DecodeModule parses the WebAssembly Binary Format (%.wasm) into a Module. This function returns when the input is
// exhausted or an error occurs. The result can be initialized for use via Store.Instantiate.
//
// Here's a description of the return values:
// * result is the module parsed or nil on error
// * err is a FormatError invoking the parser, dangling block comments or unexpected characters.
// See binary.DecodeModule and text.DecodeModule
type DecodeModule func(
wasm []byte,
features CoreFeatures,
memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32),
) (result *Module, err error)
// EncodeModule encodes the given module into a byte slice depending on the format of the implementation.
// See binary.EncodeModule
type EncodeModule func(m *Module) (bytes []byte)
// The wazero specific limitation described at RATIONALE.md.
// TL;DR; We multiply by 8 (to get offsets in bytes) and the multiplication result must be less than 32bit max
const (
MaximumGlobals = uint32(1 << 27)
MaximumFunctionIndex = uint32(1 << 27)
MaximumTableIndex = uint32(1 << 27)
)
// Module is a WebAssembly binary representation.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A8
//
// Differences from the specification:
// * NameSection is the only key ("name") decoded from the SectionIDCustom.
// * ExportSection is represented as a map for lookup convenience.
// * Code.GoFunc is contains any go `func`. It may be present when Code.Body is not.
type Module struct {
// TypeSection contains the unique FunctionType of functions imported or defined in this module.
//
// Note: Currently, there is no type ambiguity in the index as WebAssembly 1.0 only defines function type.
// In the future, other types may be introduced to support CoreFeatures such as module linking.
//
// Note: In the Binary Format, this is SectionIDType.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#types%E2%91%A0%E2%91%A0
TypeSection []*FunctionType
// ImportSection contains imported functions, tables, memories or globals required for instantiation
// (Store.Instantiate).
//
// Note: there are no unique constraints relating to the two-level namespace of Import.Module and Import.Name.
//
// Note: In the Binary Format, this is SectionIDImport.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0
ImportSection []*Import
// FunctionSection contains the index in TypeSection of each function defined in this module.
//
// Note: The function Index namespace begins with imported functions and ends with those defined in this module.
// For example, if there are two imported functions and one defined in this module, the function Index 3 is defined
// in this module at FunctionSection[0].
//
// Note: FunctionSection is index correlated with the CodeSection. If given the same position, ex. 2, a function
// type is at TypeSection[FunctionSection[2]], while its locals and body are at CodeSection[2].
//
// Note: In the Binary Format, this is SectionIDFunction.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0
FunctionSection []Index
// TableSection contains each table defined in this module.
//
// Note: The table Index namespace begins with imported tables and ends with those defined in this module.
// For example, if there are two imported tables and one defined in this module, the table Index 3 is defined in
// this module at TableSection[0].
//
// Note: Version of the WebAssembly spec allows at most one table definition per module, so the
// length of the TableSection can be zero or one, and can only be one if there is no imported table.
//
// Note: In the Binary Format, this is SectionIDTable.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0
TableSection []*Table
// MemorySection contains each memory defined in this module.
//
// Note: The memory Index namespace begins with imported memories and ends with those defined in this module.
// For example, if there are two imported memories and one defined in this module, the memory Index 3 is defined in
// this module at TableSection[0].
//
// Note: Version of the WebAssembly spec allows at most one memory definition per module, so the
// length of the MemorySection can be zero or one, and can only be one if there is no imported memory.
//
// Note: In the Binary Format, this is SectionIDMemory.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0
MemorySection *Memory
// GlobalSection contains each global defined in this module.
//
// Global indexes are offset by any imported globals because the global index space begins with imports, followed by
// ones defined in this module. For example, if there are two imported globals and three defined in this module, the
// global at index 3 is defined in this module at GlobalSection[0].
//
// Note: In the Binary Format, this is SectionIDGlobal.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0
GlobalSection []*Global
// ExportSection contains each export defined in this module.
//
// Note: In the Binary Format, this is SectionIDExport.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
ExportSection []*Export
// StartSection is the index of a function to call before returning from Store.Instantiate.
//
// Note: The index here is not the position in the FunctionSection, rather in the function index namespace, which
// begins with imported functions.
//
// Note: In the Binary Format, this is SectionIDStart.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0
StartSection *Index
// Note: In the Binary Format, this is SectionIDElement.
ElementSection []*ElementSegment
// CodeSection is index-correlated with FunctionSection and contains each
// function's locals and body.
//
// When present, the HostFunctionSection of the same index must be nil.
//
// Note: In the Binary Format, this is SectionIDCode.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
CodeSection []*Code
// Note: In the Binary Format, this is SectionIDData.
DataSection []*DataSegment
// DataCountSection is the optional section and holds the number of data segments in the data section.
//
// Note: This may exist in WebAssembly 2.0 or WebAssembly 1.0 with FeatureBulkMemoryOperations.
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions
DataCountSection *uint32
// NameSection is set when the SectionIDCustom "name" was successfully decoded from the binary format.
//
// Note: This is the only SectionIDCustom defined in the WebAssembly Binary Format.
// Others are read into CustomSections.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
NameSection *NameSection
// CustomSections are set when the SectionIDCustom other than "name" were successfully decoded from the binary format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
CustomSections []*CustomSection
}
// Index is the offset in an index namespace, not necessarily an absolute position in a Module section. This is because
// index namespaces are often preceded by a corresponding type in the Module.ImportSection.
//
// For example, the function index namespace starts with any ExternTypeFunc in the Module.ImportSection followed by
// the Module.FunctionSection
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-index
type Index = uint32
// FunctionType is a possibly empty function signature.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A0
type FunctionType struct {
// Params are the possibly empty sequence of value types accepted by a function with this signature.
Params []ValueType
// Results are the possibly empty sequence of value types returned by a function with this signature.
//
// Note: In WebAssembly 1.0 (20191205), there can be at most one result.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0
Results []ValueType
}
// EqualsSignature returns true if the function type has the same parameters and results.
func (f *FunctionType) EqualsSignature(params []ValueType, results []ValueType) bool {
return bytes.Equal(f.Params, params) && bytes.Equal(f.Results, results)
}
// Import is the binary representation of an import indicated by Type
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import
type Import struct {
Type ExternType
// Module is the possibly empty primary namespace of this import
Module string
// Module is the possibly empty secondary namespace of this import
Name string
// DescFunc is the index in Module.TypeSection when Type equals ExternTypeFunc
DescFunc Index
// DescTable is the inlined Table when Type equals ExternTypeTable
DescTable *Table
// DescMem is the inlined Memory when Type equals ExternTypeMemory
DescMem *Memory
// DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal
DescGlobal *GlobalType
}
// Memory describes the limits of pages (64KB) in a memory.
type Memory struct {
Min, Max uint32
// IsMaxEncoded true if the Max is encoded in the original source (binary or text).
IsMaxEncoded bool
}
// Table describes the limits of elements and its type in a table.
type Table struct {
Min uint32
Max *uint32
Type RefType
}
// RefType is either RefTypeFuncref or RefTypeExternref as of WebAssembly core 2.0.
type RefType = byte
const (
// RefTypeFuncref represents a reference to a function.
RefTypeFuncref = ValueTypeFuncref
// RefTypeExternref represents a reference to a host object, which is not currently supported in wazero.
RefTypeExternref = ValueTypeExternref
)
func RefTypeName(t RefType) (ret string) {
switch t {
case RefTypeFuncref:
ret = "funcref"
case RefTypeExternref:
ret = "externref"
default:
ret = fmt.Sprintf("unknown(0x%x)", t)
}
return
}
// ElementMode represents a mode of element segment which is either active, passive or declarative.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
type ElementMode = byte
const (
// ElementModeActive is the mode which requires the runtime to initialize table with the contents in .Init field combined with OffsetExpr.
ElementModeActive ElementMode = iota
// ElementModePassive is the mode which doesn't require the runtime to initialize table, and only used with OpcodeTableInitName.
ElementModePassive
// ElementModeDeclarative is introduced in reference-types proposal which can be used to declare function indexes used by OpcodeRefFunc.
ElementModeDeclarative
)
// ElementSegment are initialization instructions for a TableInstance
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-elem
type ElementSegment struct {
// OffsetExpr returns the table element offset to apply to Init indices.
// Note: This can be validated prior to instantiation unless it includes OpcodeGlobalGet (an imported global).
// Note: This is only set when Mode is active.
OffsetExpr *ConstantExpression
// TableIndex is the table's index to which this element segment is applied.
// Note: This is used if and only if the Mode is active.
TableIndex Index
// Followings are set/used regardless of the Mode.
// Init indices are (nullable) table elements where each index is the function index by which the module initialize the table.
Init []*Index
// Type holds the type of this element segment, which is the RefType in WebAssembly 2.0.
Type RefType
// Mode is the mode of this element segment.
Mode ElementMode
}
// TableInstance represents a table of (RefTypeFuncref) elements in a module.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0
type TableInstance struct {
// References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported).
//
// Currently, only function references are supported.
References []Reference
// Min is the minimum (function) elements in this table and cannot grow to accommodate ElementSegment.
Min uint32
// Max if present is the maximum (function) elements in this table, or nil if unbounded.
Max *uint32
// Type is either RefTypeFuncref or RefTypeExternRef.
Type RefType
}
// ElementInstance represents an element instance in a module.
//
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#element-instances
type ElementInstance struct {
// References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported).
References []Reference
// Type is the RefType of the references in this instance's References.
Type RefType
}
// Reference is the runtime representation of RefType which is either RefTypeFuncref or RefTypeExternref.
type Reference = uintptr
type GlobalType struct {
ValType ValueType
Mutable bool
}
type Global struct {
Type *GlobalType
Init *ConstantExpression
}
type ConstantExpression struct {
Opcode Opcode
Data []byte
}
// Export is the binary representation of an export indicated by Type
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-export
type Export struct {
Type ExternType
// Name is what the host refers to this definition as.
Name string
// Index is the index of the definition to export, the index namespace is by Type
// Ex. If ExternTypeFunc, this is a position in the function index namespace.
Index Index
}
// Code is an entry in the Module.CodeSection containing the locals and body of the function.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
type Code struct {
// LocalTypes are any function-scoped variables in insertion order.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-local
LocalTypes []ValueType
// Body is a sequence of expressions ending in OpcodeEnd
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-expr
Body []byte
}
type DataSegment struct {
OffsetExpression *ConstantExpression
Init []byte
}
// NameSection represent the known custom name subsections defined in the WebAssembly Binary Format
//
// Note: This can be nil if no names were decoded for any reason including configuration.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
type NameSection struct {
// ModuleName is the symbolic identifier for a module. Ex. math
//
// Note: This can be empty for any reason including configuration.
ModuleName string
// FunctionNames is an association of a function index to its symbolic identifier. Ex. add
//
// * the key (idx) is in the function namespace, where module defined functions are preceded by imported ones.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#functions%E2%91%A7
//
// Ex. Assuming the below text format is the second import, you would expect FunctionNames[1] = "mul"
// (import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32)))
//
// Note: FunctionNames are only used for debugging. At runtime, functions are called based on raw numeric index.
// Note: This can be nil for any reason including configuration.
FunctionNames NameMap
// LocalNames contains symbolic names for function parameters or locals that have one.
//
// Note: In the Text Format, function local names can inherit parameter names from their type. Ex.
// * (module (import (func (param $x i32) (param i32))) (func (type 0))) = [{0, {x,0}}]
// * (module (import (func (param i32) (param $y i32))) (func (type 0) (local $z i32))) = [0, [{y,1},{z,2}]]
// * (module (func (param $x i32) (local $y i32) (local $z i32))) = [{x,0},{y,1},{z,2}]
//
// Note: LocalNames are only used for debugging. At runtime, locals are called based on raw numeric index.
// Note: This can be nil for any reason including configuration.
LocalNames IndirectNameMap
}
// CustomSection contains the name and raw data of a custom section.
type CustomSection struct {
Name string
Data []byte
}
// NameMap associates an index with any associated names.
//
// Note: Often the index namespace bridges multiple sections. For example, the function index namespace starts with any
// ExternTypeFunc in the Module.ImportSection followed by the Module.FunctionSection
//
// Note: NameMap is unique by NameAssoc.Index, but NameAssoc.Name needn't be unique.
// Note: When encoding in the Binary format, this must be ordered by NameAssoc.Index
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap
type NameMap []*NameAssoc
type NameAssoc struct {
Index Index
Name string
}
// IndirectNameMap associates an index with an association of names.
//
// Note: IndirectNameMap is unique by NameMapAssoc.Index, but NameMapAssoc.NameMap needn't be unique.
// Note: When encoding in the Binary format, this must be ordered by NameMapAssoc.Index
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-indirectnamemap
type IndirectNameMap []*NameMapAssoc
type NameMapAssoc struct {
Index Index
NameMap NameMap
}
// SectionID identifies the sections of a Module in the WebAssembly Binary Format.
//
// Note: these are defined in the wasm package, instead of the binary package, as a key per section is needed regardless
// of format, and deferring to the binary type avoids confusion.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
type SectionID = byte
const (
// SectionIDCustom includes the standard defined NameSection and possibly others not defined in the standard.
SectionIDCustom SectionID = iota // don't add anything not in https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
SectionIDType
SectionIDImport
SectionIDFunction
SectionIDTable
SectionIDMemory
SectionIDGlobal
SectionIDExport
SectionIDStart
SectionIDElement
SectionIDCode
SectionIDData
// SectionIDDataCount may exist in WebAssembly 2.0 or WebAssembly 1.0 with FeatureBulkMemoryOperations enabled.
//
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions
SectionIDDataCount
)
// SectionIDName returns the canonical name of a module section.
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
func SectionIDName(sectionID SectionID) string {
switch sectionID {
case SectionIDCustom:
return "custom"
case SectionIDType:
return "type"
case SectionIDImport:
return "import"
case SectionIDFunction:
return "function"
case SectionIDTable:
return "table"
case SectionIDMemory:
return "memory"
case SectionIDGlobal:
return "global"
case SectionIDExport:
return "export"
case SectionIDStart:
return "start"
case SectionIDElement:
return "element"
case SectionIDCode:
return "code"
case SectionIDData:
return "data"
case SectionIDDataCount:
return "data_count"
}
return "unknown"
}
+180
View File
@@ -0,0 +1,180 @@
package wasm
import (
"fmt"
"math"
)
// ValueType describes a numeric type used in Web Assembly 1.0 (20191205). For example, Function parameters and results are
// only definable as a value type.
//
// The following describes how to convert between Wasm and Golang types:
//
// - ValueTypeI32 - uint64(uint32,int32)
// - ValueTypeI64 - uint64(int64)
// - ValueTypeF32 - EncodeF32 DecodeF32 from float32
// - ValueTypeF64 - EncodeF64 DecodeF64 from float64
// - ValueTypeExternref - uintptr(unsafe.Pointer(p)) where p is any pointer type in Go (e.g. *string)
//
// Ex. Given a Text Format type use (param i64) (result i64), no conversion is necessary.
//
// results, _ := fn(ctx, input)
// result := result[0]
//
// Ex. Given a Text Format type use (param f64) (result f64), conversion is necessary.
//
// results, _ := fn(ctx, api.EncodeF64(input))
// result := api.DecodeF64(result[0])
//
// Note: This is a type alias as it is easier to encode and decode in the binary format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-valtype
type ValueType = byte
const (
// ValueTypeI32 is a 32-bit integer.
ValueTypeI32 ValueType = 0x7f
// ValueTypeI64 is a 64-bit integer.
ValueTypeI64 ValueType = 0x7e
// ValueTypeF32 is a 32-bit floating point number.
ValueTypeF32 ValueType = 0x7d
// ValueTypeF64 is a 64-bit floating point number.
ValueTypeF64 ValueType = 0x7c
// ValueTypeExternref is an externref type.
//
// Note: in wazero, externref type value are opaque raw 64-bit pointers,
// and the ValueTypeExternref type in the signature will be translated as
// uintptr in wazero's API level.
//
// For example, given the import function:
// (func (import "env" "f") (param externref) (result externref))
//
// This can be defined in Go as:
// r.NewModuleBuilder("env").ExportFunctions(map[string]interface{}{
// "f": func(externref uintptr) (resultExternRef uintptr) { return },
// })
//
// Note: The usage of this type is toggled with WithFeatureBulkMemoryOperations.
ValueTypeExternref ValueType = 0x6f
ValueTypeV128 ValueType = 0x7b
ValueTypeFuncref ValueType = 0x70
)
// ValueTypeName returns the type name of the given ValueType as a string.
// These type names match the names used in the WebAssembly text format.
//
// Note: This returns "unknown", if an undefined ValueType value is passed.
func ValueTypeName(t ValueType) string {
switch t {
case ValueTypeI32:
return "i32"
case ValueTypeI64:
return "i64"
case ValueTypeF32:
return "f32"
case ValueTypeF64:
return "f64"
case ValueTypeExternref:
return "externref"
case ValueTypeFuncref:
return "funcref"
case ValueTypeV128:
return "v128"
}
return "unknown"
}
// ExternType classifies imports and exports with their respective types.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#external-types%E2%91%A0
type ExternType = byte
const (
ExternTypeFunc ExternType = 0x00
ExternTypeTable ExternType = 0x01
ExternTypeMemory ExternType = 0x02
ExternTypeGlobal ExternType = 0x03
)
// The below are exported to consolidate parsing behavior for external types.
const (
// ExternTypeFuncName is the name of the WebAssembly Text Format field for ExternTypeFunc.
ExternTypeFuncName = "func"
// ExternTypeTableName is the name of the WebAssembly Text Format field for ExternTypeTable.
ExternTypeTableName = "table"
// ExternTypeMemoryName is the name of the WebAssembly Text Format field for ExternTypeMemory.
ExternTypeMemoryName = "memory"
// ExternTypeGlobalName is the name of the WebAssembly Text Format field for ExternTypeGlobal.
ExternTypeGlobalName = "global"
)
// ExternTypeName returns the name of the WebAssembly Text Format field of the given type.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A4
func ExternTypeName(et ExternType) string {
switch et {
case ExternTypeFunc:
return ExternTypeFuncName
case ExternTypeTable:
return ExternTypeTableName
case ExternTypeMemory:
return ExternTypeMemoryName
case ExternTypeGlobal:
return ExternTypeGlobalName
}
return fmt.Sprintf("%#x", et)
}
// EncodeI32 encodes the input as a ValueTypeI32.
func EncodeI32(input int32) uint64 {
return uint64(uint32(input))
}
// EncodeI64 encodes the input as a ValueTypeI64.
func EncodeI64(input int64) uint64 {
return uint64(input)
}
// EncodeF32 encodes the input as a ValueTypeF32.
//
// See DecodeF32
func EncodeF32(input float32) uint64 {
return uint64(math.Float32bits(input))
}
// DecodeF32 decodes the input as a ValueTypeF32.
//
// See EncodeF32
func DecodeF32(input uint64) float32 {
return math.Float32frombits(uint32(input))
}
// EncodeF64 encodes the input as a ValueTypeF64.
//
// See EncodeF32
func EncodeF64(input float64) uint64 {
return math.Float64bits(input)
}
// DecodeF64 decodes the input as a ValueTypeF64.
//
// See EncodeF64
func DecodeF64(input uint64) float64 {
return math.Float64frombits(input)
}
// EncodeExternref encodes the input as a ValueTypeExternref.
//
// See DecodeExternref
func EncodeExternref(input uintptr) uint64 {
return uint64(input)
}
// DecodeExternref decodes the input as a ValueTypeExternref.
//
// See EncodeExternref
func DecodeExternref(input uint64) uintptr {
return uintptr(input)
}