From 3d8437d1529a8532162c10f2957ea986142fe66e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 31 Aug 2026 10:23:09 +0100 Subject: [PATCH] Get the ARM64 frontend working --- src/check_asm.cpp | 15 +++- src/llvm_backend_asm.cpp | 151 +++++++++++++++++++++++++++++++++++---- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index c9b2e7415..966d89414 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -2350,18 +2350,29 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * gbString s = type_to_string(rhs.type); error(rhs.expr, "Expected an integer immediate as an index, got %s", s); gb_string_free(s); + } else if (rhs.mode != Addressing_Constant || rhs.value.kind != ExactValue_Integer) { + Entity *pe = entity_of_node(rhs.expr); + bool is_poly_const = pe != nullptr && pe->kind == Entity_Variable && + (pe->flags & EntityFlag_PolyConst) != 0; + if (!is_poly_const) { + error(rhs.expr, "A vector lane index must be an assemble-time integer constant"); + } } if (build_context.metrics.arch != TargetArch_arm64) { error(expr, "The target platform does not support vector element extraction syntax"); } - + operand->expr = expr; operand->mode = Addressing_Value; operand->type = base_array_type(lhs.type); operand->value = {}; - add_type_and_value(ctx, expr, Addressing_Value, operand->type, {}); + add_type_and_value(ctx, operand->expr, operand->mode, operand->type, operand->value); + + if (rhs.mode == Addressing_Constant) { + add_type_and_value(ctx, rhs.expr, rhs.mode, rhs.type, rhs.value); + } return; case_end; } diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index 7f3884f8d..1fedb24ca 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -980,18 +980,18 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { return false; } - // AArch64 immediates carry their own '#'; registers are bare. No AT&T-style + // ARM64 immediates carry their own '#'; registers are bare. No AT&T-style // prefixes, so the generic PrintPrefixes bit is unused here. u32 default_operand_write_flags() override { return WriteOperandFlag_NONE; } - // #clobber flags -> the AArch64 condition-code clobber (NZCV). + // #clobber flags -> the ARM64 condition-code clobber (NZCV). void emit_flags_clobber() override { sep(); raw("~{cc}"); } - // LLVM inline-asm constraint class letters for AArch64. + // LLVM inline-asm constraint class letters for ARM64. char const *class_letter(AsmRegClass rc) override { switch (rc) { case AsmRegClass_Integer: return "r"; // GPR (x/w) @@ -1010,7 +1010,7 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { return false; } - // AArch64 immediates are written '#'; no scale/log2 addressing forms. + // ARM64 immediates are written '#'; no scale/log2 addressing forms. void write_constant_operand(Ast *op, u32 flags) override { GB_ASSERT(op->tav.mode == Addressing_Constant); op->tav.value = exact_value_to_integer(op->tav.value); @@ -1036,13 +1036,31 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { } } + // ARM64 lane element qualifier for `vN.[i]`, from the operand's element type. + char arm64_lane_qualifier_for_type(Type *t) { + Type *bt = base_type(t); + Type *elem = bt; + if (bt->kind == Type_SimdVector) { + elem = bt->SimdVector.elem; + } else if (bt->kind == Type_Array) { + elem = bt->Array.elem; + } + switch (type_size_of(base_type(elem))) { + case 1: return 'b'; + case 2: return 'h'; + case 4: return 's'; + case 8: return 'd'; + } + return 0; + } + void write_operand(Slice const &op_number, Ast *op, u32 flags) override { if (op->tav.mode == Addressing_Constant) { this->write_constant_operand(op, flags); return; } - // No '*' indirection on AArch64; the register prints normally. + // No '*' indirection on ARM64; the register prints normally. flags &= ~WriteOperandFlag_IndirectBranch; bool negate = (flags & WriteOperandFlag_Negate) != 0; @@ -1056,7 +1074,7 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { if (ed->view_of >= 0) { // Width-view (e.g. `p0w: u32 = p0`): the allocator picks one register; // print it at the requested width via LLVM's w/x operand modifier so both - // names share it. AArch64 GPRs only expose 32-bit (w) and 64-bit (x) + // names share it. ARM64 GPRs only expose 32-bit (w) and 64-bit (x) // names; sub-word views still use the w register. i32 idx = op_number[ed->view_of]; GB_ASSERT(idx >= 0); @@ -1064,7 +1082,7 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { switch (ed->view_bits) { case 8: case 16: case 32: mod = 'w'; break; case 64: mod = 'x'; break; - default: GB_PANIC("asm: invalid AArch64 width-view size %d", ed->view_bits); break; + default: GB_PANIC("asm: invalid ARM64 width-view size %d", ed->view_bits); break; } asm_string = gb_string_append_fmt(asm_string, "${%d:%c}", idx, mod); } else { @@ -1095,18 +1113,119 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { case_ast_node(reg, AsmRegister, op); this->write_string(reg->name.string); // bare: x0, w0, sp, xzr, v0, ... case_end; + case_ast_node(ie, IndexExpr, op); + // Vector-lane access: `acc[0]` -> `$N.d[0]` (i.e. v.d[0]). The base names + // the SIMD operand; the index is a constant lane encoded in the instruction. + Ast *base_op = ie->expr; + Ast *index = ie->index; + + // The lane index is an assemble-time constant, validated and folded by the + // checker onto ie->index. If it isn't present here the checker already + // reported the error (non-constant / out-of-range lane), so bail rather than + // assert — lowering must not be the thing that crashes on a rejected program. + if (index->tav.mode != Addressing_Constant) { + return; + } + ExactValue lane_ev = exact_value_to_integer(index->tav.value); + if (lane_ev.kind != ExactValue_Integer) { + return; + } + i64 lane = exact_value_to_i64(lane_ev); + GB_ASSERT(lane >= 0); // checker guarantees non-negative + + switch (base_op->kind) { + case_ast_node(reg, AsmRegister, base_op); + // Explicit register: the arrangement (`.d`, `.2d`, `.4s`) is part of the + // spelling, so print the name verbatim and append the lane. If it was + // written bare (`v0`), there is no element size to index by. + String rname = reg->name.string; + bool has_arrangement = false; + for (isize i = 0; i < rname.len; i++) { + if (rname.text[i] == '.') { + has_arrangement = true; + break; + } + } + GB_ASSERT_MSG(has_arrangement, + "asm: AArch64 lane access on register '%.*s' needs an element qualifier " + "(e.g. '%.*s.d[%lld]')", + LIT(rname), LIT(rname), cast(long long)lane); + this->write_string(rname); + asm_string = gb_string_append_fmt(asm_string, "[%lld]", cast(long long)lane); + case_end; + + case_ast_node(id, Ident, base_op); + Entity *e = entity_of_node(base_op); + auto *ed = entity_op(e); + + // Lane form needs the v-register, so index the source operand directly with no + // width modifier (a width-view would force a scalar d/s name that can't take a lane). + i32 idx = (ed->view_of >= 0) ? op_number[ed->view_of] : op_number[ed->total_index]; + GB_ASSERT(idx >= 0); + + char q = this->arm64_lane_qualifier_for_type(ed->entity->type); + GB_ASSERT_MSG(q != 0, "asm: cannot determine AArch64 lane element size for '%.*s'", + LIT(e->token.string)); + + asm_string = gb_string_append_fmt(asm_string, "$%d.%c[%lld]", idx, q, cast(long long)lane); + case_end; + + default: + GB_PANIC("asm: AArch64 lane base must be an operand or explicit register, got '%s'", + expr_to_string(base_op)); + break; + } + case_end; default: GB_PANIC("TODO(bill): write_operand for '%s'", expr_to_string(op)); break; } } - // AArch64 addressing here: `[base]`, `[base, #disp]`, or `[base, Xindex]`. - // (Scaled/extended index and pre/post-index writebacks aren't expressible via - // this explicit-syntax path.) + // Resolve a memory scale to an ARM64 `LSL #n` shift amount. The frontend + // encodes scale either as a multiply (index * {1,2,4,8,16}) or as an explicit + // shift (index << n); ARM64 register-offset addressing always wants the shift. + i64 arm64_scale_shift_amount(AstAsmMemoryOperand *mem_op) { + Ast *scale = mem_op->scale; + GB_ASSERT(scale != nullptr); + GB_ASSERT_MSG(scale->tav.mode == Addressing_Constant, + "asm: ARM64 memory scale must be a constant shift amount"); + ExactValue ev = exact_value_to_integer(scale->tav.value); + GB_ASSERT(ev.kind == ExactValue_Integer); + i64 v = exact_value_to_i64(ev); + + switch (mem_op->scale_op.kind) { + case Token_Mul: + switch (v) { + case 1: return 0; + case 2: return 1; + case 4: return 2; + case 8: return 3; + case 16: return 4; // 128-bit (Q) transfers + default: + error(scale, "asm: ARM64 memory scale must be 1, 2, 4, 8, or 16, got %lld", cast(long long)v); + return 0; + } + case Token_Shl: + case Token_Shr: + if (v < 0 || v > 4) { + error(scale, "asm: ARM64 memory shift amount must be between 0 and 4, got %lld", cast(long long)v); + return 0; + } + return v; + default: + GB_PANIC("asm: invalid ARM64 memory scale operator"); + return 0; + } + } + + // ARM64 addressing: `[base]`, `[base, #disp]`, `[base, Xindex]`, or + // `[base, Xindex, LSL #n]`. Base+index and base+disp are mutually exclusive + // addressing modes, so an index precludes a displacement. void write_memory_operand(Slice const &op_number, AstAsmMemoryOperand *mem_op, u32 flags) override { - GB_ASSERT_MSG(mem_op->segment_override == nullptr, "asm: AArch64 has no segment overrides"); - GB_ASSERT_MSG(mem_op->scale == nullptr, "asm: AArch64 memory operands take no scaled index here"); + GB_ASSERT_MSG(mem_op->segment_override == nullptr, "asm: ARM64 has no segment overrides"); + GB_ASSERT_MSG(mem_op->scale == nullptr || mem_op->index != nullptr, + "asm: ARM64 memory scale requires an index register"); write_cstr("["); if (mem_op->base != nullptr) { @@ -1115,6 +1234,14 @@ struct lbAsmGenerate_arm64 : lbAsmGenerate { if (mem_op->index != nullptr) { write_cstr(", "); this->write_operand(op_number, mem_op->index, flags&~WriteOperandFlag_PrintPrefixes); + if (mem_op->scale != nullptr) { + i64 shift = this->arm64_scale_shift_amount(mem_op); + // Omit the no-op shift so `[x0, x1, LSL #0]` prints as the canonical + // `[x0, x1]`. + if (shift != 0) { + asm_string = gb_string_append_fmt(asm_string, ", lsl #%lld", cast(long long)shift); + } + } } else if (mem_op->disp) { write_cstr(", "); u32 disp_flags = flags;