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