diff --git a/src/check_asm.cpp b/src/check_asm.cpp index c03b081fd..cb535a5d3 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -2644,7 +2644,6 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity check_asm_cfg_build(asm_ctx, &cfg, d->init_expr, entity); check_asm_cfg_analyse(asm_ctx, &cfg, ctx, entity); - check_asm_cfg_liveness(asm_ctx, &cfg, entity, /*emit_dead_writes*/all_instructions_good); bool vet_unused = false; { diff --git a/src/check_asm_cfg.cpp b/src/check_asm_cfg.cpp index a6c717b27..914a15cf7 100644 --- a/src/check_asm_cfg.cpp +++ b/src/check_asm_cfg.cpp @@ -648,127 +648,3 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont } } } - -// Backward liveness: a value is live at a point if some path from there reads it before overwriting it. -// Dual of the forward definite-assignment pass. -// Used to find dead writes (a definition never read before being overwritten or before template exit). -// -// NOTE(bill): only set `emit_dead_writes` to be true when all instructions are "good". -template -gb_internal bool check_asm_cfg_liveness(AsmCtx *asm_ctx, AsmCfg *cfg, Entity *entity, bool emit_dead_writes) { - isize const n = cfg->blocks.count; - if (n == 0) { - return false; - } - - u16 const REG_TOP = asm_ctx->CLOBBER_REGS_NAMED; - - u16 exit_live = 0; - u16 output_regs = 0; - for_array(i, entity->AsmTemplate.decls) { - auto const &ed = entity->AsmTemplate.decls[i]; - if (ed.param_group == AsmTemplateEntityDeclParamGroup_Output) { - exit_live |= cfg->decl_pin_bit[i]; // pinned/view-inherited output reg, if any - output_regs |= cfg->decl_pin_bit[i]; - } - } - for (String const ® : entity->AsmTemplate.clobber_registers_set) { - exit_live |= asm_ctx->clobber_bit_for_reg_name(reg); - } - - auto live_in = slice_make(heap_allocator(), n); defer (slice_free(&live_in, heap_allocator())); - auto live_out = slice_make(heap_allocator(), n); defer (slice_free(&live_out, heap_allocator())); - - bool changed = true; - while (changed) { - changed = false; - // Iterate in reverse for faster convergence - for (isize bi = n - 1; bi >= 0; bi--) { - if (!cfg->blocks[bi].reachable) { - continue; - } - AsmBlock const &b = cfg->blocks[bi]; - - // live_out = union of successors' live_in, plus exit_live if this block leaves. - u16 lo = 0; - for (i32 s : b.succs) { - if (0 <= s && s < cast(i32)n && cfg->blocks[s].reachable) { - lo |= live_in[s]; - } - } - if (check_asm_cfg_block_leaves(cfg, cast(i32)bi)) { - lo |= exit_live; - } - - u16 live = lo; - for (i32 ii = b.last; ii >= b.first; ii--) { - AsmInstructionFacts *f = cfg->insts[ii]->facts; - if (f == nullptr) { - continue; - } - live = (live & ~f->gen_regs) | (f->read_regs & REG_TOP); - } - - if (lo != live_out[bi] || live != live_in[bi]) { - live_out[bi] = lo; - live_in[bi] = live; - changed = true; - } - } - } - - for_array(bi, cfg->blocks) { - cfg->blocks[bi].live_in_regs = live_in[bi]; - cfg->blocks[bi].live_out_regs = live_out[bi]; - } - - if (!emit_dead_writes) { - // Lattice has been computed but no diagnostic until the read facts are validated - return false; - } - - // Dead-write detection: walk each block forward, tracking live-out per instruction. - // A written reg that is not live immediately after the write (and not re-read in - // this same instruction) is dead. - for_array(bi, cfg->blocks) { - AsmBlock const &b = cfg->blocks[bi]; - if (!b.reachable) { - continue; - } - // recompute per-instruction live-out by replaying the transfer from block live_out - // (cheap: block is short). Build an array of live-after-each-instruction. - u16 live = live_out[bi]; - if (check_asm_cfg_block_leaves(cfg, cast(i32)bi)) { - live |= exit_live; // already folded above, but harmless - } - for (i32 ii = b.last; ii >= b.first; ii--) { - AsmInstructionFacts *f = cfg->insts[ii]->facts; - if (f == nullptr) { - continue; - } - u16 live_after = live; - // A register this instruction writes but that is not live afterward, - // and that it does not itself read (self-use like `xor r,r` or `add r,x`), - // is a dead write. - u16 dead = f->gen_regs & ~live_after & ~f->read_regs; - for (u16 bit = 1; bit != 0; bit <<= 1) { - if ((dead & bit) == 0) { - continue; - } - if ((bit & output_regs) != 0) { - warning(f->node->name, - "'%.*s' writes output register %%%s, but that value is overwritten before the template returns; " - "the output's final value does not come from this instruction", - LIT(f->name), asm_ctx->clobber_reg_bit_name(bit)); - } else { - warning(f->node->name, - "'%.*s' writes %%%s but its value is never read before being overwritten or the template ends", - LIT(f->name), asm_ctx->clobber_reg_bit_name(bit)); - } - } - live = (live & ~f->gen_regs) | (f->read_regs & REG_TOP); - } - } - - return true; -} \ No newline at end of file