Fixes global numeric types to have max of signed encoding (#442)

This adjusts towards the exiting code which used int32/64 instead of
uint32/64. The reason is that the spec indicates intepretation as signed
numbers, which affects the maximum value.

See https://www.w3.org/TR/wasm-core-1/#value-types%E2%91%A2

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-04-06 06:35:31 +08:00
committed by GitHub
parent b1cffcc58e
commit f5598c9a8e
14 changed files with 153 additions and 62 deletions

View File

@@ -31,9 +31,13 @@ import (
type ValueType = byte type ValueType = byte
const ( const (
// ValueTypeI32 is a 32-bit integer.
ValueTypeI32 ValueType = 0x7f ValueTypeI32 ValueType = 0x7f
// ValueTypeI64 is a 64-bit integer.
ValueTypeI64 ValueType = 0x7e ValueTypeI64 ValueType = 0x7e
// ValueTypeF32 is a 32-bit floating point number.
ValueTypeF32 ValueType = 0x7d ValueTypeF32 ValueType = 0x7d
// ValueTypeF64 is a 32-bit floating point number.
ValueTypeF64 ValueType = 0x7c ValueTypeF64 ValueType = 0x7c
) )

View File

@@ -64,11 +64,11 @@ func TestEncodeDecodeF64(t *testing.T) {
} { } {
t.Run(fmt.Sprintf("%f", v), func(t *testing.T) { t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
encoded := EncodeF64(v) encoded := EncodeF64(v)
binary := DecodeF64(encoded) val := DecodeF64(encoded)
if math.IsNaN(binary) { // cannot use require.Equal as NaN by definition doesn't equal itself if math.IsNaN(val) { // cannot use require.Equal as NaN by definition doesn't equal itself
require.True(t, math.IsNaN(binary)) require.True(t, math.IsNaN(val))
} else { } else {
require.Equal(t, v, binary) require.Equal(t, v, val)
} }
}) })
} }

View File

@@ -5,6 +5,7 @@ import (
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128" "github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wasm"
) )
@@ -105,8 +106,10 @@ type ModuleBuilder interface {
// builder.ExportGlobalI32("canvas_width", 1024) // builder.ExportGlobalI32("canvas_width", 1024)
// //
// Note: If a global is already exported with the same name, this overwrites it. // 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/#syntax-globaltype // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalI32(name string, v uint32) ModuleBuilder ExportGlobalI32(name string, v int32) ModuleBuilder
// ExportGlobalI64 exports a global constant of type api.ValueTypeI64. // ExportGlobalI64 exports a global constant of type api.ValueTypeI64.
// //
@@ -115,8 +118,10 @@ type ModuleBuilder interface {
// builder.ExportGlobalI64("start_epoch", 1620216263544) // builder.ExportGlobalI64("start_epoch", 1620216263544)
// //
// Note: If a global is already exported with the same name, this overwrites it. // 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/#syntax-globaltype // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-globaltype
ExportGlobalI64(name string, v uint64) ModuleBuilder ExportGlobalI64(name string, v int64) ModuleBuilder
// ExportGlobalF32 exports a global constant of type api.ValueTypeF32. // ExportGlobalF32 exports a global constant of type api.ValueTypeF32.
// //
@@ -194,19 +199,21 @@ func (b *moduleBuilder) ExportMemoryWithMax(name string, minPages, maxPages uint
} }
// ExportGlobalI32 implements ModuleBuilder.ExportGlobalI32 // ExportGlobalI32 implements ModuleBuilder.ExportGlobalI32
func (b *moduleBuilder) ExportGlobalI32(name string, v uint32) ModuleBuilder { func (b *moduleBuilder) ExportGlobalI32(name string, v int32) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{ b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(v)}, // 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))},
} }
return b return b
} }
// ExportGlobalI64 implements ModuleBuilder.ExportGlobalI64 // ExportGlobalI64 implements ModuleBuilder.ExportGlobalI64
func (b *moduleBuilder) ExportGlobalI64(name string, v uint64) ModuleBuilder { func (b *moduleBuilder) ExportGlobalI64(name string, v int64) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{ b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(v)}, // 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))},
} }
return b return b
} }
@@ -215,7 +222,7 @@ func (b *moduleBuilder) ExportGlobalI64(name string, v uint64) ModuleBuilder {
func (b *moduleBuilder) ExportGlobalF32(name string, v float32) ModuleBuilder { func (b *moduleBuilder) ExportGlobalF32(name string, v float32) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{ b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: leb128.EncodeUint64(api.EncodeF32(v))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(v))},
} }
return b return b
} }
@@ -224,7 +231,7 @@ func (b *moduleBuilder) ExportGlobalF32(name string, v float32) ModuleBuilder {
func (b *moduleBuilder) ExportGlobalF64(name string, v float64) ModuleBuilder { func (b *moduleBuilder) ExportGlobalF64(name string, v float64) ModuleBuilder {
b.nameToGlobal[name] = &wasm.Global{ b.nameToGlobal[name] = &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: leb128.EncodeUint64(api.EncodeF64(v))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(v))},
} }
return b return b
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128" "github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wasm"
) )
@@ -221,13 +222,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
{ {
name: "ExportGlobalI32 overwrites", name: "ExportGlobalI32 overwrites",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalI32("canvas_width", 1024).ExportGlobalI32("canvas_width", 2048) return r.NewModuleBuilder("").ExportGlobalI32("canvas_width", 1024).ExportGlobalI32("canvas_width", math.MaxInt32)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
GlobalSection: []*wasm.Global{ GlobalSection: []*wasm.Global{
{ {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(2048)}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(math.MaxInt32)},
}, },
}, },
ExportSection: map[string]*wasm.Export{ ExportSection: map[string]*wasm.Export{
@@ -255,13 +256,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
{ {
name: "ExportGlobalI64 overwrites", name: "ExportGlobalI64 overwrites",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalI64("start_epoch", 1620216263544).ExportGlobalI64("start_epoch", 1620216263544000) return r.NewModuleBuilder("").ExportGlobalI64("start_epoch", 1620216263544).ExportGlobalI64("start_epoch", math.MaxInt64)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
GlobalSection: []*wasm.Global{ GlobalSection: []*wasm.Global{
{ {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(1620216263544000)}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(math.MaxInt64)},
}, },
}, },
ExportSection: map[string]*wasm.Export{ ExportSection: map[string]*wasm.Export{
@@ -278,7 +279,7 @@ func TestNewModuleBuilder_Build(t *testing.T) {
GlobalSection: []*wasm.Global{ GlobalSection: []*wasm.Global{
{ {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: leb128.EncodeUint64(api.EncodeF32(3.1415926536))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(3.1415926536))},
}, },
}, },
ExportSection: map[string]*wasm.Export{ ExportSection: map[string]*wasm.Export{
@@ -289,13 +290,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
{ {
name: "ExportGlobalF32 overwrites", name: "ExportGlobalF32 overwrites",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalF32("math/pi", 3.1415926536).ExportGlobalF32("math/pi", 3.14159) return r.NewModuleBuilder("").ExportGlobalF32("math/pi", 3.1415926536).ExportGlobalF32("math/pi", math.MaxFloat32)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
GlobalSection: []*wasm.Global{ GlobalSection: []*wasm.Global{
{ {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: leb128.EncodeUint64(api.EncodeF32(3.14159))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(math.MaxFloat32))},
}, },
}, },
ExportSection: map[string]*wasm.Export{ ExportSection: map[string]*wasm.Export{
@@ -312,7 +313,7 @@ func TestNewModuleBuilder_Build(t *testing.T) {
GlobalSection: []*wasm.Global{ GlobalSection: []*wasm.Global{
{ {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: leb128.EncodeUint64(api.EncodeF64(math.Pi))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(math.Pi))},
}, },
}, },
ExportSection: map[string]*wasm.Export{ ExportSection: map[string]*wasm.Export{
@@ -323,13 +324,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
{ {
name: "ExportGlobalF64 overwrites", name: "ExportGlobalF64 overwrites",
input: func(r Runtime) ModuleBuilder { input: func(r Runtime) ModuleBuilder {
return r.NewModuleBuilder("").ExportGlobalF64("math/pi", math.Pi).ExportGlobalF64("math/pi", 3.14159) return r.NewModuleBuilder("").ExportGlobalF64("math/pi", math.Pi).ExportGlobalF64("math/pi", math.MaxFloat64)
}, },
expected: &wasm.Module{ expected: &wasm.Module{
GlobalSection: []*wasm.Global{ GlobalSection: []*wasm.Global{
{ {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: leb128.EncodeUint64(api.EncodeF64(3.14159))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64))},
}, },
}, },
ExportSection: map[string]*wasm.Export{ ExportSection: map[string]*wasm.Export{

View File

@@ -6,6 +6,8 @@ import (
"math" "math"
) )
// DecodeFloat32 decodes a float32 in IEEE 754 binary representation.
// See https://www.w3.org/TR/wasm-core-1/#floating-point%E2%91%A2
func DecodeFloat32(r io.Reader) (float32, error) { func DecodeFloat32(r io.Reader) (float32, error) {
buf := make([]byte, 4) buf := make([]byte, 4)
_, err := io.ReadFull(r, buf) _, err := io.ReadFull(r, buf)
@@ -16,6 +18,8 @@ func DecodeFloat32(r io.Reader) (float32, error) {
return math.Float32frombits(raw), nil return math.Float32frombits(raw), nil
} }
// DecodeFloat64 decodes a float64 in IEEE 754 binary representation.
// See https://www.w3.org/TR/wasm-core-1/#floating-point%E2%91%A2
func DecodeFloat64(r io.Reader) (float64, error) { func DecodeFloat64(r io.Reader) (float64, error) {
buf := make([]byte, 8) buf := make([]byte, 8)
_, err := io.ReadFull(r, buf) _, err := io.ReadFull(r, buf)

View File

@@ -58,6 +58,7 @@ func TestDecodeUint32(t *testing.T) {
{bytes: []byte{0x80, 0x7f}, exp: 16256}, {bytes: []byte{0x80, 0x7f}, exp: 16256},
{bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485}, {bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485},
{bytes: []byte{0x80, 0x80, 0x80, 0x4f}, exp: 165675008}, {bytes: []byte{0x80, 0x80, 0x80, 0x4f}, exp: 165675008},
{bytes: []byte{0xff, 0xff, 0xff, 0xff, 0xf}, exp: math.MaxUint32},
{bytes: []byte{0x83, 0x80, 0x80, 0x80, 0x80, 0x00}, expErr: true}, {bytes: []byte{0x83, 0x80, 0x80, 0x80, 0x80, 0x00}, expErr: true},
{bytes: []byte{0x82, 0x80, 0x80, 0x80, 0x70}, expErr: true}, {bytes: []byte{0x82, 0x80, 0x80, 0x80, 0x70}, expErr: true},
{bytes: []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x00}, expErr: true}, {bytes: []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x00}, expErr: true},
@@ -83,6 +84,8 @@ func TestDecodeUint64(t *testing.T) {
{bytes: []byte{0x80, 0x7f}, exp: 16256}, {bytes: []byte{0x80, 0x7f}, exp: 16256},
{bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485}, {bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485},
{bytes: []byte{0x80, 0x80, 0x80, 0x4f}, exp: 165675008}, {bytes: []byte{0x80, 0x80, 0x80, 0x4f}, exp: 165675008},
{bytes: []byte{0xff, 0xff, 0xff, 0xff, 0xf}, exp: math.MaxUint32},
{bytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1}, exp: math.MaxUint64},
{bytes: []byte{0x89, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x71}, expErr: true}, {bytes: []byte{0x89, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x71}, expErr: true},
} { } {
actual, num, err := DecodeUint64(bytes.NewReader(c.bytes)) actual, num, err := DecodeUint64(bytes.NewReader(c.bytes))

15
internal/u64/u64.go Normal file
View File

@@ -0,0 +1,15 @@
package u64
// LeBytes returns a byte array corresponding to the 8 bytes in the uint64 in little-endian byte order.
func LeBytes(v uint64) []byte {
return []byte{
byte(v),
byte(v >> 8),
byte(v >> 16),
byte(v >> 24),
byte(v >> 32),
byte(v >> 40),
byte(v >> 48),
byte(v >> 56),
}
}

39
internal/u64/u64_test.go Normal file
View File

@@ -0,0 +1,39 @@
package u64
import (
"encoding/binary"
"math"
"testing"
"github.com/stretchr/testify/require"
)
func TestBytes(t *testing.T) {
tests := []struct {
name string
input uint64
}{
{
name: "zero",
input: 0,
},
{
name: "half",
input: math.MaxUint32,
},
{
name: "max",
input: math.MaxUint64,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
expected := make([]byte, 8)
binary.LittleEndian.PutUint64(expected, tc.input)
require.Equal(t, expected, LeBytes(tc.input))
})
}
}

View File

@@ -2,12 +2,13 @@ package wasm
import ( import (
"context" "context"
gobinary "encoding/binary" "math"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/u64"
) )
func TestGlobalTypes(t *testing.T) { func TestGlobalTypes(t *testing.T) {
@@ -26,6 +27,13 @@ func TestGlobalTypes(t *testing.T) {
expectedVal: 1, expectedVal: 1,
expectedString: "global(1)", expectedString: "global(1)",
}, },
{
name: "i32 - immutable - max",
global: globalI32(math.MaxInt32),
expectedType: ValueTypeI32,
expectedVal: math.MaxInt32,
expectedString: "global(2147483647)",
},
{ {
name: "i64 - immutable", name: "i64 - immutable",
global: globalI64(1), global: globalI64(1),
@@ -33,6 +41,13 @@ func TestGlobalTypes(t *testing.T) {
expectedVal: 1, expectedVal: 1,
expectedString: "global(1)", expectedString: "global(1)",
}, },
{
name: "i64 - immutable - max",
global: globalI64(math.MaxInt64),
expectedType: ValueTypeI64,
expectedVal: math.MaxInt64,
expectedString: "global(9223372036854775807)",
},
{ {
name: "f32 - immutable", name: "f32 - immutable",
global: globalF32(api.EncodeF32(1.0)), global: globalF32(api.EncodeF32(1.0)),
@@ -40,6 +55,13 @@ func TestGlobalTypes(t *testing.T) {
expectedVal: api.EncodeF32(1.0), expectedVal: api.EncodeF32(1.0),
expectedString: "global(1.000000)", expectedString: "global(1.000000)",
}, },
{
name: "f32 - immutable - max",
global: globalF32(api.EncodeF32(math.MaxFloat32)),
expectedType: ValueTypeF32,
expectedVal: api.EncodeF32(math.MaxFloat32),
expectedString: "global(340282346638528859811704183484516925440.000000)",
},
{ {
name: "f64 - immutable", name: "f64 - immutable",
global: globalF64(api.EncodeF64(1.0)), global: globalF64(api.EncodeF64(1.0)),
@@ -47,6 +69,13 @@ func TestGlobalTypes(t *testing.T) {
expectedVal: api.EncodeF64(1.0), expectedVal: api.EncodeF64(1.0),
expectedString: "global(1.000000)", expectedString: "global(1.000000)",
}, },
{
name: "f64 - immutable - max",
global: globalF64(api.EncodeF64(math.MaxFloat64)),
expectedType: ValueTypeF64,
expectedVal: api.EncodeF64(math.MaxFloat64),
expectedString: "global(179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000)",
},
{ {
name: "i32 - mutable", name: "i32 - mutable",
global: &mutableGlobal{g: &GlobalInstance{ global: &mutableGlobal{g: &GlobalInstance{
@@ -165,7 +194,7 @@ func TestPublicModule_Global(t *testing.T) {
{ {
Type: &GlobalType{ValType: ValueTypeF32}, Type: &GlobalType{ValType: ValueTypeF32},
Init: &ConstantExpression{Opcode: OpcodeF32Const, Init: &ConstantExpression{Opcode: OpcodeF32Const,
Data: uint64Le(api.EncodeF32(1.0)), Data: u64.LeBytes(api.EncodeF32(1.0)),
}, },
}, },
}, },
@@ -180,7 +209,7 @@ func TestPublicModule_Global(t *testing.T) {
{ {
Type: &GlobalType{ValType: ValueTypeF64}, Type: &GlobalType{ValType: ValueTypeF64},
Init: &ConstantExpression{Opcode: OpcodeF64Const, Init: &ConstantExpression{Opcode: OpcodeF64Const,
Data: uint64Le(api.EncodeF64(1.0)), Data: u64.LeBytes(api.EncodeF64(1.0)),
}, },
}, },
}, },
@@ -225,7 +254,7 @@ func TestPublicModule_Global(t *testing.T) {
{ {
Type: &GlobalType{ValType: ValueTypeF32, Mutable: true}, Type: &GlobalType{ValType: ValueTypeF32, Mutable: true},
Init: &ConstantExpression{Opcode: OpcodeF32Const, Init: &ConstantExpression{Opcode: OpcodeF32Const,
Data: uint64Le(api.EncodeF32(1.0)), Data: u64.LeBytes(api.EncodeF32(1.0)),
}, },
}, },
}, },
@@ -242,7 +271,7 @@ func TestPublicModule_Global(t *testing.T) {
{ {
Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Type: &GlobalType{ValType: ValueTypeF64, Mutable: true},
Init: &ConstantExpression{Opcode: OpcodeF64Const, Init: &ConstantExpression{Opcode: OpcodeF64Const,
Data: uint64Le(api.EncodeF64(1.0)), Data: u64.LeBytes(api.EncodeF64(1.0)),
}, },
}, },
}, },
@@ -271,9 +300,3 @@ func TestPublicModule_Global(t *testing.T) {
}) })
} }
} }
func uint64Le(v uint64) (ret []byte) {
ret = make([]byte, 8)
gobinary.LittleEndian.PutUint64(ret, v)
return
}

View File

@@ -447,6 +447,8 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*Glo
gv = api.EncodeF32(v) gv = api.EncodeF32(v)
case float64: case float64:
gv = api.EncodeF64(v) gv = api.EncodeF64(v)
default:
panic(fmt.Errorf("BUG: invalid conversion %d", v))
} }
globals = append(globals, &GlobalInstance{Type: gs.Type, Val: gv}) globals = append(globals, &GlobalInstance{Type: gs.Type, Val: gv})
} }

View File

@@ -1,7 +1,6 @@
package wasm package wasm
import ( import (
"encoding/binary"
"fmt" "fmt"
"math" "math"
"reflect" "reflect"
@@ -10,6 +9,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/u64"
) )
func TestFunctionType_String(t *testing.T) { func TestFunctionType_String(t *testing.T) {
@@ -186,20 +187,19 @@ func TestValidateConstExpression(t *testing.T) {
for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} { for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
t.Run(ValueTypeName(vt), func(t *testing.T) { t.Run(ValueTypeName(vt), func(t *testing.T) {
t.Run("valid", func(t *testing.T) { t.Run("valid", func(t *testing.T) {
// Allocate bytes with enough size for all types. expr := &ConstantExpression{}
expr := &ConstantExpression{Data: make([]byte, 8)}
switch vt { switch vt {
case ValueTypeI32: case ValueTypeI32:
expr.Data[0] = 1 expr.Data = []byte{1}
expr.Opcode = OpcodeI32Const expr.Opcode = OpcodeI32Const
case ValueTypeI64: case ValueTypeI64:
expr.Data[0] = 2 expr.Data = []byte{2}
expr.Opcode = OpcodeI64Const expr.Opcode = OpcodeI64Const
case ValueTypeF32: case ValueTypeF32:
binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32)) expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
expr.Opcode = OpcodeF32Const expr.Opcode = OpcodeF32Const
case ValueTypeF64: case ValueTypeF64:
binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64)) expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
expr.Opcode = OpcodeF64Const expr.Opcode = OpcodeF64Const
} }
@@ -661,32 +661,25 @@ func TestModule_validateExports(t *testing.T) {
} }
func TestModule_buildGlobalInstances(t *testing.T) { func TestModule_buildGlobalInstances(t *testing.T) {
data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
binary.LittleEndian.PutUint64(data, math.Float64bits(1.0))
m := Module{GlobalSection: []*Global{ m := Module{GlobalSection: []*Global{
{ {
Type: &GlobalType{Mutable: true, ValType: ValueTypeF64}, Type: &GlobalType{Mutable: true, ValType: ValueTypeF64},
Init: &ConstantExpression{Opcode: OpcodeF64Const, Init: &ConstantExpression{Opcode: OpcodeF64Const,
Data: []byte{0, 0, 0, 0, 0, 0, 0xf0, 0x3f}}, // == float64(1.0) Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64))},
}, },
{ {
Type: &GlobalType{Mutable: false, ValType: ValueTypeI32}, Type: &GlobalType{Mutable: false, ValType: ValueTypeI32},
Init: &ConstantExpression{Opcode: OpcodeI32Const, Init: &ConstantExpression{Opcode: OpcodeI32Const,
Data: []byte{1}}, Data: leb128.EncodeUint32(math.MaxInt32)},
}, },
}} }}
globals := m.buildGlobals(nil) globals := m.buildGlobals(nil)
expectedGlobals := []*GlobalInstance{ expectedGlobals := []*GlobalInstance{
{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: math.Float64bits(1.0)}, {Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(math.MaxFloat64)},
{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(1)}, {Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: math.MaxInt32},
}
require.Len(t, globals, len(expectedGlobals))
for i := range globals {
actual, expected := globals[i], expectedGlobals[i]
require.Equal(t, expected, actual)
} }
require.Equal(t, expectedGlobals, globals)
} }
func TestModule_buildFunctionInstances(t *testing.T) { func TestModule_buildFunctionInstances(t *testing.T) {

View File

@@ -2,7 +2,6 @@ package wasm
import ( import (
"context" "context"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math" "math"
@@ -13,6 +12,7 @@ import (
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/testing/hammer" "github.com/tetratelabs/wazero/internal/testing/hammer"
"github.com/tetratelabs/wazero/internal/u64"
) )
func TestModuleInstance_Memory(t *testing.T) { func TestModuleInstance_Memory(t *testing.T) {
@@ -466,20 +466,19 @@ func TestExecuteConstExpression(t *testing.T) {
t.Run("non global expr", func(t *testing.T) { t.Run("non global expr", func(t *testing.T) {
for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} { for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
t.Run(ValueTypeName(vt), func(t *testing.T) { t.Run(ValueTypeName(vt), func(t *testing.T) {
// Allocate bytes with enough size for all types. expr := &ConstantExpression{}
expr := &ConstantExpression{Data: make([]byte, 8)}
switch vt { switch vt {
case ValueTypeI32: case ValueTypeI32:
expr.Data[0] = 1 expr.Data = []byte{1}
expr.Opcode = OpcodeI32Const expr.Opcode = OpcodeI32Const
case ValueTypeI64: case ValueTypeI64:
expr.Data[0] = 2 expr.Data = []byte{2}
expr.Opcode = OpcodeI64Const expr.Opcode = OpcodeI64Const
case ValueTypeF32: case ValueTypeF32:
binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32)) expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
expr.Opcode = OpcodeF32Const expr.Opcode = OpcodeF32Const
case ValueTypeF64: case ValueTypeF64:
binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64)) expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
expr.Opcode = OpcodeF64Const expr.Opcode = OpcodeF64Const
} }

View File

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

View File

@@ -15,6 +15,7 @@ import (
"github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128" "github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasm/binary" "github.com/tetratelabs/wazero/internal/wasm/binary"
"github.com/tetratelabs/wazero/internal/wasm/interpreter" "github.com/tetratelabs/wazero/internal/wasm/interpreter"
@@ -251,14 +252,14 @@ func addSpectestModule(t *testing.T, store *wasm.Store) {
// (global (export "global_f32") f32 (f32.const 666)) // (global (export "global_f32") f32 (f32.const 666))
mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{ mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: leb128.EncodeUint64(api.EncodeF32(666))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(666))},
}) })
mod.ExportSection["global_f32"] = &wasm.Export{Name: "global_f32", Index: 1, Type: wasm.ExternTypeGlobal} mod.ExportSection["global_f32"] = &wasm.Export{Name: "global_f32", Index: 1, Type: wasm.ExternTypeGlobal}
// (global (export "global_f64") f64 (f64.const 666)) // (global (export "global_f64") f64 (f64.const 666))
mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{ mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64}, Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: leb128.EncodeUint64(api.EncodeF64(666))}, Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(666))},
}) })
mod.ExportSection["global_f64"] = &wasm.Export{Name: "global_f64", Index: 2, Type: wasm.ExternTypeGlobal} mod.ExportSection["global_f64"] = &wasm.Export{Name: "global_f64", Index: 2, Type: wasm.ExternTypeGlobal}