From 7bbf655e6f2ba585cff8e8a1ac48e9e7685aaf2c Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Mon, 29 Jan 2018 12:08:21 +0100 Subject: [PATCH] Optimization bugfixes --- .../scala/millfork/OptimizationPresets.scala | 2 + .../opt/AlwaysGoodOptimizations.scala | 76 ++++++++++---- .../assembly/opt/CoarseFlowAnalyzer.scala | 21 ++++ .../assembly/opt/LaterOptimizations.scala | 4 +- .../assembly/opt/ReverseFlowAnalyzer.scala | 43 +++++--- .../opt/RuleBasedAssemblyOptimization.scala | 98 ++++++++++--------- src/test/scala/millfork/test/ShiftSuite.scala | 4 +- 7 files changed, 161 insertions(+), 87 deletions(-) diff --git a/src/main/scala/millfork/OptimizationPresets.scala b/src/main/scala/millfork/OptimizationPresets.scala index 91aa10cd..5582abca 100644 --- a/src/main/scala/millfork/OptimizationPresets.scala +++ b/src/main/scala/millfork/OptimizationPresets.scala @@ -103,6 +103,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.PointlessMathFromFlow, AlwaysGoodOptimizations.PointlessMathFromFlow, AlwaysGoodOptimizations.PointlessMathFromFlow, + AlwaysGoodOptimizations.SimplifiableCondition, AlwaysGoodOptimizations.IncrementingIndexRegistersAfterTransfer, AlwaysGoodOptimizations.MathOperationOnTwoIdenticalMemoryOperands, LaterOptimizations.UseZeropageAddressingMode, @@ -154,6 +155,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.RemoveNops, AlwaysGoodOptimizations.ReverseFlowAnalysis, AlwaysGoodOptimizations.SimplifiableBitOpsSequence, + AlwaysGoodOptimizations.SimplifiableCondition, AlwaysGoodOptimizations.SmarterShiftingWords, AlwaysGoodOptimizations.UnconditionalJumpRemoval, UnusedLabelRemoval, diff --git a/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala index 679cdb94..f9c3fe83 100644 --- a/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/opt/AlwaysGoodOptimizations.scala @@ -17,6 +17,8 @@ import millfork.env._ object AlwaysGoodOptimizations { val counter = new AtomicInteger(30000) + val LdxAddrModes = Set(Immediate, Absolute, ZeroPage, ZeroPageY, AbsoluteY) + val LdyAddrModes = Set(Immediate, Absolute, ZeroPage, ZeroPageX, AbsoluteX) def getNextLabel(prefix: String) = f".$prefix%s__${counter.getAndIncrement()}%05d" @@ -340,15 +342,22 @@ object AlwaysGoodOptimizations { 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 actualFlagsToTrashForStore = List(State.N, State.Z) ++ flagsToTrash ++ (store match{ + case STA => List(State.A) + case STY => List(State.Y) + case STX => List(State.X) + case SAX => List(State.A, State.X) + case STZ => Nil + }) + val actualFlagsToTrashForModify = List(State.N, State.Z) ++ flagsToTrash + val init = Elidable & HasOpcode(store) & HasAddrMode(addrMode) & MatchAddrMode(3) & MatchParameter(0) & DoesntMatterWhatItDoesWith(actualFlagsToTrashForStore: _*) & initExtra + val meantime = (Linear & DoesNotConcernMemoryAt(3, 0) & meantimeExtra).* + val oneModification = Elidable & HasOpcode(modify) & HasAddrMode(addrMode) & MatchParameter(0) & DoesntMatterWhatItDoesWith(actualFlagsToTrashForModify: _*) 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) => + (init ~ 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) @@ -356,8 +365,8 @@ object AlwaysGoodOptimizations { } } - val ModificationOfJustWrittenValue = new RuleBasedAssemblyOptimization("Modification of Just written value", - needsFlowInfo = FlowInfoRequirement.ForwardFlow, + val ModificationOfJustWrittenValue = new RuleBasedAssemblyOptimization("Modification of just written value", + needsFlowInfo = FlowInfoRequirement.BothFlows, modificationOfJustWrittenValue(STA, Absolute, MatchA(5), INC, Anything, atLeastTwo = false, Seq(), (c, i) => List( AssemblyLine.immediate(LDA, (c.get[Int](5) + i) & 0xff) )), @@ -572,10 +581,10 @@ object AlwaysGoodOptimizations { val PointlessLoadBeforeTransfer = new RuleBasedAssemblyOptimization("Pointless load before transfer", needsFlowInfo = FlowInfoRequirement.BackwardFlow, - loadBeforeTransfer(LDX, LDA, ConcernsX, State.X, TXA, Set(Immediate, ZeroPage, Absolute, IndexedY, AbsoluteY)), - loadBeforeTransfer(LDA, LDX, ConcernsA, State.A, TAX, Set(Immediate, ZeroPage, Absolute, IndexedY, AbsoluteY)), - loadBeforeTransfer(LDY, LDA, ConcernsY, State.Y, TYA, Set(Immediate, ZeroPage, Absolute, ZeroPageX, IndexedX, AbsoluteX)), - loadBeforeTransfer(LDA, LDY, ConcernsA, State.A, TAY, Set(Immediate, ZeroPage, Absolute, ZeroPageX, IndexedX, AbsoluteX)), + loadBeforeTransfer(LDX, LDA, ConcernsX, State.X, TXA, LdxAddrModes), + loadBeforeTransfer(LDA, LDX, ConcernsA, State.A, TAX, LdxAddrModes), + loadBeforeTransfer(LDY, LDA, ConcernsY, State.Y, TYA, LdyAddrModes), + loadBeforeTransfer(LDA, LDY, ConcernsA, State.A, TAY, LdyAddrModes), ) private def immediateLoadBeforeTwoTransfers(ld1: Opcode.Value, ld2: Opcode.Value, concerns1: AssemblyLinePattern, overwrites1: State.Value, t12: Opcode.Value, t21: Opcode.Value) = @@ -698,17 +707,21 @@ object AlwaysGoodOptimizations { 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)) ~ + (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) + (HasOpcode(LDA) & MatchParameter(0) & MatchAddrMode(1) & DoesNotConcernMemoryAt(5, 4)) ~ + (Elidable & HasOpcode(STA) & MatchParameter(2) & MatchAddrMode(3) & DoesntChangeMemoryAt(1, 0) & DoesntChangeMemoryAt(5, 4) & restriction) + } else { + (HasOpcode(LDA) & MatchParameter(0) & MatchAddrMode(1)) ~ + (Elidable & HasOpcode(STA) & MatchParameter(2) & MatchAddrMode(3) & DoesntChangeMemoryAt(1, 0) & restriction) ~ + (HasOpcode(ldax) & HasImmediate(0)) ~ + (Elidable & HasOpcode(stax) & MatchParameter(4) & MatchAddrMode(5) & DoesntChangeMemoryAt(1, 0) & DoesntChangeMemoryAt(3, 2)) } - val middle = (Linear & Not(ConcernsMemory) & DoesntChangeIndexingInAddrMode(3) & DoesntChangeIndexingInAddrMode(5)).* + val middle = (Linear + & DoesntChangeIndexingInAddrMode(3) + & DoesntChangeIndexingInAddrMode(5) + & DoesNotConcernMemoryAt(3, 2) + & DoesNotConcernMemoryAt(5, 4)).* 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)) @@ -1092,4 +1105,27 @@ object AlwaysGoodOptimizations { code.tail.init :+ code.head }, ) + + private def remapZ2N(line: AssemblyLine) = line.opcode match { + case BNE => line.copy(opcode = BMI) + case BEQ => line.copy(opcode = BPL) + } + private def remapZ2V(line: AssemblyLine) = line.opcode match { + case BNE => line.copy(opcode = BVS) + case BEQ => line.copy(opcode = BVC) + } + + val SimplifiableCondition = new RuleBasedAssemblyOptimization("Simplifiable condition", + needsFlowInfo = FlowInfoRequirement.BackwardFlow, + HasOpcode(LDA) ~ + (Elidable & HasOpcode(AND) & HasImmediate(0x80)) ~ + (Elidable & HasOpcodeIn(Set(BNE, BEQ)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> {code => + List(code(0), remapZ2N(code(2))) + }, + (Elidable & HasOpcode(LDA) & HasImmediate(0x80)) ~ + (Elidable & HasOpcode(AND)) ~ + (Elidable & HasOpcodeIn(Set(BNE, BEQ)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> {code => + List(code(1).copy(opcode = LDA), remapZ2N(code(2))) + }, + ) } diff --git a/src/main/scala/millfork/assembly/opt/CoarseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/opt/CoarseFlowAnalyzer.scala index 511e5295..12c924cf 100644 --- a/src/main/scala/millfork/assembly/opt/CoarseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/opt/CoarseFlowAnalyzer.scala @@ -46,6 +46,18 @@ object Status { SingleStatus(y >= 0x80) case _ => AnyStatus() } + + def adc(value: Int, carry: Status[Boolean], decimal: Status[Boolean]): Status[Int] = inner match { + case SingleStatus(x) => decimal match { + case SingleStatus(false) => carry match { + case SingleStatus(true) => SingleStatus(x + value + 1) + case SingleStatus(false) => SingleStatus(x + value) + case _ => AnyStatus() + } + case _ => AnyStatus() + } + case _ => AnyStatus() + } } } @@ -195,6 +207,10 @@ object CoarseFlowAnalyzer { val n = nn.toInt currentStatus = currentStatus.nz(n).copy(a = SingleStatus(n), x = SingleStatus(n)) + case AssemblyLine(ADC, Immediate, NumericConstant(nn, _), _) => + val n = nn.toInt + val newA = currentStatus.a.adc(n, currentStatus.c, currentStatus.d) + currentStatus = currentStatus.copy(n = newA.n(), z = newA.z(), a = newA, c = AnyStatus(), v = AnyStatus()) case AssemblyLine(EOR, Immediate, NumericConstant(nn, _), _) => val n = nn.toInt currentStatus = currentStatus.copy(n = currentStatus.a.n(_ ^ n), z = currentStatus.a.z(_ ^ n), a = currentStatus.a.map(_ ^ n)) @@ -236,6 +252,11 @@ object CoarseFlowAnalyzer { case AssemblyLine(TYA, _, _, _) => currentStatus = currentStatus.copy(a = currentStatus.y, n = currentStatus.y.n(), z = currentStatus.y.z()) + case AssemblyLine(ASL, Implied, _, _) => + currentStatus = currentStatus.copy(a = currentStatus.a.map(_ << 1), n = currentStatus.a.n(_ << 1), z = currentStatus.a.z(_ << 1),c = currentStatus.a.map(a => a.&(0xff).!=(0))) + case AssemblyLine(LSR, Implied, _, _) => + currentStatus = currentStatus.copy(a = currentStatus.a.map(a => a.>>(1).&(0x7f)), n = currentStatus.a.n(a => a.>>(1).&(0x7f)), z = currentStatus.a.z(a => a.>>(1).&(0x7f)),c = currentStatus.a.map(a => a.&(1).!=(0))) + case AssemblyLine(opcode, addrMode, parameter, _) => if (OpcodeClasses.ChangesX(opcode)) currentStatus = currentStatus.copy(x = AnyStatus()) if (OpcodeClasses.ChangesY(opcode)) currentStatus = currentStatus.copy(y = AnyStatus()) diff --git a/src/main/scala/millfork/assembly/opt/LaterOptimizations.scala b/src/main/scala/millfork/assembly/opt/LaterOptimizations.scala index 2e50fa36..ddccee15 100644 --- a/src/main/scala/millfork/assembly/opt/LaterOptimizations.scala +++ b/src/main/scala/millfork/assembly/opt/LaterOptimizations.scala @@ -244,7 +244,7 @@ object LaterOptimizations { // TODO: make it more generic val IndexSwitchingOptimization = new RuleBasedAssemblyOptimization("Index switching optimization", needsFlowInfo = FlowInfoRequirement.BackwardFlow, - (Elidable & HasOpcode(LDY) & MatchAddrMode(2) & Not(ReadsX) & MatchParameter(0)) ~ + (Elidable & HasOpcode(LDY) & MatchAddrMode(2) & Not(ReadsX) & MatchParameter(0) & HasAddrModeIn(LdxAddrModes)) ~ (Elidable & Linear & Not(ChangesY) & HasAddrMode(AbsoluteY) & SupportsAbsoluteX & Not(ConcernsX)) ~ (HasOpcode(LDY) & Not(ConcernsX)) ~ (Linear & Not(ChangesY) & Not(ConcernsX) & HasAddrModeIn(Set(AbsoluteY, IndexedY, ZeroPageY))) ~ @@ -257,7 +257,7 @@ object LaterOptimizations { code(3), code(5).copy(addrMode = AbsoluteX)) }, - (Elidable & HasOpcode(LDX) & MatchAddrMode(2) & Not(ReadsY) & MatchParameter(0)) ~ + (Elidable & HasOpcode(LDX) & MatchAddrMode(2) & Not(ReadsY) & MatchParameter(0) & HasAddrModeIn(LdyAddrModes)) ~ (Elidable & Linear & Not(ChangesX) & HasAddrMode(AbsoluteX) & SupportsAbsoluteY & Not(ConcernsY)) ~ (HasOpcode(LDX) & Not(ConcernsY)) ~ (Linear & Not(ChangesX) & Not(ConcernsY) & HasAddrModeIn(Set(AbsoluteX, IndexedX, ZeroPageX, AbsoluteIndexedX))) ~ diff --git a/src/main/scala/millfork/assembly/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/opt/ReverseFlowAnalyzer.scala index b92b71f0..280be179 100644 --- a/src/main/scala/millfork/assembly/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/opt/ReverseFlowAnalyzer.scala @@ -55,14 +55,15 @@ case class CpuImportance(a: Importance = UnknownImportance, ) def isUnimportant(state: State.Value): Boolean = state match { - case State.A => a == Unimportant - case State.X => x == Unimportant - case State.Y => y == Unimportant - case State.Z => z == Unimportant - case State.N => n == Unimportant - case State.C => c == Unimportant - case State.V => v == Unimportant - case State.D => d == Unimportant + // UnknownImportance is usually an effect of unreachable code + case State.A => a != Important + case State.X => x != Important + case State.Y => y != Important + case State.Z => z != Important + case State.N => n != Important + case State.C => c != Important + case State.V => v != Important + case State.D => d != Important } } @@ -97,8 +98,16 @@ object ReverseFlowAnalyzer { case _ => } codeArray(i) match { - case AssemblyLine(JSR, _, MemoryAddressConstant(fun:FunctionInMemory), _) => - var result = new CpuImportance().copy(d = Important) + case AssemblyLine(JSR | JMP, Absolute, MemoryAddressConstant(fun:FunctionInMemory), _) => + var result = new CpuImportance( + a = Unimportant, + x = Unimportant, + y = Unimportant, + z = Unimportant, + n = Unimportant, + c = Unimportant, + v = Unimportant, + d = Important) fun.params match { case AssemblyParamSignature(params) => params.foreach(_.variable match { @@ -119,16 +128,16 @@ object ReverseFlowAnalyzer { case _ => } currentImportance = result - case AssemblyLine(JSR, _, _, _) => + case AssemblyLine(JSR | BRK, _, _, _) => currentImportance = finalImportance - case AssemblyLine(JMP, Absolute, MemoryAddressConstant(Label(l)), _) => + case AssemblyLine(JMP | BRA, Absolute | Relative, MemoryAddressConstant(Label(l)), _) => val L = l val labelIndex = codeArray.indexWhere { case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(L)), _) => true case _ => false } currentImportance = if (labelIndex < 0) finalImportance else importanceArray(labelIndex) - case AssemblyLine(JMP, Indirect, _, _) => + case AssemblyLine(JMP, Indirect | AbsoluteIndexedX, _, _) => currentImportance = finalImportance case AssemblyLine(BNE | BEQ, _, _, _) => currentImportance = currentImportance.copy(z = Important) @@ -136,7 +145,7 @@ object ReverseFlowAnalyzer { currentImportance = currentImportance.copy(n = Important) case AssemblyLine(SED | CLD, _, _, _) => currentImportance = currentImportance.copy(d = Unimportant) - case AssemblyLine(RTS, _, _, _) => + case AssemblyLine(RTS | RTI, _, _, _) => currentImportance = finalImportance case AssemblyLine(DISCARD_XF, _, _, _) => currentImportance = currentImportance.copy(x = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant) @@ -158,8 +167,10 @@ object ReverseFlowAnalyzer { if (OpcodeClasses.ReadsYAlways(opcode)) currentImportance = currentImportance.copy(y = Important) if (OpcodeClasses.ReadsAAlways(opcode)) currentImportance = currentImportance.copy(a = Important) if (OpcodeClasses.ReadsAIfImplied(opcode) && addrMode == Implied) currentImportance = currentImportance.copy(a = Important) - if (addrMode == AbsoluteX || addrMode == IndexedX || addrMode == ZeroPageX) currentImportance = currentImportance.copy(x = Important) - if (addrMode == AbsoluteY || addrMode == IndexedY || addrMode == ZeroPageY) currentImportance = currentImportance.copy(y = Important) + if (addrMode == AbsoluteX || addrMode == IndexedX || addrMode == ZeroPageX || addrMode == AbsoluteIndexedX) + currentImportance = currentImportance.copy(x = Important) + if (addrMode == AbsoluteY || addrMode == IndexedY || addrMode == ZeroPageY) + currentImportance = currentImportance.copy(y = Important) } } } diff --git a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala index 09b64a6d..eae5a57b 100644 --- a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala @@ -72,6 +72,8 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf class AssemblyMatchingContext { private val map = mutable.Map[Int, Any]() + override def toString: String = map.mkString(", ") + def addObject(i: Int, o: Any): Boolean = { if (map.contains(i)) { map(i) == o @@ -81,6 +83,16 @@ class AssemblyMatchingContext { } } + def addAddrModeLoosely(i: Int, o: AddrMode.Value): Boolean = { + if (map.contains(i)) { + val a = map(i).asInstanceOf[AddrMode.Value] + a == o || a == AddrMode.ZeroPage && o == AddrMode.Absolute || a == AddrMode.Absolute && o == AddrMode.ZeroPage + } else { + map(i) = o + true + } + } + def dontMatch(i: Int, o: Any): Boolean = { if (map.contains(i)) { map(i) != o @@ -134,44 +146,6 @@ class AssemblyMatchingContext { jumps.isEmpty } - def areMemoryReferencesProvablyNonOverlapping(param1: Int, addrMode1: Int, param2: Int, addrMode2: Int): Boolean = { - val p1 = get[Constant](param1).quickSimplify - val a1 = get[AddrMode.Value](addrMode1) - val p2 = get[Constant](param2).quickSimplify - val a2 = get[AddrMode.Value](addrMode2) - import AddrMode._ - val badAddrModes = Set(IndexedX, IndexedY, ZeroPageIndirect, AbsoluteIndexedX) - if (badAddrModes(a1) || badAddrModes(a2)) return false - - def handleKnownDistance(distance: Short): Boolean = { - val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY) - val a1Indexing = indexingAddrModes(a1) - val a2Indexing = indexingAddrModes(a2) - (a1Indexing, a2Indexing) match { - case (false, false) => distance != 0 - case (true, false) => distance > 255 || distance < 0 - case (false, true) => distance > 0 || distance < -255 - case (true, true) => distance > 255 || distance < -255 - } - } - - (p1, p2) match { - case (NumericConstant(n1, _), NumericConstant(n2, _)) => - handleKnownDistance((n2 - n1).toShort) - case (a, CompoundConstant(MathOperator.Plus, b, NumericConstant(distance, _))) if a.quickSimplify == b.quickSimplify => - handleKnownDistance(distance.toShort) - case (CompoundConstant(MathOperator.Plus, a, NumericConstant(distance, _)), b) if a.quickSimplify == b.quickSimplify => - handleKnownDistance((-distance).toShort) - case (a, CompoundConstant(MathOperator.Minus, b, NumericConstant(distance, _))) if a.quickSimplify == b.quickSimplify => - handleKnownDistance((-distance).toShort) - case (CompoundConstant(MathOperator.Minus, a, NumericConstant(distance, _)), b) if a.quickSimplify == b.quickSimplify => - handleKnownDistance(distance.toShort) - case (MemoryAddressConstant(MemoryVariable(a, _, _)), MemoryAddressConstant(MemoryVariable(b, _, _))) => - a.takeWhile(_ != '.') != a.takeWhile(_ != '.') // TODO: ??? - case _ => - false - } - } } case class AssemblyRule(pattern: AssemblyPattern, result: (List[AssemblyLine], AssemblyMatchingContext) => List[AssemblyLine]) { @@ -236,15 +210,37 @@ trait AssemblyPattern { true // TODO: ??? case (MemoryAddressConstant(a: ThingInMemory), MemoryAddressConstant(b: ThingInMemory)) => a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? - case (CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(a: ThingInMemory), NumericConstant(_, _)), + case (CompoundConstant(op@(MathOperator.Plus | MathOperator.Minus), MemoryAddressConstant(a: ThingInMemory), NumericConstant(offset, _)), MemoryAddressConstant(b: ThingInMemory)) => - a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? + if (a.name == b.name) { + if (op == MathOperator.Plus) { + handleKnownDistance((-offset).toShort) + } else { + handleKnownDistance(offset.toShort) + } + } else { + a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? + } case (MemoryAddressConstant(a: ThingInMemory), - CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(b: ThingInMemory), NumericConstant(_, _))) => - a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? - case (CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(a: ThingInMemory), NumericConstant(_, _)), - CompoundConstant(MathOperator.Plus | MathOperator.Minus, MemoryAddressConstant(b: ThingInMemory), NumericConstant(_, _))) => - a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? + CompoundConstant(op@(MathOperator.Plus | MathOperator.Minus), MemoryAddressConstant(b: ThingInMemory), NumericConstant(offset, _))) => + if (a.name == b.name) { + if (op == MathOperator.Minus) { + handleKnownDistance((-offset).toShort) + } else { + handleKnownDistance(offset.toShort) + } + } else { + a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? + } + case (CompoundConstant(op1@(MathOperator.Plus | MathOperator.Minus), MemoryAddressConstant(a: ThingInMemory), NumericConstant(o1, _)), + CompoundConstant(op2@(MathOperator.Plus | MathOperator.Minus), MemoryAddressConstant(b: ThingInMemory), NumericConstant(o2, _))) => + if (a.name == b.name) { + val offset1 = if (op1==MathOperator.Plus) o1 else -o1 + val offset2 = if (op2==MathOperator.Plus) o2 else -o2 + handleKnownDistance((offset2 - offset1).toShort) + } else { + a.name.takeWhile(_ != '.') != b.name.takeWhile(_ != '.') // TODO: ??? + } case _ => false } @@ -550,6 +546,14 @@ case object Elidable extends AssemblyLinePattern { line.elidable } +case object DebugMatching extends AssemblyPattern { + override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = { + println(ctx) + code.foreach(println) + Some(code) + } +} + case object Linear extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = OpcodeClasses.AllLinear(line.opcode) @@ -641,7 +645,7 @@ case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLin case object ConcernsMemory extends TrivialAssemblyLinePattern { override def apply(line: AssemblyLine): Boolean = - ReadsMemory(line) && ChangesMemory(line) + ReadsMemory(line) || ChangesMemory(line) } case class DoesNotConcernMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLinePattern { @@ -715,7 +719,7 @@ case class DontMatchParameter(i: Int) extends AssemblyLinePattern { case class MatchAddrMode(i: Int) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = - ctx.addObject(i, line.addrMode) + ctx.addAddrModeLoosely(i, line.addrMode) override def toString: String = s"¬(?<$i>AddrMode)" } diff --git a/src/test/scala/millfork/test/ShiftSuite.scala b/src/test/scala/millfork/test/ShiftSuite.scala index 68ab499b..0c9857fa 100644 --- a/src/test/scala/millfork/test/ShiftSuite.scala +++ b/src/test/scala/millfork/test/ShiftSuite.scala @@ -1,5 +1,5 @@ package millfork.test -import millfork.test.emu.{EmuBenchmarkRun, EmuUnoptimizedRun} +import millfork.test.emu.{EmuBenchmarkRun, EmuUltraBenchmarkRun, EmuUnoptimizedRun} import org.scalatest.{FunSuite, Matchers} /** @@ -42,7 +42,7 @@ class ShiftSuite extends FunSuite with Matchers { } test("Long shifting left") { - EmuBenchmarkRun(""" + EmuUltraBenchmarkRun(""" | long output @$c000 | void main () { | output = $1010301