aboutsummaryrefslogtreecommitdiff
path: root/src/regalloc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/regalloc.c')
-rw-r--r--src/regalloc.c337
1 files changed, 282 insertions, 55 deletions
diff --git a/src/regalloc.c b/src/regalloc.c
index 474e5a4..68e68a2 100644
--- a/src/regalloc.c
+++ b/src/regalloc.c
@@ -1,90 +1,317 @@
#include <assert.h>
+#include <stdlib.h>
#include <qbt/regalloc.h>
#include <qbt/vec.h>
#include <qbt/abi.h>
+/* first try to use temporaries, then callee-save, then finally args.
+ * Idea is that callee-save cost is paid once at the start of the function call,
+ * after that they're free. Argument registers are generally important to keep
+ * free to make sure function calls in loops etc. don't have to shuffle
+ * registers around as much */
static const int64_t tr_map[] = {
+ RT0, RT1, RT2, RT3, RT4, RT5, RT6, RT7, RT8, RT9,
+ RT10, RT11, RT12, RT13, RT14, RT15, RT16, RT17, RT18, RT19,
+ RT20, RT21, RT22, RT23,
+
RS0, RS1, RS2, RS3, RS4, RS5, RS6, RS7, RS8, RS9,
RS10, RS11, RS12, RS13, RS14, RS15, RS16, RS17, RS18, RS19,
- RS20, RS21, RS22, RS23, RS24
+ RS20, RS21, RS22, RS23,
+
+ RA0, RA1, RA2, RA3, RA4, RA5, RA6, RA7, RA8, RA9,
+ RA10, RA11, RA12, RA13, RA14, RA15, RA16, RA17, RA18, RA19,
+ RA20, RA21, RA22, RA23,
};
-#define reg_at(v, i)\
- vect_at(int64_t, v, i)
+#define reg_at(v, i) \
+ vect_at(struct val, v, i)
-static struct val rewrite_tmp(struct vec rmap, struct val t)
+static bool has_rewrite_rule(struct vec *rmap, struct val t)
{
- assert(t.class == TMP);
- assert(t.r < (int64_t)vec_len(&rmap));
- int64_t r = reg_at(rmap, t.r);
- assert(r);
- return reg_val(r);
+ if (t.r >= (int64_t)vec_len(rmap))
+ return false;
+
+ struct val r = reg_at(*rmap, t.r);
+ return r.class != NOCLASS;
}
-static size_t cur_reg = 0;
-static void add_rewrite_rule(struct vec *rmap, struct val t)
+static struct val rewrite_tmp(struct vec *rmap, struct val t)
{
assert(t.class == TMP);
- assert(cur_reg < 25
- && "ran out of temp registers, time to implement proper regalloc!");
+ assert(t.r < (int64_t)vec_len(rmap));
+ struct val r = reg_at(*rmap, t.r);
+ assert(r.class != NOCLASS);
+ return r;
+}
+
+static void add_rewrite_rule(struct vec *rmap, struct val from, struct val to)
+{
+ assert(from.class == TMP);
+ assert(to.class == REG);
/* note <=, we go one 'beyond' just to make sure that 0 fits */
- while ((int64_t)vec_len(rmap) <= t.r) {
- int64_t zero = 0;
- vec_append(rmap, &zero);
+ while ((int64_t)vec_len(rmap) <= from.r) {
+ struct val no = noclass();
+ vec_append(rmap, &no);
}
- if (reg_at(*rmap, t.r) == 0)
- reg_at(*rmap, t.r) = tr_map[cur_reg++];
+ reg_at(*rmap, from.r) = to;
}
-void regalloc(struct fn *f)
+static void add_hint(struct vec *hints, struct val from, struct val to)
{
- /* very ugly, fix once we get the proper regalloc implemented */
- cur_reg = 0;
- struct vec rmap = vec_create(sizeof(int64_t));
+ /* kind of a hack */
+ add_rewrite_rule(hints, from, to);
+}
- /* here we would ideally do some kind of lifetime checking, to start
- * with we only assign to different temporary registers, we have enough
- * of them to work for some smaller test functions */
- /* lifetime info would probably also be useful for saving callee-save
- * registers during calls, so that should probably also be done during
- * register allocation? maybe? */
- foreach_blk(bi, f->blks) {
- struct blk *b = blk_at(f->blks, bi);
- foreach_insn(i, b->insns) {
- struct insn n = insn_at(b->insns, i);
- if (n.in[0].class == TMP) {
- n.in[0] = rewrite_tmp(rmap, n.in[0]);
- }
+static struct val get_hint(struct vec *hints, struct val from)
+{
+ if ((int64_t)vec_len(hints) <= from.r)
+ return noclass();
- if (n.in[1].class == TMP) {
- n.in[1] = rewrite_tmp(rmap, n.in[1]);
- }
+ return rewrite_tmp(hints, from);
+}
- if (n.out.class == TMP) {
- add_rewrite_rule(&rmap, n.out);
- n.out = rewrite_tmp(rmap, n.out);
- }
+struct lifetime {
+ struct val v;
+ size_t start;
+ size_t end;
+ size_t used;
+};
+
+#define lifetime_at(lifetimes, i) \
+ vect_at(struct lifetime, lifetimes, i)
+
+#define foreach_lifetime(iter, lifetimes) \
+ foreach_vec(iter, lifetimes)
+
+static void add_def(struct vec *lifetimes, struct val v, size_t i)
+{
+ while ((int64_t)vec_len(lifetimes) <= v.r) {
+ vec_append(lifetimes, &(struct lifetime){noclass(), 0, 0, 0});
+ }
+
+ /* I guess a def isn't techincally a 'use' but good enough, we assume
+ * that unused variables have already been removed by some earlier stage
+ * (not currently true but that's the intention) */
+ lifetime_at(*lifetimes, v.r) = (struct lifetime){v, i, 0, 1};
+}
- /* write back changes */
- insn_at(b->insns, i) = n;
+static void add_use(struct vec *lifetimes, struct val v, size_t i)
+{
+ assert((int64_t)vec_len(lifetimes) > v.r);
+ struct lifetime l = lifetime_at(*lifetimes, v.r);
+ assert(l.used);
+ l.used++;
+ l.end = i;
+ lifetime_at(*lifetimes, v.r) = l;
+}
+
+static void collect_lifetimes(struct blk *b, struct vec *hints,
+ struct vec *lifetimes)
+{
+ foreach_blk_param(pi, b->params) {
+ struct val v = blk_param_at(b->params, pi);
+ add_def(lifetimes, v, 0);
+ }
+
+ size_t pos = 1;
+ foreach_insn(ii, b->insns) {
+ struct insn i = insn_at(b->insns, ii);
+ if (i.in[0].class == TMP)
+ add_use(lifetimes, i.in[0], pos);
- /* do this here since the register allocation is the
- * last stage before actually lowering to assembly, so
- * no possibility of dead code elimination or stuff like
- * that */
- if (n.type == CALL)
- f->has_calls = true;
+ if (i.in[1].class == TMP)
+ add_use(lifetimes, i.in[1], pos);
+
+ if (i.out.class == TMP)
+ add_def(lifetimes, i.out, pos);
+
+ /* collect some early hints */
+ if (i.type == MOVE) {
+ /* input arguments, retvals */
+ if (i.out.class == TMP && i.in[0].class == REG)
+ add_hint(hints, i.out, i.in[0]);
+
+ if (i.out.class == REG && i.in[0].class == TMP)
+ add_hint(hints, i.in[0], i.out);
}
- if (b->cmp[0].class == TMP)
- b->cmp[0] = rewrite_tmp(rmap, b->cmp[0]);
+ pos++;
+ }
+
+ if (b->cmp[0].class == TMP)
+ add_use(lifetimes, b->cmp[0], pos);
+
+ if (b->cmp[1].class == TMP)
+ add_use(lifetimes, b->cmp[1], pos);
+
+ foreach_blk_param(pi, b->args1) {
+ struct val v = blk_param_at(b->args1, pi);
+ add_use(lifetimes, v, pos);
+ }
+
+ foreach_blk_param(pi, b->args2) {
+ struct val v = blk_param_at(b->args2, pi);
+ add_use(lifetimes, v, pos);
+ }
+}
+
+static void build_active(struct vec *active, struct vec *lifetimes, size_t i)
+{
+ struct lifetime ref = lifetime_at(*lifetimes, i);
+ foreach_lifetime(li, *lifetimes) {
+ struct lifetime l = lifetime_at(*lifetimes, li);
+ if (l.used == 0)
+ continue;
+
+ if (l.end < ref.start)
+ continue;
+
+ if (l.start > ref.end)
+ continue;
+
+ vec_append(active, &l);
+ }
+}
- if (b->cmp[1].class == TMP)
- b->cmp[1] = rewrite_tmp(rmap, b->cmp[1]);
+static void build_reserved(struct vec *reserved, struct vec *active,
+ struct vec *rmap)
+{
+ foreach_lifetime(li, *active) {
+ struct lifetime l = lifetime_at(*active, li);
+ if (l.used == 0)
+ continue;
+
+ if (has_rewrite_rule(rmap, l.v)) {
+ struct val act = rewrite_tmp(rmap, l.v);
+ vec_append(reserved, &act);
+ }
+ }
+}
+
+static bool reg_free(struct vec *reserved, struct val f)
+{
+ assert(f.class == REG);
+ /* not the fastest way in the world, but good enough for now */
+ foreach_val(ri, *reserved) {
+ struct val r = val_at(*reserved, ri);
+ if (same_val(r, f))
+ return false;
+ }
+
+ return true;
+}
+
+static struct val find_free_reg(struct vec *reserved)
+{
+ for (size_t i = 0; i < sizeof(tr_map) / sizeof(tr_map[0]); ++i) {
+ struct val r = reg_val(tr_map[i]);
+ if (reg_free(reserved, r))
+ return r;
+ }
+
+ /* handle spill case later */
+ assert(0 &&
+ "ran out of registers, time to implement proper spill handling");
+ abort();
+}
+
+static void build_rmap(struct vec *hints, struct vec *lifetimes,
+ struct vec *rmap)
+{
+ struct vec active = vec_create(sizeof(struct lifetime));
+ struct vec reserved = vec_create(sizeof(struct val));
+
+ foreach_lifetime(li, *lifetimes) {
+ struct lifetime l = lifetime_at(*lifetimes, li);
+ if (l.used == 0)
+ continue;
+
+ vec_reset(&active);
+ build_active(&active, lifetimes, li);
+ build_reserved(&reserved, &active, rmap);
+
+ struct val h = get_hint(hints, l.v);
+ if (h.class != NOCLASS) {
+ if (reg_free(&reserved, h)) {
+ add_rewrite_rule(rmap, l.v, h);
+ continue;
+ }
+ }
+
+ /* eventually we should select the spill register only for the
+ * least used register, but that's a bit more complicated than
+ * just this linear scan */
+ struct val f = find_free_reg(&reserved);
+ add_rewrite_rule(rmap, l.v, f);
+ }
+}
+
+/* has_calls is kind of in an iffy place but I guess it's fine */
+static void do_rewrites(struct blk *b, struct vec *rmap, struct fn *f)
+{
+ foreach_blk_param(pi, b->params) {
+ struct val p = blk_param_at(b->params, pi);
+ blk_param_at(b->params, pi) = rewrite_tmp(rmap, p);
+ }
+
+ foreach_insn(ii, b->insns) {
+ struct insn i = insn_at(b->insns, ii);
+ if (i.in[0].class == TMP)
+ i.in[0] = rewrite_tmp(rmap, i.in[0]);
+
+ if (i.in[1].class == TMP)
+ i.in[1] = rewrite_tmp(rmap, i.in[1]);
+
+ if (i.out.class == TMP)
+ i.out = rewrite_tmp(rmap, i.out);
+
+ /** @todo save caller-save at callsites here */
+ if (i.type == CALL)
+ f->has_calls = true;
+
+ /* write back changes, christ I forget this a lot */
+ insn_at(b->insns, ii) = i;
+ }
+
+ if (b->cmp[0].class == TMP)
+ b->cmp[0] = rewrite_tmp(rmap, b->cmp[0]);
+
+ if (b->cmp[1].class == TMP)
+ b->cmp[1] = rewrite_tmp(rmap, b->cmp[1]);
+
+ foreach_blk_param(pi, b->args1) {
+ struct val p = blk_param_at(b->args1, pi);
+ blk_param_at(b->args1, pi) = rewrite_tmp(rmap, p);
+ }
+
+ foreach_blk_param(pi, b->args2) {
+ struct val p = blk_param_at(b->args2, pi);
+ blk_param_at(b->args2, pi) = rewrite_tmp(rmap, p);
+ }
+}
+
+/* assumes ssa form */
+void regalloc(struct fn *f)
+{
+ struct vec hints = vec_create(sizeof(struct val));
+ struct vec lifetimes = vec_create(sizeof(struct lifetime));
+ struct vec rmap = vec_create(sizeof(struct val));
+
+ /* here it would probably make sense to iterate through lifetimes in
+ * order of priority, like loop nesting/count etc */
+ foreach_blk(bi, f->blks) {
+ struct blk *b = blk_at(f->blks, bi);
+ vec_reset(&lifetimes);
+ /** @todo collect hints from args/params */
+ collect_lifetimes(b, &hints, &lifetimes);
+ build_rmap(&hints, &lifetimes, &rmap);
+ do_rewrites(b, &rmap, f);
+ /** @todo forward_hints(b, &hints, &rmap) */
}
- f->max_callee_save = cur_reg;
+ vec_destroy(&hints);
+ vec_destroy(&lifetimes);
vec_destroy(&rmap);
}