From b1ff7e8bf24b40cf64127a6194d45661db3a4baf Mon Sep 17 00:00:00 2001 From: Carson Fleming Date: Fri, 31 Jul 2026 23:52:09 -0400 Subject: code gen can use registers besides RAX now --- README.md | 14 ++-- ccc.h | 1 + codegen.c | 273 +++++++++++++++++++++++++++++++++++++++---------------------- register.c | 119 ++++++++++++++++++++++----- register.h | 40 ++++++--- scope.h | 4 +- 6 files changed, 311 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index 2cfb498..3ded28b 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ fuck it, we ball TODO soon: -- [ ] let cgen use more than one register - - [ ] basic used/unused register tracking so allocate_temporary can allocate +- [x] let cgen use more than one register + - [x] basic used/unused register tracking so allocate_temporary can allocate a register - - [ ] proper deallocation of temporaries when they are no longer used - - [ ] spilling of values occupying calling convention registers when a + - [x] proper deallocation of temporaries when they are no longer used + - [x] spilling of values occupying calling convention registers when a function is called - - [ ] allowing variable values to be kept in registers temporarily and then - spilled to their permanent memory locations + - [ ] ~~allowing variable values to be kept in registers temporarily and then + spilled to their permanent memory locations~~ PUNTED - [ ] use evaluated types to make code gen around math better - [ ] support for functions/function pointers as types - - [ ] move the "expression not callable" stuff to the type checker \ No newline at end of file + - [ ] move the "expression not callable" stuff to the type checker diff --git a/ccc.h b/ccc.h index 3542e60..37bcbf1 100644 --- a/ccc.h +++ b/ccc.h @@ -5,6 +5,7 @@ #define ARRAY_SZ(x) (sizeof(x) / sizeof(x[0])) typedef unsigned long long integral_t; +typedef long long sintegral_t; typedef double floating_t; void* ccc_alloc(integral_t sz); diff --git a/codegen.c b/codegen.c index 5cba20f..3d96d33 100644 --- a/codegen.c +++ b/codegen.c @@ -19,18 +19,13 @@ struct lval_def { struct storage_location loc; }; -static const struct storage_location RV_LOC = { - .type = STO_REG, - .reg = &RAX, -}; -static const struct storage_location MULDIV_LOC = RV_LOC; -static const struct storage_location MULDIV_OVERFLOW_LOC = { - .type = STO_REG, - .reg = &RDX, -}; +static struct reg* RV_REG = &RAX; +static struct reg* MULDIV_REG = &RAX; +static struct reg* MULDIV_OVERFLOW_REG = &RDX; -#define RETURN_LABEL_FMT "%s@coda" +#define RETURN_LABEL_FMT "%s@ret" #define FULL_REG_SZ 8 +#define WORD_SZ 2 static struct scope* scope; static const struct fn_decl_node* active_fn; @@ -57,14 +52,30 @@ static void exit_scope(struct scope* child_scope, bool save_bp_offset) { if (save_bp_offset) scope->bp_offset = child_scope->bp_offset; } -static struct lval_def allocate_register(const struct type* type) { - return (struct lval_def) { - .loc = { - .type = STO_REG, - .reg = &RAX, /* TODO: no real register coloring happening LOL */ - }, - .type = type, - }; +static struct reg* allocate_register() { + for (integral_t i = 0; DATA_REGS[i] != NULL; i++) { + if (!DATA_REGS[i]->is_occupied) { + DATA_REGS[i]->is_occupied = true; + return DATA_REGS[i]; + } + } + return NULL; +} + +static void release_register(struct reg* reg) { + reg->is_occupied = false; +} + +static void spill_register(FILE* outfile, struct reg* reg) { + fprintf(outfile, "\tpush %s\n", reg->qword); + reg->is_occupied = false; + scope->bp_offset += FULL_REG_SZ; +} + +static void unspill_register(FILE* outfile, struct reg* reg) { + fprintf(outfile, "\tpop %s\n", reg->qword); + reg->is_occupied = true; + scope->bp_offset -= FULL_REG_SZ; } static const struct data_type* get_effective_data_type( @@ -99,16 +110,29 @@ static struct lval_def allocate_temporary( FILE* outfile, const struct type* type ) { - return allocate_stack(outfile, type); + struct reg* reg = allocate_register(); + if (reg == NULL) return allocate_stack(outfile, type); + return (struct lval_def) { + .type = type, + .loc = { + .type = STO_REG, + .reg = reg, + }, + }; } -static void deallocate_temporary(FILE* outfile, const struct lval_def* tmp) { - if (tmp->loc.type == STO_STACK) { - integral_t type_sz = get_effective_data_type(tmp->type)->sz; - fprintf(outfile, "\tadd rsp, %llu\n", type_sz); - scope->bp_offset -= type_sz; - } else if (tmp->loc.type == STO_REG) { - /* TOOD: release the register back to the algo */ +static void release_temporary(FILE* outfile, const struct lval_def* tmp) { + switch (tmp->loc.type) { + case STO_REG: + release_register(tmp->loc.reg); + break; + case STO_STACK: + case STO_IMM: + case STO_FN: + case STO_LABEL: + break; + case STO_UNRESOLVED: + CGEN_PANIC("can't release unresolved storage"); } } @@ -201,9 +225,36 @@ static void emit_mov( case STO_STACK: if (src->type == STO_STACK) { /* `mov mem, mem` is illegal in x86_64 */ - struct lval_def tmp = allocate_register(dst->type); + if (dst_sz == FULL_REG_SZ || dst_sz == WORD_SZ) { + /* if we can swing it, use the stack as the intermediary */ + fprintf(outfile, "\tpush "); + emit_size_const(outfile, dst_sz); + emit_storage_loc(outfile, &dst->loc, dst_sz); + fprintf(outfile, "\n\tpop "); + emit_size_const(outfile, dst_sz); + emit_storage_loc(outfile, src, dst_sz); + break; + } + + struct reg* tmp_reg = allocate_register(); + bool spill_reg = tmp_reg == NULL; + if (spill_reg) { + spill_register(outfile, &RCX); + tmp_reg = &RCX; + } + + struct lval_def tmp = { + .type = dst->type, + .loc = { + .type = STO_REG, + .reg = tmp_reg, + } + }; emit_mov(outfile, &tmp, src); emit_mov(outfile, dst, &tmp.loc); + + if (spill_reg) unspill_register(outfile, &RCX); + else release_register(tmp_reg); return; } @@ -319,7 +370,7 @@ static void emit_decl( allocate_stack(outfile, node->def_ref->type); node->def_ref->loc = var_dst.loc; - fprintf(outfile, "\t; '%s' lives in: ", node->def_ref->name); + fprintf(outfile, "\t; %s", node->def_ref->name); integral_t dst_sz = get_effective_data_type(var_dst.type)->sz; emit_storage_loc(outfile, &var_dst.loc, dst_sz); fprintf(outfile, "\n"); @@ -359,67 +410,71 @@ static void emit_assignment( if (dst != NULL) emit_mov(outfile, dst, &lval_def.loc); } +static integral_t push_stack_args(FILE* outfile, struct expr_list_node* arg) { + if (arg == NULL) return 0; + + integral_t args_sz = + get_effective_data_type(arg->resolved_type)->sz + + push_stack_args(outfile, arg->next); + + struct lval_def arg_dst = allocate_stack(outfile, arg->resolved_type); + emit_expr(outfile, arg->expr, &arg_dst); + + return args_sz; +} + static void emit_call( FILE* outfile, const struct call_node* node, const struct lval_def* dst ) { - integral_t orig_bp_offset = scope->bp_offset; - integral_t arg_bp_offset = orig_bp_offset; - - struct arg_decl_node* arg_decl = node->fn_ref->args; - struct expr_list_node* arg_eval = node->args; - while (arg_decl != NULL && arg_eval != NULL) { - struct lval_def arg_dst = - allocate_stack(outfile, arg_decl->def_ref->type); - emit_expr(outfile, arg_eval->expr, &arg_dst); - - arg_decl = arg_decl->next; - arg_eval = arg_eval->next; + /* 1. spill all existing data registers besides dst */ + const struct reg* dst_reg = dst->loc.type == STO_REG ? dst->loc.reg : NULL; + integral_t reg_occupied = 0, n_data_regs = 0; + for (; DATA_REGS[n_data_regs] != NULL; n_data_regs++) { + if (!DATA_REGS[n_data_regs]->is_occupied) continue; + if (DATA_REGS[n_data_regs] == dst_reg) continue; + + reg_occupied |= 1 << n_data_regs; + spill_register(outfile, DATA_REGS[n_data_regs]); } - unsigned char arg_regnum = 0; - arg_decl = node->fn_ref->args; - while (arg_decl != NULL) { - const struct type* arg_type = arg_decl->def_ref->type; - arg_bp_offset += get_effective_data_type(arg_type)->sz; - - struct lval_def arg_dst; - /* TODO: if the convention register is used, - * will need to spill the current value */ - if (arg_regnum < CC_N_REGS) - arg_dst = (struct lval_def) { - .type = arg_type, - .loc = (struct storage_location) { - .type = STO_REG, - .reg = CALLING_CONV[arg_regnum++], - }, - }; - else - arg_dst = allocate_stack(outfile, arg_type); - - emit_mov(outfile, &arg_dst, &(struct storage_location) { - .type = STO_STACK, - .bp_offset = arg_bp_offset, + /* 2. evaluate arguments in reverse order into their respective locations */ + struct expr_list_node* arg = node->args; + for (integral_t i = 0; + arg != NULL && CALLING_CONV[i] != NULL; + arg = arg->next, i++) { + CALLING_CONV[i]->is_occupied = true; + emit_expr(outfile, arg->expr, &(struct lval_def) { + .type = arg->resolved_type, + .loc = { + .type = STO_REG, + .reg = CALLING_CONV[i], + } }); - arg_decl = arg_decl->next; } + integral_t arg_stack_space = push_stack_args(outfile, arg); + /* 3. `call