diff --git a/src/main/scala/millfork/assembly/z80/opt/ByteVariableToRegisterOptimization.scala b/src/main/scala/millfork/assembly/z80/opt/ByteVariableToRegisterOptimization.scala index 39351be8..f8c0dd73 100644 --- a/src/main/scala/millfork/assembly/z80/opt/ByteVariableToRegisterOptimization.scala +++ b/src/main/scala/millfork/assembly/z80/opt/ByteVariableToRegisterOptimization.scala @@ -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) } diff --git a/src/main/scala/millfork/assembly/z80/opt/CompactStackFrame.scala b/src/main/scala/millfork/assembly/z80/opt/CompactStackFrame.scala index 2e054a7e..d6d27812 100644 --- a/src/main/scala/millfork/assembly/z80/opt/CompactStackFrame.scala +++ b/src/main/scala/millfork/assembly/z80/opt/CompactStackFrame.scala @@ -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 { diff --git a/src/main/scala/millfork/assembly/z80/opt/EmptyMemoryStoreRemoval.scala b/src/main/scala/millfork/assembly/z80/opt/EmptyMemoryStoreRemoval.scala index 16866d8c..780e2ecb 100644 --- a/src/main/scala/millfork/assembly/z80/opt/EmptyMemoryStoreRemoval.scala +++ b/src/main/scala/millfork/assembly/z80/opt/EmptyMemoryStoreRemoval.scala @@ -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 } diff --git a/src/main/scala/millfork/assembly/z80/opt/VariableLifetime.scala b/src/main/scala/millfork/assembly/z80/opt/VariableLifetime.scala index b1593499..31309588 100644 --- a/src/main/scala/millfork/assembly/z80/opt/VariableLifetime.scala +++ b/src/main/scala/millfork/assembly/z80/opt/VariableLifetime.scala @@ -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) { diff --git a/src/main/scala/millfork/assembly/z80/opt/VariableStatus.scala b/src/main/scala/millfork/assembly/z80/opt/VariableStatus.scala index b9afa6c2..de56fbad 100644 --- a/src/main/scala/millfork/assembly/z80/opt/VariableStatus.scala +++ b/src/main/scala/millfork/assembly/z80/opt/VariableStatus.scala @@ -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)) } } \ No newline at end of file diff --git a/src/main/scala/millfork/assembly/z80/opt/WordVariableToRegisterOptimization.scala b/src/main/scala/millfork/assembly/z80/opt/WordVariableToRegisterOptimization.scala index aab09018..9bc63ad7 100644 --- a/src/main/scala/millfork/assembly/z80/opt/WordVariableToRegisterOptimization.scala +++ b/src/main/scala/millfork/assembly/z80/opt/WordVariableToRegisterOptimization.scala @@ -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) }