github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/compiler/compiler_controlflow_test.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "testing" 6 "unsafe" 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_compileHostFunction(t *testing.T) { 15 env := newCompilerEnvironment() 16 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 17 18 err := compiler.compileGoDefinedHostFunction() 19 require.NoError(t, err) 20 21 // Get the location of caller function's location stored in the stack, which depends on the type. 22 // In this test, the host function has empty sig. 23 _, _, callerFuncLoc := compiler.runtimeValueLocationStack().getCallFrameLocations(&wasm.FunctionType{}) 24 25 code := asm.CodeSegment{} 26 defer func() { require.NoError(t, code.Unmap()) }() 27 28 // Generate the machine code for the test. 29 _, err = compiler.compile(code.NextCodeSection()) 30 require.NoError(t, err) 31 32 // Set the caller's function which always exists in the real usecase. 33 f := &function{moduleInstance: &wasm.ModuleInstance{}} 34 env.stack()[callerFuncLoc.stackPointer] = uint64(uintptr(unsafe.Pointer(f))) 35 env.exec(code.Bytes()) 36 37 // On the return, the code must exit with the host call status. 38 require.Equal(t, nativeCallStatusCodeCallGoHostFunction, env.compilerStatus()) 39 // Plus, the exitContext holds the caller's wasm.FunctionInstance. 40 require.Equal(t, f.moduleInstance, env.ce.exitContext.callerModuleInstance) 41 42 // Re-enter the return address. 43 require.NotEqual(t, uintptr(0), uintptr(env.ce.returnAddress)) 44 nativecall(env.ce.returnAddress, env.callEngine(), env.module()) 45 46 // After that, the code must exit with returned status. 47 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 48 } 49 50 func TestCompiler_compileLabel(t *testing.T) { 51 label := wazeroir.NewLabel(wazeroir.LabelKindContinuation, 100) 52 for _, expectSkip := range []bool{false, true} { 53 expectSkip := expectSkip 54 t.Run(fmt.Sprintf("expect skip=%v", expectSkip), func(t *testing.T) { 55 env := newCompilerEnvironment() 56 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 57 58 if expectSkip { 59 // If the initial stack is not set, compileLabel must return skip=true. 60 actual := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(label))) 61 require.True(t, actual) 62 } else { 63 err := compiler.compileBr(operationPtr(wazeroir.NewOperationBr(label))) 64 require.NoError(t, err) 65 actual := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(label))) 66 require.False(t, actual) 67 } 68 }) 69 } 70 } 71 72 func TestCompiler_compileBrIf(t *testing.T) { 73 unreachableStatus, thenLabelExitStatus, elseLabelExitStatus := nativeCallStatusCodeUnreachable, nativeCallStatusCodeUnreachable+1, nativeCallStatusCodeUnreachable+2 74 thenBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1) 75 elseBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 2) 76 77 tests := []struct { 78 name string 79 setupFunc func(t *testing.T, compiler compilerImpl, shouldGoElse bool) 80 }{ 81 { 82 name: "cond on register", 83 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 84 val := uint32(1) 85 if shouldGoElse { 86 val = 0 87 } 88 err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(val))) 89 require.NoError(t, err) 90 }, 91 }, 92 { 93 name: "LS", 94 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 95 x1, x2 := uint32(1), uint32(2) 96 if shouldGoElse { 97 x2, x1 = x1, x2 98 } 99 requirePushTwoInt32Consts(t, x1, x2, compiler) 100 // Le on unsigned integer produces the value on COND_LS register. 101 err := compiler.compileLe(operationPtr(wazeroir.NewOperationLe(wazeroir.SignedTypeUint32))) 102 require.NoError(t, err) 103 }, 104 }, 105 { 106 name: "LE", 107 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 108 x1, x2 := uint32(1), uint32(2) 109 if shouldGoElse { 110 x2, x1 = x1, x2 111 } 112 requirePushTwoInt32Consts(t, x1, x2, compiler) 113 // Le on signed integer produces the value on COND_LE register. 114 err := compiler.compileLe(operationPtr(wazeroir.NewOperationLe(wazeroir.SignedTypeInt32))) 115 require.NoError(t, err) 116 }, 117 }, 118 { 119 name: "HS", 120 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 121 x1, x2 := uint32(2), uint32(1) 122 if shouldGoElse { 123 x2, x1 = x1, x2 124 } 125 requirePushTwoInt32Consts(t, x1, x2, compiler) 126 // Ge on unsigned integer produces the value on COND_HS register. 127 err := compiler.compileGe(operationPtr(wazeroir.NewOperationGe(wazeroir.SignedTypeUint32))) 128 require.NoError(t, err) 129 }, 130 }, 131 { 132 name: "GE", 133 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 134 x1, x2 := uint32(2), uint32(1) 135 if shouldGoElse { 136 x2, x1 = x1, x2 137 } 138 requirePushTwoInt32Consts(t, x1, x2, compiler) 139 // Ge on signed integer produces the value on COND_GE register. 140 err := compiler.compileGe(operationPtr(wazeroir.NewOperationGe(wazeroir.SignedTypeInt32))) 141 require.NoError(t, err) 142 }, 143 }, 144 { 145 name: "HI", 146 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 147 x1, x2 := uint32(2), uint32(1) 148 if shouldGoElse { 149 x2, x1 = x1, x2 150 } 151 requirePushTwoInt32Consts(t, x1, x2, compiler) 152 // Gt on unsigned integer produces the value on COND_HI register. 153 err := compiler.compileGt(operationPtr(wazeroir.NewOperationGt(wazeroir.SignedTypeUint32))) 154 require.NoError(t, err) 155 }, 156 }, 157 { 158 name: "GT", 159 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 160 x1, x2 := uint32(2), uint32(1) 161 if shouldGoElse { 162 x2, x1 = x1, x2 163 } 164 requirePushTwoInt32Consts(t, x1, x2, compiler) 165 // Gt on signed integer produces the value on COND_GT register. 166 err := compiler.compileGt(operationPtr(wazeroir.NewOperationGt(wazeroir.SignedTypeInt32))) 167 require.NoError(t, err) 168 }, 169 }, 170 { 171 name: "LO", 172 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 173 x1, x2 := uint32(1), uint32(2) 174 if shouldGoElse { 175 x2, x1 = x1, x2 176 } 177 requirePushTwoInt32Consts(t, x1, x2, compiler) 178 // Lt on unsigned integer produces the value on COND_LO register. 179 err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeUint32))) 180 require.NoError(t, err) 181 }, 182 }, 183 { 184 name: "LT", 185 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 186 x1, x2 := uint32(1), uint32(2) 187 if shouldGoElse { 188 x2, x1 = x1, x2 189 } 190 requirePushTwoInt32Consts(t, x1, x2, compiler) 191 // Lt on signed integer produces the value on COND_LT register. 192 err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeInt32))) 193 require.NoError(t, err) 194 }, 195 }, 196 { 197 name: "MI", 198 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 199 x1, x2 := float32(1), float32(2) 200 if shouldGoElse { 201 x2, x1 = x1, x2 202 } 203 requirePushTwoFloat32Consts(t, x1, x2, compiler) 204 // Lt on floats produces the value on COND_MI register. 205 err := compiler.compileLt(operationPtr(wazeroir.NewOperationLt(wazeroir.SignedTypeFloat32))) 206 require.NoError(t, err) 207 }, 208 }, 209 { 210 name: "EQ", 211 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 212 x1, x2 := uint32(1), uint32(1) 213 if shouldGoElse { 214 x2++ 215 } 216 requirePushTwoInt32Consts(t, x1, x2, compiler) 217 err := compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))) 218 require.NoError(t, err) 219 }, 220 }, 221 { 222 name: "NE", 223 setupFunc: func(t *testing.T, compiler compilerImpl, shouldGoElse bool) { 224 x1, x2 := uint32(1), uint32(2) 225 if shouldGoElse { 226 x2 = x1 227 } 228 requirePushTwoInt32Consts(t, x1, x2, compiler) 229 err := compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32))) 230 require.NoError(t, err) 231 }, 232 }, 233 } 234 235 for _, tt := range tests { 236 tc := tt 237 t.Run(tc.name, func(t *testing.T) { 238 for _, shouldGoToElse := range []bool{false, true} { 239 shouldGoToElse := shouldGoToElse 240 t.Run(fmt.Sprintf("should_goto_else=%v", shouldGoToElse), func(t *testing.T) { 241 env := newCompilerEnvironment() 242 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 243 err := compiler.compilePreamble() 244 require.NoError(t, err) 245 246 tc.setupFunc(t, compiler, shouldGoToElse) 247 requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler) 248 249 err = compiler.compileBrIf(operationPtr(wazeroir.NewOperationBrIf(thenBranchTarget, elseBranchTarget, wazeroir.NopInclusiveRange))) 250 require.NoError(t, err) 251 compiler.compileExitFromNativeCode(unreachableStatus) 252 253 // Emit code for .then label. 254 skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(thenBranchTarget))) 255 require.False(t, skip) 256 compiler.compileExitFromNativeCode(thenLabelExitStatus) 257 258 // Emit code for .else label. 259 skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(elseBranchTarget))) 260 require.False(t, skip) 261 compiler.compileExitFromNativeCode(elseLabelExitStatus) 262 263 code := asm.CodeSegment{} 264 defer func() { require.NoError(t, code.Unmap()) }() 265 266 _, err = compiler.compile(code.NextCodeSection()) 267 require.NoError(t, err) 268 269 // The generated code looks like this: 270 // 271 // ... code from compilePreamble() 272 // ... code from tc.setupFunc() 273 // br_if .then, .else 274 // exit $unreachableStatus 275 // .then: 276 // exit $thenLabelExitStatus 277 // .else: 278 // exit $elseLabelExitStatus 279 // 280 // Therefore, if we start executing from the top, we must end up exiting with an appropriate status. 281 env.exec(code.Bytes()) 282 require.NotEqual(t, unreachableStatus, env.compilerStatus()) 283 if shouldGoToElse { 284 require.Equal(t, elseLabelExitStatus, env.compilerStatus()) 285 } else { 286 require.Equal(t, thenLabelExitStatus, env.compilerStatus()) 287 } 288 }) 289 } 290 }) 291 } 292 } 293 294 func TestCompiler_compileBrTable(t *testing.T) { 295 requireRunAndExpectedValueReturned := func(t *testing.T, env *compilerEnv, c compilerImpl, expValue uint32) { 296 // Emit code for each label which returns the frame ID. 297 for returnValue := uint32(0); returnValue < 7; returnValue++ { 298 label := wazeroir.NewLabel(wazeroir.LabelKindHeader, returnValue) 299 err := c.compileBr(operationPtr(wazeroir.NewOperationBr(label))) 300 require.NoError(t, err) 301 _ = c.compileLabel(operationPtr(wazeroir.NewOperationLabel(label))) 302 _ = c.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(label.FrameID())))) 303 err = c.compileReturnFunction() 304 require.NoError(t, err) 305 } 306 307 code := asm.CodeSegment{} 308 defer func() { require.NoError(t, code.Unmap()) }() 309 310 // Generate the code under test and run. 311 _, err := c.compile(code.NextCodeSection()) 312 require.NoError(t, err) 313 env.exec(code.Bytes()) 314 315 // Check the returned value. 316 require.Equal(t, uint64(1), env.stackPointer()) 317 require.Equal(t, expValue, env.stackTopAsUint32()) 318 } 319 320 getBranchLabelFromFrameID := func(frameid uint32) uint64 { 321 return uint64(wazeroir.NewLabel(wazeroir.LabelKindHeader, frameid)) 322 } 323 324 tests := []struct { 325 name string 326 index int64 327 o *wazeroir.UnionOperation 328 expectedValue uint32 329 }{ 330 { 331 name: "only default with index 0", 332 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 333 getBranchLabelFromFrameID(6), 334 wazeroir.NopInclusiveRange.AsU64(), 335 })), 336 index: 0, 337 expectedValue: 6, 338 }, 339 { 340 name: "only default with index 100", 341 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 342 getBranchLabelFromFrameID(6), 343 wazeroir.NopInclusiveRange.AsU64(), 344 })), 345 index: 100, 346 expectedValue: 6, 347 }, 348 { 349 name: "select default with targets and good index", 350 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 351 getBranchLabelFromFrameID(1), 352 wazeroir.NopInclusiveRange.AsU64(), 353 getBranchLabelFromFrameID(2), 354 wazeroir.NopInclusiveRange.AsU64(), 355 getBranchLabelFromFrameID(6), // default 356 wazeroir.NopInclusiveRange.AsU64(), 357 })), 358 index: 3, 359 expectedValue: 6, 360 }, 361 { 362 name: "select default with targets and huge index", 363 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 364 getBranchLabelFromFrameID(1), 365 wazeroir.NopInclusiveRange.AsU64(), 366 getBranchLabelFromFrameID(2), 367 wazeroir.NopInclusiveRange.AsU64(), 368 getBranchLabelFromFrameID(6), // default 369 wazeroir.NopInclusiveRange.AsU64(), 370 }, 371 )), 372 index: 100000, 373 expectedValue: 6, 374 }, 375 { 376 name: "select first with two targets", 377 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 378 getBranchLabelFromFrameID(1), 379 wazeroir.NopInclusiveRange.AsU64(), 380 getBranchLabelFromFrameID(2), 381 wazeroir.NopInclusiveRange.AsU64(), 382 getBranchLabelFromFrameID(5), // default 383 wazeroir.NopInclusiveRange.AsU64(), 384 })), 385 index: 0, 386 expectedValue: 1, 387 }, 388 { 389 name: "select last with two targets", 390 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 391 getBranchLabelFromFrameID(1), 392 wazeroir.NopInclusiveRange.AsU64(), 393 getBranchLabelFromFrameID(2), 394 wazeroir.NopInclusiveRange.AsU64(), 395 getBranchLabelFromFrameID(6), // default 396 wazeroir.NopInclusiveRange.AsU64(), 397 })), 398 index: 1, 399 expectedValue: 2, 400 }, 401 { 402 name: "select first with five targets", 403 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 404 getBranchLabelFromFrameID(1), 405 wazeroir.NopInclusiveRange.AsU64(), 406 getBranchLabelFromFrameID(2), 407 wazeroir.NopInclusiveRange.AsU64(), 408 getBranchLabelFromFrameID(3), 409 wazeroir.NopInclusiveRange.AsU64(), 410 getBranchLabelFromFrameID(4), 411 wazeroir.NopInclusiveRange.AsU64(), 412 getBranchLabelFromFrameID(5), 413 wazeroir.NopInclusiveRange.AsU64(), 414 getBranchLabelFromFrameID(5), // default 415 wazeroir.NopInclusiveRange.AsU64(), 416 })), 417 index: 0, 418 expectedValue: 1, 419 }, 420 { 421 name: "select middle with five targets", 422 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 423 getBranchLabelFromFrameID(1), 424 wazeroir.NopInclusiveRange.AsU64(), 425 getBranchLabelFromFrameID(2), 426 wazeroir.NopInclusiveRange.AsU64(), 427 getBranchLabelFromFrameID(3), 428 wazeroir.NopInclusiveRange.AsU64(), 429 getBranchLabelFromFrameID(4), 430 wazeroir.NopInclusiveRange.AsU64(), 431 getBranchLabelFromFrameID(5), 432 wazeroir.NopInclusiveRange.AsU64(), 433 getBranchLabelFromFrameID(5), // default 434 wazeroir.NopInclusiveRange.AsU64(), 435 })), 436 index: 2, 437 expectedValue: 3, 438 }, 439 { 440 name: "select last with five targets", 441 o: operationPtr(wazeroir.NewOperationBrTable([]uint64{ 442 getBranchLabelFromFrameID(1), 443 wazeroir.NopInclusiveRange.AsU64(), 444 getBranchLabelFromFrameID(2), 445 wazeroir.NopInclusiveRange.AsU64(), 446 getBranchLabelFromFrameID(3), 447 wazeroir.NopInclusiveRange.AsU64(), 448 getBranchLabelFromFrameID(4), 449 wazeroir.NopInclusiveRange.AsU64(), 450 getBranchLabelFromFrameID(5), 451 wazeroir.NopInclusiveRange.AsU64(), 452 getBranchLabelFromFrameID(5), // default 453 wazeroir.NopInclusiveRange.AsU64(), 454 })), 455 index: 4, 456 expectedValue: 5, 457 }, 458 } 459 460 for _, tt := range tests { 461 tc := tt 462 t.Run(tc.name, func(t *testing.T) { 463 env := newCompilerEnvironment() 464 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 465 466 err := compiler.compilePreamble() 467 require.NoError(t, err) 468 469 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(tc.index)))) 470 require.NoError(t, err) 471 472 err = compiler.compileBrTable(tc.o) 473 require.NoError(t, err) 474 475 require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list())) 476 477 requireRunAndExpectedValueReturned(t, env, compiler, tc.expectedValue) 478 }) 479 } 480 } 481 482 func requirePushTwoInt32Consts(t *testing.T, x1, x2 uint32, compiler compilerImpl) { 483 err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(x1))) 484 require.NoError(t, err) 485 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(x2))) 486 require.NoError(t, err) 487 } 488 489 func requirePushTwoFloat32Consts(t *testing.T, x1, x2 float32, compiler compilerImpl) { 490 err := compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(x1))) 491 require.NoError(t, err) 492 err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(x2))) 493 require.NoError(t, err) 494 } 495 496 func TestCompiler_compileBr(t *testing.T) { 497 t.Run("return", func(t *testing.T) { 498 env := newCompilerEnvironment() 499 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 500 err := compiler.compilePreamble() 501 require.NoError(t, err) 502 503 // Branch into nil label is interpreted as return. See BranchTarget.IsReturnTarget 504 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(wazeroir.NewLabel(wazeroir.LabelKindReturn, 0)))) 505 require.NoError(t, err) 506 507 code := asm.CodeSegment{} 508 defer func() { require.NoError(t, code.Unmap()) }() 509 510 // Compile and execute the code under test. 511 // Note: we don't invoke "compiler.return()" as the code emitted by compilerBr is enough to exit. 512 _, err = compiler.compile(code.NextCodeSection()) 513 require.NoError(t, err) 514 env.exec(code.Bytes()) 515 516 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 517 }) 518 t.Run("back-and-forth br", func(t *testing.T) { 519 env := newCompilerEnvironment() 520 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 521 err := compiler.compilePreamble() 522 require.NoError(t, err) 523 524 // Emit the forward br, meaning that handle Br instruction where the target label hasn't been compiled yet. 525 forwardLabel := wazeroir.NewLabel(wazeroir.LabelKindHeader, 0) 526 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(forwardLabel))) 527 require.NoError(t, err) 528 529 // We must not reach the code after Br, so emit the code exiting with Unreachable status. 530 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable) 531 require.NoError(t, err) 532 533 exitLabel := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1) 534 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(exitLabel))) 535 require.NoError(t, err) 536 537 // Emit code for the exitLabel. 538 skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(exitLabel))) 539 require.False(t, skip) 540 compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned) 541 require.NoError(t, err) 542 543 // Emit code for the forwardLabel. 544 skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(forwardLabel))) 545 require.False(t, skip) 546 err = compiler.compileBr(operationPtr(wazeroir.NewOperationBr(exitLabel))) 547 require.NoError(t, err) 548 549 code := asm.CodeSegment{} 550 defer func() { require.NoError(t, code.Unmap()) }() 551 552 _, err = compiler.compile(code.NextCodeSection()) 553 require.NoError(t, err) 554 555 // The generated code looks like this:) 556 // 557 // ... code from compilePreamble() 558 // br .forwardLabel 559 // exit nativeCallStatusCodeUnreachable // must not be reached 560 // br .exitLabel // must not be reached 561 // .exitLabel: 562 // exit nativeCallStatusCodeReturned 563 // .forwardLabel: 564 // br .exitLabel 565 // 566 // Therefore, if we start executing from the top, we must end up exiting nativeCallStatusCodeReturned. 567 env.exec(code.Bytes()) 568 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 569 }) 570 } 571 572 func TestCompiler_compileCallIndirect(t *testing.T) { 573 t.Run("out of bounds", func(t *testing.T) { 574 env := newCompilerEnvironment() 575 env.addTable(&wasm.TableInstance{References: make([]wasm.Reference, 10)}) 576 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 577 Types: []wasm.FunctionType{{}}, 578 HasTable: true, 579 }) 580 err := compiler.compilePreamble() 581 require.NoError(t, err) 582 583 targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0)) 584 585 // Place the offset value. 586 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10))) 587 require.NoError(t, err) 588 589 err = compiler.compileCallIndirect(targetOperation) 590 require.NoError(t, err) 591 592 // We expect to exit from the code in callIndirect so the subsequent code must be unreachable. 593 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable) 594 595 code := asm.CodeSegment{} 596 defer func() { require.NoError(t, code.Unmap()) }() 597 598 // Generate the code under test and run. 599 _, err = compiler.compile(code.NextCodeSection()) 600 require.NoError(t, err) 601 env.exec(code.Bytes()) 602 603 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 604 }) 605 606 t.Run("uninitialized", func(t *testing.T) { 607 env := newCompilerEnvironment() 608 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 609 Types: []wasm.FunctionType{{}}, 610 HasTable: true, 611 }) 612 err := compiler.compilePreamble() 613 require.NoError(t, err) 614 615 targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0)) 616 targetOffset := operationPtr(wazeroir.NewOperationConstI32(uint32(0))) 617 618 // and the typeID doesn't match the table[targetOffset]'s type ID. 619 table := make([]wasm.Reference, 10) 620 env.addTable(&wasm.TableInstance{References: table}) 621 env.module().TypeIDs = make([]wasm.FunctionTypeID, 10) 622 623 // Place the offset value. 624 err = compiler.compileConstI32(targetOffset) 625 require.NoError(t, err) 626 err = compiler.compileCallIndirect(targetOperation) 627 require.NoError(t, err) 628 629 // We expect to exit from the code in callIndirect so the subsequent code must be unreachable. 630 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable) 631 require.NoError(t, err) 632 633 code := asm.CodeSegment{} 634 defer func() { require.NoError(t, code.Unmap()) }() 635 636 // Generate the code under test and run. 637 _, err = compiler.compile(code.NextCodeSection()) 638 require.NoError(t, err) 639 env.exec(code.Bytes()) 640 641 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 642 }) 643 644 t.Run("type not match", func(t *testing.T) { 645 env := newCompilerEnvironment() 646 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 647 Types: []wasm.FunctionType{{}}, 648 HasTable: true, 649 }) 650 err := compiler.compilePreamble() 651 require.NoError(t, err) 652 653 targetOperation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0)) 654 targetOffset := operationPtr(wazeroir.NewOperationConstI32(uint32(0))) 655 env.module().TypeIDs = []wasm.FunctionTypeID{1000} 656 // Ensure that the module instance has the type information for targetOperation.TypeIndex, 657 // and the typeID doesn't match the table[targetOffset]'s type ID. 658 table := make([]wasm.Reference, 10) 659 env.addTable(&wasm.TableInstance{References: table}) 660 661 cf := &function{typeID: 50} 662 table[0] = uintptr(unsafe.Pointer(cf)) 663 664 // Place the offset value. 665 err = compiler.compileConstI32(targetOffset) 666 require.NoError(t, err) 667 668 // Now emit the code. 669 require.NoError(t, compiler.compileCallIndirect(targetOperation)) 670 671 // We expect to exit from the code in callIndirect so the subsequent code must be unreachable. 672 compiler.compileExitFromNativeCode(nativeCallStatusCodeUnreachable) 673 require.NoError(t, err) 674 675 code := asm.CodeSegment{} 676 defer func() { require.NoError(t, code.Unmap()) }() 677 678 // Generate the code under test and run. 679 _, err = compiler.compile(code.NextCodeSection()) 680 require.NoError(t, err) 681 env.exec(code.Bytes()) 682 683 require.Equal(t, nativeCallStatusCodeTypeMismatchOnIndirectCall.String(), env.compilerStatus().String()) 684 }) 685 686 t.Run("ok", func(t *testing.T) { 687 targetType := wasm.FunctionType{ 688 Results: []wasm.ValueType{wasm.ValueTypeI32}, 689 ResultNumInUint64: 1, 690 } 691 const typeIndex = 0 692 targetTypeID := wasm.FunctionTypeID(10) 693 operation := operationPtr(wazeroir.NewOperationCallIndirect(typeIndex, 0)) 694 695 table := make([]wasm.Reference, 10) 696 env := newCompilerEnvironment() 697 env.addTable(&wasm.TableInstance{References: table}) 698 699 // Ensure that the module instance has the type information for targetOperation.TypeIndex, 700 // and the typeID matches the table[targetOffset]'s type ID. 701 env.module().TypeIDs = make([]wasm.FunctionTypeID, 100) 702 env.module().TypeIDs[typeIndex] = targetTypeID 703 env.module().Engine = &moduleEngine{functions: []function{}} 704 705 me := env.moduleEngine() 706 me.functions = make([]function, len(table)) 707 for i := 0; i < len(table); i++ { 708 // First, we create the call target function for the table element i. 709 // To match its function type, it must return one value. 710 expectedReturnValue := uint32(i * 1000) 711 712 compiler := env.requireNewCompiler(t, &targetType, newCompiler, &wazeroir.CompilationResult{}) 713 err := compiler.compilePreamble() 714 require.NoError(t, err) 715 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expectedReturnValue))) 716 require.NoError(t, err) 717 718 requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler) 719 // The function result value must be set at the bottom of the stack. 720 err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(int(compiler.runtimeValueLocationStack().sp-1), false))) 721 require.NoError(t, err) 722 err = compiler.compileReturnFunction() 723 require.NoError(t, err) 724 725 code := asm.CodeSegment{} 726 defer func() { require.NoError(t, code.Unmap()) }() 727 728 _, err = compiler.compile(code.NextCodeSection()) 729 require.NoError(t, err) 730 731 makeExecutable(code.Bytes()) 732 733 // Now that we've generated the code for this function, 734 // add it to the module engine and assign its pointer to the table index. 735 me.functions[i] = function{ 736 codeInitialAddress: uintptr(unsafe.Pointer(&code.Bytes()[0])), 737 moduleInstance: env.moduleInstance, 738 typeID: targetTypeID, 739 } 740 table[i] = uintptr(unsafe.Pointer(&me.functions[i])) 741 } 742 743 // Test to ensure that we can call all the functions stored in the table. 744 for i := 1; i < len(table); i++ { 745 expectedReturnValue := uint32(i * 1000) 746 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 747 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, 748 &wazeroir.CompilationResult{ 749 Types: []wasm.FunctionType{targetType}, 750 HasTable: true, 751 }, 752 ) 753 err := compiler.compilePreamble() 754 require.NoError(t, err) 755 756 // Place the offset value. Here we try calling a function of functionaddr == table[i].FunctionIndex. 757 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(i)))) 758 require.NoError(t, err) 759 760 // At this point, we should have one item (offset value) on the stack. 761 requireRuntimeLocationStackPointerEqual(t, 1, compiler) 762 763 require.NoError(t, compiler.compileCallIndirect(operation)) 764 765 // At this point, we consumed the offset value, but the function returns one value, 766 // so the stack pointer results in the same. 767 requireRuntimeLocationStackPointerEqual(t, 1, compiler) 768 769 err = compiler.compileReturnFunction() 770 require.NoError(t, err) 771 772 code := asm.CodeSegment{} 773 defer func() { require.NoError(t, code.Unmap()) }() 774 775 // Generate the code under test and run. 776 _, err = compiler.compile(code.NextCodeSection()) 777 require.NoError(t, err) 778 env.exec(code.Bytes()) 779 780 require.Equal(t, nativeCallStatusCodeReturned.String(), env.compilerStatus().String()) 781 require.Equal(t, uint64(1), env.stackPointer()) 782 require.Equal(t, expectedReturnValue, uint32(env.ce.popValue())) 783 }) 784 } 785 }) 786 } 787 788 // TestCompiler_callIndirect_largeTypeIndex ensures that non-trivial large type index works well during call_indirect. 789 // Note: any index larger than 8-bit range is considered as large for arm64 compiler. 790 func TestCompiler_callIndirect_largeTypeIndex(t *testing.T) { 791 env := newCompilerEnvironment() 792 table := make([]wasm.Reference, 1) 793 env.addTable(&wasm.TableInstance{References: table}) 794 // Ensure that the module instance has the type information for targetOperation.TypeIndex, 795 // and the typeID matches the table[targetOffset]'s type ID. 796 const typeIndex, typeID = 12345, 0 797 operation := operationPtr(wazeroir.NewOperationCallIndirect(typeIndex, 0)) 798 env.module().TypeIDs = make([]wasm.FunctionTypeID, typeIndex+1) 799 env.module().TypeIDs[typeIndex] = typeID 800 env.module().Engine = &moduleEngine{functions: []function{}} 801 802 types := make([]wasm.FunctionType, typeIndex+1) 803 types[typeIndex] = wasm.FunctionType{} 804 805 code1 := asm.CodeSegment{} 806 code2 := asm.CodeSegment{} 807 defer func() { 808 require.NoError(t, code1.Unmap()) 809 require.NoError(t, code2.Unmap()) 810 }() 811 812 me := env.moduleEngine() 813 { // Compiling call target. 814 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil) 815 err := compiler.compilePreamble() 816 require.NoError(t, err) 817 err = compiler.compileReturnFunction() 818 require.NoError(t, err) 819 820 _, err = compiler.compile(code1.NextCodeSection()) 821 require.NoError(t, err) 822 823 makeExecutable(code1.Bytes()) 824 f := function{ 825 parent: &compiledFunction{parent: &compiledCode{executable: code1}}, 826 codeInitialAddress: uintptr(unsafe.Pointer(&code1.Bytes()[0])), 827 moduleInstance: env.moduleInstance, 828 } 829 me.functions = append(me.functions, f) 830 table[0] = uintptr(unsafe.Pointer(&f)) 831 } 832 833 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 834 Types: types, 835 HasTable: true, 836 }) 837 err := compiler.compilePreamble() 838 require.NoError(t, err) 839 840 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(0))) 841 require.NoError(t, err) 842 843 require.NoError(t, compiler.compileCallIndirect(operation)) 844 845 err = compiler.compileReturnFunction() 846 require.NoError(t, err) 847 848 // Generate the code under test and run. 849 _, err = compiler.compile(code2.NextCodeSection()) 850 require.NoError(t, err) 851 env.exec(code2.Bytes()) 852 } 853 854 func TestCompiler_compileCall(t *testing.T) { 855 env := newCompilerEnvironment() 856 me := env.moduleEngine() 857 expectedValue := uint32(0) 858 859 // Emit the call target function. 860 const numCalls = 3 861 targetFunctionType := wasm.FunctionType{ 862 Params: []wasm.ValueType{wasm.ValueTypeI32}, 863 Results: []wasm.ValueType{wasm.ValueTypeI32}, 864 ParamNumInUint64: 1, ResultNumInUint64: 1, 865 } 866 867 for i := 0; i < numCalls; i++ { 868 // Each function takes one argument, adds the value with 100 + i and returns the result. 869 addTargetValue := uint32(100 + i) 870 expectedValue += addTargetValue 871 compiler := env.requireNewCompiler(t, &targetFunctionType, newCompiler, &wazeroir.CompilationResult{}) 872 873 err := compiler.compilePreamble() 874 require.NoError(t, err) 875 876 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(addTargetValue))) 877 require.NoError(t, err) 878 // Picks the function argument placed at the bottom of the stack. 879 err = compiler.compilePick(operationPtr(wazeroir.NewOperationPick(int(compiler.runtimeValueLocationStack().sp-1), false))) 880 require.NoError(t, err) 881 // Adds the const to the picked value. 882 err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32))) 883 require.NoError(t, err) 884 // Then store the added result into the bottom of the stack (which is treated as the result of the function). 885 err = compiler.compileSet(operationPtr(wazeroir.NewOperationSet(int(compiler.runtimeValueLocationStack().sp-1), false))) 886 require.NoError(t, err) 887 888 err = compiler.compileReturnFunction() 889 require.NoError(t, err) 890 891 code := asm.CodeSegment{} 892 defer func() { require.NoError(t, code.Unmap()) }() 893 894 _, err = compiler.compile(code.NextCodeSection()) 895 require.NoError(t, err) 896 897 makeExecutable(code.Bytes()) 898 me.functions = append(me.functions, function{ 899 parent: &compiledFunction{parent: &compiledCode{executable: code}}, 900 codeInitialAddress: uintptr(unsafe.Pointer(&code.Bytes()[0])), 901 moduleInstance: env.moduleInstance, 902 }) 903 } 904 905 // Now we start building the caller's code. 906 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 907 Functions: make([]uint32, numCalls), 908 Types: []wasm.FunctionType{targetFunctionType}, 909 }) 910 911 err := compiler.compilePreamble() 912 require.NoError(t, err) 913 914 const initialValue = 100 915 expectedValue += initialValue 916 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(1234))) // Dummy value so the base pointer would be non-trivial for callees. 917 require.NoError(t, err) 918 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(initialValue))) 919 require.NoError(t, err) 920 921 // Call all the built functions. 922 for i := 0; i < numCalls; i++ { 923 err = compiler.compileCall(operationPtr(wazeroir.NewOperationCall(1))) 924 require.NoError(t, err) 925 } 926 927 // Set the result slot 928 err = compiler.compileReturnFunction() 929 require.NoError(t, err) 930 931 code := asm.CodeSegment{} 932 defer func() { require.NoError(t, code.Unmap()) }() 933 934 _, err = compiler.compile(code.NextCodeSection()) 935 require.NoError(t, err) 936 env.exec(code.Bytes()) 937 938 // Check status and returned values. 939 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 940 require.Equal(t, uint64(0), env.stackBasePointer()) 941 require.Equal(t, uint64(2), env.stackPointer()) // Must be 2 (dummy value + the calculation results) 942 require.Equal(t, expectedValue, env.stackTopAsUint32()) 943 }