c65gm/internal/optimizer/pass_load.go

93 lines
2.4 KiB
Go

package optimizer
import "strings"
// passLoadElimination removes redundant memory-to-register loads.
// Pattern: lda X; ... (X not modified) ...; lda X → remove second
// Applies to A, X, Y registers identically.
func passLoadElimination(lines []asmLine, cfg *Config) []asmLine {
rs := newRegState()
var result []asmLine
for _, line := range lines {
if !line.isCode {
if line.isLabel {
rs.reset()
}
result = append(result, line)
continue
}
if isChainBreaker(line.opcode) {
rs.reset()
result = append(result, line)
continue
}
// Check for redundant memory load
if line.opcode == "lda" && !isImmediate(line.operand) {
if isIndexedOperand(line.operand) {
updateRegState(line, rs)
} else if isIOAddr(line.operand, cfg) {
// I/O register reads may return different values — don't track
rs.resetA()
updateRegState(line, rs)
} else if rs.a.src == line.operand {
continue // redundant, remove
} else {
rs.a.src = line.operand
}
} else if line.opcode == "ldx" && !isImmediate(line.operand) {
if isIndexedOperand(line.operand) {
updateRegState(line, rs)
} else if isIOAddr(line.operand, cfg) {
rs.resetX()
updateRegState(line, rs)
} else if rs.x.src == line.operand {
continue
} else {
rs.x.src = line.operand
}
} else if line.opcode == "ldy" && !isImmediate(line.operand) {
if isIndexedOperand(line.operand) {
updateRegState(line, rs)
} else if isIOAddr(line.operand, cfg) {
rs.resetY()
updateRegState(line, rs)
} else if rs.y.src == line.operand {
continue
} else {
rs.y.src = line.operand
}
} else {
updateRegState(line, rs)
}
result = append(result, line)
}
return result
}
// isIndexedOperand returns true for addressing modes where the effective address
// depends on X or Y registers (e.g. (zp),y, $xxxx,x, zp,x).
// Such loads can't be tracked by operand text alone since the register may change.
func isIndexedOperand(operand string) bool {
return strings.Contains(operand, "(") || strings.Contains(operand, ",")
}
// isIOAddr returns true if operand is a hex address marked as I/O in the config.
func isIOAddr(operand string, cfg *Config) bool {
if cfg == nil {
return false
}
if !strings.HasPrefix(operand, "$") {
return false
}
addr := parseHexOrDec(operand)
return addr >= 0 && addr < 65536 && cfg.IOMap[addr]
}
func isImmediate(operand string) bool {
return len(operand) > 0 && operand[0] == '#'
}