compiler: Check fixed instruction printing

084f3c7e33db6e56e4693399f5307a3819be15eaf2f0130bf3fb82a2c6d51cb6
Alexis Sellier committed ago 1 parent 41188def
lib/std/lang/il/printer.rad +74 -64
178 178
// Instruction printing //
179 179
//////////////////////////
180 180
181 181
/// Write an instruction.
182 182
unsafe fn writeInstr(out: &mut opaque sexpr::Output, blocks: *unsafe [super::Block], inst: super::Instr) {
183 +
    match inst {
184 +
        // Call.
185 +
        case super::Instr::Call { dst, retTy, func, args } => {
186 +
            write(out, "call ");
187 +
            writeType(out, retTy);
188 +
            write(out, " ");
189 +
            if let d = dst {
190 +
                writeReg(out, d);
191 +
                write(out, " ");
192 +
            }
193 +
            writeVal(out, func);
194 +
            writeArgs(out, args);
195 +
        }
196 +
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 +
            }
203 +
        }
204 +
        case super::Instr::Br { op, typ, a: va, b: vb, thenTarget, thenArgs, elseTarget, elseArgs } => {
205 +
            write(out, "br.");
206 +
            match op {
207 +
                case super::CmpOp::Eq  => write(out, "eq"),
208 +
                case super::CmpOp::Ne  => write(out, "ne"),
209 +
                case super::CmpOp::Slt => write(out, "slt"),
210 +
                case super::CmpOp::Ult => write(out, "ult"),
211 +
            }
212 +
            write(out, " ");
213 +
            writeType(out, typ);
214 +
            write(out, " ");
215 +
            writeVal(out, va);
216 +
            write(out, " ");
217 +
            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 +
            }
228 +
        }
229 +
        case super::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
230 +
            write(out, "switch ");
231 +
            writeVal(out, val);
232 +
            for c in cases {
233 +
                write(out, " (");
234 +
                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 +
                }
240 +
                write(out, ")");
241 +
            }
242 +
            write(out, " @");
243 +
            write(out, blocks[defaultTarget].label);
244 +
            if defaultArgs.len > 0 {
245 +
                writeArgs(out, defaultArgs);
246 +
            }
247 +
        }
248 +
        else => writeFixedInstr(out, inst),
249 +
    }
250 +
}
251 +
252 +
/// Write an instruction whose operands need no graph traversal.
253 +
fn writeFixedInstr(out: &mut opaque sexpr::Output, inst: super::Instr) {
183 254
    match inst {
184 255
        // Memory operations.
185 256
        case super::Instr::Reserve { dst, size, alignment } => {
186 257
            write(out, "reserve ");
187 258
            writeReg(out, dst);
245 316
        case super::Instr::Zext { dst, typ, val } =>
246 317
            writeTypedUnaryOp(out, "zext", typ, dst, val),
247 318
        case super::Instr::Sext { dst, typ, val } =>
248 319
            writeTypedUnaryOp(out, "sext", typ, dst, val),
249 320
250 -
        // Call.
251 -
        case super::Instr::Call { dst, retTy, func, args } => {
252 -
            write(out, "call ");
253 -
            writeType(out, retTy);
254 -
            write(out, " ");
255 -
            if let d = dst {
256 -
                writeReg(out, d);
257 -
                write(out, " ");
258 -
            }
259 -
            writeVal(out, func);
260 -
            writeArgs(out, args);
261 -
        }
262 -
263 321
        // Terminators.
264 322
        case super::Instr::Ret { val } => {
265 323
            write(out, "ret");
266 324
            if let v = val {
267 325
                write(out, " ");
268 326
                writeVal(out, v);
269 327
            }
270 328
        }
271 -
        case super::Instr::Jmp { target, args } => {
272 -
            write(out, "jmp @");
273 -
            write(out, blocks[target].label);
274 -
            if args.len > 0 {
275 -
                writeArgs(out, args);
276 -
            }
277 -
        }
278 -
        case super::Instr::Br { op, typ, a: va, b: vb, thenTarget, thenArgs, elseTarget, elseArgs } => {
279 -
            write(out, "br.");
280 -
            match op {
281 -
                case super::CmpOp::Eq  => write(out, "eq"),
282 -
                case super::CmpOp::Ne  => write(out, "ne"),
283 -
                case super::CmpOp::Slt => write(out, "slt"),
284 -
                case super::CmpOp::Ult => write(out, "ult"),
285 -
            }
286 -
            write(out, " ");
287 -
            writeType(out, typ);
288 -
            write(out, " ");
289 -
            writeVal(out, va);
290 -
            write(out, " ");
291 -
            writeVal(out, vb);
292 -
            write(out, " @");
293 -
            write(out, blocks[thenTarget].label);
294 -
            if thenArgs.len > 0 {
295 -
                writeArgs(out, thenArgs);
296 -
            }
297 -
            write(out, " @");
298 -
            write(out, blocks[elseTarget].label);
299 -
            if elseArgs.len > 0 {
300 -
                writeArgs(out, elseArgs);
301 -
            }
302 -
        }
303 -
        case super::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
304 -
            write(out, "switch ");
305 -
            writeVal(out, val);
306 -
            for c in cases {
307 -
                write(out, " (");
308 -
                writeI64(out, c.value);
309 -
                write(out, " @");
310 -
                write(out, blocks[c.target].label);
311 -
                if c.args.len > 0 {
312 -
                    writeArgs(out, c.args);
313 -
                }
314 -
                write(out, ")");
315 -
            }
316 -
            write(out, " @");
317 -
            write(out, blocks[defaultTarget].label);
318 -
            if defaultArgs.len > 0 {
319 -
                writeArgs(out, defaultArgs);
320 -
            }
321 -
        }
322 329
        case super::Instr::Unreachable => {
323 330
            write(out, "unreachable");
324 331
        }
325 332
326 333
        // Intrinsics.
350 357
            write(out, "ebreak");
351 358
        }
352 359
        case super::Instr::MemoryFence => {
353 360
            write(out, "memory-fence");
354 361
        }
362 +
        case super::Instr::Call { .. }, super::Instr::Jmp { .. },
363 +
             super::Instr::Br { .. }, super::Instr::Switch { .. } =>
364 +
            panic "writeFixedInstr: expected fixed operands",
355 365
    }
356 366
}
357 367
358 368
/// Write a typed binary operation: `op type %dst %a %b`.
359 369
fn writeTypedBinOp(
lib/std/lang/il/tests.rad +53 -0
1 1
//! Tests for RIL source register iteration.
2 2
3 3
use std::testing;
4 4
use std::lang::sexpr;
5 +
use std::lang::il::printer;
6 +
7 +
/// Check one instruction between the function header and closing delimiter.
8 +
unsafe fn checkPrintedInstruction(instr: super::Instr, expected: &[u8]) throws (testing::TestError) {
9 +
    let mut instructions = [instr];
10 +
    let blocks = [super::Block {
11 +
        label: "entry", params: &[], instrs: &mut instructions[..],
12 +
        locs: &[], preds: &[], loopDepth: 0,
13 +
    }];
14 +
    let function = super::Fn {
15 +
        name: "sample", params: &[], returnType: super::Type::W64,
16 +
        isExtern: false, isLeaf: true, blocks: &blocks[..],
17 +
    };
18 +
    let functions: [*unsafe super::Fn; 1] = [&function];
19 +
    let program = super::Program { data: &[], fns: &functions[..] };
20 +
    let mut storage: [u8; 512] = [0; 512];
21 +
    let bytes: 'output = &mut storage[..] in {
22 +
        let mut out = sexpr::Buffer 'output { buf: bytes, pos: 0 };
23 +
        printer::printProgram(&mut out, &program);
24 +
        let prefix = "fn w64 $sample() {\n  @entry\n    ";
25 +
        let suffix = ";\n}\n";
26 +
        try testing::expect(out.pos == prefix.len + expected.len + suffix.len);
27 +
        try testing::expectBytesEq(&out.buf[..prefix.len], prefix);
28 +
        try testing::expectBytesEq(&out.buf[prefix.len..prefix.len + expected.len], expected);
29 +
        try testing::expectBytesEq(&out.buf[prefix.len + expected.len..out.pos], suffix);
30 +
    }
31 +
}
32 +
33 +
/// Fixed instruction formatting preserves operands, widths, and optional returns.
34 +
@test unsafe fn testFixedInstructionPrinting() throws (testing::TestError) {
35 +
    let dst = super::Reg { n: 4294967295 };
36 +
    let reg = super::Reg { n: 2 };
37 +
    let value = super::Val::Imm(-9223372036854775808);
38 +
    let source = super::Val::Reg(reg);
39 +
    try checkPrintedInstruction(super::Instr::Reserve { dst, size: value, alignment: 8 }, "reserve %4294967295 -9223372036854775808 8");
40 +
    try checkPrintedInstruction(super::Instr::Load { typ: super::Type::W8, dst, src: reg, offset: -2147483648 }, "load w8 %4294967295 %2 -2147483648");
41 +
    try checkPrintedInstruction(super::Instr::Sload { typ: super::Type::W16, dst, src: reg, offset: 2147483647 }, "sload w16 %4294967295 %2 2147483647");
42 +
    try checkPrintedInstruction(super::Instr::Store { typ: super::Type::W32, src: source, dst: reg, offset: 0 }, "store w32 %2 %2 0");
43 +
    try checkPrintedInstruction(super::Instr::Blit { dst, src: reg, size: value }, "blit %4294967295 %2 -9223372036854775808");
44 +
    try checkPrintedInstruction(super::Instr::Copy { dst, val: super::Val::Undef }, "copy %4294967295 undefined");
45 +
    try checkPrintedInstruction(super::Instr::BinOp { op: super::BinOp::Add, typ: super::Type::W64, dst, a: source, b: value }, "add w64 %4294967295 %2 -9223372036854775808");
46 +
    try checkPrintedInstruction(super::Instr::UnOp { op: super::UnOp::Not, typ: super::Type::W64, dst, a: source }, "not w64 %4294967295 %2");
47 +
    try checkPrintedInstruction(super::Instr::Zext { typ: super::Type::W8, dst, val: source }, "zext w8 %4294967295 %2");
48 +
    try checkPrintedInstruction(super::Instr::Sext { typ: super::Type::W32, dst, val: value }, "sext w32 %4294967295 -9223372036854775808");
49 +
    try checkPrintedInstruction(super::Instr::Ret { val: nil }, "ret");
50 +
    try checkPrintedInstruction(super::Instr::Ret { val: value }, "ret -9223372036854775808");
51 +
    try checkPrintedInstruction(super::Instr::Unreachable, "unreachable");
52 +
    try checkPrintedInstruction(super::Instr::Ecall { dst, num: value, a0: source, a1: value, a2: source, a3: value }, "ecall %4294967295 -9223372036854775808 %2 -9223372036854775808 %2 -9223372036854775808");
53 +
    try checkPrintedInstruction(super::Instr::DeviceRead { typ: super::Type::W8, dst, handle: source, offset: value }, "device-read w8 %4294967295 %2 -9223372036854775808");
54 +
    try checkPrintedInstruction(super::Instr::DeviceWrite { typ: super::Type::W64, handle: source, offset: value, value: source }, "device-write w64 %2 -9223372036854775808 %2");
55 +
    try checkPrintedInstruction(super::Instr::Ebreak, "ebreak");
56 +
    try checkPrintedInstruction(super::Instr::MemoryFence, "memory-fence");
57 +
}
5 58
6 59
/// Buffered output truncates at capacity and preserves adjacent storage.
7 60
@test fn testSafePrintBuffer() throws (testing::TestError) {
8 61
    let mut storage: [u8; 7] = [42; 7];
9 62
    let bytes: 'output = &mut storage[1..6] in {