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