This introduces the new API wazero.Cache interface which can be passed to wazero.RuntimeConfig. Users can configure this to share the underlying compilation cache across multiple wazero.Runtime. And along the way, this deletes the experimental file cache API as it's replaced by this new API. Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io> Co-authored-by: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com>
576 lines
21 KiB
Go
576 lines
21 KiB
Go
package interpreter
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"testing"
|
|
"unsafe"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/experimental"
|
|
"github.com/tetratelabs/wazero/experimental/logging"
|
|
"github.com/tetratelabs/wazero/internal/testing/enginetest"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
"github.com/tetratelabs/wazero/internal/wazeroir"
|
|
)
|
|
|
|
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
|
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
|
|
|
func TestInterpreter_peekValues(t *testing.T) {
|
|
ce := &callEngine{}
|
|
require.Nil(t, ce.peekValues(0))
|
|
|
|
ce.stack = []uint64{5, 4, 3, 2, 1}
|
|
require.Nil(t, ce.peekValues(0))
|
|
require.Equal(t, []uint64{2, 1}, ce.peekValues(2))
|
|
}
|
|
|
|
func TestInterpreter_CallEngine_PushFrame(t *testing.T) {
|
|
f1 := &callFrame{}
|
|
f2 := &callFrame{}
|
|
|
|
ce := callEngine{}
|
|
require.Zero(t, len(ce.frames), "expected no frames")
|
|
|
|
ce.pushFrame(f1)
|
|
require.Equal(t, []*callFrame{f1}, ce.frames)
|
|
|
|
ce.pushFrame(f2)
|
|
require.Equal(t, []*callFrame{f1, f2}, ce.frames)
|
|
}
|
|
|
|
func TestInterpreter_CallEngine_PushFrame_StackOverflow(t *testing.T) {
|
|
saved := callStackCeiling
|
|
defer func() { callStackCeiling = saved }()
|
|
|
|
callStackCeiling = 3
|
|
|
|
f1 := &callFrame{}
|
|
f2 := &callFrame{}
|
|
f3 := &callFrame{}
|
|
f4 := &callFrame{}
|
|
|
|
vm := callEngine{}
|
|
vm.pushFrame(f1)
|
|
vm.pushFrame(f2)
|
|
vm.pushFrame(f3)
|
|
|
|
captured := require.CapturePanic(func() { vm.pushFrame(f4) })
|
|
require.EqualError(t, captured, "stack overflow")
|
|
}
|
|
|
|
// et is used for tests defined in the enginetest package.
|
|
var (
|
|
et = &engineTester{}
|
|
functionLog bytes.Buffer
|
|
listenerFactory = logging.NewLoggingListenerFactory(&functionLog)
|
|
)
|
|
|
|
// engineTester implements enginetest.EngineTester.
|
|
type engineTester struct{}
|
|
|
|
// IsCompiler implements enginetest.EngineTester NewEngine.
|
|
func (e engineTester) IsCompiler() bool {
|
|
return false
|
|
}
|
|
|
|
// ListenerFactory implements enginetest.EngineTester NewEngine.
|
|
func (e engineTester) ListenerFactory() experimental.FunctionListenerFactory {
|
|
return listenerFactory
|
|
}
|
|
|
|
// NewEngine implements enginetest.EngineTester NewEngine.
|
|
func (e engineTester) NewEngine(enabledFeatures api.CoreFeatures) wasm.Engine {
|
|
return NewEngine(context.Background(), enabledFeatures, nil)
|
|
}
|
|
|
|
// CompiledFunctionPointerValue implements enginetest.EngineTester CompiledFunctionPointerValue.
|
|
func (e engineTester) CompiledFunctionPointerValue(me wasm.ModuleEngine, funcIndex wasm.Index) uint64 {
|
|
internal := me.(*moduleEngine)
|
|
return uint64(uintptr(unsafe.Pointer(&internal.functions[funcIndex])))
|
|
}
|
|
|
|
func TestInterpreter_MemoryGrowInRecursiveCall(t *testing.T) {
|
|
defer functionLog.Reset()
|
|
enginetest.RunTestEngine_MemoryGrowInRecursiveCall(t, et)
|
|
}
|
|
|
|
func TestInterpreter_Engine_NewModuleEngine(t *testing.T) {
|
|
enginetest.RunTestEngine_NewModuleEngine(t, et)
|
|
}
|
|
|
|
func TestInterpreter_ModuleEngine_LookupFunction(t *testing.T) {
|
|
enginetest.RunTestModuleEngine_LookupFunction(t, et)
|
|
}
|
|
|
|
func TestInterpreter_ModuleEngine_Call(t *testing.T) {
|
|
defer functionLog.Reset()
|
|
enginetest.RunTestModuleEngine_Call(t, et)
|
|
require.Equal(t, `
|
|
--> .$0(1,2)
|
|
<-- (1,2)
|
|
`, "\n"+functionLog.String())
|
|
}
|
|
|
|
func TestInterpreter_ModuleEngine_Call_HostFn(t *testing.T) {
|
|
defer functionLog.Reset()
|
|
enginetest.RunTestModuleEngine_Call_HostFn(t, et)
|
|
}
|
|
|
|
func TestInterpreter_ModuleEngine_Call_Errors(t *testing.T) {
|
|
defer functionLog.Reset()
|
|
enginetest.RunTestModuleEngine_Call_Errors(t, et)
|
|
|
|
// TODO: Currently, the listener doesn't get notified on errors as they are
|
|
// implemented with panic. This means the end hooks aren't make resulting
|
|
// in dangling logs like this:
|
|
// ==> host.host_div_by(-1)
|
|
// instead of seeing a return like
|
|
// <== DivByZero
|
|
require.Equal(t, `
|
|
--> imported.div_by.wasm(1)
|
|
<-- 1
|
|
--> imported.div_by.wasm(1)
|
|
<-- 1
|
|
--> imported.div_by.wasm(0)
|
|
--> imported.div_by.wasm(1)
|
|
<-- 1
|
|
--> imported.call->div_by.go(-1)
|
|
==> host.div_by.go(-1)
|
|
--> imported.call->div_by.go(1)
|
|
==> host.div_by.go(1)
|
|
<== 1
|
|
<-- 1
|
|
--> importing.call_import->call->div_by.go(0)
|
|
--> imported.call->div_by.go(0)
|
|
==> host.div_by.go(0)
|
|
--> importing.call_import->call->div_by.go(1)
|
|
--> imported.call->div_by.go(1)
|
|
==> host.div_by.go(1)
|
|
<== 1
|
|
<-- 1
|
|
<-- 1
|
|
--> importing.call_import->call->div_by.go(-1)
|
|
--> imported.call->div_by.go(-1)
|
|
==> host.div_by.go(-1)
|
|
--> importing.call_import->call->div_by.go(1)
|
|
--> imported.call->div_by.go(1)
|
|
==> host.div_by.go(1)
|
|
<== 1
|
|
<-- 1
|
|
<-- 1
|
|
--> importing.call_import->call->div_by.go(0)
|
|
--> imported.call->div_by.go(0)
|
|
==> host.div_by.go(0)
|
|
--> importing.call_import->call->div_by.go(1)
|
|
--> imported.call->div_by.go(1)
|
|
==> host.div_by.go(1)
|
|
<== 1
|
|
<-- 1
|
|
<-- 1
|
|
`, "\n"+functionLog.String())
|
|
}
|
|
|
|
func TestInterpreter_ModuleEngine_Memory(t *testing.T) {
|
|
enginetest.RunTestModuleEngine_Memory(t, et)
|
|
}
|
|
|
|
func TestInterpreter_NonTrappingFloatToIntConversion(t *testing.T) {
|
|
_0x80000000 := uint32(0x80000000)
|
|
_0xffffffff := uint32(0xffffffff)
|
|
_0x8000000000000000 := uint64(0x8000000000000000)
|
|
_0xffffffffffffffff := uint64(0xffffffffffffffff)
|
|
|
|
tests := []struct {
|
|
op wasm.OpcodeMisc
|
|
inputType wazeroir.Float
|
|
outputType wazeroir.SignedInt
|
|
input32bit []float32
|
|
input64bit []float64
|
|
expected32bit []int32
|
|
expected64bit []int64
|
|
}{
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L261-L282
|
|
op: wasm.OpcodeMiscI32TruncSatF32S,
|
|
inputType: wazeroir.Float32,
|
|
outputType: wazeroir.SignedInt32,
|
|
input32bit: []float32{
|
|
0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, -1.0, -0x1.19999ap+0,
|
|
-1.5, -1.9, -2.0, 2147483520.0, -2147483648.0, 2147483648.0, -2147483904.0,
|
|
float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()),
|
|
float32(math.NaN()), float32(math.NaN()),
|
|
},
|
|
expected32bit: []int32{
|
|
0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 2147483520, -2147483648, 0x7fffffff,
|
|
int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0, 0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L284-L304
|
|
op: wasm.OpcodeMiscI32TruncSatF32U,
|
|
inputType: wazeroir.Float32,
|
|
outputType: wazeroir.SignedUint32,
|
|
input32bit: []float32{
|
|
0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 1.9, 2.0, 2147483648, 4294967040.0,
|
|
-0x1.ccccccp-1, -0x1.fffffep-1, 4294967296.0, -1.0, float32(math.Inf(1)), float32(math.Inf(-1)),
|
|
float32(math.NaN()), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()),
|
|
},
|
|
expected32bit: []int32{
|
|
0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -256, 0, 0, int32(_0xffffffff), 0x00000000,
|
|
int32(_0xffffffff), 0x00000000, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L355-L378
|
|
op: wasm.OpcodeMiscI64TruncSatF32S,
|
|
inputType: wazeroir.Float32,
|
|
outputType: wazeroir.SignedInt64,
|
|
input32bit: []float32{
|
|
0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, -1.0, -0x1.19999ap+0, -1.5, -1.9, -2.0, 4294967296,
|
|
-4294967296, 9223371487098961920.0, -9223372036854775808.0, 9223372036854775808.0, -9223373136366403584.0,
|
|
float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()),
|
|
float32(math.NaN()),
|
|
},
|
|
expected64bit: []int64{
|
|
0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 4294967296, -4294967296, 9223371487098961920, -9223372036854775808,
|
|
0x7fffffffffffffff, int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L380-L398
|
|
op: wasm.OpcodeMiscI64TruncSatF32U,
|
|
inputType: wazeroir.Float32,
|
|
outputType: wazeroir.SignedUint64,
|
|
input32bit: []float32{
|
|
0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 4294967296,
|
|
18446742974197923840.0, -0x1.ccccccp-1, -0x1.fffffep-1, 18446744073709551616.0, -1.0,
|
|
float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()),
|
|
float32(math.NaN()), float32(math.NaN()),
|
|
},
|
|
expected64bit: []int64{
|
|
0, 0, 0, 0, 1, 1, 1,
|
|
4294967296, -1099511627776, 0, 0, int64(_0xffffffffffffffff), 0x0000000000000000,
|
|
int64(_0xffffffffffffffff), 0x0000000000000000, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L306-L327
|
|
op: wasm.OpcodeMiscI32TruncSatF64S,
|
|
inputType: wazeroir.Float64,
|
|
outputType: wazeroir.SignedInt32,
|
|
input64bit: []float64{
|
|
0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0,
|
|
-0x1.199999999999ap+0, -1.5, -1.9, -2.0, 2147483647.0, -2147483648.0, 2147483648.0,
|
|
-2147483649.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
|
|
},
|
|
expected32bit: []int32{
|
|
0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2,
|
|
2147483647, -2147483648, 0x7fffffff, int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0,
|
|
0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L329-L353
|
|
op: wasm.OpcodeMiscI32TruncSatF64U,
|
|
inputType: wazeroir.Float64,
|
|
outputType: wazeroir.SignedUint32,
|
|
input64bit: []float64{
|
|
0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 1.9, 2.0,
|
|
2147483648, 4294967295.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 4294967296.0, -1.0, 1e16, 1e30,
|
|
9223372036854775808, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
|
|
},
|
|
expected32bit: []int32{
|
|
0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -1,
|
|
0, 0, 100000000, int32(_0xffffffff), 0x00000000, int32(_0xffffffff), int32(_0xffffffff), int32(_0xffffffff),
|
|
int32(_0xffffffff), 0x00000000, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L400-L423
|
|
op: wasm.OpcodeMiscI64TruncSatF64S,
|
|
inputType: wazeroir.Float64,
|
|
outputType: wazeroir.SignedInt64,
|
|
input64bit: []float64{
|
|
0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0,
|
|
-0x1.199999999999ap+0, -1.5, -1.9, -2.0, 4294967296, -4294967296, 9223372036854774784.0, -9223372036854775808.0,
|
|
9223372036854775808.0, -9223372036854777856.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(),
|
|
math.NaN(),
|
|
},
|
|
expected64bit: []int64{
|
|
0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2,
|
|
4294967296, -4294967296, 9223372036854774784, -9223372036854775808, 0x7fffffffffffffff,
|
|
int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0,
|
|
},
|
|
},
|
|
{
|
|
// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L425-L447
|
|
op: wasm.OpcodeMiscI64TruncSatF64U,
|
|
inputType: wazeroir.Float64,
|
|
outputType: wazeroir.SignedUint64,
|
|
input64bit: []float64{
|
|
0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 4294967295, 4294967296,
|
|
18446744073709549568.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 1e16, 9223372036854775808,
|
|
18446744073709551616.0, -1.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
|
|
},
|
|
expected64bit: []int64{
|
|
0, 0, 0, 0, 1, 1, 1, 0xffffffff, 0x100000000, -2048, 0, 0, 100000000, 10000000000000000,
|
|
-9223372036854775808, int64(_0xffffffffffffffff), 0x0000000000000000, int64(_0xffffffffffffffff),
|
|
0x0000000000000000, 0, 0, 0, 0,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(wasm.MiscInstructionName(tc.op), func(t *testing.T) {
|
|
in32bit := len(tc.input32bit) > 0
|
|
casenum := len(tc.input32bit)
|
|
if !in32bit {
|
|
casenum = len(tc.input64bit)
|
|
}
|
|
for i := 0; i < casenum; i++ {
|
|
i := i
|
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
var body []*interpreterOp
|
|
if in32bit {
|
|
body = append(body, &interpreterOp{
|
|
kind: wazeroir.OperationKindConstF32,
|
|
us: []uint64{uint64(math.Float32bits(tc.input32bit[i]))},
|
|
})
|
|
} else {
|
|
body = append(body, &interpreterOp{
|
|
kind: wazeroir.OperationKindConstF64,
|
|
us: []uint64{uint64(math.Float64bits(tc.input64bit[i]))},
|
|
})
|
|
}
|
|
|
|
body = append(body, &interpreterOp{
|
|
kind: wazeroir.OperationKindITruncFromF,
|
|
b1: byte(tc.inputType),
|
|
b2: byte(tc.outputType),
|
|
b3: true, // NonTrapping = true.
|
|
})
|
|
|
|
// Return from function.
|
|
body = append(body,
|
|
&interpreterOp{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
|
|
)
|
|
|
|
ce := &callEngine{}
|
|
f := &function{
|
|
source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
|
|
parent: &code{body: body},
|
|
}
|
|
ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
|
|
|
|
if len(tc.expected32bit) > 0 {
|
|
require.Equal(t, tc.expected32bit[i], int32(uint32(ce.popValue())))
|
|
} else {
|
|
require.Equal(t, tc.expected64bit[i], int64((ce.popValue())))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
|
|
translateToIROperationKind := func(op wasm.Opcode) (kind wazeroir.OperationKind) {
|
|
switch op {
|
|
case wasm.OpcodeI32Extend8S:
|
|
kind = wazeroir.OperationKindSignExtend32From8
|
|
case wasm.OpcodeI32Extend16S:
|
|
kind = wazeroir.OperationKindSignExtend32From16
|
|
case wasm.OpcodeI64Extend8S:
|
|
kind = wazeroir.OperationKindSignExtend64From8
|
|
case wasm.OpcodeI64Extend16S:
|
|
kind = wazeroir.OperationKindSignExtend64From16
|
|
case wasm.OpcodeI64Extend32S:
|
|
kind = wazeroir.OperationKindSignExtend64From32
|
|
}
|
|
return
|
|
}
|
|
t.Run("32bit", func(t *testing.T) {
|
|
tests := []struct {
|
|
in int32
|
|
expected int32
|
|
opcode wasm.Opcode
|
|
}{
|
|
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L270-L276
|
|
{in: 0, expected: 0, opcode: wasm.OpcodeI32Extend8S},
|
|
{in: 0x7f, expected: 127, opcode: wasm.OpcodeI32Extend8S},
|
|
{in: 0x80, expected: -128, opcode: wasm.OpcodeI32Extend8S},
|
|
{in: 0xff, expected: -1, opcode: wasm.OpcodeI32Extend8S},
|
|
{in: 0x012345_00, expected: 0, opcode: wasm.OpcodeI32Extend8S},
|
|
{in: -19088768 /* = 0xfedcba_80 bit pattern */, expected: -0x80, opcode: wasm.OpcodeI32Extend8S},
|
|
{in: -1, expected: -1, opcode: wasm.OpcodeI32Extend8S},
|
|
|
|
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L278-L284
|
|
{in: 0, expected: 0, opcode: wasm.OpcodeI32Extend16S},
|
|
{in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI32Extend16S},
|
|
{in: 0x8000, expected: -32768, opcode: wasm.OpcodeI32Extend16S},
|
|
{in: 0xffff, expected: -1, opcode: wasm.OpcodeI32Extend16S},
|
|
{in: 0x0123_0000, expected: 0, opcode: wasm.OpcodeI32Extend16S},
|
|
{in: -19103744 /* = 0xfedc_8000 bit pattern */, expected: -0x8000, opcode: wasm.OpcodeI32Extend16S},
|
|
{in: -1, expected: -1, opcode: wasm.OpcodeI32Extend16S},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(fmt.Sprintf("%s(i32.const(0x%x))", wasm.InstructionName(tc.opcode), tc.in), func(t *testing.T) {
|
|
ce := &callEngine{}
|
|
f := &function{
|
|
source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
|
|
parent: &code{body: []*interpreterOp{
|
|
{kind: wazeroir.OperationKindConstI32, us: []uint64{uint64(uint32(tc.in))}},
|
|
{kind: translateToIROperationKind(tc.opcode)},
|
|
{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
|
|
}},
|
|
}
|
|
ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
|
|
require.Equal(t, tc.expected, int32(uint32(ce.popValue())))
|
|
})
|
|
}
|
|
})
|
|
t.Run("64bit", func(t *testing.T) {
|
|
tests := []struct {
|
|
in int64
|
|
expected int64
|
|
opcode wasm.Opcode
|
|
}{
|
|
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L271-L277
|
|
{in: 0, expected: 0, opcode: wasm.OpcodeI64Extend8S},
|
|
{in: 0x7f, expected: 127, opcode: wasm.OpcodeI64Extend8S},
|
|
{in: 0x80, expected: -128, opcode: wasm.OpcodeI64Extend8S},
|
|
{in: 0xff, expected: -1, opcode: wasm.OpcodeI64Extend8S},
|
|
{in: 0x01234567_89abcd_00, expected: 0, opcode: wasm.OpcodeI64Extend8S},
|
|
{in: 81985529216486784 /* = 0xfedcba98_765432_80 bit pattern */, expected: -0x80, opcode: wasm.OpcodeI64Extend8S},
|
|
{in: -1, expected: -1, opcode: wasm.OpcodeI64Extend8S},
|
|
|
|
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L279-L285
|
|
{in: 0, expected: 0, opcode: wasm.OpcodeI64Extend16S},
|
|
{in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI64Extend16S},
|
|
{in: 0x8000, expected: -32768, opcode: wasm.OpcodeI64Extend16S},
|
|
{in: 0xffff, expected: -1, opcode: wasm.OpcodeI64Extend16S},
|
|
{in: 0x12345678_9abc_0000, expected: 0, opcode: wasm.OpcodeI64Extend16S},
|
|
{in: 81985529216466944 /* = 0xfedcba98_7654_8000 bit pattern */, expected: -0x8000, opcode: wasm.OpcodeI64Extend16S},
|
|
{in: -1, expected: -1, opcode: wasm.OpcodeI64Extend16S},
|
|
|
|
// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L287-L296
|
|
{in: 0, expected: 0, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0x8000, expected: 32768, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0xffff, expected: 65535, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0x7fffffff, expected: 0x7fffffff, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0x80000000, expected: -0x80000000, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0xffffffff, expected: -1, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: 0x01234567_00000000, expected: 0, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: -81985529054232576 /* = 0xfedcba98_80000000 bit pattern */, expected: -0x80000000, opcode: wasm.OpcodeI64Extend32S},
|
|
{in: -1, expected: -1, opcode: wasm.OpcodeI64Extend32S},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(fmt.Sprintf("%s(i64.const(0x%x))", wasm.InstructionName(tc.opcode), tc.in), func(t *testing.T) {
|
|
ce := &callEngine{}
|
|
f := &function{
|
|
source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
|
|
parent: &code{body: []*interpreterOp{
|
|
{kind: wazeroir.OperationKindConstI64, us: []uint64{uint64(tc.in)}},
|
|
{kind: translateToIROperationKind(tc.opcode)},
|
|
{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
|
|
}},
|
|
}
|
|
ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
|
|
require.Equal(t, tc.expected, int64(ce.popValue()))
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestInterpreter_Compile(t *testing.T) {
|
|
t.Run("uncompiled", func(t *testing.T) {
|
|
e := et.NewEngine(api.CoreFeaturesV1).(*engine)
|
|
_, err := e.NewModuleEngine("foo",
|
|
&wasm.Module{},
|
|
nil, // functions
|
|
)
|
|
require.EqualError(t, err, "source module for foo must be compiled before instantiation")
|
|
})
|
|
t.Run("fail", func(t *testing.T) {
|
|
e := et.NewEngine(api.CoreFeaturesV1).(*engine)
|
|
|
|
errModule := &wasm.Module{
|
|
TypeSection: []*wasm.FunctionType{{}},
|
|
FunctionSection: []wasm.Index{0, 0, 0},
|
|
CodeSection: []*wasm.Code{
|
|
{Body: []byte{wasm.OpcodeEnd}},
|
|
{Body: []byte{wasm.OpcodeEnd}},
|
|
{Body: []byte{wasm.OpcodeCall}}, // Call instruction without immediate for call target index is invalid and should fail to compile.
|
|
},
|
|
ID: wasm.ModuleID{},
|
|
}
|
|
errModule.BuildFunctionDefinitions()
|
|
|
|
err := e.CompileModule(testCtx, errModule, nil)
|
|
require.EqualError(t, err, "failed to lower func[.$2] to wazeroir: handling instruction: apply stack failed for call: reading immediates: EOF")
|
|
|
|
// On the compilation failure, all the compiled functions including succeeded ones must be released.
|
|
_, ok := e.codes[errModule.ID]
|
|
require.False(t, ok)
|
|
})
|
|
t.Run("ok", func(t *testing.T) {
|
|
e := et.NewEngine(api.CoreFeaturesV1).(*engine)
|
|
|
|
okModule := &wasm.Module{
|
|
TypeSection: []*wasm.FunctionType{{}},
|
|
FunctionSection: []wasm.Index{0, 0, 0, 0},
|
|
CodeSection: []*wasm.Code{
|
|
{Body: []byte{wasm.OpcodeEnd}},
|
|
{Body: []byte{wasm.OpcodeEnd}},
|
|
{Body: []byte{wasm.OpcodeEnd}},
|
|
{Body: []byte{wasm.OpcodeEnd}},
|
|
},
|
|
ID: wasm.ModuleID{},
|
|
}
|
|
err := e.CompileModule(testCtx, okModule, nil)
|
|
require.NoError(t, err)
|
|
|
|
compiled, ok := e.codes[okModule.ID]
|
|
require.True(t, ok)
|
|
require.Equal(t, len(okModule.FunctionSection), len(compiled))
|
|
|
|
_, ok = e.codes[okModule.ID]
|
|
require.True(t, ok)
|
|
})
|
|
}
|
|
|
|
func TestEngine_CachedcodesPerModule(t *testing.T) {
|
|
e := et.NewEngine(api.CoreFeaturesV1).(*engine)
|
|
exp := []*code{
|
|
{body: []*interpreterOp{}},
|
|
{body: []*interpreterOp{}},
|
|
}
|
|
m := &wasm.Module{}
|
|
|
|
e.addCodes(m, exp)
|
|
|
|
actual, ok := e.getCodes(m)
|
|
require.True(t, ok)
|
|
require.Equal(t, len(exp), len(actual))
|
|
for i := range actual {
|
|
require.Equal(t, exp[i], actual[i])
|
|
}
|
|
|
|
e.deleteCodes(m)
|
|
_, ok = e.getCodes(m)
|
|
require.False(t, ok)
|
|
}
|