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

Z80: Various optimizations for stack variables

This commit is contained in:
Karol Stasiak 2018-07-24 14:31:23 +02:00
parent 9bcaffaa30
commit 43c044c80c
12 changed files with 386 additions and 33 deletions

View File

@ -7,11 +7,13 @@ import scala.collection.mutable.ListBuffer
*/ */
object NonOverlappingIntervals { object NonOverlappingIntervals {
def apply[T](intervals: Iterable[T], start: T => Int, end: T => Int): Seq[Set[T]] = { def apply[T](intervals: Iterable[T], start: T => Int, end: T => Int): Seq[Set[T]] = {
def nonEmpty(interval: T): Boolean = start(interval) != end(interval)
val builder = ListBuffer[Set[T]]() val builder = ListBuffer[Set[T]]()
def scan(set: Set[T], lastEnd: Int): Unit = { def scan(set: Set[T], lastEnd: Int): Unit = {
builder += set builder += set
for (interval <- intervals) { for (interval <- intervals if nonEmpty(interval)) {
if (start(interval) >= lastEnd) { if (start(interval) >= lastEnd) {
scan(set + interval, end(interval)) scan(set + interval, end(interval))
} }

View File

@ -232,6 +232,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case DISCARD_BCDEIX => " ; DISCARD_BCDEIX" case DISCARD_BCDEIX => " ; DISCARD_BCDEIX"
case BYTE => " !byte " + parameter.toString // TODO: format? case BYTE => " !byte " + parameter.toString // TODO: format?
case LABEL => parameter.toString + ":" case LABEL => parameter.toString + ":"
case RST => s" RST $parameter"
case EX_AF_AF => " EX AF,AF'" case EX_AF_AF => " EX AF,AF'"
case EX_SP => registers match { case EX_SP => registers match {
case OneRegister(r) => s" EX (SP),${asAssemblyString(r)})" case OneRegister(r) => s" EX (SP),${asAssemblyString(r)})"
@ -292,6 +293,10 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case TwoRegisters(HL, HL) => r == H || r == L case TwoRegisters(HL, HL) => r == H || r == L
case TwoRegisters(HL, BC) => r == H || r == L || r == B || r == C case TwoRegisters(HL, BC) => r == H || r == L || r == B || r == C
case TwoRegisters(HL, DE) => r == H || r == L || r == D || r == E case TwoRegisters(HL, DE) => r == H || r == L || r == D || r == E
case TwoRegisters(HL, SP) => r == H || r == L || r == SP
case TwoRegisters(IX, DE) => r == IXH || r == IXL || r == D || r == E
case TwoRegisters(IX, BC) => r == IXH || r == IXL || r == B || r == C
case TwoRegisters(IX, SP) => r == IXH || r == IXL || r == SP
case _ => true case _ => true
} }
case LD => (registers match { case LD => (registers match {
@ -472,12 +477,13 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case _ => false case _ => false
} }
case JP | JR | RET | RETI | RETN | case JP | JR | RET | RETI | RETN |
POP | PUSH |
DISCARD_A | DISCARD_BCDEIX | DISCARD_HL | DISCARD_F => false DISCARD_A | DISCARD_BCDEIX | DISCARD_HL | DISCARD_F => false
case ADD | ADC | AND | OR | XOR | SUB | SBC | DAA | NEG | CPL => r == A case ADD | ADC | AND | OR | XOR | SUB | SBC | DAA | NEG | CPL => r == A
case CP => false case CP => false
case DJNZ => r == B case DJNZ => r == B
case LABEL | DI | EI | NOP | HALT => false case LABEL | DI | EI | NOP | HALT => false
case CALL => r != IXH && r != IXL && r != SP
case _ => true // TODO case _ => true // TODO
} }
} }

View File

@ -204,6 +204,10 @@ object AlwaysGoodZ80Optimizations {
(Linear & Not(Changes(register)) & DoesntChangeMemoryAt(1)).* ~ (Linear & Not(Changes(register)) & DoesntChangeMemoryAt(1)).* ~
(Elidable & Is16BitLoad(register, ZRegister.MEM_ABS_16) & MatchParameter(0)) ~~> (_.init) (Elidable & Is16BitLoad(register, ZRegister.MEM_ABS_16) & MatchParameter(0)) ~~> (_.init)
), ),
// 60
(HasOpcode(LD) & MatchSourceRegisterAndOffset(0) & MatchTargetRegisterAndOffset(1)) ~
Where(ctx => ctx.get[RegisterAndOffset](0).register != ZRegister.MEM_ABS_8 && ctx.get[RegisterAndOffset](1).register != ZRegister.MEM_ABS_8) ~
(Elidable & HasOpcode(LD) & MatchSourceRegisterAndOffset(1) & MatchTargetRegisterAndOffset(0)) ~~> (_.init)
) )
val PointlessStackStashing = new RuleBasedAssemblyOptimization("Pointless stack stashing", val PointlessStackStashing = new RuleBasedAssemblyOptimization("Pointless stack stashing",
@ -607,6 +611,49 @@ object AlwaysGoodZ80Optimizations {
}, },
(Elidable & Is8BitLoad(ZRegister.H, ZRegister.D)) ~
(Elidable & Is8BitLoad(ZRegister.L, ZRegister.E)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.HL) & DoesntMatterWhatItDoesWith(ZRegister.HL)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.DE))
},
(Elidable & Is8BitLoad(ZRegister.H, ZRegister.B)) ~
(Elidable & Is8BitLoad(ZRegister.L, ZRegister.C)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.HL) & DoesntMatterWhatItDoesWith(ZRegister.HL)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.BC))
},
(Elidable & Is8BitLoad(ZRegister.D, ZRegister.H)) ~
(Elidable & Is8BitLoad(ZRegister.E, ZRegister.L)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.DE) & DoesntMatterWhatItDoesWith(ZRegister.DE)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.HL))
},
(Elidable & Is8BitLoad(ZRegister.B, ZRegister.H)) ~
(Elidable & Is8BitLoad(ZRegister.C, ZRegister.L)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.BC) & DoesntMatterWhatItDoesWith(ZRegister.BC)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.HL))
},
(Elidable & Is8BitLoad(ZRegister.L, ZRegister.E)) ~
(Elidable & Is8BitLoad(ZRegister.H, ZRegister.D)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.HL) & DoesntMatterWhatItDoesWith(ZRegister.HL)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.DE))
},
(Elidable & Is8BitLoad(ZRegister.L, ZRegister.C)) ~
(Elidable & Is8BitLoad(ZRegister.H, ZRegister.B)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.HL) & DoesntMatterWhatItDoesWith(ZRegister.HL)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.BC))
},
(Elidable & Is8BitLoad(ZRegister.E, ZRegister.L)) ~
(Elidable & Is8BitLoad(ZRegister.D, ZRegister.H)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.DE) & DoesntMatterWhatItDoesWith(ZRegister.DE)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.HL))
},
(Elidable & Is8BitLoad(ZRegister.C, ZRegister.L)) ~
(Elidable & Is8BitLoad(ZRegister.B, ZRegister.H)) ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(ZRegister.BC) & DoesntMatterWhatItDoesWith(ZRegister.BC)) ~~> {_ =>
List(ZLine.register(PUSH, ZRegister.HL))
},
) )
val UnusedCodeRemoval = new RuleBasedAssemblyOptimization("Unreachable code removal", val UnusedCodeRemoval = new RuleBasedAssemblyOptimization("Unreachable code removal",

View File

@ -22,6 +22,8 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case class CyclesAndBytes(bytes: Int, cycles: Int) { case class CyclesAndBytes(bytes: Int, cycles: Int) {
def +(that: CyclesAndBytes) = CyclesAndBytes(this.bytes + that.bytes, this.cycles + that.cycles) def +(that: CyclesAndBytes) = CyclesAndBytes(this.bytes + that.bytes, this.cycles + that.cycles)
def *(scale: Double) = CyclesAndBytes(bytes.*(scale).round.toInt, cycles.*(scale).round.toInt)
} }
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = { override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
@ -29,13 +31,29 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
val options = optimizationContext.options val options = optimizationContext.options
val removeVariablesForReal = !options.flag(CompilationFlag.InternalCurrentlyOptimizingForMeasurement) val removeVariablesForReal = !options.flag(CompilationFlag.InternalCurrentlyOptimizingForMeasurement)
val costFunction: CyclesAndBytes => Int = if (options.flag(CompilationFlag.OptimizeForSpeed)) _.cycles else _.bytes val costFunction: CyclesAndBytes => Int = if (options.flag(CompilationFlag.OptimizeForSpeed)) _.cycles else _.bytes
lazy val savingsForRemovingOneStackVariable = {
val localVariableAreaSize = code.flatMap {
case ZLine(_, OneRegisterOffset(ZRegister.MEM_IX_D, offset), _, _) => Some(offset)
case ZLine(_, TwoRegistersOffset(_, ZRegister.MEM_IX_D, offset), _, _) => Some(offset)
case ZLine(_, TwoRegistersOffset(ZRegister.MEM_IX_D, _, offset), _, _) => Some(offset)
case _ => None
}.toSet.size
val prologueAndEpilogue = if (f.returnType.size == 2) CyclesAndBytes(107, 20) else CyclesAndBytes(95, 17)
localVariableAreaSize match {
case 1 => prologueAndEpilogue
case 2 => prologueAndEpilogue * 0.4
case 3 => prologueAndEpilogue * 0.1
case _ => CyclesAndBytes.Zero
}
val bCandidates = getCandidates(vs, _.b, ZRegister.B) }
val cCandidates = getCandidates(vs, _.c, ZRegister.C)
val dCandidates = getCandidates(vs, _.d, ZRegister.D) val bCandidates = getCandidates(vs, _.b, ZRegister.B, savingsForRemovingOneStackVariable)
val eCandidates = getCandidates(vs, _.e, ZRegister.E) val cCandidates = getCandidates(vs, _.c, ZRegister.C, savingsForRemovingOneStackVariable)
val hCandidates = getCandidates(vs, _.h, ZRegister.H) val dCandidates = getCandidates(vs, _.d, ZRegister.D, savingsForRemovingOneStackVariable)
val lCandidates = getCandidates(vs, _.l, ZRegister.L) val eCandidates = getCandidates(vs, _.e, ZRegister.E, savingsForRemovingOneStackVariable)
val hCandidates = getCandidates(vs, _.h, ZRegister.H, savingsForRemovingOneStackVariable)
val lCandidates = getCandidates(vs, _.l, ZRegister.L, savingsForRemovingOneStackVariable)
val bCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](bCandidates, _._2.start, _._2.end) val bCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](bCandidates, _._2.start, _._2.end)
val cCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](cCandidates, _._2.start, _._2.end) val cCandidateSets = NonOverlappingIntervals.apply[(String, Range, CyclesAndBytes)](cCandidates, _._2.start, _._2.end)
@ -114,7 +132,7 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
reportOptimizedBlock(oldCode, newCode) reportOptimizedBlock(oldCode, newCode)
output ++= newCode output ++= newCode
i = range.end i = range.end
if (removeVariablesForReal && contains(range, vs.variablesWithLifetimesMap(v))) { if (removeVariablesForReal && !v.startsWith("IX+") && vs.variablesWithLifetimesMap.contains(v) && contains(range, vs.variablesWithLifetimesMap(v))) {
f.environment.removeVariable(v) f.environment.removeVariable(v)
} }
true true
@ -150,7 +168,7 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
} }
} }
private def getCandidates(vs: VariableStatus, importanceExtractor: CpuImportance => Importance, register: ZRegister.Value) = { private def getCandidates(vs: VariableStatus, importanceExtractor: CpuImportance => Importance, register: ZRegister.Value, savingsForRemovingOneStackVariable: =>CyclesAndBytes) = {
vs.variablesWithLifetimes.filter { vs.variablesWithLifetimes.filter {
case (v, range) => case (v, range) =>
val tuple = vs.codeWithFlow(range.start) val tuple = vs.codeWithFlow(range.start)
@ -160,8 +178,15 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
} }
}.flatMap { }.flatMap {
case (v, range) => case (v, range) =>
canBeInlined(v.name, synced = false, register, Some(false), Some(false), Some(false), vs.codeWithFlow.slice(range.start, range.end)).map { score => val id = v match {
(v.name, range, if (vs.variablesWithRegisterHint(v.name)) score + CyclesAndBytes(16, 16) else score) case MemoryVariable(name, _, _) => name
case StackVariable(_, _, offset) => "IX+" + offset
}
var bonus = CyclesAndBytes.Zero
if (vs.variablesWithRegisterHint(v.name)) bonus += CyclesAndBytes(16, 16)
if (id.startsWith("IX+")) bonus += savingsForRemovingOneStackVariable
canBeInlined(id, synced = false, register, Some(false), Some(false), Some(false), vs.codeWithFlow.slice(range.start, range.end)).map { score =>
(id, range, score + bonus)
} }
} }
} }
@ -173,9 +198,9 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
import millfork.assembly.z80.ZOpcode._ import millfork.assembly.z80.ZOpcode._
import millfork.node.ZRegister._ import millfork.node.ZRegister._
def add(first: Boolean, ifTrue: CyclesAndBytes, ifFalse: CyclesAndBytes): CyclesAndBytes=>CyclesAndBytes = { c => def add(first: Boolean, ifTrue: CyclesAndBytes, ifFalse: CyclesAndBytes): CyclesAndBytes => CyclesAndBytes =
if (first) c + ifTrue else c + ifFalse if (first) _ + ifTrue else _ + ifFalse
}
def add(value: CyclesAndBytes): CyclesAndBytes=>CyclesAndBytes = _ + value def add(value: CyclesAndBytes): CyclesAndBytes=>CyclesAndBytes = _ + value
def canBeInlined(vname: String, synced: Boolean, target: ZRegister.Value, addressInHl: Option[Boolean], addressInBc: Option[Boolean], addressInDe: Option[Boolean], code: List[(FlowInfo, ZLine)]): Option[CyclesAndBytes] = { def canBeInlined(vname: String, synced: Boolean, target: ZRegister.Value, addressInHl: Option[Boolean], addressInBc: Option[Boolean], addressInDe: Option[Boolean], code: List[(FlowInfo, ZLine)]): Option[CyclesAndBytes] = {
@ -192,12 +217,22 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case _ => None case _ => None
} }
} }
object ThisOffset {
def unapply(c: Int): Option[Int] = if ("IX+" + c == vname) Some(c) else None
}
code match { code match {
case (_, ZLine(LD, TwoRegisters(A, MEM_ABS_8), ThisVar(_), _)) :: xs => case (_, ZLine(LD, TwoRegisters(A, MEM_ABS_8), ThisVar(_), _)) :: xs =>
canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(CyclesAndBytes(9, 2))) canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(CyclesAndBytes(9, 2)))
case (_, ZLine(LD, TwoRegisters(MEM_ABS_8, A), ThisVar(_), _)) :: xs => case (_, ZLine(LD, TwoRegisters(MEM_ABS_8, A), ThisVar(_), _)) :: xs =>
canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(CyclesAndBytes(9, 2))) canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(CyclesAndBytes(9, 2)))
case (_, ZLine(LD, TwoRegistersOffset(reg, MEM_IX_D, ThisOffset(_)), _, _)) :: xs =>
canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(reg == target, CyclesAndBytes(19, 3), CyclesAndBytes(15, 2)))
case (_, ZLine(LD, TwoRegistersOffset(MEM_IX_D, reg, ThisOffset(_)), _, _)) :: xs =>
canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(reg == target, CyclesAndBytes(19, 3), CyclesAndBytes(15, 2)))
case (_, ZLine(_, OneRegisterOffset(MEM_IX_D, ThisOffset(_)), _, _)) :: xs =>
canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(CyclesAndBytes(15, 2)))
case (_, ZLine(LD_16, TwoRegisters(HL, IMM_16), ThisVar(_), _)) :: xs => case (_, ZLine(LD_16, TwoRegisters(HL, IMM_16), ThisVar(_), _)) :: xs =>
if (target == H || target == L) fail(61) else if (target == H || target == L) fail(61) else
canBeInlined(vname, synced, target, addressInHl = Some(true), addressInBc, addressInDe, xs).map(add(CyclesAndBytes(10, 3))) canBeInlined(vname, synced, target, addressInHl = Some(true), addressInBc, addressInDe, xs).map(add(CyclesAndBytes(10, 3)))
@ -233,6 +268,16 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case (_, ZLine(_, _, SubbyteConstant(ThisVar(_), _), _)) :: _ => fail(6) case (_, ZLine(_, _, SubbyteConstant(ThisVar(_), _), _)) :: _ => fail(6)
case (_, ZLine(_, _, ThisVar(_), _)) :: _ => fail(4) case (_, ZLine(_, _, ThisVar(_), _)) :: _ => fail(4)
case (_, ZLine(_, _, CompoundConstant(_, ThisVar(_), _), _)) :: _ => fail(5) case (_, ZLine(_, _, CompoundConstant(_, ThisVar(_), _), _)) :: _ => fail(5)
case (_, ZLine(CALL, _, _, _)) :: xs =>
// TODO: check return type and allow HL sometimes
target match {
case ZRegister.B | ZRegister.C | ZRegister.D | ZRegister.E =>
canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs).map(add(CyclesAndBytes(-21, -2)))
case _ =>
fail(3)
}
case (_, x) :: xs if x.changesRegister(target) => fail(1) case (_, x) :: xs if x.changesRegister(target) => fail(1)
case (_, x) :: xs if x.readsRegister(target) && !synced => fail(2) case (_, x) :: xs if x.readsRegister(target) && !synced => fail(2)
@ -249,19 +294,26 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case (_, x) :: xs if x.readsRegister(DE) && addressInDe.contains(true) => fail(83) case (_, x) :: xs if x.readsRegister(DE) && addressInDe.contains(true) => fail(83)
case (_, ZLine(LABEL, _, _, _)) :: xs => canBeInlined(vname, synced = false, target, addressInHl, addressInBc, addressInDe, xs) case (_, ZLine(LABEL, _, _, _)) :: xs => canBeInlined(vname, synced = false, target, addressInHl, addressInBc, addressInDe, xs)
case (_, ZLine(CALL, _, _, _)) :: xs => fail(3)
case _ :: xs => canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs) case _ :: xs => canBeInlined(vname, synced, target, addressInHl, addressInBc, addressInDe, xs)
case _ => Some(CyclesAndBytes.Zero) case _ => Some(CyclesAndBytes.Zero)
} }
} }
def inlineVars(vname: String, target: ZRegister.Value, addressInHl: Boolean, addressInBc: Boolean, addressInDe: Boolean, code: List[ZLine]): List[ZLine] = { def inlineVars(vname: String, target: ZRegister.Value, addressInHl: Boolean, addressInBc: Boolean, addressInDe: Boolean, code: List[ZLine]): List[ZLine] = {
if (code.nonEmpty) println(code.head) // if (code.nonEmpty) println(code.head)
code match { code match {
case ZLine(LD, TwoRegisters(A, MEM_ABS_8), MemoryAddressConstant(th), _) :: xs if th.name == vname => case ZLine(LD, TwoRegisters(A, MEM_ABS_8), MemoryAddressConstant(th), _) :: xs if th.name == vname =>
ZLine.ld8(A, target) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs) ZLine.ld8(A, target) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case ZLine(LD, TwoRegisters(MEM_ABS_8, A), MemoryAddressConstant(th), _) :: xs if th.name == vname => case ZLine(LD, TwoRegisters(MEM_ABS_8, A), MemoryAddressConstant(th), _) :: xs if th.name == vname =>
ZLine.ld8(target, A) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs) ZLine.ld8(target, A) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case ZLine(LD, TwoRegistersOffset(reg, MEM_IX_D, off), _, _) :: xs if "IX+" + off == vname =>
if (reg == target) inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
else ZLine.ld8(reg, target) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case ZLine(LD, TwoRegistersOffset(MEM_IX_D, reg, off), _, _) :: xs if "IX+" + off == vname =>
if (reg == target) inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
else ZLine.ld8(target, reg) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case (l@ZLine(_, OneRegisterOffset(MEM_IX_D, off), _, _)) :: xs if "IX+" + off == vname =>
l.copy(registers = OneRegister(target)) :: inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case ZLine(LD_16, TwoRegisters(HL, IMM_16), MemoryAddressConstant(th), _) :: xs if th.name == vname => case ZLine(LD_16, TwoRegisters(HL, IMM_16), MemoryAddressConstant(th), _) :: xs if th.name == vname =>
inlineVars(vname, target, addressInHl = true, addressInBc, addressInDe, xs) inlineVars(vname, target, addressInHl = true, addressInBc, addressInDe, xs)
@ -295,6 +347,17 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
x.copy(registers = TwoRegisters(reg, target), parameter = p) :: x.copy(registers = TwoRegisters(reg, target), parameter = p) ::
inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs) inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case (x@ZLine(CALL,_,_,_))::xs =>
// TODO: this push/pull pair shouldn't prevent the inlining to the other register in the pair
target match {
case ZRegister.B | ZRegister.C =>
ZLine.register(PUSH, BC) :: x :: ZLine.register(POP, BC) ::
inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
case ZRegister.D | ZRegister.E =>
ZLine.register(PUSH, DE) :: x :: ZLine.register(POP, DE) ::
inlineVars(vname, target, addressInHl, addressInBc, addressInDe, xs)
}
case x :: xs if x.changesRegister(HL) => case x :: xs if x.changesRegister(HL) =>
x :: inlineVars(vname, target, addressInHl = false, addressInBc, addressInDe, xs) x :: inlineVars(vname, target, addressInHl = false, addressInBc, addressInDe, xs)
case x :: xs if x.changesRegister(BC) => case x :: xs if x.changesRegister(BC) =>

View File

@ -0,0 +1,137 @@
package millfork.assembly.z80.opt
import millfork.assembly.{AssemblyOptimization, OptimizationContext}
import millfork.assembly.z80._
import millfork.env.{MemoryAddressConstant, NormalFunction, NumericConstant}
import millfork.error.ErrorReporting
import millfork.node.ZRegister
/**
* @author Karol Stasiak
*/
object CompactStackFrame extends AssemblyOptimization[ZLine] {
override def name: String = "Compacting the stack frame"
override def optimize(f: NormalFunction, code: List[ZLine], context: OptimizationContext): List[ZLine] = {
optimizeStart(code) match {
case Some((optimized, before, after)) =>
ErrorReporting.debug(s"Optimized stack frame from $before to $after bytes")
optimized
case None => code
}
}
def optimizeStart(code: List[ZLine]): Option[(List[ZLine], Int, Int)] = {
import millfork.assembly.z80.ZOpcode._
import millfork.node.ZRegister._
code match {
case (name@ZLine(LABEL, _, _, _)) ::
ZLine(PUSH, OneRegister(IX), _, true) ::
ZLine(LD_16, TwoRegisters(IX, IMM_16), NumericConstant(negativeSize, _), true) ::
ZLine(ADD_16, TwoRegisters(IX, SP), _, true) ::
ZLine(LD_16, TwoRegisters(SP, IX), _, true) :: tail =>
val sourceSize = (-negativeSize).&(0xffff).toInt
val usedOffsets: Set[Int] = findUsedOffsets(tail)
val targetSize = usedOffsets.size + usedOffsets.size.&(1)
if (targetSize == sourceSize) None else {
val prologue = if (targetSize == 0) Nil else List(
ZLine.register(PUSH, IX),
ZLine.ldImm16(IX, 0x10000 - targetSize),
ZLine.registers(ADD_16, IX, SP),
ZLine.ld16(SP, IX))
val map = usedOffsets.toSeq.sorted.zipWithIndex.toMap
optimizeContinue(tail, sourceSize, targetSize, map).map { optTail =>
(name :: prologue ++ optTail, sourceSize, targetSize)
}
}
case _ =>
None
}
}
def findUsedOffsets(code: List[ZLine]): Set[Int] = {
code.flatMap {
case ZLine(_, OneRegisterOffset(ZRegister.MEM_IX_D, offset), _, _) => Some(offset)
case ZLine(_, TwoRegistersOffset(_, ZRegister.MEM_IX_D, offset), _, _) => Some(offset)
case ZLine(_, TwoRegistersOffset(ZRegister.MEM_IX_D, _, offset), _, _) => Some(offset)
case _ => None
}.toSet
}
def optimizeContinue(code: List[ZLine], sourceSize: Int, targetSize: Int, mapping: Map[Int, Int]): Option[List[ZLine]] = {
import millfork.assembly.z80.ZOpcode._
import millfork.node.ZRegister._
code match {
case (head@ZLine(_, TwoRegistersOffset(reg, MEM_IX_D, offset), _, _)) :: tail =>
optimizeContinue(tail, sourceSize, targetSize, mapping).map(
head.copy(registers = TwoRegistersOffset(reg, MEM_IX_D, mapping(offset))) :: _)
case (head@ZLine(_, TwoRegistersOffset(MEM_IX_D, reg, offset), _, _)) :: tail =>
optimizeContinue(tail, sourceSize, targetSize, mapping).map(
head.copy(registers = TwoRegistersOffset(MEM_IX_D, reg, mapping(offset))) :: _)
case (head@ZLine(_, OneRegisterOffset(MEM_IX_D, offset), _, _)) :: tail =>
optimizeContinue(tail, sourceSize, targetSize, mapping).map(
head.copy(registers = OneRegisterOffset(MEM_IX_D, mapping(offset))) :: _)
case
ZLine(LD_16, TwoRegisters(IX, IMM_16), NumericConstant(size, _), _) ::
ZLine(ADD_16, TwoRegisters(IX, SP), _, _) ::
ZLine(LD_16, TwoRegisters(SP, IX), _, _) ::
ZLine(POP, OneRegister(IX), _, _) :: tail =>
if (size != sourceSize) None
else {
stripReturn(tail).flatMap {
case (ret, rest) =>
val epilogue = if (targetSize == 0) Nil else {
List(
ZLine.ldImm16(IX, targetSize),
ZLine.registers(ADD_16, IX, SP),
ZLine.ld16(SP, IX),
ZLine.register(POP, IX))
}
optimizeContinue(rest, sourceSize, targetSize, mapping).map(epilogue ++ ret ++ _)
}
}
case
ZLine(LD_16, TwoRegisters(HL, IMM_16), NumericConstant(size, _), _) ::
ZLine(ADD_16, TwoRegisters(HL, SP), _, _) ::
ZLine(LD_16, TwoRegisters(SP, HL), _, _) ::
ZLine(POP, OneRegister(IX), _, _) :: tail =>
if (size != sourceSize) {
println("Mismatched stack frame sizes")
None
} else {
stripReturn(tail).flatMap {
case (ret, rest) =>
val epilogue = if (targetSize == 0) Nil else {
List(
ZLine.ldImm16(HL, targetSize),
ZLine.registers(ADD_16, HL, SP),
ZLine.ld16(SP, HL),
ZLine.register(POP, IX))
}
optimizeContinue(rest, sourceSize, targetSize, mapping).map(epilogue ++ ret ++ _)
}
}
case ZLine(RET | RETI | RETN | BYTE, _, _, _) :: _ => None
case ZLine(JP, _, MemoryAddressConstant(f: NormalFunction), _) :: _ => None
case x :: _ if x.changesRegister(ZRegister.IX) => None
case x :: xs => optimizeContinue(xs, sourceSize, targetSize, mapping).map(x :: _)
case Nil => Some(Nil)
}
}
def stripReturn(code: List[ZLine]): Option[(List[ZLine], List[ZLine])] = {
val (discards, rest) = code.span(l => ZOpcodeClasses.NoopDiscards(l.opcode))
if (rest.isEmpty) return None
import millfork.assembly.z80.ZOpcode._
val potentialResult = (discards :+ rest.head) -> rest.tail
rest.head match {
case ZLine(RET | RETI | RETN, _, _, _) => Some(potentialResult)
case ZLine(JP, NoRegisters, MemoryAddressConstant(f: NormalFunction), _) => Some(potentialResult)
case _ => None
}
}
}

View File

@ -24,7 +24,7 @@ object EmptyMemoryStoreRemoval extends AssemblyOptimization[ZLine] {
val toRemove = mutable.Set[Int]() val toRemove = mutable.Set[Int]()
val badVariables = mutable.Set[String]() val badVariables = mutable.Set[String]()
for((v, lifetime) <- vs.variablesWithLifetimes) { for((v, lifetime) <- vs.variablesWithLifetimes if lifetime.nonEmpty) {
val lastaccess = lifetime.last val lastaccess = lifetime.last
if (lastaccess >= 0) { if (lastaccess >= 0) {
val lastVariableAccess = code(lastaccess) val lastVariableAccess = code(lastaccess)

View File

@ -0,0 +1,59 @@
package millfork.assembly.z80.opt
import millfork.assembly.opt.SingleStatus
import millfork.assembly.z80._
import millfork.env._
import millfork.node.ZRegister
/**
* @author Karol Stasiak
*/
object StackVariableLifetime {
// This only works for non-stack variables.
// TODO: this is also probably very wrong
def apply(variableOffset: Int, codeWithFlow: List[(FlowInfo, ZLine)]): Range = {
val flags = codeWithFlow.map {
case (_, ZLine(_, OneRegisterOffset(ZRegister.MEM_IX_D, i), _, _)) => i == variableOffset
case (_, ZLine(_, TwoRegistersOffset(ZRegister.MEM_IX_D, _, i), _, _)) => i == variableOffset
case (_, ZLine(_, TwoRegistersOffset(_, ZRegister.MEM_IX_D, i), _, _)) => i == variableOffset
case _ => false
}
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 {
case MemoryAddressConstant(Label(l)) => List(l -> a._2)
case _ => Nil
}).groupBy(_._1).mapValues(_.map(_._2).toSet)
while (changed) {
changed = false
for ((label, indices) <- labelMap) {
if (indices.exists(i => i >= min && i < max)) {
indices.foreach { i =>
val before = max - min
min = min min i
max = max max (i + 1)
if (max - min != before) {
changed = true
}
}
}
}
}
// ErrorReporting.trace("Lifetime for IX+" + variableOffset)
// codeWithFlow.zipWithIndex.foreach {
// case ((_, line), index) =>
// if (index >= min && index < max) {
// ErrorReporting.trace(f"$line%-30s <")
// } else {
// ErrorReporting.trace(line.toString)
// }
// }
Range(min, max)
}
}

View File

@ -66,6 +66,7 @@ object VariableStatus {
val localVariables = allLocalVariables.filter { val localVariables = allLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Zeropage) => case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Zeropage) =>
typFilter(typ) && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) typFilter(typ) && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case StackVariable(name, typ, _) => typFilter(typ)
case _ => false case _ => false
} }
val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter { val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter {
@ -73,11 +74,15 @@ object VariableStatus {
typFilter(typ) && (typ.size == 1 || typ.size == 2) && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) typFilter(typ) && (typ.size == 1 || typ.size == 2) && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case _ => false case _ => false
}.map(_.name).toSet }.map(_.name).toSet
val variablesWithLifetimes = localVariables.map(v => val variablesWithLifetimes = localVariables.map {
v -> VariableLifetime.apply(v.name, flow) case v: MemoryVariable =>
) v -> VariableLifetime.apply(v.name, flow)
case v: StackVariable =>
v -> StackVariableLifetime.apply(v.baseOffset, flow)
}
val variablesWithLifetimesMap = variablesWithLifetimes.map { val variablesWithLifetimesMap = variablesWithLifetimes.map {
case (v, lt) => v.name -> lt case (v: MemoryVariable, lt) => v.name -> lt
case (v: StackVariable, lt) => ("IX+" + v.baseOffset) -> lt
}.toMap }.toMap
Some(new VariableStatus( Some(new VariableStatus(
paramVariables, paramVariables,

View File

@ -274,6 +274,12 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case (_, ZLine(_, _, MemoryAddressConstant(th), _)) :: _ if th.name == vname => fail(4) case (_, ZLine(_, _, MemoryAddressConstant(th), _)) :: _ if th.name == vname => fail(4)
case (_, ZLine(_, _, CompoundConstant(_, MemoryAddressConstant(th), _), _)) :: _ if th.name == vname => fail(5) case (_, ZLine(_, _, CompoundConstant(_, MemoryAddressConstant(th), _), _)) :: _ if th.name == vname => fail(5)
case (_, ZLine(_, _, SubbyteConstant(MemoryAddressConstant(th), _), _)) :: _ if th.name == vname => fail(6) case (_, ZLine(_, _, SubbyteConstant(MemoryAddressConstant(th), _), _)) :: _ if th.name == vname => fail(6)
case (_, ZLine(CALL, _, _, _)) :: xs => target match {
// TODO: check return type and allow HL sometimes
case BC | DE =>
canBeInlined(vname, synced, target, xs).map(add(CyclesAndBytes(-21, -2)))
case _ => fail(3)
}
case (_, x) :: xs if x.changesRegister(target) => fail(1) case (_, x) :: xs if x.changesRegister(target) => fail(1)
case (_, x) :: xs if x.readsRegister(target) && !synced => fail(2) case (_, x) :: xs if x.readsRegister(target) && !synced => fail(2)
case (_, ZLine(LABEL, _, _, _)) :: xs => canBeInlined(vname, synced = false, target, xs) case (_, ZLine(LABEL, _, _, _)) :: xs => canBeInlined(vname, synced = false, target, xs)
@ -400,19 +406,29 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case ZLine(LD, TwoRegisters(MEM_ABS_8, A), MemoryAddressConstant(th), _) :: xs if th.name == de => case ZLine(LD, TwoRegisters(MEM_ABS_8, A), MemoryAddressConstant(th), _) :: xs if th.name == de =>
ZLine.ld8(E, A) :: inlineVars(hl, bc, de, xs) ZLine.ld8(E, A) :: inlineVars(hl, bc, de, xs)
case ZLine(LD, TwoRegisters(A, MEM_ABS_8), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1,_)), _) :: xs if th.name == hl => case ZLine(LD, TwoRegisters(A, MEM_ABS_8), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) :: xs if th.name == hl =>
ZLine.ld8(A, H) :: inlineVars(hl, bc, de, xs) ZLine.ld8(A, H) :: inlineVars(hl, bc, de, xs)
case ZLine(LD, TwoRegisters(MEM_ABS_8, A), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1,_)), _) :: xs if th.name == hl => case ZLine(LD, TwoRegisters(MEM_ABS_8, A), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) :: xs if th.name == hl =>
ZLine.ld8(H, A) :: inlineVars(hl, bc, de, xs) ZLine.ld8(H, A) :: inlineVars(hl, bc, de, xs)
case ZLine(LD, TwoRegisters(A, MEM_ABS_8), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1,_)), _) :: xs if th.name == bc => case ZLine(LD, TwoRegisters(A, MEM_ABS_8), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) :: xs if th.name == bc =>
ZLine.ld8(A, B) :: inlineVars(hl, bc, de, xs) ZLine.ld8(A, B) :: inlineVars(hl, bc, de, xs)
case ZLine(LD, TwoRegisters(MEM_ABS_8, A), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1,_)), _) :: xs if th.name == bc => case ZLine(LD, TwoRegisters(MEM_ABS_8, A), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) :: xs if th.name == bc =>
ZLine.ld8(B, A) :: inlineVars(hl, bc, de, xs) ZLine.ld8(B, A) :: inlineVars(hl, bc, de, xs)
case ZLine(LD, TwoRegisters(A, MEM_ABS_8), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1,_)), _) :: xs if th.name == de => case ZLine(LD, TwoRegisters(A, MEM_ABS_8), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) :: xs if th.name == de =>
ZLine.ld8(A, D) :: inlineVars(hl, bc, de, xs) ZLine.ld8(A, D) :: inlineVars(hl, bc, de, xs)
case ZLine(LD, TwoRegisters(MEM_ABS_8, A), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1,_)), _) :: xs if th.name == de => case ZLine(LD, TwoRegisters(MEM_ABS_8, A), CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) :: xs if th.name == de =>
ZLine.ld8(D, A) :: inlineVars(hl, bc, de, xs) ZLine.ld8(D, A) :: inlineVars(hl, bc, de, xs)
case (x@ZLine(CALL, _, _, _)) :: xs =>
if (bc != "") {
ZLine.register(PUSH, BC) :: x :: ZLine.register(POP, BC) :: inlineVars(hl, bc, de, xs)
} else if (de != "") {
ZLine.register(PUSH, DE) :: x :: ZLine.register(POP, DE) :: inlineVars(hl, bc, de, xs)
} else {
throw new IllegalStateException()
}
case x :: ZLine(LD_16, TwoRegisters(MEM_ABS_16, HL), MemoryAddressConstant(th), _) :: xs if x.changesRegister(HL) && th.name == hl => case x :: ZLine(LD_16, TwoRegisters(MEM_ABS_16, HL), MemoryAddressConstant(th), _) :: xs if x.changesRegister(HL) && th.name == hl =>
x :: inlineVars(hl, bc, de, xs) x :: inlineVars(hl, bc, de, xs)
case x :: ZLine(LD_16, TwoRegisters(MEM_ABS_16, BC), MemoryAddressConstant(th), _) :: xs if x.changesRegister(BC) && th.name == bc => case x :: ZLine(LD_16, TwoRegisters(MEM_ABS_16, BC), MemoryAddressConstant(th), _) :: xs if x.changesRegister(BC) && th.name == bc =>
@ -420,9 +436,9 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
case x :: ZLine(LD_16, TwoRegisters(MEM_ABS_16, DE), MemoryAddressConstant(th), _) :: xs if x.changesRegister(DE) && th.name == de => case x :: ZLine(LD_16, TwoRegisters(MEM_ABS_16, DE), MemoryAddressConstant(th), _) :: xs if x.changesRegister(DE) && th.name == de =>
x :: inlineVars(hl, bc, de, xs) x :: inlineVars(hl, bc, de, xs)
case x :: _ if bc != "" && x.changesRegister(BC) => ??? case x :: _ if bc != "" && x.changesRegister(BC) => throw new IllegalStateException()
case x :: _ if de != "" && x.changesRegister(DE) => ??? case x :: _ if de != "" && x.changesRegister(DE) => throw new IllegalStateException()
case x :: _ if hl != "" && x.changesRegister(HL) => ??? case x :: _ if hl != "" && x.changesRegister(HL) => throw new IllegalStateException()
case x :: xs => x :: inlineVars(hl, bc, de, xs) case x :: xs => x :: inlineVars(hl, bc, de, xs)
case Nil => Nil case Nil => Nil

View File

@ -16,7 +16,7 @@ object Z80OptimizationPresets {
EmptyParameterStoreRemoval, EmptyParameterStoreRemoval,
EmptyMemoryStoreRemoval) EmptyMemoryStoreRemoval)
).flatten ++ ).flatten ++
List(WordVariableToRegisterOptimization, ByteVariableToRegisterOptimization) List(WordVariableToRegisterOptimization, ByteVariableToRegisterOptimization, CompactStackFrame)
).flatten ).flatten
} }

View File

@ -29,7 +29,7 @@ object Z80Compiler extends AbstractCompiler[ZLine] {
List(ZLine.ldAbs16(param.toAddress, ZRegister.HL)) List(ZLine.ldAbs16(param.toAddress, ZRegister.HL))
case _ => Nil case _ => Nil
} }
label :: (storeParamsFromRegisters ++ stackPointerFixAtBeginning(ctx) ++ chunk) label :: (stackPointerFixAtBeginning(ctx) ++ storeParamsFromRegisters ++ chunk)
} }
def stackPointerFixAtBeginning(ctx: CompilationContext): List[ZLine] = { def stackPointerFixAtBeginning(ctx: CompilationContext): List[ZLine] = {

View File

@ -204,4 +204,22 @@ class StackVarSuite extends FunSuite with Matchers {
| } | }
""".stripMargin){m => m.readWord(0xc055) should equal(0x66) } """.stripMargin){m => m.readWord(0xc055) should equal(0x66) }
} }
test("Double array with stack variables") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(
"""
| array output[5]@$c001
| array input = [0,1,4,9,16,25,36,49]
| void main () {
| stack byte i
| for i,0,until,output.length {
| output[i] = input[i+1]<<1
| }
| }
| void _panic(){while(true){}}
""".stripMargin){ m=>
m.readByte(0xc001) should equal (2)
m.readByte(0xc005) should equal (50)
}
}
} }