diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/asm.c | 96 | ||||
| -rw-r--r-- | src/compiler.c | 2 | ||||
| -rw-r--r-- | src/main.c | 2 | ||||
| -rw-r--r-- | src/ops.c | 135 |
4 files changed, 179 insertions, 56 deletions
diff --git a/src/asm.c b/src/asm.c new file mode 100644 index 0000000..4102265 --- /dev/null +++ b/src/asm.c @@ -0,0 +1,96 @@ +#include <ek/ops.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +/* I guess using the zero register might be okay in some scenarios, but for now + * I'll just keep it an illegal register */ +#define ASSERT_REG(x) {assert(x->kind == LOC_REG); assert(x->reg > 0 && x->reg < 81);} +#define ASSERT_MEM(x) {assert(x->kind == LOC_MEM); assert(x->reg > 0 && x->reg < 81);} + +static int print_comment(struct op *op, FILE *f) +{ + fprintf(f, "/* %s */\n", op->string); + return 0; +} + +static int print_label(struct op *op, FILE *f) +{ + fprintf(f, "%s:\n", op->string); + return 0; +} + +static int print_li(struct op *op, FILE *f) +{ + struct loc *o = &op->outputs; + assert(o->next == NULL); + /* unsure if this will always hold, but for now */ + ASSERT_REG(o); + + fprintf(f, "li x%zd, %lld\n", o->reg, op->constant); + return 0; +} + +static int print_mv(struct op *op, FILE *f) +{ + struct loc *i = &op->inputs; + struct loc *o = &op->outputs; + assert(i->next == NULL); + assert(o->next == NULL); + ASSERT_REG(i); + ASSERT_REG(o); + + fprintf(f, "mv x%zd, x%zd\n", o->reg, i->reg); + return 0; +} + +static int print_stt(struct op *op, FILE *f) +{ + struct loc *i = &op->inputs; + struct loc *o = &op->outputs; + assert(i->next == NULL); + assert(o->next == NULL); + ASSERT_REG(i); + ASSERT_MEM(o); + + fprintf(f, "st t, x%zd, %lld(x%zd)\n", i->reg, o->off, o->reg); + return 0; +} + +static int print_op(struct op *op, FILE *f) +{ + int ret = 0; + switch (op->opcode) { + case OP_COMMENT: ret = print_comment(op, f); break; + case OP_LABEL: ret = print_label(op, f); break; + case OP_LI: ret = print_li(op, f); break; + case OP_MV: ret = print_mv(op, f); break; + case OP_STT: ret = print_stt(op, f); break; + default: abort(); + } + + return ret; +} + +int print_asm(struct ops *ops, const char *output) +{ + FILE *f = fopen(output, "w"); + + /* main should probably be mangled here as well */ + fprintf(f, "jal x0, main\n"); + int ret = 0; + struct op *op = ops->base; + while (op) { + if ((ret = print_op(op, f))) + break; + + op = op->next; + } + + /* tell simulator to turn off (very much temp) */ + fprintf(f, "li x1, 3\n"); + fprintf(f, "csrrw mpower, x0, x1\n"); + + fclose(f); + return ret; +} diff --git a/src/compiler.c b/src/compiler.c index 8801c7f..a096878 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -192,7 +192,7 @@ int compile(const char *input, const char *output) { return ret; } - ret = analyze_lifetime(ops); + ret = alloc_regs(ops); if (ret) { destroy_ops(ops); error("compilation of %s stopped due to errors", input); @@ -50,7 +50,7 @@ static void usage() int main(int argc, char *argv[]) { int opt; - const char *output = "e.out"; + const char *output = "e.t"; while ((opt = getopt(argc, argv, "hI:o:")) != -1) { switch (opt) { case 'o': @@ -10,20 +10,6 @@ * move everything down into them. Typically the top output is used as input in * some other step. */ -enum loc_kind { - LOC_NONE, LOC_REG, LOC_MEM -}; - -struct loc { - enum loc_kind kind; - struct loc *next; - size_t start; - size_t end; - size_t reg; - long long off; - size_t width; -}; - static void set_reg(struct loc *loc, size_t reg) { loc->kind = LOC_REG; @@ -57,38 +43,6 @@ static size_t trivial_type_width(struct ast_node *type) return 3; } -enum opcode { - /* small subset for now */ - OP_LI, - OP_LA, - OP_ADD, - OP_ADDI, - OP_STT, - OP_LDT, - OP_STW, - OP_LDW, - OP_MV, /* meta op, will be realized as either load/store or register move */ - OP_LABEL, - OP_COMMENT, -}; - -struct op { - enum opcode opcode; - struct loc inputs; - struct loc outputs; - size_t loc; - union { - long long constant; - const char *string; - }; - struct op *next; -}; - -struct ops { - struct op *base; - struct op *head; -}; - struct ops *create_ops() { struct ops *ops = calloc(1, sizeof(struct ops)); @@ -123,9 +77,9 @@ static size_t next_virtual_reg() return reg++; } -#define HEAD_OUTPUTS(ops) ops->head->outputs - static int lower_op(struct ast_node *n, struct ops *ops); + +#define HEAD_OUTPUTS(ops) ops->head->outputs static struct op *op_head(struct ops *ops) { return ops->head; @@ -143,13 +97,28 @@ static struct op *append_op(struct ops *ops, enum opcode opcode) return n; } +/* this should really only be called after lifetime analysis, should I add in + * some checks against incorrect use...? */ +static struct op *insert_op_after(struct op *op, enum opcode opcode) +{ + struct op *n = calloc(1, sizeof(struct op)); + n->opcode = opcode; + n->next = op->next; + op->next = n; + return n; +} + static int lower_proc(struct ast_node *n, struct ops *ops) { struct op *op = append_op(ops, OP_LABEL); /** @todo name mangling */ struct ast_node *id = AST_PROC(n).id; op->string = strdup(AST_ID(id).id); - return lower_op(AST_PROC(n).body, ops); + int ret = lower_op(AST_PROC(n).body, ops); + if (ret) + return ret; + + return 0; } static int lower_block(struct ast_node *n, struct ops *ops) @@ -382,14 +351,72 @@ int lower_ops(struct scope *root, struct ops *ops) return 0; } -int analyze_lifetime(struct ops *ops) +static enum opcode st_opc(struct loc *loc) { - return 0; + switch (loc->width) { + case 1: return OP_STT; + case 3: return OP_STW; + } + + abort(); + return OP_STW; +} + +static enum opcode ld_opc(struct loc *loc) +{ + switch (loc->width) { + case 1: return OP_LDT; + case 3: return OP_LDW; + } + + abort(); + return OP_LDW; } -/* this is probably going to balloon up, should probably move this into another - * file */ -int print_asm(struct ops *ops, const char *output) +static int realize_moves(struct ops *ops) { + /* completely arbitrary and *will* have to be made better in the near + * future */ + static const size_t tmp_reg = 9; + struct op *op = ops->base; + for (; op; op = op->next) { + if (op->opcode != OP_MV) { + continue; + } + + struct loc *i = &op->inputs; + struct loc *o = &op->outputs; + assert(i->next == NULL); + assert(i->next == NULL); + + if (i->kind == LOC_REG && o->kind == LOC_REG) { + continue; + } else if (i->kind == LOC_REG && o->kind == LOC_MEM) { + op->opcode = st_opc(o); + } else if (i->kind == LOC_MEM && o->kind == LOC_REG) { + op->opcode = ld_opc(i); + } else if (i->kind == LOC_MEM && o->kind == LOC_MEM) { + op->opcode = ld_opc(i); + /* our input/output are references, so call this before + * setting registers for our original operation. Also, + * at this point the lifetime stuff is finished, so no + * big deal that our node IDs change */ + struct op *n = insert_op_after(op, st_opc(o)); + set_mem(&n->outputs, o->reg, o->off, o->width); + set_reg(&n->inputs, tmp_reg); + set_reg(&op->outputs, tmp_reg); + } + } + return 0; } + +int alloc_regs(struct ops *ops) +{ + /** @todo analyze lifetime, for now just convert moves to correct ldst + * etc. */ + int ret = realize_moves(ops); + printf("Lowered ops after lifetime analysis:\n"); + print_ops(ops); + return ret; +} |
