diff --git a/CHANGELOG.md b/CHANGELOG.md index cd072ff4..39d6f1b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ * 6502: Few minor optimization improvements. +* 6502: Inlining improvements. + ## 0.3.6 * **Breaking change!** diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index d2903b93..110a408a 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -445,6 +445,8 @@ trait ParamSignature { def canBePointedTo: Boolean def requireTrampoline(compilationOptions: CompilationOptions): Boolean + + def paramThingNames: Set[String] } case class NormalParamSignature(params: List[VariableInMemory]) extends ParamSignature { @@ -459,6 +461,8 @@ case class NormalParamSignature(params: List[VariableInMemory]) extends ParamSig case _ => false } + def paramThingNames: Set[String] = params.map(_.name).toSet + } sealed trait ParamPassingConvention { @@ -525,6 +529,8 @@ case class AssemblyParamSignature(params: List[AssemblyParam]) extends ParamSign override def requireTrampoline(compilationOptions: CompilationOptions): Boolean = false // all pointable functions with this kind of signature by definition use the pure register-cased parameter passing convention + + def paramThingNames: Set[String] = params.map(_.variable.name).toSet } case class EmptyFunctionParamSignature(paramType: Type) extends ParamSignature { @@ -535,4 +541,6 @@ case class EmptyFunctionParamSignature(paramType: Type) extends ParamSignature { def canBePointedTo: Boolean = false override def requireTrampoline(compilationOptions: CompilationOptions): Boolean = false + + def paramThingNames: Set[String] = Set.empty } diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index 4377752f..1f4532db 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -236,7 +236,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program val code = compileFunction(function, optimizations, options, inlinedFunctions, labelMap.toMap, niceFunctionProperties.toSet) val strippedCodeForInlining = for { limit <- potentiallyInlineable.get(f) - if code.map(_.sizeInBytes).sum <= limit + if inliningCalculator.calculateExpectedSizeAfterInlining(options, function.params, code) <= limit s <- inliningCalculator.codeForInlining(f, functionsThatCanBeCalledFromInlinedFunctions, code) } yield s strippedCodeForInlining match { diff --git a/src/main/scala/millfork/output/AbstractInliningCalculator.scala b/src/main/scala/millfork/output/AbstractInliningCalculator.scala index 8455d688..61d0e0c8 100644 --- a/src/main/scala/millfork/output/AbstractInliningCalculator.scala +++ b/src/main/scala/millfork/output/AbstractInliningCalculator.scala @@ -1,10 +1,11 @@ package millfork.output -import millfork.JobContext -import millfork.assembly.{AbstractCode, Elidability} +import millfork.assembly.m6809.MOpcode import millfork.assembly.mos.Opcode import millfork.assembly.z80.ZOpcode -import millfork.compiler.AbstractCompiler +import millfork.{CompilationOptions, JobContext} +import millfork.assembly.{AbstractCode, Elidability} +import millfork.env.ParamSignature import millfork.node._ import scala.collection.mutable @@ -16,8 +17,10 @@ import scala.collection.mutable case class InliningResult(potentiallyInlineableFunctions: Map[String, Int], nonInlineableFunctions: Set[String]) abstract class AbstractInliningCalculator[T <: AbstractCode] { + def codeForInlining(fname: String, functionsThatCanBeCalledFromInlinedFunctions: Set[String], code: List[T]): Option[List[T]] def inline(code: List[T], inlinedFunctions: Map[String, List[T]], jobContext: JobContext): List[T] + def calculateExpectedSizeAfterInlining(options: CompilationOptions, params: ParamSignature, code: List[T]): Int private val sizes = Seq(64, 64, 8, 6, 5, 5, 4) @@ -74,6 +77,7 @@ abstract class AbstractInliningCalculator[T <: AbstractCode] { case Assignment(VariableExpression(_), expr) => getAllCalledFunctions(expr :: Nil) case MosAssemblyStatement(Opcode.JSR, _, VariableExpression(name), Elidability.Elidable) => (name -> false) :: Nil case Z80AssemblyStatement(ZOpcode.CALL, _, _, VariableExpression(name), Elidability.Elidable) => (name -> false) :: Nil + case M6809AssemblyStatement(MOpcode.JSR, _, VariableExpression(name), Elidability.Elidable) => (name -> false) :: Nil case s: Statement => getAllCalledFunctions(s.getAllExpressions) case s: VariableExpression => Set( s.name, @@ -94,4 +98,14 @@ abstract class AbstractInliningCalculator[T <: AbstractCode] { case SeparateBytesExpression(h, l) => getAllCalledFunctions(List(h, l)) case _ => Nil } + + protected def extractThingName(fullName: String): String = { + var result = fullName.takeWhile(_ != '.') + if (result.length == fullName.length) return result + val suffix = fullName.drop(result.length) + if (suffix == ".return" || suffix.startsWith(".return.")) { + result += ".return" + } + result + } } diff --git a/src/main/scala/millfork/output/M6809InliningCalculator.scala b/src/main/scala/millfork/output/M6809InliningCalculator.scala index 9c382bde..5d0204b8 100644 --- a/src/main/scala/millfork/output/M6809InliningCalculator.scala +++ b/src/main/scala/millfork/output/M6809InliningCalculator.scala @@ -1,7 +1,8 @@ package millfork.output -import millfork.JobContext +import millfork.{CompilationOptions, JobContext} import millfork.assembly.m6809.MLine +import millfork.env.ParamSignature /** * @author Karol Stasiak @@ -9,6 +10,14 @@ import millfork.assembly.m6809.MLine object M6809InliningCalculator extends AbstractInliningCalculator[MLine] { override def codeForInlining(fname: String, functionsThatCanBeCalledFromInlinedFunctions: Set[String], code: List[MLine]): Option[List[MLine]] = None + override def calculateExpectedSizeAfterInlining(options: CompilationOptions, params: ParamSignature, code: List[MLine]): Int = { + var sum = 0 + for (c <- code) { + sum += c.sizeInBytes + } + sum + } + override def inline(code: List[MLine], inlinedFunctions: Map[String, List[MLine]], jobContext: JobContext): List[MLine] = { if (inlinedFunctions.isEmpty) code else ??? diff --git a/src/main/scala/millfork/output/MosInliningCalculator.scala b/src/main/scala/millfork/output/MosInliningCalculator.scala index 7754e4a2..ad3b00af 100644 --- a/src/main/scala/millfork/output/MosInliningCalculator.scala +++ b/src/main/scala/millfork/output/MosInliningCalculator.scala @@ -1,6 +1,6 @@ package millfork.output -import millfork.JobContext +import millfork.{CompilationOptions, JobContext} import millfork.assembly.Elidability import millfork.assembly.mos.Opcode._ import millfork.assembly.mos.{AddrMode, _} @@ -16,10 +16,38 @@ import scala.collection.mutable object MosInliningCalculator extends AbstractInliningCalculator[AssemblyLine] { - private val sizes = Seq(64, 64, 8, 6, 5, 5, 4) - private val badOpcodes = Set(RTI, RTS, JSR, BRK, RTL, BSR, BYTE) ++ OpcodeClasses.ChangesStack private val jumpingRelatedOpcodes = Set(LABEL, JMP) ++ OpcodeClasses.ShortBranching + def calculateExpectedSizeAfterInlining(options: CompilationOptions, params: ParamSignature, code: List[AssemblyLine]): Int = { + var sum = 0 + var beforeParams = true + val paramNames = params.paramThingNames + for ((c, ix) <- code.zipWithIndex) { + import Opcode._ + import millfork.assembly.mos.AddrMode._ + sum += (c match { + case AssemblyLine0(LABEL, _, _) if ix == 0 => 0 + + case AssemblyLine0( + LDA | LDX | LDY | LDZ, + Absolute | ZeroPage, + MemoryAddressConstant(thing)) + if beforeParams && paramNames(extractThingName(thing.name)) + => 1 // a guess of how likely parameter copying can be avoided + + case AssemblyLine0( + STA | STX | STY | STZ, + Absolute | ZeroPage | LongAbsolute, + _) + => c.sizeInBytes + + case _ => + beforeParams = false + c.sizeInBytes + }) + } + sum + } def codeForInlining(fname: String, functionsThatCanBeCalledFromInlinedFunctions: Set[String], code: List[AssemblyLine]): Option[List[AssemblyLine]] = { if (code.isEmpty) return None diff --git a/src/main/scala/millfork/output/Z80InliningCalculator.scala b/src/main/scala/millfork/output/Z80InliningCalculator.scala index c427faf0..b7a151fa 100644 --- a/src/main/scala/millfork/output/Z80InliningCalculator.scala +++ b/src/main/scala/millfork/output/Z80InliningCalculator.scala @@ -1,9 +1,8 @@ package millfork.output -import millfork.JobContext +import millfork.{CompilationOptions, JobContext} import millfork.assembly.Elidability import millfork.assembly.z80._ -import millfork.compiler.AbstractCompiler import millfork.env._ import scala.collection.GenTraversableOnce @@ -18,6 +17,14 @@ object Z80InliningCalculator extends AbstractInliningCalculator[ZLine] { private val badOpcodes = Set(RET, RETI, RETN, CALL, BYTE, POP, PUSH) private val jumpingRelatedOpcodes = Set(LABEL, JP, JR) + override def calculateExpectedSizeAfterInlining(options: CompilationOptions, params: ParamSignature, code: List[ZLine]): Int = { + var sum = 0 + for (c <- code) { + sum += c.sizeInBytes + } + sum + } + override def codeForInlining(fname: String, functionsThatCanBeCalledFromInlinedFunctions: Set[String], code: List[ZLine]): Option[List[ZLine]] = { if (code.isEmpty) return None code.last match {