aboutsummaryrefslogtreecommitdiff
path: root/src/codegen.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/codegen.c')
-rw-r--r--src/codegen.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/codegen.c b/src/codegen.c
index bb38f17..271c5ae 100644
--- a/src/codegen.c
+++ b/src/codegen.c
@@ -1328,6 +1328,372 @@ static void emit_proc_x86(struct Program* program, struct FloatTable* floats, st
}
}
+// ---------------------------------------------------------------------------
+// AArch64 target
+//
+// A separate instruction selector: the register model (logical rN -> xN-1, or
+// native x0.., w0.., sp, lr), 3-operand arithmetic, ldr/str memory, cmp + b.cond
+// control flow and svc #0 syscalls are all its own. Shares only the arch-neutral
+// helpers above (fold_const, resolve_token, the AST).
+// ---------------------------------------------------------------------------
+
+static bool is_a64_register(struct Token token)
+{
+ // logical rN (mapped to xN-1)
+ if (token.length >= 2 && token.start[0] == 'r' && token.start[1] >= '0' && token.start[1] <= '9')
+ {
+ for (size_t i = 1; i < token.length; i += 1)
+ if (token.start[i] < '0' || token.start[i] > '9')
+ return false;
+ return true;
+ }
+
+ // native names x0..x30 / w0..w30
+ if ((token.start[0] == 'x' || token.start[0] == 'w') && token.length >= 2
+ && token.start[1] >= '0' && token.start[1] <= '9')
+ return true;
+
+ return token_matches(token, "sp") || token_matches(token, "lr")
+ || token_matches(token, "fp") || token_matches(token, "xzr")
+ || token_matches(token, "wzr");
+}
+
+static void emit_a64_reg(struct Emitter* emitter, struct Token token)
+{
+ struct Token r = resolve_token(emitter, token);
+ if (r.length >= 2 && r.start[0] == 'r' && r.start[1] >= '0' && r.start[1] <= '9')
+ {
+ uint32_t index = 0;
+ for (size_t i = 1; i < r.length; i += 1)
+ index = index * 10 + (uint32_t)(r.start[i] - '0');
+ fprintf(emitter->out, "x%u", index - 1);
+ return;
+ }
+ fprintf(emitter->out, "%.*s", (int)r.length, r.start);
+}
+
+// an operand in register or immediate position: a register maps through, and
+// anything that folds to a constant becomes an #immediate
+static void emit_a64_operand(struct Emitter* emitter, struct Expr* expr)
+{
+ if (expr->kind == EXPR_PRIMARY && is_a64_register(resolve_token(emitter, expr->primary.token)))
+ {
+ emit_a64_reg(emitter, expr->primary.token);
+ return;
+ }
+
+ uint64_t value;
+ if (fold_const(emitter->program, expr, &value))
+ {
+ fprintf(emitter->out, "#%lld", (long long)value);
+ return;
+ }
+
+ if (expr->kind == EXPR_PRIMARY)
+ fprintf(emitter->out, "#%.*s", (int)expr->primary.token.length, expr->primary.token.start);
+ else
+ fprintf(emitter->out, "; TODO: unsupported operand");
+}
+
+static const char* a64_binop(enum TokenType op)
+{
+ switch (op)
+ {
+ case TOKEN_PLUS: case TOKEN_PLUS_EQUAL: return "add";
+ case TOKEN_MINUS: case TOKEN_MINUS_EQUAL: return "sub";
+ case TOKEN_STAR: case TOKEN_STAR_EQUAL: return "mul";
+ case TOKEN_SLASH: case TOKEN_SLASH_EQUAL: return "sdiv";
+ default: return NULL;
+ }
+}
+
+// branch taken when the comparison is false (to skip the guarded body)
+static const char* a64_jump_if_false(enum TokenType comparison)
+{
+ switch (comparison)
+ {
+ case TOKEN_EQUAL_EQUAL: return "ne";
+ case TOKEN_BANG_EQUAL: return "eq";
+ case TOKEN_LESS: return "ge";
+ case TOKEN_LESS_EQUAL: return "gt";
+ case TOKEN_GREATER: return "le";
+ case TOKEN_GREATER_EQUAL: return "lt";
+ default: return NULL;
+ }
+}
+
+static void emit_a64_statement(struct Emitter* emitter, struct Statement* statement);
+
+static void emit_a64_block(struct Emitter* emitter, struct Statement* body, size_t count)
+{
+ for (size_t i = 0; i < count; i += 1)
+ emit_a64_statement(emitter, &body[i]);
+}
+
+static void emit_a64_assign(struct Emitter* emitter, struct AssignStatement* assign)
+{
+ FILE* out = emitter->out;
+
+ // store through a pointer: ^[size] p = value
+ if (assign->target_deref)
+ {
+ const char* store = assign->store_size == STORE_SIZE_BYTE ? "strb"
+ : assign->store_size == STORE_SIZE_WORD ? "strh" : "str";
+ fprintf(out, "\t%s ", store);
+ emit_a64_operand(emitter, assign->value);
+ fprintf(out, ", [");
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, "]\n");
+ return;
+ }
+
+ struct Expr* value = assign->value;
+
+ // load through a pointer: dst = ^[size] p
+ if (assign->op.type == TOKEN_EQUAL && value->kind == EXPR_DEREF
+ && value->deref.address->kind == EXPR_PRIMARY)
+ {
+ const char* load = value->deref.size == STORE_SIZE_BYTE ? "ldrb"
+ : value->deref.size == STORE_SIZE_WORD ? "ldrh" : "ldr";
+ fprintf(out, "\t%s ", load);
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", [");
+ emit_a64_reg(emitter, value->deref.address->primary.token);
+ fprintf(out, "]\n");
+ return;
+ }
+
+ // three-operand arithmetic: dst = a op b
+ if (assign->op.type == TOKEN_EQUAL && value->kind == EXPR_BINARY)
+ {
+ const char* mnemonic = a64_binop(value->binary.op.type);
+ if (mnemonic == NULL)
+ {
+ fprintf(out, "\t; TODO: unsupported expression\n");
+ return;
+ }
+ fprintf(out, "\t%s ", mnemonic);
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", ");
+ emit_a64_operand(emitter, value->binary.left);
+ fprintf(out, ", ");
+ emit_a64_operand(emitter, value->binary.right);
+ fprintf(out, "\n");
+ return;
+ }
+
+ // compound assignment: dst op= value -> op dst, dst, value
+ if (assign->op.type != TOKEN_EQUAL)
+ {
+ const char* mnemonic = a64_binop(assign->op.type);
+ if (mnemonic == NULL)
+ {
+ fprintf(out, "\t; TODO: unsupported assignment\n");
+ return;
+ }
+ fprintf(out, "\t%s ", mnemonic);
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", ");
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", ");
+ emit_a64_operand(emitter, value);
+ fprintf(out, "\n");
+ return;
+ }
+
+ // plain move: dst = <register | immediate | symbol/address>
+ if (value->kind == EXPR_PRIMARY && is_a64_register(resolve_token(emitter, value->primary.token)))
+ {
+ fprintf(out, "\tmov ");
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", ");
+ emit_a64_reg(emitter, value->primary.token);
+ fprintf(out, "\n");
+ return;
+ }
+
+ uint64_t folded;
+ if (fold_const(emitter->program, value, &folded))
+ {
+ fprintf(out, "\tmov ");
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", #%lld\n", (long long)folded);
+ return;
+ }
+
+ // a data label or other symbol: load its address/value through the pool
+ if (value->kind == EXPR_PRIMARY)
+ {
+ fprintf(out, "\tldr ");
+ emit_a64_reg(emitter, assign->target);
+ fprintf(out, ", =%.*s\n", (int)value->primary.token.length, value->primary.token.start);
+ return;
+ }
+
+ fprintf(out, "\t; TODO: unsupported assignment\n");
+}
+
+static bool emit_a64_branch_test(struct Emitter* emitter, struct Expr* left,
+ struct Token comparison, struct Expr* right, const char* target)
+{
+ const char* cond = a64_jump_if_false(comparison.type);
+ if (cond == NULL || left->kind != EXPR_PRIMARY)
+ {
+ fprintf(emitter->out, "\t; TODO: unsupported condition\n");
+ return false;
+ }
+
+ fprintf(emitter->out, "\tcmp ");
+ emit_a64_operand(emitter, left);
+ fprintf(emitter->out, ", ");
+ emit_a64_operand(emitter, right);
+ fprintf(emitter->out, "\n\tb.%s %s\n", cond, target);
+ return true;
+}
+
+static void emit_a64_if(struct Emitter* emitter, struct IfStatement* branch)
+{
+ bool has_else = branch->else_count > 0;
+ uint32_t id = emitter->label_id;
+ emitter->label_id += 1;
+
+ char target[32];
+ snprintf(target, sizeof(target), ".if_%s_%u", has_else ? "else" : "end", id);
+
+ if (!emit_a64_branch_test(emitter, branch->left, branch->comparison, branch->right, target))
+ return;
+
+ emit_a64_block(emitter, branch->body, branch->body_count);
+
+ if (has_else)
+ {
+ fprintf(emitter->out, "\tb .if_end_%u\n", id);
+ fprintf(emitter->out, ".if_else_%u:\n", id);
+ emit_a64_block(emitter, branch->else_body, branch->else_count);
+ }
+
+ fprintf(emitter->out, ".if_end_%u:\n", id);
+}
+
+static void emit_a64_while(struct Emitter* emitter, struct WhileStatement* loop)
+{
+ char top[64];
+ char end[64];
+ if (loop->named)
+ {
+ snprintf(top, sizeof(top), ".%.*s", (int)loop->name.length, loop->name.start);
+ snprintf(end, sizeof(end), ".%.*s_end", (int)loop->name.length, loop->name.start);
+ }
+ else
+ {
+ uint32_t id = emitter->label_id;
+ emitter->label_id += 1;
+ snprintf(top, sizeof(top), ".while_%u", id);
+ snprintf(end, sizeof(end), ".while_end_%u", id);
+ }
+
+ fprintf(emitter->out, "%s:\n", top);
+ if (!emit_a64_branch_test(emitter, loop->left, loop->comparison, loop->right, end))
+ return;
+ emit_a64_block(emitter, loop->body, loop->body_count);
+ fprintf(emitter->out, "\tb %s\n", top);
+ fprintf(emitter->out, "%s:\n", end);
+}
+
+static void emit_a64_call(struct Emitter* emitter, struct CallStatement* call)
+{
+ const struct ProcDecl* callee = NULL;
+ for (size_t i = 0; i < emitter->program->proc_count; i += 1)
+ if (tokens_equal(emitter->program->procs[i].name, call->name))
+ callee = &emitter->program->procs[i];
+
+ if (callee != NULL)
+ for (size_t i = 0; i < call->arg_count && i < callee->param_count; i += 1)
+ {
+ fprintf(emitter->out, "\tmov ");
+ emit_a64_reg(emitter, callee->params[i].reg);
+ fprintf(emitter->out, ", ");
+ emit_a64_operand(emitter, call->args[i]);
+ fprintf(emitter->out, "\n");
+ }
+
+ fprintf(emitter->out, "\tbl %.*s\n", (int)call->name.length, call->name.start);
+}
+
+static void emit_a64_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)
+ {
+ struct Expr* operand = insn->operands[i];
+ fprintf(emitter->out, "%s", i == 0 ? " " : ", ");
+ if (operand->kind == EXPR_DEREF && operand->deref.address->kind == EXPR_PRIMARY)
+ {
+ fprintf(emitter->out, "[");
+ emit_a64_reg(emitter, operand->deref.address->primary.token);
+ fprintf(emitter->out, "]");
+ }
+ else
+ {
+ emit_a64_operand(emitter, operand);
+ }
+ }
+ fprintf(emitter->out, "\n");
+}
+
+static void emit_a64_statement(struct Emitter* emitter, struct Statement* statement)
+{
+ FILE* out = emitter->out;
+ switch (statement->kind)
+ {
+ case STATEMENT_ASSIGN:
+ emit_a64_assign(emitter, &statement->assign);
+ break;
+ case STATEMENT_LABEL:
+ fprintf(out, "%.*s:\n", (int)statement->label.name.length, statement->label.name.start);
+ break;
+ case STATEMENT_GOTO:
+ fprintf(out, "\tb %.*s\n", (int)statement->jump.label.length, statement->jump.label.start);
+ break;
+ case STATEMENT_SYSCALL:
+ fprintf(out, "\tsvc #0\n");
+ break;
+ case STATEMENT_IF:
+ emit_a64_if(emitter, &statement->branch);
+ break;
+ case STATEMENT_WHILE:
+ emit_a64_while(emitter, &statement->loop);
+ break;
+ case STATEMENT_CALL:
+ emit_a64_call(emitter, &statement->call);
+ break;
+ case STATEMENT_STACK:
+ fprintf(out, "\t; TODO: stack buffers not yet supported on aarch64\n");
+ break;
+ case STATEMENT_INSTRUCTION:
+ emit_a64_instruction(emitter, &statement->instruction);
+ break;
+ }
+}
+
+static void emit_proc_aarch64(struct Program* program, struct FloatTable* floats, struct ProcDecl* proc, bool is_entry, FILE* out)
+{
+ struct Emitter emitter;
+ emitter.program = program;
+ emitter.proc = proc;
+ emitter.floats = floats;
+ emitter.out = out;
+ emitter.label_id = 0;
+
+ fprintf(out, "%.*s:\n", (int)proc->name.length, proc->name.start);
+
+ for (size_t i = 0; i < proc->body_count; i += 1)
+ emit_a64_statement(&emitter, &proc->body[i]);
+
+ if (!is_entry)
+ fprintf(out, "\tret\n");
+}
+
// 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
@@ -1342,6 +1708,10 @@ static const struct Arch x86_arch = {
emit_proc_x86,
};
+static const struct Arch aarch64_arch = {
+ emit_proc_aarch64,
+};
+
struct Backend
{
void (*prologue)(const struct Program* program, FILE* out);
@@ -1536,6 +1906,70 @@ static const struct Backend fasm_backend = {
fasm_boot_signature,
};
+// GNU as (the assembler for the ARM targets): different directives from the
+// Intel-syntax assemblers, but the same framing shape.
+static void gas_prologue(const struct Program* program, FILE* out)
+{
+ (void)program;
+ fprintf(out, ".arch armv8-a\n");
+}
+
+static void gas_constant(struct ConstDecl decl, FILE* out)
+{
+ fprintf(out, ".equ %.*s, ", (int)decl.name.length, decl.name.start);
+ emit_const_expr(decl.value, out);
+ fprintf(out, "\n");
+}
+
+static void gas_data_section(FILE* out)
+{
+ fprintf(out, ".data\n");
+}
+
+static void gas_string_data(struct DataDecl decl, FILE* out)
+{
+ // GNU as .ascii interprets the same C escapes NASM's backtick strings do,
+ // so the inner content passes through unchanged (no trailing NUL, matching)
+ fprintf(out, "%.*s: .ascii \"%.*s\"\n",
+ (int)decl.name.length, decl.name.start,
+ (int)(decl.value.length - 2), decl.value.start + 1);
+ fprintf(out, ".equ %.*s.len, . - %.*s\n",
+ (int)decl.name.length, decl.name.start,
+ (int)decl.name.length, decl.name.start);
+}
+
+static void gas_float_slot(size_t index, struct Token literal, FILE* out)
+{
+ fprintf(out, "__float%zu: .double %.*s\n", index, (int)literal.length, literal.start);
+}
+
+static void gas_text_section(FILE* out)
+{
+ fprintf(out, ".text\n");
+}
+
+static void gas_global(struct Token name, FILE* out)
+{
+ fprintf(out, ".global %.*s\n", (int)name.length, name.start);
+}
+
+static void gas_boot_signature(FILE* out)
+{
+ // boot sectors are an x86/BIOS concept; not meaningful for the ARM targets
+ (void)out;
+}
+
+static const struct Backend gas_backend = {
+ gas_prologue,
+ gas_constant,
+ gas_data_section,
+ gas_string_data,
+ gas_float_slot,
+ gas_text_section,
+ gas_global,
+ gas_boot_signature,
+};
+
// 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)
@@ -1624,3 +2058,8 @@ void generate_fasm(struct Program* program, FILE* out)
{
generate(program, out, &x86_arch, &fasm_backend);
}
+
+void generate_aarch64(struct Program* program, FILE* out)
+{
+ generate(program, out, &aarch64_arch, &gas_backend);
+}