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 module vox.be.reg_alloc.live_interval;
5 
6 import vox.all;
7 
8 struct LiveInterval
9 {
10 	// See addRange for details on why
11 	InvertedArray!LiveRange ranges;
12 	UsePosition[] uses;
13 	// hint tells from which range to start searching
14 	// Allows to restart search from the position we previously ended on.
15 	// On big functions allows to reduce O(n^2) search complexity to 2 iterations per search.
16 	LiveRangeIndex lastHit = LiveRangeIndex(0);
17 
18 	uint from() {
19 		if (ranges.length > 0) return ranges[0].from;
20 		return MAX_USE_POS;
21 	}
22 	uint to() {
23 		if (ranges.length > 0) return ranges.back.to;
24 		return MAX_USE_POS;
25 	}
26 
27 	IrIndex reg;
28 	IrIndex definition; // phys or virt reg
29 	IrIndex storageHint;
30 	IntervalIndex parent; // prev split interval to the left
31 	IntervalIndex child; // next split interval to the right
32 	ubyte regClass;
33 
34 	bool isFixed() { return definition.isPhysReg; }
35 	bool isSplitChild() { return !parent.isNull; }
36 	bool isSplit() { return !child.isNull; }
37 
38 	void toString(scope void delegate(const(char)[]) sink) {
39 		import std.format : formattedWrite;
40 		sink.formattedWrite("int(");
41 		if (definition.isDefined) sink.formattedWrite("%s, ", definition);
42 		sink.formattedWrite("%s, %s", ranges, uses);
43 		if (!parent.isNull) sink.formattedWrite(", par %s", parent);
44 		if (!child.isNull) sink.formattedWrite(", child %s", child);
45 		sink(")");
46 	}
47 
48 	/// Set hint for register allocator
49 	void setVirtRegHint(IrIndex hint) {
50 		storageHint = hint;
51 	}
52 
53 	// Skips all ranges to the left of the position, and returns first range after that
54 	// Range before returned does not contain position
55 	// Returned range.to > position
56 	// Returned range.from may be <= position or may be > position
57 	// If all ranges were skipped then returns NULL
58 	//
59 	// Unoptimized version:
60 	// foreach(i, range; ranges) {
61 	//     if (position < range.to) return LiveRangeIndex(i);
62 	// }
63 	LiveRangeIndex getRightmostRange(uint position)
64 	{
65 		if (ranges.length == 0) return LiveRangeIndex.NULL;
66 		LiveRangeIndex hint = lastHit;
67 		if (lastHit >= ranges.length) hint = ranges.length - 1;
68 		if (ranges[hint].to > position) {
69 			if (hint == 0) {
70 				lastHit = LiveRangeIndex(0);
71 				return LiveRangeIndex(0);
72 			}
73 			foreach_reverse(i, range; ranges[0..hint].enumerate) {
74 				if (range.to <= position) {
75 					return LiveRangeIndex(i+1);
76 				}
77 				lastHit = LiveRangeIndex(i);
78 			}
79 		} else {
80 			foreach(i, range; ranges[hint..$].enumerate) {
81 				lastHit = LiveRangeIndex(i+hint);
82 				if (range.to > position) {
83 					return lastHit;
84 				}
85 			}
86 		}
87 
88 		return LiveRangeIndex.NULL;
89 	}
90 
91 	// returns rangeId pointing to range covering position or one to the left of pos.
92 	// returns NULL if empty interval or no ranges to the left
93 	LiveRangeIndex getLeftRange(uint position)
94 	{
95 		LiveRangeIndex result = LiveRangeIndex.NULL;
96 		foreach(i, range; ranges[].enumerate) {
97 			if (position >= range.from)
98 				return result;
99 			result = LiveRangeIndex(i);
100 		}
101 		return result;
102 	}
103 
104 	// sets the definition position
105 	void setFrom(CompilationContext* context, uint from) {
106 		version(LivePrint) writefln("[LIVE] setFrom vreg.%s from %s", virtReg, from);
107 
108 		if (ranges.empty) { // can happen if vreg had no uses (it is probably dead or used in phi above definition)
109 			addRange(context, from, from);
110 		} else {
111 			ranges[0].from = from;
112 		}
113 	}
114 
115 	void prependUse(UsePosition use) {
116 		//writefln("prependUse %s %s", definition, use);
117 		uses[$-1] = use;
118 		--uses.length;
119 	}
120 
121 	// Measurements showed that atm 100% of adds are either insert/merge at pos 0 or append
122 	// And 100% of appends happen when length is 0, which is essentially free
123 	// frontInserts 2499999 100% at pos 0
124 	// mergeInserts  779987 100% at pos 0
125 	// appends      2840014
126 	// Inserts to the front happen because new ranges are added in reverse order, as we iterate the CFG
127 	// The use of regular array caused quadratic time on add to the front, because all other items need to be shifted on each insert
128 	// With reversed array all items are reversed, so prepending actually appends, which is O(1) with array (amortized)
129 
130 	// bounds are from block start to block end of the same block
131 	// from is always == to block start for virtual intervals
132 	void addRange(CompilationContext* context, uint from, uint to)
133 	{
134 		version(LivePrint) writefln("[LIVE] addRange %s [%s; %s)", definition, from, to);
135 		LiveRange newRange = LiveRange(from, to);
136 
137 		size_t cur = 0;
138 		size_t len = ranges.length;
139 
140 		while (cur < len)
141 		{
142 			LiveRange* r = &ranges[cur];
143 
144 			if (r.canBeMergedWith(newRange))
145 			{
146 				// merge all intersecting ranges into one
147 				r.merge(newRange);
148 
149 				++cur;
150 				size_t firstToRemove = cur;
151 
152 				while (cur < len && r.canBeMergedWith(ranges[cur])) {
153 					r.merge(ranges[cur]);
154 					++cur;
155 				}
156 				ranges.removeByShift(firstToRemove, cur-firstToRemove);
157 
158 				return;
159 			}
160 			else if (to < r.from)
161 			{
162 				// we found insertion point before cur
163 				ranges.putAt(context.arrayArena, cur, newRange);
164 				return;
165 			}
166 
167 			++cur;
168 		}
169 
170 		// insert after last, no merge/insertion was made
171 		ranges.put(context.arrayArena, newRange);
172 	}
173 
174 	bool hasUseAt(uint pos) {
175 		foreach (UsePosition use; uses) {
176 			if (use.pos == pos) return true;
177 		}
178 		return false;
179 	}
180 
181 	uint firstUse() {
182 		// don't require uses by phi functions to be in register
183 		foreach(UsePosition use; uses)
184 			if (use.kind == UseKind.instruction) return use.pos;
185 		return MAX_USE_POS;
186 	}
187 
188 	// incudes after
189 	uint nextUseAfter(uint after)
190 	{
191 		uint closest = MAX_USE_POS;
192 		foreach_reverse (UsePosition use; uses)
193 		{
194 			if (use.pos < after) break;
195 			// don't require uses by phi functions to be in register
196 			if (use.kind == UseKind.instruction)
197 				closest = use.pos;
198 		}
199 		return closest;
200 	}
201 
202 	// others intervals can't use interval's register in this pos
203 	bool exclusivePosition(uint position)
204 	{
205 		foreach(range; ranges) {
206 			if (position < range.from) return false;
207 			if (position == range.from) return true;
208 			if (position < range.to) return true;
209 			// position >= to
210 		}
211 		return false;
212 	}
213 
214 	// interval's register contains valid value at this position
215 	bool coversPosition(uint position)
216 	{
217 		foreach(range; ranges) {
218 			if (position < range.from)
219 				return false;
220 			else if (position <= range.to)
221 				return true;
222 			// position >= to
223 		}
224 		return false;
225 	}
226 
227 	// retains uses before `pos`, returns uses >= `pos`
228 	UsePosition[] splitUsesBefore(uint pos) {
229 		foreach(i, use; uses) {
230 			if (use.pos >= pos) {
231 				UsePosition[] copy = uses;
232 				uses = uses[0..i];
233 				return copy[i..$];
234 			}
235 		}
236 		return null;
237 	}
238 }
239 
240 // Typical usage is that a is short interval starting at monotonically growing position during linear scan
241 // b is bigger interval beginning before a start.
242 // hint of b should be very close to the start of a
243 uint firstIntersection(LiveInterval* a, LiveInterval* b)
244 {
245 	size_t len_a = a.ranges.length;
246 	if (len_a == 0) return MAX_USE_POS;
247 
248 	size_t len_b = b.ranges.length;
249 	if (len_b == 0) return MAX_USE_POS;
250 
251 	size_t i_a = 0;
252 	size_t i_b = 0;
253 
254 	LiveRange r_a = a.ranges[i_a];
255 	LiveRange r_b = b.ranges[i_b];
256 
257 	// Optimization, skip the start of r_b
258 	if (r_b.from < r_a.from) {
259 		// We skip all ranges of b until the start of a. getRightmostRange can reuse hint, which dramatically cuts the number of iterations.
260 		i_b = b.getRightmostRange(r_a.from);
261 		r_b = b.ranges[i_b];
262 	}
263 
264 	while (true)
265 	{
266 		if (r_a.intersectsWith(r_b)) {
267 			return max(r_a.from, r_b.from);
268 		} else if (r_a.from < r_b.from) {
269 			if (i_a == len_a) return MAX_USE_POS;
270 			r_a = a.ranges[i_a];
271 			++i_a;
272 		} else { // r_b.from > r_a.from
273 			if (i_b == len_b) return MAX_USE_POS;
274 			r_b = b.ranges[i_b];
275 			++i_b;
276 		}
277 	}
278 }
279 
280 struct IntervalIndex
281 {
282 	this(size_t idx) { index = cast(uint)idx; }
283 	uint index = uint.max;
284 	alias index this;
285 	enum NULL = IntervalIndex(uint.max);
286 	bool isNull() { return index == uint.max; }
287 	void toString(scope void delegate(const(char)[]) sink) {
288 		import std.format : formattedWrite;
289 		if (isNull) sink("it_null");
290 		else sink.formattedWrite("it%s", index);
291 	}
292 }