Add more checks for flag clobbering

This commit is contained in:
gingerBill
2026-08-19 14:09:46 +01:00
parent adbd1bd930
commit 7638ac1bd4
2 changed files with 36 additions and 4 deletions

View File

@@ -149,6 +149,17 @@ struct Asm_amd64 {
ClobberFlag_IF = 1<<7,
ClobberFlag_TF = 1<<8,
};
static u16 const CLOBBER_FLAGS_COND = ClobberFlag_CF|ClobberFlag_PF|ClobberFlag_AF|ClobberFlag_ZF|ClobberFlag_SF|ClobberFlag_OF;
char const *clobber_flag_bit_name(u16 bit) {
switch (bit) {
case ClobberFlag_CF: return "c"; case ClobberFlag_PF: return "p";
case ClobberFlag_AF: return "a"; case ClobberFlag_ZF: return "z";
case ClobberFlag_SF: return "s"; case ClobberFlag_OF: return "o";
}
return "?";
}
enum SideEffectFlags : u16 {
SideEffectFlag_FENCE = 1<<0, // memory-ordering barrier (LFENCE/SFENCE/MFENCE, LOCK)
SideEffectFlag_SERIALIZING = 1<<1, // architecturally serializing (drains pipeline)

View File

@@ -514,7 +514,7 @@ gb_internal void check_asm_specs(AsmCtx *asm_ctx, CheckerContext *ctx, Scope *sc
error(spec->value, "Pinned register flag %%%.*s.%.*s has already been assigned", LIT(pin), LIT(pin_flag));
}
}
if (string_set_update(&pin_set, pin)) {
if (string_set_update(&pin_set, pin) && pin != "flags") {
error(spec->value, "Pinned register %%%.*s has already been assigned", LIT(pin));
}
if (reg->flag.string.len == 0) {
@@ -867,6 +867,9 @@ struct AsmMnemonicAccumulator {
// Did the template contain any instructions at all? An empty diverging body can't diverge.
bool saw_any_instructions;
u16 explicitly_produced_regs;
u16 stale_outputs;
};
@@ -1022,9 +1025,14 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
tmpl_entity->AsmTemplate.has_observable_side_effect |= clobber.writes_mem;
u16 pinned_mask = 0;
u16 output_only_pin_mask = 0;
for (auto const &ed : tmpl_entity->AsmTemplate.decls) {
if (ed.pin.len != 0) {
pinned_mask |= asm_ctx->clobber_bit_for_reg_name(ed.pin);
u16 b = asm_ctx->clobber_bit_for_reg_name(ed.pin);
pinned_mask |= b;
if (ed.param_group == AsmTemplateEntityDeclParamGroup_Output && ed.tie < 0) {
output_only_pin_mask |= b;
}
}
}
@@ -1045,6 +1053,7 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
}
u16 produced = cast(u16)clobber.implicit_wr & asm_ctx->CLOBBER_REGS_NAMED;
u16 explicit_writes = 0;
// Explicit destination operands that name a concrete register also produce it
// (e.g. `mov eax, $leaf` before CPUID). Only literal %reg operands pin a known
@@ -1057,7 +1066,9 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
}
Ast *e = operands[i].expr;
if (e && e->kind == Ast_AsmRegister) {
produced |= asm_ctx->clobber_bit_for_reg_name(e->AsmRegister.name.string);
u16 b = asm_ctx->clobber_bit_for_reg_name(e->AsmRegister.name.string);
produced |= b;
explicit_writes |= b;
}
}
asm_acc->defined_regs |= produced;
@@ -1070,6 +1081,17 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
asm_acc->implicit_clobbered_regs |= implicit_wr & ~pinned_mask;
}
// Approximate staleness. An output that was explicitly produced (literal %reg write)
// and is later implicitly clobbered — without this same instruction re-producing it —
// is marked stale. Explicit re-production clears it. Implicitly-produced outputs
// (RDTSC->RDX) are never tracked, so they never false-fire.
{
u16 implicit_clobber = cast(u16)clobber.implicit_wr & asm_ctx->CLOBBER_REGS_NAMED;
asm_acc->explicitly_produced_regs |= explicit_writes;
asm_acc->stale_outputs &= ~explicit_writes;
asm_acc->stale_outputs |= implicit_clobber & asm_acc->explicitly_produced_regs & ~explicit_writes;
}
// Terminality for a #diverging template: this instruction ends straight-line
// flow off the end (jmp/ret/etc. -> CONTROL, hlt/ud2 -> HALT). A conditional
// branch does NOT terminate (it can fall through), so require that the form
@@ -1940,7 +1962,6 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
}
}
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");