From 22f2e2ac7869348c66ddf8debea70ad2d9a075b6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Aug 2026 23:38:36 +0100 Subject: [PATCH] Try to lower to LLVM IR from Odin's `asm` template syntax --- src/check_decl.cpp | 65 +++++-- src/entity.cpp | 42 ++++- src/llvm_backend_asm.cpp | 378 +++++++++++++++++++++++++++++++++++---- 3 files changed, 434 insertions(+), 51 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index ea35e96c5..77095b7ac 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -2004,6 +2004,25 @@ gb_internal bool is_valid_asm_parameter_type(Type *type) { return false; } +gb_internal AsmRegClass check_asm_reg_class_from_type(Type *type) { + if (is_type_integer(type)) { + return AsmRegClass_Integer; + } + if (is_type_float(type)) { + return AsmRegClass_Float; + } + if (is_type_boolean(type)) { + return AsmRegClass_Integer; + } + if (is_type_pointer(type) || is_type_multi_pointer(type)) { + return AsmRegClass_Integer; + } + if (is_type_simd_vector(type)) { + return AsmRegClass_Vector; + } + return AsmRegClass_Unknown; +} + gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool input_parameters, Array *asm_template_entity_decls) { Type *tuple = alloc_type_tuple(); if (_params == nullptr) { @@ -2015,6 +2034,7 @@ gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope Array variables = {}; variables.allocator = heap_allocator(); + i32 param_index = 0; for (Ast *param : params) { ast_node(field, Field, param); @@ -2068,21 +2088,21 @@ gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope if (found == nullptr) { array_add(&variables, entity); - AsmTemplateEntityDecl ed = {}; - ed.entity = entity; - ed.kind = AsmTemplateEntityDecl_Register; - if (is_type_internally_pointer_like(type)) { - ed.kind = AsmTemplateEntityDecl_Memory; - } + AsmTemplateEntityDecl ed = asm_template_entity_decl_default(entity); if (is_poly_name) { ed.kind = AsmTemplateEntityDecl_Immediate; } if (input_parameters) { ed.param_group = AsmTemplateEntityDeclParamGroup_Input; + ed.param_index = param_index++; + ed.result_index = -1; } else { ed.param_group = AsmTemplateEntityDeclParamGroup_Output; + ed.param_index = -1; + ed.result_index = param_index++; } + ed.total_index = cast(i32)asm_template_entity_decls->count; array_add(asm_template_entity_decls, ed); } else { TokenPos pos = found->token.pos; @@ -2100,12 +2120,15 @@ gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope return tuple; } -gb_internal AsmTemplateEntityDeclParamGroup check_asm_find_group(Entity *entity, Array const &asm_template_entity_decls) { - for (auto const &ed : asm_template_entity_decls) { +gb_internal AsmTemplateEntityDeclParamGroup check_asm_find_group(Entity *entity, Array const &asm_template_entity_decls, i32 *index_) { + for_array(i, asm_template_entity_decls) { + auto const &ed = asm_template_entity_decls[i]; if (ed.entity == entity) { + if (index_) *index_ = cast(i32)i; return ed.param_group; } } + if (index_) *index_ = -1; return AsmTemplateEntityDeclParamGroup_Unknown; }; @@ -2120,7 +2143,6 @@ gb_internal AsmTemplateEntityDeclKind check_asm_find_kind(Entity *entity, Array< gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice const &specs, Array *asm_template_entity_decls) { - for (Ast *spec_ : specs) { if (spec_->kind != Ast_AsmSpec) { continue; @@ -2150,14 +2172,9 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice Entity *found = scope_insert(scope, entity); if (found == nullptr) { - AsmTemplateEntityDecl ed = {}; - ed.entity = entity; - ed.kind = AsmTemplateEntityDecl_Register; - if (is_type_internally_pointer_like(type)) { - ed.kind = AsmTemplateEntityDecl_Memory; - } + AsmTemplateEntityDecl ed = asm_template_entity_decl_default(entity); ed.param_group = AsmTemplateEntityDeclParamGroup_Scratch; - + ed.total_index = cast(i32)asm_template_entity_decls->count; array_add(asm_template_entity_decls, ed); } else { TokenPos pos = found->token.pos; @@ -2190,8 +2207,11 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice continue; } - auto input_group = check_asm_find_group(input, *asm_template_entity_decls); - auto output_group = check_asm_find_group(output, *asm_template_entity_decls); + i32 input_index = -1; + i32 output_index = -1; + + auto input_group = check_asm_find_group(input, *asm_template_entity_decls, &input_index); + auto output_group = check_asm_find_group(output, *asm_template_entity_decls, &output_index); if (input_group != AsmTemplateEntityDeclParamGroup_Input) { error(input->token, "Parameter tied with '%.*s' must be an input parameter", LIT(output->token.string)); continue; @@ -2201,6 +2221,15 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice continue; } + GB_ASSERT(input_index >= 0); + GB_ASSERT(output_index >= 0); + + auto *i = &(*asm_template_entity_decls)[input_index]; + auto *o = &(*asm_template_entity_decls)[output_index]; + + i->tie = output_index; + o->tie = input_index; + must_check_value = true; } diff --git a/src/entity.cpp b/src/entity.cpp index c439404b6..f2a3f3add 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -169,6 +169,14 @@ enum AsmTemplateEntityDeclKind : u8 { AsmTemplateEntityDecl_COUNT }; +enum AsmRegClass : u8 { + AsmRegClass_Unknown, + AsmRegClass_Integer, + AsmRegClass_Float, + AsmRegClass_Vector, + AsmRegClass_Mask, +}; + enum AsmTemplateEntityDeclParamGroup : u8 { AsmTemplateEntityDeclParamGroup_Unknown, AsmTemplateEntityDeclParamGroup_Input, @@ -183,7 +191,15 @@ struct AsmTemplateEntityDecl { Entity * tied_entity; AsmTemplateEntityDeclKind kind; AsmTemplateEntityDeclParamGroup param_group; - u16 register_map; + AsmRegClass reg_class; + + String pin; + + i32 total_index; + + i32 param_index; // index into the Proc signature's params (inputs), else -1 + i32 result_index; // index into results (outputs), else -1 + i32 tie; // InOut: index into operands[] of the tied output; else -1 }; // An Entity is a named "thing" in the language @@ -333,12 +349,16 @@ struct Entity { bool is_align_stack; Scope *param_scope; Scope *label_scope; - Array decls; + } AsmTemplate; }; }; + +gb_internal AsmRegClass check_asm_reg_class_from_type(Type *type); +gb_internal bool is_type_internally_pointer_like(Type *t); + gb_internal InternedString entity_interned_name(Entity *entity) { auto name = entity->interned_name.load(); if (name.value == 0) { @@ -349,6 +369,24 @@ gb_internal InternedString entity_interned_name(Entity *entity) { return name; } + +gb_internal AsmTemplateEntityDecl asm_template_entity_decl_default(Entity *entity) { + AsmTemplateEntityDecl ed = {}; + ed.kind = AsmTemplateEntityDecl_Register; + if (is_type_internally_pointer_like(entity->type)) { + ed.kind = AsmTemplateEntityDecl_Memory; + } + ed.reg_class = check_asm_reg_class_from_type(entity->type); + ed.entity = entity; + ed.total_index = -1; + ed.param_index = -1; + ed.result_index = -1; + ed.tie = -1; + + return ed; +} + + gb_internal bool is_entity_kind_exported(EntityKind kind, bool allow_builtin = false) { switch (kind) { case Entity_Builtin: diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index ad8e54a0e..eaae31320 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -1,43 +1,359 @@ +gb_internal AsmTemplateEntityDecl *lb_asm_entity_decl(Array *decls, Entity *e) { + for (AsmTemplateEntityDecl &op : *decls) { + if (op.entity == e) { + return &op; + } + } + GB_PANIC("Could not find asm entity %s", LIT(e->token.string)); + return nullptr; +}; + +gb_internal gbString lb_asm_write_label_name(gbString asm_string, AstIdent *label_ident) { + String name = label_ident->token.string; + asm_string = gb_string_appendc(asm_string, ".L"); + asm_string = gb_string_append_length(asm_string, name.text, name.len); + // ${:uid} expands to a per-instantiation unique integer, so repeated + // inlining of the same template can't collide on the label symbol. + asm_string = gb_string_appendc(asm_string, "${:uid}"); + return asm_string; +} + +gb_internal gbString lb_asm_write_operand(gbString asm_string, Array op_number, Array *decls, Ast *op, bool print_prefixes=true) { + switch (op->kind) { + case_ast_node(i, Ident, op); + Entity *e = entity_of_node(op); + auto *ed = lb_asm_entity_decl(decls, e); + + i32 idx = op_number[ed->total_index]; + GB_ASSERT(idx >= 0); + asm_string = gb_string_append_fmt(asm_string, "$%d", idx); + case_end; + case_ast_node(mem_op, AsmMemoryOperand, op); + if (mem_op->disp) { + asm_string = lb_asm_write_operand(asm_string, op_number, decls, mem_op->disp, /*print_prefixes*/false); + } + asm_string = gb_string_appendc(asm_string, "("); + GB_ASSERT(mem_op->base != nullptr); + asm_string = lb_asm_write_operand(asm_string, op_number, decls, mem_op->base); + if (mem_op->index) { + asm_string = gb_string_appendc(asm_string, ","); + asm_string = lb_asm_write_operand(asm_string, op_number, decls, mem_op->index); + if (mem_op->scale) { + asm_string = gb_string_appendc(asm_string, ","); + asm_string = lb_asm_write_operand(asm_string, op_number, decls, mem_op->scale, /*print_prefixes*/false); + } + } + asm_string = gb_string_appendc(asm_string, ")"); + case_end; + + case_ast_node(bl, BasicLit, op); + ExactValue ev = op->tav.value; + GB_ASSERT(ev.kind != ExactValue_Invalid); + switch (ev.kind) { + case ExactValue_Integer: { + String s = big_int_to_string(heap_allocator(), &ev.value_integer, 10); + if (print_prefixes) { + asm_string = gb_string_appendc(asm_string, "$$"); + } + asm_string = gb_string_append_length(asm_string, s.text, s.len); + gb_free(heap_allocator(), s.text); + break; + } + default: + GB_PANIC("Unsupported asm immediate literal %s", expr_to_string(op)); + break; + } + case_end; + + case_ast_node(label, AsmLabelDecl, op); + String name = label->name->Ident.token.string; + asm_string = lb_asm_write_label_name(asm_string, &label->name->Ident); + case_end; + default: + GB_PANIC("TODO %s", expr_to_string(op)); + break; + } + return asm_string; +} + + gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Array const &args) { - GB_ASSERT(entity->kind == Entity_AsmTemplate); + GB_ASSERT(entity != nullptr); + lbModule *m = p->module; + LLVMContextRef ctx = m->ctx; - // GB_PANIC("TODO(bill): lb_emit_asm_template_call"); - gbString asm_string = gb_string_make(heap_allocator(), ""); - gbString constraints = gb_string_make(heap_allocator(), ""); + // Assumed frontend accessor: template string, flags, dialect, and the operand table. + auto &tmpl = entity->AsmTemplate; + Array &ops = tmpl.decls; - auto *ate = &entity->AsmTemplate; - GB_ASSERT(ate->node->kind == Ast_AsmTemplate); - auto *node = &ate->node->AsmTemplate; + TEMPORARY_ALLOCATOR_GUARD(); + gbString asm_string = gb_string_make_reserve(temporary_allocator(), 64); + gbString constraints = gb_string_make_reserve(temporary_allocator(), 64); + + auto param_types = array_make(temporary_allocator(), 0, ops.count); + auto call_args = array_make(temporary_allocator(), 0, ops.count); + auto ret_types = array_make(temporary_allocator(), 0, ops.count); + + // Per-operand bookkeeping, indexed the same as `ops`. + auto op_number = array_make(temporary_allocator(), ops.count, ops.count); // $N, or -1 for clobbers + auto ret_slot = array_make(temporary_allocator(), ops.count, ops.count); // return-struct index, or -1 + for_array(i, ops) { + op_number[i] = -1; + ret_slot[i] = -1; + } + + // elementtype() attrs to attach after the call is built (indirect/memory operands). + struct ElemAttr { + unsigned arg_pos; + LLVMTypeRef elem; + }; + auto elem_attrs = array_make(temporary_allocator(), 0, ops.count); + + i32 next_op = 0; // running $N counter (outputs first, then inputs) + + auto sep = [&]() { + if (gb_string_length(constraints) != 0) { + constraints = gb_string_appendc(constraints, ","); + } + }; + auto raw = [&](char const *s) { + constraints = gb_string_appendc(constraints, s); + }; + auto put = [&](String s) { + constraints = gb_string_append_length(constraints, s.text, s.len); + }; + + auto class_letter = [&](AsmRegClass rc) -> char const * { + switch (rc) { + case AsmRegClass_Integer: return "r"; + case AsmRegClass_Float: return "x"; // TODO(bill): target-dependent + case AsmRegClass_Vector: return "x"; // TODO(bill): target-dependent + case AsmRegClass_Mask: return "^Yk"; // AVX-512 k-regs + default: GB_PANIC("asm: unknown reg class"); return "r"; + } + }; + + // LLVM type of a returned register output, taken from the proc signature's results. + auto output_llvm_type = [&](AsmTemplateEntityDecl const &e) -> LLVMTypeRef { + Type *pt = base_type(entity->type); + Type *rt = pt->Proc.results->Tuple.variables[e.result_index]->type; + return lb_type(m, rt); + }; + + auto add_arg = [&](LLVMValueRef v) -> unsigned { + unsigned pos = cast(unsigned)call_args.count; + array_add(¶m_types, LLVMTypeOf(v)); + array_add(&call_args, v); + return pos; + }; + + for_array(i, ops) { + AsmTemplateEntityDecl const &e = ops[i]; + + bool is_output = e.param_group == AsmTemplateEntityDeclParamGroup_Output; + bool is_alloc_scratch = e.param_group == AsmTemplateEntityDeclParamGroup_Scratch + && e.kind == AsmTemplateEntityDecl_Register + && e.pin.len == 0; + if (!is_output && !is_alloc_scratch) { + continue; + } + + sep(); + + if (e.kind == AsmTemplateEntityDecl_Memory) { + // Indirect memory output: writes through a pointer, so it takes an arg + // and contributes nothing to the return type. + raw("=*m"); + lbValue ptr = args[e.param_index]; + unsigned pos = add_arg(ptr.value); + array_add(&elem_attrs, ElemAttr{pos, lb_type(m, type_deref(ptr.type))}); + op_number[i] = next_op++; + } else { + // Register output: '=' ['&'] ( '{pin}' | class-letter ) + raw("="); + if (is_alloc_scratch) raw("&"); // early-clobber: keep scratch off any input reg + if (e.pin.len != 0) { raw("{"); put(e.pin); raw("}"); } + else raw(class_letter(e.reg_class)); + + LLVMTypeRef ty = is_alloc_scratch + ? lb_type(m, t_uintptr) // TODO: pick a register-width type per reg_class + : output_llvm_type(e); + + ret_slot[i] = cast(i32)ret_types.count; + array_add(&ret_types, ty); + op_number[i] = next_op++; + } + } + + // ---- Pass 2: inputs ------------------------------------------------------ + for (isize i = 0; i < ops.count; i++) { + AsmTemplateEntityDecl const &e = ops[i]; + if (e.param_group != AsmTemplateEntityDeclParamGroup_Input) continue; + + sep(); + lbValue v = args[e.param_index]; + + if (e.tie >= 0) { + // Tied read-write input: a matching constraint referencing the tied + // output's operand number (e.g. "0"). + i32 n = op_number[e.tie]; + GB_ASSERT(n >= 0); + constraints = gb_string_append_fmt(constraints, "%d", n); + add_arg(v.value); + } else { + switch (e.kind) { + case AsmTemplateEntityDecl_Register: + if (e.pin.len != 0) { raw("{"); put(e.pin); raw("}"); } + else raw(class_letter(e.reg_class)); + add_arg(v.value); + break; + case AsmTemplateEntityDecl_Memory: { + raw("*m"); // indirect + unsigned pos = add_arg(v.value); + array_add(&elem_attrs, ElemAttr{pos, lb_type(m, type_deref(v.type))}); + break; + } + case AsmTemplateEntityDecl_Immediate: + raw("i"); // TODO: "n" if a known-constant integer is required + add_arg(v.value); + break; + default: + GB_PANIC("asm: invalid input operand kind"); + } + } + op_number[i] = next_op++; + } + + GB_ASSERT(tmpl.node->kind = Ast_AsmTemplate); + auto *node = &tmpl.node->AsmTemplate; for_array(i, node->instructions) { - auto *instruction = node->instructions[i]; - gb_unused(instruction); + if (i > 0) { + asm_string = gb_string_appendc(asm_string, "\n"); + } + Ast *instr_ = node->instructions[i]; + switch (instr_->kind) { + case_ast_node(instr, AsmInstruction, instr_); + asm_string = gb_string_appendc(asm_string, "\t"); + String name = instr->name->Ident.token.string; + asm_string = gb_string_append_length(asm_string, name.text, name.len); + asm_string = gb_string_appendc(asm_string, " "); + for (isize j = instr->operands.count-1; j >= 0; j -= 1) { + Ast *op = instr->operands[j]; + if (j < instr->operands.count-1) { + asm_string = gb_string_appendc(asm_string, ", "); + } + asm_string = lb_asm_write_operand(asm_string, op_number, &ops, op); + } + + + case_end; + case_ast_node(label, AsmLabelDecl, instr_); + asm_string = lb_asm_write_label_name(asm_string, &label->name->Ident); + asm_string = gb_string_appendc(asm_string, ":"); + case_end; + default: + GB_PANIC("Invalid asm instruction"); + break; + } } - // asm_string = gb_string_appendc(asm_string, "lock cmpxchg16b $3"); - // asm_string = gb_string_appendc(asm_string, "\\0A\\09"); - // asm_string = gb_string_appendc(asm_string, "setz $2"); - // constraints = gb_string_appendc(constraints, "*m,={ax},={dx},=r,{ax},{dx},{bx},{cx},~{cc},~{memory}"); + gb_printf_err("%s\n", asm_string); - Type *proc_type = base_type(entity->type); - GB_ASSERT(proc_type->kind == Type_Proc); + // ---- Pass 3: clobbers ---------------------------------------------------- + // Only the Scratch group. Unpinned register scratch was already emitted as an + // output in Pass 1, so it is skipped here. + for (isize i = 0; i < ops.count; i++) { + AsmTemplateEntityDecl const &e = ops[i]; + if (e.param_group != AsmTemplateEntityDeclParamGroup_Scratch) { + continue; + } + if (e.kind == AsmTemplateEntityDecl_Register && e.pin.len == 0) { + continue; + } - LLVMTypeRef llvm_type = lb_type_internal_for_procedures_raw(p->module, proc_type); - gb_printf_err("%s\n", LLVMPrintTypeToString(llvm_type)); - - LLVMValueRef fn = LLVMGetInlineAsm(llvm_type, asm_string, gb_string_length(asm_string), - constraints, gb_string_length(constraints), - entity->AsmTemplate.has_side_effects, - entity->AsmTemplate.is_align_stack, - LLVMInlineAsmDialectATT, /*CanThrow*/false); - - LLVMValueRef *llvm_args = gb_alloc_array(heap_allocator(), LLVMValueRef, args.count); - for_array(i, args) { - llvm_args[i] = args[i].value; + sep(); + switch (e.kind) { + case AsmTemplateEntityDecl_Register: // pinned -> real clobber + GB_ASSERT(e.pin.len != 0); + raw("~{"); put(e.pin); raw("}"); + break; + case AsmTemplateEntityDecl_Memory: // general memory clobber + raw("~{memory}"); + break; + default: + GB_PANIC("asm: invalid scratch operand kind"); + } } - LLVMValueRef result = LLVMBuildCall2(p->builder, llvm_type, fn, llvm_args, cast(unsigned)args.count, ""); - Type *result_type = reduce_tuple_to_single_type(proc_type->Proc.results); - gb_printf_err("%s\n", LLVMPrintValueToString(result)); - return {result, result_type}; + LLVMTypeRef ret_ty = nullptr; + if (ret_types.count == 0) { + ret_ty = LLVMVoidTypeInContext(ctx); + } else if (ret_types.count == 1) { + ret_ty = ret_types[0]; + } else { + ret_ty = LLVMStructTypeInContext(ctx, ret_types.data, cast(unsigned)ret_types.count, /*packed*/false); + } + + LLVMTypeRef fn_ty = LLVMFunctionType(ret_ty, param_types.data, cast(unsigned)param_types.count, /*vararg*/false); + + LLVMValueRef ia = LLVMGetInlineAsm( + fn_ty, + asm_string, cast(size_t)gb_string_length(asm_string), + constraints, cast(size_t)gb_string_length(constraints), + /*HasSideEffects*/ tmpl.has_side_effects, + /*IsAlignStack*/ tmpl.is_align_stack, + LLVMInlineAsmDialectATT, + /*CanThrow*/ false); + + LLVMValueRef call = LLVMBuildCall2(p->builder, fn_ty, ia, call_args.data, cast(unsigned)call_args.count, ""); + + gb_printf_err("%s\n", LLVMPrintValueToString(call)); + + // Attach elementtype() to every indirect operand's pointer arg (opaque-pointer requirement). + unsigned et_kind = LLVMGetEnumAttributeKindForName("elementtype", 11); + for (isize k = 0; k < elem_attrs.count; k++) { + LLVMAttributeRef attr = LLVMCreateTypeAttribute(ctx, et_kind, elem_attrs[k].elem); + LLVMAddCallSiteAttribute(call, cast(LLVMAttributeIndex)(elem_attrs[k].arg_pos + 1), attr); + } + + // ---- Repackage results in Odin result order ------------------------------ + Type *pt = base_type(entity->type); + isize result_count = (pt->Proc.results != nullptr) ? pt->Proc.results->Tuple.variables.count : 0; + if (result_count == 0) { + return lbValue{}; // void asm (memory outputs already wrote through their pointers) + } + + // The LLVM return struct is ordered by operand, and includes scratch slots; + // pull out only the real register outputs and index them by result_index. + auto result_vals = array_make(temporary_allocator(), result_count, result_count); + for (isize i = 0; i < result_count; i++) result_vals[i] = nullptr; + + for (isize i = 0; i < ops.count; i++) { + AsmTemplateEntityDecl const &e = ops[i]; + if (e.param_group != AsmTemplateEntityDeclParamGroup_Output) continue; + if (e.result_index < 0) continue; // memory output: not a returned value + GB_ASSERT(ret_slot[i] >= 0); + + LLVMValueRef v = (ret_types.count == 1) + ? call // single-element return is not a struct + : LLVMBuildExtractValue(p->builder, call, cast(unsigned)ret_slot[i], ""); + result_vals[e.result_index] = v; + } + + if (result_count == 1) { + Type *rt = pt->Proc.results->Tuple.variables[0]->type; + return lbValue{result_vals[0], rt}; + } + + // Multiple results -> assemble Odin's result aggregate in result order. + Type *results_type = pt->Proc.results; + LLVMValueRef agg = LLVMGetUndef(lb_type(m, results_type)); + for (isize i = 0; i < result_count; i++) { + GB_ASSERT(result_vals[i] != nullptr); + agg = LLVMBuildInsertValue(p->builder, agg, result_vals[i], cast(unsigned)i, ""); + } + + + return lbValue{agg, results_type}; } \ No newline at end of file