Optimize the optimizer

This commit is contained in:
Karol Stasiak 2022-02-08 14:42:30 +01:00
parent 7b205a2754
commit 6af84d1628
5 changed files with 135 additions and 105 deletions

View File

@ -30,7 +30,7 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
@ -43,29 +43,30 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override def optimize(f: NormalFunction, code: List[MLine], optimizationContext: OptimizationContext): List[MLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, MLine)], optimizationContext: OptimizationContext): (Boolean, List[MLine]) = {
final def optimizeImpl(f: NormalFunction, code: List[MLine], taggedCode: List[(FlowInfo, MLine)], optimizationContext: OptimizationContext): List[MLine] = {
val log = optimizationContext.log
code match {
case Nil => false -> Nil
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
for ((rule, index) <- actualRulesWithIndex) {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
optimizationContext.niceFunctionProperties,
head._1.labelUseCount(_)
)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, MLine)]) =>
val matchedChunkToOptimize: List[MLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[MLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -74,28 +75,34 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}

View File

@ -8,6 +8,8 @@ import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext
import millfork.env._
import millfork.error.ConsoleLogger
import scala.collection.mutable
/**
* @author Karol Stasiak
*/
@ -23,48 +25,52 @@ object EmptyParameterStoreRemoval extends AssemblyOptimization[AssemblyLine] {
case AssemblyLine0(JSR | BSR | JMP, _, NumericConstant(addr, _)) => Some("$" + addr.toHexString)
case _ => None
}.toSet
val foreignVariables = f.environment.root.things.values.flatMap {
val foreignVariables = mutable.Set[String]()
f.environment.root.things.values.foreach {
case other: NormalFunction if !other.name.endsWith(".trampoline") =>
val address = other.address match {
case Some(NumericConstant(addr, _)) => "$" + addr.toHexString
case _ => ""
}
if (other.name == f.name || usedFunctions(other.name) || usedFunctions(address)) {
Nil
// do nothing
} else {
val params = other.params match {
case NormalParamSignature(ps) => ps.map(_.name)
case _ => Nil
other.params match {
case NormalParamSignature(ps) =>
ps.foreach(p => foreignVariables += p.name)
case _ =>
}
val locals = other.environment.things.values.flatMap{
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Auto => Some(th.name)
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Zeropage => Some(th.name) // TODO: ???
case _ => None
other.environment.things.values.foreach {
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Auto =>
foreignVariables += th.name
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Zeropage =>
foreignVariables += th.name // TODO: ???
case _ =>
}
if (other.returnType.size > Cpu.getMaxSizeReturnableViaRegisters(optimizationContext.options.platform.cpu, optimizationContext.options)) {
other.name + ".return" :: (params ++ locals)
} else {
params ++ locals
foreignVariables += other.name + ".return"
}
}
case _ => Nil
}.toSet
val stillReadOrStoredVariables = code.flatMap {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => Some(th.name)
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => Some(th.name)
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => Some(th.name)
case _ => None
}.toSet
val stillReadVariables = code.flatMap {
case _ =>
}
val stillReadOrStoredVariables = mutable.Set[String]()
code.foreach {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => stillReadOrStoredVariables += th.name
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => stillReadOrStoredVariables += th.name
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => stillReadOrStoredVariables += th.name
case _ =>
}
val stillReadVariables = mutable.Set[String]()
code.foreach {
case AssemblyLine(op, am, MemoryAddressConstant(th), Elidability.Elidable, _)
if storeInstructions(op) && storeAddrModes(am) => Nil
if storeInstructions(op) && storeAddrModes(am) =>
case AssemblyLine(op, am, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(_, _)), Elidability.Elidable, _)
if storeInstructions(op) && storeAddrModes(am) => Nil
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => Some(th.name)
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => Some(th.name)
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => Some(th.name)
case _ => None
}.toSet
if storeInstructions(op) && storeAddrModes(am) =>
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => stillReadVariables += th.name
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => stillReadVariables += th.name
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => stillReadVariables += th.name
case _ =>
}
val unusedForeignVariables = (foreignVariables & stillReadOrStoredVariables) -- stillReadVariables
if (unusedForeignVariables.isEmpty) {

View File

@ -30,7 +30,7 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
@ -43,20 +43,20 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, AssemblyLine)], optimizationContext: OptimizationContext): (Boolean, List[AssemblyLine]) = {
final def optimizeImpl(f: NormalFunction, code: List[AssemblyLine], taggedCode: List[(FlowInfo, AssemblyLine)], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val log = optimizationContext.log
code match {
case Nil => false -> Nil
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
for ((rule, index) <- actualRulesWithIndex) {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
@ -64,9 +64,10 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
optimizationContext.niceFunctionProperties,
head._1.labelUseCount(_)
)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, AssemblyLine)]) =>
val matchedChunkToOptimize: List[AssemblyLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[AssemblyLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -75,28 +76,34 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}

View File

@ -9,6 +9,7 @@ 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}
@ -147,24 +148,26 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
// assembly functions do not get this optimization
return code
}
val stillUsedVariables = code.flatMap {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => Some(th.name)
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
}.toSet
val variablesWithAddressesTaken = code.flatMap {
case AssemblyLine0(_, _, SubbyteConstant(MemoryAddressConstant(th), _)) => Some(th.name)
case _ => None
}.toSet
val localVariables = f.environment.getAllLocalVariables.filter {
case v@MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
}
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 variablesWithRegisterHint = f.environment.getAllLocalVariables.filter {
case v@MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
case _ => false
}.map(_.name).toSet
val variablesWithLifetimes = localVariables.map(v =>
v.name -> VariableLifetime.apply(v.name, code)
@ -187,10 +190,12 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
log = log
)
val labelsUsedOnce: Set[String] = code.flatMap {
case AssemblyLine0(op, _, MemoryAddressConstant(Label(l))) if op != Opcode.LABEL => Some(l)
case _ => None
}.groupBy(identity).filter(_._2.size == 1).keySet
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),

View File

@ -30,7 +30,7 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
@ -43,23 +43,24 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, ZLine)], optimizationContext: OptimizationContext): (Boolean, List[ZLine]) = {
final def optimizeImpl(f: NormalFunction, code:List[ZLine], taggedCode: List[(FlowInfo, ZLine)], optimizationContext: OptimizationContext): List[ZLine] = {
val log = optimizationContext.log
code match {
case Nil => (false, Nil)
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
for ((rule, index) <- actualRulesWithIndex) {
val ctx = new AssemblyMatchingContext(optimizationContext.options)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, ZLine)]) =>
val matchedChunkToOptimize: List[ZLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[ZLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -68,30 +69,34 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
if (log.traceEnabled) {
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
}
if (log.traceEnabled) {
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}