mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-26 06:51:34 +00:00
Add more checks for flag clobbering
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user