From 7773596beb6756f58a7b5ed924f0ec18bc65b0fb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 30 Aug 2026 18:03:26 +0100 Subject: [PATCH] Handle status snapshot style instructions --- .../isa/arm64/tablegen/cpp-compiler/cpp-gen.odin | 5 +++++ .../isa/riscv/tablegen/cpp-compiler/cpp-gen.odin | 13 +++++++++++++ .../isa/x86/tablegen/cpp-compiler/cpp-gen.odin | 9 +++++++++ src/asm_tables_amd64.cpp | 9 +++++++++ src/asm_tables_arm64.cpp | 5 +++++ src/asm_tables_riscv.cpp | 13 +++++++++++++ src/check_asm_cfg.cpp | 12 ++++++++++++ 7 files changed, 66 insertions(+) diff --git a/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin index e2d37b2f7..1b6e89674 100644 --- a/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/arm64/tablegen/cpp-compiler/cpp-gen.odin @@ -354,6 +354,11 @@ main :: proc() { bool is_atomic = (cast(u16)side_effects & SideEffectFlag_ATOMIC) != 0; return (implicit & ClobberReg_SP) != 0 || is_atomic; } + + bool is_status_snapshot() const { + u16 flags = cast(u16)this->flags_rd_call(); + return gb_count_set_bits(flags & CLOBBER_FLAGS_COND) >= 4; + } }; void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) { diff --git a/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin index 6463d11d4..ef4096aaf 100644 --- a/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin @@ -289,6 +289,19 @@ main :: proc() { bool is_atomic = false; // TODO(bill): Add ATOMIC flag to SideEffectFlags in the original INSTRUCTION_TABLE return (implicit & (ClobberReg_SP)) != 0 || is_atomic; } + bool is_status_snapshot() const { + // RiscV64 has no architectural condition flags: branches compare two GPRs + // directly (beq/bltu/…) and slt/sltu materialise a 0/1 into a GPR, so nothing + // ever consumes status as an implicit condition — flags_rd_call() is always + // empty and the flag read-before-write check is already vacuous here. + // + // The only status that exists — the accrued fcsr exception flags (fflags[4:0]) + // and the frm rounding field — is read solely through an explicit CSR access + // (csrr* / frflags / frrm), which pulls the register out as an opaque value + // into a GPR. That is a snapshot by construction and must never require a + // producer, so every status read that RV64 can express qualifies. + return true; + } }; void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) { diff --git a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin index 2281167fc..448e7376d 100644 --- a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin @@ -153,6 +153,7 @@ main :: proc() { 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\"; + case ClobberFlag_DF: return \"d\"; case ClobberFlag_IF: return \"i\"; } return \"?\"; } @@ -363,6 +364,14 @@ main :: proc() { u16 implicit = cast(u16)implicit_rd | cast(u16)implicit_wr; return (implicit & (ClobberReg_RSP|ClobberReg_RSI|ClobberReg_RDI|ClobberReg_RBX)) != 0; } + + bool is_status_snapshot() const { + u16 flags = cast(u16)this->flags_rd_call(); + u16 const STATUS_FLAGS = ClobberFlag_CF | ClobberFlag_PF | ClobberFlag_AF | + ClobberFlag_ZF | ClobberFlag_SF | ClobberFlag_OF; + return ((flags & ClobberFlag_IF) != 0) || + (gb_count_set_bits(flags & STATUS_FLAGS) >= 4); + } }; void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) { diff --git a/src/asm_tables_amd64.cpp b/src/asm_tables_amd64.cpp index 4a8ad4888..2698ade27 100644 --- a/src/asm_tables_amd64.cpp +++ b/src/asm_tables_amd64.cpp @@ -156,6 +156,7 @@ struct Asm_amd64 { 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"; + case ClobberFlag_DF: return "d"; case ClobberFlag_IF: return "i"; } return "?"; } @@ -366,6 +367,14 @@ struct Asm_amd64 { u16 implicit = cast(u16)implicit_rd | cast(u16)implicit_wr; return (implicit & (ClobberReg_RSP|ClobberReg_RSI|ClobberReg_RDI|ClobberReg_RBX)) != 0; } + + bool is_status_snapshot() const { + u16 flags = cast(u16)this->flags_rd_call(); + u16 const STATUS_FLAGS = ClobberFlag_CF | ClobberFlag_PF | ClobberFlag_AF | + ClobberFlag_ZF | ClobberFlag_SF | ClobberFlag_OF; + return ((flags & ClobberFlag_IF) != 0) || + (gb_count_set_bits(flags & STATUS_FLAGS) >= 4); + } }; void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) { diff --git a/src/asm_tables_arm64.cpp b/src/asm_tables_arm64.cpp index 3e3a182c9..0a8efd4ae 100644 --- a/src/asm_tables_arm64.cpp +++ b/src/asm_tables_arm64.cpp @@ -341,6 +341,11 @@ struct Asm_arm64 { bool is_atomic = (cast(u16)side_effects & SideEffectFlag_ATOMIC) != 0; return (implicit & ClobberReg_SP) != 0 || is_atomic; } + + bool is_status_snapshot() const { + u16 flags = cast(u16)this->flags_rd_call(); + return gb_count_set_bits(flags & CLOBBER_FLAGS_COND) >= 4; + } }; void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) { diff --git a/src/asm_tables_riscv.cpp b/src/asm_tables_riscv.cpp index 3d86b5f79..867e29ea7 100644 --- a/src/asm_tables_riscv.cpp +++ b/src/asm_tables_riscv.cpp @@ -237,6 +237,19 @@ struct Asm_riscv { bool is_atomic = false; // TODO(bill): Add ATOMIC flag to SideEffectFlags in the original INSTRUCTION_TABLE return (implicit & (ClobberReg_SP)) != 0 || is_atomic; } + bool is_status_snapshot() const { + // RiscV64 has no architectural condition flags: branches compare two GPRs + // directly (beq/bltu/…) and slt/sltu materialise a 0/1 into a GPR, so nothing + // ever consumes status as an implicit condition — flags_rd_call() is always + // empty and the flag read-before-write check is already vacuous here. + // + // The only status that exists — the accrued fcsr exception flags (fflags[4:0]) + // and the frm rounding field — is read solely through an explicit CSR access + // (csrr* / frflags / frrm), which pulls the register out as an opaque value + // into a GPR. That is a snapshot by construction and must never require a + // producer, so every status read that RV64 can express qualifies. + return true; + } }; void clobber_implicit_regs(StringSet *clobber_registers_set, u16 implicit_regs) { diff --git a/src/check_asm_cfg.cpp b/src/check_asm_cfg.cpp index e6e134d82..758b969fd 100644 --- a/src/check_asm_cfg.cpp +++ b/src/check_asm_cfg.cpp @@ -692,7 +692,19 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont check_asm_cfg_report_undef_reg(asm_ctx, cfg, entity, instr, f->name, bit); } + bool is_status_snapshot = false; + if (instr->mnemonic && instr->valid_form_index >= 0) { + auto clobber_forms = asm_ctx->clobber_forms(instr->mnemonic); + if (clobber_forms.count > 0 && instr->valid_form_index < clobber_forms.count) { + auto &clobber = clobber_forms[instr->valid_form_index]; + is_status_snapshot = clobber.is_status_snapshot(); + } + } + u16 undef_flags = f->read_flags & ~run_flags & ~reported_flags; + if (is_status_snapshot) { + undef_flags = 0; + } if (undef_flags != 0) { reported_flags |= undef_flags;