93 lines
2.4 KiB
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] == '#'
|
|
}
|