updated vendor
This commit is contained in:
+28
-4
@@ -45,11 +45,22 @@ func decodeCode(r *bytes.Reader, codeSectionStart uint64, ret *wasm.Code) (err e
|
||||
}
|
||||
|
||||
bytesRead += n + 1
|
||||
switch vt := b; vt {
|
||||
switch vt := b; wasm.ValueType(vt) {
|
||||
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
|
||||
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128:
|
||||
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128,
|
||||
wasm.ValueTypeExnref:
|
||||
default:
|
||||
return fmt.Errorf("invalid local type: 0x%x", vt)
|
||||
switch vt {
|
||||
case wasm.RefPrefixNullable, wasm.RefPrefixNonNullable:
|
||||
// Read and skip the heap type.
|
||||
_, htNum, err := leb128.DecodeInt33AsInt64(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read local ref heap type: %v", err)
|
||||
}
|
||||
bytesRead += htNum
|
||||
default:
|
||||
return fmt.Errorf("invalid local type: 0x%x", vt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +89,21 @@ func decodeCode(r *bytes.Reader, codeSectionStart uint64, ret *wasm.Code) (err e
|
||||
return fmt.Errorf("read type of local: %v", err)
|
||||
}
|
||||
|
||||
var vt wasm.ValueType
|
||||
switch b {
|
||||
case wasm.RefPrefixNullable, wasm.RefPrefixNonNullable:
|
||||
before := r.Len()
|
||||
vt, err = decodeRefType(r, b == wasm.RefPrefixNullable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remaining -= int64(before - r.Len())
|
||||
default:
|
||||
vt = wasm.ValueType(b)
|
||||
}
|
||||
|
||||
for j := uint32(0); j < num; j++ {
|
||||
localTypes = append(localTypes, b)
|
||||
localTypes = append(localTypes, vt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+95
-84
@@ -6,100 +6,111 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/ieee754"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeConstantExpression(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ConstantExpression) error {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 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:
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read f32 constant: %v", err)
|
||||
}
|
||||
_, err = ieee754.DecodeFloat32(buf)
|
||||
case wasm.OpcodeF64Const:
|
||||
buf := make([]byte, 8)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read f64 constant: %v", err)
|
||||
}
|
||||
_, err = ieee754.DecodeFloat64(buf)
|
||||
case wasm.OpcodeGlobalGet:
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeRefNull:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("ref.null is not supported as %w", err)
|
||||
}
|
||||
reftype, err := r.ReadByte()
|
||||
lenAtStart := r.Len()
|
||||
startPos := r.Size() - int64(lenAtStart)
|
||||
for {
|
||||
opcode, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read reference type for ref.null: %w", err)
|
||||
} else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref {
|
||||
return fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
|
||||
return fmt.Errorf("read const expression opcode: %v", err)
|
||||
}
|
||||
case wasm.OpcodeRefFunc:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("ref.func is not supported as %w", err)
|
||||
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.OpcodeI32Add, wasm.OpcodeI32Sub, wasm.OpcodeI32Mul:
|
||||
// No immediate to read.
|
||||
if !enabledFeatures.IsEnabled(experimental.CoreFeaturesExtendedConst) {
|
||||
return fmt.Errorf("%v is not supported in a constant expression as feature \"extended-const\" is disabled", wasm.InstructionName(opcode))
|
||||
}
|
||||
case wasm.OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
_, _, err = leb128.DecodeInt64(r)
|
||||
case wasm.OpcodeI64Add, wasm.OpcodeI64Sub, wasm.OpcodeI64Mul:
|
||||
// No immediate to read.
|
||||
if !enabledFeatures.IsEnabled(experimental.CoreFeaturesExtendedConst) {
|
||||
return fmt.Errorf("%v is not supported in a constant expression as feature \"extended-const\" is disabled", wasm.InstructionName(opcode))
|
||||
}
|
||||
case wasm.OpcodeF32Const:
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read f32 constant: %v", err)
|
||||
}
|
||||
_, err = ieee754.DecodeFloat32(buf)
|
||||
case wasm.OpcodeF64Const:
|
||||
buf := make([]byte, 8)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read f64 constant: %v", err)
|
||||
}
|
||||
_, err = ieee754.DecodeFloat64(buf)
|
||||
case wasm.OpcodeGlobalGet:
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeRefNull:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("ref.null is not supported as %w", err)
|
||||
}
|
||||
b, err := r.ReadByte()
|
||||
reftype := wasm.ValueType(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read reference type for ref.null: %w", err)
|
||||
}
|
||||
switch reftype {
|
||||
case wasm.RefTypeFuncref, wasm.RefTypeExternref, wasm.ValueTypeExnref:
|
||||
// Valid abstract heap type.
|
||||
default:
|
||||
// Could be a concrete type index; unread the byte and try reading as LEB128.
|
||||
if err := r.UnreadByte(); err != nil {
|
||||
return fmt.Errorf("unread byte for ref.null: %w", err)
|
||||
}
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
|
||||
}
|
||||
}
|
||||
case wasm.OpcodeRefFunc:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("ref.func is not supported as %w", err)
|
||||
}
|
||||
// Parsing index.
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeVecPrefix:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil {
|
||||
return fmt.Errorf("vector instructions are not supported as %w", err)
|
||||
}
|
||||
opcode, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read vector instruction opcode suffix: %w", err)
|
||||
}
|
||||
|
||||
if opcode != wasm.OpcodeVecV128Const {
|
||||
return fmt.Errorf("invalid vector opcode for const expression: %#x", opcode)
|
||||
}
|
||||
|
||||
n, err := r.Read(make([]byte, 16))
|
||||
if err != nil {
|
||||
return fmt.Errorf("read vector const instruction immediates: %w", err)
|
||||
} else if n != 16 {
|
||||
return fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n)
|
||||
}
|
||||
case wasm.OpcodeEnd:
|
||||
data := make([]byte, lenAtStart-(r.Len()))
|
||||
if _, err := r.ReadAt(data, startPos); err != nil {
|
||||
return fmt.Errorf("error re-buffering ConstantExpression.Data: %w", err)
|
||||
}
|
||||
ret.Data = data
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%v for const expression op code: %#x", ErrInvalidByte, opcode)
|
||||
}
|
||||
// Parsing index.
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeVecPrefix:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil {
|
||||
return fmt.Errorf("vector instructions are not supported as %w", err)
|
||||
}
|
||||
opcode, err = r.ReadByte()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("read vector instruction opcode suffix: %w", err)
|
||||
return fmt.Errorf("read value: %v", err)
|
||||
}
|
||||
|
||||
if opcode != wasm.OpcodeVecV128Const {
|
||||
return 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 fmt.Errorf("read vector const instruction immediates: %w", err)
|
||||
} else if n != 16 {
|
||||
return fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("read value: %v", err)
|
||||
}
|
||||
|
||||
if b, err = r.ReadByte(); err != nil {
|
||||
return fmt.Errorf("look for end opcode: %v", err)
|
||||
}
|
||||
|
||||
if b != wasm.OpcodeEnd {
|
||||
return fmt.Errorf("constant expression has been not terminated")
|
||||
}
|
||||
|
||||
ret.Data = make([]byte, remainingBeforeData-int64(r.Len())-1)
|
||||
if _, err = r.ReadAt(ret.Data, offsetAtData); err != nil {
|
||||
return fmt.Errorf("error re-buffering ConstantExpression.Data")
|
||||
}
|
||||
ret.Opcode = opcode
|
||||
return nil
|
||||
}
|
||||
|
||||
+16
-6
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
@@ -113,7 +114,7 @@ func DecodeModule(
|
||||
case wasm.SectionIDType:
|
||||
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
|
||||
case wasm.SectionIDImport:
|
||||
m.ImportSection, m.ImportPerModule, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures)
|
||||
m.ImportSection, m.ImportPerModule, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, m.ImportTagCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures)
|
||||
if err != nil {
|
||||
return nil, err // avoid re-wrapping the error.
|
||||
}
|
||||
@@ -123,6 +124,11 @@ func DecodeModule(
|
||||
m.TableSection, err = decodeTableSection(r, enabledFeatures)
|
||||
case wasm.SectionIDMemory:
|
||||
m.MemorySection, err = decodeMemorySection(r, enabledFeatures, memSizer, memoryLimitPages)
|
||||
case wasm.SectionIDTag:
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
|
||||
return nil, fmt.Errorf("tag section not supported as %v", err)
|
||||
}
|
||||
m.TagSection, err = decodeTagSection(r)
|
||||
case wasm.SectionIDGlobal:
|
||||
if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil {
|
||||
return nil, err // avoid re-wrapping the error.
|
||||
@@ -176,8 +182,15 @@ func checkSectionOrder(current, previous wasm.SectionID) (byte, bool) {
|
||||
return previous, true
|
||||
}
|
||||
|
||||
// DataCount was introduced in Wasm 2.0,
|
||||
// and it's the maximum we support so far.
|
||||
// Tag section (ID 13) must come after Memory (5) and before Global (6).
|
||||
if current == wasm.SectionIDTag {
|
||||
return current, previous <= wasm.SectionIDMemory
|
||||
}
|
||||
if previous == wasm.SectionIDTag {
|
||||
return current, current >= wasm.SectionIDGlobal
|
||||
}
|
||||
|
||||
// DataCount was introduced in Wasm 2.0.
|
||||
// It must come after Element and before Code.
|
||||
if current > wasm.SectionIDDataCount {
|
||||
return current, false
|
||||
@@ -189,9 +202,6 @@ func checkSectionOrder(current, previous wasm.SectionID) (byte, bool) {
|
||||
return current, current >= wasm.SectionIDCode
|
||||
}
|
||||
|
||||
// Tag will be introduced in Wasm 3.0.
|
||||
// It must come after Memory and before Global.
|
||||
|
||||
// Otherwise, strictly increasing order.
|
||||
return current, current > previous
|
||||
}
|
||||
|
||||
+24
-43
@@ -2,7 +2,6 @@ package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
@@ -21,13 +20,13 @@ func ensureElementKindFuncRef(r *bytes.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.Index, error) {
|
||||
func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.ConstantExpression, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
vec := make([]wasm.Index, vs)
|
||||
vec := make([]wasm.ConstantExpression, vs)
|
||||
for i := range vec {
|
||||
u32, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
@@ -37,61 +36,43 @@ func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.Index, error) {
|
||||
if u32 >= wasm.MaximumFunctionIndex {
|
||||
return nil, fmt.Errorf("too large function index in Element init: %d", u32)
|
||||
}
|
||||
vec[i] = u32
|
||||
vec[i] = wasm.NewConstantExpressionFromOpcode(wasm.OpcodeRefFunc, leb128.EncodeUint32(u32))
|
||||
}
|
||||
return vec, nil
|
||||
}
|
||||
|
||||
func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]wasm.Index, error) {
|
||||
func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]wasm.ConstantExpression, 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)
|
||||
vec := make([]wasm.ConstantExpression, vs)
|
||||
for i := range vec {
|
||||
var expr wasm.ConstantExpression
|
||||
err := decodeConstantExpression(r, enabledFeatures, &expr)
|
||||
err := decodeConstantExpression(r, enabledFeatures, &vec[i])
|
||||
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.LoadUint32(expr.Data)
|
||||
if v >= wasm.MaximumFunctionIndex {
|
||||
return nil, fmt.Errorf("too large function index in Element init: %d", v)
|
||||
}
|
||||
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] = wasm.ElementInitNullReference
|
||||
case wasm.OpcodeGlobalGet:
|
||||
i32, _, _ := leb128.LoadInt32(expr.Data)
|
||||
// Resolving the reference type from globals is done at instantiation phase. See the comment on
|
||||
// wasm.elementInitImportedGlobalReferenceType.
|
||||
vec[i] = wasm.WrapGlobalIndexAsElementInit(wasm.Index(i32))
|
||||
default:
|
||||
return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode))
|
||||
}
|
||||
// Expression will be validated later since we don't yet have globals to resolve the types yet.
|
||||
|
||||
}
|
||||
return vec, nil
|
||||
}
|
||||
|
||||
func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) {
|
||||
ret, err = r.ReadByte()
|
||||
func decodeElementRefType(r *bytes.Reader) (wasm.RefType, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read element ref type: %w", err)
|
||||
return
|
||||
return 0, fmt.Errorf("read element ref type: %w", err)
|
||||
}
|
||||
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")
|
||||
switch b {
|
||||
case wasm.RefPrefixNullable, wasm.RefPrefixNonNullable:
|
||||
return decodeRefType(r, b == wasm.RefPrefixNullable)
|
||||
default:
|
||||
ret := wasm.ValueType(b)
|
||||
if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref {
|
||||
return 0, fmt.Errorf("invalid ref type for element: 0x%x", b)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -142,7 +123,7 @@ func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret
|
||||
}
|
||||
|
||||
ret.Mode = wasm.ElementModeActive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
ret.Type = wasm.RefTypeFuncref.AsNonNullable()
|
||||
return nil
|
||||
case elementSegmentPrefixPassiveFuncrefValueVector:
|
||||
// Prefix 1 requires funcref.
|
||||
@@ -155,7 +136,7 @@ func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret
|
||||
return err
|
||||
}
|
||||
ret.Mode = wasm.ElementModePassive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
ret.Type = wasm.RefTypeFuncref.AsNonNullable()
|
||||
return nil
|
||||
case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex:
|
||||
ret.TableIndex, _, err = leb128.DecodeUint32(r)
|
||||
@@ -185,7 +166,7 @@ func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret
|
||||
}
|
||||
|
||||
ret.Mode = wasm.ElementModeActive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
ret.Type = wasm.RefTypeFuncref.AsNonNullable()
|
||||
return nil
|
||||
case elementSegmentPrefixDeclarativeFuncrefValueVector:
|
||||
// Prefix 3 requires funcref.
|
||||
@@ -196,7 +177,7 @@ func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
ret.Type = wasm.RefTypeFuncref.AsNonNullable()
|
||||
ret.Mode = wasm.ElementModeDeclarative
|
||||
return nil
|
||||
case elementSegmentPrefixActiveFuncrefConstExprVector:
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ func decodeExport(r *bytes.Reader, ret *wasm.Export) (err error) {
|
||||
|
||||
ret.Type = b
|
||||
switch ret.Type {
|
||||
case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal:
|
||||
case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal, wasm.ExternTypeTag:
|
||||
if ret.Index, _, err = leb128.DecodeUint32(r); err != nil {
|
||||
err = fmt.Errorf("error decoding export index: %w", err)
|
||||
}
|
||||
|
||||
+16
@@ -42,6 +42,22 @@ func decodeImport(
|
||||
ret.DescMem, err = decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages)
|
||||
case wasm.ExternTypeGlobal:
|
||||
ret.DescGlobal, err = decodeGlobalType(r)
|
||||
case wasm.ExternTypeTag:
|
||||
if err = enabledFeatures.RequireEnabled(api.CoreFeatureSIMD << 4); err != nil { // CoreFeaturesExceptionHandling
|
||||
err = fmt.Errorf("tag imports require exception handling feature: %w", err)
|
||||
break
|
||||
}
|
||||
// Tag import: read attribute byte (must be 0x00) then type index.
|
||||
var attr byte
|
||||
attr, err = r.ReadByte()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if attr != 0x00 {
|
||||
err = fmt.Errorf("invalid tag attribute: %#x", attr)
|
||||
break
|
||||
}
|
||||
ret.DescTag, _, err = leb128.DecodeUint32(r)
|
||||
default:
|
||||
err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
|
||||
+86
-3
@@ -16,15 +16,70 @@ func decodeTypeSection(enabledFeatures api.CoreFeatures, r *bytes.Reader) ([]was
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.FunctionType, vs)
|
||||
var result []wasm.FunctionType
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if err = decodeFunctionType(enabledFeatures, r, &result[i]); err != nil {
|
||||
// Peek at the leading byte to check for rec group (0x4e, GC proposal).
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %d-th type: %v", i, err)
|
||||
}
|
||||
if b == 0x4e {
|
||||
// Rec group: contains multiple types.
|
||||
recCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read rec group count: %v", err)
|
||||
}
|
||||
startIdx := uint32(len(result))
|
||||
for j := uint32(0); j < recCount; j++ {
|
||||
var ft wasm.FunctionType
|
||||
if err = decodeFunctionType(enabledFeatures, r, &ft); err != nil {
|
||||
return nil, fmt.Errorf("read %d-th type in rec group: %v", j, err)
|
||||
}
|
||||
ft.RecGroupSize = int(recCount)
|
||||
ft.RecGroupPosition = int(j)
|
||||
result = append(result, ft)
|
||||
}
|
||||
for j := uint32(0); j < recCount; j++ {
|
||||
if err := validateTypeForwardRefs(&result[startIdx+j], startIdx+recCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Put back the byte and decode as a regular function type.
|
||||
if err := r.UnreadByte(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ft wasm.FunctionType
|
||||
if err = decodeFunctionType(enabledFeatures, r, &ft); err != nil {
|
||||
return nil, fmt.Errorf("read %d-th type: %v", i, err)
|
||||
}
|
||||
if err := validateTypeForwardRefs(&ft, uint32(len(result))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, ft)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// validateTypeForwardRefs rejects concrete reference types (ref $t) whose type
|
||||
// index is not yet defined. For standalone types, maxTypeIndex is the count of
|
||||
// types decoded so far; for rec groups, it is the index after the last member,
|
||||
// allowing mutual references within the group.
|
||||
func validateTypeForwardRefs(ft *wasm.FunctionType, maxTypeIndex uint32) error {
|
||||
for i, vt := range ft.Params {
|
||||
if vt.IsConcreteRef() && vt.TypeIndex() >= maxTypeIndex {
|
||||
return fmt.Errorf("unknown type index %d in param[%d]", vt.TypeIndex(), i)
|
||||
}
|
||||
}
|
||||
for i, vt := range ft.Results {
|
||||
if vt.IsConcreteRef() && vt.TypeIndex() >= maxTypeIndex {
|
||||
return fmt.Errorf("unknown type index %d in result[%d]", vt.TypeIndex(), i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeImportSection decodes the decoded import segments plus the count per wasm.ExternType.
|
||||
func decodeImportSection(
|
||||
r *bytes.Reader,
|
||||
@@ -33,7 +88,7 @@ func decodeImportSection(
|
||||
enabledFeatures api.CoreFeatures,
|
||||
) (result []wasm.Import,
|
||||
perModule map[string][]*wasm.Import,
|
||||
funcCount, globalCount, memoryCount, tableCount wasm.Index, err error,
|
||||
funcCount, globalCount, memoryCount, tableCount, tagCount wasm.Index, err error,
|
||||
) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
@@ -61,6 +116,9 @@ func decodeImportSection(
|
||||
case wasm.ExternTypeTable:
|
||||
imp.IndexPerType = tableCount
|
||||
tableCount++
|
||||
case wasm.ExternTypeTag:
|
||||
imp.IndexPerType = tagCount
|
||||
tagCount++
|
||||
}
|
||||
perModule[imp.Module] = append(perModule[imp.Module], imp)
|
||||
}
|
||||
@@ -216,6 +274,31 @@ func decodeDataSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]was
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeTagSection(r *bytes.Reader) ([]wasm.Tag, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.Tag, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
// Read attribute byte (must be 0x00 per spec).
|
||||
attr, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read tag[%d] attribute: %w", i, err)
|
||||
}
|
||||
if attr != 0x00 {
|
||||
return nil, fmt.Errorf("tag[%d] has invalid attribute: %#x", i, attr)
|
||||
}
|
||||
// Read type index.
|
||||
result[i].Type, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read tag[%d] type index: %w", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) {
|
||||
v, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil && err != io.EOF {
|
||||
|
||||
+37
-1
@@ -12,11 +12,39 @@ import (
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
|
||||
func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Table) (err error) {
|
||||
ret.Type, err = r.ReadByte()
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read leading byte: %v", err)
|
||||
}
|
||||
|
||||
hasInitExpr := false
|
||||
if b == 0x40 {
|
||||
// Table with initializer expression: 0x40 0x00 tabletype expr
|
||||
reserved, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read reserved byte after 0x40: %v", err)
|
||||
}
|
||||
if reserved != 0x00 {
|
||||
return fmt.Errorf("expected 0x00 after 0x40 table prefix, got 0x%02x", reserved)
|
||||
}
|
||||
hasInitExpr = true
|
||||
b, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read table ref type: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch b {
|
||||
case wasm.RefPrefixNullable, wasm.RefPrefixNonNullable:
|
||||
vt, err := decodeRefType(r, b == wasm.RefPrefixNullable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Type = vt
|
||||
default:
|
||||
ret.Type = wasm.ValueType(b)
|
||||
}
|
||||
|
||||
if ret.Type != wasm.RefTypeFuncref {
|
||||
if err = enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
|
||||
return fmt.Errorf("table type funcref is invalid: %w", err)
|
||||
@@ -39,5 +67,13 @@ func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Ta
|
||||
if shared {
|
||||
return fmt.Errorf("tables cannot be marked as shared")
|
||||
}
|
||||
|
||||
if hasInitExpr {
|
||||
var initExpr wasm.ConstantExpression
|
||||
if err := decodeConstantExpression(r, enabledFeatures, &initExpr); err != nil {
|
||||
return fmt.Errorf("read table init expr: %v", err)
|
||||
}
|
||||
ret.InitExpr = &initExpr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+49
-11
@@ -16,23 +16,61 @@ func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ret := make([]wasm.ValueType, num)
|
||||
_, err := io.ReadFull(r, ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range ret {
|
||||
switch v {
|
||||
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128:
|
||||
ret := make([]wasm.ValueType, 0, num)
|
||||
for i := uint32(0); i < num; i++ {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch b {
|
||||
case wasm.ValueTypeI32.Kind(), wasm.ValueTypeF32.Kind(), wasm.ValueTypeI64.Kind(), wasm.ValueTypeF64.Kind(),
|
||||
wasm.ValueTypeExternref.Kind(), wasm.ValueTypeFuncref.Kind(), wasm.ValueTypeV128.Kind(),
|
||||
wasm.ValueTypeExnref.Kind():
|
||||
ret = append(ret, wasm.ValueType(b))
|
||||
case wasm.RefPrefixNullable, wasm.RefPrefixNonNullable:
|
||||
vt, err := decodeRefType(r, b == wasm.RefPrefixNullable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = append(ret, vt)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid value type: %d", v)
|
||||
return nil, fmt.Errorf("invalid value type: %d", b)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// decodeRefType decodes a heap type from r and returns the corresponding
|
||||
// ValueType with the given nullability. Abstract nullable refs are desugared
|
||||
// to their short forms:
|
||||
// - (ref null func) -> funcref
|
||||
// - (ref null extern) -> externref
|
||||
// - (ref null exn) -> exnref
|
||||
func decodeRefType(r *bytes.Reader, nullable bool) (wasm.ValueType, error) {
|
||||
ht, _, err := leb128.DecodeInt33AsInt64(r)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("read ref heap type: %w", err)
|
||||
}
|
||||
var vt wasm.ValueType
|
||||
switch ht {
|
||||
case wasm.HeapTypeFunc:
|
||||
vt = wasm.ValueTypeFuncref
|
||||
case wasm.HeapTypeExtern:
|
||||
vt = wasm.ValueTypeExternref
|
||||
case wasm.HeapTypeExn:
|
||||
vt = wasm.ValueTypeExnref
|
||||
default:
|
||||
if ht < 0 {
|
||||
return 0, fmt.Errorf("unknown abstract heap type: %d", ht)
|
||||
}
|
||||
vt = wasm.ValueTypeConcreteRef(uint32(ht), nullable)
|
||||
}
|
||||
if !nullable {
|
||||
vt = vt.AsNonNullable()
|
||||
}
|
||||
return vt, 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) {
|
||||
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
)
|
||||
|
||||
type ConstantExpression struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func evaluateConstExpr(e *ConstantExpression, globalResolver func(globalIndex Index) (ValueType, uint64, uint64, error), funcRefResolver func(funcIndex Index) (Reference, error)) ([]uint64, ValueType, error) {
|
||||
var stack []uint64
|
||||
var typeStack []ValueType
|
||||
var pc uint64
|
||||
data := e.Data
|
||||
for {
|
||||
if pc >= uint64(len(data)) {
|
||||
return nil, 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
opCode := data[pc]
|
||||
pc++
|
||||
switch opCode {
|
||||
case OpcodeI32Const:
|
||||
v, n, err := leb128.LoadInt32(data[pc:])
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("read i32: %w", err)
|
||||
}
|
||||
pc += n
|
||||
stack = append(stack, uint64(uint32(v)))
|
||||
typeStack = append(typeStack, ValueTypeI32)
|
||||
case OpcodeI64Const:
|
||||
v, n, err := leb128.LoadInt64(data[pc:])
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("read i64: %w", err)
|
||||
}
|
||||
pc += n
|
||||
stack = append(stack, uint64(v))
|
||||
typeStack = append(typeStack, ValueTypeI64)
|
||||
case OpcodeF32Const:
|
||||
if len(data[pc:]) < 4 {
|
||||
return nil, 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := binary.LittleEndian.Uint32(data[pc:])
|
||||
pc += 4
|
||||
stack = append(stack, uint64(v))
|
||||
typeStack = append(typeStack, ValueTypeF32)
|
||||
case OpcodeF64Const:
|
||||
if len(data[pc:]) < 8 {
|
||||
return nil, 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
v := binary.LittleEndian.Uint64(data[pc:])
|
||||
pc += 8
|
||||
stack = append(stack, uint64(v))
|
||||
typeStack = append(typeStack, ValueTypeF64)
|
||||
case OpcodeGlobalGet:
|
||||
v, n, err := leb128.LoadUint32(data[pc:])
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("read index of global: %w", err)
|
||||
}
|
||||
pc += n
|
||||
typ, lo, hi, err := globalResolver(Index(v))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
switch typ {
|
||||
case ValueTypeV128:
|
||||
stack = append(stack, lo, hi)
|
||||
default:
|
||||
stack = append(stack, lo)
|
||||
}
|
||||
typeStack = append(typeStack, typ)
|
||||
case OpcodeRefNull:
|
||||
// Reference types are opaque 64bit pointer at runtime.
|
||||
if pc >= uint64(len(data)) {
|
||||
return nil, 0, fmt.Errorf("read reference type for ref.null: %w", io.ErrShortBuffer)
|
||||
}
|
||||
b := data[pc]
|
||||
var valType ValueType
|
||||
switch b {
|
||||
case RefTypeFuncref.Kind():
|
||||
valType = RefTypeFuncref
|
||||
pc++
|
||||
case RefTypeExternref.Kind():
|
||||
valType = RefTypeExternref
|
||||
pc++
|
||||
case ValueTypeExnref.Kind():
|
||||
valType = ValueTypeExnref
|
||||
pc++
|
||||
default:
|
||||
// Concrete type index encoded as LEB128.
|
||||
typeIdx, n, err := leb128.LoadUint32(data[pc:])
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("invalid type for ref.null: 0x%x", b)
|
||||
}
|
||||
pc += n
|
||||
valType = ValueTypeConcreteRef(typeIdx, true)
|
||||
}
|
||||
stack = append(stack, 0)
|
||||
typeStack = append(typeStack, valType)
|
||||
case OpcodeRefFunc:
|
||||
v, n, err := leb128.LoadUint32(data[pc:])
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("read i32: %w", err)
|
||||
}
|
||||
pc += n
|
||||
ref, err := funcRefResolver(Index(v))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
stack = append(stack, uint64(ref))
|
||||
typeStack = append(typeStack, ValueTypeFuncref)
|
||||
case OpcodeVecPrefix:
|
||||
if data[pc] != OpcodeVecV128Const {
|
||||
return nil, 0, fmt.Errorf("invalid vector opcode for const expression: %#x", data[pc-1])
|
||||
}
|
||||
pc++
|
||||
if len(data[pc:]) < 16 {
|
||||
return nil, 0, fmt.Errorf("%s needs 16 bytes but was %d bytes", OpcodeVecV128ConstName, len(data[pc:]))
|
||||
}
|
||||
lo := binary.LittleEndian.Uint64(data[pc:])
|
||||
pc += 8
|
||||
hi := binary.LittleEndian.Uint64(data[pc:])
|
||||
pc += 8
|
||||
stack = append(stack, lo, hi)
|
||||
typeStack = append(typeStack, ValueTypeV128)
|
||||
case OpcodeI32Add:
|
||||
if len(typeStack) < 2 {
|
||||
return nil, 0, errors.New("stack underflow on i32.add")
|
||||
}
|
||||
v1 := typeStack[len(typeStack)-1]
|
||||
v2 := typeStack[len(typeStack)-2]
|
||||
if v1 != ValueTypeI32 || v2 != ValueTypeI32 {
|
||||
return nil, 0, fmt.Errorf("type mismatch on i32.add: %s, %s", ValueTypeName(v2), ValueTypeName(v1))
|
||||
}
|
||||
b, a := stack[len(stack)-1], stack[len(stack)-2]
|
||||
stack = stack[:len(stack)-2]
|
||||
stack = append(stack, uint64(uint32(a)+uint32(b)))
|
||||
typeStack = typeStack[:len(typeStack)-2]
|
||||
typeStack = append(typeStack, ValueTypeI32)
|
||||
case OpcodeI32Sub:
|
||||
if len(typeStack) < 2 {
|
||||
return nil, 0, errors.New("stack underflow on i32.sub")
|
||||
}
|
||||
v1 := typeStack[len(typeStack)-1]
|
||||
v2 := typeStack[len(typeStack)-2]
|
||||
if v1 != ValueTypeI32 || v2 != ValueTypeI32 {
|
||||
return nil, 0, fmt.Errorf("type mismatch on i32.sub: %s, %s", ValueTypeName(v2), ValueTypeName(v1))
|
||||
}
|
||||
b, a := stack[len(stack)-1], stack[len(stack)-2]
|
||||
stack = stack[:len(stack)-2]
|
||||
stack = append(stack, uint64(uint32(a)-uint32(b)))
|
||||
typeStack = typeStack[:len(typeStack)-2]
|
||||
typeStack = append(typeStack, ValueTypeI32)
|
||||
case OpcodeI32Mul:
|
||||
if len(typeStack) < 2 {
|
||||
return nil, 0, errors.New("stack underflow on i32.mul")
|
||||
}
|
||||
v1 := typeStack[len(typeStack)-1]
|
||||
v2 := typeStack[len(typeStack)-2]
|
||||
if v1 != ValueTypeI32 || v2 != ValueTypeI32 {
|
||||
return nil, 0, fmt.Errorf("type mismatch on i32.mul: %s, %s", ValueTypeName(v2), ValueTypeName(v1))
|
||||
}
|
||||
b, a := stack[len(stack)-1], stack[len(stack)-2]
|
||||
stack = stack[:len(stack)-2]
|
||||
stack = append(stack, uint64(uint32(a)*uint32(b)))
|
||||
typeStack = typeStack[:len(typeStack)-2]
|
||||
typeStack = append(typeStack, ValueTypeI32)
|
||||
case OpcodeI64Add:
|
||||
if len(typeStack) < 2 {
|
||||
return nil, 0, errors.New("stack underflow on i64.add")
|
||||
}
|
||||
v1 := typeStack[len(typeStack)-1]
|
||||
v2 := typeStack[len(typeStack)-2]
|
||||
if v1 != ValueTypeI64 || v2 != ValueTypeI64 {
|
||||
return nil, 0, fmt.Errorf("type mismatch on i64.add: %s, %s", ValueTypeName(v2), ValueTypeName(v1))
|
||||
}
|
||||
b, a := stack[len(stack)-1], stack[len(stack)-2]
|
||||
stack = stack[:len(stack)-2]
|
||||
stack = append(stack, a+b)
|
||||
typeStack = typeStack[:len(typeStack)-2]
|
||||
typeStack = append(typeStack, ValueTypeI64)
|
||||
case OpcodeI64Sub:
|
||||
if len(typeStack) < 2 {
|
||||
return nil, 0, errors.New("stack underflow on i64.sub")
|
||||
}
|
||||
v1 := typeStack[len(typeStack)-1]
|
||||
v2 := typeStack[len(typeStack)-2]
|
||||
if v1 != ValueTypeI64 || v2 != ValueTypeI64 {
|
||||
return nil, 0, fmt.Errorf("type mismatch on i64.sub: %s, %s", ValueTypeName(v2), ValueTypeName(v1))
|
||||
}
|
||||
b, a := stack[len(stack)-1], stack[len(stack)-2]
|
||||
stack = stack[:len(stack)-2]
|
||||
stack = append(stack, a-b)
|
||||
typeStack = typeStack[:len(typeStack)-2]
|
||||
typeStack = append(typeStack, ValueTypeI64)
|
||||
case OpcodeI64Mul:
|
||||
if len(typeStack) < 2 {
|
||||
return nil, 0, errors.New("stack underflow on i64.mul")
|
||||
}
|
||||
v1 := typeStack[len(typeStack)-1]
|
||||
v2 := typeStack[len(typeStack)-2]
|
||||
if v1 != ValueTypeI64 || v2 != ValueTypeI64 {
|
||||
return nil, 0, fmt.Errorf("type mismatch on i64.mul: %s, %s", ValueTypeName(v2), ValueTypeName(v1))
|
||||
}
|
||||
b, a := stack[len(stack)-1], stack[len(stack)-2]
|
||||
stack = stack[:len(stack)-2]
|
||||
stack = append(stack, a*b)
|
||||
typeStack = typeStack[:len(typeStack)-2]
|
||||
typeStack = append(typeStack, ValueTypeI64)
|
||||
case OpcodeEnd:
|
||||
if len(typeStack) != 1 {
|
||||
return nil, 0, errors.New("stack has more than one value at end of constant expression")
|
||||
}
|
||||
return stack, typeStack[0], nil
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("invalid opcode for const expression: 0x%x", opCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateConstExprInModuleInstance(e *ConstantExpression, m *ModuleInstance) []uint64 {
|
||||
v, _, _ := evaluateConstExpr(
|
||||
e,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
g := m.Globals[globalIndex]
|
||||
return g.Type.ValType, g.Val, g.ValHi, nil
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
return m.Engine.FunctionInstanceReference(funcIndex), nil
|
||||
},
|
||||
)
|
||||
return v
|
||||
}
|
||||
|
||||
func NewConstantExpressionFromOpcode(
|
||||
opcode byte, opData []byte,
|
||||
) ConstantExpression {
|
||||
data := make([]byte, 0, 3+len(opData)) // 2 for opcode and optional vec prefix, 1 for end
|
||||
if opcode == OpcodeVecV128Const {
|
||||
data = append(data, OpcodeVecPrefix)
|
||||
}
|
||||
data = append(data, opcode)
|
||||
data = append(data, opData...)
|
||||
data = append(data, OpcodeEnd)
|
||||
return ConstantExpression{Data: data}
|
||||
}
|
||||
|
||||
func NewConstantExpressionFromI32(val int32) ConstantExpression {
|
||||
return NewConstantExpressionFromOpcode(OpcodeI32Const, leb128.EncodeInt32(val))
|
||||
}
|
||||
|
||||
func NewConstantExpressionFromI64(val int64) ConstantExpression {
|
||||
return NewConstantExpressionFromOpcode(OpcodeI64Const, leb128.EncodeInt64(val))
|
||||
}
|
||||
+2
@@ -45,6 +45,8 @@ func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as
|
||||
return uint32(len(m.CodeSection))
|
||||
case SectionIDData:
|
||||
return uint32(len(m.DataSection))
|
||||
case SectionIDTag:
|
||||
return uint32(len(m.TagSection))
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unknown section: %d", sectionID))
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package wasm
|
||||
|
||||
// Exception represents a thrown WebAssembly exception.
|
||||
type Exception struct {
|
||||
// Tag is the tag instance that was thrown.
|
||||
Tag *TagInstance
|
||||
// Params holds the argument values matching the tag's function type params.
|
||||
Params []uint64
|
||||
}
|
||||
+472
-46
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -29,9 +30,9 @@ const maximumValuesOnStack = 1 << 27
|
||||
// Returns an error if the instruction sequence is not valid,
|
||||
// or potentially it can exceed the maximum number of values on the stack.
|
||||
func (m *Module) validateFunction(sts *stacks, enabledFeatures api.CoreFeatures, idx Index, functions []Index,
|
||||
globals []GlobalType, memory *Memory, tables []Table, declaredFunctionIndexes map[Index]struct{}, br *bytes.Reader,
|
||||
globals []GlobalType, memory *Memory, tables []Table, tags []Index, declaredFunctionIndexes map[Index]struct{}, br *bytes.Reader,
|
||||
) error {
|
||||
return m.validateFunctionWithMaxStackValues(sts, enabledFeatures, idx, functions, globals, memory, tables, maximumValuesOnStack, declaredFunctionIndexes, br)
|
||||
return m.validateFunctionWithMaxStackValues(sts, enabledFeatures, idx, functions, globals, memory, tables, tags, maximumValuesOnStack, declaredFunctionIndexes, br)
|
||||
}
|
||||
|
||||
func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err error) {
|
||||
@@ -69,6 +70,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
globals []GlobalType,
|
||||
memory *Memory,
|
||||
tables []Table,
|
||||
tags []Index,
|
||||
maxStackValues int,
|
||||
declaredFunctionIndexes map[Index]struct{},
|
||||
br *bytes.Reader,
|
||||
@@ -98,7 +100,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
} else {
|
||||
instName = InstructionName(op)
|
||||
}
|
||||
fmt.Printf("handling %s, stack=%s, blocks: %v\n", instName, valueTypeStack.stack, controlBlockStack)
|
||||
fmt.Printf("handling %s, stack=%v, blocks: %v\n", instName, valueTypeStack.stack, controlBlockStack)
|
||||
}
|
||||
|
||||
if len(controlBlockStack.stack) == 0 {
|
||||
@@ -347,6 +349,9 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
|
||||
OpcodeLocalGetName, index, l)
|
||||
}
|
||||
if err := sts.requireLocalInit(index, inputLen, localTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
if index < inputLen {
|
||||
valueTypeStack.push(functionType.Params[index])
|
||||
} else {
|
||||
@@ -367,6 +372,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
if err := valueTypeStack.popAndVerifyType(expType); err != nil {
|
||||
return err
|
||||
}
|
||||
sts.markLocalInit(index, inputLen, localTypes)
|
||||
case OpcodeLocalTee:
|
||||
inputLen := uint32(len(functionType.Params))
|
||||
if l := uint32(len(localTypes)) + inputLen; index >= l {
|
||||
@@ -383,6 +389,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return err
|
||||
}
|
||||
valueTypeStack.push(expType)
|
||||
sts.markLocalInit(index, inputLen, localTypes)
|
||||
case OpcodeGlobalGet:
|
||||
if index >= uint32(len(globals)) {
|
||||
return fmt.Errorf("invalid index for %s", OpcodeGlobalGetName)
|
||||
@@ -503,9 +510,8 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return err
|
||||
}
|
||||
if actual == valueTypeUnknown {
|
||||
// Re-assign the expected type to unknown.
|
||||
defaultLabelType[index] = valueTypeUnknown
|
||||
} else if actual != exp {
|
||||
} else if !isRefSubtypeOf(actual, exp) {
|
||||
return typeMismatchError(true, OpcodeBrTableName, actual, exp, i)
|
||||
}
|
||||
}
|
||||
@@ -530,14 +536,151 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("inconsistent block type length for %s at %d; %v (ln=%d) != %v (l=%d)", OpcodeBrTableName, l, defaultLabelType, ln, tableLabelType, l)
|
||||
}
|
||||
for i := range defaultLabelType {
|
||||
if defaultLabelType[i] != valueTypeUnknown && defaultLabelType[i] != tableLabelType[i] {
|
||||
return fmt.Errorf("incosistent block type for %s at %d", OpcodeBrTableName, l)
|
||||
if defaultLabelType[i] != valueTypeUnknown && !areRefTypesCompatible(defaultLabelType[i], tableLabelType[i]) {
|
||||
return fmt.Errorf("inconsistent block type for %s at %d", OpcodeBrTableName, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// br_table instruction is stack-polymorphic.
|
||||
valueTypeStack.unreachable()
|
||||
} else if op == OpcodeThrow {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeThrowName, err)
|
||||
}
|
||||
pc++
|
||||
tagIndex, num, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read immediate: %v", err)
|
||||
}
|
||||
pc += num - 1
|
||||
if tagIndex >= uint32(len(tags)) {
|
||||
return fmt.Errorf("invalid tag index for %s: %d", OpcodeThrowName, tagIndex)
|
||||
}
|
||||
tagType := &m.TypeSection[tags[tagIndex]]
|
||||
// Pop values matching the tag's params in reverse order.
|
||||
for i := len(tagType.Params) - 1; i >= 0; i-- {
|
||||
if err := valueTypeStack.popAndVerifyType(tagType.Params[i]); err != nil {
|
||||
return fmt.Errorf("type mismatch on %s operation: %v", OpcodeThrowName, err)
|
||||
}
|
||||
}
|
||||
// throw is stack-polymorphic (never returns).
|
||||
valueTypeStack.unreachable()
|
||||
} else if op == OpcodeThrowRef {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeThrowRefName, err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeExnref); err != nil {
|
||||
return fmt.Errorf("type mismatch on %s: %v", OpcodeThrowRefName, err)
|
||||
}
|
||||
// throw_ref is stack-polymorphic (never returns).
|
||||
valueTypeStack.unreachable()
|
||||
} else if op == OpcodeTryTable {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesExceptionHandling); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeTryTableName, err)
|
||||
}
|
||||
br.Reset(body[pc+1:])
|
||||
bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
pc += num
|
||||
|
||||
// Read catch clause count.
|
||||
catchCount, catchNum, err := leb128.LoadUint32(body[pc+1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read catch count: %v", err)
|
||||
}
|
||||
pc += catchNum
|
||||
|
||||
// Validate each catch clause.
|
||||
for i := uint32(0); i < catchCount; i++ {
|
||||
pc++
|
||||
catchKind := body[pc]
|
||||
switch catchKind {
|
||||
case CatchKindCatch, CatchKindCatchRef:
|
||||
pc++
|
||||
tagIdx, tagNum, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read catch tag index: %v", err)
|
||||
}
|
||||
pc += tagNum - 1
|
||||
if tagIdx >= uint32(len(tags)) {
|
||||
return fmt.Errorf("invalid tag index in catch clause: %d", tagIdx)
|
||||
}
|
||||
tagType := &m.TypeSection[tags[tagIdx]]
|
||||
pc++
|
||||
labelIdx, labelNum, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read catch label index: %v", err)
|
||||
}
|
||||
pc += labelNum - 1
|
||||
if int(labelIdx) >= len(controlBlockStack.stack) {
|
||||
return fmt.Errorf("invalid label index in catch clause: %d", labelIdx)
|
||||
}
|
||||
// Validate that the target label can accept the catch values.
|
||||
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(labelIdx)-1]
|
||||
var expectedTypes []ValueType
|
||||
if target.op == OpcodeLoop {
|
||||
expectedTypes = target.blockType.Params
|
||||
} else {
|
||||
expectedTypes = target.blockType.Results
|
||||
}
|
||||
var catchTypes []ValueType
|
||||
catchTypes = append(catchTypes, tagType.Params...)
|
||||
if catchKind == CatchKindCatchRef {
|
||||
catchTypes = append(catchTypes, ValueTypeExnref.AsNonNullable())
|
||||
}
|
||||
if len(catchTypes) != len(expectedTypes) {
|
||||
return fmt.Errorf("catch clause type mismatch: catch delivers %d values but label expects %d", len(catchTypes), len(expectedTypes))
|
||||
}
|
||||
for j := range catchTypes {
|
||||
if !isRefSubtypeOf(catchTypes[j], expectedTypes[j]) {
|
||||
return fmt.Errorf("catch clause type mismatch at index %d: %v is not a subtype of %v", j, catchTypes[j], expectedTypes[j])
|
||||
}
|
||||
}
|
||||
case CatchKindCatchAll, CatchKindCatchAllRef:
|
||||
pc++
|
||||
labelIdx, labelNum, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read catch_all label index: %v", err)
|
||||
}
|
||||
pc += labelNum - 1
|
||||
if int(labelIdx) >= len(controlBlockStack.stack) {
|
||||
return fmt.Errorf("invalid label index in catch_all clause: %d", labelIdx)
|
||||
}
|
||||
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(labelIdx)-1]
|
||||
var expectedTypes []ValueType
|
||||
if target.op == OpcodeLoop {
|
||||
expectedTypes = target.blockType.Params
|
||||
} else {
|
||||
expectedTypes = target.blockType.Results
|
||||
}
|
||||
var catchTypes []ValueType
|
||||
if catchKind == CatchKindCatchAllRef {
|
||||
catchTypes = append(catchTypes, ValueTypeExnref.AsNonNullable())
|
||||
}
|
||||
if len(catchTypes) != len(expectedTypes) {
|
||||
return fmt.Errorf("catch_all clause type mismatch: catch delivers %d values but label expects %d", len(catchTypes), len(expectedTypes))
|
||||
}
|
||||
for j := range catchTypes {
|
||||
if !isRefSubtypeOf(catchTypes[j], expectedTypes[j]) {
|
||||
return fmt.Errorf("catch_all clause type mismatch at index %d", j)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid catch kind: %#x", catchKind)
|
||||
}
|
||||
}
|
||||
|
||||
controlBlockStack.push(pc, 0, 0, bt, 0, op)
|
||||
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range bt.Params {
|
||||
valueTypeStack.push(p)
|
||||
}
|
||||
valueTypeStack.pushStackLimit(len(bt.Params))
|
||||
} else if op == OpcodeCall || op == OpcodeTailCallReturnCall {
|
||||
pc++
|
||||
index, num, err := leb128.LoadUint32(body[pc:])
|
||||
@@ -557,20 +700,11 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
}
|
||||
|
||||
funcType := &m.TypeSection[functions[index]]
|
||||
for i := 0; i < len(funcType.Params); i++ {
|
||||
if err := valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil {
|
||||
return fmt.Errorf("type mismatch on %s operation param type: %v", opcodeName, err)
|
||||
}
|
||||
}
|
||||
for _, exp := range funcType.Results {
|
||||
valueTypeStack.push(exp)
|
||||
if err := sts.validateCallSignature(opcodeName, funcType); err != nil {
|
||||
return err
|
||||
}
|
||||
if op == OpcodeTailCallReturnCall {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeTailCallReturnCallName, err)
|
||||
}
|
||||
// Same formatting as OpcodeEnd on the outer-most block
|
||||
if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
|
||||
if err := sts.validateReturnCall(OpcodeTailCallReturnCallName, enabledFeatures, funcType, functionType); err != nil {
|
||||
return err
|
||||
}
|
||||
// behaves as a jump.
|
||||
@@ -619,21 +753,39 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("cannot pop the offset in table for %s", opcodeName)
|
||||
}
|
||||
funcType := &m.TypeSection[typeIndex]
|
||||
for i := 0; i < len(funcType.Params); i++ {
|
||||
if err = valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil {
|
||||
return fmt.Errorf("type mismatch on %s operation input type", opcodeName)
|
||||
}
|
||||
if err := sts.validateCallSignature(opcodeName, funcType); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, exp := range funcType.Results {
|
||||
valueTypeStack.push(exp)
|
||||
}
|
||||
|
||||
if op == OpcodeTailCallReturnCallIndirect {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeTailCallReturnCallIndirectName, err)
|
||||
if err := sts.validateReturnCall(OpcodeTailCallReturnCallIndirectName, enabledFeatures, funcType, functionType); err != nil {
|
||||
return err
|
||||
}
|
||||
// Same formatting as OpcodeEnd on the outer-most block
|
||||
if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
|
||||
// behaves as a jump.
|
||||
valueTypeStack.unreachable()
|
||||
}
|
||||
} else if op == OpcodeCallRef || op == OpcodeReturnCallRef {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", instructionNames[op], err)
|
||||
}
|
||||
pc++
|
||||
typeIndex, num, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read immediate: %v", err)
|
||||
}
|
||||
pc += num - 1
|
||||
if int(typeIndex) >= len(m.TypeSection) {
|
||||
return fmt.Errorf("invalid type index at %s: %d", instructionNames[op], typeIndex)
|
||||
}
|
||||
// Pop the funcref operand (nullable concrete ref to the type).
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeConcreteRef(typeIndex, true)); err != nil {
|
||||
return fmt.Errorf("type mismatch on %s operation: %v", instructionNames[op], err)
|
||||
}
|
||||
funcType := &m.TypeSection[typeIndex]
|
||||
if err := sts.validateCallSignature(instructionNames[op], funcType); err != nil {
|
||||
return err
|
||||
}
|
||||
if op == OpcodeReturnCallRef {
|
||||
if err := sts.validateReturnCall(OpcodeReturnCallRefName, enabledFeatures, funcType, functionType); err != nil {
|
||||
return err
|
||||
}
|
||||
// behaves as a jump.
|
||||
@@ -859,13 +1011,28 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
switch op {
|
||||
case OpcodeRefNull:
|
||||
pc++
|
||||
switch reftype := body[pc]; reftype {
|
||||
switch reftype := body[pc]; ValueType(reftype) {
|
||||
case ValueTypeExternref:
|
||||
valueTypeStack.push(ValueTypeExternref)
|
||||
case ValueTypeFuncref:
|
||||
valueTypeStack.push(ValueTypeFuncref)
|
||||
case ValueTypeExnref:
|
||||
valueTypeStack.push(ValueTypeExnref)
|
||||
default:
|
||||
return fmt.Errorf("unknown type for ref.null: 0x%x", reftype)
|
||||
// Concrete type index encoded as LEB128 u32.
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
|
||||
return fmt.Errorf("ref.null with concrete type invalid as %v", err)
|
||||
}
|
||||
br.Reset(body[pc:])
|
||||
typeIdx, num, err := leb128.DecodeUint32(br)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unknown type for ref.null: 0x%x", reftype)
|
||||
}
|
||||
if int(typeIdx) >= len(m.TypeSection) {
|
||||
return fmt.Errorf("unknown type for ref.null: type index %d out of range", typeIdx)
|
||||
}
|
||||
pc += uint64(num) - 1
|
||||
valueTypeStack.push(ValueTypeConcreteRef(typeIdx, true))
|
||||
}
|
||||
case OpcodeRefIsNull:
|
||||
tp, err := valueTypeStack.pop()
|
||||
@@ -885,8 +1052,119 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("undeclared function index %d for ref.func", index)
|
||||
}
|
||||
pc += num - 1
|
||||
valueTypeStack.push(ValueTypeFuncref)
|
||||
if enabledFeatures.IsEnabled(experimental.CoreFeaturesTypedFunctionReferences) {
|
||||
valueTypeStack.push(ValueTypeConcreteRef(functions[index], false))
|
||||
} else {
|
||||
valueTypeStack.push(ValueTypeFuncref)
|
||||
}
|
||||
}
|
||||
} else if op == OpcodeRefAsNonNull {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeRefAsNonNullName, err)
|
||||
}
|
||||
tp, err := valueTypeStack.pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeRefAsNonNullName, err)
|
||||
} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
|
||||
return fmt.Errorf("type mismatch: expected reference type but was %s for %s", ValueTypeName(tp), OpcodeRefAsNonNullName)
|
||||
}
|
||||
if tp == valueTypeUnknown {
|
||||
valueTypeStack.push(tp)
|
||||
} else {
|
||||
valueTypeStack.push(tp.AsNonNullable())
|
||||
}
|
||||
} else if op == OpcodeBrOnNull {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeBrOnNullName, err)
|
||||
}
|
||||
pc++
|
||||
index, num, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read immediate: %v", err)
|
||||
} else if int(index) >= len(controlBlockStack.stack) {
|
||||
return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrOnNullName)
|
||||
}
|
||||
pc += num - 1
|
||||
// Pop the reference operand.
|
||||
tp, err := valueTypeStack.pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeBrOnNullName, err)
|
||||
} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
|
||||
return fmt.Errorf("type mismatch: expected reference type but was %s for %s", ValueTypeName(tp), OpcodeBrOnNullName)
|
||||
}
|
||||
// If null, branch with the label's expected values (without the ref).
|
||||
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
|
||||
var targetResultType []ValueType
|
||||
if target.op == OpcodeLoop {
|
||||
targetResultType = target.blockType.Params
|
||||
} else {
|
||||
targetResultType = target.blockType.Results
|
||||
}
|
||||
if err = valueTypeStack.popResults(op, targetResultType, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Push back the label results (they stay on the stack for the fall-through path).
|
||||
for _, t := range targetResultType {
|
||||
valueTypeStack.push(t)
|
||||
}
|
||||
// On the fall-through path, the non-null ref is on the stack.
|
||||
if tp != valueTypeUnknown {
|
||||
valueTypeStack.push(tp.AsNonNullable())
|
||||
} else {
|
||||
valueTypeStack.push(tp)
|
||||
}
|
||||
} else if op == OpcodeBrOnNonNull {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTypedFunctionReferences); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", OpcodeBrOnNonNullName, err)
|
||||
}
|
||||
pc++
|
||||
index, num, err := leb128.LoadUint32(body[pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read immediate: %v", err)
|
||||
} else if int(index) >= len(controlBlockStack.stack) {
|
||||
return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrOnNonNullName)
|
||||
}
|
||||
pc += num - 1
|
||||
// Pop the reference operand.
|
||||
tp, err := valueTypeStack.pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeBrOnNonNullName, err)
|
||||
} else if !isReferenceValueType(tp) && tp != valueTypeUnknown {
|
||||
return fmt.Errorf("type mismatch: expected reference type but was %s for %s", ValueTypeName(tp), OpcodeBrOnNonNullName)
|
||||
}
|
||||
// If non-null, branch to label with the label's expected values.
|
||||
// The non-null ref is the LAST value delivered to the label.
|
||||
target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
|
||||
var targetResultType []ValueType
|
||||
if target.op == OpcodeLoop {
|
||||
targetResultType = target.blockType.Params
|
||||
} else {
|
||||
targetResultType = target.blockType.Results
|
||||
}
|
||||
// The branch delivers: [label types..., non-null ref].
|
||||
// So the last type in the target must match the non-null ref.
|
||||
if len(targetResultType) == 0 {
|
||||
return fmt.Errorf("type mismatch on %s: label has no results but needs a ref type", OpcodeBrOnNonNullName)
|
||||
}
|
||||
nonNullTp := tp
|
||||
if nonNullTp != valueTypeUnknown {
|
||||
nonNullTp = tp.AsNonNullable()
|
||||
}
|
||||
lastTarget := targetResultType[len(targetResultType)-1]
|
||||
if nonNullTp != valueTypeUnknown && !isRefSubtypeOf(nonNullTp, lastTarget) {
|
||||
return fmt.Errorf("type mismatch on %s: ref type %s is not a subtype of label's last result %s",
|
||||
OpcodeBrOnNonNullName, ValueTypeName(nonNullTp), ValueTypeName(lastTarget))
|
||||
}
|
||||
// Pop and verify the remaining label types (all except the last which is the ref).
|
||||
remaining := targetResultType[:len(targetResultType)-1]
|
||||
if err = valueTypeStack.popResults(op, remaining, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Push back the remaining label results for the fall-through path.
|
||||
for _, t := range remaining {
|
||||
valueTypeStack.push(t)
|
||||
}
|
||||
// On the fall-through (null) path, nothing extra is pushed.
|
||||
} else if op == OpcodeTableGet || op == OpcodeTableSet {
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
|
||||
return fmt.Errorf("%s is invalid as %v", InstructionName(op), err)
|
||||
@@ -902,7 +1180,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
|
||||
refType := tables[tableIndex].Type
|
||||
if op == OpcodeTableGet {
|
||||
if err := valueTypeStack.popAndVerifyType(api.ValueTypeI32); err != nil {
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for table.get: %v", err)
|
||||
}
|
||||
valueTypeStack.push(refType)
|
||||
@@ -910,7 +1188,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
if err := valueTypeStack.popAndVerifyType(refType); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for table.set: %v", err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(api.ValueTypeI32); err != nil {
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for table.set: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1039,7 +1317,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("table of index %d not found", tableIndex)
|
||||
}
|
||||
|
||||
if m.ElementSection[elementIndex].Type != tables[tableIndex].Type {
|
||||
if !isRefSubtypeOf(m.ElementSection[elementIndex].Type, tables[tableIndex].Type) {
|
||||
return fmt.Errorf("type mismatch for table.init: element type %s does not match table type %s",
|
||||
RefTypeName(m.ElementSection[elementIndex].Type),
|
||||
RefTypeName(tables[tableIndex].Type),
|
||||
@@ -1086,7 +1364,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("table of index %d not found", srcTableIndex)
|
||||
}
|
||||
|
||||
if tables[srcTableIndex].Type != tables[dstTableIndex].Type {
|
||||
if !isRefSubtypeOf(tables[srcTableIndex].Type, tables[dstTableIndex].Type) {
|
||||
return fmt.Errorf("table type mismatch for table.copy: %s (src) != %s (dst)",
|
||||
RefTypeName(tables[srcTableIndex].Type), RefTypeName(tables[dstTableIndex].Type))
|
||||
}
|
||||
@@ -1446,6 +1724,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
controlBlockStack.push(pc, 0, 0, bt, num, 0)
|
||||
controlBlockStack.stack[len(controlBlockStack.stack)-1].savedInitLocals = maps.Clone(sts.initLocals)
|
||||
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1834,6 +2113,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
controlBlockStack.push(pc, 0, 0, bt, num, op)
|
||||
controlBlockStack.stack[len(controlBlockStack.stack)-1].savedInitLocals = maps.Clone(sts.initLocals)
|
||||
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1850,6 +2130,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
controlBlockStack.push(pc, 0, 0, bt, num, op)
|
||||
controlBlockStack.stack[len(controlBlockStack.stack)-1].savedInitLocals = maps.Clone(sts.initLocals)
|
||||
if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for 'if': %v", err)
|
||||
}
|
||||
@@ -1873,6 +2154,8 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
if err := valueTypeStack.popResults(OpcodeIf, bl.blockType.Results, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restore init locals to the state at if-entry for the else branch.
|
||||
sts.initLocals = maps.Clone(bl.savedInitLocals)
|
||||
// Before entering instructions inside else, we pop all the values pushed by then block.
|
||||
valueTypeStack.resetAtStackLimit()
|
||||
// Plus we have to push any block params again.
|
||||
@@ -1882,6 +2165,10 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
} else if op == OpcodeEnd {
|
||||
bl := controlBlockStack.pop()
|
||||
bl.endAt = pc
|
||||
// Restore init locals to the state at block entry (skip for outermost function block).
|
||||
if len(controlBlockStack.stack) > 0 {
|
||||
sts.initLocals = bl.savedInitLocals
|
||||
}
|
||||
|
||||
// OpcodeEnd can end a block or the function itself. Check to see what it is:
|
||||
|
||||
@@ -1889,7 +2176,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
if ifMissingElse {
|
||||
// If this is the end of block without else, the number of block's results and params must be same.
|
||||
// Otherwise, the value stack would result in the inconsistent state at runtime.
|
||||
if !bytes.Equal(bl.blockType.Results, bl.blockType.Params) {
|
||||
if !slices.Equal(bl.blockType.Results, bl.blockType.Params) {
|
||||
return typeCountError(false, OpcodeElseName, bl.blockType.Params, bl.blockType.Results)
|
||||
}
|
||||
// -1 skips else, to handle if block without else properly.
|
||||
@@ -1952,20 +2239,50 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
return fmt.Errorf("too many type immediates for %s", InstructionName(op))
|
||||
}
|
||||
pc++
|
||||
tp := body[pc]
|
||||
if tp != ValueTypeI32 && tp != ValueTypeI64 && tp != ValueTypeF32 && tp != ValueTypeF64 &&
|
||||
tp != api.ValueTypeExternref && tp != ValueTypeFuncref && tp != ValueTypeV128 {
|
||||
return fmt.Errorf("invalid type %s for %s", ValueTypeName(tp), OpcodeTypedSelectName)
|
||||
b := body[pc]
|
||||
switch b {
|
||||
case RefPrefixNullable, RefPrefixNonNullable:
|
||||
br.Reset(body[pc+1:])
|
||||
ht, num, err := leb128.DecodeInt33AsInt64(br)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read heap type for %s: %v", OpcodeTypedSelectName, err)
|
||||
}
|
||||
pc += uint64(num)
|
||||
switch ht {
|
||||
case HeapTypeFunc:
|
||||
// ok
|
||||
case HeapTypeExtern:
|
||||
// ok
|
||||
case HeapTypeExn:
|
||||
// ok
|
||||
default:
|
||||
if ht < 0 {
|
||||
return fmt.Errorf("invalid heap type for %s: %d", OpcodeTypedSelectName, ht)
|
||||
}
|
||||
if int(ht) >= len(m.TypeSection) {
|
||||
return fmt.Errorf("unknown type for %s: type index %d out of range", OpcodeTypedSelectName, ht)
|
||||
}
|
||||
}
|
||||
default:
|
||||
tp := ValueType(b)
|
||||
if tp != ValueTypeI32 && tp != ValueTypeI64 && tp != ValueTypeF32 && tp != ValueTypeF64 &&
|
||||
tp != ValueTypeExternref && tp != ValueTypeFuncref && tp != ValueTypeV128 {
|
||||
return fmt.Errorf("invalid type %s for %s", ValueTypeName(tp), OpcodeTypedSelectName)
|
||||
}
|
||||
}
|
||||
} else if isReferenceValueType(v1) || isReferenceValueType(v2) {
|
||||
return fmt.Errorf("reference types cannot be used for non typed select instruction")
|
||||
}
|
||||
|
||||
if v1 != v2 && v1 != valueTypeUnknown && v2 != valueTypeUnknown {
|
||||
if v1 != valueTypeUnknown && v2 != valueTypeUnknown && !areRefTypesCompatible(v1, v2) {
|
||||
return fmt.Errorf("type mismatch on 1st and 2nd select operands")
|
||||
}
|
||||
if v1 == valueTypeUnknown {
|
||||
valueTypeStack.push(v2)
|
||||
} else if v2 == valueTypeUnknown {
|
||||
valueTypeStack.push(v1)
|
||||
} else if isRefSubtypeOf(v1, v2) {
|
||||
valueTypeStack.push(v2)
|
||||
} else {
|
||||
valueTypeStack.push(v1)
|
||||
}
|
||||
@@ -1973,6 +2290,10 @@ func (m *Module) validateFunctionWithMaxStackValues(
|
||||
// unreachable instruction is stack-polymorphic.
|
||||
valueTypeStack.unreachable()
|
||||
} else if op == OpcodeNop {
|
||||
} else if enabledFeatures.IsEnabled(experimental.CoreFeaturesExceptionHandling) &&
|
||||
(op == OpcodeLegacyTry || op == OpcodeLegacyCatch || op == OpcodeLegacyRethrow ||
|
||||
op == OpcodeLegacyDelegate || op == OpcodeLegacyCatchAll) {
|
||||
return fmt.Errorf("legacy exception handling instruction 0x%x not supported; recompile with wasm-opt --translate-to-exnref", op)
|
||||
} else {
|
||||
return fmt.Errorf("invalid instruction 0x%x", op)
|
||||
}
|
||||
@@ -2047,6 +2368,8 @@ type stacks struct {
|
||||
cs controlBlockStack
|
||||
// ls is the label slice that is reused for each br_table instruction.
|
||||
ls []uint32
|
||||
// initLocals tracks which non-nullable ref locals have been initialized.
|
||||
initLocals map[uint32]struct{}
|
||||
}
|
||||
|
||||
func (sts *stacks) reset(functionType *FunctionType) {
|
||||
@@ -2057,12 +2380,64 @@ func (sts *stacks) reset(functionType *FunctionType) {
|
||||
sts.cs.stack = sts.cs.stack[:0]
|
||||
sts.cs.stack = append(sts.cs.stack, controlBlock{blockType: functionType})
|
||||
sts.ls = sts.ls[:0]
|
||||
clear(sts.initLocals)
|
||||
if sts.initLocals == nil {
|
||||
sts.initLocals = make(map[uint32]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
func (sts *stacks) validateCallSignature(opName string, funcType *FunctionType) error {
|
||||
for i := len(funcType.Params) - 1; i >= 0; i-- {
|
||||
if err := sts.vs.popAndVerifyType(funcType.Params[i]); err != nil {
|
||||
return fmt.Errorf("type mismatch on %s operation param type: %v", opName, err)
|
||||
}
|
||||
}
|
||||
for _, exp := range funcType.Results {
|
||||
sts.vs.push(exp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sts *stacks) validateReturnCall(
|
||||
opName string,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
calleeFuncType, callerFuncType *FunctionType,
|
||||
) error {
|
||||
if err := enabledFeatures.RequireEnabled(experimental.CoreFeaturesTailCall); err != nil {
|
||||
return fmt.Errorf("%s invalid as %v", opName, err)
|
||||
}
|
||||
if len(calleeFuncType.Results) != len(callerFuncType.Results) {
|
||||
return fmt.Errorf("type mismatch on %s: caller returns %d values but callee returns %d",
|
||||
opName, len(callerFuncType.Results), len(calleeFuncType.Results))
|
||||
}
|
||||
return sts.vs.requireStackValues(false, "", callerFuncType.Results, false)
|
||||
}
|
||||
|
||||
type controlBlockStack struct {
|
||||
stack []controlBlock
|
||||
}
|
||||
|
||||
func (sts *stacks) requireLocalInit(index, inputLen uint32, localTypes []ValueType) error {
|
||||
if index >= inputLen {
|
||||
lt := localTypes[index-inputLen]
|
||||
if lt.IsRef() && !lt.IsNullable() {
|
||||
if _, ok := sts.initLocals[index]; !ok {
|
||||
return fmt.Errorf("uninitialized local %d", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sts *stacks) markLocalInit(index, inputLen uint32, localTypes []ValueType) {
|
||||
if index >= inputLen {
|
||||
lt := localTypes[index-inputLen]
|
||||
if lt.IsRef() && !lt.IsNullable() {
|
||||
sts.initLocals[index] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *controlBlockStack) pop() *controlBlock {
|
||||
tail := len(s.stack) - 1
|
||||
ret := &s.stack[tail]
|
||||
@@ -2125,7 +2500,7 @@ func (s *valueTypeStack) popAndVerifyType(expected ValueType) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("%s missing", ValueTypeName(expected))
|
||||
}
|
||||
if have != expected && have != valueTypeUnknown && expected != valueTypeUnknown {
|
||||
if have != valueTypeUnknown && expected != valueTypeUnknown && !isRefSubtypeOf(have, expected) {
|
||||
return fmt.Errorf("type mismatch: expected %s, but was %s", ValueTypeName(expected), ValueTypeName(have))
|
||||
}
|
||||
return nil
|
||||
@@ -2207,7 +2582,7 @@ func (s *valueTypeStack) requireStackValues(
|
||||
// Finally, check the types of the values:
|
||||
for i, v := range s.requireStackValuesTmp {
|
||||
nextWant := want[countWanted-i-1] // have is in reverse order (stack)
|
||||
if v != nextWant && v != valueTypeUnknown && nextWant != valueTypeUnknown {
|
||||
if v != valueTypeUnknown && nextWant != valueTypeUnknown && !isRefSubtypeOf(v, nextWant) {
|
||||
return typeMismatchError(isParam, context, v, nextWant, i)
|
||||
}
|
||||
}
|
||||
@@ -2304,6 +2679,8 @@ type controlBlock struct {
|
||||
blockTypeBytes uint64
|
||||
// op is zero when the outermost block
|
||||
op Opcode
|
||||
// savedInitLocals is the set of initialized locals at the time this block was entered.
|
||||
savedInitLocals map[uint32]struct{}
|
||||
}
|
||||
|
||||
// DecodeBlockType decodes the type index from a positive 33-bit signed integer. Negative numbers indicate up to one
|
||||
@@ -2336,6 +2713,54 @@ func DecodeBlockType(types []FunctionType, r *bytes.Reader, enabledFeatures api.
|
||||
ret = blockType_v_funcref
|
||||
case -17: // 0x6f in original byte = externref
|
||||
ret = blockType_v_externref
|
||||
case -23: // 0x69 in original byte = exnref
|
||||
ret = blockType_v_exnref
|
||||
case -29: // 0x63 = ref null (nullable)
|
||||
ht, htNum, err := leb128.DecodeInt33AsInt64(r)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("read ref heap type in block: %w", err)
|
||||
}
|
||||
num += htNum
|
||||
switch ht {
|
||||
case HeapTypeExn:
|
||||
ret = blockType_v_exnref
|
||||
case HeapTypeFunc:
|
||||
ret = blockType_v_funcref
|
||||
case HeapTypeExtern:
|
||||
ret = blockType_v_externref
|
||||
default:
|
||||
if ht < 0 {
|
||||
return nil, 0, fmt.Errorf("unknown abstract heap type in block: %d", ht)
|
||||
}
|
||||
if int64(len(types)) <= ht {
|
||||
return nil, 0, fmt.Errorf("unknown type")
|
||||
}
|
||||
vt := ValueTypeConcreteRef(uint32(ht), true)
|
||||
ret = &FunctionType{Results: []ValueType{vt}, ResultNumInUint64: 1}
|
||||
}
|
||||
case -28: // 0x64 = ref (non-nullable)
|
||||
ht, htNum, err := leb128.DecodeInt33AsInt64(r)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("read ref heap type in block: %w", err)
|
||||
}
|
||||
num += htNum
|
||||
switch ht {
|
||||
case HeapTypeExn:
|
||||
ret = &FunctionType{Results: []ValueType{ValueTypeExnref.AsNonNullable()}, ResultNumInUint64: 1}
|
||||
case HeapTypeFunc:
|
||||
ret = &FunctionType{Results: []ValueType{ValueTypeFuncref.AsNonNullable()}, ResultNumInUint64: 1}
|
||||
case HeapTypeExtern:
|
||||
ret = &FunctionType{Results: []ValueType{ValueTypeExternref.AsNonNullable()}, ResultNumInUint64: 1}
|
||||
default:
|
||||
if ht < 0 {
|
||||
return nil, 0, fmt.Errorf("unknown abstract heap type in block: %d", ht)
|
||||
}
|
||||
if int64(len(types)) <= ht {
|
||||
return nil, 0, fmt.Errorf("unknown type")
|
||||
}
|
||||
vt := ValueTypeConcreteRef(uint32(ht), false)
|
||||
ret = &FunctionType{Results: []ValueType{vt}, ResultNumInUint64: 1}
|
||||
}
|
||||
default:
|
||||
if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
|
||||
return nil, num, fmt.Errorf("block with function type return invalid as %v", err)
|
||||
@@ -2358,6 +2783,7 @@ var (
|
||||
blockType_v_v128 = &FunctionType{Results: []ValueType{ValueTypeV128}, ResultNumInUint64: 2}
|
||||
blockType_v_funcref = &FunctionType{Results: []ValueType{ValueTypeFuncref}, ResultNumInUint64: 1}
|
||||
blockType_v_externref = &FunctionType{Results: []ValueType{ValueTypeExternref}, ResultNumInUint64: 1}
|
||||
blockType_v_exnref = &FunctionType{Results: []ValueType{ValueTypeExnref}, ResultNumInUint64: 1}
|
||||
)
|
||||
|
||||
// SplitCallStack returns the input stack resliced to the count of params and
|
||||
|
||||
+26
-4
@@ -168,8 +168,8 @@ func (f *FunctionDefinition) GoFunction() interface{} {
|
||||
}
|
||||
|
||||
// ParamTypes implements api.FunctionDefinition ParamTypes.
|
||||
func (f *FunctionDefinition) ParamTypes() []ValueType {
|
||||
return f.Functype.Params
|
||||
func (f *FunctionDefinition) ParamTypes() []api.ValueType {
|
||||
return ToApiValueType(f.Functype.Params)
|
||||
}
|
||||
|
||||
// ParamNames implements the same method as documented on api.FunctionDefinition.
|
||||
@@ -178,11 +178,33 @@ func (f *FunctionDefinition) ParamNames() []string {
|
||||
}
|
||||
|
||||
// ResultTypes implements api.FunctionDefinition ResultTypes.
|
||||
func (f *FunctionDefinition) ResultTypes() []ValueType {
|
||||
return f.Functype.Results
|
||||
func (f *FunctionDefinition) ResultTypes() []api.ValueType {
|
||||
return ToApiValueType(f.Functype.Results)
|
||||
}
|
||||
|
||||
// ResultNames implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) ResultNames() []string {
|
||||
return f.resultNames
|
||||
}
|
||||
|
||||
func ToApiValueType(values []ValueType) []api.ValueType {
|
||||
if values == nil {
|
||||
return nil
|
||||
}
|
||||
apiValues := make([]api.ValueType, len(values))
|
||||
for i, v := range values {
|
||||
apiValues[i] = api.ValueType(v)
|
||||
}
|
||||
return apiValues
|
||||
}
|
||||
|
||||
func FromApiValueType(apiValues []api.ValueType) []ValueType {
|
||||
if apiValues == nil {
|
||||
return nil
|
||||
}
|
||||
values := make([]ValueType, len(apiValues))
|
||||
for i, v := range apiValues {
|
||||
values[i] = ValueType(v)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ type constantGlobal struct {
|
||||
|
||||
// Type implements api.Global.
|
||||
func (g constantGlobal) Type() api.ValueType {
|
||||
return g.g.Type.ValType
|
||||
return g.g.Type.ValType.Kind()
|
||||
}
|
||||
|
||||
// Get implements api.Global.
|
||||
@@ -35,7 +35,7 @@ type mutableGlobal struct {
|
||||
|
||||
// Type implements api.Global.
|
||||
func (g mutableGlobal) Type() api.ValueType {
|
||||
return g.g.Type.ValType
|
||||
return g.g.Type.ValType.Kind()
|
||||
}
|
||||
|
||||
// Get implements api.Global.
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
@@ -47,7 +47,7 @@ func (f *reflectGoModuleFunction) EqualTo(that interface{}) bool {
|
||||
return false
|
||||
} else {
|
||||
// TODO compare reflect pointers
|
||||
return bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
|
||||
return slices.Equal(f.params, f2.params) && slices.Equal(f.results, f2.results)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (f *reflectGoFunction) EqualTo(that interface{}) bool {
|
||||
} else {
|
||||
// TODO compare reflect pointers
|
||||
return f.pk == f2.pk &&
|
||||
bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
|
||||
slices.Equal(f.params, f2.params) && slices.Equal(f.results, f2.results)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+77
-6
@@ -20,6 +20,20 @@ const (
|
||||
// OpcodeElse brackets a sequence of instructions enclosed by an OpcodeIf. A branch instruction on a then label
|
||||
// breaks out to after the OpcodeEnd on the enclosing OpcodeIf.
|
||||
OpcodeElse Opcode = 0x05
|
||||
|
||||
// Exception handling instructions (toggled with CoreFeaturesExceptionHandling)
|
||||
|
||||
// OpcodeThrow throws an exception with the given tag.
|
||||
OpcodeThrow Opcode = 0x08
|
||||
// OpcodeThrowRef re-throws the exception referenced by an exnref value.
|
||||
OpcodeThrowRef Opcode = 0x0a
|
||||
|
||||
// Legacy exception handling opcodes (not supported; use wasm-opt --translate-to-exnref)
|
||||
|
||||
OpcodeLegacyTry Opcode = 0x06
|
||||
OpcodeLegacyCatch Opcode = 0x07
|
||||
OpcodeLegacyRethrow Opcode = 0x09
|
||||
|
||||
// OpcodeEnd terminates a control instruction OpcodeBlock, OpcodeLoop or OpcodeIf.
|
||||
OpcodeEnd Opcode = 0x0b
|
||||
|
||||
@@ -48,6 +62,16 @@ const (
|
||||
OpcodeSelect Opcode = 0x1b
|
||||
OpcodeTypedSelect Opcode = 0x1c
|
||||
|
||||
// Legacy exception handling opcodes (not supported; use wasm-opt --translate-to-exnref)
|
||||
|
||||
OpcodeLegacyDelegate Opcode = 0x18
|
||||
OpcodeLegacyCatchAll Opcode = 0x19
|
||||
|
||||
// Exception handling instructions (toggled with CoreFeaturesExceptionHandling)
|
||||
|
||||
// OpcodeTryTable brackets a sequence of instructions with catch clauses for exception handling.
|
||||
OpcodeTryTable Opcode = 0x1f
|
||||
|
||||
// variable instructions
|
||||
|
||||
OpcodeLocalGet Opcode = 0x20
|
||||
@@ -250,6 +274,18 @@ const (
|
||||
// Currently, this is only supported in the constant expression in element segments.
|
||||
OpcodeRefFunc = 0xd2
|
||||
|
||||
// Typed function references instructions (toggled with CoreFeaturesTypedFunctionReferences)
|
||||
|
||||
// OpcodeRefAsNonNull pops a nullable reference and traps if null, otherwise pushes
|
||||
// the non-nullable version.
|
||||
OpcodeRefAsNonNull Opcode = 0xd4
|
||||
// OpcodeBrOnNull pops a reference and branches if null, otherwise pushes
|
||||
// the non-nullable reference.
|
||||
OpcodeBrOnNull Opcode = 0xd5
|
||||
// OpcodeBrOnNonNull pops a reference and branches if non-null (carrying the ref),
|
||||
// otherwise falls through.
|
||||
OpcodeBrOnNonNull Opcode = 0xd6
|
||||
|
||||
// Below are toggled with CoreFeatureSignExtensionOps
|
||||
|
||||
// OpcodeI32Extend8S extends a signed 8-bit integer to a 32-bit integer.
|
||||
@@ -787,6 +823,14 @@ const (
|
||||
OpcodeTailCallReturnCallIndirect OpcodeTailCall = 0x13
|
||||
)
|
||||
|
||||
// OpcodeCallRef and OpcodeReturnCallRef are typed function references instructions.
|
||||
//
|
||||
// These opcodes are toggled with CoreFeaturesTypedFunctionReferences.
|
||||
const (
|
||||
OpcodeCallRef Opcode = 0x14
|
||||
OpcodeReturnCallRef Opcode = 0x15
|
||||
)
|
||||
|
||||
const (
|
||||
OpcodeUnreachableName = "unreachable"
|
||||
OpcodeNopName = "nop"
|
||||
@@ -962,9 +1006,14 @@ const (
|
||||
OpcodeF32ReinterpretI32Name = "f32.reinterpret_i32"
|
||||
OpcodeF64ReinterpretI64Name = "f64.reinterpret_i64"
|
||||
|
||||
OpcodeRefNullName = "ref.null"
|
||||
OpcodeRefIsNullName = "ref.is_null"
|
||||
OpcodeRefFuncName = "ref.func"
|
||||
OpcodeRefNullName = "ref.null"
|
||||
OpcodeRefIsNullName = "ref.is_null"
|
||||
OpcodeRefFuncName = "ref.func"
|
||||
OpcodeRefAsNonNullName = "ref.as_non_null"
|
||||
OpcodeBrOnNullName = "br_on_null"
|
||||
OpcodeBrOnNonNullName = "br_on_non_null"
|
||||
OpcodeCallRefName = "call_ref"
|
||||
OpcodeReturnCallRefName = "return_call_ref"
|
||||
|
||||
OpcodeTableGetName = "table.get"
|
||||
OpcodeTableSetName = "table.set"
|
||||
@@ -989,6 +1038,8 @@ var instructionNames = [256]string{
|
||||
OpcodeLoop: OpcodeLoopName,
|
||||
OpcodeIf: OpcodeIfName,
|
||||
OpcodeElse: OpcodeElseName,
|
||||
OpcodeThrow: OpcodeThrowName,
|
||||
OpcodeThrowRef: OpcodeThrowRefName,
|
||||
OpcodeEnd: OpcodeEndName,
|
||||
OpcodeBr: OpcodeBrName,
|
||||
OpcodeBrIf: OpcodeBrIfName,
|
||||
@@ -996,6 +1047,7 @@ var instructionNames = [256]string{
|
||||
OpcodeReturn: OpcodeReturnName,
|
||||
OpcodeCall: OpcodeCallName,
|
||||
OpcodeCallIndirect: OpcodeCallIndirectName,
|
||||
OpcodeTryTable: OpcodeTryTableName,
|
||||
OpcodeDrop: OpcodeDropName,
|
||||
OpcodeSelect: OpcodeSelectName,
|
||||
OpcodeTypedSelect: OpcodeTypedSelectName,
|
||||
@@ -1157,9 +1209,14 @@ var instructionNames = [256]string{
|
||||
OpcodeF32ReinterpretI32: OpcodeF32ReinterpretI32Name,
|
||||
OpcodeF64ReinterpretI64: OpcodeF64ReinterpretI64Name,
|
||||
|
||||
OpcodeRefNull: OpcodeRefNullName,
|
||||
OpcodeRefIsNull: OpcodeRefIsNullName,
|
||||
OpcodeRefFunc: OpcodeRefFuncName,
|
||||
OpcodeRefNull: OpcodeRefNullName,
|
||||
OpcodeRefIsNull: OpcodeRefIsNullName,
|
||||
OpcodeRefFunc: OpcodeRefFuncName,
|
||||
OpcodeRefAsNonNull: OpcodeRefAsNonNullName,
|
||||
OpcodeBrOnNull: OpcodeBrOnNullName,
|
||||
OpcodeBrOnNonNull: OpcodeBrOnNonNullName,
|
||||
OpcodeCallRef: OpcodeCallRefName,
|
||||
OpcodeReturnCallRef: OpcodeReturnCallRefName,
|
||||
|
||||
OpcodeTableGet: OpcodeTableGetName,
|
||||
OpcodeTableSet: OpcodeTableSetName,
|
||||
@@ -1889,3 +1946,17 @@ var tailCallInstructionName = map[OpcodeTailCall]string{
|
||||
func TailCallInstructionName(oc OpcodeTailCall) (ret string) {
|
||||
return tailCallInstructionName[oc]
|
||||
}
|
||||
|
||||
// Catch clause kinds used within try_table encoding.
|
||||
const (
|
||||
CatchKindCatch byte = 0x00
|
||||
CatchKindCatchRef byte = 0x01
|
||||
CatchKindCatchAll byte = 0x02
|
||||
CatchKindCatchAllRef byte = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
OpcodeThrowName = "throw"
|
||||
OpcodeThrowRefName = "throw_ref"
|
||||
OpcodeTryTableName = "try_table"
|
||||
)
|
||||
|
||||
+405
-124
@@ -6,15 +6,13 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/ieee754"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
)
|
||||
|
||||
@@ -97,6 +95,19 @@ type Module struct {
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0
|
||||
MemorySection *Memory
|
||||
|
||||
// TagSection contains each tag defined in this module for exception handling.
|
||||
//
|
||||
// Tag indexes are offset by any imported tags because the tag index begins with imports, followed by
|
||||
// ones defined in this module.
|
||||
//
|
||||
// Note: In the Binary Format, this is SectionIDTag.
|
||||
//
|
||||
// See https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md
|
||||
TagSection []Tag
|
||||
|
||||
// ImportTagCount is the cached count of imported tags set during decoding.
|
||||
ImportTagCount Index
|
||||
|
||||
// GlobalSection contains each global defined in this module.
|
||||
//
|
||||
// Global indexes are offset by any imported globals because the global index begins with imports, followed by
|
||||
@@ -227,6 +238,16 @@ func boolToByte(b bool) (ret byte) {
|
||||
|
||||
// typeOfFunction returns the wasm.FunctionType for the given function space index or nil.
|
||||
func (m *Module) typeOfFunction(funcIdx Index) *FunctionType {
|
||||
typeIdx, ok := m.typeIndexOfFunction(funcIdx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &m.TypeSection[typeIdx]
|
||||
}
|
||||
|
||||
// typeIndexOfFunction returns the type section index for the given function
|
||||
// space index, or false if the index is out of range.
|
||||
func (m *Module) typeIndexOfFunction(funcIdx Index) (Index, bool) {
|
||||
typeSectionLength, importedFunctionCount := uint32(len(m.TypeSection)), m.ImportFunctionCount
|
||||
if funcIdx < importedFunctionCount {
|
||||
// Imports are not exclusively functions. This is the current function index in the loop.
|
||||
@@ -238,9 +259,9 @@ func (m *Module) typeOfFunction(funcIdx Index) *FunctionType {
|
||||
}
|
||||
if funcIdx == cur {
|
||||
if imp.DescFunc >= typeSectionLength {
|
||||
return nil
|
||||
return 0, false
|
||||
}
|
||||
return &m.TypeSection[imp.DescFunc]
|
||||
return imp.DescFunc, true
|
||||
}
|
||||
cur++
|
||||
}
|
||||
@@ -248,13 +269,13 @@ func (m *Module) typeOfFunction(funcIdx Index) *FunctionType {
|
||||
|
||||
funcSectionIdx := funcIdx - m.ImportFunctionCount
|
||||
if funcSectionIdx >= uint32(len(m.FunctionSection)) {
|
||||
return nil
|
||||
return 0, false
|
||||
}
|
||||
typeIdx := m.FunctionSection[funcSectionIdx]
|
||||
if typeIdx >= typeSectionLength {
|
||||
return nil
|
||||
return 0, false
|
||||
}
|
||||
return &m.TypeSection[typeIdx]
|
||||
return typeIdx, true
|
||||
}
|
||||
|
||||
func (m *Module) Validate(enabledFeatures api.CoreFeatures) error {
|
||||
@@ -263,15 +284,23 @@ func (m *Module) Validate(enabledFeatures api.CoreFeatures) error {
|
||||
tp.CacheNumInUint64()
|
||||
}
|
||||
|
||||
if err := m.validateConcreteRefTypes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.validateStartSection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
functions, globals, memory, tables, err := m.AllDeclarations()
|
||||
functions, globals, memory, tables, tags, err := m.AllDeclarations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.validateTableInitExprs(globals, uint32(len(functions))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.validateImports(enabledFeatures); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,12 +313,12 @@ func (m *Module) Validate(enabledFeatures api.CoreFeatures) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.validateExports(enabledFeatures, functions, globals, memory, tables); err != nil {
|
||||
if err = m.validateExports(enabledFeatures, functions, globals, memory, tables, tags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.CodeSection != nil {
|
||||
if err = m.validateFunctions(enabledFeatures, functions, globals, memory, tables, MaximumFunctionIndex); err != nil {
|
||||
if err = m.validateFunctions(enabledFeatures, functions, globals, memory, tables, tags, MaximumFunctionIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
} // No need to validate host functions as NewHostModule validates
|
||||
@@ -301,6 +330,65 @@ func (m *Module) Validate(enabledFeatures api.CoreFeatures) error {
|
||||
if err = m.validateDataCountSection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.validateTagSection(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) validateConcreteRefTypes() error {
|
||||
numTypes := uint32(len(m.TypeSection))
|
||||
for i, g := range m.GlobalSection {
|
||||
if vt := g.Type.ValType; vt.IsConcreteRef() && vt.TypeIndex() >= numTypes {
|
||||
return fmt.Errorf("unknown type %d in global[%d]", vt.TypeIndex(), i)
|
||||
}
|
||||
}
|
||||
for i, t := range m.TableSection {
|
||||
if vt := t.Type; vt.IsConcreteRef() && vt.TypeIndex() >= numTypes {
|
||||
return fmt.Errorf("unknown type %d in table[%d]", vt.TypeIndex(), i)
|
||||
}
|
||||
}
|
||||
for i, c := range m.CodeSection {
|
||||
for j, lt := range c.LocalTypes {
|
||||
if lt.IsConcreteRef() && lt.TypeIndex() >= numTypes {
|
||||
return fmt.Errorf("unknown type %d in func[%d].local[%d]", lt.TypeIndex(), i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, e := range m.ElementSection {
|
||||
if vt := e.Type; vt.IsConcreteRef() && vt.TypeIndex() >= numTypes {
|
||||
return fmt.Errorf("unknown type %d in element[%d]", vt.TypeIndex(), i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) validateTableInitExprs(globals []GlobalType, numFuncs uint32) error {
|
||||
importedGlobals := globals[:m.ImportGlobalCount]
|
||||
for i, t := range m.TableSection {
|
||||
if !t.Type.IsNullable() && t.InitExpr == nil {
|
||||
return fmt.Errorf("type mismatch: non-nullable table[%d] requires an init expression", i)
|
||||
}
|
||||
if t.InitExpr != nil {
|
||||
if err := m.validateConstExpression(importedGlobals, numFuncs, t.InitExpr, t.Type); err != nil {
|
||||
return fmt.Errorf("table[%d] init: %w", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) validateTagSection() error {
|
||||
for i, tag := range m.TagSection {
|
||||
if tag.Type >= uint32(len(m.TypeSection)) {
|
||||
return fmt.Errorf("tag[%d] type index out of range", i)
|
||||
}
|
||||
ft := &m.TypeSection[tag.Type]
|
||||
if len(ft.Results) > 0 {
|
||||
return fmt.Errorf("tag[%d] type must have empty results, got %v", i, ft.Results)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -330,14 +418,14 @@ func (m *Module) validateGlobals(globals []GlobalType, numFuncts, maxGlobals uin
|
||||
importedGlobals := globals[:m.ImportGlobalCount]
|
||||
for i := range m.GlobalSection {
|
||||
g := &m.GlobalSection[i]
|
||||
if err := validateConstExpression(importedGlobals, numFuncts, &g.Init, g.Type.ValType); err != nil {
|
||||
if err := m.validateConstExpression(importedGlobals, numFuncts, &g.Init, g.Type.ValType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, maximumFunctionIndex uint32) error {
|
||||
func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, tags []Index, maximumFunctionIndex uint32) error {
|
||||
if uint32(len(functions)) > maximumFunctionIndex {
|
||||
return fmt.Errorf("too many functions (%d) in a module", len(functions))
|
||||
}
|
||||
@@ -353,7 +441,7 @@ func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions [
|
||||
return fmt.Errorf("code count (%d) != function count (%d)", codeCount, functionCount)
|
||||
}
|
||||
|
||||
declaredFuncIndexes, err := m.declaredFunctionIndexes()
|
||||
declaredFuncIndexes, err := m.declaredFunctionIndexes(enabledFeatures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -371,7 +459,7 @@ func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions [
|
||||
if c.GoFunc != nil {
|
||||
continue
|
||||
}
|
||||
if err = m.validateFunction(vs, enabledFeatures, Index(idx), functions, globals, memory, tables, declaredFuncIndexes, br); err != nil {
|
||||
if err = m.validateFunction(vs, enabledFeatures, Index(idx), functions, globals, memory, tables, tags, declaredFuncIndexes, br); err != nil {
|
||||
return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err)
|
||||
}
|
||||
}
|
||||
@@ -397,7 +485,7 @@ func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions [
|
||||
//
|
||||
// See https://github.com/WebAssembly/reference-types/issues/31
|
||||
// See https://github.com/WebAssembly/reference-types/issues/76
|
||||
func (m *Module) declaredFunctionIndexes() (ret map[Index]struct{}, err error) {
|
||||
func (m *Module) declaredFunctionIndexes(enabledFeatures api.CoreFeatures) (ret map[Index]struct{}, err error) {
|
||||
ret = map[uint32]struct{}{}
|
||||
|
||||
for i := range m.ExportSection {
|
||||
@@ -409,23 +497,39 @@ func (m *Module) declaredFunctionIndexes() (ret map[Index]struct{}, err error) {
|
||||
|
||||
for i := range m.GlobalSection {
|
||||
g := &m.GlobalSection[i]
|
||||
if g.Init.Opcode == OpcodeRefFunc {
|
||||
var index uint32
|
||||
index, _, err = leb128.LoadUint32(g.Init.Data)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s[%d] failed to initialize: %w", SectionIDName(SectionIDGlobal), i, err)
|
||||
return
|
||||
}
|
||||
ret[index] = struct{}{}
|
||||
|
||||
_, _, initErr := evaluateConstExpr(
|
||||
&g.Init,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
vt, err := m.resolveConstExprGlobalType(enabledFeatures, SectionIDGlobal, Index(i), globalIndex)
|
||||
return vt, 0, 0, err
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
ret[funcIndex] = struct{}{}
|
||||
return 0, nil
|
||||
},
|
||||
)
|
||||
|
||||
if initErr != nil {
|
||||
err = fmt.Errorf("%s[%d] failed to initialize: %w", SectionIDName(SectionIDGlobal), i, initErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for i := range m.ElementSection {
|
||||
elem := &m.ElementSection[i]
|
||||
for _, index := range elem.Init {
|
||||
if index != ElementInitNullReference {
|
||||
ret[index] = struct{}{}
|
||||
}
|
||||
for _, initExpr := range elem.Init {
|
||||
_, _, _ = evaluateConstExpr(
|
||||
&initExpr,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
vt, err := m.resolveConstExprGlobalType(enabledFeatures, SectionIDElement, Index(i), globalIndex)
|
||||
return vt, 0, 0, err
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
ret[funcIndex] = struct{}{}
|
||||
return 0, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -467,7 +571,7 @@ func (m *Module) validateMemory(memory *Memory, globals []GlobalType, _ api.Core
|
||||
for i := range m.DataSection {
|
||||
d := &m.DataSection[i]
|
||||
if !d.IsPassive() {
|
||||
if err := validateConstExpression(importedGlobals, 0, &d.OffsetExpression, ValueTypeI32); err != nil {
|
||||
if err := m.validateConstExpression(importedGlobals, 0, &d.OffsetExpression, ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("calculate offset: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -493,12 +597,19 @@ func (m *Module) validateImports(enabledFeatures api.CoreFeatures) error {
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil {
|
||||
return fmt.Errorf("invalid import[%q.%q] global: %w", imp.Module, imp.Name, err)
|
||||
}
|
||||
case ExternTypeTag:
|
||||
if int(imp.DescTag) >= len(m.TypeSection) {
|
||||
return fmt.Errorf("invalid import[%q.%q] tag: type index out of range", imp.Module, imp.Name)
|
||||
}
|
||||
if len(m.TypeSection[imp.DescTag].Results) > 0 {
|
||||
return fmt.Errorf("invalid import[%q.%q] tag: tag types must have no results", imp.Module, imp.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) validateExports(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table) error {
|
||||
func (m *Module) validateExports(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, tags []Index) error {
|
||||
for i := range m.ExportSection {
|
||||
exp := &m.ExportSection[i]
|
||||
index := exp.Index
|
||||
@@ -525,78 +636,43 @@ func (m *Module) validateExports(enabledFeatures api.CoreFeatures, functions []I
|
||||
if index >= uint32(len(tables)) {
|
||||
return fmt.Errorf("table for export[%q] out of range", exp.Name)
|
||||
}
|
||||
case ExternTypeTag:
|
||||
if index >= uint32(len(tags)) {
|
||||
return fmt.Errorf("tag for export[%q] out of range", exp.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConstExpression(globals []GlobalType, numFuncs uint32, expr *ConstantExpression, expectedType ValueType) (err error) {
|
||||
var actualType ValueType
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
_, _, err = leb128.LoadInt32(expr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read i32: %w", err)
|
||||
}
|
||||
actualType = ValueTypeI32
|
||||
case OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
_, _, err = leb128.LoadInt64(expr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read i64: %w", err)
|
||||
}
|
||||
actualType = ValueTypeI64
|
||||
case OpcodeF32Const:
|
||||
_, err = ieee754.DecodeFloat32(expr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read f32: %w", err)
|
||||
}
|
||||
actualType = ValueTypeF32
|
||||
case OpcodeF64Const:
|
||||
_, err = ieee754.DecodeFloat64(expr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read f64: %w", err)
|
||||
}
|
||||
actualType = ValueTypeF64
|
||||
case OpcodeGlobalGet:
|
||||
id, _, err := leb128.LoadUint32(expr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read index of global: %w", err)
|
||||
}
|
||||
if uint32(len(globals)) <= id {
|
||||
return fmt.Errorf("global index out of range")
|
||||
}
|
||||
actualType = globals[id].ValType
|
||||
case OpcodeRefNull:
|
||||
if len(expr.Data) == 0 {
|
||||
return fmt.Errorf("read reference type for ref.null: %w", io.ErrShortBuffer)
|
||||
}
|
||||
reftype := expr.Data[0]
|
||||
if reftype != RefTypeFuncref && reftype != RefTypeExternref {
|
||||
return fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
|
||||
}
|
||||
actualType = reftype
|
||||
case OpcodeRefFunc:
|
||||
index, _, err := leb128.LoadUint32(expr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read i32: %w", err)
|
||||
} else if index >= numFuncs {
|
||||
return fmt.Errorf("ref.func index out of range [%d] with length %d", index, numFuncs-1)
|
||||
}
|
||||
actualType = ValueTypeFuncref
|
||||
case OpcodeVecV128Const:
|
||||
if len(expr.Data) != 16 {
|
||||
return fmt.Errorf("%s needs 16 bytes but was %d bytes", OpcodeVecV128ConstName, len(expr.Data))
|
||||
}
|
||||
actualType = ValueTypeV128
|
||||
default:
|
||||
return fmt.Errorf("invalid opcode for const expression: 0x%x", expr.Opcode)
|
||||
func (m *Module) validateConstExpression(globals []GlobalType, numFuncs uint32, expr *ConstantExpression, expectedType ValueType) (err error) {
|
||||
var lastRefFuncIdx Index
|
||||
_, typ, err := evaluateConstExpr(
|
||||
expr,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
if uint32(len(globals)) <= globalIndex {
|
||||
return 0, 0, 0, fmt.Errorf("global index out of range")
|
||||
}
|
||||
return globals[globalIndex].ValType, 0, 0, nil
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
if funcIndex >= numFuncs {
|
||||
return 0, fmt.Errorf("ref.func index out of range [%d] with length %d", funcIndex, numFuncs-1)
|
||||
}
|
||||
lastRefFuncIdx = funcIndex
|
||||
return 0, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if actualType != expectedType {
|
||||
return fmt.Errorf("const expression type mismatch expected %s but got %s",
|
||||
ValueTypeName(expectedType), ValueTypeName(actualType))
|
||||
if typ == ValueTypeFuncref {
|
||||
if typeIndex, ok := m.typeIndexOfFunction(lastRefFuncIdx); ok {
|
||||
typ = ValueTypeConcreteRef(typeIndex, false)
|
||||
}
|
||||
}
|
||||
if !isRefSubtypeOf(typ, expectedType) {
|
||||
return fmt.Errorf("const expression type mismatch expected %s but got %s", ValueTypeName(expectedType), ValueTypeName(typ))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -609,6 +685,16 @@ func (m *Module) validateDataCountSection() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) buildTags(module *Module) {
|
||||
for i := range module.TagSection {
|
||||
tag := &module.TagSection[i]
|
||||
t := &TagInstance{
|
||||
Type: &module.TypeSection[tag.Type],
|
||||
}
|
||||
m.Tags[i+int(module.ImportTagCount)] = t
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcIndex Index) Reference) {
|
||||
importedGlobals := m.Globals[:module.ImportGlobalCount]
|
||||
|
||||
@@ -627,6 +713,48 @@ func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcI
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) resolveConstExprGlobalType(enabledFeatures api.CoreFeatures, sectionID SectionID, sectionIdx Index, idx Index) (ValueType, error) {
|
||||
if idx < m.ImportGlobalCount {
|
||||
// Imports are not exclusively globals. This is the current global index in the loop.
|
||||
cur := uint32(0)
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
if imp.Type != ExternTypeGlobal {
|
||||
continue
|
||||
}
|
||||
if idx == cur {
|
||||
return imp.DescGlobal.ValType, nil
|
||||
}
|
||||
cur++
|
||||
}
|
||||
|
||||
// should not happen as idx < ImportGlobalCount
|
||||
return 0, fmt.Errorf("index %d not found in imported globals", idx)
|
||||
}
|
||||
|
||||
// NOTE: in the <= 2.0 spec, global.get in a constant expression can only refer to imported globals.
|
||||
// In version 3.0, this restriction is removed, and all globals prior to the current one are allowed.
|
||||
// To avoid implementing too many flags, this relaxation is gated behind the CoreFeaturesExtendedConst flag,
|
||||
// which includes other related extensions in constant expressions.
|
||||
if !enabledFeatures.IsEnabled(experimental.CoreFeaturesExtendedConst) {
|
||||
return 0, fmt.Errorf("%s[%d] (global.get %d): out of range of imported globals", SectionIDName(sectionID), sectionIdx, idx)
|
||||
}
|
||||
|
||||
idx -= uint32(m.ImportGlobalCount)
|
||||
|
||||
// Check that the given global has been initialized.
|
||||
if sectionIdx == Index(SectionIDGlobal) && idx >= sectionIdx {
|
||||
return 0, fmt.Errorf("%s[%d] global %d out of range of initialized globals", SectionIDName(sectionID), sectionIdx, idx)
|
||||
}
|
||||
|
||||
// Bounds check:
|
||||
if idx >= uint32(len(m.GlobalSection)) {
|
||||
return 0, fmt.Errorf("%s[%d] (global.get %d): out of range of initialized globals", SectionIDName(sectionID), sectionIdx, idx)
|
||||
}
|
||||
|
||||
return m.GlobalSection[idx].Type.ValType, nil
|
||||
}
|
||||
|
||||
func paramNames(localNames IndirectNameMap, funcIdx uint32, paramLen int) []string {
|
||||
for i := range localNames {
|
||||
nm := &localNames[i]
|
||||
@@ -685,6 +813,13 @@ type FunctionType struct {
|
||||
|
||||
// ResultsNumInUint64 is the number of uint64 values requires to represent the Wasm result type.
|
||||
ResultNumInUint64 int
|
||||
|
||||
// RecGroupSize is the size of the rec group this type belongs to.
|
||||
// Standalone types (not in an explicit rec group) have RecGroupSize 1.
|
||||
RecGroupSize int
|
||||
|
||||
// RecGroupPosition is the 0-based position of this type within its rec group.
|
||||
RecGroupPosition int
|
||||
}
|
||||
|
||||
func (f *FunctionType) CacheNumInUint64() {
|
||||
@@ -709,7 +844,16 @@ func (f *FunctionType) CacheNumInUint64() {
|
||||
|
||||
// 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)
|
||||
return slices.Equal(f.Params, params) && slices.Equal(f.Results, results)
|
||||
}
|
||||
|
||||
// EqualsType returns true if the function types are structurally equal AND
|
||||
// belong to the same rec group position/size (GC proposal type identity).
|
||||
func (f *FunctionType) EqualsType(other *FunctionType) bool {
|
||||
if !f.EqualsSignature(other.Params, other.Results) {
|
||||
return false
|
||||
}
|
||||
return f.RecGroupSize == other.RecGroupSize && f.RecGroupPosition == other.RecGroupPosition
|
||||
}
|
||||
|
||||
// key gets or generates the key for Store.typeIDs. e.g. "i32_v" for one i32 parameter and no (void) result.
|
||||
@@ -732,6 +876,9 @@ func (f *FunctionType) key() string {
|
||||
if len(f.Results) == 0 {
|
||||
ret += "v"
|
||||
}
|
||||
if f.RecGroupSize > 1 {
|
||||
ret += fmt.Sprintf("|rec%d/%d", f.RecGroupPosition, f.RecGroupSize)
|
||||
}
|
||||
f.string = ret
|
||||
return ret
|
||||
}
|
||||
@@ -757,6 +904,8 @@ type Import struct {
|
||||
DescMem *Memory
|
||||
// DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal
|
||||
DescGlobal GlobalType
|
||||
// DescTag is the type index when Type equals ExternTypeTag
|
||||
DescTag Index
|
||||
// IndexPerType has the index of this import per ExternType.
|
||||
IndexPerType Index
|
||||
}
|
||||
@@ -793,6 +942,13 @@ func (m *Memory) Validate(memoryLimitPages uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tag represents an exception tag defined in the tag section.
|
||||
// The Type field is an index into the TypeSection; the referenced function type
|
||||
// must have empty results (tags carry parameters but produce no results).
|
||||
type Tag struct {
|
||||
Type Index
|
||||
}
|
||||
|
||||
type GlobalType struct {
|
||||
ValType ValueType
|
||||
Mutable bool
|
||||
@@ -803,11 +959,6 @@ type Global struct {
|
||||
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 {
|
||||
@@ -931,8 +1082,8 @@ type NameMapAssoc struct {
|
||||
NameMap NameMap
|
||||
}
|
||||
|
||||
// AllDeclarations returns all declarations for functions, globals, memories and tables in a module including imported ones.
|
||||
func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, memory *Memory, tables []Table, err error) {
|
||||
// AllDeclarations returns all declarations for functions, globals, memories, tables and tags in a module including imported ones.
|
||||
func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, memory *Memory, tables []Table, tags []Index, err error) {
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
switch imp.Type {
|
||||
@@ -944,6 +1095,8 @@ func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, mem
|
||||
memory = imp.DescMem
|
||||
case ExternTypeTable:
|
||||
tables = append(tables, imp.DescTable)
|
||||
case ExternTypeTag:
|
||||
tags = append(tags, imp.DescTag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -952,6 +1105,10 @@ func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, mem
|
||||
g := &m.GlobalSection[i]
|
||||
globals = append(globals, g.Type)
|
||||
}
|
||||
for i := range m.TagSection {
|
||||
t := &m.TagSection[i]
|
||||
tags = append(tags, t.Type)
|
||||
}
|
||||
if m.MemorySection != nil {
|
||||
if memory != nil { // shouldn't be possible due to Validate
|
||||
err = errors.New("at most one table allowed in module")
|
||||
@@ -993,6 +1150,11 @@ const (
|
||||
// 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
|
||||
|
||||
// SectionIDTag is for exception handling tags.
|
||||
//
|
||||
// See https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md
|
||||
SectionIDTag SectionID = 13
|
||||
)
|
||||
|
||||
// SectionIDName returns the canonical name of a module section.
|
||||
@@ -1025,37 +1187,151 @@ func SectionIDName(sectionID SectionID) string {
|
||||
return "data"
|
||||
case SectionIDDataCount:
|
||||
return "data_count"
|
||||
case SectionIDTag:
|
||||
return "tag"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ValueType is an alias of api.ValueType defined to simplify imports.
|
||||
type ValueType = api.ValueType
|
||||
// ValueType represents a WebAssembly value type as a uint64.
|
||||
//
|
||||
// Layout:
|
||||
//
|
||||
// bits 0-7: kind byte (backward-compatible with api.ValueType)
|
||||
// bits 8-15: flags (nullability, concrete ref)
|
||||
// bits 32-63: type index (for concrete refs like (ref $3))
|
||||
type ValueType uint64
|
||||
|
||||
const (
|
||||
ValueTypeI32 = api.ValueTypeI32
|
||||
ValueTypeI64 = api.ValueTypeI64
|
||||
ValueTypeF32 = api.ValueTypeF32
|
||||
ValueTypeF64 = api.ValueTypeF64
|
||||
// TODO: ValueTypeV128 is not exposed in the api pkg yet.
|
||||
ValueTypeV128 ValueType = 0x7b
|
||||
// TODO: ValueTypeFuncref is not exposed in the api pkg yet.
|
||||
ValueTypeFuncref ValueType = 0x70
|
||||
ValueTypeExternref = api.ValueTypeExternref
|
||||
flagNonNullable ValueType = 1 << 8
|
||||
flagConcreteRef ValueType = 1 << 9
|
||||
)
|
||||
|
||||
// ValueTypeName is an alias of api.ValueTypeName defined to simplify imports.
|
||||
func ValueTypeName(t ValueType) string {
|
||||
if t == ValueTypeFuncref {
|
||||
return "funcref"
|
||||
} else if t == ValueTypeV128 {
|
||||
return "v128"
|
||||
const (
|
||||
ValueTypeI32 ValueType = 0x7f
|
||||
ValueTypeI64 ValueType = 0x7e
|
||||
ValueTypeF32 ValueType = 0x7d
|
||||
ValueTypeF64 ValueType = 0x7c
|
||||
ValueTypeV128 ValueType = 0x7b
|
||||
ValueTypeFuncref ValueType = 0x70
|
||||
ValueTypeExternref ValueType = 0x6f
|
||||
ValueTypeExnref ValueType = 0x69
|
||||
)
|
||||
|
||||
// Kind returns the base type byte (bits 0-7).
|
||||
func (v ValueType) Kind() byte { return byte(v) }
|
||||
|
||||
// IsRef returns true if this is a reference type (including non-nullable variants).
|
||||
func (v ValueType) IsRef() bool {
|
||||
k := v.Kind()
|
||||
return k == ValueTypeFuncref.Kind() || k == ValueTypeExternref.Kind() || k == ValueTypeExnref.Kind() ||
|
||||
v&flagConcreteRef != 0
|
||||
}
|
||||
|
||||
// IsNullable returns true if this reference type is nullable. Must only be called on ref types.
|
||||
func (v ValueType) IsNullable() bool { return v.IsRef() && v&flagNonNullable == 0 }
|
||||
|
||||
// IsConcreteRef returns true if this is a concrete reference type with a type index.
|
||||
func (v ValueType) IsConcreteRef() bool { return v&flagConcreteRef != 0 }
|
||||
|
||||
// TypeIndex returns the concrete type index (bits 32-63).
|
||||
func (v ValueType) TypeIndex() uint32 { return uint32(v >> 32) }
|
||||
|
||||
// AsNonNullable returns a copy with the non-nullable flag set.
|
||||
func (v ValueType) AsNonNullable() ValueType { return v | flagNonNullable }
|
||||
|
||||
// AsNullable returns a copy with the non-nullable flag cleared.
|
||||
func (v ValueType) AsNullable() ValueType { return v &^ flagNonNullable }
|
||||
|
||||
// ValueTypeConcreteRef creates a concrete reference type with the given type index and nullability.
|
||||
func ValueTypeConcreteRef(typeIndex uint32, nullable bool) ValueType {
|
||||
v := ValueTypeFuncref | flagConcreteRef | ValueType(typeIndex)<<32
|
||||
if !nullable {
|
||||
v |= flagNonNullable
|
||||
}
|
||||
return api.ValueTypeName(t)
|
||||
return v
|
||||
}
|
||||
|
||||
const (
|
||||
// RefPrefixNullable is the binary encoding prefix for nullable reference types (ref null <heaptype>).
|
||||
RefPrefixNullable byte = 0x63
|
||||
// RefPrefixNonNullable is the binary encoding prefix for non-nullable reference types (ref <heaptype>).
|
||||
RefPrefixNonNullable byte = 0x64
|
||||
)
|
||||
|
||||
const (
|
||||
// HeapTypeFunc is the abstract heap type for function references.
|
||||
HeapTypeFunc int64 = -16
|
||||
// HeapTypeExtern is the abstract heap type for external references.
|
||||
HeapTypeExtern int64 = -17
|
||||
// HeapTypeExn is the abstract heap type for exception references.
|
||||
HeapTypeExn int64 = -23
|
||||
)
|
||||
|
||||
// ValueTypeName returns the name of a ValueType.
|
||||
func ValueTypeName(t ValueType) string {
|
||||
if t.IsConcreteRef() {
|
||||
if t.IsNullable() {
|
||||
return fmt.Sprintf("(ref null %d)", t.TypeIndex())
|
||||
}
|
||||
return fmt.Sprintf("(ref %d)", t.TypeIndex())
|
||||
}
|
||||
switch t.AsNullable() {
|
||||
case ValueTypeI32:
|
||||
return "i32"
|
||||
case ValueTypeI64:
|
||||
return "i64"
|
||||
case ValueTypeF32:
|
||||
return "f32"
|
||||
case ValueTypeF64:
|
||||
return "f64"
|
||||
case ValueTypeV128:
|
||||
return "v128"
|
||||
case ValueTypeFuncref:
|
||||
if !t.IsNullable() {
|
||||
return "(ref func)"
|
||||
}
|
||||
return "funcref"
|
||||
case ValueTypeExternref:
|
||||
if !t.IsNullable() {
|
||||
return "(ref extern)"
|
||||
}
|
||||
return "externref"
|
||||
case ValueTypeExnref:
|
||||
if !t.IsNullable() {
|
||||
return "(ref exn)"
|
||||
}
|
||||
return "exnref"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func isReferenceValueType(vt ValueType) bool {
|
||||
return vt == ValueTypeExternref || vt == ValueTypeFuncref
|
||||
return vt.IsRef()
|
||||
}
|
||||
|
||||
// isRefSubtypeOf returns true if actual is a subtype of (or equal to) expected.
|
||||
// Non-nullable is a subtype of nullable. Concrete function refs are subtypes of funcref.
|
||||
func isRefSubtypeOf(actual, expected ValueType) bool {
|
||||
if actual == expected {
|
||||
return true
|
||||
}
|
||||
// Non-nullable is subtype of nullable (same kind/index).
|
||||
if actual.AsNullable() == expected.AsNullable() && expected.IsNullable() {
|
||||
return true
|
||||
}
|
||||
// Concrete function ref is subtype of (abstract) funcref (nullable or non-nullable).
|
||||
if actual.IsConcreteRef() && expected.Kind() == ValueTypeFuncref.Kind() {
|
||||
if !actual.IsNullable() || expected.IsNullable() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// areRefTypesCompatible returns true if either type is a subtype of the other.
|
||||
func areRefTypesCompatible(a, b ValueType) bool {
|
||||
return isRefSubtypeOf(a, b) || isRefSubtypeOf(b, a)
|
||||
}
|
||||
|
||||
// ExternType is an alias of api.ExternType defined to simplify imports.
|
||||
@@ -1070,9 +1346,14 @@ const (
|
||||
ExternTypeMemoryName = api.ExternTypeMemoryName
|
||||
ExternTypeGlobal = api.ExternTypeGlobal
|
||||
ExternTypeGlobalName = api.ExternTypeGlobalName
|
||||
ExternTypeTag = ExternType(0x04)
|
||||
ExternTypeTagName = "tag"
|
||||
)
|
||||
|
||||
// ExternTypeName is an alias of api.ExternTypeName defined to simplify imports.
|
||||
func ExternTypeName(t ValueType) string {
|
||||
func ExternTypeName(t ExternType) string {
|
||||
if t == ExternTypeTag {
|
||||
return ExternTypeTagName
|
||||
}
|
||||
return api.ExternTypeName(t)
|
||||
}
|
||||
|
||||
+144
-97
@@ -2,7 +2,6 @@ package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
@@ -79,6 +77,7 @@ type (
|
||||
Globals []*GlobalInstance
|
||||
MemoryInstance *MemoryInstance
|
||||
Tables []*TableInstance
|
||||
Tags []*TagInstance
|
||||
|
||||
// Engine implements function calls for this module.
|
||||
Engine ModuleEngine
|
||||
@@ -151,6 +150,13 @@ type (
|
||||
Index Index
|
||||
}
|
||||
|
||||
// TagInstance represents an instantiated exception handling tag.
|
||||
// Tags are compared by identity (pointer equality), not structural type equality.
|
||||
TagInstance struct {
|
||||
// Type is the function type of this tag (params only; results must be empty).
|
||||
Type *FunctionType
|
||||
}
|
||||
|
||||
// FunctionTypeID is a uniquely assigned integer for a function type.
|
||||
// This is wazero specific runtime object and specific to a store,
|
||||
// and used at runtime to do type-checks on indirect function calls.
|
||||
@@ -174,21 +180,15 @@ func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID {
|
||||
func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) {
|
||||
m.ElementInstances = make([][]Reference, len(elements))
|
||||
for i, elm := range elements {
|
||||
if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive {
|
||||
if elm.Type.Kind() == RefTypeFuncref.Kind() && elm.Mode == ElementModePassive {
|
||||
// Only passive elements can be access as element instances.
|
||||
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
|
||||
inits := elm.Init
|
||||
inst := make([]Reference, len(inits))
|
||||
m.ElementInstances[i] = inst
|
||||
for j, idx := range inits {
|
||||
if index, ok := unwrapElementInitGlobalReference(idx); ok {
|
||||
global := m.Globals[index]
|
||||
inst[j] = Reference(global.Val)
|
||||
} else {
|
||||
if idx != ElementInitNullReference {
|
||||
inst[j] = m.Engine.FunctionInstanceReference(idx)
|
||||
}
|
||||
}
|
||||
initExprResults := evaluateConstExprInModuleInstance(&idx, m)
|
||||
inst[j] = Reference(initExprResults[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,17 +202,8 @@ func (m *ModuleInstance) applyElements(elems []ElementSegment) {
|
||||
len(elem.Init) == 0 {
|
||||
continue
|
||||
}
|
||||
var offset uint32
|
||||
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
|
||||
// Ignore error as it's already validated.
|
||||
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||||
global := m.Globals[globalIdx]
|
||||
offset = uint32(global.Val)
|
||||
} else {
|
||||
// Ignore error as it's already validated.
|
||||
o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
|
||||
offset = uint32(o)
|
||||
}
|
||||
offsetExprResults := evaluateConstExprInModuleInstance(&elem.OffsetExpr, m)
|
||||
offset := uint32(offsetExprResults[0])
|
||||
|
||||
table := m.Tables[elem.TableIndex]
|
||||
references := table.References
|
||||
@@ -233,18 +224,8 @@ func (m *ModuleInstance) applyElements(elems []ElementSegment) {
|
||||
}
|
||||
} else {
|
||||
for i, init := range elem.Init {
|
||||
if init == ElementInitNullReference {
|
||||
continue
|
||||
}
|
||||
|
||||
var ref Reference
|
||||
if index, ok := unwrapElementInitGlobalReference(init); ok {
|
||||
global := m.Globals[index]
|
||||
ref = Reference(global.Val)
|
||||
} else {
|
||||
ref = m.Engine.FunctionInstanceReference(index)
|
||||
}
|
||||
references[offset+uint32(i)] = ref
|
||||
initExprResults := evaluateConstExprInModuleInstance(&init, m)
|
||||
references[offset+uint32(i)] = Reference(initExprResults[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +237,26 @@ func (m *ModuleInstance) validateData(data []DataSegment) (err error) {
|
||||
for i := range data {
|
||||
d := &data[i]
|
||||
if !d.IsPassive() {
|
||||
offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression))
|
||||
results, typ, err := evaluateConstExpr(
|
||||
&d.OffsetExpression,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
if globalIndex >= Index(len(m.Globals)) {
|
||||
return 0, 0, 0, errors.New("global index out of range")
|
||||
}
|
||||
g := m.Globals[globalIndex]
|
||||
return g.Type.ValType, g.Val, g.ValHi, nil
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
return m.Engine.FunctionInstanceReference(funcIndex), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d] failed to evaluate offset expression: %w", SectionIDName(SectionIDData), i, err)
|
||||
}
|
||||
if typ != ValueTypeI32 {
|
||||
return fmt.Errorf("%s[%d] offset expression must return i32 but was %s", SectionIDName(SectionIDData), i, ValueTypeName(typ))
|
||||
}
|
||||
offset := int(results[0])
|
||||
ceil := offset + len(d.Init)
|
||||
if offset < 0 || ceil > len(m.MemoryInstance.Buffer) {
|
||||
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
|
||||
@@ -275,8 +275,9 @@ func (m *ModuleInstance) applyData(data []DataSegment) error {
|
||||
d := &data[i]
|
||||
m.DataInstances[i] = d.Init
|
||||
if !d.IsPassive() {
|
||||
offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression)
|
||||
if offset < 0 || int(offset)+len(d.Init) > len(m.MemoryInstance.Buffer) {
|
||||
offsetExprResults := evaluateConstExprInModuleInstance(&d.OffsetExpression, m)
|
||||
offset := int(offsetExprResults[0])
|
||||
if offset < 0 || offset+len(d.Init) > len(m.MemoryInstance.Buffer) {
|
||||
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
|
||||
}
|
||||
copy(m.MemoryInstance.Buffer[offset:], d.Init)
|
||||
@@ -348,6 +349,7 @@ func (s *Store) instantiate(
|
||||
|
||||
m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection))
|
||||
m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection))
|
||||
m.Tags = make([]*TagInstance, int(module.ImportTagCount)+len(module.TagSection))
|
||||
m.Engine, err = s.Engine.NewModuleEngine(module, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -367,6 +369,7 @@ func (s *Store) instantiate(
|
||||
allocator, _ := ctx.Value(expctxkeys.MemoryAllocatorKey{}).(experimental.MemoryAllocator)
|
||||
|
||||
m.buildGlobals(module, m.Engine.FunctionInstanceReference)
|
||||
m.buildTags(module)
|
||||
m.buildMemory(module, allocator)
|
||||
m.Exports = module.Exports
|
||||
for _, exp := range m.Exports {
|
||||
@@ -441,7 +444,15 @@ func (m *ModuleInstance) resolveImports(ctx context.Context, module *Module) (er
|
||||
expectedType := &module.TypeSection[i.DescFunc]
|
||||
src := importedModule.Source
|
||||
actual := src.typeOfFunction(imported.Index)
|
||||
if !actual.EqualsSignature(expectedType.Params, expectedType.Results) {
|
||||
matched := false
|
||||
if m.TypeIDs != nil && importedModule.TypeIDs != nil {
|
||||
// Use structural type IDs for comparison (handles concrete ref types across modules).
|
||||
actualTypeIdx, ok := src.typeIndexOfFunction(imported.Index)
|
||||
matched = ok && importedModule.TypeIDs[actualTypeIdx] == m.TypeIDs[i.DescFunc]
|
||||
} else {
|
||||
matched = actual.EqualsSignature(expectedType.Params, expectedType.Results)
|
||||
}
|
||||
if !matched {
|
||||
err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual))
|
||||
return
|
||||
}
|
||||
@@ -503,12 +514,22 @@ func (m *ModuleInstance) resolveImports(ctx context.Context, module *Module) (er
|
||||
return
|
||||
}
|
||||
|
||||
if expected.ValType != importedGlobal.Type.ValType {
|
||||
if expected.Mutable && expected.ValType != importedGlobal.Type.ValType ||
|
||||
!expected.Mutable && !isRefSubtypeOf(importedGlobal.Type.ValType, expected.ValType) {
|
||||
err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s",
|
||||
ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
|
||||
return
|
||||
}
|
||||
m.Globals[i.IndexPerType] = importedGlobal
|
||||
case ExternTypeTag:
|
||||
expected := &module.TypeSection[i.DescTag]
|
||||
importedTag := importedModule.Tags[imported.Index]
|
||||
if !importedTag.Type.EqualsType(expected) {
|
||||
err = errorInvalidImport(i, fmt.Errorf("tag type mismatch: %s != %s",
|
||||
expected, importedTag.Type))
|
||||
return
|
||||
}
|
||||
m.Tags[i.IndexPerType] = importedTag
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -531,67 +552,27 @@ func errorInvalidImport(i *Import, err error) error {
|
||||
return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err)
|
||||
}
|
||||
|
||||
// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
|
||||
// The validity of the expression is ensured when calling this function as this is only called
|
||||
// during instantiation phrase, and the validation happens in compilation (validateConstExpression).
|
||||
func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) {
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
ret, _, _ = leb128.LoadInt32(expr.Data)
|
||||
case OpcodeGlobalGet:
|
||||
id, _, _ := leb128.LoadUint32(expr.Data)
|
||||
g := importedGlobals[id]
|
||||
ret = int32(g.Val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initialize initializes the value of this global instance given the const expr and imported globals.
|
||||
// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr.
|
||||
//
|
||||
// Global initialization constant expression can only reference the imported globals.
|
||||
// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
|
||||
func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) {
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ := leb128.LoadInt32(expr.Data)
|
||||
g.Val = uint64(uint32(v))
|
||||
case OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ := leb128.LoadInt64(expr.Data)
|
||||
g.Val = uint64(v)
|
||||
case OpcodeF32Const:
|
||||
g.Val = uint64(binary.LittleEndian.Uint32(expr.Data))
|
||||
case OpcodeF64Const:
|
||||
g.Val = binary.LittleEndian.Uint64(expr.Data)
|
||||
case OpcodeGlobalGet:
|
||||
id, _, _ := leb128.LoadUint32(expr.Data)
|
||||
importedG := importedGlobals[id]
|
||||
switch importedG.Type.ValType {
|
||||
case ValueTypeI32:
|
||||
g.Val = uint64(uint32(importedG.Val))
|
||||
case ValueTypeI64:
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeF32:
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeF64:
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeV128:
|
||||
g.Val, g.ValHi = importedG.Val, importedG.ValHi
|
||||
case ValueTypeFuncref, ValueTypeExternref:
|
||||
g.Val = importedG.Val
|
||||
}
|
||||
case OpcodeRefNull:
|
||||
switch expr.Data[0] {
|
||||
case ValueTypeExternref, ValueTypeFuncref:
|
||||
g.Val = 0 // Reference types are opaque 64bit pointer at runtime.
|
||||
}
|
||||
case OpcodeRefFunc:
|
||||
v, _, _ := leb128.LoadUint32(expr.Data)
|
||||
g.Val = uint64(funcRefResolver(v))
|
||||
case OpcodeVecV128Const:
|
||||
g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])
|
||||
result, _, _ := evaluateConstExpr(
|
||||
expr,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
g := importedGlobals[globalIndex]
|
||||
return g.Type.ValType, g.Val, g.ValHi, nil
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
return funcRefResolver(funcIndex), nil
|
||||
},
|
||||
)
|
||||
switch len(result) {
|
||||
case 1:
|
||||
g.Val = result[0]
|
||||
case 2:
|
||||
g.Val, g.ValHi = result[0], result[1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,18 +609,84 @@ func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error)
|
||||
ret := make([]FunctionTypeID, len(ts))
|
||||
for i := range ts {
|
||||
t := &ts[i]
|
||||
inst, err := s.GetFunctionTypeID(t)
|
||||
key := structuralTypeKey(t, ret)
|
||||
id, err := s.getFunctionTypeIDByKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = inst
|
||||
ret[i] = id
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// structuralValueTypeName returns a string representation of a ValueType where
|
||||
// concrete ref type indices are replaced with their FunctionTypeID. This makes
|
||||
// the name independent of module-local type index numbering.
|
||||
func structuralValueTypeName(vt ValueType, typeIDs []FunctionTypeID) string {
|
||||
if vt.IsConcreteRef() {
|
||||
idx := vt.TypeIndex()
|
||||
if int(idx) < len(typeIDs) {
|
||||
if vt.IsNullable() {
|
||||
return fmt.Sprintf("(ref null tid=%d)", typeIDs[idx])
|
||||
}
|
||||
return fmt.Sprintf("(ref tid=%d)", typeIDs[idx])
|
||||
}
|
||||
}
|
||||
return ValueTypeName(vt)
|
||||
}
|
||||
|
||||
// structuralTypeKey returns a string key for a FunctionType that is stable
|
||||
// across modules. For signatures without concrete ref types it falls back to
|
||||
// FunctionType.key(). When concrete refs are present, local type indices are
|
||||
// replaced with their already-assigned FunctionTypeID so that two modules
|
||||
// defining structurally identical types at different indices produce the same
|
||||
// key and share a single FunctionTypeID.
|
||||
func structuralTypeKey(ft *FunctionType, typeIDs []FunctionTypeID) string {
|
||||
hasConcreteRef := false
|
||||
for _, p := range ft.Params {
|
||||
if p.IsConcreteRef() {
|
||||
hasConcreteRef = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasConcreteRef {
|
||||
for _, r := range ft.Results {
|
||||
if r.IsConcreteRef() {
|
||||
hasConcreteRef = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasConcreteRef {
|
||||
return ft.key()
|
||||
}
|
||||
var ret string
|
||||
for _, b := range ft.Params {
|
||||
ret += structuralValueTypeName(b, typeIDs)
|
||||
}
|
||||
if len(ft.Params) == 0 {
|
||||
ret += "v_"
|
||||
} else {
|
||||
ret += "_"
|
||||
}
|
||||
for _, b := range ft.Results {
|
||||
ret += structuralValueTypeName(b, typeIDs)
|
||||
}
|
||||
if len(ft.Results) == 0 {
|
||||
ret += "v"
|
||||
}
|
||||
if ft.RecGroupSize > 1 {
|
||||
ret += fmt.Sprintf("|rec%d/%d", ft.RecGroupPosition, ft.RecGroupSize)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
|
||||
return s.getFunctionTypeIDByKey(t.key())
|
||||
}
|
||||
|
||||
func (s *Store) getFunctionTypeIDByKey(key string) (FunctionTypeID, error) {
|
||||
s.mux.RLock()
|
||||
key := t.key()
|
||||
id, ok := s.typeIDs[key]
|
||||
s.mux.RUnlock()
|
||||
if !ok {
|
||||
|
||||
+86
-108
@@ -6,18 +6,18 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
)
|
||||
|
||||
// Table describes the limits of elements and its type in a table.
|
||||
type Table struct {
|
||||
Min uint32
|
||||
Max *uint32
|
||||
Type RefType
|
||||
Min uint32
|
||||
Max *uint32
|
||||
Type RefType
|
||||
InitExpr *ConstantExpression
|
||||
}
|
||||
|
||||
// RefType is either RefTypeFuncref or RefTypeExternref as of WebAssembly core 2.0.
|
||||
type RefType = byte
|
||||
// RefType is a reference type used for table elements.
|
||||
type RefType = ValueType
|
||||
|
||||
const (
|
||||
// RefTypeFuncref represents a reference to a function.
|
||||
@@ -66,8 +66,8 @@ type ElementSegment struct {
|
||||
|
||||
// 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
|
||||
// Init expressions are table elements where each expression evaluates to the function index by which the module initialize the table.
|
||||
Init []ConstantExpression
|
||||
|
||||
// Type holds the type of this element segment, which is the RefType in WebAssembly 2.0.
|
||||
Type RefType
|
||||
@@ -76,39 +76,6 @@ type ElementSegment struct {
|
||||
Mode ElementMode
|
||||
}
|
||||
|
||||
const (
|
||||
// ElementInitNullReference represents the null reference in ElementSegment's Init.
|
||||
// In Wasm spec, an init item represents either Function's Index or null reference,
|
||||
// and in wazero, we limit the maximum number of functions available in a module to
|
||||
// MaximumFunctionIndex. Therefore, it is safe to use 1 << 31 to represent the null
|
||||
// reference in Element segments.
|
||||
ElementInitNullReference Index = 1 << 31
|
||||
// elementInitImportedGlobalReferenceType represents an init item which is resolved via an imported global constexpr.
|
||||
// The actual function reference stored at Global is only known at instantiation-time, so we set this flag
|
||||
// to items of ElementSegment.Init at binary decoding, and unwrap this flag at instantiation to resolve the value.
|
||||
//
|
||||
// This might collide the init element resolved via ref.func instruction which is resolved with the func index at decoding,
|
||||
// but in practice, that is not allowed in wazero thanks to our limit MaximumFunctionIndex. Thus, it is safe to set this flag
|
||||
// in init element to indicate as such.
|
||||
elementInitImportedGlobalReferenceType Index = 1 << 30
|
||||
)
|
||||
|
||||
// unwrapElementInitGlobalReference takes an item of the init vector of an ElementSegment,
|
||||
// and returns the Global index if it is supposed to get generated from a global.
|
||||
// ok is true if the given init item is as such.
|
||||
func unwrapElementInitGlobalReference(init Index) (_ Index, ok bool) {
|
||||
if init&elementInitImportedGlobalReferenceType == elementInitImportedGlobalReferenceType {
|
||||
return init &^ elementInitImportedGlobalReferenceType, true
|
||||
}
|
||||
return init, false
|
||||
}
|
||||
|
||||
// WrapGlobalIndexAsElementInit wraps the given index as an init item which is resolved via an imported global value.
|
||||
// See the comments on elementInitImportedGlobalReferenceType for more details.
|
||||
func WrapGlobalIndexAsElementInit(init Index) Index {
|
||||
return init | elementInitImportedGlobalReferenceType
|
||||
}
|
||||
|
||||
// IsActive returns true if the element segment is "active" mode which requires the runtime to initialize table
|
||||
// with the contents in .Init field.
|
||||
func (e *ElementSegment) IsActive() bool {
|
||||
@@ -174,20 +141,39 @@ func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table,
|
||||
|
||||
// Any offset applied is to the element, not the function index: validate here if the funcidx is sound.
|
||||
for ei, init := range elem.Init {
|
||||
if init == ElementInitNullReference {
|
||||
continue
|
||||
_, initType, err := evaluateConstExpr(
|
||||
&init,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
if globalIndex >= Index(globalsCount) {
|
||||
return 0, 0, 0, fmt.Errorf("%s[%d].init[%d] global index %d out of range", SectionIDName(SectionIDElement), idx, ei, globalIndex)
|
||||
}
|
||||
vt, err := m.resolveConstExprGlobalType(enabledFeatures, SectionIDElement, idx, globalIndex)
|
||||
return vt, 0, 0, err
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
if funcIndex >= Index(funcCount) {
|
||||
return 0, fmt.Errorf("%s[%d].init[%d] func index %d out of range", SectionIDName(SectionIDElement), idx, ei, funcIndex)
|
||||
}
|
||||
return 0, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index, ok := unwrapElementInitGlobalReference(init)
|
||||
if ok {
|
||||
if index >= globalsCount {
|
||||
return fmt.Errorf("%s[%d].init[%d] global index %d out of range", SectionIDName(SectionIDElement), idx, ei, index)
|
||||
|
||||
switch elem.Type {
|
||||
case RefTypeFuncref:
|
||||
if initType != ValueTypeFuncref {
|
||||
return fmt.Errorf("%s[%d].init[%d] must be funcref but was %s", SectionIDName(SectionIDElement), idx, ei, ValueTypeName(initType))
|
||||
}
|
||||
} else {
|
||||
if elem.Type == RefTypeExternref {
|
||||
return fmt.Errorf("%s[%d].init[%d] must be ref.null but was %d", SectionIDName(SectionIDElement), idx, ei, init)
|
||||
case RefTypeExternref:
|
||||
if initType != ValueTypeExternref {
|
||||
return fmt.Errorf("%s[%d].init[%d] must be externref but was %s", SectionIDName(SectionIDElement), idx, ei, ValueTypeName(initType))
|
||||
}
|
||||
if index >= funcCount {
|
||||
return fmt.Errorf("%s[%d].init[%d] func index %d out of range", SectionIDName(SectionIDElement), idx, ei, index)
|
||||
default:
|
||||
if !isRefSubtypeOf(initType, elem.Type) && initType != ValueTypeFuncref {
|
||||
return fmt.Errorf("%s[%d].init[%d] must be %s but was %s",
|
||||
SectionIDName(SectionIDElement), idx, ei, ValueTypeName(elem.Type), ValueTypeName(initType))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,38 +184,49 @@ func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table,
|
||||
}
|
||||
|
||||
t := tables[elem.TableIndex]
|
||||
if t.Type != elem.Type {
|
||||
if !isRefSubtypeOf(elem.Type, t.Type) {
|
||||
return fmt.Errorf("element type mismatch: table has %s but element has %s",
|
||||
RefTypeName(t.Type), RefTypeName(elem.Type),
|
||||
)
|
||||
}
|
||||
|
||||
// global.get needs to be discovered during initialization
|
||||
oc := elem.OffsetExpr.Opcode
|
||||
if oc == OpcodeGlobalGet {
|
||||
globalIdx, _, err := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d] couldn't read global.get parameter: %w", SectionIDName(SectionIDElement), idx, err)
|
||||
} else if err = m.verifyImportGlobalI32(SectionIDElement, idx, globalIdx); err != nil {
|
||||
hasGlobalRef := false
|
||||
|
||||
offsetExprResults, offsetExprType, err := evaluateConstExpr(
|
||||
&elem.OffsetExpr,
|
||||
func(globalIndex Index) (ValueType, uint64, uint64, error) {
|
||||
hasGlobalRef = true
|
||||
|
||||
if globalIndex >= Index(globalsCount) {
|
||||
return 0, 0, 0, fmt.Errorf("%s[%d] global index %d out of range", SectionIDName(SectionIDElement), idx, globalIndex)
|
||||
}
|
||||
|
||||
vt, err := m.resolveConstExprGlobalType(enabledFeatures, SectionIDElement, idx, globalIndex)
|
||||
if err != nil {
|
||||
return 0, 0, 0, err
|
||||
}
|
||||
|
||||
if vt != ValueTypeI32 {
|
||||
return 0, 0, 0, fmt.Errorf("%s[%d] (global.get %d): import[%d].global.ValType != i32", SectionIDName(SectionIDElement), idx, globalIndex, i)
|
||||
}
|
||||
return ValueTypeI32, 0, 0, nil
|
||||
},
|
||||
func(funcIndex Index) (Reference, error) {
|
||||
return 0, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d] couldn't evaluate offset expression: %w", SectionIDName(SectionIDElement), idx, err)
|
||||
}
|
||||
if offsetExprType != ValueTypeI32 {
|
||||
return fmt.Errorf("%s[%d] offset expression must return i32 but was %s", SectionIDName(SectionIDElement), idx, ValueTypeName(offsetExprType))
|
||||
}
|
||||
|
||||
if !enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) && !hasGlobalRef && elem.TableIndex >= importedTableCount {
|
||||
offset := uint32(offsetExprResults[0])
|
||||
if err = checkSegmentBounds(t.Min, uint64(initCount)+uint64(offset), idx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if oc == OpcodeI32Const {
|
||||
// Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 we must pass if imported
|
||||
// table has set its min=0. Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142, we
|
||||
// have to do fail if module-defined min=0.
|
||||
if !enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) && elem.TableIndex >= importedTableCount {
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
o, _, err := leb128.LoadInt32(elem.OffsetExpr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d] couldn't read i32.const parameter: %w", SectionIDName(SectionIDElement), idx, err)
|
||||
}
|
||||
offset := Index(o)
|
||||
if err = checkSegmentBounds(t.Min, uint64(initCount)+uint64(offset), idx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("%s[%d] has an invalid const expression: %s", SectionIDName(SectionIDElement), idx, InstructionName(oc))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,11 +244,20 @@ func (m *ModuleInstance) buildTables(module *Module, skipBoundCheck bool) (err e
|
||||
idx := module.ImportTableCount
|
||||
for i := range module.TableSection {
|
||||
tsec := &module.TableSection[i]
|
||||
// The module defining the table is the one that sets its Min/Max etc.
|
||||
m.Tables[idx] = &TableInstance{
|
||||
t := &TableInstance{
|
||||
References: make([]Reference, tsec.Min), Min: tsec.Min, Max: tsec.Max,
|
||||
Type: tsec.Type,
|
||||
}
|
||||
if tsec.InitExpr != nil {
|
||||
initVals := evaluateConstExprInModuleInstance(tsec.InitExpr, m)
|
||||
if len(initVals) > 0 && initVals[0] != 0 {
|
||||
initRef := Reference(initVals[0])
|
||||
for j := range t.References {
|
||||
t.References[j] = initRef
|
||||
}
|
||||
}
|
||||
}
|
||||
m.Tables[idx] = t
|
||||
idx++
|
||||
}
|
||||
|
||||
@@ -259,18 +265,7 @@ func (m *ModuleInstance) buildTables(module *Module, skipBoundCheck bool) (err e
|
||||
for elemI := range module.ElementSection { // Do not loop over the value since elementSegments is a slice of value.
|
||||
elem := &module.ElementSection[elemI]
|
||||
table := m.Tables[elem.TableIndex]
|
||||
var offset uint32
|
||||
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
|
||||
// Ignore error as it's already validated.
|
||||
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||||
global := m.Globals[globalIdx]
|
||||
offset = uint32(global.Val)
|
||||
} else { // i32.const
|
||||
// Ignore error as it's already validated.
|
||||
o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
|
||||
offset = uint32(o)
|
||||
}
|
||||
|
||||
offset := uint32(evaluateConstExprInModuleInstance(&elem.OffsetExpr, m)[0])
|
||||
// Check to see if we are out-of-bounds
|
||||
initCount := uint64(len(elem.Init))
|
||||
if err = checkSegmentBounds(table.Min, uint64(offset)+initCount, Index(elemI)); err != nil {
|
||||
@@ -295,23 +290,6 @@ func checkSegmentBounds(min uint32, requireMin uint64, idx Index) error { // uin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) verifyImportGlobalI32(sectionID SectionID, sectionIdx Index, idx uint32) error {
|
||||
ig := uint32(math.MaxUint32) // +1 == 0
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
if imp.Type == ExternTypeGlobal {
|
||||
ig++
|
||||
if ig == idx {
|
||||
if imp.DescGlobal.ValType != ValueTypeI32 {
|
||||
return fmt.Errorf("%s[%d] (global.get %d): import[%d].global.ValType != i32", SectionIDName(sectionID), sectionIdx, idx, i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s[%d] (global.get %d): out of range of imported globals", SectionIDName(sectionID), sectionIdx, idx)
|
||||
}
|
||||
|
||||
// Grow appends the `initialRef` by `delta` times into the References slice.
|
||||
// Returns -1 if the operation is not valid, otherwise the old length of the table.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user