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 }