compiler: Check call instruction printing
b1a85855d70c0606ec50c5b0ab7edbe6dfad45199c8778109dbbf6b7e2e2abaf
1 parent
cc046d63
lib/std/lang/il/printer.rad
+19 -17
| 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 | 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 | - | } |
|
| 184 | + | case super::Instr::Call { args, .. } => writeOperandInstr(out, inst, args), |
|
| 196 | 185 | ||
| 197 | 186 | case super::Instr::Jmp { target, args } => { |
|
| 198 | 187 | write(out, "jmp "); |
|
| 199 | 188 | writeTarget(out, blocks, target, args); |
|
| 200 | 189 | } |
| 228 | 217 | write(out, ")"); |
|
| 229 | 218 | } |
|
| 230 | 219 | write(out, " "); |
|
| 231 | 220 | writeTarget(out, blocks, defaultTarget, defaultArgs); |
|
| 232 | 221 | } |
|
| 233 | - | else => writeFixedInstr(out, inst), |
|
| 222 | + | else => writeOperandInstr(out, inst, &[]), |
|
| 234 | 223 | } |
|
| 235 | 224 | } |
|
| 236 | 225 | ||
| 237 | 226 | /// Write a target label and its optional block arguments. |
|
| 238 | 227 | fn writeTarget(out: &mut opaque sexpr::Output, blocks: &[super::Block], target: u32, args: &[super::Val]) { |
| 241 | 230 | if args.len > 0 { |
|
| 242 | 231 | writeArgs(out, args); |
|
| 243 | 232 | } |
|
| 244 | 233 | } |
|
| 245 | 234 | ||
| 246 | - | /// Write an instruction whose operands need no graph traversal. |
|
| 247 | - | fn writeFixedInstr(out: &mut opaque sexpr::Output, inst: super::Instr) { |
|
| 235 | + | /// Write fixed operands and borrowed call arguments. |
|
| 236 | + | fn writeOperandInstr(out: &mut opaque sexpr::Output, inst: super::Instr, args: &[super::Val]) { |
|
| 248 | 237 | match inst { |
|
| 238 | + | // Call. |
|
| 239 | + | case super::Instr::Call { dst, retTy, func, .. } => { |
|
| 240 | + | write(out, "call "); |
|
| 241 | + | writeType(out, retTy); |
|
| 242 | + | write(out, " "); |
|
| 243 | + | if let d = dst { |
|
| 244 | + | writeReg(out, d); |
|
| 245 | + | write(out, " "); |
|
| 246 | + | } |
|
| 247 | + | writeVal(out, func); |
|
| 248 | + | writeArgs(out, args); |
|
| 249 | + | } |
|
| 250 | + | ||
| 249 | 251 | // Memory operations. |
|
| 250 | 252 | case super::Instr::Reserve { dst, size, alignment } => { |
|
| 251 | 253 | write(out, "reserve "); |
|
| 252 | 254 | writeReg(out, dst); |
|
| 253 | 255 | write(out, " "); |
| 351 | 353 | write(out, "ebreak"); |
|
| 352 | 354 | } |
|
| 353 | 355 | case super::Instr::MemoryFence => { |
|
| 354 | 356 | write(out, "memory-fence"); |
|
| 355 | 357 | } |
|
| 356 | - | case super::Instr::Call { .. }, super::Instr::Jmp { .. }, |
|
| 358 | + | case super::Instr::Jmp { .. }, |
|
| 357 | 359 | super::Instr::Br { .. }, super::Instr::Switch { .. } => |
|
| 358 | - | panic "writeFixedInstr: expected fixed operands", |
|
| 360 | + | panic "writeOperandInstr: expected value operands", |
|
| 359 | 361 | } |
|
| 360 | 362 | } |
|
| 361 | 363 | ||
| 362 | 364 | /// Write a typed binary operation: `op type %dst %a %b`. |
|
| 363 | 365 | fn writeTypedBinOp( |
lib/std/lang/il/tests.rad
+24 -1
| 1 | - | //! Tests for RIL source register iteration. |
|
| 1 | + | //! Tests for RIL formatting and source register iteration. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | use std::lang::sexpr; |
|
| 5 | 5 | use std::lang::il::printer; |
|
| 6 | 6 |
| 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 | + | /// Call printing preserves destinations, callee forms, and argument order. |
|
| 60 | + | @test unsafe fn testCallPrinting() throws (testing::TestError) { |
|
| 61 | + | let dst = super::Reg { n: 7 }; |
|
| 62 | + | let direct = super::Val::FnAddr("callee"); |
|
| 63 | + | let indirect = super::Val::Reg(super::Reg { n: 2 }); |
|
| 64 | + | let args = [super::Val::Imm(-1), super::Val::Reg(dst), super::Val::FnAddr("pkg::callback")]; |
|
| 65 | + | try checkPrintedInstruction(super::Instr::Call { |
|
| 66 | + | retTy: super::Type::W8, dst: nil, func: direct, args: &[], |
|
| 67 | + | }, "call w8 $callee()"); |
|
| 68 | + | try checkPrintedInstruction(super::Instr::Call { |
|
| 69 | + | retTy: super::Type::W16, dst, func: direct, args: &[], |
|
| 70 | + | }, "call w16 %7 $callee()"); |
|
| 71 | + | try checkPrintedInstruction(super::Instr::Call { |
|
| 72 | + | retTy: super::Type::W32, dst: nil, func: indirect, args: &args[..1], |
|
| 73 | + | }, "call w32 %2(-1)"); |
|
| 74 | + | try checkPrintedInstruction(super::Instr::Call { |
|
| 75 | + | retTy: super::Type::W64, dst, func: indirect, args: &args[..], |
|
| 76 | + | }, "call w64 %7 %2(-1, %7, $\"pkg::callback\")"); |
|
| 77 | + | try checkPrintedInstruction(super::Instr::Call { |
|
| 78 | + | retTy: super::Type::W64, dst, func: super::Val::FnAddr("pkg::callee"), args: &args[..], |
|
| 79 | + | }, "call w64 %7 $\"pkg::callee\"(-1, %7, $\"pkg::callback\")"); |
|
| 80 | + | } |
|
| 81 | + | ||
| 59 | 82 | /// Control-flow printing preserves target labels and empty or populated arguments. |
|
| 60 | 83 | @test unsafe fn testControlFlowPrinting() throws (testing::TestError) { |
|
| 61 | 84 | let value = super::Val::Reg(super::Reg { n: 3 }); |
|
| 62 | 85 | let other = super::Val::Imm(-7); |
|
| 63 | 86 | let mut args = [value, other]; |