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 }