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