compiler: Check fixed instruction printing
084f3c7e33db6e56e4693399f5307a3819be15eaf2f0130bf3fb82a2c6d51cb6
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 { |