1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-02-03 11:33:33 +00:00

Interprocedural optimization plus some minor fixes:

– fixed handling LDX/LDY/LDZ when inlining variables into registers
– fixed CLA/CLX/CLY instruction generation
– refactored optimization definitions
This commit is contained in:
Karol Stasiak 2018-06-25 21:29:04 +02:00
parent 57740cc6b4
commit 2500f842e9
30 changed files with 750 additions and 371 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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).

View File

@ -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()))
}

View File

@ -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)

View File

@ -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

View File

@ -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 &&

View File

@ -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)

View File

@ -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)),

View File

@ -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
}
}
}

View File

@ -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)

View File

@ -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")

View File

@ -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)) ~

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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,
)

View File

@ -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)) {

View File

@ -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("-")

View File

@ -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 {

View File

@ -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 = {

View File

@ -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
}

View File

@ -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 = {

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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