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