Fix pinning logic for asm registers

This commit is contained in:
gingerBill
2026-08-10 12:06:28 +01:00
parent b4da67c6af
commit cee5bd79e0
4 changed files with 89 additions and 45 deletions

View File

@@ -2155,6 +2155,22 @@ gb_internal void check_asm_specs(CheckerContext *ctx, Scope *scope, Slice<Ast *>
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<Ast *>
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<Ast *>
} 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<Ast *>
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
}
}
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -497,6 +497,7 @@ struct AstSplitArgs {
Ast * name; \
}) \
AST_KIND(AsmInstruction, "asm instruction", struct { \
Ast * prefix; /*optional*/ \
Ast * name; \
Slice<Ast *> operands; \
}) \