From cee5bd79e06219be31c0e1eca037fc57c9ab97b2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Aug 2026 12:06:28 +0100 Subject: [PATCH] Fix pinning logic for asm registers --- src/check_decl.cpp | 37 ++++++++++++++--- src/llvm_backend_asm.cpp | 87 ++++++++++++++++++++++------------------ src/parser.cpp | 9 ++++- src/parser.hpp | 1 + 4 files changed, 89 insertions(+), 45 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 77095b7ac..2783e982d 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -2155,6 +2155,22 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice bool must_check_value = false; + String pin = {}; + if (spec->value != nullptr) { + if (spec->value->kind != Ast_AsmRegister) { + gbString s = expr_to_string(spec->value); + error(spec->value, "Expected an asm register, got %s", s); + gb_string_free(s); + continue; + } + + ast_node(reg, AsmRegister, spec->value); + pin = reg->name.string; + if (pin == "any") { + pin = {}; + } + } + if (spec->tied_name == nullptr) { if (spec->type != nullptr) { Type *type = check_type(ctx, spec->type); @@ -2175,6 +2191,7 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice AsmTemplateEntityDecl ed = asm_template_entity_decl_default(entity); ed.param_group = AsmTemplateEntityDeclParamGroup_Scratch; ed.total_index = cast(i32)asm_template_entity_decls->count; + ed.pin = pin; array_add(asm_template_entity_decls, ed); } else { TokenPos pos = found->token.pos; @@ -2188,6 +2205,17 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice } else if (input == nullptr) { error(spec->name, "Undefined parameter declaration '%.*s'", LIT(spec->name->Ident.token.string)); continue; + } else { + i32 index = -1; + auto group = check_asm_find_group(input, *asm_template_entity_decls, &index); + gb_unused(group); + GB_ASSERT(index >= 0); + auto *i = &(*asm_template_entity_decls)[index]; + if (i->pin.len == 0) { + i->pin = pin; + } else { + error(spec_, "Asm register has already been pinned"); + } } } else { @@ -2230,13 +2258,12 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice i->tie = output_index; o->tie = input_index; + i->pin = pin; + o->pin = pin; + + must_check_value = true; } - - if (spec->value != nullptr) { - // TODO(bill): check registers - } - } } diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index 2b5141dfa..cc08841ff 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -147,7 +147,7 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar return pos; }; - // ---- Pass 1: outputs ----------------------------------------------------- + // Pass 1: outputs // Real outputs plus *unpinned* register scratch (modeled as discarded // early-clobber outputs, since a clobber can only name a fixed register). for_array(i, ops) { @@ -163,37 +163,32 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar sep(); - if (false && 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)); - - // Use the entity's real declared type so the return-struct slot matches - // the constraint's width/class (e.g. <4 x float> for a #simd[4]f32 scratch). - LLVMTypeRef ty = is_alloc_scratch - ? lb_type(m, e.entity->type) - : output_llvm_type(e); - - ret_slot[i] = cast(i32)ret_types.count; - array_add(&ret_types, ty); - op_number[i] = next_op++; + // Register output: '=' ['&'] ( '{pin}' | class-letter ) + raw("="); + if (is_alloc_scratch) { // early-clobber: keep scratch off any input reg + raw("&"); } + if (e.pin.len != 0) { + raw("{"); put(e.pin); raw("}"); + } else { + raw(class_letter(e.reg_class)); + } + + // Use the entity's real declared type so the return-struct slot matches + // the constraint's width/class (e.g. <4 x float> for a #simd[4]f32 scratch). + LLVMTypeRef ty = is_alloc_scratch ? lb_type(m, e.entity->type) : 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 ------------------------------------------------------ + // Pass 2: inputs for (isize i = 0; i < ops.count; i++) { AsmTemplateEntityDecl const &e = ops[i]; - if (e.param_group != AsmTemplateEntityDeclParamGroup_Input) continue; + if (e.param_group != AsmTemplateEntityDeclParamGroup_Input) { + continue; + } sep(); lbValue v = args[e.param_index]; @@ -209,8 +204,11 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar switch (e.kind) { case AsmTemplateEntityDecl_Register: case AsmTemplateEntityDecl_Memory: - if (e.pin.len != 0) { raw("{"); put(e.pin); raw("}"); } - else raw(class_letter(e.reg_class)); + 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: { @@ -230,7 +228,7 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar op_number[i] = next_op++; } - // ---- Build the template text --------------------------------------------- + // Build the template text GB_ASSERT(tmpl.node->kind == Ast_AsmTemplate); auto *node = &tmpl.node->AsmTemplate; for_array(i, node->instructions) { @@ -263,7 +261,7 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar } } - // ---- Pass 3: clobbers ---------------------------------------------------- + // 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++) { @@ -289,7 +287,10 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar } } - // ---- Build the callee type ----------------------------------------------- + // Build the callee type + // NOTE(bill): Even though the user has given a signature, this might not actually match what + // LLVM requires it to be due to the scratch parameters and more, so many of the results might + // need to be completely ignored to match the user's given signature. LLVMTypeRef ret_ty = nullptr; if (ret_types.count == 0) { ret_ty = LLVMVoidTypeInContext(ctx); @@ -301,21 +302,27 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar LLVMTypeRef fn_ty = LLVMFunctionType(ret_ty, param_types.data, cast(unsigned)param_types.count, /*vararg*/false); + // TODO(bill): determine all the cases when side-effects happen + bool has_side_effects = tmpl.has_side_effects; + 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, + /*HasSideEffects*/ 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, ""); - { + { // DEBUG PRINT!!! + // DEBUG PRINT!!! + // DEBUG PRINT!!! + // DEBUG PRINT!!! gb_printf_err("%s\n", asm_string); char *ir = LLVMPrintValueToString(call); - gb_printf_err("%s\n", ir); + gb_printf_err("%s\n\n", ir); LLVMDisposeMessage(ir); } @@ -326,7 +333,7 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar LLVMAddCallSiteAttribute(call, cast(LLVMAttributeIndex)(elem_attrs[k].arg_pos + 1), attr); } - // ---- Repackage results in Odin result order ------------------------------ + // 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) { @@ -340,8 +347,12 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar 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 + 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) diff --git a/src/parser.cpp b/src/parser.cpp index 10d2e7cdd..48e933c63 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -542,6 +542,7 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { n->AsmLabelDecl.name = clone_ast(n->AsmLabelDecl.name, f); break; case Ast_AsmInstruction: + n->AsmInstruction.prefix = clone_ast(n->AsmInstruction.prefix, f); n->AsmInstruction.name = clone_ast(n->AsmInstruction.name, f); n->AsmInstruction.operands = clone_ast_array(n->AsmInstruction.operands, f); break; @@ -2628,8 +2629,12 @@ gb_internal Ast *parse_asm_template(AstFile *f) { value = parse_asm_register(f); } - if (type == nullptr && value == nullptr) { - syntax_error(f->curr_token, "An asm specification must specify at least either a type or a value"); + if (tied_name != nullptr) { + if (type != nullptr) { + syntax_error(f->curr_token, "An asm specification for tied values cannot declare a type"); + } + } else if (type == nullptr && value == nullptr) { + syntax_error(f->curr_token, "An asm specification must specify at least either a type or a value if the value is not tied"); } spec = alloc_ast_node(f, Ast_AsmSpec); diff --git a/src/parser.hpp b/src/parser.hpp index 0390937c0..22ddc0938 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -497,6 +497,7 @@ struct AstSplitArgs { Ast * name; \ }) \ AST_KIND(AsmInstruction, "asm instruction", struct { \ + Ast * prefix; /*optional*/ \ Ast * name; \ Slice operands; \ }) \