From bf3bd19f79936fa519a5285701763da58935592a Mon Sep 17 00:00:00 2001 From: hachem Date: Wed, 9 Sep 2026 05:43:06 +0200 Subject: refactor: split code gen into arch seam --- src/codegen.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 26 deletions(-) (limited to 'src/codegen.c') diff --git a/src/codegen.c b/src/codegen.c index 2eaa8bd..bb38f17 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -1127,6 +1127,41 @@ static void emit_while(struct Emitter* emitter, struct WhileStatement* loop) fprintf(emitter->out, "%s:\n", end); } +// renders a raw instruction's operand: registers, immediates, constants and +// members reuse emit_operand; memory (^x -> [x], with an optional size) and +// address math are handled here so operands like `^byte si` and `gdt + 2` work +static void emit_instruction_operand(struct Emitter* emitter, struct Expr* operand) +{ + switch (operand->kind) + { + case EXPR_DEREF: + fprintf(emitter->out, "%s[", store_size_keyword(operand->deref.size)); + emit_instruction_operand(emitter, operand->deref.address); + fprintf(emitter->out, "]"); + break; + case EXPR_BINARY: + emit_instruction_operand(emitter, operand->binary.left); + fprintf(emitter->out, " %.*s ", + (int)operand->binary.op.length, operand->binary.op.start); + emit_instruction_operand(emitter, operand->binary.right); + break; + default: + emit_operand(emitter, operand); + break; + } +} + +static void emit_instruction(struct Emitter* emitter, struct InstructionStatement* insn) +{ + fprintf(emitter->out, "\t%.*s", (int)insn->mnemonic.length, insn->mnemonic.start); + for (size_t i = 0; i < insn->operand_count; i += 1) + { + fprintf(emitter->out, "%s", i == 0 ? " " : ", "); + emit_instruction_operand(emitter, insn->operands[i]); + } + fprintf(emitter->out, "\n"); +} + static void emit_statement(struct Emitter* emitter, struct Statement* statement) { FILE* out = emitter->out; @@ -1155,6 +1190,9 @@ static void emit_statement(struct Emitter* emitter, struct Statement* statement) break; case STATEMENT_STACK: break; + case STATEMENT_INSTRUCTION: + emit_instruction(emitter, &statement->instruction); + break; default: fprintf(out, "\t; TODO: unsupported statement\n"); break; @@ -1260,7 +1298,7 @@ static struct FloatTable collect_floats(struct Program* program) return floats; } -static void emit_proc(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, FILE* out) +static void emit_proc_x86(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, bool is_entry, FILE* out) { struct Emitter emitter; emitter.program = program; @@ -1269,11 +1307,6 @@ static void emit_proc(struct Program* program, struct FloatTable* floats, struct emitter.out = out; emitter.label_id = 0; - struct Config config = program->config; - bool is_entry = config.has_entry - && proc->name.length == config.entry.length - && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; - fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start); uint64_t stack_size = proc_stack_size(program, proc); @@ -1295,10 +1328,20 @@ static void emit_proc(struct Program* program, struct FloatTable* floats, struct } } -// The instruction bodies above are plain Intel syntax, identical for every -// target assembler. Only the framing around them — the file header, constants, -// section directives, data definitions and the exported entry symbol — differs, -// so each backend supplies just those. +// Instruction selection lives behind the Arch seam: turning a procedure's +// statements into a target's instructions (register model, mnemonics, stack +// frames) is all an architecture decides. The Backend below is the orthogonal +// axis — the assembler *syntax* (framing, data, labels) for a given arch. +struct Arch +{ + void (*emit_proc)(struct Program* program, struct FloatTable* floats, + struct ProcDecl* proc, bool is_entry, FILE* out); +}; + +static const struct Arch x86_arch = { + emit_proc_x86, +}; + struct Backend { void (*prologue)(const struct Program* program, FILE* out); @@ -1308,11 +1351,15 @@ struct Backend void (*float_slot)(size_t index, struct Token literal, FILE* out); void (*text_section)(FILE* out); void (*global)(struct Token name, FILE* out); + void (*boot_signature)(FILE* out); }; static void nasm_prologue(const struct Program* program, FILE* out) { - fprintf(out, "bits %u\n", program->config.bits); + struct Config config = program->config; + fprintf(out, "bits %u\n", config.bits); + if (config.has_org) + fprintf(out, "org %.*s\n", (int)config.org.length, config.org.start); } static void nasm_constant(struct ConstDecl decl, FILE* out) @@ -1352,6 +1399,13 @@ static void nasm_global(struct Token name, FILE* out) fprintf(out, "global %.*s\n", (int)name.length, name.start); } +// pad to 510 bytes and append the 0x55AA boot signature (little-endian dw) +static void nasm_boot_signature(FILE* out) +{ + fprintf(out, "times 510-($-$$) db 0\n"); + fprintf(out, "dw 0xAA55\n"); +} + static const struct Backend nasm_backend = { nasm_prologue, nasm_constant, @@ -1360,11 +1414,23 @@ static const struct Backend nasm_backend = { nasm_float_slot, nasm_text_section, nasm_global, + nasm_boot_signature, }; static void fasm_prologue(const struct Program* program, FILE* out) { - fprintf(out, "format ELF%s\n", program->config.bits == 64 ? "64" : ""); + struct Config config = program->config; + if (config.format == OUTPUT_BIN) + { + fprintf(out, "format binary\n"); + if (config.has_org) + fprintf(out, "org %.*s\n", (int)config.org.length, config.org.start); + fprintf(out, "use%u\n", config.bits); + } + else + { + fprintf(out, "format ELF%s\n", config.bits == 64 ? "64" : ""); + } } static void fasm_constant(struct ConstDecl decl, FILE* out) @@ -1453,6 +1519,12 @@ static void fasm_global(struct Token name, FILE* out) fprintf(out, "public %.*s\n", (int)name.length, name.start); } +static void fasm_boot_signature(FILE* out) +{ + fprintf(out, "db (510 - ($ - $$)) dup (0)\n"); + fprintf(out, "dw 0xAA55\n"); +} + static const struct Backend fasm_backend = { fasm_prologue, fasm_constant, @@ -1461,9 +1533,33 @@ static const struct Backend fasm_backend = { fasm_float_slot, fasm_text_section, fasm_global, + fasm_boot_signature, }; -static void generate(struct Program* program, FILE* out, const struct Backend* backend) +// the entry procedure drops its trailing `ret`. It is the [entry: NAME] proc if +// given; otherwise a flat binary starts at its first proc. +static bool proc_is_entry(struct Program* program, size_t index) +{ + struct Config config = program->config; + struct ProcDecl* proc = &program->procs[index]; + + if (config.has_entry) + return proc->name.length == config.entry.length + && memcmp(proc->name.start, config.entry.start, proc->name.length) == 0; + + return config.format == OUTPUT_BIN && index == 0; +} + +static void emit_data_block(struct Program* program, struct FloatTable* floats, + const struct Backend* backend, FILE* out) +{ + for (size_t i = 0; i < program->data_count; i += 1) + backend->string_data(program->data_decls[i], out); + for (size_t i = 0; i < floats->count; i += 1) + backend->float_slot(i, floats->items[i], out); +} + +static void generate(struct Program* program, FILE* out, const struct Arch* arch, const struct Backend* backend) { struct FloatTable floats = collect_floats(program); @@ -1477,21 +1573,43 @@ static void generate(struct Program* program, FILE* out, const struct Backend* b fprintf(out, "\n"); } - backend->data_section(out); - for (size_t i = 0; i < program->data_count; i += 1) - backend->string_data(program->data_decls[i], out); - for (size_t i = 0; i < floats.count; i += 1) - backend->float_slot(i, floats.items[i], out); - fprintf(out, "\n"); + if (program->config.format == OUTPUT_BIN) + { + // a flat binary executes from its origin, so code comes first, then data + for (size_t i = 0; i < program->proc_count; i += 1) + { + if (i > 0) + fprintf(out, "\n"); + arch->emit_proc(program, &floats, &program->procs[i], proc_is_entry(program, i), out); + } - backend->text_section(out); - if (program->config.has_entry) - backend->global(program->config.entry, out); + if (program->data_count > 0 || floats.count > 0) + { + fprintf(out, "\n"); + emit_data_block(program, &floats, backend, out); + } - for (size_t i = 0; i < program->proc_count; i += 1) + if (program->config.boot) + { + fprintf(out, "\n"); + backend->boot_signature(out); + } + } + else { + backend->data_section(out); + emit_data_block(program, &floats, backend, out); fprintf(out, "\n"); - emit_proc(program, &floats, &program->procs[i], out); + + backend->text_section(out); + if (program->config.has_entry) + backend->global(program->config.entry, out); + + for (size_t i = 0; i < program->proc_count; i += 1) + { + fprintf(out, "\n"); + arch->emit_proc(program, &floats, &program->procs[i], proc_is_entry(program, i), out); + } } free(floats.items); @@ -1499,10 +1617,10 @@ static void generate(struct Program* program, FILE* out, const struct Backend* b void generate_nasm(struct Program* program, FILE* out) { - generate(program, out, &nasm_backend); + generate(program, out, &x86_arch, &nasm_backend); } void generate_fasm(struct Program* program, FILE* out) { - generate(program, out, &fasm_backend); + generate(program, out, &x86_arch, &fasm_backend); } -- cgit v1.3