mirror of
https://github.com/KarolS/millfork.git
synced 2024-06-12 22:29:33 +00:00
1227 lines
62 KiB
Scala
1227 lines
62 KiB
Scala
package millfork.assembly.mos.opt
|
|
|
|
import millfork.{CompilationFlag, NonOverlappingIntervals}
|
|
import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext}
|
|
import millfork.assembly.mos._
|
|
import millfork.assembly.mos.Opcode._
|
|
import AddrMode._
|
|
import millfork.env._
|
|
import millfork.error.Logger
|
|
import millfork.node.MosNiceFunctionProperty
|
|
|
|
import scala.collection.mutable
|
|
import scala.collection.mutable.ListBuffer
|
|
import scala.util.control.TailCalls.{TailRec, done, tailcall}
|
|
|
|
/**
|
|
* @author Karol Stasiak
|
|
*/
|
|
object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] {
|
|
|
|
override def minimumRequiredLines: Int = 2
|
|
|
|
override def requiredFlags: Set[CompilationFlag.Value] = Set(CompilationFlag.RegisterVariables)
|
|
|
|
object CyclesAndBytes {
|
|
val Zero = CyclesAndBytes(0, 0)
|
|
}
|
|
case class CyclesAndBytes(bytes: Int, cycles: Int) {
|
|
def +(that: CyclesAndBytes) = CyclesAndBytes(this.bytes + that.bytes, this.cycles + that.cycles)
|
|
}
|
|
|
|
case class FeaturesForIndexRegisters(
|
|
useIdentityPage: Boolean,
|
|
izIsAlwaysZero: Boolean,
|
|
indexRegisterTransfers: Boolean,
|
|
functionsSafeForX: Set[String],
|
|
functionsSafeForY: Set[String],
|
|
functionsSafeForZ: Set[String],
|
|
identityArray: Constant,
|
|
log: Logger)
|
|
|
|
case class FeaturesForAccumulator(
|
|
cmos: Boolean,
|
|
safeFunctions: Set[String],
|
|
labelsUsedOnce: Set[String],
|
|
labelsSyncedAt: Set[String],
|
|
log: Logger) {
|
|
override def toString: String = s"{cmos=$cmos, safeFunctions=[${safeFunctions.mkString(",")}], labelsSyncedAt=[${safeFunctions.mkString(",")}]}"
|
|
|
|
def addSafeLabel(label: String): FeaturesForAccumulator = copy(labelsSyncedAt = this.labelsSyncedAt + label)
|
|
}
|
|
|
|
// 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(
|
|
JSR, STX, TXA, INX, DEX, CPX,
|
|
LDX_W, STX_W, CPX_W, DEX_W, INX_W,
|
|
PHX, PLX, PHX_W, PLX_W,
|
|
SBX, SAX, LXA, XAA, AHX, SHX, SHY, LAS, TAS,
|
|
HuSAX, SXY, TXY, TXY,
|
|
BYTE,
|
|
)
|
|
|
|
private val opcodesThatAlwaysPrecludeYAllocation = Set(
|
|
JSR, STY, TYA, INY, DEY, CPY,
|
|
LDY_W, STY_W, CPY_W, DEY_W, INY_W,
|
|
PHY, PLY, PHY_W, PLY_W,
|
|
AHX, SHX, SHY, LAS, TAS,
|
|
SAY, SXY, TXY, TYX,
|
|
BYTE,
|
|
)
|
|
|
|
private val opcodesThatAlwaysPrecludeZAllocation = Set(
|
|
JSR, STZ, TZA, INZ, DEZ, CPZ,
|
|
PHZ, PLZ,
|
|
BYTE,
|
|
)
|
|
|
|
private val opcodesThatAlwaysPrecludeAAllocation = Set(
|
|
JSR, PHA, PLA,
|
|
ADC, SBC, ORA, EOR, AND, BIT,
|
|
ADC_W, SBC_W, ORA_W, EOR_W, AND_W, BIT_W,
|
|
RRA, RLA, ISC, SLO, SRE,
|
|
ALR, ARR, ANC, SBX, LXA, XAA,
|
|
AHX, SHX, SHY, LAS, TAS,
|
|
HuSAX, SAY,
|
|
TCD, TDC, TSC, TCS, XBA,
|
|
BYTE,
|
|
)
|
|
|
|
private val opcodesThatNeverPrecludeAAllocation = Set(
|
|
INX, INY, DEX, DEY,
|
|
CLC, SEC, CLV, SED, CLD, CLI, SEI, NOP, BRK,
|
|
INX_W, DEX_W, INY_W, DEY_W,
|
|
PHX, PHY, PHZ, PHX_W, PHY_W,
|
|
PLX, PLY, PLZ, PLX_W, PLY_W,
|
|
TXY, TYX, TXS, TSX,
|
|
)
|
|
|
|
// If any of these opcodes is used on a variable
|
|
// then it's too hard to assign that variable to a register.
|
|
// Also, LDY prevents assigning a variable to X and LDX and LAX prevent assigning a variable to Y.
|
|
private val opcodesThatCannotBeUsedWithIndexRegistersAsParameters = Set(
|
|
LDX_W, LDY_W, LDA_W,
|
|
BIT, CPX, CPY, STY,
|
|
BIT_W, CPX_W, CPY_W, STY_W,
|
|
EOR, ORA, AND, ADC, SBC, CMP,
|
|
EOR_W, ORA_W, AND_W, ADC_W, SBC_W, CMP_W,
|
|
ROL, ROR, LSR, ASL, STX,
|
|
ROL_W, ROR_W, LSR_W, ASL_W, STX_W,
|
|
INC_W, DEC_W,
|
|
SAX, SLO, SRE, ISC, DCP, RLA, RRA,
|
|
AHX, SHY, SHX, LAS, TAS,
|
|
TRB, TSB,
|
|
ASR,
|
|
)
|
|
|
|
private val opcodesThatCannotBeUsedWithAccumulatorAsParameter = Set(
|
|
BIT, CPX, CPY,
|
|
BIT_W, CPX_W, CPY_W,
|
|
EOR, ORA, AND, ADC, SBC, CMP, STA,
|
|
EOR_W, ORA_W, AND_W, ADC_W, SBC_W, CMP_W, STA_W,
|
|
INC_W, DEC_W,
|
|
SAX, SLO, SRE, ISC, DCP, RLA, RRA,
|
|
AHX, SHY, SHX, LAS, TAS,
|
|
TRB, TSB,
|
|
ASR,
|
|
)
|
|
|
|
private val opcodesCommutative = Set(AND, ORA, EOR, ADC)
|
|
private val opcodesIdentityTable = Set(AND, ORA, EOR, CMP, ADC, SBC)
|
|
|
|
private val LdxAddrModes = Set(ZeroPage, Absolute, Immediate, AbsoluteY, ZeroPageY)
|
|
private val LdyAddrModes = Set(ZeroPage, Absolute, Immediate, AbsoluteX, ZeroPageX)
|
|
private val LdzAddrModes = Set(Absolute, Immediate, AbsoluteX)
|
|
private val CpxyzAddrModes = Set(Absolute, Immediate, ZeroPage)
|
|
|
|
override def name = "Allocating variables to index registers"
|
|
|
|
|
|
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
|
|
val options = optimizationContext.options
|
|
val log = options.log
|
|
val paramVariables = f.params match {
|
|
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 =>
|
|
Set[String]()
|
|
case NormalParamSignature(ps) =>
|
|
ps.map(_.name).toSet
|
|
case _ =>
|
|
// assembly functions do not get this optimization
|
|
return code
|
|
}
|
|
val stillUsedVariables = mutable.Set[String]()
|
|
val variablesWithAddressesTaken = mutable.Set[String]()
|
|
code.foreach {
|
|
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => stillUsedVariables += th.name
|
|
case AssemblyLine0(_, _, SubbyteConstant(MemoryAddressConstant(th), _)) => variablesWithAddressesTaken += th.name
|
|
case _ => None
|
|
}
|
|
val localVariables = mutable.Set[Variable]()
|
|
val variablesWithRegisterHint = mutable.Set[String]()
|
|
|
|
f.environment.getAllLocalVariables.foreach {
|
|
case v@MemoryVariable(name, typ, alloc@(VariableAllocationMethod.Auto | VariableAllocationMethod.Register)) =>
|
|
if (typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile){
|
|
localVariables += v
|
|
if (alloc == VariableAllocationMethod.Register) {
|
|
variablesWithRegisterHint += v.name
|
|
}
|
|
}
|
|
case _ => false
|
|
}
|
|
|
|
val variablesWithLifetimes = localVariables.map(v =>
|
|
v.name -> VariableLifetime.apply(v.name, code)
|
|
).toMap
|
|
|
|
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 useIdentityPage = options.flag(CompilationFlag.IdentityPage)
|
|
val identityArray = f.environment.identityPage
|
|
val izIsAlwaysZero = !options.flag(CompilationFlag.Emit65CE02Opcodes)
|
|
val featuresForIndices = FeaturesForIndexRegisters(
|
|
useIdentityPage = useIdentityPage,
|
|
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,
|
|
log = log
|
|
)
|
|
|
|
val tmpUsedLabelList = mutable.ListBuffer[String]()
|
|
code.foreach{
|
|
case AssemblyLine0(op, _, MemoryAddressConstant(Label(l))) if op != Opcode.LABEL => tmpUsedLabelList += l
|
|
case _ =>
|
|
}
|
|
val labelsUsedOnce: Set[String] = tmpUsedLabelList.groupBy(identity).filter(_._2.size == 1).keySet
|
|
|
|
val featuresForAcc = FeaturesForAccumulator(
|
|
cmos = options.flag(CompilationFlag.EmitCmosOpcodes),
|
|
safeFunctions = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeA).map(_._2),
|
|
labelsUsedOnce = labelsUsedOnce,
|
|
labelsSyncedAt = Set(),
|
|
log = log
|
|
)
|
|
|
|
val xCandidates = variablesWithLifetimes.filter {
|
|
case (vName, range) =>
|
|
importances(range.start).x != Important
|
|
}.flatMap {
|
|
case (vName, range) =>
|
|
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)
|
|
}
|
|
}
|
|
|
|
val yCandidates = variablesWithLifetimes.filter {
|
|
case (vName, range) =>
|
|
importances(range.start).y != Important
|
|
}.flatMap {
|
|
case (vName, range) =>
|
|
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)
|
|
}
|
|
}
|
|
|
|
val zCandidates = if (izIsAlwaysZero) Nil else variablesWithLifetimes.filter {
|
|
case (vName, range) =>
|
|
importances(range.start).iz != Important
|
|
}.flatMap {
|
|
case (vName, range) =>
|
|
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)
|
|
}
|
|
}
|
|
|
|
val aCandidates = variablesWithLifetimes.filter {
|
|
case (vName, range) =>
|
|
importances(range.start).a != Important
|
|
}.flatMap {
|
|
case (vName, range) =>
|
|
canBeInlinedToAccumulator(
|
|
featuresForAcc,
|
|
start = true,
|
|
synced = false,
|
|
vName,
|
|
code.zip(importances).slice(range.start, range.end)).map { score =>
|
|
(vName, range, if (variablesWithRegisterHint(vName)) score + CyclesAndBytes(16, 16) else score)
|
|
}
|
|
}
|
|
// println(s"X: $xCandidates")
|
|
// println(s"Y: $yCandidates")
|
|
// println(s"Z: $zCandidates")
|
|
// println(s"A: $aCandidates")
|
|
|
|
val xCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](xCandidates, _._2.start, _._2.end)
|
|
val yCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](yCandidates, _._2.start, _._2.end)
|
|
val zCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](zCandidates, _._2.start, _._2.end)
|
|
val aCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](aCandidates, _._2.start, _._2.end)
|
|
|
|
val variants = for {
|
|
vx <- if (options.flag(CompilationFlag.SingleThreaded)) xCandidateSets else xCandidateSets.par
|
|
nx = vx.map(_._1)
|
|
|
|
vy <- yCandidateSets
|
|
ny = vy.map(_._1)
|
|
if (nx & ny).isEmpty
|
|
|
|
vz <- zCandidateSets
|
|
nz = vz.map(_._1)
|
|
if (nx & nz).isEmpty
|
|
if (ny & nz).isEmpty
|
|
|
|
va <- aCandidateSets
|
|
na = va.map(_._1)
|
|
if (nz & na).isEmpty
|
|
if (nx & na).isEmpty
|
|
if (ny & na).isEmpty
|
|
|
|
score = vx.toSeq.map(x => costFunction(x._3)).sum +
|
|
vy.toSeq.map(x => costFunction(x._3)).sum +
|
|
va.toSeq.map(x => costFunction(x._3)).sum +
|
|
vz.toSeq.map(x => costFunction(x._3)).sum
|
|
} yield (score, vx, vy, vz, va)
|
|
|
|
if (variants.isEmpty) {
|
|
return code
|
|
}
|
|
|
|
// variants.foreach(println)
|
|
|
|
val (_, bestXs, bestYs, bestZs, bestAs) = variants.maxBy(_._1)
|
|
|
|
def reportOptimizedBlock[T](oldCode: List[(AssemblyLine, T)], newCode: List[AssemblyLine]): Unit = {
|
|
if (log.traceEnabled) {
|
|
oldCode.foreach(l => log.trace(l._1.toString))
|
|
log.trace(" ↓")
|
|
newCode.foreach(l => log.trace(l.toString))
|
|
}
|
|
}
|
|
|
|
if (bestXs.nonEmpty || bestYs.nonEmpty || bestZs.nonEmpty || bestAs.nonEmpty) {
|
|
val output = ListBuffer[AssemblyLine]()
|
|
var i = 0
|
|
while (i < code.length) {
|
|
var done = false
|
|
bestXs.find(_._2.start == i).foreach {
|
|
case (v, range, _) =>
|
|
log.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, featuresForIndices, oldCode).result
|
|
reportOptimizedBlock(oldCode, newCode)
|
|
output ++= newCode
|
|
i = range.end
|
|
if (removeVariablesForReal && contains(range, variablesWithLifetimes(v))) {
|
|
f.environment.removeVariable(v)
|
|
}
|
|
done = true
|
|
}
|
|
if (!done) {
|
|
bestYs.find(_._2.start == i).foreach {
|
|
case (v, range, _) =>
|
|
log.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, featuresForIndices, oldCode).result
|
|
reportOptimizedBlock(oldCode, newCode)
|
|
output ++= newCode
|
|
i = range.end
|
|
if (removeVariablesForReal && contains(range, variablesWithLifetimes(v))) {
|
|
f.environment.removeVariable(v)
|
|
}
|
|
done = true
|
|
}
|
|
}
|
|
if (!done) {
|
|
bestZs.find(_._2.start == i).foreach {
|
|
case (v, range, _) =>
|
|
log.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, featuresForIndices, oldCode).result
|
|
reportOptimizedBlock(oldCode, newCode)
|
|
output ++= newCode
|
|
i = range.end
|
|
if (removeVariablesForReal && contains(range, variablesWithLifetimes(v))) {
|
|
f.environment.removeVariable(v)
|
|
}
|
|
done = true
|
|
}
|
|
}
|
|
if (!done) {
|
|
bestAs.find(_._2.start == i).foreach {
|
|
case (v, range, _) =>
|
|
log.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), featuresForIndices, oldCode).result
|
|
reportOptimizedBlock(code.zip(importances), newCode)
|
|
output ++= newCode
|
|
i = range.end
|
|
if (removeVariablesForReal && contains(range, variablesWithLifetimes(v))) {
|
|
f.environment.removeVariable(v)
|
|
}
|
|
done = true
|
|
}
|
|
}
|
|
if (!done) {
|
|
output += code(i)
|
|
i += 1
|
|
}
|
|
}
|
|
output.toList
|
|
} else {
|
|
code
|
|
}
|
|
}
|
|
|
|
def contains(outer: Range, inner: Range): Boolean = {
|
|
outer.contains(inner.start) && outer.contains(inner.end - 1)
|
|
}
|
|
|
|
// TODO: STA has different flag behaviour than TAX, keep it in mind!
|
|
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("-")
|
|
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 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 accessingX || accessingY || accessingZ =>
|
|
// if an address of a variable is used, then that variable cannot be assigned to a register
|
|
None
|
|
|
|
case (AssemblyLine0(LDX | STX, AbsoluteY | ZeroPageY | IndexedY | IndexedSY | IndexedZ, _), _) :: xs if xCandidate.isDefined =>
|
|
None
|
|
|
|
case (AssemblyLine0(LDY | STY, AbsoluteX | ZeroPageX | IndexedX | LongAbsoluteX | IndexedZ, _), _) :: xs if yCandidate.isDefined =>
|
|
None
|
|
|
|
case (AssemblyLine0(LDZ | STZ, AbsoluteX | ZeroPageX | IndexedX | LongAbsoluteX | AbsoluteY | ZeroPageY | IndexedY | IndexedSY, _), _) :: xs if zCandidate.isDefined =>
|
|
None
|
|
|
|
case (AssemblyLine0(_, AbsoluteX | AbsoluteY | LongAbsoluteX |
|
|
ZeroPageX | ZeroPageY |
|
|
IndexedY | IndexedX | IndexedZ |
|
|
LongIndexedY | LongIndexedZ |
|
|
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 (accessingX || accessingY || accessingZ) {
|
|
None
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine0(SEP | REP, Immediate, NumericConstant(nn, _)), _) :: xs =>
|
|
if ((nn & 0x10) == 0) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
else None
|
|
|
|
case (AssemblyLine0(SEP | REP, _, _), _) :: xs => None
|
|
|
|
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 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 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 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 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 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 accessingX => 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.useIdentityPage =>
|
|
if (accessingX || accessingY) {
|
|
if (elidability == Elidability.Elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 0, cycles = -1))
|
|
else None
|
|
} else {
|
|
if (accessingZ) None
|
|
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
|
|
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 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 accessingZ && (opcode == LDX || opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
|
|
// if a variable is used by some opcodes, then it cannot be assigned to a register
|
|
None
|
|
|
|
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), imp) :: xs
|
|
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 && accessingX) {
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs
|
|
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 && accessingX) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), imp) :: xs if yCandidate.isDefined =>
|
|
// 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 && accessingY) {
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), imp) :: xs if zCandidate.isDefined =>
|
|
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 {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(LDX, _, _), _) :: xs if xCandidate.isDefined =>
|
|
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
|
|
None
|
|
|
|
case (AssemblyLine0(LDY, _, _), _) :: xs if yCandidate.isDefined =>
|
|
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
|
|
None
|
|
|
|
case (AssemblyLine0(LDZ, _, _), _) :: xs if zCandidate.isDefined =>
|
|
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
|
|
None
|
|
|
|
case (AssemblyLine(LDA, _, _, elidability, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidability2, _), _) :: xs
|
|
if opcodesCommutative(op) =>
|
|
// LDAw/ANDx -> TXA/ANDw
|
|
if (th.name == vx || th.name == vy) {
|
|
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
|
|
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine(LDA, _, _, elidability, _), _) :: (AssemblyLine0(CLC, _, _),_) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidability2, _), _) :: xs
|
|
if opcodesCommutative(op) =>
|
|
if (th.name == vx || th.name == vy) {
|
|
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
|
|
else None
|
|
} else {
|
|
if (accessingZ) None
|
|
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: (AssemblyLine(TAX, _, _, elidability2, _), _) :: xs
|
|
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 && accessingX) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: (AssemblyLine(TAY, _, _, elidability2, _), _) :: xs
|
|
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 && accessingY) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: (AssemblyLine(TAZ, _, _, elidability2, _), _) :: xs
|
|
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 && accessingY) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(LDA | STA, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
|
|
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
|
|
if (accessingY || accessingX || accessingZ) {
|
|
if (elidability == Elidability.Elidable) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine(INC | DEC, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
|
|
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
|
|
if (accessingY || accessingX || accessingZ) {
|
|
if (elidability == Elidability.Elidable) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
|
|
// changing STZ->LDX saves 1 byte
|
|
if (accessingY || accessingX) {
|
|
if (elidability == Elidability.Elidable && features.izIsAlwaysZero) {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case (AssemblyLine0(TAX, _, _), _) :: xs if xCandidate.isDefined =>
|
|
// a variable cannot be inlined if there is TAX not after LDA of that variable
|
|
None
|
|
|
|
case (AssemblyLine0(TAY, _, _), _) :: xs if yCandidate.isDefined =>
|
|
// a variable cannot be inlined if there is TAY not after LDA of that variable
|
|
None
|
|
|
|
case (AssemblyLine0(TAZ, _, _), _) :: xs if zCandidate.isDefined =>
|
|
// a variable cannot be inlined if there is TAZ not after LDA of that variable
|
|
None
|
|
|
|
case (AssemblyLine0(LABEL, _, _), _) :: xs =>
|
|
// labels always end the initial section
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
|
|
case (AssemblyLine0(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
|
|
} else if (yCandidate.isDefined && opcodesThatAlwaysPrecludeYAllocation(x.opcode)) {
|
|
None
|
|
} else if (zCandidate.isDefined && opcodesThatAlwaysPrecludeZAllocation(x.opcode)) {
|
|
None
|
|
} else {
|
|
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
|
|
}
|
|
|
|
case Nil => Some(CyclesAndBytes.Zero)
|
|
}
|
|
}
|
|
|
|
private def isReturn(code: List[(AssemblyLine, CpuImportance)], allowLdx: Boolean = true): Boolean = {
|
|
import Opcode._
|
|
code match {
|
|
case (AssemblyLine0(LDX, Immediate, _), _) :: xs => allowLdx && isReturn(xs, allowLdx = false)
|
|
case (AssemblyLine0(RTS | RTI | RTL, _, _), _) :: _ => true
|
|
case (AssemblyLine0(op, _, _), _) :: xs if OpcodeClasses.NoopDiscardsFlags(op) => isReturn(xs, allowLdx = false)
|
|
case _ => false
|
|
}
|
|
}
|
|
|
|
def isAllVeryNiceForA(label: String, code: List[(AssemblyLine, CpuImportance)]): (Boolean, List[(AssemblyLine, CpuImportance)]) = {
|
|
code match {
|
|
case (AssemblyLine0(op, _, MemoryAddressConstant(Label(l))), _) :: xs if l == label => (true, xs)
|
|
case (AssemblyLine0(op, _, _), _) :: xs if opcodesThatNeverPrecludeAAllocation(op) => isAllVeryNiceForA(label, xs)
|
|
case _ => (false, Nil)
|
|
}
|
|
}
|
|
|
|
def syncsSoon(candidate: String, code: List[(AssemblyLine, CpuImportance)]): Boolean = {
|
|
code match {
|
|
case (AssemblyLine0(op, _, _), _) :: xs if opcodesThatNeverPrecludeAAllocation(op) => syncsSoon(candidate, xs)
|
|
case (AssemblyLine0(LDA, ZeroPage | Absolute | LongAbsolute, MemoryAddressConstant(th)), _) :: xs if th.name == candidate => true
|
|
case _ => false
|
|
}
|
|
}
|
|
|
|
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), Elidability.Elidable, _), _) :: xs
|
|
if th.name == candidate && start || synced =>
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4))
|
|
|
|
case (AssemblyLine0(LABEL, _, MemoryAddressConstant(Label(l))), _) :: xs =>
|
|
if ((synced || features.labelsSyncedAt(l)) && syncsSoon(candidate, xs)) {
|
|
canBeInlinedToAccumulator(features, start = start, synced = synced, candidate, xs)
|
|
} else {
|
|
if (features.labelsUsedOnce(l)) {
|
|
val (isNice, tail) = isAllVeryNiceForA(l, xs)
|
|
if (isNice) {
|
|
canBeInlinedToAccumulator(features, start = start, synced = synced, candidate, tail)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
case (AssemblyLine0(op, Absolute | Relative | LongRelative, MemoryAddressConstant(Label(l))), _) :: xs if OpcodeClasses.AllDirectJumps(op) =>
|
|
if (synced) {
|
|
canBeInlinedToAccumulator(features.addSafeLabel(l), start = start, synced = synced, candidate, xs)
|
|
} else {
|
|
if (features.labelsUsedOnce(l)) {
|
|
val (isNice, tail) = isAllVeryNiceForA(l, xs)
|
|
if (isNice) {
|
|
canBeInlinedToAccumulator(features, start = start, synced = synced, candidate, tail)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
case (AssemblyLine0(_, TripleAbsolute | ZeroPageWithRelative, _), _) :: xs =>
|
|
// TODO: maybe improve later?
|
|
None
|
|
|
|
case (AssemblyLine0(LABEL, _, _), _) :: xs =>
|
|
None
|
|
|
|
case (AssemblyLine0(op, _, _),_) :: xs if opcodesThatAlwaysPrecludeAAllocation(op) =>
|
|
None
|
|
|
|
case (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
|
|
if th.name == candidate && opcodesThatCannotBeUsedWithAccumulatorAsParameter(op) =>
|
|
// if a variable is used by some opcodes, then it cannot be assigned to a register
|
|
None
|
|
|
|
case (AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)), _) :: xs
|
|
if th.name == candidate =>
|
|
// if an address of a variable is used, then that variable cannot be assigned to a register
|
|
None
|
|
|
|
case (AssemblyLine0(_, AbsoluteX | AbsoluteY | ZeroPageX | ZeroPageY | IndexedY | IndexedX | IndexedZ | Indirect | AbsoluteIndexedX, MemoryAddressConstant(th)), _) :: xs
|
|
if th.name == candidate =>
|
|
// if a variable is used as an array or a pointer, then it cannot be assigned to a register
|
|
None
|
|
|
|
case (AssemblyLine0(SEP | REP, Immediate, NumericConstant(nn, _)), _) :: xs =>
|
|
if ((nn & 0x20) == 0) canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs)
|
|
else None
|
|
|
|
case (AssemblyLine0(SEP | REP, _, _), _) :: xs => None
|
|
|
|
case (AssemblyLine(STA, _, MemoryAddressConstant(th), elidability, _), _) :: xs if th.name == candidate =>
|
|
if (synced && elidability == Elidability.Elidable) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(DCP, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == candidate =>
|
|
if (synced) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs)
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(STA | SAX, _, MemoryAddressConstant(th), elidability, _), _) :: xs if th.name != candidate =>
|
|
if (synced) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs)
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(STA | SAX, _, NumericConstant(_, _)), _) :: xs =>
|
|
if (synced) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs)
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(SAX, _, MemoryAddressConstant(th)), _) :: xs if th.name == candidate =>
|
|
// if XAA had stable magic $FF, then SAXv/LDAv would correspond to XAA#ff
|
|
// but there's no point in even thinking about that
|
|
None
|
|
|
|
case (AssemblyLine0(TAX | TAY, _, _),_) :: xs =>
|
|
if (synced) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs)
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(LDA | TYA | TXA | TZA | CLA, _, _), _) :: xs if isReturn(xs) =>
|
|
canBeInlinedToAccumulator(features, start = start, synced = synced, candidate, xs)
|
|
|
|
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, _), imp) :: xs
|
|
if th.name == candidate =>
|
|
// removing LDA saves 3 bytes
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
} else {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
|
|
}
|
|
|
|
case (AssemblyLine(LDA, _, _, elidability, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidability2, _), _) :: xs
|
|
if opcodesCommutative(op) && synced =>
|
|
if (th.name == candidate) {
|
|
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable) canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
else None
|
|
} else canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs)
|
|
|
|
case (AssemblyLine(LDA, _, _, elidability, _), _) :: (AssemblyLine0(CLC, _, _),_) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidability2, _), _) :: xs
|
|
if opcodesCommutative(op) && synced =>
|
|
if (th.name == candidate) {
|
|
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable) canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
|
|
else None
|
|
} else canBeInlinedToAccumulator(features, start = false, synced = synced, candidate, xs)
|
|
|
|
case (AssemblyLine(LDX | LDY | LAX, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs
|
|
if th.name == candidate && synced =>
|
|
// converting a load into a transfer saves 2 bytes
|
|
if (elidability == Elidability.Elidable) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = true, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(LDA | LAX, _, _),_) :: xs =>
|
|
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
|
|
None
|
|
|
|
case (AssemblyLine(ASL | LSR | ROR | ROL, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs
|
|
if th.name == candidate =>
|
|
if (elidability == Elidability.Elidable) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(INC | DEC, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs
|
|
if th.name == candidate =>
|
|
if (features.cmos && elidability == Elidability.Elidable) {
|
|
canBeInlinedToAccumulator(features, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine(TXA | TYA, _, _, elidability, _), imp) :: xs =>
|
|
if (imp.a == Unimportant && imp.c == Unimportant && imp.v == Unimportant && elidability == Elidability.Elidable) {
|
|
// TYA/TXA has to be converted to CPY#0/CPX#0
|
|
canBeInlinedToAccumulator(features, start = false, synced = false, candidate, xs).map(_ + CyclesAndBytes(bytes = -1, cycles = 0))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
case (AssemblyLine0(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)
|
|
}
|
|
}
|
|
|
|
def isNot(v: String, param: Constant): Boolean = param match {
|
|
case MemoryAddressConstant(th) => th.name != v
|
|
case CompoundConstant(_, MemoryAddressConstant(th), _) => th.name != v
|
|
case _ => true
|
|
}
|
|
|
|
def inlineVars(xCandidate: Option[String],
|
|
yCandidate: Option[String],
|
|
zCandidate: Option[String],
|
|
aCandidate: Option[String],
|
|
features: FeaturesForIndexRegisters,
|
|
lines: List[(AssemblyLine, CpuImportance)]): TailRec[List[AssemblyLine]] = {
|
|
// only one of these will be defined:
|
|
val vx = xCandidate.getOrElse("-")
|
|
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 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 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 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 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 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 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 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 accessingX =>
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs))
|
|
} else {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPX, 0).pos(s) :: _)
|
|
}
|
|
|
|
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
|
|
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) && 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) && 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) && 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
|
|
if opcodesCommutative(op) && th.name == va =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(clc :: l.copy(opcode = op) :: _)
|
|
|
|
case (l@AssemblyLine(LDA, _, _, _, s), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
|
|
if opcodesCommutative(op) && th.name == vx =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: l.copy(opcode = op) :: _)
|
|
|
|
case (l@AssemblyLine(LDA, _, _, _, s), _) :: (clc@AssemblyLine0(CLC, _, _), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
|
|
if opcodesCommutative(op) && th.name == vx =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: clc :: l.copy(opcode = op) :: _)
|
|
|
|
case (l@AssemblyLine(LDA, _, _, _, s), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
|
|
if opcodesCommutative(op) && th.name == vy =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: l.copy(opcode = op) :: _)
|
|
|
|
case (l@AssemblyLine(LDA, _, _, _, s), _) :: (clc@AssemblyLine0(CLC, _, _), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
|
|
if opcodesCommutative(op) && th.name == vy =>
|
|
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 accessingA =>
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
|
|
} else {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CMP, 0).pos(s) :: _)
|
|
}
|
|
|
|
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
|
|
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 accessingY =>
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
|
|
} else {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPY, 0).pos(s) :: _)
|
|
}
|
|
|
|
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
|
|
if accessingZ =>
|
|
if (imp.z == Unimportant && imp.n == Unimportant) {
|
|
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
|
|
} else {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPZ, 0).pos(s) :: _)
|
|
}
|
|
|
|
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAX, _, _, Elidability.Elidable, _), _) :: xs
|
|
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 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 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 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 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 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 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 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 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 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 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 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 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) {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPX, 0).pos(l0.source) :: l1.copy(addrMode = AbsoluteX) :: _)
|
|
} else {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(INX).pos(l0.source) :: AssemblyLine.implied(DEX).pos(l0.source) :: l1.copy(addrMode = AbsoluteX) :: _)
|
|
}
|
|
|
|
|
|
case (l0@AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
|
|
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteX, _), f):: xs
|
|
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) {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPY, 0).pos(l0.source) :: l1.copy(addrMode = AbsoluteY) :: _)
|
|
} else {
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(INY).pos(l0.source) :: AssemblyLine.implied(DEY).pos(l0.source) :: l1.copy(addrMode = AbsoluteY) :: _)
|
|
}
|
|
|
|
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 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 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 accessingX => features.log.fatal("Unexpected LDY")
|
|
|
|
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
|
|
if accessingY => features.log.fatal("Unexpected LDX")
|
|
|
|
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
|
|
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 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 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
|
|
if th.name == vx && LdxAddrModes(am) =>
|
|
// these TXA's may get optimized away by a different optimization
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine(LDX, am, param).pos(s1, s2) :: AssemblyLine.implied(TXA).pos(s1, s2) :: _)
|
|
|
|
case (AssemblyLine(LDA, am, param, Elidability.Elidable, s1), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s2), _) :: xs
|
|
if th.name == vy && LdyAddrModes(am) =>
|
|
// these TYA's may get optimized away by a different optimization
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine(LDY, am, param).pos(s1, s2) :: AssemblyLine.implied(TYA).pos(s1, s2) :: _)
|
|
|
|
case (AssemblyLine(LDA, am, param, Elidability.Elidable, s1), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s2), _) :: xs
|
|
if th.name == vz && LdzAddrModes(am) =>
|
|
// these TZA's may get optimized away by a different optimization
|
|
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 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 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 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 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 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 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 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 accessingY => features.log.fatal("Unexpected STX")
|
|
|
|
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
|
|
if accessingX => features.log.fatal("Unexpected STY")
|
|
|
|
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
|
|
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 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 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) :: _)
|
|
|
|
case (AssemblyLine0(TAX, _, _), _) :: xs if xCandidate.isDefined =>
|
|
features.log.fatal("Unexpected TAX")
|
|
|
|
case (AssemblyLine0(TAY, _, _), _) :: xs if yCandidate.isDefined =>
|
|
features.log.fatal("Unexpected TAY")
|
|
|
|
case (AssemblyLine0(TAZ, _, _), _) :: xs if zCandidate.isDefined =>
|
|
features.log.fatal("Unexpected TAZ")
|
|
|
|
case (AssemblyLine(TXA, _, _, _, s), _) :: xs if aCandidate.isDefined =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPX, 0).pos(s) :: _)
|
|
|
|
case (AssemblyLine(TYA, _, _, _, s), _) :: xs if aCandidate.isDefined =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPY, 0).pos(s) :: _)
|
|
|
|
case (AssemblyLine(TZA, _, _, _, s), _) :: xs if aCandidate.isDefined =>
|
|
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(CPZ, 0).pos(s) :: _)
|
|
|
|
case (x, _) :: xs => tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(x :: _)
|
|
|
|
case Nil => done(Nil)
|
|
}
|
|
}
|
|
|
|
def doesntUseXOrY(am: AddrMode.Value): Boolean = am match {
|
|
case Immediate | WordImmediate | ZeroPage | Absolute | LongAbsolute | Relative | LongRelative | Indirect | LongIndirect | Stack | IndexedZ => true
|
|
case _ => false
|
|
}
|
|
}
|