wa-lang.org/wazero@v1.0.2/internal/engine/interpreter/interpreter_test.go (about)

     1  package interpreter
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"math"
     8  	"strconv"
     9  	"testing"
    10  	"unsafe"
    11  
    12  	"wa-lang.org/wazero/api"
    13  	"wa-lang.org/wazero/experimental"
    14  	"wa-lang.org/wazero/experimental/logging"
    15  	"wa-lang.org/wazero/internal/testing/enginetest"
    16  	"wa-lang.org/wazero/internal/testing/require"
    17  	"wa-lang.org/wazero/internal/wasm"
    18  	"wa-lang.org/wazero/internal/wazeroir"
    19  )
    20  
    21  // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    22  var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    23  
    24  func TestInterpreter_peekValues(t *testing.T) {
    25  	ce := &callEngine{}
    26  	require.Nil(t, ce.peekValues(0))
    27  
    28  	ce.stack = []uint64{5, 4, 3, 2, 1}
    29  	require.Nil(t, ce.peekValues(0))
    30  	require.Equal(t, []uint64{2, 1}, ce.peekValues(2))
    31  }
    32  
    33  func TestInterpreter_CallEngine_PushFrame(t *testing.T) {
    34  	f1 := &callFrame{}
    35  	f2 := &callFrame{}
    36  
    37  	ce := callEngine{}
    38  	require.Zero(t, len(ce.frames), "expected no frames")
    39  
    40  	ce.pushFrame(f1)
    41  	require.Equal(t, []*callFrame{f1}, ce.frames)
    42  
    43  	ce.pushFrame(f2)
    44  	require.Equal(t, []*callFrame{f1, f2}, ce.frames)
    45  }
    46  
    47  func TestInterpreter_CallEngine_PushFrame_StackOverflow(t *testing.T) {
    48  	saved := callStackCeiling
    49  	defer func() { callStackCeiling = saved }()
    50  
    51  	callStackCeiling = 3
    52  
    53  	f1 := &callFrame{}
    54  	f2 := &callFrame{}
    55  	f3 := &callFrame{}
    56  	f4 := &callFrame{}
    57  
    58  	vm := callEngine{}
    59  	vm.pushFrame(f1)
    60  	vm.pushFrame(f2)
    61  	vm.pushFrame(f3)
    62  
    63  	captured := require.CapturePanic(func() { vm.pushFrame(f4) })
    64  	require.EqualError(t, captured, "stack overflow")
    65  }
    66  
    67  // et is used for tests defined in the enginetest package.
    68  var (
    69  	et              = &engineTester{}
    70  	functionLog     bytes.Buffer
    71  	listenerFactory = logging.NewLoggingListenerFactory(&functionLog)
    72  )
    73  
    74  // engineTester implements enginetest.EngineTester.
    75  type engineTester struct{}
    76  
    77  // IsCompiler implements enginetest.EngineTester NewEngine.
    78  func (e engineTester) IsCompiler() bool {
    79  	return false
    80  }
    81  
    82  // ListenerFactory implements enginetest.EngineTester NewEngine.
    83  func (e engineTester) ListenerFactory() experimental.FunctionListenerFactory {
    84  	return listenerFactory
    85  }
    86  
    87  // NewEngine implements enginetest.EngineTester NewEngine.
    88  func (e engineTester) NewEngine(enabledFeatures api.CoreFeatures) wasm.Engine {
    89  	return NewEngine(context.Background(), enabledFeatures)
    90  }
    91  
    92  // CompiledFunctionPointerValue implements enginetest.EngineTester CompiledFunctionPointerValue.
    93  func (e engineTester) CompiledFunctionPointerValue(me wasm.ModuleEngine, funcIndex wasm.Index) uint64 {
    94  	internal := me.(*moduleEngine)
    95  	return uint64(uintptr(unsafe.Pointer(internal.functions[funcIndex])))
    96  }
    97  
    98  func TestInterpreter_Engine_NewModuleEngine(t *testing.T) {
    99  	enginetest.RunTestEngine_NewModuleEngine(t, et)
   100  }
   101  
   102  func TestInterpreter_Engine_InitializeFuncrefGlobals(t *testing.T) {
   103  	enginetest.RunTestEngine_InitializeFuncrefGlobals(t, et)
   104  }
   105  
   106  func TestInterpreter_ModuleEngine_LookupFunction(t *testing.T) {
   107  	enginetest.RunTestModuleEngine_LookupFunction(t, et)
   108  }
   109  
   110  func TestInterpreter_ModuleEngine_Call(t *testing.T) {
   111  	defer functionLog.Reset()
   112  	enginetest.RunTestModuleEngine_Call(t, et)
   113  	require.Equal(t, `
   114  --> .$0(1,2)
   115  <-- (1,2)
   116  `, "\n"+functionLog.String())
   117  }
   118  
   119  func TestInterpreter_ModuleEngine_Call_HostFn(t *testing.T) {
   120  	defer functionLog.Reset()
   121  	enginetest.RunTestModuleEngine_Call_HostFn(t, et)
   122  }
   123  
   124  func TestInterpreter_ModuleEngine_Call_Errors(t *testing.T) {
   125  	defer functionLog.Reset()
   126  	enginetest.RunTestModuleEngine_Call_Errors(t, et)
   127  
   128  	// TODO: Currently, the listener doesn't get notified on errors as they are
   129  	// implemented with panic. This means the end hooks aren't make resulting
   130  	// in dangling logs like this:
   131  	//	==> host.host_div_by(4294967295)
   132  	// instead of seeing a return like
   133  	//	<== DivByZero
   134  	require.Equal(t, `
   135  --> imported.div_by.wasm(1)
   136  <-- (1)
   137  --> imported.div_by.wasm(1)
   138  <-- (1)
   139  --> imported.div_by.wasm(0)
   140  --> imported.div_by.wasm(1)
   141  <-- (1)
   142  --> imported.call->div_by.go(4294967295)
   143  	==> host.div_by.go(4294967295)
   144  --> imported.call->div_by.go(1)
   145  	==> host.div_by.go(1)
   146  	<== (1)
   147  <-- (1)
   148  --> importing.call_import->call->div_by.go(0)
   149  	--> imported.call->div_by.go(0)
   150  		==> host.div_by.go(0)
   151  --> importing.call_import->call->div_by.go(1)
   152  	--> imported.call->div_by.go(1)
   153  		==> host.div_by.go(1)
   154  		<== (1)
   155  	<-- (1)
   156  <-- (1)
   157  --> importing.call_import->call->div_by.go(4294967295)
   158  	--> imported.call->div_by.go(4294967295)
   159  		==> host.div_by.go(4294967295)
   160  --> importing.call_import->call->div_by.go(1)
   161  	--> imported.call->div_by.go(1)
   162  		==> host.div_by.go(1)
   163  		<== (1)
   164  	<-- (1)
   165  <-- (1)
   166  --> importing.call_import->call->div_by.go(0)
   167  	--> imported.call->div_by.go(0)
   168  		==> host.div_by.go(0)
   169  --> importing.call_import->call->div_by.go(1)
   170  	--> imported.call->div_by.go(1)
   171  		==> host.div_by.go(1)
   172  		<== (1)
   173  	<-- (1)
   174  <-- (1)
   175  `, "\n"+functionLog.String())
   176  }
   177  
   178  func TestInterpreter_ModuleEngine_Memory(t *testing.T) {
   179  	enginetest.RunTestModuleEngine_Memory(t, et)
   180  }
   181  
   182  func TestInterpreter_NonTrappingFloatToIntConversion(t *testing.T) {
   183  	_0x80000000 := uint32(0x80000000)
   184  	_0xffffffff := uint32(0xffffffff)
   185  	_0x8000000000000000 := uint64(0x8000000000000000)
   186  	_0xffffffffffffffff := uint64(0xffffffffffffffff)
   187  
   188  	tests := []struct {
   189  		op            wasm.OpcodeMisc
   190  		inputType     wazeroir.Float
   191  		outputType    wazeroir.SignedInt
   192  		input32bit    []float32
   193  		input64bit    []float64
   194  		expected32bit []int32
   195  		expected64bit []int64
   196  	}{
   197  		{
   198  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L261-L282
   199  			op:         wasm.OpcodeMiscI32TruncSatF32S,
   200  			inputType:  wazeroir.Float32,
   201  			outputType: wazeroir.SignedInt32,
   202  			input32bit: []float32{
   203  				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, -1.0, -0x1.19999ap+0,
   204  				-1.5, -1.9, -2.0, 2147483520.0, -2147483648.0, 2147483648.0, -2147483904.0,
   205  				float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()),
   206  				float32(math.NaN()), float32(math.NaN()),
   207  			},
   208  			expected32bit: []int32{
   209  				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 2147483520, -2147483648, 0x7fffffff,
   210  				int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0, 0, 0, 0,
   211  			},
   212  		},
   213  		{
   214  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L284-L304
   215  			op:         wasm.OpcodeMiscI32TruncSatF32U,
   216  			inputType:  wazeroir.Float32,
   217  			outputType: wazeroir.SignedUint32,
   218  			input32bit: []float32{
   219  				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 1.9, 2.0, 2147483648, 4294967040.0,
   220  				-0x1.ccccccp-1, -0x1.fffffep-1, 4294967296.0, -1.0, float32(math.Inf(1)), float32(math.Inf(-1)),
   221  				float32(math.NaN()), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()),
   222  			},
   223  			expected32bit: []int32{
   224  				0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -256, 0, 0, int32(_0xffffffff), 0x00000000,
   225  				int32(_0xffffffff), 0x00000000, 0, 0, 0, 0,
   226  			},
   227  		},
   228  		{
   229  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L355-L378
   230  			op:         wasm.OpcodeMiscI64TruncSatF32S,
   231  			inputType:  wazeroir.Float32,
   232  			outputType: wazeroir.SignedInt64,
   233  			input32bit: []float32{
   234  				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,
   235  				-4294967296, 9223371487098961920.0, -9223372036854775808.0, 9223372036854775808.0, -9223373136366403584.0,
   236  				float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()),
   237  				float32(math.NaN()),
   238  			},
   239  			expected64bit: []int64{
   240  				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 4294967296, -4294967296, 9223371487098961920, -9223372036854775808,
   241  				0x7fffffffffffffff, int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0,
   242  			},
   243  		},
   244  		{
   245  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L380-L398
   246  			op:         wasm.OpcodeMiscI64TruncSatF32U,
   247  			inputType:  wazeroir.Float32,
   248  			outputType: wazeroir.SignedUint64,
   249  			input32bit: []float32{
   250  				0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 4294967296,
   251  				18446742974197923840.0, -0x1.ccccccp-1, -0x1.fffffep-1, 18446744073709551616.0, -1.0,
   252  				float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()),
   253  				float32(math.NaN()), float32(math.NaN()),
   254  			},
   255  			expected64bit: []int64{
   256  				0, 0, 0, 0, 1, 1, 1,
   257  				4294967296, -1099511627776, 0, 0, int64(_0xffffffffffffffff), 0x0000000000000000,
   258  				int64(_0xffffffffffffffff), 0x0000000000000000, 0, 0, 0, 0,
   259  			},
   260  		},
   261  		{
   262  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L306-L327
   263  			op:         wasm.OpcodeMiscI32TruncSatF64S,
   264  			inputType:  wazeroir.Float64,
   265  			outputType: wazeroir.SignedInt32,
   266  			input64bit: []float64{
   267  				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0,
   268  				-0x1.199999999999ap+0, -1.5, -1.9, -2.0, 2147483647.0, -2147483648.0, 2147483648.0,
   269  				-2147483649.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
   270  			},
   271  			expected32bit: []int32{
   272  				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2,
   273  				2147483647, -2147483648, 0x7fffffff, int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0,
   274  				0, 0, 0,
   275  			},
   276  		},
   277  		{
   278  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L329-L353
   279  			op:         wasm.OpcodeMiscI32TruncSatF64U,
   280  			inputType:  wazeroir.Float64,
   281  			outputType: wazeroir.SignedUint32,
   282  			input64bit: []float64{
   283  				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 1.9, 2.0,
   284  				2147483648, 4294967295.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 4294967296.0, -1.0, 1e16, 1e30,
   285  				9223372036854775808, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
   286  			},
   287  			expected32bit: []int32{
   288  				0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -1,
   289  				0, 0, 100000000, int32(_0xffffffff), 0x00000000, int32(_0xffffffff), int32(_0xffffffff), int32(_0xffffffff),
   290  				int32(_0xffffffff), 0x00000000, 0, 0, 0, 0,
   291  			},
   292  		},
   293  		{
   294  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L400-L423
   295  			op:         wasm.OpcodeMiscI64TruncSatF64S,
   296  			inputType:  wazeroir.Float64,
   297  			outputType: wazeroir.SignedInt64,
   298  			input64bit: []float64{
   299  				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0,
   300  				-0x1.199999999999ap+0, -1.5, -1.9, -2.0, 4294967296, -4294967296, 9223372036854774784.0, -9223372036854775808.0,
   301  				9223372036854775808.0, -9223372036854777856.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(),
   302  				math.NaN(),
   303  			},
   304  			expected64bit: []int64{
   305  				0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2,
   306  				4294967296, -4294967296, 9223372036854774784, -9223372036854775808, 0x7fffffffffffffff,
   307  				int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0,
   308  			},
   309  		},
   310  		{
   311  			// https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L425-L447
   312  			op:         wasm.OpcodeMiscI64TruncSatF64U,
   313  			inputType:  wazeroir.Float64,
   314  			outputType: wazeroir.SignedUint64,
   315  			input64bit: []float64{
   316  				0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 4294967295, 4294967296,
   317  				18446744073709549568.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 1e16, 9223372036854775808,
   318  				18446744073709551616.0, -1.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(),
   319  			},
   320  			expected64bit: []int64{
   321  				0, 0, 0, 0, 1, 1, 1, 0xffffffff, 0x100000000, -2048, 0, 0, 100000000, 10000000000000000,
   322  				-9223372036854775808, int64(_0xffffffffffffffff), 0x0000000000000000, int64(_0xffffffffffffffff),
   323  				0x0000000000000000, 0, 0, 0, 0,
   324  			},
   325  		},
   326  	}
   327  
   328  	for _, tt := range tests {
   329  		tc := tt
   330  		t.Run(wasm.MiscInstructionName(tc.op), func(t *testing.T) {
   331  			in32bit := len(tc.input32bit) > 0
   332  			casenum := len(tc.input32bit)
   333  			if !in32bit {
   334  				casenum = len(tc.input64bit)
   335  			}
   336  			for i := 0; i < casenum; i++ {
   337  				i := i
   338  				t.Run(strconv.Itoa(i), func(t *testing.T) {
   339  					var body []*interpreterOp
   340  					if in32bit {
   341  						body = append(body, &interpreterOp{
   342  							kind: wazeroir.OperationKindConstF32,
   343  							us:   []uint64{uint64(math.Float32bits(tc.input32bit[i]))},
   344  						})
   345  					} else {
   346  						body = append(body, &interpreterOp{
   347  							kind: wazeroir.OperationKindConstF64,
   348  							us:   []uint64{uint64(math.Float64bits(tc.input64bit[i]))},
   349  						})
   350  					}
   351  
   352  					body = append(body, &interpreterOp{
   353  						kind: wazeroir.OperationKindITruncFromF,
   354  						b1:   byte(tc.inputType),
   355  						b2:   byte(tc.outputType),
   356  						b3:   true, // NonTrapping = true.
   357  					})
   358  
   359  					// Return from function.
   360  					body = append(body,
   361  						&interpreterOp{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
   362  					)
   363  
   364  					ce := &callEngine{}
   365  					f := &function{
   366  						source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
   367  						body:   body,
   368  					}
   369  					ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
   370  
   371  					if len(tc.expected32bit) > 0 {
   372  						require.Equal(t, tc.expected32bit[i], int32(uint32(ce.popValue())))
   373  					} else {
   374  						require.Equal(t, tc.expected64bit[i], int64((ce.popValue())))
   375  					}
   376  				})
   377  			}
   378  		})
   379  
   380  	}
   381  }
   382  
   383  func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
   384  	translateToIROperationKind := func(op wasm.Opcode) (kind wazeroir.OperationKind) {
   385  		switch op {
   386  		case wasm.OpcodeI32Extend8S:
   387  			kind = wazeroir.OperationKindSignExtend32From8
   388  		case wasm.OpcodeI32Extend16S:
   389  			kind = wazeroir.OperationKindSignExtend32From16
   390  		case wasm.OpcodeI64Extend8S:
   391  			kind = wazeroir.OperationKindSignExtend64From8
   392  		case wasm.OpcodeI64Extend16S:
   393  			kind = wazeroir.OperationKindSignExtend64From16
   394  		case wasm.OpcodeI64Extend32S:
   395  			kind = wazeroir.OperationKindSignExtend64From32
   396  		}
   397  		return
   398  	}
   399  	t.Run("32bit", func(t *testing.T) {
   400  		tests := []struct {
   401  			in       int32
   402  			expected int32
   403  			opcode   wasm.Opcode
   404  		}{
   405  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L270-L276
   406  			{in: 0, expected: 0, opcode: wasm.OpcodeI32Extend8S},
   407  			{in: 0x7f, expected: 127, opcode: wasm.OpcodeI32Extend8S},
   408  			{in: 0x80, expected: -128, opcode: wasm.OpcodeI32Extend8S},
   409  			{in: 0xff, expected: -1, opcode: wasm.OpcodeI32Extend8S},
   410  			{in: 0x012345_00, expected: 0, opcode: wasm.OpcodeI32Extend8S},
   411  			{in: -19088768 /* = 0xfedcba_80 bit pattern */, expected: -0x80, opcode: wasm.OpcodeI32Extend8S},
   412  			{in: -1, expected: -1, opcode: wasm.OpcodeI32Extend8S},
   413  
   414  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L278-L284
   415  			{in: 0, expected: 0, opcode: wasm.OpcodeI32Extend16S},
   416  			{in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI32Extend16S},
   417  			{in: 0x8000, expected: -32768, opcode: wasm.OpcodeI32Extend16S},
   418  			{in: 0xffff, expected: -1, opcode: wasm.OpcodeI32Extend16S},
   419  			{in: 0x0123_0000, expected: 0, opcode: wasm.OpcodeI32Extend16S},
   420  			{in: -19103744 /* = 0xfedc_8000 bit pattern */, expected: -0x8000, opcode: wasm.OpcodeI32Extend16S},
   421  			{in: -1, expected: -1, opcode: wasm.OpcodeI32Extend16S},
   422  		}
   423  
   424  		for _, tt := range tests {
   425  			tc := tt
   426  			t.Run(fmt.Sprintf("%s(i32.const(0x%x))", wasm.InstructionName(tc.opcode), tc.in), func(t *testing.T) {
   427  				ce := &callEngine{}
   428  				f := &function{
   429  					source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
   430  					body: []*interpreterOp{
   431  						{kind: wazeroir.OperationKindConstI32, us: []uint64{uint64(uint32(tc.in))}},
   432  						{kind: translateToIROperationKind(tc.opcode)},
   433  						{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
   434  					},
   435  				}
   436  				ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
   437  				require.Equal(t, tc.expected, int32(uint32(ce.popValue())))
   438  			})
   439  		}
   440  	})
   441  	t.Run("64bit", func(t *testing.T) {
   442  		tests := []struct {
   443  			in       int64
   444  			expected int64
   445  			opcode   wasm.Opcode
   446  		}{
   447  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L271-L277
   448  			{in: 0, expected: 0, opcode: wasm.OpcodeI64Extend8S},
   449  			{in: 0x7f, expected: 127, opcode: wasm.OpcodeI64Extend8S},
   450  			{in: 0x80, expected: -128, opcode: wasm.OpcodeI64Extend8S},
   451  			{in: 0xff, expected: -1, opcode: wasm.OpcodeI64Extend8S},
   452  			{in: 0x01234567_89abcd_00, expected: 0, opcode: wasm.OpcodeI64Extend8S},
   453  			{in: 81985529216486784 /* = 0xfedcba98_765432_80 bit pattern */, expected: -0x80, opcode: wasm.OpcodeI64Extend8S},
   454  			{in: -1, expected: -1, opcode: wasm.OpcodeI64Extend8S},
   455  
   456  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L279-L285
   457  			{in: 0, expected: 0, opcode: wasm.OpcodeI64Extend16S},
   458  			{in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI64Extend16S},
   459  			{in: 0x8000, expected: -32768, opcode: wasm.OpcodeI64Extend16S},
   460  			{in: 0xffff, expected: -1, opcode: wasm.OpcodeI64Extend16S},
   461  			{in: 0x12345678_9abc_0000, expected: 0, opcode: wasm.OpcodeI64Extend16S},
   462  			{in: 81985529216466944 /* = 0xfedcba98_7654_8000 bit pattern */, expected: -0x8000, opcode: wasm.OpcodeI64Extend16S},
   463  			{in: -1, expected: -1, opcode: wasm.OpcodeI64Extend16S},
   464  
   465  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L287-L296
   466  			{in: 0, expected: 0, opcode: wasm.OpcodeI64Extend32S},
   467  			{in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI64Extend32S},
   468  			{in: 0x8000, expected: 32768, opcode: wasm.OpcodeI64Extend32S},
   469  			{in: 0xffff, expected: 65535, opcode: wasm.OpcodeI64Extend32S},
   470  			{in: 0x7fffffff, expected: 0x7fffffff, opcode: wasm.OpcodeI64Extend32S},
   471  			{in: 0x80000000, expected: -0x80000000, opcode: wasm.OpcodeI64Extend32S},
   472  			{in: 0xffffffff, expected: -1, opcode: wasm.OpcodeI64Extend32S},
   473  			{in: 0x01234567_00000000, expected: 0, opcode: wasm.OpcodeI64Extend32S},
   474  			{in: -81985529054232576 /* = 0xfedcba98_80000000 bit pattern */, expected: -0x80000000, opcode: wasm.OpcodeI64Extend32S},
   475  			{in: -1, expected: -1, opcode: wasm.OpcodeI64Extend32S},
   476  		}
   477  
   478  		for _, tt := range tests {
   479  			tc := tt
   480  			t.Run(fmt.Sprintf("%s(i64.const(0x%x))", wasm.InstructionName(tc.opcode), tc.in), func(t *testing.T) {
   481  				ce := &callEngine{}
   482  				f := &function{
   483  					source: &wasm.FunctionInstance{Module: &wasm.ModuleInstance{Engine: &moduleEngine{}}},
   484  					body: []*interpreterOp{
   485  						{kind: wazeroir.OperationKindConstI64, us: []uint64{uint64(tc.in)}},
   486  						{kind: translateToIROperationKind(tc.opcode)},
   487  						{kind: wazeroir.OperationKindBr, us: []uint64{math.MaxUint64}},
   488  					},
   489  				}
   490  				ce.callNativeFunc(testCtx, &wasm.CallContext{}, f)
   491  				require.Equal(t, tc.expected, int64(ce.popValue()))
   492  			})
   493  		}
   494  	})
   495  }
   496  
   497  func TestInterpreter_Compile(t *testing.T) {
   498  	t.Run("uncompiled", func(t *testing.T) {
   499  		e := et.NewEngine(api.CoreFeaturesV1).(*engine)
   500  		_, err := e.NewModuleEngine("foo",
   501  			&wasm.Module{},
   502  			nil, // imports
   503  			nil, // moduleFunctions
   504  		)
   505  		require.EqualError(t, err, "source module for foo must be compiled before instantiation")
   506  	})
   507  	t.Run("fail", func(t *testing.T) {
   508  		e := et.NewEngine(api.CoreFeaturesV1).(*engine)
   509  
   510  		errModule := &wasm.Module{
   511  			TypeSection:     []*wasm.FunctionType{{}},
   512  			FunctionSection: []wasm.Index{0, 0, 0},
   513  			CodeSection: []*wasm.Code{
   514  				{Body: []byte{wasm.OpcodeEnd}},
   515  				{Body: []byte{wasm.OpcodeEnd}},
   516  				{Body: []byte{wasm.OpcodeCall}}, // Call instruction without immediate for call target index is invalid and should fail to compile.
   517  			},
   518  			ID: wasm.ModuleID{},
   519  		}
   520  		errModule.BuildFunctionDefinitions()
   521  
   522  		err := e.CompileModule(testCtx, errModule, nil)
   523  		require.EqualError(t, err, "failed to lower func[.$2] to wazeroir: handling instruction: apply stack failed for call: reading immediates: EOF")
   524  
   525  		// On the compilation failure, all the compiled functions including succeeded ones must be released.
   526  		_, ok := e.codes[errModule.ID]
   527  		require.False(t, ok)
   528  	})
   529  	t.Run("ok", func(t *testing.T) {
   530  		e := et.NewEngine(api.CoreFeaturesV1).(*engine)
   531  
   532  		okModule := &wasm.Module{
   533  			TypeSection:     []*wasm.FunctionType{{}},
   534  			FunctionSection: []wasm.Index{0, 0, 0, 0},
   535  			CodeSection: []*wasm.Code{
   536  				{Body: []byte{wasm.OpcodeEnd}},
   537  				{Body: []byte{wasm.OpcodeEnd}},
   538  				{Body: []byte{wasm.OpcodeEnd}},
   539  				{Body: []byte{wasm.OpcodeEnd}},
   540  			},
   541  			ID: wasm.ModuleID{},
   542  		}
   543  		err := e.CompileModule(testCtx, okModule, nil)
   544  		require.NoError(t, err)
   545  
   546  		compiled, ok := e.codes[okModule.ID]
   547  		require.True(t, ok)
   548  		require.Equal(t, len(okModule.FunctionSection), len(compiled))
   549  
   550  		_, ok = e.codes[okModule.ID]
   551  		require.True(t, ok)
   552  	})
   553  }
   554  
   555  func TestEngine_CachedcodesPerModule(t *testing.T) {
   556  	e := et.NewEngine(api.CoreFeaturesV1).(*engine)
   557  	exp := []*code{
   558  		{body: []*interpreterOp{}},
   559  		{body: []*interpreterOp{}},
   560  	}
   561  	m := &wasm.Module{}
   562  
   563  	e.addCodes(m, exp)
   564  
   565  	actual, ok := e.getCodes(m)
   566  	require.True(t, ok)
   567  	require.Equal(t, len(exp), len(actual))
   568  	for i := range actual {
   569  		require.Equal(t, exp[i], actual[i])
   570  	}
   571  
   572  	e.deleteCodes(m)
   573  	_, ok = e.getCodes(m)
   574  	require.False(t, ok)
   575  }