c65gm/internal/optimizer/pass_load.go

72 lines
1.9 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) []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) {
// Indexed/indirect addressing: address depends on X/Y, can't track by operand text alone
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 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 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, ",")
}
func isImmediate(operand string) bool {
return len(operand) > 0 && operand[0] == '#'
}