millfork/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala

849 lines
52 KiB
Scala
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package millfork.assembly.opt
import java.util.UUID
import java.util.concurrent.atomic.AtomicInteger
import millfork.assembly.{opt, _}
import millfork.assembly.Opcode._
import millfork.assembly.AddrMode._
import millfork.assembly.OpcodeClasses._
import millfork.env._
/**
* These optimizations should not remove opportunities for more complex optimizations to trigger.
*
* @author Karol Stasiak
*/
object AlwaysGoodOptimizations {
val counter = new AtomicInteger(30000)
def getNextLabel(prefix: String) = f".${prefix}%s__${counter.getAndIncrement()}%05d"
val PointlessMath = new RuleBasedAssemblyOptimization("Pointless math",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcode(CLC) & Elidable) ~
(HasOpcode(ADC) & Elidable & MatchParameter(0)) ~
(HasOpcode(SEC) & Elidable) ~
(HasOpcode(SBC) & Elidable & MatchParameter(0)) ~
(LinearOrLabel & Not(ReadsNOrZ) & Not(ReadsV) & Not(ReadsC) & Not(NoopDiscardsFlags) & Not(Set(ADC, SBC))).* ~
(NoopDiscardsFlags | Set(ADC, SBC)) ~~> (_.drop(4)),
(HasOpcode(LDA) & HasImmediate(0) & Elidable) ~
(HasOpcode(CLC) & Elidable) ~
(HasOpcode(ADC) & Elidable) ~
(LinearOrLabel & Not(ReadsV) & Not(NoopDiscardsFlags) & Not(ChangesNAndZ)).* ~
(NoopDiscardsFlags | ChangesNAndZ) ~~> (code => code(2).copy(opcode = LDA) :: code.drop(3))
)
val PointlessMathFromFlow = new RuleBasedAssemblyOptimization("Pointless math from flow analysis",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & MatchA(0) &
HasOpcode(ASL) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, ctx.get[Int](0) << 1) :: Nil
},
(Elidable & MatchA(0) &
HasOpcode(LSR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, (ctx.get[Int](0) & 0xff) >> 1) :: Nil
},
(Elidable & MatchA(0) &
HasClear(State.C) & HasOpcode(ROL) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, ctx.get[Int](0) << 1) :: Nil
},
(Elidable & MatchA(0) &
HasClear(State.C) & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, (ctx.get[Int](0) & 0xff) >> 1) :: Nil
},
(Elidable & MatchA(0) &
HasSet(State.C) & HasOpcode(ROL) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, ctx.get[Int](0) * 2 + 1) :: Nil
},
(Elidable & MatchA(0) &
HasSet(State.C) & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, 0x80 + (ctx.get[Int](0) & 0xff) / 2) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(ADC) & HasAddrMode(Immediate) &
HasClear(State.D) & HasClear(State.C) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, ctx.get[Constant](1) + ctx.get[Int](0)) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(ADC) & HasAddrMode(Immediate) &
HasClear(State.D) & HasClear(State.C) & DoesntMatterWhatItDoesWith(State.V)) ~
Where(ctx => (ctx.get[Constant](1) + ctx.get[Int](0)).quickSimplify match {
case NumericConstant(x, _) => x == (x & 0xff)
case _ => false
}) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, ctx.get[Constant](1) + ctx.get[Int](0)) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(ADC) & HasAddrMode(Immediate) &
HasClear(State.D) & HasSet(State.C) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, ctx.get[Constant](1) + ((ctx.get[Int](0) + 1) & 0xff)) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(SBC) & HasAddrMode(Immediate) &
HasClear(State.D) & HasSet(State.C) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.Minus, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(EOR) & HasAddrMode(Immediate)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.Exor, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(ORA) & HasAddrMode(Immediate)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.Or, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(AND) & HasAddrMode(Immediate)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.And, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
},
(Elidable &
MatchA(0) & MatchParameter(1) &
HasOpcode(ANC) & HasAddrMode(Immediate) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
AssemblyLine.immediate(LDA, CompoundConstant(MathOperator.And, NumericConstant(ctx.get[Int](0), 1), ctx.get[Constant](1)).quickSimplify) :: Nil
},
)
val MathOperationOnTwoIdenticalMemoryOperands = new RuleBasedAssemblyOptimization("Math operation on two identical memory operands",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
(HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)),
(HasOpcodeIn(Set(STA, LDA)) & HasAddrMode(AbsoluteX) & MatchAddrMode(9) & MatchParameter(0)) ~
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA) & Not(ChangesX)).* ~
(HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrMode(AbsoluteX) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)),
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrMode(AbsoluteY) & MatchAddrMode(9) & MatchParameter(0)) ~
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA) & Not(ChangesY)).* ~
(HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrMode(AbsoluteY) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)),
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
(DoesntMatterWhatItDoesWith(State.N, State.Z) & HasOpcodeIn(Set(ORA, AND)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init),
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
(DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & HasOpcode(ANC) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init),
(HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~
(Linear & DoesntChangeMemoryAt(9, 0) & Not(ChangesA)).* ~
(HasOpcode(EOR) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.immediate(LDA, 0)),
)
val PointlessStoreAfterLoad = new RuleBasedAssemblyOptimization("Pointless store after load",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~
(LinearOrLabel & DoesntChangeMemoryAt(0,1) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0)).* ~
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
(HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~
(LinearOrLabel & DoesntChangeMemoryAt(0,1) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0)).* ~
(Elidable & HasOpcode(STX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
(HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~
(LinearOrLabel & DoesntChangeMemoryAt(0,1) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0)).* ~
(Elidable & HasOpcode(STY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
)
val PoinlessStoreBeforeStore = new RuleBasedAssemblyOptimization("Pointless store before store",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STY, STZ)) ~
(LinearOrLabel & DoesNotConcernMemoryAt(2, 1)).* ~
(MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STY, STZ)) ~~> (_.tail),
(Elidable & HasAddrModeIn(Set(AbsoluteX, ZeroPageX)) & MatchParameter(1) & MatchAddrMode(2) & Set(STA, STY, STZ)) ~
(LinearOrLabel & DoesntChangeMemoryAt(2, 1) & Not(ReadsMemory) & Not(ChangesX)).* ~
(MatchParameter(1) & MatchAddrMode(2) & Set(STA, STY, STZ)) ~~> (_.tail),
(Elidable & HasAddrModeIn(Set(AbsoluteY, ZeroPageY)) & MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STZ)) ~
(LinearOrLabel & DoesntChangeMemoryAt(2, 1) & Not(ReadsMemory) & Not(ChangesY)).* ~
(MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STZ)) ~~> (_.tail),
)
val PointlessLoadBeforeReturn = new RuleBasedAssemblyOptimization("Pointless load before return",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Set(LDA, TXA, TYA, EOR, AND, ORA, ANC) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_AF))).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail),
(Set(LDX, TAX, TSX, INX, DEX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_XF))).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail),
(Set(LDY, TAY, INY, DEY) & Elidable) ~ (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_YF))).* ~ HasOpcode(DISCARD_YF) ~~> (_.tail),
(HasOpcode(LDX) & Elidable & MatchAddrMode(3)) ~
(LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~
(HasOpcode(TXA) & Elidable) ~
((LinearOrLabel & Not(ConcernsX) & Not(HasOpcode(DISCARD_XF))).* ~
HasOpcode(DISCARD_XF)).capture(2) ~~> { (c, ctx) =>
ctx.get[List[AssemblyLine]](1) ++ (c.head.copy(opcode = LDA) :: ctx.get[List[AssemblyLine]](2))
},
(HasOpcode(LDY) & Elidable & MatchAddrMode(3)) ~
(LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~
(HasOpcode(TYA) & Elidable) ~
((LinearOrLabel & Not(ConcernsY) & Not(HasOpcode(DISCARD_YF))).* ~
HasOpcode(DISCARD_YF)).capture(2) ~~> { (c, ctx) =>
ctx.get[List[AssemblyLine]](1) ++ (c.head.copy(opcode = LDA) :: ctx.get[List[AssemblyLine]](2))
},
)
private def operationPairBuilder(op1: Opcode.Value, op2: Opcode.Value, middle: AssemblyLinePattern) = {
(HasOpcode(op1) & Elidable) ~
(Linear & middle).*.capture(1) ~
(HasOpcode(op2) & Elidable) ~
((LinearOrLabel & Not(ReadsNOrZ) & Not(ChangesNAndZ)).* ~ ChangesNAndZ).capture(2) ~~> { (_, ctx) =>
ctx.get[List[AssemblyLine]](1) ++ ctx.get[List[AssemblyLine]](2)
}
}
val PointlessOperationPairRemoval = new RuleBasedAssemblyOptimization("Pointless operation pair",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
operationPairBuilder(PHA, PLA, Not(ConcernsA) & Not(ConcernsStack)),
operationPairBuilder(PHX, PLX, Not(ConcernsX) & Not(ConcernsStack)),
operationPairBuilder(PHY, PLY, Not(ConcernsY) & Not(ConcernsStack)),
operationPairBuilder(INX, DEX, Not(ConcernsX) & Not(ReadsNOrZ)),
operationPairBuilder(DEX, INX, Not(ConcernsX) & Not(ReadsNOrZ)),
operationPairBuilder(INY, DEY, Not(ConcernsX) & Not(ReadsNOrZ)),
operationPairBuilder(DEY, INY, Not(ConcernsX) & Not(ReadsNOrZ)),
)
val BranchInPlaceRemoval = new RuleBasedAssemblyOptimization("Branch in place",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(AllDirectJumps & MatchParameter(0) & Elidable) ~
HasOpcodeIn(NoopDiscardsFlags).* ~
(HasOpcode(LABEL) & MatchParameter(0)) ~~> (c => c.last :: Nil)
)
val ImpossibleBranchRemoval = new RuleBasedAssemblyOptimization("Impossible branch",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(HasOpcode(BCC) & HasSet(State.C) & Elidable) ~~> (_ => Nil),
(HasOpcode(BCS) & HasClear(State.C) & Elidable) ~~> (_ => Nil),
(HasOpcode(BVC) & HasSet(State.V) & Elidable) ~~> (_ => Nil),
(HasOpcode(BVS) & HasClear(State.V) & Elidable) ~~> (_ => Nil),
(HasOpcode(BNE) & HasSet(State.Z) & Elidable) ~~> (_ => Nil),
(HasOpcode(BEQ) & HasClear(State.Z) & Elidable) ~~> (_ => Nil),
(HasOpcode(BPL) & HasSet(State.N) & Elidable) ~~> (_ => Nil),
(HasOpcode(BMI) & HasClear(State.N) & Elidable) ~~> (_ => Nil),
)
val UnconditionalJumpRemoval = new RuleBasedAssemblyOptimization("Unconditional jump removal",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(JMP) & HasAddrMode(Absolute) & MatchParameter(0)) ~
(Elidable & LinearOrBranch).* ~
(HasOpcode(LABEL) & MatchParameter(0)) ~~> (_ => Nil),
(Elidable & HasOpcode(JMP) & HasAddrMode(Absolute) & MatchParameter(0)) ~
(Not(HasOpcode(LABEL)) & Not(MatchParameter(0))).* ~
(HasOpcode(LABEL) & MatchParameter(0)) ~
(HasOpcode(LABEL) | HasOpcodeIn(NoopDiscardsFlags)).* ~
HasOpcode(RTS) ~~> (code => AssemblyLine.implied(RTS) :: code.tail),
(Elidable & HasOpcodeIn(ShortBranching) & MatchParameter(0)) ~
(HasOpcodeIn(NoopDiscardsFlags).* ~
(Elidable & HasOpcode(RTS))).capture(1) ~
(HasOpcode(LABEL) & MatchParameter(0)) ~
HasOpcodeIn(NoopDiscardsFlags).* ~
(Elidable & HasOpcode(RTS)) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1)),
)
val TailCallOptimization = new RuleBasedAssemblyOptimization("Tail call optimization",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(JSR)) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (Elidable & HasOpcode(RTS)) ~~> (c => c.tail.init :+ c.head.copy(opcode = JMP)),
(Elidable & HasOpcode(JSR)) ~
HasOpcode(LABEL).* ~
HasOpcodeIn(NoopDiscardsFlags).*.capture(0) ~
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](0) ++ (code.head.copy(opcode = JMP) :: code.tail)),
)
val UnusedCodeRemoval = new RuleBasedAssemblyOptimization("Unreachable code removal",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
HasOpcode(JMP) ~ (Not(HasOpcode(LABEL)) & Elidable).+ ~~> (c => c.head :: Nil)
)
val PoinlessFlagChange = new RuleBasedAssemblyOptimization("Pointless flag change",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcodeIn(Set(CMP, CPX, CPY)) & Elidable) ~ NoopDiscardsFlags ~~> (_.tail),
(OverwritesC & Elidable) ~ (LinearOrLabel & Not(ReadsC) & Not(DiscardsC)).* ~ DiscardsC ~~> (_.tail),
(OverwritesD & Elidable) ~ (LinearOrLabel & Not(ReadsD) & Not(DiscardsD)).* ~ DiscardsD ~~> (_.tail),
(OverwritesV & Elidable) ~ (LinearOrLabel & Not(ReadsV) & Not(DiscardsV)).* ~ DiscardsV ~~> (_.tail)
)
val FlagFlowAnalysis = new RuleBasedAssemblyOptimization("Flag flow analysis",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(HasSet(State.C) & HasOpcode(SEC) & Elidable) ~~> (_ => Nil),
(HasSet(State.D) & HasOpcode(SED) & Elidable) ~~> (_ => Nil),
(HasClear(State.C) & HasOpcode(CLC) & Elidable) ~~> (_ => Nil),
(HasClear(State.D) & HasOpcode(CLD) & Elidable) ~~> (_ => Nil),
(HasClear(State.V) & HasOpcode(CLV) & Elidable) ~~> (_ => Nil),
(HasSet(State.C) & HasOpcode(BCS) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasClear(State.C) & HasOpcode(BCC) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasSet(State.N) & HasOpcode(BMI) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasClear(State.N) & HasOpcode(BPL) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasClear(State.V) & HasOpcode(BVC) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasSet(State.V) & HasOpcode(BVS) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasSet(State.Z) & HasOpcode(BEQ) & Elidable) ~~> (c => c.map(_.copy(opcode = JMP, addrMode = Absolute))),
(HasClear(State.Z) & HasOpcode(BNE) & Elidable) ~~> (_ => Nil),
)
val ReverseFlowAnalysis = new RuleBasedAssemblyOptimization("Reverse flow analysis",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcodeIn(Set(TXA, TYA, LDA, EOR, ORA, AND)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcode(ANC) & DoesntMatterWhatItDoesWith(State.A, State.C, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(TAX, TSX, LDX, INX, DEX)) & DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(TAY, LDY, DEY, INY)) & DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(LAX)) & DoesntMatterWhatItDoesWith(State.A, State.X, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(SEC, CLC)) & DoesntMatterWhatItDoesWith(State.C)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(CLD, SED)) & DoesntMatterWhatItDoesWith(State.D)) ~~> (_ => Nil),
(Elidable & HasOpcode(CLV) & DoesntMatterWhatItDoesWith(State.V)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(CMP, CPX, CPY)) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(BIT)) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z, State.V)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(ASL, LSR, ROL, ROR)) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.C, State.N, State.Z)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(ADC, SBC)) & DoesntMatterWhatItDoesWith(State.A, State.C, State.V, State.N, State.Z)) ~~> (_ => Nil),
)
private def modificationOfJustWrittenValue(store: Opcode.Value,
addrMode: AddrMode.Value,
initExtra: AssemblyLinePattern,
modify: Opcode.Value,
meantimeExtra: AssemblyLinePattern,
atLeastTwo: Boolean,
flagsToTrash: Seq[State.Value],
fix: ((AssemblyMatchingContext, Int) => List[AssemblyLine]),
alternateStore: Opcode.Value = LABEL) = {
val actualFlagsToTrash = List(State.N, State.Z) ++ flagsToTrash
val init = Elidable & HasOpcode(store) & HasAddrMode(addrMode) & MatchAddrMode(3) & MatchParameter(0) & DoesntMatterWhatItDoesWith(actualFlagsToTrash: _*) & initExtra
val meantime = (Linear & Not(ConcernsMemory) & meantimeExtra).*
val oneModification = Elidable & HasOpcode(modify) & HasAddrMode(addrMode) & MatchParameter(0) & DoesntMatterWhatItDoesWith(actualFlagsToTrash: _*)
val modifications = (if (atLeastTwo) oneModification ~ oneModification.+ else oneModification.+).captureLength(1)
if (alternateStore == LABEL) {
((init ~ meantime).capture(2) ~ modifications) ~~> ((code, ctx) => fix(ctx, ctx.get[Int](1)) ++ ctx.get[List[AssemblyLine]](2))
} else {
(init.capture(3) ~ meantime.capture(2) ~ modifications) ~~> { (code, ctx) =>
fix(ctx, ctx.get[Int](1)) ++
List(AssemblyLine(alternateStore, ctx.get[AddrMode.Value](3), ctx.get[Constant](0))) ++
ctx.get[List[AssemblyLine]](2)
}
}
}
val ModificationOfJustWrittenValue = new RuleBasedAssemblyOptimization("Modification of Just written value",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
modificationOfJustWrittenValue(STA, Absolute, MatchA(5), INC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff)
)),
modificationOfJustWrittenValue(STA, Absolute, MatchA(5), DEC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
AssemblyLine.immediate(LDA, (c.get[Int](5) - i) & 0xff)
)),
modificationOfJustWrittenValue(STA, ZeroPage, MatchA(5), INC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff)
)),
modificationOfJustWrittenValue(STA, ZeroPage, MatchA(5), DEC, Anything, atLeastTwo = false, Seq(), (c, i) => List(
AssemblyLine.immediate(LDA, (c.get[Int](5) - i) & 0xff)
)),
modificationOfJustWrittenValue(STA, AbsoluteX, MatchA(5), INC, Not(ChangesX), atLeastTwo = false, Seq(), (c, i) => List(
AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff)
)),
modificationOfJustWrittenValue(STA, AbsoluteX, MatchA(5), DEC, Not(ChangesX), atLeastTwo = false, Seq(), (c, i) => List(
AssemblyLine.immediate(LDA, (c.get[Int](5) - i) & 0xff)
)),
modificationOfJustWrittenValue(STA, Absolute, Anything, INC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
AssemblyLine.implied(CLC),
AssemblyLine.immediate(ADC, i)
)),
modificationOfJustWrittenValue(STA, Absolute, Anything, DEC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
AssemblyLine.implied(SEC),
AssemblyLine.immediate(SBC, i)
)),
modificationOfJustWrittenValue(STA, ZeroPage, Anything, INC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
AssemblyLine.implied(CLC),
AssemblyLine.immediate(ADC, i)
)),
modificationOfJustWrittenValue(STA, ZeroPage, Anything, DEC, Anything, atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
AssemblyLine.implied(SEC),
AssemblyLine.immediate(SBC, i)
)),
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, INC, Not(ChangesX), atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
AssemblyLine.implied(CLC),
AssemblyLine.immediate(ADC, i)
)),
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, DEC, Not(ChangesX), atLeastTwo = true, Seq(State.C, State.V), (_, i) => List(
AssemblyLine.implied(SEC),
AssemblyLine.immediate(SBC, i)
)),
modificationOfJustWrittenValue(STA, Absolute, Anything, ASL, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(ASL))),
modificationOfJustWrittenValue(STA, Absolute, Anything, LSR, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(LSR))),
modificationOfJustWrittenValue(STA, ZeroPage, Anything, ASL, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(ASL))),
modificationOfJustWrittenValue(STA, ZeroPage, Anything, LSR, Anything, atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(LSR))),
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, ASL, Not(ChangesX), atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(ASL))),
modificationOfJustWrittenValue(STA, AbsoluteX, Anything, LSR, Not(ChangesX), atLeastTwo = false, Seq(State.C), (_, i) => List.fill(i)(AssemblyLine.implied(LSR))),
modificationOfJustWrittenValue(STX, Absolute, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INX))),
modificationOfJustWrittenValue(STX, Absolute, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEX))),
modificationOfJustWrittenValue(STY, Absolute, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INY))),
modificationOfJustWrittenValue(STY, Absolute, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEY))),
modificationOfJustWrittenValue(STZ, Absolute, Anything, ASL, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
modificationOfJustWrittenValue(STZ, Absolute, Anything, LSR, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
modificationOfJustWrittenValue(STZ, Absolute, Anything, INC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, i)), STA),
modificationOfJustWrittenValue(STZ, Absolute, Anything, DEC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, 256 - i)), STA),
modificationOfJustWrittenValue(STX, ZeroPage, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INX))),
modificationOfJustWrittenValue(STX, ZeroPage, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEX))),
modificationOfJustWrittenValue(STY, ZeroPage, Anything, INC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(INY))),
modificationOfJustWrittenValue(STY, ZeroPage, Anything, DEC, Anything, atLeastTwo = false, Seq(), (_, i) => List.fill(i)(AssemblyLine.implied(DEY))),
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, ASL, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, LSR, Anything, atLeastTwo = false, Seq(), (_, i) => Nil),
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, INC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, i)), STA),
modificationOfJustWrittenValue(STZ, ZeroPage, Anything, DEC, Anything, atLeastTwo = false, Seq(State.A), (_, i) => List(AssemblyLine.immediate(LDA, 256 - i)), STA),
)
val ConstantFlowAnalysis = new RuleBasedAssemblyOptimization("Constant flow analysis",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Absolute, parameter = l.parameter + ctx.get[Int](0)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Absolute, parameter = l.parameter + ctx.get[Int](0)))
},
(MatchX(0) & HasAddrMode(ZeroPageX) & Elidable) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = ZeroPage, parameter = l.parameter + ctx.get[Int](0)))
},
(MatchY(0) & HasAddrMode(ZeroPageY) & Elidable) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = ZeroPage, parameter = l.parameter + ctx.get[Int](0)))
},
)
val IdempotentDuplicateRemoval = new RuleBasedAssemblyOptimization("Idempotent duplicate operation",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
HasOpcode(RTS) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (HasOpcode(RTS) ~ Elidable) ~~> (_.take(1)) ::
HasOpcode(RTI) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (HasOpcode(RTI) ~ Elidable) ~~> (_.take(1)) ::
HasOpcode(DISCARD_XF) ~ (Not(HasOpcode(DISCARD_XF)) & HasOpcodeIn(NoopDiscardsFlags + LABEL)).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail) ::
HasOpcode(DISCARD_AF) ~ (Not(HasOpcode(DISCARD_AF)) & HasOpcodeIn(NoopDiscardsFlags + LABEL)).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail) ::
HasOpcode(DISCARD_YF) ~ (Not(HasOpcode(DISCARD_YF)) & HasOpcodeIn(NoopDiscardsFlags + LABEL)).* ~ HasOpcode(DISCARD_YF) ~~> (_.tail) ::
List(RTS, RTI, SEC, CLC, CLV, CLD, SED, SEI, CLI, TAX, TXA, TYA, TAY, TXS, TSX).flatMap { opcode =>
Seq(
(HasOpcode(opcode) & Elidable) ~ (HasOpcodeIn(NoopDiscardsFlags) | HasOpcode(LABEL)).* ~ HasOpcode(opcode) ~~> (_.tail),
HasOpcode(opcode) ~ (HasOpcode(opcode) ~ Elidable) ~~> (_.init),
)
}: _*
)
val PointlessRegisterTransfers = new RuleBasedAssemblyOptimization("Pointless register transfers",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
HasOpcode(TYA) ~ (Elidable & Set(TYA, TAY)) ~~> (_.init),
HasOpcode(TXA) ~ (Elidable & Set(TXA, TAX)) ~~> (_.init),
HasOpcode(TAY) ~ (Elidable & Set(TYA, TAY)) ~~> (_.init),
HasOpcode(TAX) ~ (Elidable & Set(TXA, TAX)) ~~> (_.init),
HasOpcode(TSX) ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
HasOpcode(TXS) ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
HasOpcode(TSX) ~ (Not(ChangesX) & Not(ChangesS) & Linear).* ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
HasOpcode(TXS) ~ (Not(ChangesX) & Not(ChangesS) & Linear).* ~ (Elidable & Set(TXS, TSX)) ~~> (_.init),
)
val PointlessRegisterTransfersBeforeStore = new RuleBasedAssemblyOptimization("Pointless register transfers before store",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(TXA)) ~
(Linear & Not(ConcernsA) & Not(ConcernsX)).* ~
(Elidable & HasOpcode(STA) & HasAddrModeIn(Set(ZeroPage, ZeroPageY, Absolute)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (code => code.tail.init :+ code.last.copy(opcode = STX)),
(Elidable & HasOpcode(TYA)) ~
(Linear & Not(ConcernsA) & Not(ConcernsY)).* ~
(Elidable & HasOpcode(STA) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (code => code.tail.init :+ code.last.copy(opcode = STY)),
)
val PointlessRegisterTransfersBeforeReturn = new RuleBasedAssemblyOptimization("Pointless register transfers before return",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcode(TAX) & Elidable) ~
HasOpcode(LABEL).* ~
HasOpcode(TXA).? ~
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_XF)).capture(1) ~
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
(HasOpcode(TSX) & Elidable) ~
HasOpcode(LABEL).* ~
HasOpcode(TSX).? ~
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_XF)).capture(1) ~
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
(HasOpcode(TXA) & Elidable) ~
HasOpcode(LABEL).* ~
HasOpcode(TAX).? ~
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_AF)).capture(1) ~
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
(HasOpcode(TAY) & Elidable) ~
HasOpcode(LABEL).* ~
HasOpcode(TYA).? ~
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_YF)).capture(1) ~
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
(HasOpcode(TYA) & Elidable) ~
HasOpcode(LABEL).* ~
HasOpcode(TAY).? ~
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_AF)).capture(1) ~
HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1) ++ (AssemblyLine.implied(RTS) :: code.tail)),
)
val PointlessRegisterTransfersBeforeCompare = new RuleBasedAssemblyOptimization("Pointless register transfers before compare",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
HasOpcodeIn(Set(DEX, INX, LDX, LAX)) ~
(HasOpcode(TXA) & Elidable & DoesntMatterWhatItDoesWith(State.A)) ~~> (code => code.init),
HasOpcodeIn(Set(DEY, INY, LDY)) ~
(HasOpcode(TYA) & Elidable & DoesntMatterWhatItDoesWith(State.A)) ~~> (code => code.init),
)
private def stashing(tai: Opcode.Value, tia: Opcode.Value, readsI: AssemblyLinePattern, concernsI: AssemblyLinePattern, discardIF: Opcode.Value, withRts: Boolean, withBeq: Boolean) = {
val init: AssemblyPattern = if (withBeq) {
(Linear & ChangesNAndZ & ChangesA) ~
(HasOpcode(tai) & Elidable) ~
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* ~
(ShortBranching & ReadsNOrZ & MatchParameter(0))
} else {
(HasOpcode(tai) & Elidable) ~
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* ~
((ShortBranching -- ReadsNOrZ) & MatchParameter(0))
}
val inner: AssemblyPattern = if (withRts) {
(Linear & Not(readsI) & Not(ReadsNOrZ ++ NoopDiscardsFlags)).* ~
ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(discardIF)) ~
HasOpcodeIn(Set(RTS, RTI)) ~
Not(HasOpcode(LABEL)).*
} else {
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).*
}
val end: AssemblyPattern =
(HasOpcode(LABEL) & MatchParameter(0)) ~
(Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* ~
(HasOpcode(tia) & Elidable)
val total = init ~ inner ~ end
if (withBeq) {
total ~~> (code => code.head :: (code.tail.tail.init :+ AssemblyLine.implied(tai)))
} else {
total ~~> (code => code.tail.init :+ AssemblyLine.implied(tai))
}
}
// Optimize the following patterns:
// TAX - B__ .a - don't change A - .a - TXA
// TAX - B__ .a - change A discard X RTS - .a - TXA
// by removing the first transfer and flipping the second one
val PointlessStashingToIndexOverShortSafeBranch = new RuleBasedAssemblyOptimization("Pointless stashing into index over short safe branch",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
// stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = false, withBeq = false),
stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = true, withBeq = false),
// stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = false, withBeq = true),
// stashing(TAX, TXA, ReadsX, ConcernsX, DISCARD_XF, withRts = true, withBeq = true),
//
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = false, withBeq = false),
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = true, withBeq = false),
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = false, withBeq = true),
// stashing(TAY, TYA, ReadsY, ConcernsY, DISCARD_YF, withRts = true, withBeq = true),
)
private def loadBeforeTransfer(ld1: Opcode.Value, ld2: Opcode.Value, concerns1: AssemblyLinePattern, overwrites1: State.Value, t12: Opcode.Value, ams: Set[AddrMode.Value]) =
(Elidable & HasOpcode(ld1) & MatchAddrMode(0) & MatchParameter(1) & HasAddrModeIn(ams)) ~
(Linear & Not(ReadsNOrZ) & Not(concerns1) & DoesntChangeMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0) & Not(HasOpcode(t12))).*.capture(2) ~
(HasOpcode(t12) & Elidable & DoesntMatterWhatItDoesWith(overwrites1, State.N, State.Z)) ~~> { (code, ctx) =>
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = ld2)
}
val PointlessLoadBeforeTransfer = new RuleBasedAssemblyOptimization("Pointless load before transfer",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
loadBeforeTransfer(LDX, LDA, ConcernsX, State.X, TXA, Set(ZeroPage, Absolute, IndexedY, AbsoluteY)),
loadBeforeTransfer(LDA, LDX, ConcernsA, State.A, TAX, Set(ZeroPage, Absolute, IndexedY, AbsoluteY)),
loadBeforeTransfer(LDY, LDA, ConcernsY, State.Y, TYA, Set(ZeroPage, Absolute, ZeroPageX, IndexedX, AbsoluteX)),
loadBeforeTransfer(LDA, LDY, ConcernsA, State.A, TAY, Set(ZeroPage, Absolute, ZeroPageX, IndexedX, AbsoluteX)),
)
private def immediateLoadBeforeTwoTransfers(ld1: Opcode.Value, ld2: Opcode.Value, concerns1: AssemblyLinePattern, overwrites1: State.Value, t12: Opcode.Value, t21: Opcode.Value) =
(Elidable & HasOpcode(ld1) & HasAddrMode(Immediate)) ~
(Linear & Not(ReadsNOrZ) & Not(concerns1) & Not(HasOpcode(t12))).*.capture(2) ~
(HasOpcode(t12) & Elidable & DoesntMatterWhatItDoesWith(overwrites1, State.N, State.Z)) ~~> { (code, ctx) =>
ctx.get[List[AssemblyLine]](2) :+ code.head.copy(opcode = ld2)
}
val YYY = new RuleBasedAssemblyOptimization("Pointless load before transfer",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
immediateLoadBeforeTwoTransfers(LDA, LDY, ConcernsA, State.A, TAY, TYA),
)
val ConstantIndexPropagation = new RuleBasedAssemblyOptimization("Constant index propagation",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcode(LDX) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Linear & Not(ChangesX) & Not(HasAddrMode(AbsoluteX))).* ~
(Elidable & SupportsAbsolute & HasAddrMode(AbsoluteX)) ~~> { (lines, ctx) =>
val last = lines.last
val offset = ctx.get[Constant](0)
lines.init :+ last.copy(addrMode = Absolute, parameter = last.parameter + offset)
},
(HasOpcode(LDY) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Linear & Not(ChangesY) & Not(HasAddrMode(AbsoluteY))).* ~
(Elidable & SupportsAbsolute & HasAddrMode(AbsoluteY)) ~~> { (lines, ctx) =>
val last = lines.last
val offset = ctx.get[Constant](0)
lines.init :+ last.copy(addrMode = Absolute, parameter = last.parameter + offset)
},
)
val PoinlessLoadBeforeAnotherLoad = new RuleBasedAssemblyOptimization("Pointless load before another load",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Set(LDA, TXA, TYA) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ)).* ~ OverwritesA ~~> (_.tail),
(Set(LDX, TAX, TSX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ)).* ~ OverwritesX ~~> (_.tail),
(Set(LDY, TAY) & Elidable) ~ (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ)).* ~ OverwritesY ~~> (_.tail),
)
// TODO: better proofs that memory doesn't change
val PointlessLoadAfterLoadOrStore = new RuleBasedAssemblyOptimization("Pointless load after load or store",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcodeIn(Set(LDA, STA)) & HasAddrMode(Implied) & MatchParameter(1)) ~
(Linear & Not(ChangesA)).* ~
(Elidable & HasOpcode(LDA) & HasAddrMode(Implied) & MatchParameter(1)) ~~> (_.init),
(HasOpcodeIn(Set(LDX, STX)) & HasAddrMode(Implied) & MatchParameter(1)) ~
(Linear & Not(ChangesX)).* ~
(Elidable & HasOpcode(LDX) & HasAddrMode(Implied) & MatchParameter(1)) ~~> (_.init),
(HasOpcodeIn(Set(LDY, STY)) & HasAddrMode(Implied) & MatchParameter(1)) ~
(Linear & Not(ChangesY)).* ~
(Elidable & HasOpcode(LDY) & HasAddrMode(Implied) & MatchParameter(1)) ~~> (_.init),
(HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
(Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
(HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ChangesX) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
(Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
(HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ChangesY) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
(Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
)
val PointlessOperationAfterLoad = new RuleBasedAssemblyOptimization("Pointless operation after load",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(ChangesA & ChangesNAndZ) ~ (Elidable & HasOpcode(EOR) & HasImmediate(0)) ~~> (_.init),
(ChangesA & ChangesNAndZ) ~ (Elidable & HasOpcode(ORA) & HasImmediate(0)) ~~> (_.init),
(ChangesA & ChangesNAndZ) ~ (Elidable & HasOpcode(AND) & HasImmediate(0xff)) ~~> (_.init)
)
val SimplifiableBitOpsSequence = new RuleBasedAssemblyOptimization("Simplifiable sequence of bit operations",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(EOR) & MatchImmediate(0)) ~
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsA)).* ~
(Elidable & HasOpcode(EOR) & MatchImmediate(1)) ~~> { (lines, ctx) =>
lines.init.tail :+ AssemblyLine.immediate(EOR, CompoundConstant(MathOperator.Exor, ctx.get[Constant](0), ctx.get[Constant](1)))
},
(Elidable & HasOpcode(ORA) & MatchImmediate(0)) ~
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsA)).* ~
(Elidable & HasOpcode(ORA) & MatchImmediate(1)) ~~> { (lines, ctx) =>
lines.init.tail :+ AssemblyLine.immediate(ORA, CompoundConstant(MathOperator.Or, ctx.get[Constant](0), ctx.get[Constant](1)))
},
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsA)).* ~
(Elidable & HasOpcode(AND) & MatchImmediate(1)) ~~> { (lines, ctx) =>
lines.init.tail :+ AssemblyLine.immediate(AND, CompoundConstant(MathOperator.And, ctx.get[Constant](0), ctx.get[Constant](1)))
},
(Elidable & HasOpcode(ANC) & MatchImmediate(0)) ~
(Linear & Not(ChangesA) & Not(ReadsNOrZ) & Not(ReadsC) & Not(ReadsA)).* ~
(Elidable & HasOpcode(ANC) & MatchImmediate(1)) ~~> { (lines, ctx) =>
lines.init.tail :+ AssemblyLine.immediate(ANC, CompoundConstant(MathOperator.And, ctx.get[Constant](0), ctx.get[Constant](1)))
},
)
val RemoveNops = new RuleBasedAssemblyOptimization("Removing NOP instructions",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(NOP)) ~~> (_ => Nil)
)
val RearrangeMath = new RuleBasedAssemblyOptimization("Rearranging math",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(LDA) & HasAddrMode(Immediate)) ~
(Elidable & HasOpcodeIn(Set(CLC, SEC))) ~
(Elidable & HasOpcode(ADC) & Not(HasAddrMode(Immediate))) ~~> { c =>
c.last.copy(opcode = LDA) :: c(1) :: c.head.copy(opcode = ADC) :: Nil
},
(Elidable & HasOpcode(LDA) & HasAddrMode(Immediate)) ~
(Elidable & HasOpcodeIn(Set(ADC, EOR, ORA, AND)) & Not(HasAddrMode(Immediate))) ~~> { c =>
c.last.copy(opcode = LDA) :: c.head.copy(opcode = c.last.opcode) :: Nil
},
)
private def wordShifting(i: Int, hiFirst: Boolean, hiFromX: Boolean) = {
val ldax = if (hiFromX) LDX else LDA
val stax = if (hiFromX) STX else STA
val restriction = if (hiFromX) Not(ReadsX) else Anything
val originalStart = if (hiFirst) {
(Elidable & HasOpcode(LDA) & MatchParameter(0) & MatchAddrMode(1)) ~
(Elidable & HasOpcode(STA) & MatchParameter(2) & MatchAddrMode(3) & restriction) ~
(Elidable & HasOpcode(ldax) & HasImmediate(0)) ~
(Elidable & HasOpcode(stax) & MatchParameter(4) & MatchAddrMode(5))
} else {
(Elidable & HasOpcode(ldax) & HasImmediate(0)) ~
(Elidable & HasOpcode(stax) & MatchParameter(4) & MatchAddrMode(5)) ~
(Elidable & HasOpcode(LDA) & MatchParameter(0) & MatchAddrMode(1)) ~
(Elidable & HasOpcode(STA) & MatchParameter(2) & MatchAddrMode(3) & restriction)
}
val middle = (Linear & Not(ConcernsMemory) & DoesntChangeIndexingInAddrMode(3) & DoesntChangeIndexingInAddrMode(5)).*
val singleOriginalShift =
(Elidable & HasOpcode(ASL) & MatchParameter(2) & MatchAddrMode(3)) ~
(Elidable & HasOpcode(ROL) & MatchParameter(4) & MatchAddrMode(5) & DoesntMatterWhatItDoesWith(State.C, State.N, State.V, State.Z))
val originalShifting = (1 to i).map(_ => singleOriginalShift).reduce(_ ~ _)
originalStart ~ middle.capture(6) ~ originalShifting ~~> { (code, ctx) =>
val newStart = List(
code(0),
code(1).copy(addrMode = code(3).addrMode, parameter = code(3).parameter),
code(2),
code(3).copy(addrMode = code(1).addrMode, parameter = code(1).parameter))
val middle = ctx.get[List[AssemblyLine]](6)
val singleNewShift = List(
AssemblyLine(LSR, ctx.get[AddrMode.Value](5), ctx.get[Constant](4)),
AssemblyLine(ROR, ctx.get[AddrMode.Value](3), ctx.get[Constant](2)))
newStart ++ middle ++ (i until 8).flatMap(_ => singleNewShift)
}
}
val SmarterShiftingWords = new RuleBasedAssemblyOptimization("Smarter shifting of words",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
wordShifting(8, hiFirst = false, hiFromX = true),
wordShifting(8, hiFirst = false, hiFromX = false),
wordShifting(8, hiFirst = true, hiFromX = true),
wordShifting(8, hiFirst = true, hiFromX = false),
wordShifting(7, hiFirst = false, hiFromX = true),
wordShifting(7, hiFirst = false, hiFromX = false),
wordShifting(7, hiFirst = true, hiFromX = true),
wordShifting(7, hiFirst = true, hiFromX = false),
wordShifting(6, hiFirst = false, hiFromX = true),
wordShifting(6, hiFirst = false, hiFromX = false),
wordShifting(6, hiFirst = true, hiFromX = true),
wordShifting(6, hiFirst = true, hiFromX = false),
wordShifting(5, hiFirst = false, hiFromX = true),
wordShifting(5, hiFirst = false, hiFromX = false),
wordShifting(5, hiFirst = true, hiFromX = true),
wordShifting(5, hiFirst = true, hiFromX = false),
)
private def carryFlagConversionCase(shift: Int, firstSet: Boolean, zeroIfSet: Boolean) = {
val nonZero = 1 << shift
val test = Elidable & HasOpcode(if (firstSet) BCC else BCS) & MatchParameter(0)
val ifSet = Elidable & HasOpcode(LDA) & HasImmediate(if (zeroIfSet) 0 else nonZero)
val ifClear = Elidable & HasOpcode(LDA) & HasImmediate(if (zeroIfSet) nonZero else 0)
val jump = Elidable & HasOpcodeIn(Set(JMP, if (firstSet) BCS else BCC, if (zeroIfSet) BEQ else BNE)) & MatchParameter(1)
val elseLabel = Elidable & HasOpcode(LABEL) & MatchParameter(0)
val afterLabel = Elidable & HasOpcode(LABEL) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.N, State.V, State.Z)
val store = Elidable & (Not(ReadsC) & Linear | HasOpcodeIn(Set(RTS, JSR, RTI)))
val secondReturn = (Elidable & HasOpcodeIn(Set(RTS, RTI) | NoopDiscardsFlags)).*.capture(6)
val where = Where { ctx =>
ctx.get[List[AssemblyLine]](4) == ctx.get[List[AssemblyLine]](5) ||
ctx.get[List[AssemblyLine]](4) == ctx.get[List[AssemblyLine]](5) ++ ctx.get[List[AssemblyLine]](6)
}
val pattern =
if (firstSet) test ~ ifSet ~ store.*.capture(4) ~ jump ~ elseLabel ~ ifClear ~ store.*.capture(5) ~ afterLabel ~ secondReturn ~ where
else test ~ ifClear ~ store.*.capture(4) ~ jump ~ elseLabel ~ ifSet ~ store.*.capture(5) ~ afterLabel ~ secondReturn ~ where
pattern ~~> { (_, ctx) =>
List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.implied(if (shift >= 4) ROR else ROL)) ++
(if (shift >= 4) List.fill(7 - shift)(AssemblyLine.implied(LSR)) else List.fill(shift)(AssemblyLine.implied(ASL))) ++
(if (zeroIfSet) List(AssemblyLine.immediate(EOR, nonZero)) else Nil) ++
ctx.get[List[AssemblyLine]](5) ++
ctx.get[List[AssemblyLine]](6)
}
}
val CarryFlagConversion = new RuleBasedAssemblyOptimization("Carry flag conversion",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
// TODO: These yield 2 cycles more but 12 bytes less
// TODO: Add an "optimize for size" compilation option?
// carryFlagConversionCase(2, firstSet = false, zeroIfSet = false),
// carryFlagConversionCase(2, firstSet = true, zeroIfSet = false),
// carryFlagConversionCase(1, firstSet = true, zeroIfSet = true),
// carryFlagConversionCase(1, firstSet = false, zeroIfSet = true),
carryFlagConversionCase(1, firstSet = false, zeroIfSet = false),
carryFlagConversionCase(1, firstSet = true, zeroIfSet = false),
carryFlagConversionCase(0, firstSet = true, zeroIfSet = true),
carryFlagConversionCase(0, firstSet = false, zeroIfSet = true),
carryFlagConversionCase(0, firstSet = false, zeroIfSet = false),
carryFlagConversionCase(0, firstSet = true, zeroIfSet = false),
// carryFlagConversionCase(5, firstSet = false, zeroIfSet = false),
// carryFlagConversionCase(5, firstSet = true, zeroIfSet = false),
// carryFlagConversionCase(6, firstSet = true, zeroIfSet = true),
// carryFlagConversionCase(6, firstSet = false, zeroIfSet = true),
carryFlagConversionCase(6, firstSet = false, zeroIfSet = false),
carryFlagConversionCase(6, firstSet = true, zeroIfSet = false),
carryFlagConversionCase(7, firstSet = true, zeroIfSet = true),
carryFlagConversionCase(7, firstSet = false, zeroIfSet = true),
carryFlagConversionCase(7, firstSet = false, zeroIfSet = false),
carryFlagConversionCase(7, firstSet = true, zeroIfSet = false),
)
val Adc0Optimization = new RuleBasedAssemblyOptimization("ADC #0/#1 optimization",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(LDA) & HasImmediate(0) & HasClear(State.D)) ~
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
val label = getNextLabel("ah")
List(
AssemblyLine.relative(BCC, label),
code.last.copy(opcode = INC),
AssemblyLine.label(label))
},
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
(Elidable & HasOpcode(ADC) & HasImmediate(0) & HasClear(State.D)) ~
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
val label = getNextLabel("ah")
List(
AssemblyLine.relative(BCC, label),
code.last.copy(opcode = INC),
AssemblyLine.label(label))
},
(Elidable & HasOpcode(LDA) & HasImmediate(1) & HasClear(State.D) & HasClear(State.C)) ~
(Elidable & HasOpcode(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
List(code.last.copy(opcode = INC))
},
(Elidable & HasOpcode(LDA) & MatchAddrMode(1) & HasClear(State.C) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~
(Elidable & HasOpcode(ADC) & HasImmediate(1) & HasClear(State.D)) ~
(Elidable & HasOpcode(STA) & MatchAddrMode(1) & MatchParameter(2) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
List(code.last.copy(opcode = INC))
},
(Elidable & HasOpcode(TXA) & HasClear(State.D)) ~
(Elidable & HasOpcode(ADC) & HasImmediate(0)) ~
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
val label = getNextLabel("ah")
List(
AssemblyLine.relative(BCC, label),
AssemblyLine.implied(INX),
AssemblyLine.label(label))
},
(Elidable & HasOpcode(TYA) & HasClear(State.D)) ~
(Elidable & HasOpcode(ADC) & HasImmediate(0)) ~
(Elidable & HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
val label = getNextLabel("ah")
List(
AssemblyLine.relative(BCC, label),
AssemblyLine.implied(INY),
AssemblyLine.label(label))
},
(Elidable & HasOpcode(TXA) & HasClear(State.D) & HasClear(State.C)) ~
(Elidable & HasOpcode(ADC) & HasImmediate(1)) ~
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
List(AssemblyLine.implied(INX))
},
(Elidable & HasOpcode(TYA) & HasClear(State.D) & HasClear(State.C)) ~
(Elidable & HasOpcode(ADC) & HasImmediate(1)) ~
(Elidable & HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.A, State.C, State.Z, State.N, State.V)) ~~> { code =>
List(AssemblyLine.implied(INY))
},
)
val IndexSequenceOptimization = new RuleBasedAssemblyOptimization("Index sequence optimization",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(Elidable & HasOpcode(LDY) & MatchImmediate(1) & MatchY(0)) ~
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0))) ~~> (_ => Nil),
(Elidable & HasOpcode(LDY) & MatchImmediate(1) & MatchY(0)) ~
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)+1)) ~~> (_ => List(AssemblyLine.implied(INY))),
(Elidable & HasOpcode(LDY) & MatchImmediate(1) & MatchY(0)) ~
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)-1)) ~~> (_ => List(AssemblyLine.implied(DEY))),
(Elidable & HasOpcode(LDX) & MatchImmediate(1) & MatchX(0)) ~
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0))) ~~> (_ => Nil),
(Elidable & HasOpcode(LDX) & MatchImmediate(1) & MatchX(0)) ~
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)+1)) ~~> (_ => List(AssemblyLine.implied(INX))),
(Elidable & HasOpcode(LDX) & MatchImmediate(1) & MatchX(0)) ~
Where(ctx => ctx.get[Constant](1).quickSimplify.isLowestByteAlwaysEqual(ctx.get[Int](0)-1)) ~~> (_ => List(AssemblyLine.implied(DEX))),
)
}