1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-22 16:31:02 +00:00

Bugfixes for undocumented optimizations and a better test suite for them

This commit is contained in:
Karol Stasiak 2017-12-13 16:21:19 +01:00
parent d95d74104f
commit 810ac4f00e
2 changed files with 172 additions and 31 deletions

View File

@ -15,9 +15,7 @@ object UndocumentedOptimizations {
val counter = new AtomicInteger(30000)
def getNextLabel(prefix: String) = f".${prefix}%s__${counter.getAndIncrement()}%05d"
// TODO: test these
def getNextLabel(prefix: String) = f".$prefix%s__${counter.getAndIncrement()}%05d"
private val LaxAddrModeRestriction = Not(HasAddrModeIn(Set(AbsoluteX, ZeroPageX, IndexedX, Immediate)))
@ -72,23 +70,21 @@ object UndocumentedOptimizations {
val SaxModes: Set[AddrMode.Value] = Set(ZeroPage, IndexedX, ZeroPageY, Absolute)
val UseSax = new RuleBasedAssemblyOptimization("Using undocumented instruction SAX",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ConcernsA) & Not(ConcernsX)).?.capture(10) ~
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.X)) ~
(Linear & Not(ConcernsA) & Not(ConcernsX) & DoesntChangeMemoryAt(0, 1)).*.capture(10) ~
(HasOpcode(AND) & Elidable & MatchAddrMode(2) & MatchParameter(3) & Not(ReadsX)) ~
(Linear & Not(ConcernsA) & Not(ConcernsX)).?.capture(11) ~
(HasOpcode(STA) & Elidable & MatchAddrMode(4) & MatchParameter(5) & HasAddrModeIn(SaxModes) & DontMatchParameter(0)) ~
(Linear & Not(ConcernsA) & Not(ConcernsX) & Not(ChangesMemory)).?.capture(12) ~
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~
(LinearOrLabel & Not(ConcernsX)).*.capture(13) ~ OverwritesX ~~> { (code, ctx) =>
(Linear & Not(ConcernsA) & Not(ConcernsX) & DoesntChangeMemoryAt(0, 1)).*.capture(11) ~
(HasOpcode(STA) & Elidable & MatchAddrMode(4) & MatchParameter(5) & HasAddrModeIn(SaxModes) & DoesntChangeMemoryAt(0, 1)) ~
(Linear & Not(ConcernsA) & Not(ConcernsX) & DoesntChangeMemoryAt(0, 1)).*.capture(12) ~
(HasOpcode(LDA) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
val lda = code.head
val ldx = AssemblyLine(LDX, ctx.get[AddrMode.Value](2), ctx.get[Constant](3))
val sax = AssemblyLine(SAX, ctx.get[AddrMode.Value](4), ctx.get[Constant](5))
val fragment0 = lda :: ctx.get[List[AssemblyLine]](10)
val fragment1 = ldx :: ctx.get[List[AssemblyLine]](11)
val fragment2 = sax :: ctx.get[List[AssemblyLine]](12)
val fragment3 = ctx.get[List[AssemblyLine]](13)
List(fragment0, fragment1, fragment2, fragment3).flatten
List(fragment0, fragment1, fragment2).flatten
},
)
@ -104,10 +100,10 @@ object UndocumentedOptimizations {
(Elidable & HasOpcode(LDA) & HasImmediate(0) & HasClear(State.C)) ~~> (_ => List(AssemblyLine.immediate(ANC, 0))),
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
Where(c => andConstant(c.get[Constant](0), 0x80).contains(0)) ~
(Elidable & HasOpcode(CLC)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
(Elidable & HasOpcode(CLC)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Constant](0)))),
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
Where(c => andConstant(c.get[Constant](0), 0x80).contains(0x80)) ~
(Elidable & HasOpcode(SEC)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
(Elidable & HasOpcode(SEC)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Constant](0)))),
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
(Elidable & HasOpcode(CMP) & HasImmediate(0x80) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> ((_, ctx) => List(AssemblyLine.immediate(ANC, ctx.get[Int](0)))),
(Elidable & HasOpcode(AND) & MatchImmediate(0)) ~
@ -122,29 +118,29 @@ object UndocumentedOptimizations {
val UseSbx = new RuleBasedAssemblyOptimization("Using undocumented instruction SBX",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.A, State.C)).+.captureLength(0) ~
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.A)).+.captureLength(0) ~
Where(_.get[Int](0) > 2) ~~> ((_, ctx) => List(
AssemblyLine.implied(TXA),
AssemblyLine.immediate(SBX, ctx.get[Int](0)),
AssemblyLine.immediate(SBX, ctx.get[Constant](0)),
)),
(Elidable & HasOpcode(INX) & DoesntMatterWhatItDoesWith(State.A, State.C)).+.captureLength(0) ~
(Elidable & HasOpcode(INX) & DoesntMatterWhatItDoesWith(State.A)).+.captureLength(0) ~
Where(_.get[Int](0) > 2) ~~> ((_, ctx) => List(
AssemblyLine.implied(TXA),
AssemblyLine.immediate(SBX, 256 - ctx.get[Int](0)),
AssemblyLine.immediate(SBX, Constant.Zero - ctx.get[Constant](0)),
)),
HasOpcode(TXA) ~
(Elidable & HasOpcode(CLC)).? ~
(Elidable & HasClear(State.C) & HasClear(State.D) & HasOpcode(ADC) & MatchImmediate(0)) ~
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.C, State.A)) ~~> ((code, ctx) => List(
code.head,
AssemblyLine.immediate(SBX, 256 - ctx.get[Int](0)),
AssemblyLine.immediate(SBX, Constant.Zero - ctx.get[Constant](0)),
)),
HasOpcode(TXA) ~
(Elidable & HasOpcode(SEC)).? ~
(Elidable & HasSet(State.C) & HasClear(State.D) & HasOpcode(SBC) & MatchImmediate(0)) ~
(Elidable & HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.C, State.A)) ~~> ((code, ctx) => List(
code.head,
AssemblyLine.immediate(SBX, ctx.get[Int](0)),
AssemblyLine.immediate(SBX, ctx.get[Constant](0)),
)),
)
@ -152,11 +148,11 @@ object UndocumentedOptimizations {
val UseAlr = new RuleBasedAssemblyOptimization("Using undocumented instruction ALR",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(AND) & HasAddrMode(Immediate)) ~
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~~> { (code, ctx) =>
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~~> { code =>
List(AssemblyLine.immediate(ALR, code.head.parameter))
},
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CLC)) ~~> { (code, ctx) =>
(Elidable & HasOpcode(CLC)) ~~> { _ =>
List(AssemblyLine.immediate(ALR, 0xFE))
},
)
@ -164,7 +160,7 @@ object UndocumentedOptimizations {
val UseArr = new RuleBasedAssemblyOptimization("Using undocumented instruction ARR",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(HasClear(State.D) & Elidable & HasOpcode(AND) & HasAddrMode(Immediate)) ~
(Elidable & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { (code, ctx) =>
(Elidable & HasOpcode(ROR) & HasAddrMode(Implied) & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> { code =>
List(AssemblyLine.immediate(ARR, code.head.parameter))
},
)
@ -193,7 +189,7 @@ object UndocumentedOptimizations {
}
val UseSlo = new RuleBasedAssemblyOptimization("Using undocumented instruction SLO",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
needsFlowInfo = FlowInfoRequirement.BothFlows,
trivialSequence1(ASL, ORA, Not(ConcernsC), SLO),
trivialSequence2(ASL, ORA, Not(ConcernsC), SLO),
trivialCommutativeSequence(ASL, ORA, SLO),
@ -206,11 +202,16 @@ object UndocumentedOptimizations {
(Linear & Not(ConcernsMemory) & Not(ChangesA)).*.capture(2) ~
(Elidable & HasOpcode(ASL) & HasAddrMode(Implied)) ~
(Linear & Not(ConcernsMemory) & Not(ChangesA) & Not(ReadsC) & Not(ReadsNOrZ)).*.capture(3) ~
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (_, ctx) =>
List(AssemblyLine.immediate(LDA, 0), AssemblyLine(SRE, ctx.get[AddrMode.Value](0), ctx.get[Constant](1))) ++
ctx.get[List[AssemblyLine]](2) ++
ctx.get[List[AssemblyLine]](3)
},
(Elidable & HasA(0) & HasOpcode(ASL) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & DoesntChangeMemoryAt(0, 1) & Not(ChangesA)).* ~
(Elidable & HasA(0) & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.Z, State.N, State.C)) ~~> { code =>
code.head.copy(opcode = SLO) :: code.tail.init
},
)
val UseSre = new RuleBasedAssemblyOptimization("Using undocumented instruction SRE",
@ -227,7 +228,7 @@ object UndocumentedOptimizations {
(Linear & Not(ConcernsMemory) & Not(ChangesA)).*.capture(2) ~
(Elidable & HasOpcode(LSR) & HasAddrMode(Implied)) ~
(Linear & Not(ConcernsMemory) & Not(ChangesA) & Not(ReadsC) & Not(ReadsNOrZ)).*.capture(3) ~
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
(Elidable & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (_, ctx) =>
List(AssemblyLine.immediate(LDA, 0), AssemblyLine(SRE, ctx.get[AddrMode.Value](0), ctx.get[Constant](1))) ++
ctx.get[List[AssemblyLine]](2) ++
ctx.get[List[AssemblyLine]](3)
@ -262,7 +263,11 @@ object UndocumentedOptimizations {
(Elidable & HasOpcode(LDA) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
(Elidable & HasOpcode(CMP) & MatchAddrMode(2) & MatchParameter(3) & DoesntMatterWhatItDoesWith(State.V, State.C, State.N, State.A)) ~~> { code =>
List(code(2).copy(opcode = LDA), code(1).copy(opcode = DCP))
}
},
(Elidable & HasOpcode(DEC) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1)) ~
(Elidable & HasOpcode(LDA) & Not(HasAddrMode(Immediate)) & MatchAddrMode(0) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.V, State.C, State.N, State.A)) ~~> { code =>
List(AssemblyLine.immediate(LDA, 0), code(1).copy(opcode = DCP))
},
)
val UseIsc = new RuleBasedAssemblyOptimization("Using undocumented instruction ISC",

View File

@ -9,23 +9,49 @@ import org.scalatest.{FunSuite, Matchers}
class IllegalSuite extends FunSuite with Matchers {
test("ALR test 1") {
EmuUndocumentedRun("""
val m = EmuUndocumentedRun("""
| byte output @$c000
| byte input @$cfff
| void main () {
| init()
| output = (input & 0x45) >> 1
| }
| void init() {
| input = $45
| }
""".stripMargin)
m.readByte(0xc000) should equal(0x22)
}
test("ALR test 2") {
EmuUndocumentedRun("""
val m = EmuUndocumentedRun("""
| byte output @$c000
| byte input @$cfff
| void main () {
| output = (input & 0x45) >> 1
| init()
| output = (input >> 1) + 1
| }
| void init() {
| input = $8A
| }
""".stripMargin)
m.readByte(0xc000) should equal(0x46)
}
test("ANC test") {
val m = EmuUndocumentedRun("""
| byte output @$c000
| byte input @$cfff
| void main () {
| init()
| output = (input & $45) + 1
| }
|
| void init() {
| input = $45
| }
""".stripMargin)
m.readByte(0xc000) should equal(0x46)
}
test("ISC/DCP test") {
@ -48,4 +74,114 @@ class IllegalSuite extends FunSuite with Matchers {
m.readByte(0xc005) should equal(37)
m.readByte(0xc007) should equal(51)
}
test("DCP test") {
val m = EmuUndocumentedRun("""
| byte output @$c000
| array integers [10] @$c060
| void main () {
| byte i
| for i,0,paralleluntil,10 { integers[i] += i }
| output = loop()
| }
| byte loop () {
| byte result
| result = 0
| byte i
| i = 7
| do {
| result += integers[i]
| integers[i + i] += 1
| integers[i] += 1
| i -= 1
| } while i != 0
| return result
| }
""".stripMargin)
m.readByte(0xc000) should equal(28)
}
test("SLO test") {
val m = EmuUndocumentedRun("""
| long output @$c000
| byte main () {
| output = five()
| output <<= 1
| return output
| }
| byte five () {
| return 5
| }
""".stripMargin)
m.readLong(0xc000) should equal(10)
}
test("SAX test") {
val m = EmuUndocumentedRun("""
| byte output @$c000
| void main () {
| byte a
| byte b
| byte c
| b = five(a)
| c = five(a)
| a = c
| five(a)
| c = a & b
| output = a + b + c
| }
| byte five (byte ignored) {
| return 5
| }
""".stripMargin)
m.readLong(0xc000) should equal(15)
}
test("LAX test 1") {
val m = EmuUndocumentedRun("""
| word output @$c000
| void main () {
| output = five(5)
| }
| word five (byte a) {
| return a:a
| }
""".stripMargin)
m.readLong(0xc000) should equal(0x505)
}
test("LAX test 2") {
val m = EmuUndocumentedRun("""
| byte output @$c000
| void main () {
| output = five(1)
| }
| byte five (byte a) {
| byte b
| b = a
| a += 4
| return b & a
| }
""".stripMargin)
m.readLong(0xc000) should equal(1)
}
test("SBX test 1") {
val m = EmuUndocumentedRun("""
| array output [10] @$c000
| void main () {
| output[6] = 7
| five(1)
| }
| void five (byte a) {
| byte b
| b = a
| output[b] += 1
| b += 5
| output[b] += 1
| }
""".stripMargin)
m.readByte(0xc006) should equal(8)
}
}