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 /// IR Function
7 module vox.ir.ir_function;
8 
9 import std.string : format;
10 
11 import vox.all;
12 
13 /// Stores info about single function in IR
14 /// All data of a function is stored in a number of arenas
15 /// Every function has its data stored sequentially in each arena, and items in each function have separate indexing
16 /// Indices are relative to the pointers.
17 /// All this implies that in order to modify the function's IR it needs to be at the end of each arena.
18 /// Then we can freely append new items to the end of arenas.
19 struct IrFunction
20 {
21 	IrInstrHeader* instrPtr;
22 	IrIndex* instrPayloadPtr;
23 	IrIndex* instrNextPtr;
24 	IrIndex* instrPrevPtr;
25 	IrPhi* phiPtr;
26 	uint* arrayPtr;
27 	IrVirtualRegister* vregPtr;
28 	// index 0 must be always entry block
29 	// index 1 must be always exit block
30 	IrBasicBlock* basicBlockPtr;
31 	StackSlot* stackSlotPtr;
32 
33 	// Optional. Used for IR interpretation
34 	uint* vmSlotOffsets;
35 	// Optional. Used for IR interpretation
36 	uint frameSize;
37 
38 	/// Used for instrPtr, instrNextPtr, instrPrevPtr
39 	uint numInstructions;
40 	uint numPayloadSlots;     /// instrPayloadPtr
41 	uint numPhis;             /// phiPtr
42 	uint arrayLength;         /// arrayPtr
43 	uint numVirtualRegisters; /// vregPtr
44 	uint numBasicBlocks;      /// basicBlockPtr
45 	uint numStackSlots;       /// stackSlotPtr
46 
47 
48 	/// Special block. Automatically created. Program entry. Created first.
49 	enum IrIndex entryBasicBlock = IrIndex(0, IrValueKind.basicBlock);
50 	/// Special block. Automatically created. All returns must jump to it.
51 	enum IrIndex exitBasicBlock = IrIndex(1, IrValueKind.basicBlock);
52 
53 	/// IrTypeFunction index
54 	IrIndex type;
55 	///
56 	IrInstructionSet instructionSet;
57 
58 	/// How many bytes we need to allocate in prolog and deallocate in epilog
59 	int stackFrameSize;
60 	// number of calls in the function
61 	// collected in abi lowering pass
62 	// if 0 or 1, we can merge stack allocation with function's stack (TODO)
63 	// if 0, we can omit stack alignment
64 	uint numCalls;
65 
66 	VregIterator virtualRegisters() return { return VregIterator(&this); }
67 
68 	///
69 	Identifier name;
70 
71 	CallConvention getCallConvEnum(CompilationContext* c) {
72 		return c.types.get!IrTypeFunction(type).callConv;
73 	}
74 	CallConv* getCallConv(CompilationContext* c) {
75 		return callConventions[c.types.get!IrTypeFunction(type).callConv];
76 	}
77 
78 	IrBasicBlock[] blocksArray() {
79 		return basicBlockPtr[0..numBasicBlocks];
80 	}
81 
82 	StackSlot[] stackSlots() {
83 		return stackSlotPtr[0..numStackSlots];
84 	}
85 
86 	IrIndex lastBasicBlock() {
87 		if (numBasicBlocks < 2) return IrIndex();
88 		return getBlock(exitBasicBlock).prevBlock;
89 	}
90 
91 	IrIndex firstVirtReg() {
92 		if (numVirtualRegisters == 0) return IrIndex();
93 		return IrIndex(0, IrValueKind.virtualRegister);
94 	}
95 
96 	IrIndex lastVirtReg() {
97 		if (numVirtualRegisters == 0) return IrIndex();
98 		return IrIndex(numVirtualRegisters - 1, IrValueKind.virtualRegister);
99 	}
100 
101 	BlockIterator blocks() return { return BlockIterator(&this); }
102 	BlockReverseIterator blocksReverse() return { return BlockReverseIterator(&this); }
103 
104 	alias getBlock = get!IrBasicBlock;
105 	alias getPhi = get!IrPhi;
106 	alias getVirtReg = get!IrVirtualRegister;
107 	alias getInstr = get!IrInstrHeader;
108 	alias getStackSlot = get!StackSlot;
109 
110 	T* get(T)(IrIndex index)
111 	{
112 		enum IrValueKind kind = getIrValueKind!T;
113 		assert(index.kind != IrValueKind.none, "get!"~T.stringof~" null index");
114 		assert(index.kind == kind, format("expected %s, got %s", kind, index.kind));
115 		static if (kind == IrValueKind.instruction)
116 			return cast(T*)(&instrPtr[index.storageUintIndex]);
117 		else static if (kind == IrValueKind.basicBlock)
118 			return &basicBlockPtr[index.storageUintIndex];
119 		else static if (kind == IrValueKind.phi)
120 			return &phiPtr[index.storageUintIndex];
121 		else static if (kind == IrValueKind.virtualRegister)
122 			return &vregPtr[index.storageUintIndex];
123 		else static if (kind == IrValueKind.stackSlot)
124 			return &stackSlotPtr[index.storageUintIndex];
125 		else
126 			static assert(false, format("Cannot get %s from IrFunction", T.stringof));
127 	}
128 
129 	IrIndex* getArray(IrIndex index)
130 	{
131 		assert(index.kind != IrValueKind.none, "null index");
132 		assert(index.kind == IrValueKind.array, format("expected %s, got %s", IrValueKind.array, index.kind));
133 		return cast(IrIndex*)(&arrayPtr[index.storageUintIndex]);
134 	}
135 
136 	// In correct code there must be no dangling basic blocks left
137 	// But if there were, they would appear after exit block
138 	void orderBlocks()
139 	{
140 		IrIndex first;
141 		IrIndex firstLink;
142 
143 		void walk(IrIndex node)
144 		{
145 			IrBasicBlock* block = getBlock(node);
146 			block.visitFlag = true;
147 			foreach(ref IrIndex succ; block.successors.range(&this))
148 				if (!getBlock(succ).visitFlag)
149 					walk(succ);
150 			if (first.isDefined)
151 				linkSingleBlockBefore(&this, node, first);
152 			else
153 				firstLink = node;
154 			first = node;
155 		}
156 
157 		walk(entryBasicBlock);
158 
159 		// find last block by iterating forward
160 		IrIndex last;
161 
162 		// clear all flags
163 		foreach (idx, ref IrBasicBlock block; blocks)
164 		{
165 			last = idx;
166 			block.visitFlag = false;
167 		}
168 
169 		// bring exit block to the end of function
170 		if (last != exitBasicBlock) {
171 			moveBlockAfter(&this, exitBasicBlock, last);
172 		}
173 	}
174 
175 	void removeAllPhis() {
176 		foreach (IrIndex index, ref IrBasicBlock block; blocks)
177 			block.removeAllPhis;
178 	}
179 
180 	void freeIrArray(IrIndex offset, uint capacity) {
181 		// noop for now
182 	}
183 
184 	void assignSequentialBlockIndices()
185 	{
186 		uint index;
187 		foreach (idx, ref IrBasicBlock block; blocks)
188 		{
189 			block.seqIndex = index++;
190 		}
191 	}
192 
193 	IrIndex getValueType(CompilationContext* context, IrIndex someIndex)
194 	{
195 		return .getValueType(someIndex, &this, context);
196 	}
197 
198 	ref IrIndex prevInstr(IrIndex instrIndex) {
199 		return instrPrevPtr[instrIndex.storageUintIndex];
200 	}
201 
202 	ref IrIndex nextInstr(IrIndex instrIndex) {
203 		return instrNextPtr[instrIndex.storageUintIndex];
204 	}
205 
206 	size_t byteLength() {
207 		return
208 			(IrInstrHeader.sizeof + uint.sizeof + uint.sizeof) * numInstructions +
209 			IrIndex.sizeof * numPayloadSlots +
210 			IrPhi.sizeof * numPhis +
211 			uint.sizeof * arrayLength +
212 			IrVirtualRegister.sizeof * numVirtualRegisters +
213 			IrBasicBlock.sizeof * numBasicBlocks;
214 	}
215 
216 	void dumpStackSlots(CompilationContext* context)
217 	{
218 		writefln("Slots %s, size 0x%X", numStackSlots, stackFrameSize);
219 		foreach (i, ref slot; stackSlots)
220 		{
221 			writefln("% 2s size 0x%X align %s disp 0x%X", i, slot.sizealign.size, slot.sizealign.alignment, slot.displacement);
222 		}
223 	}
224 }
225 
226 void dupSingleIrStorage(T)(ref Arena!T arena, ref T* ptr, uint length) {
227 	//writefln("arena %s %s..%s %s", arena.length, ptr, ptr + length, length);
228 	T[] buf = ptr[0..length];
229 	ptr = arena.nextPtr;
230 	arena.put(buf);
231 	//writefln("arena %s %s..%s %s", arena.length, ptr, ptr + length, length);
232 }
233 
234 void dupIrStorage(IrFunction* ir, CompilationContext* c)
235 {
236 	dupSingleIrStorage(c.irStorage.instrHeaderBuffer, ir.instrPtr, ir.numInstructions);
237 	dupSingleIrStorage(c.irStorage.instrPayloadBuffer, ir.instrPayloadPtr, ir.numPayloadSlots);
238 	dupSingleIrStorage(c.irStorage.instrNextBuffer, ir.instrNextPtr, ir.numInstructions);
239 	dupSingleIrStorage(c.irStorage.instrPrevBuffer, ir.instrPrevPtr, ir.numInstructions);
240 	dupSingleIrStorage(c.irStorage.phiBuffer, ir.phiPtr, ir.numPhis);
241 	dupSingleIrStorage(c.irStorage.vregBuffer, ir.vregPtr, ir.numVirtualRegisters);
242 	dupSingleIrStorage(c.irStorage.arrayBuffer, ir.arrayPtr, ir.arrayLength);
243 	dupSingleIrStorage(c.irStorage.basicBlockBuffer, ir.basicBlockPtr, ir.numBasicBlocks);
244 	dupSingleIrStorage(c.irStorage.stackSlotBuffer, ir.stackSlotPtr, ir.numStackSlots);
245 }
246 
247 // mainIr must be in editable state, at the end of all arenas
248 // irToCopy is appended after mainIr and mainIr length is updated.
249 // Appended slots are iterated and all references are updated
250 void appendIrStorage(IrFunction* mainIr, const IrFunction* irToCopy, CompilationContext* c)
251 {
252 	// create table of offsets per IrValueKind
253 	// align to cache line
254 	align(64) uint[16] offsets;
255 	offsets[IrValueKind.instruction]     = mainIr.numInstructions;
256 	offsets[IrValueKind.basicBlock]      = mainIr.numBasicBlocks;
257 	offsets[IrValueKind.phi]             = mainIr.numPhis;
258 	offsets[IrValueKind.virtualRegister] = mainIr.numVirtualRegisters;
259 	offsets[IrValueKind.array]           = mainIr.arrayLength;
260 	offsets[IrValueKind.stackSlot]       = mainIr.numStackSlots;
261 	// others remain 0 and do not affect IrIndex being fixed
262 
263 	void dupAndFixStorage(T)(ref Arena!T arena, const T* ptr, uint length) {
264 		const(IrIndex)[] oldData = cast(const(IrIndex)[])ptr[0..length];
265 		IrIndex[] newDataBuf = cast(IrIndex[])arena.voidPut(length);
266 
267 		foreach(i, ref IrIndex index; newDataBuf) {
268 			IrIndex oldIndex = oldData[i];
269 			// Fix each IrIndex. Only affects IrIndex when offset is non-zero. Otherwise copies without modification
270 			// Incrementing the whole uint is safe as long as `storageUintIndex` part doesn't overflow
271 			index.asUint = oldIndex.asUint + offsets[oldIndex.kind];
272 		}
273 	}
274 
275 	IrInstrHeader[] instrs = c.irStorage.instrHeaderBuffer.put(irToCopy.instrPtr[0..irToCopy.numInstructions]); // dup IrInstrHeader
276 	foreach(ref IrInstrHeader instr; instrs) instr._payloadOffset += mainIr.numPayloadSlots; // fix
277 	// phis, vregs and basic block consist out of IrIndex entries or have integer data of type IrValueKind.none.
278 	// The bitflags are designed so that 4 bits are 0 at the time of this operation
279 	dupAndFixStorage(c.irStorage.instrPayloadBuffer, irToCopy.instrPayloadPtr, irToCopy.numPayloadSlots);
280 	dupAndFixStorage(c.irStorage.instrNextBuffer, irToCopy.instrNextPtr, irToCopy.numInstructions);
281 	dupAndFixStorage(c.irStorage.instrPrevBuffer, irToCopy.instrPrevPtr, irToCopy.numInstructions);
282 	dupAndFixStorage(c.irStorage.phiBuffer, irToCopy.phiPtr, irToCopy.numPhis);
283 	dupAndFixStorage(c.irStorage.vregBuffer, irToCopy.vregPtr, irToCopy.numVirtualRegisters);
284 	dupAndFixStorage(c.irStorage.arrayBuffer, irToCopy.arrayPtr, irToCopy.arrayLength);
285 	dupAndFixStorage(c.irStorage.basicBlockBuffer, irToCopy.basicBlockPtr, irToCopy.numBasicBlocks);
286 	// dup StackSlot. Fixes are not needed because StackSlot.type and StackSlot.baseReg are of type and physical register kind.
287 	StackSlot[] stackSlots = c.irStorage.stackSlotBuffer.put(irToCopy.stackSlotPtr[0..irToCopy.numStackSlots]);
288 
289 	// make sure numInstructions is incremented once
290 	mainIr.numInstructions += irToCopy.numInstructions;
291 	mainIr.numPayloadSlots += irToCopy.numPayloadSlots;
292 	mainIr.numPhis += irToCopy.numPhis;
293 	mainIr.numVirtualRegisters += irToCopy.numVirtualRegisters;
294 	mainIr.arrayLength += irToCopy.arrayLength;
295 	mainIr.numBasicBlocks += irToCopy.numBasicBlocks;
296 	mainIr.numStackSlots += irToCopy.numStackSlots;
297 }
298 
299 struct IrFuncStorage
300 {
301 	Arena!IrInstrHeader instrHeaderBuffer;
302 	Arena!IrIndex instrPayloadBuffer; // stores variadic results + arguments per instruction
303 	Arena!IrIndex instrNextBuffer; // index of next instruction
304 	Arena!IrIndex instrPrevBuffer; // index of previous instruction
305 	Arena!IrPhi phiBuffer;
306 	Arena!IrVirtualRegister vregBuffer;
307 	Arena!uint arrayBuffer; // stores data of IrSmallArray
308 	Arena!IrBasicBlock basicBlockBuffer;
309 	Arena!StackSlot stackSlotBuffer;
310 
311 	void printMemSize(ref TextSink sink)
312 	{
313 		size_t byteLength;
314 		size_t committedBytes;
315 		size_t reservedBytes;
316 		void collect(T)(ref Arena!T arena) {
317 			byteLength += arena.byteLength;
318 			committedBytes += arena.committedBytes;
319 			reservedBytes += arena.reservedBytes;
320 		}
321 		collect(instrHeaderBuffer);
322 		collect(instrPayloadBuffer);
323 		collect(instrNextBuffer);
324 		collect(instrPrevBuffer);
325 		collect(phiBuffer);
326 		collect(vregBuffer);
327 		collect(arrayBuffer);
328 		collect(basicBlockBuffer);
329 		collect(stackSlotBuffer);
330 		sink.putfln("  %-16s%-6iB    %-6iB   %-6iB",
331 			"  IR total",
332 			scaledNumberFmt(byteLength),
333 			scaledNumberFmt(committedBytes),
334 			scaledNumberFmt(reservedBytes));
335 	}
336 }
337 
338 // phi iterators are aware of this
339 // only safe to delete current phi while iterating
340 void removePhi(CompilationContext* context, IrFunction* ir, IrIndex phiIndex)
341 {
342 	version(IrPrint) writefln("[IR] remove phi %s", phiIndex);
343 	IrPhi* phi = ir.getPhi(phiIndex);
344 	IrBasicBlock* block = ir.getBlock(phi.blockIndex);
345 	version(IrPrint) {
346 		foreach(IrIndex phiIndex, ref IrPhi phi; block.phis(ir)) {
347 			writefln("[IR]   %s = %s", phi.result, phiIndex);
348 		}
349 	}
350 	// TODO: free list of phis
351 	if (block.firstPhi == phiIndex) block.firstPhi = phi.nextPhi;
352 	if (phi.nextPhi.isDefined) ir.getPhi(phi.nextPhi).prevPhi = phi.prevPhi;
353 	if (phi.prevPhi.isDefined) ir.getPhi(phi.prevPhi).nextPhi = phi.nextPhi;
354 	version(IrPrint) writefln("[IR] after remove phi %s", phiIndex);
355 	version(IrPrint) {
356 		foreach(IrIndex phiIndex, ref IrPhi phi; block.phis(ir)) {
357 			writefln("[IR]   %s = %s", phi.result, phiIndex);
358 		}
359 	}
360 
361 	// mark as removed
362 	phi.blockIndex = IrIndex();
363 
364 	// remove args
365 	foreach(IrIndex arg; phi.args(ir)) {
366 		removeUser(context, ir, phiIndex, arg);
367 	}
368 	phi.args.free(ir);
369 }
370 
371 // instruction iterators are aware of this
372 // only safe to delete current instruction while iterating
373 void removeInstruction(IrFunction* ir, IrIndex instrIndex)
374 {
375 	if (ir.prevInstr(instrIndex).isInstruction)
376 		ir.nextInstr(ir.prevInstr(instrIndex)) = ir.nextInstr(instrIndex);
377 	else if (ir.prevInstr(instrIndex).isBasicBlock)
378 		ir.getBlock(ir.prevInstr(instrIndex)).firstInstr = ir.nextInstr(instrIndex);
379 	else assert(false);
380 
381 	if (ir.nextInstr(instrIndex).isInstruction)
382 		ir.prevInstr(ir.nextInstr(instrIndex)) = ir.prevInstr(instrIndex);
383 	else if (ir.nextInstr(instrIndex).isBasicBlock)
384 		ir.getBlock(ir.nextInstr(instrIndex)).lastInstr = ir.prevInstr(instrIndex);
385 	else assert(false);
386 }
387 
388 // ditto
389 void replaceInstruction(IrFunction* ir, IrIndex instrIndex, IrIndex replaceBy)
390 {
391 	ir.prevInstr(replaceBy) = ir.prevInstr(instrIndex);
392 	ir.nextInstr(replaceBy) = ir.nextInstr(instrIndex);
393 
394 	if (ir.prevInstr(instrIndex).isInstruction)
395 		ir.nextInstr(ir.prevInstr(instrIndex)) = replaceBy;
396 	else if (ir.prevInstr(instrIndex).isBasicBlock)
397 		ir.getBlock(ir.prevInstr(instrIndex)).firstInstr = replaceBy;
398 	else assert(false);
399 
400 	if (ir.nextInstr(instrIndex).isInstruction)
401 		ir.prevInstr(ir.nextInstr(instrIndex)) = replaceBy;
402 	else if (ir.nextInstr(instrIndex).isBasicBlock)
403 		ir.getBlock(ir.nextInstr(instrIndex)).lastInstr = replaceBy;
404 	else assert(false);
405 }
406 
407 void removeUser(CompilationContext* context, IrFunction* ir, IrIndex user, IrIndex used) {
408 	assert(used.isDefined, "used is undefined");
409 	final switch (used.kind) with(IrValueKind) {
410 		case none: assert(false, "removeUser none");
411 		case array: assert(false, "removeUser array");
412 		case instruction: assert(false, "removeUser instruction");
413 		case basicBlock: break; // allowed. As argument of jmp jcc
414 		case constant, constantAggregate, constantZero: break; // allowed, noop
415 		case global:
416 			context.globals.get(used).removeUser(user);
417 			break;
418 		case phi: assert(false, "removeUser phi"); // must be virt reg instead
419 		case stackSlot: break; // allowed, noop
420 		case virtualRegister:
421 			ir.getVirtReg(used).users.remove(ir, user);
422 			break;
423 		case physicalRegister: break; // allowed, noop
424 		case type: break; // no user tracking
425 		case variable: assert(false);
426 		case func: break; // allowed, noop
427 	}
428 }
429 
430 struct BlockIterator
431 {
432 	IrFunction* ir;
433 	int opApply(scope int delegate(IrIndex, ref IrBasicBlock) dg) {
434 		IrIndex next = ir.entryBasicBlock;
435 		while (next.isDefined)
436 		{
437 			IrBasicBlock* block = ir.getBlock(next);
438 			if (int res = dg(next, *block))
439 				return res;
440 			next = block.nextBlock;
441 		}
442 		return 0;
443 	}
444 }
445 
446 struct VregIterator
447 {
448 	IrFunction* ir;
449 	int opApply(scope int delegate(IrIndex, ref IrVirtualRegister) dg) {
450 		foreach(size_t i, ref IrVirtualRegister vreg; ir.vregPtr[0..ir.numVirtualRegisters])
451 			if (int res = dg(IrIndex(cast(uint)i, IrValueKind.virtualRegister), vreg))
452 				return res;
453 		return 0;
454 	}
455 }
456 
457 struct BlockReverseIterator
458 {
459 	IrFunction* ir;
460 	int opApply(scope int delegate(IrIndex, ref IrBasicBlock) dg) {
461 		IrIndex prev = ir.exitBasicBlock;
462 		while (prev.isDefined)
463 		{
464 			IrBasicBlock* block = ir.getBlock(prev);
465 			if (int res = dg(prev, *block))
466 				return res;
467 			prev = block.prevBlock;
468 		}
469 		return 0;
470 	}
471 }