1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-04-07 11:39:08 +00:00

Z80: Fix stack variable optimizations when addresses of stack variables are taken (2)

This commit is contained in:
Karol Stasiak 2019-06-26 10:46:59 +02:00
parent afa871abcf
commit 22cdd1c768
4 changed files with 91 additions and 23 deletions

View File

@ -35,6 +35,9 @@ object CompactStackFrame extends AssemblyOptimization[ZLine] {
ZLine(ADD_16, TwoRegisters(Index, SP), _, Elidability.Elidable, s3) ::
ZLine(LD_16, TwoRegisters(SP, Index), _, Elidability.Elidable, s4) :: tail =>
val sourceSize = (-negativeSize).&(0xffff).toInt
if (mayAccessStackViaPointers(tail)) {
return None
}
val usedOffsets: Set[Int] = findUsedOffsets(tail, Index match {
case IX => MEM_IX_D
case IY => MEM_IY_D
@ -56,29 +59,41 @@ object CompactStackFrame extends AssemblyOptimization[ZLine] {
}
}
def mayAccessStackViaPointers(code: List[ZLine]): Boolean = {
import millfork.assembly.z80.ZOpcode._
import millfork.node.ZRegister._
val mayUsePointer = code.map {
case ZLine0(_, TwoRegisters(HL, SP), _) => true
case ZLine0(_, TwoRegisters(SP, HL), _) => true
case ZLine0(LD_HLSP | LD_DESP, _, _) => true
case ZLine0(_, TwoRegistersOffset(_, MEM_HL | MEM_BC | MEM_DE, offset), _) => true
case ZLine0(_, TwoRegistersOffset(MEM_HL | MEM_BC | MEM_DE, _, offset), _) => true
case ZLine0(CALL, _, _) => true
case _ => false
}.toArray
val range = VariableLifetime.expandRangeToCoverLoops(code, mayUsePointer)
if (range.nonEmpty) {
val criticalCodeSlice = code.slice(range.start, range.end)
if (criticalCodeSlice.exists {
case ZLine0(_, TwoRegisters(_, SP), _) => true
case ZLine0(_, TwoRegisters(SP, _), _) => true
case ZLine0(LD_HLSP | LD_DESP, _, _) => true
case ZLine0(EX_SP, _, _) => true
case _ => false
}) {
return true
}
}
false
}
def findUsedOffsets(code: List[ZLine], Mem: ZRegister.Value): Set[Int] = {
val used = code.flatMap {
code.flatMap {
case ZLine0(_, OneRegisterOffset(Mem, offset), _) => Some(offset)
case ZLine0(_, TwoRegistersOffset(_, Mem, offset), _) => Some(offset)
case ZLine0(_, TwoRegistersOffset(Mem, _, offset), _) => Some(offset)
case _ => None
}.toSet
import ZOpcode._
import ZRegister._
if (used.isEmpty){
used
} else if (code.exists {
case ZLine0(LD_16, TwoRegisters(_, SP), _) => true
case ZLine0(LD_16, TwoRegisters(SP, _), _) => true
case ZLine0(ADD_16 | SBC_16, TwoRegisters(_, SP), _) => true
case ZLine0(LD_HLSP | LD_DESP, _, _) => true
case ZLine0(EX_SP, _, _) => true
case _ => false
}){
(0 to used.max).toSet
} else used
}
def optimizeContinue(code: List[ZLine], Index: ZRegister.Value, sourceSize: Int, targetSize: Int, mapping: Map[Int, Int]): Option[List[ZLine]] = {

View File

@ -46,11 +46,16 @@ object VariableLifetime {
pointerReadAt.foreach(i => flags(i) = true)
}
val code = codeWithFlow.map(_._2)
expandRangeToCoverLoops(code, flags)
}
def expandRangeToCoverLoops(code: List[ZLine], flags: Array[Boolean]): Range = {
if (flags.forall(!_)) return Range(0, 0)
var min = flags.indexOf(true)
var max = flags.lastIndexOf(true) + 1
var changed = true
val labelMap = codeWithFlow.zipWithIndex.flatMap(a => a._1._2.parameter match {
val labelMap = code.zipWithIndex.flatMap(a => a._1.parameter match {
case MemoryAddressConstant(Label(l)) => List(l -> a._2)
case _ => Nil
}).groupBy(_._1).mapValues(_.map(_._2).toSet)

View File

@ -49,14 +49,18 @@ object UnusedLocalVariables extends NodeOptimization {
case _ => Nil
}
def getAllReadVariables(expression: (String, Expression)): List[String] = {
if (expression._2.isPure) getAllReadVariables(List(expression._2)).filter(_ != expression._1)
else getAllReadVariables(List(expression._2))
}
def optimizeVariables(log: Logger, statements: List[Statement]): List[Statement] = {
val allLocals = getAllLocalVariables(statements)
val allRead = getAllReadVariables(statements.flatMap {
case Assignment(VariableExpression(_), expression) => List(expression)
case ExpressionStatement(FunctionCallExpression(op, VariableExpression(_) :: params)) if op.endsWith("=") => params
case x => x.getAllExpressions
}).toSet
val allRead = statements.flatMap {
case Assignment(VariableExpression(v), expression) => List(v.takeWhile(_ != '.') -> expression)
case ExpressionStatement(FunctionCallExpression(op, VariableExpression(_) :: params)) if op.endsWith("=") => params.map("```" -> _)
case x => x.getAllExpressions.map("```" -> _)
}.flatMap(getAllReadVariables(_)).toSet
val localsToRemove = allLocals.filterNot(allRead).toSet
if (localsToRemove.nonEmpty) {
log.debug("Removing unused local variables: " + localsToRemove.mkString(", "))

View File

@ -123,12 +123,22 @@ class StackVarSuite extends FunSuite with Matchers {
| }
| word fib(byte i) {
| stack byte j
| stack word sum
| stack word w
| stack word v
| j = i
| if j < 2 {
| return 1
| }
| stack word sum
|
| sum = fib(j-1)
|
| // see if optimizer cleans these off:
| w = sum
| sum = w
| v = sum
| sum = v
|
| sum += fib(j-2)
| sum &= $0F3F
| return sum
@ -410,5 +420,39 @@ class StackVarSuite extends FunSuite with Matchers {
""".stripMargin)
}
test("Pointers to stack variables 4") {
val m = EmuOptimizedZ80Run(
"""
| noinline word f() = 6
| noinline void g(word x) {}
| asm void __loop() {
| nop
| nop
| nop
| jp __loop
| }
| noinline void h (pointer.word p) {
| p[0] = __loop.addr + 1
| }
| void main () {
| stack word b
| stack word a
| stack word c
| stack word d
| h(a.pointer)
| h(b.pointer)
| h(c.pointer)
| h(d.pointer)
| a = f()
| a += 5
| a += a.addr.lo
| g(a)
| if f() == 0 { main() }
| }
""".stripMargin)
}
}