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 }