1 package compiler
2
3 import (
4 "fmt"
5 "math"
6 "testing"
7
8 "github.com/tetratelabs/wazero/internal/asm"
9 "github.com/tetratelabs/wazero/internal/testing/require"
10 "github.com/tetratelabs/wazero/internal/wasm"
11 "github.com/tetratelabs/wazero/internal/wazeroir"
12 )
13
14 func TestCompiler_compileReinterpret(t *testing.T) {
15 for _, kind := range []wazeroir.OperationKind{
16 wazeroir.OperationKindF32ReinterpretFromI32,
17 wazeroir.OperationKindF64ReinterpretFromI64,
18 wazeroir.OperationKindI32ReinterpretFromF32,
19 wazeroir.OperationKindI64ReinterpretFromF64,
20 } {
21 kind := kind
22 t.Run(kind.String(), func(t *testing.T) {
23 for _, originOnStack := range []bool{false, true} {
24 originOnStack := originOnStack
25 t.Run(fmt.Sprintf("%v", originOnStack), func(t *testing.T) {
26 for _, v := range []uint64{
27 0, 1, 1 << 16, 1 << 31, 1 << 32, 1 << 63,
28 math.MaxInt32, math.MaxUint32, math.MaxUint64,
29 } {
30 v := v
31 t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
32 env := newCompilerEnvironment()
33 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
34 err := compiler.compilePreamble()
35 require.NoError(t, err)
36
37 if originOnStack {
38 loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
39 env.stack()[loc.stackPointer] = v
40 env.setStackPointer(1)
41 }
42
43 var is32Bit bool
44 switch kind {
45 case wazeroir.OperationKindF32ReinterpretFromI32:
46 is32Bit = true
47 if !originOnStack {
48 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(v))))
49 require.NoError(t, err)
50 }
51 err = compiler.compileF32ReinterpretFromI32()
52 require.NoError(t, err)
53 case wazeroir.OperationKindF64ReinterpretFromI64:
54 if !originOnStack {
55 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
56 require.NoError(t, err)
57 }
58 err = compiler.compileF64ReinterpretFromI64()
59 require.NoError(t, err)
60 case wazeroir.OperationKindI32ReinterpretFromF32:
61 is32Bit = true
62 if !originOnStack {
63 err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v)))))
64 require.NoError(t, err)
65 }
66 err = compiler.compileI32ReinterpretFromF32()
67 require.NoError(t, err)
68 case wazeroir.OperationKindI64ReinterpretFromF64:
69 if !originOnStack {
70 err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(math.Float64frombits(v))))
71 require.NoError(t, err)
72 }
73 err = compiler.compileI64ReinterpretFromF64()
74 require.NoError(t, err)
75 default:
76 t.Fail()
77 }
78
79 err = compiler.compileReturnFunction()
80 require.NoError(t, err)
81
82 code := asm.CodeSegment{}
83 defer func() { require.NoError(t, code.Unmap()) }()
84
85
86 _, err = compiler.compile(code.NextCodeSection())
87 require.NoError(t, err)
88 env.exec(code.Bytes())
89
90
91 if is32Bit {
92 require.Equal(t, uint32(v), env.stackTopAsUint32())
93 } else {
94 require.Equal(t, v, env.stackTopAsUint64())
95 }
96 })
97 }
98 })
99 }
100 })
101 }
102 }
103
104 func TestCompiler_compileExtend(t *testing.T) {
105 for _, signed := range []bool{false, true} {
106 signed := signed
107 t.Run(fmt.Sprintf("signed=%v", signed), func(t *testing.T) {
108 for _, v := range []uint32{
109 0, 1, 1 << 14, 1 << 31, math.MaxUint32, 0xFFFFFFFF, math.MaxInt32,
110 } {
111 v := v
112 t.Run(fmt.Sprintf("%v", v), func(t *testing.T) {
113 env := newCompilerEnvironment()
114 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
115 err := compiler.compilePreamble()
116 require.NoError(t, err)
117
118
119 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(v)))
120 require.NoError(t, err)
121
122 err = compiler.compileExtend(operationPtr(wazeroir.NewOperationExtend(signed)))
123 require.NoError(t, err)
124
125 err = compiler.compileReturnFunction()
126 require.NoError(t, err)
127
128 code := asm.CodeSegment{}
129 defer func() { require.NoError(t, code.Unmap()) }()
130
131
132 _, err = compiler.compile(code.NextCodeSection())
133 require.NoError(t, err)
134 env.exec(code.Bytes())
135
136 require.Equal(t, uint64(1), env.stackPointer())
137 if signed {
138 expected := int64(int32(v))
139 require.Equal(t, expected, env.stackTopAsInt64())
140 } else {
141 expected := uint64(uint32(v))
142 require.Equal(t, expected, env.stackTopAsUint64())
143 }
144 })
145 }
146 })
147 }
148 }
149
150 func TestCompiler_compileITruncFromF(t *testing.T) {
151 tests := []struct {
152 outputType wazeroir.SignedInt
153 inputType wazeroir.Float
154 nonTrapping bool
155 }{
156 {outputType: wazeroir.SignedInt32, inputType: wazeroir.Float32},
157 {outputType: wazeroir.SignedInt32, inputType: wazeroir.Float64},
158 {outputType: wazeroir.SignedInt64, inputType: wazeroir.Float32},
159 {outputType: wazeroir.SignedInt64, inputType: wazeroir.Float64},
160 {outputType: wazeroir.SignedUint32, inputType: wazeroir.Float32},
161 {outputType: wazeroir.SignedUint32, inputType: wazeroir.Float64},
162 {outputType: wazeroir.SignedUint64, inputType: wazeroir.Float32},
163 {outputType: wazeroir.SignedUint64, inputType: wazeroir.Float64},
164 {outputType: wazeroir.SignedInt32, inputType: wazeroir.Float32, nonTrapping: true},
165 {outputType: wazeroir.SignedInt32, inputType: wazeroir.Float64, nonTrapping: true},
166 {outputType: wazeroir.SignedInt64, inputType: wazeroir.Float32, nonTrapping: true},
167 {outputType: wazeroir.SignedInt64, inputType: wazeroir.Float64, nonTrapping: true},
168 {outputType: wazeroir.SignedUint32, inputType: wazeroir.Float32, nonTrapping: true},
169 {outputType: wazeroir.SignedUint32, inputType: wazeroir.Float64, nonTrapping: true},
170 {outputType: wazeroir.SignedUint64, inputType: wazeroir.Float32, nonTrapping: true},
171 {outputType: wazeroir.SignedUint64, inputType: wazeroir.Float64, nonTrapping: true},
172 }
173
174 for _, tt := range tests {
175 tc := tt
176 t.Run(fmt.Sprintf("%s from %s (non-trapping=%v)", tc.outputType, tc.inputType, tc.nonTrapping), func(t *testing.T) {
177 for _, v := range []float64{
178 1.0,
179 } {
180 v := v
181 if v == math.MaxInt32 {
182
183 require.Equal(t, float32(2147483648.0) , float32(v))
184 } else if v == math.MaxUint32 {
185
186 require.Equal(t, float32(4294967296 ), float32(v))
187 } else if v == math.MaxInt64 {
188
189 require.Equal(t, float32(9223372036854775808.0) , float32(v))
190 require.Equal(t, float64(9223372036854775808.0) , float64(v))
191 } else if v == math.MaxUint64 {
192
193 require.Equal(t, float32(18446744073709551616.0) , float32(v))
194 require.Equal(t, float64(18446744073709551616.0) , float64(v))
195 }
196
197 t.Run(fmt.Sprintf("%v", v), func(t *testing.T) {
198 env := newCompilerEnvironment()
199 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
200 err := compiler.compilePreamble()
201 require.NoError(t, err)
202
203
204 if tc.inputType == wazeroir.Float32 {
205 err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(float32(v))))
206 } else {
207 err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(v)))
208 }
209 require.NoError(t, err)
210
211 err = compiler.compileITruncFromF(operationPtr(wazeroir.NewOperationITruncFromF(
212 tc.inputType, tc.outputType, tc.nonTrapping,
213 )))
214 require.NoError(t, err)
215
216 err = compiler.compileReturnFunction()
217 require.NoError(t, err)
218
219 code := asm.CodeSegment{}
220 defer func() { require.NoError(t, code.Unmap()) }()
221
222
223 _, err = compiler.compile(code.NextCodeSection())
224 require.NoError(t, err)
225 env.exec(code.Bytes())
226
227
228 expStatus := nativeCallStatusCodeReturned
229 if math.IsNaN(v) {
230 if tc.nonTrapping {
231 v = 0
232 } else {
233 expStatus = nativeCallStatusCodeInvalidFloatToIntConversion
234 }
235 }
236 if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedInt32 {
237 f32 := float32(v)
238 exp := int32(math.Trunc(float64(f32)))
239 if f32 < math.MinInt32 || f32 >= math.MaxInt32 {
240 if tc.nonTrapping {
241 if f32 < 0 {
242 exp = math.MinInt32
243 } else {
244 exp = math.MaxInt32
245 }
246 } else {
247 expStatus = nativeCallStatusIntegerOverflow
248 }
249 }
250 if expStatus == nativeCallStatusCodeReturned {
251 require.Equal(t, exp, env.stackTopAsInt32())
252 }
253 } else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedInt64 {
254 f32 := float32(v)
255 exp := int64(math.Trunc(float64(f32)))
256 if f32 < math.MinInt64 || f32 >= math.MaxInt64 {
257 if tc.nonTrapping {
258 if f32 < 0 {
259 exp = math.MinInt64
260 } else {
261 exp = math.MaxInt64
262 }
263 } else {
264 expStatus = nativeCallStatusIntegerOverflow
265 }
266 }
267 if expStatus == nativeCallStatusCodeReturned {
268 require.Equal(t, exp, env.stackTopAsInt64())
269 }
270 } else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedInt32 {
271 if v < math.MinInt32 || v > math.MaxInt32 {
272 if tc.nonTrapping {
273 if v < 0 {
274 v = math.MinInt32
275 } else {
276 v = math.MaxInt32
277 }
278 } else {
279 expStatus = nativeCallStatusIntegerOverflow
280 }
281 }
282 if expStatus == nativeCallStatusCodeReturned {
283 require.Equal(t, int32(math.Trunc(v)), env.stackTopAsInt32())
284 }
285 } else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedInt64 {
286 exp := int64(math.Trunc(v))
287 if v < math.MinInt64 || v >= math.MaxInt64 {
288 if tc.nonTrapping {
289 if v < 0 {
290 exp = math.MinInt64
291 } else {
292 exp = math.MaxInt64
293 }
294 } else {
295 expStatus = nativeCallStatusIntegerOverflow
296 }
297 }
298 if expStatus == nativeCallStatusCodeReturned {
299 require.Equal(t, exp, env.stackTopAsInt64())
300 }
301 } else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedUint32 {
302 f32 := float32(v)
303 exp := uint32(math.Trunc(float64(f32)))
304 if f32 < 0 || f32 >= math.MaxUint32 {
305 if tc.nonTrapping {
306 if v < 0 {
307 exp = 0
308 } else {
309 exp = math.MaxUint32
310 }
311 } else {
312 expStatus = nativeCallStatusIntegerOverflow
313 }
314 }
315 if expStatus == nativeCallStatusCodeReturned {
316 require.Equal(t, exp, env.stackTopAsUint32())
317 }
318 } else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedUint32 {
319 exp := uint32(math.Trunc(v))
320 if v < 0 || v > math.MaxUint32 {
321 if tc.nonTrapping {
322 if v < 0 {
323 exp = 0
324 } else {
325 exp = math.MaxUint32
326 }
327 } else {
328 expStatus = nativeCallStatusIntegerOverflow
329 }
330 }
331 if expStatus == nativeCallStatusCodeReturned {
332 require.Equal(t, exp, env.stackTopAsUint32())
333 }
334 } else if tc.inputType == wazeroir.Float32 && tc.outputType == wazeroir.SignedUint64 {
335 f32 := float32(v)
336 exp := uint64(math.Trunc(float64(f32)))
337 if f32 < 0 || f32 >= math.MaxUint64 {
338 if tc.nonTrapping {
339 if v < 0 {
340 exp = 0
341 } else {
342 exp = math.MaxUint64
343 }
344 } else {
345 expStatus = nativeCallStatusIntegerOverflow
346 }
347 }
348 if expStatus == nativeCallStatusCodeReturned {
349 require.Equal(t, exp, env.stackTopAsUint64())
350 }
351 } else if tc.inputType == wazeroir.Float64 && tc.outputType == wazeroir.SignedUint64 {
352 exp := uint64(math.Trunc(v))
353 if v < 0 || v >= math.MaxUint64 {
354 if tc.nonTrapping {
355 if v < 0 {
356 exp = 0
357 } else {
358 exp = math.MaxUint64
359 }
360 } else {
361 expStatus = nativeCallStatusIntegerOverflow
362 }
363 }
364 if expStatus == nativeCallStatusCodeReturned {
365 require.Equal(t, exp, env.stackTopAsUint64())
366 }
367 }
368 require.Equal(t, expStatus, env.compilerStatus())
369 })
370 }
371 })
372 }
373 }
374
375 func TestCompiler_compileFConvertFromI(t *testing.T) {
376 tests := []struct {
377 inputType wazeroir.SignedInt
378 outputType wazeroir.Float
379 }{
380 {inputType: wazeroir.SignedInt32, outputType: wazeroir.Float32},
381 {inputType: wazeroir.SignedInt32, outputType: wazeroir.Float64},
382 {inputType: wazeroir.SignedInt64, outputType: wazeroir.Float32},
383 {inputType: wazeroir.SignedInt64, outputType: wazeroir.Float64},
384 {inputType: wazeroir.SignedUint32, outputType: wazeroir.Float32},
385 {inputType: wazeroir.SignedUint32, outputType: wazeroir.Float64},
386 {inputType: wazeroir.SignedUint64, outputType: wazeroir.Float32},
387 {inputType: wazeroir.SignedUint64, outputType: wazeroir.Float64},
388 }
389
390 for _, tt := range tests {
391 tc := tt
392 t.Run(fmt.Sprintf("%s from %s", tc.outputType, tc.inputType), func(t *testing.T) {
393 for _, v := range []uint64{
394 0, 1, 12345, 1 << 31, 1 << 32, 1 << 54, 1 << 63,
395 0xffff_ffff_ffff_ffff, 0xffff_ffff,
396 0xffff_ffff_ffff_fffe, 0xffff_fffe,
397 math.MaxUint32, math.MaxUint64, math.MaxInt32, math.MaxInt64,
398 } {
399 t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
400 env := newCompilerEnvironment()
401 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
402 err := compiler.compilePreamble()
403 require.NoError(t, err)
404
405
406 if tc.inputType == wazeroir.SignedInt32 || tc.inputType == wazeroir.SignedUint32 {
407 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(v))))
408 } else {
409 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(uint64(v))))
410 }
411 require.NoError(t, err)
412
413 err = compiler.compileFConvertFromI(operationPtr(wazeroir.NewOperationFConvertFromI(
414 tc.inputType, tc.outputType,
415 )))
416 require.NoError(t, err)
417
418 err = compiler.compileReturnFunction()
419 require.NoError(t, err)
420
421 code := asm.CodeSegment{}
422 defer func() { require.NoError(t, code.Unmap()) }()
423
424
425 _, err = compiler.compile(code.NextCodeSection())
426 require.NoError(t, err)
427 env.exec(code.Bytes())
428
429
430 require.Equal(t, uint64(1), env.stackPointer())
431 actualBits := env.stackTopAsUint64()
432 if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedInt32 {
433 exp := float32(int32(v))
434 actual := math.Float32frombits(uint32(actualBits))
435 require.Equal(t, exp, actual)
436 } else if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedInt64 {
437 exp := float32(int64(v))
438 actual := math.Float32frombits(uint32(actualBits))
439 require.Equal(t, exp, actual)
440 } else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedInt32 {
441 exp := float64(int32(v))
442 actual := math.Float64frombits(actualBits)
443 require.Equal(t, exp, actual)
444 } else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedInt64 {
445 exp := float64(int64(v))
446 actual := math.Float64frombits(actualBits)
447 require.Equal(t, exp, actual)
448 } else if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedUint32 {
449 exp := float32(uint32(v))
450 actual := math.Float32frombits(uint32(actualBits))
451 require.Equal(t, exp, actual)
452 } else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedUint32 {
453 exp := float64(uint32(v))
454 actual := math.Float64frombits(actualBits)
455 require.Equal(t, exp, actual)
456 } else if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedUint64 {
457 exp := float32(v)
458 actual := math.Float32frombits(uint32(actualBits))
459 require.Equal(t, exp, actual)
460 } else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedUint64 {
461 exp := float64(v)
462 actual := math.Float64frombits(actualBits)
463 require.Equal(t, exp, actual)
464 }
465 })
466 }
467 })
468 }
469 }
470
471 func TestCompiler_compileF64PromoteFromF32(t *testing.T) {
472 for _, v := range []float32{
473 0, 100, -100, 1, -1,
474 100.01234124, -100.01234124, 200.12315,
475 math.MaxFloat32,
476 math.SmallestNonzeroFloat32,
477 float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()),
478 } {
479 t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
480 env := newCompilerEnvironment()
481 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
482 err := compiler.compilePreamble()
483 require.NoError(t, err)
484
485
486 err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(v)))
487 require.NoError(t, err)
488
489 err = compiler.compileF64PromoteFromF32()
490 require.NoError(t, err)
491
492 err = compiler.compileReturnFunction()
493 require.NoError(t, err)
494
495 code := asm.CodeSegment{}
496 defer func() { require.NoError(t, code.Unmap()) }()
497
498
499 _, err = compiler.compile(code.NextCodeSection())
500 require.NoError(t, err)
501 env.exec(code.Bytes())
502
503
504 require.Equal(t, uint64(1), env.stackPointer())
505 if math.IsNaN(float64(v)) {
506 require.True(t, math.IsNaN(env.stackTopAsFloat64()))
507 } else {
508 exp := float64(v)
509 actual := env.stackTopAsFloat64()
510 require.Equal(t, exp, actual)
511 }
512 })
513 }
514 }
515
516 func TestCompiler_compileF32DemoteFromF64(t *testing.T) {
517 for _, v := range []float64{
518 0, 100, -100, 1, -1,
519 100.01234124, -100.01234124, 200.12315,
520 math.MaxFloat32,
521 math.SmallestNonzeroFloat32,
522 math.MaxFloat64,
523 math.SmallestNonzeroFloat64,
524 6.8719476736e+10,
525 1.37438953472e+11,
526 math.Inf(1), math.Inf(-1), math.NaN(),
527 } {
528 t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
529 env := newCompilerEnvironment()
530 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
531 err := compiler.compilePreamble()
532 require.NoError(t, err)
533
534
535 err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(v)))
536 require.NoError(t, err)
537
538 err = compiler.compileF32DemoteFromF64()
539 require.NoError(t, err)
540
541 err = compiler.compileReturnFunction()
542 require.NoError(t, err)
543
544 code := asm.CodeSegment{}
545 defer func() { require.NoError(t, code.Unmap()) }()
546
547
548 _, err = compiler.compile(code.NextCodeSection())
549 require.NoError(t, err)
550 env.exec(code.Bytes())
551
552
553 require.Equal(t, uint64(1), env.stackPointer())
554 if math.IsNaN(v) {
555 require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
556 } else {
557 exp := float32(v)
558 actual := env.stackTopAsFloat32()
559 require.Equal(t, exp, actual)
560 }
561 })
562 }
563 }
564
View as plain text