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 }