From bf157cf09b8b0888c54a165b887309a4e801f608 Mon Sep 17 00:00:00 2001 From: hachem Date: Thu, 10 Sep 2026 04:42:33 +0200 Subject: feat: add aarch64 as target --- src/codegen.c | 439 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) (limited to 'src/codegen.c') 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 = + 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); +} -- cgit v1.3