Correct instruction_size_suffix so that the correct suffix can be selected for the instruction to adhere to AT&T syntax

This commit is contained in:
gingerBill
2026-08-17 12:59:23 +01:00
parent db94c5a6cb
commit d1f561ec88
4 changed files with 137 additions and 25 deletions

View File

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

View File

@@ -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<i32> const &op_number, AstAsmMemoryOperand *mem_op, u32 flags) = 0;
virtual lbValue emit_call(lbProcedure *p, Array<lbValue> 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<suffix>` 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<i32> 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<suffix>`, 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);
}
}

View File

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

View File

@@ -497,9 +497,10 @@ struct AstSplitArgs {
Ast * name; \
}) \
AST_KIND(AsmInstruction, "asm instruction", struct { \
Ast * prefix; /*optional*/ \
Ast * name; \
Slice<Ast *> operands; \
u16 mnemonic; \
i32 valid_form_index; \
}) \
AST_KIND(AsmMemoryOperand, "asm memory operand", struct { \
Token open; \