1
0
mirror of https://github.com/janet-lang/janet synced 2024-11-16 05:34:48 +00:00

More work on x64 backend, especially branching.

Needs changes to IR to allow encoding immediates in all
instructions where possible. This makes the IR denser, means
we don't need `constant` and `callk`, and allows certain optimizations
like comparing to zero, using `inc` and `dec`, etc which are
specializations of more general instructions with constants.
This commit is contained in:
Calvin Rose 2024-06-08 13:20:34 -05:00
parent 3995fa86e2
commit af10c1d4b5
5 changed files with 275 additions and 91 deletions

View File

@ -367,5 +367,5 @@
####
(compile1 myprog)
#(dump)
(dump)
(dumpx64)

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -25,17 +25,64 @@
#include <janet.h>
#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.
}