compiler: Select conditional branches in checked code
d266f79a93d9f6b7977af137c5cc218fb81142d304594d49dd6c9adb17de0625
1 parent
b166fdc9
lib/std/arch/rv64/bounds.rad
+67 -2
| 250 | 250 | } |
|
| 251 | 251 | } |
|
| 252 | 252 | } |
|
| 253 | 253 | } |
|
| 254 | 254 | ||
| 255 | + | /// Conditional edges preserve capacity checks for each comparison and layout. |
|
| 256 | + | @test unsafe fn branchSelectionCapacity() throws (testing::TestError) { |
|
| 257 | + | for op in [il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] { |
|
| 258 | + | for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] { |
|
| 259 | + | for layout in 0..2 { |
|
| 260 | + | for argumentSide in 0..3 { |
|
| 261 | + | try checkBranchCapacity(op, typ, layout, argumentSide); |
|
| 262 | + | } |
|
| 263 | + | } |
|
| 264 | + | } |
|
| 265 | + | } |
|
| 266 | + | } |
|
| 267 | + | ||
| 268 | + | /// Build a branch with no arguments or arguments on exactly one edge. |
|
| 269 | + | unsafe fn checkBranchCapacity(op: il::CmpOp, typ: il::Type, layout: u32, argumentSide: u32) |
|
| 270 | + | throws (testing::TestError) |
|
| 271 | + | { |
|
| 272 | + | let a = il::Val::Reg(il::Reg { n: 0 }); |
|
| 273 | + | let b = il::Val::Reg(il::Reg { n: 1 }); |
|
| 274 | + | let params = [ |
|
| 275 | + | il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }, |
|
| 276 | + | il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }, |
|
| 277 | + | ]; |
|
| 278 | + | let thenParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }]; |
|
| 279 | + | let elseParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }]; |
|
| 280 | + | let thenCount: u32 = 1 if argumentSide == 1 else 0; |
|
| 281 | + | let elseCount: u32 = 1 if argumentSide == 2 else 0; |
|
| 282 | + | let mut thenArgs = [b]; |
|
| 283 | + | let mut elseArgs = [a]; |
|
| 284 | + | let mut entry = [il::Instr::Br { |
|
| 285 | + | op, typ, a, b: il::Val::Imm(0) if argumentSide == 1 else b, |
|
| 286 | + | thenTarget: 1 if layout == 0 else 2, thenArgs: &mut thenArgs[..thenCount], |
|
| 287 | + | elseTarget: 2 if layout == 0 else 1, elseArgs: &mut elseArgs[..elseCount], |
|
| 288 | + | }]; |
|
| 289 | + | let mut thenBody = [il::Instr::Ret { |
|
| 290 | + | val: il::Val::Reg(il::Reg { n: 2 }) if thenCount > 0 else il::Val::Imm(10), |
|
| 291 | + | }]; |
|
| 292 | + | let mut elseBody = [il::Instr::Ret { |
|
| 293 | + | val: il::Val::Reg(il::Reg { n: 3 }) if elseCount > 0 else il::Val::Imm(20), |
|
| 294 | + | }]; |
|
| 295 | + | let thenBlock = il::Block { |
|
| 296 | + | label: "then", params: &thenParams[..thenCount], instrs: &mut thenBody[..], |
|
| 297 | + | locs: &[], preds: &[0], loopDepth: 0, |
|
| 298 | + | }; |
|
| 299 | + | let elseBlock = il::Block { |
|
| 300 | + | label: "else", params: &elseParams[..elseCount], instrs: &mut elseBody[..], |
|
| 301 | + | locs: &[], preds: &[0], loopDepth: 0, |
|
| 302 | + | }; |
|
| 303 | + | let blocks = [ |
|
| 304 | + | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
|
| 305 | + | thenBlock if layout == 0 else elseBlock, |
|
| 306 | + | elseBlock if layout == 0 else thenBlock, |
|
| 307 | + | ]; |
|
| 308 | + | let func = il::Fn { |
|
| 309 | + | name: "p::branch", params: ¶ms[..], returnType: il::Type::W64, |
|
| 310 | + | isExtern: false, isLeaf: true, blocks: &blocks[..], |
|
| 311 | + | }; |
|
| 312 | + | try checkFunctionCapacity(&func, nil); |
|
| 313 | + | } |
|
| 314 | + | ||
| 255 | 315 | /// Verify instruction errors, exact-fit output, and shorter-buffer canaries. |
|
| 256 | 316 | unsafe fn checkSelectionCapacity(instr: il::Instr, expectedError: ?super::Error) throws (testing::TestError) { |
|
| 257 | 317 | let mut body = [instr, il::Instr::Ret { val: il::Val::Imm(0) }]; |
|
| 258 | 318 | let mut count: u32 = 2; |
|
| 259 | 319 | if let case il::Instr::Ret { .. } = instr { |
| 266 | 326 | let func = il::Fn { |
|
| 267 | 327 | name: "p::inline", params: ¶ms[..], returnType: il::Type::W64, |
|
| 268 | 328 | isExtern: false, isLeaf: not il::isCall(instr), |
|
| 269 | 329 | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..count], locs: &[], preds: &[], loopDepth: 0 }], |
|
| 270 | 330 | }; |
|
| 331 | + | try checkFunctionCapacity(&func, expectedError); |
|
| 332 | + | } |
|
| 333 | + | ||
| 334 | + | /// Verify function output capacity and failure propagation with guard words. |
|
| 335 | + | unsafe fn checkFunctionCapacity(func: &il::Fn, expectedError: ?super::Error) throws (testing::TestError) { |
|
| 271 | 336 | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 272 | 337 | let mut gen = generator(&mut arena); |
|
| 273 | 338 | let mut scratch = alloc::new(&mut SCRATCH[..]); |
|
| 274 | - | super::generateFunction(&mut gen, &func, &mut scratch); |
|
| 339 | + | super::generateFunction(&mut gen, func, &mut scratch); |
|
| 275 | 340 | assert gen.e.error == expectedError; |
|
| 276 | 341 | if expectedError <> nil { |
|
| 277 | 342 | let count = gen.e.codeLen; |
|
| 278 | 343 | emit::emit(&mut gen.e, encode::nop()); |
|
| 279 | 344 | assert gen.e.codeLen == count; |
| 291 | 356 | set words[i] = 0xdeadbeef; |
|
| 292 | 357 | } |
|
| 293 | 358 | alloc::reset(&mut arena); |
|
| 294 | 359 | set gen = generator(&mut arena); |
|
| 295 | 360 | set gen.e.code = &mut words[1..capacity + 1]; |
|
| 296 | - | super::generateFunction(&mut gen, &func, &mut scratch); |
|
| 361 | + | super::generateFunction(&mut gen, func, &mut scratch); |
|
| 297 | 362 | assert gen.e.codeLen <= capacity; |
|
| 298 | 363 | assert words[0] == 0xdeadbeef; |
|
| 299 | 364 | for i in (capacity + 1)..words.len { |
|
| 300 | 365 | assert words[i] == 0xdeadbeef; |
|
| 301 | 366 | } |
lib/std/arch/rv64/isel.rad
+76 -58
| 421 | 421 | // Skip branch if target is the next block (fallthrough). |
|
| 422 | 422 | if target <> blockIdx + 1 { |
|
| 423 | 423 | emit::recordBranch(s.e, target, emit::BranchKind::Jump); |
|
| 424 | 424 | } |
|
| 425 | 425 | }, |
|
| 426 | - | case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => { |
|
| 427 | - | // Use zero register directly for immediate `0` operands. |
|
| 428 | - | let aIsZero = isZeroImm(a); |
|
| 429 | - | let bIsZero = isZeroImm(b); |
|
| 430 | - | ||
| 431 | - | let rs1 = super::ZERO if aIsZero else resolveVal(s, super::SCRATCH1, a); |
|
| 432 | - | let rs2 = super::ZERO if bIsZero else resolveVal(s, super::SCRATCH2, b); |
|
| 433 | - | ||
| 434 | - | // Normalize sub-word operands so that both registers have the same |
|
| 435 | - | // canonical representation. Without this, eg. `-1 : i8 ` loaded as |
|
| 436 | - | // `0xFFFFFFFFFFFFFFFF` and `255 : i8` loaded as `0xFF` would compare |
|
| 437 | - | // unequal even though they are the same 8-bit pattern. |
|
| 438 | - | // |
|
| 439 | - | // For SLT: sign-extension needed (signed comparison). |
|
| 440 | - | // For ULT: zero-extension needed (unsigned magnitude comparison). |
|
| 441 | - | // For EQ/NE with W32: sign-extension is cheaper. |
|
| 442 | - | // For EQ/NE with W8/W16: keep zero-extension. |
|
| 443 | - | // Skip extension for zero register. |
|
| 444 | - | let mut signed = false; |
|
| 445 | - | if let case il::CmpOp::Slt = op { |
|
| 446 | - | set signed = true; |
|
| 447 | - | } |
|
| 448 | - | let useSext = cmpUsesSext(typ, signed); |
|
| 449 | - | if not aIsZero and not isExtendedImm(a, typ, useSext) { |
|
| 450 | - | emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 451 | - | } |
|
| 452 | - | if not bIsZero and not isExtendedImm(b, typ, useSext) { |
|
| 453 | - | emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 454 | - | } |
|
| 455 | - | // Block-argument moves must only execute on the taken path. |
|
| 456 | - | // When `thenArgs` is non-empty, invert the branch so that the |
|
| 457 | - | // then-moves land on the fall-through (taken) side. |
|
| 458 | - | // |
|
| 459 | - | // When one target is the next block in layout order, we can |
|
| 460 | - | // eliminate the trailing unconditional jump by arranging the |
|
| 461 | - | // conditional branch to skip to the *other* target and letting |
|
| 462 | - | // execution fall through. |
|
| 463 | - | if thenArgs.len > 0 and elseArgs.len > 0 { |
|
| 464 | - | panic "selectInstr: both `then` and `else` have block arguments"; |
|
| 465 | - | } else if thenArgs.len > 0 { |
|
| 466 | - | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 467 | - | emitBlockArgs(s, func.blocks[thenTarget].params, thenArgs); |
|
| 468 | - | // Skip trailing jump if then is the next block (fallthrough). |
|
| 469 | - | if thenTarget <> blockIdx + 1 { |
|
| 470 | - | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump); |
|
| 471 | - | } |
|
| 472 | - | } else if thenTarget == blockIdx + 1 and elseArgs.len == 0 { |
|
| 473 | - | // Then is the next block and no else args: invert the |
|
| 474 | - | // condition to branch to else and fall through to then. |
|
| 475 | - | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 476 | - | } else { |
|
| 477 | - | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 }); |
|
| 478 | - | emitBlockArgs(s, func.blocks[elseTarget].params, elseArgs); |
|
| 479 | - | // Skip trailing jump if else is the next block (fallthrough). |
|
| 480 | - | if elseTarget <> blockIdx + 1 { |
|
| 481 | - | emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump); |
|
| 482 | - | } |
|
| 483 | - | } |
|
| 426 | + | case il::Instr::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => { |
|
| 427 | + | let params: *unsafe [il::Param] = func.blocks[thenTarget].params |
|
| 428 | + | if thenArgs.len > 0 and elseArgs.len == 0 |
|
| 429 | + | else func.blocks[elseTarget].params if elseArgs.len > 0 and thenArgs.len == 0 |
|
| 430 | + | else &[]; |
|
| 431 | + | selectBranch(s, blockIdx, instr, params, thenArgs, elseArgs); |
|
| 484 | 432 | }, |
|
| 485 | 433 | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 486 | 434 | let rs1 = resolveVal(s, super::SCRATCH1, val); |
|
| 487 | 435 | // When a case has block args, invert the branch to skip past |
|
| 488 | 436 | // the arg moves. |
| 512 | 460 | case il::Instr::Call { dst, func, args, .. } => selectCall(s, &func, args, dst), |
|
| 513 | 461 | else => selectFixedInstr(s, blockIdx, instr, frame), |
|
| 514 | 462 | } |
|
| 515 | 463 | } |
|
| 516 | 464 | ||
| 465 | + | /// Select a conditional branch with parameters for its argument-bearing edge. |
|
| 466 | + | fn selectBranch 'scratch 'selection ( |
|
| 467 | + | s: &mut Selector 'scratch 'selection, |
|
| 468 | + | blockIdx: u32, |
|
| 469 | + | instr: &il::Instr, |
|
| 470 | + | params: &[il::Param], |
|
| 471 | + | thenArgs: &[il::Val], |
|
| 472 | + | elseArgs: &[il::Val] |
|
| 473 | + | ) where 'scratch: 'selection { |
|
| 474 | + | let case il::Instr::Br { op, typ, a, b, thenTarget, elseTarget, .. } = *instr |
|
| 475 | + | else panic "selectBranch: expected conditional branch"; |
|
| 476 | + | // Use zero register directly for immediate `0` operands. |
|
| 477 | + | let aIsZero = isZeroImm(a); |
|
| 478 | + | let bIsZero = isZeroImm(b); |
|
| 479 | + | ||
| 480 | + | let rs1 = super::ZERO if aIsZero else resolveVal(s, super::SCRATCH1, a); |
|
| 481 | + | let rs2 = super::ZERO if bIsZero else resolveVal(s, super::SCRATCH2, b); |
|
| 482 | + | ||
| 483 | + | // Normalize sub-word operands so that both registers have the same |
|
| 484 | + | // canonical representation. Without this, eg. `-1 : i8 ` loaded as |
|
| 485 | + | // `0xFFFFFFFFFFFFFFFF` and `255 : i8` loaded as `0xFF` would compare |
|
| 486 | + | // unequal even though they are the same 8-bit pattern. |
|
| 487 | + | // |
|
| 488 | + | // For SLT: sign-extension needed (signed comparison). |
|
| 489 | + | // For ULT: zero-extension needed (unsigned magnitude comparison). |
|
| 490 | + | // For EQ/NE with W32: sign-extension is cheaper. |
|
| 491 | + | // For EQ/NE with W8/W16: keep zero-extension. |
|
| 492 | + | // Skip extension for zero register. |
|
| 493 | + | let mut signed = false; |
|
| 494 | + | if let case il::CmpOp::Slt = op { |
|
| 495 | + | set signed = true; |
|
| 496 | + | } |
|
| 497 | + | let useSext = cmpUsesSext(typ, signed); |
|
| 498 | + | if not aIsZero and not isExtendedImm(a, typ, useSext) { |
|
| 499 | + | emitCmpExt(s.e, rs1, rs1, typ, useSext); |
|
| 500 | + | } |
|
| 501 | + | if not bIsZero and not isExtendedImm(b, typ, useSext) { |
|
| 502 | + | emitCmpExt(s.e, rs2, rs2, typ, useSext); |
|
| 503 | + | } |
|
| 504 | + | // Block-argument moves must only execute on the taken path. |
|
| 505 | + | // When `thenArgs` is non-empty, invert the branch so that the |
|
| 506 | + | // then-moves land on the fall-through (taken) side. |
|
| 507 | + | // |
|
| 508 | + | // When one target is the next block in layout order, we can |
|
| 509 | + | // eliminate the trailing unconditional jump by arranging the |
|
| 510 | + | // conditional branch to skip to the *other* target and letting |
|
| 511 | + | // execution fall through. |
|
| 512 | + | if thenArgs.len > 0 and elseArgs.len > 0 { |
|
| 513 | + | panic "selectBranch: both `then` and `else` have block arguments"; |
|
| 514 | + | } else if thenArgs.len > 0 { |
|
| 515 | + | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 516 | + | emitBlockArgs(s, params, thenArgs); |
|
| 517 | + | // Skip trailing jump if then is the next block (fallthrough). |
|
| 518 | + | if thenTarget <> blockIdx + 1 { |
|
| 519 | + | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump); |
|
| 520 | + | } |
|
| 521 | + | } else if thenTarget == blockIdx + 1 and elseArgs.len == 0 { |
|
| 522 | + | // Then is the next block and no else args: invert the |
|
| 523 | + | // condition to branch to else and fall through to then. |
|
| 524 | + | emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 }); |
|
| 525 | + | } else { |
|
| 526 | + | emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 }); |
|
| 527 | + | emitBlockArgs(s, params, elseArgs); |
|
| 528 | + | // Skip trailing jump if else is the next block (fallthrough). |
|
| 529 | + | if elseTarget <> blockIdx + 1 { |
|
| 530 | + | emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump); |
|
| 531 | + | } |
|
| 532 | + | } |
|
| 533 | + | } |
|
| 534 | + | ||
| 517 | 535 | /// Select a call from its target, borrowed arguments, and optional result. |
|
| 518 | 536 | fn selectCall 'scratch 'selection ( |
|
| 519 | 537 | s: &mut Selector 'scratch 'selection, |
|
| 520 | 538 | func: &il::Val, |
|
| 521 | 539 | args: &[il::Val], |