aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ek/ops.h48
-rw-r--r--src/asm.c96
-rw-r--r--src/compiler.c2
-rw-r--r--src/main.c2
-rw-r--r--src/ops.c135
5 files changed, 225 insertions, 58 deletions
diff --git a/include/ek/ops.h b/include/ek/ops.h
index 330de0c..e913d37 100644
--- a/include/ek/ops.h
+++ b/include/ek/ops.h
@@ -3,12 +3,56 @@
#include <ek/ast.h>
-struct ops;
+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;
+};
+
+enum opcode {
+ /* small subset for now */
+ OP_LI,
+ OP_LA,
+ OP_ADD,
+ OP_ADDI,
+ OP_STT,
+ OP_LDT,
+ OP_STW,
+ OP_LDW,
+ OP_MV, /* kind of 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();
void destroy_ops(struct ops *ops);
int lower_ops(struct scope *root, struct ops *ops);
-int analyze_lifetime(struct ops *ops);
+int alloc_regs(struct ops *ops);
int print_asm(struct ops *ops, const char *output);
#endif /* EK_OPS_H */
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);
diff --git a/src/main.c b/src/main.c
index 1e8901a..142a575 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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':
diff --git a/src/ops.c b/src/ops.c
index d237ae0..625f0cf 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -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;
+}