From d1f561ec88391ff1696eae0da4545975a632475b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 17 Aug 2026 12:59:23 +0100 Subject: [PATCH] Correct `instruction_size_suffix` so that the correct suffix can be selected for the instruction to adhere to AT&T syntax --- src/check_asm.cpp | 16 +++-- src/llvm_backend_asm.cpp | 141 +++++++++++++++++++++++++++++++++------ src/parser.cpp | 2 +- src/parser.hpp | 3 +- 4 files changed, 137 insertions(+), 25 deletions(-) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index 7f2d1298c..24528dda6 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -597,6 +597,10 @@ gb_internal void check_asm_specs(AsmCtx *asm_ctx, CheckerContext *ctx, Scope *sc auto *i = &(*asm_template_entity_decls)[index]; if (i->pin.len == 0) { i->pin = pin; + i->pin_flag = pin_flag; + if (pin_flag.len != 0 && group != AsmTemplateEntityDeclParamGroup_Output) { + error(spec->value, "Input parameters cannot be pinned to a flag style register"); + } } else { error(spec_, "Asm register has already been pinned"); } @@ -648,14 +652,14 @@ gb_internal void check_asm_specs(AsmCtx *asm_ctx, CheckerContext *ctx, Scope *sc i->pin = pin; o->pin = pin; - - i->pin_flag = pin_flag; - o->pin_flag = pin_flag; - if (other_scratch != nullptr) { GB_ASSERT(spec->value != nullptr); error(spec->value, "Another parameter must be assigned/paired with a scratch parameter declaration, not a tie"); } + + if (pin_flag.len != 0) { + error(spec->value, "Input parameters, and thus tied parameters, cannot be pinned to a flag style register"); + } } } } @@ -920,6 +924,10 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm GB_ASSERT(tmpl_entity->kind == Entity_AsmTemplate); + GB_ASSERT(valid_form_index >= 0); + instr->mnemonic = mnemonic; + instr->valid_form_index = cast(i32)valid_form_index; + // Handle clobbering from mnemonic auto clobber = asm_ctx->clobber(mnemonic); diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index ff502cf47..e34b33122 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -138,20 +138,6 @@ struct lbAsmGenerate { } }; - // Scan an instruction's operands for an annotated memory operand and return its - // size suffix, or 0 if none. The checker has already verified the annotation - // agrees with the matched encoding form, so a suffix here can never conflict. - char instruction_size_suffix(AstAsmInstruction *instr) { - char suffix = 0; - for (Ast *operand : instr->operands) { - char s = this->size_suffix_for_operand(operand); - if (s != 0) { - suffix = s; - } - } - return suffix; - } - // LLVM type of a returned register output, taken from the proc signature's results. LLVMTypeRef output_llvm_type(lbModule *m, AsmTemplateEntityDecl const &e) { @@ -160,9 +146,18 @@ struct lbAsmGenerate { return lb_type(m, rt); }; + // The declared Odin result type for an output entity. + Type *result_type_of(AsmTemplateEntityDecl const &e) { + Type *pt = base_type(tmpl_entity->type); + return pt->Proc.results->Tuple.variables[e.result_index]->type; + } + + virtual char instruction_size_suffix(AstAsmInstruction *instr) = 0; virtual char size_suffix_for_operand(Ast *op) = 0; virtual gbString write_memory_operand(gbString asm_string, Array const &op_number, AstAsmMemoryOperand *mem_op, u32 flags) = 0; virtual lbValue emit_call(lbProcedure *p, Array const &args) = 0; + virtual String flag_output_cc_suffix(String const &pin_flag) = 0; + }; struct lbAsmGenerate_amd64 : lbAsmGenerate { @@ -193,6 +188,72 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { return 0; } + + // Scan an instruction's operands for an annotated memory operand and return its + // size suffix, or 0 if none. The checker has already verified the annotation + // agrees with the matched encoding form, so a suffix here can never conflict. + char instruction_size_suffix(AstAsmInstruction *instr) override { + char suffix = 0; + for (Ast *operand : instr->operands) { + char s = this->size_suffix_for_operand(operand); + if (s != 0) { + suffix = s; + } + } + if (suffix) { + return suffix; + } + GB_ASSERT(instr->mnemonic != 0); + GB_ASSERT(instr->valid_form_index >= 0); + // Otherwise derive from the matched form's operand widths. + auto forms = g_asm_amd64.encoding_forms(instr->mnemonic); + auto &form = forms[instr->valid_form_index]; + + i32 width = 0; + bool any_vector = false; + for (isize k = 0; k < gb_count_of(form.ops); k++) { + auto ot = form.ops[k]; + if (ot == g_asm_amd64.OP_NONE) { + break; + } + if (g_asm_amd64.operand_type_is_implicit(ot)) { + continue; + } + AsmRegClass cls = g_asm_amd64.operand_type_reg_class(ot); + if (cls == AsmRegClass_Vector || cls == AsmRegClass_Mask) { + any_vector = true; + continue; // xmm/ymm/zmm/k forms take no b/w/l/q suffix + } + i32 w = g_asm_amd64.operand_type_bit_width(ot); + if (w == 8 || w == 16 || w == 32 || w == 64) { + width = gb_max(width, w); // GP/memory width + } + } + + if (any_vector || width == 0) { + return 0; // vector op, or nothing that needs a GP-width suffix + } + switch (width) { + case 8: return 'b'; + case 16: return 'w'; + case 32: return 'l'; + case 64: return 'q'; + } + return 0; + } + + // Map an EFLAGS flag name to its LLVM `=@cc` setcc condition, or {} if + // the flag has no single-flag setcc form (af/df/if/... can't be a flag output). + String flag_output_cc_suffix(String const &pin_flag) override { + if (pin_flag == "cf") return str_lit("c"); // carry + if (pin_flag == "pf") return str_lit("p"); // parity (even) + if (pin_flag == "zf") return str_lit("z"); // zero + if (pin_flag == "sf") return str_lit("s"); // sign + if (pin_flag == "of") return str_lit("o"); // overflow + return {}; + } + + gbString write_memory_operand(gbString asm_string, Array const &op_number, AstAsmMemoryOperand *mem_op, u32 flags) override { if (mem_op->disp) { asm_string = this->write_operand(asm_string, op_number, mem_op->disp, flags&~WriteOperandFlag_PrintPrefixes); @@ -280,6 +341,28 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { continue; // width-view: resolved to its source's operand, owns no slot } + // Flag output: an output pinned to a condition flag (e.g. `= %flags.zf`). + // Lowers to LLVM's `=@cc`, which yields an i1 (0/1). It takes a + // return-struct slot but is NEVER referenced in the body (the instruction + // sets the flag as a side effect), so it gets no $N operand number. + if (e.param_group == AsmTemplateEntityDeclParamGroup_Output && e.pin_flag.len != 0) { + GB_ASSERT(e.pin == "flags"); + String suffix = this->flag_output_cc_suffix(e.pin_flag); + GB_ASSERT_MSG(suffix.len != 0, "asm: flag '%.*s' has no setcc condition form", LIT(e.pin_flag)); + + sep(); + raw("={@cc"); + put(suffix); + raw("}"); + + ret_slot[i] = cast(i32)ret_types.count; + array_add(&ret_types, LLVMInt8TypeInContext(ctx)); + + // Counted in $N even though never referenced in the body. + op_number[i] = next_op++; + continue; + } + bool is_output = e.param_group == AsmTemplateEntityDeclParamGroup_Output; bool is_alloc_scratch = e.param_group == AsmTemplateEntityDeclParamGroup_Scratch && e.kind == AsmTemplateEntityDecl_Register @@ -311,7 +394,7 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { } // Pass 2: inputs - for (isize i = 0; i < ops->count; i++) { + for_array(i, *ops) { AsmTemplateEntityDecl const &e = (*ops)[i]; if (e.view_of >= 0) { @@ -411,7 +494,7 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { string_set_init(&emitted_reg_clobbers); defer (string_set_destroy(&emitted_reg_clobbers)); - for (isize i = 0; i < ops->count; i++) { + for_array(i, *ops) { AsmTemplateEntityDecl const &e = (*ops)[i]; if (e.view_of >= 0) { @@ -455,7 +538,7 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { if (tmpl_entity->AsmTemplate.clobber_flags) { sep(); if (build_context.metrics.arch == TargetArch_amd64) { - raw("~{flags}"); // x86 EFLAGS condition codes + raw("~{dirflag},~{fpsr},~{flags}"); // clang's canonical x86 flags clobber } else { raw("~{cc}"); // AArch64 uses ~{cc} } @@ -522,7 +605,7 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { result_vals[i] = nullptr; } - for (isize i = 0; i < ops->count; i++) { + for_array(i, *ops) { AsmTemplateEntityDecl const &e = (*ops)[i]; if (e.view_of >= 0) { continue; // width-view: never a returned value @@ -538,6 +621,26 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { LLVMValueRef v = (ret_types.count == 1) ? call // single-element return is not a struct : LLVMBuildExtractValue(p->builder, call, cast(unsigned)ret_slot[i], ""); + + // A flag output is delivered as i8; coerce it to the declared result type + // (e.g. i1, or a wider bool). zext when widening, trunc when narrowing. + // zext (not sext) is correct: a flag output is 0 or 1. + if (e.pin_flag.len != 0) { + Type *rt = this->result_type_of(e); + LLVMTypeRef want = lb_type(m, rt); + LLVMTypeRef got = LLVMTypeOf(v); + if (want != got) { + unsigned want_w = LLVMGetIntTypeWidth(want); + unsigned got_w = LLVMGetIntTypeWidth(got); + if (want_w < got_w) { + v = LLVMBuildTrunc(p->builder, v, want, ""); + } else if (want_w > got_w) { + v = LLVMBuildZExt(p->builder, v, want, ""); + } + // want_w == got_w with differing type identity: same width, no-op. + } + } + result_vals[e.result_index] = v; } @@ -563,4 +666,4 @@ gb_internal lbValue lb_emit_asm_template_call(lbProcedure *p, Entity *entity, Ar lbAsmGenerate_amd64 generator = {}; generator.init(entity); return generator.emit_call(p, args); -} +} \ No newline at end of file diff --git a/src/parser.cpp b/src/parser.cpp index 518beb2f0..2d78991c2 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -541,7 +541,6 @@ 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; @@ -2561,6 +2560,7 @@ gb_internal Ast *parse_asm_instruction(AstFile *f) { Ast *instruction = alloc_ast_node(f, Ast_AsmInstruction); instruction->AsmInstruction.name = name; instruction->AsmInstruction.operands = operands; + instruction->AsmInstruction.valid_form_index = -1; return instruction; } case Token_Period: diff --git a/src/parser.hpp b/src/parser.hpp index cbbf84867..fb3445825 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -497,9 +497,10 @@ struct AstSplitArgs { Ast * name; \ }) \ AST_KIND(AsmInstruction, "asm instruction", struct { \ - Ast * prefix; /*optional*/ \ Ast * name; \ Slice operands; \ + u16 mnemonic; \ + i32 valid_form_index; \ }) \ AST_KIND(AsmMemoryOperand, "asm memory operand", struct { \ Token open; \