compiler: Select switch cases in checked code
ce06be751a392a2c75e7a318fe4c590b98099bcadfa0a82df9c4e0b3890172ea
1 parent
003268b3
lib/std/arch/rv64/bounds.rad
+61 -0
| 250 | 250 | } |
|
| 251 | 251 | } |
|
| 252 | 252 | } |
|
| 253 | 253 | } |
|
| 254 | 254 | ||
| 255 | + | /// Switch cases and default edges respect every shorter output capacity. |
|
| 256 | + | @test unsafe fn switchSelectionCapacity() throws (testing::TestError) { |
|
| 257 | + | for count in 0..3 { |
|
| 258 | + | for argumentMask in 0..8 { |
|
| 259 | + | try checkSwitchCapacity(count, argumentMask); |
|
| 260 | + | } |
|
| 261 | + | } |
|
| 262 | + | } |
|
| 263 | + | ||
| 264 | + | /// Build a switch with independently selected case and default arguments. |
|
| 265 | + | unsafe fn checkSwitchCapacity(count: u32, argumentMask: u32) throws (testing::TestError) { |
|
| 266 | + | let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }]; |
|
| 267 | + | let firstParams = [il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }]; |
|
| 268 | + | let secondParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }]; |
|
| 269 | + | let defaultParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }]; |
|
| 270 | + | let firstCount: u32 = 1 if argumentMask & 1 <> 0 else 0; |
|
| 271 | + | let secondCount: u32 = 1 if argumentMask & 2 <> 0 else 0; |
|
| 272 | + | let defaultCount: u32 = 1 if argumentMask & 4 <> 0 else 0; |
|
| 273 | + | let mut firstArgs = [il::Val::Imm(11)]; |
|
| 274 | + | let mut secondArgs = [il::Val::Imm(22)]; |
|
| 275 | + | let mut defaultArgs = [il::Val::Reg(il::Reg { n: 0 })]; |
|
| 276 | + | let mut cases = [ |
|
| 277 | + | il::SwitchCase { value: 0, target: 1, args: &mut firstArgs[..firstCount] }, |
|
| 278 | + | il::SwitchCase { value: 0x123456789abcdef, target: 2, args: &mut secondArgs[..secondCount] }, |
|
| 279 | + | ]; |
|
| 280 | + | let mut entry = [il::Instr::Switch { |
|
| 281 | + | val: il::Val::Reg(il::Reg { n: 0 }), defaultTarget: 3, |
|
| 282 | + | defaultArgs: &mut defaultArgs[..defaultCount], cases: &mut cases[..count], |
|
| 283 | + | }]; |
|
| 284 | + | let mut first = [il::Instr::Ret { |
|
| 285 | + | val: il::Val::Reg(il::Reg { n: 1 }) if firstCount > 0 else il::Val::Imm(11), |
|
| 286 | + | }]; |
|
| 287 | + | let mut second = [il::Instr::Ret { |
|
| 288 | + | val: il::Val::Reg(il::Reg { n: 2 }) if secondCount > 0 else il::Val::Imm(22), |
|
| 289 | + | }]; |
|
| 290 | + | let mut fallback = [il::Instr::Ret { |
|
| 291 | + | val: il::Val::Reg(il::Reg { n: 3 }) if defaultCount > 0 else il::Val::Imm(33), |
|
| 292 | + | }]; |
|
| 293 | + | let predecessors = [0 as u32]; |
|
| 294 | + | let blocks = [ |
|
| 295 | + | il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 }, |
|
| 296 | + | il::Block { |
|
| 297 | + | label: "first", params: &firstParams[..firstCount], instrs: &mut first[..], |
|
| 298 | + | locs: &[], preds: &predecessors[..1 if count > 0 else 0], loopDepth: 0, |
|
| 299 | + | }, |
|
| 300 | + | il::Block { |
|
| 301 | + | label: "second", params: &secondParams[..secondCount], instrs: &mut second[..], |
|
| 302 | + | locs: &[], preds: &predecessors[..1 if count > 1 else 0], loopDepth: 0, |
|
| 303 | + | }, |
|
| 304 | + | il::Block { |
|
| 305 | + | label: "default", params: &defaultParams[..defaultCount], instrs: &mut fallback[..], |
|
| 306 | + | locs: &[], preds: &predecessors[..], loopDepth: 0, |
|
| 307 | + | }, |
|
| 308 | + | ]; |
|
| 309 | + | let func = il::Fn { |
|
| 310 | + | name: "p::switch", params: ¶ms[..], returnType: il::Type::W64, |
|
| 311 | + | isExtern: false, isLeaf: true, blocks: &blocks[..], |
|
| 312 | + | }; |
|
| 313 | + | try checkFunctionCapacity(&func, nil); |
|
| 314 | + | } |
|
| 315 | + | ||
| 255 | 316 | /// Jumps preserve argument moves and capacity checks in both block layouts. |
|
| 256 | 317 | @test unsafe fn jumpSelectionCapacity() throws (testing::TestError) { |
|
| 257 | 318 | for target in [1 as u32, 2] { |
|
| 258 | 319 | for count in 0..3 { |
|
| 259 | 320 | let params = [ |
lib/std/arch/rv64/isel.rad
+33 -19
| 425 | 425 | else &[]; |
|
| 426 | 426 | selectBranch(s, blockIdx, instr, params, thenArgs, elseArgs); |
|
| 427 | 427 | }, |
|
| 428 | 428 | case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => { |
|
| 429 | 429 | let rs1 = resolveVal(s, super::SCRATCH1, val); |
|
| 430 | - | // When a case has block args, invert the branch to skip past |
|
| 431 | - | // the arg moves. |
|
| 432 | 430 | for c in cases { |
|
| 433 | - | emit::loadImm(s.e, super::SCRATCH2, c.value); |
|
| 434 | - | ||
| 435 | - | if c.args.len > 0 { |
|
| 436 | - | let skip = s.nextSynthBlock; |
|
| 437 | - | set s.nextSynthBlock = skip + 1; |
|
| 438 | - | ||
| 439 | - | emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond { |
|
| 440 | - | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 441 | - | }); |
|
| 442 | - | emitBlockArgs(s, func.blocks[c.target].params, c.args); |
|
| 443 | - | emit::recordBranch(s.e, c.target, emit::BranchKind::Jump); |
|
| 444 | - | emit::recordBlock(s.e, skip); |
|
| 445 | - | } else { |
|
| 446 | - | emit::recordBranch(s.e, c.target, emit::BranchKind::Cond { |
|
| 447 | - | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 448 | - | }); |
|
| 449 | - | } |
|
| 431 | + | let params: *unsafe [il::Param] = func.blocks[c.target].params |
|
| 432 | + | if c.args.len > 0 else &[]; |
|
| 433 | + | selectSwitchCase(s, rs1, c.value, c.target, params, c.args); |
|
| 450 | 434 | } |
|
| 451 | 435 | // Fall through to default. |
|
| 452 | 436 | emitBlockArgs(s, func.blocks[defaultTarget].params, defaultArgs); |
|
| 453 | 437 | emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump); |
|
| 454 | 438 | }, |
|
| 455 | 439 | case il::Instr::Call { dst, func, args, .. } => selectCall(s, &func, args, dst), |
|
| 456 | 440 | else => selectFixedInstr(s, blockIdx, instr, frame), |
|
| 457 | 441 | } |
|
| 458 | 442 | } |
|
| 459 | 443 | ||
| 444 | + | /// Compare one switch case and emit its argument moves only on a match. |
|
| 445 | + | fn selectSwitchCase 'scratch 'selection ( |
|
| 446 | + | s: &mut Selector 'scratch 'selection, |
|
| 447 | + | rs1: gen::Reg, |
|
| 448 | + | value: i64, |
|
| 449 | + | target: u32, |
|
| 450 | + | params: &[il::Param], |
|
| 451 | + | args: &[il::Val] |
|
| 452 | + | ) where 'scratch: 'selection { |
|
| 453 | + | emit::loadImm(s.e, super::SCRATCH2, value); |
|
| 454 | + | ||
| 455 | + | // When a case has block args, invert the branch to skip past |
|
| 456 | + | // the arg moves. |
|
| 457 | + | if args.len > 0 { |
|
| 458 | + | let skip = s.nextSynthBlock; |
|
| 459 | + | set s.nextSynthBlock = skip + 1; |
|
| 460 | + | ||
| 461 | + | emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond { |
|
| 462 | + | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 463 | + | }); |
|
| 464 | + | emitBlockArgs(s, params, args); |
|
| 465 | + | emit::recordBranch(s.e, target, emit::BranchKind::Jump); |
|
| 466 | + | emit::recordBlock(s.e, skip); |
|
| 467 | + | } else { |
|
| 468 | + | emit::recordBranch(s.e, target, emit::BranchKind::Cond { |
|
| 469 | + | op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2, |
|
| 470 | + | }); |
|
| 471 | + | } |
|
| 472 | + | } |
|
| 473 | + | ||
| 460 | 474 | /// Move edge arguments and emit a jump unless its destination follows the block. |
|
| 461 | 475 | fn selectJump 'scratch 'selection ( |
|
| 462 | 476 | s: &mut Selector 'scratch 'selection, |
|
| 463 | 477 | blockIdx: u32, |
|
| 464 | 478 | target: u32, |