nooc

nooc programming language compiler
git clone git://git.nihaljere.xyz/nooc
Log | Files | Refs | LICENSE

commit 7fe9805c274d37befaaead9e01a7caf8e7cc75f0
parent e4ea36bfcb331ff7df5e19a6775250d97a5288c3
Author: Nihal Jere <nihal@nihaljere.xyz>
Date:   Sun,  9 Jan 2022 23:29:21 -0600

add x64 backend and do basic register allocation

A lot of cleanup can be done, but all tests pass.

The register allocation algorithm is linear scan, but implemented
naively.

To allow for multiple returns in the future, the IR has no concept
of a return value. Instead we pass pointers to where to put the
return values after all the normal parameters. Typechecking had to
be adjusted to account for this, although perhaps there's a nicer
way where typechecking can be uniform.

Diffstat:
Mir.c | 249++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mir.h | 15+++++++++++++++
Mmain.c | 56++++++++++++++++++++++++++++++++++++++++----------------
Mnooc.h | 12++++++++++--
Mparse.c | 5+++--
Mtest/add.pass.nooc | 2+-
Mtest/array.pass.nooc | 4++--
Mtest/assign_int.pass.nooc | 2+-
Dtest/decl.pass.nooc | 4----
Mtest/declare_from_ident.pass.nooc | 2+-
Mtest/exit.pass.nooc | 2+-
Mtest/exitwrite.pass.nooc | 4++--
Atest/gcd.staging.nooc | 10++++++++++
Mtest/global.pass.nooc | 4++--
Mtest/global_i16.pass.nooc | 4++--
Mtest/global_i32.pass.nooc | 4++--
Mtest/global_i8.pass.nooc | 4++--
Mtest/i32.pass.nooc | 2+-
Mtest/local_i8.pass.nooc | 4++--
Mtest/mainexit.pass.nooc | 2+-
Mtest/pointer_global.pass.nooc | 4++--
Mtest/pointer_local.pass.nooc | 4++--
Mtest/proc.pass.nooc | 4++--
Mtest/syscall_ret.pass.nooc | 6+++---
Mtest/yes.pass.nooc | 4++--
Mtype.c | 19++++++++++++++++++-
Mtype.h | 1+
Mutil.c | 8++++++--
Mx64.c | 188++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mx64.h | 2++
30 files changed, 506 insertions(+), 125 deletions(-)

diff --git a/ir.c b/ir.c @@ -15,10 +15,42 @@ extern struct types types; extern struct exprs exprs; extern struct assgns assgns; -#define PUTINS(op, val) ins = (struct instr){(op), (val)} ; array_add(out, ins) ; +#define PUTINS(op, val) ins = (struct instr){(op), (val)} ; bumpinterval(out, &ins, val) ; array_add(out, ins) ; +#define STARTINS(op, val) curi++ ; PUTINS((op), (val)) ; +#define NEWTMP tmpi++; interval.active = 0; interval.start = curi + 1; interval.end = curi + 1; array_add((&out->intervals), interval); + +extern struct target targ; static uint64_t tmpi; static uint64_t labeli; +static uint64_t curi; +static struct interval interval; +static uint16_t regs; // used register bitfield + +uint64_t out_index; + +static uint8_t +regalloc() +{ + // FIXME: can probably use ffs or something + for (uint8_t i = 0; i < 16; i++) { + if (regs & (1 << i)) + continue; + + regs |= (1 << i); + return i; + } + + die("out of registers!"); + return 0; +} + +static void +regfree(uint8_t reg) +{ + assert(regs & (1 << reg)); + regs &= ~(1 << reg); +} static void genblock(struct iproc *out, struct block *block); @@ -36,6 +68,35 @@ procindex(struct toplevel *top, struct slice *s) return 0; } +// some ops don't take temporaries, so only bump on ops that take temporaries +void +bumpinterval(struct iproc *out, struct instr *instr, size_t index) { + switch (instr->op) { + case IR_NONE: + case IR_IMM: + case IR_CALL: + case IR_RETURN: + case IR_LABEL: + case IR_CONDJUMP: + case IR_JUMP: + case IR_SIZE: + case IR_ALLOC: + break; + case IR_STORE: + case IR_LOAD: + case IR_ADD: + case IR_CEQ: + case IR_ASSIGN: + case IR_CALLARG: + case IR_IN: + case IR_EXTRA: + out->intervals.data[index].end = curi; + break; + default: + die("bumpinterval"); + } +} + static uint64_t genexpr(struct iproc *out, size_t expri) { @@ -46,8 +107,8 @@ genexpr(struct iproc *out, size_t expri) case EXPR_LIT: switch (expr->class) { case C_INT: - what = tmpi++; - PUTINS(IR_ASSIGN, what); + what = NEWTMP; + STARTINS(IR_ASSIGN, what); PUTINS(IR_SIZE, 8); // FIXME: should not be hardcoded PUTINS(IR_IMM, expr->d.v.v.i64); break; @@ -60,47 +121,51 @@ genexpr(struct iproc *out, size_t expri) struct type *type = &types.data[decl->type]; uint64_t where; if (decl->toplevel) { - where = tmpi++; - PUTINS(IR_ASSIGN, where); + where = NEWTMP; + STARTINS(IR_ASSIGN, where); PUTINS(IR_SIZE, 8); // FIXME: should not be hardcoded PUTINS(IR_IMM, decl->w.addr); - } else { - where = decl->w.index; - } - what = tmpi++; - PUTINS(IR_ASSIGN, what); - switch (type->size) { - case 1: - case 2: - case 4: - case 8: - PUTINS(IR_SIZE, type->size); - break; - default: - die("genexpr: unknown size"); - } + what = NEWTMP; + STARTINS(IR_ASSIGN, what); + switch (type->size) { + case 1: + case 2: + case 4: + case 8: + PUTINS(IR_SIZE, type->size); + break; + default: + die("genexpr: unknown size"); + } PUTINS(IR_LOAD, where); + } else if (decl->in) { + what = decl->index; + } else { + what = NEWTMP; + STARTINS(IR_ASSIGN, what); + PUTINS(IR_SIZE, 8); // FIXME: should not be hardcoded + PUTINS(IR_LOAD, decl->index); + } break; } case EXPR_BINARY: { uint64_t left = genexpr(out, expr->d.bop.left); uint64_t right = genexpr(out, expr->d.bop.right); - what = tmpi++; + what = NEWTMP; + STARTINS(IR_ASSIGN, what); + STARTINS(IR_SIZE, 8); switch (expr->d.bop.kind) { case BOP_PLUS: - PUTINS(IR_ASSIGN, what); PUTINS(IR_ADD, left); // FIXME: operand size? - PUTINS(IR_EXTRA, right); break; case BOP_EQUAL: - PUTINS(IR_ASSIGN, what); PUTINS(IR_CEQ, left); - PUTINS(IR_EXTRA, right); break; default: die("genexpr: EXPR_BINARY: unhandled binop kind"); } + PUTINS(IR_EXTRA, right); break; } case EXPR_UNARY: { @@ -111,11 +176,12 @@ genexpr(struct iproc *out, size_t expri) struct decl *decl = finddecl(operand->d.s); // a global if (decl->toplevel) { - what = tmpi++; - PUTINS(IR_ASSIGN, what); + what = NEWTMP; + STARTINS(IR_ASSIGN, what); + PUTINS(IR_SIZE, 8); // FIXME: should not be hardcoded PUTINS(IR_IMM, decl->w.addr); } else { - what = decl->w.index; + what = decl->index; } break; } @@ -125,16 +191,25 @@ genexpr(struct iproc *out, size_t expri) break; } case EXPR_FCALL: { - // what doesn't matter - what = 1; uint64_t proc = procindex(out->top, &expr->d.call.name); size_t params[20]; - assert(expr->d.call.params.len <= 20); + assert(expr->d.call.params.len < 20); for (size_t i = 0; i < expr->d.call.params.len; i++) { params[i] = genexpr(out, expr->d.call.params.data[i]); } + if (!out_index) { + // allocate memory even if we don't need to store the return value + out_index = NEWTMP; + STARTINS(IR_ASSIGN, out_index); + STARTINS(IR_SIZE, 8); // don't hardcode size + STARTINS(IR_ALLOC, 1); // don't hardcode size + } + params[expr->d.call.params.len] = out_index; + what = NEWTMP; + STARTINS(IR_ASSIGN, what); + PUTINS(IR_SIZE, 8); PUTINS(IR_CALL, proc); - for (size_t i = 0; i < expr->d.call.params.len; i++) { + for (size_t i = expr->d.call.params.len; i <= expr->d.call.params.len; i--) { PUTINS(IR_CALLARG, params[i]); } break; @@ -144,10 +219,10 @@ genexpr(struct iproc *out, size_t expri) size_t condtmp = genexpr(out, expr->d.cond.cond); size_t elselabel = labeli++; size_t endlabel = labeli++; - PUTINS(IR_CONDJUMP, elselabel); + STARTINS(IR_CONDJUMP, elselabel); PUTINS(IR_EXTRA, condtmp); genblock(out, &expr->d.cond.bif); - PUTINS(IR_JUMP, endlabel); + STARTINS(IR_JUMP, endlabel); PUTINS(IR_LABEL, elselabel); genblock(out, &expr->d.cond.belse); PUTINS(IR_LABEL, endlabel); @@ -176,8 +251,8 @@ genblock(struct iproc *out, struct block *block) case ITEM_DECL: decl = &block->decls.data[item->idx]; type = &types.data[decl->type]; - decl->w.index = tmpi++; - PUTINS(IR_ASSIGN, (decl->w.index)); + decl->index = NEWTMP; + STARTINS(IR_ASSIGN, (decl->index)); switch (type->size) { case 1: case 2: @@ -189,37 +264,47 @@ genblock(struct iproc *out, struct block *block) die("ir_genproc: unknown size"); } PUTINS(IR_ALLOC, 1); - what = genexpr(out, decl->val); - switch (type->size) { - case 1: - case 2: - case 4: - case 8: - PUTINS(IR_SIZE, type->size); - break; - default: - die("ir_genproc: unknown size"); + if (exprs.data[decl->val].kind == EXPR_FCALL) { + out_index = decl->index; + what = genexpr(out, decl->val); + } else { + what = genexpr(out, decl->val); + switch (type->size) { + case 1: + case 2: + case 4: + case 8: + STARTINS(IR_SIZE, type->size); + break; + default: + die("ir_genproc: unknown size"); + } + PUTINS(IR_STORE, what); + PUTINS(IR_EXTRA, decl->index); } - PUTINS(IR_STORE, what); - PUTINS(IR_EXTRA, decl->w.index); break; case ITEM_ASSGN: assgn = &assgns.data[item->idx]; decl = finddecl(assgn->s); type = &types.data[decl->type]; - what = genexpr(out, assgn->val); - switch (type->size) { - case 1: - case 2: - case 4: - case 8: - PUTINS(IR_SIZE, type->size); - break; - default: - die("ir_genproc: unknown size"); + if (exprs.data[assgn->val].kind == EXPR_FCALL) { + out_index = decl->index; + what = genexpr(out, assgn->val); + } else { + what = genexpr(out, assgn->val); + switch (type->size) { + case 1: + case 2: + case 4: + case 8: + STARTINS(IR_SIZE, type->size); + break; + default: + die("ir_genproc: unknown size"); + } + PUTINS(IR_STORE, what); + PUTINS(IR_EXTRA, decl->index); } - PUTINS(IR_STORE, what); - PUTINS(IR_EXTRA, decl->w.index); break; case ITEM_EXPR: genexpr(out, item->idx); @@ -236,21 +321,53 @@ genblock(struct iproc *out, struct block *block) void genproc(struct iproc *out, struct proc *proc) { - tmpi = 1; - labeli = 1; + tmpi = labeli = curi = 1; + regs = targ.reserved; struct instr ins; struct type *type; blockpush(&proc->block); - for (size_t i = 0; i < proc->in.len; i++) { - type = &types.data[proc->in.data[i].type]; - PUTINS(IR_IN, tmpi++); + // put a blank interval, since tmpi starts at 1 + array_add((&out->intervals), interval); + size_t i = 0; + + for (size_t j = 0; j < proc->in.len; j++, i++) { + struct decl *decl = finddecl(proc->in.data[j].name); + type = &types.data[proc->in.data[j].type]; + size_t what = NEWTMP; + decl->index = what; + STARTINS(IR_ASSIGN, what); PUTINS(IR_SIZE, type->size); + STARTINS(IR_IN, i); + } + + for (size_t j = 0; j < proc->out.len; j++, i++) { + struct decl *decl = finddecl(proc->out.data[j].name); + type = &types.data[proc->out.data[j].type]; + size_t what = NEWTMP; + decl->index = what; + STARTINS(IR_ASSIGN, what); + PUTINS(IR_SIZE, 8); + STARTINS(IR_IN, i); } genblock(out, &proc->block); - dumpir(out); + // FIXME: this is obviously not close to optimal + for (uint64_t i = 0; i <= curi; i++) { + for (size_t j = 0; j < out->intervals.len; j++) { + if (out->intervals.data[j].active && out->intervals.data[j].end == i - 1) { + out->intervals.data[j].active = false; + regfree(out->intervals.data[j].reg); + } + + if (!out->intervals.data[j].active && out->intervals.data[j].start == i) { + out->intervals.data[j].active = true; + out->intervals.data[j].reg = regalloc(); + } + } + } + blockpop(); } diff --git a/ir.h b/ir.h @@ -29,12 +29,25 @@ struct instr { uint64_t id; }; +struct interval { + uint64_t start; + uint64_t end; + bool active; + uint8_t reg; +}; + struct iproc { size_t len; size_t cap; + uint64_t addr; struct instr *data; struct toplevel *top; // FIXME: basically just used to pass a parameter... struct slice s; + struct { + size_t len; + size_t cap; + struct interval *data; + } intervals; }; struct iprocs { @@ -44,7 +57,9 @@ struct iprocs { struct toplevel { struct data data; + struct data text; struct iprocs code; + uint64_t entry; }; void genproc(struct iproc *out, struct proc *proc); diff --git a/main.c b/main.c @@ -10,10 +10,10 @@ #include <unistd.h> #include "array.h" -#include "x64.h" #include "nooc.h" #include "ir.h" #include "util.h" +#include "x64.h" #include "elf.h" #include "lex.h" #include "parse.h" @@ -31,6 +31,9 @@ struct assgns assgns; struct exprs exprs; extern struct types types; +extern const struct target x64_target; +struct target targ; + char *infile; // TODO: remove @@ -92,16 +95,29 @@ evalexpr(struct decl *decl) } } -size_t +void gentoplevel(struct toplevel *toplevel, struct block *block) { + char syscallname[] = "syscall0"; blockpush(block); typecheck(block); - size_t total = 0; + size_t len = 0; struct iproc iproc = { 0 }; - - iproc.s = (struct slice){7, 7, "syscall"}; - array_add((&toplevel->code), iproc); + uint64_t curaddr = TEXT_OFFSET; + + iproc.s = (struct slice){8, 8, syscallname}; + for (int i = 1; i < 8; i++) { + syscallname[7]++; + iproc.s.data = strdup(syscallname); + iproc.addr = curaddr; + len = targ.emitsyscall(NULL, i); + void *buf = xcalloc(1, len); // FIXME: unnecessary + len = targ.emitsyscall(buf, i); + array_push((&toplevel->text), buf, len); + free(buf); + array_add((&toplevel->code), iproc); + curaddr += len; + } for (int i = 0; i < block->len; i++) { struct item *item = &block->data[i]; @@ -116,17 +132,31 @@ gentoplevel(struct toplevel *toplevel, struct block *block) decl_alloc(block, decl); + // FIXME: clean this whole thing up if (expr->class == C_PROC) { + if (slice_cmplit(&decl->s, "main") == 0) { + toplevel->entry = curaddr; + } assert(expr->kind = EXPR_PROC); + blockpush(&expr->d.proc.block); + typecheck(&expr->d.proc.block); iproc = (struct iproc){ 0 }; iproc.top = toplevel; iproc.s = decl->s; + iproc.addr = curaddr; genproc(&iproc, &(expr->d.proc)); array_add((&toplevel->code), iproc); + len = emitproc(NULL, &iproc); + void *buf = xcalloc(1, len); // FIXME: unnecessary + len = emitproc(buf, &iproc); + curaddr += len; + array_push((&toplevel->text), buf, len); + free(buf); + + blockpop(); } else { evalexpr(decl); } - decl->declared = true; break; } default: @@ -135,8 +165,6 @@ gentoplevel(struct toplevel *toplevel, struct block *block) } blockpop(); - - return total; } struct stat statbuf; @@ -144,6 +172,7 @@ struct stat statbuf; int main(int argc, char *argv[]) { + targ = x64_target; if (argc < 3) { fprintf(stderr, "not enough args\n"); return 1; @@ -185,14 +214,9 @@ main(int argc, char *argv[]) return 1; } - blockpush(&items); - struct decl *main = finddecl((struct slice){4, 4, "main"}); - if (main == NULL) { - fprintf(stderr, "main function not found\n"); - return 1; - } - blockpop(); munmap(addr, statbuf.st_size); + elf(toplevel.entry, toplevel.text.data, toplevel.text.len, data_seg.data, data_seg.len, out); + fclose(out); } diff --git a/nooc.h b/nooc.h @@ -120,10 +120,13 @@ struct decl { struct slice s; size_t type; size_t val; // struct exprs - bool declared; + bool in; + bool out; bool toplevel; + uint64_t index; + union { - uint64_t index; + int64_t offset; uint64_t addr; } w; struct token *start; @@ -245,3 +248,8 @@ struct exprs { size_t len; struct expr *data; }; + +struct target { + uint32_t reserved; + size_t (*emitsyscall)(char *buf, uint8_t paramcount); +}; diff --git a/parse.c b/parse.c @@ -174,6 +174,7 @@ parseexpr(struct block *block) for (int i = expr.d.proc.in.len - 1; i >= 0; i--) { decl.s = expr.d.proc.in.data[i].name; decl.type = expr.d.proc.in.data[i].type; + decl.in = true; type = &types.data[decl.type]; offset += type->size; array_add((&expr.d.proc.block.decls), decl); @@ -181,8 +182,8 @@ parseexpr(struct block *block) for (size_t i = 0; i < expr.d.proc.out.len; i++) { decl.s = expr.d.proc.out.data[i].name; - decl.type = expr.d.proc.out.data[i].type; - decl.declared = true; + decl.type = typeref(expr.d.proc.out.data[i].type); + decl.in = decl.out = true; type = &types.data[decl.type]; offset += type->size; array_add((&expr.d.proc.block.decls), decl); diff --git a/test/add.pass.nooc b/test/add.pass.nooc @@ -1,5 +1,5 @@ let main proc() = proc() { let a i64 = 10 a = + a 10 - syscall(60, 0) + syscall2(60, 0) } \ No newline at end of file diff --git a/test/array.pass.nooc b/test/array.pass.nooc @@ -1,6 +1,6 @@ let arr [5]i8 = "sup!\n" let main proc() = proc() { - syscall(1, 1, $arr, 5) - syscall(60, 0) + syscall4(1, 1, $arr, 5) + syscall2(60, 0) } diff --git a/test/assign_int.pass.nooc b/test/assign_int.pass.nooc @@ -2,5 +2,5 @@ let main proc() = proc() { let i i64 = 10 i = 5 - syscall(60, 0) + syscall2(60, 0) } diff --git a/test/decl.pass.nooc b/test/decl.pass.nooc @@ -1,3 +0,0 @@ -let main proc() = proc() { - let foo i64 = 0 -}- \ No newline at end of file diff --git a/test/declare_from_ident.pass.nooc b/test/declare_from_ident.pass.nooc @@ -2,5 +2,5 @@ let a i64 = 10 let main proc() = proc() { let b i64 = a - syscall(60, 0) + syscall2(60, 0) } diff --git a/test/exit.pass.nooc b/test/exit.pass.nooc @@ -1,5 +1,5 @@ let exit proc(i64) = proc(code i64) { - syscall(60, code) + syscall2(60, code) } let main proc() = proc() { diff --git a/test/exitwrite.pass.nooc b/test/exitwrite.pass.nooc @@ -2,13 +2,13 @@ let s [6]i8 = "hello\n" let write proc(i64, $i8, i64) (i64) = proc(fd i64, data $i8, len i64) (out i64) { let a i64 = 0 - out = syscall(1, fd, data, len) + out = syscall4(1, fd, data, len) return } let exit proc(i64) = proc(code i64) { - syscall(60, code) + syscall2(60, code) } let main proc() = proc() { diff --git a/test/gcd.staging.nooc b/test/gcd.staging.nooc @@ -0,0 +1,10 @@ +let gcd proc(i64, i64) (i64) = proc(a i64, b i64) (out i64) { + let c i64 = 0 + loop != b 0 { + out = b + b = a % b + a = out + } + + return +} diff --git a/test/global.pass.nooc b/test/global.pass.nooc @@ -2,8 +2,8 @@ let a i32 = 10 let main proc() = proc() { if = a 10 { - syscall(60, 0) + syscall2(60, 0) } else { - syscall(60, 1) + syscall2(60, 1) } } diff --git a/test/global_i16.pass.nooc b/test/global_i16.pass.nooc @@ -2,8 +2,8 @@ let a i16 = 300 let main proc() = proc() { if = a 300 { - syscall(60, 0) + syscall2(60, 0) } else { - syscall(60, 1) + syscall2(60, 1) } } diff --git a/test/global_i32.pass.nooc b/test/global_i32.pass.nooc @@ -2,8 +2,8 @@ let a i32 = 65540 let main proc() = proc() { if = a 65540 { - syscall(60, 0) + syscall2(60, 0) } else { - syscall(60, 1) + syscall2(60, 1) } } diff --git a/test/global_i8.pass.nooc b/test/global_i8.pass.nooc @@ -2,8 +2,8 @@ let a i8 = 1 let main proc() = proc() { if = a 1 { - syscall(60, 0) + syscall2(60, 0) } else { - syscall(60, 1) + syscall2(60, 1) } } diff --git a/test/i32.pass.nooc b/test/i32.pass.nooc @@ -2,5 +2,5 @@ let main proc() = proc() { let a i64 = 100 let b i32 = 0 - syscall(60, b) + syscall2(60, b) } diff --git a/test/local_i8.pass.nooc b/test/local_i8.pass.nooc @@ -1,8 +1,8 @@ let main proc() = proc() { let a i8 = 1 if = a 1 { - syscall(60, 0) + syscall2(60, 0) } else { - syscall(60, 1) + syscall2(60, 1) } } diff --git a/test/mainexit.pass.nooc b/test/mainexit.pass.nooc @@ -1,4 +1,4 @@ let main proc() = proc() { - syscall(60, 0) + syscall2(60, 0) } diff --git a/test/pointer_global.pass.nooc b/test/pointer_global.pass.nooc @@ -1,6 +1,6 @@ let c i8 = 65 let main proc() = proc() { - syscall(1, 1, $c, 1) - syscall(60, 0) + syscall4(1, 1, $c, 1) + syscall2(60, 0) } diff --git a/test/pointer_local.pass.nooc b/test/pointer_local.pass.nooc @@ -1,5 +1,5 @@ let main proc() = proc() { let c i8 = 65 - syscall(1, 1, $c, 1) - syscall(60, 0) + syscall4(1, 1, $c, 1) + syscall2(60, 0) } diff --git a/test/proc.pass.nooc b/test/proc.pass.nooc @@ -1,11 +1,11 @@ let s [6]i8 = "hello\n" let hello proc() = proc() { - syscall(1, 1, $s, 6) + syscall4(1, 1, $s, 6) return } let main proc() = proc() { hello() - syscall(60, 0) + syscall2(60, 0) } diff --git a/test/syscall_ret.pass.nooc b/test/syscall_ret.pass.nooc @@ -1,10 +1,10 @@ let s [12]i8 = "syscall_ret\n" let main proc() = proc() { - let ret i64 = syscall(1, 1, $s, 11) + let ret i64 = syscall4(1, 1, $s, 11) if = ret 11 { - syscall(60, 0) + syscall2(60, 0) } else { - syscall(60, 1) + syscall2(60, 1) } } diff --git a/test/yes.pass.nooc b/test/yes.pass.nooc @@ -4,6 +4,6 @@ let main proc() = proc() { let write i64 = 1 let stdout i64 = 1 let count i64 = 0 - syscall(write, stdout, $y, 2) - syscall(60, 0) + syscall4(write, stdout, $y, 2) + syscall2(60, 0) } diff --git a/type.c b/type.c @@ -202,6 +202,18 @@ typecompat(size_t typei, size_t expri) } } +size_t +typeref(size_t typei) +{ + struct type ref = { + .class = TYPE_REF, + .size = 8, + .d.subtype = typei + }; + + return type_put(&ref); +} + static void typecheckcall(struct expr *expr) { @@ -269,7 +281,12 @@ typecheck(struct block *block) error(assgn->start->line, assgn->start->col, "typecheck: unknown name '%.*s'", assgn->s.len, assgn->s.data); typecheckexpr(assgn->val); - typecompat(decl->type, assgn->val); + if (decl->out) { + struct type *type = &types.data[decl->type]; + typecompat(type->d.subtype, assgn->val); + } else { + typecompat(decl->type, assgn->val); + } break; case ITEM_DECL: decl = &block->decls.data[item->idx]; diff --git a/type.h b/type.h @@ -1,4 +1,5 @@ size_t type_get(uint8_t hash[16]); size_t type_put(struct type *type); void inittypes(); +size_t typeref(size_t typei); void typecheck(struct block *block); diff --git a/util.c b/util.c @@ -139,10 +139,10 @@ dumpir(struct iproc *instrs) switch (instr->op) { case IR_IN: - fprintf(stderr, "in %%%lu\n", instr->id); + fprintf(stderr, "in %lu\n", instr->id); break; case IR_SIZE: - fprintf(stderr, "size %lu\n", instr->id); + fprintf(stderr, "s%lu ", instr->id); break; case IR_IMM: fprintf(stderr, "imm %lu\n", instr->id); @@ -196,6 +196,10 @@ dumpir(struct iproc *instrs) if (callarg) putc('\n', stderr); putc('\n', stderr); + + for (size_t i = 0; i < instrs->intervals.len; i++) { + fprintf(stderr, "%lu: %lu to %lu\n", i, instrs->intervals.data[i].start, instrs->intervals.data[i].end); + } } int diff --git a/x64.c b/x64.c @@ -4,8 +4,8 @@ #include <string.h> #include "nooc.h" -#include "x64.h" #include "ir.h" +#include "x64.h" #include "util.h" enum rex { @@ -27,6 +27,192 @@ enum mod { char abi_arg[] = {RAX, RDI, RSI, RDX, R10, R8, R9}; unsigned short used_reg; +#define NEXT ins++; assert(ins <= end); + +static size_t +emitsyscall(char *buf, uint8_t paramcount) +{ + assert(paramcount < 8); + size_t total = 0; + total += push_r64(buf ? buf + total : NULL, RBP); + total += mov_r64_r64(buf ? buf + total : NULL, RBP, RSP); + + for (size_t i = 0; i < paramcount; i++) { + total += push_r64(buf ? buf + total : NULL, abi_arg[i]); + total += mov_disp8_r64_m64(buf ? buf + total : NULL, abi_arg[i], RBP, 8*i + 16); + } + + if (buf) { + *(buf + total++) = 0x0f; + *(buf + total++) = 0x05; + } else { + total += 2; + } + + total += mov_disp8_r64_m64(buf ? buf + total : NULL, RDI, RBP, 8*paramcount + 16); + total += mov_mr64_r64(buf ? buf + total : NULL, RDI, RAX); + + for (size_t i = paramcount - 1; i < paramcount; i--) { + total += pop_r64(buf ? buf + total : NULL, abi_arg[i]); + } + + total += pop_r64(buf ? buf + total : NULL, RBP); + total += ret(buf ? buf + total : NULL); + + return total; +} + +const struct target x64_target = { + .reserved = (1 << RSP) | (1 << RBP), + .emitsyscall = emitsyscall +}; + +size_t +emitblock(char *buf, struct iproc *proc, struct instr *start, struct instr *end, uint64_t end_label) +{ + struct instr *ins = start ? start : proc->data; + end = end ? end : &proc->data[proc->len]; + + uint64_t dest, src, size, count, tmp, label; + uint64_t localalloc = 0; + + size_t total = 0; + if (!start) { + total += push_r64(buf ? buf + total : NULL, RBP); + total += mov_r64_r64(buf ? buf + total : NULL, RBP, RSP); + } + + while (ins < end) { + switch (ins->op) { + // FIXME: we don't handle jumps backward yet + case IR_JUMP: + total += jmp(buf ? buf + total : NULL, emitblock(NULL, proc, ins + 1, end, ins->id)); + NEXT; + break; + case IR_RETURN: + total += add_r64_imm(buf ? buf + total : NULL, RSP, localalloc); + total += pop_r64(buf ? buf + total : NULL, RBP); + total += ret(buf ? buf + total : NULL); + NEXT; + break; + case IR_SIZE: + size = ins->id; + NEXT; + + switch (ins->op) { + case IR_STORE: + src = proc->intervals.data[ins->id].reg; + NEXT; + assert(ins->op == IR_EXTRA); + total += mov_mr64_r64(buf ? buf + total : NULL, proc->intervals.data[ins->id].reg, src); + NEXT; + break; + default: + die("x64 emitblock: unhandled size instruction"); + } + break; + case IR_ASSIGN: + tmp = ins->id; + dest = proc->intervals.data[ins->id].reg; + NEXT; + + assert(ins->op == IR_SIZE); + size = ins->id; + NEXT; + + switch (ins->op) { + case IR_CEQ: + total += mov_r64_r64(buf ? buf + total : NULL, dest, proc->intervals.data[ins->id].reg); + NEXT; + total += cmp_r64_r64(buf ? buf + total : NULL, dest, proc->intervals.data[ins->id].reg); + NEXT; + if (ins->op == IR_CONDJUMP) { + label = ins->id; + NEXT; + assert(ins->op == IR_EXTRA); + if (ins->id == tmp) { + total += jne(buf ? buf + total : NULL, emitblock(NULL, proc, ins + 1, end, label)); + } + NEXT; + } + break; + case IR_ADD: + total += mov_r64_r64(buf ? buf + total : NULL, dest, proc->intervals.data[ins->id].reg); + NEXT; + total += add_r64_r64(buf ? buf + total : NULL, dest, proc->intervals.data[ins->id].reg); + NEXT; + break; + case IR_IMM: + total += mov_r64_imm(buf ? buf + total : NULL, dest, ins->id); + NEXT; + break; + case IR_IN: + total += mov_disp8_r64_m64(buf ? buf + total : NULL, dest, RBP, 8*ins->id + 16); + NEXT; + break; + case IR_LOAD: + total += mov_r64_mr64(buf ? buf + total : NULL, dest, proc->intervals.data[ins->id].reg); + NEXT; + break; + case IR_ALLOC: + total += mov_r64_r64(buf ? buf + total : NULL, dest, RSP); + total += sub_r64_imm(buf ? buf + total : NULL, RSP, 8); // FIXME: hardcoding + localalloc += 8; + NEXT; + break; + case IR_CALL: + count = 0; + total += mov_r64_imm(buf ? buf + total : NULL, dest, proc->top->code.data[ins->id].addr); + NEXT; + + for (int i = 0; i < 16; i++) { + total += push_r64(buf ? buf + total : NULL, i); + } + + while (ins->op == IR_CALLARG) { + count++; + total += push_r64(buf ? buf + total : NULL, proc->intervals.data[ins->id].reg); + NEXT; + } + total += call(buf ? buf + total : NULL, dest); + // FIXME: this won't work with non-64-bit things + total += add_r64_imm(buf ? buf + total : NULL, RSP, 8*count); + for (int i = 15; i >= 0; i--) { + total += pop_r64(buf ? buf + total : NULL, i); + } + break; + default: + die("x64 emitblock: unhandled assign instruction"); + } + break; + case IR_LABEL: + if (ins->id == end_label) + goto done; + + NEXT; + break; + case IR_STORE: + case IR_IMM: + case IR_ALLOC: + die("x64 emitblock: invalid start of instruction"); + default: + die("x64 emitproc: unknown instruction"); + } + } + +done: + return total; +} + +// FIXME: use array_push +size_t +emitproc(char *buf, struct iproc *proc) +{ + return emitblock(buf, proc, NULL, NULL, 0); +} + +#undef NEXT + void clearreg() { diff --git a/x64.h b/x64.h @@ -63,3 +63,5 @@ size_t call(char *buf, enum reg reg); size_t ret(char *buf); size_t push_r64(char *buf, enum reg reg); size_t pop_r64(char *buf, enum reg reg); + +size_t emitproc(char *buf, struct iproc *proc);