From 36de4f902c4eec9f8d918e00cf0bf89d6b670cf5 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Wed, 3 Apr 2024 16:00:15 +0300 Subject: add ssa form --- include/qbt/nodes.h | 34 ++++++- include/qbt/unreachable.h | 2 +- src/main.c | 2 - src/nodes.c | 48 +++++++--- src/opt.c | 16 +++- src/parser.y | 10 +- src/ssa.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++ src/unreachable.c | 34 ++----- 8 files changed, 329 insertions(+), 48 deletions(-) diff --git a/include/qbt/nodes.h b/include/qbt/nodes.h index 63bbc58..d3ddc5f 100644 --- a/include/qbt/nodes.h +++ b/include/qbt/nodes.h @@ -125,11 +125,17 @@ struct blk { struct blk *s1; struct blk *s2; struct vec insns; + /* input parameters, tmp values effectively */ + struct vec params; + /* arguments for s1 */ + struct vec args1; + /* arguments for s2 (if any) */ + struct vec args2; /* used to temporarily store label targets */ const char *to; - /* used by reachability analysis */ - bool reachable; + /* used by some algorithms to mark visited */ + int visited; }; struct fn { @@ -206,6 +212,24 @@ static inline struct val tmp_val(int64_t t) }; } +static inline bool same_val(struct val v1, struct val v2) +{ + if (v1.class != v2.class) + return false; + + switch (v1.class) { + case REG: return v1.r == v2.r; + case TMP: return v1.r == v2.r; + case IMM: return v1.v == v2.v; + case MEM: return v1.v == v2.v; + case REF: return v1.r == v2.r && v1.v == v2.v; + case NOCLASS: return true; + } + + /* shouldn't be reachable */ + return false; +} + static inline struct insn insn_create(enum insn_type o, enum val_type t, struct val r, struct val a0, struct val a1) { return (struct insn) { @@ -264,6 +288,12 @@ struct label_map { #define foreach_blk(iter, blocks)\ foreach_vec(iter, blocks) +#define foreach_blk_param(iter, block_params)\ + foreach_vec(iter, block_params) + +#define blk_param_at(v, i)\ + vect_at(struct val, v, i) + #define label_at(v, i)\ vect_at(struct label_map, v, i) diff --git a/include/qbt/unreachable.h b/include/qbt/unreachable.h index 0f0c39e..8ad152e 100644 --- a/include/qbt/unreachable.h +++ b/include/qbt/unreachable.h @@ -3,6 +3,6 @@ #include -void unreachable(struct fn *f); +void remove_unvisited(struct fn *f, int visited); #endif /* UNREACHABLE_H */ diff --git a/src/main.c b/src/main.c index d6abb2d..8a59127 100644 --- a/src/main.c +++ b/src/main.c @@ -57,10 +57,8 @@ int main(int argc, char *argv[]) foreach_fn(i, p->fns) { struct fn_map m = fn_at(p->fns, i); - dump_function(m.fn); // also handles things like register mapping etc. optimize(m.fn); - dump_function(m.fn); // for now output(m.fn, stdout); } diff --git a/src/nodes.c b/src/nodes.c index 294b531..a6ff0b8 100644 --- a/src/nodes.c +++ b/src/nodes.c @@ -48,8 +48,11 @@ struct blk *new_block(struct fn *f) { struct blk *b = calloc(1, sizeof(struct blk)); b->id = ++f->nblk; - b->reachable = false; + b->visited = 0; b->insns = vec_create(sizeof(struct insn)); + b->params = vec_create(sizeof(struct val)); + b->args1 = vec_create(sizeof(struct val)); + b->args2 = vec_create(sizeof(struct val)); vec_append(&f->blks, &b); return b; } @@ -102,6 +105,8 @@ void finish_function(struct fn *f, const char *name) } b->s2 = m.b; + if (b->btype == J) + b->s1 = b->s2; } } @@ -136,6 +141,7 @@ void destroy_function(struct fn *f) void destroy_block(struct blk *b) { vec_destroy(&b->insns); + vec_destroy(&b->params); free(b); } @@ -191,7 +197,16 @@ bool return_blk(struct blk *b) void dump_block(struct blk *b) { printf("//\t/*** block %lld ", (long long)b->id); - if (b->name) printf("(%s) ", b->name); + if (b->name) printf("\"%s\" ", b->name); + + printf("("); + foreach_blk_param(pi, b->params) { + struct val v = blk_param_at(b->params, pi); + dump_val(v); + printf(", "); + } + printf(") "); + printf("***/\n"); foreach_insn(i, b->insns) { @@ -204,20 +219,29 @@ void dump_block(struct blk *b) return; } - if (b->btype != J) { - assert(b->s2); - struct blk *s2 = b->s2; - printf("//\t%s ", op_str(b->btype)); - dump_val(b->cmp[0]); - printf(" "); - dump_val(b->cmp[1]); - printf(" -> %lli\n", (long long)s2->id); + assert(b->s2); + struct blk *s2 = b->s2; + printf("//\t%s ", op_str(b->btype)); + dump_val(b->cmp[0]); + printf(" "); + dump_val(b->cmp[1]); + printf(" -> %lli (", (long long)s2->id); + foreach_blk_param(pi, b->args2) { + struct val a = blk_param_at(b->args2, pi); + dump_val(a); + printf(", "); + } + printf("), else %lli (", (long long)b->s1->id); + foreach_blk_param(pi, b->args1) { + struct val a = blk_param_at(b->args1, pi); + dump_val(a); + printf(", "); } - printf("//\n"); + printf(")\n"); } void dump_function(struct fn *f) { - printf("/*** function %s ***/\n", f->name); + printf("//\t/*** function %s ***/\n", f->name); foreach_blk(i, f->blks) { struct blk *b = blk_at(f->blks, i); dump_block(b); diff --git a/src/opt.c b/src/opt.c index d52c9d0..882587d 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1,14 +1,26 @@ +#include + #include #include -#include #include #include void optimize(struct fn *f) { + printf("\n// initial:\n"); + dump_function(f); + + /* unreachability is done in several steps I guess */ ssa(f); + printf("\n// after SSA:\n"); + dump_function(f); + abi0(f); + printf("\n// after abi0:\n"); + dump_function(f); + /* ... do more stuff ... */ - unreachable(f); regalloc(f); + printf("\n// after regalloc:\n"); + dump_function(f); } diff --git a/src/parser.y b/src/parser.y index 4edcdcf..ec133eb 100644 --- a/src/parser.y +++ b/src/parser.y @@ -157,7 +157,15 @@ static inline void do_new_block(struct parser *p, const char *label) { finish_block(p->b, type, a0, a1, label); - p->b = p->b->s1 = new_block(p->f); + struct blk *b = new_block(p->f); + + if (p->b->btype == RET) + p->b = b; + /* kind of a special case, a jump to the direct next block */ + else if (p->b->btype == J && label == NULL) + p->b = p->b->s1 = p->b->s2 = b; + else + p->b = p->b->s1 = b; } static inline void do_insadd(struct parser *p, diff --git a/src/ssa.c b/src/ssa.c index a9d6e6c..59e7254 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -1,8 +1,239 @@ +#include #include +#include + +static void add_val(struct vec *map, struct val v) +{ + assert(v.class == TMP); + size_t i = v.r; + while (vec_len(map) <= i) { + struct val no = noclass(); + vec_append(map, &no); + } + + blk_param_at(*map, i) = v; +} + +static void remove_val(struct vec *map, struct val v) +{ + assert(v.class == TMP); + size_t i = v.r; + if (vec_len(map) <= i) + return; + + blk_param_at(*map, i) = noclass(); +} + +static bool has_val(struct vec *map, struct val v) +{ + assert(v.class == TMP); + size_t i = v.r; + if (vec_len(map) <= i) + return false; + + struct val p = blk_param_at(*map, i); + return p.class != NOCLASS; +} + +static void build_params(struct blk *b, int visited) +{ + if (b->visited > visited) + return; + + b->visited++; + + if (b->s1) + build_params(b->s1, visited); + + if (b->s2) + build_params(b->s2, visited); + + struct vec generated = vec_create(sizeof(struct val)); + struct vec forward = vec_create(sizeof(struct val)); + struct vec required = vec_create(sizeof(struct val)); + + /* first, collect all forwards */ + if (b->s1) { + foreach_blk_param(pi, b->s1->params) { + struct val p = blk_param_at(b->s1->params, pi); + add_val(&forward, p); + } + } + + if (b->s2) { + foreach_blk_param(pi, b->s2->params) { + struct val p = blk_param_at(b->s2->params, pi); + add_val(&forward, p); + } + } + + /* go through each instruction and add inputs to required and outputs to + * generated */ + foreach_insn(ii, b->insns) { + struct insn i = insn_at(b->insns, ii); + struct val in1 = i.in[0]; + if (in1.class == TMP && !has_val(&generated, in1)) + add_val(&required, in1); + + struct val in2 = i.in[1]; + if (in2.class == TMP && !has_val(&generated, in2)) + add_val(&required, in2); + + struct val out = i.out; + if (out.class == TMP) { + add_val(&generated, out); + /* we generate this value so no need to forward it */ + if (has_val(&forward, out)) + remove_val(&forward, out); + } + } + + if (b->cmp[0].class == TMP && !has_val(&generated, b->cmp[0])) + add_val(&required, b->cmp[0]); + + if (b->cmp[1].class == TMP && !has_val(&generated, b->cmp[1])) + add_val(&required, b->cmp[1]); + + /* add values that need forwarding to our required list */ + foreach_blk_param(pi, forward) { + /* if value already is in list, don't do anything */ + struct val p = blk_param_at(forward, pi); + + /* skip empty slots */ + if (p.class == NOCLASS) + continue; + + if (has_val(&forward, p)) + continue; + + add_val(&required, p); + } + + /* convert from the hashmap-like structure to regular vector */ + foreach_blk_param(pi, required) { + struct val p = blk_param_at(required, pi); + if (p.class == NOCLASS) + continue; + + vec_append(&b->params, &p); + } + + vec_destroy(&required); + vec_destroy(&forward); + vec_destroy(&generated); +} + +static void collect_params(struct blk *b, int visited) +{ + if (b->visited > visited) + return; + + b->visited++; + + if (b->s1) { + collect_params(b->s1, visited); + foreach_blk_param(pi, b->s1->params) { + struct val p = blk_param_at(b->s1->params, pi); + vec_append(&b->args1, &p); + } + } + + if (b->s2) { + collect_params(b->s2, visited); + foreach_blk_param(pi, b->s2->params) { + struct val p = blk_param_at(b->s2->params, pi); + vec_append(&b->args2, &p); + } + } +} + +#define tmpval_at(rmap, i)\ + vect_at(struct val, rmap, i) + +static void add_rewrite_rule(struct vec *rmap, struct val from, struct val to) +{ + assert(from.class == TMP); + assert(to.class == TMP); + + while ((int64_t)vec_len(rmap) <= from.r) { + struct val no = noclass(); + vec_append(rmap, &no); + } + + tmpval_at(*rmap, from.r) = to; +} + +static struct val rewrite_tmp(struct vec *rmap, struct val from) +{ + assert(from.class == TMP); + assert(from.r <= (int64_t)vec_len(rmap)); + struct val v = tmpval_at(*rmap, from.r); + assert(v.class == TMP); + return v; +} + +static size_t rename_temps(struct blk *b, size_t i) +{ + struct vec rmap = vec_create(sizeof(struct val)); + + foreach_blk_param(pi, b->params) { + struct val p = blk_param_at(b->params, pi); + /* this is very similar to what's going on in regalloc.c, hmm */ + add_rewrite_rule(&rmap, p, tmp_val(i++)); + blk_param_at(b->params, pi) = rewrite_tmp(&rmap, p); + } + + foreach_insn(ii, b->insns) { + struct insn n = insn_at(b->insns, ii); + + if (n.in[0].class == TMP) + n.in[0] = rewrite_tmp(&rmap, n.in[0]); + + if (n.in[1].class == TMP) + n.in[1] = rewrite_tmp(&rmap, n.in[1]); + + if (n.out.class == TMP) { + add_rewrite_rule(&rmap, n.out, tmp_val(i++)); + n.out = rewrite_tmp(&rmap, n.out); + } + + insn_at(b->insns, ii) = n; + } + + 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); + } + + vec_destroy(&rmap); + + return i; +} void ssa(struct fn *f) { /* do a depth-first traversal of blocks, mark locations within as either * generated (i.e. we assign something to the register) or required * (i.e. it must be taken as a block parameter) */ + struct blk *b = blk_at(f->blks, 0); + build_params(b, b->visited); + remove_unvisited(f, b->visited); + collect_params(b, b->visited); + + size_t i = 0; + foreach_blk(bi, f->blks) { + struct blk *b = blk_at(f->blks, bi); + i = rename_temps(b, i); + } } diff --git a/src/unreachable.c b/src/unreachable.c index 90a65bd..aa04f46 100644 --- a/src/unreachable.c +++ b/src/unreachable.c @@ -1,38 +1,16 @@ #include -void build_reachable_vec(struct vec *reachable, struct blk *cur) +void remove_unvisited(struct fn *f, int visited) { - if (cur->reachable) - return; - - cur->reachable = true; - vec_append(reachable, &cur); - - if (cur->btype == RET) - return; - - if (cur->btype == J) - cur->s1 = cur->s2; - - if (cur->s1) - build_reachable_vec(reachable, cur->s1); - - if (cur->s2) - build_reachable_vec(reachable, cur->s2); -} - -void unreachable(struct fn *f) -{ - struct vec reachable = vec_create(sizeof(struct blk *)); - struct blk *b = blk_at(f->blks, 0); - build_reachable_vec(&reachable, b); + struct vec new = vec_create(sizeof(struct blk *)); foreach_blk(bi, f->blks) { struct blk *b = blk_at(f->blks, bi); - if (!b->reachable) + if (b->visited < visited) destroy_block(b); - + else + vec_append(&new, &b); } vec_destroy(&f->blks); - f->blks = reachable; + f->blks = new; } -- cgit v1.3