1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
|
#include "stdafx.h"
#include "UsedRegs.h"
#include "Arena.h"
namespace code {
using impl::WorkItem;
impl::WorkItem::WorkItem(Nat line) : line(line), inWork(false), nextDep(null), nextWork(null) {}
impl::WorkItem::WorkItem(Nat line, WorkItem *nextDep) : line(line), inWork(false), nextDep(nextDep), nextWork(null) {}
UsedRegs::UsedRegs(Array<RegSet *> *used, RegSet *all) : used(used), all(all) {}
static void operator +=(RegSet &to, const Operand &op) {
if (op.type() == opRegister)
to.put(op.reg());
}
static void operator -=(RegSet &to, const Operand &op) {
if (op.type() == opRegister)
to.remove(op.reg());
}
static void addIndirect(RegSet *to, const Operand &op) {
if (op.type() == opRelative)
to->put(op.reg());
}
static void addIndirect(RegSet *to, Instr *instr) {
addIndirect(to, instr->src());
addIndirect(to, instr->dest());
}
static void processInstruction(const Arena *arena, Instr *instr, RegSet *used) {
switch (instr->op()) {
case op::endBlock:
case op::jmpBlock:
case op::prolog:
// These do not preserve any registers. This means that 'used' should be empty by here
// for "proper" programs.
used->clear();
break;
case op::jmp:
// Jumps to arbitrary points are handled by assuming no dependencies. Currently, they
// are only used to jump to other functions, and other low-level things.
used->clear();
break;
case op::beginBlock:
case op::swap:
// No effect on used registers;
break;
case op::fnCall:
case op::fnCallRef:
case op::call:
// Only the registers preserved through fn calls can be assumed to be preserved.
if (arena)
arena->removeFnRegs(used);
else
used->clear();
break;
case op::bxor:
if (instr->src() == instr->dest()) {
// Used to zero registers.
*used -= instr->src();
break;
}
// Intentional fall-through.
default:
addIndirect(used, instr);
*used += instr->src();
if (instr->mode() & destWrite)
*used -= instr->dest();
if (instr->mode() & destRead)
*used += instr->dest();
break;
}
}
class RegState {
public:
RegState(const Arena *arena, const Listing *src)
: arena(arena), src(src), work(null), workEnd(null) {
Engine &e = src->engine();
usedNow = new (e) RegSet();
used = new (e) Array<RegSet *>(src->count(), null);
for (Nat i = 0; i < src->count(); i++)
used->at(i) = new (e) RegSet();
labelSrc = runtime::allocArray<Nat>(e, &natArrayType, src->labelCount());
labelDeps = runtime::allocArray<WorkItem *>(e, StormInfo<WorkItem *>::handle(e).gcArrayType, src->labelCount());
}
// Arena.
const Arena *arena;
// Listing.
const Listing *src;
// Variable for 'usedNow', so we don't have to re-allocate it many times.
RegSet *usedNow;
// Set of registers used for all lines in the code.
Array<RegSet *> *used;
// Source line for labels.
GcArray<Nat> *labelSrc;
// Dependencies for all labels encountered so far.
GcArray<WorkItem *> *labelDeps;
// List of work to perform.
WorkItem *work;
// End of the list of work.
WorkItem *workEnd;
// Add work to the work-list.
void addWork(WorkItem *item) {
if (!item)
return;
// Add them in reverse order to make sure the highest line is processed first.
// This list will never be very long, so a recursive add is fine.
if (item->nextDep)
addWork(item->nextDep);
if (item->inWork)
return;
item->inWork = true;
if (workEnd) {
workEnd->nextWork = item;
workEnd = item;
} else {
work = workEnd = item;
}
}
// Pop an item from the work queue.
WorkItem *popWork() {
WorkItem *result = work;
if (result)
work = result->nextWork;
if (!work)
workEnd = null;
return result;
}
};
static Bool isLabelJump(Instr *instr) {
return (instr->op() == op::jmp) & (instr->dest().type() == opLabel);
}
static Bool processJump(RegState &state, Instr *instr, Nat target, RegSet *usedNow) {
Nat targetLine = state.labelSrc->v[target];
switch (instr->src().condFlag()) {
case ifAlways:
// Only use the target.
usedNow->set(state.used->at(targetLine));
return true;
case ifNever:
// Nothing special to do.
return false;
default:
usedNow->put(state.used->at(targetLine));
return true;
}
}
// First-time traversal. Always traverses all instructions in the listing.
static void traverseFirst(RegState &state, RegSet *all) {
RegSet *usedNow = state.usedNow;
usedNow->clear();
for (Nat i = state.src->count(); i > 0; i--) {
Nat line = i - 1;
Instr *instr = state.src->at(line);
if (instr->op() == op::shadowMov)
continue;
if (isLabelJump(instr)) {
Nat target = instr->dest().label().key();
if (processJump(state, instr, target, usedNow)) {
// Record the dependency
state.labelDeps->v[target] = new (state.src) WorkItem(line, state.labelDeps->v[target]);
}
} else {
processInstruction(state.arena, instr, usedNow);
}
state.used->at(line)->set(usedNow);
// Keep 'all' up to date.
if (instr->mode() & destWrite)
*all += instr->dest();
// If there are labels, we always post them to the work-list. Since we work backwards,
// this means that only back-edges will be added to the work-list, which happens to be
// exactly what we want.
// Also: record line numbers for labels so that we can use them in 'processJump'.
if (Array<Label> *labels = state.src->labels(line)) {
for (Nat i = 0; i < labels->count(); i++) {
Nat lbl = labels->at(i).key();
state.labelSrc->v[lbl] = line;
state.addWork(state.labelDeps->v[lbl]);
}
}
}
}
// Subsequent traversals. Processes rows from the starting line until the state no longer
// changes. Triggers items on the work-list as needed.
// Note: if we happen to process "too far" on one iteration, any remaining work items
// that we happened to process "early" will simply terminate almost immediately when
// they are processed.
static void traverseNext(RegState &state, Nat start) {
RegSet *usedNow = state.usedNow;
if (start + 1 < state.src->count())
usedNow->set(state.used->at(start + 1));
else
usedNow->clear();
for (Nat i = start + 1; i > 0; i--) {
Nat line = i - 1;
Instr *instr = state.src->at(line);
if (instr->op() == op::shadowMov)
continue;
if (isLabelJump(instr)) {
processJump(state, instr, instr->dest().label().key(), usedNow);
// TODO: With slightly better data structures we could remove this node from the
// work-list at this point if it is there. This means that we will avoid some calls
// to the 'traverseNext' function that would be unneccessary. Our check below would
// still make it terminate quickly, so it is not too expensive.
} else {
processInstruction(state.arena, instr, usedNow);
}
Bool changed = *usedNow != *state.used->at(line);
// Since we have our work-list, we can just exit whenever the state stops changing.
if (!changed)
return;
state.used->at(line)->set(usedNow);
// Post new items to the work queue.
if (Array<Label> *labels = state.src->labels(line)) {
for (Nat i = 0; i < labels->count(); i++) {
Nat lbl = labels->at(i).key();
state.addWork(state.labelDeps->v[lbl]);
}
}
}
}
UsedRegs usedRegs(const Arena *arena, const Listing *src) {
// This function implements a backwards-flow fixpoint algorithm for finding an
// over-estimation of the used registers at the point of each instruction.
// This is done by iterating through the instructions backwards in the code
// and computing all usages. Any back-edges are detected, and re-computed at
// a later pass. This might need to be repeated a number of times until the
// computation converges.
// TODO: Look at labels that can catch exceptions and treat them differently?
// As these labels "generate" ptrA, we should treat them with some care.
RegState state(arena, src);
// All registers.
RegSet *all = new (src) RegSet();
// First pass. Simply go through the code from the end and update the state as necessary.
traverseFirst(state, all);
// Process items on the work-list as necessary.
while (WorkItem *work = state.popWork()) {
traverseNext(state, work->line);
}
return UsedRegs(state.used, all);
}
RegSet *allUsedRegs(const Listing *src) {
RegSet *all = new (src) RegSet();
for (Nat i = 0; i < src->count(); i++) {
Instr *instr = src->at(i);
if (instr->op() == op::shadowMov)
continue;
if (instr->mode() & destWrite)
*all += instr->dest();
}
return all;
}
}
|