package compiler import ( "testing" "github.com/tetratelabs/wazero/internal/asm" "github.com/tetratelabs/wazero/internal/testing/require" "github.com/tetratelabs/wazero/internal/wasm" "github.com/tetratelabs/wazero/internal/wazeroir" ) func TestCompiler_compileGlobalGet(t *testing.T) { const globalValue uint64 = 12345 for _, tp := range []wasm.ValueType{ wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref, } { tp := tp t.Run(wasm.ValueTypeName(tp), func(t *testing.T) { env := newCompilerEnvironment() compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ Globals: []wasm.GlobalType{{}, {ValType: tp}}, }) // Setup the global. (start with nil as a dummy so that global index can be non-trivial.) globals := []*wasm.GlobalInstance{nil, {Val: globalValue, Type: wasm.GlobalType{ValType: tp}}} env.addGlobals(globals...) // Emit the code. err := compiler.compilePreamble() require.NoError(t, err) op := operationPtr(wazeroir.NewOperationGlobalGet(1)) err = compiler.compileGlobalGet(op) require.NoError(t, err) // At this point, the top of stack must be the retrieved global on a register. global := compiler.runtimeValueLocationStack().peek() require.True(t, global.onRegister()) require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list())) switch tp { case wasm.ValueTypeF32, wasm.ValueTypeF64: require.True(t, isVectorRegister(global.register)) case wasm.ValueTypeI32, wasm.ValueTypeI64: require.True(t, isGeneralPurposeRegister(global.register)) } err = compiler.compileReturnFunction() require.NoError(t, err) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) // Run the code assembled above. env.exec(code.Bytes()) // Since we call global.get, the top of the stack must be the global value. require.Equal(t, globalValue, env.stackTopAsUint64()) // Plus as we push the value, the stack pointer must be incremented. require.Equal(t, uint64(1), env.stackPointer()) }) } } func TestCompiler_compileGlobalGet_v128(t *testing.T) { const v128Type = wasm.ValueTypeV128 env := newCompilerEnvironment() compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ Globals: []wasm.GlobalType{{}, {ValType: v128Type}}, }) // Setup the global. (start with nil as a dummy so that global index can be non-trivial.) globals := []*wasm.GlobalInstance{nil, {Val: 12345, ValHi: 6789, Type: wasm.GlobalType{ValType: v128Type}}} env.addGlobals(globals...) // Emit the code. err := compiler.compilePreamble() require.NoError(t, err) op := operationPtr(wazeroir.NewOperationGlobalGet(1)) err = compiler.compileGlobalGet(op) require.NoError(t, err) // At this point, the top of stack must be the retrieved global on a register. global := compiler.runtimeValueLocationStack().peek() require.True(t, global.onRegister()) require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list())) require.True(t, isVectorRegister(global.register)) err = compiler.compileReturnFunction() require.NoError(t, err) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) // Run the code assembled above. env.exec(code.Bytes()) require.Equal(t, uint64(2), env.stackPointer()) require.Equal(t, nativeCallStatusCodeReturned, env.callEngine().statusCode) // Since we call global.get, the top of the stack must be the global value. actual := globals[1] sp := env.ce.stackContext.stackPointer stack := env.stack() require.Equal(t, actual.Val, stack[sp-2]) require.Equal(t, actual.ValHi, stack[sp-1]) } func TestCompiler_compileGlobalSet(t *testing.T) { const valueToSet uint64 = 12345 for _, tp := range []wasm.ValueType{ wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref, } { tp := tp t.Run(wasm.ValueTypeName(tp), func(t *testing.T) { env := newCompilerEnvironment() compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ Globals: []wasm.GlobalType{{}, {ValType: tp}}, }) // Setup the global. (start with nil as a dummy so that global index can be non-trivial.) env.addGlobals(nil, &wasm.GlobalInstance{Val: 40, Type: wasm.GlobalType{ValType: tp}}) err := compiler.compilePreamble() require.NoError(t, err) // Place the set target value. loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() switch tp { case wasm.ValueTypeI32: loc.valueType = runtimeValueTypeI32 case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref: loc.valueType = runtimeValueTypeI64 case wasm.ValueTypeF32: loc.valueType = runtimeValueTypeF32 case wasm.ValueTypeF64: loc.valueType = runtimeValueTypeF64 } env.stack()[loc.stackPointer] = valueToSet const index = 1 op := operationPtr(wazeroir.NewOperationGlobalSet(index)) err = compiler.compileGlobalSet(op) requireRuntimeLocationStackPointerEqual(t, 0, compiler) require.NoError(t, err) err = compiler.compileReturnFunction() require.NoError(t, err) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) // The global value should be set to valueToSet. actual := env.globals()[index] require.Equal(t, valueToSet, actual.Val) // Plus we consumed the top of the stack, the stack pointer must be decremented. require.Equal(t, uint64(0), env.stackPointer()) }) } } func TestCompiler_compileGlobalSet_v128(t *testing.T) { const v128Type = wasm.ValueTypeV128 const valueToSetLo, valueToSetHi uint64 = 0xffffff, 1 env := newCompilerEnvironment() compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ Globals: []wasm.GlobalType{{}, {ValType: v128Type}}, }) // Setup the global. (start with nil as a dummy so that global index can be non-trivial.) env.addGlobals(nil, &wasm.GlobalInstance{Val: 0, ValHi: 0, Type: wasm.GlobalType{ValType: v128Type}}) err := compiler.compilePreamble() require.NoError(t, err) // Place the set target value. lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() lo.valueType = runtimeValueTypeV128Lo env.stack()[lo.stackPointer] = valueToSetLo hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() hi.valueType = runtimeValueTypeV128Hi env.stack()[hi.stackPointer] = valueToSetHi const index = 1 op := operationPtr(wazeroir.NewOperationGlobalSet(index)) err = compiler.compileGlobalSet(op) requireRuntimeLocationStackPointerEqual(t, 0, compiler) require.NoError(t, err) err = compiler.compileReturnFunction() require.NoError(t, err) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) require.Equal(t, uint64(0), env.stackPointer()) require.Equal(t, nativeCallStatusCodeReturned, env.callEngine().statusCode) // The global value should be set to valueToSet. actual := env.globals()[index] require.Equal(t, valueToSetLo, actual.Val) require.Equal(t, valueToSetHi, actual.ValHi) }