Repository: tetratelabs/wazero
Pull request #442 (2022-04-05 11:33:50)

Fixes global numeric types to have max of signed encoding
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

codefromthecrypt: Note: I left function signatures allowing uints because they often use numbers as raw bytes or use unsigned types to hint something can never be negative.

Patch:
diff --git a/api/wasm.go b/api/wasm.go
index 408d4d09..3c32735d 100644
--- a/api/wasm.go
+++ b/api/wasm.go
@@ -31,9 +31,13 @@ import (
 type ValueType = byte
 
 const (
+	// ValueTypeI32 is a 32-bit integer.
 	ValueTypeI32 ValueType = 0x7f
+	// ValueTypeI64 is a 64-bit integer.
 	ValueTypeI64 ValueType = 0x7e
+	// ValueTypeF32 is a 32-bit floating point number.
 	ValueTypeF32 ValueType = 0x7d
+	// ValueTypeF64 is a 32-bit floating point number.
 	ValueTypeF64 ValueType = 0x7c
 )
 
diff --git a/builder.go b/builder.go
index 783eeab7..2f856447 100644
--- a/builder.go
+++ b/builder.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/u64"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
@@ -105,8 +106,10 @@ type ModuleBuilder interface {
 	//	builder.ExportGlobalI32("canvas_width", 1024)
 	//
 	// 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
-	ExportGlobalI32(name string, v uint32) ModuleBuilder
+	ExportGlobalI32(name string, v int32) ModuleBuilder
 
 	// ExportGlobalI64 exports a global constant of type api.ValueTypeI64.
 	//
@@ -115,8 +118,10 @@ type ModuleBuilder interface {
 	//	builder.ExportGlobalI64("start_epoch", 1620216263544)
 	//
 	// 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
-	ExportGlobalI64(name string, v uint64) ModuleBuilder
+	ExportGlobalI64(name string, v int64) ModuleBuilder
 
 	// 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
-func (b *moduleBuilder) ExportGlobalI32(name string, v uint32) ModuleBuilder {
+func (b *moduleBuilder) ExportGlobalI32(name string, v int32) ModuleBuilder {
 	b.nameToGlobal[name] = &wasm.Global{
 		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
 }
 
 // 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{
 		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
 }
@@ -215,7 +222,7 @@ func (b *moduleBuilder) ExportGlobalI64(name string, v uint64) ModuleBuilder {
 func (b *moduleBuilder) ExportGlobalF32(name string, v float32) ModuleBuilder {
 	b.nameToGlobal[name] = &wasm.Global{
 		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
 }
@@ -224,7 +231,7 @@ func (b *moduleBuilder) ExportGlobalF32(name string, v float32) ModuleBuilder {
 func (b *moduleBuilder) ExportGlobalF64(name string, v float64) ModuleBuilder {
 	b.nameToGlobal[name] = &wasm.Global{
 		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
 }
diff --git a/internal/ieee754/ieee754.go b/internal/ieee754/ieee754.go
index dd12d1e5..246cbcda 100644
--- a/internal/ieee754/ieee754.go
+++ b/internal/ieee754/ieee754.go
@@ -6,6 +6,8 @@ import (
 	"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) {
 	buf := make([]byte, 4)
 	_, err := io.ReadFull(r, buf)
@@ -16,6 +18,8 @@ func DecodeFloat32(r io.Reader) (float32, error) {
 	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) {
 	buf := make([]byte, 8)
 	_, err := io.ReadFull(r, buf)
diff --git a/internal/u64/u64.go b/internal/u64/u64.go
new file mode 100644
index 00000000..a47d9d60
--- /dev/null
+++ b/internal/u64/u64.go
@@ -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),
+	}
+}
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 19088ebf..ed08007a 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -447,6 +447,8 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*Glo
 			gv = api.EncodeF32(v)
 		case float64:
 			gv = api.EncodeF64(v)
+		default:
+			panic(fmt.Errorf("BUG: invalid conversion %d", v))
 		}
 		globals = append(globals, &GlobalInstance{Type: gs.Type, Val: gv})
 	}
diff --git a/internal/wasm/table.go b/internal/wasm/table.go
index fbeb42cd..34cd0bd8 100644
--- a/internal/wasm/table.go
+++ b/internal/wasm/table.go
@@ -113,7 +113,7 @@ func (m *Module) validateTable() ([]*validatedElementSegment, error) {
 
 			ret = append(ret, &validatedElementSegment{oc, globalIdx, elem.Init})
 		} 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 {
 				return nil, fmt.Errorf("%s[%d] couldn't read i32.const parameter: %w", SectionIDName(SectionIDElement), idx, err)
 			}


Test patch:
diff --git a/api/wasm_test.go b/api/wasm_test.go
index 9d0d82f7..456f958c 100644
--- a/api/wasm_test.go
+++ b/api/wasm_test.go
@@ -64,11 +64,11 @@ func TestEncodeDecodeF64(t *testing.T) {
 	} {
 		t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
 			encoded := EncodeF64(v)
-			binary := DecodeF64(encoded)
-			if math.IsNaN(binary) { // cannot use require.Equal as NaN by definition doesn't equal itself
-				require.True(t, math.IsNaN(binary))
+			val := DecodeF64(encoded)
+			if math.IsNaN(val) { // cannot use require.Equal as NaN by definition doesn't equal itself
+				require.True(t, math.IsNaN(val))
 			} else {
-				require.Equal(t, v, binary)
+				require.Equal(t, v, val)
 			}
 		})
 	}
diff --git a/builder_test.go b/builder_test.go
index b567a35a..62be33aa 100644
--- a/builder_test.go
+++ b/builder_test.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/u64"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
@@ -221,13 +222,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 		{
 			name: "ExportGlobalI32 overwrites",
 			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{
 				GlobalSection: []*wasm.Global{
 					{
 						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{
@@ -255,13 +256,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 		{
 			name: "ExportGlobalI64 overwrites",
 			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{
 				GlobalSection: []*wasm.Global{
 					{
 						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{
@@ -278,7 +279,7 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 				GlobalSection: []*wasm.Global{
 					{
 						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{
@@ -289,13 +290,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 		{
 			name: "ExportGlobalF32 overwrites",
 			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{
 				GlobalSection: []*wasm.Global{
 					{
 						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{
@@ -312,7 +313,7 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 				GlobalSection: []*wasm.Global{
 					{
 						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{
@@ -323,13 +324,13 @@ func TestNewModuleBuilder_Build(t *testing.T) {
 		{
 			name: "ExportGlobalF64 overwrites",
 			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{
 				GlobalSection: []*wasm.Global{
 					{
 						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{
diff --git a/internal/leb128/leb128_test.go b/internal/leb128/leb128_test.go
index a23d7d92..93e3f68e 100644
--- a/internal/leb128/leb128_test.go
+++ b/internal/leb128/leb128_test.go
@@ -58,6 +58,7 @@ func TestDecodeUint32(t *testing.T) {
 		{bytes: []byte{0x80, 0x7f}, exp: 16256},
 		{bytes: []byte{0xe5, 0x8e, 0x26}, exp: 624485},
 		{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{0x82, 0x80, 0x80, 0x80, 0x70}, 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{0xe5, 0x8e, 0x26}, exp: 624485},
 		{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},
 	} {
 		actual, num, err := DecodeUint64(bytes.NewReader(c.bytes))
diff --git a/internal/u64/u64_test.go b/internal/u64/u64_test.go
new file mode 100644
index 00000000..778318a2
--- /dev/null
+++ b/internal/u64/u64_test.go
@@ -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))
+		})
+	}
+}
diff --git a/internal/wasm/global_test.go b/internal/wasm/global_test.go
index 124ddeff..9aea570c 100644
--- a/internal/wasm/global_test.go
+++ b/internal/wasm/global_test.go
@@ -2,12 +2,13 @@ package wasm
 
 import (
 	"context"
-	gobinary "encoding/binary"
+	"math"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
 	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/u64"
 )
 
 func TestGlobalTypes(t *testing.T) {
@@ -26,6 +27,13 @@ func TestGlobalTypes(t *testing.T) {
 			expectedVal:    1,
 			expectedString: "global(1)",
 		},
+		{
+			name:           "i32 - immutable - max",
+			global:         globalI32(math.MaxInt32),
+			expectedType:   ValueTypeI32,
+			expectedVal:    math.MaxInt32,
+			expectedString: "global(2147483647)",
+		},
 		{
 			name:           "i64 - immutable",
 			global:         globalI64(1),
@@ -33,6 +41,13 @@ func TestGlobalTypes(t *testing.T) {
 			expectedVal:    1,
 			expectedString: "global(1)",
 		},
+		{
+			name:           "i64 - immutable - max",
+			global:         globalI64(math.MaxInt64),
+			expectedType:   ValueTypeI64,
+			expectedVal:    math.MaxInt64,
+			expectedString: "global(9223372036854775807)",
+		},
 		{
 			name:           "f32 - immutable",
 			global:         globalF32(api.EncodeF32(1.0)),
@@ -40,6 +55,13 @@ func TestGlobalTypes(t *testing.T) {
 			expectedVal:    api.EncodeF32(1.0),
 			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",
 			global:         globalF64(api.EncodeF64(1.0)),
@@ -47,6 +69,13 @@ func TestGlobalTypes(t *testing.T) {
 			expectedVal:    api.EncodeF64(1.0),
 			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",
 			global: &mutableGlobal{g: &GlobalInstance{
@@ -165,7 +194,7 @@ func TestPublicModule_Global(t *testing.T) {
 					{
 						Type: &GlobalType{ValType: ValueTypeF32},
 						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},
 						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},
 						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},
 						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
-}
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index 51a1b0c7..e4ffd13e 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -1,7 +1,6 @@
 package wasm
 
 import (
-	"encoding/binary"
 	"fmt"
 	"math"
 	"reflect"
@@ -10,6 +9,8 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/u64"
 )
 
 func TestFunctionType_String(t *testing.T) {
@@ -186,20 +187,19 @@ func TestValidateConstExpression(t *testing.T) {
 	for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
 		t.Run(ValueTypeName(vt), func(t *testing.T) {
 			t.Run("valid", func(t *testing.T) {
-				// Allocate bytes with enough size for all types.
-				expr := &ConstantExpression{Data: make([]byte, 8)}
+				expr := &ConstantExpression{}
 				switch vt {
 				case ValueTypeI32:
-					expr.Data[0] = 1
+					expr.Data = []byte{1}
 					expr.Opcode = OpcodeI32Const
 				case ValueTypeI64:
-					expr.Data[0] = 2
+					expr.Data = []byte{2}
 					expr.Opcode = OpcodeI64Const
 				case ValueTypeF32:
-					binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32))
+					expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
 					expr.Opcode = OpcodeF32Const
 				case ValueTypeF64:
-					binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64))
+					expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
 					expr.Opcode = OpcodeF64Const
 				}
 
@@ -661,32 +661,25 @@ func TestModule_validateExports(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{
 		{
 			Type: &GlobalType{Mutable: true, ValType: ValueTypeF64},
 			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},
 			Init: &ConstantExpression{Opcode: OpcodeI32Const,
-				Data: []byte{1}},
+				Data: leb128.EncodeUint32(math.MaxInt32)},
 		},
 	}}
 
 	globals := m.buildGlobals(nil)
 	expectedGlobals := []*GlobalInstance{
-		{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: math.Float64bits(1.0)},
-		{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(1)},
-	}
-
-	require.Len(t, globals, len(expectedGlobals))
-	for i := range globals {
-		actual, expected := globals[i], expectedGlobals[i]
-		require.Equal(t, expected, actual)
+		{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(math.MaxFloat64)},
+		{Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: math.MaxInt32},
 	}
+	require.Equal(t, expectedGlobals, globals)
 }
 
 func TestModule_buildFunctionInstances(t *testing.T) {
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index 40a682da..5b8b8e37 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -2,7 +2,6 @@ package wasm
 
 import (
 	"context"
-	"encoding/binary"
 	"errors"
 	"fmt"
 	"math"
@@ -13,6 +12,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/testing/hammer"
+	"github.com/tetratelabs/wazero/internal/u64"
 )
 
 func TestModuleInstance_Memory(t *testing.T) {
@@ -466,20 +466,19 @@ func TestExecuteConstExpression(t *testing.T) {
 	t.Run("non global expr", func(t *testing.T) {
 		for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
 			t.Run(ValueTypeName(vt), func(t *testing.T) {
-				// Allocate bytes with enough size for all types.
-				expr := &ConstantExpression{Data: make([]byte, 8)}
+				expr := &ConstantExpression{}
 				switch vt {
 				case ValueTypeI32:
-					expr.Data[0] = 1
+					expr.Data = []byte{1}
 					expr.Opcode = OpcodeI32Const
 				case ValueTypeI64:
-					expr.Data[0] = 2
+					expr.Data = []byte{2}
 					expr.Opcode = OpcodeI64Const
 				case ValueTypeF32:
-					binary.LittleEndian.PutUint32(expr.Data, math.Float32bits(math.MaxFloat32))
+					expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
 					expr.Opcode = OpcodeF32Const
 				case ValueTypeF64:
-					binary.LittleEndian.PutUint64(expr.Data, math.Float64bits(math.MaxFloat64))
+					expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
 					expr.Opcode = OpcodeF64Const
 				}
 
diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go
index 689f0ef9..03aef6fa 100644
--- a/tests/spectest/spec_test.go
+++ b/tests/spectest/spec_test.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/u64"
 	"github.com/tetratelabs/wazero/internal/wasm"
 	"github.com/tetratelabs/wazero/internal/wasm/binary"
 	"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))
 	mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
 		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}
 
 	// (global (export "global_f64") f64 (f64.const 666))
 	mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
 		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}