asm: minor cleanups

This commit is contained in:
gingerBill
2026-08-24 16:39:23 +01:00
parent 171bdaf378
commit 427d300562

View File

@@ -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<Entity *> 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<Entity *> 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");