This moves to a new end-user API under the root package `wazero`. This
simplifies call sites while hardening function calls to their known
return value. Most importantly, this moves most logic internal, as
noted in the RATIONALE.md.
Ex.
```go
// Read WebAssembly binary containing an exported "fac" function.
source, _ := os.ReadFile("./tests/engine/testdata/fac.wasm")
// Decode the binary as WebAssembly module.
mod, _ := wazero.DecodeModuleBinary(source)
// Initialize the execution environment called "store" with Interpreter-based engine.
store := wazero.NewStore()
// Instantiate the module, which returns its exported functions
functions, _ := store.Instantiate(mod)
// Get the factorial function
fac, _ := functions.GetFunctionI64Return("fac")
// Discover 7! is 5040
fmt.Println(fac(context.Background(), 7))
```
PS I changed the README to factorial because the wat version of
fibonacci is not consistent with the TinyGo one!
Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Takaya Saeki <takaya@tetrate.io>
Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
451 lines
12 KiB
Go
451 lines
12 KiB
Go
package internalwasm
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMemoryInstance_HasLen(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: make([]byte, 100)}
|
|
|
|
tests := []struct {
|
|
name string
|
|
offset uint32
|
|
sizeInBytes uint64
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "simple valid arguments",
|
|
offset: 0, // arbitrary valid offset
|
|
sizeInBytes: 8, // arbitrary valid size
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "maximum valid sizeInBytes",
|
|
offset: memory.Len() - 8,
|
|
sizeInBytes: 8,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "sizeInBytes exceeds the valid size by 1",
|
|
offset: 100, // arbitrary valid offset
|
|
sizeInBytes: uint64(memory.Len() - 99),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "offset exceeds the memory size",
|
|
offset: memory.Len(),
|
|
sizeInBytes: 1, // arbitrary size
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expected, memory.hasLen(tc.offset, uint32(tc.sizeInBytes)))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_ReadUint32Le(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
memory []byte
|
|
offset uint32
|
|
expected uint32
|
|
expectedOk bool
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.MaxUint32,
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
memory: []byte{0xfe, 0xff, 0xff, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.MaxUint32 - 1,
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: 1,
|
|
memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00},
|
|
expected: 1, // arbitrary valid v
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff},
|
|
offset: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: tc.memory}
|
|
|
|
v, ok := memory.ReadUint32Le(tc.offset)
|
|
require.Equal(t, tc.expectedOk, ok)
|
|
require.Equal(t, tc.expected, v)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_ReadUint64Le(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
memory []byte
|
|
offset uint32
|
|
expected uint64
|
|
expectedOk bool
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.MaxUint64,
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
memory: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.MaxUint64 - 1,
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: 1,
|
|
memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
expected: 1, // arbitrary valid v
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
offset: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: tc.memory}
|
|
|
|
v, ok := memory.ReadUint64Le(tc.offset)
|
|
require.Equal(t, tc.expectedOk, ok)
|
|
require.Equal(t, tc.expected, v)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_ReadFloat32Le(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
memory []byte
|
|
offset uint32
|
|
expected float32
|
|
expectedOk bool
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
memory: []byte{0xff, 0x00, 0x00, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.Float32frombits(uint32(0xff0000ff)),
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
memory: []byte{0xfe, 0x00, 0x00, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.Float32frombits(uint32(0xff0000fe)),
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: 1,
|
|
memory: []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d},
|
|
expected: 0.1, // arbitrary valid v
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff},
|
|
offset: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: tc.memory}
|
|
|
|
v, ok := memory.ReadFloat32Le(tc.offset)
|
|
require.Equal(t, tc.expectedOk, ok)
|
|
require.Equal(t, tc.expected, v)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
memory []byte
|
|
offset uint32
|
|
expected float64
|
|
expectedOk bool
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
memory: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.Float64frombits(uint64(0xff000000000000ff)),
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
|
|
offset: 0, // arbitrary valid offset.
|
|
expected: math.MaxFloat64, // arbitrary valid v
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: 1,
|
|
memory: []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
|
|
expected: math.MaxFloat64, // arbitrary valid v
|
|
expectedOk: true,
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
offset: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: tc.memory}
|
|
|
|
v, ok := memory.ReadFloat64Le(tc.offset)
|
|
require.Equal(t, tc.expectedOk, ok)
|
|
require.Equal(t, tc.expected, v)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_WriteUint32Le(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: make([]byte, 100)}
|
|
|
|
tests := []struct {
|
|
name string
|
|
offset uint32
|
|
v uint32
|
|
expectedOk bool
|
|
expectedBytes []byte
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.MaxUint32,
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.MaxUint32 - 1,
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff},
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: memory.Len() - 4, // 4 is the size of uint32
|
|
v: 1, // arbitrary valid v
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0x1, 0x00, 0x00, 0x00},
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
offset: memory.Len() - 4 + 1, // 4 is the size of uint32
|
|
v: 1, // arbitrary valid v
|
|
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v))
|
|
if tc.expectedOk {
|
|
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_WriteUint64Le(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: make([]byte, 100)}
|
|
tests := []struct {
|
|
name string
|
|
offset uint32
|
|
v uint64
|
|
expectedOk bool
|
|
expectedBytes []byte
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.MaxUint64,
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.MaxUint64 - 1,
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: memory.Len() - 8, // 8 is the size of uint64
|
|
v: 1, // arbitrary valid v
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
offset: memory.Len() - 8 + 1, // 8 is the size of uint64
|
|
v: 1, // arbitrary valid v
|
|
expectedOk: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v))
|
|
if tc.expectedOk {
|
|
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: make([]byte, 100)}
|
|
|
|
tests := []struct {
|
|
name string
|
|
offset uint32
|
|
v float32
|
|
expectedOk bool
|
|
expectedBytes []byte
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.Float32frombits(uint32(0xff0000ff)),
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xff, 0x00, 0x00, 0xff},
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff},
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: memory.Len() - 4, // 4 is the size of float32
|
|
v: 0.1, // arbitrary valid v
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d},
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
offset: memory.Len() - 4 + 1, // 4 is the size of float32
|
|
v: math.MaxFloat32, // arbitrary valid v
|
|
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v))
|
|
if tc.expectedOk {
|
|
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
|
|
memory := &MemoryInstance{Buffer: make([]byte, 100)}
|
|
tests := []struct {
|
|
name string
|
|
offset uint32
|
|
v float64
|
|
expectedOk bool
|
|
expectedBytes []byte
|
|
}{
|
|
{
|
|
name: "valid offset with an endian-insensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.Float64frombits(uint64(0xff000000000000ff)),
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
|
|
},
|
|
{
|
|
name: "valid offset with an endian-sensitive v",
|
|
offset: 0, // arbitrary valid offset.
|
|
v: math.MaxFloat64, // arbitrary valid v
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
|
|
},
|
|
{
|
|
name: "maximum boundary valid offset",
|
|
offset: memory.Len() - 8, // 8 is the size of float64
|
|
v: math.MaxFloat64, // arbitrary valid v
|
|
expectedOk: true,
|
|
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
|
|
},
|
|
{
|
|
name: "offset exceeds the maximum valid offset by 1",
|
|
offset: memory.Len() - 8 + 1, // 8 is the size of float64
|
|
v: math.MaxFloat64, // arbitrary valid v
|
|
expectedOk: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v))
|
|
if tc.expectedOk {
|
|
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64
|
|
}
|
|
})
|
|
}
|
|
}
|