diff --git a/examples/sysir/frontend.janet b/examples/sysir/frontend.janet index dfdde5d8..0b1a3349 100644 --- a/examples/sysir/frontend.janet +++ b/examples/sysir/frontend.janet @@ -367,5 +367,5 @@ #### (compile1 myprog) -#(dump) +(dump) (dumpx64) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index dd948e28..b48f1e93 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -2226,8 +2226,8 @@ (defn thaw `Thaw an object (make it mutable) and do a deep copy, making - child value also mutable. Closures, fibers, and abstract - types will not be recursively thawed, but all other types will` + child values also mutable. Closures, fibers, and abstract + types will not be recursively thawed, but all other types will.` [ds] (case (type ds) :array (walk-ind thaw ds) diff --git a/src/core/sysir.c b/src/core/sysir.c index 0db31a84..ed50bb4b 100644 --- a/src/core/sysir.c +++ b/src/core/sysir.c @@ -27,7 +27,7 @@ */ /* TODO - * [ ] named fields (for debugging mostly) + * [ ] encode constants directly in 3 address codes - makes codegen easier * [x] named registers and types * [x] better type errors (perhaps mostly for compiler debugging - full type system goes on top) * [ ] x86/x64 machine code target @@ -87,7 +87,7 @@ static const JanetPrimName prim_names[] = { {"void", JANET_PRIM_VOID}, }; -static const char *prim_to_prim_name[] = { +const char *prim_to_prim_name[] = { "u8", "s8", "u16", @@ -1757,6 +1757,9 @@ static int sysir_gcmark(void *p, size_t s) { if (ir->link_name != NULL) { janet_mark(janet_wrap_string(ir->link_name)); } + janet_mark(janet_wrap_table(ir->labels)); + janet_mark(janet_wrap_table(ir->register_name_lookup)); + janet_mark(janet_wrap_abstract(ir->linkage)); return 0; } diff --git a/src/core/sysir.h b/src/core/sysir.h index 0b25605b..524c9c22 100644 --- a/src/core/sysir.h +++ b/src/core/sysir.h @@ -126,6 +126,11 @@ typedef struct { uint32_t type; } JanetSysTypeField; +/* Allow read arguments to be constants to allow + * encoding immediates. This makes codegen easier. */ +#define JANET_SYS_MAX_OPERAND 0x7FFFFFFFU +#define JANET_SYS_CONSTANT_PREFIX 0x80000000U + typedef struct { JanetSysOp opcode; union { @@ -251,10 +256,19 @@ typedef struct { JANET_SYS_SPILL_BOTH } spills[3]; uint32_t regs[3]; + uint32_t stack_offsets[3]; + uint32_t stack_sizes[3]; } JanetSysSpill; +/* Delay alignment info for the most part to the lowering phase */ +typedef struct { + uint32_t size; + uint32_t alignment; +} JanetSysTypeLayout; + /* Keep track of names for each instruction */ extern const char *janet_sysop_names[]; +extern const char *prim_to_prim_name[]; /* Lowering */ void janet_sys_ir_lower_to_ir(JanetSysIRLinkage *linkage, JanetArray *into); diff --git a/src/core/sysir_x86.c b/src/core/sysir_x86.c index f9e09653..b2f46c0a 100644 --- a/src/core/sysir_x86.c +++ b/src/core/sysir_x86.c @@ -25,17 +25,64 @@ #include #include "sysir.h" #include "vector.h" +#include "util.h" #endif -/* - * Wrap stuff up in a context struct - */ +static const char *register_names[] = { + "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" +}; -static uint32_t v2reg(JanetTable *assignments, uint32_t var) { - return (uint32_t) janet_unwrap_number(janet_table_get(assignments, janet_wrap_number(var))); +/* Get the layout for types */ +JanetSysTypeLayout get_x64layout(JanetSysTypeInfo info) { + JanetSysTypeLayout layout; + switch (info.prim) { + default: + layout.size = 1; + layout.alignment = 1; + break; + case JANET_PRIM_S8: + case JANET_PRIM_U8: + case JANET_PRIM_BOOLEAN: + layout.size = 1; + layout.alignment = 1; + break; + case JANET_PRIM_S16: + case JANET_PRIM_U16: + layout.size = 2; + layout.alignment = 2; + break; + case JANET_PRIM_S32: + case JANET_PRIM_U32: + layout.size = 4; + layout.alignment = 4; + break; + case JANET_PRIM_U64: + case JANET_PRIM_S64: + case JANET_PRIM_POINTER: + layout.size = 8; + layout.alignment = 8; + break; + case JANET_PRIM_F32: + case JANET_PRIM_F64: + layout.size = 8; + layout.alignment = 8; + break; + } + return layout; } -JanetSysSpill *assign_registers(JanetSysIR *ir, JanetTable *assignments, +static uint32_t v2reg_dflt(JanetTable *assignments, uint32_t var, uint32_t dflt) { + Janet check = janet_table_get(assignments, janet_wrap_number(var)); + if (janet_checktype(check, JANET_NUMBER)) { + return (uint32_t) janet_unwrap_number(check); + } + return dflt; +} + +JanetSysSpill *assign_registers(JanetSysIR *ir, + JanetSysTypeLayout *layouts, + JanetTable *assignments, uint32_t max_reg) { /* simplest register assignment algorithm - first n variables @@ -48,13 +95,19 @@ JanetSysSpill *assign_registers(JanetSysIR *ir, JanetTable *assignments, for (uint32_t i = 0; i < ir->register_count; i++) { if (i < max_reg) { janet_table_put(assignments, janet_wrap_number(i), janet_wrap_number(i)); - } else { - janet_table_put(assignments, janet_wrap_number(i), janet_wrap_number(max_reg)); } } - // TODO - keep track of where we spill to. Simple idea would be to assign each variable - // a stack location. + /* Assign all slots a stack location */ + /* TODO - be smarter about this */ + uint32_t *stack_locations = janet_smalloc(ir->register_count * sizeof(uint32_t)); + uint32_t next_loc = 0; + for (uint32_t i = 0; i < ir->register_count; i++) { + JanetSysTypeLayout layout = layouts[i]; + next_loc = (next_loc + layout.alignment - 1) / layout.alignment * layout.alignment; + stack_locations[i] = next_loc; + next_loc += layout.size; + } /* Generate spills. Spills occur iff using the temporary register (max_reg) */ JanetSysSpill *spills = NULL; @@ -89,20 +142,26 @@ JanetSysSpill *assign_registers(JanetSysIR *ir, JanetTable *assignments, case JANET_SYSOP_GTE: case JANET_SYSOP_POINTER_ADD: case JANET_SYSOP_POINTER_SUBTRACT: - rega = v2reg(assignments, instruction.three.dest); - regb = v2reg(assignments, instruction.three.lhs); - regc = v2reg(assignments, instruction.three.rhs); - if (rega == max_reg) { + rega = v2reg_dflt(assignments, instruction.three.dest, max_reg); + regb = v2reg_dflt(assignments, instruction.three.lhs, max_reg + 1); + regc = v2reg_dflt(assignments, instruction.three.rhs, max_reg + 2); + spill.regs[0] = rega; + spill.regs[1] = regb; + spill.regs[2] = regc; + if (rega >= max_reg) { spill.spills[0] = JANET_SYS_SPILL_WRITE; - spill.regs[0] = instruction.three.dest; + spill.stack_offsets[0] = stack_locations[instruction.three.dest]; + spill.stack_sizes[0] = layouts[instruction.three.dest].size; } - if (regb == max_reg) { + if (regb >= max_reg) { spill.spills[1] = JANET_SYS_SPILL_READ; - spill.regs[1] = instruction.three.lhs; + spill.stack_offsets[1] = stack_locations[instruction.three.lhs]; + spill.stack_sizes[1] = layouts[instruction.three.lhs].size; } - if (regc == max_reg) { + if (regc >= max_reg) { spill.spills[2] = JANET_SYS_SPILL_READ; - spill.regs[2] = instruction.three.rhs; + spill.stack_offsets[2] = stack_locations[instruction.three.rhs]; + spill.stack_sizes[2] = layouts[instruction.three.rhs].size; } break; @@ -110,41 +169,51 @@ JanetSysSpill *assign_registers(JanetSysIR *ir, JanetTable *assignments, case JANET_SYSOP_MOVE: case JANET_SYSOP_CAST: case JANET_SYSOP_BNOT: - rega = v2reg(assignments, instruction.two.dest); - regb = v2reg(assignments, instruction.two.src); - if (rega == max_reg) { + rega = v2reg_dflt(assignments, instruction.two.dest, max_reg); + regb = v2reg_dflt(assignments, instruction.two.src, max_reg + 1); + spill.regs[0] = rega; + spill.regs[1] = regb; + if (rega >= max_reg) { spill.spills[0] = JANET_SYS_SPILL_WRITE; - spill.regs[0] = instruction.two.dest; + spill.stack_offsets[0] = stack_locations[instruction.two.dest]; + spill.stack_sizes[0] = layouts[instruction.two.dest].size; } - if (regb == max_reg) { + if (regb >= max_reg) { spill.spills[1] = JANET_SYS_SPILL_READ; - spill.regs[1] = instruction.two.src; + spill.stack_offsets[1] = stack_locations[instruction.two.src]; + spill.stack_sizes[1] = layouts[instruction.two.src].size; } break; /* branch COND */ case JANET_SYSOP_BRANCH: case JANET_SYSOP_BRANCH_NOT: - rega = v2reg(assignments, instruction.branch.cond); - if (rega == max_reg) { + rega = v2reg_dflt(assignments, instruction.branch.cond, max_reg); + spill.regs[0] = rega; + if (rega >= max_reg) { spill.spills[0] = JANET_SYS_SPILL_READ; - spill.regs[0] = instruction.branch.cond; + spill.stack_offsets[0] = stack_locations[instruction.branch.cond]; + spill.stack_sizes[0] = layouts[instruction.branch.cond].size; } break; case JANET_SYSOP_CONSTANT: - rega = v2reg(assignments, instruction.constant.dest); - if (rega == max_reg) { + rega = v2reg_dflt(assignments, instruction.constant.dest, max_reg); + spill.regs[0] = rega; + if (rega >= max_reg) { spill.spills[0] = JANET_SYS_SPILL_WRITE; - spill.regs[0] = instruction.constant.dest; + spill.stack_offsets[0] = stack_locations[instruction.constant.dest]; + spill.stack_sizes[0] = layouts[instruction.constant.dest].size; } break; case JANET_SYSOP_RETURN: - rega = v2reg(assignments, instruction.one.src); - if (rega == max_reg) { + rega = v2reg_dflt(assignments, instruction.one.src, max_reg); + spill.regs[0] = rega; + if (rega >= max_reg) { spill.spills[0] = JANET_SYS_SPILL_READ; - spill.regs[0] = instruction.one.src; + spill.stack_offsets[0] = stack_locations[instruction.one.src]; + spill.stack_sizes[0] = layouts[instruction.one.src].size; } break; @@ -152,10 +221,12 @@ JanetSysSpill *assign_registers(JanetSysIR *ir, JanetTable *assignments, case JANET_SYSOP_ARG: for (int j = 0; j < 3; j++) { uint32_t var = instruction.arg.args[j]; - rega = v2reg(assignments, var); - if (rega == max_reg) { + rega = v2reg_dflt(assignments, var, 0); + spill.regs[j] = rega; + if (rega >= max_reg) { /* Unused elements must be 0 */ spill.spills[j] = JANET_SYS_SPILL_READ; - spill.regs[j] = var; + spill.stack_offsets[j] = stack_locations[instruction.arg.args[j]]; + spill.stack_sizes[j] = layouts[instruction.arg.args[j]].size; } } break; @@ -163,46 +234,88 @@ JanetSysSpill *assign_registers(JanetSysIR *ir, JanetTable *assignments, /* Variable arg */ case JANET_SYSOP_CALL: case JANET_SYSOP_CALLK: + /* TODO */ break; } janet_v_push(spills, spill); } + janet_sfree(layouts); + janet_sfree(stack_locations); + return spills; } -static void do_spills(JanetBuffer *buffer, JanetSysSpill *spills, uint32_t index) { +typedef struct { + uint32_t temps[3]; +} JanetTempRegs; + +static JanetTempRegs do_spills_read(JanetBuffer *buffer, JanetSysSpill *spills, uint32_t index) { + JanetSysSpill spill = spills[index]; + JanetTempRegs temps; + for (int spi = 0; spi < 3; spi++) { + uint32_t reg = spill.regs[spi]; + temps.temps[spi] = reg; + if (spill.spills[spi] == JANET_SYS_SPILL_READ || spill.spills[spi] == JANET_SYS_SPILL_BOTH) { + // emit load + uint32_t x = spill.stack_offsets[spi]; + uint32_t s = spill.stack_sizes[spi]; + janet_formatb(buffer, "load%u r%u from stack[%u] ; SPILL\n", s, reg, x); + } + } + return temps; +} + +static void do_spills_write(JanetBuffer *buffer, JanetSysSpill *spills, uint32_t index) { JanetSysSpill spill = spills[index]; for (int spi = 0; spi < 3; spi++) { if (spill.spills[spi] == JANET_SYS_SPILL_WRITE || spill.spills[spi] == JANET_SYS_SPILL_BOTH) { // emit store uint32_t reg = spill.regs[spi]; - void *x = (void *) 0x123456; - janet_formatb(buffer, "store r%u to %v ; SPILL\n", reg, janet_wrap_pointer(x)); - } - if (spill.spills[spi] == JANET_SYS_SPILL_READ || spill.spills[spi] == JANET_SYS_SPILL_BOTH) { - // emit load - uint32_t reg = spill.regs[spi]; - void *x = (void *) 0x123456; - janet_formatb(buffer, "load r%u from %v ; SPILL\n", reg, janet_wrap_pointer(x)); + uint32_t x = spill.stack_offsets[spi]; + uint32_t s = spill.stack_sizes[spi]; + janet_formatb(buffer, "store%u r%u to stack[%u] ; SPILL\n", s, reg, x); } } } void janet_sys_ir_lower_to_x64(JanetSysIRLinkage *linkage, JanetBuffer *buffer) { + /* For now, emit assembly for nasm. Eventually an assembler for use with Janet would be good. */ + + JanetSysTypeLayout *all_layouts = janet_smalloc(linkage->type_def_count * sizeof(JanetSysTypeLayout)); + for (uint32_t i = 0; i < linkage->type_def_count; i++) { + all_layouts[i] = get_x64layout(linkage->type_defs[i]); + } + + /* Emit prelude */ + janet_formatb(buffer, "bits 64\ndefault rel\n\n"); + janet_formatb(buffer, "segment .text\n"); /* Do register allocation */ for (int32_t i = 0; i < linkage->ir_ordered->count; i++) { JanetSysIR *ir = janet_unwrap_pointer(linkage->ir_ordered->data[i]); JanetTable *assignments = janet_table(0); - /* 16 total 64 bit registers - 3 temp */ - JanetSysSpill *spills = assign_registers(ir, assignments, 113); + JanetTempRegs temps; + /* Get type layouts */ + JanetSysTypeLayout *layouts = janet_smalloc(ir->register_count * sizeof(JanetSysTypeLayout)); + for (uint32_t i = 0; i < ir->register_count; i++) { + layouts[i] = all_layouts[ir->types[i]]; + } + JanetSysSpill *spills = assign_registers(ir, layouts, assignments, 13); + + /* Allow combining compare + branch instructions more easily */ + int skip_branch = 0; + + /* Emit constant strings */ + for (int32_t j = 0; j < ir->constant_count; j++) { + janet_formatb(buffer, ".CONST%u:\n .string %p\n", j, ir->constants[j]); + } /* Emit prelude */ if (ir->link_name != NULL) { - janet_formatb(buffer, ".%s\n", ir->link_name); + janet_formatb(buffer, "\n%s:\n", ir->link_name); } else { - janet_formatb(buffer, "._section_%d\n", i); + janet_formatb(buffer, "\n_section_%d:\n", i); } for (uint32_t j = 0; j < ir->instruction_count; j++) { JanetSysInstruction instruction = ir->instructions[j]; @@ -225,34 +338,34 @@ void janet_sys_ir_lower_to_x64(JanetSysIRLinkage *linkage, JanetBuffer *buffer) case JANET_SYSOP_SUBTRACT: case JANET_SYSOP_MULTIPLY: case JANET_SYSOP_DIVIDE: - do_spills(buffer, spills, j); + temps = do_spills_read(buffer, spills, j); janet_formatb(buffer, "r%u = %s r%u, r%u\n", - v2reg(assignments, instruction.three.dest), + temps.temps[0], janet_sysop_names[instruction.opcode], - v2reg(assignments, instruction.three.lhs), - v2reg(assignments, instruction.three.rhs)); + temps.temps[1], + temps.temps[2]); + do_spills_write(buffer, spills, j); break; case JANET_SYSOP_MOVE: - do_spills(buffer, spills, j); - janet_formatb(buffer, "r%u = r%u\n", - v2reg(assignments, instruction.two.dest), - v2reg(assignments, instruction.two.src)); + temps = do_spills_read(buffer, spills, j); + //janet_formatb(buffer, "r%u = r%u\n", temps.temps[0], temps.temps[1]); + janet_formatb(buffer, "mov %s, %s\n", + register_names[temps.temps[0]], + register_names[temps.temps[1]]); + do_spills_write(buffer, spills, j); break; case JANET_SYSOP_RETURN: - do_spills(buffer, spills, j); - janet_formatb(buffer, "return r%u\n", - v2reg(assignments, instruction.one.src)); + temps = do_spills_read(buffer, spills, j); + //janet_formatb(buffer, "return r%u\n", temps.temps[0]); + janet_formatb(buffer, "leave\n mov %s, rax\nret\n", register_names[temps.temps[0]]); break; case JANET_SYSOP_CONSTANT: - do_spills(buffer, spills, j); - janet_formatb(buffer, "r%u = constant $%v\n", - v2reg(assignments, instruction.constant.dest), - ir->constants[instruction.constant.constant]); + temps = do_spills_read(buffer, spills, j); + janet_formatb(buffer, "r%u = constant $%v\n", temps.temps[0], ir->constants[instruction.constant.constant]); + do_spills_write(buffer, spills, j); break; case JANET_SYSOP_LABEL: - do_spills(buffer, spills, j); - janet_formatb(buffer, "label_%u:\n", - v2reg(assignments, instruction.label.id)); + janet_formatb(buffer, "label_%u:\n", instruction.label.id); break; case JANET_SYSOP_EQ: case JANET_SYSOP_NEQ: @@ -260,19 +373,74 @@ void janet_sys_ir_lower_to_x64(JanetSysIRLinkage *linkage, JanetBuffer *buffer) case JANET_SYSOP_LTE: case JANET_SYSOP_GT: case JANET_SYSOP_GTE: - do_spills(buffer, spills, j); - janet_formatb(buffer, "r%u = %s r%u, r%u\n", - v2reg(assignments, instruction.three.dest), - janet_sysop_names[instruction.opcode], - v2reg(assignments, instruction.three.lhs), - v2reg(assignments, instruction.three.rhs)); + temps = do_spills_read(buffer, spills, j); + JanetSysInstruction nexti = ir->instructions[j + 1]; + /* Combine compare and branch into one instruction */ + /* TODO - handle when lhs or rhs is 0 */ + /* TODO - handle floats */ + janet_formatb(buffer, "cmp %s, %s\n", register_names[temps.temps[1]], register_names[temps.temps[2]]); + if ((nexti.opcode == JANET_SYSOP_BRANCH || + nexti.opcode == JANET_SYSOP_BRANCH_NOT) + && nexti.branch.cond == instruction.three.dest) { + skip_branch = 1; + int invert = nexti.opcode == JANET_SYSOP_BRANCH_NOT; + if (instruction.opcode == JANET_SYSOP_EQ) { + janet_formatb(buffer, "%s label_%u\n", invert ? "jne" : "je", nexti.branch.to); + } else if (instruction.opcode == JANET_SYSOP_NEQ) { + janet_formatb(buffer, "%s label_%u\n", invert ? "je" : "jne", nexti.branch.to); + } else if (instruction.opcode == JANET_SYSOP_GT) { + janet_formatb(buffer, "%s label_%u\n", invert ? "jle" : "jg", nexti.branch.to); + } else if (instruction.opcode == JANET_SYSOP_GTE) { + janet_formatb(buffer, "%s label_%u\n", invert ? "jl" : "jge", nexti.branch.to); + } else if (instruction.opcode == JANET_SYSOP_LT) { + janet_formatb(buffer, "%s label_%u\n", invert ? "jge" : "jl", nexti.branch.to); + } else if (instruction.opcode == JANET_SYSOP_LTE) { + janet_formatb(buffer, "%s label_%u\n", invert ? "jg" : "jle", nexti.branch.to); + } else { + janet_panic("unreachable"); + } + do_spills_write(buffer, spills, j); + break; + } + /* Fallback to set* instructions */ + if (instruction.opcode == JANET_SYSOP_EQ) { + janet_formatb(buffer, "sete %s\n", register_names[temps.temps[0]]); + } else if (instruction.opcode == JANET_SYSOP_NEQ) { + janet_formatb(buffer, "setne %s\n", register_names[temps.temps[0]]); + } else if (instruction.opcode == JANET_SYSOP_GT) { + janet_formatb(buffer, "setg %s\n", register_names[temps.temps[0]]); + } else if (instruction.opcode == JANET_SYSOP_GTE) { + janet_formatb(buffer, "setge %s\n", register_names[temps.temps[0]]); + } else if (instruction.opcode == JANET_SYSOP_LT) { + janet_formatb(buffer, "setl %s\n", register_names[temps.temps[0]]); + } else if (instruction.opcode == JANET_SYSOP_LTE) { + janet_formatb(buffer, "setle %s\n", register_names[temps.temps[0]]); + } else { + janet_panic("unreachable"); + } + do_spills_write(buffer, spills, j); break; case JANET_SYSOP_BRANCH: case JANET_SYSOP_BRANCH_NOT: - do_spills(buffer, spills, j); - janet_formatb(buffer, "branch label_%u if r%u\n", - instruction.branch.to, - v2reg(assignments, instruction.branch.cond)); + ; + if (skip_branch) { + skip_branch = 0; + break; + } + temps = do_spills_read(buffer, spills, j); + if (instruction.opcode == JANET_SYSOP_BRANCH) { + janet_formatb(buffer, "jnz %s label_%u\n", + register_names[temps.temps[0]], + instruction.branch.to); + } else { + janet_formatb(buffer, "jz %s label_%u\n", + register_names[temps.temps[0]], + instruction.branch.to); + } + break; + case JANET_SYSOP_JUMP: + janet_formatb(buffer, "jmp label_%u\n", + instruction.jump.to); break; case JANET_SYSOP_CALLK: case JANET_SYSOP_CALL: @@ -284,15 +452,15 @@ void janet_sys_ir_lower_to_x64(JanetSysIRLinkage *linkage, JanetBuffer *buffer) while (offset * 3 + sub_count >= (int32_t) instruction.call.arg_count) { sub_count--; } - JanetSysInstruction arg_instruction = ir->instructions[j + offset]; - do_spills(buffer, spills, j + offset); + temps = do_spills_read(buffer, spills, j + offset); for (int x = sub_count; x >= 0; x--) { - janet_formatb(buffer, "push r%u\n", v2reg(assignments, arg_instruction.arg.args[x])); + janet_formatb(buffer, "push r%u\n", temps.temps[x]); } } + temps = do_spills_read(buffer, spills, j); if (instruction.opcode == JANET_SYSOP_CALLK) { if (instruction.callk.has_dest) { - janet_formatb(buffer, "r%u = call %p\n", v2reg(assignments, instruction.callk.dest), + janet_formatb(buffer, "r%u = call %p\n", temps.temps[0], ir->constants[instruction.callk.constant]); } else { janet_formatb(buffer, "call %p\n", @@ -300,13 +468,12 @@ void janet_sys_ir_lower_to_x64(JanetSysIRLinkage *linkage, JanetBuffer *buffer) } } else { if (instruction.call.has_dest) { - janet_formatb(buffer, "r%u = call r%u\n", v2reg(assignments, instruction.call.dest), - v2reg(assignments, instruction.call.callee)); + janet_formatb(buffer, "r%u = call r%u\n", temps.temps[0], temps.temps[1]); } else { - janet_formatb(buffer, "call r%u\n", - v2reg(assignments, instruction.call.callee)); + janet_formatb(buffer, "call r%u\n", temps.temps[0]); } } + do_spills_write(buffer, spills, j); break; // On a comparison, if next instruction is branch that reads from dest, combine into a single op. }