github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/compiler/compiler_conversion_test.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "math" 6 "testing" 7 8 "github.com/bananabytelabs/wazero/internal/asm" 9 "github.com/bananabytelabs/wazero/internal/testing/require" 10 "github.com/bananabytelabs/wazero/internal/wasm" 11 "github.com/bananabytelabs/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 // Generate and run the code under test. 86 _, err = compiler.compile(code.NextCodeSection()) 87 require.NoError(t, err) 88 env.exec(code.Bytes()) 89 90 // Reinterpret must preserve the bit-pattern. 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 // Setup the promote target. 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 // Generate and run the code under test. 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 // Note that math.MaxInt32 is rounded up to math.MaxInt32+1 in 32-bit float representation. 183 require.Equal(t, float32(2147483648.0) /* = math.MaxInt32+1 */, float32(v)) 184 } else if v == math.MaxUint32 { 185 // Note that math.MaxUint32 is rounded up to math.MaxUint32+1 in 32-bit float representation. 186 require.Equal(t, float32(4294967296 /* = math.MaxUint32+1 */), float32(v)) 187 } else if v == math.MaxInt64 { 188 // Note that math.MaxInt64 is rounded up to math.MaxInt64+1 in 32/64-bit float representation. 189 require.Equal(t, float32(9223372036854775808.0) /* = math.MaxInt64+1 */, float32(v)) 190 require.Equal(t, float64(9223372036854775808.0) /* = math.MaxInt64+1 */, float64(v)) 191 } else if v == math.MaxUint64 { 192 // Note that math.MaxUint64 is rounded up to math.MaxUint64+1 in 32/64-bit float representation. 193 require.Equal(t, float32(18446744073709551616.0) /* = math.MaxInt64+1 */, float32(v)) 194 require.Equal(t, float64(18446744073709551616.0) /* = math.MaxInt64+1 */, 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 // Setup the conversion target. 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 // Generate and run the code under test. 223 _, err = compiler.compile(code.NextCodeSection()) 224 require.NoError(t, err) 225 env.exec(code.Bytes()) 226 227 // Check the result. 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 // Setup the conversion target. 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 // Generate and run the code under test. 425 _, err = compiler.compile(code.NextCodeSection()) 426 require.NoError(t, err) 427 env.exec(code.Bytes()) 428 429 // Check the result. 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 // Setup the promote target. 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 // Generate and run the code under test. 499 _, err = compiler.compile(code.NextCodeSection()) 500 require.NoError(t, err) 501 env.exec(code.Bytes()) 502 503 // Check the result. 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, /* = 1 << 36 */ 525 1.37438953472e+11, /* = 1 << 37 */ 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 // Setup the demote target. 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 // Generate and run the code under test. 548 _, err = compiler.compile(code.NextCodeSection()) 549 require.NoError(t, err) 550 env.exec(code.Bytes()) 551 552 // Check the result. 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 }