Files
wazero/internal/engine/compiler/compiler_initialization_test.go
Crypt Keeper c815060196 Renames JIT to Compiler and notes it is AOT (#564)
This notably changes NewRuntimeJIT to NewRuntimeCompiler as well renames
packages from jit to compiler.

This clarifies the implementation is AOT, not JIT, at least when
clarified to where it occurs (Runtime.CompileModule). In doing so, we
reduce any concern that compilation will happen during function
execution. We also free ourselves to create a JIT option without
confusion in the future via CompileConfig or otherwise.

Fixes #560

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-05-17 08:50:56 +09:00

251 lines
9.2 KiB
Go

package compiler
import (
"fmt"
"reflect"
"testing"
"unsafe"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wazeroir"
)
func TestCompiler_compileModuleContextInitialization(t *testing.T) {
for _, tc := range []struct {
name string
moduleInstance *wasm.ModuleInstance
}{
{
name: "no nil",
moduleInstance: &wasm.ModuleInstance{
Globals: []*wasm.GlobalInstance{{Val: 100}},
Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
Tables: []*wasm.TableInstance{
{References: make([]wasm.Reference, 20)},
{References: make([]wasm.Reference, 10)},
},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: make([][]byte, 10),
ElementInstances: make([]wasm.ElementInstance, 10),
},
},
{
name: "element instances nil",
moduleInstance: &wasm.ModuleInstance{
Globals: []*wasm.GlobalInstance{{Val: 100}},
Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
Tables: []*wasm.TableInstance{{References: make([]wasm.Reference, 20)}},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: make([][]byte, 10),
ElementInstances: nil,
},
},
{
name: "data instances nil",
moduleInstance: &wasm.ModuleInstance{
Globals: []*wasm.GlobalInstance{{Val: 100}},
Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
Tables: []*wasm.TableInstance{{References: make([]wasm.Reference, 20)}},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: nil,
ElementInstances: make([]wasm.ElementInstance, 10),
},
},
{
name: "globals nil",
moduleInstance: &wasm.ModuleInstance{
Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
Tables: []*wasm.TableInstance{{References: make([]wasm.Reference, 20)}},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: make([][]byte, 10),
ElementInstances: make([]wasm.ElementInstance, 10),
},
},
{
name: "memory nil",
moduleInstance: &wasm.ModuleInstance{
Globals: []*wasm.GlobalInstance{{Val: 100}},
Tables: []*wasm.TableInstance{{References: make([]wasm.Reference, 20)}},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: make([][]byte, 10),
ElementInstances: make([]wasm.ElementInstance, 10),
},
},
{
name: "table nil",
moduleInstance: &wasm.ModuleInstance{
Memory: &wasm.MemoryInstance{Buffer: make([]byte, 10)},
Tables: []*wasm.TableInstance{{References: nil}},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: make([][]byte, 10),
ElementInstances: make([]wasm.ElementInstance, 10),
},
},
{
name: "table empty",
moduleInstance: &wasm.ModuleInstance{
Tables: []*wasm.TableInstance{{References: make([]wasm.Reference, 20)}},
TypeIDs: make([]wasm.FunctionTypeID, 10),
DataInstances: make([][]byte, 10),
ElementInstances: make([]wasm.ElementInstance, 10),
},
},
{
name: "memory zero length",
moduleInstance: &wasm.ModuleInstance{
Memory: &wasm.MemoryInstance{Buffer: make([]byte, 0)},
},
},
{
name: "all nil except mod engine",
moduleInstance: &wasm.ModuleInstance{},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
env := newCompilerEnvironment()
env.moduleInstance = tc.moduleInstance
ce := env.callEngine()
ir := &wazeroir.CompilationResult{
HasMemory: tc.moduleInstance.Memory != nil,
HasTable: len(tc.moduleInstance.Tables) > 0,
NeedsAccessToDataInstances: len(tc.moduleInstance.DataInstances) > 0,
NeedsAccessToElementInstances: len(tc.moduleInstance.ElementInstances) > 0,
}
for _, g := range tc.moduleInstance.Globals {
ir.Globals = append(ir.Globals, g.Type)
}
compiler := env.requireNewCompiler(t, newCompiler, ir)
me := &moduleEngine{functions: make([]*function, 10)}
tc.moduleInstance.Engine = me
// The golang-asm assembler skips the first instruction, so we emit NOP here which is ignored.
// TODO: delete after #233
compiler.compileNOP()
err := compiler.compileModuleContextInitialization()
require.NoError(t, err)
require.Equal(t, 0, len(compiler.valueLocationStack().usedRegisters), "expected no usedRegisters")
compiler.compileExitFromNativeCode(compilerCallStatusCodeReturned)
// Generate the code under test.
code, _, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// Check the exit status.
require.Equal(t, compilerCallStatusCodeReturned, env.compilerStatus())
// Check if the fields of callEngine.moduleContext are updated.
bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Globals))
require.Equal(t, bufSliceHeader.Data, ce.moduleContext.globalElement0Address)
if tc.moduleInstance.Memory != nil {
bufSliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Memory.Buffer))
require.Equal(t, uint64(bufSliceHeader.Len), ce.moduleContext.memorySliceLen)
require.Equal(t, bufSliceHeader.Data, ce.moduleContext.memoryElement0Address)
}
if len(tc.moduleInstance.Tables) > 0 {
tableHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.Tables))
require.Equal(t, tableHeader.Data, ce.moduleContext.tablesElement0Address)
require.Equal(t, uintptr(unsafe.Pointer(&tc.moduleInstance.TypeIDs[0])), ce.moduleContext.typeIDsElement0Address)
require.Equal(t, uintptr(unsafe.Pointer(&tc.moduleInstance.Tables[0])), ce.moduleContext.tablesElement0Address)
}
if len(tc.moduleInstance.DataInstances) > 0 {
dataInstancesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.DataInstances))
require.Equal(t, dataInstancesHeader.Data, ce.moduleContext.dataInstancesElement0Address)
require.Equal(t, uintptr(unsafe.Pointer(&tc.moduleInstance.DataInstances[0])), ce.moduleContext.dataInstancesElement0Address)
}
if len(tc.moduleInstance.ElementInstances) > 0 {
elementInstancesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&tc.moduleInstance.ElementInstances))
require.Equal(t, elementInstancesHeader.Data, ce.moduleContext.elementInstancesElemen0Address)
require.Equal(t, uintptr(unsafe.Pointer(&tc.moduleInstance.ElementInstances[0])), ce.moduleContext.elementInstancesElemen0Address)
}
require.Equal(t, uintptr(unsafe.Pointer(&me.functions[0])), ce.moduleContext.functionsElement0Address)
})
}
}
func TestCompiler_compileMaybeGrowValueStack(t *testing.T) {
t.Run("not grow", func(t *testing.T) {
const stackPointerCeil = 5
for _, baseOffset := range []uint64{5, 10, 20} {
t.Run(fmt.Sprintf("%d", baseOffset), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
// The golang-asm assembler skips the first instruction, so we emit NOP here which is ignored.
// TODO: delete after #233
compiler.compileNOP()
err := compiler.compileMaybeGrowValueStack()
require.NoError(t, err)
require.NotNil(t, compiler.getOnStackPointerCeilDeterminedCallBack())
valueStackLen := uint64(len(env.stack()))
stackBasePointer := valueStackLen - baseOffset // Ceil <= valueStackLen - stackBasePointer = no need to grow!
compiler.getOnStackPointerCeilDeterminedCallBack()(stackPointerCeil)
env.setValueStackBasePointer(stackBasePointer)
compiler.compileExitFromNativeCode(compilerCallStatusCodeReturned)
// Generate and run the code under test.
code, _, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// The status code must be "Returned", not "BuiltinFunctionCall".
require.Equal(t, compilerCallStatusCodeReturned, env.compilerStatus())
})
}
})
t.Run("grow", func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
// The golang-asm assembler skips the first instruction, so we emit NOP here which is ignored.
// TODO: delete after #233
compiler.compileNOP()
err := compiler.compileMaybeGrowValueStack()
require.NoError(t, err)
// On the return from grow value stack, we simply return.
err = compiler.compileReturnFunction()
require.NoError(t, err)
stackPointerCeil := uint64(6)
compiler.setStackPointerCeil(stackPointerCeil)
valueStackLen := uint64(len(env.stack()))
stackBasePointer := valueStackLen - 5 // Ceil > valueStackLen - stackBasePointer = need to grow!
env.setValueStackBasePointer(stackBasePointer)
// Generate and run the code under test.
code, _, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// Check if the call exits with builtin function call status.
require.Equal(t, compilerCallStatusCodeCallBuiltInFunction, env.compilerStatus())
// Reenter from the return address.
returnAddress := env.callFrameStackPeek().returnAddress
require.True(t, returnAddress != 0, "returnAddress was non-zero %d", returnAddress)
compilercall(
returnAddress, uintptr(unsafe.Pointer(env.callEngine())),
uintptr(unsafe.Pointer(env.module())),
)
// Check the result. This should be "Returned".
require.Equal(t, compilerCallStatusCodeReturned, env.compilerStatus())
})
}