From 62984fce9b0cff64133d9c0bea956b9d325a5462 Mon Sep 17 00:00:00 2001
From: Karol Stasiak <karol.m.stasiak@gmail.com>
Date: Thu, 2 May 2019 13:33:45 +0200
Subject: [PATCH] 8080: optimize handling 2-byte parameters

---
 .../z80/opt/AlwaysGoodI80Optimizations.scala  | 15 +++++++++++
 .../scala/millfork/test/StructSuite.scala     | 26 ++++++++++++++++++-
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala b/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala
index f0c95a6c..8898904b 100644
--- a/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala
+++ b/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodI80Optimizations.scala
@@ -1007,6 +1007,21 @@ object AlwaysGoodI80Optimizations {
         ctx.get[List[ZLine]](4) ++
         ctx.get[List[ZLine]](7).map(x => x.copy(registers = x.registers.asInstanceOf[TwoRegisters].copy(source = DE)))
     },
+
+    (Elidable & HasOpcode(LD_16) & HasRegisters(TwoRegisters(MEM_ABS_16, HL)) & MatchParameter(0)).captureLine(10) ~
+      (Linear & DoesntChangeMemoryAt(10) & Not(Concerns(HL))).*.capture(55) ~
+      (Elidable & HasOpcode(LD_16) & HasRegisters(TwoRegisters(HL, IMM_16)) & MatchParameter(0)) ~
+      (Elidable & HasOpcodeIn(Set(ADD,ADC,INC,DEC,SUB,SBC,AND,OR,XOR)) & HasRegisters(OneRegister(MEM_HL)) & DoesntMatterWhatItDoesWith(HL)).capture(11) ~~> { (code, ctx) =>
+      ctx.get[List[ZLine]](55) ++ ctx.get[List[ZLine]](11).map(_.copy(registers = OneRegister(L))) :+ code.head
+    },
+
+    (Elidable & HasOpcode(LD_16) & HasRegisters(TwoRegisters(MEM_ABS_16, HL)) & MatchParameter(0)).captureLine(10) ~
+      (Linear & DoesntChangeMemoryAt(10) & Not(Concerns(HL))).*.capture(55) ~
+      (Elidable & HasOpcode(LD_16) & HasRegisters(TwoRegisters(HL, IMM_16)) & MatchParameter(1)) ~
+      Where(ctx => ctx.get[Constant](0).+(1).quickSimplify == ctx.get[Constant](1)) ~
+      (Elidable & HasOpcodeIn(Set(ADD,ADC,INC,DEC,SUB,SBC,AND,OR,XOR)) & HasRegisters(OneRegister(MEM_HL)) & DoesntMatterWhatItDoesWith(HL)).capture(11) ~~> { (code, ctx) =>
+      ctx.get[List[ZLine]](55) ++ ctx.get[List[ZLine]](11).map(_.copy(registers = OneRegister(H))) :+ code.head
+    },
   )
 
   val UnusedCodeRemoval = new RuleBasedAssemblyOptimization("Unreachable code removal",
diff --git a/src/test/scala/millfork/test/StructSuite.scala b/src/test/scala/millfork/test/StructSuite.scala
index 89b0eabc..a48dbad7 100644
--- a/src/test/scala/millfork/test/StructSuite.scala
+++ b/src/test/scala/millfork/test/StructSuite.scala
@@ -1,7 +1,7 @@
 package millfork.test
 
 import millfork.Cpu
-import millfork.test.emu.EmuUnoptimizedCrossPlatformRun
+import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun}
 import org.scalatest.{FunSuite, Matchers}
 
 /**
@@ -83,4 +83,28 @@ class StructSuite extends FunSuite with Matchers {
       m.readWord(0xc000) should equal(0x201)
     }
   }
+
+  test("Optimize struct modifications") {
+    EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)("""
+        | struct point { byte x, byte y }
+        | enum direction { none, right }
+        | direction last_direction @$c400
+        | noinline point move_right(point p) {
+        |   last_direction = right
+        |   p.x += 1
+        |   return p
+        | }
+        | byte output @$c000
+        | void main () {
+        |   point p
+        |   p.x = 1
+        |   p.y = 2
+        |   p = move_right(p)
+        |   output = p.x
+        | }
+      """.stripMargin) { m =>
+      m.readByte(0xc000) should equal(2)
+      m.readByte(0xc400) should equal(1)
+    }
+  }
 }