aboutsummaryrefslogtreecommitdiff
path: root/src/codegen.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen.c')
-rw-r--r--src/codegen.c170
1 files changed, 144 insertions, 26 deletions
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);
}