1 /**
2 Copyright: Copyright (c) 2017-2019 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 /// Convertion of function IR to textual representation
8 module vox.ir.ir_dump;
9 
10 import std.stdio;
11 import std.format : formattedWrite;
12 
13 import vox.all;
14 
15 struct IrDumpContext
16 {
17 	CompilationContext* context;
18 	IrFunction* ir;
19 	FuncDumpSettings* settings; // nullable, uses default if null
20 	TextSink* sink; // nullable, writes to stdout if null
21 	LivenessInfo* liveness; // nullable, doesn't print liveness if null
22 	string passName; // string to printed after function signature. Optional
23 }
24 
25 struct InstrPrintInfo
26 {
27 	CompilationContext* context;
28 	TextSink* sink;
29 	IrFunction* ir;
30 	IrIndex blockIndex;
31 	IrBasicBlock* block;
32 	IrIndex instrIndex;
33 	IrInstrHeader* instrHeader;
34 	FuncDumpSettings* settings;
35 	IrDumpHandlers* handlers;
36 	void dumpInstr() { handlers.instrDumper(this); }
37 }
38 
39 struct IrDumpHandlers
40 {
41 	InstructionDumper instrDumper;
42 	IrIndexDumper indexDumper;
43 }
44 
45 struct IrIndexDump
46 {
47 	this(IrIndex index, ref InstrPrintInfo printInfo) {
48 		this.index = index;
49 		this.context = printInfo.context;
50 		this.instrSet = printInfo.ir.instructionSet;
51 	}
52 	this(IrIndex index, CompilationContext* c, IrInstructionSet instrSet) {
53 		this.index = index;
54 		this.context = c;
55 		this.instrSet = instrSet;
56 	}
57 	this(IrIndex index, CompilationContext* context, IrFunction* ir) {
58 		this.index = index;
59 		this.context = context;
60 		this.instrSet = ir.instructionSet;
61 	}
62 	this(IrIndex index, IrBuilder* builder) {
63 		this.index = index;
64 		this.context = builder.context;
65 		this.instrSet = builder.ir.instructionSet;
66 	}
67 	IrIndex index;
68 	IrInstructionSet instrSet;
69 	CompilationContext* context;
70 
71 	void toString(scope void delegate(const(char)[]) sink) {
72 		instrSetDumpHandlers[instrSet].indexDumper(sink, *context, index);
73 	}
74 }
75 
76 alias InstructionDumper = void function(ref InstrPrintInfo p);
77 alias IrIndexDumper = void function(scope void delegate(const(char)[]) sink, ref CompilationContext, IrIndex);
78 
79 struct FuncDumpSettings
80 {
81 	bool printVars = false;
82 	bool printBlockFlags = false;
83 	bool printBlockIns = true;
84 	bool printBlockOuts = false;
85 	bool printBlockRefs = false;
86 	bool printInstrIndexEnabled = true;
87 	bool printLivenessLinearIndex = false;
88 	bool printVregLiveness = false;
89 	bool printPregLiveness = false;
90 	bool printUses = true;
91 	bool printLiveness = false;
92 	bool printPassName = true;
93 	bool escapeForDot = false;
94 }
95 
96 IrDumpHandlers[] instrSetDumpHandlers = [
97 	IrDumpHandlers(&dumpIrInstr, &dumpIrIndex),
98 	IrDumpHandlers(&dumpAmd64Instr, &dumpLirAmd64Index),
99 ];
100 
101 void dumpTypes(ref TextSink sink, ref CompilationContext ctx)
102 {
103 	IrIndex type = ctx.types.firstType;
104 	while (type.isDefined)
105 	{
106 		sink.putfln("%s", IrTypeDump(type, ctx));
107 		type = ctx.types.get!IrTypeHeader(type).nextType;
108 	}
109 }
110 
111 void dumpFunction(CompilationContext* context, IrFunction* ir, string passName = null)
112 {
113 	IrDumpContext dumpCtx = { context : context, ir : ir, passName : passName };
114 	dumpFunction(&dumpCtx);
115 }
116 
117 void dumpFunction(IrDumpContext* c)
118 {
119 	assert(c.context, "context is null");
120 	assert(c.ir, "ir is null");
121 
122 	bool defaultSink = false;
123 	TextSink sink;
124 	FuncDumpSettings settings;
125 
126 	if (c.sink is null) {
127 		defaultSink = true;
128 		c.sink = &sink;
129 	}
130 
131 	if (c.settings is null) {
132 		c.settings = &settings;
133 	}
134 
135 	dumpFunctionImpl(c);
136 
137 	if (defaultSink) writeln(sink.text);
138 }
139 
140 void dumpFunctionImpl(IrDumpContext* c)
141 {
142 	IrFunction* ir = c.ir;
143 	TextSink* sink = c.sink;
144 	CompilationContext* ctx = c.context;
145 	FuncDumpSettings* settings = c.settings;
146 	LivenessInfo* liveness = c.liveness;
147 
148 	InstrPrintInfo printer;
149 	printer.context = ctx;
150 	printer.sink = sink;
151 	printer.ir = ir;
152 	printer.handlers = &instrSetDumpHandlers[ir.instructionSet];
153 	printer.settings = settings;
154 
155 	sink.put("func ");
156 	// results
157 	auto funcType = &ctx.types.get!IrTypeFunction(ir.type);
158 	foreach(i, result; funcType.resultTypes) {
159 		if (i > 0) sink.put(", ");
160 		sink.putf("%s", IrIndexDump(result, printer));
161 	}
162 	if (funcType.numResults > 0) sink.put(" ");
163 	sink.put(ctx.idString(ir.name));
164 
165 	// parameters
166 	sink.put("(");
167 	foreach(i, param; funcType.parameterTypes) {
168 		if (i > 0) sink.put(", ");
169 		sink.putf("%s", IrIndexDump(param, printer));
170 	}
171 	sink.put(")");
172 	sink.putf(` %s bytes ir:"%s"`, ir.byteLength, instr_set_names[ir.instructionSet]);
173 	if (settings.printPassName && c.passName.length) {
174 		sink.put(` pass:"`);
175 		sink.put(c.passName);
176 		sink.put(`"`);
177 	}
178 	sink.putln(" {");
179 
180 	int indexPadding = max(ir.numBasicBlocks, ir.numInstructions).numDigitsInNumber10;
181 	int liveIndexPadding = 0;
182 	if (liveness) liveIndexPadding = liveness.maxLinearIndex.numDigitsInNumber10;
183 
184 	void printInstrLiveness(IrIndex linearKeyIndex, IrIndex instrIndex) {
185 		if (!settings.printLiveness) return;
186 		if (liveness is null) return;
187 
188 		uint linearInstrIndex = liveness.linearIndices[linearKeyIndex];
189 
190 		if (settings.printPregLiveness)
191 		{
192 			foreach(ref interval; liveness.physicalIntervals)
193 			{
194 				if (interval.coversPosition(linearInstrIndex))
195 					sink.put("|");
196 				else
197 					sink.put(" ");
198 			}
199 			if (settings.printVregLiveness) sink.put("#");
200 		}
201 
202 		size_t[] blockLiveIn;
203 		if (instrIndex.isBasicBlock)
204 		{
205 			blockLiveIn = liveness.bitmap.blockLiveInBuckets(instrIndex);
206 		}
207 
208 		if (settings.printVregLiveness)
209 		foreach(ref LiveInterval interval; liveness.virtualIntervals)
210 		{
211 			auto vreg = ir.getVirtReg(interval.definition);
212 
213 			if (interval.hasUseAt(linearInstrIndex))
214 			{
215 				if (vreg.definition == instrIndex) // we are printing at definition site
216 					sink.put("D"); // virtual register is defined by this instruction / phi
217 				else
218 				{
219 					// some use
220 					if (instrIndex.isPhi)
221 					{
222 						// some phi
223 						if (vreg.users.contains(ir, instrIndex))
224 							sink.put("U"); // phi uses current vreg
225 						else
226 							sink.put("|"); // phi doesn't use current phi
227 					}
228 					else if (instrIndex.isBasicBlock)
229 					{
230 						// phi uses are located on the next basic block linear position
231 						// this is a use in phi function
232 						IrIndex prevBlock = ir.getBlock(instrIndex).prevBlock;
233 						enum UseState : ubyte {
234 							none = 0b00,
235 							above = 0b01,
236 							below = 0b10,
237 							above_and_below = 0b11
238 						}
239 						UseState useState;
240 						foreach (index, uint numUses; vreg.users.range(ir))
241 						{
242 							if (index.isPhi)
243 							{
244 								IrPhi* phi = ir.getPhi(index);
245 								IrIndex[] preds = ir.getBlock(phi.blockIndex).predecessors.data(ir);
246 								foreach(size_t arg_i, IrIndex phiArg; phi.args(ir))
247 								{
248 									// we only want phi functions that are in blocks that have this block as predecessor
249 									if (preds[arg_i] == prevBlock && phiArg == interval.definition)
250 									{
251 										uint phiPos = liveness.linearIndices[phi.blockIndex];
252 										if (phiPos < linearInstrIndex)
253 											useState |= UseState.above; // vreg is used by phi above
254 										else
255 											useState |= UseState.below; // vreg is used by phi below
256 									}
257 								}
258 							}
259 						}
260 						final switch(useState) {
261 							case UseState.none: sink.put(" "); break;
262 							case UseState.above: sink.put("^"); break;
263 							case UseState.below: sink.put("v"); break;
264 							case UseState.above_and_below: sink.put("x"); break;
265 						}
266 					}
267 					else
268 						sink.put("U");
269 				}
270 			}
271 			else if (interval.coversPosition(linearInstrIndex))
272 			{
273 				if (vreg.definition == instrIndex)
274 					sink.put("D"); // virtual register is defined by this instruction / phi
275 				else
276 				{
277 					if (instrIndex.isBasicBlock)
278 					{
279 						if (blockLiveIn.getBitAt(interval.definition.storageUintIndex))
280 							sink.put("+"); // virtual register is in "live in" of basic block
281 						else
282 							sink.put(" "); // phi result
283 					}
284 					else
285 						sink.put("|"); // virtual register is live in this position
286 				}
287 			}
288 			else
289 			{
290 				if (instrIndex.isPhi)
291 				{
292 					if (vreg.users.contains(ir, instrIndex))
293 						sink.put("U");
294 					else
295 						sink.put(" ");
296 				}
297 				else
298 					sink.put(" ");
299 			}
300 		}
301 
302 		if (settings.printLivenessLinearIndex)
303 		{
304 			sink.putf(" %*s| ", liveIndexPadding, linearInstrIndex);
305 		}
306 	}
307 
308 	void printInstrIndex(IrIndex someIndex) {
309 		import std.range : repeat;
310 		if (!settings.printInstrIndexEnabled) return;
311 		if (someIndex.isInstruction)
312 			sink.putf("%*s|", indexPadding, someIndex.storageUintIndex);
313 		else
314 			sink.putf("%s|", ' '.repeat(indexPadding));
315 	}
316 
317 	void printRegUses(IrIndex result) {
318 		if (!result.isVirtReg) return;
319 		auto vreg = ir.getVirtReg(result);
320 		sink.put(" users [");
321 		uint i = 0;
322 		foreach (IrIndex user, uint numUses; vreg.users.range(ir))
323 		{
324 			if (i > 0) sink.put(", ");
325 			sink.putf("%s", IrIndexDump(user, printer));
326 			if (numUses > 1) sink.putf(":%s", numUses);
327 			++i;
328 		}
329 		sink.put("]");
330 	}
331 
332 	IrIndex blockIndex = ir.entryBasicBlock;
333 	IrBasicBlock* block;
334 	while (blockIndex.isDefined)
335 	{
336 		if (!blockIndex.isBasicBlock)
337 		{
338 			sink.putfln("  invalid block %s", IrIndexDump(blockIndex, printer));
339 			break;
340 		}
341 
342 		block = ir.getBlock(blockIndex);
343 		scope(exit) blockIndex = block.nextBlock;
344 
345 		printer.blockIndex = blockIndex;
346 		printer.block = block;
347 
348 		printInstrLiveness(blockIndex, blockIndex);
349 		printInstrIndex(blockIndex);
350 		sink.putf("  %s", IrIndexDump(blockIndex, printer));
351 
352 		if (settings.printBlockFlags)
353 		{
354 			if (block.isSealed) sink.put(" S");
355 			else sink.put(" .");
356 
357 			if (block.isFinished) sink.put("F");
358 			else sink.put(".");
359 
360 			if (block.isLoopHeader) sink.put("L");
361 			else sink.put(".");
362 		}
363 
364 		if (settings.printBlockIns && block.predecessors.length > 0)
365 		{
366 			sink.putf(" in(");
367 			foreach(i, predIndex; block.predecessors.range(ir)) {
368 				if (i > 0) sink.put(", ");
369 				sink.putf("%s", IrIndexDump(predIndex, printer));
370 			}
371 			sink.put(")");
372 		}
373 		if (settings.printBlockOuts && block.successors.length > 0)
374 		{
375 			sink.putf(" out(");
376 			foreach(i, succIndex; block.successors.range(ir)) {
377 				if (i > 0) sink.put(", ");
378 				sink.putf("%s", IrIndexDump(succIndex, printer));
379 			}
380 			sink.put(")");
381 		}
382 		sink.putln;
383 
384 		// phis
385 		IrIndex phiIndex = block.firstPhi;
386 		IrPhi* phi;
387 		while (phiIndex.isDefined)
388 		{
389 			if (!phiIndex.isPhi)
390 			{
391 				sink.putfln("  invalid phi %s", IrIndexDump(phiIndex, printer));
392 				break;
393 			}
394 
395 			phi = ir.getPhi(phiIndex);
396 			scope(exit) phiIndex = phi.nextPhi;
397 
398 			printInstrLiveness(blockIndex, phiIndex);
399 			printInstrIndex(phiIndex);
400 			sink.putf("    %s %s = %s(",
401 				IrIndexDump(phi.result, printer),
402 				IrIndexDump(ir.getVirtReg(phi.result).type, printer),
403 				IrIndexDump(phiIndex, printer));
404 			IrIndex[] phiPreds = ir.getBlock(phi.blockIndex).predecessors.data(ir);
405 			foreach(size_t arg_i, ref IrIndex phiArg; phi.args(ir))
406 			{
407 				if (arg_i > 0) sink.put(", ");
408 				sink.putf("%s", IrIndexDump(phiPreds[arg_i], printer));
409 				dumpArg(phiArg, printer);
410 			}
411 			sink.put(")");
412 			if (settings.printUses) printRegUses(phi.result);
413 			sink.putln;
414 		}
415 
416 		// instrs
417 		foreach(IrIndex instrIndex, ref IrInstrHeader instrHeader; block.instructions(ir))
418 		{
419 			printInstrLiveness(instrIndex, instrIndex);
420 			printInstrIndex(instrIndex);
421 
422 			// print instr
423 			printer.instrIndex = instrIndex;
424 			printer.instrHeader = &instrHeader;
425 
426 			printer.dumpInstr();
427 
428 			if (settings.printUses && instrHeader.hasResult) printRegUses(instrHeader.result(ir));
429 			sink.putln;
430 		}
431 	}
432 
433 	sink.putln("}");
434 }
435 
436 void dumpFunctionCFG(IrFunction* ir, ref TextSink sink, CompilationContext* ctx, ref FuncDumpSettings settings)
437 {
438 	settings.escapeForDot = true;
439 	sink.put(`digraph "`);
440 	sink.put("function ");
441 	sink.put(ctx.idString(ir.name));
442 	sink.putfln(`() %s bytes" {`, ir.byteLength * uint.sizeof);
443 	int indexPadding = ir.numInstructions.numDigitsInNumber10;
444 
445 	InstrPrintInfo p;
446 	p.context = ctx;
447 	p.sink = &sink;
448 	p.ir = ir;
449 	p.settings = &settings;
450 
451 	foreach (IrIndex blockIndex, ref IrBasicBlock block; ir.blocks)
452 	{
453 		foreach(i, succIndex; block.successors.range(ir)) {
454 			sink.putfln("node_%s -> node_%s;",
455 				blockIndex.storageUintIndex, succIndex.storageUintIndex);
456 		}
457 
458 		sink.putf(`node_%s [shape=record,label="{`, blockIndex.storageUintIndex);
459 
460 		p.blockIndex = blockIndex;
461 		p.block = &block;
462 
463 		sink.putf(`  %s`, IrIndexDump(blockIndex, p));
464 		sink.put(`\l`);
465 
466 		// phis
467 		foreach(IrIndex phiIndex, ref IrPhi phi; block.phis(ir))
468 		{
469 			sink.putf("    %s %s = %s(",
470 				IrIndexDump(phi.result, p),
471 				IrIndexDump(ir.getVirtReg(phi.result).type, p),
472 				IrIndexDump(phiIndex, p));
473 			IrIndex[] phiPreds = ir.getBlock(phi.blockIndex).predecessors.data(ir);
474 			foreach(size_t arg_i, ref IrIndex phiArg; phi.args(ir))
475 			{
476 				if (arg_i > 0) sink.put(", ");
477 				sink.putf("%s %s", IrIndexDump(phiArg, p), IrIndexDump(phiPreds[arg_i], p));
478 			}
479 			sink.put(")");
480 			sink.put(`\l`);
481 		}
482 
483 		// instrs
484 		foreach(IrIndex instrIndex, ref IrInstrHeader instrHeader; block.instructions(ir))
485 		{
486 			// print instr
487 			p.instrIndex = instrIndex;
488 			p.instrHeader = &instrHeader;
489 			p.dumpInstr();
490 			sink.put(`\l`);
491 		}
492 		sink.putfln(`}"];`);
493 	}
494 
495 	sink.putln("}");
496 }
497 
498 void dumpIrIndex(scope void delegate(const(char)[]) sink, ref CompilationContext context, IrIndex index)
499 {
500 	if (!index.isDefined) {
501 		sink("<null>");
502 		return;
503 	}
504 
505 	final switch(index.kind) with(IrValueKind) {
506 		case none: sink.formattedWrite("0x%X", index.asUint); break;
507 		case array: sink.formattedWrite("arr%s", index.storageUintIndex); break;
508 		case instruction: sink.formattedWrite("i.%s", index.storageUintIndex); break;
509 		case basicBlock: sink.formattedWrite("@%s", index.storageUintIndex); break;
510 		case constant:
511 			auto con = context.constants.get(index);
512 			if (con.type.isTypeBasic) {
513 				switch (con.type.basicType(&context)) {
514 					case IrBasicType.i8:  sink.formattedWrite("%s",  con.i8);  break;
515 					case IrBasicType.i16: sink.formattedWrite("%s", con.i16); break;
516 					case IrBasicType.i32: sink.formattedWrite("%s", con.i32); break;
517 					case IrBasicType.i64: sink.formattedWrite("%s", con.i64); break;
518 					case IrBasicType.f32: sink.formattedWrite("%s", con.f32); break;
519 					case IrBasicType.f64: sink.formattedWrite("%s", con.f64); break;
520 					default: sink.formattedWrite("%s", con.i64); break;
521 				}
522 				break;
523 			}
524 			sink.formattedWrite("%s", con.i64);
525 			break;
526 		case constantAggregate:
527 			sink("{");
528 			foreach(i, m; context.constants.getAggregate(index).members) {
529 				if (i > 0) sink(", ");
530 				dumpIrIndex(sink, context, m);
531 			}
532 			sink("}");
533 			break;
534 		case constantZero:
535 			if (index.typeKind == IrTypeKind.basic)
536 				sink("0");
537 			else
538 				sink("zeroinit");
539 			break;
540 		case global: sink.formattedWrite("g%s", index.storageUintIndex); break;
541 		case phi: sink.formattedWrite("phi%s", index.storageUintIndex); break;
542 		case stackSlot: sink.formattedWrite("s%s", index.storageUintIndex); break;
543 		case virtualRegister: sink.formattedWrite("v%s", index.storageUintIndex); break;
544 		case physicalRegister: sink.formattedWrite("r%s<c%s s%s>", index.physRegIndex, index.physRegClass, index.physRegSize); break;
545 		case type: dumpIrType(sink, context, index); break;
546 		case variable: assert(false);
547 		case func: sink.formattedWrite("f%s", index.storageUintIndex); break;
548 	}
549 }
550 
551 struct IrTypeDump
552 {
553 	this(IrIndex index, ref CompilationContext ctx) {
554 		this.index = index;
555 		this.ctx = &ctx;
556 	}
557 	IrIndex index;
558 	CompilationContext* ctx;
559 
560 	void toString(scope void delegate(const(char)[]) sink) {
561 		dumpIrType(sink, *ctx, index);
562 	}
563 }
564 
565 void dumpIrType(scope void delegate(const(char)[]) sink, ref CompilationContext ctx, IrIndex type, bool recurse = true)
566 {
567 	if (type.isUndefined) {
568 		sink("<null>");
569 		return;
570 	}
571 	final switch(type.typeKind) with(IrTypeKind) {
572 		case basic:
573 			final switch(cast(IrBasicType)type.typeIndex) with(IrBasicType) {
574 				case noreturn_t: sink("noreturn"); break;
575 				case void_t: sink("void"); break;
576 				case i8: sink("i8"); break;
577 				case i16: sink("i16"); break;
578 				case i32: sink("i32"); break;
579 				case i64: sink("i64"); break;
580 				case f32: sink("f32"); break;
581 				case f64: sink("f64"); break;
582 			}
583 			break;
584 		case pointer:
585 			dumpIrType(sink, ctx, ctx.types.get!IrTypePointer(type).baseType, false);
586 			sink("*");
587 			break;
588 		case array:
589 			auto array = ctx.types.get!IrTypeArray(type);
590 			sink.formattedWrite("[%s x ", array.numElements);
591 			dumpIrType(sink, ctx, array.elemType);
592 			sink("]");
593 			break;
594 		case struct_t:
595 			if (!recurse) {
596 				sink("{...}");
597 				break;
598 			}
599 			IrTypeStruct* struct_t = &ctx.types.get!IrTypeStruct(type);
600 			sink("{");
601 			foreach(i, IrTypeStructMember member; struct_t.members)
602 			{
603 				if (i > 0) {
604 					if (struct_t.isUnion) sink(" | ");
605 					else sink(", ");
606 				}
607 				dumpIrType(sink, ctx, member.type, false);
608 			}
609 			sink("}");
610 			break;
611 		case func_t:
612 			// results
613 			auto funcType = &ctx.types.get!IrTypeFunction(type);
614 			foreach(i, result; funcType.resultTypes) {
615 				if (i > 0) sink(", ");
616 				dumpIrType(sink, ctx, result);
617 			}
618 
619 			// parameters
620 			sink("(");
621 			foreach(i, param; funcType.parameterTypes) {
622 				if (i > 0) sink(", ");
623 				dumpIrType(sink, ctx, param);
624 			}
625 			sink(")");
626 			break;
627 	}
628 }
629 
630 
631 void dumpIrInstr(ref InstrPrintInfo p)
632 {
633 	switch(p.instrHeader.op)
634 	{
635 		case IrOpcode.branch_unary: dumpUnBranch(p); break;
636 		case IrOpcode.branch_binary: dumpBinBranch(p); break;
637 		case IrOpcode.jump: dumpJmp(p); break;
638 		case IrOpcode.branch_switch: dumpSwitch(p); break;
639 
640 		case IrOpcode.parameter:
641 			uint paramIndex = p.ir.get!IrInstr_parameter(p.instrIndex).index(p.ir);
642 			dumpOptionalResult(p);
643 			p.sink.putf("parameter%s", paramIndex);
644 			break;
645 
646 		case IrOpcode.ret:
647 			p.sink.put("    return");
648 			break;
649 
650 		case IrOpcode.ret_val:
651 			p.sink.put("    return");
652 			dumpArg(p.instrHeader.arg(p.ir, 0), p);
653 			break;
654 
655 		default:
656 			dumpOptionalResult(p);
657 			p.sink.putf("%s", cast(IrOpcode)p.instrHeader.op);
658 			dumpArgs(p);
659 			break;
660 	}
661 }
662 
663 void dumpOptionalResult(ref InstrPrintInfo p)
664 {
665 	if (p.instrHeader.hasResult)
666 	{
667 		if (p.instrHeader.result(p.ir).isVirtReg)
668 		{
669 			p.sink.putf("    %s %s = ",
670 				IrIndexDump(p.instrHeader.result(p.ir), p),
671 				IrIndexDump(p.ir.getVirtReg(p.instrHeader.result(p.ir)).type, p));
672 		}
673 		else
674 		{
675 			p.sink.putf("    %s = ", IrIndexDump(p.instrHeader.result(p.ir), p));
676 		}
677 	}
678 	else
679 	{
680 		p.sink.put("    ");
681 	}
682 }
683 
684 void dumpArgs(ref InstrPrintInfo p)
685 {
686 	foreach (i, IrIndex arg; p.instrHeader.args(p.ir))
687 	{
688 		if (i > 0) p.sink.put(",");
689 		dumpArg(arg, p);
690 	}
691 }
692 
693 void dumpArg(IrIndex arg, ref InstrPrintInfo p)
694 {
695 	if (arg.isPhysReg)
696 	{
697 		p.sink.putf(" %s", IrIndexDump(arg, p));
698 	}
699 	else if (arg.isFunction)
700 	{
701 		FunctionDeclNode* func = p.context.getFunction(arg);
702 		p.sink.putf(" %s", p.context.idString(func.id));
703 	}
704 	else
705 	{
706 		if (arg.isDefined)
707 			p.sink.putf(" %s %s",
708 				IrIndexDump(p.ir.getValueType(p.context, arg), p),
709 				IrIndexDump(arg, p));
710 		else p.sink.put(" <null>");
711 	}
712 }
713 
714 void dumpJmp(ref InstrPrintInfo p)
715 {
716 	p.sink.put("    jmp ");
717 	if (p.block.successors.length > 0)
718 		p.sink.putf("%s", IrIndexDump(p.block.successors[0, p.ir], p));
719 	else
720 		p.sink.put(p.settings.escapeForDot ? `\<null\>` : "<null>");
721 }
722 
723 void dumpSwitch(ref InstrPrintInfo p)
724 {
725 	p.sink.put("    switch ");
726 	IrIndex[] succ = p.block.successors.data(p.ir);
727 	IrIndex[] args = p.instrHeader.args(p.ir);
728 
729 	if (args.length > 0) p.sink.putf("%s, ", IrIndexDump(args[0], p));
730 	else p.sink.put(p.settings.escapeForDot ? `\<null\>, ` : "<null>, ");
731 	if (succ.length > 0) p.sink.putf("%s", IrIndexDump(succ[0], p));
732 	else p.sink.put(p.settings.escapeForDot ? `\<null\>` : "<null>");
733 
734 	foreach(i; 1..max(succ.length, args.length))
735 	{
736 		p.sink.put(", ");
737 		if (succ.length > i) p.sink.putf("%s ", IrIndexDump(succ[i], p));
738 		else p.sink.put(p.settings.escapeForDot ? `\<null\> ` : "<null> ");
739 		if (args.length > i) p.sink.putf("%s", IrIndexDump(args[i], p));
740 		else p.sink.put(p.settings.escapeForDot ? `\<null\>` : "<null>");
741 	}
742 }
743 
744 void dumpUnBranch(ref InstrPrintInfo p)
745 {
746 	p.sink.putf("    if%s", unaryCondStrings[p.instrHeader.cond]);
747 	dumpArg(p.instrHeader.arg(p.ir, 0), p);
748 	p.sink.put(" then ");
749 	dumpBranchTargets(p);
750 }
751 
752 void dumpBinBranch(ref InstrPrintInfo p)
753 {
754 	string[] opStrings = p.settings.escapeForDot ? binaryCondStringsEscapedForDot : binaryCondStrings;
755 	p.sink.put("    if");
756 	dumpArg(p.instrHeader.arg(p.ir, 0), p);
757 	p.sink.putf(" %s", opStrings[p.instrHeader.cond]);
758 	dumpArg(p.instrHeader.arg(p.ir, 1), p);
759 	p.sink.put(" then ");
760 
761 	dumpBranchTargets(p);
762 }
763 
764 void dumpBranchTargets(ref InstrPrintInfo p)
765 {
766 	switch (p.block.successors.length) {
767 		case 0:
768 			p.sink.put(p.settings.escapeForDot ? `\<null\> else \<null\>` : "<null> else <null>");
769 			break;
770 		case 1:
771 			p.sink.putf(p.settings.escapeForDot ? `%s else \<null\>` : "%s else <null>",
772 				IrIndexDump(p.block.successors[0, p.ir], p));
773 			break;
774 		default:
775 			p.sink.putf("%s else %s",
776 				IrIndexDump(p.block.successors[0, p.ir], p),
777 				IrIndexDump(p.block.successors[1, p.ir], p));
778 			break;
779 	}
780 }