compiler: Select conditional branches in checked code

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