diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-04-07 01:28:45 +0300 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-04-07 01:28:45 +0300 |
| commit | 170f3ddc3d8967c0b5d4755c0221c396db215b2f (patch) | |
| tree | 00314209cd7878b9d5b3fe6fc2e0c7297f76dec8 /src/ops.c | |
| parent | 2bdf1f8b1856bca8091d66a7a447e6c90993c297 (diff) | |
| download | ek-170f3ddc3d8967c0b5d4755c0221c396db215b2f.tar.gz ek-170f3ddc3d8967c0b5d4755c0221c396db215b2f.zip | |
implement some initial qbt backend stuff
Diffstat (limited to 'src/ops.c')
| -rw-r--r-- | src/ops.c | 487 |
1 files changed, 0 insertions, 487 deletions
diff --git a/src/ops.c b/src/ops.c deleted file mode 100644 index cc2650c..0000000 --- a/src/ops.c +++ /dev/null @@ -1,487 +0,0 @@ -#include <ek/ops.h> -#include <ek/scope.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -/* hopefully not too difficult to follow what's goind on, but to start with we - * assume we have an effectively infinite amount of virtual registers and we - * move everything down into them. Typically the top output is used as input in - * some other step. */ - -static void set_reg(struct loc *loc, size_t reg) -{ - loc->kind = LOC_REG; - loc->reg = reg; -} - -static void set_mem(struct loc *loc, size_t reg, long long off, size_t width) -{ - loc->kind = LOC_MEM; - loc->reg = reg; - loc->off = off; - loc->width = width; -} - -static size_t trivial_type_width(struct ast_node *type) -{ - switch (AST_TYPE(type).kind) { - case AST_TYPE_POINTER: return 3; - case AST_TYPE_PRIMITIVE: { - if (AST_PRIMITIVE_TYPE(type).type == AST_I27) - return 3; - - if (AST_PRIMITIVE_TYPE(type).type == AST_I9) - return 1; - - abort(); - break; - } - default: abort(); - } - return 3; -} - -static struct ops *create_ops() -{ - struct ops *ops = calloc(1, sizeof(struct ops)); - - struct op *op = calloc(1, sizeof(struct op)); - op->opcode = OP_COMMENT; - op->string = strdup("start"); - - ops->base = op; - ops->head = op; - return ops; -} - -static void destroy_ops(struct ops *ops) -{ - if (!ops) - return; - - struct op *op = ops->base; - while (op) { - struct op *prev = op; - op = op->next; - - switch (prev->opcode) { - case OP_COMMENT: free((void *)prev->string); break; - case OP_LABEL: free((void *)prev->string); break; - default: - } - - free(prev); - } - - free(ops); -} - -static size_t next_virtual_reg() -{ - static size_t reg = 1; - return reg++; -} - -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; -} - -static struct op *append_op(struct ops *ops, enum opcode opcode) -{ - static size_t i = 1; - struct op *op = op_head(ops); - struct op *n = calloc(1, sizeof(struct op)); - n->opcode = opcode; - op->next = n; - op->loc = i++; - ops->head = n; - 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); - 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) -{ - struct ast_node *b = AST_BLOCK(n).body; - while (b) { - int ret = lower_op(b, ops); - if (ret) - return ret; - - b = b->next; - } - - return 0; -} - -static int lower_var(struct ast_node *n, struct ops *ops) -{ - struct ast_node *d = scope_find_var(n->scope, AST_VAR(n).id); - /* structs should be handled as well, would it be better to try and fit - * them into regs or just dump them on the stack to make sure we don't - * immediately run out of registers? */ - struct ast_node *type = d->type; - if (AST_TYPE(type).kind != AST_TYPE_PRIMITIVE - && AST_TYPE(type).kind != AST_TYPE_POINTER) { - semantic_error(n->scope->fctx, n, - "only trivial type lowering implemented"); - return -1; - } - - d->reg = next_virtual_reg(); - - if (AST_VAR(n).init) { - int ret = lower_op(AST_VAR(n).init, ops); - if (ret) - return ret; - - struct loc inputs = HEAD_OUTPUTS(ops); - /* work with only primitive types for now */ - assert(inputs.next == NULL); - - struct op *op = append_op(ops, OP_MV); - op->inputs = inputs; - set_reg(&op->outputs, d->reg); - } - - return 0; -} - -static int lower_cast(struct ast_node *n, struct ops *ops) -{ - /** @todo lob off extra trits or something? */ - return lower_op(AST_CAST(n).expr, ops); -} - -static int lower_const(struct ast_node *n, struct ops *ops) -{ - if (AST_CONST(n).kind != AST_CONST_INTEGER) { - semantic_error(n->scope->fctx, n, - "only integer constant lowering implemented"); - return -1; - } - - struct op *op = append_op(ops, OP_LI); - op->constant = AST_CONST(n).integer; - set_reg(&op->outputs, next_virtual_reg()); - return 0; -} - -static int lower_assign(struct ast_node *n, struct ops *ops) -{ - int ret = lower_op(AST_ASSIGN(n).from, ops); - if (ret) - return ret; - - struct loc from = HEAD_OUTPUTS(ops); - - ret = lower_op(AST_ASSIGN(n).to, ops); - if (ret) - return ret; - - struct loc to = HEAD_OUTPUTS(ops); - struct op *op = append_op(ops, OP_MV); - op->inputs = from; - op->outputs = to; - return 0; -} - -static int lower_unop(struct ast_node *n, struct ops *ops) -{ - if (AST_UNOP(n).op != AST_DEREF) { - semantic_error(n->scope->fctx, n, - "unop lowering not implemented"); - return -1; - } - - if (AST_UNOP(n).op == AST_DEREF) { - int ret = lower_op(AST_UNOP(n).expr, ops); - if (ret) - return ret; - - struct loc *l = &HEAD_OUTPUTS(ops); - if (l->kind == LOC_MEM) { - /* load value from memory and use it as the next step - * location */ - struct op *op = append_op(ops, OP_LDW); - op->inputs = *l; - /* hard coded 3 for now, pointer is three trytes */ - set_mem(&op->outputs, next_virtual_reg(), 0, 3); - return 0; - } - - /* use the value as if it was a memory location */ - size_t w = trivial_type_width(n->type); - set_mem(l, l->reg, 0, w); - return 0; - } - - return -1; -} - -static int lower_id(struct ast_node *n, struct ops *ops) -{ - struct ast_node *d = scope_find_var(n->scope, n); - assert(d->reg); - - /* feels like a massive hack, but gives fairly readable debug output so - * I'll keep it for now. Eventually once I figure out what I'm doing - * this whole system will probably have to be rewritten anyway. */ - struct op *op = append_op(ops, OP_COMMENT); - op->string = strdup(AST_ID(n).id); - set_reg(&op->outputs, d->reg); - return 0; -} - -static int lower_ret(struct ast_node *n, struct ops *ops) -{ - if (AST_RETURN(n).expr) { - int ret = lower_op(AST_RETURN(n).expr, ops); - if (ret) - return ret; - } - - struct op *op = append_op(ops, OP_RET); - if (AST_RETURN(n).expr) - set_reg(&op->inputs, HEAD_OUTPUTS(ops).reg); - - return 0; -} - -static int lower_op(struct ast_node *n, struct ops *ops) -{ - int ret = 0; - switch (n->node_type) { - case AST_PROC: ret = lower_proc(n, ops); break; - case AST_BLOCK: ret = lower_block(n, ops); break; - case AST_VAR: ret = lower_var(n, ops); break; - case AST_CAST: ret = lower_cast(n, ops); break; - case AST_CONST: ret = lower_const(n, ops); break; - case AST_ASSIGN: ret = lower_assign(n, ops); break; - case AST_UNOP: ret = lower_unop(n, ops); break; - case AST_ID: ret = lower_id(n, ops); break; - case AST_RETURN: ret = lower_ret(n, ops); break; - default: - semantic_error(n->scope->fctx, n, "unimplemented lowering"); - return -1; - } - - if (ret) - return ret; - - return 0; -} - -static void print_locs(struct loc *locs) -{ - if (locs->kind == LOC_NONE) - return; - - printf(" ( "); - - while (locs) { - if (locs->kind == LOC_MEM) - printf("%lld(", locs->off); - - printf("r%zd", locs->reg); - - if (locs->kind == LOC_MEM) - printf(", %zd)", locs->width); - - printf(" "); - locs = locs->next; - } - - printf(")"); -} - -static void print_op(struct op *op) -{ - if (!op) - return; - - print_locs(&op->outputs); - putchar(' '); - switch (op->opcode) { - case OP_LABEL: printf("%s:", op->string); break; - case OP_COMMENT: printf("/* %s */", op->string); break; - case OP_LI: printf("li %lld", op->constant); break; - case OP_LA: printf("la %s", op->string); break; - case OP_ADD: printf("add"); break; - case OP_ADDI: printf("addi %lld", op->constant); break; - case OP_STT: printf("stt"); break; - case OP_LDT: printf("ldt"); break; - case OP_STW: printf("stw"); break; - case OP_LDW: printf("ldw"); break; - case OP_RET: printf("ret"); break; - case OP_MV: printf("mv"); break; - default: printf("unimp"); break; - } - putchar(' '); - print_locs(&op->inputs); - printf("\n"); -} - -static void print_ops(struct ops *ops) -{ - if (!ops) - return; - - struct op *base = ops->base; - while (base) { - print_op(base); - base = base->next; - } -} - -static enum opcode st_opc(struct loc *loc) -{ - 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; -} - -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 *prev = NULL; - struct op *op = ops->base; - for (; op; prev = 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) { - /* if move is between the same register, skip it */ - if (i->reg == o->reg && prev) - prev->next = op->next; - - } - 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; -} - -static int alloc_regs(struct ops *ops) -{ - /** @todo analyze lifetime, for now just convert moves to correct ldst - * etc. */ - /** @todo lifetime analysis could be done by looping over all ops, and - * when we encounter a virtual register we haven't seen before, add it - * to a list. When we encounter it used again, extend its lifetime to - * wherever we are in the function. */ - return realize_moves(ops); -} - -int lower_ops(struct scope *root, const char *fname) -{ - FILE *f = fopen(fname, "w"); - - /* main should probably be mangled here as well */ - fprintf(f, "jal x21, main\n"); - /* tell simulator to turn off (very much temp) */ - fprintf(f, "li x1, 3\n"); - fprintf(f, "csrrw mpower, x0, x1\n"); - - /* this can potentially be parallelized in the future */ - int ret = 0; - for (struct actual *a = root->actuals; a; a = a->next) { - assert(a->node); - - struct ops *ops = create_ops(); - if ((ret = lower_op(a->node, ops))) { - destroy_ops(ops); - break; - } - - printf("Lowered ops before lifetime analysis:\n"); - print_ops(ops); - - if ((ret = alloc_regs(ops))) { - destroy_ops(ops); - break; - } - - printf("Lowered ops after lifetime analysis:\n"); - print_ops(ops); - - if ((ret = print_asm(ops, f))) { - /* kind of silly as of now but eh */ - destroy_ops(ops); - break; - } - - destroy_ops(ops); - } - - fclose(f); - return ret; -} - |
