#include "ccc.h" #include "codegen.h" #include "scope.h" #include "register.h" #include #include #include #define CGEN_PANIC(format, ...) {\ fprintf(\ stderr,\ "ccc: code gen error: " format "\n" __VA_OPT__(,)\ __VA_ARGS__);\ exit(1);\ } struct lval_def { const struct type* type; struct storage_location loc; }; static struct reg* RV_REG = &RAX; static struct reg* MULDIV_REG = &RAX; static struct reg* MULDIV_OVERFLOW_REG = &RDX; #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; static integral_t branch_counter = 0; static integral_t loop_counter = 0; static void enter_scope( struct scope* child_scope, integral_t bp_offset ) { if (child_scope == NULL || child_scope->next_out != scope) CGEN_PANIC("enter_scope: scopes are misaligned"); scope = child_scope; scope->bp_offset = bp_offset; } static void exit_scope(struct scope* child_scope, bool save_bp_offset) { if (child_scope != scope || child_scope->next_out == NULL) CGEN_PANIC("exit_scope: scopes are misaligned"); scope = child_scope->next_out; if (save_bp_offset) scope->bp_offset = child_scope->bp_offset; } 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( const struct type* type ) { switch (type->type) { case TP_DATA: return type->data.data_type; case TP_PTR: return &long_long_type; } CGEN_PANIC("unhandled type of type case"); } static struct lval_def allocate_stack( FILE* outfile, const struct type* type ) { integral_t type_sz = get_effective_data_type(type)->sz; fprintf(outfile, "\tsub rsp, %llu\n", type_sz); scope->bp_offset += type_sz; return (struct lval_def) { .loc = { .type = STO_STACK, .bp_offset = scope->bp_offset, }, .type = type, }; } static struct lval_def allocate_temporary( FILE* outfile, const struct type* 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 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"); } } static void emit_storage_loc( FILE* outfile, const struct storage_location* loc, integral_t sz ) { switch (loc->type) { case STO_LABEL: fprintf(outfile, "%s", loc->label); break; case STO_FN: fprintf(outfile, "%s", loc->decl->name); break; case STO_REG: if (sz > 4) fprintf(outfile, "%s", loc->reg->qword); else if (sz > 2) fprintf(outfile, "%s", loc->reg->dword); else if (sz > 1) fprintf(outfile, "%s", loc->reg->word); else fprintf(outfile, "%s", loc->reg->byte); break; case STO_STACK: if (loc->bp_offset < 0) fprintf(outfile, "[rbp + %lld]", -loc->bp_offset); else if (loc->bp_offset > 0) fprintf(outfile, "[rbp - %lld]", loc->bp_offset); else fprintf(outfile, "[rbp]"); break; case STO_IMM: fprintf(outfile, "%llu", loc->value); break; case STO_UNRESOLVED: CGEN_PANIC("can't emit unresolved storage location"); } } static bool locs_equal( const struct storage_location* a, const struct storage_location* b ) { if (a->type != b->type) return false; switch (a->type) { case STO_IMM: return a->value == b->value; case STO_REG: return strcmp(a->reg->qword, b->reg->qword) == 0; case STO_STACK: return a->bp_offset == b->bp_offset; case STO_LABEL: return strcmp(a->label, b->label) == 0; case STO_FN: return a->decl == b->decl; case STO_UNRESOLVED: return false; } CGEN_PANIC("unhandled storage type case"); } static void emit_size_const(FILE* outfile, integral_t sz) { if (sz > 4) fprintf(outfile, "qword "); else if (sz > 2) fprintf(outfile, "dword "); else if (sz > 1) fprintf(outfile, "word "); else fprintf(outfile, "byte "); } static void emit_mov( FILE* outfile, const struct lval_def* dst, const struct storage_location* src ) { /* first optimization: if dst == src, emit nothing */ if (locs_equal(&dst->loc, src)) return; integral_t dst_sz = get_effective_data_type(dst->type)->sz; switch (dst->loc.type) { case STO_REG: if (src->type == STO_REG && dst_sz < 4) { fprintf(outfile, "\tmovzx "); emit_storage_loc(outfile, &dst->loc, FULL_REG_SZ); } else { fprintf(outfile, "\tmov "); emit_storage_loc(outfile, &dst->loc, dst_sz); } fprintf(outfile, ", "); emit_storage_loc(outfile, src, dst_sz); break; case STO_STACK: if (src->type == STO_STACK) { /* `mov mem, mem` is illegal in x86_64 */ 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; } fprintf(outfile, "\tmov "); if (src->type == STO_IMM) emit_size_const(outfile, dst_sz); emit_storage_loc(outfile, &dst->loc, dst_sz); fprintf(outfile, ", "); emit_storage_loc(outfile, src, dst_sz); break; case STO_LABEL: case STO_IMM: case STO_FN: case STO_UNRESOLVED: CGEN_PANIC("can't move value into storage type"); } fprintf(outfile, "\n"); } static void emit_cmp_zero(FILE* outfile, const struct lval_def* lval) { fprintf(outfile, "\tcmp "); integral_t type_sz = get_effective_data_type(lval->type)->sz; switch (lval->loc.type) { case STO_REG: emit_storage_loc(outfile, &lval->loc, type_sz); break; case STO_STACK: emit_size_const(outfile, type_sz); emit_storage_loc(outfile, &lval->loc, type_sz); break; case STO_LABEL: case STO_IMM: case STO_FN: case STO_UNRESOLVED: CGEN_PANIC("can't compare this storage type") } fprintf(outfile, ", 0\n"); } static void emit_expr( FILE* outfile, const struct expr_node* node, const struct lval_def* dst); static void emit_int_lit( FILE* outfile, const struct int_lit_node* node, const struct lval_def* dst ) { if (dst != NULL) emit_mov( outfile, dst, &(struct storage_location) { .type = STO_IMM, .value = node->val, }); } static void emit_float_lit( FILE* outfile, const struct float_lit_node* node, const struct lval_def* dst ) { if (dst != NULL) { CGEN_PANIC("float literals are not implemented"); } } static void emit_char_lit( FILE* outfile, const struct char_lit_node* node, const struct lval_def* dst ) { if (dst != NULL) { emit_mov( outfile, dst, &(struct storage_location) { .type = STO_IMM, .value = (integral_t) node->val }); } } static void emit_str_lit( FILE* outfile, const struct str_lit_node* node, const struct lval_def* dst ) { if (dst != NULL) { CGEN_PANIC("string literals are not implemented"); } } static void emit_var_ref( FILE* outfile, const struct var_ref_node* node, const struct lval_def* dst ) { if (dst != NULL) { emit_mov(outfile, dst, &node->def_ref->loc); } } static void emit_stmt(FILE* outfile, const struct stmt_node* node); static void emit_decl( FILE* outfile, const struct decl_node* node ) { struct lval_def var_dst = allocate_stack(outfile, node->def_ref->type); node->def_ref->loc = var_dst.loc; 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"); if (node->initial_value != NULL) emit_expr(outfile, node->initial_value, &var_dst); } static void emit_decl_list( FILE* outfile, const struct decl_list_node* node ) { for (const struct decl_node* cur = node->head; cur != NULL; cur = cur->next) emit_decl(outfile, cur); } static void emit_assignment( FILE* outfile, const struct assign_node* node, const struct lval_def* dst ) { struct lval_def lval_def; switch (node->lval->type) { case EXPR_VAR_REF: struct var_ref_node* var_ref = &node->lval->inner.var_ref; lval_def = (struct lval_def) { .type = var_ref->def_ref->type, .loc = var_ref->def_ref->loc, }; break; default: CGEN_PANIC("expression is not assignable"); } emit_expr(outfile, node->rval, &lval_def); 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 ) { /* 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]); } /* 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], } }); } integral_t arg_stack_space = push_stack_args(outfile, arg); /* 3. `call