Compare commits

...

12 Commits

Author SHA1 Message Date
Karol Stasiak d8c11a9c50 Minor optimizations 2023-02-03 14:46:01 +01:00
Karol Stasiak ee47ccef5a fix z80 indexing in hardwritten assembly (#137) 2023-01-27 18:27:53 +01:00
Karol Stasiak 24de2f7530 enable tests of identity page 2023-01-27 18:19:19 +01:00
Karol Stasiak 1beb695151 more tests 2023-01-27 18:17:41 +01:00
Karol Stasiak 9229092309 random minor stuff 2023-01-27 18:16:25 +01:00
Karol Stasiak ca7166d1ae oops 2023-01-27 18:16:02 +01:00
Karol Stasiak 1594d63a9a Add a command line flag for identity page 2023-01-27 18:15:40 +01:00
Karol Stasiak 75cc34663c Z80: fix an optimization 2023-01-27 18:15:10 +01:00
Karol Stasiak f4d2fdd370 6502: use identity page for maths 2023-01-27 18:14:50 +01:00
Karol Stasiak 29c1e3f2a6 6502 and Z80: Optimize optimizations 2023-01-27 18:13:21 +01:00
Karol Stasiak e9cfec54b5 Various 6502 optimization fixes 2023-01-27 17:34:09 +01:00
Karol Stasiak 1bc1ab3539 Fix some codegen bugs on 6502 2023-01-27 17:00:29 +01:00
45 changed files with 520 additions and 174 deletions

View File

@ -575,7 +575,7 @@ object CompilationFlag extends Enumeration {
EmitIllegals, DecimalMode, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly, EnableBreakpoints,
// compilation options for MOS:
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitSC02Opcodes, EmitRockwellOpcodes, EmitWdcOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack, IdentityPage,
// compilation options for I80
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitR800Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, EmitZ80NextOpcodes,
UseShadowRegistersForInterrupts,
@ -654,6 +654,7 @@ object CompilationFlag extends Enumeration {
"u_stack" -> UseUForStack,
"y_stack" -> UseYForStack,
"software_stack" -> SoftwareStack,
"identity_page" -> IdentityPage,
"use_shadow_registers_for_irq" -> UseShadowRegistersForInterrupts,
"output_intel_syntax" -> UseIntelSyntaxForOutput,
"input_intel_syntax" -> UseIntelSyntaxForInput,

View File

@ -175,7 +175,7 @@ object Main {
f"${path.getFileName}%s ${start}%04X ${start}%04X ${codeLength}%04X".getBytes(StandardCharsets.UTF_8))
}
}
errorReporting.debug(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
errorReporting.info(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
c.runFileName.foreach{ program =>
if (File.separatorChar == '\\') {
if (!new File(program).exists() && !new File(program + ".exe").exists()) {
@ -293,6 +293,7 @@ object Main {
if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.All else Nil,
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil,
if (options.flag(CompilationFlag.EmitNative65816Opcodes)) SixteenOptimizations.AllForNative else Nil,
if (options.flag(CompilationFlag.IdentityPage)) IdentityPageOptimizations.All else Nil,
if (options.flag(CompilationFlag.DangerousOptimizations)) DangerousOptimizations.All else Nil
).flatten
val goodCycle = List.fill(optLevel - 2)(OptimizationPresets.Good ++ goodExtras).flatten
@ -757,6 +758,9 @@ object Main {
boolean("-fregister-variables", "-fno-register-variables").action { (c, v) =>
c.changeFlag(CompilationFlag.RegisterVariables, v)
}.description("Allow moving local variables into CPU registers. Enabled by default.")
boolean("-fidentity-page", "-fno-identity-page").action { (c, v) =>
c.changeFlag(CompilationFlag.IdentityPage, v)
}.description("Whether should use an identity page to optimize certain operations.")
flag("-Os", "--size").repeatable().action { c =>
c.changeFlag(CompilationFlag.OptimizeForSize, true).
changeFlag(CompilationFlag.OptimizeForSpeed, false).
@ -769,6 +773,7 @@ object Main {
}.description("Prefer faster code even if it is slightly bigger (experimental). Implies -finline.")
flag("-Ob", "--blast-processing").repeatable().action { c =>
c.changeFlag(CompilationFlag.OptimizeForSize, false).
changeFlag(CompilationFlag.IdentityPage, true).
changeFlag(CompilationFlag.OptimizeForSpeed, true).
changeFlag(CompilationFlag.OptimizeForSonicSpeed, true)
}.description("Prefer faster code even if it is much bigger (experimental). Implies -finline.")

View File

@ -2,7 +2,7 @@ package millfork.assembly
import millfork.{CompilationFlag, CompilationOptions}
import millfork.compiler.LabelGenerator
import millfork.env.{NormalFunction, ThingInMemory}
import millfork.env.{Constant, NormalFunction, ThingInMemory}
import millfork.error.Logger
import millfork.node.NiceFunctionProperty
@ -12,6 +12,7 @@ import millfork.node.NiceFunctionProperty
case class OptimizationContext(options: CompilationOptions,
labelMap: Map[String, (String, Int)],
zreg: Option[ThingInMemory],
identityPage: Constant,
niceFunctionProperties: Set[(NiceFunctionProperty, String)]) {
@inline
def log: Logger = options.log
@ -25,4 +26,6 @@ trait AssemblyOptimization[T <: AbstractCode] {
def optimize(f: NormalFunction, code: List[T], context: OptimizationContext): List[T]
def requiredFlags: Set[CompilationFlag.Value] = Set.empty
def minimumRequiredLines: Int
}

View File

@ -37,6 +37,8 @@ object FlowInfoRequirement extends Enumeration {
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[MLine] {
@ -45,6 +47,9 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[MLine], optimizationContext: OptimizationContext): List[MLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
@ -56,7 +61,11 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRulesWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
@ -218,10 +227,14 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
case class AssemblyRule(pattern: AssemblyPattern, result: (List[MLine], AssemblyMatchingContext) => List[MLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).sum
}
trait AssemblyPattern {
@ -242,6 +255,8 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
object HelperCheckers {
private def badAddrModes(am: MAddrMode): Boolean = am match {
@ -344,6 +359,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = 0
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -356,6 +373,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = 0
}
@ -365,6 +384,8 @@ case class Where(predicate: AssemblyMatchingContext => Boolean) extends Assembly
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -387,6 +408,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: MLinePattern) extends AssemblyPattern {
@ -412,6 +435,8 @@ case class Many(rule: MLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: MLinePattern, atLeastOneIsThis: MLinePattern) extends AssemblyPattern {
@ -446,6 +471,8 @@ case class ManyWhereAtLeastOne(rule: MLinePattern, atLeastOneIsThis: MLinePatter
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = 1
}
case class Opt(rule: MLinePattern) extends AssemblyPattern {
@ -468,6 +495,8 @@ case class Opt(rule: MLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait MLinePattern extends AssemblyPattern {
@ -495,6 +524,8 @@ trait MLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
//noinspection ScalaUnnecessaryParentheses
@ -516,6 +547,8 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext
val s2s = ctx.get[List[MLine]](ix2)
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1, s2)))) Some(code) else None
}
override def minimumRequiredLines: Int = 0
}
case class MatchA(i: Int) extends MLinePattern {
@ -761,6 +794,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends MLinePattern {
@ -1281,6 +1316,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: MLinePattern, lastLineP
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsNotALabelUsedManyTimes extends MLinePattern {

View File

@ -995,16 +995,10 @@ object AlwaysGoodOptimizations {
val ConstantFlowAnalysis = new RuleBasedAssemblyOptimization("Constant flow analysis",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Elidable & HasParameterWhere({
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
})) ~~> { (code, ctx) =>
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Not(HasOpcode(BIT)) & Elidable & HasIdentityPageParameter) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Immediate, parameter = NumericConstant(ctx.get[Int](0), 1)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable & HasParameterWhere({
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
})) ~~> { (code, ctx) =>
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable & HasIdentityPageParameter) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Immediate, parameter = NumericConstant(ctx.get[Int](0), 1)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>

View File

@ -1,10 +1,11 @@
package millfork.assembly.mos.opt
import millfork.assembly.mos.{AssemblyLine, AssemblyLine0, OpcodeClasses}
import millfork.assembly.mos.{AddrMode, AssemblyLine, AssemblyLine0, OpcodeClasses}
import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext}
import millfork.env.NormalFunction
import millfork.error.Logger
import scala.annotation.tailrec
import scala.util.control.TailCalls.{TailRec, done, tailcall}
/**
@ -34,20 +35,22 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
override def name = "Changing index registers"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val addressingModesUsingX = Set(AbsoluteX, ZeroPageX, IndexedX)
val addressingModesUsingY = Set(AbsoluteY, ZeroPageY, IndexedY, LongIndexedY, IndexedSY)
val usesX = code.exists(l =>
OpcodeClasses.ReadsXAlways(l.opcode) ||
OpcodeClasses.ReadsYAlways(l.opcode) ||
OpcodeClasses.ChangesX(l.opcode) ||
OpcodeClasses.ChangesY(l.opcode) ||
Set(AbsoluteX, AbsoluteY, ZeroPageY, ZeroPageX, IndexedX, IndexedY, LongIndexedY, IndexedSY)(l.addrMode)
addressingModesUsingX(l.addrMode)
)
val usesY = code.exists(l =>
OpcodeClasses.ReadsXAlways(l.opcode) ||
OpcodeClasses.ReadsYAlways(l.opcode) ||
OpcodeClasses.ChangesX(l.opcode) ||
val usesY = code.exists(l => {
OpcodeClasses.ReadsYAlways(l.opcode) ||
OpcodeClasses.ChangesY(l.opcode) ||
Set(AbsoluteX, AbsoluteY, ZeroPageY, ZeroPageX, IndexedX, IndexedY, LongIndexedY, IndexedSY)(l.addrMode)
addressingModesUsingY(l.addrMode)
}
)
if (!usesX && !usesY) {
return code
@ -101,21 +104,25 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
}
//noinspection OptionEqualsSome
private def canOptimize(code: List[AssemblyLine], dir: IndexDirection, loaded: Option[IndexReg]): Boolean = code match {
@tailrec
private def canOptimize(code: List[AssemblyLine], dir: IndexDirection, loaded: Option[IndexReg]): Boolean = {
val notX = loaded != Some(X)
val notY = loaded != Some(Y)
code match {
case AssemblyLine0(INC | DEC | ASL | ROL | ROR | LSR | STZ | LDZ | BIT, AbsoluteX | ZeroPageX, _) :: xs if dir == X2Y => false
case AssemblyLine0(LDY | STY, AbsoluteX | ZeroPageX, _) :: xs => false
case AssemblyLine0(LDX | STX, AbsoluteY | ZeroPageY, _) :: xs => false
case AssemblyLine0(_, AbsoluteY, _) :: xs if loaded != Some(Y) => false
case AssemblyLine0(_, ZeroPageY, _) :: xs if loaded != Some(Y) => false
case AssemblyLine0(_, IndexedY, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine0(_, LongIndexedY, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine0(_, IndexedSY, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine0(_, AbsoluteX, _) :: xs if loaded != Some(X) => false
case AssemblyLine0(_, LongAbsoluteX, _) :: xs if loaded != Some(X) => false
case AssemblyLine0(_, ZeroPageX, _) :: xs if loaded != Some(X) => false
case AssemblyLine0(_, IndexedX, _) :: xs if dir == X2Y || loaded != Some(X) => false
case AssemblyLine0(_, AbsoluteY, _) :: xs if notY => false
case AssemblyLine0(_, ZeroPageY, _) :: xs if notY => false
case AssemblyLine0(_, IndexedY, _) :: xs if dir == Y2X || notY => false
case AssemblyLine0(_, LongIndexedY, _) :: xs if dir == Y2X || notY => false
case AssemblyLine0(_, IndexedSY, _) :: xs if dir == Y2X || notY => false
case AssemblyLine0(_, AbsoluteX, _) :: xs if notX => false
case AssemblyLine0(_, LongAbsoluteX, _) :: xs if notX => false
case AssemblyLine0(_, ZeroPageX, _) :: xs if notX => false
case AssemblyLine0(_, IndexedX | ImmediateWithAbsoluteX | ImmediateWithZeroPageX, _) :: xs if dir == X2Y || notX => false
case AssemblyLine0(_, AbsoluteIndexedX, _) :: xs if dir == X2Y => false
case AssemblyLine0(SHX | SHY | AHX | TAS | LAS, _, _) :: xs => false
case AssemblyLine(TXY, _, _, e, _) :: xs => (e == Elidability.Elidable || e == Elidability.Volatile) && loaded == Some(X) && canOptimize(xs, dir, Some(Y))
@ -155,11 +162,12 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
(e == Elidability.Elidable || e == Elidability.Volatile || dir == X2Y) && loaded == Some(Y) && canOptimize(xs, dir, Some(Y))
case AssemblyLine0(SAX | TXS | SBX, _, _) :: xs => dir == Y2X && loaded == Some(X) && canOptimize(xs, dir, Some(X))
case AssemblyLine0(TSX, _, _) :: xs => dir == Y2X && loaded != Some(Y) && canOptimize(xs, dir, Some(X))
case AssemblyLine0(TSX, _, _) :: xs => dir == Y2X && notY && canOptimize(xs, dir, Some(X))
case _ :: xs => canOptimize(xs, dir, loaded)
case Nil => true
}
}
private def switchX2Y(code: List[AssemblyLine])(implicit log: Logger): TailRec[List[AssemblyLine]] = code match {

View File

@ -56,7 +56,8 @@ object CoarseFlowAnalyzer {
changed = false
var currentStatus: CpuStatus = functionStartStatus
var staSpWasLast = false
for (i <- codeArray.indices) {
var i = 0
while (i < codeArray.length) {
import millfork.assembly.mos.Opcode._
import millfork.assembly.mos.AddrMode._
import millfork.node.MosNiceFunctionProperty._
@ -184,6 +185,7 @@ object CoarseFlowAnalyzer {
}
}
tFlag = codeArray(i).opcode == SET
i += 1;
}
// flagArray.zip(codeArray).foreach{
// case (fl, y) => if (y.isPrintable) println(f"$fl%-32s $y%-32s")

View File

@ -160,11 +160,6 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
}
def hasSet(state: State.Value): Boolean = state match {
case State.A => false
case State.AH => false
case State.X => false
case State.Y => false
case State.IZ => false
case State.Z => z.contains(true)
case State.N => n.contains(true)
case State.C => c.contains(true)

View File

@ -20,7 +20,7 @@ object DangerousOptimizations {
(HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
(Linear & Not(ConcernsY)).*
).capture(1) ~
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
ctx.get[List[AssemblyLine]](1) :+ last.copy(parameter = last.parameter.+(ctx.get[Constant](0)).quickSimplify)
},
@ -30,27 +30,27 @@ object DangerousOptimizations {
(HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
(Linear & Not(ConcernsX)).*
).capture(1) ~
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
ctx.get[List[AssemblyLine]](1) :+ last.copy(parameter = last.parameter.+(ctx.get[Constant](0)).quickSimplify)
},
(Elidable & HasOpcode(INY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(1).quickSimplify))
},
(Elidable & HasOpcode(DEY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(-1).quickSimplify))
},
(Elidable & HasOpcode(INX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(1).quickSimplify))
},
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(-1).quickSimplify))
},

View File

@ -16,6 +16,8 @@ import scala.collection.{immutable, mutable}
object EmptyMemoryStoreRemoval extends AssemblyOptimization[AssemblyLine] {
override def name = "Removing pointless stores to automatic variables"
override def minimumRequiredLines: Int = 2
private val storeAddrModes = Set(Absolute, ZeroPage, AbsoluteX, AbsoluteY, ZeroPageX, ZeroPageY)
private val directStorageOpcodes = Set(
STA, STX, STY, SAX, STZ, SHX, SHY, AHX,

View File

@ -16,6 +16,8 @@ import scala.collection.mutable
object EmptyParameterStoreRemoval extends AssemblyOptimization[AssemblyLine] {
override def name = "Removing pointless stores to foreign variables"
override def minimumRequiredLines: Int = 2
private val storeInstructions = Set(STA, STX, STY, SAX, STZ, STA_W, STX_W, STY_W, STZ_W)
private val storeAddrModes = Set(Absolute, ZeroPage, AbsoluteX, AbsoluteY, ZeroPageX, ZeroPageY, LongAbsolute, LongAbsoluteX)

View File

@ -21,12 +21,16 @@ case class FlowInfo(holder: FlowHolder, index: Int, _labelUseCountMap: () => Opt
lazy val importanceAfter: CpuImportance = holder.importanceAfter(index)
lazy val labelUseCountMap: Option[Map[String, Int]] = _labelUseCountMap()
@inline
def hasClear(state: State.Value): Boolean = statusBefore.hasClear(state)
@inline
def hasSet(state: State.Value): Boolean = statusBefore.hasSet(state)
@inline
def isUnimportant(state: State.Value): Boolean = importanceAfter.isUnimportant(state)
@inline
def labelUseCount(label: String): Int = labelUseCountMap.map(_.getOrElse(label, 0)).getOrElse(-1)
override def toString: String = holder.toString(index)

View File

@ -0,0 +1,50 @@
package millfork.assembly.mos.opt
import millfork.assembly.mos.AddrMode._
import millfork.assembly.mos.{AssemblyLine, State}
import millfork.assembly.mos.Opcode._
import millfork.env.NumericConstant
object IdentityPageOptimizations {
val SimplifiableAccess = new RuleBasedAssemblyOptimization("Simplifiable identity page access",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(LDA) & HasAddrMode(AbsoluteX) & HasIdentityPageParameter) ~~> { c =>
List(AssemblyLine.implied(TXA))
},
(Elidable & HasOpcode(LDA) & HasAddrMode(AbsoluteY) & HasIdentityPageParameter) ~~> { c =>
List(AssemblyLine.implied(TYA))
},
)
val UseInsteadOfStack = new RuleBasedAssemblyOptimization("Use identity page instead of stack",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(PHA)) ~
(Not(ConcernsX) & Not(ConcernsStack)).*.capture(0) ~
(Elidable & HasOpcode(TSX) & HasAddrMode(AbsoluteX) & HasIdentityPageParameter) ~
(Elidable & HasAddrMode(AbsoluteX) & HasParameterWhere{
case NumericConstant(0x101, _) => true
case _ => false
}).capture(1) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
List(AssemblyLine.implied(TAX)) ++ ctx.get[List[AssemblyLine]](0) ++ List(ctx.get[AssemblyLine](1).copy(parameter = ctx.identityPage))
},
(Elidable & HasOpcode(PHA)) ~
(Not(ConcernsY) & Not(ConcernsStack)).*.capture(0) ~
(Elidable & HasOpcode(TSX) & HasAddrMode(AbsoluteX) & HasIdentityPageParameter) ~
(Elidable & HasAddrMode(AbsoluteX) & HasParameterWhere{
case NumericConstant(0x101, _) => true
case _ => false
}).capture(1) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
List(AssemblyLine.implied(TAY)) ++ ctx.get[List[AssemblyLine]](0) ++ List(ctx.get[AssemblyLine](1).copy(parameter = ctx.identityPage, addrMode = AbsoluteY))
},
)
val All: List[RuleBasedAssemblyOptimization] = List(
SimplifiableAccess,
UseInsteadOfStack)
}

View File

@ -145,8 +145,8 @@ object LaterOptimizations {
//noinspection ZeroIndexToHead
private def InterleavedLoads(load: Opcode.Value, store: Opcode.Value) = {
(Elidable & HasOpcode(load) & MatchAddrMode(0) & MatchParameter(1)).capture(12) ~
(Elidable & HasOpcode(store)).+.capture(10) ~
(Elidable & HasOpcode(load) & MatchAddrMode(2) & MatchParameter(3) & DoesNotConcernMemoryAt(0, 1)).capture(13) ~
(Elidable & HasOpcode(store) & MatchAddrMode(20) & MatchParameter(21)).+.capture(10) ~
(Elidable & HasOpcode(load) & MatchAddrMode(2) & MatchParameter(3) & DoesNotConcernMemoryAt(0, 1) & DoesNotConcernMemoryAt(20, 21)).capture(13) ~
(Elidable & HasOpcode(store) & DoesNotConcernMemoryAt(0, 1) & DoesNotConcernMemoryAt(2, 3)).+.capture(11) ~
(Elidable & HasOpcode(load) & MatchAddrMode(0) & MatchParameter(1)) ~
WhereNoMemoryAccessOverlapBetweenTwoLineLists(10, 11) ~~> { (_, ctx) =>

View File

@ -17,6 +17,8 @@ object LocalVariableReadOptimization extends AssemblyOptimization[AssemblyLine]
override def name: String = "Local variable read optimization"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val stillUsedVariables = code.flatMap {

View File

@ -18,6 +18,8 @@ import scala.util.control.TailCalls.tailcall
case class RepeatedIndexCalculationOptimization(forX: Boolean) extends AssemblyOptimization[AssemblyLine] {
override def name: String = "Repeated index calculation into " + (if (forX) "X" else "Y")
override def minimumRequiredLines: Int = 4
override def optimize(f: NormalFunction, code: List[AssemblyLine], context: OptimizationContext): List[AssemblyLine] = {
val log = context.log
val allRuns = findAllRuns(code, 0, None).result

View File

@ -37,6 +37,8 @@ object FlowInfoRequirement extends Enumeration {
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[AssemblyLine] {
@ -45,6 +47,9 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
@ -56,11 +61,16 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRulesWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
optimizationContext.zreg,
optimizationContext.identityPage,
optimizationContext.niceFunctionProperties,
head._1.labelUseCount(_)
)
@ -111,6 +121,7 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
val labelMap: Map[String, (String, Int)],
val zeropageRegister: Option[ThingInMemory],
val identityPage: Constant,
val niceFunctionProperties: Set[(NiceFunctionProperty, String)],
val labelUseCount: String => Int) {
@inline
@ -138,7 +149,17 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
def functionReadsMemory(name: String): Boolean = !niceFunctionProperties(NiceFunctionProperty.DoesntReadMemory -> name)
private val map = new mutable.HashMap[Int, Any]()
private var m_map: mutable.HashMap[Int, Any] = _
@inline
private def map = {
var m = m_map
if (m eq null) {
m = new mutable.HashMap[Int, Any]()
m_map = m
}
m
}
override def toString: String = if (map.isEmpty) "<empty context>" else map.mkString(", ")
@ -242,10 +263,14 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
case class AssemblyRule(pattern: AssemblyPattern, result: (List[AssemblyLine], AssemblyMatchingContext) => List[AssemblyLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).min
}
trait AssemblyPattern {
@ -266,6 +291,8 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
object HelperCheckers {
import AddrMode._
@ -374,6 +401,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -386,6 +415,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
@ -395,6 +426,8 @@ case class Where(predicate: AssemblyMatchingContext => Boolean) extends Assembly
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -417,6 +450,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -442,6 +477,8 @@ case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: AssemblyLinePattern) extends AssemblyPattern {
@ -476,6 +513,8 @@ case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: Asse
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = rule.minimumRequiredLines
}
case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -498,6 +537,8 @@ case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait AssemblyLinePattern extends AssemblyPattern {
@ -525,6 +566,8 @@ trait AssemblyLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
//noinspection ScalaUnnecessaryParentheses
@ -538,6 +581,8 @@ case class Match(predicate: AssemblyMatchingContext => Boolean) extends Assembly
override def toString: String = "Match(...)"
override def hitRate: Double = 0.5 // ?
override def minimumRequiredLines: Int = 0
}
case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) extends AssemblyPattern {
@ -546,6 +591,8 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext
val s2s = ctx.get[List[AssemblyLine]](ix2)
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1, s2)))) Some(code) else None
}
override def minimumRequiredLines: Int = 0
}
case class MatchA(i: Int) extends AssemblyLinePattern {
@ -866,6 +913,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends AssemblyLinePattern {
@ -1466,6 +1515,27 @@ case class HasParameterWhere(predicate: Constant => Boolean) extends TrivialAsse
override def hitRate: Double = 0.332
}
case object HasSmallParameter extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = line.parameter match {
case MemoryAddressConstant(th : VariableInMemory) => th.typ.size < 255
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th : VariableInMemory), NumericConstant(_, 1)) => th.typ.size < 255
case MemoryAddressConstant(th : MfArray) => th.sizeInBytes < 255
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th : MfArray), NumericConstant(_, 1)) => th.sizeInBytes < 255
case _ => false
}
override def hitRate: Double = 0.332
}
case object HasIdentityPageParameter extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = line.parameter match {
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
}
override def hitRate: Double = 0.04
}
case class MatchObject(i: Int, f: Function[AssemblyLine, Any]) extends AssemblyLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
ctx.addObject(i, f(line))
@ -1608,6 +1678,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: AssemblyLinePattern, la
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsZeroPage extends AssemblyLinePattern {

View File

@ -15,6 +15,8 @@ import scala.collection.mutable.ListBuffer
*/
object SingleAssignmentVariableOptimization extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
private val BadOpcodes = Set(RTS, JSR, JMP, RTI)
private val GoodOpcodes = Set(
LDA, LDX, LAX, LDY, EOR, AND, ORA, ADC, SBC, CMP, CPX, CPY, BIT,

View File

@ -14,6 +14,8 @@ import scala.collection.mutable
*/
object SuperOptimizer extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
override def optimize(m: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val options = optimizationContext.options
val log = optimizationContext.log

View File

@ -17,6 +17,8 @@ import scala.util.control.TailCalls.{TailRec, done, tailcall}
*/
object TwoVariablesToIndexRegistersOptimization extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
override def requiredFlags: Set[CompilationFlag.Value] = Set(CompilationFlag.RegisterVariables)
object CyclesAndBytes {

View File

@ -35,4 +35,6 @@ object UnusedLabelRemoval extends AssemblyOptimization[AssemblyLine] {
}
override def name = "Unused label removal"
override def minimumRequiredLines: Int = 2
}

View File

@ -16,6 +16,8 @@ object UseAccumulatorInsteadOfXRegister extends UseAccumulatorInsteadOfIndexRegi
class UseAccumulatorInsteadOfIndexRegister(doY: Boolean) extends AssemblyOptimization[AssemblyLine] {
override def name: String = if (doY) "Use accumulator instead of the Y register" else "Use accumulator instead of the X register"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[AssemblyLine], context: OptimizationContext): List[AssemblyLine] = {
val log = context.log
if (f.params.length == 1) return code

View File

@ -1,6 +1,6 @@
package millfork.assembly.mos.opt
import millfork.assembly.mos.{AssemblyLine, AssemblyLine0, OpcodeClasses, State}
import millfork.assembly.mos.{AddrMode, AssemblyLine, AssemblyLine0, OpcodeClasses, State}
import millfork.env._
import millfork.error.ConsoleLogger
import millfork.node.NiceFunctionProperty
@ -12,10 +12,10 @@ object VariableLifetime {
// This only works for non-stack variables.
def apply(variableName: String, code: List[AssemblyLine], expandToIncludeIndexing: Boolean = false, expandToIncludeUsesOfLoadedIndices: Option[Set[(NiceFunctionProperty, String)]] = None): Range = {
val flags = code.map(_.parameter match {
val flags = code.map(line => line.parameter match {
case MemoryAddressConstant(MemoryVariable(n, _, _)) if n == variableName => true
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(MemoryVariable(n, _, _)), NumericConstant(_, 1)) if n == variableName => true
case _ => false
case p => line.addrMode == AddrMode.IndexedX && p.refersTo(variableName)
})
if (flags.forall(!_)) return Range(0, 0)
var min = flags.indexOf(true)

View File

@ -18,6 +18,8 @@ import scala.util.control.TailCalls.{TailRec, done, tailcall}
*/
object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
override def requiredFlags: Set[CompilationFlag.Value] = Set(CompilationFlag.RegisterVariables)
object CyclesAndBytes {
@ -28,14 +30,14 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case class FeaturesForIndexRegisters(
blastProcessing: Boolean,
izIsAlwaysZero: Boolean,
indexRegisterTransfers: Boolean,
functionsSafeForX: Set[String],
functionsSafeForY: Set[String],
functionsSafeForZ: Set[String],
identityArray: Constant,
log: Logger)
useIdentityPage: Boolean,
izIsAlwaysZero: Boolean,
indexRegisterTransfers: Boolean,
functionsSafeForX: Set[String],
functionsSafeForY: Set[String],
functionsSafeForZ: Set[String],
identityArray: Constant,
log: Logger)
case class FeaturesForAccumulator(
cmos: Boolean,
@ -176,11 +178,11 @@ 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, optimizationContext)
val blastProcessing = options.flag(CompilationFlag.OptimizeForSonicSpeed)
val identityArray = f.environment.maybeGet[ThingInMemory]("identity$").map(MemoryAddressConstant).getOrElse(Constant.Zero)
val useIdentityPage = options.flag(CompilationFlag.IdentityPage)
val identityArray = f.environment.identityPage
val izIsAlwaysZero = !options.flag(CompilationFlag.Emit65CE02Opcodes)
val featuresForIndices = FeaturesForIndexRegisters(
blastProcessing = blastProcessing,
useIdentityPage = useIdentityPage,
izIsAlwaysZero = izIsAlwaysZero,
indexRegisterTransfers = options.flag(CompilationFlag.EmitEmulation65816Opcodes),
functionsSafeForX = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeX).map(_._2),
@ -383,14 +385,26 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
val vx = xCandidate.getOrElse("-")
val vy = yCandidate.getOrElse("-")
val vz = zCandidate.getOrElse("-")
val accessingX = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vx
case _ => false
}
val accessingY = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vy
case _ => false
}
val accessingZ = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vz
case _ => false
}
lines match {
case (AssemblyLine0(_, Immediate, MemoryAddressConstant(th)), _) :: xs
if th.name == vx || th.name == vy || th.name == vz =>
if accessingX || accessingY || accessingZ =>
// if an address of a variable is used, then that variable cannot be assigned to a register
None
case (AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)), _) :: xs
if th.name == vx || th.name == vy || th.name == vz =>
if accessingX || accessingY || accessingZ =>
// if an address of a variable is used, then that variable cannot be assigned to a register
None
@ -410,7 +424,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
Indirect | LongIndirect |
AbsoluteIndexedX, MemoryAddressConstant(th)), _) :: xs =>
// if a variable is used as an array or a pointer, then it cannot be assigned to a register
if (th.name == vx || th.name == vy || th.name == vz) {
if (accessingX || accessingY || accessingZ) {
None
} else {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
@ -422,56 +436,56 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine0(SEP | REP, _, _), _) :: xs => None
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vx && (features.indexRegisterTransfers) =>
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingX && (features.indexRegisterTransfers) =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vy && (features.indexRegisterTransfers) =>
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingY && (features.indexRegisterTransfers) =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if th.name == vx && f.y == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if accessingX && f.y == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if th.name == vy && f.x == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if accessingY && f.x == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(SEC | CLC, _, _), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if th.name == vx && f.y == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if accessingX && f.y == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(SEC | CLC, _, _), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if th.name == vy && f.x == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if accessingY && f.x == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vx => None
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingX => None
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vy => None
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingY => None
case (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs
if opcodesIdentityTable(op) && features.blastProcessing =>
if (th.name == vx || th.name == vy) {
if opcodesIdentityTable(op) && features.useIdentityPage =>
if (accessingX || accessingY) {
if (elidability == Elidability.Elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 0, cycles = -1))
else None
} else {
if (th.name == vz) None
if (accessingZ) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if th.name == vx && (opcode == LDY || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
if accessingX && (opcode == LDY || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
case (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if th.name == vy && (opcode == LDX || opcode == LAX || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
if accessingY && (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 (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if th.name == vz && (opcode == LDX || opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
if accessingZ && (opcode == LDX || opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
@ -479,7 +493,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if xCandidate.isDefined =>
// if a register is populated with a different variable, then this variable cannot be assigned to that register
// removing LDX saves 3 cycles
if (elidability == Elidability.Elidable && th.name == vx) {
if (elidability == Elidability.Elidable && accessingX) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
} else {
@ -493,7 +507,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if xCandidate.isDefined =>
// LAX = LDX-LDA, and since LDX simplifies to nothing and LDA simplifies to TXA,
// LAX simplifies to TXA, saving two bytes
if (elidability == Elidability.Elidable && th.name == vx) {
if (elidability == Elidability.Elidable && accessingX) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
} else {
None
@ -503,7 +517,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
// if a register is populated with a different variable, then this variable cannot be assigned to that register
// removing LDX saves 3 bytes
// sometimes that LDX has to be converted into CPX#0
if (elidability == Elidability.Elidable && th.name == vy) {
if (elidability == Elidability.Elidable && accessingY) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
@ -514,7 +528,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), imp) :: xs if zCandidate.isDefined =>
if (elidability == Elidability.Elidable && th.name == vz) {
if (elidability == Elidability.Elidable && accessingZ) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
@ -553,7 +567,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
else None
} else {
if (th.name == vz) None
if (accessingZ) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
@ -561,7 +575,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if xCandidate.isDefined =>
// a variable cannot be inlined if there is TAX not after LDA of that variable
// but LDA-TAX can be simplified to TXA
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && th.name == vx) {
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && accessingX) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
None
@ -571,7 +585,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if yCandidate.isDefined =>
// a variable cannot be inlined if there is TAY not after LDA of that variable
// but LDA-TAY can be simplified to TYA
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && th.name == vy) {
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && accessingY) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
None
@ -581,7 +595,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if zCandidate.isDefined =>
// a variable cannot be inlined if there is TAZ not after LDA of that variable
// but LDA-TAZ can be simplified to TZA
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && th.name == vy) {
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && accessingY) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
None
@ -589,7 +603,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine(LDA | STA, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
if (th.name == vy || th.name == vx || th.name == vz) {
if (accessingY || accessingX || accessingZ) {
if (elidability == Elidability.Elidable) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
} else {
@ -601,7 +615,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine(INC | DEC, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
if (th.name == vy || th.name == vx || th.name == vz) {
if (accessingY || accessingX || accessingZ) {
if (elidability == Elidability.Elidable) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4))
} else {
@ -613,7 +627,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
// changing STZ->LDX saves 1 byte
if (th.name == vy || th.name == vx) {
if (accessingY || accessingX) {
if (elidability == Elidability.Elidable && features.izIsAlwaysZero) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
} else {
@ -888,37 +902,53 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
val vy = yCandidate.getOrElse("-")
val vz = zCandidate.getOrElse("-")
val va = aCandidate.getOrElse("-")
val accessingX = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vx
case _ => false
}
val accessingY = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vy
case _ => false
}
val accessingZ = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vz
case _ => false
}
val accessingA = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == va
case _ => false
}
lines match {
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map( AssemblyLine.implied(INX).pos(s) :: _)
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(INY).pos(s) :: _)
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(INZ).pos(s) :: _)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(DEX).pos(s) :: _)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(DEY).pos(s) :: _)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(DEZ).pos(s) :: _)
case (AssemblyLine(opcode@(DEC | INC | ROL | ROR | ASL | LSR), Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(opcode).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == vx =>
if accessingX =>
if (imp.z == Unimportant && imp.n == Unimportant) {
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs))
} else {
@ -926,22 +956,22 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (l@AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if opcodesIdentityTable(op) && th.name == vx =>
if opcodesIdentityTable(op) && accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l.copy(addrMode = AbsoluteX, parameter = features.identityArray) :: _)
case (l@AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if opcodesIdentityTable(op) && th.name == vy =>
if opcodesIdentityTable(op) && accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l.copy(addrMode = AbsoluteY, parameter = features.identityArray) :: _)
case (l@AssemblyLine0(LDA | TYA | TXA | TZA | CLA, _, _), _) :: xs if aCandidate.isDefined && isReturn(xs) =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l :: _)
case (l@AssemblyLine0(LDA, _, _), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if opcodesCommutative(op) && th.name == va =>
if opcodesCommutative(op) && accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l.copy(opcode = op) :: _)
case (l@AssemblyLine0(LDA, _, _), _) :: (clc@AssemblyLine0(CLC, _, _), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
@ -965,7 +995,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: clc :: l.copy(opcode = op) :: _)
case (AssemblyLine(LDA | STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == va =>
if accessingA =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
@ -973,11 +1003,11 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == vy =>
if accessingY =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
@ -985,7 +1015,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == vz =>
if accessingZ =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
@ -993,64 +1023,64 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAX, _, _, Elidability.Elidable, _), _) :: xs
if th.name == vx =>
if accessingX =>
// these TXA's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAY, _, _, Elidability.Elidable, _), _) :: xs
if th.name == vy =>
if accessingY =>
// these TYA's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAZ, _, _, Elidability.Elidable, _), _) :: xs
if th.name == vz =>
if accessingZ =>
// these TZA's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TXA, _, _, Elidability.Elidable, _), _) :: xs
if th.name == va =>
if accessingA =>
// these TAX's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, _), _) :: (AssemblyLine(TYA, _, _, Elidability.Elidable, _), _) :: xs
if th.name == va =>
if accessingA =>
// these TAY's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAY) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s1), _) :: (AssemblyLine(CMP, am, param, Elidability.Elidable, s2), _) :: xs
if th.name == vx && CpxyzAddrModes(am) && isNot(vx, param) =>
if accessingX && CpxyzAddrModes(am) && isNot(vx, param) =>
// ditto
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s1) :: AssemblyLine(CPX, am, param).pos(s2) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s1), _) :: (AssemblyLine(CMP, am, param, Elidability.Elidable, s2), _) :: xs
if th.name == vy && CpxyzAddrModes(am) && isNot(vx, param) =>
if accessingY && CpxyzAddrModes(am) && isNot(vx, param) =>
// ditto
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s1) :: AssemblyLine(CPY, am, param).pos(s2) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s1), _) :: (AssemblyLine(CMP, am, param, Elidability.Elidable, s2), _) :: xs
if th.name == vy && CpxyzAddrModes(am) && isNot(vx, param) =>
if accessingY && CpxyzAddrModes(am) && isNot(vx, param) =>
// ditto
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s1) :: AssemblyLine(CPZ, am, param).pos(s2) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx && features.indexRegisterTransfers =>
if accessingX && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXY).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy && features.indexRegisterTransfers =>
if accessingY && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYX).pos(s) :: _)
case (l0@AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteY, _), f):: xs
if th.name == vx =>
if accessingX =>
if (l1.opcode != STA || f.n == Unimportant && f.z == Unimportant) {
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l1.copy(addrMode = AbsoluteX) :: _)
} else if (f.c == Unimportant) {
@ -1062,7 +1092,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (l0@AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteX, _), f):: xs
if th.name == vy =>
if accessingY =>
if (l1.opcode != STA || f.n == Unimportant && f.z == Unimportant) {
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l1.copy(addrMode = AbsoluteY) :: _)
} else if (f.c == Unimportant) {
@ -1074,31 +1104,31 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (l0@AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l5@AssemblyLine0(SEC | CLC, _, _), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteY, _), _):: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l5 :: l1.copy(addrMode = AbsoluteX) :: _)
case (l0@AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l5@AssemblyLine0(SEC | CLC, _, _), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteX, _), _):: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l5 :: l1.copy(addrMode = AbsoluteY) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx => features.log.fatal("Unexpected LDY")
if accessingX => features.log.fatal("Unexpected LDY")
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy => features.log.fatal("Unexpected LDX")
if accessingY => features.log.fatal("Unexpected LDX")
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAY).pos(s) :: _)
case (AssemblyLine(LDA, am, param, Elidability.Elidable, s1), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s2), _) :: xs
@ -1117,51 +1147,51 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine(LDZ, am, param).pos(s1, s2) :: AssemblyLine.implied(TZA).pos(s1, s2) :: _)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAY).pos(s) :: _)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAZ).pos(s) :: _)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: _)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy && features.indexRegisterTransfers =>
if accessingY && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXY).pos(s) :: _)
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx && features.indexRegisterTransfers =>
if accessingX && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYX).pos(s) :: _)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy => features.log.fatal("Unexpected STX")
if accessingY => features.log.fatal("Unexpected STX")
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx => features.log.fatal("Unexpected STY")
if accessingX => features.log.fatal("Unexpected STY")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
if (features.izIsAlwaysZero) tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(LDX, 0).pos(s) :: _)
else features.log.fatal("Unexpected STZ")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
if (features.izIsAlwaysZero) tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(LDY, 0).pos(s) :: _)
else features.log.fatal("Unexpected STZ")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
if (features.izIsAlwaysZero) tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(LDA, 0).pos(s) :: _)
else tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s) :: _)

View File

@ -953,7 +953,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
if (result.contains("???")) s" ??? (${this.toString.stripPrefix(" ")})" else result
}
def readsRegister(r: ZRegister.Value): Boolean = {
def readsRegister(r: ZRegister.Value): Boolean = { // TODO: optimize
import ZOpcode._
import ZRegister._
r match {

View File

@ -17,6 +17,8 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
override def name = "Allocating variables to single registers"
override def minimumRequiredLines: Int = 3
object CyclesAndBytes {
val Zero = CyclesAndBytes(0, 0)
}

View File

@ -17,6 +17,8 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
import millfork.node.ZRegister._
override def minimumRequiredLines: Int = 3
case class Loaded(b: Boolean = false, c: Boolean = false, d: Boolean = false, e: Boolean = false) {
def load(register: ZRegister.Value): Loaded = register match {
case B => copy(b = true, d = false, e = false)
@ -172,6 +174,11 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
case (x@ZLine0(_, TwoRegisters(r, BC), _)) :: xs =>
x.copy(registers = TwoRegisters(r, DE)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(MEM_BC, r), _)) :: xs =>
x.copy(registers = TwoRegisters(MEM_DE, r)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(r, MEM_BC), _)) :: xs =>
x.copy(registers = TwoRegisters(r, MEM_DE)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(B, r), _)) :: xs =>
x.copy(registers = TwoRegisters(D, r)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(r, B), _)) :: xs =>
@ -220,6 +227,11 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
case (x@ZLine0(_, TwoRegisters(r, DE), _)) :: xs =>
x.copy(registers = TwoRegisters(r, BC)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(MEM_DE, r), _)) :: xs =>
x.copy(registers = TwoRegisters(MEM_BC, r)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(r, MEM_DE), _)) :: xs =>
x.copy(registers = TwoRegisters(r, MEM_BC)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(D, r), _)) :: xs =>
x.copy(registers = TwoRegisters(B, r)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(r, D), _)) :: xs =>

View File

@ -12,6 +12,8 @@ import millfork.node.ZRegister
object CompactStackFrame extends AssemblyOptimization[ZLine] {
override def name: String = "Compacting the stack frame"
override def minimumRequiredLines: Int = 9
override def optimize(f: NormalFunction, code: List[ZLine], context: OptimizationContext): List[ZLine] = {
val register =
if (context.options.flag(CompilationFlag.UseIxForStack)) ZRegister.IX

View File

@ -14,6 +14,8 @@ import scala.collection.mutable
object EmptyMemoryStoreRemoval extends AssemblyOptimization[ZLine] {
override def name = "Removing pointless stores to automatic variables"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val vs = VariableStatus(f, code, optimizationContext, _ => true, allowParams = true).getOrElse(return code)
if (vs.localVariables.isEmpty) {

View File

@ -12,6 +12,8 @@ import millfork.env._
object EmptyParameterStoreRemoval extends AssemblyOptimization[ZLine] {
override def name = "Removing pointless stores to foreign variables"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val usedFunctions = code.flatMap {
case ZLine0(CALL | JP | JR, _, MemoryAddressConstant(th)) => Some(th.name)

View File

@ -37,6 +37,8 @@ object FlowInfoRequirement extends Enumeration {
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[ZLine]{
@ -45,6 +47,10 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
optimizeImpl(f, code, taggedCode, optimizationContext)
@ -55,7 +61,11 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRulesWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(optimizationContext.options)
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, ZLine)]) =>
@ -245,11 +255,11 @@ object HelperCheckers {
case LD | LD_16 | ADD_16 | ADC_16 | SBC_16 => l.registers match {
case TwoRegisters(MEM_HL | MEM_IX_D | MEM_IY_D | MEM_BC | MEM_DE, _) => true
case TwoRegisters(_, MEM_HL | MEM_IX_D | MEM_IY_D | MEM_BC | MEM_DE) => true
case TwoRegisters(_, _) => false
case _ => false
}
case ADD | SUB | SBC | ADC | XOR | CP | OR | AND => l.registers match {
case OneRegister(MEM_HL | MEM_IX_D | MEM_IY_D | MEM_BC | MEM_DE) => true
case OneRegister(_) => false
case _ => false
}
case CHANGED_MEM => true
case POP | PUSH => false
@ -293,10 +303,14 @@ object HelperCheckers {
}
case class AssemblyRule(pattern: AssemblyPattern, result: (List[ZLine], AssemblyMatchingContext) => List[ZLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).min
}
trait AssemblyPattern {
@ -317,6 +331,7 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -329,6 +344,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class CaptureLine(i: Int, pattern: AssemblyLinePattern) extends AssemblyLinePattern {
@ -340,6 +357,8 @@ case class CaptureLine(i: Int, pattern: AssemblyLinePattern) extends AssemblyLin
}
override def hitRate: Double = 0.025
override def minimumRequiredLines: Int = 0
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -352,6 +371,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
@ -361,6 +382,8 @@ case class Where(predicate: (AssemblyMatchingContext => Boolean)) extends Assemb
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -383,6 +406,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -408,6 +433,8 @@ case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: AssemblyLinePattern) extends AssemblyPattern {
@ -442,6 +469,8 @@ case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: Asse
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = rule.minimumRequiredLines
}
case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -464,6 +493,8 @@ case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait AssemblyLinePattern extends AssemblyPattern {
@ -493,6 +524,8 @@ trait AssemblyLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
trait TrivialAssemblyLinePattern extends AssemblyLinePattern with (ZLine => Boolean) {
@ -931,6 +964,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends AssemblyLinePattern {
@ -1342,6 +1377,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: AssemblyLinePattern, la
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsNotALabelUsedManyTimes extends AssemblyLinePattern {

View File

@ -19,6 +19,8 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
override def name = "Allocating variables to register pairs"
override def minimumRequiredLines: Int = 3
object CyclesAndBytes {
val Zero = CyclesAndBytes(0, 0)
}
@ -565,7 +567,7 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
} else if (de != "") {
tailcall(inlineVars(hl, bc, de, xs)).map(ZLine.register(PUSH, DE).pos(s) :: x :: ZLine.register(POP, DE).pos(s) :: _)
} else {
throw new IllegalStateException()
tailcall(inlineVars(hl, bc, de, xs)).map(x :: _)
}

View File

@ -74,11 +74,17 @@ object BuiltIns {
case FunctionCallExpression(name, List(param)) if env.maybeGet[Type](name).isDefined =>
return simpleOperation(opcode, ctx, param, indexChoice, preserveA, commutative, decimal)
case _: FunctionCallExpression | _: SumExpression if commutative =>
val code = MosExpressionCompiler.compileToA(ctx, source)
if (ctx.options.flags(CompilationFlag.IdentityPage)
&& !code.exists(_.concernsX)
&& AssemblyLine.treatment(code, State.X) == Treatment.Unchanged) {
return List(AssemblyLine.implied(TAX)) ++ code ++ wrapInSedCldIfNeeded(decimal, List(AssemblyLine.absoluteX(opcode, env.identityPage)))
}
// TODO: is it ok?
if (ctx.options.zpRegisterSize >= 1) {
val reg = ctx.env.get[ThingInMemory]("__reg")
return List(AssemblyLine.implied(PHA)) ++
MosExpressionCompiler.compileToA(ctx, source) ++
MosExpressionCompiler.fixTsx(code) ++
List(AssemblyLine.zeropage(STA, reg), AssemblyLine.implied(PLA)) ++
wrapInSedCldIfNeeded(decimal, List(AssemblyLine.zeropage(opcode, reg)))
} else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {

View File

@ -72,6 +72,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) :: (AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8)
} else AssemblyLine.tsx(ctx) ++ List(
// TODO: ???
AssemblyLine.implied(TSX),
AssemblyLine.immediate(LDA, expr.loByte),
AssemblyLine.dataStackX(ctx, STA, offset),
@ -155,10 +156,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
val cmos = ctx.options.flag(CompilationFlag.EmitCmosOpcodes)
if (register == MosRegister.AX && !code.exists(_.concernsX)) {
if (register == MosRegister.AX && !code.exists(_.concernsX) && code.forall(_.treatment(State.X).==(Treatment.Unchanged))) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (register == MosRegister.AY && !code.exists(_.concernsY)) {
if (register == MosRegister.AY && !code.exists(_.concernsY) && code.forall(_.treatment(State.Y).==(Treatment.Unchanged))) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (states.exists(state => AssemblyLine.treatment(code, state) != Treatment.Unchanged)) {
@ -672,7 +673,11 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
case RegisterVariable(MosRegister.Y, _) => actualOffset match {
case 0 => List(tsx)
case 1 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(TAY), AssemblyLine.implied(INY))
case 1 =>
if (ctx.options.flag(CompilationFlag.IdentityPage))
List(tsx, AssemblyLine.absoluteX(LDY, ctx.env.identityPage), AssemblyLine.implied(INY))
else
List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(TAY), AssemblyLine.implied(INY))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset), AssemblyLine.implied(TAY))
}
case _ =>

View File

@ -241,7 +241,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
}
}
val registers = (reg, offset) match {
case (OneRegister(r), Some(o)) => env.evalForAsm(expression) match {
case (OneRegister(r), Some(o)) => env.evalForAsm(o) match {
case Some(NumericConstant(v, _)) => OneRegisterOffset(r, v.toInt)
case Some(_) =>
ctx.log.error("Non-numeric constant", o.position)
@ -250,7 +250,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
ctx.log.error("Inlining failed due to non-constant things", o.position)
reg
}
case (TwoRegisters(t, s), Some(o)) => env.evalForAsm(expression) match {
case (TwoRegisters(t, s), Some(o)) => env.evalForAsm(o) match {
case Some(NumericConstant(v, _)) => TwoRegistersOffset(t, s, v.toInt)
case Some(_) =>
ctx.log.error("Non-numeric constant", o.position)

View File

@ -295,18 +295,20 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
private def removeVariableImpl(str: String): Unit = {
def extractThingName(fullName: String): String = {
var result = fullName.takeWhile(_ != '.')
if (result.length == fullName.length) return result
val suffix = fullName.drop(result.length)
val ix = fullName.indexOf('.')
if (ix < 0) return fullName
var result = fullName.substring(0, ix)
val suffix = fullName.substring(ix)
if (suffix == ".return" || suffix.startsWith(".return.")) {
result += ".return"
}
result
}
val strWithoutPrefix = str.stripPrefix(prefix)
val toRemove = things.keys.filter { n =>
val baseName = extractThingName(n)
baseName == str || baseName == str.stripPrefix(prefix)
baseName == str || baseName == strWithoutPrefix
}.toSet
removedThings ++= toRemove.map(_.stripPrefix(prefix))
things --= toRemove
@ -393,6 +395,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
@inline
final def identityPage: Constant = maybeGet[MfArray]("identity$").fold(Constant.Zero)(_.toAddress)
def getPointy(name: String): Pointy = {
InitializedMemoryVariable
UninitializedMemoryVariable
@ -2618,7 +2623,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def collectDeclarations(program: Program, options: CompilationOptions): Unit = {
val b = get[VariableType]("byte")
val v = get[Type]("void")
if (options.flag(CompilationFlag.OptimizeForSonicSpeed)) {
if (options.flag(CompilationFlag.IdentityPage)) {
addThing(InitializedArray("identity$", None, IndexedSeq.tabulate(256)(n => LiteralExpression(n, 1)), declaredBank = None, b, b, readOnly = true, Set.empty, defaultArrayAlignment(options, 256)), None)
}
program.declarations.foreach {

View File

@ -240,8 +240,8 @@ object MosNiceFunctionProperty {
case object DoesntConcernD extends NiceFunctionProperty("D")
case object DoesntChangeZpRegister extends NiceFunctionProperty("reg")
case class SetsSourceOfNZ(sourceOfNZ: SourceOfNZ) extends NiceFunctionProperty(sourceOfNZ + "NZ")
case class SetsXTo(value: Int) extends NiceFunctionProperty("Y=" + value)
case class SetsYTo(value: Int) extends NiceFunctionProperty("Z=" + value)
case class SetsXTo(value: Int) extends NiceFunctionProperty("X=" + value)
case class SetsYTo(value: Int) extends NiceFunctionProperty("Y=" + value)
case class SetsATo(value: Int) extends NiceFunctionProperty("A=" + value)
case class Bit0OfA(value: Boolean) extends NiceFunctionProperty("A0=" + value)
case class Bit7OfA(value: Boolean) extends NiceFunctionProperty("A7=" + value)

View File

@ -328,8 +328,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val labelMapImm = labelMap.toMap
val niceFunctionPropertiesImm = niceFunctionProperties.toSet
val extraOptimizedCode = veryLateOptimizations(thisFunctionNiceProperties, options).foldLeft(code) { (c, opt) =>
val ocode = opt.optimize(function, c, OptimizationContext(options, labelMapImm, env.maybeGet[ThingInMemory]("__reg"), niceFunctionPropertiesImm))
if (ocode eq c) code else quickSimplify(ocode)
if (c.length < opt.minimumRequiredLines) {
c
} else {
val ocode = opt.optimize(function, c, OptimizationContext(options, labelMapImm, env.maybeGet[ThingInMemory]("__reg"), env.identityPage, niceFunctionPropertiesImm))
if (ocode eq c) c else quickSimplify(ocode)
}
}
compiledFunctions(f) = NormalCompiledFunction(
function.declaredBank.getOrElse(platform.defaultCodeBank),
@ -663,6 +667,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val defaultBank = mem.banks("default").index
if (platform.freeZpBytes.nonEmpty) {
// TODO: reimplement indexOf/LastIndexOf for speed
val zpUsageOffset = platform.freeZpBytes.min
val zeropageOccupation = zpOccupied.slice(zpUsageOffset, platform.freeZpBytes.max + 1)
unimportantLabelMap += "__zeropage_usage" -> ("default", zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1)
@ -841,8 +846,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
unoptimizedCodeSize += unoptimized.map(_.sizeInBytes).sum
// unoptimized.foreach(l => log.trace(l.toString))
val code = optimizations.foldLeft(quickSimplify(unoptimized)) { (c, opt) =>
val ocode = opt.optimize(f, c, OptimizationContext(options, labelMap, env.maybeGet[ThingInMemory]("__reg"), niceFunctionProperties))
if (ocode eq c) ocode else quickSimplify(ocode)
if (c.length < opt.minimumRequiredLines) {
c
} else {
val ocode = opt.optimize(f, c, OptimizationContext(options, labelMap, env.maybeGet[ThingInMemory]("__reg"), env.identityPage, niceFunctionProperties))
if (ocode eq c) ocode else quickSimplify(ocode)
}
}
performFinalOptimizationPass(f, optimizations.nonEmpty, options, code)
}

View File

@ -51,6 +51,7 @@ class MemoryBank(val index: Int, val isBigEndian: Boolean) {
def readWord(addrHi: Int, addrLo: Int): Int = readByte(addrLo) + (readByte(addrHi) << 8)
// TODO: use new:
val output: Array[Byte] = Array.fill[Byte](1 << 16)(0)
val occupied: Array[Boolean] = Array.fill(1 << 16)(false)
val initialized: Array[Boolean] = Array.fill(1 << 16)(false)

View File

@ -22,7 +22,7 @@ class MosAssembler(program: Program,
override def performFinalOptimizationPass(f: NormalFunction, actuallyOptimize: Boolean, options: CompilationOptions, code: List[AssemblyLine]):List[AssemblyLine] = {
val optimizationContext = OptimizationContext(options, Map(), f.environment.maybeGet[ThingInMemory]("__reg"), Set())
val optimizationContext = OptimizationContext(options, Map(), f.environment.maybeGet[ThingInMemory]("__reg"), f.environment.identityPage, Set())
if (actuallyOptimize) {
val finalCode = if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.removeLoadZero(f, code, optimizationContext) else code
JumpShortening(f, JumpShortening(f, JumpFixing(f, JumpFollowing(options, finalCode), options), optimizationContext), optimizationContext)
@ -167,7 +167,7 @@ class MosAssembler(program: Program,
case AssemblyLine0(BRK | RTI, _, _) => true
case _ => false
}) return
val optimizationContext = OptimizationContext(options, Map(), function.environment.maybeGet[ThingInMemory]("__reg"), Set())
val optimizationContext = OptimizationContext(options, Map(), function.environment.maybeGet[ThingInMemory]("__reg"), function.environment.identityPage, Set())
val flow = CoarseFlowAnalyzer.analyze(function, code, optimizationContext)
def rtsPropertyScan[T](extractor: CpuStatus => Status[T])(niceFunctionProperty: Status[T] => Option[NiceFunctionProperty]): Unit = {
val statuses = code.zipWithIndex.flatMap{

View File

@ -865,7 +865,7 @@ class Z80Assembler(program: Program,
case ZLine0(RST, _, _) => true
case _ => false
}) return
val optimizationContext = OptimizationContext(options, Map(), None, Set())
val optimizationContext = OptimizationContext(options, Map(), None, Constant.Zero, Set())
val flow = CoarseFlowAnalyzer.analyze(function, code, optimizationContext)
def retPropertyScan[T](extractor: CpuStatus => Status[T])(niceFunctionProperty: Status[T] => Option[NiceFunctionProperty]): Unit = {
val statuses = code.zipWithIndex.flatMap{

View File

@ -714,4 +714,21 @@ class ArraySuite extends FunSuite with Matchers with AppendedClues {
m.readByte(0x4008) should equal('9'.toInt)
}
}
test("Arrays of pointers") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos)(
"""
|const array arr1 = [1, 2, 3]
|const array arr2 = [101, 102, 103]
|array(pointer) arrs = [ arr1, arr2 ]
|byte output @$c000
|void main() {
| pointer p
| p = arrs[1]
| output = p[0]
|}
""".stripMargin) { m =>
m.readByte(0xc000) should equal(101)
}
}
}

View File

@ -362,4 +362,28 @@ class StructSuite extends FunSuite with Matchers {
| }
|""".stripMargin){m => }
}
test("Assigning struct fields via pointers") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086, Cpu.Motorola6809)("""
struct STRUCT1 { word a }
struct STRUCT2 { word b }
pointer.STRUCT1 pS1
pointer.STRUCT2 pS2
STRUCT1 s1 @$c000
STRUCT2 s2
noinline void f() {
pS1->a = pS2->b
}
void main() {
s2.b = $405
pS1 = s1.pointer
pS2 = s2.pointer
f()
}
"""){m =>
m.readWord(0xc000) should equal(0x405)
}
}
}

View File

@ -728,9 +728,9 @@ class Z80AssemblySuite extends FunSuite with Matchers {
"""
| asm void main () {
| ret
| add a,ix(0)
| adc a,ix(0)
| sub ix(0)
| add a,ix(1)
| adc a,ix(2)
| sub ix(3)
| sbc a,ix(0)
| and ix(0)
| xor ix(0)
@ -760,8 +760,8 @@ class Z80AssemblySuite extends FunSuite with Matchers {
| ex (sp),ix
| jp (ix)
| ld sp,ix
| ld a,ix(0)
| ld ix(0),a
| ld a,ix(2)
| ld ix(2),a
|
| ret
| }
@ -773,9 +773,9 @@ class Z80AssemblySuite extends FunSuite with Matchers {
"""
| asm void main () {
| ret
| add a,iy(0)
| adc a,iy(0)
| sub iy(0)
| add a,iy(1)
| adc a,iy(2)
| sub iy(3)
| sbc a,iy(0)
| and iy(0)
| xor iy(0)
@ -805,8 +805,8 @@ class Z80AssemblySuite extends FunSuite with Matchers {
| ex (sp),iy
| jp (iy)
| ld sp,iy
| ld a,iy(0)
| ld iy(0),a
| ld a,iy(2)
| ld iy(2),a
|
| ret
| }

View File

@ -149,6 +149,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
CompilationFlag.InterproceduralOptimization -> true,
CompilationFlag.CompactReturnDispatchParams -> true,
CompilationFlag.UseOptimizationHints -> true,
CompilationFlag.IdentityPage -> blastProcessing, // TODO
CompilationFlag.SoftwareStack -> softwareStack,
CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu),
CompilationFlag.EmitSC02Opcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu),