working commit
This commit is contained in:
+201
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 ""
|
||||
}
|
||||
+1550
File diff suppressed because it is too large
Load Diff
+45
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user