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 1–2 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))), ) }