compiler: Select switch cases in checked code

ce06be751a392a2c75e7a318fe4c590b98099bcadfa0a82df9c4e0b3890172ea
Alexis Sellier committed ago 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: &params[..], 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,