From 427d300562ce9ac33c26cbd8f4fb4c59f8ee84dc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 24 Aug 2026 16:39:23 +0100 Subject: [PATCH] asm: minor cleanups --- src/check_asm.cpp | 258 ++++++++++++++++++---------------------------- 1 file changed, 103 insertions(+), 155 deletions(-) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index 6cdc6c1db..43e68923b 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -7,9 +7,6 @@ gb_internal i32 check_asm_operand_bit_width(Type *type) { if (is_type_untyped(type)) { return -1; } - if (is_type_boolean(type)) { - return 1; - } i64 sz = type_size_of(base_type(type)); if (sz <= 0) { return 0; @@ -85,6 +82,20 @@ gb_internal AsmOperandKind determine_asm_operand_kind(Operand const *operand) { return AsmOperand_Invalid; } +gb_internal bool asm_reg_class_compatible(AsmRegClass want, AsmRegClass got) { + switch (want) { + case AsmRegClass_Integer: + return got == AsmRegClass_Integer; + case AsmRegClass_Vector: + // A scalar float uses only the low lane, so it is valid in any vector + // register slot; a #simd vector matches the vector class exactly. + return got == AsmRegClass_Vector || got == AsmRegClass_Float; + case AsmRegClass_Mask: + return got == AsmRegClass_Mask; + } + return true; +} + gb_internal void check_asm_pin_type_compat(AsmRegClass reg_class, i32 reg_w, Type *decl_type, Ast *at, String pin_name, String param_name) { if (reg_class == AsmRegClass_Unknown || @@ -95,14 +106,7 @@ gb_internal void check_asm_pin_type_compat(AsmRegClass reg_class, i32 reg_w, Typ AsmRegClass got_class = check_asm_reg_class_from_type(decl_type); i32 got_w = check_asm_operand_bit_width(decl_type); - bool class_ok; - switch (reg_class) { - case AsmRegClass_Integer: class_ok = (got_class == AsmRegClass_Integer); break; - case AsmRegClass_Vector: class_ok = (got_class == AsmRegClass_Vector || got_class == AsmRegClass_Float); break; - case AsmRegClass_Mask: class_ok = (got_class == AsmRegClass_Mask); break; - default: class_ok = true; break; - } - if (!class_ok) { + if (!!asm_reg_class_compatible(reg_class, got_class)) { error(at, "Parameter '%.*s' is pinned to %%%.*s, but its type is in the wrong register class for that register", LIT(param_name), LIT(pin_name)); return; @@ -304,24 +308,7 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: // must not be held against the slot's register class. Only width matters for the // memory interpretation. Register operands still get the full class check. if (want_class != AsmRegClass_Unknown && !is_memory) { - bool class_ok; - switch (want_class) { - case AsmRegClass_Integer: - class_ok = (got_class == AsmRegClass_Integer); - break; - case AsmRegClass_Vector: - // A scalar float uses only the low lane, so it is valid in any vector - // register slot; a #simd vector matches the vector class exactly. - class_ok = (got_class == AsmRegClass_Vector || got_class == AsmRegClass_Float); - break; - case AsmRegClass_Mask: - class_ok = (got_class == AsmRegClass_Mask); - break; - default: - class_ok = true; - break; - } - if (!class_ok) { + if (!asm_reg_class_compatible(want_class, got_class)) { if (mismatch_) *mismatch_ = AsmMismatch_Class; return false; } @@ -1034,7 +1021,8 @@ gb_internal CheckMnemomicResult check_mnemonic_name(AsmCtx *asm_ctx, AstAsmInstr } struct AsmMnemonicAccumulator { - u16 defined_regs; + u16 defined_regs; + PtrSet defined_params; // Union of registers implicitly clobbered by matched forms (for redundant-#clobber hints). u16 implicit_clobbered_regs; @@ -1061,9 +1049,6 @@ struct AsmMnemonicAccumulator { bool can_be_pure; char const *impure_reason; Ast * impure_reason_node; - - - PtrSet defined_params; }; gb_internal bool check_asm_instr_targets_internal_label(AstAsmInstruction *instr) { @@ -1083,16 +1068,16 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm u8 previous_prefix, Ast *previous_prefix_instr, AsmMnemonicAccumulator *asm_acc) { GB_ASSERT(mnemonic > 0); - auto forms = asm_ctx->encoding_forms(mnemonic); - auto clobber_forms = asm_ctx->clobber_forms(mnemonic); - String name = asm_ctx->mnemonic_strings[mnemonic]; + auto forms = asm_ctx->encoding_forms(mnemonic); + auto clobber_forms = asm_ctx->clobber_forms(mnemonic); + String name = asm_ctx->mnemonic_strings[mnemonic]; auto alias = asm_ctx->pseudo_alias(cast(u16)pseudo_mnemonic); if (pseudo_mnemonic) { name = asm_ctx->pseudo_mnemonic_strings[pseudo_mnemonic]; } - bool is_pseudo = pseudo_mnemonic != 0; + bool is_pseudo = pseudo_mnemonic != 0; int target_explicit_count = is_pseudo ? alias.nargs : -1; auto form_user_operand_count = [&](typename AsmCtx::Encoding const &form) -> int { @@ -1310,8 +1295,8 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } error_line("\tPossible forms for '%.*s':\n", LIT(name)); - error_line("\t(r: int, v: vector, f: float, k: mask, m: memory,\n"); - error_line("\t r/m: reg-or-mem, imm: immediate; number: bit=width)\n"); + error_line("\t\t(r: int, v: vector, f: float, k: mask, m: memory,\n"); + error_line("\t\t r/m: reg-or-mem, imm: immediate; number: bit=width)\n"); isize const MAX_SHOWN = 32; isize shown = gb_min(lines.count, MAX_SHOWN); @@ -1382,14 +1367,10 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm for_array(form_index, forms) { auto &form = forms[form_index]; - if (is_pseudo) { - if (cast(int)form.explicit_count() < target_explicit_count) { - continue; - } - } else { - if (operands.count != cast(int)form.explicit_count()) { - continue; - } + if (is_pseudo && cast(int)form.explicit_count() < target_explicit_count) { + continue; + } else if (operands.count != cast(int)form.explicit_count()) { + continue; } int score = 0; @@ -1405,8 +1386,7 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm bool kind_ok = (dst == src) || (dst == AsmOperand_Register_Or_Memory && (src == AsmOperand_Register || src == AsmOperand_Memory)); - // Tertiary key: bias toward wider register slots so an r64 form outranks - // an otherwise-equal r32 form. + // Bias toward wider register slots so an r64 form outranks an otherwise-equal r32 form. width_pref += cast(int)asm_ctx->operand_type_bit_width(type); bool spot_ok = false; @@ -1443,7 +1423,7 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } // Lexicographic rank: score desc, then width_dist asc, then width_pref desc. - bool better; + bool better = false; if (score != best_score) { better = score > best_score; } else if (width_dist != best_dist) { @@ -1736,8 +1716,8 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm // A load's result depends on memory, which is not a value input. why = "it reads from memory"; } else if (clobber.is_nondeterministic() || - (pseudo_mnemonic > 0 && alias.is_nondeterministic())) { - // rdtsc / rdrand / cpuid on x86 + (is_pseudo && alias.is_nondeterministic())) { + // rdtsc/rdrand/cpuid on x86 // counter/entropy CSR reads on RISC-V. why = "it is nondeterministic"; } else if (clobber.implies_clobber_memory() && mem_is_real) { @@ -1760,11 +1740,11 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm return; } - // failure path - enum { MAX_VARIANT_COUNT = 32 }; - AsmMismatch mismatch[MAX_VARIANT_COUNT] = {}; + // NOTE(bill): Failure path + enum { MAX_VARIANT_COUNT = 8 }; + AsmMismatch mismatch [MAX_VARIANT_COUNT] = {}; i32 want_bits[MAX_VARIANT_COUNT] = {}; - i32 got_bits[MAX_VARIANT_COUNT] = {}; + i32 got_bits [MAX_VARIANT_COUNT] = {}; if (best_form >= 0) { auto &form = forms[best_form]; for_array(i, operands) { @@ -1793,82 +1773,80 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } } - { + begin_error_block(); + + bool nearly = best_score >= gb_max(operands.count*2 - 2, 0); + if (nearly) { + error(instr->name, "'%.*s' operands nearly matched the expected encoding forms", LIT(name)); + } else { + error(instr->name, "'%.*s' operands matched none of the expected encoding forms", LIT(name)); + } + + for_array(i, valid_spots) { + if (valid_spots[i] || i >= operands.count) { + continue; + } + auto dst = possible_kinds[i]; + AsmOperandKind src = determine_asm_operand_kind(&operands[i]); + + AsmRegClass dst_reg_class = possible_class_kinds[i]; + AsmRegClass src_reg_class = check_asm_reg_class_from_type(operands[i].type); + + AsmMismatch m = (i < MAX_VARIANT_COUNT) ? mismatch[i] : AsmMismatch_None; + + end_error_block(); begin_error_block(); - bool nearly = best_score >= gb_max(operands.count*2 - 2, 0); - if (nearly) { - error(instr->name, "'%.*s' operands nearly matched the expected encoding forms", LIT(name)); - } else { - error(instr->name, "'%.*s' operands matched none of the expected encoding forms", LIT(name)); - } + if (m == AsmMismatch_ImmRange) { - for_array(i, valid_spots) { - if (valid_spots[i] || i >= operands.count) { - continue; - } - auto dst = possible_kinds[i]; - AsmOperandKind src = determine_asm_operand_kind(&operands[i]); - - AsmRegClass dst_reg_class = possible_class_kinds[i]; - AsmRegClass src_reg_class = check_asm_reg_class_from_type(operands[i].type); - - AsmMismatch m = (i < MAX_VARIANT_COUNT) ? mismatch[i] : AsmMismatch_None; - - end_error_block(); - begin_error_block(); - - if (m == AsmMismatch_ImmRange) { - - ExactValue ev = operands[i].value; - gbString vs = exact_value_to_string(ev); - i32 bits_required = 0; - check_asm_immediate_value_fits(ev, want_bits[i], &bits_required, nullptr); - if (bits_required > 0) { - error(operands[i].expr, "'%.*s' operand-%td is a %d-bit immediate value, but the value %s does not fit in the %d-bit immediate this form encodes", - LIT(name), i, bits_required, vs, cast(int)want_bits[i]); - } else { - error(operands[i].expr, "'%.*s' operand-%td is an immediate value, but the value %s does not fit in the %d-bit immediate this form encodes", - LIT(name), i, vs, cast(int)want_bits[i]); - } - gb_string_free(vs); - } else if (m == AsmMismatch_ImmType) { - error(operands[i].expr, "'%.*s' operand-%td: a floating-point constant cannot be used as an immediate", - LIT(name), i); - } else if (m == AsmMismatch_Size && want_bits[i] && got_bits[i]) { - error(operands[i].expr, "'%.*s' operand-%td has the wrong size: expected a %u-bit %.*s operand, got %u-bit", - LIT(name), i, - cast(unsigned)want_bits[i], LIT(asm_reg_class_strings[dst_reg_class]), - cast(unsigned)got_bits[i]); - } else if (m == AsmMismatch_Class) { - error(operands[i].expr, "'%.*s' operand-%td is in the wrong register class, expected %d-bit %.*s %.*s, got %d-bit %.*s %.*s", - LIT(name), i, - want_bits[i], LIT(asm_reg_class_strings[dst_reg_class]), LIT(asm_operand_kind_strings[dst]), - got_bits[i], LIT(asm_reg_class_strings[src_reg_class]), LIT(asm_operand_kind_strings[src])); - } else if (m == AsmMismatch_NamedReg) { - auto slot = operand_slot_type(forms[best_form], cast(int)i); - error(operands[i].expr, "'%.*s' operand-%td must be a named %.*s register, got a %.*s", - LIT(name), i, - LIT(asm_ctx->named_reg_class_string(asm_ctx->operand_type_named_reg_class(slot))), - LIT(asm_operand_kind_strings[src])); - } else if (dst == AsmOperand_Immediate) { - error(operands[i].expr, "'%.*s' operand-%td must be an assemble-time constant or a $ immediate parameter, got a %.*s", - LIT(name), i, LIT(asm_operand_kind_strings[src])); - } else if (dst) { - error(operands[i].expr, "'%.*s' operand-%td has an invalid kind, expected %.*s operand", - LIT(name), i, LIT(asm_operand_kind_expected_strings[dst])); + ExactValue ev = operands[i].value; + gbString vs = exact_value_to_string(ev); + i32 bits_required = 0; + check_asm_immediate_value_fits(ev, want_bits[i], &bits_required, nullptr); + if (bits_required > 0) { + error(operands[i].expr, "'%.*s' operand-%td is a %d-bit immediate value, but the value %s does not fit in the %d-bit immediate this form encodes", + LIT(name), i, bits_required, vs, cast(int)want_bits[i]); } else { - error(operands[i].expr, "'%.*s' operand-%td has an invalid kind", LIT(name), i); + error(operands[i].expr, "'%.*s' operand-%td is an immediate value, but the value %s does not fit in the %d-bit immediate this form encodes", + LIT(name), i, vs, cast(int)want_bits[i]); } - } - - if (nearly && best_form >= 0) { - print_closest_form(best_form); + gb_string_free(vs); + } else if (m == AsmMismatch_ImmType) { + error(operands[i].expr, "'%.*s' operand-%td: a floating-point constant cannot be used as an immediate", + LIT(name), i); + } else if (m == AsmMismatch_Size && want_bits[i] && got_bits[i]) { + error(operands[i].expr, "'%.*s' operand-%td has the wrong size: expected a %u-bit %.*s operand, got %u-bit", + LIT(name), i, + cast(unsigned)want_bits[i], LIT(asm_reg_class_strings[dst_reg_class]), + cast(unsigned)got_bits[i]); + } else if (m == AsmMismatch_Class) { + error(operands[i].expr, "'%.*s' operand-%td is in the wrong register class, expected %d-bit %.*s %.*s, got %d-bit %.*s %.*s", + LIT(name), i, + want_bits[i], LIT(asm_reg_class_strings[dst_reg_class]), LIT(asm_operand_kind_strings[dst]), + got_bits[i], LIT(asm_reg_class_strings[src_reg_class]), LIT(asm_operand_kind_strings[src])); + } else if (m == AsmMismatch_NamedReg) { + auto slot = operand_slot_type(forms[best_form], cast(int)i); + error(operands[i].expr, "'%.*s' operand-%td must be a named %.*s register, got a %.*s", + LIT(name), i, + LIT(asm_ctx->named_reg_class_string(asm_ctx->operand_type_named_reg_class(slot))), + LIT(asm_operand_kind_strings[src])); + } else if (dst == AsmOperand_Immediate) { + error(operands[i].expr, "'%.*s' operand-%td must be an assemble-time constant or a $ immediate parameter, got a %.*s", + LIT(name), i, LIT(asm_operand_kind_strings[src])); + } else if (dst) { + error(operands[i].expr, "'%.*s' operand-%td has an invalid kind, expected %.*s operand", + LIT(name), i, LIT(asm_operand_kind_expected_strings[dst])); } else { - print_possible_forms(); + error(operands[i].expr, "'%.*s' operand-%td has an invalid kind", LIT(name), i); } - end_error_block(); } + + if (nearly && best_form >= 0) { + print_closest_form(best_form); + } else { + print_possible_forms(); + } + end_error_block(); } @@ -2720,7 +2698,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity for (auto const &ed : ate->decls) { bool is_scratch = ed.param_group == AsmTemplateEntityDeclParamGroup_Scratch && ed.view_of < 0; - bool is_immediate = ed.kind == AsmTemplateEntityDecl_Immediate; + bool is_immediate = ed.kind == AsmTemplateEntityDecl_Immediate; bool is_input = ed.param_group == AsmTemplateEntityDeclParamGroup_Input; if ((!is_scratch && !is_immediate && !is_input) || ed.entity == nullptr) { continue; @@ -2760,36 +2738,6 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity "that would require the stack to be realigned"); } - if (false) { - // TODO(bill): is this even a good idea? The programmer might have just added it for the reason so that he can - // tell if an asm template clobbers something specific or if it is #volatile. - // I'll leave this in an `if (false)` block for the time being just in case it might be useful in the future. - - // Redundant #clobber hint - for (Ast *clobber_ : at->clobbers) { - ast_node(clobber, AsmClobber, clobber_); - if (clobber->value == nullptr || clobber->value->kind != Ast_AsmRegister) { - continue; - } - String reg = clobber->value->AsmRegister.name.string; - u16 bit = asm_ctx->clobber_bit_for_reg_name(reg); - if (bit && (asm_acc.implicit_clobbered_regs & bit) != 0) { - warning(clobber->value, "#clobber %%%.*s is redundant; an instruction in this template already clobbers it implicitly", LIT(reg)); - } - } - - // Redundant #volatile hint - if (entity->AsmTemplate.is_volatile && entity->AsmTemplate.has_observable_side_effect) { - for (Ast *clobber_ : at->clobbers) { - ast_node(clobber, AsmClobber, clobber_); - if (clobber->value == nullptr && clobber->name.string == "volatile") { - warning(clobber->name, "#volatile is redundant; an instruction in this template already has an observable side effect"); - break; - } - } - } - } - if (type->Proc.diverging) { if (!asm_acc.saw_any_instructions) { error(entity->token, "This asm template is declared as diverging (-> !) but its body is empty and cannot diverge");