wa-lang.org/wazero@v1.0.2/internal/engine/compiler/compiler_numeric_test.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "math" 6 "math/bits" 7 "testing" 8 9 "wa-lang.org/wazero/internal/moremath" 10 "wa-lang.org/wazero/internal/testing/require" 11 "wa-lang.org/wazero/internal/wazeroir" 12 ) 13 14 func TestCompiler_compileConsts(t *testing.T) { 15 for _, op := range []wazeroir.OperationKind{ 16 wazeroir.OperationKindConstI32, 17 wazeroir.OperationKindConstI64, 18 wazeroir.OperationKindConstF32, 19 wazeroir.OperationKindConstF64, 20 wazeroir.OperationKindV128Const, 21 } { 22 op := op 23 t.Run(op.String(), func(t *testing.T) { 24 for _, val := range []uint64{ 25 0x0, 0x1, 0x1111000, 1 << 16, 1 << 21, 1 << 27, 1 << 32, 1<<32 + 1, 1 << 53, 26 math.Float64bits(math.Inf(1)), 27 math.Float64bits(math.Inf(-1)), 28 math.Float64bits(math.NaN()), 29 math.MaxUint32, 30 math.MaxInt32, 31 math.MaxUint64, 32 math.MaxInt64, 33 uint64(math.Float32bits(float32(math.Inf(1)))), 34 uint64(math.Float32bits(float32(math.Inf(-1)))), 35 uint64(math.Float32bits(float32(math.NaN()))), 36 } { 37 t.Run(fmt.Sprintf("0x%x", val), func(t *testing.T) { 38 env := newCompilerEnvironment() 39 40 // Compile code. 41 compiler := env.requireNewCompiler(t, newCompiler, nil) 42 err := compiler.compilePreamble() 43 require.NoError(t, err) 44 45 switch op { 46 case wazeroir.OperationKindConstI32: 47 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(val)}) 48 case wazeroir.OperationKindConstI64: 49 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: val}) 50 case wazeroir.OperationKindConstF32: 51 err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(val))}) 52 case wazeroir.OperationKindConstF64: 53 err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(val)}) 54 case wazeroir.OperationKindV128Const: 55 err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: val, Hi: ^val}) 56 } 57 require.NoError(t, err) 58 59 // After compiling const operations, we must see the register allocated value on the top of value. 60 loc := compiler.runtimeValueLocationStack().peek() 61 require.True(t, loc.onRegister()) 62 63 if op == wazeroir.OperationKindV128Const { 64 require.Equal(t, runtimeValueTypeV128Hi, loc.valueType) 65 } 66 67 err = compiler.compileReturnFunction() 68 require.NoError(t, err) 69 70 // Generate the code under test. 71 code, _, err := compiler.compile() 72 require.NoError(t, err) 73 74 // Run native code. 75 env.exec(code) 76 77 // Compiler status must be returned. 78 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 79 if op == wazeroir.OperationKindV128Const { 80 require.Equal(t, uint64(2), env.stackPointer()) // a vector value consists of two uint64. 81 } else { 82 require.Equal(t, uint64(1), env.stackPointer()) 83 } 84 85 switch op { 86 case wazeroir.OperationKindConstI32, wazeroir.OperationKindConstF32: 87 require.Equal(t, uint32(val), env.stackTopAsUint32()) 88 case wazeroir.OperationKindConstI64, wazeroir.OperationKindConstF64: 89 require.Equal(t, val, env.stackTopAsUint64()) 90 case wazeroir.OperationKindV128Const: 91 lo, hi := env.stackTopAsV128() 92 require.Equal(t, val, lo) 93 require.Equal(t, ^val, hi) 94 } 95 }) 96 } 97 }) 98 } 99 } 100 101 func TestCompiler_compile_Add_Sub_Mul(t *testing.T) { 102 for _, kind := range []wazeroir.OperationKind{ 103 wazeroir.OperationKindAdd, 104 wazeroir.OperationKindSub, 105 wazeroir.OperationKindMul, 106 } { 107 kind := kind 108 t.Run(kind.String(), func(t *testing.T) { 109 for _, unsignedType := range []wazeroir.UnsignedType{ 110 wazeroir.UnsignedTypeI32, 111 wazeroir.UnsignedTypeI64, 112 wazeroir.UnsignedTypeF32, 113 wazeroir.UnsignedTypeF64, 114 } { 115 unsignedType := unsignedType 116 t.Run(unsignedType.String(), func(t *testing.T) { 117 for _, values := range [][2]uint64{ 118 {0, 0}, 119 {1, 1}, 120 {2, 1}, 121 {100, 1}, 122 {1, 0}, 123 {0, 1}, 124 {math.MaxInt16, math.MaxInt32}, 125 {1 << 14, 1 << 21}, 126 {1 << 14, 1 << 21}, 127 {0xffff_ffff_ffff_ffff, 0}, 128 {0xffff_ffff_ffff_ffff, 1}, 129 {0, 0xffff_ffff_ffff_ffff}, 130 {1, 0xffff_ffff_ffff_ffff}, 131 {0, math.Float64bits(math.Inf(1))}, 132 {0, math.Float64bits(math.Inf(-1))}, 133 {math.Float64bits(math.Inf(1)), 1}, 134 {math.Float64bits(math.Inf(-1)), 1}, 135 {math.Float64bits(1.11231), math.Float64bits(math.Inf(1))}, 136 {math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))}, 137 {math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)}, 138 {math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)}, 139 {math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())}, 140 {math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())}, 141 {math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))}, 142 {math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))}, 143 } { 144 x1, x2 := values[0], values[1] 145 t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) { 146 env := newCompilerEnvironment() 147 compiler := env.requireNewCompiler(t, newCompiler, nil) 148 err := compiler.compilePreamble() 149 require.NoError(t, err) 150 151 // Emit consts operands. 152 for _, v := range []uint64{x1, x2} { 153 switch unsignedType { 154 case wazeroir.UnsignedTypeI32: 155 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)}) 156 case wazeroir.UnsignedTypeI64: 157 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v}) 158 case wazeroir.UnsignedTypeF32: 159 err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))}) 160 case wazeroir.UnsignedTypeF64: 161 err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)}) 162 } 163 require.NoError(t, err) 164 } 165 166 // At this point, two values exist. 167 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 168 169 // Emit the operation. 170 switch kind { 171 case wazeroir.OperationKindAdd: 172 err = compiler.compileAdd(&wazeroir.OperationAdd{Type: unsignedType}) 173 case wazeroir.OperationKindSub: 174 err = compiler.compileSub(&wazeroir.OperationSub{Type: unsignedType}) 175 case wazeroir.OperationKindMul: 176 err = compiler.compileMul(&wazeroir.OperationMul{Type: unsignedType}) 177 } 178 require.NoError(t, err) 179 180 // We consumed two values, but push the result back. 181 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 182 resultLocation := compiler.runtimeValueLocationStack().peek() 183 // Plus the result must be located on a register. 184 require.True(t, resultLocation.onRegister()) 185 // Also, the result must have an appropriate register type. 186 if unsignedType == wazeroir.UnsignedTypeF32 || unsignedType == wazeroir.UnsignedTypeF64 { 187 require.Equal(t, registerTypeVector, resultLocation.getRegisterType()) 188 } else { 189 require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType()) 190 } 191 192 err = compiler.compileReturnFunction() 193 require.NoError(t, err) 194 195 // Compile and execute the code under test. 196 code, _, err := compiler.compile() 197 require.NoError(t, err) 198 env.exec(code) 199 200 // Check the stack. 201 require.Equal(t, uint64(1), env.stackPointer()) 202 203 switch kind { 204 case wazeroir.OperationKindAdd: 205 switch unsignedType { 206 case wazeroir.UnsignedTypeI32: 207 require.Equal(t, uint32(x1)+uint32(x2), env.stackTopAsUint32()) 208 case wazeroir.UnsignedTypeI64: 209 require.Equal(t, x1+x2, env.stackTopAsUint64()) 210 case wazeroir.UnsignedTypeF32: 211 exp := math.Float32frombits(uint32(x1)) + math.Float32frombits(uint32(x2)) 212 // NaN cannot be compared with themselves, so we have to use IsNaN 213 if math.IsNaN(float64(exp)) { 214 require.True(t, math.IsNaN(float64(env.stackTopAsFloat32()))) 215 } else { 216 require.Equal(t, exp, env.stackTopAsFloat32()) 217 } 218 case wazeroir.UnsignedTypeF64: 219 exp := math.Float64frombits(x1) + math.Float64frombits(x2) 220 // NaN cannot be compared with themselves, so we have to use IsNaN 221 if math.IsNaN(exp) { 222 require.True(t, math.IsNaN(env.stackTopAsFloat64())) 223 } else { 224 require.Equal(t, exp, env.stackTopAsFloat64()) 225 } 226 } 227 case wazeroir.OperationKindSub: 228 switch unsignedType { 229 case wazeroir.UnsignedTypeI32: 230 require.Equal(t, uint32(x1)-uint32(x2), env.stackTopAsUint32()) 231 case wazeroir.UnsignedTypeI64: 232 require.Equal(t, x1-x2, env.stackTopAsUint64()) 233 case wazeroir.UnsignedTypeF32: 234 exp := math.Float32frombits(uint32(x1)) - math.Float32frombits(uint32(x2)) 235 // NaN cannot be compared with themselves, so we have to use IsNaN 236 if math.IsNaN(float64(exp)) { 237 require.True(t, math.IsNaN(float64(env.stackTopAsFloat32()))) 238 } else { 239 require.Equal(t, exp, env.stackTopAsFloat32()) 240 } 241 case wazeroir.UnsignedTypeF64: 242 exp := math.Float64frombits(x1) - math.Float64frombits(x2) 243 // NaN cannot be compared with themselves, so we have to use IsNaN 244 if math.IsNaN(exp) { 245 require.True(t, math.IsNaN(env.stackTopAsFloat64())) 246 } else { 247 require.Equal(t, exp, env.stackTopAsFloat64()) 248 } 249 } 250 case wazeroir.OperationKindMul: 251 switch unsignedType { 252 case wazeroir.UnsignedTypeI32: 253 require.Equal(t, uint32(x1)*uint32(x2), env.stackTopAsUint32()) 254 case wazeroir.UnsignedTypeI64: 255 require.Equal(t, x1*x2, env.stackTopAsUint64()) 256 case wazeroir.UnsignedTypeF32: 257 exp := math.Float32frombits(uint32(x1)) * math.Float32frombits(uint32(x2)) 258 // NaN cannot be compared with themselves, so we have to use IsNaN 259 if math.IsNaN(float64(exp)) { 260 require.True(t, math.IsNaN(float64(env.stackTopAsFloat32()))) 261 } else { 262 require.Equal(t, exp, env.stackTopAsFloat32()) 263 } 264 case wazeroir.UnsignedTypeF64: 265 exp := math.Float64frombits(x1) * math.Float64frombits(x2) 266 // NaN cannot be compared with themselves, so we have to use IsNaN 267 if math.IsNaN(exp) { 268 require.True(t, math.IsNaN(env.stackTopAsFloat64())) 269 } else { 270 require.Equal(t, exp, env.stackTopAsFloat64()) 271 } 272 } 273 } 274 }) 275 } 276 }) 277 } 278 }) 279 } 280 } 281 282 func TestCompiler_compile_And_Or_Xor_Shl_Rotl_Rotr(t *testing.T) { 283 for _, kind := range []wazeroir.OperationKind{ 284 wazeroir.OperationKindAnd, 285 wazeroir.OperationKindOr, 286 wazeroir.OperationKindXor, 287 wazeroir.OperationKindShl, 288 wazeroir.OperationKindRotl, 289 wazeroir.OperationKindRotr, 290 } { 291 kind := kind 292 t.Run(kind.String(), func(t *testing.T) { 293 for _, unsignedInt := range []wazeroir.UnsignedInt{ 294 wazeroir.UnsignedInt32, 295 wazeroir.UnsignedInt64, 296 } { 297 unsignedInt := unsignedInt 298 t.Run(unsignedInt.String(), func(t *testing.T) { 299 for _, values := range [][2]uint64{ 300 {0, 0}, 301 {0, 1}, 302 {1, 0}, 303 {1, 1}, 304 {1 << 31, 1}, 305 {1, 1 << 31}, 306 {1 << 31, 1 << 31}, 307 {1 << 63, 1}, 308 {1, 1 << 63}, 309 {1 << 63, 1 << 63}, 310 } { 311 x1, x2 := values[0], values[1] 312 for _, x1OnRegister := range []bool{ 313 true, false, 314 } { 315 x1OnRegister := x1OnRegister 316 t.Run(fmt.Sprintf("x1=0x%x(on_register=%v),x2=0x%x", x1, x1OnRegister, x2), func(t *testing.T) { 317 env := newCompilerEnvironment() 318 compiler := env.requireNewCompiler(t, newCompiler, nil) 319 err := compiler.compilePreamble() 320 require.NoError(t, err) 321 322 // Emit consts operands. 323 var x1Location *runtimeValueLocation 324 switch unsignedInt { 325 case wazeroir.UnsignedInt32: 326 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(x1)}) 327 require.NoError(t, err) 328 x1Location = compiler.runtimeValueLocationStack().peek() 329 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2}) 330 require.NoError(t, err) 331 case wazeroir.UnsignedInt64: 332 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x1}) 333 require.NoError(t, err) 334 x1Location = compiler.runtimeValueLocationStack().peek() 335 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2}) 336 require.NoError(t, err) 337 } 338 339 if !x1OnRegister { 340 compiler.compileReleaseRegisterToStack(x1Location) 341 } 342 343 // At this point, two values exist. 344 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 345 346 // Emit the operation. 347 switch kind { 348 case wazeroir.OperationKindAnd: 349 err = compiler.compileAnd(&wazeroir.OperationAnd{Type: unsignedInt}) 350 case wazeroir.OperationKindOr: 351 err = compiler.compileOr(&wazeroir.OperationOr{Type: unsignedInt}) 352 case wazeroir.OperationKindXor: 353 err = compiler.compileXor(&wazeroir.OperationXor{Type: unsignedInt}) 354 case wazeroir.OperationKindShl: 355 err = compiler.compileShl(&wazeroir.OperationShl{Type: unsignedInt}) 356 case wazeroir.OperationKindRotl: 357 err = compiler.compileRotl(&wazeroir.OperationRotl{Type: unsignedInt}) 358 case wazeroir.OperationKindRotr: 359 err = compiler.compileRotr(&wazeroir.OperationRotr{Type: unsignedInt}) 360 } 361 require.NoError(t, err) 362 363 // We consumed two values, but push the result back. 364 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 365 resultLocation := compiler.runtimeValueLocationStack().peek() 366 // Also, the result must have an appropriate register type. 367 require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType()) 368 369 err = compiler.compileReturnFunction() 370 require.NoError(t, err) 371 372 // Compile and execute the code under test. 373 code, _, err := compiler.compile() 374 require.NoError(t, err) 375 env.exec(code) 376 377 // Check the stack. 378 require.Equal(t, uint64(1), env.stackPointer()) 379 380 switch kind { 381 case wazeroir.OperationKindAnd: 382 switch unsignedInt { 383 case wazeroir.UnsignedInt32: 384 require.Equal(t, uint32(x1)&uint32(x2), env.stackTopAsUint32()) 385 case wazeroir.UnsignedInt64: 386 require.Equal(t, x1&x2, env.stackTopAsUint64()) 387 } 388 case wazeroir.OperationKindOr: 389 switch unsignedInt { 390 case wazeroir.UnsignedInt32: 391 require.Equal(t, uint32(x1)|uint32(x2), env.stackTopAsUint32()) 392 case wazeroir.UnsignedInt64: 393 require.Equal(t, x1|x2, env.stackTopAsUint64()) 394 } 395 case wazeroir.OperationKindXor: 396 switch unsignedInt { 397 case wazeroir.UnsignedInt32: 398 require.Equal(t, uint32(x1)^uint32(x2), env.stackTopAsUint32()) 399 case wazeroir.UnsignedInt64: 400 require.Equal(t, x1^x2, env.stackTopAsUint64()) 401 } 402 case wazeroir.OperationKindShl: 403 switch unsignedInt { 404 case wazeroir.UnsignedInt32: 405 require.Equal(t, uint32(x1)<<uint32(x2%32), env.stackTopAsUint32()) 406 case wazeroir.UnsignedInt64: 407 require.Equal(t, x1<<(x2%64), env.stackTopAsUint64()) 408 } 409 case wazeroir.OperationKindRotl: 410 switch unsignedInt { 411 case wazeroir.UnsignedInt32: 412 require.Equal(t, bits.RotateLeft32(uint32(x1), int(x2)), env.stackTopAsUint32()) 413 case wazeroir.UnsignedInt64: 414 require.Equal(t, bits.RotateLeft64(x1, int(x2)), env.stackTopAsUint64()) 415 } 416 case wazeroir.OperationKindRotr: 417 switch unsignedInt { 418 case wazeroir.UnsignedInt32: 419 require.Equal(t, bits.RotateLeft32(uint32(x1), -int(x2)), env.stackTopAsUint32()) 420 case wazeroir.UnsignedInt64: 421 require.Equal(t, bits.RotateLeft64(x1, -int(x2)), env.stackTopAsUint64()) 422 } 423 } 424 }) 425 } 426 } 427 }) 428 } 429 }) 430 } 431 } 432 433 func TestCompiler_compileShr(t *testing.T) { 434 kind := wazeroir.OperationKindShr 435 t.Run(kind.String(), func(t *testing.T) { 436 for _, signedInt := range []wazeroir.SignedInt{ 437 wazeroir.SignedInt32, 438 wazeroir.SignedInt64, 439 wazeroir.SignedUint32, 440 wazeroir.SignedUint64, 441 } { 442 signedInt := signedInt 443 t.Run(signedInt.String(), func(t *testing.T) { 444 for _, values := range [][2]uint64{ 445 {0, 0}, 446 {0, 1}, 447 {1, 0}, 448 {1, 1}, 449 {1 << 31, 1}, 450 {1, 1 << 31}, 451 {1 << 31, 1 << 31}, 452 {1 << 63, 1}, 453 {1, 1 << 63}, 454 {1 << 63, 1 << 63}, 455 } { 456 x1, x2 := values[0], values[1] 457 t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) { 458 env := newCompilerEnvironment() 459 compiler := env.requireNewCompiler(t, newCompiler, nil) 460 err := compiler.compilePreamble() 461 require.NoError(t, err) 462 463 // Emit consts operands. 464 for _, v := range []uint64{x1, x2} { 465 switch signedInt { 466 case wazeroir.SignedInt32: 467 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))}) 468 case wazeroir.SignedInt64: 469 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v}) 470 case wazeroir.SignedUint32: 471 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)}) 472 case wazeroir.SignedUint64: 473 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v}) 474 } 475 require.NoError(t, err) 476 } 477 478 // At this point, two values exist. 479 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 480 481 // Emit the operation. 482 err = compiler.compileShr(&wazeroir.OperationShr{Type: signedInt}) 483 require.NoError(t, err) 484 485 // We consumed two values, but push the result back. 486 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 487 resultLocation := compiler.runtimeValueLocationStack().peek() 488 // Plus the result must be located on a register. 489 require.True(t, resultLocation.onRegister()) 490 // Also, the result must have an appropriate register type. 491 require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType()) 492 493 err = compiler.compileReturnFunction() 494 require.NoError(t, err) 495 496 // Compile and execute the code under test. 497 code, _, err := compiler.compile() 498 require.NoError(t, err) 499 env.exec(code) 500 501 // Check the stack. 502 require.Equal(t, uint64(1), env.stackPointer()) 503 504 switch signedInt { 505 case wazeroir.SignedInt32: 506 require.Equal(t, int32(x1)>>(uint32(x2)%32), env.stackTopAsInt32()) 507 case wazeroir.SignedInt64: 508 require.Equal(t, int64(x1)>>(x2%64), env.stackTopAsInt64()) 509 case wazeroir.SignedUint32: 510 require.Equal(t, uint32(x1)>>(uint32(x2)%32), env.stackTopAsUint32()) 511 case wazeroir.SignedUint64: 512 require.Equal(t, x1>>(x2%64), env.stackTopAsUint64()) 513 } 514 }) 515 } 516 }) 517 } 518 }) 519 } 520 521 func TestCompiler_compile_Le_Lt_Gt_Ge_Eq_Eqz_Ne(t *testing.T) { 522 for _, kind := range []wazeroir.OperationKind{ 523 wazeroir.OperationKindEq, 524 wazeroir.OperationKindEqz, 525 wazeroir.OperationKindNe, 526 wazeroir.OperationKindLe, 527 wazeroir.OperationKindLt, 528 wazeroir.OperationKindGe, 529 wazeroir.OperationKindGt, 530 } { 531 kind := kind 532 t.Run(kind.String(), func(t *testing.T) { 533 for _, signedType := range []wazeroir.SignedType{ 534 wazeroir.SignedTypeUint32, 535 wazeroir.SignedTypeUint64, 536 wazeroir.SignedTypeInt32, 537 wazeroir.SignedTypeInt64, 538 wazeroir.SignedTypeFloat32, 539 wazeroir.SignedTypeFloat64, 540 } { 541 signedType := signedType 542 t.Run(signedType.String(), func(t *testing.T) { 543 for _, values := range [][2]uint64{ 544 {0, 0}, 545 {1, 1}, 546 {2, 1}, 547 {100, 1}, 548 {1, 0}, 549 {0, 1}, 550 {math.MaxInt16, math.MaxInt32}, 551 {1 << 14, 1 << 21}, 552 {1 << 14, 1 << 21}, 553 {0xffff_ffff_ffff_ffff, 0}, 554 {0xffff_ffff_ffff_ffff, 1}, 555 {0, 0xffff_ffff_ffff_ffff}, 556 {1, 0xffff_ffff_ffff_ffff}, 557 {1, math.Float64bits(math.NaN())}, 558 {math.Float64bits(math.NaN()), 1}, 559 {0xffff_ffff_ffff_ffff, math.Float64bits(math.NaN())}, 560 {math.Float64bits(math.NaN()), 0xffff_ffff_ffff_ffff}, 561 {math.Float64bits(math.MaxFloat32), 1}, 562 {math.Float64bits(math.SmallestNonzeroFloat32), 1}, 563 {math.Float64bits(math.MaxFloat64), 1}, 564 {math.Float64bits(math.SmallestNonzeroFloat64), 1}, 565 {0, math.Float64bits(math.Inf(1))}, 566 {0, math.Float64bits(math.Inf(-1))}, 567 {math.Float64bits(math.Inf(1)), 0}, 568 {math.Float64bits(math.Inf(-1)), 0}, 569 {math.Float64bits(math.Inf(1)), 1}, 570 {math.Float64bits(math.Inf(-1)), 1}, 571 {math.Float64bits(1.11231), math.Float64bits(math.Inf(1))}, 572 {math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))}, 573 {math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)}, 574 {math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)}, 575 {math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())}, 576 {math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())}, 577 {math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))}, 578 {math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))}, 579 } { 580 x1, x2 := values[0], values[1] 581 isEqz := kind == wazeroir.OperationKindEqz 582 if isEqz && (signedType == wazeroir.SignedTypeFloat32 || signedType == wazeroir.SignedTypeFloat64) { 583 // Eqz isn't defined for float. 584 return 585 } 586 t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) { 587 env := newCompilerEnvironment() 588 compiler := env.requireNewCompiler(t, newCompiler, nil) 589 err := compiler.compilePreamble() 590 require.NoError(t, err) 591 592 // Emit consts operands. 593 for _, v := range []uint64{x1, x2} { 594 switch signedType { 595 case wazeroir.SignedTypeUint32: 596 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)}) 597 case wazeroir.SignedTypeInt32: 598 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))}) 599 case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64: 600 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v}) 601 case wazeroir.SignedTypeFloat32: 602 err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))}) 603 case wazeroir.SignedTypeFloat64: 604 err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)}) 605 } 606 require.NoError(t, err) 607 } 608 609 if isEqz { 610 // Eqz only needs one value, so pop the top one (x2). 611 compiler.runtimeValueLocationStack().pop() 612 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 613 } else { 614 // At this point, two values exist for comparison. 615 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 616 } 617 618 // Emit the operation. 619 switch kind { 620 case wazeroir.OperationKindLe: 621 err = compiler.compileLe(&wazeroir.OperationLe{Type: signedType}) 622 case wazeroir.OperationKindLt: 623 err = compiler.compileLt(&wazeroir.OperationLt{Type: signedType}) 624 case wazeroir.OperationKindGe: 625 err = compiler.compileGe(&wazeroir.OperationGe{Type: signedType}) 626 case wazeroir.OperationKindGt: 627 err = compiler.compileGt(&wazeroir.OperationGt{Type: signedType}) 628 case wazeroir.OperationKindEq: 629 // Eq uses UnsignedType instead, so we translate the signed one. 630 switch signedType { 631 case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32: 632 err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32}) 633 case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64: 634 err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI64}) 635 case wazeroir.SignedTypeFloat32: 636 err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeF32}) 637 case wazeroir.SignedTypeFloat64: 638 err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeF64}) 639 } 640 case wazeroir.OperationKindNe: 641 // Ne uses UnsignedType, so we translate the signed one. 642 switch signedType { 643 case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32: 644 err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32}) 645 case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64: 646 err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI64}) 647 case wazeroir.SignedTypeFloat32: 648 err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeF32}) 649 case wazeroir.SignedTypeFloat64: 650 err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeF64}) 651 } 652 case wazeroir.OperationKindEqz: 653 // Eqz uses UnsignedInt, so we translate the signed one. 654 switch signedType { 655 case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32: 656 err = compiler.compileEqz(&wazeroir.OperationEqz{Type: wazeroir.UnsignedInt32}) 657 case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64: 658 err = compiler.compileEqz(&wazeroir.OperationEqz{Type: wazeroir.UnsignedInt64}) 659 } 660 } 661 require.NoError(t, err) 662 663 // We consumed two values, but push the result back. 664 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 665 666 err = compiler.compileReturnFunction() 667 require.NoError(t, err) 668 669 // Compile and execute the code under test. 670 code, _, err := compiler.compile() 671 require.NoError(t, err) 672 env.exec(code) 673 674 // There should only be one value on the stack 675 require.Equal(t, uint64(1), env.stackPointer()) 676 677 actual := env.stackTopAsUint32() == 1 678 679 switch kind { 680 case wazeroir.OperationKindLe: 681 switch signedType { 682 case wazeroir.SignedTypeInt32: 683 require.Equal(t, int32(x1) <= int32(x2), actual) 684 case wazeroir.SignedTypeUint32: 685 require.Equal(t, uint32(x1) <= uint32(x2), actual) 686 case wazeroir.SignedTypeInt64: 687 require.Equal(t, int64(x1) <= int64(x2), actual) 688 case wazeroir.SignedTypeUint64: 689 require.Equal(t, x1 <= x2, actual) 690 case wazeroir.SignedTypeFloat32: 691 require.Equal(t, math.Float32frombits(uint32(x1)) <= math.Float32frombits(uint32(x2)), actual) 692 case wazeroir.SignedTypeFloat64: 693 require.Equal(t, math.Float64frombits(x1) <= math.Float64frombits(x2), actual) 694 } 695 case wazeroir.OperationKindLt: 696 switch signedType { 697 case wazeroir.SignedTypeInt32: 698 require.Equal(t, int32(x1) < int32(x2), actual) 699 case wazeroir.SignedTypeUint32: 700 require.Equal(t, uint32(x1) < uint32(x2), actual) 701 case wazeroir.SignedTypeInt64: 702 require.Equal(t, int64(x1) < int64(x2), actual) 703 case wazeroir.SignedTypeUint64: 704 require.Equal(t, x1 < x2, actual) 705 case wazeroir.SignedTypeFloat32: 706 require.Equal(t, math.Float32frombits(uint32(x1)) < math.Float32frombits(uint32(x2)), actual) 707 case wazeroir.SignedTypeFloat64: 708 require.Equal(t, math.Float64frombits(x1) < math.Float64frombits(x2), actual) 709 } 710 case wazeroir.OperationKindGe: 711 switch signedType { 712 case wazeroir.SignedTypeInt32: 713 require.Equal(t, int32(x1) >= int32(x2), actual) 714 case wazeroir.SignedTypeUint32: 715 require.Equal(t, uint32(x1) >= uint32(x2), actual) 716 case wazeroir.SignedTypeInt64: 717 require.Equal(t, int64(x1) >= int64(x2), actual) 718 case wazeroir.SignedTypeUint64: 719 require.Equal(t, x1 >= x2, actual) 720 case wazeroir.SignedTypeFloat32: 721 require.Equal(t, math.Float32frombits(uint32(x1)) >= math.Float32frombits(uint32(x2)), actual) 722 case wazeroir.SignedTypeFloat64: 723 require.Equal(t, math.Float64frombits(x1) >= math.Float64frombits(x2), actual) 724 } 725 case wazeroir.OperationKindGt: 726 switch signedType { 727 case wazeroir.SignedTypeInt32: 728 require.Equal(t, int32(x1) > int32(x2), actual) 729 case wazeroir.SignedTypeUint32: 730 require.Equal(t, uint32(x1) > uint32(x2), actual) 731 case wazeroir.SignedTypeInt64: 732 require.Equal(t, int64(x1) > int64(x2), actual) 733 case wazeroir.SignedTypeUint64: 734 require.Equal(t, x1 > x2, actual) 735 case wazeroir.SignedTypeFloat32: 736 require.Equal(t, math.Float32frombits(uint32(x1)) > math.Float32frombits(uint32(x2)), actual) 737 case wazeroir.SignedTypeFloat64: 738 require.Equal(t, math.Float64frombits(x1) > math.Float64frombits(x2), actual) 739 } 740 case wazeroir.OperationKindEq: 741 switch signedType { 742 case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32: 743 require.Equal(t, uint32(x1) == uint32(x2), actual) 744 case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64: 745 require.Equal(t, x1 == x2, actual) 746 case wazeroir.SignedTypeFloat32: 747 require.Equal(t, math.Float32frombits(uint32(x1)) == math.Float32frombits(uint32(x2)), actual) 748 case wazeroir.SignedTypeFloat64: 749 require.Equal(t, math.Float64frombits(x1) == math.Float64frombits(x2), actual) 750 } 751 case wazeroir.OperationKindNe: 752 switch signedType { 753 case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32: 754 require.Equal(t, uint32(x1) != uint32(x2), actual) 755 case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64: 756 require.Equal(t, x1 != x2, actual) 757 case wazeroir.SignedTypeFloat32: 758 require.Equal(t, math.Float32frombits(uint32(x1)) != math.Float32frombits(uint32(x2)), actual) 759 case wazeroir.SignedTypeFloat64: 760 require.Equal(t, math.Float64frombits(x1) != math.Float64frombits(x2), actual) 761 } 762 case wazeroir.OperationKindEqz: 763 switch signedType { 764 case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32: 765 require.Equal(t, uint32(x1) == 0, actual) 766 case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64: 767 require.Equal(t, x1 == 0, actual) 768 } 769 } 770 }) 771 } 772 }) 773 } 774 }) 775 } 776 } 777 778 func TestCompiler_compile_Clz_Ctz_Popcnt(t *testing.T) { 779 for _, kind := range []wazeroir.OperationKind{ 780 wazeroir.OperationKindClz, 781 wazeroir.OperationKindCtz, 782 wazeroir.OperationKindPopcnt, 783 } { 784 kind := kind 785 t.Run(kind.String(), func(t *testing.T) { 786 for _, tp := range []wazeroir.UnsignedInt{wazeroir.UnsignedInt32, wazeroir.UnsignedInt64} { 787 tp := tp 788 is32bit := tp == wazeroir.UnsignedInt32 789 t.Run(tp.String(), func(t *testing.T) { 790 for _, v := range []uint64{ 791 0, 1, 1 << 4, 1 << 6, 1 << 31, 792 0b11111111110000, 0b010101010, 0b1111111111111, math.MaxUint64, 793 } { 794 name := fmt.Sprintf("%064b", v) 795 if is32bit { 796 name = fmt.Sprintf("%032b", v) 797 } 798 t.Run(name, func(t *testing.T) { 799 env := newCompilerEnvironment() 800 compiler := env.requireNewCompiler(t, newCompiler, nil) 801 err := compiler.compilePreamble() 802 require.NoError(t, err) 803 804 if is32bit { 805 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)}) 806 } else { 807 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v}) 808 } 809 require.NoError(t, err) 810 811 switch kind { 812 case wazeroir.OperationKindClz: 813 err = compiler.compileClz(&wazeroir.OperationClz{Type: tp}) 814 case wazeroir.OperationKindCtz: 815 err = compiler.compileCtz(&wazeroir.OperationCtz{Type: tp}) 816 case wazeroir.OperationKindPopcnt: 817 err = compiler.compilePopcnt(&wazeroir.OperationPopcnt{Type: tp}) 818 } 819 require.NoError(t, err) 820 821 err = compiler.compileReturnFunction() 822 require.NoError(t, err) 823 824 // Generate and run the code under test. 825 code, _, err := compiler.compile() 826 require.NoError(t, err) 827 env.exec(code) 828 829 // One value must be pushed as a result. 830 require.Equal(t, uint64(1), env.stackPointer()) 831 832 switch kind { 833 case wazeroir.OperationKindClz: 834 if is32bit { 835 require.Equal(t, bits.LeadingZeros32(uint32(v)), int(env.stackTopAsUint32())) 836 } else { 837 require.Equal(t, bits.LeadingZeros64(v), int(env.stackTopAsUint32())) 838 } 839 case wazeroir.OperationKindCtz: 840 if is32bit { 841 require.Equal(t, bits.TrailingZeros32(uint32(v)), int(env.stackTopAsUint32())) 842 } else { 843 require.Equal(t, bits.TrailingZeros64(v), int(env.stackTopAsUint32())) 844 } 845 case wazeroir.OperationKindPopcnt: 846 if is32bit { 847 require.Equal(t, bits.OnesCount32(uint32(v)), int(env.stackTopAsUint32())) 848 } else { 849 require.Equal(t, bits.OnesCount64(v), int(env.stackTopAsUint32())) 850 } 851 } 852 }) 853 } 854 }) 855 } 856 }) 857 } 858 } 859 860 func TestCompiler_compile_Min_Max_Copysign(t *testing.T) { 861 tests := []struct { 862 name string 863 is32bit bool 864 setupFunc func(t *testing.T, compiler compilerImpl) 865 verifyFunc func(t *testing.T, x1, x2 float64, raw uint64) 866 }{ 867 { 868 name: "min-32-bit", 869 is32bit: true, 870 setupFunc: func(t *testing.T, compiler compilerImpl) { 871 err := compiler.compileMin(&wazeroir.OperationMin{Type: wazeroir.Float32}) 872 require.NoError(t, err) 873 }, 874 verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) { 875 exp := moremath.WasmCompatMin32(float32(x1), float32(x2)) 876 actual := math.Float32frombits(uint32(raw)) 877 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 878 require.True(t, math.IsNaN(float64(actual))) 879 } else { 880 require.Equal(t, math.Float32bits(exp), math.Float32bits(actual)) 881 } 882 }, 883 }, 884 { 885 name: "min-64-bit", 886 is32bit: false, 887 setupFunc: func(t *testing.T, compiler compilerImpl) { 888 err := compiler.compileMin(&wazeroir.OperationMin{Type: wazeroir.Float64}) 889 require.NoError(t, err) 890 }, 891 verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) { 892 exp := moremath.WasmCompatMin64(x1, x2) 893 actual := math.Float64frombits(raw) 894 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 895 require.True(t, math.IsNaN(actual)) 896 } else { 897 require.Equal(t, math.Float64bits(exp), math.Float64bits(actual)) 898 } 899 }, 900 }, 901 { 902 name: "max-32-bit", 903 is32bit: true, 904 setupFunc: func(t *testing.T, compiler compilerImpl) { 905 err := compiler.compileMax(&wazeroir.OperationMax{Type: wazeroir.Float32}) 906 require.NoError(t, err) 907 }, 908 verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) { 909 exp := moremath.WasmCompatMax32(float32(x1), float32(x2)) 910 actual := math.Float32frombits(uint32(raw)) 911 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 912 require.True(t, math.IsNaN(float64(actual))) 913 } else { 914 require.Equal(t, math.Float32bits(exp), math.Float32bits(actual)) 915 } 916 }, 917 }, 918 { 919 name: "max-64-bit", 920 is32bit: false, 921 setupFunc: func(t *testing.T, compiler compilerImpl) { 922 err := compiler.compileMax(&wazeroir.OperationMax{Type: wazeroir.Float64}) 923 require.NoError(t, err) 924 }, 925 verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) { 926 exp := moremath.WasmCompatMax64(x1, x2) 927 actual := math.Float64frombits(raw) 928 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 929 require.True(t, math.IsNaN(actual)) 930 } else { 931 require.Equal(t, math.Float64bits(exp), math.Float64bits(actual)) 932 } 933 }, 934 }, 935 { 936 name: "copysign-32-bit", 937 is32bit: true, 938 setupFunc: func(t *testing.T, compiler compilerImpl) { 939 err := compiler.compileCopysign(&wazeroir.OperationCopysign{Type: wazeroir.Float32}) 940 require.NoError(t, err) 941 }, 942 verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) { 943 exp := float32(math.Copysign(float64(float32(x1)), float64(float32(x2)))) 944 actual := math.Float32frombits(uint32(raw)) 945 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 946 require.True(t, math.IsNaN(float64(actual))) 947 } else { 948 require.Equal(t, exp, actual) 949 } 950 }, 951 }, 952 { 953 name: "copysign-64-bit", 954 is32bit: false, 955 setupFunc: func(t *testing.T, compiler compilerImpl) { 956 err := compiler.compileCopysign(&wazeroir.OperationCopysign{Type: wazeroir.Float64}) 957 require.NoError(t, err) 958 }, 959 verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) { 960 exp := math.Copysign(x1, x2) 961 actual := math.Float64frombits(raw) 962 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 963 require.True(t, math.IsNaN(actual)) 964 } else { 965 require.Equal(t, exp, actual) 966 } 967 }, 968 }, 969 } 970 971 for _, tt := range tests { 972 tc := tt 973 t.Run(tc.name, func(t *testing.T) { 974 for _, vs := range [][2]float64{ 975 {math.Copysign(0, 1), math.Copysign(0, 1)}, 976 {math.Copysign(0, -1), math.Copysign(0, 1)}, 977 {math.Copysign(0, 1), math.Copysign(0, -1)}, 978 {math.Copysign(0, -1), math.Copysign(0, -1)}, 979 {100, -1.1}, 980 {100, 0}, 981 {0, 0}, 982 {1, 1}, 983 {-1, 100}, 984 {100, 200}, 985 {100.01234124, 100.01234124}, 986 {100.01234124, -100.01234124}, 987 {200.12315, 100}, 988 {6.8719476736e+10 /* = 1 << 36 */, 100}, 989 {6.8719476736e+10 /* = 1 << 36 */, 1.37438953472e+11 /* = 1 << 37*/}, 990 {math.Inf(1), 100}, 991 {math.Inf(1), -100}, 992 {100, math.Inf(1)}, 993 {-100, math.Inf(1)}, 994 {math.Inf(-1), 100}, 995 {math.Inf(-1), -100}, 996 {100, math.Inf(-1)}, 997 {-100, math.Inf(-1)}, 998 {math.Inf(1), 0}, 999 {math.Inf(-1), 0}, 1000 {0, math.Inf(1)}, 1001 {0, math.Inf(-1)}, 1002 {math.NaN(), 0}, 1003 {0, math.NaN()}, 1004 {math.NaN(), 12321}, 1005 {12313, math.NaN()}, 1006 {math.NaN(), math.NaN()}, 1007 } { 1008 x1, x2 := vs[0], vs[1] 1009 t.Run(fmt.Sprintf("x1=%f_x2=%f", x1, x2), func(t *testing.T) { 1010 env := newCompilerEnvironment() 1011 compiler := env.requireNewCompiler(t, newCompiler, nil) 1012 err := compiler.compilePreamble() 1013 require.NoError(t, err) 1014 1015 // Setup the target values. 1016 if tc.is32bit { 1017 err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(x1)}) 1018 require.NoError(t, err) 1019 err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(x2)}) 1020 require.NoError(t, err) 1021 } else { 1022 err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: x1}) 1023 require.NoError(t, err) 1024 err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: x2}) 1025 require.NoError(t, err) 1026 } 1027 1028 // At this point two values are pushed. 1029 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 1030 require.Equal(t, 2, len(compiler.runtimeValueLocationStack().usedRegisters)) 1031 1032 tc.setupFunc(t, compiler) 1033 1034 // We consumed two values, but push one value after operation. 1035 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 1036 require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters)) 1037 1038 err = compiler.compileReturnFunction() 1039 require.NoError(t, err) 1040 1041 // Generate and run the code under test. 1042 code, _, err := compiler.compile() 1043 require.NoError(t, err) 1044 env.exec(code) 1045 1046 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 1047 require.Equal(t, uint64(1), env.stackPointer()) // Result must be pushed! 1048 1049 tc.verifyFunc(t, x1, x2, env.stackTopAsUint64()) 1050 }) 1051 } 1052 }) 1053 } 1054 } 1055 1056 func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) { 1057 tests := []struct { 1058 name string 1059 is32bit bool 1060 setupFunc func(t *testing.T, compiler compilerImpl) 1061 verifyFunc func(t *testing.T, v float64, raw uint64) 1062 }{ 1063 { 1064 name: "abs-32-bit", 1065 is32bit: true, 1066 setupFunc: func(t *testing.T, compiler compilerImpl) { 1067 err := compiler.compileAbs(&wazeroir.OperationAbs{Type: wazeroir.Float32}) 1068 require.NoError(t, err) 1069 }, 1070 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1071 exp := float32(math.Abs(float64(v))) 1072 actual := math.Float32frombits(uint32(raw)) 1073 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1074 require.True(t, math.IsNaN(float64(actual))) 1075 } else { 1076 require.Equal(t, exp, actual) 1077 } 1078 }, 1079 }, 1080 { 1081 name: "abs-64-bit", 1082 is32bit: false, 1083 setupFunc: func(t *testing.T, compiler compilerImpl) { 1084 err := compiler.compileAbs(&wazeroir.OperationAbs{Type: wazeroir.Float64}) 1085 require.NoError(t, err) 1086 }, 1087 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1088 exp := math.Abs(v) 1089 actual := math.Float64frombits(raw) 1090 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1091 require.True(t, math.IsNaN(actual)) 1092 } else { 1093 require.Equal(t, exp, actual) 1094 } 1095 }, 1096 }, 1097 { 1098 name: "neg-32-bit", 1099 is32bit: true, 1100 setupFunc: func(t *testing.T, compiler compilerImpl) { 1101 err := compiler.compileNeg(&wazeroir.OperationNeg{Type: wazeroir.Float32}) 1102 require.NoError(t, err) 1103 }, 1104 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1105 exp := -float32(v) 1106 actual := math.Float32frombits(uint32(raw)) 1107 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1108 require.True(t, math.IsNaN(float64(actual))) 1109 } else { 1110 require.Equal(t, exp, actual) 1111 } 1112 }, 1113 }, 1114 { 1115 name: "neg-64-bit", 1116 is32bit: false, 1117 setupFunc: func(t *testing.T, compiler compilerImpl) { 1118 err := compiler.compileNeg(&wazeroir.OperationNeg{Type: wazeroir.Float64}) 1119 require.NoError(t, err) 1120 }, 1121 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1122 exp := -v 1123 actual := math.Float64frombits(raw) 1124 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1125 require.True(t, math.IsNaN(actual)) 1126 } else { 1127 require.Equal(t, exp, actual) 1128 } 1129 }, 1130 }, 1131 { 1132 name: "ceil-32-bit", 1133 is32bit: true, 1134 setupFunc: func(t *testing.T, compiler compilerImpl) { 1135 err := compiler.compileCeil(&wazeroir.OperationCeil{Type: wazeroir.Float32}) 1136 require.NoError(t, err) 1137 }, 1138 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1139 exp := float32(math.Ceil(float64(v))) 1140 actual := math.Float32frombits(uint32(raw)) 1141 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1142 require.True(t, math.IsNaN(float64(actual))) 1143 } else { 1144 require.Equal(t, exp, actual) 1145 } 1146 }, 1147 }, 1148 { 1149 name: "ceil-64-bit", 1150 is32bit: false, 1151 setupFunc: func(t *testing.T, compiler compilerImpl) { 1152 err := compiler.compileCeil(&wazeroir.OperationCeil{Type: wazeroir.Float64}) 1153 require.NoError(t, err) 1154 }, 1155 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1156 exp := math.Ceil(v) 1157 actual := math.Float64frombits(raw) 1158 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1159 require.True(t, math.IsNaN(actual)) 1160 } else { 1161 require.Equal(t, exp, actual) 1162 } 1163 }, 1164 }, 1165 { 1166 name: "floor-32-bit", 1167 is32bit: true, 1168 setupFunc: func(t *testing.T, compiler compilerImpl) { 1169 err := compiler.compileFloor(&wazeroir.OperationFloor{Type: wazeroir.Float32}) 1170 require.NoError(t, err) 1171 }, 1172 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1173 exp := float32(math.Floor(float64(v))) 1174 actual := math.Float32frombits(uint32(raw)) 1175 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1176 require.True(t, math.IsNaN(float64(actual))) 1177 } else { 1178 require.Equal(t, exp, actual) 1179 } 1180 }, 1181 }, 1182 { 1183 name: "floor-64-bit", 1184 is32bit: false, 1185 setupFunc: func(t *testing.T, compiler compilerImpl) { 1186 err := compiler.compileFloor(&wazeroir.OperationFloor{Type: wazeroir.Float64}) 1187 require.NoError(t, err) 1188 }, 1189 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1190 exp := math.Floor(v) 1191 actual := math.Float64frombits(raw) 1192 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1193 require.True(t, math.IsNaN(actual)) 1194 } else { 1195 require.Equal(t, exp, actual) 1196 } 1197 }, 1198 }, 1199 { 1200 name: "trunc-32-bit", 1201 is32bit: true, 1202 setupFunc: func(t *testing.T, compiler compilerImpl) { 1203 err := compiler.compileTrunc(&wazeroir.OperationTrunc{Type: wazeroir.Float32}) 1204 require.NoError(t, err) 1205 }, 1206 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1207 exp := float32(math.Trunc(float64(v))) 1208 actual := math.Float32frombits(uint32(raw)) 1209 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1210 require.True(t, math.IsNaN(float64(actual))) 1211 } else { 1212 require.Equal(t, exp, actual) 1213 } 1214 }, 1215 }, 1216 { 1217 name: "trunc-64-bit", 1218 is32bit: false, 1219 setupFunc: func(t *testing.T, compiler compilerImpl) { 1220 err := compiler.compileTrunc(&wazeroir.OperationTrunc{Type: wazeroir.Float64}) 1221 require.NoError(t, err) 1222 }, 1223 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1224 exp := math.Trunc(v) 1225 actual := math.Float64frombits(raw) 1226 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1227 require.True(t, math.IsNaN(actual)) 1228 } else { 1229 require.Equal(t, exp, actual) 1230 } 1231 }, 1232 }, 1233 { 1234 name: "nearest-32-bit", 1235 is32bit: true, 1236 setupFunc: func(t *testing.T, compiler compilerImpl) { 1237 err := compiler.compileNearest(&wazeroir.OperationNearest{Type: wazeroir.Float32}) 1238 require.NoError(t, err) 1239 }, 1240 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1241 exp := moremath.WasmCompatNearestF32(float32(v)) 1242 actual := math.Float32frombits(uint32(raw)) 1243 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1244 require.True(t, math.IsNaN(float64(actual))) 1245 } else { 1246 require.Equal(t, exp, actual) 1247 } 1248 }, 1249 }, 1250 { 1251 name: "nearest-64-bit", 1252 is32bit: false, 1253 setupFunc: func(t *testing.T, compiler compilerImpl) { 1254 err := compiler.compileNearest(&wazeroir.OperationNearest{Type: wazeroir.Float64}) 1255 require.NoError(t, err) 1256 }, 1257 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1258 exp := moremath.WasmCompatNearestF64(v) 1259 actual := math.Float64frombits(raw) 1260 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1261 require.True(t, math.IsNaN(actual)) 1262 } else { 1263 require.Equal(t, exp, actual) 1264 } 1265 }, 1266 }, 1267 { 1268 name: "sqrt-32-bit", 1269 is32bit: true, 1270 setupFunc: func(t *testing.T, compiler compilerImpl) { 1271 err := compiler.compileSqrt(&wazeroir.OperationSqrt{Type: wazeroir.Float32}) 1272 require.NoError(t, err) 1273 }, 1274 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1275 exp := float32(math.Sqrt(float64(v))) 1276 actual := math.Float32frombits(uint32(raw)) 1277 if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN 1278 require.True(t, math.IsNaN(float64(actual))) 1279 } else { 1280 require.Equal(t, exp, actual) 1281 } 1282 }, 1283 }, 1284 { 1285 name: "sqrt-64-bit", 1286 is32bit: false, 1287 setupFunc: func(t *testing.T, compiler compilerImpl) { 1288 err := compiler.compileSqrt(&wazeroir.OperationSqrt{Type: wazeroir.Float64}) 1289 require.NoError(t, err) 1290 }, 1291 verifyFunc: func(t *testing.T, v float64, raw uint64) { 1292 exp := math.Sqrt(v) 1293 actual := math.Float64frombits(raw) 1294 if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN 1295 require.True(t, math.IsNaN(actual)) 1296 } else { 1297 require.Equal(t, exp, actual) 1298 } 1299 }, 1300 }, 1301 } 1302 1303 for _, tt := range tests { 1304 tc := tt 1305 t.Run(tc.name, func(t *testing.T) { 1306 for _, v := range []float64{ 1307 0, 1 << 63, 1<<63 | 12345, 1 << 31, 1308 1<<31 | 123455, 6.8719476736e+10, 1309 // This verifies that the impl is Wasm compatible in nearest, rather than being equivalent of math.Round. 1310 // See moremath.WasmCompatNearestF32 and moremath.WasmCompatNearestF64 1311 -4.5, 1312 1.37438953472e+11, -1.3, 1313 -1231.123, 1.3, 100.3, -100.3, 1231.123, 1314 math.Inf(1), math.Inf(-1), math.NaN(), 1315 } { 1316 v := v 1317 t.Run(fmt.Sprintf("%f", v), func(t *testing.T) { 1318 env := newCompilerEnvironment() 1319 compiler := env.requireNewCompiler(t, newCompiler, nil) 1320 err := compiler.compilePreamble() 1321 require.NoError(t, err) 1322 1323 if tc.is32bit { 1324 err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(v)}) 1325 require.NoError(t, err) 1326 } else { 1327 err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: v}) 1328 require.NoError(t, err) 1329 } 1330 1331 // At this point two values are pushed. 1332 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 1333 require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters)) 1334 1335 tc.setupFunc(t, compiler) 1336 1337 // We consumed one value, but push the result after operation. 1338 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 1339 require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters)) 1340 1341 err = compiler.compileReturnFunction() 1342 require.NoError(t, err) 1343 1344 // Generate and run the code under test. 1345 code, _, err := compiler.compile() 1346 require.NoError(t, err) 1347 env.exec(code) 1348 1349 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 1350 require.Equal(t, uint64(1), env.stackPointer()) // Result must be pushed! 1351 1352 tc.verifyFunc(t, v, env.stackTopAsUint64()) 1353 }) 1354 } 1355 }) 1356 } 1357 } 1358 1359 func TestCompiler_compile_Div_Rem(t *testing.T) { 1360 for _, kind := range []wazeroir.OperationKind{ 1361 wazeroir.OperationKindDiv, 1362 wazeroir.OperationKindRem, 1363 } { 1364 kind := kind 1365 t.Run(kind.String(), func(t *testing.T) { 1366 for _, signedType := range []wazeroir.SignedType{ 1367 wazeroir.SignedTypeUint32, 1368 wazeroir.SignedTypeUint64, 1369 wazeroir.SignedTypeInt32, 1370 wazeroir.SignedTypeInt64, 1371 wazeroir.SignedTypeFloat32, 1372 wazeroir.SignedTypeFloat64, 1373 } { 1374 signedType := signedType 1375 t.Run(signedType.String(), func(t *testing.T) { 1376 for _, values := range [][2]uint64{ 1377 {0, 0}, 1378 {1, 1}, 1379 {2, 1}, 1380 {100, 1}, 1381 {1, 0}, 1382 {0, 1}, 1383 {math.MaxInt16, math.MaxInt32}, 1384 {1234, 5}, 1385 {5, 1234}, 1386 {4, 2}, 1387 {40, 4}, 1388 {123456, 4}, 1389 {1 << 14, 1 << 21}, 1390 {1 << 14, 1 << 21}, 1391 {0xffff_ffff_ffff_ffff, 0}, 1392 {0xffff_ffff_ffff_ffff, 1}, 1393 {0, 0xffff_ffff_ffff_ffff}, 1394 {1, 0xffff_ffff_ffff_ffff}, 1395 {0x80000000, 0xffffffff}, // This is equivalent to (-2^31 / -1) and results in overflow for 32-bit signed div. 1396 {0x8000000000000000, 0xffffffffffffffff}, // This is equivalent to (-2^63 / -1) and results in overflow for 64-bit signed div. 1397 {0xffffffff /* -1 in signed 32bit */, 0xfffffffe /* -2 in signed 32bit */}, 1398 {0xffffffffffffffff /* -1 in signed 64bit */, 0xfffffffffffffffe /* -2 in signed 64bit */}, 1399 {1, 0xffff_ffff_ffff_ffff}, 1400 {math.Float64bits(1.11231), math.Float64bits(12312312.12312)}, 1401 {math.Float64bits(1.11231), math.Float64bits(-12312312.12312)}, 1402 {math.Float64bits(-1.11231), math.Float64bits(12312312.12312)}, 1403 {math.Float64bits(-1.11231), math.Float64bits(-12312312.12312)}, 1404 {math.Float64bits(1.11231), math.Float64bits(12312312.12312)}, 1405 {math.Float64bits(-12312312.12312), math.Float64bits(1.11231)}, 1406 {math.Float64bits(12312312.12312), math.Float64bits(-1.11231)}, 1407 {math.Float64bits(-12312312.12312), math.Float64bits(-1.11231)}, 1408 {1, math.Float64bits(math.NaN())}, 1409 {math.Float64bits(math.NaN()), 1}, 1410 {0xffff_ffff_ffff_ffff, math.Float64bits(math.NaN())}, 1411 {math.Float64bits(math.NaN()), 0xffff_ffff_ffff_ffff}, 1412 {math.Float64bits(math.MaxFloat32), 1}, 1413 {math.Float64bits(math.SmallestNonzeroFloat32), 1}, 1414 {math.Float64bits(math.MaxFloat64), 1}, 1415 {math.Float64bits(math.SmallestNonzeroFloat64), 1}, 1416 {0, math.Float64bits(math.Inf(1))}, 1417 {0, math.Float64bits(math.Inf(-1))}, 1418 {math.Float64bits(math.Inf(1)), 0}, 1419 {math.Float64bits(math.Inf(-1)), 0}, 1420 {math.Float64bits(math.Inf(1)), 1}, 1421 {math.Float64bits(math.Inf(-1)), 1}, 1422 {math.Float64bits(1.11231), math.Float64bits(math.Inf(1))}, 1423 {math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))}, 1424 {math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)}, 1425 {math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)}, 1426 {math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())}, 1427 {math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())}, 1428 {math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))}, 1429 {math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))}, 1430 } { 1431 x1, x2 := values[0], values[1] 1432 t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) { 1433 env := newCompilerEnvironment() 1434 compiler := env.requireNewCompiler(t, newCompiler, nil) 1435 err := compiler.compilePreamble() 1436 require.NoError(t, err) 1437 1438 // Emit consts operands. 1439 for _, v := range []uint64{x1, x2} { 1440 switch signedType { 1441 case wazeroir.SignedTypeUint32: 1442 // In order to test zero value on non-zero register, we directly assign an register. 1443 loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 1444 err = compiler.compileEnsureOnRegister(loc) 1445 require.NoError(t, err) 1446 env.stack()[loc.stackPointer] = uint64(v) 1447 case wazeroir.SignedTypeInt32: 1448 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))}) 1449 case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64: 1450 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v}) 1451 case wazeroir.SignedTypeFloat32: 1452 err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))}) 1453 case wazeroir.SignedTypeFloat64: 1454 err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)}) 1455 } 1456 require.NoError(t, err) 1457 } 1458 1459 // At this point, two values exist for comparison. 1460 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 1461 1462 switch kind { 1463 case wazeroir.OperationKindDiv: 1464 err = compiler.compileDiv(&wazeroir.OperationDiv{Type: signedType}) 1465 case wazeroir.OperationKindRem: 1466 switch signedType { 1467 case wazeroir.SignedTypeInt32: 1468 err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedInt32}) 1469 case wazeroir.SignedTypeInt64: 1470 err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedInt64}) 1471 case wazeroir.SignedTypeUint32: 1472 err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedUint32}) 1473 case wazeroir.SignedTypeUint64: 1474 err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedUint64}) 1475 case wazeroir.SignedTypeFloat32: 1476 // Rem undefined for float32. 1477 return 1478 case wazeroir.SignedTypeFloat64: 1479 // Rem undefined for float64. 1480 return 1481 } 1482 } 1483 require.NoError(t, err) 1484 1485 err = compiler.compileReturnFunction() 1486 require.NoError(t, err) 1487 1488 // Compile and execute the code under test. 1489 code, _, err := compiler.compile() 1490 require.NoError(t, err) 1491 env.exec(code) 1492 1493 switch kind { 1494 case wazeroir.OperationKindDiv: 1495 switch signedType { 1496 case wazeroir.SignedTypeUint32: 1497 if uint32(x2) == 0 { 1498 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1499 } else { 1500 require.Equal(t, uint32(x1)/uint32(x2), env.stackTopAsUint32()) 1501 } 1502 case wazeroir.SignedTypeInt32: 1503 v1, v2 := int32(x1), int32(x2) 1504 if v2 == 0 { 1505 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1506 } else if v1 == math.MinInt32 && v2 == -1 { 1507 require.Equal(t, nativeCallStatusIntegerOverflow, env.compilerStatus()) 1508 } else { 1509 require.Equal(t, v1/v2, env.stackTopAsInt32()) 1510 } 1511 case wazeroir.SignedTypeUint64: 1512 if x2 == 0 { 1513 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1514 } else { 1515 require.Equal(t, x1/x2, env.stackTopAsUint64()) 1516 } 1517 case wazeroir.SignedTypeInt64: 1518 v1, v2 := int64(x1), int64(x2) 1519 if v2 == 0 { 1520 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1521 } else if v1 == math.MinInt64 && v2 == -1 { 1522 require.Equal(t, nativeCallStatusIntegerOverflow, env.compilerStatus()) 1523 } else { 1524 require.Equal(t, v1/v2, env.stackTopAsInt64()) 1525 } 1526 case wazeroir.SignedTypeFloat32: 1527 exp := math.Float32frombits(uint32(x1)) / math.Float32frombits(uint32(x2)) 1528 // NaN cannot be compared with themselves, so we have to use IsNaN 1529 if math.IsNaN(float64(exp)) { 1530 require.True(t, math.IsNaN(float64(env.stackTopAsFloat32()))) 1531 } else { 1532 require.Equal(t, exp, env.stackTopAsFloat32()) 1533 } 1534 case wazeroir.SignedTypeFloat64: 1535 exp := math.Float64frombits(x1) / math.Float64frombits(x2) 1536 // NaN cannot be compared with themselves, so we have to use IsNaN 1537 if math.IsNaN(exp) { 1538 require.True(t, math.IsNaN(env.stackTopAsFloat64())) 1539 } else { 1540 require.Equal(t, exp, env.stackTopAsFloat64()) 1541 } 1542 } 1543 case wazeroir.OperationKindRem: 1544 switch signedType { 1545 case wazeroir.SignedTypeInt32: 1546 v1, v2 := int32(x1), int32(x2) 1547 if v2 == 0 { 1548 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1549 } else { 1550 require.Equal(t, v1%v2, env.stackTopAsInt32()) 1551 } 1552 case wazeroir.SignedTypeInt64: 1553 v1, v2 := int64(x1), int64(x2) 1554 if v2 == 0 { 1555 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1556 } else { 1557 require.Equal(t, v1%v2, env.stackTopAsInt64()) 1558 } 1559 case wazeroir.SignedTypeUint32: 1560 v1, v2 := uint32(x1), uint32(x2) 1561 if v2 == 0 { 1562 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1563 } else { 1564 require.Equal(t, v1%v2, env.stackTopAsUint32()) 1565 } 1566 case wazeroir.SignedTypeUint64: 1567 if x2 == 0 { 1568 require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus()) 1569 } else { 1570 require.Equal(t, x1%x2, env.stackTopAsUint64()) 1571 } 1572 1573 } 1574 } 1575 }) 1576 } 1577 }) 1578 } 1579 }) 1580 } 1581 }