Consistently uses LEB128 signed encoding for global constants (#443)

Global constants can be defined in wasm or in ModuleBuilder. In either
case, they end up being decoded and interpreted during instantiation.
This chooses signed encoding to avoid surprises. A more comprehensive
explanation was added to RATIONALE.md, but the motivation was a global
100 coming out negative.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-04-06 09:50:47 +08:00
committed by GitHub
parent f5598c9a8e
commit fb0b311844
22 changed files with 188 additions and 68 deletions

View File

@@ -186,9 +186,26 @@ See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6a
Their semantics match when `pathLen` == the length of `path`, so in practice this difference won't matter match.
## Signed encoding of integer global constant initializers
wazero treats integer global constant initializers signed as their interpretation is not known at declaration time. For
example, there is no signed integer [value type](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0).
To get at the problem, let's use an example.
```
(global (export "start_epoch") i64 (i64.const 1620216263544))
```
In both signed and unsigned LEB128 encoding, this value is the same bit pattern. The problem is that some numbers are
not. For example, 16256 is `807f` encoded as unsigned, but `80ff00` encoded as signed.
While the specification mentions uninterpreted integers are in abstract [unsigned values](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#integers%E2%91%A0),
the binary encoding is clear that they are encoded [signed](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#integers%E2%91%A4).
For consistency, we go with signed encoding in the special case of global constant initializers.
## Implementation limitations
WebAssembly 1.0 (20191205) specification allows runtimes to [limit certain aspects of Wasm module or execution](https://www.w3.org/TR/2019/REC-2019/REC-wasm-core-1-20191205/#a2-implementation-limitations).
WebAssembly 1.0 (20191205) specification allows runtimes to [limit certain aspects of Wasm module or execution](https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#a2-implementation-limitations).
wazero limitations are imposed pragmatically and described below.

View File

@@ -107,7 +107,7 @@ type ModuleBuilder interface {
//
// Note: If a global is already exported with the same name, this overwrites it.
// Note: The maximum value of v is math.MaxInt32 to match constraints of initialization in binary format.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A2
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalI32(name string, v int32) ModuleBuilder
@@ -119,7 +119,7 @@ type ModuleBuilder interface {
//
// Note: If a global is already exported with the same name, this overwrites it.
// Note: The maximum value of v is math.MaxInt64 to match constraints of initialization in binary format.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A2
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#value-types%E2%91%A0
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalI64(name string, v int64) ModuleBuilder
@@ -202,8 +202,8 @@ func (b *moduleBuilder) ExportMemoryWithMax(name string, minPages, maxPages uint
func (b *moduleBuilder) ExportGlobalI32(name string, v int32) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
// Signed per https://www.w3.org/TR/wasm-core-1/#value-types%E2%91%A2
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(uint32(v))},
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(v)},
}
return b
}
@@ -212,8 +212,8 @@ func (b *moduleBuilder) ExportGlobalI32(name string, v int32) ModuleBuilder {
func (b *moduleBuilder) ExportGlobalI64(name string, v int64) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
// Signed per https://www.w3.org/TR/wasm-core-1/#value-types%E2%91%A2
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(uint64(v))},
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(v)},
}
return b
}

View File

@@ -211,7 +211,7 @@ func TestNewModuleBuilder_Build(t *testing.T) {
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(1024)},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1024)},
},
},
ExportSection: map[string]*wasm.Export{
@@ -262,7 +262,7 @@ func TestNewModuleBuilder_Build(t *testing.T) {
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(math.MaxInt64)},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(math.MaxInt64)},
},
},
ExportSection: map[string]*wasm.Export{

View File

@@ -81,7 +81,7 @@ func (c *RuntimeConfig) WithContext(ctx context.Context) *RuntimeConfig {
// * Zero is a valid value and results in a crash if any module uses memory.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
// See https://www.w3.org/TR/wasm-core-1/#memory-types%E2%91%A0
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-types%E2%91%A0
func (c *RuntimeConfig) WithMemoryMaxPages(memoryMaxPages uint32) *RuntimeConfig {
ret := c.clone()
ret.memoryMaxPages = memoryMaxPages

View File

@@ -7,7 +7,7 @@ import (
)
// DecodeFloat32 decodes a float32 in IEEE 754 binary representation.
// See https://www.w3.org/TR/wasm-core-1/#floating-point%E2%91%A2
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2
func DecodeFloat32(r io.Reader) (float32, error) {
buf := make([]byte, 4)
_, err := io.ReadFull(r, buf)
@@ -19,7 +19,7 @@ func DecodeFloat32(r io.Reader) (float32, error) {
}
// DecodeFloat64 decodes a float64 in IEEE 754 binary representation.
// See https://www.w3.org/TR/wasm-core-1/#floating-point%E2%91%A2
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#floating-point%E2%91%A2
func DecodeFloat64(r io.Reader) (float64, error) {
buf := make([]byte, 8)
_, err := io.ReadFull(r, buf)

View File

@@ -29,6 +29,40 @@ var encodeCache = [0x80][]byte{
{0x70}, {0x71}, {0x72}, {0x73}, {0x74}, {0x75}, {0x76}, {0x77}, {0x78}, {0x79}, {0x7a}, {0x7b}, {0x7c}, {0x7d}, {0x7e}, {0x7f},
}
// EncodeInt32 encodes the signed value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer
func EncodeInt32(value int32) []byte {
return EncodeInt64(int64(value))
}
// EncodeInt64 encodes the signed value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_signed_integer
func EncodeInt64(value int64) (buf []byte) {
for {
// Take 7 remaining low-order bits from the value into b.
b := uint8(value & 0x7f)
// Extract the sign bit.
s := uint8(value & 0x40)
value >>= 7
// The encoding unsigned numbers is simpler as it only needs to check if the value is non-zero to tell if there
// are more bits to encode. Signed is a little more complicated as you have to double-check the sign bit.
// If either case, set the high-order bit to tell the reader there are more bytes in this int.
if (value != -1 || s == 0) && (value != 0 || s != 0) {
b |= 0x80
}
// Append b into the buffer
buf = append(buf, b)
if b&0x80 == 0 {
break
}
}
return buf
}
// EncodeUint32 encodes the value into a buffer in LEB128 format
//
// See https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer
@@ -51,7 +85,7 @@ func EncodeUint64(value uint64) (buf []byte) {
value = value >> 7
// If there are remaining bits, the value won't be zero: Set the high-
// order bit to tell the reader there are more bytes in this uint32.
// order bit to tell the reader there are more bytes in this uint.
if value != 0 {
b |= 0x80
}

View File

@@ -10,6 +10,58 @@ import (
"github.com/stretchr/testify/require"
)
func TestEncode_DecodeInt32(t *testing.T) {
for _, c := range []struct {
input int32
expected []byte
}{
{input: -165675008, expected: []byte{0x80, 0x80, 0x80, 0xb1, 0x7f}},
{input: -624485, expected: []byte{0x9b, 0xf1, 0x59}},
{input: -16256, expected: []byte{0x80, 0x81, 0x7f}},
{input: -4, expected: []byte{0x7c}},
{input: -1, expected: []byte{0x7f}},
{input: 0, expected: []byte{0x00}},
{input: 1, expected: []byte{0x01}},
{input: 4, expected: []byte{0x04}},
{input: 16256, expected: []byte{0x80, 0xff, 0x0}},
{input: 624485, expected: []byte{0xe5, 0x8e, 0x26}},
{input: 165675008, expected: []byte{0x80, 0x80, 0x80, 0xcf, 0x0}},
{input: int32(math.MaxInt32), expected: []byte{0xff, 0xff, 0xff, 0xff, 0x7}},
} {
require.Equal(t, c.expected, EncodeInt32(c.input))
decoded, _, err := DecodeInt32(bytes.NewReader(c.expected))
require.NoError(t, err)
require.Equal(t, c.input, decoded)
}
}
func TestEncode_DecodeInt64(t *testing.T) {
for _, c := range []struct {
input int64
expected []byte
}{
{input: -math.MaxInt32, expected: []byte{0x81, 0x80, 0x80, 0x80, 0x78}},
{input: -165675008, expected: []byte{0x80, 0x80, 0x80, 0xb1, 0x7f}},
{input: -624485, expected: []byte{0x9b, 0xf1, 0x59}},
{input: -16256, expected: []byte{0x80, 0x81, 0x7f}},
{input: -4, expected: []byte{0x7c}},
{input: -1, expected: []byte{0x7f}},
{input: 0, expected: []byte{0x00}},
{input: 1, expected: []byte{0x01}},
{input: 4, expected: []byte{0x04}},
{input: 16256, expected: []byte{0x80, 0xff, 0x0}},
{input: 624485, expected: []byte{0xe5, 0x8e, 0x26}},
{input: 165675008, expected: []byte{0x80, 0x80, 0x80, 0xcf, 0x0}},
{input: math.MaxInt32, expected: []byte{0xff, 0xff, 0xff, 0xff, 0x7}},
{input: math.MaxInt64, expected: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0}},
} {
require.Equal(t, c.expected, EncodeInt64(c.input))
decoded, _, err := DecodeInt64(bytes.NewReader(c.expected))
require.NoError(t, err)
require.Equal(t, c.input, decoded)
}
}
func TestEncodeUint32(t *testing.T) {
for _, c := range []struct {
input uint32

View File

@@ -21,8 +21,10 @@ func decodeConstantExpression(r *bytes.Reader) (*wasm.ConstantExpression, error)
opcode := b
switch opcode {
case wasm.OpcodeI32Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt32(r)
case wasm.OpcodeI64Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt64(r)
case wasm.OpcodeF32Const:
_, err = ieee754.DecodeFloat32(r)

View File

@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -180,7 +181,7 @@ func TestModule_Encode(t *testing.T) {
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: i32, Mutable: true},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0}},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(0)},
},
},
ExportSection: map[string]*wasm.Export{

View File

@@ -5,6 +5,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -18,7 +19,7 @@ func TestEncodeGlobal(t *testing.T) {
name: "const",
input: &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{1}},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1)},
},
expected: []byte{
wasm.ValueTypeI32, 0x00, // 0 == const
@@ -29,7 +30,7 @@ func TestEncodeGlobal(t *testing.T) {
name: "var",
input: &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{1}},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1)},
},
expected: []byte{
wasm.ValueTypeI32, 0x01, // 1 == var

View File

@@ -203,7 +203,7 @@ func TestModule_ImportGlobalCount(t *testing.T) {
func TestModule_SectionElementCount(t *testing.T) {
i32, f32 := ValueTypeI32, ValueTypeF32
zero := uint32(0)
empty := &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x00}}
empty := &ConstantExpression{Opcode: OpcodeI32Const, Data: const0}
fn := reflect.ValueOf(func(api.Module) {})
tests := []struct {

View File

@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/u64"
)
@@ -156,7 +157,7 @@ func TestPublicModule_Global(t *testing.T) {
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
},
@@ -167,7 +168,7 @@ func TestPublicModule_Global(t *testing.T) {
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
@@ -180,7 +181,7 @@ func TestPublicModule_Global(t *testing.T) {
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI64},
Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: []byte{1}},
Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: leb128.EncodeInt64(1)},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
@@ -223,7 +224,7 @@ func TestPublicModule_Global(t *testing.T) {
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32, Mutable: true},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
@@ -238,7 +239,7 @@ func TestPublicModule_Global(t *testing.T) {
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI64, Mutable: true},
Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: []byte{1}},
Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: leb128.EncodeInt64(1)},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},

View File

@@ -93,22 +93,22 @@ func TestNewHostModule(t *testing.T) {
nameToGlobal: map[string]*Global{
"g2": {
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(2)},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)},
},
"g1": {
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(1)},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
expected: &Module{
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(1)},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
{
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(2)},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)},
},
},
ExportSection: map[string]*Export{
@@ -129,7 +129,7 @@ func TestNewHostModule(t *testing.T) {
nameToGlobal: map[string]*Global{
"g": {
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(1)},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
expected: &Module{
@@ -141,7 +141,7 @@ func TestNewHostModule(t *testing.T) {
GlobalSection: []*Global{
{
Type: &GlobalType{ValType: i32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(1)},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
MemorySection: &Memory{Min: 1, Max: 1},

View File

@@ -169,7 +169,7 @@ func (m *MemoryInstance) PageSize() (result uint32) {
// PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. Ex. 1 -> "64Ki"
//
// See https://www.w3.org/TR/wasm-core-1/#memory-instances%E2%91%A0
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
func PagesToUnitOfBytes(pages uint32) string {
k := pages * 64
if k < 1024 {

View File

@@ -152,14 +152,14 @@ type Module struct {
// When present, the CodeSection must be nil.
//
// Note: This section currently has no serialization format, so is not encodable.
// See https://www.w3.org/TR/wasm-core-1/#host-functions%E2%91%A2
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
HostFunctionSection []*reflect.Value
// elementSegments are built on Validate when SectionIDElement is non-empty and all inputs are valid.
//
// Note: elementSegments retain Module.ElementSection order. Since an ElementSegment can overlap with another, order
// preservation ensures a consistent initialization result.
// See https://www.w3.org/TR/wasm-core-1/#table-instances%E2%91%A0
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0
validatedElementSegments []*validatedElementSegment
}
@@ -393,12 +393,14 @@ func validateConstExpression(globals []*GlobalType, expr *ConstantExpression, ex
r := bytes.NewReader(expr.Data)
switch expr.Opcode {
case OpcodeI32Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt32(r)
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.DecodeInt64(r)
if err != nil {
return fmt.Errorf("read i64: %w", err)

View File

@@ -368,7 +368,7 @@ func TestModule_validateGlobals(t *testing.T) {
m := Module{GlobalSection: []*Global{
{
Type: &GlobalType{ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
},
}}
err := m.validateGlobals(nil, 9)
@@ -500,7 +500,7 @@ func TestModule_validateMemory(t *testing.T) {
Init: []byte{0x1},
OffsetExpression: &ConstantExpression{
Opcode: OpcodeI32Const,
Data: []byte{0x1},
Data: leb128.EncodeInt32(1),
},
}}}
err := m.validateMemory(&Memory{}, nil)
@@ -670,7 +670,7 @@ func TestModule_buildGlobalInstances(t *testing.T) {
{
Type: &GlobalType{Mutable: false, ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const,
Data: leb128.EncodeUint32(math.MaxInt32)},
Data: leb128.EncodeInt32(math.MaxInt32)},
},
}}

View File

@@ -474,8 +474,10 @@ func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression)
r := bytes.NewReader(expr.Data)
switch expr.Opcode {
case OpcodeI32Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
v, _, _ = leb128.DecodeInt32(r)
case OpcodeI64Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
v, _, _ = leb128.DecodeInt64(r)
case OpcodeF32Const:
v, _ = ieee754.DecodeFloat32(r)

View File

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/hammer"
"github.com/tetratelabs/wazero/internal/u64"
)
@@ -148,7 +149,7 @@ func TestStore_CloseModule(t *testing.T) {
TypeSection: []*FunctionType{{}},
ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
MemorySection: &Memory{Min: 1},
GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}}},
GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}},
TableSection: &Table{Min: 10},
}, importingModuleName, nil)
require.NoError(t, err)
@@ -192,7 +193,7 @@ func TestStore_hammer(t *testing.T) {
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
MemorySection: &Memory{Min: 1},
GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}}},
GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}},
TableSection: &Table{Min: 10},
ImportSection: []*Import{
{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
@@ -680,21 +681,21 @@ func TestModuleInstance_validateData(t *testing.T) {
{
name: "ok",
data: []*DataSegment{
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}, Init: []byte{0}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x2}}, Init: []byte{0}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []byte{0}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []byte{0}},
},
},
{
name: "out of bounds - single one byte",
data: []*DataSegment{
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x5}}, Init: []byte{0}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(5)}, Init: []byte{0}},
},
expErr: true,
},
{
name: "out of bounds - multi bytes",
data: []*DataSegment{
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x3}}, Init: []byte{0, 1, 2}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(3)}, Init: []byte{0, 1, 2}},
},
expErr: true,
},
@@ -714,8 +715,8 @@ func TestModuleInstance_validateData(t *testing.T) {
func TestModuleInstance_applyData(t *testing.T) {
m := &ModuleInstance{Memory: &MemoryInstance{Buffer: make([]byte, 10)}}
m.applyData([]*DataSegment{
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}, Init: []byte{0xa, 0xf}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x8}}, Init: []byte{0x1, 0x5}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []byte{0xa, 0xf}},
{OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{0x1, 0x5}},
})
require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.Memory.Buffer)
}

View File

@@ -113,7 +113,8 @@ func (m *Module) validateTable() ([]*validatedElementSegment, error) {
ret = append(ret, &validatedElementSegment{oc, globalIdx, elem.Init})
} else if oc == OpcodeI32Const {
o, _, err := leb128.DecodeUint32(bytes.NewReader(elem.OffsetExpr.Data))
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
o, _, err := leb128.DecodeInt32(bytes.NewReader(elem.OffsetExpr.Data))
if err != nil {
return nil, fmt.Errorf("%s[%d] couldn't read i32.const parameter: %w", SectionIDName(SectionIDElement), idx, err)
}

View File

@@ -80,7 +80,7 @@ func TestModule_validateTable(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0}},
},
},
expected: []*validatedElementSegment{},
@@ -94,7 +94,7 @@ func TestModule_validateTable(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},
@@ -112,7 +112,7 @@ func TestModule_validateTable(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},
@@ -130,7 +130,7 @@ func TestModule_validateTable(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},
@@ -148,7 +148,7 @@ func TestModule_validateTable(t *testing.T) {
CodeSection: []*Code{codeEnd, codeEnd, codeEnd, codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
Init: []Index{0, 2},
},
},
@@ -271,7 +271,7 @@ func TestModule_validateTable(t *testing.T) {
CodeSection: []*Code{codeEnd, codeEnd, codeEnd, codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
Init: []Index{0, 2},
},
{
@@ -334,7 +334,7 @@ func TestModule_validateTable_Errors(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI64Const, Data: []byte{0x0}}, Init: []Index{0}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI64Const, Data: const0}, Init: []Index{0}},
},
},
expectedErr: "element[0] has an invalid const expression: i64.const",
@@ -346,7 +346,7 @@ func TestModule_validateTable_Errors(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}, Init: []Index{0}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []Index{0}},
},
},
expectedErr: "element was defined, but not table",
@@ -359,7 +359,7 @@ func TestModule_validateTable_Errors(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x2}}, Init: []Index{0}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []Index{0}},
},
},
expectedErr: "element[0].init exceeds min table size",
@@ -372,8 +372,8 @@ func TestModule_validateTable_Errors(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}, Init: []Index{0}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}, Init: []Index{0, 0}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 0}},
},
},
expectedErr: "element[1].init exceeds min table size",
@@ -386,7 +386,7 @@ func TestModule_validateTable_Errors(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x2}}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}},
},
},
expectedErr: "element[0].init exceeds min table size",
@@ -399,7 +399,7 @@ func TestModule_validateTable_Errors(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}, Init: []Index{0, 1}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 1}},
},
},
expectedErr: "element[0].init[1] funcidx 1 out of range",
@@ -513,6 +513,9 @@ func TestModule_validateTable_Errors(t *testing.T) {
}
}
var const0 = leb128.EncodeInt32(0)
var const1 = leb128.EncodeInt32(1)
func TestModule_buildTable(t *testing.T) {
three := uint32(3)
tests := []struct {
@@ -553,7 +556,7 @@ func TestModule_buildTable(t *testing.T) {
FunctionSection: []Index{0},
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}}},
{OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0}},
},
validatedElementSegments: []*validatedElementSegment{},
},
@@ -568,7 +571,7 @@ func TestModule_buildTable(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},
@@ -588,7 +591,7 @@ func TestModule_buildTable(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},
@@ -606,7 +609,7 @@ func TestModule_buildTable(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},
@@ -624,7 +627,7 @@ func TestModule_buildTable(t *testing.T) {
CodeSection: []*Code{codeEnd, codeEnd, codeEnd, codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
Init: []Index{0, 2},
},
},
@@ -766,7 +769,7 @@ func TestModule_buildTable(t *testing.T) {
CodeSection: []*Code{codeEnd, codeEnd, codeEnd, codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
Init: []Index{0, 2},
},
{
@@ -822,7 +825,7 @@ func TestModule_buildTable_Errors(t *testing.T) {
CodeSection: []*Code{codeEnd},
ElementSection: []*ElementSegment{
{
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x0}},
OffsetExpr: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0},
Init: []Index{0},
},
},

View File

@@ -245,7 +245,7 @@ func addSpectestModule(t *testing.T, store *wasm.Store) {
// (global (export "global_i32") i32 (i32.const 666))
mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(666)},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(666)},
})
mod.ExportSection["global_i32"] = &wasm.Export{Name: "global_i32", Index: 0, Type: wasm.ExternTypeGlobal}

View File

@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasm/binary"
)
@@ -164,6 +165,8 @@ func TestModule_Memory(t *testing.T) {
// TestModule_Global only covers a couple cases to avoid duplication of internal/wasm/global_test.go
func TestModule_Global(t *testing.T) {
globalVal := int64(100) // intentionally a value that differs in signed vs unsigned encoding
tests := []struct {
name string
module *wasm.Module // module as wat doesn't yet support globals
@@ -180,7 +183,7 @@ func TestModule_Global(t *testing.T) {
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: []byte{1}},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
},
},
},
@@ -188,7 +191,7 @@ func TestModule_Global(t *testing.T) {
{
name: "global exported",
builder: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder(t.Name()).ExportGlobalI64("global", 1)
return r.NewModuleBuilder(t.Name()).ExportGlobalI64("global", globalVal)
},
expected: true,
},
@@ -198,7 +201,7 @@ func TestModule_Global(t *testing.T) {
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: []byte{1}},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
},
},
ExportSection: map[string]*wasm.Export{
@@ -232,7 +235,7 @@ func TestModule_Global(t *testing.T) {
require.Nil(t, global)
return
}
require.Equal(t, uint64(1), global.Get())
require.Equal(t, uint64(globalVal), global.Get())
mutable, ok := global.(api.MutableGlobal)
require.Equal(t, tc.expectedMutable, ok)