1 /// Copyright: Copyright (c) 2018-2019 Andrey Penechko. 2 /// License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 3 /// Authors: Andrey Penechko. 4 5 module vox.be.reg_alloc.move_solver; 6 7 import vox.all; 8 9 /// Reorders a set of moves, to produce correct behavior 10 /// Nodes can be in 3 states: 11 /// RO - value is only read. Those are not added to writtenNodes array. 12 /// RW - value is read 1 or more times and written 1 time. Indices of these nodes are at the beginning of writtenNodes 13 /// WO - value is only written. Indices are at the end of writtenNodes. 14 struct MoveSolver 15 { 16 CompilationContext* context; 17 IrFunction* ir; 18 19 ValueInfo[] stackSlots; 20 ValueInfo[NUM_REGS_PER_CLASS][NUM_REG_CLASSES] registers; 21 ValueInfo anyConstant; 22 IrIndex* writtenNodesPtr; 23 size_t savedBufLength; 24 uint numWrittenNodes; 25 uint numWriteOnlyValues; 26 27 // allocate buffers 28 // takes unique ownership of tempBuffer 29 // Inits all register and stack slot infos 30 // after each `placeMovesBeforeInstr` call they need to be in init state for reuse 31 void setup(IrFunction* ir) 32 { 33 this.ir = ir; // for errors 34 savedBufLength = context.tempBuffer.length; 35 stackSlots = context.allocateTempArray!ValueInfo(ir.numStackSlots); 36 writtenNodesPtr = cast(IrIndex*)context.tempBuffer.nextPtr; 37 } 38 39 void reset() 40 { 41 assert(anyConstant == ValueInfo.init); 42 assert(numWriteOnlyValues == 0); 43 assert(numWrittenNodes == 0); 44 } 45 46 // releases unique ownership of tempBuffer 47 void release() 48 { 49 context.tempBuffer.length = savedBufLength; 50 51 savedBufLength = 0; 52 stackSlots = null; 53 writtenNodesPtr = null; 54 assert(anyConstant == ValueInfo.init); 55 assert(numWriteOnlyValues == 0); 56 assert(numWrittenNodes == 0); 57 } 58 59 ref ValueInfo getInfo(IrIndex index) return { 60 switch(index.kind) with(IrValueKind) { 61 case constant, constantZero, global, func: return anyConstant; 62 case stackSlot: return stackSlots[index.storageUintIndex]; 63 case physicalRegister: return registers[index.physRegClass][index.physRegIndex]; 64 default: context.internal_error("getInfo(%s)", index); 65 } 66 } 67 68 void addMove(IrIndex fromIndex, IrIndex toIndex, IrArgSize argSize, FromValue fromIsValue) 69 { 70 assert(fromIndex.isDefined); 71 assert(toIndex.isDefined); 72 if (fromIndex == toIndex) return; 73 74 ValueInfo* from = &getInfo(fromIndex); 75 ValueInfo* to = &getInfo(toIndex); 76 77 from.onRead(fromIndex, fromIsValue); 78 // no longer write only 79 if (from.numReads == 1 && from.readFrom.isDefined) { 80 wo_to_rw(from.arrayPos); 81 } 82 83 context.assertf(toIndex.isPhysReg || toIndex.isStackSlot, "toIndex is %s", toIndex.kind); 84 context.assertf(!to.readFrom.isDefined, "Second write to %s detected", IrIndexDump(toIndex, context, ir)); 85 86 to.onWrite(fromIndex, toIndex, argSize); 87 to.arrayPos = numWrittenNodes; 88 context.tempBuffer.put(toIndex.asUint); 89 ++numWrittenNodes; 90 ++numWriteOnlyValues; 91 92 if (to.numReads > 0) { 93 wo_to_rw(to.arrayPos); 94 } 95 } 96 97 void print() { 98 IrIndex[] writtenNodes = writtenNodesPtr[0..numWrittenNodes]; 99 foreach(IrIndex index; writtenNodes[0..$-numWriteOnlyValues]) 100 writef("(%s %s) ", index, getInfo(index)); 101 write("| "); 102 foreach(IrIndex index; writtenNodes[$-numWriteOnlyValues..$]) 103 writef("(%s %s) ", index, getInfo(index)); 104 writeln; 105 } 106 107 void wo_to_rw(uint arrayPos) 108 { 109 IrIndex[] writtenNodes = writtenNodesPtr[0..numWrittenNodes]; 110 assert(numWriteOnlyValues > 0); 111 size_t from = arrayPos; 112 size_t to = numWrittenNodes - numWriteOnlyValues; 113 if (from != to) { 114 swap(writtenNodes[from], writtenNodes[to]); 115 getInfo(writtenNodes[from]).arrayPos = cast(uint)from; 116 getInfo(writtenNodes[to]).arrayPos = cast(uint)to; 117 } 118 --numWriteOnlyValues; 119 } 120 121 void rw_to_wo(uint arrayPos) 122 { 123 IrIndex[] writtenNodes = writtenNodesPtr[0..numWrittenNodes]; 124 ++numWriteOnlyValues; 125 size_t from = arrayPos; 126 size_t to = numWrittenNodes - numWriteOnlyValues; 127 if (from != to) { 128 swap(writtenNodes[from], writtenNodes[to]); 129 getInfo(writtenNodes[from]).arrayPos = cast(uint)from; 130 getInfo(writtenNodes[to]).arrayPos = cast(uint)to; 131 } 132 } 133 134 void removeItem(ValueInfo* item) 135 { 136 IrIndex[] writtenNodes = writtenNodesPtr[0..numWrittenNodes]; 137 size_t from = numWrittenNodes-1; 138 size_t to = item.arrayPos; 139 if (from != to) { 140 writtenNodes[to] = writtenNodes[from]; 141 getInfo(writtenNodes[to]).arrayPos = cast(uint)to; 142 } 143 *item = ValueInfo(); 144 --numWrittenNodes; 145 context.tempBuffer.unput(1); 146 } 147 148 void placeMovesBeforeInstr(IrBuilder* builder, IrIndex beforeInstr, IrIndex delegate() getScratchSpillSlot) 149 { 150 void makeStore(IrIndex dst, IrIndex src, IrArgSize argSize) 151 { 152 ExtraInstrArgs extra = { argSize : argSize }; 153 IrIndex instr = builder.emitInstr!(Amd64Opcode.store)(extra, dst, src); 154 builder.insertBeforeInstr(beforeInstr, instr); 155 } 156 157 void makeLoad(IrIndex dst, IrIndex src, IrArgSize argSize) 158 { 159 ExtraInstrArgs extra = { result : dst, argSize : argSize }; 160 InstrWithResult instr = builder.emitInstr!(Amd64Opcode.load)(extra, src); 161 builder.insertBeforeInstr(beforeInstr, instr.instruction); 162 } 163 164 void makeMov(IrIndex dst, IrIndex src, IrArgSize argSize) 165 { 166 ExtraInstrArgs extra = { result : dst, argSize : argSize }; 167 InstrWithResult instr = builder.emitInstr!(Amd64Opcode.mov)(extra, src); 168 builder.insertBeforeInstr(beforeInstr, instr.instruction); 169 } 170 171 while (numWrittenNodes) 172 { 173 IrIndex toIndex = writtenNodesPtr[numWrittenNodes-1]; 174 ValueInfo* to = &getInfo(toIndex); 175 IrIndex fromIndex = to.readFrom; 176 ValueInfo* from = &getInfo(fromIndex); 177 178 if (to.numReads == 0) 179 { 180 version(RAPrint_resolve) writefln("insert move %s <- %s", toIndex, fromIndex); 181 if (fromIndex.isStackSlot) 182 { 183 if (toIndex.isStackSlot) // stack slot -> stack slot 184 { 185 IrIndex scratchSpillSlot = getScratchSpillSlot(); 186 IrIndex scratchReg = IrIndex(0, IrArgSize.size64, 0); 187 188 makeStore(scratchSpillSlot, scratchReg, IrArgSize.size64); 189 190 // we don't have correct argSize inside `from` because size is only registered for move dst 191 scratchReg.physRegSize = to.argSize; 192 makeLoad(scratchReg, fromIndex, to.argSize); 193 makeStore(toIndex, scratchReg, to.argSize); 194 195 scratchReg.physRegSize = IrArgSize.size64; 196 makeLoad(scratchReg, scratchSpillSlot, IrArgSize.size64); 197 } 198 else // stack slot -> con or reg 199 { 200 assert(toIndex.isSomeReg); 201 if (from.isValue) 202 makeMov(toIndex, fromIndex, to.argSize); 203 else 204 makeLoad(toIndex, fromIndex, to.argSize); 205 } 206 } 207 else // from is reg or constant 208 { 209 if (toIndex.isStackSlot) // con or reg -> stack slot 210 { 211 makeStore(toIndex, fromIndex, to.argSize); 212 } 213 else // con or reg -> reg 214 { 215 makeMov(toIndex, fromIndex, to.argSize); 216 } 217 } 218 219 --from.numReads; 220 --numWriteOnlyValues; 221 removeItem(to); 222 223 if (from.numReads == 0 && from.readFrom.isDefined) 224 rw_to_wo(from.arrayPos); 225 } 226 else // to.numReads > 0 227 { 228 // process cycled items 229 // from is non-constant in this scope 230 231 // to <-- from <-- from.readFrom 232 // mark from as removed and rewrite as: 233 // to <-- from.readFrom 234 235 version(RAPrint_resolve) writefln("xchg from %s to %s %s", *from, *to, toIndex); 236 237 // For now assume both are physical registers 238 IrIndex reg0 = fromIndex; 239 IrIndex reg1 = from.readFrom; 240 context.assertf(reg0.isPhysReg, "%s is not phys reg", reg0); 241 context.assertf(reg1.isPhysReg, "%s is not phys reg", reg1); 242 243 // In case one register is bigger that the other, make them of equal size, so that code emitter doesn't complain 244 uint physRegSize = max(reg0.physRegSize, reg1.physRegSize); 245 reg0.physRegSize = physRegSize; 246 reg1.physRegSize = physRegSize; 247 248 IrIndex instr = builder.emitInstr!(Amd64Opcode.xchg)(ExtraInstrArgs(), reg0, reg1); 249 builder.insertBeforeInstr(beforeInstr, instr); 250 251 if (from.readFrom == toIndex) { 252 // handle case when from.readFrom == to 253 // ... to <-- from <-- to ... 254 removeItem(to); 255 } else { 256 to.readFrom = from.readFrom; 257 --from.numReads; 258 } 259 removeItem(from); 260 } 261 } 262 } 263 } 264 265 enum FromValue : bool { 266 no = false, 267 yes = true, 268 } 269 270 struct ValueInfo 271 { 272 uint arrayPos; // index into writtenNodes 273 IrIndex readFrom; // can be null 274 ushort numReads; 275 IrArgSize argSize; // used for memory moves 276 FromValue isValue; 277 278 void onRead(IrIndex self, FromValue isValue) 279 { 280 ++numReads; 281 this.isValue = isValue; 282 } 283 284 void onWrite(IrIndex from, IrIndex self, IrArgSize argSize) 285 { 286 readFrom = from; 287 this.argSize = argSize; 288 } 289 }