1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-12 06:29:34 +00:00

6502: Improve inlining of functions with lots of parameters.

This commit is contained in:
Karol Stasiak 2019-09-16 21:32:57 +02:00
parent fa7844e0b8
commit 457472080f
7 changed files with 78 additions and 10 deletions

View File

@ -28,6 +28,8 @@
* 6502: Few minor optimization improvements.
* 6502: Inlining improvements.
## 0.3.6
* **Breaking change!**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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