1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-31 18:41:30 +00:00

Various optimization improvements and fixes, mostly for 6809

This commit is contained in:
Karol Stasiak 2021-05-16 23:31:52 +02:00
parent 21d4d3252f
commit 9028d55a7e
10 changed files with 254 additions and 12 deletions

View File

@ -168,6 +168,7 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
def changesRegister(reg: M6809Register.Value): Boolean = {
import M6809Register._
if (MOpcode.NotActualOpcodes(opcode)) return false
def overlaps(other: M6809Register.Value): Boolean = {
if (reg == D && (other == A || other == B)) true
else if (other == D && (reg == A || reg == B)) true
@ -202,12 +203,13 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
}
}
def changesCarryFlag: Boolean = !MOpcode.PreservesC(opcode)
def changesCarryFlag: Boolean = !MOpcode.NotActualOpcodes(opcode) && !MOpcode.PreservesC(opcode)
def readsRegister(reg: M6809Register.Value): Boolean = {
import M6809Register._
if (MOpcode.NotActualOpcodes(opcode)) return false
def overlaps(other: M6809Register.Value): Boolean = {
if (reg == D && (other == A || other == B)) true
else if (other == D && (reg == A || reg == B)) true
@ -261,6 +263,7 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
def readsMemory(): Boolean = {
import MOpcode._
if (MOpcode.NotActualOpcodes(opcode)) return false
val opcodeIsForReading = opcode match {
case LDA | LDB | LDD | LDX | LDY | LDU | LDS | PULU | PULS => true
case ADDA | SUBA | ADCA | SBCA | ORA | EORA | ANDA | CMPA | BITA => true
@ -289,6 +292,7 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
def changesMemory(): Boolean = {
import MOpcode._
if (NotActualOpcodes(opcode)) return false
val opcodeIsForWriting = opcode match {
case LDA | LDB | LDD | LDX | LDY | LDU | LDS | PULU | PULS => false
case ADDA | SUBA | ADCA | SBCA | ORA | EORA | ANDA | CMPA | BITA => false

View File

@ -32,9 +32,10 @@ object MOpcode extends Enumeration {
TFR, TST,
DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL = Value
val NotActualOpcodes: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL)
private val toMap: Map[String, MOpcode.Value] = {
val notActualOpcodes: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL)
values.filterNot(notActualOpcodes).map(o => o.toString -> o).toMap ++ Map("BHS" -> BCC, "BLO" -> BCS, "LSL" -> ASL)
values.filterNot(NotActualOpcodes).map(o => o.toString -> o).toMap ++ Map("BHS" -> BCC, "BLO" -> BCS, "LSL" -> ASL)
}
val NoopDiscard: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC)
val PrefixedBy10: Set[MOpcode.Value] = Set(CMPD, CMPY, LDS, LDY, SWI2, STS, STY) // TODO: branches

View File

@ -3,7 +3,8 @@ package millfork.assembly.m6809.opt
import millfork.assembly.AssemblyOptimization
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, LongRelative, MAddrMode, MLine, MState, PostIncremented, RegisterSet}
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, InherentA, InherentB, LongRelative, MAddrMode, MLine, MState, PostIncremented, RegisterSet}
import millfork.env.{CompoundConstant, Constant, MathOperator}
import millfork.node.M6809Register
/**
@ -34,6 +35,21 @@ object AlwaysGoodMOptimizations {
(Elidable & HasOpcode(CMPA) & HasImmediate(0) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> {code => code.init},
(HasOpcodeIn(LDB, ANDB, ORB, EORB, ADDB, ADCB, SUBB, SBCB)) ~
(Elidable & HasOpcode(CMPB) & HasImmediate(0) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> {code => code.init},
(Elidable & HasOpcode(EORA) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.A, MState.NF, MState.VF, MState.CF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
(Elidable & HasOpcode(EORB) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.B, MState.NF, MState.VF, MState.CF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
)
val SimplifiableZeroStore = new RuleBasedAssemblyOptimization("Simplifiable zero store",
@ -58,6 +74,75 @@ object AlwaysGoodMOptimizations {
},
)
val SimplifiableComparison = new RuleBasedAssemblyOptimization("Simplifiable comparison",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(EORB) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
(Elidable & HasOpcode(ADDB) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(SUBB) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(INC) & HasAddrMode(InherentB)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(DEC) & HasAddrMode(InherentB)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(EORA) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
(Elidable & HasOpcode(ADDA) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(SUBA) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(INC) & HasAddrMode(InherentA)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(DEC) & HasAddrMode(InherentA)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
)
val PointlessRegisterTransfers = new RuleBasedAssemblyOptimization("Pointless register transfers",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & IsTfr(M6809Register.D, M6809Register.X)) ~
@ -161,15 +246,27 @@ object AlwaysGoodMOptimizations {
(HasOpcode(LABEL) & MatchParameter(1)) ~~> { code =>
List(code.head, code(3).copy(opcode = ORB)) ++ code.drop(4)
},
(Elidable & HasOpcode(ADDB) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentB(INC)) },
(Elidable & HasOpcode(ADDA) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentA(INC)) },
(Elidable & HasOpcode(SUBB) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentB(DEC)) },
(Elidable & HasOpcode(SUBA) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentA(DEC)) },
)
val UnusedLabelRemoval = new RuleBasedAssemblyOptimization("Unused label removal",
needsFlowInfo = FlowInfoRequirement.JustLabels,
(Elidable & HasOpcode(LABEL) & HasCallerCount(0) & ParameterIsLocalLabel) ~~> (_ => Nil)
)
val All: Seq[AssemblyOptimization[MLine]] = Seq(
PointlessLoad,
PointlessCompare,
PointlessRegisterTransfers,
SimplifiableArithmetics,
SimplifiableComparison,
SimplifiableJumps,
SimplifiableZeroStore
SimplifiableZeroStore,
UnusedLabelRemoval
)
}

View File

@ -110,8 +110,9 @@ object ReverseFlowAnalyzer {
var changed = true
changed = true
val actualFinalImportance = f.returnType match {
case FlagBooleanType(_, _, _) => finalImportance.copy(cf = Important, zf =Important, nf = Important, vf = Important)
case FlagBooleanType(_, _, _) => finalImportance.copy(cf = Important, zf = Important, nf = Important, vf = Important)
case t if t.size == 1 => finalImportance.copy(a = Unimportant)
case t if t.size == 0 => finalImportance.copy(a = Unimportant, b = Unimportant)
case _ => finalImportance
}
while (changed) {
@ -133,12 +134,21 @@ object ReverseFlowAnalyzer {
case _ => false
}
currentImportance = if (labelIndex < 0) actualFinalImportance else importanceArray(labelIndex) ~ currentImportance
case MLine0(JMP | BRA, _, MemoryAddressConstant(Label(l))) =>
val L = l
val labelIndex = codeArray.indexWhere {
case MLine0(LABEL, _, MemoryAddressConstant(Label(L))) => true
case _ => false
}
currentImportance = if (labelIndex < 0) actualFinalImportance else importanceArray(labelIndex)
case _ =>
}
currentLine match {
case MLine0(RTS, _, _) =>
currentImportance = actualFinalImportance
case MLine0(LABEL, _, _) =>
// do nothing
case MLine0(JSR | JMP, Absolute(false), MemoryAddressConstant(fun: FunctionInMemory)) =>
// this case has to be handled first, because the generic JSR importance handler is too conservative
var result = importanceBeforeJsr

View File

@ -1290,3 +1290,14 @@ case object IsNotALabelUsedManyTimes extends MLinePattern {
override def hitRate: Double = 0.92 // ?
}
object ParameterIsLocalLabel extends MLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: MLine): Boolean =
line match {
case MLine0(MOpcode.LABEL, _, MemoryAddressConstant(Label(l))) => l.startsWith(".")
case _ => false
}
override def hitRate: Double = 0.056
}

View File

@ -2991,7 +2991,9 @@ object AlwaysGoodOptimizations {
(Elidable & HasOpcode(EOR) & MatchImmediate(0)) ~
(Elidable & HasOpcode(CMP) & MatchImmediate(1) & DoesntMatterWhatItDoesWith(State.A, State.N, State.C)) ~~> { (code, ctx) =>
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, ctx.get[Constant](0), ctx.get[Constant](1))))
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
MultipleAssemblyRules(for {
@ -3115,6 +3117,42 @@ object AlwaysGoodOptimizations {
code(1).copy(opcode = branch, parameter = code(3).parameter),
code(4))
},
(Elidable & HasOpcode(INC) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CMP) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.A, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(INX) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPX) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.X, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(INY) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPY) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Y, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(DEC) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CMP) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.A, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(DEX) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPX) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.X, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(DEY) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPY) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Y, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
)
private val powersOf2: List[(Int, Int)] = List(

View File

@ -928,9 +928,16 @@ object AlwaysGoodI80Optimizations {
code(2).copy(opcode=JP, registers = branch, parameter = code(4).parameter),
code(5))
},
)
),
(Elidable & HasOpcode(XOR) & HasRegisterParam(ZRegister.IMM_8) & MatchParameter(0)) ~
(Elidable & HasOpcode(CP) & HasRegisterParam(ZRegister.IMM_8) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(ZRegister.A) & DoesntMatterWhatItDoesWithFlagsOtherThanSZ) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
)
val FreeHL = new RuleBasedAssemblyOptimization("Free HL",

View File

@ -986,4 +986,74 @@ class AssemblyOptimizationSuite extends FunSuite with Matchers {
m.readByte(0xc000) should equal(code.count(_ == '↑'))
}
}
test("Optimize XOR comparisons") {
val code =
"""
|byte output @$c000
|const byte c = 4
|
|noinline void inc(byte x) {
| if x ^ c == c { output += 1 }
|}
|
|void main() {
| output = 0
| inc(0)
| inc(1)
| inc(2)
|}
|""".stripMargin
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
code) { m =>
m.readByte(0xc000) should equal(1)
}
}
test("Optimize +1 comparisons") {
val code =
"""
|byte output @$c000
|const byte c = 4
|
|noinline void inc(byte x) {
| if x + 1 == c { output += 1 }
|}
|
|void main() {
| output = 0
| inc(3)
| inc(4)
| inc(4)
| inc(5)
| inc(5)
|}
|""".stripMargin
EmuCrossPlatformBenchmarkRun(Cpu.Cmos, Cpu.Z80, Cpu.Motorola6809)(
code) { m =>
m.readByte(0xc000) should equal(1)
}
}
test("Don't optimize other XOR comparisons") {
val code =
"""
|byte output @$c000
|const byte c = 4
|
|noinline void inc(byte x) {
| if x ^ c >= c { output += 1 }
|}
|
|void main() {
| output = 0
| inc(4)
|}
|""".stripMargin
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
code) { m =>
m.readByte(0xc000) should equal(0)
}
}
}

View File

@ -2,12 +2,12 @@ package millfork.test
import millfork.Cpu
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun, ShouldNotCompile}
import org.scalatest.{FunSuite, Matchers}
import org.scalatest.{AppendedClues, FunSuite, Matchers}
/**
* @author Karol Stasiak
*/
class BooleanSuite extends FunSuite with Matchers {
class BooleanSuite extends FunSuite with Matchers with AppendedClues {
test("Not") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
@ -336,7 +336,7 @@ class BooleanSuite extends FunSuite with Matchers {
|""".stripMargin) { m =>
val MAX_SIZE = 4
val bool = x < MAX_SIZE && y < MAX_SIZE && x + w < MAX_SIZE && y + h < MAX_SIZE
m.readByte(0xc000) should equal(if (bool) 1 else 0)
m.readByte(0xc000) should equal(if (bool) 1 else 0) withClue s"x=$x y=$y w=$w h=$h"
}
}
}

View File

@ -1,7 +1,7 @@
package millfork.test
import millfork.Cpu
import millfork.env.{BasicPlainType, DerivedPlainType, NumericConstant}
import millfork.env.{BasicPlainType, CompoundConstant, DerivedPlainType, MathOperator, NumericConstant}
import millfork.test.emu.{EmuBenchmarkRun, EmuOptimizedCmosRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun, ShouldNotCompile}
import org.scalatest.{FunSuite, Matchers}
@ -34,6 +34,10 @@ class ConstantSuite extends FunSuite with Matchers {
NumericConstant(-1, 8).isProvablyNegative(signed(8)) should be(true)
}
test("Constants should simplify nicely") {
CompoundConstant(MathOperator.Exor, NumericConstant(4, 1), NumericConstant(4, 1)).quickSimplify should equal(NumericConstant(0, 1))
}
test ("Overflow errors should be nice") {
ShouldNotCompile(
"""