mirror of
https://github.com/KarolS/millfork.git
synced 2024-11-01 21:04:51 +00:00
8080: Optimize function parameters to registers
This commit is contained in:
parent
3b78206c35
commit
e2b7b28b63
@ -28,7 +28,7 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}
|
||||
|
||||
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
|
||||
val vs = VariableStatus(f, code, optimizationContext, _.size == 1).getOrElse(return code)
|
||||
val vs = VariableStatus(f, code, optimizationContext, _.size == 1, allowParams = true).getOrElse(return code)
|
||||
val options = optimizationContext.options
|
||||
val useIx = options.flag(CompilationFlag.UseIxForStack)
|
||||
val useIy = options.flag(CompilationFlag.UseIyForStack)
|
||||
@ -139,6 +139,15 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
val oldCode = vs.codeWithFlow.slice(range.start, range.end)
|
||||
val newCode = inlineVars(v, register, addressInHl = false, addressInBc = false, addressInDe = false, oldCode.map(_._2))
|
||||
reportOptimizedBlock(oldCode, newCode)
|
||||
if (vs.paramVariables(v)) {
|
||||
val addr = vs.localVariables.find(_.name.==(v)).get.asInstanceOf[MemoryVariable].toAddress
|
||||
if (register == ZRegister.A) {
|
||||
output += ZLine.ldAbs8(ZRegister.A, addr)
|
||||
} else {
|
||||
output += ZLine.ldAbs8(ZRegister.A, addr)
|
||||
output += ZLine.ld8(register, ZRegister.A)
|
||||
}
|
||||
}
|
||||
output ++= newCode
|
||||
i = range.end
|
||||
if (removeVariablesForReal &&
|
||||
@ -147,7 +156,7 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
!v.startsWith("SP+") &&
|
||||
vs.variablesWithLifetimesMap.contains(v) &&
|
||||
contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
f.environment.removeVariable(v)
|
||||
if (!vs.paramVariables(v)) f.environment.removeVariable(v)
|
||||
}
|
||||
true
|
||||
case _ => false
|
||||
@ -208,6 +217,33 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}
|
||||
}
|
||||
|
||||
def okPrefix(vs: VariableStatus, v: Variable, range: Range, reg: ZRegister.Value): Boolean = {
|
||||
if (!vs.paramVariables(v.name)) return true
|
||||
if (vs.paramRegs.contains(reg)) {
|
||||
// println(s"okPrefix $v false: reg $reg in use")
|
||||
return false
|
||||
}
|
||||
if (range.size < 2) {
|
||||
// println(s"okPrefix $v false: range $range too small")
|
||||
return false
|
||||
}
|
||||
if (range.start < 1) {
|
||||
// println(s"okPrefix $v true: range $range early enough")
|
||||
return true
|
||||
}
|
||||
import ZOpcode._
|
||||
vs.codeWithFlow.take(range.start) match {
|
||||
case Nil =>
|
||||
case (_, ZLine0(LABEL, _, _)) :: xs =>
|
||||
if (xs.exists { case (_, l) => l.opcode == LABEL || ZOpcodeClasses.NonLinear(l.opcode) }) {
|
||||
// println(s"okPrefix false: LABEL in prefix")
|
||||
return false
|
||||
}
|
||||
case _ => return false
|
||||
}
|
||||
vs.codeWithFlow(range.start - 1)._1.importanceAfter.getRegister(reg) == Unimportant
|
||||
}
|
||||
|
||||
def contains(outer: Range, inner: Range): Boolean = {
|
||||
outer.contains(inner.start) && outer.contains(inner.end - 1)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ object CompactStackFrame extends AssemblyOptimization[ZLine] {
|
||||
case ZLine0(CALL, _, _) => true
|
||||
case _ => false
|
||||
}.toArray
|
||||
val range = VariableLifetime.expandRangeToCoverLoops(code, mayUsePointer)
|
||||
val range = VariableLifetime.expandRangeToCoverLoops(code, mayUsePointer, stretchBackwards = false)
|
||||
if (range.nonEmpty) {
|
||||
val criticalCodeSlice = code.slice(range.start, range.end)
|
||||
if (criticalCodeSlice.exists {
|
||||
|
@ -15,7 +15,7 @@ object EmptyMemoryStoreRemoval extends AssemblyOptimization[ZLine] {
|
||||
override def name = "Removing pointless stores to automatic variables"
|
||||
|
||||
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
|
||||
val vs = VariableStatus(f, code, optimizationContext, _ => true).getOrElse(return code)
|
||||
val vs = VariableStatus(f, code, optimizationContext, _ => true, allowParams = true).getOrElse(return code)
|
||||
if (vs.localVariables.isEmpty) {
|
||||
return code
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package millfork.assembly.z80.opt
|
||||
|
||||
import millfork.assembly.opt.SingleStatus
|
||||
import millfork.assembly.z80.{NoRegisters, OneRegister, TwoRegisters, ZLine, ZLine0, ZOpcode}
|
||||
import millfork.assembly.z80.{NoRegisters, OneRegister, TwoRegisters, ZLine, ZLine0, ZOpcode, ZOpcodeClasses}
|
||||
import millfork.env._
|
||||
import millfork.error.ConsoleLogger
|
||||
import millfork.node.ZRegister
|
||||
@ -13,7 +13,7 @@ object VariableLifetime {
|
||||
|
||||
// This only works for non-stack variables.
|
||||
// TODO: this is also probably very wrong
|
||||
def apply(variableName: String, codeWithFlow: List[(FlowInfo, ZLine)]): Range = {
|
||||
def apply(variableName: String, codeWithFlow: List[(FlowInfo, ZLine)], stretchBackwards: Boolean = false): Range = {
|
||||
import ZRegister._
|
||||
import ZOpcode._
|
||||
|
||||
@ -44,7 +44,7 @@ object VariableLifetime {
|
||||
}
|
||||
|
||||
val code = codeWithFlow.map(_._2)
|
||||
val range = expandRangeToCoverLoops(code, flags)
|
||||
val range = expandRangeToCoverLoops(code, flags, stretchBackwards)
|
||||
|
||||
// val log = new ConsoleLogger
|
||||
// log.verbosity = 3
|
||||
@ -156,7 +156,7 @@ object VariableLifetime {
|
||||
false
|
||||
}
|
||||
|
||||
def expandRangeToCoverLoops(code: List[ZLine], flags: Array[Boolean]): Range = {
|
||||
def expandRangeToCoverLoops(code: List[ZLine], flags: Array[Boolean], stretchBackwards: Boolean): Range = {
|
||||
if (flags.forall(!_)) return Range(0, 0)
|
||||
var min = flags.indexOf(true)
|
||||
var max = flags.lastIndexOf(true) + 1
|
||||
@ -166,6 +166,15 @@ object VariableLifetime {
|
||||
case _ => Nil
|
||||
}).groupBy(_._1).mapValues(_.map(_._2).toSet).view.force
|
||||
|
||||
// a lifetime of a parameter variable should defensively be assumed to start at the beginning of the very first loop in the function:
|
||||
if (stretchBackwards) {
|
||||
for((l, i) <- code.zipWithIndex) {
|
||||
if (i != 0 && i < min && (l.opcode == ZOpcode.LABEL || ZOpcodeClasses.NonLinear(l.opcode))) {
|
||||
flags(i) = true
|
||||
}
|
||||
}
|
||||
min = flags.indexOf(true)
|
||||
}
|
||||
while (changed) {
|
||||
changed = false
|
||||
for ((label, indices) <- labelMap) {
|
||||
|
@ -18,26 +18,27 @@ class VariableStatus(val paramVariables: Set[String],
|
||||
val localVariables: List[Variable],
|
||||
val variablesWithLifetimes: List[(Variable, Range)],
|
||||
val variablesWithLifetimesMap: Map[String, Range],
|
||||
val codeWithFlow: List[(FlowInfo, ZLine)]) {
|
||||
val codeWithFlow: List[(FlowInfo, ZLine)],
|
||||
val paramRegs: Set[ZRegister.Value]) {
|
||||
|
||||
override def toString = s"VariableStatus(paramVariables=$paramVariables, stillUsedVariables=$stillUsedVariables, variablesWithAddressesTaken=$variablesWithAddressesTaken, localVariables=$localVariables, variablesWithLifetimesMap=$variablesWithLifetimesMap)"
|
||||
}
|
||||
|
||||
object VariableStatus {
|
||||
def apply(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext, typFilter: Type => Boolean): Option[VariableStatus] = {
|
||||
def apply(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext, typFilter: Type => Boolean, allowParams: Boolean): Option[VariableStatus] = {
|
||||
val flow = FlowAnalyzer.analyze(f, code, optimizationContext, FlowInfoRequirement.BothFlows)
|
||||
import millfork.node.ZRegister._
|
||||
val paramVariables = f.params match {
|
||||
val (paramVariables, paramRegs) = f.params match {
|
||||
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 =>
|
||||
Set[String]()
|
||||
Set[String]() -> Set(ZRegister.A)
|
||||
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 2 =>
|
||||
Set[String]()
|
||||
Set[String]() -> Set(ZRegister.HL, ZRegister.H, ZRegister.L)
|
||||
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 3 =>
|
||||
Set[String]()
|
||||
Set[String]() -> Set(ZRegister.HL, ZRegister.H, ZRegister.L, ZRegister.DE, ZRegister.E)
|
||||
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 4 =>
|
||||
Set[String]()
|
||||
Set[String]() -> Set(ZRegister.HL, ZRegister.H, ZRegister.L, ZRegister.DE, ZRegister.D, ZRegister.E)
|
||||
case NormalParamSignature(ps) =>
|
||||
ps.map(_.name).toSet
|
||||
ps.map(_.name).toSet -> Set[ZRegister.Value]()
|
||||
case _ =>
|
||||
// assembly functions do not get this optimization
|
||||
return None
|
||||
@ -79,7 +80,7 @@ object VariableStatus {
|
||||
val allLocalVariables = f.environment.getAllLocalVariables
|
||||
val localVariables = allLocalVariables.filter {
|
||||
case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Zeropage) =>
|
||||
typFilter(typ) && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
|
||||
typFilter(typ) && (allowParams || !paramVariables(name)) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
|
||||
case StackVariable(name, typ, _) => typFilter(typ)
|
||||
case _ => false
|
||||
}
|
||||
@ -90,7 +91,7 @@ object VariableStatus {
|
||||
}.map(_.name).toSet
|
||||
val variablesWithLifetimes = localVariables.map {
|
||||
case v: MemoryVariable =>
|
||||
v -> VariableLifetime.apply(v.name, flow)
|
||||
v -> VariableLifetime.apply(v.name, flow, stretchBackwards = paramVariables(v.name))
|
||||
case v: StackVariable =>
|
||||
v -> StackVariableLifetime.apply(v.baseOffset, flow)
|
||||
}
|
||||
@ -110,7 +111,8 @@ object VariableStatus {
|
||||
localVariables,
|
||||
variablesWithLifetimes,
|
||||
variablesWithLifetimesMap,
|
||||
flow))
|
||||
flow,
|
||||
paramRegs))
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,7 @@ package millfork.assembly.z80.opt
|
||||
|
||||
import millfork.{CompilationFlag, NonOverlappingIntervals}
|
||||
import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext}
|
||||
import millfork.assembly.z80.{OneRegister, TwoRegisters, ZFlag, ZLine, ZLine0}
|
||||
import millfork.assembly.z80.{OneRegister, TwoRegisters, ZFlag, ZLine, ZLine0, ZOpcode, ZOpcodeClasses}
|
||||
import millfork.env._
|
||||
import millfork.error.ConsoleLogger
|
||||
import millfork.node.ZRegister
|
||||
@ -28,16 +28,18 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}
|
||||
|
||||
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
|
||||
val vs = VariableStatus(f, code, optimizationContext, _.size == 2).getOrElse(return code)
|
||||
val vs = VariableStatus(f, code, optimizationContext, _.size == 2, allowParams = true).getOrElse(return code)
|
||||
val options = optimizationContext.options
|
||||
val log = options.log
|
||||
val removeVariablesForReal = !options.flag(CompilationFlag.InternalCurrentlyOptimizingForMeasurement)
|
||||
val costFunction: CyclesAndBytes => Int = if (options.flag(CompilationFlag.OptimizeForSpeed)) _.cycles else _.bytes
|
||||
val z80 = optimizationContext.options.flag(CompilationFlag.EmitZ80Opcodes)
|
||||
val exdehl = optimizationContext.options.flag(CompilationFlag.EmitIntel8080Opcodes)
|
||||
|
||||
val hlCandidates = vs.variablesWithLifetimes.filter {
|
||||
case (v, range) =>
|
||||
val tuple = vs.codeWithFlow(range.start)
|
||||
tuple._1.importanceAfter.h != Important &&
|
||||
okPrefix(vs, v, range, ZRegister.HL, allowDirectLoad = true, allowIndirectLoad = false) && tuple._1.importanceAfter.h != Important &&
|
||||
tuple._1.importanceAfter.l != Important || {
|
||||
// println(s"Cannot inline ${v.name} to HL because of early $tuple")
|
||||
false
|
||||
@ -45,14 +47,17 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}.flatMap {
|
||||
case (v, range) =>
|
||||
canBeInlined(v.name, synced = false, ZRegister.HL, vs.codeWithFlow.slice(range.start, range.end)).map { score =>
|
||||
(v.name, range, if (vs.variablesWithRegisterHint(v.name)) score + CyclesAndBytes(16, 16) else score)
|
||||
(v.name, range, score +
|
||||
(if (vs.variablesWithRegisterHint(v.name)) CyclesAndBytes(16, 16) else CyclesAndBytes.Zero) +
|
||||
(if (vs.paramVariables(v.name)) CyclesAndBytes(-16, -3) else CyclesAndBytes.Zero)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val bcCandidates = vs.variablesWithLifetimes.filter {
|
||||
case (v, range) =>
|
||||
val tuple = vs.codeWithFlow(range.start)
|
||||
tuple._1.importanceAfter.b != Important &&
|
||||
okPrefix(vs, v, range, ZRegister.BC, z80, allowIndirectLoad = false) && tuple._1.importanceAfter.b != Important &&
|
||||
tuple._1.importanceAfter.c != Important || {
|
||||
// println(s"Cannot inline ${v.name} to BC because of early $tuple")
|
||||
false
|
||||
@ -60,14 +65,20 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}.flatMap {
|
||||
case (v, range) =>
|
||||
canBeInlined(v.name, synced = false, ZRegister.BC, vs.codeWithFlow.slice(range.start, range.end)).map { score =>
|
||||
(v.name, range, if (vs.variablesWithRegisterHint(v.name)) score + CyclesAndBytes(16, 16) else score)
|
||||
(v.name, range, score +
|
||||
(if (vs.variablesWithRegisterHint(v.name)) CyclesAndBytes(16, 16) else CyclesAndBytes.Zero) +
|
||||
(if (vs.paramVariables(v.name)) {
|
||||
if (z80) CyclesAndBytes(-20, -4)
|
||||
else CyclesAndBytes(-24, -5)
|
||||
} else CyclesAndBytes.Zero)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val deCandidates = vs.variablesWithLifetimes.filter {
|
||||
case (v, range) =>
|
||||
val tuple = vs.codeWithFlow(range.start)
|
||||
tuple._1.importanceAfter.d != Important &&
|
||||
okPrefix(vs, v, range, ZRegister.DE, z80, exdehl) && tuple._1.importanceAfter.d != Important &&
|
||||
tuple._1.importanceAfter.e != Important || {
|
||||
// println(s"Cannot inline ${v.name} to DE because of early $tuple")
|
||||
false
|
||||
@ -75,7 +86,13 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}.flatMap {
|
||||
case (v, range) =>
|
||||
canBeInlined(v.name, synced = false, ZRegister.DE, vs.codeWithFlow.slice(range.start, range.end)).map { score =>
|
||||
(v.name, range, if (vs.variablesWithRegisterHint(v.name)) score + CyclesAndBytes(16, 16) else score)
|
||||
(v.name, range, score +
|
||||
(if (vs.variablesWithRegisterHint(v.name)) CyclesAndBytes(16, 16) else CyclesAndBytes.Zero) +
|
||||
(if (vs.paramVariables(v.name)) {
|
||||
if (z80 || exdehl) CyclesAndBytes(-20, -4)
|
||||
else CyclesAndBytes(-24, -5)
|
||||
} else CyclesAndBytes.Zero)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,9 +146,13 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
val oldCode = vs.codeWithFlow.slice(range.start, range.end)
|
||||
val newCode = inlineVars(v, "", "", oldCode).result
|
||||
reportOptimizedBlock(oldCode, newCode)
|
||||
if (vs.paramVariables(v)) {
|
||||
val addr = vs.localVariables.find(_.name.==(v)).get.asInstanceOf[MemoryVariable].toAddress
|
||||
output += ZLine.ldAbs16(ZRegister.HL, addr)
|
||||
}
|
||||
output ++= newCode
|
||||
i = range.end
|
||||
if (removeVariablesForReal && contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
if (removeVariablesForReal && !vs.paramVariables(v) && contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
f.environment.removeVariable(v)
|
||||
}
|
||||
done = true
|
||||
@ -143,9 +164,19 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
val oldCode = vs.codeWithFlow.slice(range.start, range.end)
|
||||
val newCode = inlineVars("", v, "", oldCode).result
|
||||
reportOptimizedBlock(oldCode, newCode)
|
||||
if (vs.paramVariables(v)) {
|
||||
val addr = vs.localVariables.find(_.name.==(v)).get.asInstanceOf[MemoryVariable].toAddress
|
||||
if (z80) {
|
||||
output += ZLine.ldAbs16(ZRegister.BC, addr)
|
||||
} else {
|
||||
output += ZLine.ldAbs16(ZRegister.HL, addr)
|
||||
output += ZLine.ld8(ZRegister.B, ZRegister.H)
|
||||
output += ZLine.ld8(ZRegister.C, ZRegister.L)
|
||||
}
|
||||
}
|
||||
output ++= newCode
|
||||
i = range.end
|
||||
if (removeVariablesForReal && contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
if (removeVariablesForReal && !vs.paramVariables(v) && contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
f.environment.removeVariable(v)
|
||||
}
|
||||
done = true
|
||||
@ -158,9 +189,22 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
val oldCode = vs.codeWithFlow.slice(range.start, range.end)
|
||||
val newCode = inlineVars("", "", v, oldCode).result
|
||||
reportOptimizedBlock(oldCode, newCode)
|
||||
if (vs.paramVariables(v)) {
|
||||
val addr = vs.localVariables.find(_.name.==(v)).get.asInstanceOf[MemoryVariable].toAddress
|
||||
if (z80) {
|
||||
output += ZLine.ldAbs16(ZRegister.DE, addr)
|
||||
} else if (exdehl) {
|
||||
output += ZLine.ldAbs16(ZRegister.HL, addr)
|
||||
output += ZLine.implied(ZOpcode.EX_DE_HL)
|
||||
} else {
|
||||
output += ZLine.ldAbs16(ZRegister.HL, addr)
|
||||
output += ZLine.ld8(ZRegister.D, ZRegister.H)
|
||||
output += ZLine.ld8(ZRegister.E, ZRegister.L)
|
||||
}
|
||||
}
|
||||
output ++= newCode
|
||||
i = range.end
|
||||
if (removeVariablesForReal && contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
if (removeVariablesForReal && !vs.paramVariables(v) && contains(range, vs.variablesWithLifetimesMap(v))) {
|
||||
f.environment.removeVariable(v)
|
||||
}
|
||||
done = true
|
||||
@ -177,6 +221,42 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
|
||||
}
|
||||
}
|
||||
|
||||
def okPrefix(vs: VariableStatus, v: Variable, range: Range, reg: ZRegister.Value, allowDirectLoad: Boolean, allowIndirectLoad: Boolean): Boolean = {
|
||||
if (!vs.paramVariables(v.name)) return true
|
||||
if (!allowDirectLoad && !allowIndirectLoad) {
|
||||
// println(s"okPrefix $v false: better cpu required for $reg")
|
||||
return false
|
||||
}
|
||||
if (vs.paramRegs.contains(reg) || vs.paramRegs.contains(ZRegister.A)) {
|
||||
// println(s"okPrefix $v false: reg $reg in use")
|
||||
return false
|
||||
}
|
||||
if (range.size < 2) {
|
||||
// println(s"okPrefix $v false: range $range too small")
|
||||
return false
|
||||
}
|
||||
if (range.start < 1) {
|
||||
// println(s"okPrefix $v true: range $range early enough")
|
||||
return true
|
||||
}
|
||||
import ZOpcode._
|
||||
vs.codeWithFlow.take(range.start) match {
|
||||
case Nil =>
|
||||
case (_, ZLine0(LABEL, _, _)) :: xs =>
|
||||
if (xs.exists { case (_, l) => l.opcode == LABEL || ZOpcodeClasses.NonLinear(l.opcode) }) {
|
||||
// println(s"okPrefix false: LABEL in prefix")
|
||||
return false
|
||||
}
|
||||
case _ => return false
|
||||
}
|
||||
val importance = vs.codeWithFlow(range.start - 1)._1.importanceAfter
|
||||
if (allowDirectLoad) {
|
||||
importance.getRegister(reg) == Unimportant && importance.a == Unimportant
|
||||
} else {
|
||||
importance.getRegister(reg) == Unimportant && importance.a == Unimportant && importance.h == Unimportant && importance.l == Unimportant
|
||||
}
|
||||
}
|
||||
|
||||
def contains(outer: Range, inner: Range): Boolean = {
|
||||
outer.contains(inner.start) && outer.contains(inner.end - 1)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user