compiler: Check control-flow target printing

cc046d6315be9ba6aba1df8f0831072802b02f2854770d08a5ab3e6704a78fc9
Alexis Sellier committed ago 1 parent 084f3c7e
lib/std/lang/il/printer.rad +19 -25
193 193
            writeVal(out, func);
194 194
            writeArgs(out, args);
195 195
        }
196 196
197 197
        case super::Instr::Jmp { target, args } => {
198 -
            write(out, "jmp @");
199 -
            write(out, blocks[target].label);
200 -
            if args.len > 0 {
201 -
                writeArgs(out, args);
202 -
            }
198 +
            write(out, "jmp ");
199 +
            writeTarget(out, blocks, target, args);
203 200
        }
204 201
        case super::Instr::Br { op, typ, a: va, b: vb, thenTarget, thenArgs, elseTarget, elseArgs } => {
205 202
            write(out, "br.");
206 203
            match op {
207 204
                case super::CmpOp::Eq  => write(out, "eq"),
213 210
            writeType(out, typ);
214 211
            write(out, " ");
215 212
            writeVal(out, va);
216 213
            write(out, " ");
217 214
            writeVal(out, vb);
218 -
            write(out, " @");
219 -
            write(out, blocks[thenTarget].label);
220 -
            if thenArgs.len > 0 {
221 -
                writeArgs(out, thenArgs);
222 -
            }
223 -
            write(out, " @");
224 -
            write(out, blocks[elseTarget].label);
225 -
            if elseArgs.len > 0 {
226 -
                writeArgs(out, elseArgs);
227 -
            }
215 +
            write(out, " ");
216 +
            writeTarget(out, blocks, thenTarget, thenArgs);
217 +
            write(out, " ");
218 +
            writeTarget(out, blocks, elseTarget, elseArgs);
228 219
        }
229 220
        case super::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
230 221
            write(out, "switch ");
231 222
            writeVal(out, val);
232 223
            for c in cases {
233 224
                write(out, " (");
234 225
                writeI64(out, c.value);
235 -
                write(out, " @");
236 -
                write(out, blocks[c.target].label);
237 -
                if c.args.len > 0 {
238 -
                    writeArgs(out, c.args);
239 -
                }
226 +
                write(out, " ");
227 +
                writeTarget(out, blocks, c.target, c.args);
240 228
                write(out, ")");
241 229
            }
242 -
            write(out, " @");
243 -
            write(out, blocks[defaultTarget].label);
244 -
            if defaultArgs.len > 0 {
245 -
                writeArgs(out, defaultArgs);
246 -
            }
230 +
            write(out, " ");
231 +
            writeTarget(out, blocks, defaultTarget, defaultArgs);
247 232
        }
248 233
        else => writeFixedInstr(out, inst),
249 234
    }
250 235
}
251 236
237 +
/// Write a target label and its optional block arguments.
238 +
fn writeTarget(out: &mut opaque sexpr::Output, blocks: &[super::Block], target: u32, args: &[super::Val]) {
239 +
    write(out, "@");
240 +
    write(out, blocks[target].label);
241 +
    if args.len > 0 {
242 +
        writeArgs(out, args);
243 +
    }
244 +
}
245 +
252 246
/// Write an instruction whose operands need no graph traversal.
253 247
fn writeFixedInstr(out: &mut opaque sexpr::Output, inst: super::Instr) {
254 248
    match inst {
255 249
        // Memory operations.
256 250
        case super::Instr::Reserve { dst, size, alignment } => {
lib/std/lang/il/tests.rad +35 -0
54 54
    try checkPrintedInstruction(super::Instr::DeviceWrite { typ: super::Type::W64, handle: source, offset: value, value: source }, "device-write w64 %2 -9223372036854775808 %2");
55 55
    try checkPrintedInstruction(super::Instr::Ebreak, "ebreak");
56 56
    try checkPrintedInstruction(super::Instr::MemoryFence, "memory-fence");
57 57
}
58 58
59 +
/// Control-flow printing preserves target labels and empty or populated arguments.
60 +
@test unsafe fn testControlFlowPrinting() throws (testing::TestError) {
61 +
    let value = super::Val::Reg(super::Reg { n: 3 });
62 +
    let other = super::Val::Imm(-7);
63 +
    let mut args = [value, other];
64 +
    try checkPrintedInstruction(super::Instr::Jmp { target: 0, args: &mut [] }, "jmp @entry");
65 +
    try checkPrintedInstruction(super::Instr::Jmp { target: 0, args: &mut args[..] }, "jmp @entry(%3, -7)");
66 +
    try checkPrintedInstruction(super::Instr::Br {
67 +
        op: super::CmpOp::Eq, typ: super::Type::W8, a: value, b: other,
68 +
        thenTarget: 0, thenArgs: &mut [], elseTarget: 0, elseArgs: &mut [],
69 +
    }, "br.eq w8 %3 -7 @entry @entry");
70 +
    try checkPrintedInstruction(super::Instr::Br {
71 +
        op: super::CmpOp::Ne, typ: super::Type::W16, a: value, b: other,
72 +
        thenTarget: 0, thenArgs: &mut args[..], elseTarget: 0, elseArgs: &mut [],
73 +
    }, "br.ne w16 %3 -7 @entry(%3, -7) @entry");
74 +
    try checkPrintedInstruction(super::Instr::Br {
75 +
        op: super::CmpOp::Slt, typ: super::Type::W32, a: value, b: other,
76 +
        thenTarget: 0, thenArgs: &mut [], elseTarget: 0, elseArgs: &mut args[..],
77 +
    }, "br.slt w32 %3 -7 @entry @entry(%3, -7)");
78 +
    try checkPrintedInstruction(super::Instr::Br {
79 +
        op: super::CmpOp::Ult, typ: super::Type::W64, a: value, b: other,
80 +
        thenTarget: 0, thenArgs: &mut args[..], elseTarget: 0, elseArgs: &mut args[..],
81 +
    }, "br.ult w64 %3 -7 @entry(%3, -7) @entry(%3, -7)");
82 +
    let mut cases = [
83 +
        super::SwitchCase { value: -9223372036854775808, target: 0, args: &mut [] },
84 +
        super::SwitchCase { value: 9223372036854775807, target: 0, args: &mut args[..] },
85 +
    ];
86 +
    try checkPrintedInstruction(super::Instr::Switch {
87 +
        val: value, defaultTarget: 0, defaultArgs: &mut [], cases: &mut [],
88 +
    }, "switch %3 @entry");
89 +
    try checkPrintedInstruction(super::Instr::Switch {
90 +
        val: value, defaultTarget: 0, defaultArgs: &mut args[..], cases: &mut cases[..],
91 +
    }, "switch %3 (-9223372036854775808 @entry) (9223372036854775807 @entry(%3, -7)) @entry(%3, -7)");
92 +
}
93 +
59 94
/// Buffered output truncates at capacity and preserves adjacent storage.
60 95
@test fn testSafePrintBuffer() throws (testing::TestError) {
61 96
    let mut storage: [u8; 7] = [42; 7];
62 97
    let bytes: 'output = &mut storage[1..6] in {
63 98
        let mut out = sexpr::Buffer 'output { buf: bytes, pos: 0 };