diff --git a/docs/abi/variable-storage.md b/docs/abi/variable-storage.md index f07c128d..2f9ea9d9 100644 --- a/docs/abi/variable-storage.md +++ b/docs/abi/variable-storage.md @@ -53,7 +53,7 @@ and ending when the function returns. Most automatic variables reside in memory. They can share their memory location with other automatic variables and parameters, to conserve memory usage. -Some small automatic variables may be inlined to index registers. +Some small automatic variables may be inlined to registers. They are not automatically initialized before reading, reading them before initialization yields an undefined value. Automatic local variables are not safe to use with reentrant functions, see the [relevant documentation](../lang/reentrancy.md) for more details. @@ -66,7 +66,7 @@ of the function call to the function they're defined in and ending when the function returns. They reside in memory and can share their memory location with other parameters and automatic variables, to conserve memory usage. -Unlike automatic variables, they are never inlined into index registers. +Unlike automatic variables, they are almost never inlined into registers. Parameters are not safe to use with reentrant functions, see the [relevant documentation](../lang/reentrancy.md) for more details. diff --git a/docs/api/command-line.md b/docs/api/command-line.md index 57be176f..88823c38 100644 --- a/docs/api/command-line.md +++ b/docs/api/command-line.md @@ -89,13 +89,19 @@ This may cause problems if the parameter table is stored next to a hardware regi * `--inline` – Inline functions automatically (experimental). See the [documentation about inlining](../abi/inlining.md). Computationally easy, can give decent gains. +* `--fipo`, `--fno-ipo` – Whether should perform interprocedural optimization. +It enables certain optimization similar to what inlining would enable, but without actual inlining. + * `--size` – Optimize for size, sacrificing some speed (experimental). * `--fast` – Optimize for speed, even if it increases the size a bit (experimental). * `--blast-processing` – Optimize for speed, even if it increases the size a lot (experimental). +Enables `--inline` automatically. -* `--dangerous-optimizations` – Use dangerous optimizations (experimental). Dangerous optimizations are more likely to result in broken code. +* `--dangerous-optimizations` – Use dangerous optimizations (experimental). +Dangerous optimizations are more likely to result in broken code. +Enables `--fipo` automatically. ## Warning options diff --git a/docs/api/getting-started.md b/docs/api/getting-started.md index 55a2ff0e..225d6979 100644 --- a/docs/api/getting-started.md +++ b/docs/api/getting-started.md @@ -45,6 +45,8 @@ You may be also interested in the following: * `--inline` – automatically inline functions for better optimization +* `--fipo` – enable interprocedural optimization + * `-s` – additionally generate assembly output * `-g` – additionally generate a label file, in format compatible with VICE emulator diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index a6c7b87f..5df39892 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -206,7 +206,7 @@ object CompilationFlag extends Enumeration { // compilation options for Z80 EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, UseIxForStack, // optimization options: - DangerousOptimizations, InlineFunctions, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed, + DangerousOptimizations, InlineFunctions, InterproceduralOptimization, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed, // memory allocation options VariableOverlap, CompactReturnDispatchParams, LUnixRelocatableCode, // runtime check options @@ -232,6 +232,7 @@ object CompilationFlag extends Enumeration { "emit_x80" -> EmitExtended80Opcodes, "emit_sharp" -> EmitSharpOpcodes, "ix_stack" -> UseIxForStack, + "ipo" -> InterproceduralOptimization, "zeropage_register" -> ZeropagePseudoregister, "decimal_mode" -> DecimalMode, "ro_arrays" -> ReadOnlyArrays, diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 3e11f188..a421734b 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -302,6 +302,12 @@ object Main { flag("--inline").action { c => c.changeFlag(CompilationFlag.InlineFunctions, true) }.description("Inline functions automatically.") + flag("--ipo").action { c => + c.changeFlag(CompilationFlag.InterproceduralOptimization, true) + }.description("Interprocedural optimization.").hidden() + boolean("--fipo", "--fno-ipo").action { (c, v) => + c.changeFlag(CompilationFlag.InterproceduralOptimization, v) + }.description("Interprocedural optimization.") flag("-Os", "--size").action { c => c.changeFlag(CompilationFlag.OptimizeForSize, true). changeFlag(CompilationFlag.OptimizeForSpeed, false). diff --git a/src/main/scala/millfork/assembly/AssemblyOptimization.scala b/src/main/scala/millfork/assembly/AssemblyOptimization.scala index b736228a..a70fe518 100644 --- a/src/main/scala/millfork/assembly/AssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/AssemblyOptimization.scala @@ -2,14 +2,19 @@ package millfork.assembly import millfork.CompilationOptions import millfork.env.NormalFunction +import millfork.node.NiceFunctionProperty /** * @author Karol Stasiak */ +case class OptimizationContext(options: CompilationOptions, + labelMap: Map[String, Int], + niceFunctionProperties: Set[(NiceFunctionProperty, String)]) + trait AssemblyOptimization[T <: AbstractCode] { def name: String - def optimize(f: NormalFunction, code: List[T], options: CompilationOptions, labelMap: Map[String, Int]): List[T] = optimize(f, code, options) + def optimize(f: NormalFunction, code: List[T], context: OptimizationContext): List[T] = optimize(f, code, context.options) - def optimize(f: NormalFunction, code: List[T], options: CompilationOptions): List[T] = optimize(f, code, options, Map()) + def optimize(f: NormalFunction, code: List[T], options: CompilationOptions): List[T] = optimize(f, code, OptimizationContext(options, Map(), Set())) } diff --git a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala index fba17663..df395c55 100644 --- a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala @@ -7,7 +7,6 @@ import millfork.assembly.mos.OpcodeClasses._ import millfork.assembly.mos.{AddrMode, opt, _} import millfork.assembly.mos.AddrMode._ import millfork.env._ -import org.apache.commons.lang3.builder.HashCodeExclude /** * These optimizations should not remove opportunities for more complex optimizations to trigger. @@ -152,7 +151,7 @@ object AlwaysGoodOptimizations { 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 }, - (Elidable & HasA(0) & HasOpcodeIn(Set(ORA, EOR))) ~~> (code => code.map(_.copy(opcode = LDA))), + (Elidable & HasA(0) & HasOpcodeIn(ORA, EOR)) ~~> (code => code.map(_.copy(opcode = LDA))), (Elidable & HasA(0) & HasOpcode(ADC) & HasClear(State.D) & HasClear(State.C) & // C stays cleared! @@ -171,42 +170,42 @@ object AlwaysGoodOptimizations { 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)) ~ + (HasOpcodeIn(STA, LDA, LAX) & HasAddrModeIn(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)), + (HasClear(State.D) & HasClear(State.C) & HasOpcode(ADC) & HasAddrModeIn(ZeroPage, Absolute) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.implied(ASL)), - (HasOpcodeIn(Set(STA, LDA)) & HasAddrMode(AbsoluteX) & MatchAddrMode(9) & MatchParameter(0)) ~ + (HasOpcodeIn(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)) ~ + (HasOpcodeIn(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)) ~ + (HasOpcodeIn(STA, LDA, LAX) & HasAddrModeIn(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), + (DoesntMatterWhatItDoesWith(State.N, State.Z) & HasOpcodeIn(ORA, AND) & HasAddrModeIn(ZeroPage, Absolute) & MatchParameter(0) & Elidable) ~~> (code => code.init), - (HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~ + (HasOpcodeIn(STA, LDA, LAX) & HasAddrModeIn(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), + (DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & HasOpcode(ANC) & HasAddrModeIn(ZeroPage, Absolute) & MatchParameter(0) & Elidable) ~~> (code => code.init), - (HasOpcodeIn(Set(STA, LDA, LAX)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(9) & MatchParameter(0)) ~ + (HasOpcodeIn(STA, LDA, LAX) & HasAddrModeIn(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)), + (HasOpcode(EOR) & HasAddrModeIn(ZeroPage, Absolute) & MatchParameter(0) & Elidable) ~~> (code => code.init :+ AssemblyLine.immediate(LDA, 0)), ) 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)) ~ + (Elidable & HasAddrModeIn(Absolute, ZeroPage) & MatchParameter(1) & MatchAddrMode(2) & HasOpcodeIn(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)) ~ + (MatchParameter(1) & MatchAddrMode(2) & HasOpcodeIn(STA, SAX, STX, STY, STZ)) ~~> (_.tail), + (Elidable & HasAddrModeIn(AbsoluteX, ZeroPageX) & MatchParameter(1) & MatchAddrMode(2) & HasOpcodeIn(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)) ~ + (MatchParameter(1) & MatchAddrMode(2) & HasOpcodeIn(STA, STY, STZ)) ~~> (_.tail), + (Elidable & HasAddrModeIn(AbsoluteY, ZeroPageY) & MatchParameter(1) & MatchAddrMode(2) & HasOpcodeIn(STA, SAX, STX, STZ)) ~ (LinearOrLabel & DoesntChangeMemoryAt(2, 1) & Not(ReadsMemory) & Not(ChangesY)).* ~ - (MatchParameter(1) & MatchAddrMode(2) & Set(STA, SAX, STX, STZ)) ~~> (_.tail), + (MatchParameter(1) & MatchAddrMode(2) & HasOpcodeIn(STA, SAX, STX, STZ)) ~~> (_.tail), ) private def pointlessNonimmediateStoreToTheSameVariable(ld1: Set[Opcode.Value], st1: Opcode.Value, ld2: Opcode.Value, st2: Opcode.Value) = { @@ -222,13 +221,13 @@ object AlwaysGoodOptimizations { (HasOpcode(st1) & MatchAddrMode(2) & MatchParameter(3) & match1) ~ (Linear & DoesntChangeIndexingInAddrMode(2) & ( DoesntChangeMemoryAt(2, 3) | - HasAddrModeIn(Set(IndexedX, IndexedY, IndexedZ, LongIndexedY, LongIndexedZ, Indirect)) & + HasAddrModeIn(IndexedX, IndexedY, IndexedZ, LongIndexedY, LongIndexedZ, Indirect) & MatchParameter(3) & HasParameterWhere({ case MemoryAddressConstant(th) => th.name.startsWith("__") case _ => false }) )).* ~ - (Elidable & match2 & HasOpcode(st2) & MatchAddrMode(2) & MatchParameter(3)) ~~> (code => code.init), + (Elidable & match2 & HasOpcode(st2) & MatchAddrMode(2) & MatchParameter(3)) ~~> (code => code.init) } val PointlessStoreToTheSameVariable = new RuleBasedAssemblyOptimization("Pointless store to the same variable", @@ -280,7 +279,7 @@ object AlwaysGoodOptimizations { (HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & Linear & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0)).*.capture(5) ~ (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Elidable & HasOpcode(STA) & HasAddrModeIn(Set(ZeroPage, Absolute, LongAbsolute)) & DoesNotConcernMemoryAt(0, 1)).capture(4) ~ + (Elidable & HasOpcode(STA) & HasAddrModeIn(ZeroPage, Absolute, LongAbsolute) & DoesNotConcernMemoryAt(0, 1)).capture(4) ~ Where(ctx => { val sta = ctx.get[List[AssemblyLine]](4).head val middleCode = ctx.get[List[AssemblyLine]](5) @@ -290,7 +289,7 @@ object AlwaysGoodOptimizations { }, (HasOpcode(TAX)) ~ (Elidable & Linear & Not(ChangesX) & Not(HasOpcode(STX))).*.capture(5) ~ - (Elidable & HasOpcode(STX) & HasAddrModeIn(Set(ZeroPage, Absolute, LongAbsolute))).capture(4) ~ + (Elidable & HasOpcode(STX) & HasAddrModeIn(ZeroPage, Absolute, LongAbsolute)).capture(4) ~ Where(ctx => { val sta = ctx.get[List[AssemblyLine]](4).head val middleCode = ctx.get[List[AssemblyLine]](5) @@ -300,7 +299,7 @@ object AlwaysGoodOptimizations { }, (HasOpcode(TAY)) ~ (Elidable & Linear & Not(ChangesY) & Not(HasOpcode(STY))).*.capture(5) ~ - (Elidable & HasOpcode(STY) & HasAddrModeIn(Set(ZeroPage, Absolute, LongAbsolute))).capture(4) ~ + (Elidable & HasOpcode(STY) & HasAddrModeIn(ZeroPage, Absolute, LongAbsolute)).capture(4) ~ Where(ctx => { val sta = ctx.get[List[AssemblyLine]](4).head val middleCode = ctx.get[List[AssemblyLine]](5) @@ -310,7 +309,7 @@ object AlwaysGoodOptimizations { }, (HasOpcode(TAZ)) ~ (Elidable & Linear & Not(ChangesIZ) & Not(HasOpcode(STZ))).*.capture(5) ~ - (Elidable & HasOpcode(STZ) & HasAddrModeIn(Set(ZeroPage, Absolute, LongAbsolute))).capture(4) ~ + (Elidable & HasOpcode(STZ) & HasAddrModeIn(ZeroPage, Absolute, LongAbsolute)).capture(4) ~ Where(ctx => { val sta = ctx.get[List[AssemblyLine]](4).head val middleCode = ctx.get[List[AssemblyLine]](5) @@ -373,9 +372,9 @@ object AlwaysGoodOptimizations { 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), + (HasOpcodeIn(LDA, TXA, TYA, EOR, AND, ORA, ANC) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_AF))).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail), + (HasOpcodeIn(LDX, TAX, TSX, INX, DEX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_XF))).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail), + (HasOpcodeIn(LDY, TAY, INY, DEY) & Elidable) ~ (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_YF))).* ~ HasOpcode(DISCARD_YF) ~~> (_.tail), (HasOpcode(LDX) & Elidable & MatchAddrMode(3) & MatchParameter(4)) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & DoesntChangeMemoryAt(3, 4) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~ (HasOpcode(TXA) & Elidable) ~ @@ -395,17 +394,17 @@ object AlwaysGoodOptimizations { val InefficientStashingToRegister = new RuleBasedAssemblyOptimization("Inefficient stashing to register", needsFlowInfo = FlowInfoRequirement.BackwardFlow, - (Elidable & HasOpcodeIn(Set(LDA, STA)) & IsZeroPage & MatchAddrMode(0) & MatchParameter(1)) ~ + (Elidable & HasOpcodeIn(LDA, STA) & IsZeroPage & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ (Linear & Not(ConcernsX) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.X)) ~~> (code => code.head :: (code.drop(2).init :+ code.head.copy(opcode = LDA))), - (Elidable & HasOpcodeIn(Set(LDA, STA)) & IsZeroPage & MatchAddrMode(0) & MatchParameter(1)) ~ + (Elidable & HasOpcodeIn(LDA, STA) & IsZeroPage & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ (Linear & Not(ConcernsY) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(TYA) & DoesntMatterWhatItDoesWith(State.Y)) ~~> (code => code.head :: (code.drop(2).init :+ code.head.copy(opcode = LDA))), - (Elidable & HasOpcodeIn(Set(LDA, STA)) & IsZeroPage & MatchAddrMode(0) & MatchParameter(1)) ~ + (Elidable & HasOpcodeIn(LDA, STA) & IsZeroPage & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(TAZ) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ (Linear & Not(ConcernsIZ) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(TZA) & DoesntMatterWhatItDoesWith(State.IZ)) ~~> (code => code.head :: (code.drop(2).init :+ code.head.copy(opcode = LDA))), @@ -451,10 +450,10 @@ object AlwaysGoodOptimizations { } private def operationPairBuilder4(op1: Opcode.Value, op1extra: AssemblyLinePattern, middle: AssemblyLinePattern, op2: Opcode.Value, op2extra: AssemblyLinePattern) = { - (HasOpcode(op1) & op1extra & Elidable & HasAddrModeIn(Set(Absolute, ZeroPage, LongAbsolute)) & MatchParameter(3)) ~ + (HasOpcode(op1) & op1extra & Elidable & HasAddrModeIn(Absolute, ZeroPage, LongAbsolute) & MatchParameter(3)) ~ middle.*.capture(1) ~ Where(_.isExternallyLinearBlock(1)) ~ - (HasOpcode(op2) & op2extra & Elidable & HasAddrModeIn(Set(Absolute, ZeroPage, LongAbsolute)) & MatchParameter(3)) ~~> { (_, ctx) => + (HasOpcode(op2) & op2extra & Elidable & HasAddrModeIn(Absolute, ZeroPage, LongAbsolute) & MatchParameter(3)) ~~> { (_, ctx) => ctx.get[List[AssemblyLine]](1) } } @@ -493,8 +492,8 @@ object AlwaysGoodOptimizations { operationPairBuilder3(PHX, Anything, PLX, Not(ChangesX) & Not(ConcernsStack), Some(DISCARD_XF)), operationPairBuilder3(PHY, Anything, PLY, Not(ChangesY) & Not(ConcernsStack), Some(DISCARD_YF)), operationPairBuilder3(PHZ, Anything, PLZ, Not(ChangesIZ) & Not(ConcernsStack), Some(DISCARD_YF)), - operationPairBuilder3(PHD, Anything, PLD, Not(ChangesDirectPageRegister), None), - operationPairBuilder3(PHB, Anything, PLB, Not(ChangesDataBankRegister), None), + operationPairBuilder3(PHD, Anything, PLD, Not(HasOpcodeIn(ChangesDirectPageRegister)), None), + operationPairBuilder3(PHB, Anything, PLB, Not(HasOpcodeIn(ChangesDataBankRegister)), None), operationPairBuilder3(INX, DoesntMatterWhatItDoesWith(State.N, State.Z), DEX, Not(ConcernsX) & Not(ReadsNOrZ), None), operationPairBuilder3(DEX, DoesntMatterWhatItDoesWith(State.N, State.Z), INX, Not(ConcernsX) & Not(ReadsNOrZ), None), operationPairBuilder3(INY, DoesntMatterWhatItDoesWith(State.N, State.Z), DEY, Not(ConcernsX) & Not(ReadsNOrZ), None), @@ -529,19 +528,19 @@ object AlwaysGoodOptimizations { needsFlowInfo = FlowInfoRequirement.NoRequirement, (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate)) ~ (Elidable & HasOpcode(PHA)) ~ - (Linear & Not(ConcernsStack) | HasOpcodeIn(Set(JSR, BSR))).* ~ + (Linear & Not(ConcernsStack) | HasOpcodeIn(JSR, BSR)).* ~ (Elidable & HasOpcode(PLA)) ~~> { code => code.head :: (code.drop(2).init :+ code.head) }, (Elidable & HasOpcode(LDX) & HasAddrMode(Immediate)) ~ (Elidable & HasOpcode(PHX)) ~ - (Linear & Not(ConcernsStack) | HasOpcodeIn(Set(JSR, BSR))).* ~ + (Linear & Not(ConcernsStack) | HasOpcodeIn(JSR, BSR)).* ~ (Elidable & HasOpcode(PLX)) ~~> { code => code.head :: (code.drop(2).init :+ code.head) }, (Elidable & HasOpcode(LDY) & HasAddrMode(Immediate)) ~ (Elidable & HasOpcode(PHY)) ~ - (Linear & Not(ConcernsStack) | HasOpcodeIn(Set(JSR, BSR))).* ~ + (Linear & Not(ConcernsStack) | HasOpcodeIn(JSR, BSR)).* ~ (Elidable & HasOpcode(PLY)) ~~> { code => code.head :: (code.drop(2).init :+ code.head) }, @@ -598,10 +597,10 @@ object AlwaysGoodOptimizations { val BranchInPlaceRemoval = new RuleBasedAssemblyOptimization("Branch in place", needsFlowInfo = FlowInfoRequirement.NoRequirement, - (AllDirectJumps & HasAddrModeIn(Set(Absolute, Relative, LongAbsolute, LongRelative)) & MatchParameter(0) & Elidable) ~ - HasOpcodeIn(NoopDiscardsFlags).* ~ + (HasOpcodeIn(AllDirectJumps) & HasAddrModeIn(Absolute, Relative, LongAbsolute, LongRelative) & MatchParameter(0) & Elidable) ~ + NoopDiscardsFlags.* ~ (HasOpcode(LABEL) & MatchParameter(0)) ~~> (c => c.last :: Nil), - (AllDirectJumps & HasAddrModeIn(Set(Absolute, Relative, LongAbsolute, LongRelative)) & MatchParameter(0) & Elidable) ~ + (HasOpcodeIn(AllDirectJumps) & HasAddrModeIn(Absolute, Relative, LongAbsolute, LongRelative) & MatchParameter(0) & Elidable) ~ (HasOpcode(LABEL) & Not(MatchParameter(0))).* ~ (HasOpcode(LABEL) & MatchParameter(0)) ~~> (_.tail), (HasOpcode(BEQ) & MatchParameter(0) & Elidable) ~ HasOpcode(BNE) ~ @@ -653,13 +652,13 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(JMP) & HasAddrMode(Absolute) & MatchParameter(0)) ~ (Not(HasOpcode(LABEL)) & Not(MatchParameter(0))).* ~ (HasOpcode(LABEL) & MatchParameter(0)) ~ - (HasOpcode(LABEL) | HasOpcodeIn(NoopDiscardsFlags)).* ~ + (HasOpcode(LABEL) | NoopDiscardsFlags).* ~ HasOpcode(RTS) ~~> (code => AssemblyLine.implied(RTS) :: code.tail), - (Elidable & HasOpcodeIn(ShortBranching) & MatchParameter(0)) ~ - (HasOpcodeIn(NoopDiscardsFlags).* ~ + (Elidable & ShortBranching & MatchParameter(0)) ~ + (NoopDiscardsFlags.* ~ (Elidable & HasOpcode(RTS))).capture(1) ~ (HasOpcode(LABEL) & MatchParameter(0)) ~ - HasOpcodeIn(NoopDiscardsFlags).* ~ + NoopDiscardsFlags.* ~ (Elidable & HasOpcode(RTS)) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](1)), ) @@ -693,10 +692,10 @@ object AlwaysGoodOptimizations { val TailCallOptimization = new RuleBasedAssemblyOptimization("Tail call optimization", needsFlowInfo = FlowInfoRequirement.NoRequirement, - (Elidable & HasOpcode(JSR)) ~ HasOpcodeIn(NoopDiscardsFlags).* ~ (Elidable & HasOpcode(RTS)) ~~> (c => c.head.copy(opcode = JMP) :: Nil), + (Elidable & HasOpcode(JSR)) ~ NoopDiscardsFlags.* ~ (Elidable & HasOpcode(RTS)) ~~> (c => c.head.copy(opcode = JMP) :: Nil), (Elidable & HasOpcode(JSR)) ~ HasOpcode(LABEL).* ~ - HasOpcodeIn(NoopDiscardsFlags).*.capture(0) ~ + NoopDiscardsFlags.*.capture(0) ~ HasOpcode(RTS) ~~> ((code, ctx) => ctx.get[List[AssemblyLine]](0) ++ (code.head.copy(opcode = JMP) :: code.tail)), ) @@ -707,10 +706,10 @@ object AlwaysGoodOptimizations { val PoinlessFlagChange = new RuleBasedAssemblyOptimization("Pointless flag change", needsFlowInfo = FlowInfoRequirement.NoRequirement, - (HasOpcodeIn(Set(CMP, CPX, CPY)) & Elidable) ~ NoopDiscardsFlags ~~> (_.tail), - (OverwritesC & Elidable & Not(ChangesStack)) ~ (LinearOrLabel & Not(ReadsC) & Not(DiscardsC)).* ~ DiscardsC ~~> (_.tail), - (OverwritesD & Elidable & Not(ChangesStack)) ~ (LinearOrLabel & Not(ReadsD) & Not(DiscardsD)).* ~ DiscardsD ~~> (_.tail), - (OverwritesV & Elidable & Not(ChangesStack)) ~ (LinearOrLabel & Not(ReadsV) & Not(DiscardsV)).* ~ DiscardsV ~~> (_.tail) + (HasOpcodeIn(CMP, CPX, CPY) & Elidable) ~ NoopDiscardsFlags ~~> (_.tail), + (HasOpcodeIn(OverwritesC) & Elidable & Not(ChangesStack)) ~ (LinearOrLabel & Not(ReadsC) & Not(HasOpcodeIn(DiscardsC))).* ~ HasOpcodeIn(DiscardsC) ~~> (_.tail), + (HasOpcodeIn(OverwritesD) & Elidable & Not(ChangesStack)) ~ (LinearOrLabel & Not(ReadsD) & Not(HasOpcodeIn(DiscardsD))).* ~ HasOpcodeIn(DiscardsD) ~~> (_.tail), + (HasOpcodeIn(OverwritesV) & Elidable & Not(ChangesStack)) ~ (LinearOrLabel & Not(ReadsV) & Not(HasOpcodeIn(DiscardsV))).* ~ HasOpcodeIn(DiscardsV) ~~> (_.tail) ) // Optimizing Bxx to JMP is generally bad, but it may allow for better optimizations later @@ -733,18 +732,18 @@ object AlwaysGoodOptimizations { 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 & HasOpcodeIn(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 & HasOpcodeIn(TAX, TSX, LDX, INX, DEX) & DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(TAY, LDY, DEY, INY) & DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(LAX) & DoesntMatterWhatItDoesWith(State.A, State.X, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(SEC, CLC) & DoesntMatterWhatItDoesWith(State.C)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(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), + (Elidable & HasOpcodeIn(CMP, CPX, CPY) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(BIT) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z, State.V)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(ASL, LSR, ROL, ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.C, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(ADC, SBC) & DoesntMatterWhatItDoesWith(State.A, State.C, State.V, State.N, State.Z)) ~~> (_ => Nil), ) private def modificationOfJustWrittenValue(store: Opcode.Value, @@ -892,14 +891,14 @@ object AlwaysGoodOptimizations { 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) :: + HasOpcode(RTS) ~ NoopDiscardsFlags.* ~ (HasOpcode(RTS) ~ Elidable) ~~> (_.take(1)) :: + HasOpcode(RTI) ~ NoopDiscardsFlags.* ~ (HasOpcode(RTI) ~ Elidable) ~~> (_.take(1)) :: + HasOpcode(DISCARD_XF) ~ (Not(HasOpcode(DISCARD_XF)) & NoopDiscardsFlagsOrLabel).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail) :: + HasOpcode(DISCARD_AF) ~ (Not(HasOpcode(DISCARD_AF)) & NoopDiscardsFlagsOrLabel).* ~ HasOpcode(DISCARD_AF) ~~> (_.tail) :: + HasOpcode(DISCARD_YF) ~ (Not(HasOpcode(DISCARD_YF)) & NoopDiscardsFlagsOrLabel).* ~ 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) & Elidable) ~ (NoopDiscardsFlags | HasOpcode(LABEL)).* ~ HasOpcode(opcode) ~~> (_.tail), HasOpcode(opcode) ~ (HasOpcode(opcode) ~ Elidable) ~~> (_.init), ) }: _* @@ -907,30 +906,30 @@ object AlwaysGoodOptimizations { 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), - HasOpcodeIn(Set(TXA, TAX, LAX, LXA)) ~ + HasOpcode(TYA) ~ (Elidable & HasOpcodeIn(TYA, TAY)) ~~> (_.init), + HasOpcode(TXA) ~ (Elidable & HasOpcodeIn(TXA, TAX)) ~~> (_.init), + HasOpcode(TAY) ~ (Elidable & HasOpcodeIn(TYA, TAY)) ~~> (_.init), + HasOpcode(TAX) ~ (Elidable & HasOpcodeIn(TXA, TAX)) ~~> (_.init), + HasOpcode(TSX) ~ (Elidable & HasOpcodeIn(TXS, TSX)) ~~> (_.init), + HasOpcode(TXS) ~ (Elidable & HasOpcodeIn(TXS, TSX)) ~~> (_.init), + HasOpcodeIn(TXA, TAX, LAX, LXA) ~ (Linear & Not(ChangesNAndZ) & Not(ChangesA) & Not(ChangesX)).* ~ - (Elidable & HasOpcodeIn(Set(TXA, TAX))) ~~> (_.init), - HasOpcodeIn(Set(TYA, TAY)) ~ + (Elidable & HasOpcodeIn(TXA, TAX)) ~~> (_.init), + HasOpcodeIn(TYA, TAY) ~ (Linear & Not(ChangesNAndZ) & Not(ChangesA) & Not(ChangesX)).* ~ - (Elidable & HasOpcodeIn(Set(TYA, TAY))) ~~> (_.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), + (Elidable & HasOpcodeIn(TYA, TAY)) ~~> (_.init), + HasOpcode(TSX) ~ (Not(ChangesX) & Not(ChangesS) & Linear).* ~ (Elidable & HasOpcodeIn(TXS, TSX)) ~~> (_.init), + HasOpcode(TXS) ~ (Not(ChangesX) & Not(ChangesS) & Linear).* ~ (Elidable & HasOpcodeIn(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(STA) & HasAddrModeIn(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)), + (Elidable & HasOpcode(STA) & HasAddrModeIn(ZeroPage, ZeroPageX, Absolute) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (code => code.tail.init :+ code.last.copy(opcode = STY)), ) @@ -939,41 +938,41 @@ object AlwaysGoodOptimizations { (HasOpcode(TAX) & Elidable) ~ HasOpcode(LABEL).* ~ HasOpcode(TXA).? ~ - ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(DISCARD_XF)).capture(1) ~ + ManyWhereAtLeastOne(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) ~ + ManyWhereAtLeastOne(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) ~ + ManyWhereAtLeastOne(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) ~ + ManyWhereAtLeastOne(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) ~ + ManyWhereAtLeastOne(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 and loads before compare", needsFlowInfo = FlowInfoRequirement.BackwardFlow, - HasOpcodeIn(Set(DEX, INX, LDX, LAX)) ~ + HasOpcodeIn(DEX, INX, LDX, LAX) ~ (HasOpcode(TXA) & Elidable & DoesntMatterWhatItDoesWith(State.A)) ~~> (code => code.init), - HasOpcodeIn(Set(DEY, INY, LDY)) ~ + HasOpcodeIn(DEY, INY, LDY) ~ (HasOpcode(TYA) & Elidable & DoesntMatterWhatItDoesWith(State.A)) ~~> (code => code.init), - (HasOpcodeIn(Set(DEC, INC, ASL, ROL, ROR, LSR, SLO, SRE, RLA, RRA, ISC, DCP)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(DEC, INC, ASL, ROL, ROR, LSR, SLO, SRE, RLA, RRA, ISC, DCP) & MatchAddrMode(0) & MatchParameter(1)) ~ (HasOpcode(LDA) & Elidable & DoesntMatterWhatItDoesWith(State.A) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init), - (HasOpcodeIn(Set(DEC, INC, ASL, ROL, ROR, LSR, SLO, SRE, RLA, RRA, ISC, DCP)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(DEC, INC, ASL, ROL, ROR, LSR, SLO, SRE, RLA, RRA, ISC, DCP) & MatchAddrMode(0) & MatchParameter(1)) ~ (HasOpcode(LDX) & Elidable & DoesntMatterWhatItDoesWith(State.X) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init), - (HasOpcodeIn(Set(DEC, INC, ASL, ROL, ROR, LSR, SLO, SRE, RLA, RRA, ISC, DCP)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(DEC, INC, ASL, ROL, ROR, LSR, SLO, SRE, RLA, RRA, ISC, DCP) & MatchAddrMode(0) & MatchParameter(1)) ~ (HasOpcode(LDY) & Elidable & DoesntMatterWhatItDoesWith(State.Y) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init), ) @@ -989,9 +988,9 @@ object AlwaysGoodOptimizations { ((ShortBranching -- ReadsNOrZ) & MatchParameter(0)) } val inner: AssemblyPattern = if (withRts) { - (Linear & Not(readsI) & Not(ReadsNOrZ ++ NoopDiscardsFlags)).* ~ - ManyWhereAtLeastOne(HasOpcodeIn(NoopDiscardsFlags), HasOpcode(discardIF)) ~ - HasOpcodeIn(Set(RTS, RTI)) ~ + (Linear & Not(readsI) & Not(ReadsNOrZ | NoopDiscardsFlags)).* ~ + ManyWhereAtLeastOne(NoopDiscardsFlags, HasOpcode(discardIF)) ~ + HasOpcodeIn(RTS, RTI) ~ Not(HasOpcode(LABEL)).* } else { (Linear & Not(concernsI) & Not(ChangesA) & Not(ReadsNOrZ)).* @@ -1072,36 +1071,36 @@ object AlwaysGoodOptimizations { 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), + (HasOpcodeIn(LDA, TXA, TYA) & Elidable) ~ (LinearOrLabel & Not(ConcernsA) & Not(ReadsNOrZ)).* ~ OverwritesA ~~> (_.tail), + (HasOpcodeIn(LDX, TAX, TSX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ)).* ~ OverwritesX ~~> (_.tail), + (HasOpcodeIn(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.BackwardFlow, - (HasOpcodeIn(Set(LDA, STA)) & HasAddrMode(Immediate) & MatchParameter(1)) ~ + (HasOpcodeIn(LDA, STA) & HasAddrMode(Immediate) & MatchParameter(1)) ~ (Linear & Not(ChangesA) & Not(HasOpcode(DISCARD_AF))).* ~ (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDX, STX)) & HasAddrMode(Immediate) & MatchParameter(1)) ~ + (HasOpcodeIn(LDX, STX) & HasAddrMode(Immediate) & MatchParameter(1)) ~ (Linear & Not(ChangesX) & Not(HasOpcode(DISCARD_XF))).* ~ (Elidable & HasOpcode(LDX) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDY, STY)) & HasAddrMode(Immediate) & MatchParameter(1)) ~ + (HasOpcodeIn(LDY, STY) & HasAddrMode(Immediate) & MatchParameter(1)) ~ (Linear & Not(ChangesY) & Not(HasOpcode(DISCARD_YF))).* ~ (Elidable & HasOpcode(LDY) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDA, STA) & MatchAddrMode(0) & MatchParameter(1)) ~ (Linear & Not(ChangesA) & Not(HasOpcode(DISCARD_AF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDX, STX) & MatchAddrMode(0) & MatchParameter(1)) ~ (Linear & Not(ChangesX) & Not(HasOpcode(DISCARD_XF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDY, STY) & MatchAddrMode(0) & MatchParameter(1)) ~ (Linear & Not(ChangesY) & Not(HasOpcode(DISCARD_YF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), @@ -1129,49 +1128,49 @@ object AlwaysGoodOptimizations { (Linear & Not(ChangesY) & Not(ChangesNAndZ) & Not(HasOpcode(DISCARD_YF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init), - (HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDA, STA) & MatchAddrMode(0) & MatchParameter(1)) ~ (ShortConditionalBranching & MatchParameter(2)) ~ (Linear & Not(ChangesA) & Not(HasOpcode(DISCARD_AF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (HasOpcode(LABEL) & MatchParameter(2)) ~ (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDX, STX) & MatchAddrMode(0) & MatchParameter(1)) ~ (ShortConditionalBranching & MatchParameter(2)) ~ (Linear & Not(ChangesX) & Not(HasOpcode(DISCARD_XF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (HasOpcode(LABEL) & MatchParameter(2)) ~ (Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDY, STY) & MatchAddrMode(0) & MatchParameter(1)) ~ (ShortConditionalBranching & MatchParameter(2)) ~ (Linear & Not(ChangesY) & Not(HasOpcode(DISCARD_YF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (HasOpcode(LABEL) & MatchParameter(2)) ~ (Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~ - (HasOpcodeIn(ShortBranching) & MatchParameter(3)) ~ + (HasOpcodeIn(LDA, STA) & MatchAddrMode(0) & MatchParameter(1)) ~ + (ShortBranching & MatchParameter(3)) ~ (Linear & Not(ChangesA) & Not(HasOpcode(DISCARD_AF)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ (HasOpcode(LABEL) & MatchParameter(3) & HasCallerCount(1)) ~ (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - HasOpcodeIn(Set(TXA, TAX, LAX, LXA)) ~ - (Not(Set(TXA, TAX)) & Linear & Not(ChangesA) & Not(ChangesX)).* ~ - (Elidable & HasOpcodeIn(Set(TXA, TAX)) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), + HasOpcodeIn(TXA, TAX, LAX, LXA) ~ + (Not(HasOpcodeIn(TXA, TAX)) & Linear & Not(ChangesA) & Not(ChangesX)).* ~ + (Elidable & HasOpcodeIn(TXA, TAX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - HasOpcodeIn(Set(TYA, TAY)) ~ - (Not(Set(TYA, TAY)) & Linear & Not(ChangesA) & Not(ChangesY)).* ~ - (Elidable & HasOpcodeIn(Set(TYA, TAY)) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), + HasOpcodeIn(TYA, TAY) ~ + (Not(HasOpcodeIn(TYA, TAY)) & Linear & Not(ChangesA) & Not(ChangesY)).* ~ + (Elidable & HasOpcodeIn(TYA, TAY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(STA, LDA)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(STA, LDA) & HasAddrModeIn(ZeroPage, Absolute) & MatchAddrMode(0) & MatchParameter(1)) ~ (Linear & Not(HasOpcode(TAX)) & Not(ChangesA) & DoesntChangeMemoryAt(0, 1)).* ~ HasOpcode(TAX) ~ (LinearOrBranch & Not(ChangesX) & DoesntChangeMemoryAt(0, 1)).* ~ - (Elidable & HasOpcode(LDX) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), + (Elidable & HasOpcode(LDX) & HasAddrModeIn(ZeroPage, Absolute) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), - (HasOpcodeIn(Set(STA, LDA)) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(STA, LDA) & HasAddrModeIn(ZeroPage, Absolute) & MatchAddrMode(0) & MatchParameter(1)) ~ (Linear & Not(HasOpcode(TAY)) & Not(ChangesA) & DoesntChangeMemoryAt(0, 1)).* ~ HasOpcode(TAY) ~ (LinearOrBranch & Not(ChangesY) & DoesntChangeMemoryAt(0, 1)).* ~ - (Elidable & HasOpcode(LDY) & HasAddrModeIn(Set(ZeroPage, Absolute)) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), + (Elidable & HasOpcode(LDY) & HasAddrModeIn(ZeroPage, Absolute) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_.init), ) val RearrangableLoadFromTheSameLocation = new RuleBasedAssemblyOptimization("Rearrangable load from the same location", @@ -1223,19 +1222,19 @@ object AlwaysGoodOptimizations { val PointlessOperationFromFlow = new RuleBasedAssemblyOptimization("Pointless operation from flow", needsFlowInfo = FlowInfoRequirement.BackwardFlow, - (Elidable & HasOpcodeIn(Set(LDA, TXA, TYA, AND, EOR, ORA, XAA)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(LDX, TSX, TAX, SBX, INX, DEX)) & DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(LDY, TAY, INY, DEY)) & DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(LAX, LXA)) & DoesntMatterWhatItDoesWith(State.A, State.X, State.N, State.Z)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(ANC, ALR)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z, State.C)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(ADC, SBC, ARR)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z, State.C, State.V)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(CMP, CPY, CPX, BIT)) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C, State.V)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(ROL, ROR, ASL, LSR)) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z, State.C)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(INC, DEC)) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(CLC, SEC)) & DoesntMatterWhatItDoesWith(State.C)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(CLD, SED)) & DoesntMatterWhatItDoesWith(State.D)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(CLV)) & DoesntMatterWhatItDoesWith(State.V)) ~~> (_ => Nil), - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & HasImmediate(0) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(LDA, TXA, TYA, AND, EOR, ORA, XAA) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(LDX, TSX, TAX, SBX, INX, DEX) & DoesntMatterWhatItDoesWith(State.X, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(LDY, TAY, INY, DEY) & DoesntMatterWhatItDoesWith(State.Y, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(LAX, LXA) & DoesntMatterWhatItDoesWith(State.A, State.X, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(ANC, ALR) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z, State.C)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(ADC, SBC, ARR) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z, State.C, State.V)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(CMP, CPY, CPX, BIT) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C, State.V)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(ROL, ROR, ASL, LSR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z, State.C)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(INC, DEC) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(CLC, SEC) & DoesntMatterWhatItDoesWith(State.C)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(CLD, SED) & DoesntMatterWhatItDoesWith(State.D)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(CLV) & DoesntMatterWhatItDoesWith(State.V)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(ORA, EOR) & HasImmediate(0) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_ => Nil), (Elidable & HasOpcode(AND) & HasImmediate(0xff) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_ => Nil), (Elidable & HasOpcode(ANC) & HasImmediate(0xff) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C)) ~~> (_ => Nil), ) @@ -1311,12 +1310,12 @@ object AlwaysGoodOptimizations { val RearrangeMath = new RuleBasedAssemblyOptimization("Rearranging math", needsFlowInfo = FlowInfoRequirement.NoRequirement, (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate)) ~ - (Elidable & HasOpcodeIn(Set(CLC, SEC))) ~ + (Elidable & HasOpcodeIn(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 => + (Elidable & HasOpcodeIn(ADC, EOR, ORA, AND) & Not(HasAddrMode(Immediate))) ~~> { c => c.last.copy(opcode = LDA) :: c.head.copy(opcode = c.last.opcode) :: Nil }, ) @@ -1433,17 +1432,17 @@ object AlwaysGoodOptimizations { needsFlowInfo = FlowInfoRequirement.BackwardFlow, (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.Z, State.N, State.A)) ~ (Linear & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0)).* ~ - (Elidable & HasOpcodeIn(Set(ASL, LSR)) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.Z, State.N)) ~~> { code => + (Elidable & HasOpcodeIn(ASL, LSR) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.Z, State.N)) ~~> { code => code.last.copy(addrMode = AddrMode.Implied, parameter = Constant.Zero) :: code.init }, (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.Z, State.N, State.A)) ~ (Linear & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0) & Not(ChangesC)).* ~ - (Elidable & HasOpcodeIn(Set(ASL, LSR)) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> { code => + (Elidable & HasOpcodeIn(ASL, LSR) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> { code => code.last.copy(addrMode = AddrMode.Implied, parameter = Constant.Zero) :: code.init }, (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.N, State.A)) ~ (Linear & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0) & Not(ChangesC)).* ~ - (Elidable & HasOpcodeIn(Set(ROL, ROR)) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> { code => + (Elidable & HasOpcodeIn(ROL, ROR) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> { code => code.last.copy(addrMode = AddrMode.Implied, parameter = Constant.Zero) :: code.init }, ) @@ -1513,8 +1512,8 @@ object AlwaysGoodOptimizations { 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, RTL, BSR))) - val secondReturn = (Elidable & HasOpcodeIn(Set(RTS, RTI) | NoopDiscardsFlags)).*.capture(6) + val store = Elidable & (Not(ReadsC) & Linear | HasOpcodeIn(RTS, JSR, RTI, RTL, BSR)) + val secondReturn = (Elidable & (HasOpcodeIn(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) @@ -1562,7 +1561,7 @@ object AlwaysGoodOptimizations { 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(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(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( @@ -1570,7 +1569,7 @@ object AlwaysGoodOptimizations { code.last.copy(opcode = INC), AssemblyLine.label(label)) }, - (Elidable & HasOpcode(LDA) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~ + (Elidable & HasOpcode(LDA) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(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") @@ -1580,11 +1579,11 @@ object AlwaysGoodOptimizations { 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(ADC) & MatchAddrMode(1) & MatchParameter(2) & HasAddrModeIn(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(LDA) & MatchAddrMode(1) & HasClear(State.C) & MatchParameter(2) & HasAddrModeIn(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)) @@ -1633,7 +1632,7 @@ object AlwaysGoodOptimizations { (HasOpcode(AND) & HasImmediate(1)) ~ (Linear & Not(ChangesNAndZ) & Not(HasOpcode(CLC)) & Not(ChangesA)).* ~ (Elidable & HasOpcode(CLC) & HasClear(State.D)) ~ - (Elidable & HasOpcode(ADC) & MatchAddrMode(0) & MatchParameter(1) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~ + (Elidable & HasOpcode(ADC) & MatchAddrMode(0) & MatchParameter(1) & HasAddrModeIn(ZeroPage, ZeroPageX, Absolute, AbsoluteX)) ~ (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.V, State.Z, State.N, State.A)) ~~> { code => val label = getNextLabel("in") code.take(code.length - 3) ++ List( @@ -1644,7 +1643,7 @@ object AlwaysGoodOptimizations { }, (HasOpcode(ANC) & HasImmediate(1)) ~ (Linear & Not(ChangesNAndZ) & Not(ChangesA) & (Not(ChangesC) | HasOpcode(CLC))).* ~ - (Elidable & HasOpcode(ADC) & MatchAddrMode(0) & MatchParameter(1) & HasClear(State.D) & HasAddrModeIn(Set(ZeroPage, ZeroPageX, Absolute, AbsoluteX))) ~ + (Elidable & HasOpcode(ADC) & MatchAddrMode(0) & MatchParameter(1) & HasClear(State.D) & HasAddrModeIn(ZeroPage, ZeroPageX, Absolute, AbsoluteX)) ~ (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.V, State.Z, State.N, State.A)) ~~> { code => val label = getNextLabel("in") code.head.copy(opcode = AND) :: code.take(code.length - 2).tail ++ List( @@ -1758,8 +1757,8 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(AND) & HasImmediate(1)) ~ ((Elidable & Linear & Not(ChangesMemory) & DoesNotConcernMemoryAt(0, 1) & Not(ChangesA)).* ~ (Elidable & HasOpcode(STA) & DoesNotConcernMemoryAt(0, 1))).capture(3) ~ - ((Elidable & HasOpcodeIn(Set(LSR, ROR)) & Not(ChangesA) & MatchAddrMode(0) & Not(MatchParameter(1))).* ~ - (Elidable & HasOpcodeIn(Set(LSR, ROR)) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.C, State.N))).capture(2) ~~> { (code, ctx) => + ((Elidable & HasOpcodeIn(LSR, ROR) & Not(ChangesA) & MatchAddrMode(0) & Not(MatchParameter(1))).* ~ + (Elidable & HasOpcodeIn(LSR, ROR) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.C, State.N))).capture(2) ~~> { (code, ctx) => ctx.get[List[AssemblyLine]](2) ++ List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL)) ++ ctx.get[List[AssemblyLine]](3) @@ -1768,8 +1767,8 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(AND) & MatchAddrMode(0) & MatchParameter(1)) ~ ((Elidable & Linear & Not(ChangesMemory) & DoesNotConcernMemoryAt(0, 1) & Not(ChangesA)).* ~ (Elidable & HasOpcode(STA) & DoesNotConcernMemoryAt(0, 1))).capture(3) ~ - ((Elidable & HasOpcodeIn(Set(LSR, ROR)) & Not(ChangesA) & MatchAddrMode(0) & Not(MatchParameter(1))).* ~ - (Elidable & HasOpcodeIn(Set(LSR, ROR)) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.C, State.N))).capture(2) ~~> { (code, ctx) => + ((Elidable & HasOpcodeIn(LSR, ROR) & Not(ChangesA) & MatchAddrMode(0) & Not(MatchParameter(1))).* ~ + (Elidable & HasOpcodeIn(LSR, ROR) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.C, State.N))).capture(2) ~~> { (code, ctx) => ctx.get[List[AssemblyLine]](2) ++ List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL)) ++ ctx.get[List[AssemblyLine]](3) @@ -1777,7 +1776,7 @@ object AlwaysGoodOptimizations { (Elidable & (HasOpcode(ASL) | HasOpcode(ROL) & HasClear(State.C)) & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(ROL) & Not(ChangesA) & MatchAddrMode(0) & Not(MatchParameter(1))).*.capture(2) ~ (Elidable & HasOpcode(CLC)).? ~ - (Elidable & HasOpcodeIn(Set(LDA, TYA, TXA, PLA))).capture(3) ~ + (Elidable & HasOpcodeIn(LDA, TYA, TXA, PLA)).capture(3) ~ (Elidable & HasOpcode(AND) & HasImmediate(1)) ~ (Elidable & HasOpcode(CLC)).? ~ (Elidable & (HasOpcode(ORA) | HasOpcode(ADC) & HasClear(State.C) & HasClear(State.D)) & MatchAddrMode(0) & MatchParameter(1)) ~ @@ -1787,19 +1786,19 @@ object AlwaysGoodOptimizations { ctx.get[List[AssemblyLine]](2) }, (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Elidable & HasOpcodeIn(Set(ROL, ASL)) & DoesntMatterWhatItDoesWith(State.A)) ~ + (Elidable & HasOpcodeIn(ROL, ASL) & DoesntMatterWhatItDoesWith(State.A)) ~ (Linear & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0)).* ~ - (Elidable & HasOpcodeIn(Set(ROL, ASL)) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => + (Elidable & HasOpcodeIn(ROL, ASL) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => code.last :: code.drop(2).init }, (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Elidable & HasOpcodeIn(Set(ROR, LSR)) & DoesntMatterWhatItDoesWith(State.A)) ~ + (Elidable & HasOpcodeIn(ROR, LSR) & DoesntMatterWhatItDoesWith(State.A)) ~ (Linear & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0)).* ~ (Elidable & HasOpcode(LSR) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => code.last :: code.drop(2).init }, (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Elidable & HasOpcodeIn(Set(ROR, LSR)) & DoesntMatterWhatItDoesWith(State.A)) ~ + (Elidable & HasOpcodeIn(ROR, LSR) & DoesntMatterWhatItDoesWith(State.A)) ~ (Linear & Not(HasOpcode(LSR)) & DoesNotConcernMemoryAt(0, 1) & DoesntChangeIndexingInAddrMode(0)).* ~ (Elidable & HasOpcode(LSR) & DoesNotConcernMemoryAt(0, 1)) ~ (Elidable & HasOpcode(ROR) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => @@ -1815,7 +1814,7 @@ object AlwaysGoodOptimizations { List(AssemblyLine.implied(SEC), code.head.copy(opcode = ROL)) }, (Elidable & HasOpcode(ASL) & HasAddrMode(AddrMode.Implied)) ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & HasImmediate(1)) ~~> { code => + (Elidable & HasOpcodeIn(ORA, EOR) & HasImmediate(1)) ~~> { code => List(AssemblyLine.implied(SEC), code.head.copy(opcode = ROL)) }, ) @@ -1823,9 +1822,9 @@ object AlwaysGoodOptimizations { private def blockIsIdempotentWhenItComesToIndexRegisters(i: Int) = Where(ctx => { val code = ctx.get[List[AssemblyLine]](i) val rx = code.indexWhere(ReadsX) - val wx = code.indexWhere(l => ChangesX(l.opcode)) + val wx = code.indexWhere(l => OpcodeClasses.ChangesX(l.opcode)) val ry = code.indexWhere(ReadsY) - val wy = code.indexWhere(l => ChangesY(l.opcode)) + val wy = code.indexWhere(l => OpcodeClasses.ChangesY(l.opcode)) val xOk = rx < 0 || wx < 0 || rx >= wx val yOk = ry < 0 || wy < 0 || ry >= wy xOk && yOk @@ -1834,25 +1833,25 @@ object AlwaysGoodOptimizations { val CommonExpressionInConditional = new RuleBasedAssemblyOptimization("Common expression in conditional", needsFlowInfo = FlowInfoRequirement.BackwardFlow, ( - (HasOpcodeIn(Set(LDA, LAX)) & MatchAddrMode(0) & MatchParameter(1)) ~ - HasOpcodeIn(Set(LDY, LDX, AND, ORA, EOR, ADC, SBC, CLC, SEC, CPY, CPX, CMP)).* + (HasOpcodeIn(LDA, LAX) & MatchAddrMode(0) & MatchParameter(1)) ~ + HasOpcodeIn(LDY, LDX, AND, ORA, EOR, ADC, SBC, CLC, SEC, CPY, CPX, CMP).* ).capture(7) ~ blockIsIdempotentWhenItComesToIndexRegisters(7) ~ - HasOpcodeIn(ShortConditionalBranching) ~ + ShortConditionalBranching ~ MatchElidableCopyOf(7, Anything, DoesntMatterWhatItDoesWith(State.C, State.Z, State.N, State.V)) ~~> { code => code.take(code.length / 2 + 1) }, - (Elidable & HasOpcodeIn(Set(LDA, LAX)) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Elidable & HasOpcode(AND) & HasAddrModeIn(Set(Absolute, ZeroPage)) & DoesntMatterWhatItDoesWith(State.C, State.V, State.A)) ~ - HasOpcodeIn(Set(BEQ, BNE)) ~ - (HasOpcodeIn(Set(LDA, LAX)) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => + (Elidable & HasOpcodeIn(LDA, LAX) & MatchAddrMode(0) & MatchParameter(1)) ~ + (Elidable & HasOpcode(AND) & HasAddrModeIn(Absolute, ZeroPage) & DoesntMatterWhatItDoesWith(State.C, State.V, State.A)) ~ + HasOpcodeIn(BEQ, BNE) ~ + (HasOpcodeIn(LDA, LAX) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => List(code(0), code(1).copy(opcode = BIT), code(2)) }, - (Elidable & HasOpcode(LDA) & HasAddrModeIn(Set(Absolute, ZeroPage))) ~ + (Elidable & HasOpcode(LDA) & HasAddrModeIn(Absolute, ZeroPage)) ~ (Elidable & HasOpcode(AND) & MatchAddrMode(0) & MatchParameter(1)) ~ - HasOpcodeIn(Set(BEQ, BNE)) ~ + HasOpcodeIn(BEQ, BNE) ~ (HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { code => List(code(1).copy(opcode = LDA), code(0).copy(opcode = BIT), code(2)) }, @@ -1874,14 +1873,14 @@ object AlwaysGoodOptimizations { needsFlowInfo = FlowInfoRequirement.JustLabels, ((Elidable & HasOpcode(LABEL) & MatchParameter(1)) ~ LinearOrLabel.*).capture(10) ~ Where(ctx => ctx.get[List[AssemblyLine]](10).map(_.sizeInBytes).sum < 100) ~ - (Elidable & HasOpcodeIn(ShortConditionalBranching) & MatchParameter(0)) ~ + (Elidable & ShortConditionalBranching & MatchParameter(0)) ~ (Elidable & HasOpcode(JMP) & MatchParameter(1)) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(0)) ~~> { (code, ctx) => ctx.get[List[AssemblyLine]](10) ++ List(negate(code(code.length - 3).copy(parameter = ctx.get[Constant](1))), code.last) }, ((Elidable & HasOpcode(LABEL) & MatchParameter(1)) ~ LinearOrLabel.*).capture(10) ~ Where(ctx => ctx.get[List[AssemblyLine]](10).map(_.sizeInBytes).sum < 100) ~ - (Elidable & HasOpcodeIn(ShortConditionalBranching) & MatchParameter(0)).capture(13) ~ + (Elidable & ShortConditionalBranching & MatchParameter(0)).capture(13) ~ ((Elidable & Not(MatchParameter(0))).* ~ (Elidable & HasOpcode(LABEL) & MatchParameter(0)) ~ (Elidable & HasOpcode(JMP) & MatchParameter(1))).capture(11) ~~> { (code, ctx) => @@ -1898,17 +1897,17 @@ object AlwaysGoodOptimizations { val IndexComparisonOptimization = new RuleBasedAssemblyOptimization("Index comparison optimization", needsFlowInfo = FlowInfoRequirement.BackwardFlow, - (Elidable & HasOpcodeIn(Set(DEX, INX)) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ + (Elidable & HasOpcodeIn(DEX, INX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ (Linear & Not(ConcernsX)).* ~ (Elidable & (HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.A) | HasOpcode(CPX) & HasImmediate(0) & DoesntMatterWhatItDoesWith(State.C, State.V))) ~~> { code => code.tail.init :+ code.head }, - (Elidable & HasOpcodeIn(Set(DEY, INY)) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ + (Elidable & HasOpcodeIn(DEY, INY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ (Linear & Not(ConcernsY)).* ~ (Elidable & (HasOpcode(TYA) & DoesntMatterWhatItDoesWith(State.A) | HasOpcode(CPY) & HasImmediate(0) & DoesntMatterWhatItDoesWith(State.C, State.V))) ~~> { code => code.tail.init :+ code.head }, - (Elidable & HasAddrMode(Implied) & HasOpcodeIn(Set(DEC, INC)) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ + (Elidable & HasAddrMode(Implied) & HasOpcodeIn(DEC, INC) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~ (Linear & Not(ConcernsA)).* ~ (Elidable & ( HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.Y) @@ -1999,22 +1998,22 @@ object AlwaysGoodOptimizations { needsFlowInfo = FlowInfoRequirement.BackwardFlow, HasOpcode(LDA) ~ (Elidable & HasOpcode(AND) & HasImmediate(0x80)) ~ - (Elidable & HasOpcodeIn(Set(BNE, BEQ)) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> {code => + (Elidable & HasOpcodeIn(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 => + (Elidable & HasOpcodeIn(BNE, BEQ) & DoesntMatterWhatItDoesWith(State.A, State.N, State.Z)) ~~> {code => List(code(1).copy(opcode = LDA), remapZ2N(code(2))) }, ) val PointlessSignCheck: RuleBasedAssemblyOptimization = { def loadOldSignedVariable: AssemblyPattern = ( - (HasOpcodeIn(Set(AND, ANC)) & HasImmediateWhere(i => (i & 0x80) == 0)) ~ - (HasOpcode(STA) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(AND, ANC) & HasImmediateWhere(i => (i & 0x80) == 0)) ~ + (HasOpcode(STA) & HasAddrModeIn(Absolute, ZeroPage) & MatchAddrMode(0) & MatchParameter(1)) ~ DoesNotConcernMemoryAt(0, 1).* ~ - (HasOpcode(LDA) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(1)) + (HasOpcode(LDA) & HasAddrModeIn(Absolute, ZeroPage) & MatchParameter(1)) ).capture(10) ~ Where(_.isExternallyLinearBlock(10)) val isNonnegative: Int => Boolean = i => (i & 0x80) == 0 @@ -2026,21 +2025,21 @@ object AlwaysGoodOptimizations { loadOldSignedVariable ~ (Elidable & HasOpcode(BMI)) ~~> { (code, ctx) => ctx.get[List[AssemblyLine]](10) }, loadOldSignedVariable ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & HasImmediateWhere(isNonnegative)) ~ + (Elidable & HasOpcodeIn(ORA, EOR) & HasImmediateWhere(isNonnegative)) ~ (Elidable & HasOpcode(BMI)) ~ OverwritesA ~~> { (code, ctx) => ctx.get[List[AssemblyLine]](10) :+ code.last }, loadOldSignedVariable ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & HasImmediateWhere(isNonnegative)) ~ + (Elidable & HasOpcodeIn(ORA, EOR) & HasImmediateWhere(isNonnegative)) ~ (Elidable & HasOpcode(BMI)) ~~> { code => code.init }, loadOldSignedVariable ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & HasImmediateWhere(isNonnegative)).? ~ + (Elidable & HasOpcodeIn(ORA, EOR) & HasImmediateWhere(isNonnegative)).? ~ (Elidable & HasOpcode(BPL)) ~~> { code => code.init :+ code.last.copy(opcode = JMP, addrMode = Absolute) }, loadOldSignedVariable ~ ((Linear & Not(ConcernsX) & Not(ChangesA)).* ~ HasOpcode(TAX) ~ (Linear & Not(ConcernsX)).*).capture(11) ~ (Elidable & HasOpcode(TXA)) ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & HasImmediateWhere(isNonnegative)).? ~ + (Elidable & HasOpcodeIn(ORA, EOR) & HasImmediateWhere(isNonnegative)).? ~ (Elidable & HasOpcode(BMI)) ~ OverwritesA ~~> { (code, ctx) => ctx.get[List[AssemblyLine]](10) ++ ctx.get[List[AssemblyLine]](11) :+ code.last }, loadOldSignedVariable ~ @@ -2048,14 +2047,14 @@ object AlwaysGoodOptimizations { HasOpcode(TAX) ~ (Linear & Not(ConcernsX)).* ~ HasOpcode(TXA) ~ - (HasOpcodeIn(Set(ORA, EOR)) & HasImmediateWhere(isNonnegative)).? ~ + (HasOpcodeIn(ORA, EOR) & HasImmediateWhere(isNonnegative)).? ~ (Elidable & HasOpcode(BMI)) ~~> { code => code.init }, loadOldSignedVariable ~ (Linear & Not(ConcernsX) & Not(ChangesA)).* ~ HasOpcode(TAX) ~ (Linear & Not(ConcernsX)).* ~ HasOpcode(TXA) ~ - (HasOpcodeIn(Set(ORA, EOR)) & HasImmediateWhere(isNonnegative)).? ~ + (HasOpcodeIn(ORA, EOR) & HasImmediateWhere(isNonnegative)).? ~ (Elidable & HasOpcode(BPL)) ~~> { code => code.init :+ code.last.copy(opcode = JMP, addrMode = Absolute) }, ) } @@ -2141,10 +2140,10 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(BCC) & MatchParameter(14)) ~ (Elidable & HasOpcode(INX)) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(14) & HasCallerCount(1)) ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & MatchAddrMode(0) & MatchParameter(1) & Not(ConcernsX)) ~ + (Elidable & HasOpcodeIn(ORA, EOR) & MatchAddrMode(0) & MatchParameter(1) & Not(ConcernsX)) ~ (Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & Not(ConcernsX)) ~ (Elidable & HasOpcode(TXA)) ~ - (Elidable & HasOpcodeIn(Set(ORA, EOR)) & MatchAddrMode(2) & MatchParameter(3) & Not(ConcernsX) & DoesNotConcernMemoryAt(0, 1)) ~ + (Elidable & HasOpcodeIn(ORA, EOR) & 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( @@ -2161,12 +2160,12 @@ object AlwaysGoodOptimizations { 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)) + if (firstLda) HasOpcodeIn(LDA, STA, LAX) & HasAddrModeIn(Absolute, ZeroPage, Immediate) & MatchParameter(1) + else if (targetY) HasOpcodeIn(TXA, TAX) + else HasOpcodeIn(TAY, TYA) ) val secondLoad = Elidable & ( - if (firstLda) HasOpcode(LDA) & HasAddrModeIn(Set(Absolute, ZeroPage, Immediate)) & MatchParameter(1) + if (firstLda) HasOpcode(LDA) & HasAddrModeIn(Absolute, ZeroPage, Immediate) & MatchParameter(1) else if (targetY) HasOpcode(TXA) else HasOpcode(TYA) ) @@ -2176,8 +2175,8 @@ object AlwaysGoodOptimizations { 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)) + HasAddrMode(Implied) & HasOpcodeIn(ASL, CLC, CLD, SEC, SED, LSR, INC, DEC) | + HasOpcodeIn(ADC, ORA, EOR, AND, SBC) & HasAddrModeIn(Absolute, ZeroPage, Immediate) val firstFiller = fillerLine.* val secondFiller = (Elidable & fillerLine).* val betweenLines = (Linear & Not(secondLoad) & Not(if (targetY) ChangesY else ChangesX)).+ @@ -2224,7 +2223,7 @@ object AlwaysGoodOptimizations { val ConstantPointer = new RuleBasedAssemblyOptimization("Constant pointer optimization", needsFlowInfo = FlowInfoRequirement.ForwardFlow, - (HasOpcode(STA) & MatchA(0) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(4)) ~ + (HasOpcode(STA) & MatchA(0) & HasAddrModeIn(Absolute, ZeroPage) & MatchParameter(4)) ~ Where(ctx => { val lo = ctx.get[Constant](4) ctx.addObject(5, lo + 1) @@ -2232,7 +2231,7 @@ object AlwaysGoodOptimizations { true }) ~ (Linear & DoesNotConcernMemoryAt(3,4) & DoesNotConcernMemoryAt(3,5)).* ~ - (HasOpcode(STA) & MatchA(1) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(5)) ~ + (HasOpcode(STA) & MatchA(1) & HasAddrModeIn(Absolute, ZeroPage) & MatchParameter(5)) ~ Where(ctx => { val lo = ctx.get[Int](0) & 0xff val hi = ctx.get[Int](1) & 0xff @@ -2240,13 +2239,13 @@ object AlwaysGoodOptimizations { true }) ~ (Linear & DoesNotConcernMemoryAt(3,4) & DoesNotConcernMemoryAt(3,5)).* ~ - (Elidable & MatchParameter(6) & HasAddrModeIn(Set(IndexedZ, IndexedY))) ~~> { (code, ctx) => + (Elidable & MatchParameter(6) & HasAddrModeIn(IndexedZ, IndexedY)) ~~> { (code, ctx) => val addr = ctx.get[Int](2) val last = code.last code.init :+ last.copy(parameter = NumericConstant(addr, 2), addrMode = if (last.addrMode == IndexedZ) Absolute else AbsoluteY) }, - (HasOpcode(STA) & MatchA(0) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(4)) ~ + (HasOpcode(STA) & MatchA(0) & HasAddrModeIn(Absolute, ZeroPage) & MatchParameter(4)) ~ Where(ctx => { val lo = ctx.get[Constant](4) ctx.addObject(5, lo + 1) @@ -2254,7 +2253,7 @@ object AlwaysGoodOptimizations { true }) ~ (Linear & DoesNotConcernMemoryAt(3,4) & DoesNotConcernMemoryAt(3,5)).* ~ - (HasOpcode(STX) & MatchX(1) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(5)) ~ + (HasOpcode(STX) & MatchX(1) & HasAddrModeIn(Absolute, ZeroPage) & MatchParameter(5)) ~ Where(ctx => { val lo = ctx.get[Int](0) & 0xff val hi = ctx.get[Int](1) & 0xff @@ -2262,7 +2261,7 @@ object AlwaysGoodOptimizations { true }) ~ (Linear & DoesNotConcernMemoryAt(3,4) & DoesNotConcernMemoryAt(3,5)).* ~ - (Elidable & MatchParameter(6) & HasAddrModeIn(Set(IndexedZ, IndexedY))) ~~> { (code, ctx) => + (Elidable & MatchParameter(6) & HasAddrModeIn(IndexedZ, IndexedY)) ~~> { (code, ctx) => val addr = ctx.get[Int](2) val last = code.last code.init :+ last.copy(parameter = NumericConstant(addr, 2), addrMode = if (last.addrMode == IndexedZ) Absolute else AbsoluteY) diff --git a/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala index d4829c80..e9461d86 100644 --- a/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala @@ -1,5 +1,6 @@ package millfork.assembly.mos.opt +import millfork.assembly.OptimizationContext import millfork.{CompilationFlag, CompilationOptions} import millfork.assembly.mos.AssemblyLine import millfork.assembly.mos.OpcodeClasses @@ -11,7 +12,9 @@ import millfork.env.{Label, MemoryAddressConstant, NormalFunction, NumericConsta */ object CoarseFlowAnalyzer { - def analyze(f: NormalFunction, code: List[AssemblyLine], compilationOptions: CompilationOptions): List[CpuStatus] = { + def analyze(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[CpuStatus] = { + val compilationOptions = optimizationContext.options + val niceFunctionProperties = optimizationContext.niceFunctionProperties val ceFlag = compilationOptions.flag(CompilationFlag.Emit65CE02Opcodes) val cmosFlag = compilationOptions.flag(CompilationFlag.EmitCmosOpcodes) val initialStatus = @@ -36,6 +39,7 @@ object CoarseFlowAnalyzer { for (i <- codeArray.indices) { import millfork.assembly.mos.Opcode._ import millfork.assembly.mos.AddrMode._ + import millfork.node.MosNiceFunctionProperty._ if (flagArray(i) != currentStatus) { changed = true flagArray(i) = currentStatus @@ -48,6 +52,17 @@ object CoarseFlowAnalyzer { case _ => None }).fold(currentStatus)(_ ~ _) + case AssemblyLine(JSR, _, MemoryAddressConstant(th), _) => + currentStatus = initialStatus.copy( + a = if (niceFunctionProperties(DoesntChangeA -> th.name)) currentStatus.a else AnyStatus, + ah = if (niceFunctionProperties(DoesntChangeAH -> th.name)) currentStatus.ah else AnyStatus, + x = if (niceFunctionProperties(DoesntChangeX -> th.name)) currentStatus.x else AnyStatus, + eqSX = if (niceFunctionProperties(DoesntChangeX -> th.name)) currentStatus.eqSX else false, + y = if (niceFunctionProperties(DoesntChangeY -> th.name)) currentStatus.y else AnyStatus, + iz = if (niceFunctionProperties(DoesntChangeIZ -> th.name)) currentStatus.iz else AnyStatus, + c = if (niceFunctionProperties(DoesntChangeC -> th.name)) currentStatus.c else AnyStatus + ) + case AssemblyLine(JSR | BYTE, _, _, _) => currentStatus = initialStatus diff --git a/src/main/scala/millfork/assembly/mos/opt/EmptyMemoryStoreRemoval.scala b/src/main/scala/millfork/assembly/mos/opt/EmptyMemoryStoreRemoval.scala index 28afea89..468adae8 100644 --- a/src/main/scala/millfork/assembly/mos/opt/EmptyMemoryStoreRemoval.scala +++ b/src/main/scala/millfork/assembly/mos/opt/EmptyMemoryStoreRemoval.scala @@ -1,7 +1,7 @@ package millfork.assembly.mos.opt import millfork.CompilationOptions -import millfork.assembly.AssemblyOptimization +import millfork.assembly.{AssemblyOptimization, OptimizationContext} import millfork.assembly.mos.AssemblyLine import millfork.env._ import millfork.assembly.mos.Opcode._ @@ -18,7 +18,7 @@ object EmptyMemoryStoreRemoval extends AssemblyOptimization[AssemblyLine] { private val storeAddrModes = Set(Absolute, ZeroPage, AbsoluteX, AbsoluteY, ZeroPageX, ZeroPageY) - override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = { + override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { val paramVariables = f.params match { case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 => Set[String]() @@ -62,30 +62,30 @@ object EmptyMemoryStoreRemoval extends AssemblyOptimization[AssemblyLine] { case STA | STX | STY | SAX | STZ | SHX | SHY | AHX => true case TSB | TRB => - if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code) + if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) importances(lastaccess).z != Important case INC | DEC => - if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code) + if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) importances(lastaccess).z != Important && importances(lastaccess).n != Important case ASL | LSR | ROL | ROR | DCP => - if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code) + if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) importances(lastaccess).z != Important && importances(lastaccess).n != Important && importances(lastaccess).c != Important case ISC => - if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code) + if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) importances(lastaccess).z != Important && importances(lastaccess).n != Important && importances(lastaccess).a != Important case DCP | SLO | SRE | RLA => - if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code) + if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) importances(lastaccess).z != Important && importances(lastaccess).n != Important && importances(lastaccess).c != Important && importances(lastaccess).a != Important case RRA => - if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code) + if (importances eq null) importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) importances(lastaccess).z != Important && importances(lastaccess).n != Important && importances(lastaccess).c != Important && diff --git a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala index 812421ae..0fbf610e 100644 --- a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala @@ -1,6 +1,7 @@ package millfork.assembly.mos.opt import millfork.CompilationOptions +import millfork.assembly.OptimizationContext import millfork.assembly.mos.{AssemblyLine, Opcode, State} import millfork.env.{Label, MemoryAddressConstant, NormalFunction} @@ -33,20 +34,20 @@ object FlowAnalyzer { private val EmptyCpuStatus = CpuStatus() private val EmptyCpuImportance = CpuImportance() - def analyze(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions, req: FlowInfoRequirement.Value): List[(FlowInfo, AssemblyLine)] = { + def analyze(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext, req: FlowInfoRequirement.Value): List[(FlowInfo, AssemblyLine)] = { val forwardFlow = req match { case FlowInfoRequirement.BothFlows | FlowInfoRequirement.ForwardFlow => - () => CoarseFlowAnalyzer.analyze(f, code, options) + () => CoarseFlowAnalyzer.analyze(f, code, optimizationContext) case FlowInfoRequirement.BackwardFlow | FlowInfoRequirement.JustLabels | FlowInfoRequirement.NoRequirement => () => List.fill(code.size)(EmptyCpuStatus) } val reverseFlow = req match { case FlowInfoRequirement.BothFlows | FlowInfoRequirement.BackwardFlow => - () => ReverseFlowAnalyzer.analyze(f, code) + () => ReverseFlowAnalyzer.analyze(f, code, optimizationContext) case FlowInfoRequirement.ForwardFlow | FlowInfoRequirement.JustLabels | FlowInfoRequirement.NoRequirement => () => List.fill(code.size)(EmptyCpuImportance) } - val labelMap: (() => Option[Map[String, Int]]) = () => req match { + val labelMap: () => Option[Map[String, Int]] = () => req match { case FlowInfoRequirement.NoRequirement => None case _ => Some(code.flatMap { case AssemblyLine(op, _, MemoryAddressConstant(Label(l)), _) if op != Opcode.LABEL => Some(l) diff --git a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala index fd6683ed..e542953d 100644 --- a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala +++ b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala @@ -32,6 +32,9 @@ object FlowAnalyzerForImplied { CLC -> (_.copy(c = Status.SingleFalse)), SEC -> (_.copy(c = Status.SingleTrue)), CLV -> (_.copy(v = Status.SingleFalse)), + CLA -> (currentStatus => currentStatus.copy(a = Status.SingleZero, src = currentStatus.src.butNotA)), + CLX -> (currentStatus => currentStatus.copy(x = Status.SingleZero, src = currentStatus.src.butNotX)), + CLY -> (currentStatus => currentStatus.copy(y = Status.SingleZero, src = currentStatus.src.butNotY)), XCE -> (_.copy(c = AnyStatus, m = AnyStatus, w = AnyStatus, x = AnyStatus, y = AnyStatus, eqSX = false)), PLA -> (_.copy(src = SourceOfNZ.A, a = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), PLX -> (_.copy(src = SourceOfNZ.X, x = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), diff --git a/src/main/scala/millfork/assembly/mos/opt/HudsonOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/HudsonOptimizations.scala index 83609cfc..6a963531 100644 --- a/src/main/scala/millfork/assembly/mos/opt/HudsonOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/HudsonOptimizations.scala @@ -1,10 +1,11 @@ package millfork.assembly.mos.opt -import millfork.assembly.AssemblyOptimization +import millfork.CompilationOptions +import millfork.assembly.{AssemblyOptimization, OptimizationContext} import millfork.assembly.mos.AssemblyLine import millfork.assembly.mos.Opcode._ import millfork.assembly.mos.AddrMode._ -import millfork.env.NumericConstant +import millfork.env.{NormalFunction, NumericConstant} /** * @author Karol Stasiak @@ -13,10 +14,13 @@ object HudsonOptimizations { val All: List[AssemblyOptimization[AssemblyLine]] = List() - def removeLoadZero(code: List[AssemblyLine]): List[AssemblyLine] = code.map{ - case AssemblyLine(LDA, Immediate, NumericConstant(0, _), true) => AssemblyLine.implied(CLA) - case AssemblyLine(LDX, Immediate, NumericConstant(0, _), true) => AssemblyLine.implied(CLX) - case AssemblyLine(LDY, Immediate, NumericConstant(0, _), true) => AssemblyLine.implied(CLY) - case l => l + def removeLoadZero(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { + ReverseFlowAnalyzer.analyze(f, code, optimizationContext).zip(code).map { + case (i, l) if i.n != Unimportant || i.z != Unimportant => l + case (i, AssemblyLine(LDA, Immediate, NumericConstant(0, _), true)) => AssemblyLine.implied(CLA) + case (_, AssemblyLine(LDX, Immediate, NumericConstant(0, _), true)) => AssemblyLine.implied(CLX) + case (_, AssemblyLine(LDY, Immediate, NumericConstant(0, _), true)) => AssemblyLine.implied(CLY) + case (_, l) => l + } } } diff --git a/src/main/scala/millfork/assembly/mos/opt/JumpShortening.scala b/src/main/scala/millfork/assembly/mos/opt/JumpShortening.scala index 5f427145..daf72a3f 100644 --- a/src/main/scala/millfork/assembly/mos/opt/JumpShortening.scala +++ b/src/main/scala/millfork/assembly/mos/opt/JumpShortening.scala @@ -1,5 +1,6 @@ package millfork.assembly.mos.opt +import millfork.assembly.OptimizationContext import millfork.assembly.mos.{AddrMode, AssemblyLine} import millfork.{CompilationFlag, CompilationOptions} import millfork.assembly.mos.Opcode._ @@ -16,7 +17,8 @@ object JumpShortening { distance.toByte == distance } - def apply(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = { + def apply(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { + val options = optimizationContext.options val offsets = new Array[Int](code.length) var o = 0 code.zipWithIndex.foreach{ @@ -46,7 +48,7 @@ object JumpShortening { case (line, _) => line } } else { - FlowAnalyzer.analyze(f, code, options, FlowInfoRequirement.ForwardFlow).zipWithIndex.map { + FlowAnalyzer.analyze(f, code, optimizationContext, FlowInfoRequirement.ForwardFlow).zipWithIndex.map { case ((info, line@AssemblyLine(JMP, AddrMode.Absolute, MemoryAddressConstant(Label(label)), _)), ix) => labelOffsets.get(label).fold(line) { labelOffset => val thisOffset = offsets(ix) diff --git a/src/main/scala/millfork/assembly/mos/opt/LocalVariableReadOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/LocalVariableReadOptimization.scala index a14e3ffd..2b01f943 100644 --- a/src/main/scala/millfork/assembly/mos/opt/LocalVariableReadOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/LocalVariableReadOptimization.scala @@ -1,7 +1,7 @@ package millfork.assembly.mos.opt import millfork.CompilationOptions -import millfork.assembly.AssemblyOptimization +import millfork.assembly.{AssemblyOptimization, OptimizationContext} import millfork.assembly.mos.Opcode._ import millfork.assembly.mos.AddrMode._ import millfork.assembly.mos.{AssemblyLine, OpcodeClasses} @@ -16,7 +16,7 @@ object LocalVariableReadOptimization extends AssemblyOptimization[AssemblyLine] override def name: String = "Local variable read optimization" - override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = { + override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { val stillUsedVariables = code.flatMap { case AssemblyLine(_, _, MemoryAddressConstant(th: MemoryVariable), _) => th match { @@ -36,7 +36,7 @@ object LocalVariableReadOptimization extends AssemblyOptimization[AssemblyLine] return code } - val statuses = CoarseFlowAnalyzer.analyze(f, code, options) + val statuses = CoarseFlowAnalyzer.analyze(f, code, optimizationContext) val (optimized, result) = optimizeImpl(code.zip(statuses), eligibleVariables, Map()) if (optimized) { ErrorReporting.debug("Optimized local variable reads") diff --git a/src/main/scala/millfork/assembly/mos/opt/LoopUnrolling.scala b/src/main/scala/millfork/assembly/mos/opt/LoopUnrolling.scala index 94c5ba2a..dc16e2ac 100644 --- a/src/main/scala/millfork/assembly/mos/opt/LoopUnrolling.scala +++ b/src/main/scala/millfork/assembly/mos/opt/LoopUnrolling.scala @@ -87,8 +87,8 @@ object LoopUnrolling { (Elidable & HasOpcode(LDX) & MatchNumericImmediate(Start) & Not(HasImmediate(0))).capture(Initialization) ~ (Elidable & HasOpcode(BEQ) & MatchParameter(Skip)) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~ - ((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesX)).*.capture(Body) ~ - (Elidable & HasOpcodeIn(Set(DEX, INX))).capture(Step) + ((Elidable & Not(HasOpcodeIn(RTS, JSR, RTI, RTL)) & Not(ChangesX)).*.capture(Body) ~ + (Elidable & HasOpcodeIn(DEX, INX)).capture(Step) ).capture(BodyWithStep) ~ (Elidable & HasOpcode(CPX) & MatchNumericImmediate(End)).? ~ (Elidable & HasOpcode(BNE) & MatchParameter(Back) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~ @@ -101,8 +101,8 @@ object LoopUnrolling { }, (Elidable & HasOpcode(LDX) & MatchNumericImmediate(Start)).capture(Initialization) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~ - ((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesX)).*.capture(Body) ~ - (Elidable & HasOpcodeIn(Set(DEX, INX))).capture(Step) + ((Elidable & Not(HasOpcodeIn(RTS, JSR, RTI, RTL)) & Not(ChangesX)).*.capture(Body) ~ + (Elidable & HasOpcodeIn(DEX, INX)).capture(Step) ).capture(BodyWithStep) ~ (Elidable & HasOpcode(CPX) & MatchNumericImmediate(End)).? ~ (Elidable & HasOpcode(BNE) & MatchParameter(Back) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~ @@ -114,8 +114,8 @@ object LoopUnrolling { }, (Elidable & HasOpcode(LDX) & MatchNumericImmediate(Start)).capture(Initialization) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~ - ((Elidable & HasOpcodeIn(Set(DEX, INX))).capture(Step) ~ - (Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL, BNE, CPX, TXA))) & Not(ChangesX)).*.capture(Body) + ((Elidable & HasOpcodeIn(DEX, INX)).capture(Step) ~ + (Elidable & Not(HasOpcodeIn(RTS, JSR, RTI, RTL, BNE, CPX, TXA)) & Not(ChangesX)).*.capture(Body) ).capture(BodyWithStep) ~ (Elidable & HasOpcode(CPX) & MatchNumericImmediate(End) | Elidable & HasOpcode(TXA)) ~ (Elidable & HasOpcode(BNE) & MatchParameter(Back)) ~ @@ -128,8 +128,8 @@ object LoopUnrolling { (Elidable & HasOpcode(LDY) & MatchNumericImmediate(Start) & Not(HasImmediate(0))).capture(Initialization) ~ (Elidable & HasOpcode(BEQ) & MatchParameter(Skip)) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~ - ((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesY)).*.capture(Body) ~ - (Elidable & HasOpcodeIn(Set(DEY, INY))).capture(Step) + ((Elidable & Not(HasOpcodeIn(RTS, JSR, RTI, RTL)) & Not(ChangesY)).*.capture(Body) ~ + (Elidable & HasOpcodeIn(DEY, INY)).capture(Step) ).capture(BodyWithStep) ~ (Elidable & HasOpcode(CPY) & MatchNumericImmediate(End)).? ~ (Elidable & HasOpcode(BNE) & MatchParameter(Back) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~ @@ -142,8 +142,8 @@ object LoopUnrolling { }, (Elidable & HasOpcode(LDY) & MatchNumericImmediate(Start)).capture(Initialization) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~ - ((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesY)).*.capture(Body) ~ - (Elidable & HasOpcodeIn(Set(DEY, INY))).capture(Step) + ((Elidable & Not(HasOpcodeIn(RTS, JSR, RTI, RTL)) & Not(ChangesY)).*.capture(Body) ~ + (Elidable & HasOpcodeIn(DEY, INY)).capture(Step) ).capture(BodyWithStep) ~ (Elidable & HasOpcode(CPY) & MatchNumericImmediate(End)).? ~ (Elidable & HasOpcode(BNE) & MatchParameter(Back) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~ @@ -155,8 +155,8 @@ object LoopUnrolling { }, (Elidable & HasOpcode(LDY) & MatchNumericImmediate(Start)).capture(Initialization) ~ (Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~ - ((Elidable & HasOpcodeIn(Set(DEY, INY))).capture(Step) ~ - (Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL, BNE, CPY, TYA))) & Not(ChangesY)).*.capture(Body) + ((Elidable & HasOpcodeIn(DEY, INY)).capture(Step) ~ + (Elidable & Not(HasOpcodeIn(RTS, JSR, RTI, RTL, BNE, CPY, TYA)) & Not(ChangesY)).*.capture(Body) ).capture(BodyWithStep) ~ (Elidable & HasOpcode(CPY) & MatchNumericImmediate(End) | Elidable & HasOpcode(TYA)) ~ (Elidable & HasOpcode(BNE) & MatchParameter(Back) & DoesntMatterWhatItDoesWith(State.C, State.N, State.Z)) ~ diff --git a/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala index a6648036..d099bd57 100644 --- a/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala @@ -118,7 +118,8 @@ object ReverseFlowAnalyzer { r0 = Important, r1 = Important) //noinspection RedundantNewCaseClass - def analyze(f: NormalFunction, code: List[AssemblyLine]): List[CpuImportance] = { + def analyze(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[CpuImportance] = { + val niceFunctionProperties = optimizationContext.niceFunctionProperties val importanceArray = Array.fill[CpuImportance](code.length)(new CpuImportance()) val codeArray = code.toArray @@ -130,6 +131,7 @@ object ReverseFlowAnalyzer { for (i <- codeArray.indices.reverse) { import millfork.assembly.mos.Opcode._ import AddrMode._ + import millfork.node.MosNiceFunctionProperty._ if (importanceArray(i) != currentImportance) { changed = true importanceArray(i) = currentImportance @@ -173,10 +175,27 @@ object ReverseFlowAnalyzer { result = result.copy(a = Important) case _ => } - if (ZeropageRegisterOptimizations.functionsThatUsePseudoregisterAsInput(fun.name)) { - result = result.copy(r0 = Important, r1 = Important) - } - currentImportance = result + currentImportance = result.copy( + a = if (niceFunctionProperties(DoesntChangeA -> fun.name)) currentImportance.a ~ result.a else result.a, + ah = if (niceFunctionProperties(DoesntChangeAH -> fun.name)) currentImportance.ah ~ result.ah else result.ah, + x = if (niceFunctionProperties(DoesntChangeX -> fun.name)) currentImportance.x ~ result.x else result.x, + y = if (niceFunctionProperties(DoesntChangeY -> fun.name)) currentImportance.y ~ result.y else result.y, + iz = if (niceFunctionProperties(DoesntChangeIZ -> fun.name)) currentImportance.iz ~ result.iz else result.iz, + c = if (niceFunctionProperties(DoesntChangeC -> fun.name)) currentImportance.c ~ result.c else result.c, + d = if (niceFunctionProperties(DoesntConcernD -> fun.name)) currentImportance.d else result.d, + r0 = + if (ZeropageRegisterOptimizations.functionsThatUsePseudoregisterAsInput(fun.name)) + Important + else if (niceFunctionProperties(DoesntChangeZpRegister -> fun.name)) + currentImportance.r0 ~ result.r0 + else result.r0, + r1 = + if (ZeropageRegisterOptimizations.functionsThatUsePseudoregisterAsInput(fun.name)) + Important + else if (niceFunctionProperties(DoesntChangeZpRegister -> fun.name)) + currentImportance.r1 ~ result.r1 + else result.r1 + ) case AssemblyLine(ANC, _, NumericConstant(0, _), _) => currentImportance = currentImportance.copy(c = Unimportant, n = Unimportant, z = Unimportant, a = Unimportant) diff --git a/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzerPerImpliedOpcode.scala b/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzerPerImpliedOpcode.scala index 58feb110..37c5a974 100644 --- a/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzerPerImpliedOpcode.scala +++ b/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzerPerImpliedOpcode.scala @@ -174,6 +174,10 @@ object ReverseFlowAnalyzerPerImpiedOpcode { SED -> (_.copy(d = Unimportant)), CLV -> (_.copy(v = Unimportant)), + CLA -> (_.copy(a = Unimportant)), + CLX -> (_.copy(x = Unimportant)), + CLY -> (_.copy(y = Unimportant)), + ) def hasDefinition(opcode: Opcode.Value): Boolean = map.contains(opcode) diff --git a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala index e75955a8..2abd710d 100644 --- a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala @@ -1,11 +1,12 @@ package millfork.assembly.mos.opt -import millfork.{CompilationFlag, CompilationOptions} +import millfork.CompilationOptions import millfork.assembly._ import millfork.assembly.mos._ import millfork.assembly.opt.SingleStatus import millfork.env._ import millfork.error.ErrorReporting +import millfork.node.{MosNiceFunctionProperty, NiceFunctionProperty} import scala.collection.mutable @@ -32,18 +33,18 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf rules.foreach(_.pattern.validate(needsFlowInfo)) - override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions, labelMap: Map[String, Int]): List[AssemblyLine] = { + override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { val effectiveCode = code.map(a => a.copy(parameter = a.parameter.quickSimplify)) - val taggedCode = FlowAnalyzer.analyze(f, effectiveCode, options, needsFlowInfo) - optimizeImpl(f, taggedCode, options, labelMap) + val taggedCode = FlowAnalyzer.analyze(f, effectiveCode, optimizationContext, needsFlowInfo) + optimizeImpl(f, taggedCode, optimizationContext) } - def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, AssemblyLine)], options: CompilationOptions, labelMap: Map[String, Int]): List[AssemblyLine] = { + def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, AssemblyLine)], optimizationContext: OptimizationContext): List[AssemblyLine] = { code match { case Nil => Nil case head :: tail => for ((rule, index) <- rules.zipWithIndex) { - val ctx = new AssemblyMatchingContext(options, labelMap) + val ctx = new AssemblyMatchingContext(optimizationContext.options, optimizationContext.labelMap, optimizationContext.niceFunctionProperties) rule.pattern.matchTo(ctx, code) match { case Some(rest: List[(FlowInfo, AssemblyLine)]) => val matchedChunkToOptimize: List[AssemblyLine] = code.take(code.length - rest.length).map(_._2) @@ -59,19 +60,40 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf ErrorReporting.trace(" ↓") optimizedChunk.filter(_.isPrintable).foreach(l => ErrorReporting.trace(l.toString)) if (needsFlowInfo != FlowInfoRequirement.NoRequirement) { - return optimizedChunk ++ optimizeImpl(f, rest, options, labelMap) + return optimizedChunk ++ optimizeImpl(f, rest, optimizationContext) } else { - return optimize(f, optimizedChunk ++ rest.map(_._2), options) + return optimize(f, optimizedChunk ++ rest.map(_._2), optimizationContext) } case None => () } } - head._2 :: optimizeImpl(f, tail, options, labelMap) + head._2 :: optimizeImpl(f, tail, optimizationContext) } } } -class AssemblyMatchingContext(val compilationOptions: CompilationOptions, val labelMap: Map[String, Int]) { +class AssemblyMatchingContext(val compilationOptions: CompilationOptions, + val labelMap: Map[String, Int], + val niceFunctionProperties: Set[(NiceFunctionProperty, String)]) { + + def functionChangesA(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntChangeA -> name) + + def functionChangesX(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntChangeX -> name) + + def functionChangesY(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntChangeY -> name) + + def functionChangesIZ(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntChangeIZ -> name) + + def functionChangesAH(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntChangeAH -> name) + + def functionChangesC(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntChangeC -> name) + + def functionReadsD(name: String): Boolean = !niceFunctionProperties(MosNiceFunctionProperty.DoesntConcernD -> name) + + def functionChangesMemory(name: String): Boolean = !niceFunctionProperties(NiceFunctionProperty.DoesntWriteMemory -> name) + + def functionReadsMemory(name: String): Boolean = !niceFunctionProperties(NiceFunctionProperty.DoesntReadMemory -> name) + private val map = mutable.Map[Int, Any]() override def toString: String = map.mkString(", ") @@ -310,7 +332,7 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte } -case class Where(predicate: (AssemblyMatchingContext => Boolean)) extends AssemblyPattern { +case class Where(predicate: AssemblyMatchingContext => Boolean) extends AssemblyPattern { def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = { if (predicate(ctx)) Some(code) else None } @@ -442,11 +464,12 @@ trait AssemblyLinePattern extends AssemblyPattern { def &(x: AssemblyLinePattern): AssemblyLinePattern = Both(this, x) } +//noinspection ScalaUnnecessaryParentheses trait TrivialAssemblyLinePattern extends AssemblyLinePattern with (AssemblyLine => Boolean) { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = this (line) } -case class Match(predicate: (AssemblyMatchingContext => Boolean)) extends AssemblyLinePattern { +case class Match(predicate: AssemblyMatchingContext => Boolean) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = predicate(ctx) override def toString: String = "Match(...)" @@ -460,11 +483,6 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext } } -//noinspection LanguageFeature -object AssemblyLinePattern { - implicit def __implicitOpcodeIn(ops: Set[Opcode.Value]): AssemblyLinePattern = HasOpcodeIn(ops) -} - case class MatchA(i: Int) extends AssemblyLinePattern { override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = FlowInfoRequirement.assertForward(needsFlowInfo) @@ -777,14 +795,108 @@ case object ConcernsIZ extends TrivialAssemblyLinePattern { OpcodeClasses.ConcernsIZAlways(line.opcode) || IZAddrModes(line.addrMode) } -case object ChangesA extends TrivialAssemblyLinePattern { - override def apply(line: AssemblyLine): Boolean = - OpcodeClasses.ChangesAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ChangesAIfImplied(line.opcode) +case object ChangesA extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import Opcode._ + import AddrMode._ + line match { + case AssemblyLine(JSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionChangesA(th.name) + case AssemblyLine(_, Implied, _, _) => OpcodeClasses.ChangesAIfImplied(line.opcode) || OpcodeClasses.ChangesAAlways(line.opcode) + case _ => OpcodeClasses.ChangesAAlways(line.opcode) + } + } } -case object ChangesAH extends TrivialAssemblyLinePattern { - override def apply(line: AssemblyLine): Boolean = - OpcodeClasses.ChangesAHAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ChangesAHIfImplied(line.opcode) +case object ChangesX extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import Opcode._ + import AddrMode._ + line match { + case AssemblyLine(JSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionChangesX(th.name) + case _ => OpcodeClasses.ChangesX(line.opcode) + } + } +} + +case object ChangesY extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import Opcode._ + import AddrMode._ + line match { + case AssemblyLine(JSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionChangesY(th.name) + case _ => OpcodeClasses.ChangesY(line.opcode) + } + } +} + +case object ReadsNOrZ extends HasOpcodeIn(OpcodeClasses.ReadsNOrZ) + +case object ReadsC extends HasOpcodeIn(OpcodeClasses.ReadsC) + +case object ReadsD extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import Opcode._ + import AddrMode._ + line match { + case AssemblyLine(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionReadsD(th.name) + case _ => OpcodeClasses.ReadsD(line.opcode) + } + } +} + +case object ReadsV extends HasOpcodeIn(OpcodeClasses.ReadsV) + +case object ChangesNAndZ extends HasOpcodeIn(OpcodeClasses.ChangesNAndZ) + +case object ChangesC extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import Opcode._ + import AddrMode._ + line match { + case AssemblyLine(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionChangesC(th.name) + case _ => OpcodeClasses.ChangesC(line.opcode) + } + } +} + +case object ChangesV extends HasOpcodeIn(OpcodeClasses.ChangesV) + +case object ChangesStack extends HasOpcodeIn(OpcodeClasses.ChangesStack) + +case object ChangesIZ extends HasOpcodeIn(OpcodeClasses.ChangesIZ) + +case object ChangesS extends HasOpcodeIn(OpcodeClasses.ChangesS) + +case object SupportsAbsolute extends HasOpcodeIn(OpcodeClasses.SupportsAbsolute) + +case object SupportsAbsoluteX extends HasOpcodeIn(OpcodeClasses.SupportsAbsoluteX) + +case object SupportsAbsoluteY extends HasOpcodeIn(OpcodeClasses.SupportsAbsoluteY) + +case object ShortConditionalBranching extends HasOpcodeIn(OpcodeClasses.ShortConditionalBranching) + +case object ShortBranching extends HasOpcodeIn(OpcodeClasses.ShortBranching) + +case object OverwritesA extends HasOpcodeIn(OpcodeClasses.OverwritesA) + +case object OverwritesX extends HasOpcodeIn(OpcodeClasses.OverwritesX) + +case object OverwritesY extends HasOpcodeIn(OpcodeClasses.OverwritesY) + +case object NoopDiscardsFlags extends HasOpcodeIn(OpcodeClasses.NoopDiscardsFlags) + +case object NoopDiscardsFlagsOrLabel extends HasOpcodeIn(OpcodeClasses.NoopDiscardsFlags + Opcode.LABEL) + +case object ChangesAH extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import Opcode._ + import AddrMode._ + line match { + case AssemblyLine(JSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionChangesAH(th.name) + case AssemblyLine(_, Implied, _, _) => OpcodeClasses.ChangesAHIfImplied(line.opcode) || OpcodeClasses.ChangesAHAlways(line.opcode) + case _ => OpcodeClasses.ChangesAHAlways(line.opcode) + } + } } case object ChangesM extends TrivialAssemblyLinePattern { @@ -801,34 +913,57 @@ case object ChangesW extends TrivialAssemblyLinePattern { case _ => false } } -case object ChangesMemory extends TrivialAssemblyLinePattern { - override def apply(line: AssemblyLine): Boolean = - OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode) + +case object ChangesMemory extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import AddrMode._ + import Opcode._ + line match { + case AssemblyLine(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => ctx.functionChangesMemory(th.name) + case AssemblyLine(op, Implied, _, _) => OpcodeClasses.ChangesMemoryAlways(op) + case AssemblyLine(op, _, _, _) => OpcodeClasses.ChangesMemoryAlways(op) || OpcodeClasses.ChangesMemoryIfNotImplied(op) + case _ => false + } + } } case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int, opcode: Opcode.Value = Opcode.NOP) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { - val p1 = ctx.get[Constant](param1) - val a1 = ctx.get[AddrMode.Value](addrMode1) - val changesSomeMemory = OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode) - // TODO: NOP - // this will break if the actual instruction was 16-bit - !changesSomeMemory || HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(opcode, a1, p1), line) + import AddrMode._ + import Opcode._ + line match { + case AssemblyLine(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => !ctx.functionChangesMemory(th.name) + case _ => + val p1 = ctx.get[Constant](param1) + val a1 = ctx.get[AddrMode.Value](addrMode1) + val changesSomeMemory = OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode) + // TODO: NOP + // this will break if the actual instruction was 16-bit + !changesSomeMemory || HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(opcode, a1, p1), line) + } } } -case object ConcernsMemory extends TrivialAssemblyLinePattern { - override def apply(line: AssemblyLine): Boolean = - ReadsMemory(line) || ChangesMemory(line) +case object ConcernsMemory extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = + ReadsMemory.matchLineTo(ctx, flowInfo, line) || ChangesMemory.matchLineTo(ctx, flowInfo, line) } case class DoesNotConcernMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { - val p1 = ctx.get[Constant](param1) - val a1 = ctx.get[AddrMode.Value](addrMode1) - // TODO: NOP - // this will break if the actual instruction was 16-bit - HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(Opcode.NOP, a1, p1), line) + { + import AddrMode._ + import Opcode._ + line match { + case AssemblyLine(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => !ctx.functionReadsMemory(th.name) && !ctx.functionChangesMemory(th.name) + case _ => + val p1 = ctx.get[Constant](param1) + val a1 = ctx.get[AddrMode.Value](addrMode1) + // TODO: NOP + // this will break if the actual instruction was 16-bit + HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(Opcode.NOP, a1, p1), line) + } + } } } @@ -931,11 +1066,20 @@ case class CallsAnyExcept(identifiers: Set[String]) extends TrivialAssemblyLineP override def toString: String = identifiers.mkString("(JSR ¬{", ",", "})") } -case class HasOpcodeIn(ops: Set[Opcode.Value]) extends TrivialAssemblyLinePattern { +class HasOpcodeIn(val ops: Set[Opcode.Value]) extends TrivialAssemblyLinePattern { override def apply(line: AssemblyLine): Boolean = ops(line.opcode) override def toString: String = ops.mkString("{", ",", "}") + + def |(that: HasOpcodeIn): HasOpcodeIn = new HasOpcodeIn(ops ++ that.ops) + + def --(that: HasOpcodeIn): HasOpcodeIn = new HasOpcodeIn(ops -- that.ops) +} + +object HasOpcodeIn { + def apply(ops: Opcode.Value*): HasOpcodeIn = new HasOpcodeIn(ops.toSet) + def apply(ops: Set[Opcode.Value]): HasOpcodeIn = new HasOpcodeIn(ops) } case class HasAddrMode(am: AddrMode.Value) extends TrivialAssemblyLinePattern { @@ -950,6 +1094,13 @@ case class HasAddrModeIn(ams: Set[AddrMode.Value]) extends TrivialAssemblyLinePa ams(line.addrMode) override def toString: String = ams.mkString("{", ",", "}") + + def |(that: HasAddrModeIn): HasAddrModeIn = HasAddrModeIn(ams ++ that.ams) + + def --(that: HasAddrModeIn): HasAddrModeIn = HasAddrModeIn(ams -- that.ams) +} +object HasAddrModeIn { + def apply(ams: AddrMode.Value*): HasAddrModeIn = HasAddrModeIn(ams.toSet) } case class HasImmediate(i: Int) extends TrivialAssemblyLinePattern { @@ -1034,11 +1185,11 @@ case class MatchNumericImmediate(i: Int) extends AssemblyLinePattern { case class DoesntChangeIndexingInAddrMode(i: Int) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = ctx.get[AddrMode.Value](i) match { - case AddrMode.ZeroPageX | AddrMode.AbsoluteX | AddrMode.LongAbsoluteX | AddrMode.IndexedX | AddrMode.AbsoluteIndexedX => !OpcodeClasses.ChangesX.contains(line.opcode) - case AddrMode.ZeroPageY | AddrMode.AbsoluteY | AddrMode.IndexedY | AddrMode.LongIndexedY => !OpcodeClasses.ChangesY.contains(line.opcode) - case AddrMode.IndexedZ | AddrMode.LongIndexedZ => !OpcodeClasses.ChangesIZ.contains(line.opcode) + case AddrMode.ZeroPageX | AddrMode.AbsoluteX | AddrMode.LongAbsoluteX | AddrMode.IndexedX | AddrMode.AbsoluteIndexedX => !ChangesX.matchLineTo(ctx, flowInfo, line) + case AddrMode.ZeroPageY | AddrMode.AbsoluteY | AddrMode.IndexedY | AddrMode.LongIndexedY => !ChangesY.matchLineTo(ctx, flowInfo, line) + case AddrMode.IndexedZ | AddrMode.LongIndexedZ => !ChangesIZ.matchLineTo(ctx, flowInfo, line) case AddrMode.Stack => !OpcodeClasses.ChangesS.contains(line.opcode) - case AddrMode.IndexedSY => !OpcodeClasses.ChangesS.contains(line.opcode) && !OpcodeClasses.ChangesY.contains(line.opcode) + case AddrMode.IndexedSY => !OpcodeClasses.ChangesS.contains(line.opcode) && !ChangesY.matchLineTo(ctx, flowInfo, line) case _ => true } diff --git a/src/main/scala/millfork/assembly/mos/opt/SingleAssignmentVariableOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/SingleAssignmentVariableOptimization.scala index 8b2f4fe3..4c33702b 100644 --- a/src/main/scala/millfork/assembly/mos/opt/SingleAssignmentVariableOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/SingleAssignmentVariableOptimization.scala @@ -33,7 +33,7 @@ object SingleAssignmentVariableOptimization extends AssemblyOptimization[Assembl override def name = "Single assignment variable optimization" - override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = { + override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { val paramVariables = f.params match { case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 => Set[String]() @@ -65,7 +65,7 @@ object SingleAssignmentVariableOptimization extends AssemblyOptimization[Assembl val slice = code.slice(lifetime.start, lifetime.end) slice.forall(l => GoodOpcodes.contains(l.opcode) || !BadOpcodes.contains(l.opcode) && source.forall(s => HelperCheckers.memoryAccessDoesntOverlap(s,l))) }.flatMap{case (v,source) => - replaceVariable(v.name, source, code.zip(ReverseFlowAnalyzer.analyze(f, code))).map(v -> _) + replaceVariable(v.name, source, code.zip(ReverseFlowAnalyzer.analyze(f, code, optimizationContext))).map(v -> _) } if (finalGoodVariables.isEmpty) return code diff --git a/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala index 12b04823..03ec4d44 100644 --- a/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala @@ -33,52 +33,52 @@ object SixteenOptimizations { val RepSepWeakening = new RuleBasedAssemblyOptimization("REP/SEP weakening", needsFlowInfo = FlowInfoRequirement.BothFlows, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & HasImmediate(0)) ~~> (_ => Nil), + (Elidable & HasOpcodeIn(SEP, REP) & HasImmediate(0)) ~~> (_ => Nil), (HasOpcode(SEP) & HasImmediate(0x20)) ~ - (Linear & Not(HasOpcodeIn(Set(SEP, REP, PLP)))).* ~ + (Linear & Not(HasOpcodeIn(SEP, REP, PLP))).* ~ (Elidable & HasOpcode(SEP) & HasImmediate(0x20)) ~~> (_.init), (HasOpcode(REP) & HasImmediate(0x20)) ~ - (Linear & Not(HasOpcodeIn(Set(SEP, REP, PLP)))).* ~ + (Linear & Not(HasOpcodeIn(SEP, REP, PLP))).* ~ (Elidable & HasOpcode(REP) & HasImmediate(0x20)) ~~> (_.init), (HasOpcode(SEP) & HasImmediate(0x10)) ~ - (Linear & Not(HasOpcodeIn(Set(SEP, REP, PLP)))).* ~ + (Linear & Not(HasOpcodeIn(SEP, REP, PLP))).* ~ (Elidable & HasOpcode(SEP) & HasImmediate(0x10)) ~~> (_.init), (HasOpcode(REP) & HasImmediate(0x10)) ~ - (Linear & Not(HasOpcodeIn(Set(SEP, REP, PLP)))).* ~ + (Linear & Not(HasOpcodeIn(SEP, REP, PLP))).* ~ (Elidable & HasOpcode(REP) & HasImmediate(0x10)) ~~> (_.init), - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.C)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.C)) ~ Where(c => c.get[Int](0).&(0x1).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0xFE if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) }, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.Z)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.Z)) ~ Where(c => c.get[Int](0).&(0x2).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0xFD if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) }, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.D)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.D)) ~ Where(c => c.get[Int](0).&(0x8).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0xF7 if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) }, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.W)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.W)) ~ Where(c => c.get[Int](0).&(0x10).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0xEF if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) }, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.M)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.M)) ~ Where(c => c.get[Int](0).&(0x20).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0xDF if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) }, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.V)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.V)) ~ Where(c => c.get[Int](0).&(0x40).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0xBF if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) }, - (Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.N)) ~ + (Elidable & HasOpcodeIn(SEP, REP) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.N)) ~ Where(c => c.get[Int](0).&(0x80).!=(0)) ~~> { (code, ctx) => val i = ctx.get[Int](0) & 0x7F if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i)) @@ -122,11 +122,11 @@ object SixteenOptimizations { val PointlessLoadAfterLoadOrStore = new RuleBasedAssemblyOptimization("Pointless 16-bit load after load or store", needsFlowInfo = FlowInfoRequirement.NoRequirement, - (HasOpcodeIn(Set(LDA_W, STA_W)) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~ + (HasOpcodeIn(LDA_W, STA_W) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~ (Linear & Not(ChangesA) & Not(ChangesAH)).* ~ (Elidable & HasOpcode(LDA_W) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~~> (_.init), - (HasOpcodeIn(Set(LDA_W, STA_W)) & MatchAddrMode(0) & MatchParameter(1)) ~ + (HasOpcodeIn(LDA_W, STA_W) & MatchAddrMode(0) & MatchParameter(1)) ~ (Linear & Not(ChangesA) & Not(ChangesAH) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1, LDA_W)).* ~ (Elidable & HasOpcode(LDA_W) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init), ) @@ -136,7 +136,7 @@ object SixteenOptimizations { (Elidable & HasY(0) /*& HasZ(0)*/ & HasIndex8 & HasAddrMode(LongIndexedY) & HasOpcodeIn(SupportsLongIndexedZ)) ~~> (code => code.map(_.copy(addrMode = LongIndexedZ))), ) - private val SupportsStackAddressing = Set( + private val SupportsStackAddressing = HasOpcodeIn( ADC, AND, EOR, ORA, LDA, STA, SBC, CMP, ) diff --git a/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala b/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala index 5aae02d6..207245c5 100644 --- a/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala @@ -1,7 +1,7 @@ package millfork.assembly.mos.opt import millfork.{CompilationFlag, CompilationOptions, OptimizationPresets} -import millfork.assembly.AssemblyOptimization +import millfork.assembly.{AssemblyOptimization, OptimizationContext} import millfork.assembly.mos.{AddrMode, AssemblyLine, Opcode} import millfork.env.NormalFunction import millfork.error.ErrorReporting @@ -13,7 +13,8 @@ import scala.collection.mutable */ object SuperOptimizer extends AssemblyOptimization[AssemblyLine] { - override def optimize(m: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = { + override def optimize(m: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { + val options = optimizationContext.options val oldVerbosity = ErrorReporting.verbosity ErrorReporting.verbosity = -1 var allOptimizers = OptimizationPresets.Good ++ LaterOptimizations.All @@ -55,6 +56,7 @@ object SuperOptimizer extends AssemblyOptimization[AssemblyLine] { AlwaysGoodOptimizations.UnusedCodeRemoval ) val optionsForMeasurements = options.copy(commandLineFlags = options.commandLineFlags + (CompilationFlag.InternalCurrentlyOptimizingForMeasurement -> true)) + val optimizationContextForMeasurements = optimizationContext.copy(options = optionsForMeasurements) val quicklyCleanedCode = quickScrub.foldLeft(code)((c, o) => o.optimize(m, c, optionsForMeasurements)) seenSoFar += viewCode(quicklyCleanedCode) queue.enqueue(quickScrub.reverse -> quicklyCleanedCode) @@ -64,7 +66,7 @@ object SuperOptimizer extends AssemblyOptimization[AssemblyLine] { var isLeaf = true (if (optionsForMeasurements.flag(CompilationFlag.SingleThreaded)) allOptimizers else allOptimizers.par).foreach { o => - val optimized = o.optimize(m, codeSoFar, optionsForMeasurements) + val optimized = o.optimize(m, codeSoFar, optimizationContextForMeasurements) val view = viewCode(optimized) seenSoFar.synchronized{ if (!seenSoFar(view)) { diff --git a/src/main/scala/millfork/assembly/mos/opt/VariableToRegisterOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/VariableToRegisterOptimization.scala index 861ef03b..c3c69e49 100644 --- a/src/main/scala/millfork/assembly/mos/opt/VariableToRegisterOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/VariableToRegisterOptimization.scala @@ -1,12 +1,13 @@ package millfork.assembly.mos.opt -import millfork.{CompilationFlag, CompilationOptions, NonOverlappingIntervals} -import millfork.assembly.AssemblyOptimization +import millfork.{CompilationFlag, NonOverlappingIntervals} +import millfork.assembly.{AssemblyOptimization, OptimizationContext} import millfork.assembly.mos._ import millfork.assembly.mos.Opcode._ import AddrMode._ import millfork.env._ import millfork.error.ErrorReporting +import millfork.node.MosNiceFunctionProperty import scala.collection.mutable.ListBuffer @@ -22,12 +23,19 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] def +(that: CyclesAndBytes) = CyclesAndBytes(this.bytes + that.bytes, this.cycles + that.cycles) } - case class Features( + case class FeaturesForIndexRegisters( blastProcessing: Boolean, izIsAlwaysZero: Boolean, indexRegisterTransfers: Boolean, + functionsSafeForX: Set[String], + functionsSafeForY: Set[String], + functionsSafeForZ: Set[String], identityArray: Constant) + case class FeaturesForAccumulator( + cmos: Boolean, + safeFunctions: Set[String]) + // If any of these opcodes is present within a method, // then it's too hard to assign any variable to a register. private val opcodesThatAlwaysPrecludeXAllocation = Set( @@ -103,7 +111,8 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] override def name = "Allocating variables to index registers" - override def optimize(f: NormalFunction, code: List[AssemblyLine], options: CompilationOptions): List[AssemblyLine] = { + override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = { + val options = optimizationContext.options val paramVariables = f.params match { case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 => Set[String]() @@ -138,23 +147,30 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] val removeVariablesForReal = !options.flag(CompilationFlag.InternalCurrentlyOptimizingForMeasurement) val costFunction: CyclesAndBytes => Int = if (options.flag(CompilationFlag.OptimizeForSpeed)) _.cycles else _.bytes - val importances = ReverseFlowAnalyzer.analyze(f, code) + val importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext) val blastProcessing = options.flag(CompilationFlag.OptimizeForSonicSpeed) val identityArray = f.environment.maybeGet[ThingInMemory]("identity$").map(MemoryAddressConstant).getOrElse(Constant.Zero) val izIsAlwaysZero = !options.flag(CompilationFlag.Emit65CE02Opcodes) - val features = Features( + val featuresForIndices = FeaturesForIndexRegisters( blastProcessing = blastProcessing, izIsAlwaysZero = izIsAlwaysZero, indexRegisterTransfers = options.flag(CompilationFlag.EmitEmulation65816Opcodes), + functionsSafeForX = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeX).map(_._2), + functionsSafeForY = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeY).map(_._2), + functionsSafeForZ = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeIZ).map(_._2), identityArray = identityArray ) + val featuresForAcc = FeaturesForAccumulator( + cmos = options.flag(CompilationFlag.EmitCmosOpcodes), + safeFunctions = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeA).map(_._2) + ) val xCandidates = variablesWithLifetimes.filter { case (vName, range) => importances(range.start).x != Important }.flatMap { case (vName, range) => - canBeInlined(Some(vName), None, None, features, code.zip(importances).slice(range.start, range.end)).map { score => + canBeInlined(Some(vName), None, None, featuresForIndices, code.zip(importances).slice(range.start, range.end)).map { score => (vName, range, if (variablesWithRegisterHint(vName)) score + CyclesAndBytes(16, 16) else score) } } @@ -164,7 +180,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] importances(range.start).y != Important }.flatMap { case (vName, range) => - canBeInlined(None, Some(vName), None, features, code.zip(importances).slice(range.start, range.end)).map { score => + canBeInlined(None, Some(vName), None, featuresForIndices, code.zip(importances).slice(range.start, range.end)).map { score => (vName, range, if (variablesWithRegisterHint(vName)) score + CyclesAndBytes(16, 16) else score) } } @@ -174,7 +190,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] importances(range.start).iz != Important }.flatMap { case (vName, range) => - canBeInlined(None, None, Some(vName), features, code.zip(importances).slice(range.start, range.end)).map { score => + canBeInlined(None, None, Some(vName), featuresForIndices, code.zip(importances).slice(range.start, range.end)).map { score => (vName, range, if (variablesWithRegisterHint(vName)) score + CyclesAndBytes(16, 16) else score) } } @@ -185,7 +201,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] }.flatMap { case (vName, range) => canBeInlinedToAccumulator( - options, + featuresForAcc, start = true, synced = false, vName, @@ -251,7 +267,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (v, range, _) => ErrorReporting.debug(s"Inlining $v to register X") val oldCode = code.zip(importances).slice(range.start, range.end) - val newCode = inlineVars(Some(v), None, None, None, features, oldCode) + val newCode = inlineVars(Some(v), None, None, None, featuresForIndices, oldCode) reportOptimizedBlock(oldCode, newCode) output ++= newCode i = range.end @@ -265,7 +281,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (v, range, _) => ErrorReporting.debug(s"Inlining $v to register Y") val oldCode = code.zip(importances).slice(range.start, range.end) - val newCode = inlineVars(None, Some(v), None, None, features, oldCode) + val newCode = inlineVars(None, Some(v), None, None, featuresForIndices, oldCode) reportOptimizedBlock(oldCode, newCode) output ++= newCode i = range.end @@ -280,7 +296,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (v, range, _) => ErrorReporting.debug(s"Inlining $v to register Z") val oldCode = code.zip(importances).slice(range.start, range.end) - val newCode = inlineVars(None, None, Some(v), None, features, oldCode) + val newCode = inlineVars(None, None, Some(v), None, featuresForIndices, oldCode) reportOptimizedBlock(oldCode, newCode) output ++= newCode i = range.end @@ -295,7 +311,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (v, range, _) => ErrorReporting.debug(s"Inlining $v to register A") val oldCode = code.zip(importances).slice(range.start, range.end) - val newCode = inlineVars(None, None, None, Some(v), features, oldCode) + val newCode = inlineVars(None, None, None, Some(v), featuresForIndices, oldCode) reportOptimizedBlock(oldCode, newCode) output ++= newCode i = range.end @@ -321,7 +337,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] } // TODO: STA has different flag behaviour than TAX, keep it in mind! - def canBeInlined(xCandidate: Option[String], yCandidate: Option[String], zCandidate: Option[String], features: Features, lines: List[(AssemblyLine, CpuImportance)]): Option[CyclesAndBytes] = { + def canBeInlined(xCandidate: Option[String], yCandidate: Option[String], zCandidate: Option[String], features: FeaturesForIndexRegisters, lines: List[(AssemblyLine, CpuImportance)]): Option[CyclesAndBytes] = { val vx = xCandidate.getOrElse("-") val vy = yCandidate.getOrElse("-") val vz = zCandidate.getOrElse("-") @@ -369,17 +385,17 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] } case (AssemblyLine(opcode, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs - if th.name == vx && (opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) => + if th.name == vx && (opcode == LDY || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) => // if a variable is used by some opcodes, then it cannot be assigned to a register None case (AssemblyLine(opcode, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs - if th.name == vy && (opcode == LDX || opcode == LAX || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) => + if th.name == vy && (opcode == LDX || opcode == LAX || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) => // if a variable is used by some opcodes, then it cannot be assigned to a register None case (AssemblyLine(opcode, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs - if th.name == vz && (opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) => + if th.name == vz && (opcode == LDX || opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) => // if a variable is used by some opcodes, then it cannot be assigned to a register None @@ -547,6 +563,14 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] // labels always end the initial section canBeInlined(xCandidate, yCandidate, zCandidate, features, xs) + case (AssemblyLine(JSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _), _) :: xs => + if ( + xCandidate.isDefined && features.functionsSafeForX(th.name) || + yCandidate.isDefined && features.functionsSafeForY(th.name) || + zCandidate.isDefined && features.functionsSafeForZ(th.name) + ) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs) + else None + case (x, _) :: xs => if (xCandidate.isDefined && opcodesThatAlwaysPrecludeXAllocation(x.opcode)) { None @@ -562,13 +586,12 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] } } - def canBeInlinedToAccumulator(options: CompilationOptions, start: Boolean, synced: Boolean, candidate: String, lines: List[(AssemblyLine, CpuImportance)]): Option[CyclesAndBytes] = { - val cmos = options.flags(CompilationFlag.EmitCmosOpcodes) + def canBeInlinedToAccumulator(features: FeaturesForAccumulator, start: Boolean, synced: Boolean, candidate: String, lines: List[(AssemblyLine, CpuImportance)]): Option[CyclesAndBytes] = { lines match { case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), true),_) :: xs if th.name == candidate && start || synced => - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4)) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4)) case (AssemblyLine(op, _, _, _),_) :: xs if opcodesThatAlwaysPrecludeAAllocation(op) => None @@ -589,35 +612,35 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] None case (AssemblyLine(SEP | REP, Immediate, NumericConstant(nn, _), _), _) :: xs => - if ((nn & 0x20) == 0) canBeInlinedToAccumulator(options, start = false, synced = synced, candidate, xs) + if ((nn & 0x20) == 0) canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs) else None case (AssemblyLine(SEP | REP, _, _, _), _) :: xs => None case (AssemblyLine(STA, _, MemoryAddressConstant(th), elidable) ,_):: xs if th.name == candidate => if (synced && elidable) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) } else { None } case (AssemblyLine(DCP, Absolute | ZeroPage, MemoryAddressConstant(th), _) ,_):: xs if th.name == candidate => if (synced) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs) } else { None } case (AssemblyLine(STA | SAX, _, MemoryAddressConstant(th), elidable) ,_):: xs if th.name != candidate => if (synced) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs) } else { None } case (AssemblyLine(STA | SAX, _, NumericConstant(_, _), _) ,_):: xs => if (synced) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs) } else { None } @@ -629,7 +652,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (AssemblyLine(TAX | TAY, _, _, _),_) :: xs => if (synced) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs) } else { None } @@ -638,30 +661,30 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] if th.name == candidate => // removing LDA saves 3 bytes if (imp.z == Unimportant && imp.n == Unimportant) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) } else { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2)) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2)) } case (AssemblyLine(LDA, _, _, elidable),_) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidable2),_) :: xs if opcodesCommutative(op) => if (th.name == candidate) { - if (elidable && elidable2) canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) + if (elidable && elidable2) canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) else None - } else canBeInlinedToAccumulator(options, start = false, synced = synced, candidate, xs) + } else canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs) case (AssemblyLine(LDA, _, _, elidable),_) :: (AssemblyLine(CLC, _, _, _),_) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidable2),_) :: xs if opcodesCommutative(op) => if (th.name == candidate) { - if (elidable && elidable2) canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) + if (elidable && elidable2) canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4)) else None - } else canBeInlinedToAccumulator(options, start = false, synced = synced, candidate, xs) + } else canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs) case (AssemblyLine(LDX | LDY | LAX, Absolute | ZeroPage, MemoryAddressConstant(th), elidable),_) :: xs if th.name == candidate => // converting a load into a transfer saves 2 bytes if (elidable) { - canBeInlinedToAccumulator(options, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2)) + canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2)) } else { None } @@ -673,15 +696,15 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (AssemblyLine(ASL | LSR | ROR | ROL, Absolute | ZeroPage, MemoryAddressConstant(th), elidable),_) :: xs if th.name == candidate => if (elidable) { - canBeInlinedToAccumulator(options, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4)) + canBeInlinedToAccumulator(features, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4)) } else { None } case (AssemblyLine(INC | DEC, Absolute | ZeroPage, MemoryAddressConstant(th), elidable),_) :: xs if th.name == candidate => - if (cmos && elidable) { - canBeInlinedToAccumulator(options, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4)) + if (features.cmos && elidable) { + canBeInlinedToAccumulator(features, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4)) } else { None } @@ -689,12 +712,16 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case (AssemblyLine(TXA | TYA, _, _, elidable), imp) :: xs => if (imp.a == Unimportant && imp.c == Unimportant && imp.v == Unimportant && elidable) { // TYA/TXA has to be converted to CPY#0/CPX#0 - canBeInlinedToAccumulator(options, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = -1, cycles = 0)) + canBeInlinedToAccumulator(features, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = -1, cycles = 0)) } else { None } - case (x, _) :: xs => canBeInlinedToAccumulator(options, start = false, synced = synced && OpcodeClasses.AllLinear(x.opcode), candidate, xs) + case (AssemblyLine(JSR, Absolute | LongAbsolute, MemoryAddressConstant(th), _), _) :: xs => + if (features.safeFunctions(th.name)) canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs) + else None + + case (x, _) :: xs => canBeInlinedToAccumulator(features, start = false, synced = synced && OpcodeClasses.AllLinear(x.opcode), candidate, xs) case Nil => Some(CyclesAndBytes.Zero) } @@ -706,7 +733,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] case _ => true } - def inlineVars(xCandidate: Option[String], yCandidate: Option[String], zCandidate: Option[String], aCandidate: Option[String], features: Features, lines: List[(AssemblyLine, CpuImportance)]): List[AssemblyLine] = { + def inlineVars(xCandidate: Option[String], yCandidate: Option[String], zCandidate: Option[String], aCandidate: Option[String], features: FeaturesForIndexRegisters, lines: List[(AssemblyLine, CpuImportance)]): List[AssemblyLine] = { val vx = xCandidate.getOrElse("-") val vy = yCandidate.getOrElse("-") val vz = zCandidate.getOrElse("-") diff --git a/src/main/scala/millfork/assembly/opt/Status.scala b/src/main/scala/millfork/assembly/opt/Status.scala index 6e3fae6d..b7358b87 100644 --- a/src/main/scala/millfork/assembly/opt/Status.scala +++ b/src/main/scala/millfork/assembly/opt/Status.scala @@ -124,6 +124,18 @@ object Status { case SingleStatus(v) => v.iz case _ => false } + def butNotA: Status[SourceOfNZ] = inner match { + case SingleStatus(v) => if (v.a || v.aw) AnyStatus else inner + case _ => inner + } + def butNotX: Status[SourceOfNZ] = inner match { + case SingleStatus(v) => if (v.x) AnyStatus else inner + case _ => inner + } + def butNotY: Status[SourceOfNZ] = inner match { + case SingleStatus(v) => if (v.y) AnyStatus else inner + case _ => inner + } } implicit class IntStatusOps(val inner: Status[Int]) extends AnyVal { diff --git a/src/main/scala/millfork/compiler/CompilationContext.scala b/src/main/scala/millfork/compiler/CompilationContext.scala index e2bdd0d0..6ecfb2cd 100644 --- a/src/main/scala/millfork/compiler/CompilationContext.scala +++ b/src/main/scala/millfork/compiler/CompilationContext.scala @@ -1,6 +1,7 @@ package millfork.compiler import millfork.env.{Environment, Label, NormalFunction} +import millfork.node.NiceFunctionProperty import millfork.{CompilationFlag, CompilationOptions} /** @@ -10,6 +11,7 @@ case class CompilationContext(env: Environment, function: NormalFunction, extraStackOffset: Int, options: CompilationOptions, + niceFunctionProperties: Set[(NiceFunctionProperty, String)], breakLabels: Map[String, Label] = Map(), continueLabels: Map[String, Label] = Map()){ def withInlinedEnv(environment: Environment, newLabel: String): CompilationContext = { diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index d8f979d8..4a101711 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -64,6 +64,24 @@ case class HalfWordExpression(expression: Expression, hiByte: Boolean) extends E HalfWordExpression(expression.replaceVariable(variable, actualParam), hiByte) } +sealed class NiceFunctionProperty(override val toString: String) + +object NiceFunctionProperty { + case object DoesntReadMemory extends NiceFunctionProperty("MR") + case object DoesntWriteMemory extends NiceFunctionProperty("MW") +} + +object MosNiceFunctionProperty { + case object DoesntChangeA extends NiceFunctionProperty("A") + case object DoesntChangeX extends NiceFunctionProperty("X") + case object DoesntChangeY extends NiceFunctionProperty("Y") + case object DoesntChangeIZ extends NiceFunctionProperty("Z") + case object DoesntChangeAH extends NiceFunctionProperty("AH") + case object DoesntChangeC extends NiceFunctionProperty("C") + case object DoesntConcernD extends NiceFunctionProperty("D") + case object DoesntChangeZpRegister extends NiceFunctionProperty("reg") +} + object MosRegister extends Enumeration { val A, X, Y, AX, AY, YA, XA, XY, YX, AW = Value } @@ -72,7 +90,7 @@ object ZRegister extends Enumeration { val A, B, C, D, E, H, L, AF, BC, HL, DE, SP, IXH, IXL, IYH, IYL, IX, IY, R, I, MEM_HL, MEM_BC, MEM_DE, MEM_IX_D, MEM_IY_D, MEM_ABS_8, MEM_ABS_16, IMM_8, IMM_16 = Value - def size(reg: Value) = reg match { + def size(reg: Value): Int = reg match { case AF | BC | DE | HL | IX | IY | IMM_16 => 2 case A | B | C | D | E | H | L | IXH | IXL | IYH | IYL | R | I | IMM_8 => 1 } diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index d7d2369e..944a7cf3 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -4,7 +4,7 @@ import millfork.assembly._ import millfork.compiler.{AbstractCompiler, CompilationContext} import millfork.env._ import millfork.error.ErrorReporting -import millfork.node.{CallGraph, Program} +import millfork.node.{CallGraph, NiceFunctionProperty, Program} import millfork._ import scala.collection.mutable @@ -97,7 +97,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program ??? } } catch { - case e: StackOverflowError => + case _: StackOverflowError => ErrorReporting.fatal("Stack overflow " + c) } case UnexpandedConstant(name, _) => @@ -200,9 +200,10 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program var inlinedFunctions = Map[String, List[T]]() val compiledFunctions = mutable.Map[String, List[T]]() val recommendedCompilationOrder = callGraph.recommendedCompilationOrder + val niceFunctionProperties = mutable.Set[(NiceFunctionProperty, String)]() recommendedCompilationOrder.foreach { f => env.maybeGet[NormalFunction](f).foreach { function => - val code = compileFunction(function, optimizations, options, inlinedFunctions, labelMap.toMap) + val code = compileFunction(function, optimizations, options, inlinedFunctions, labelMap.toMap, niceFunctionProperties.toSet) val strippedCodeForInlining = for { limit <- potentiallyInlineable.get(f) if code.map(_.sizeInBytes).sum <= limit @@ -217,10 +218,18 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program nonInlineableFunctions += function.name compiledFunctions(f) = code optimizedCodeSize += code.map(_.sizeInBytes).sum + if (options.flag(CompilationFlag.InterproceduralOptimization)) { + gatherNiceFunctionProperties(niceFunctionProperties, f, code) + } } function.environment.removedThings.foreach(env.removeVariable) } } + if (ErrorReporting.traceEnabled) { + niceFunctionProperties.toList.groupBy(_._2).mapValues(_.map(_._1).sortBy(_.toString)).toList.sortBy(_._1).foreach{ case (fname, properties) => + ErrorReporting.trace(fname.padTo(30, ' ') + properties.mkString(" ")) + } + } rootEnv.things.foreach{case (name, thing) => if (!env.things.contains(name)) { @@ -442,20 +451,27 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program def injectLabels(labelMap: Map[String, Int], code: List[T]): List[T] - private def compileFunction(f: NormalFunction, optimizations: Seq[AssemblyOptimization[T]], options: CompilationOptions, inlinedFunctions: Map[String, List[T]], labelMap: Map[String, Int]): List[T] = { + private def compileFunction(f: NormalFunction, + optimizations: Seq[AssemblyOptimization[T]], + options: CompilationOptions, + inlinedFunctions: Map[String, List[T]], + labelMap: Map[String, Int], + niceFunctionProperties: Set[(NiceFunctionProperty, String)]): List[T] = { ErrorReporting.debug("Compiling: " + f.name, f.position) val unoptimized: List[T] = injectLabels(labelMap, inliningCalculator.inline( - compiler.compile(CompilationContext(env = f.environment, function = f, extraStackOffset = 0, options = options)), + compiler.compile(CompilationContext(env = f.environment, function = f, extraStackOffset = 0, options = options, niceFunctionProperties = niceFunctionProperties)), inlinedFunctions, compiler)) unoptimizedCodeSize += unoptimized.map(_.sizeInBytes).sum val code = optimizations.foldLeft(unoptimized) { (c, opt) => - opt.optimize(f, c, options, labelMap) + opt.optimize(f, c, OptimizationContext(options, labelMap, niceFunctionProperties)) } performFinalOptimizationPass(f, optimizations.nonEmpty, options, code) } + def gatherNiceFunctionProperties(niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], functionName: String, code: List[T]): Unit + def performFinalOptimizationPass(f: NormalFunction, actuallyOptimize: Boolean, options: CompilationOptions, code: List[T]): List[T] private def outputFunction(bank: String, code: List[T], startFrom: Int, assOut: mutable.ArrayBuffer[String], options: CompilationOptions): Int = { diff --git a/src/main/scala/millfork/output/MosAssembler.scala b/src/main/scala/millfork/output/MosAssembler.scala index 88025bcb..e25a7abf 100644 --- a/src/main/scala/millfork/output/MosAssembler.scala +++ b/src/main/scala/millfork/output/MosAssembler.scala @@ -4,11 +4,12 @@ import millfork.assembly.mos.opt.{HudsonOptimizations, JumpFixing, JumpShortenin import millfork.assembly._ import millfork.env._ import millfork.error.ErrorReporting -import millfork.node.Program +import millfork.node.{MosNiceFunctionProperty, NiceFunctionProperty, Program} import millfork._ -import millfork.assembly.mos.{AddrMode, AssemblyLine, Opcode} +import millfork.assembly.mos.{AddrMode, AssemblyLine, Opcode, OpcodeClasses} import millfork.compiler.mos.MosCompiler +import scala.annotation.tailrec import scala.collection.mutable /** @@ -20,9 +21,10 @@ class MosAssembler(program: Program, override def performFinalOptimizationPass(f: NormalFunction, actuallyOptimize: Boolean, options: CompilationOptions, code: List[AssemblyLine]):List[AssemblyLine] = { + val optimizationContext = OptimizationContext(options, Map(), Set()) if (actuallyOptimize) { - val finalCode = if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.removeLoadZero(code) else code - JumpShortening(f, JumpShortening(f, JumpFixing(f, finalCode, options), options), options) + val finalCode = if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.removeLoadZero(f, code, optimizationContext) else code + JumpShortening(f, JumpShortening(f, JumpFixing(f, finalCode, options), optimizationContext), optimizationContext) } else JumpFixing(f, code, options) } @@ -34,8 +36,8 @@ class MosAssembler(program: Program, case AssemblyLine(BYTE, RawByte, c, _) => writeByte(bank, index, c) index + 1 - case AssemblyLine(BYTE, _, _, _) => ??? - case AssemblyLine(_, RawByte, _, _) => ??? + case AssemblyLine(BYTE, _, _, _) => ErrorReporting.fatal("BYTE opcode failure") + case AssemblyLine(_, RawByte, _, _) => ErrorReporting.fatal("BYTE opcode failure") case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(labelName)), _) => labelMap(labelName) = index index @@ -87,18 +89,93 @@ class MosAssembler(program: Program, case l => l } } + + @tailrec + private def isNaughty(code: List[AssemblyLine]): Boolean = { + import Opcode._ + import AddrMode._ + code match { + case AssemblyLine(JMP | JSR | BSR, Indirect | LongIndirect | AbsoluteIndexedX, _, _) :: _ => true + case AssemblyLine(PHA, _, _, _) :: AssemblyLine(RTS | RTL, _, _, _) :: _ => true + case _ :: xs => isNaughty(xs) + case Nil => false + } + } + + override def gatherNiceFunctionProperties(niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], functionName: String, code: List[AssemblyLine]): Unit = { + import Opcode._ + import AddrMode._ + import MosNiceFunctionProperty._ + import NiceFunctionProperty._ + if (isNaughty(code)) return + val localLabels = code.flatMap { + case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(l)), _) => Some(l) + case _ => None + }.toSet + def genericPropertyScan(niceFunctionProperty: NiceFunctionProperty)(predicate: AssemblyLine => Boolean): Unit = { + val preserved = code.forall { + case AssemblyLine(JSR | BSR | JMP, Absolute | LongAbsolute, MemoryAddressConstant(th), _) => niceFunctionProperties(niceFunctionProperty -> th.name) + case AssemblyLine(JSR | BSR, _, _, _) => false + case AssemblyLine(op, _, MemoryAddressConstant(Label(label)), _) if OpcodeClasses.AllDirectJumps(op) => localLabels(label) + case AssemblyLine(op, _, _, _) if OpcodeClasses.AllDirectJumps(op) => false + case l => predicate(l) + } + if (preserved) { + niceFunctionProperties += (niceFunctionProperty -> functionName) + } + } + genericPropertyScan(DoesntChangeX) { + case AssemblyLine(op, _, _, _) => !OpcodeClasses.ChangesX(op) + } + genericPropertyScan(DoesntChangeY) { + case AssemblyLine(op, _, _, _) => !OpcodeClasses.ChangesY(op) + } + genericPropertyScan(DoesntChangeA) { + case AssemblyLine(op, _, _, _) if OpcodeClasses.ChangesAAlways(op) => false + case AssemblyLine(op, _, Implied, _) if OpcodeClasses.ChangesAIfImplied(op) => false + case _ => true + } + genericPropertyScan(DoesntChangeIZ) { + case AssemblyLine(op, _, _, _) => !OpcodeClasses.ChangesIZ(op) + } + genericPropertyScan(DoesntChangeAH) { + case AssemblyLine(op, _, _, _) if OpcodeClasses.ChangesAHAlways(op) => false + case AssemblyLine(op, _, Implied, _) if OpcodeClasses.ChangesAHIfImplied(op) => false + case _ => true + } + genericPropertyScan(DoesntChangeC) { + case AssemblyLine(SEP | REP, Immediate, NumericConstant(imm, _), _) => (imm & 1) == 0 + case AssemblyLine(SEP | REP, _, _, _) => false + case AssemblyLine(op, _, _, _) => !OpcodeClasses.ChangesC(op) + } + genericPropertyScan(DoesntConcernD) { + case AssemblyLine(SEP | REP, Immediate, NumericConstant(imm, _), _) => (imm & 8) == 0 + case AssemblyLine(SEP | REP, _, _, _) => false + case AssemblyLine(op, _, _, _) => !OpcodeClasses.ReadsD(op) && !OpcodeClasses.OverwritesD(op) + } + genericPropertyScan(DoesntReadMemory) { + case AssemblyLine(op, _, Implied | Immediate | WordImmediate, _) => true + case AssemblyLine(op, _, _, _) if OpcodeClasses.ReadsMemoryIfNotImpliedOrImmediate(op) => false + case _ => true + } + genericPropertyScan(DoesntWriteMemory) { + case AssemblyLine(op, _, Implied | Immediate | WordImmediate, _) => true + case AssemblyLine(op, _, _, _) if OpcodeClasses.ChangesMemoryIfNotImplied(op) || OpcodeClasses.ChangesMemoryAlways(op) => false + case _ => true + } + } } object MosAssembler { - val opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val illegalOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val cmosOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val cmosNopOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val ce02Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val hudsonOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val emulation65816Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() - val native65816Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val illegalOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val cmosOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val cmosNopOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val ce02Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val hudsonOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val emulation65816Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() + private val native65816Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]() def opcodeFor(opcode: Opcode.Value, addrMode: AddrMode.Value, options: CompilationOptions): Byte = { val key = opcode -> addrMode diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index 92d2837d..1586c40a 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -4,7 +4,9 @@ import millfork.{CompilationOptions, Platform} import millfork.assembly.z80.ZLine import millfork.compiler.z80.Z80Compiler import millfork.env.{Environment, NormalFunction} -import millfork.node.Program +import millfork.node.{NiceFunctionProperty, Program} + +import scala.collection.mutable /** * @author Karol Stasiak @@ -20,6 +22,10 @@ class Z80Assembler(program: Program, } override def injectLabels(labelMap: Map[String, Int], code: List[ZLine]): List[ZLine] = code // TODO + + override def gatherNiceFunctionProperties(niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], functionName: String, code: List[ZLine]): Unit = { + // do nothing yet + } } object Z80Assembler { diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 41a29e00..9065b331 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -102,6 +102,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], val options = CompilationOptions(platform, Map( CompilationFlag.EmitIllegals -> this.emitIllegals, CompilationFlag.InlineFunctions -> this.inline, + CompilationFlag.InterproceduralOptimization -> true, CompilationFlag.CompactReturnDispatchParams -> true, CompilationFlag.ZeropagePseudoregister -> true, CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu), @@ -136,7 +137,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], // print unoptimized asm env.allPreallocatables.foreach { case f: NormalFunction => - val unoptimized = MosCompiler.compile(CompilationContext(f.environment, f, 0, options)) + val unoptimized = MosCompiler.compile(CompilationContext(f.environment, f, 0, options, Set())) unoptimizedSize += unoptimized.map(_.sizeInBytes).sum case d: InitializedArray => unoptimizedSize += d.contents.length diff --git a/src/test/scala/millfork/test/emu/EmuZ80Run.scala b/src/test/scala/millfork/test/emu/EmuZ80Run.scala index de64bec2..026b8ce0 100644 --- a/src/test/scala/millfork/test/emu/EmuZ80Run.scala +++ b/src/test/scala/millfork/test/emu/EmuZ80Run.scala @@ -51,7 +51,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio // print unoptimized asm env.allPreallocatables.foreach { case f: NormalFunction => - val unoptimized = Z80Compiler.compile(CompilationContext(f.environment, f, 0, options)) + val unoptimized = Z80Compiler.compile(CompilationContext(f.environment, f, 0, options, Set())) unoptimizedSize += unoptimized.map(_.sizeInBytes).sum case d: InitializedArray => unoptimizedSize += d.contents.length