github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/interpreter/interpreter_test.go (about) 1 package interpreter 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "strconv" 8 "testing" 9 10 "github.com/tetratelabs/wazero/api" 11 "github.com/tetratelabs/wazero/internal/testing/require" 12 "github.com/tetratelabs/wazero/internal/wasm" 13 ) 14 15 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 16 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 17 18 func TestInterpreter_peekValues(t *testing.T) { 19 ce := &callEngine{} 20 require.Nil(t, ce.peekValues(0)) 21 22 ce.stack = []uint64{5, 4, 3, 2, 1} 23 require.Nil(t, ce.peekValues(0)) 24 require.Equal(t, []uint64{2, 1}, ce.peekValues(2)) 25 } 26 27 func TestInterpreter_CallEngine_PushFrame(t *testing.T) { 28 f1 := &callFrame{} 29 f2 := &callFrame{} 30 31 ce := callEngine{} 32 require.Zero(t, len(ce.frames), "expected no frames") 33 34 ce.pushFrame(f1) 35 require.Equal(t, []*callFrame{f1}, ce.frames) 36 37 ce.pushFrame(f2) 38 require.Equal(t, []*callFrame{f1, f2}, ce.frames) 39 } 40 41 func TestInterpreter_CallEngine_PushFrame_StackOverflow(t *testing.T) { 42 saved := callStackCeiling 43 defer func() { callStackCeiling = saved }() 44 45 callStackCeiling = 3 46 47 f1 := &callFrame{} 48 f2 := &callFrame{} 49 f3 := &callFrame{} 50 f4 := &callFrame{} 51 52 vm := callEngine{} 53 vm.pushFrame(f1) 54 vm.pushFrame(f2) 55 vm.pushFrame(f3) 56 57 captured := require.CapturePanic(func() { vm.pushFrame(f4) }) 58 require.EqualError(t, captured, "stack overflow") 59 } 60 61 func TestInterpreter_NonTrappingFloatToIntConversion(t *testing.T) { 62 _0x80000000 := uint32(0x80000000) 63 _0xffffffff := uint32(0xffffffff) 64 _0x8000000000000000 := uint64(0x8000000000000000) 65 _0xffffffffffffffff := uint64(0xffffffffffffffff) 66 67 tests := []struct { 68 op wasm.OpcodeMisc 69 inputType float 70 outputType signedInt 71 input32bit []float32 72 input64bit []float64 73 expected32bit []int32 74 expected64bit []int64 75 }{ 76 { 77 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L261-L282 78 op: wasm.OpcodeMiscI32TruncSatF32S, 79 inputType: f32, 80 outputType: signedInt32, 81 input32bit: []float32{ 82 0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, -1.0, -0x1.19999ap+0, 83 -1.5, -1.9, -2.0, 2147483520.0, -2147483648.0, 2147483648.0, -2147483904.0, 84 float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()), 85 float32(math.NaN()), float32(math.NaN()), 86 }, 87 expected32bit: []int32{ 88 0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 2147483520, -2147483648, 0x7fffffff, 89 int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0, 0, 0, 0, 90 }, 91 }, 92 { 93 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L284-L304 94 op: wasm.OpcodeMiscI32TruncSatF32U, 95 inputType: f32, 96 outputType: signedUint32, 97 input32bit: []float32{ 98 0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 1.9, 2.0, 2147483648, 4294967040.0, 99 -0x1.ccccccp-1, -0x1.fffffep-1, 4294967296.0, -1.0, float32(math.Inf(1)), float32(math.Inf(-1)), 100 float32(math.NaN()), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()), 101 }, 102 expected32bit: []int32{ 103 0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -256, 0, 0, int32(_0xffffffff), 0x00000000, 104 int32(_0xffffffff), 0x00000000, 0, 0, 0, 0, 105 }, 106 }, 107 { 108 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L355-L378 109 op: wasm.OpcodeMiscI64TruncSatF32S, 110 inputType: f32, 111 outputType: signedInt64, 112 input32bit: []float32{ 113 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, 114 -4294967296, 9223371487098961920.0, -9223372036854775808.0, 9223372036854775808.0, -9223373136366403584.0, 115 float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()), float32(math.NaN()), 116 float32(math.NaN()), 117 }, 118 expected64bit: []int64{ 119 0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 4294967296, -4294967296, 9223371487098961920, -9223372036854775808, 120 0x7fffffffffffffff, int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0, 121 }, 122 }, 123 { 124 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L380-L398 125 op: wasm.OpcodeMiscI64TruncSatF32U, 126 inputType: f32, 127 outputType: signedUint64, 128 input32bit: []float32{ 129 0.0, 0.0, 0x1p-149, -0x1p-149, 1.0, 0x1.19999ap+0, 1.5, 4294967296, 130 18446742974197923840.0, -0x1.ccccccp-1, -0x1.fffffep-1, 18446744073709551616.0, -1.0, 131 float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()), float32(math.NaN()), 132 float32(math.NaN()), float32(math.NaN()), 133 }, 134 expected64bit: []int64{ 135 0, 0, 0, 0, 1, 1, 1, 136 4294967296, -1099511627776, 0, 0, int64(_0xffffffffffffffff), 0x0000000000000000, 137 int64(_0xffffffffffffffff), 0x0000000000000000, 0, 0, 0, 0, 138 }, 139 }, 140 { 141 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L306-L327 142 op: wasm.OpcodeMiscI32TruncSatF64S, 143 inputType: f64, 144 outputType: signedInt32, 145 input64bit: []float64{ 146 0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0, 147 -0x1.199999999999ap+0, -1.5, -1.9, -2.0, 2147483647.0, -2147483648.0, 2147483648.0, 148 -2147483649.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 149 }, 150 expected32bit: []int32{ 151 0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 152 2147483647, -2147483648, 0x7fffffff, int32(_0x80000000), 0x7fffffff, int32(_0x80000000), 0, 153 0, 0, 0, 154 }, 155 }, 156 { 157 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L329-L353 158 op: wasm.OpcodeMiscI32TruncSatF64U, 159 inputType: f64, 160 outputType: signedUint32, 161 input64bit: []float64{ 162 0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 1.9, 2.0, 163 2147483648, 4294967295.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 4294967296.0, -1.0, 1e16, 1e30, 164 9223372036854775808, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 165 }, 166 expected32bit: []int32{ 167 0, 0, 0, 0, 1, 1, 1, 1, 2, -2147483648, -1, 168 0, 0, 100000000, int32(_0xffffffff), 0x00000000, int32(_0xffffffff), int32(_0xffffffff), int32(_0xffffffff), 169 int32(_0xffffffff), 0x00000000, 0, 0, 0, 0, 170 }, 171 }, 172 { 173 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L400-L423 174 op: wasm.OpcodeMiscI64TruncSatF64S, 175 inputType: f64, 176 outputType: signedInt64, 177 input64bit: []float64{ 178 0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, -1.0, 179 -0x1.199999999999ap+0, -1.5, -1.9, -2.0, 4294967296, -4294967296, 9223372036854774784.0, -9223372036854775808.0, 180 9223372036854775808.0, -9223372036854777856.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), 181 math.NaN(), 182 }, 183 expected64bit: []int64{ 184 0, 0, 0, 0, 1, 1, 1, -1, -1, -1, -1, -2, 185 4294967296, -4294967296, 9223372036854774784, -9223372036854775808, 0x7fffffffffffffff, 186 int64(_0x8000000000000000), 0x7fffffffffffffff, int64(_0x8000000000000000), 0, 0, 0, 0, 187 }, 188 }, 189 { 190 // https://github.com/WebAssembly/spec/blob/c8fd933fa51eb0b511bce027b573aef7ee373726/test/core/conversions.wast#L425-L447 191 op: wasm.OpcodeMiscI64TruncSatF64U, 192 inputType: f64, 193 outputType: signedUint64, 194 input64bit: []float64{ 195 0.0, 0.0, 0x0.0000000000001p-1022, -0x0.0000000000001p-1022, 1.0, 0x1.199999999999ap+0, 1.5, 4294967295, 4294967296, 196 18446744073709549568.0, -0x1.ccccccccccccdp-1, -0x1.fffffffffffffp-1, 1e8, 1e16, 9223372036854775808, 197 18446744073709551616.0, -1.0, math.Inf(1), math.Inf(-1), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 198 }, 199 expected64bit: []int64{ 200 0, 0, 0, 0, 1, 1, 1, 0xffffffff, 0x100000000, -2048, 0, 0, 100000000, 10000000000000000, 201 -9223372036854775808, int64(_0xffffffffffffffff), 0x0000000000000000, int64(_0xffffffffffffffff), 202 0x0000000000000000, 0, 0, 0, 0, 203 }, 204 }, 205 } 206 207 for _, tt := range tests { 208 tc := tt 209 t.Run(wasm.MiscInstructionName(tc.op), func(t *testing.T) { 210 in32bit := len(tc.input32bit) > 0 211 casenum := len(tc.input32bit) 212 if !in32bit { 213 casenum = len(tc.input64bit) 214 } 215 for i := 0; i < casenum; i++ { 216 i := i 217 t.Run(strconv.Itoa(i), func(t *testing.T) { 218 var body []unionOperation 219 if in32bit { 220 body = append(body, unionOperation{ 221 Kind: operationKindConstF32, 222 U1: uint64(math.Float32bits(tc.input32bit[i])), 223 }) 224 } else { 225 body = append(body, unionOperation{ 226 Kind: operationKindConstF64, 227 U1: math.Float64bits(tc.input64bit[i]), 228 }) 229 } 230 231 body = append(body, unionOperation{ 232 Kind: operationKindITruncFromF, 233 B1: byte(tc.inputType), 234 B2: byte(tc.outputType), 235 B3: true, // NonTrapping = true. 236 }) 237 238 // Return from function. 239 body = append(body, 240 unionOperation{Kind: operationKindBr, U1: uint64(math.MaxUint64)}, 241 ) 242 243 ce := &callEngine{} 244 f := &function{ 245 moduleInstance: &wasm.ModuleInstance{Engine: &moduleEngine{}}, 246 parent: &compiledFunction{body: body}, 247 } 248 ce.callNativeFunc(testCtx, &wasm.ModuleInstance{}, f) 249 250 if len(tc.expected32bit) > 0 { 251 require.Equal(t, tc.expected32bit[i], int32(uint32(ce.popValue()))) 252 } else { 253 require.Equal(t, tc.expected64bit[i], int64((ce.popValue()))) 254 } 255 }) 256 } 257 }) 258 259 } 260 } 261 262 func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) { 263 translateToIRoperationKind := func(op wasm.Opcode) (kind operationKind) { 264 switch op { 265 case wasm.OpcodeI32Extend8S: 266 kind = operationKindSignExtend32From8 267 case wasm.OpcodeI32Extend16S: 268 kind = operationKindSignExtend32From16 269 case wasm.OpcodeI64Extend8S: 270 kind = operationKindSignExtend64From8 271 case wasm.OpcodeI64Extend16S: 272 kind = operationKindSignExtend64From16 273 case wasm.OpcodeI64Extend32S: 274 kind = operationKindSignExtend64From32 275 } 276 return 277 } 278 t.Run("32bit", func(t *testing.T) { 279 tests := []struct { 280 in int32 281 expected int32 282 opcode wasm.Opcode 283 }{ 284 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L270-L276 285 {in: 0, expected: 0, opcode: wasm.OpcodeI32Extend8S}, 286 {in: 0x7f, expected: 127, opcode: wasm.OpcodeI32Extend8S}, 287 {in: 0x80, expected: -128, opcode: wasm.OpcodeI32Extend8S}, 288 {in: 0xff, expected: -1, opcode: wasm.OpcodeI32Extend8S}, 289 {in: 0x012345_00, expected: 0, opcode: wasm.OpcodeI32Extend8S}, 290 {in: -19088768 /* = 0xfedcba_80 bit pattern */, expected: -0x80, opcode: wasm.OpcodeI32Extend8S}, 291 {in: -1, expected: -1, opcode: wasm.OpcodeI32Extend8S}, 292 293 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L278-L284 294 {in: 0, expected: 0, opcode: wasm.OpcodeI32Extend16S}, 295 {in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI32Extend16S}, 296 {in: 0x8000, expected: -32768, opcode: wasm.OpcodeI32Extend16S}, 297 {in: 0xffff, expected: -1, opcode: wasm.OpcodeI32Extend16S}, 298 {in: 0x0123_0000, expected: 0, opcode: wasm.OpcodeI32Extend16S}, 299 {in: -19103744 /* = 0xfedc_8000 bit pattern */, expected: -0x8000, opcode: wasm.OpcodeI32Extend16S}, 300 {in: -1, expected: -1, opcode: wasm.OpcodeI32Extend16S}, 301 } 302 303 for _, tt := range tests { 304 tc := tt 305 t.Run(fmt.Sprintf("%s(i32.const(0x%x))", wasm.InstructionName(tc.opcode), tc.in), func(t *testing.T) { 306 ce := &callEngine{} 307 f := &function{ 308 moduleInstance: &wasm.ModuleInstance{Engine: &moduleEngine{}}, 309 parent: &compiledFunction{body: []unionOperation{ 310 {Kind: operationKindConstI32, U1: uint64(uint32(tc.in))}, 311 {Kind: translateToIRoperationKind(tc.opcode)}, 312 {Kind: operationKindBr, U1: uint64(math.MaxUint64)}, 313 }}, 314 } 315 ce.callNativeFunc(testCtx, &wasm.ModuleInstance{}, f) 316 require.Equal(t, tc.expected, int32(uint32(ce.popValue()))) 317 }) 318 } 319 }) 320 t.Run("64bit", func(t *testing.T) { 321 tests := []struct { 322 in int64 323 expected int64 324 opcode wasm.Opcode 325 }{ 326 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L271-L277 327 {in: 0, expected: 0, opcode: wasm.OpcodeI64Extend8S}, 328 {in: 0x7f, expected: 127, opcode: wasm.OpcodeI64Extend8S}, 329 {in: 0x80, expected: -128, opcode: wasm.OpcodeI64Extend8S}, 330 {in: 0xff, expected: -1, opcode: wasm.OpcodeI64Extend8S}, 331 {in: 0x01234567_89abcd_00, expected: 0, opcode: wasm.OpcodeI64Extend8S}, 332 {in: 81985529216486784 /* = 0xfedcba98_765432_80 bit pattern */, expected: -0x80, opcode: wasm.OpcodeI64Extend8S}, 333 {in: -1, expected: -1, opcode: wasm.OpcodeI64Extend8S}, 334 335 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L279-L285 336 {in: 0, expected: 0, opcode: wasm.OpcodeI64Extend16S}, 337 {in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI64Extend16S}, 338 {in: 0x8000, expected: -32768, opcode: wasm.OpcodeI64Extend16S}, 339 {in: 0xffff, expected: -1, opcode: wasm.OpcodeI64Extend16S}, 340 {in: 0x12345678_9abc_0000, expected: 0, opcode: wasm.OpcodeI64Extend16S}, 341 {in: 81985529216466944 /* = 0xfedcba98_7654_8000 bit pattern */, expected: -0x8000, opcode: wasm.OpcodeI64Extend16S}, 342 {in: -1, expected: -1, opcode: wasm.OpcodeI64Extend16S}, 343 344 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L287-L296 345 {in: 0, expected: 0, opcode: wasm.OpcodeI64Extend32S}, 346 {in: 0x7fff, expected: 32767, opcode: wasm.OpcodeI64Extend32S}, 347 {in: 0x8000, expected: 32768, opcode: wasm.OpcodeI64Extend32S}, 348 {in: 0xffff, expected: 65535, opcode: wasm.OpcodeI64Extend32S}, 349 {in: 0x7fffffff, expected: 0x7fffffff, opcode: wasm.OpcodeI64Extend32S}, 350 {in: 0x80000000, expected: -0x80000000, opcode: wasm.OpcodeI64Extend32S}, 351 {in: 0xffffffff, expected: -1, opcode: wasm.OpcodeI64Extend32S}, 352 {in: 0x01234567_00000000, expected: 0, opcode: wasm.OpcodeI64Extend32S}, 353 {in: -81985529054232576 /* = 0xfedcba98_80000000 bit pattern */, expected: -0x80000000, opcode: wasm.OpcodeI64Extend32S}, 354 {in: -1, expected: -1, opcode: wasm.OpcodeI64Extend32S}, 355 } 356 357 for _, tt := range tests { 358 tc := tt 359 t.Run(fmt.Sprintf("%s(i64.const(0x%x))", wasm.InstructionName(tc.opcode), tc.in), func(t *testing.T) { 360 ce := &callEngine{} 361 f := &function{ 362 moduleInstance: &wasm.ModuleInstance{Engine: &moduleEngine{}}, 363 parent: &compiledFunction{body: []unionOperation{ 364 {Kind: operationKindConstI64, U1: uint64(tc.in)}, 365 {Kind: translateToIRoperationKind(tc.opcode)}, 366 {Kind: operationKindBr, U1: uint64(math.MaxUint64)}, 367 }}, 368 } 369 ce.callNativeFunc(testCtx, &wasm.ModuleInstance{}, f) 370 require.Equal(t, tc.expected, int64(ce.popValue())) 371 }) 372 } 373 }) 374 } 375 376 func TestInterpreter_Compile(t *testing.T) { 377 t.Run("uncompiled", func(t *testing.T) { 378 e := NewEngine(testCtx, api.CoreFeaturesV1, nil).(*engine) 379 _, err := e.NewModuleEngine( 380 &wasm.Module{}, 381 nil, // functions 382 ) 383 require.EqualError(t, err, "source module must be compiled before instantiation") 384 }) 385 t.Run("fail", func(t *testing.T) { 386 e := NewEngine(testCtx, api.CoreFeaturesV1, nil).(*engine) 387 388 errModule := &wasm.Module{ 389 TypeSection: []wasm.FunctionType{{}}, 390 FunctionSection: []wasm.Index{0, 0, 0}, 391 CodeSection: []wasm.Code{ 392 {Body: []byte{wasm.OpcodeEnd}}, 393 {Body: []byte{wasm.OpcodeEnd}}, 394 {Body: []byte{wasm.OpcodeCall}}, // Call instruction without immediate for call target index is invalid and should fail to compile. 395 }, 396 ID: wasm.ModuleID{}, 397 } 398 399 err := e.CompileModule(testCtx, errModule, nil, false) 400 require.EqualError(t, err, "handling instruction: apply stack failed for call: reading immediates: EOF") 401 402 // On the compilation failure, all the compiled functions including succeeded ones must be released. 403 _, ok := e.compiledFunctions[errModule.ID] 404 require.False(t, ok) 405 }) 406 t.Run("ok", func(t *testing.T) { 407 e := NewEngine(testCtx, api.CoreFeaturesV1, nil).(*engine) 408 409 okModule := &wasm.Module{ 410 TypeSection: []wasm.FunctionType{{}}, 411 FunctionSection: []wasm.Index{0, 0, 0, 0}, 412 CodeSection: []wasm.Code{ 413 {Body: []byte{wasm.OpcodeEnd}}, 414 {Body: []byte{wasm.OpcodeEnd}}, 415 {Body: []byte{wasm.OpcodeEnd}}, 416 {Body: []byte{wasm.OpcodeEnd}}, 417 }, 418 ID: wasm.ModuleID{}, 419 } 420 err := e.CompileModule(testCtx, okModule, nil, false) 421 require.NoError(t, err) 422 423 compiled, ok := e.compiledFunctions[okModule.ID] 424 require.True(t, ok) 425 require.Equal(t, len(okModule.FunctionSection), len(compiled)) 426 427 _, ok = e.compiledFunctions[okModule.ID] 428 require.True(t, ok) 429 }) 430 } 431 432 func TestEngine_CachedCompiledFunctionPerModule(t *testing.T) { 433 e := NewEngine(testCtx, api.CoreFeaturesV1, nil).(*engine) 434 exp := []compiledFunction{ 435 {body: []unionOperation{}}, 436 {body: []unionOperation{}}, 437 } 438 m := &wasm.Module{} 439 440 e.addCompiledFunctions(m, exp) 441 442 actual, ok := e.getCompiledFunctions(m) 443 require.True(t, ok) 444 require.Equal(t, len(exp), len(actual)) 445 for i := range actual { 446 require.Equal(t, exp[i], actual[i]) 447 } 448 449 e.deleteCompiledFunctions(m) 450 _, ok = e.getCompiledFunctions(m) 451 require.False(t, ok) 452 }