github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/compiler/compiler_stack_test.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "math" 6 "testing" 7 8 "github.com/wasilibs/wazerox/internal/asm" 9 "github.com/wasilibs/wazerox/internal/testing/require" 10 "github.com/wasilibs/wazerox/internal/wasm" 11 "github.com/wasilibs/wazerox/internal/wazeroir" 12 ) 13 14 func TestCompiler_releaseRegisterToStack(t *testing.T) { 15 const val = 10000 16 tests := []struct { 17 name string 18 stackPointer uint64 19 isFloat bool 20 }{ 21 {name: "int", stackPointer: 10, isFloat: false}, 22 {name: "float", stackPointer: 10, isFloat: true}, 23 {name: "int-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: false}, 24 {name: "float-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: true}, 25 } 26 27 for _, tt := range tests { 28 tc := tt 29 t.Run(tc.name, func(t *testing.T) { 30 env := newCompilerEnvironment() 31 32 // Compile code. 33 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 34 err := compiler.compilePreamble() 35 require.NoError(t, err) 36 37 // Set up the location stack so that we push the const on the specified height. 38 s := &runtimeValueLocationStack{ 39 sp: tc.stackPointer, 40 stack: make([]runtimeValueLocation, tc.stackPointer), 41 unreservedVectorRegisters: unreservedVectorRegisters, 42 unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters, 43 } 44 // Peek must be non-nil. Otherwise, compileConst* would fail. 45 compiler.setRuntimeValueLocationStack(s) 46 47 if tc.isFloat { 48 err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(math.Float64frombits(val)))) 49 } else { 50 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(val))) 51 } 52 require.NoError(t, err) 53 // Release the register allocated value to the memory stack so that we can see the value after exiting. 54 compiler.compileReleaseRegisterToStack(compiler.runtimeValueLocationStack().peek()) 55 compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned) 56 57 code := asm.CodeSegment{} 58 defer func() { require.NoError(t, code.Unmap()) }() 59 60 // Generate the code under test. 61 _, err = compiler.compile(code.NextCodeSection()) 62 require.NoError(t, err) 63 64 // Run native code after growing the value stack. 65 env.callEngine().builtinFunctionGrowStack(tc.stackPointer) 66 env.exec(code.Bytes()) 67 68 // Compiler status must be returned and stack pointer must end up the specified one. 69 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 70 require.Equal(t, tc.stackPointer+1, env.ce.stackPointer) 71 72 if tc.isFloat { 73 require.Equal(t, math.Float64frombits(val), env.stackTopAsFloat64()) 74 } else { 75 require.Equal(t, uint64(val), env.stackTopAsUint64()) 76 } 77 }) 78 } 79 } 80 81 func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) { 82 const val = 123 83 tests := []struct { 84 name string 85 stackPointer uint64 86 isFloat bool 87 }{ 88 {name: "int", stackPointer: 10, isFloat: false}, 89 {name: "float", stackPointer: 10, isFloat: true}, 90 {name: "int-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: false}, 91 {name: "float-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: true}, 92 } 93 94 for _, tt := range tests { 95 tc := tt 96 t.Run(tc.name, func(t *testing.T) { 97 env := newCompilerEnvironment() 98 99 // Compile code. 100 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 101 err := compiler.compilePreamble() 102 require.NoError(t, err) 103 104 // Setup the location stack so that we push the const on the specified height. 105 compiler.runtimeValueLocationStack().sp = tc.stackPointer 106 compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, tc.stackPointer) 107 108 require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list())) 109 loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 110 if tc.isFloat { 111 loc.valueType = runtimeValueTypeF64 112 } else { 113 loc.valueType = runtimeValueTypeI64 114 } 115 // At this point the value must be recorded as being on stack. 116 require.True(t, loc.onStack()) 117 118 // Release the stack-allocated value to register. 119 err = compiler.compileEnsureOnRegister(loc) 120 require.NoError(t, err) 121 require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list())) 122 require.True(t, loc.onRegister()) 123 124 // To verify the behavior, increment the value on the register. 125 if tc.isFloat { 126 err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(1))) 127 require.NoError(t, err) 128 err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeF64))) 129 require.NoError(t, err) 130 } else { 131 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(1))) 132 require.NoError(t, err) 133 err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI64))) 134 require.NoError(t, err) 135 } 136 137 // Release the value to the memory stack so that we can see the value after exiting. 138 compiler.compileReleaseRegisterToStack(loc) 139 require.NoError(t, err) 140 compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned) 141 require.NoError(t, err) 142 143 code := asm.CodeSegment{} 144 defer func() { require.NoError(t, code.Unmap()) }() 145 146 // Generate the code under test. 147 _, err = compiler.compile(code.NextCodeSection()) 148 require.NoError(t, err) 149 150 // Run native code after growing the value stack, and place the original value. 151 env.callEngine().builtinFunctionGrowStack(tc.stackPointer) 152 env.stack()[tc.stackPointer] = val 153 env.exec(code.Bytes()) 154 155 // Compiler status must be returned and stack pointer must end up the specified one. 156 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 157 require.Equal(t, tc.stackPointer+1, env.ce.stackPointer) 158 159 if tc.isFloat { 160 require.Equal(t, math.Float64frombits(val)+1, env.stackTopAsFloat64()) 161 } else { 162 require.Equal(t, uint64(val)+1, env.stackTopAsUint64()) 163 } 164 }) 165 } 166 } 167 168 func TestCompiler_compilePick_v128(t *testing.T) { 169 const pickTargetLo, pickTargetHi uint64 = 12345, 6789 170 171 op := operationPtr(wazeroir.NewOperationPick(2, true)) 172 tests := []struct { 173 name string 174 isPickTargetOnRegister bool 175 }{ 176 {name: "target on register", isPickTargetOnRegister: false}, 177 {name: "target on stack", isPickTargetOnRegister: true}, 178 } 179 180 for _, tt := range tests { 181 tc := tt 182 t.Run(tc.name, func(t *testing.T) { 183 env := newCompilerEnvironment() 184 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 185 err := compiler.compilePreamble() 186 require.NoError(t, err) 187 188 // Set up the stack before picking. 189 if tc.isPickTargetOnRegister { 190 err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(pickTargetLo, pickTargetHi))) 191 require.NoError(t, err) 192 } else { 193 lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo 194 lo.valueType = runtimeValueTypeV128Lo 195 env.stack()[lo.stackPointer] = pickTargetLo 196 hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi 197 hi.valueType = runtimeValueTypeV128Hi 198 env.stack()[hi.stackPointer] = pickTargetHi 199 } 200 201 // Push the unused median value. 202 _ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 203 requireRuntimeLocationStackPointerEqual(t, uint64(3), compiler) 204 205 // Now ready to compile Pick operation. 206 err = compiler.compilePick(op) 207 require.NoError(t, err) 208 requireRuntimeLocationStackPointerEqual(t, uint64(5), compiler) 209 210 hiLoc := compiler.runtimeValueLocationStack().peek() 211 loLoc := compiler.runtimeValueLocationStack().stack[hiLoc.stackPointer-1] 212 require.True(t, hiLoc.onRegister()) 213 require.Equal(t, runtimeValueTypeV128Hi, hiLoc.valueType) 214 require.Equal(t, runtimeValueTypeV128Lo, loLoc.valueType) 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 // Compile and execute the code under test. 223 _, err = compiler.compile(code.NextCodeSection()) 224 require.NoError(t, err) 225 env.exec(code.Bytes()) 226 227 // Check the returned status and stack pointer. 228 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 229 require.Equal(t, uint64(5), env.stackPointer()) 230 231 // Verify the top value is the picked one and the pick target's value stays the same. 232 lo, hi := env.stackTopAsV128() 233 require.Equal(t, pickTargetLo, lo) 234 require.Equal(t, pickTargetHi, hi) 235 require.Equal(t, pickTargetLo, env.stack()[loLoc.stackPointer]) 236 require.Equal(t, pickTargetHi, env.stack()[hiLoc.stackPointer]) 237 }) 238 } 239 } 240 241 func TestCompiler_compilePick(t *testing.T) { 242 const pickTargetValue uint64 = 12345 243 op := operationPtr(wazeroir.NewOperationPick(1, false)) 244 tests := []struct { 245 name string 246 pickTargetSetupFunc func(compiler compilerImpl, ce *callEngine) error 247 isPickTargetFloat, isPickTargetOnRegister bool 248 }{ 249 { 250 name: "float on register", 251 pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error { 252 return compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(math.Float64frombits(pickTargetValue)))) 253 }, 254 isPickTargetFloat: true, 255 isPickTargetOnRegister: true, 256 }, 257 { 258 name: "int on register", 259 pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error { 260 return compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(pickTargetValue))) 261 }, 262 isPickTargetFloat: false, 263 isPickTargetOnRegister: true, 264 }, 265 { 266 name: "float on stack", 267 pickTargetSetupFunc: func(compiler compilerImpl, ce *callEngine) error { 268 pickTargetLocation := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 269 pickTargetLocation.valueType = runtimeValueTypeF64 270 ce.stack[pickTargetLocation.stackPointer] = pickTargetValue 271 return nil 272 }, 273 isPickTargetFloat: true, 274 isPickTargetOnRegister: false, 275 }, 276 { 277 name: "int on stack", 278 pickTargetSetupFunc: func(compiler compilerImpl, ce *callEngine) error { 279 pickTargetLocation := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 280 pickTargetLocation.valueType = runtimeValueTypeI64 281 ce.stack[pickTargetLocation.stackPointer] = pickTargetValue 282 return nil 283 }, 284 isPickTargetFloat: false, 285 isPickTargetOnRegister: false, 286 }, 287 } 288 289 for _, tt := range tests { 290 tc := tt 291 t.Run(tc.name, func(t *testing.T) { 292 env := newCompilerEnvironment() 293 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 294 err := compiler.compilePreamble() 295 require.NoError(t, err) 296 297 // Set up the stack before picking. 298 err = tc.pickTargetSetupFunc(compiler, env.callEngine()) 299 require.NoError(t, err) 300 pickTargetLocation := compiler.runtimeValueLocationStack().peek() 301 302 // Push the unused median value. 303 _ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 304 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 305 306 // Now ready to compile Pick operation. 307 err = compiler.compilePick(op) 308 require.NoError(t, err) 309 requireRuntimeLocationStackPointerEqual(t, uint64(3), compiler) 310 311 pickedLocation := compiler.runtimeValueLocationStack().peek() 312 require.True(t, pickedLocation.onRegister()) 313 require.Equal(t, pickTargetLocation.getRegisterType(), pickedLocation.getRegisterType()) 314 315 err = compiler.compileReturnFunction() 316 require.NoError(t, err) 317 318 code := asm.CodeSegment{} 319 defer func() { require.NoError(t, code.Unmap()) }() 320 321 // Compile and execute the code under test. 322 _, err = compiler.compile(code.NextCodeSection()) 323 require.NoError(t, err) 324 env.exec(code.Bytes()) 325 326 // Check the returned status and stack pointer. 327 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 328 require.Equal(t, uint64(3), env.stackPointer()) 329 330 // Verify the top value is the picked one and the pick target's value stays the same. 331 if tc.isPickTargetFloat { 332 require.Equal(t, math.Float64frombits(pickTargetValue), env.stackTopAsFloat64()) 333 require.Equal(t, math.Float64frombits(pickTargetValue), math.Float64frombits(env.stack()[pickTargetLocation.stackPointer])) 334 } else { 335 require.Equal(t, pickTargetValue, env.stackTopAsUint64()) 336 require.Equal(t, pickTargetValue, env.stack()[pickTargetLocation.stackPointer]) 337 } 338 }) 339 } 340 } 341 342 func TestCompiler_compileDrop(t *testing.T) { 343 t.Run("range nop", func(t *testing.T) { 344 env := newCompilerEnvironment() 345 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 346 347 err := compiler.compilePreamble() 348 require.NoError(t, err) 349 350 // Put existing contents on stack. 351 liveNum := 10 352 for i := 0; i < liveNum; i++ { 353 compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 354 } 355 requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler) 356 357 err = compiler.compileDrop(operationPtr(wazeroir.NewOperationDrop(wazeroir.NopInclusiveRange))) 358 require.NoError(t, err) 359 360 // After the nil range drop, the stack must remain the same. 361 requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler) 362 363 err = compiler.compileReturnFunction() 364 require.NoError(t, err) 365 366 code := asm.CodeSegment{} 367 defer func() { require.NoError(t, code.Unmap()) }() 368 369 _, err = compiler.compile(code.NextCodeSection()) 370 require.NoError(t, err) 371 372 env.exec(code.Bytes()) 373 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 374 }) 375 t.Run("start top", func(t *testing.T) { 376 r := wazeroir.InclusiveRange{Start: 0, End: 2} 377 dropTargetNum := int(r.End - r.Start + 1) // +1 as the range is inclusive! 378 liveNum := 5 379 380 env := newCompilerEnvironment() 381 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 382 383 err := compiler.compilePreamble() 384 require.NoError(t, err) 385 386 // Put existing contents on stack. 387 const expectedTopLiveValue = 100 388 for i := 0; i < liveNum+dropTargetNum; i++ { 389 if i == liveNum-1 { 390 err := compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(expectedTopLiveValue))) 391 require.NoError(t, err) 392 } else { 393 compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 394 } 395 } 396 requireRuntimeLocationStackPointerEqual(t, uint64(liveNum+dropTargetNum), compiler) 397 398 err = compiler.compileDrop(operationPtr(wazeroir.NewOperationDrop(r))) 399 require.NoError(t, err) 400 401 // After the drop operation, the stack contains only live contents. 402 requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler) 403 // Plus, the top value must stay on a register. 404 top := compiler.runtimeValueLocationStack().peek() 405 require.True(t, top.onRegister()) 406 407 err = compiler.compileReturnFunction() 408 require.NoError(t, err) 409 410 code := asm.CodeSegment{} 411 defer func() { require.NoError(t, code.Unmap()) }() 412 413 _, err = compiler.compile(code.NextCodeSection()) 414 require.NoError(t, err) 415 416 env.exec(code.Bytes()) 417 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 418 require.Equal(t, uint64(5), env.stackPointer()) 419 require.Equal(t, uint64(expectedTopLiveValue), env.stackTopAsUint64()) 420 }) 421 422 t.Run("start from middle", func(t *testing.T) { 423 r := wazeroir.InclusiveRange{Start: 2, End: 3} 424 liveAboveDropStartNum := 3 425 dropTargetNum := int(r.End - r.Start + 1) // +1 as the range is inclusive! 426 liveBelowDropEndNum := 5 427 total := liveAboveDropStartNum + dropTargetNum + liveBelowDropEndNum 428 liveTotal := liveAboveDropStartNum + liveBelowDropEndNum 429 430 env := newCompilerEnvironment() 431 ce := env.callEngine() 432 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 433 434 err := compiler.compilePreamble() 435 require.NoError(t, err) 436 437 // We don't need call frame in this test case, so simply pop them out! 438 for i := 0; i < callFrameDataSizeInUint64; i++ { 439 compiler.runtimeValueLocationStack().pop() 440 } 441 442 // Put existing contents except the top on stack 443 for i := 0; i < total-1; i++ { 444 loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 445 loc.valueType = runtimeValueTypeI32 446 ce.stack[loc.stackPointer] = uint64(i) // Put the initial value. 447 } 448 449 // Place the top value. 450 const expectedTopLiveValue = 100 451 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(expectedTopLiveValue))) 452 require.NoError(t, err) 453 454 require.Equal(t, uint64(total), compiler.runtimeValueLocationStack().sp) 455 456 err = compiler.compileDrop(operationPtr(wazeroir.NewOperationDrop(r))) 457 require.NoError(t, err) 458 459 // After the drop operation, the stack contains only live contents. 460 require.Equal(t, uint64(liveTotal), compiler.runtimeValueLocationStack().sp) 461 // Plus, the top value must stay on a register. 462 require.True(t, compiler.runtimeValueLocationStack().peek().onRegister()) 463 464 err = compiler.compileReturnFunction() 465 require.NoError(t, err) 466 467 code := asm.CodeSegment{} 468 defer func() { require.NoError(t, code.Unmap()) }() 469 470 _, err = compiler.compile(code.NextCodeSection()) 471 require.NoError(t, err) 472 473 env.exec(code.Bytes()) 474 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 475 require.Equal(t, uint64(liveTotal), env.ce.stackPointer) 476 477 stack := env.stack()[:env.stackPointer()] 478 for i, val := range stack { 479 if i <= liveBelowDropEndNum { 480 require.Equal(t, uint64(i), val) 481 } else if i == liveTotal-1 { 482 require.Equal(t, uint64(expectedTopLiveValue), val) 483 } else { 484 require.Equal(t, uint64(i+dropTargetNum), val) 485 } 486 } 487 }) 488 } 489 490 func TestCompiler_compileSelect(t *testing.T) { 491 // There are mainly 8 cases we have to test: 492 // - [x1 = reg, x2 = reg] select x1 493 // - [x1 = reg, x2 = reg] select x2 494 // - [x1 = reg, x2 = stack] select x1 495 // - [x1 = reg, x2 = stack] select x2 496 // - [x1 = stack, x2 = reg] select x1 497 // - [x1 = stack, x2 = reg] select x2 498 // - [x1 = stack, x2 = stack] select x1 499 // - [x1 = stack, x2 = stack] select x2 500 // And for each case, we have to test with 501 // three conditional value location: stack, gp register, conditional register. 502 // So in total we have 24 cases. 503 tests := []struct { 504 x1OnRegister, x2OnRegister bool 505 selectX1 bool 506 condlValueOnStack, condValueOnGPRegister, condValueOnCondRegister bool 507 }{ 508 // Conditional value on stack. 509 {x1OnRegister: true, x2OnRegister: true, selectX1: true, condlValueOnStack: true}, 510 {x1OnRegister: true, x2OnRegister: true, selectX1: false, condlValueOnStack: true}, 511 {x1OnRegister: true, x2OnRegister: false, selectX1: true, condlValueOnStack: true}, 512 {x1OnRegister: true, x2OnRegister: false, selectX1: false, condlValueOnStack: true}, 513 {x1OnRegister: false, x2OnRegister: true, selectX1: true, condlValueOnStack: true}, 514 {x1OnRegister: false, x2OnRegister: true, selectX1: false, condlValueOnStack: true}, 515 {x1OnRegister: false, x2OnRegister: false, selectX1: true, condlValueOnStack: true}, 516 {x1OnRegister: false, x2OnRegister: false, selectX1: false, condlValueOnStack: true}, 517 // Conditional value on register. 518 {x1OnRegister: true, x2OnRegister: true, selectX1: true, condValueOnGPRegister: true}, 519 {x1OnRegister: true, x2OnRegister: true, selectX1: false, condValueOnGPRegister: true}, 520 {x1OnRegister: true, x2OnRegister: false, selectX1: true, condValueOnGPRegister: true}, 521 {x1OnRegister: true, x2OnRegister: false, selectX1: false, condValueOnGPRegister: true}, 522 {x1OnRegister: false, x2OnRegister: true, selectX1: true, condValueOnGPRegister: true}, 523 {x1OnRegister: false, x2OnRegister: true, selectX1: false, condValueOnGPRegister: true}, 524 {x1OnRegister: false, x2OnRegister: false, selectX1: true, condValueOnGPRegister: true}, 525 {x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnGPRegister: true}, 526 // Conditional value on conditional register. 527 {x1OnRegister: true, x2OnRegister: true, selectX1: true, condValueOnCondRegister: true}, 528 {x1OnRegister: true, x2OnRegister: true, selectX1: false, condValueOnCondRegister: true}, 529 {x1OnRegister: true, x2OnRegister: false, selectX1: true, condValueOnCondRegister: true}, 530 {x1OnRegister: true, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true}, 531 {x1OnRegister: false, x2OnRegister: true, selectX1: true, condValueOnCondRegister: true}, 532 {x1OnRegister: false, x2OnRegister: true, selectX1: false, condValueOnCondRegister: true}, 533 {x1OnRegister: false, x2OnRegister: false, selectX1: true, condValueOnCondRegister: true}, 534 {x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true}, 535 } 536 537 for i, tt := range tests { 538 tc := tt 539 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 540 for _, vals := range [][2]uint64{ 541 {1, 2}, 542 {0, 1}, 543 {1, 0}, 544 {math.Float64bits(-1), math.Float64bits(-1)}, 545 {math.Float64bits(-1), math.Float64bits(1)}, 546 {math.Float64bits(1), math.Float64bits(-1)}, 547 } { 548 x1Value, x2Value := vals[0], vals[1] 549 t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", vals[0], vals[1]), func(t *testing.T) { 550 env := newCompilerEnvironment() 551 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 552 553 // To make the assertion below stable, we preallocate the underlying stack, 554 // so that the pointer to the entry will be stale. 555 compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, 100) 556 557 err := compiler.compilePreamble() 558 require.NoError(t, err) 559 560 x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 561 x1.valueType = runtimeValueTypeI64 562 env.stack()[x1.stackPointer] = x1Value 563 if tc.x1OnRegister { 564 err = compiler.compileEnsureOnRegister(x1) 565 require.NoError(t, err) 566 } 567 568 x2 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 569 x2.valueType = runtimeValueTypeI64 570 env.stack()[x2.stackPointer] = x2Value 571 if tc.x2OnRegister { 572 err = compiler.compileEnsureOnRegister(x2) 573 require.NoError(t, err) 574 } 575 576 var c *runtimeValueLocation 577 if tc.condlValueOnStack { 578 c = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 579 c.valueType = runtimeValueTypeI32 580 if tc.selectX1 { 581 env.stack()[c.stackPointer] = 1 582 } else { 583 env.stack()[c.stackPointer] = 0 584 } 585 } else if tc.condValueOnGPRegister { 586 c = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 587 c.valueType = runtimeValueTypeI32 588 if tc.selectX1 { 589 env.stack()[c.stackPointer] = 1 590 } else { 591 env.stack()[c.stackPointer] = 0 592 } 593 err = compiler.compileEnsureOnRegister(c) 594 require.NoError(t, err) 595 } else if tc.condValueOnCondRegister { 596 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0))) 597 require.NoError(t, err) 598 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0))) 599 require.NoError(t, err) 600 if tc.selectX1 { 601 err = compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))) 602 } else { 603 err = compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32))) 604 } 605 require.NoError(t, err) 606 } 607 608 // Now emit code for select. 609 err = compiler.compileSelect(operationPtr(wazeroir.NewOperationSelect(false))) 610 require.NoError(t, err) 611 612 // x1 should be top of the stack. 613 require.Equal(t, x1, compiler.runtimeValueLocationStack().peek()) 614 615 err = compiler.compileReturnFunction() 616 require.NoError(t, err) 617 618 code := asm.CodeSegment{} 619 defer func() { require.NoError(t, code.Unmap()) }() 620 621 // Run code. 622 _, err = compiler.compile(code.NextCodeSection()) 623 require.NoError(t, err) 624 env.exec(code.Bytes()) 625 626 // Check the selected value. 627 require.Equal(t, uint64(1), env.stackPointer()) 628 if tc.selectX1 { 629 require.Equal(t, env.stack()[x1.stackPointer], x1Value) 630 } else { 631 require.Equal(t, env.stack()[x1.stackPointer], x2Value) 632 } 633 }) 634 } 635 }) 636 } 637 } 638 639 func TestCompiler_compileSwap_v128(t *testing.T) { 640 const x1Lo, x1Hi uint64 = 100000, 200000 641 const x2Lo, x2Hi uint64 = 1, 2 642 643 tests := []struct { 644 x1OnRegister, x2OnRegister bool 645 }{ 646 {x1OnRegister: true, x2OnRegister: true}, 647 {x1OnRegister: true, x2OnRegister: false}, 648 {x1OnRegister: false, x2OnRegister: true}, 649 {x1OnRegister: false, x2OnRegister: false}, 650 } 651 652 for _, tt := range tests { 653 tc := tt 654 t.Run(fmt.Sprintf("x1_register=%v, x2_register=%v", tc.x1OnRegister, tc.x2OnRegister), func(t *testing.T) { 655 env := newCompilerEnvironment() 656 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 657 err := compiler.compilePreamble() 658 require.NoError(t, err) 659 660 if tc.x1OnRegister { 661 err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(x1Lo, x1Hi))) 662 require.NoError(t, err) 663 } else { 664 lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo 665 lo.valueType = runtimeValueTypeV128Lo 666 env.stack()[lo.stackPointer] = x1Lo 667 hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi 668 hi.valueType = runtimeValueTypeV128Hi 669 env.stack()[hi.stackPointer] = x1Hi 670 } 671 672 _ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // Dummy value! 673 674 if tc.x2OnRegister { 675 err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(x2Lo, x2Hi))) 676 require.NoError(t, err) 677 } else { 678 lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo 679 lo.valueType = runtimeValueTypeV128Lo 680 env.stack()[lo.stackPointer] = x2Lo 681 hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi 682 hi.valueType = runtimeValueTypeV128Hi 683 env.stack()[hi.stackPointer] = x2Hi 684 } 685 686 // Swap x1 and x2. 687 err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(4, true))) 688 require.NoError(t, err) 689 690 require.NoError(t, compiler.compileReturnFunction()) 691 692 code := asm.CodeSegment{} 693 defer func() { require.NoError(t, code.Unmap()) }() 694 695 // Generate the code under test. 696 _, err = compiler.compile(code.NextCodeSection()) 697 require.NoError(t, err) 698 699 // Run code. 700 env.exec(code.Bytes()) 701 702 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 703 require.Equal(t, uint64(3), env.stackPointer()) 704 705 // The first variable is above the call frame. 706 st := env.stack() 707 require.Equal(t, x2Lo, st[callFrameDataSizeInUint64]) 708 require.Equal(t, x2Hi, st[callFrameDataSizeInUint64+1]) 709 }) 710 } 711 } 712 713 func TestCompiler_compileSet(t *testing.T) { 714 var x1Value, x2Value int64 = 100, 200 715 tests := []struct { 716 x1OnConditionalRegister, x1OnRegister, x2OnRegister bool 717 }{ 718 {x1OnRegister: true, x2OnRegister: true}, 719 {x1OnRegister: true, x2OnRegister: false}, 720 {x1OnRegister: false, x2OnRegister: true}, 721 {x1OnRegister: false, x2OnRegister: false}, 722 // x1 on conditional register 723 {x1OnConditionalRegister: true, x2OnRegister: false}, 724 {x1OnConditionalRegister: true, x2OnRegister: true}, 725 } 726 727 for i, tt := range tests { 728 tc := tt 729 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 730 env := newCompilerEnvironment() 731 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 732 err := compiler.compilePreamble() 733 require.NoError(t, err) 734 735 x2 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 736 x2.valueType = runtimeValueTypeI32 737 env.stack()[x2.stackPointer] = uint64(x2Value) 738 if tc.x2OnRegister { 739 err = compiler.compileEnsureOnRegister(x2) 740 require.NoError(t, err) 741 } 742 743 _ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // Dummy value! 744 if tc.x1OnRegister && !tc.x1OnConditionalRegister { 745 x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 746 x1.valueType = runtimeValueTypeI32 747 env.stack()[x1.stackPointer] = uint64(x1Value) 748 err = compiler.compileEnsureOnRegister(x1) 749 require.NoError(t, err) 750 } else if !tc.x1OnConditionalRegister { 751 x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() 752 x1.valueType = runtimeValueTypeI32 753 env.stack()[x1.stackPointer] = uint64(x1Value) 754 } else { 755 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0))) 756 require.NoError(t, err) 757 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0))) 758 require.NoError(t, err) 759 err = compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))) 760 require.NoError(t, err) 761 x1Value = 1 762 } 763 764 // Set x2 into the x1. 765 err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(2, false))) 766 require.NoError(t, err) 767 768 require.NoError(t, compiler.compileReturnFunction()) 769 770 code := asm.CodeSegment{} 771 defer func() { require.NoError(t, code.Unmap()) }() 772 773 // Generate the code under test. 774 _, err = compiler.compile(code.NextCodeSection()) 775 require.NoError(t, err) 776 777 // Run code. 778 env.exec(code.Bytes()) 779 780 require.Equal(t, uint64(2), env.stackPointer()) 781 // Check the value was set. Note that it is placed above the call frame. 782 require.Equal(t, uint64(x1Value), env.stack()[callFrameDataSizeInUint64]) 783 }) 784 } 785 }