Files
wazero/internal/engine/compiler/compiler_post1_0_test.go
Takeshi Yoneda 3b4544ee48 compiler: remove embedding of pointers of jump tables (#650)
This removes the embedding of pointers of jump tables (uintptr of []byte)
used by BrTable operations. That is the last usage of unsafe.Pointer in
compiler implementations.
Alternatively, we treat jump tables as asm.StaticConst and emit them
into the constPool already implemented and used by various places.

Notably, now the native code compiled by compilers can be reusable
across multiple processes, meaning that they are independent of
any runtime pointers.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2022-06-23 13:42:46 +09:00

965 lines
31 KiB
Go

package compiler
import (
"fmt"
"strconv"
"testing"
"unsafe"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wazeroir"
)
func TestCompiler_compileSignExtend(t *testing.T) {
type fromKind byte
from8, from16, from32 := fromKind(0), fromKind(1), fromKind(2)
t.Run("32bit", func(t *testing.T) {
tests := []struct {
in int32
expected int32
fromKind fromKind
}{
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L270-L276
{in: 0, expected: 0, fromKind: from8},
{in: 0x7f, expected: 127, fromKind: from8},
{in: 0x80, expected: -128, fromKind: from8},
{in: 0xff, expected: -1, fromKind: from8},
{in: 0x012345_00, expected: 0, fromKind: from8},
{in: -19088768 /* = 0xfedcba_80 bit pattern */, expected: -0x80, fromKind: from8},
{in: -1, expected: -1, fromKind: from8},
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L278-L284
{in: 0, expected: 0, fromKind: from16},
{in: 0x7fff, expected: 32767, fromKind: from16},
{in: 0x8000, expected: -32768, fromKind: from16},
{in: 0xffff, expected: -1, fromKind: from16},
{in: 0x0123_0000, expected: 0, fromKind: from16},
{in: -19103744 /* = 0xfedc_8000 bit pattern */, expected: -0x8000, fromKind: from16},
{in: -1, expected: -1, fromKind: from16},
}
for _, tt := range tests {
tc := tt
t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Setup the promote target.
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(tc.in)})
require.NoError(t, err)
if tc.fromKind == from8 {
err = compiler.compileSignExtend32From8()
} else {
err = compiler.compileSignExtend32From16()
}
require.NoError(t, err)
// To verify the behavior, we release the value
// to the stack.
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate and run the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, tc.expected, env.stackTopAsInt32())
})
}
})
t.Run("64bit", func(t *testing.T) {
tests := []struct {
in int64
expected int64
fromKind fromKind
}{
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L271-L277
{in: 0, expected: 0, fromKind: from8},
{in: 0x7f, expected: 127, fromKind: from8},
{in: 0x80, expected: -128, fromKind: from8},
{in: 0xff, expected: -1, fromKind: from8},
{in: 0x01234567_89abcd_00, expected: 0, fromKind: from8},
{in: 81985529216486784 /* = 0xfedcba98_765432_80 bit pattern */, expected: -0x80, fromKind: from8},
{in: -1, expected: -1, fromKind: from8},
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L279-L285
{in: 0, expected: 0, fromKind: from16},
{in: 0x7fff, expected: 32767, fromKind: from16},
{in: 0x8000, expected: -32768, fromKind: from16},
{in: 0xffff, expected: -1, fromKind: from16},
{in: 0x12345678_9abc_0000, expected: 0, fromKind: from16},
{in: 81985529216466944 /* = 0xfedcba98_7654_8000 bit pattern */, expected: -0x8000, fromKind: from16},
{in: -1, expected: -1, fromKind: from16},
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L287-L296
{in: 0, expected: 0, fromKind: from32},
{in: 0x7fff, expected: 32767, fromKind: from32},
{in: 0x8000, expected: 32768, fromKind: from32},
{in: 0xffff, expected: 65535, fromKind: from32},
{in: 0x7fffffff, expected: 0x7fffffff, fromKind: from32},
{in: 0x80000000, expected: -0x80000000, fromKind: from32},
{in: 0xffffffff, expected: -1, fromKind: from32},
{in: 0x01234567_00000000, expected: 0, fromKind: from32},
{in: -81985529054232576 /* = 0xfedcba98_80000000 bit pattern */, expected: -0x80000000, fromKind: from32},
{in: -1, expected: -1, fromKind: from32},
}
for _, tt := range tests {
tc := tt
t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Setup the promote target.
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(tc.in)})
require.NoError(t, err)
if tc.fromKind == from8 {
err = compiler.compileSignExtend64From8()
} else if tc.fromKind == from16 {
err = compiler.compileSignExtend64From16()
} else {
err = compiler.compileSignExtend64From32()
}
require.NoError(t, err)
// To verify the behavior, we release the value
// to the stack.
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate and run the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, tc.expected, env.stackTopAsInt64())
})
}
})
}
func TestCompiler_compileMemoryCopy(t *testing.T) {
const checkCeil = 100
tests := []struct {
sourceOffset, destOffset, size uint32
requireOutOfBoundsError bool
}{
{sourceOffset: 0, destOffset: 0, size: 0},
{sourceOffset: 10, destOffset: 5, size: 10},
{sourceOffset: 10, destOffset: 9, size: 1},
{sourceOffset: 10, destOffset: 9, size: 2},
{sourceOffset: 0, destOffset: 10, size: 10},
{sourceOffset: 0, destOffset: 5, size: 10},
{sourceOffset: 9, destOffset: 10, size: 10},
{sourceOffset: 11, destOffset: 13, size: 4},
{sourceOffset: 0, destOffset: 10, size: 5},
{sourceOffset: 1, destOffset: 10, size: 5},
{sourceOffset: 0, destOffset: 10, size: 1},
{sourceOffset: 0, destOffset: 10, size: 0},
{sourceOffset: 5, destOffset: 10, size: 10},
{sourceOffset: 5, destOffset: 10, size: 5},
{sourceOffset: 5, destOffset: 10, size: 1},
{sourceOffset: 5, destOffset: 10, size: 0},
{sourceOffset: 10, destOffset: 0, size: 10},
{sourceOffset: 1, destOffset: 0, size: 2},
{sourceOffset: 1, destOffset: 0, size: 20},
{sourceOffset: 10, destOffset: 0, size: 5},
{sourceOffset: 10, destOffset: 0, size: 1},
{sourceOffset: 10, destOffset: 0, size: 0},
{sourceOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
{sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true},
{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true},
{sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
// Compile operands.
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size})
require.NoError(t, err)
err = compiler.compileMemoryCopy()
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Setup the source memory region.
mem := env.memory()
for i := 0; i < checkCeil; i++ {
mem[i] = byte(i)
}
// Run code.
env.exec(code)
if !tc.requireOutOfBoundsError {
exp := make([]byte, checkCeil)
for i := 0; i < checkCeil; i++ {
exp[i] = byte(i)
}
copy(exp[tc.destOffset:],
exp[tc.sourceOffset:tc.sourceOffset+tc.size])
// Check the status code and the destination memory region.
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, exp, mem[:checkCeil])
} else {
require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
}
})
}
}
func TestCompiler_compileMemoryFill(t *testing.T) {
const checkCeil = 50
tests := []struct {
v, destOffset uint32
size uint32
requireOutOfBoundsError bool
}{
{v: 0, destOffset: 10, size: 10},
{v: 0, destOffset: 10, size: 5},
{v: 0, destOffset: 10, size: 1},
{v: 0, destOffset: 10, size: 0},
{v: 5, destOffset: 10, size: 10},
{v: 5, destOffset: 10, size: 5},
{v: 5, destOffset: 10, size: 1},
{v: 5, destOffset: 10, size: 0},
{v: 10, destOffset: 0, size: 10},
{v: 10, destOffset: 0, size: 5},
{v: 10, destOffset: 0, size: 1},
{v: 10, destOffset: 0, size: 0},
{v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true},
{v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 5, requireOutOfBoundsError: true},
{v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true},
{v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
// Compile operands.
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.v})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size})
require.NoError(t, err)
err = compiler.compileMemoryFill()
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Setup the memory region.
mem := env.memory()
for i := 0; i < checkCeil; i++ {
mem[i] = byte(i)
}
// Run code.
env.exec(code)
if !tc.requireOutOfBoundsError {
exp := make([]byte, checkCeil)
for i := 0; i < checkCeil; i++ {
if i >= int(tc.destOffset) && i < int(tc.destOffset+tc.size) {
exp[i] = byte(tc.v)
} else {
exp[i] = byte(i)
}
}
// Check the status code and the destination memory region.
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, exp, mem[:checkCeil])
} else {
require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
}
})
}
}
func TestCompiler_compileDataDrop(t *testing.T) {
origins := [][]byte{
{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10},
}
for i := 0; i < len(origins); i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
env.module().DataInstances = make([][]byte, len(origins))
copy(env.module().DataInstances, origins)
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
NeedsAccessToDataInstances: true, Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
err = compiler.compileDataDrop(&wazeroir.OperationDataDrop{
DataIndex: uint32(i),
})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
// Check if the target data instance is dropped from the dataInstances slice.
for j := 0; j < len(origins); j++ {
if i == j {
require.Nil(t, env.module().DataInstances[j])
} else {
require.NotNil(t, env.module().DataInstances[j])
}
}
})
}
}
func TestCompiler_compileMemoryInit(t *testing.T) {
dataInstances := []wasm.DataInstance{
nil, {1, 2, 3, 4, 5},
}
tests := []struct {
sourceOffset, destOffset uint32
dataIndex uint32
copySize uint32
expOutOfBounds bool
}{
{sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 0},
{sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 0, expOutOfBounds: true},
{sourceOffset: 1, destOffset: 0, copySize: 0, dataIndex: 0, expOutOfBounds: true},
{sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 5, dataIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 3, dataIndex: 1},
{sourceOffset: 0, destOffset: 1, copySize: 3, dataIndex: 1},
{sourceOffset: 0, destOffset: 7, copySize: 4, dataIndex: 1},
{sourceOffset: 1, destOffset: 7, copySize: 4, dataIndex: 1},
{sourceOffset: 4, destOffset: 7, copySize: 1, dataIndex: 1},
{sourceOffset: 5, destOffset: 7, copySize: 0, dataIndex: 1},
{sourceOffset: 0, destOffset: 7, copySize: 5, dataIndex: 1},
{sourceOffset: 1, destOffset: 0, copySize: 3, dataIndex: 1},
{sourceOffset: 0, destOffset: 1, copySize: 4, dataIndex: 1},
{sourceOffset: 1, destOffset: 1, copySize: 3, dataIndex: 1},
{sourceOffset: 0, destOffset: 10, copySize: 5, dataIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 6, dataIndex: 1, expOutOfBounds: true},
{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, copySize: 5, dataIndex: 1, expOutOfBounds: true},
{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 3, copySize: 5, dataIndex: 1, expOutOfBounds: true},
{sourceOffset: 6, destOffset: 0, copySize: 0, dataIndex: 1, expOutOfBounds: true},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
env.module().DataInstances = dataInstances
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
NeedsAccessToDataInstances: true, HasMemory: true,
Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
// Compile operands.
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.copySize})
require.NoError(t, err)
err = compiler.compileMemoryInit(&wazeroir.OperationMemoryInit{
DataIndex: tc.dataIndex,
})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
if !tc.expOutOfBounds {
mem := env.memory()
exp := make([]byte, defaultMemoryPageNumInTest*wasm.MemoryPageSize)
if dataInst := dataInstances[tc.dataIndex]; dataInst != nil {
copy(exp[tc.destOffset:], dataInst[tc.sourceOffset:tc.sourceOffset+tc.copySize])
}
require.Equal(t, exp[:20], mem[:20])
} else {
require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
}
})
}
}
func TestCompiler_compileElemDrop(t *testing.T) {
origins := []wasm.ElementInstance{
{References: []wasm.Reference{1}},
{References: []wasm.Reference{2}},
{References: []wasm.Reference{3}},
{References: []wasm.Reference{4}},
{References: []wasm.Reference{5}},
}
for i := 0; i < len(origins); i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
insts := make([]wasm.ElementInstance, len(origins))
copy(insts, origins)
env.module().ElementInstances = insts
// Verify assumption that before Drop instruction, all the element instances are not empty.
for _, inst := range insts {
require.NotEqual(t, 0, len(inst.References))
}
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
NeedsAccessToElementInstances: true, Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
err = compiler.compileElemDrop(&wazeroir.OperationElemDrop{
ElemIndex: uint32(i),
})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
for j := 0; j < len(insts); j++ {
if i == j {
require.Zero(t, len(env.module().ElementInstances[j].References))
} else {
require.NotEqual(t, 0, len(env.module().ElementInstances[j].References))
}
}
})
}
}
func TestCompiler_compileTableCopy(t *testing.T) {
const tableSize = 100
tests := []struct {
sourceOffset, destOffset, size uint32
requireOutOfBoundsError bool
}{
{sourceOffset: 0, destOffset: 0, size: 0},
{sourceOffset: 10, destOffset: 5, size: 10},
{sourceOffset: 10, destOffset: 9, size: 1},
{sourceOffset: 10, destOffset: 9, size: 2},
{sourceOffset: 0, destOffset: 10, size: 10},
{sourceOffset: 0, destOffset: 5, size: 10},
{sourceOffset: 9, destOffset: 10, size: 10},
{sourceOffset: 11, destOffset: 13, size: 4},
{sourceOffset: 0, destOffset: 10, size: 5},
{sourceOffset: 1, destOffset: 10, size: 5},
{sourceOffset: 0, destOffset: 10, size: 1},
{sourceOffset: 0, destOffset: 10, size: 0},
{sourceOffset: 5, destOffset: 10, size: 10},
{sourceOffset: 5, destOffset: 10, size: 5},
{sourceOffset: 5, destOffset: 10, size: 1},
{sourceOffset: 5, destOffset: 10, size: 0},
{sourceOffset: 10, destOffset: 0, size: 10},
{sourceOffset: 1, destOffset: 0, size: 2},
{sourceOffset: 1, destOffset: 0, size: 20},
{sourceOffset: 10, destOffset: 0, size: 5},
{sourceOffset: 10, destOffset: 0, size: 1},
{sourceOffset: 10, destOffset: 0, size: 0},
{sourceOffset: tableSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
{sourceOffset: tableSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
{sourceOffset: 0, destOffset: tableSize, size: 1, requireOutOfBoundsError: true},
{sourceOffset: 0, destOffset: tableSize + 1, size: 0, requireOutOfBoundsError: true},
{sourceOffset: tableSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
{sourceOffset: 0, destOffset: tableSize - 99, size: 100, requireOutOfBoundsError: true},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasTable: true, Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
// Compile operands.
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size})
require.NoError(t, err)
err = compiler.compileTableCopy(&wazeroir.OperationTableCopy{})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Setup the table.
table := make([]wasm.Reference, tableSize)
env.addTable(&wasm.TableInstance{References: table})
for i := 0; i < tableSize; i++ {
table[i] = uintptr(i)
}
// Run code.
env.exec(code)
if !tc.requireOutOfBoundsError {
exp := make([]wasm.Reference, tableSize)
for i := 0; i < tableSize; i++ {
exp[i] = uintptr(i)
}
copy(exp[tc.destOffset:],
exp[tc.sourceOffset:tc.sourceOffset+tc.size])
// Check the status code and the destination memory region.
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, exp, table)
} else {
require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
}
})
}
}
func TestCompiler_compileTableInit(t *testing.T) {
elementInstances := []wasm.ElementInstance{
{}, {References: []wasm.Reference{1, 2, 3, 4, 5}},
}
const tableSize = 100
tests := []struct {
sourceOffset, destOffset uint32
elemIndex uint32
copySize uint32
expOutOfBounds bool
}{
{sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 0},
{sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 0, expOutOfBounds: true},
{sourceOffset: 1, destOffset: 0, copySize: 0, elemIndex: 0, expOutOfBounds: true},
{sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 5, elemIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 3, elemIndex: 1},
{sourceOffset: 0, destOffset: 1, copySize: 3, elemIndex: 1},
{sourceOffset: 0, destOffset: 7, copySize: 4, elemIndex: 1},
{sourceOffset: 1, destOffset: 7, copySize: 4, elemIndex: 1},
{sourceOffset: 4, destOffset: 7, copySize: 1, elemIndex: 1},
{sourceOffset: 5, destOffset: 7, copySize: 0, elemIndex: 1},
{sourceOffset: 0, destOffset: 7, copySize: 5, elemIndex: 1},
{sourceOffset: 1, destOffset: 0, copySize: 3, elemIndex: 1},
{sourceOffset: 0, destOffset: 1, copySize: 4, elemIndex: 1},
{sourceOffset: 1, destOffset: 1, copySize: 3, elemIndex: 1},
{sourceOffset: 0, destOffset: 10, copySize: 5, elemIndex: 1},
{sourceOffset: 0, destOffset: 0, copySize: 6, elemIndex: 1, expOutOfBounds: true},
{sourceOffset: 6, destOffset: 0, copySize: 0, elemIndex: 1, expOutOfBounds: true},
}
for i, tt := range tests {
tc := tt
t.Run(strconv.Itoa(i), func(t *testing.T) {
env := newCompilerEnvironment()
env.module().ElementInstances = elementInstances
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
NeedsAccessToElementInstances: true, HasTable: true,
Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
// Compile operands.
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.copySize})
require.NoError(t, err)
err = compiler.compileTableInit(&wazeroir.OperationTableInit{
ElemIndex: tc.elemIndex,
})
require.NoError(t, err)
// Setup the table.
table := make([]wasm.Reference, tableSize)
env.addTable(&wasm.TableInstance{References: table})
for i := 0; i < tableSize; i++ {
table[i] = uintptr(i)
}
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
if !tc.expOutOfBounds {
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
exp := make([]wasm.Reference, tableSize)
for i := 0; i < tableSize; i++ {
exp[i] = uintptr(i)
}
if inst := elementInstances[tc.elemIndex]; inst.References != nil {
copy(exp[tc.destOffset:], inst.References[tc.sourceOffset:tc.sourceOffset+tc.copySize])
}
require.Equal(t, exp, table)
} else {
require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
}
})
}
}
type dog struct{ name string }
func TestCompiler_compileTableSet(t *testing.T) {
externDog := &dog{name: "sushi"}
externrefOpaque := uintptr(unsafe.Pointer(externDog))
funcref := &function{source: &wasm.FunctionInstance{DebugName: "sushi"}}
funcrefOpaque := uintptr(unsafe.Pointer(funcref))
externTable := &wasm.TableInstance{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}}
funcrefTable := &wasm.TableInstance{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}}
tables := []*wasm.TableInstance{externTable, funcrefTable}
tests := []struct {
name string
tableIndex uint32
offset uint32
in uintptr
expExtern bool
expError bool
}{
{
name: "externref - non nil",
tableIndex: 0,
offset: 2,
in: externrefOpaque,
expExtern: true,
},
{
name: "externref - nil",
tableIndex: 0,
offset: 1,
in: 0,
expExtern: true,
},
{
name: "externref - out of bounds",
tableIndex: 0,
offset: 10,
in: 0,
expError: true,
},
{
name: "funcref - non nil",
tableIndex: 1,
offset: 4,
in: funcrefOpaque,
expExtern: false,
},
{
name: "funcref - nil",
tableIndex: 1,
offset: 3,
in: 0,
expExtern: false,
},
{
name: "funcref - out of bounds",
tableIndex: 1,
offset: 100000,
in: 0,
expError: true,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
env := newCompilerEnvironment()
for _, table := range tables {
env.addTable(table)
}
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
HasTable: true,
Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.offset})
require.NoError(t, err)
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(tc.in)})
require.NoError(t, err)
err = compiler.compileTableSet(&wazeroir.OperationTableSet{TableIndex: tc.tableIndex})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
if tc.expError {
require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
} else {
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, uint64(0), env.stackPointer())
if tc.expExtern {
actual := dogFromPtr(externTable.References[tc.offset])
exp := externDog
if tc.in == 0 {
exp = nil
}
require.Equal(t, exp, actual)
} else {
actual := functionFromPtr(funcrefTable.References[tc.offset])
exp := funcref
if tc.in == 0 {
exp = nil
}
require.Equal(t, exp, actual)
}
}
})
}
}
//go:nocheckptr ignore "pointer arithmetic result points to invalid allocation"
func dogFromPtr(ptr uintptr) *dog {
if ptr == 0 {
return nil
}
return (*dog)(unsafe.Pointer(ptr))
}
//go:nocheckptr ignore "pointer arithmetic result points to invalid allocation"
func functionFromPtr(ptr uintptr) *function {
if ptr == 0 {
return nil
}
return (*function)(unsafe.Pointer(ptr))
}
func TestCompiler_compileTableGet(t *testing.T) {
externDog := &dog{name: "sushi"}
externrefOpaque := uintptr(unsafe.Pointer(externDog))
funcref := &function{source: &wasm.FunctionInstance{DebugName: "sushi"}}
funcrefOpaque := uintptr(unsafe.Pointer(funcref))
tables := []*wasm.TableInstance{
{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}},
{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}},
}
tests := []struct {
name string
tableIndex uint32
offset uint32
exp uintptr
expError bool
}{
{
name: "externref - non nil",
tableIndex: 0,
offset: 2,
exp: externrefOpaque,
},
{
name: "externref - nil",
tableIndex: 0,
offset: 4,
exp: 0,
},
{
name: "externref - out of bounds",
tableIndex: 0,
offset: 5,
expError: true,
},
{
name: "funcref - non nil",
tableIndex: 1,
offset: 4,
exp: funcrefOpaque,
},
{
name: "funcref - nil",
tableIndex: 1,
offset: 1,
exp: 0,
},
{
name: "funcref - out of bounds",
tableIndex: 1,
offset: 1000,
expError: true,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
env := newCompilerEnvironment()
for _, table := range tables {
env.addTable(table)
}
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
HasTable: true,
Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.offset})
require.NoError(t, err)
err = compiler.compileTableGet(&wazeroir.OperationTableGet{TableIndex: tc.tableIndex})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
if tc.expError {
require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
} else {
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, uint64(tc.exp), env.stackTopAsUint64())
}
})
}
}
func TestCompiler_compileRefFunc(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
me := env.moduleEngine()
const numFuncs = 20
for i := 0; i < numFuncs; i++ {
me.functions = append(me.functions, &function{source: &wasm.FunctionInstance{DebugName: strconv.Itoa(i)}})
}
for i := 0; i < numFuncs; i++ {
i := i
t.Run(strconv.Itoa(i), func(t *testing.T) {
compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{Signature: &wasm.FunctionType{}})
err := compiler.compilePreamble()
require.NoError(t, err)
err = compiler.compileRefFunc(&wazeroir.OperationRefFunc{FunctionIndex: uint32(i)})
require.NoError(t, err)
// Generate the code under test.
err = compiler.compileReturnFunction()
require.NoError(t, err)
code, _, err := compiler.compile()
require.NoError(t, err)
// Run code.
env.exec(code)
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, uintptr(unsafe.Pointer(me.functions[i])), uintptr(env.stackTopAsUint64()))
})
}
}