1
0
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:
Karol Stasiak 2019-11-13 18:39:51 +01:00
parent 3b78206c35
commit e2b7b28b63
6 changed files with 157 additions and 30 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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