diff --git a/src/main/scala/millfork/OptimizationPresets.scala b/src/main/scala/millfork/OptimizationPresets.scala index 892168ce..1a497d98 100644 --- a/src/main/scala/millfork/OptimizationPresets.scala +++ b/src/main/scala/millfork/OptimizationPresets.scala @@ -14,6 +14,7 @@ object OptimizationPresets { UnusedGlobalVariables, ) val AssOpt: List[AssemblyOptimization] = List[AssemblyOptimization]( + AlwaysGoodOptimizations.NonetAddition, AlwaysGoodOptimizations.PointlessSignCheck, AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad, AlwaysGoodOptimizations.PointlessLoadAfterLoadOrStore, @@ -31,6 +32,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.PointlessLoadBeforeTransfer, VariableToRegisterOptimization, AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad, + AlwaysGoodOptimizations.CommonIndexSubexpressionElimination, AlwaysGoodOptimizations.PointlessOperationPairRemoval, AlwaysGoodOptimizations.PointlessOperationPairRemoval2, AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad, @@ -125,6 +127,7 @@ object OptimizationPresets { DangerousOptimizations.ConstantIndexOffsetPropagation, AlwaysGoodOptimizations.CommonBranchBodyOptimization, AlwaysGoodOptimizations.CommonExpressionInConditional, + AlwaysGoodOptimizations.CommonIndexSubexpressionElimination, AlwaysGoodOptimizations.ConstantFlowAnalysis, AlwaysGoodOptimizations.ConstantIndexPropagation, AlwaysGoodOptimizations.DoubleJumpSimplification, @@ -137,6 +140,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.IndexSequenceOptimization, AlwaysGoodOptimizations.MathOperationOnTwoIdenticalMemoryOperands, AlwaysGoodOptimizations.ModificationOfJustWrittenValue, + AlwaysGoodOptimizations.NonetAddition, AlwaysGoodOptimizations.OperationsAroundShifting, AlwaysGoodOptimizations.PoinlessFlagChange, AlwaysGoodOptimizations.PointlessLoadAfterLoadOrStore, diff --git a/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala index 5a23cbf8..55c53235 100644 --- a/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala @@ -1220,4 +1220,93 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(BPL)) ~~> { code => code.init :+ code.last.copy(opcode = JMP, addrMode = Absolute) }, ) } + + val NonetAddition = new RuleBasedAssemblyOptimization("Nonet addition", + needsFlowInfo = FlowInfoRequirement.BothFlows, + (Elidable & HasOpcode(LDX) & HasImmediate(0) & HasClear(State.D)) ~ + (Elidable & HasOpcode(BCC) & MatchParameter(14)) ~ + (Elidable & HasOpcode(INX)) ~ + (Elidable & HasOpcode(LABEL) & MatchParameter(14) & HasCallerCount(1)) ~ + (Elidable & HasOpcode(CLC)) ~ + (Elidable & HasOpcode(ADC) & MatchAddrMode(0) & MatchParameter(1) & Not(ConcernsX)) ~ + (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & Not(ConcernsX)) ~ + (Elidable & HasOpcode(TXA)) ~ + (Elidable & HasOpcode(ADC) & MatchAddrMode(2) & MatchParameter(3) & Not(ConcernsX) & DoesNotConcernMemoryAt(0, 1)) ~ + (Elidable & HasOpcode(STA) & MatchAddrMode(2) & MatchParameter(3) & Not(ConcernsX) & DoesntMatterWhatItDoesWith(State.C, State.N, State.V, State.Z)) ~~> { (code, ctx) => + val label = getNextLabel("in") + List( + code(1), // BCC + code(8).copy(opcode = INC), + code(3), //LABEL + code(4), //CLC + code(5), //ADC + code(6), //STA + AssemblyLine.relative(BCC, label), + code(8).copy(opcode = INC), + AssemblyLine.label(label)) + } + ) + + val CommonIndexSubexpressionElimination: RuleBasedAssemblyOptimization = { + def eliminate(firstLda: Boolean, targetY: Boolean): AssemblyRule = { + val firstLoad = HasClear(State.D) & ( + if (firstLda) HasOpcodeIn(Set(LDA, STA, LAX)) & HasAddrModeIn(Set(Absolute, ZeroPage, Immediate)) & MatchParameter(1) + else if (targetY) HasOpcodeIn(Set(TXA, TAX)) + else HasOpcodeIn(Set(TAY, TYA)) + ) + val secondLoad = Elidable & ( + if (firstLda) HasOpcode(LDA) & HasAddrModeIn(Set(Absolute, ZeroPage, Immediate)) & MatchParameter(1) + else if (targetY) HasOpcode(TXA) + else HasOpcode(TYA) + ) + val firstTransfer = if (targetY) HasOpcode(TAY) else HasOpcode(TAX) + val secondTransfer = Elidable & ( + if (targetY) HasOpcode(TAY) + else HasOpcode(TAX) + ) & DoesntMatterWhatItDoesWith(State.A, State.Z, State.N, State.C) + val fillerLine = + HasAddrMode(Implied) & HasOpcodeIn(Set(ASL, CLC, CLD, SEC, SED, LSR, INC, DEC)) | + HasOpcodeIn(Set(ADC, ORA, EOR, AND, SBC)) & HasAddrModeIn(Set(Absolute, ZeroPage, Immediate)) + val firstFiller = fillerLine.* + val secondFiller = (Elidable & fillerLine).* + val betweenLines = (Linear & Not(secondLoad)).+ + (firstLoad ~ firstFiller ~ firstTransfer).capture(91) ~ betweenLines.capture(95) ~ (secondLoad ~ secondFiller ~ secondTransfer).capture(92) ~ Where(ctx => { + val first = ctx.get[List[AssemblyLine]](91) + val second = ctx.get[List[AssemblyLine]](92) + val between = ctx.get[List[AssemblyLine]](95) + var currentD = false + var currentCDefined = false + first.length == second.length && + first.head.parameter == second.head.parameter && + (first.head.addrMode == Immediate) == (second.head.addrMode == Immediate) && first.tail.zip(second.tail).forall(p => { + p._1.opcode == p._2.opcode && p._1.parameter.quickSimplify == p._2.parameter.quickSimplify && (p._1.addrMode == Immediate) == (p._2.addrMode == Immediate) + }) && (for (s1 <- first; s2 <- between) yield HelperCheckers.memoryAccessDoesntOverlap(s1.addrMode, s1.parameter, s2.addrMode, s2.parameter)).forall(identity) && { + var currentD = false + var currentCDefined = false + var noAdditionDependency = true + first.tail.map(_.opcode).foreach{ + case SED => currentD = true + case CLD => currentD = false + case CLC | SEC => currentCDefined = true + case ADC | SBC => noAdditionDependency = currentCDefined + case _ => () + } + noAdditionDependency && !currentD + } + + }) ~~> { (code, ctx) => + val first = ctx.get[List[AssemblyLine]](91) + val between = ctx.get[List[AssemblyLine]](95) + first ++ between + } + } + new RuleBasedAssemblyOptimization("Common index subexpression elimination", + needsFlowInfo = FlowInfoRequirement.BothFlows, + eliminate(firstLda = true, targetY = false), + eliminate(firstLda = false, targetY = false), + eliminate(firstLda = false, targetY = true), + eliminate(firstLda = true, targetY = true), + ) + } + } diff --git a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala index 1d87e50b..07ae5ad4 100644 --- a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala @@ -170,13 +170,14 @@ trait AssemblyPattern { def captureLength(i: Int) = CaptureLength(i, this) - protected def memoryAccessDoesntOverlap(a1: AddrMode.Value, p1: Constant, a2: AddrMode.Value, p2: Constant): Boolean = { - import AddrMode._ - val badAddrModes = Set(IndexedX, IndexedY, ZeroPageIndirect, AbsoluteIndexedX) +} +object HelperCheckers { + import AddrMode._ + private val badAddrModes = Set(IndexedX, IndexedY, ZeroPageIndirect, AbsoluteIndexedX) + private val goodAddrModes = Set(Implied, Immediate, Relative) + def memoryAccessDoesntOverlap(a1: AddrMode.Value, p1: Constant, a2: AddrMode.Value, p2: Constant): Boolean = { if (badAddrModes(a1) || badAddrModes(a2)) return false - val goodAddrModes = Set(Implied, Immediate, Relative) if (goodAddrModes(a1) || goodAddrModes(a2)) return true - def handleKnownDistance(distance: Short): Boolean = { val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY) val a1Indexing = indexingAddrModes(a1) @@ -412,7 +413,7 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = { val s1s = ctx.get[List[AssemblyLine]](ix1) val s2s = ctx.get[List[AssemblyLine]](ix2) - if (s1s.forall(s1 => s2s.forall(s2 => memoryAccessDoesntOverlap(s1.addrMode, s1.parameter, s2.addrMode, s2.parameter)))) Some(code) else None + if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1.addrMode, s1.parameter, s2.addrMode, s2.parameter)))) Some(code) else None } } @@ -639,7 +640,7 @@ case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLin val a1 = ctx.get[AddrMode.Value](addrMode1) val a2 = line.addrMode val changesSomeMemory = OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode) - !changesSomeMemory || memoryAccessDoesntOverlap(a1, p1, a2, p2) + !changesSomeMemory || HelperCheckers.memoryAccessDoesntOverlap(a1, p1, a2, p2) } } @@ -654,7 +655,7 @@ case class DoesNotConcernMemoryAt(addrMode1: Int, param1: Int) extends AssemblyL val p2 = line.parameter val a1 = ctx.get[AddrMode.Value](addrMode1) val a2 = line.addrMode - memoryAccessDoesntOverlap(a1, p1, a2, p2) + HelperCheckers.memoryAccessDoesntOverlap(a1, p1, a2, p2) } } diff --git a/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala b/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala index c1ee74f8..5c3cc899 100644 --- a/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala +++ b/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala @@ -302,4 +302,42 @@ class AssemblyOptimizationSuite extends FunSuite with Matchers { | """.stripMargin) } + + test("Adding a nonet") { + EmuBenchmarkRun( + """ + | word output @$C000 + | byte source @$C002 + | void main () { + | init() + | output += source <<<< 1 + | } + | void init() { + | output = 0 + | source = $FF + | } + | + """.stripMargin){m => + m.readWord(0xc000) should equal(0x1FE) + } + } + + test("Common indexing subexpression elimination") { + EmuBenchmarkRun( + """ + | array output [55] @$C000 + | array input = [0,1,2,3,4,5,6,7,8,9,10] + | void main () { + | byte indexer + | indexer = init() + | output[(indexer + 1) & 7] = input[(indexer + 1) & 7] + | } + | byte init() { + | return 2 + | } + | + """.stripMargin){m => + m.readWord(0xc003) should equal(3) + } + } }