package optimizer // passRegDead eliminates dead stores to REGISTER variables. // Two-phase approach: // 1. Global pre-scan: if no instruction reads regVar anywhere in the input, // all stores to that variable are provably dead (labels are irrelevant). // 2. Per-store local scan: for variables that DO have at least one read, // scan forward through the current basic block. // // This is safe because REGISTER variables have a contract: nothing external // can observe their memory location (ASM/Script/Macro references are compile errors). func passRegDead(lines []asmLine, registerVars map[string]bool) []asmLine { // Phase 1 — global pre-scan: which REGISTER vars have at least one read? hasRead := make(map[string]bool, len(registerVars)) for _, l := range lines { if l.isCode && l.operand != "" && registerVars[l.operand] && readsFrom(l.opcode, l.operand, l.operand) { hasRead[l.operand] = true } } // Phase 2 — per-store decision var result []asmLine for i := 0; i < len(lines); i++ { line := lines[i] if line.isCode && line.opcode == "sta" && line.operand != "" && registerVars[line.operand] { if !hasRead[line.operand] { // Globally dead — no read anywhere in the program continue } if isRegStoreDead(lines, i+1, line.operand) { continue } } result = append(result, line) } return result } // isRegStoreDead scans forward from start to determine whether a store to a // REGISTER variable operand is dead. Returns true if the store can be removed. // A store is dead only if no instruction that reads the operand is found // anywhere forward before a label or control-flow-ending instruction. func isRegStoreDead(lines []asmLine, start int, operand string) bool { for i := start; i < len(lines); i++ { l := lines[i] if l.optMarker || l.isComment { continue } if l.isLabel { return false } if l.isCode && isCallSite(l) { return false // callee may modify A; conservatively keep the store } if l.isCode && blocksFlow(l) { return true // rts/brk/rti — execution ends here } if l.isCode && readsFrom(l.opcode, l.operand, operand) { return false // the value IS read from memory later } } return true // end of block reached with no matching load } // readsFrom returns true if the instruction reads from the given memory operand. // Covers loads (lda/ldx/ldy), compares (cmp/cpx/cpy), ALU ops (adc/sbc/and/ora/eor/bit), // and read-modify-write instructions (dec/inc/asl/lsr/rol/ror). func readsFrom(opcode, operand, varName string) bool { if operand != varName { return false } switch opcode { case "lda", "ldx", "ldy": return true case "adc", "sbc", "and", "ora", "eor", "cmp", "cpx", "cpy", "bit": return true case "dec", "inc", "asl", "lsr", "rol", "ror": return true } return false } // blocksFlow returns true for instructions that unconditionally end the current // execution path: rts, jmp, brk, rti func blocksFlow(line asmLine) bool { switch line.opcode { case "rts", "brk", "rti": return true } return false } // isCallSite returns true for instructions that transfer control to a callee // that may modify A, X, Y. The store before a call is conservatively kept. func isCallSite(line asmLine) bool { return line.opcode == "jsr" }