compiler: Select RV64 calls in checked code

b166fdc965ab805c053474a1233bcd55ab152a379a491b64767e65c6ed4c17cc
Alexis Sellier committed ago 1 parent d3ace8fb
lib/std/arch/rv64/bounds.rad +28 -4
228 228
        il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: a },
229 229
        il::Instr::Ebreak,
230 230
        il::Instr::MemoryFence,
231 231
    ];
232 232
    for instr in instructions {
233 -
        try checkSelectionCapacity(instr);
233 +
        try checkSelectionCapacity(instr, nil);
234 234
    }
235 235
}
236 236
237 -
/// Verify exact-fit output and canaries around every shorter code buffer.
238 -
unsafe fn checkSelectionCapacity(instr: il::Instr) throws (testing::TestError) {
237 +
/// Direct and indirect calls check argument and output capacities.
238 +
@test unsafe fn callSelectionCapacity() throws (testing::TestError) {
239 +
    let a = il::Val::Reg(il::Reg { n: 0 });
240 +
    let b = il::Val::Reg(il::Reg { n: 1 });
241 +
    let args = [b, a, b, a, il::Val::Imm(7), a, b, a, a];
242 +
    for callee in [il::Val::FnAddr("p::callee"), a] {
243 +
        for dst in [nil as ?il::Reg, il::Reg { n: 3 }] {
244 +
            for count in [0 as u32, 8, 9] {
245 +
                let instr = il::Instr::Call {
246 +
                    retTy: il::Type::W64, dst, func: callee, args: &args[..count],
247 +
                };
248 +
                let expected: ?super::Error = super::Error::Capacity if count == 9 else nil;
249 +
                try checkSelectionCapacity(instr, expected);
250 +
            }
251 +
        }
252 +
    }
253 +
}
254 +
255 +
/// Verify instruction errors, exact-fit output, and shorter-buffer canaries.
256 +
unsafe fn checkSelectionCapacity(instr: il::Instr, expectedError: ?super::Error) throws (testing::TestError) {
239 257
    let mut body = [instr, il::Instr::Ret { val: il::Val::Imm(0) }];
240 258
    let mut count: u32 = 2;
241 259
    if let case il::Instr::Ret { .. } = instr {
242 260
        set count = 1;
243 261
    }
252 270
    };
253 271
    let mut arena = alloc::new(&mut MEMORY[..]);
254 272
    let mut gen = generator(&mut arena);
255 273
    let mut scratch = alloc::new(&mut SCRATCH[..]);
256 274
    super::generateFunction(&mut gen, &func, &mut scratch);
257 -
    assert gen.e.error == nil;
275 +
    assert gen.e.error == expectedError;
276 +
    if expectedError <> nil {
277 +
        let count = gen.e.codeLen;
278 +
        emit::emit(&mut gen.e, encode::nop());
279 +
        assert gen.e.codeLen == count;
280 +
        return;
281 +
    }
258 282
    let length = gen.e.codeLen;
259 283
    let mut expected: [u32; 128] = [0; 128];
260 284
    assert length > 0 and length <= expected.len;
261 285
    for i in 0..length {
262 286
        set expected[i] = gen.e.code[i];
lib/std/arch/rv64/isel.rad +39 -31
507 507
            }
508 508
            // Fall through to default.
509 509
            emitBlockArgs(s, func.blocks[defaultTarget].params, defaultArgs);
510 510
            emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump);
511 511
        },
512 -
        case il::Instr::Call { retTy, dst, func, args } => {
513 -
            // For indirect calls, save target to scratch register before arg
514 -
            // setup can clobber it.
515 -
            if let case il::Val::Reg(r) = func {
516 -
                let target = getSrcReg(s, r, super::SCRATCH2);
517 -
                emitMv(s, super::SCRATCH2, target);
518 -
            }
519 -
            // Move arguments to A0-A7 using parallel move resolution.
520 -
            if args.len > super::ARG_REGS.len {
521 -
                set s.e.error = super::Error::Capacity;
522 -
                return;
523 -
            }
524 -
            emitParallelMoves(s, &super::ARG_REGS[..], args);
512 +
        case il::Instr::Call { dst, func, args, .. } => selectCall(s, &func, args, dst),
513 +
        else => selectFixedInstr(s, blockIdx, instr, frame),
514 +
    }
515 +
}
525 516
526 -
            // Emit call.
527 -
            match func {
528 -
                case il::Val::FnAddr(name) => {
529 -
                    emit::recordCall(s.e, name);
530 -
                },
531 -
                case il::Val::Reg(_) => {
532 -
                    emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0));
533 -
                },
534 -
                else => {
535 -
                    panic "selectInstr: invalid call target";
536 -
                }
537 -
            }
538 -
            // Move result from A0.
539 -
            if let d = dst {
540 -
                let rd = getDstReg(s, d, super::SCRATCH1);
541 -
                emitMv(s, rd, super::A0);
542 -
            }
517 +
/// Select a call from its target, borrowed arguments, and optional result.
518 +
fn selectCall 'scratch 'selection (
519 +
    s: &mut Selector 'scratch 'selection,
520 +
    func: &il::Val,
521 +
    args: &[il::Val],
522 +
    dst: ?il::Reg
523 +
) where 'scratch: 'selection {
524 +
    // For indirect calls, save target to scratch register before arg
525 +
    // setup can clobber it.
526 +
    if let case il::Val::Reg(r) = *func {
527 +
        let target = getSrcReg(s, r, super::SCRATCH2);
528 +
        emitMv(s, super::SCRATCH2, target);
529 +
    }
530 +
    // Move arguments to A0-A7 using parallel move resolution.
531 +
    if args.len > super::ARG_REGS.len {
532 +
        set s.e.error = super::Error::Capacity;
533 +
        return;
534 +
    }
535 +
    emitParallelMoves(s, &super::ARG_REGS[..], args);
536 +
537 +
    // Emit call.
538 +
    match *func {
539 +
        case il::Val::FnAddr(name) => {
540 +
            emit::recordCall(s.e, name);
543 541
        },
544 -
        else => selectFixedInstr(s, blockIdx, instr, frame),
542 +
        case il::Val::Reg(_) => {
543 +
            emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0));
544 +
        },
545 +
        else => {
546 +
            panic "selectCall: invalid call target";
547 +
        }
548 +
    }
549 +
    // Move result from A0.
550 +
    if let d = dst {
551 +
        let rd = getDstReg(s, d, super::SCRATCH1);
552 +
        emitMv(s, rd, super::A0);
545 553
    }
546 554
}
547 555
548 556
/// Select RV64 instructions for inline IL operands.
549 557
fn selectFixedInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::Instr, frame: &emit::Frame) where 'scratch: 'selection {
test/tests/call.checked.selection.rad added +30 -0
1 +
//! returns: 0
2 +
//! Full-arity calls preserve argument order and live values.
3 +
4 +
/// Encode eight arguments in distinct decimal positions.
5 +
fn digits(a: u64, b: u64, c: u64, d: u64, e: u64, f: u64, g: u64, h: u64) -> u64 {
6 +
    return a + 10 * b + 100 * c + 1000 * d + 10000 * e + 100000 * f + 1000000 * g + 10000000 * h;
7 +
}
8 +
9 +
/// Retain argument values and the target across two indirect calls.
10 +
fn invoke(callback: fn(u64, u64, u64, u64, u64, u64, u64, u64) -> u64, values: &[u64]) -> u64 {
11 +
    let a = values[0];
12 +
    let b = values[1];
13 +
    let c = values[2];
14 +
    let d = values[3];
15 +
    let e = values[4];
16 +
    let f = values[5];
17 +
    let g = values[6];
18 +
    let h = values[7];
19 +
    let reversed = callback(h, g, f, e, d, c, b, a);
20 +
    let ordered = callback(a, b, c, d, e, f, g, h);
21 +
    return reversed + ordered + a + b + c + d + e + f + g + h;
22 +
}
23 +
24 +
/// Check direct argument placement and repeated indirect calls.
25 +
@default fn main() -> u32 {
26 +
    let values: [u64; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
27 +
    assert digits(1, 2, 3, 4, 5, 6, 7, 8) == 87654321;
28 +
    assert invoke(digits, &values[..]) == 100000035;
29 +
    return 0;
30 +
}