From a00ba49820e91b98f719f45dc62f43150896eb12 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Fri, 6 Jul 2018 22:45:59 +0200 Subject: [PATCH] =?UTF-8?q?Multiple=20improvements=20and=20fixes:=20?= =?UTF-8?q?=E2=80=93=20reorganized=20code=20for=20future=20support=20of=20?= =?UTF-8?q?larger=20zeropage=20register=20sets=20=E2=80=93=20added=20stack?= =?UTF-8?q?-allocated=20variables=20for=20Z80=20=E2=80=93=20added=20many?= =?UTF-8?q?=20stack-related=20optimizations=20for=206502=20and=20Z80=20?= =?UTF-8?q?=E2=80=93=20fixed=20flow=20analysis=20and=20optimization=20bugs?= =?UTF-8?q?=20for=20Z80=20=E2=80=93=20flow=20analysis=20for=20stack-alloca?= =?UTF-8?q?ted=20variables=20on=20Z80=20=E2=80=93=20added=20more=20optimiz?= =?UTF-8?q?ations=20for=206502=20and=20Z80=20=E2=80=93=20fixed=20IX/IY-ind?= =?UTF-8?q?exed=20operations=20on=20Z80=20=E2=80=93=20code=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scala/millfork/CompilationOptions.scala | 12 +- src/main/scala/millfork/Main.scala | 11 +- .../scala/millfork/OptimizationPresets.scala | 9 ++ src/main/scala/millfork/Platform.scala | 13 +- .../millfork/assembly/mos/AssemblyLine.scala | 2 +- .../mos/opt/AlwaysGoodOptimizations.scala | 103 ++++++++++++++ .../millfork/assembly/mos/opt/CpuStatus.scala | 4 +- .../assembly/mos/opt/FlowAnalyzer.scala | 5 +- .../opt/RuleBasedAssemblyOptimization.scala | 9 +- .../assembly/mos/opt/SuperOptimizer.scala | 2 +- .../scala/millfork/assembly/z80/ZLine.scala | 95 ++++++++++--- .../z80/opt/AlwaysGoodZ80Optimizations.scala | 78 +++++++++- .../assembly/z80/opt/CoarseFlowAnalyzer.scala | 37 ++++- .../millfork/assembly/z80/opt/CpuStatus.scala | 16 ++- .../assembly/z80/opt/FlowAnalyzer.scala | 4 + .../z80/opt/ReverseFlowAnalyzer.scala | 41 ++++-- .../opt/RuleBasedAssemblyOptimization.scala | 133 ++++++++++++++++-- .../compiler/AbstractExpressionCompiler.scala | 4 +- .../compiler/CompilationContext.scala | 2 +- .../millfork/compiler/mos/MosCompiler.scala | 59 +++++--- .../compiler/mos/MosExpressionCompiler.scala | 2 +- .../compiler/mos/MosStatementCompiler.scala | 75 ++++++---- .../compiler/mos/PseudoregisterBuiltIns.scala | 12 +- .../millfork/compiler/z80/Z80Compiler.scala | 25 +++- .../compiler/z80/Z80ExpressionCompiler.scala | 68 ++++++++- .../compiler/z80/Z80StatementCompiler.scala | 38 +++-- .../millfork/compiler/z80/ZBuiltIns.scala | 57 ++++---- src/main/scala/millfork/env/Constant.scala | 2 +- src/main/scala/millfork/env/Environment.scala | 18 ++- .../millfork/node/opt/UnusedFunctions.scala | 10 +- .../scala/millfork/output/Z80Assembler.scala | 106 +++++++------- .../parser/MosSourceLoadingQueue.scala | 4 +- .../scala/millfork/test/StackVarSuite.scala | 29 +++- .../test/emu/EmuOptimizedCmosRun.scala | 2 + .../test/emu/EmuOptimizedInlinedRun.scala | 2 + .../millfork/test/emu/EmuOptimizedRun.scala | 2 + .../scala/millfork/test/emu/EmuPlatform.scala | 1 + src/test/scala/millfork/test/emu/EmuRun.scala | 10 +- .../scala/millfork/test/emu/EmuZ80Run.scala | 10 +- 39 files changed, 862 insertions(+), 250 deletions(-) diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index 371d837e..04f09a12 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -5,7 +5,7 @@ import millfork.error.ErrorReporting /** * @author Karol Stasiak */ -case class CompilationOptions(platform: Platform, commandLineFlags: Map[CompilationFlag.Value, Boolean], outputFileName: Option[String]) { +case class CompilationOptions(platform: Platform, commandLineFlags: Map[CompilationFlag.Value, Boolean], outputFileName: Option[String], zpRegisterSize: Int) { import CompilationFlag._ import Cpu._ @@ -21,7 +21,7 @@ case class CompilationOptions(platform: Platform, commandLineFlags: Map[Compilat if (CpuFamily.forType(platform.cpu) != CpuFamily.M6502) invalids ++= Set( EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, - ZeropagePseudoregister, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, LUnixRelocatableCode, RorWarning) + PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, LUnixRelocatableCode, RorWarning) if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set(EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, UseIxForStack) @@ -30,6 +30,9 @@ case class CompilationOptions(platform: Platform, commandLineFlags: Map[Compilat if (invalids.nonEmpty) { ErrorReporting.error("Invalid flags enabled for the currect CPU family: " + invalids.mkString(", ")) } + if (CpuFamily.forType(platform.cpu) != CpuFamily.M6502 && zpRegisterSize > 0) { + ErrorReporting.error("Invalid flags enabled for the currect CPU family: zp_register" + invalids.mkString(", ")) + } CpuFamily.forType(platform.cpu) match { case CpuFamily.M6502 => if (flags(DecimalMode)) { @@ -133,7 +136,7 @@ object Cpu extends Enumeration { import CompilationFlag._ private val mosAlwaysDefaultFlags = Set( - VariableOverlap, CompactReturnDispatchParams, ZeropagePseudoregister + VariableOverlap, CompactReturnDispatchParams ) private val i8080AlwaysDefaultFlags = Set( @@ -203,7 +206,7 @@ object CompilationFlag extends Enumeration { EmitIllegals, DecimalMode, ReadOnlyArrays, // compilation options for MOS: EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, - ZeropagePseudoregister, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, + PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, // compilation options for Z80 EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, UseIxForStack, // optimization options: @@ -236,7 +239,6 @@ object CompilationFlag extends Enumeration { "ipo" -> InterproceduralOptimization, "inline" -> InlineFunctions, "dangerous_optimizations" -> DangerousOptimizations, - "zeropage_register" -> ZeropagePseudoregister, "decimal_mode" -> DecimalMode, "ro_arrays" -> ReadOnlyArrays, "ror_warn" -> RorWarning, diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 5c055657..a09a35ef 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -24,6 +24,7 @@ case class Context(inputFileNames: List[String], outputFileName: Option[String] = None, runFileName: Option[String] = None, optimizationLevel: Option[Int] = None, + zpRegisterSize: Option[Int] = None, platform: Option[String] = None, outputAssembly: Boolean = false, outputLabels: Boolean = false, @@ -73,7 +74,7 @@ object Main { ErrorReporting.info("No platform selected, defaulting to `c64`") "c64" }) - val options = CompilationOptions(platform, c.flags, c.outputFileName) + val options = CompilationOptions(platform, c.flags, c.outputFileName, c.zpRegisterSize.getOrElse(platform.zpRegisterSize)) ErrorReporting.debug("Effective flags: ") options.flags.toSeq.sortBy(_._1).foreach{ case (f, b) => ErrorReporting.debug(f" $f%-30s : $b%s") @@ -155,7 +156,7 @@ object Main { } val callGraph = new StandardCallGraph(program) - val env = new Environment(None, "") + val env = new Environment(None, "", platform.cpuFamily) env.collectDeclarations(program, options) val assemblyOptimizations = optLevel match { @@ -166,7 +167,7 @@ object Main { val goodExtras = List( if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil, if (options.flag(CompilationFlag.EmitNative65816Opcodes)) SixteenOptimizations.AllForNative else Nil, - if (options.flag(CompilationFlag.ZeropagePseudoregister)) ZeropageRegisterOptimizations.All else Nil, + if (options.zpRegisterSize > 0) ZeropageRegisterOptimizations.All else Nil ).flatten val extras = List( if (options.flag(CompilationFlag.EmitIllegals)) UndocumentedOptimizations.All else Nil, @@ -206,7 +207,7 @@ object Main { } val callGraph = new StandardCallGraph(program) - val env = new Environment(None, "") + val env = new Environment(None, "", platform.cpuFamily) env.collectDeclarations(program, options) val assemblyOptimizations = optLevel match { @@ -305,7 +306,7 @@ object Main { c.changeFlag(CompilationFlag.EmitIllegals, v) }.description("Whether should emit illegal (undocumented) NMOS opcodes. Requires -O2 or higher to have an effect.") boolean("-fzp-register", "-fno-zp-register").action { (c, v) => - c.changeFlag(CompilationFlag.ZeropagePseudoregister, v) + c.copy(zpRegisterSize = Some(if (v) 2 else 0)) // TODO }.description("Whether should use 2 bytes of zeropage as a pseudoregister.") boolean("-fjmp-fix", "-fno-jmp-fix").action { (c, v) => c.changeFlag(CompilationFlag.PreventJmpIndirectBug, v) diff --git a/src/main/scala/millfork/OptimizationPresets.scala b/src/main/scala/millfork/OptimizationPresets.scala index 7a929875..a8616754 100644 --- a/src/main/scala/millfork/OptimizationPresets.scala +++ b/src/main/scala/millfork/OptimizationPresets.scala @@ -81,7 +81,10 @@ object OptimizationPresets { AlwaysGoodOptimizations.PointlessStackStashing, AlwaysGoodOptimizations.PointlessStashingForLaterStore, AlwaysGoodOptimizations.PointlessStashingForLaterLoad, + AlwaysGoodOptimizations.LoadingOfJustWrittenValue, + AlwaysGoodOptimizations.PointlessStackStore, AlwaysGoodOptimizations.RearrangeMath, + AlwaysGoodOptimizations.LoadingOfJustWrittenValue, EmptyMemoryStoreRemoval, AlwaysGoodOptimizations.PointlessLoadBeforeReturn, LaterOptimizations.PointessLoadingForShifting, @@ -128,6 +131,8 @@ object OptimizationPresets { AlwaysGoodOptimizations.PointlessMathFromFlow, AlwaysGoodOptimizations.PointlessMathFromFlow, AlwaysGoodOptimizations.PointlessMathFromFlow, + AlwaysGoodOptimizations.LoadingOfJustWrittenValue, + AlwaysGoodOptimizations.PointlessStackStore, AlwaysGoodOptimizations.OptimizeZeroComparisons, AlwaysGoodOptimizations.SimplifiableCondition, AlwaysGoodOptimizations.IncrementingIndexRegistersAfterTransfer, @@ -141,6 +146,8 @@ object OptimizationPresets { LaterOptimizations.IncreaseWithLimit, SingleAssignmentVariableOptimization, LocalVariableReadOptimization, + AlwaysGoodOptimizations.PointlessStackStore, + AlwaysGoodOptimizations.SimplifiableStackOperation, LaterOptimizations.UseBit, ) @@ -167,6 +174,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.IndexComparisonOptimization, AlwaysGoodOptimizations.IndexSequenceOptimization, AlwaysGoodOptimizations.InefficientStashingToRegister, + AlwaysGoodOptimizations.LoadingOfJustWrittenValue, AlwaysGoodOptimizations.LoopInvariantRegister, LoopUnrolling.LoopUnrolling, AlwaysGoodOptimizations.MathOperationOnTwoIdenticalMemoryOperands, @@ -192,6 +200,7 @@ object OptimizationPresets { AlwaysGoodOptimizations.PointlessRegisterTransfersBeforeReturn, AlwaysGoodOptimizations.PointlessSignCheck, AlwaysGoodOptimizations.PointlessStackStashing, + AlwaysGoodOptimizations.PointlessStackStore, AlwaysGoodOptimizations.PointlessStashingForLaterLoad, AlwaysGoodOptimizations.PointlessStashingForLaterStore, AlwaysGoodOptimizations.PointlessStashingToIndexOverShortSafeBranch, diff --git a/src/main/scala/millfork/Platform.scala b/src/main/scala/millfork/Platform.scala index f8ae1d60..f5cb5a8b 100644 --- a/src/main/scala/millfork/Platform.scala +++ b/src/main/scala/millfork/Platform.scala @@ -23,6 +23,7 @@ class Platform( val outputPackager: OutputPackager, val codeAllocators: Map[String, UpwardByteAllocator], val variableAllocators: Map[String, VariableAllocator], + val zpRegisterSize: Int, val freeZpPointers: List[Int], val fileExtension: String, val generateBbcMicroInfFile: Boolean, @@ -87,6 +88,15 @@ object Platform { } } val startingModules = cs.get(classOf[String], "modules", "").split("[, ]+").filter(_.nonEmpty).toList + val zpRegisterSize = cs.get(classOf[String], "zeropage_register", "2").toLowerCase match { + case "" | null => if (CpuFamily.forType(cpu) == CpuFamily.M6502) 2 else 0 + case "true" | "on" | "yes" => 2 + case "false" | "off" | "no" | "0" => 0 + case x => x.toInt + } + if (zpRegisterSize < 0 || zpRegisterSize > 128) { + ErrorReporting.error("Invalid zeropage register size: " + zpRegisterSize) + } val as = conf.getSection("allocation") @@ -178,6 +188,7 @@ object Platform { new Platform(cpu, flagOverrides, startingModules, outputPackager, codeAllocators.toMap, variableAllocators.toMap, + zpRegisterSize, freePointers, if (fileExtension == "" || fileExtension.startsWith(".")) fileExtension else "." + fileExtension, generateBbcMicroInfFile, @@ -188,7 +199,7 @@ object Platform { def parseNumberOrRange(s:String): Seq[Int] = { if (s.contains("-")) { - var segments = s.split("-") + val segments = s.split("-") if (segments.length != 2) { ErrorReporting.fatal(s"Invalid range: `$s`") } diff --git a/src/main/scala/millfork/assembly/mos/AssemblyLine.scala b/src/main/scala/millfork/assembly/mos/AssemblyLine.scala index 33339e30..a35d6870 100644 --- a/src/main/scala/millfork/assembly/mos/AssemblyLine.scala +++ b/src/main/scala/millfork/assembly/mos/AssemblyLine.scala @@ -229,7 +229,7 @@ object OpcodeClasses { ) val ConcernsStackAlways = ChangesStack ++ Set(TSX, TSY, TSC) - val ConcernsS = ChangesS ++ Set(TSX, TSY, TSC) + val ConcernsSAlways = ChangesS ++ Set(TSX, TSY, TSC) val ChangesNAndZ = Set( ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, DEX, DEY, EOR, INC, INX, INY, LDA, diff --git a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala index df395c55..c3b4698b 100644 --- a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala @@ -31,6 +31,13 @@ object AlwaysGoodOptimizations { (HasOpcode(LDA) & HasImmediate(0) & Elidable) ~ (HasOpcode(CLC) & Elidable) ~ (HasOpcode(ADC) & Elidable & DoesntMatterWhatItDoesWith(State.C, State.Z, State.V, State.N)) ~~> (code => code(2).copy(opcode = LDA) :: code.drop(3)), + (HasOpcode(CLC) & Elidable) ~ + (HasOpcode(ADC) & MatchImmediate(0) & Elidable) ~ + (HasOpcode(CLC) & Elidable) ~ + (HasOpcode(ADC) & MatchImmediate(1) & Elidable & DoesntMatterWhatItDoesWith(State.C, State.V)) ~~> ((code, ctx) => List( + AssemblyLine.implied(CLC), + AssemblyLine.immediate(ADC, (ctx.get[Constant](0) + ctx.get[Constant](1)).quickSimplify), + )), ) val PointlessAccumulatorShifting = new RuleBasedAssemblyOptimization("Pointless accumulator shifting", @@ -889,6 +896,91 @@ object AlwaysGoodOptimizations { }, ) + val LoadingOfJustWrittenValue = new RuleBasedAssemblyOptimization("Loading of just written value", + needsFlowInfo = FlowInfoRequirement.ForwardFlow, + + (HasOpcode(STA) & XContainsStackPointer & HasAddrMode(AbsoluteX) & MatchAddrMode(0) & MatchParameter(1) & MatchA(2) & HasParameterWhere(_ match { + case NumericConstant(addr, _) => addr >= 0x100 && addr <= 0x1ff + case _ => false + })) ~ + (HasOpcode(JSR) | + XContainsStackPointer & HasOpcodeIn(INC, DEC, ASL, LSR) & MatchAddrMode(0) & MatchParameter(1) | + Linear & XContainsStackPointer & HasAddrMode(AbsoluteX) & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory)) | + Linear & HasAddrMode(AbsoluteX) & Not(MatchParameter(1) & XContainsStackPointer) & Not(ChangesMemory) | + Linear & Not(ChangesS) & DoesntChangeMemoryAt(0, 1) & Not(HasAddrMode(AbsoluteX))).* ~ + (Elidable & XContainsStackPointer & HasOpcodeIn(LDA, LDX, LDY, ADC, SBC, ORA, EOR, AND, CMP) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) => + val oldA = ctx.get[Int](2) + val ADDR = ctx.get[Constant](1) + val value = code.foldLeft(oldA) { (prev, line) => + line match { + case AssemblyLine(INC, AbsoluteX, ADDR, _) => (prev + 1) & 0xff + case AssemblyLine(DEC, AbsoluteX, ADDR, _) => (prev - 1) & 0xff + case AssemblyLine(ASL, AbsoluteX, ADDR, _) => (prev << 1) & 0xff + case AssemblyLine(LSR, AbsoluteX, ADDR, _) => (prev >> 1) & 0xff + case _ => prev + } + } + code.init :+ code.last.copy(addrMode = AddrMode.Immediate, parameter = NumericConstant(value, 1)) + }, + + (HasOpcode(STA) & HasAddrMode(Stack) & MatchAddrMode(0) & MatchParameter(1) & MatchA(2)) ~ + (HasOpcode(JSR) | + Linear & HasAddrMode(Stack) & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory)) | + Linear & Not(ChangesS) & DoesntChangeMemoryAt(0, 1) & Not(HasAddrMode(Stack))).* ~ + (Elidable & HasOpcodeIn(LDA, LDX, LDY, ADC, SBC, ORA, EOR, AND, CMP) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) => + code.init :+ code.last.copy(addrMode = AddrMode.Immediate, parameter = NumericConstant(ctx.get[Int](2), 1)) + }, + + (HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & MatchA(2)) ~ + (Linear & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Elidable & HasOpcodeIn(LDA, LDX, LDY, ADC, SBC, ORA, EOR, AND, CMP) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) => + code.init :+ code.last.copy(addrMode = AddrMode.Immediate, parameter = NumericConstant(ctx.get[Int](2), 1)) + }, + + ) + + val PointlessStackStore = new RuleBasedAssemblyOptimization("Pointless stack store", + needsFlowInfo = FlowInfoRequirement.BothFlows, + + // TODO: check if JSR is OK + + (Elidable & HasOpcode(STA) & HasAddrMode(AbsoluteX) & XContainsStackPointer & HasParameterWhere(_ match { + case NumericConstant(addr, _) => addr >= 0x100 && addr <= 0x1ff + case _ => false + }) & MatchParameter(1)) ~ + (HasOpcode(JSR) | + Not(ChangesS) & Not(HasOpcodeIn(RTS, RTI)) & Linear & Not(HasAddrMode(AbsoluteX)) | + HasAddrMode(AbsoluteX) & XContainsStackPointer & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory))).* ~ + HasOpcodeIn(RTS, RTI) ~~> (_.tail), + + (Elidable & HasOpcode(STA) & HasAddrMode(AbsoluteX) & HasParameterWhere(_ match { + case NumericConstant(addr, _) => addr >= 0x100 && addr <= 0x1ff + case _ => false + })) ~ + (HasOpcode(JSR) | Not(HasOpcodeIn(RTS, RTI)) & Linear & Not(HasAddrMode(AbsoluteX))).* ~ + HasOpcodeIn(RTS, RTI) ~~> (_.tail), + + (Elidable & HasOpcodeIn(INC, DEC, ASL, LSR, ROR, ROL) & HasAddrMode(AbsoluteX) & HasParameterWhere(_ match { + case NumericConstant(addr, _) => addr >= 0x100 && addr <= 0x1ff + case _ => false + }) & DoesntMatterWhatItDoesWith(State.Z, State.N, State.C)) ~ + (HasOpcode(JSR) | Not(HasOpcodeIn(RTS, RTI)) & Linear & Not(HasAddrMode(AbsoluteX))).* ~ + HasOpcodeIn(RTS, RTI) ~~> (_.tail), + + (Elidable & HasOpcodeIn(STA, STA_W) & HasAddrMode(Stack)) ~ + (HasOpcode(JSR) | Linear & Not(HasAddrMode(Stack)) & Not(HasOpcodeIn(RTS, RTI))).* ~ + HasOpcodeIn(RTS, RTI) ~~> (_.tail), + + (HasOpcode(STA) & XContainsStackPointer & HasAddrMode(AbsoluteX) & MatchAddrMode(0) & MatchParameter(1) & HasParameterWhere(_ match { + case NumericConstant(addr, _) => addr >= 0x100 && addr <= 0x1ff + case _ => false + })) ~ + (HasOpcode(JSR) | + Linear & XContainsStackPointer & HasAddrMode(AbsoluteX) & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory)) | + Linear & Not(ChangesS) & Not(HasAddrMode(AbsoluteX)) & DoesNotConcernMemoryAt(0, 1)).* ~ + (Elidable & XContainsStackPointer & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.tail), + ) + val IdempotentDuplicateRemoval = new RuleBasedAssemblyOptimization("Idempotent duplicate operation", needsFlowInfo = FlowInfoRequirement.NoRequirement, HasOpcode(RTS) ~ NoopDiscardsFlags.* ~ (HasOpcode(RTS) ~ Elidable) ~~> (_.take(1)) :: @@ -1252,6 +1344,17 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(INX)) ~ (Elidable & HasOpcode(TXS)) ~ (ConcernsA & Not(ConcernsStack) & Linear & DoesntMatterWhatItDoesWith(State.Z, State.N, State.A)) ~~> (code => List(code.last, AssemblyLine.implied(PLA))), + + (Elidable & HasOpcodeIn(PHA, PHX, PHY)).*.captureLength(0) ~ + (HasOpcode(TSX) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~ + (Linear & Not(ConcernsS) & Not(ConcernsX) & Not(ChangesStack)).* ~ + (Elidable & HasOpcode(INX)).*.captureLength(1) ~ + Where(ctx => ctx.get[Int](0) >= ctx.get[Int](1)) ~ + (HasOpcode(TXS) & DoesntMatterWhatItDoesWith(State.Z, State.N, State.X)) ~~> {(code, ctx) => + val pushCount = ctx.get[Int](0) + val inxCount = ctx.get[Int](1) + code.take(pushCount - inxCount) ++ code.drop(pushCount + 1).dropRight(inxCount + 1) + }, ) val SimplifiableBitOpsSequence = new RuleBasedAssemblyOptimization("Simplifiable sequence of bit operations", diff --git a/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala b/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala index ae5bf5f0..14d52a1e 100644 --- a/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala +++ b/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala @@ -90,7 +90,9 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, // case _ => // } - override def toString: String = s"A=$a,B=$ah,X=$x,Y=$y,Z=$iz; Z=$z,N=$n,C=$c,V=$v,D=$d,M=$m,X=$w; A7=$a7,A0=$a0,NZ:$src" + override def toString: String = s"A=$a,B=$ah,X=$x,Y=$y,Z=$iz; Z=$z,N=$n,C=$c,V=$v,D=$d,M=$m,X=$w; A7=$a7,A0=$a0,NZ:$src" + + (if (eqSX) "; S=X" + else /* */ " ") def aw: Status[Int] = (ah, a) match { case (SingleStatus(h), SingleStatus(l)) => SingleStatus(h.&(0xff).<<(8).+(l&0xff)) diff --git a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala index 0fbf610e..c40cff18 100644 --- a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzer.scala @@ -1,6 +1,5 @@ package millfork.assembly.mos.opt -import millfork.CompilationOptions import millfork.assembly.OptimizationContext import millfork.assembly.mos.{AssemblyLine, Opcode, State} import millfork.env.{Label, MemoryAddressConstant, NormalFunction} @@ -12,6 +11,8 @@ import millfork.env.{Label, MemoryAddressConstant, NormalFunction} class FlowHolder(_statusBefore: () => List[CpuStatus], _importanceAfter: () => List[CpuImportance]) { lazy val statusBefore: List[CpuStatus] = _statusBefore() lazy val importanceAfter: List[CpuImportance] = _importanceAfter() + + def toString(index: Int): String = statusBefore(index).toString ++ " -> " ++ importanceAfter(index).toString } case class FlowInfo(holder: FlowHolder, index: Int, _labelUseCountMap: () => Option[Map[String, Int]]) { @@ -27,6 +28,8 @@ case class FlowInfo(holder: FlowHolder, index: Int, _labelUseCountMap: () => Opt def isUnimportant(state: State.Value): Boolean = importanceAfter.isUnimportant(state) def labelUseCount(label: String): Int = labelUseCountMap.map(_.getOrElse(label, 0)).getOrElse(-1) + + override def toString: String = holder.toString(index) } object FlowAnalyzer { diff --git a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala index 2abd710d..77a95e82 100644 --- a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala @@ -96,7 +96,7 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions, private val map = mutable.Map[Int, Any]() - override def toString: String = map.mkString(", ") + override def toString: String = if (map.isEmpty) "" else map.mkString(", ") def addObject(i: Int, o: Any): Boolean = { if (map.contains(i)) { @@ -774,6 +774,13 @@ case object ConcernsX extends TrivialAssemblyLinePattern { OpcodeClasses.ConcernsXAlways(line.opcode) || XAddrModes(line.addrMode) } +case object ConcernsS extends TrivialAssemblyLinePattern { + val SAddrModes = Set(AddrMode.Stack, AddrMode.IndexedSY) + + override def apply(line: AssemblyLine): Boolean = + OpcodeClasses.ConcernsSAlways(line.opcode) || SAddrModes(line.addrMode) +} + case object ConcernsY extends TrivialAssemblyLinePattern { val YAddrModes = Set(AddrMode.AbsoluteY, AddrMode.IndexedSY, AddrMode.IndexedY, AddrMode.LongIndexedY, AddrMode.ZeroPageY) diff --git a/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala b/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala index 207245c5..c346404f 100644 --- a/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/SuperOptimizer.scala @@ -38,7 +38,7 @@ object SuperOptimizer extends AssemblyOptimization[AssemblyLine] { if (options.flag(CompilationFlag.Emit65CE02Opcodes)) { allOptimizers ++= CE02Optimizations.All } - if (options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (options.zpRegisterSize > 0) { allOptimizers ++= ZeropageRegisterOptimizations.All } allOptimizers ++= List( diff --git a/src/main/scala/millfork/assembly/z80/ZLine.scala b/src/main/scala/millfork/assembly/z80/ZLine.scala index 0804fde2..ac875688 100644 --- a/src/main/scala/millfork/assembly/z80/ZLine.scala +++ b/src/main/scala/millfork/assembly/z80/ZLine.scala @@ -20,22 +20,56 @@ case class IfFlagSet(flag: ZFlag.Value) extends ZRegisters case class IfFlagClear(flag: ZFlag.Value) extends ZRegisters -case class OneRegister(register: ZRegister.Value) extends ZRegisters +case class OneRegister(register: ZRegister.Value) extends ZRegisters { + if (register == ZRegister.MEM_IY_D || register == ZRegister.MEM_IX_D) ??? +} -case class TwoRegisters(target: ZRegister.Value, source: ZRegister.Value) extends ZRegisters +case class TwoRegisters(target: ZRegister.Value, source: ZRegister.Value) extends ZRegisters { + if (target == ZRegister.MEM_IY_D || target == ZRegister.MEM_IX_D) ??? + if (source == ZRegister.MEM_IY_D || source == ZRegister.MEM_IX_D) ??? +} + +case class OneRegisterOffset(register: ZRegister.Value, offset: Int) extends ZRegisters { + if (register != ZRegister.MEM_IY_D && register != ZRegister.MEM_IX_D) ??? +} + +case class TwoRegistersOffset(target: ZRegister.Value, source: ZRegister.Value, offset: Int) extends ZRegisters { + if (target != ZRegister.MEM_IY_D && target != ZRegister.MEM_IX_D && source != ZRegister.MEM_IY_D && source != ZRegister.MEM_IX_D) ??? +} sealed abstract class LocalVariableAddressOperand(val memViaRegister: ZRegister.Value) { - def offsetConstant: Constant + def offset: Int + def oneRegister: ZRegisters + def asTargetWithSource(reg: ZRegister.Value): ZRegisters + def asSourceWithTarget(reg: ZRegister.Value): ZRegisters } case class LocalVariableAddressViaIX(offset: Int) extends LocalVariableAddressOperand(ZRegister.MEM_IX_D) { - override def offsetConstant: Constant = NumericConstant(offset, 1) + + override def oneRegister: ZRegisters = OneRegisterOffset(ZRegister.MEM_IX_D, offset) + + override def asTargetWithSource(reg: ZRegister.Value): ZRegisters = TwoRegistersOffset(ZRegister.MEM_IX_D, reg, offset) + + override def asSourceWithTarget(reg: ZRegister.Value): ZRegisters = TwoRegistersOffset(reg, ZRegister.MEM_IX_D, offset) } + case class LocalVariableAddressViaIY(offset: Int) extends LocalVariableAddressOperand(ZRegister.MEM_IY_D) { - override def offsetConstant: Constant = NumericConstant(offset, 1) + + override def oneRegister: ZRegisters = OneRegisterOffset(ZRegister.MEM_IY_D, offset) + + override def asTargetWithSource(reg: ZRegister.Value): ZRegisters = TwoRegistersOffset(ZRegister.MEM_IY_D, reg, offset) + + override def asSourceWithTarget(reg: ZRegister.Value): ZRegisters = TwoRegistersOffset(reg, ZRegister.MEM_IY_D, offset) } + case object LocalVariableAddressViaHL extends LocalVariableAddressOperand(ZRegister.MEM_HL) { - override def offsetConstant: Constant = Constant.Zero + override def offset: Int = 0 + + override def oneRegister: ZRegisters = OneRegister(ZRegister.MEM_HL) + + override def asTargetWithSource(reg: ZRegister.Value): ZRegisters = TwoRegisters(ZRegister.MEM_HL, reg) + + override def asSourceWithTarget(reg: ZRegister.Value): ZRegisters = TwoRegisters(reg, ZRegister.MEM_HL) } object ZLine { @@ -63,7 +97,7 @@ object ZLine { def register(opcode: ZOpcode.Value, register: ZRegister.Value): ZLine = ZLine(opcode, OneRegister(register), Constant.Zero) - def register(opcode: ZOpcode.Value, register: LocalVariableAddressOperand): ZLine = ZLine(opcode, OneRegister(register.memViaRegister), register.offsetConstant) + def register(opcode: ZOpcode.Value, register: LocalVariableAddressOperand): ZLine = ZLine(opcode, register.oneRegister, Constant.Zero) def imm8(opcode: ZOpcode.Value, value: Constant): ZLine = ZLine(opcode, OneRegister(IMM_8), value) @@ -73,12 +107,16 @@ object ZLine { def ld8(target: ZRegister.Value, source: ZRegister.Value): ZLine = ZLine(LD, TwoRegisters(target, source), Constant.Zero) - def ld8(target: LocalVariableAddressOperand, source: ZRegister.Value): ZLine = ZLine(LD, TwoRegisters(target.memViaRegister, source), target.offsetConstant) + def ld8(target: LocalVariableAddressOperand, source: ZRegister.Value): ZLine = ZLine(LD, target.asTargetWithSource(source), Constant.Zero) - def ld8(target: ZRegister.Value, source: LocalVariableAddressOperand): ZLine = ZLine(LD, TwoRegisters(target, source.memViaRegister), source.offsetConstant) + def ld8(target: ZRegister.Value, source: LocalVariableAddressOperand): ZLine = ZLine(LD, source.asSourceWithTarget(target), Constant.Zero) def ld16(target: ZRegister.Value, source: ZRegister.Value): ZLine = ZLine(LD_16, TwoRegisters(target, source), Constant.Zero) + def ldImm8(target: LocalVariableAddressOperand, source: Int): ZLine = ZLine(LD, target.asTargetWithSource(IMM_8), NumericConstant(source & 0xff, 1)) + + def ldImm8(target: LocalVariableAddressOperand, source: Constant): ZLine = ZLine(LD, target.asTargetWithSource(IMM_8), source) + def ldImm8(target: ZRegister.Value, source: Int): ZLine = ZLine(LD, TwoRegisters(target, IMM_8), NumericConstant(source & 0xff, 1)) def ldImm8(target: ZRegister.Value, source: Constant): ZLine = ZLine(LD, TwoRegisters(target, IMM_8), source) @@ -103,9 +141,9 @@ object ZLine { def ldAbs16(target: Constant, source: ZRegister.Value): ZLine = ZLine(LD_16, TwoRegisters(MEM_ABS_16, source), target) - def ldViaIx(target: ZRegister.Value, sourceOffset: Int): ZLine = ZLine(LD, TwoRegisters(target, ZRegister.MEM_IX_D), NumericConstant(sourceOffset, 1)) + def ldViaIx(target: ZRegister.Value, sourceOffset: Int): ZLine = ZLine(LD, TwoRegistersOffset(target, ZRegister.MEM_IX_D, sourceOffset), Constant.Zero) - def ldViaIx(targetOffset: Int, source: ZRegister.Value): ZLine = ZLine(LD, TwoRegisters(ZRegister.MEM_IX_D, source), NumericConstant(targetOffset, 1)) + def ldViaIx(targetOffset: Int, source: ZRegister.Value): ZLine = ZLine(LD, TwoRegistersOffset(ZRegister.MEM_IX_D, source, targetOffset), Constant.Zero) } case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Constant, elidable: Boolean = true) extends AbstractCode { @@ -141,7 +179,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta override def isPrintable: Boolean = true - private def asAssemblyString(r: ZRegister.Value): String = r match { + private def asAssemblyString(r: ZRegister.Value, offset: Int = 0): String = r match { case ZRegister.A => "A" case ZRegister.B => "B" case ZRegister.C => "C" @@ -166,8 +204,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case ZRegister.MEM_ABS_16 => s"($parameter)" case ZRegister.IMM_8 => s"#$parameter" case ZRegister.IMM_16 => s"#$parameter" - case ZRegister.MEM_IX_D => s"(IX,$parameter)" - case ZRegister.MEM_IY_D => s"(IY,$parameter)" + case ZRegister.MEM_IX_D => s"(IX,$offset)" + case ZRegister.MEM_IY_D => s"(IY,$offset)" case ZRegister.MEM_HL => "(HL)" case ZRegister.MEM_BC => "(BC)" case ZRegister.MEM_DE => "(DE)" @@ -199,8 +237,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case OneRegister(r) => s" (${asAssemblyString(r)})" } s" $opcode$ps" - case o => - val os = o.toString//.stripSuffix("_16") + case op => + val os = op.toString//.stripSuffix("_16") val ps = registers match { case NoRegisters => "" case IfFlagSet(ZFlag.P) => " PO" @@ -211,6 +249,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case IfFlagClear(f) => s" N$f" case OneRegister(r) => s" ${asAssemblyString(r)}" case TwoRegisters(t, s) => s" ${asAssemblyString(t)},${asAssemblyString(s)}" + case TwoRegistersOffset(t, s, o) => s" ${asAssemblyString(t, o)},${asAssemblyString(s, o)}" + case OneRegisterOffset(r, o) => s" ${asAssemblyString(r, o)}" } s" $os$ps" } @@ -294,6 +334,26 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta } } + def changesRegisterAndOffset(r: ZRegister.Value, o: Int): Boolean = { + import ZOpcode._ + import ZRegister._ + r match { + case MEM_IX_D | MEM_IY_D => + opcode match { + case LD => registers match { + case TwoRegistersOffset(s, _, p) => r == s && o == p + case _ => false + } + case INC | DEC | RL | RLC | RR | RRC | SLA | SLL | SRA | SRL => registers match { + case OneRegisterOffset(s, p) => r == s && o == p + case _ => false + } + case _ => false // TODO + } + case _ => changesRegister(r) + } + } + def changesRegister(r: ZRegister.Value): Boolean = { import ZOpcode._ import ZRegister._ @@ -304,7 +364,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case IX => changesRegister(IXH) || changesRegister(IXL) case IY => changesRegister(IYH) || changesRegister(IYL) case AF => ??? - case MEM_ABS_8 | MEM_ABS_16 | IMM_8 | IMM_16 | MEM_DE | MEM_HL | MEM_BC | MEM_IX_D | MEM_IY_D | SP => ??? + case IMM_8 | IMM_16 => false + case MEM_ABS_8 | MEM_ABS_16 | MEM_DE | MEM_HL | MEM_BC | MEM_IX_D | MEM_IY_D | SP => ??? case _ => opcode match { case LD => registers match { diff --git a/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodZ80Optimizations.scala b/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodZ80Optimizations.scala index 7e2c4a6c..0366aaf5 100644 --- a/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodZ80Optimizations.scala +++ b/src/main/scala/millfork/assembly/z80/opt/AlwaysGoodZ80Optimizations.scala @@ -1,7 +1,7 @@ package millfork.assembly.z80.opt import millfork.assembly.AssemblyOptimization -import millfork.assembly.z80.{OneRegister, TwoRegisters, ZLine, ZOpcode} +import millfork.assembly.z80._ import millfork.assembly.z80.ZOpcode._ import millfork.env.{Constant, NumericConstant} import millfork.node.ZRegister @@ -11,6 +11,13 @@ import millfork.node.ZRegister */ object AlwaysGoodZ80Optimizations { + def change8BitLoadTarget(line: ZLine, newTarget: ZRegister.Value): ZLine = { + line match { + case ZLine(LD, TwoRegistersOffset(_, s, o), p, _) => ZLine(LD, TwoRegistersOffset(newTarget, s, o), p) + case ZLine(LD, TwoRegisters(_, s), p, _) => ZLine(LD, TwoRegisters(newTarget, s), p) + } + } + def for7Registers(f: ZRegister.Value => AssemblyRuleSet) = MultipleAssemblyRules( List(ZRegister.A, ZRegister.B, ZRegister.C, ZRegister.D, ZRegister.E, ZRegister.H, ZRegister.L).map(f)) @@ -26,9 +33,23 @@ object AlwaysGoodZ80Optimizations { (Elidable & IsRegular8BitLoadFrom(register) & MatchRegister(register, 0)) ~~> ((code, ctx) => code.map(x => x.copy( parameter = NumericConstant(ctx.get[Int](0), 1), - registers = x.registers.asInstanceOf[TwoRegisters].copy(source = ZRegister.IMM_8) + registers = x.registers match { + case TwoRegisters(t, _) => TwoRegisters(t, ZRegister.IMM_8) + case TwoRegistersOffset(t@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), _, o) => TwoRegistersOffset(t, ZRegister.IMM_8, o) + case TwoRegistersOffset(t, ZRegister.MEM_IX_D | ZRegister.MEM_IY_D, _) => TwoRegisters(t, ZRegister.IMM_8) + } ))) ), + (Elidable & MatchSourceIxOffsetOf8BitLoad(0) & MatchValueAtMatchedIxOffset(0, 1)) ~~> ((code, ctx) => + code.map(x => x.copy( + parameter = NumericConstant(ctx.get[Int](1), 1), + registers = x.registers match { + case TwoRegisters(t, _) => TwoRegisters(t, ZRegister.IMM_8) + case TwoRegistersOffset(t@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), _, o) => TwoRegistersOffset(t, ZRegister.IMM_8, o) + case TwoRegistersOffset(t, ZRegister.MEM_IX_D | ZRegister.MEM_IY_D, _) => TwoRegisters(t, ZRegister.IMM_8) + } + )) + ), for6Registers(register => (Elidable & HasRegisterParam(register) & HasOpcodeIn(Set(AND, ADD, ADC, SUB, SBC, XOR, OR, CP)) & MatchRegister(register, 0)) ~~> ((code, ctx) => code.map(x => x.copy( @@ -36,6 +57,12 @@ object AlwaysGoodZ80Optimizations { registers = OneRegister(ZRegister.IMM_8) ))) ), + (Elidable & MatchIxOffset(0) & HasOpcodeIn(Set(AND, ADD, ADC, SUB, SBC, XOR, OR, CP)) & MatchValueAtMatchedIxOffset(0, 1)) ~~> ((code, ctx) => + code.map(x => x.copy( + parameter = NumericConstant(ctx.get[Int](1), 1), + registers = OneRegister(ZRegister.IMM_8) + )) + ), ) val ReloadingKnownValueFromMemory = new RuleBasedAssemblyOptimization("Reloading known value from memory", @@ -88,6 +115,28 @@ object AlwaysGoodZ80Optimizations { (Elidable & Is8BitLoadTo(ZRegister.MEM_BC)) ~ (Linear & Not(ConcernsMemory) & Not(Changes(ZRegister.BC))).* ~ Is8BitLoadTo(ZRegister.MEM_BC) ~~> (_.tail), + + (Elidable & MatchTargetIxOffsetOf8BitLoad(0) & MatchUnimportantIxOffset(0)) ~~> (_ => Nil), + + for7Registers(register => + (Elidable & Is8BitLoadTo(register) & NoOffset & MatchSourceRegisterAndOffset(1)) ~ + (Linear & Not(Concerns(register)) & DoesntChangeMatchedRegisterAndOffset(1)).* ~ + (Elidable & IsRegular8BitLoadFrom(register) & DoesntMatterWhatItDoesWith(register)) ~~> { code => + val last = code.last + val head = code.head + code.tail.init :+ ZLine(LD, (last.registers, head.registers) match { + case (TwoRegisters(t, _), TwoRegisters(_, s)) => TwoRegisters(t, s) + case (TwoRegistersOffset(t, _, o), TwoRegisters(_, s)) => TwoRegistersOffset(t, s, o) + case (TwoRegisters(t, _), TwoRegistersOffset(_, s, o)) => TwoRegistersOffset(t, s, o) + case _ => ??? + }, head.parameter) + } + ), + + for7Registers(register => + (Elidable & Is8BitLoad(register, register)) ~~> (_ => Nil) + ), + ) private def simplifiable16BitAddWithSplitTarget(targetH: ZRegister.Value, targetL: ZRegister.Value, target: ZRegister.Value, source: ZRegister.Value) = MultipleAssemblyRules(List( @@ -137,7 +186,7 @@ object AlwaysGoodZ80Optimizations { needsFlowInfo = FlowInfoRequirement.BothFlows, for6Registers(register => (Elidable & HasOpcode(ADD) & MatchRegister(ZRegister.A, 0) & HasRegisterParam(register) & MatchRegister(register, 1) & - DoesntMatterWhatItDoesWithFlags) ~~> ((code, ctx) => List(ZLine.ldImm16(ZRegister.A, (ctx.get[Int](0) + ctx.get[Int](1)) & 0xff))), + DoesntMatterWhatItDoesWithFlags) ~~> ((code, ctx) => List(ZLine.ldImm8(ZRegister.A, (ctx.get[Int](0) + ctx.get[Int](1)) & 0xff))), ), simplifiable16BitAddWithSplitTarget(ZRegister.H, ZRegister.L, ZRegister.HL, ZRegister.BC), simplifiable16BitAddWithSplitTarget(ZRegister.H, ZRegister.L, ZRegister.HL, ZRegister.DE), @@ -145,6 +194,29 @@ object AlwaysGoodZ80Optimizations { simplifiable16BitAddWithSplitTarget(ZRegister.IXH, ZRegister.IXL, ZRegister.IX, ZRegister.DE), simplifiable16BitAddWithSplitTarget(ZRegister.IYH, ZRegister.IYL, ZRegister.IY, ZRegister.BC), simplifiable16BitAddWithSplitTarget(ZRegister.IYH, ZRegister.IYL, ZRegister.IY, ZRegister.DE), + + (Elidable & Is8BitLoad(ZRegister.D, ZRegister.H)) ~ + (Elidable & Is8BitLoad(ZRegister.E, ZRegister.L)) ~ + (Elidable & Is8BitLoadTo(ZRegister.L)) ~ + (Elidable & Is8BitLoadTo(ZRegister.H)) ~ + (HasOpcode(ADD_16) & HasRegisters(TwoRegisters(ZRegister.HL, ZRegister.DE)) & DoesntMatterWhatItDoesWith(ZRegister.D, ZRegister.E)) ~~> { code => + List( + change8BitLoadTarget(code(2), ZRegister.E), + change8BitLoadTarget(code(3), ZRegister.D), + code.last) + }, + (Elidable & Is8BitLoad(ZRegister.D, ZRegister.H)) ~ + (Elidable & Is8BitLoad(ZRegister.E, ZRegister.L)) ~ + (Elidable & Is8BitLoadTo(ZRegister.H)) ~ + (Elidable & Is8BitLoadTo(ZRegister.L)) ~ + (HasOpcode(ADD_16) & HasRegisters(TwoRegisters(ZRegister.HL, ZRegister.DE)) & DoesntMatterWhatItDoesWith(ZRegister.D, ZRegister.E)) ~~> { code => + List( + change8BitLoadTarget(code(2), ZRegister.D), + change8BitLoadTarget(code(3), ZRegister.E), + code.last) + }, + + (Elidable & HasOpcodeIn(Set(ADD, OR, AND, XOR, SUB)) & Has8BitImmediate(0) & DoesntMatterWhatItDoesWithFlags) ~~> (_ => Nil), ) val FreeHL = new RuleBasedAssemblyOptimization("Free HL", diff --git a/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala index a13410e3..b2bb7783 100644 --- a/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala @@ -1,10 +1,10 @@ package millfork.assembly.z80.opt import millfork.assembly.opt.{AnyStatus, SingleStatus} -import millfork.assembly.z80.{OneRegister, TwoRegisters, ZLine, ZOpcodeClasses} +import millfork.assembly.z80._ import millfork.env.{Label, MemoryAddressConstant, NormalFunction, NumericConstant} import millfork.node.ZRegister -import millfork.{CompilationFlag, CompilationOptions} +import millfork.CompilationOptions /** * @author Karol Stasiak @@ -12,8 +12,6 @@ import millfork.{CompilationFlag, CompilationOptions} object CoarseFlowAnalyzer { def analyze(f: NormalFunction, code: List[ZLine], compilationOptions: CompilationOptions): List[CpuStatus] = { - val ceFlag = compilationOptions.flag(CompilationFlag.Emit65CE02Opcodes) - val cmosFlag = compilationOptions.flag(CompilationFlag.EmitCmosOpcodes) val initialStatus = CpuStatus() val functionStartStatus = CpuStatus() val emptyStatus = CpuStatus() @@ -39,8 +37,11 @@ object CoarseFlowAnalyzer { case _ => None }).fold(currentStatus)(_ ~ _) - case ZLine(CALL | BYTE, _, _, _) => + case ZLine(CALL, _, _, _) => + currentStatus = initialStatus.copy(memIx = currentStatus.memIx) + case ZLine(BYTE, _, _, _) => currentStatus = initialStatus + case ZLine(ADD, OneRegister(s), _, _) => currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s)) ((m, n) => (m + n) & 0xff), cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) @@ -56,10 +57,34 @@ object CoarseFlowAnalyzer { case ZLine(XOR, OneRegister(s), _, _) => currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s)) ((m, n) => (m ^ n) & 0xff), cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + + case ZLine(ADD, OneRegisterOffset(s, o), _, _) => + currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s, o)) ((m, n) => (m + n) & 0xff), + cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + case ZLine(SUB, OneRegisterOffset(s, o), _, _) => + currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s, o)) ((m, n) => (m - n) & 0xff), + cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + case ZLine(AND, OneRegisterOffset(s, o), _, _) => + currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s, o)) ((m, n) => (m & n) & 0xff), + cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + case ZLine(OR, OneRegisterOffset(s, o), _, _) => + currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s, o)) ((m, n) => (m | n) & 0xff), + cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + case ZLine(XOR, OneRegisterOffset(s, o), _, _) => + currentStatus = currentStatus.copy(a = (currentStatus.a <*> currentStatus.getRegister(s, o)) ((m, n) => (m ^ n) & 0xff), + cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + + case ZLine(CP, _, _, _) => + currentStatus = currentStatus.copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) + case ZLine(LD, TwoRegisters(t, ZRegister.IMM_8), NumericConstant(value, _), _) => currentStatus = currentStatus.setRegister(t, SingleStatus(value.toInt)) + case ZLine(LD, TwoRegistersOffset(t, ZRegister.IMM_8, o), NumericConstant(value, _), _) => + currentStatus = currentStatus.setRegister(t, SingleStatus(value.toInt), o) case ZLine(LD | LD_16, TwoRegisters(t, s), _, _) => currentStatus = currentStatus.setRegister(t, currentStatus.getRegister(s)) + case ZLine(LD | LD_16, TwoRegistersOffset(t, s, o), _, _) => + currentStatus = currentStatus.setRegister(t, currentStatus.getRegister(s, o), o) case ZLine(ADD_16, TwoRegisters(t, s), _, _) => currentStatus = currentStatus.copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus) .setRegister(t, (currentStatus.getRegister(t) <*> currentStatus.getRegister(s)) ((m, n) => (m + n) & 0xffff)) @@ -71,6 +96,8 @@ object CoarseFlowAnalyzer { registers match { case OneRegister(r) => currentStatus = currentStatus.setRegister(r, AnyStatus) case TwoRegisters(r1, r2) => currentStatus = currentStatus.setRegister(r1, AnyStatus).setRegister(r2, AnyStatus) + case OneRegisterOffset(r1, o) => currentStatus = currentStatus.setRegister(r1, AnyStatus, o) + case TwoRegistersOffset(r1, r2, o) => currentStatus = currentStatus.setRegister(r1, AnyStatus, o).setRegister(r2, AnyStatus, o) case _ => () } } diff --git a/src/main/scala/millfork/assembly/z80/opt/CpuStatus.scala b/src/main/scala/millfork/assembly/z80/opt/CpuStatus.scala index 421ed19a..9683d73b 100644 --- a/src/main/scala/millfork/assembly/z80/opt/CpuStatus.scala +++ b/src/main/scala/millfork/assembly/z80/opt/CpuStatus.scala @@ -1,7 +1,6 @@ //noinspection RedundantNewCaseClass package millfork.assembly.z80.opt -import millfork.assembly.mos.State import millfork.assembly.opt._ import millfork.assembly.z80.ZFlag import millfork.node.ZRegister @@ -21,6 +20,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, ixl: Status[Int] = UnknownStatus, iyh: Status[Int] = UnknownStatus, iyl: Status[Int] = UnknownStatus, + memIx: Map[Int, Status[Int]] = Map(), zf: Status[Boolean] = UnknownStatus, nf: Status[Boolean] = UnknownStatus, cf: Status[Boolean] = UnknownStatus, @@ -28,7 +28,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, pf: Status[Boolean] = UnknownStatus, hf: Status[Boolean] = UnknownStatus ) { - def setRegister(target: ZRegister.Value, value: Status[Int]): CpuStatus = target match { + def setRegister(target: ZRegister.Value, value: Status[Int], offset: Int = -1): CpuStatus = target match { case ZRegister.IMM_8 => this case ZRegister.IMM_16 => this case ZRegister.A => this.copy(a = value) @@ -42,7 +42,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, case ZRegister.IXL => this.copy(ixl = value) case ZRegister.IYH => this.copy(iyh = value) case ZRegister.IYL => this.copy(iyl = value) - case ZRegister.MEM_IX_D => this + case ZRegister.MEM_IX_D => if (offset < 0) this.copy(memIx = Map()) else this.copy(memIx = this.memIx + (offset -> value)) case ZRegister.MEM_IY_D => this case ZRegister.MEM_HL => this case ZRegister.MEM_BC => this @@ -62,7 +62,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, val mergeBytes: (Int, Int) => Int = (h, l) => (h & 0xff) * 256 + (l & 0xff) - def getRegister(register: ZRegister.Value): Status[Int] = register match { + def getRegister(register: ZRegister.Value, offset: Int = -1): Status[Int] = register match { case ZRegister.A => a case ZRegister.B => b case ZRegister.C => c @@ -74,7 +74,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, case ZRegister.IXL => ixl case ZRegister.IYH => iyh case ZRegister.IYL => iyl - case ZRegister.MEM_IX_D => AnyStatus + case ZRegister.MEM_IX_D => if (offset < 0) AnyStatus else memIx.getOrElse(offset, UnknownStatus) case ZRegister.MEM_IY_D => AnyStatus case ZRegister.MEM_HL => AnyStatus case ZRegister.MEM_BC => AnyStatus @@ -114,6 +114,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, ixl = this.ixl ~ that.ixl, iyh = this.iyh ~ that.iyh, iyl = this.iyl ~ that.iyl, + memIx = (this.memIx.keySet | that.memIx.keySet).map(k => k -> (this.memIx.getOrElse(k, UnknownStatus) ~ that.memIx.getOrElse(k, UnknownStatus))).toMap, nf = this.nf ~ that.nf, sf = this.sf ~ that.sf, zf = this.zf ~ that.zf, @@ -123,6 +124,9 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, ) + override def toString: String = { + val memRepr = if (memIx.isEmpty) "" else (0 to memIx.keys.max).map(i => memIx.getOrElse(i, UnknownStatus)).mkString("") + s"A=$a,B=$b,C=$c,D=$d,E=$e,H=$h,L=$l,IX=$ixh$ixl,Y=$iyh$iyl; Z=$zf,C=$cf,N=$nf,S=$sf,P=$pf,H=$hf; M=" + memRepr.padTo(4, ' ') + } - override def toString: String = s"A=$a,B=$b,C=$c,D=$d,E=$e,H=$h,L=$l,IX=$ixh$ixl,Y=$iyh$iyl; Z=$zf,C=$cf,N=$nf,S=$sf,P=$pf,H=$hf" } \ No newline at end of file diff --git a/src/main/scala/millfork/assembly/z80/opt/FlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/FlowAnalyzer.scala index 37867452..f9feb33c 100644 --- a/src/main/scala/millfork/assembly/z80/opt/FlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/FlowAnalyzer.scala @@ -11,6 +11,8 @@ import millfork.env.{Label, MemoryAddressConstant, NormalFunction} class FlowHolder(_statusBefore: () => List[CpuStatus], _importanceAfter: () => List[CpuImportance]) { lazy val statusBefore: List[CpuStatus] = _statusBefore() lazy val importanceAfter: List[CpuImportance] = _importanceAfter() + + def toString(index: Int): String = statusBefore(index).toString ++ " -> " ++ importanceAfter(index).toString } case class FlowInfo(holder: FlowHolder, index: Int, _labelUseCountMap: () => Option[Map[String, Int]]) { @@ -20,6 +22,8 @@ case class FlowInfo(holder: FlowHolder, index: Int, _labelUseCountMap: () => Opt lazy val labelUseCountMap: Option[Map[String, Int]] = _labelUseCountMap() def labelUseCount(label: String): Int = labelUseCountMap.map(_.getOrElse(label, 0)).getOrElse(-1) + + override def toString: String = holder.toString(index) } object FlowAnalyzer { diff --git a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala index 356d1fba..5126d96e 100644 --- a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala @@ -40,6 +40,7 @@ case class CpuImportance(a: Importance = UnknownImportance, ixl: Importance = UnknownImportance, iyh: Importance = UnknownImportance, iyl: Importance = UnknownImportance, + memIx: Map[Int, Importance] = Map(), zf: Importance = UnknownImportance, nf: Importance = UnknownImportance, cf: Importance = UnknownImportance, @@ -47,7 +48,10 @@ case class CpuImportance(a: Importance = UnknownImportance, pf: Importance = UnknownImportance, hf: Importance = UnknownImportance ) { - override def toString: String = s"A=$a,B=$b,C=$c,D=$d,E=$e,H=$h,L=$l,IX=$ixh$ixl,Y=$iyh$iyl; Z=$zf,C=$cf,N=$nf,S=$sf,P=$pf,H=$hf" + override def toString: String = { + val memRepr = if (memIx.isEmpty) "" else (0 to memIx.keys.max).map(i => memIx.getOrElse(i, UnknownImportance)).mkString("") + s"A=$a,B=$b,C=$c,D=$d,E=$e,H=$h,L=$l,IX=$ixh$ixl,Y=$iyh$iyl; Z=$zf,C=$cf,N=$nf,S=$sf,P=$pf,H=$hf; M=" ++ memRepr.padTo(4, ' ') + } def ~(that: CpuImportance) = new CpuImportance( a = this.a ~ that.a, @@ -61,6 +65,7 @@ case class CpuImportance(a: Importance = UnknownImportance, ixl = this.ixl ~ that.ixl, iyh = this.iyh ~ that.iyh, iyl = this.iyl ~ that.iyl, + memIx = (this.memIx.keySet | that.memIx.keySet).map(k => k -> (this.memIx.getOrElse(k, UnknownImportance) ~ that.memIx.getOrElse(k, UnknownImportance))).toMap, zf = this.zf ~ that.zf, nf = this.nf ~ that.nf, cf = this.cf ~ that.cf, @@ -68,7 +73,7 @@ case class CpuImportance(a: Importance = UnknownImportance, hf = this.hf ~ that.hf, ) - def getRegister(register: ZRegister.Value): Importance = register match { + def getRegister(register: ZRegister.Value, offset: Int = -1): Importance = register match { case ZRegister.A => a case ZRegister.B => b case ZRegister.C => c @@ -80,6 +85,7 @@ case class CpuImportance(a: Importance = UnknownImportance, case ZRegister.IXL => ixl case ZRegister.IYH => iyh case ZRegister.IYL => iyl + case ZRegister.MEM_IX_D => if (offset < 0) ??? else memIx.getOrElse(offset, UnknownImportance) case ZRegister.HL => h ~ l case ZRegister.BC => b ~ c case ZRegister.DE => d ~ e @@ -96,7 +102,7 @@ case class CpuImportance(a: Importance = UnknownImportance, case ZFlag.N => nf } - def butReadsRegister(r: ZRegister.Value) = r match { + def butReadsRegister(r: ZRegister.Value, offset: Int = -1): CpuImportance = r match { case ZRegister.A => this.copy(a = Important) case ZRegister.AF => this.copy(a = Important, zf = Important, pf = Important, hf = Important, cf = Important, sf = Important) case ZRegister.B => this.copy(b = Important) @@ -112,12 +118,13 @@ case class CpuImportance(a: Importance = UnknownImportance, case ZRegister.IXL => this.copy(ixl = Important) case ZRegister.IYH => this.copy(iyh = Important) case ZRegister.IYL => this.copy(iyl = Important) - case ZRegister.IX | ZRegister.MEM_IX_D => this.copy(ixh = Important, ixl = Important) + case ZRegister.IX => this.copy(ixh = Important, ixl = Important) + case ZRegister.MEM_IX_D => this.copy(ixh = Important, ixl = Important, memIx = if (offset < 0) memIx.mapValues(_ => Important) else memIx + (offset -> Important)) case ZRegister.IY | ZRegister.MEM_IY_D => this.copy(iyh = Important, iyl = Important) case _ => this } - def butWritesRegister(r: ZRegister.Value): CpuImportance = r match { + def butWritesRegister(r: ZRegister.Value, offset: Int = -1): CpuImportance = r match { case ZRegister.A => this.copy(a = Unimportant) case ZRegister.AF => this.copy(a = Unimportant, zf = Unimportant, pf = Unimportant, hf = Unimportant, cf = Unimportant, sf = Unimportant, nf = Unimportant) case ZRegister.B => this.copy(b = Unimportant) @@ -136,8 +143,8 @@ case class CpuImportance(a: Importance = UnknownImportance, case ZRegister.IXL => this.copy(ixl = Unimportant) case ZRegister.IYH => this.copy(iyh = Unimportant) case ZRegister.IYL => this.copy(iyl = Unimportant) - case ZRegister.IX => this.copy(ixh = Unimportant, ixl = Unimportant) - case ZRegister.MEM_IX_D => this.copy(ixh = Important, ixl = Important) + case ZRegister.IX => this.copy(ixh = Unimportant, ixl = Unimportant, memIx = memIx.mapValues(_ => Unimportant)) + case ZRegister.MEM_IX_D => this.copy(ixh = Important, ixl = Important, memIx = if (offset < 0) Map() else memIx + (offset -> Unimportant)) case ZRegister.IY => this.copy(iyh = Important, iyl = Important) case ZRegister.MEM_IY_D => this.copy(iyh = Important, iyl = Important) case _ => this @@ -208,22 +215,38 @@ object ReverseFlowAnalyzer { currentImportance = currentImportance.copy(a = Unimportant) case ZLine(DISCARD_F, _, _, _) => currentImportance = currentImportance.copy(cf = Unimportant, zf= Unimportant, sf = Unimportant , pf = Unimportant, hf = Unimportant) + case ZLine(LD, TwoRegistersOffset(t, s, o), _, _) => + currentImportance = currentImportance.butWritesRegister(t, o).butReadsRegister(s, o) case ZLine(LD | LD_16, TwoRegisters(t, s), _, _) => currentImportance = currentImportance.butWritesRegister(t).butReadsRegister(s) case ZLine(ADD_16, TwoRegisters(t, s), _, _) => currentImportance = currentImportance.butReadsRegister(t).butReadsRegister(s) + case ZLine(XOR, OneRegister(ZRegister.A), _, _) => currentImportance = currentImportance.butWritesRegister(ZRegister.A) case ZLine(OR | AND, OneRegister(ZRegister.A), _, _) => currentImportance = currentImportance.butReadsRegister(ZRegister.A) + case ZLine(AND | ADD | SUB | OR | XOR | CP, OneRegister(s), _, _) => currentImportance = currentImportance.butReadsRegister(ZRegister.A).butReadsRegister(s) case ZLine(ADC | SBC, OneRegister(s), _, _) => currentImportance = currentImportance.butReadsRegister(ZRegister.A).butReadsRegister(s).butReadsFlag(ZFlag.C) - case ZLine(DAA, _, _, _) => - currentImportance = currentImportance.butReadsRegister(ZRegister.A).butReadsFlag(ZFlag.H) + + case ZLine(AND | ADD | SUB | OR | XOR | CP, OneRegisterOffset(s, o), _, _) => + currentImportance = currentImportance.butReadsRegister(ZRegister.A).butReadsRegister(s, o) + case ZLine(ADC | SBC, OneRegisterOffset(s, o), _, _) => + currentImportance = currentImportance.butReadsRegister(ZRegister.A).butReadsRegister(s, o).butReadsFlag(ZFlag.C) + case ZLine(INC | DEC | INC_16 | DEC_16, OneRegister(s), _, _) => currentImportance = currentImportance.butReadsRegister(s) + case ZLine(INC | DEC | INC_16 | DEC_16, OneRegisterOffset(s, o), _, _) => + currentImportance = currentImportance.butReadsRegister(s, o) + case ZLine(POP, OneRegister(r), _, _) => + currentImportance = currentImportance.butWritesRegister(r) + case ZLine(PUSH, OneRegister(r), _, _) => + currentImportance = currentImportance.butReadsRegister(r) + case ZLine(CALL, NoRegisters, _, _) => + currentImportance = finalImportance.copy(memIx = currentImportance.memIx) case _ => currentImportance = finalImportance // TODO } diff --git a/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala index 3f06122a..3f768200 100644 --- a/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/z80/opt/RuleBasedAssemblyOptimization.scala @@ -1,8 +1,8 @@ package millfork.assembly.z80.opt import millfork.CompilationOptions -import millfork.assembly.{z80, _} -import millfork.assembly.opt.SingleStatus +import millfork.assembly._ +import millfork.assembly.opt.{AnyStatus, SingleStatus} import millfork.assembly.z80._ import millfork.env._ import millfork.error.ErrorReporting @@ -437,6 +437,30 @@ case class MatchRegister(register: ZRegister.Value, i: Int) extends AssemblyLine } } +case class MatchValueAtIxOffset(offset: Int, i: Int) extends AssemblyLinePattern { + override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = + FlowInfoRequirement.assertForward(needsFlowInfo) + + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = + flowInfo.statusBefore.memIx.getOrElse(offset, AnyStatus) match { + case SingleStatus(value) => ctx.addObject(i, value) + case _ => false + } +} + +case class MatchValueAtMatchedIxOffset(oi: Int, vi: Int) extends AssemblyLinePattern { + override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = + FlowInfoRequirement.assertForward(needsFlowInfo) + + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = { + val offset = ctx.get[Int](oi) + flowInfo.statusBefore.memIx.getOrElse(offset, AnyStatus) match { + case SingleStatus(value) => ctx.addObject(vi, value) + case _ => false + } + } +} + case class MatchImmediate(i: Int) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = line.registers match { @@ -445,6 +469,37 @@ case class MatchImmediate(i: Int) extends AssemblyLinePattern { } } +case class RegisterAndOffset(register: ZRegister.Value, offset: Int) + +case class MatchSourceRegisterAndOffset(i: Int) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = + line.registers match { + case TwoRegisters(_, s) => ctx.addObject(i, RegisterAndOffset(s, 0)) + case TwoRegistersOffset(_, s, o) => ctx.addObject(i, RegisterAndOffset(s, o)) + case _ => false + } +} + +case class MatchTargetRegisterAndOffset(i: Int) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = + line.registers match { + case TwoRegisters(t, _) => ctx.addObject(i, RegisterAndOffset(t, 0)) + case TwoRegistersOffset(t, _, o) => ctx.addObject(i, RegisterAndOffset(t, o)) + case _ => false + } +} + +case class DoesntChangeMatchedRegisterAndOffset(i: Int) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = { + val ro = ctx.get[RegisterAndOffset](i) + import ZRegister._ + ro.register match { + case AF | SP => false // ? + case MEM_ABS_8 | MEM_ABS_16 | MEM_HL | MEM_DE | MEM_BC => !line.changesMemory + case _ => !line.changesRegisterAndOffset(ro.register, ro.offset) + } + } +} case class MatchParameter(i: Int) extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = @@ -619,27 +674,87 @@ case class Is16BitLoad(target:ZRegister.Value, source: ZRegister.Value) extends case class IsRegular8BitLoadFrom(source: ZRegister.Value) extends TrivialAssemblyLinePattern { override def apply(line: ZLine): Boolean = - line.opcode == ZOpcode.LD && line.registers.asInstanceOf[TwoRegisters].source == source && (line.registers.asInstanceOf[TwoRegisters].target match { - case ZRegister.I | ZRegister.MEM_ABS_8 | ZRegister.R | ZRegister.MEM_DE | ZRegister.MEM_BC => false - case _ => true + line.opcode == ZOpcode.LD && (line.registers match { + case TwoRegistersOffset(ZRegister.I | ZRegister.MEM_ABS_8 | ZRegister.R | ZRegister.MEM_DE | ZRegister.MEM_BC, _, _) => false + case TwoRegisters(ZRegister.I | ZRegister.MEM_ABS_8 | ZRegister.R | ZRegister.MEM_DE | ZRegister.MEM_BC, _) => false + case TwoRegistersOffset(t, s, _) => s == source + case TwoRegisters(t, s) => s == source }) override def toString: String = "LD _," + source } +object NoOffset extends TrivialAssemblyLinePattern { + override def apply(line: ZLine): Boolean = + line.registers match { + case _:TwoRegistersOffset => false + case _:OneRegisterOffset => false + case _ => true + } +} + case class Is8BitLoadTo(target: ZRegister.Value) extends TrivialAssemblyLinePattern { override def apply(line: ZLine): Boolean = - line.opcode == ZOpcode.LD && line.registers.asInstanceOf[TwoRegisters].target == target && (line.registers.asInstanceOf[TwoRegisters].source match { - case ZRegister.I | ZRegister.MEM_ABS_8 | ZRegister.R => false - case _ => true + line.opcode == ZOpcode.LD && (line.registers match { + case TwoRegistersOffset(t, s, _) => target == t + case TwoRegisters(t, s) => target == t && (s match { + case ZRegister.I | ZRegister.MEM_ABS_8 | ZRegister.R => false + case _ => true + }) }) override def toString: String = s"LD $target,_" } +case class MatchSourceIxOffsetOf8BitLoad(i: Int) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = { + line.opcode == ZOpcode.LD && (line.registers match { + case TwoRegistersOffset(_, ZRegister.MEM_IX_D, offset) => ctx.addObject(i, offset) + case _ => false + }) + } + + override def toString: String = "LD (IX,!),_" +} + +case class MatchIxOffset(i: Int) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = { + line.registers match { + case OneRegisterOffset(ZRegister.MEM_IX_D, offset) => ctx.addObject(i, offset) + case _ => false + } + } + + override def toString: String = "LD (IX,!),_" +} + +case class MatchTargetIxOffsetOf8BitLoad(i: Int) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = { + line.opcode == ZOpcode.LD && (line.registers match { + case TwoRegistersOffset(ZRegister.MEM_IX_D, _, offset) => ctx.addObject(i, offset) + case _ => false + }) + } + + override def toString: String = "LD (IX,!),_" +} + +case class MatchUnimportantIxOffset(i: Int) extends AssemblyLinePattern { + override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = + FlowInfoRequirement.assertBackward(needsFlowInfo) + + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean = { + val offset = ctx.get[Int](i) + flowInfo.importanceAfter.memIx.getOrElse(offset, Unimportant) == Unimportant + } +} + case class Is16BitLoadTo(target: ZRegister.Value) extends TrivialAssemblyLinePattern { override def apply(line: ZLine): Boolean = - line.opcode == ZOpcode.LD_16 && line.registers.asInstanceOf[TwoRegisters].target == target + line.opcode == ZOpcode.LD_16 && (line.registers match { + case TwoRegistersOffset(t, s, _) => target == t + case TwoRegisters(t, s) => target == t + }) override def toString: String = s"LD_16 $target,_" } diff --git a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala index cb741c9b..f0d286ae 100644 --- a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala @@ -19,13 +19,13 @@ class AbstractExpressionCompiler[T <: AbstractCode] { } def callingContext(ctx: CompilationContext, v: MemoryVariable): CompilationContext = { - val result = new Environment(Some(ctx.env), "") + val result = new Environment(Some(ctx.env), "", ctx.options.platform.cpuFamily) result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, register = false, initialValue = None, address = None, bank = v.declaredBank), ctx.options) ctx.copy(env = result) } def getParamMaxSize(ctx: CompilationContext, params: List[Expression]): Int = { - params.map { case expr => getExpressionType(ctx, expr).size}.max + params.map(expr => getExpressionType(ctx, expr).size).max } def getSumSize(ctx: CompilationContext, params: List[(Boolean, Expression)]): Int = { diff --git a/src/main/scala/millfork/compiler/CompilationContext.scala b/src/main/scala/millfork/compiler/CompilationContext.scala index 6ecfb2cd..0e227890 100644 --- a/src/main/scala/millfork/compiler/CompilationContext.scala +++ b/src/main/scala/millfork/compiler/CompilationContext.scala @@ -15,7 +15,7 @@ case class CompilationContext(env: Environment, breakLabels: Map[String, Label] = Map(), continueLabels: Map[String, Label] = Map()){ def withInlinedEnv(environment: Environment, newLabel: String): CompilationContext = { - val newEnv = new Environment(Some(env), newLabel) + val newEnv = new Environment(Some(env), newLabel, environment.cpuFamily) newEnv.things ++= environment.things copy(env = newEnv) } diff --git a/src/main/scala/millfork/compiler/mos/MosCompiler.scala b/src/main/scala/millfork/compiler/mos/MosCompiler.scala index f14a9350..fd232c13 100644 --- a/src/main/scala/millfork/compiler/mos/MosCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosCompiler.scala @@ -22,6 +22,7 @@ object MosCompiler extends AbstractCompiler[AssemblyLine] { override def compile(ctx: CompilationContext): List[AssemblyLine] = { ctx.env.nameCheck(ctx.function.code) val chunk = MosStatementCompiler.compile(ctx, ctx.function.code) + val zpRegisterSize = ctx.options.zpRegisterSize val storeParamsFromRegisters = ctx.function.params match { case NormalParamSignature(List(param@MemoryVariable(_, typ, _))) if typ.size == 1 => @@ -29,31 +30,42 @@ object MosCompiler extends AbstractCompiler[AssemblyLine] { case _ => Nil } val phReg = - if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (zpRegisterSize > 0) { val reg = ctx.env.get[VariableInMemory]("__reg") - List( - AssemblyLine.zeropage(LDA, reg), - AssemblyLine.implied(PHA), - AssemblyLine.zeropage(LDA, reg, 1), - AssemblyLine.implied(PHA) - ) + (0 until zpRegisterSize).flatMap { i => + List( + AssemblyLine.zeropage(LDA, reg, i), + AssemblyLine.implied(PHA)) + }.toList } else Nil val prefix = storeParamsFromRegisters ++ (if (ctx.function.interrupt) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { - if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { - List( + if (zpRegisterSize > 0) { + val physicalRegisters = List( AssemblyLine.implied(PHB), AssemblyLine.implied(PHD), AssemblyLine.immediate(REP, 0x30), AssemblyLine.implied(PHA_W), AssemblyLine.implied(PHX_W), AssemblyLine.implied(PHY_W), - AssemblyLine.implied(PHY_W), - AssemblyLine.zeropage(LDA_W, ctx.env.get[VariableInMemory]("__reg")), - AssemblyLine.implied(PHA_W), - AssemblyLine.immediate(SEP, 0x30)) + AssemblyLine.implied(PHY_W)) + val reg = ctx.env.get[VariableInMemory]("__reg") + val initialBytes = (0 to zpRegisterSize.&(0xfe).-(2) by 2).flatMap{ i=> + List( + AssemblyLine.zeropage(LDA_W, reg, i), + AssemblyLine.implied(PHA_W)) + } + val lastByte = if (zpRegisterSize % 2 != 0){ + List( + AssemblyLine.immediate(SEP, 0x30), + AssemblyLine.zeropage(LDA, reg, zpRegisterSize - 1), + AssemblyLine.implied(PHA)) + } else { + List(AssemblyLine.immediate(SEP, 0x30)) + } + physicalRegisters ++ initialBytes ++ lastByte } else { List( AssemblyLine.implied(PHB), @@ -93,13 +105,22 @@ object MosCompiler extends AbstractCompiler[AssemblyLine] { AssemblyLine.implied(PHA), AssemblyLine.implied(CLD)) ++ phReg } - } else if (ctx.function.kernalInterrupt && ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + } else if (ctx.function.kernalInterrupt && zpRegisterSize > 0) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { - List( - AssemblyLine.accu16, - AssemblyLine.zeropage(LDA_W, ctx.env.get[VariableInMemory]("__reg")), - AssemblyLine.implied(PHA_W), - AssemblyLine.accu8) + val reg = ctx.env.get[VariableInMemory]("__reg") + val initialBytes = (0 to zpRegisterSize.&(0xfe).-(2) by 2).flatMap { i => + List( + AssemblyLine.zeropage(LDA_W, reg, i), + AssemblyLine.implied(PHA_W)) + }.toList + val lastByte = if (zpRegisterSize % 2 != 0) { + List(AssemblyLine.accu8, + AssemblyLine.zeropage(LDA_W, reg, zpRegisterSize - 1), + AssemblyLine.implied(PHA_W)) + } else { + List(AssemblyLine.accu8) + } + AssemblyLine.accu16 :: (initialBytes ++ lastByte) } else phReg } else Nil) ++ stackPointerFixAtBeginning(ctx) val label = AssemblyLine.label(Label(ctx.function.name)).copy(elidable = false) diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index e76cd169..eaca4368 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -136,7 +136,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { def prepareWordIndexing(ctx: CompilationContext, pointy: Pointy, indexExpression: Expression): List[AssemblyLine] = { val w = ctx.env.get[Type]("word") - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("16-bit indexing requires a zeropage pseudoregister") return Nil } diff --git a/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala b/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala index e3dc9cdd..e98e086c 100644 --- a/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala @@ -37,32 +37,44 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { val m = ctx.function val b = env.get[Type]("byte") val w = env.get[Type]("word") - val plReg = - if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + val zpRegisterSize = ctx.options.zpRegisterSize + lazy val plReg = + if (zpRegisterSize > 0) { val reg = env.get[VariableInMemory]("__reg") - List( - AssemblyLine.implied(PLA), - AssemblyLine.zeropage(STA, reg, 1), - AssemblyLine.implied(PLA), - AssemblyLine.zeropage(STA, reg) - ) + (zpRegisterSize.-(1) to 0 by (-1)).flatMap{ i=> + List( + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg,i)) + }.toList } else Nil val someRegisterA = Some(b, RegisterVariable(MosRegister.A, b)) val someRegisterAX = Some(w, RegisterVariable(MosRegister.AX, w)) val someRegisterYA = Some(w, RegisterVariable(MosRegister.YA, w)) - val returnInstructions = if (m.interrupt) { + lazy val returnInstructions = if (m.interrupt) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { - if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { - List( - AssemblyLine.immediate(REP, 0x30), - AssemblyLine.implied(PLA_W), - AssemblyLine.zeropage(STA_W, env.get[VariableInMemory]("__reg")), - AssemblyLine.implied(PLY), - AssemblyLine.implied(PLX), - AssemblyLine.implied(PLA), - AssemblyLine.implied(PLD), - AssemblyLine.implied(PLB), - AssemblyLine.implied(RTI)) + if (zpRegisterSize > 0) { + val reg = env.get[VariableInMemory]("__reg") + val lastByte = if (zpRegisterSize % 2 != 0) { + List( + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, zpRegisterSize - 1), + AssemblyLine.immediate(REP, 0x30)) + } else { + List(AssemblyLine.immediate(REP, 0x30)) + } + val remainingBytes = (zpRegisterSize.&(0xfe).-(2) to 0 by (-2)).flatMap { i => + List( + AssemblyLine.implied(PLA_W), + AssemblyLine.zeropage(STA_W, reg, i)) + } + lastByte ++ remainingBytes ++ + List( + AssemblyLine.implied(PLY), + AssemblyLine.implied(PLX), + AssemblyLine.implied(PLA), + AssemblyLine.implied(PLD), + AssemblyLine.implied(PLB), + AssemblyLine.implied(RTI)) } else { List( AssemblyLine.immediate(REP, 0x30), @@ -105,13 +117,24 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { AssemblyLine.implied(RTI)) } } else { - (if (m.kernalInterrupt && ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + (if (m.kernalInterrupt && zpRegisterSize > 0) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { - List( - AssemblyLine.accu16, - AssemblyLine.implied(PLA_W), - AssemblyLine.zeropage(STA_W, env.get[VariableInMemory]("__reg")), - AssemblyLine.accu8) + val reg = env.get[VariableInMemory]("__reg") + val lastByte = if (zpRegisterSize % 2 != 0) { + List( + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, zpRegisterSize - 1), + AssemblyLine.accu16) + } else { + List(AssemblyLine.accu16) + } + val remainingBytes = (zpRegisterSize.&(0xfe).-(2) to 0 by (-2)).flatMap { i => + List( + AssemblyLine.implied(PLA_W), + AssemblyLine.zeropage(STA_W, reg, i), + AssemblyLine.accu8) + } + lastByte ++ remainingBytes } else plReg } else Nil) ++ (if (m.isFar(ctx.options)) { List(AssemblyLine.implied(RTL)) diff --git a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala index d7506fcc..6cc502ce 100644 --- a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala +++ b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala @@ -38,7 +38,7 @@ object PseudoregisterBuiltIns { } } } - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("Word addition or subtraction requires the zeropage pseudoregister", params.headOption.flatMap(_._2.position)) return Nil } @@ -57,7 +57,7 @@ object PseudoregisterBuiltIns { } def addToReg(ctx: CompilationContext, r: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = { - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("Word addition or subtraction requires the zeropage pseudoregister", r.position) return Nil } @@ -108,7 +108,7 @@ object PseudoregisterBuiltIns { def compileWordBitOpsToAX(ctx: CompilationContext, params: List[Expression], op: Opcode.Value): List[AssemblyLine] = { - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("Word bit operation requires the zeropage pseudoregister", params.headOption.flatMap(_.position)) return Nil } @@ -126,7 +126,7 @@ object PseudoregisterBuiltIns { } def bitOpReg(ctx: CompilationContext, r: Expression, op: Opcode.Value): List[AssemblyLine] = { - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("Word bit operation requires the zeropage pseudoregister", r.position) return Nil } @@ -180,7 +180,7 @@ object PseudoregisterBuiltIns { } def compileWordShiftOps(left: Boolean, ctx: CompilationContext, l: Expression, r: Expression): List[AssemblyLine] = { - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("Word shifting requires the zeropage pseudoregister", l.position) return Nil } @@ -246,7 +246,7 @@ object PseudoregisterBuiltIns { } def compileByteMultiplication(ctx: CompilationContext, param1OrRegister: Option[Expression], param2: Expression, storeInRegLo: Boolean): List[AssemblyLine] = { - if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.zpRegisterSize < 2) { ErrorReporting.error("Variable byte multiplication requires the zeropage pseudoregister", param1OrRegister.flatMap(_.position)) return Nil } diff --git a/src/main/scala/millfork/compiler/z80/Z80Compiler.scala b/src/main/scala/millfork/compiler/z80/Z80Compiler.scala index 48ae6404..d1b73586 100644 --- a/src/main/scala/millfork/compiler/z80/Z80Compiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80Compiler.scala @@ -2,9 +2,12 @@ package millfork.compiler.z80 import java.util.concurrent.atomic.AtomicLong +import millfork.CompilationFlag import millfork.assembly.z80.ZLine import millfork.compiler.{AbstractCompiler, CompilationContext} import millfork.env.Label +import millfork.error.ErrorReporting +import millfork.node.ZRegister /** * @author Karol Stasiak @@ -20,6 +23,26 @@ object Z80Compiler extends AbstractCompiler[ZLine] { val chunk = Z80StatementCompiler.compile(ctx, ctx.function.code) val label = ZLine.label(Label(ctx.function.name)).copy(elidable = false) val prefix = Nil // TODO - label :: (prefix ++ chunk) + label :: (prefix ++ stackPointerFixAtBeginning(ctx) ++ chunk) + } + + def stackPointerFixAtBeginning(ctx: CompilationContext): List[ZLine] = { + val m = ctx.function + if (m.stackVariablesSize == 0) return Nil + if (!ctx.options.flags(CompilationFlag.EmitZ80Opcodes)) { + ErrorReporting.error(s"Target CPU does not support stack variables", m.position) + return Nil + } + if (m.stackVariablesSize > 127) { + ErrorReporting.error(s"Function ${m.name} has too many local stack variables", m.position) + return Nil + } + import millfork.assembly.z80.ZOpcode._ + import ZRegister._ + List( + ZLine.register(PUSH, IX), + ZLine.ldImm16(IX, 0x10000 - ctx.function.stackVariablesSize.&(1).+(ctx.function.stackVariablesSize)), + ZLine.registers(ADD_16, IX, SP), + ZLine.ld16(SP, IX)) } } diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index d0e4c6a2..393adb0b 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -8,8 +8,6 @@ import millfork.node.{ZRegister, _} import millfork.assembly.z80.ZOpcode._ import millfork.error.ErrorReporting -import scala.collection.GenTraversableOnce - /** * @author Karol Stasiak */ @@ -142,6 +140,17 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } case _ => ??? } + case v: StackVariable => + v.typ.size match { + case 0 => ??? + case 1 => loadByteViaIX(v.baseOffset, target) + case 2 => target match { + case ZExpressionTarget.NOTHING => Nil + case ZExpressionTarget.HL => + List(ZLine.ldViaIx(ZRegister.L, v.baseOffset), ZLine.ldViaIx(ZRegister.H, v.baseOffset + 1)) + } + case _ => ??? + } } case i: IndexedExpression => calculateAddressToHL(ctx, i) match { @@ -316,13 +325,13 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, ADD) - case _ => ZBuiltIns.performLongInPlace(ctx, l, r, ADD, ADC, size, decimal = false) + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, ADD, ADC, size) } case "-=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, SUB) - case _ => ZBuiltIns.performLongInPlace(ctx, l, r, SUB, SBC, size, decimal = false) + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, SUB, SBC, size) } case "+'=" => val (l, r, size) = assertAssignmentLike(ctx, params) @@ -369,19 +378,19 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, AND) - case _ => ZBuiltIns.performLongInPlace(ctx, l, r, AND, AND, size, decimal = false) + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, AND, AND, size) } case "^=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, XOR) - case _ => ZBuiltIns.performLongInPlace(ctx, l, r, XOR, XOR, size, decimal = false) + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, XOR, XOR, size) } case "|=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, OR) - case _ => ZBuiltIns.performLongInPlace(ctx, l, r, OR, OR, size, decimal = false) + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, OR, OR, size) } case _ => env.maybeGet[Type](f.functionName) match { @@ -509,6 +518,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } } + def loadByteViaIX(offset: Int, target: ZExpressionTarget.Value): List[ZLine] = { + target match { + case ZExpressionTarget.NOTHING => Nil + case ZExpressionTarget.A => List(ZLine.ldViaIx(ZRegister.A, offset)) + case ZExpressionTarget.HL => List(ZLine.ldViaIx(ZRegister.A, offset), ZLine.ld8(ZRegister.L, ZRegister.A), ZLine.ldImm8(ZRegister.H, 0)) + } + } + def loadByteViaHL(target: ZExpressionTarget.Value): List[ZLine] = { target match { case ZExpressionTarget.NOTHING => Nil @@ -534,6 +551,23 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { prepareA ++ fillUpperBytes } + def signExtendViaIX(targetOffset: Int, hiRegister: ZRegister.Value, bytes: Int, signedSource: Boolean): List[ZLine] = { + if (bytes == 0) return Nil + val prepareA = if (signedSource) { + val prefix = if (hiRegister == ZRegister.A) Nil else List(ZLine.ld8(ZRegister.A, hiRegister)) + val label = Z80Compiler.nextLabel("sx") + prefix ++ List( + ZLine.imm8(OR, 0x7f), + ZLine.jump(label, IfFlagSet(ZFlag.S)), + ZLine.ldImm8(ZRegister.A, 0), + ZLine.label(label)) + } else { + List(ZLine.ldImm8(ZRegister.A, 0)) + } + val fillUpperBytes = List.tabulate(bytes)(i => ZLine.ldViaIx(targetOffset + i, ZRegister.A)) + prepareA ++ fillUpperBytes + } + def storeA(targetAddr: Constant, targetSize: Int, signedSource: Boolean): List[ZLine] = { targetSize match { case 0 => Nil @@ -542,6 +576,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } } + def storeAViaIX(targetOffset: Int, targetSize: Int, signedSource: Boolean): List[ZLine] = { + targetSize match { + case 0 => Nil + case 1 => List(ZLine.ldViaIx(targetOffset, ZRegister.A)) + case n => ZLine.ldViaIx(targetOffset, ZRegister.A) :: signExtendViaIX(targetOffset + 1, ZRegister.A, n - 1, signedSource) + } + } + def storeHL(targetAddr: Constant, targetSize: Int, signedSource: Boolean): List[ZLine] = { // TODO: LD (nnnn),HL compatibility? targetSize match { @@ -552,12 +594,23 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } } + def storeHLViaIX(offset: Int, targetSize: Int, signedSource: Boolean): List[ZLine] = { + // TODO: LD (nnnn),HL compatibility? + targetSize match { + case 0 => Nil + case 1 => List(ZLine.ldViaIx(offset, ZRegister.L)) + case 2 => List(ZLine.ldViaIx(offset, ZRegister.L), ZLine.ldViaIx(offset + 1, ZRegister.H)) + case n => List(ZLine.ldViaIx(offset, ZRegister.L), ZLine.ldViaIx(offset + 1, ZRegister.H)) ++ signExtendViaIX(offset + 2, ZRegister.H, n - 2, signedSource) + } + } + def storeA(ctx: CompilationContext, target: LhsExpression, signedSource: Boolean): List[ZLine] = { val env = ctx.env target match { case VariableExpression(vname) => env.get[Variable](vname) match { case v: VariableInMemory => storeA(v.toAddress, v.typ.size, signedSource) + case v: StackVariable => storeAViaIX(v.baseOffset, v.typ.size, signedSource) } case i:IndexedExpression => calculateAddressToHL(ctx, i) match { @@ -577,6 +630,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case VariableExpression(vname) => env.get[Variable](vname) match { case v: VariableInMemory => storeHL(v.toAddress, v.typ.size, signedSource) + case v: StackVariable => storeHLViaIX(v.baseOffset, v.typ.size, signedSource) } case IndexedExpression(pointyName, indexExpr) => env.getPointy(pointyName) match { diff --git a/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala index de8591e1..53323371 100644 --- a/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala @@ -21,7 +21,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] { val options = ctx.options statement match { case ReturnStatement(None) => - ctx.function.returnType match { + fixStackOnReturn(ctx) ++ (ctx.function.returnType match { case _: BooleanType => List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) case t => t.size match { @@ -34,29 +34,31 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] { ErrorReporting.warn("Returning without a value", options, statement.position) List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) } - } + }) case ReturnStatement(Some(e)) => ctx.function.returnType match { case t: BooleanType => t.size match { case 0 => ErrorReporting.error("Cannot return anything from a void function", statement.position) - List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) + fixStackOnReturn(ctx) ++ + List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) case 1 => - Z80ExpressionCompiler.compileToA(ctx, e) ++ + Z80ExpressionCompiler.compileToA(ctx, e) ++ fixStackOnReturn(ctx) ++ List(ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) case 2 => - Z80ExpressionCompiler.compileToHL(ctx, e) ++ + Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++ List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) } case t => t.size match { case 0 => ErrorReporting.error("Cannot return anything from a void function", statement.position) - List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) + fixStackOnReturn(ctx) ++ + List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) case 1 => - Z80ExpressionCompiler.compileToA(ctx, e) ++ + Z80ExpressionCompiler.compileToA(ctx, e) ++ fixStackOnReturn(ctx) ++ List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) case 2 => - Z80ExpressionCompiler.compileToHL(ctx, e) ++ + Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++ List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET)) } } @@ -150,6 +152,26 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] { } } + private def fixStackOnReturn(ctx: CompilationContext): List[ZLine] = { + if (ctx.function.stackVariablesSize > 0) { + import ZRegister._ + val localVariableArea = ctx.function.stackVariablesSize.&(1).+(ctx.function.stackVariablesSize) + if (ctx.function.returnType.size == 2) { + List( + ZLine.ldImm16(IX, localVariableArea), + ZLine.registers(ADD_16, IX, SP), + ZLine.ld16(SP, IX), + ZLine.register(POP, IX)) + } else { + List( + ZLine.ldImm16(HL, localVariableArea), + ZLine.registers(ADD_16, HL, SP), + ZLine.ld16(SP, HL), + ZLine.register(POP, IX)) + } + } else Nil + } + def labelChunk(labelName: String) = List(ZLine.label(Label(labelName))) def jmpChunk(label: Label) = List(ZLine.jump(label)) diff --git a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala index e375ae7b..d197f343 100644 --- a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala +++ b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala @@ -1,6 +1,6 @@ package millfork.compiler.z80 -import millfork.assembly.z80.{TwoRegisters, ZLine, ZOpcode} +import millfork.assembly.z80._ import millfork.compiler.CompilationContext import millfork.node._ import ZOpcode._ @@ -247,13 +247,14 @@ object ZBuiltIns { } def perform8BitInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, opcode: ZOpcode.Value, decimal: Boolean = false): List[ZLine] = { - val calculateAddress = lhs match { + val (calculateAddress, lv):(List[ZLine], LocalVariableAddressOperand) = lhs match { case VariableExpression(name) => ctx.env.get[Variable](name) match { - case v: VariableInMemory => List(ZLine.ldImm16(ZRegister.HL, v.toAddress)) + case v: VariableInMemory => List(ZLine.ldImm16(ZRegister.HL, v.toAddress)) -> LocalVariableAddressViaHL + case v: StackVariable => Nil -> LocalVariableAddressViaIX(v.baseOffset) case _ => ??? } - case i: IndexedExpression => Z80ExpressionCompiler.calculateAddressToHL(ctx, i) + case i: IndexedExpression => Z80ExpressionCompiler.calculateAddressToHL(ctx, i) -> LocalVariableAddressViaHL } val constantRight = ctx.env.eval(rhs) val calculateChange = Z80ExpressionCompiler.compileToA(ctx, rhs) @@ -266,45 +267,45 @@ object ZBuiltIns { opcode match { case ADD if decimal => setup ++ List( - ZLine.register(ADD, ZRegister.MEM_HL), + ZLine.register(ADD, lv), ZLine.implied(DAA), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.ld8(lv, ZRegister.A)) case ADD if !decimal => constantRight match { case Some(NumericConstant(1, _)) => - calculateAddress :+ ZLine.register(INC, ZRegister.MEM_HL) + calculateAddress :+ ZLine.register(INC, lv) case Some(NumericConstant(0xff | -1, _)) => - calculateAddress :+ ZLine.register(DEC, ZRegister.MEM_HL) + calculateAddress :+ ZLine.register(DEC, lv) case _ => setup ++ List( - ZLine.register(ADD, ZRegister.MEM_HL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.register(ADD, lv), + ZLine.ld8(lv, ZRegister.A)) } case SUB if decimal => setup ++ List( ZLine.ld8(ZRegister.E, ZRegister.A), - ZLine.ld8(ZRegister.A, ZRegister.MEM_HL), + ZLine.ld8(ZRegister.A, lv), ZLine.register(SUB, ZRegister.E), ZLine.implied(DAA), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.ld8(lv, ZRegister.A)) case SUB if !decimal=> constantRight match { case Some(NumericConstant(1, _)) => - calculateAddress :+ ZLine.register(DEC, ZRegister.MEM_HL) + calculateAddress :+ ZLine.register(DEC, lv) case Some(NumericConstant(0xff | -1, _)) => - calculateAddress :+ ZLine.register(INC, ZRegister.MEM_HL) + calculateAddress :+ ZLine.register(INC, lv) case _ => if (ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)) { setup ++ List( ZLine.implied(NEG), - ZLine.register(ADD, ZRegister.MEM_HL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.register(ADD, lv), + ZLine.ld8(lv, ZRegister.A)) } else { setup ++ List( ZLine.implied(CPL), ZLine.register(INC, ZRegister.A), - ZLine.register(SUB, ZRegister.MEM_HL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.register(SUB, lv), + ZLine.ld8(lv, ZRegister.A)) } } case XOR => @@ -313,35 +314,35 @@ object ZBuiltIns { calculateAddress case Some(NumericConstant(0xff | -1, _)) => calculateAddress ++ List( - ZLine.ld8(ZRegister.A, ZRegister.MEM_HL), + ZLine.ld8(ZRegister.A, lv), ZLine.implied(CPL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.ld8(lv, ZRegister.A)) case _ => setup ++ List( - ZLine.register(XOR, ZRegister.MEM_HL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.register(XOR, lv), + ZLine.ld8(lv, ZRegister.A)) } case OR => constantRight match { case Some(NumericConstant(0, _)) => calculateAddress case Some(NumericConstant(0xff | -1, _)) => - calculateAddress :+ ZLine.ldImm8(ZRegister.MEM_HL, 0xff) + calculateAddress :+ ZLine.ldImm8(lv, 0xff) case _ => setup ++ List( - ZLine.register(OR, ZRegister.MEM_HL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.register(OR, lv), + ZLine.ld8(lv, ZRegister.A)) } case AND => constantRight match { case Some(NumericConstant(0, _)) => - calculateAddress :+ ZLine.ldImm8(ZRegister.MEM_HL, 0) + calculateAddress :+ ZLine.ldImm8(lv, 0) case Some(NumericConstant(0xff | -1, _)) => calculateAddress case _ => setup ++ List( - ZLine.register(AND, ZRegister.MEM_HL), - ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) + ZLine.register(AND, lv), + ZLine.ld8(lv, ZRegister.A)) } } } diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index e7077cc0..e14cd2a9 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -98,7 +98,7 @@ case class UnexpandedConstant(name: String, requiredSize: Int) extends Constant case class NumericConstant(value: Long, requiredSize: Int) extends Constant { if (requiredSize == 1) { if (value < -128 || value > 255) { - throw new IllegalArgumentException("The constant is too big") + throw new IllegalArgumentException(s"The constant $value is too big") } } override def isProvablyZero: Boolean = value == 0 diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 2c65065c..5de8bd46 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -17,10 +17,13 @@ import scala.collection.mutable * @author Karol Stasiak */ //noinspection NotImplementedCode -class Environment(val parent: Option[Environment], val prefix: String) { +class Environment(val parent: Option[Environment], val prefix: String, val cpuFamily: CpuFamily.Value) { - private var baseStackOffset = 0x101 + private var baseStackOffset: Int = cpuFamily match { + case CpuFamily.M6502 => 0x101 + case CpuFamily.I80 => 0 + } def genRelativeVariable(constant: Constant, typ: Type, zeropage: Boolean): RelativeVariable = { val variable = RelativeVariable(".rv__" + Environment.relVarId.incrementAndGet().formatted("%06d"), constant, typ, zeropage = zeropage, declaredBank = None /*TODO*/) @@ -37,7 +40,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { m.environment.getAllPrefixedThings case _ => Map[String, Thing]() }.fold(things.toMap)(_ ++ _) - val e = new Environment(None, "") + val e = new Environment(None, "", cpuFamily) e.things.clear() e.things ++= allThings e @@ -588,7 +591,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { ErrorReporting.error(s"Non-macro function `$name` cannot have inlinable parameters", stmt.position) } - val env = new Environment(Some(this), name + "$") + val env = new Environment(Some(this), name + "$", cpuFamily) stmt.params.foreach(p => env.registerParameter(p, options)) val params = if (stmt.assembly) { AssemblyParamSignature(stmt.params.map { @@ -963,7 +966,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } else { val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({ val alloc = - if (typ.name == "pointer") VariableAllocationMethod.Zeropage + if (typ.name == "pointer" || typ.name == "__reg$type") VariableAllocationMethod.Zeropage else if (stmt.global) VariableAllocationMethod.Static else if (stmt.register) VariableAllocationMethod.Register else VariableAllocationMethod.Auto @@ -1061,11 +1064,12 @@ class Environment(val parent: Option[Environment], val prefix: String) { case a: ArrayDeclarationStatement => registerArray(a, options) case i: ImportStatement => () } - if (options.flag(CompilationFlag.ZeropagePseudoregister) && !things.contains("__reg")) { + if (options.zpRegisterSize > 0 && !things.contains("__reg")) { + addThing(BasicPlainType("__reg$type", options.zpRegisterSize), None) registerVariable(VariableDeclarationStatement( name = "__reg", bank = None, - typ = "pointer", + typ = "__reg$type", global = true, stack = false, constant = false, diff --git a/src/main/scala/millfork/node/opt/UnusedFunctions.scala b/src/main/scala/millfork/node/opt/UnusedFunctions.scala index 5321f81d..5baefa64 100644 --- a/src/main/scala/millfork/node/opt/UnusedFunctions.scala +++ b/src/main/scala/millfork/node/opt/UnusedFunctions.scala @@ -10,6 +10,10 @@ import millfork.node._ */ object UnusedFunctions extends NodeOptimization { + private val operatorImplementations: List[(String, Int, String)] = List( + ("*", 2, "__mul_u8u8u8") + ) + override def optimize(nodes: List[Node], options: CompilationOptions): List[Node] = { val panicRequired = options.flags(CompilationFlag.CheckIndexOutOfBounds) val allNormalFunctions = nodes.flatMap { @@ -18,8 +22,10 @@ object UnusedFunctions extends NodeOptimization { }.toSet val allCalledFunctions = getAllCalledFunctions(nodes).toSet var unusedFunctions = allNormalFunctions -- allCalledFunctions - if (allCalledFunctions.contains("*") && options.flag(CompilationFlag.ZeropagePseudoregister)) { - unusedFunctions -= "__mul_u8u8u8" + for((op, zp, fun) <- operatorImplementations) { + if (allCalledFunctions.contains(op) && options.zpRegisterSize >= zp) { + unusedFunctions -= fun + } } if (unusedFunctions.nonEmpty) { ErrorReporting.debug("Removing unused functions: " + unusedFunctions.mkString(", ")) diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index 1a126e8f..af7d5d6b 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -35,18 +35,9 @@ class Z80Assembler(program: Program, case ZRegister.SP => 3 } - private def internalArithmeticIndex(opcode: ZOpcode.Value): Int = { - import ZOpcode._ - opcode match { - case ADD => 0x80 - case ADC => 0x88 - case SUB => 0x90 - case SBC => 0x98 - case AND => 0xa0 - case XOR => 0xa8 - case OR => 0xb0 - case CP => 0xb8 - } + private def prefixByte(reg: ZRegister.Value): Int = reg match { + case ZRegister.IX | ZRegister.MEM_IX_D => 0xdd + case ZRegister.IY | ZRegister.MEM_IY_D => 0xfd } override def emitInstruction(bank: String, options: CompilationOptions, index: Int, instr: ZLine): Int = { @@ -77,17 +68,31 @@ class Z80Assembler(program: Program, writeByte(bank, index, 0xed) writeByte(bank, index + 1, edImplieds(op)) index + 2 - case ZLine(ADD_16, TwoRegisters(ZRegister.HL, source), param, _) => + case ZLine(ADD_16, TwoRegisters(ZRegister.HL, source), _, _) => writeByte(bank, index, 9 + 16 * internalRegisterIndex(source)) index + 1 + case ZLine(ADD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), source), _, _) => + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, 9 + 16 * internalRegisterIndex(source)) + index + 2 case ZLine(SBC_16, TwoRegisters(ZRegister.HL, reg), _, _) => writeByte(bank, index, 0xed) writeByte(bank, index + 1, 0x42 + 0x10 * internalRegisterIndex(reg)) index + 2 + case ZLine(LD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), ZRegister.IMM_16), param, _) => + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, 0x21) + writeWord(bank, index + 2, param) + index + 4 case ZLine(LD_16, TwoRegisters(target, ZRegister.IMM_16), param, _) => writeByte(bank, index, 1 + 16 * internalRegisterIndex(target)) writeWord(bank, index + 1, param) index + 3 + case ZLine(LD_16, TwoRegisters(ix@(ZRegister.IX | ZRegister.IY), ZRegister.MEM_ABS_16), param, _) => + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, 0x2a) + writeWord(bank, index + 2, param) + index + 4 case ZLine(LD_16, TwoRegisters(ZRegister.HL, ZRegister.MEM_ABS_16), param, _) => writeByte(bank, index, 0x2a) writeWord(bank, index + 1, param) @@ -96,32 +101,27 @@ class Z80Assembler(program: Program, writeByte(bank, index, 0x22) writeWord(bank, index + 1, param) index + 3 + case ZLine(LD_16, TwoRegisters(ZRegister.SP, ZRegister.HL), _, _) => + writeByte(bank, index, 0xF9) + index + 1 + case ZLine(LD_16, TwoRegisters(ZRegister.SP, ix@(ZRegister.IX | ZRegister.IY)), _, _) => + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, 0xF9) + index + 2 case ZLine(op, OneRegister(ZRegister.IMM_8), param, _) if immediates.contains(op) => val o = immediates(op) writeByte(bank, index, o) writeByte(bank, index + 1, param) index + 2 - case ZLine(op, OneRegister(ZRegister.MEM_IX_D), _, _) if oneRegister.contains(op) => + case ZLine(op, OneRegisterOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset), _, _) if oneRegister.contains(op) => val o = oneRegister(op) - writeByte(bank, index, 0xdd) + writeByte(bank, index, prefixByte(ix)) writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.MEM_HL) * o.multiplier) - writeByte(bank, index + 2, instr.parameter) + writeByte(bank, index + 2, offset) index + 3 - case ZLine(op, OneRegister(ZRegister.MEM_IY_D), _, _) if oneRegister.contains(op) => + case ZLine(op, OneRegister(ix@(ZRegister.IX | ZRegister.IY)), _, _) if oneRegister.contains(op) => val o = oneRegister(op) - writeByte(bank, index, 0xfd) - writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.MEM_HL) * o.multiplier) - writeByte(bank, index + 2, instr.parameter) - index + 3 - case ZLine(op, OneRegister(ZRegister.IX), _, _) if oneRegister.contains(op) => - val o = oneRegister(op) - writeByte(bank, index, 0xdd) - writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.HL) * o.multiplier) - writeByte(bank, index + 2, instr.parameter) - index + 3 - case ZLine(op, OneRegister(ZRegister.IY), _, _) if oneRegister.contains(op) => - val o = oneRegister(op) - writeByte(bank, index, 0xfd) + writeByte(bank, index, prefixByte(ix)) writeByte(bank, index + 1, o.opcode + internalRegisterIndex(ZRegister.HL) * o.multiplier) writeByte(bank, index + 2, instr.parameter) index + 3 @@ -140,6 +140,12 @@ class Z80Assembler(program: Program, writeByte(bank, index, 6 + 8 * internalRegisterIndex(reg)) writeByte(bank, index + 1, instr.parameter) index + 2 + case TwoRegistersOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), ZRegister.IMM_8, offset) => + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, 0x36) + writeByte(bank, index + 2, offset) + writeByte(bank, index + 3, instr.parameter) + index + 4 case TwoRegisters(ZRegister.A, ZRegister.MEM_ABS_8) => writeByte(bank, index, 0x3a) writeWord(bank, index + 1, instr.parameter) @@ -160,25 +166,15 @@ class Z80Assembler(program: Program, case TwoRegisters(ZRegister.A, ZRegister.MEM_DE) => writeByte(bank, index, 0x1a) index + 1 - case TwoRegisters(ZRegister.MEM_IX_D, source) => - writeByte(bank, index, 0xdd) + case TwoRegistersOffset(ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), source, offset) => + writeByte(bank, index, prefixByte(ix)) writeByte(bank, index + 1, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(ZRegister.MEM_HL) * 8) - writeByte(bank, index + 1, instr.parameter) + writeByte(bank, index + 2, offset) index + 3 - case TwoRegisters(ZRegister.MEM_IY_D, source) => - writeByte(bank, index, 0xfd) - writeByte(bank, index + 1, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(ZRegister.MEM_HL) * 8) - writeByte(bank, index + 1, instr.parameter) - index + 3 - case TwoRegisters(target, ZRegister.MEM_IX_D) => - writeByte(bank, index, 0xdd) - writeByte(bank, index, 0x40 + internalRegisterIndex(ZRegister.MEM_HL) + internalRegisterIndex(target) * 8) - writeByte(bank, index + 1, instr.parameter) - index + 3 - case TwoRegisters(target, ZRegister.MEM_IY_D) => - writeByte(bank, index, 0xfd) - writeByte(bank, index, 0x40 + internalRegisterIndex(ZRegister.MEM_HL) + internalRegisterIndex(target) * 8) - writeByte(bank, index + 1, instr.parameter) + case TwoRegistersOffset(target, ix@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), offset) => + writeByte(bank, index, prefixByte(ix)) + writeByte(bank, index + 1, 0x40 + internalRegisterIndex(ZRegister.MEM_HL) + internalRegisterIndex(target) * 8) + writeByte(bank, index + 2, offset) index + 3 case TwoRegisters(target, source) => writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8) @@ -267,29 +263,29 @@ class Z80Assembler(program: Program, writeWord(bank, index + 1, param) index + 3 - case ZLine(RET, IfFlagClear(ZFlag.Z), param, _) => + case ZLine(RET, IfFlagClear(ZFlag.Z), _, _) => writeByte(bank, index, 0xc0) index + 1 - case ZLine(RET, IfFlagClear(ZFlag.C), param, _) => + case ZLine(RET, IfFlagClear(ZFlag.C), _, _) => writeByte(bank, index, 0xd0) index + 1 - case ZLine(RET, IfFlagClear(ZFlag.P), param, _) => + case ZLine(RET, IfFlagClear(ZFlag.P), _, _) => writeByte(bank, index, 0xe0) index + 1 - case ZLine(RET, IfFlagClear(ZFlag.S), param, _) => + case ZLine(RET, IfFlagClear(ZFlag.S), _, _) => writeByte(bank, index, 0xf0) index + 1 - case ZLine(RET, IfFlagSet(ZFlag.Z), param, _) => + case ZLine(RET, IfFlagSet(ZFlag.Z), _, _) => writeByte(bank, index, 0xc8) index + 1 - case ZLine(RET, IfFlagSet(ZFlag.C), param, _) => + case ZLine(RET, IfFlagSet(ZFlag.C), _, _) => writeByte(bank, index, 0xd8) index + 1 - case ZLine(RET, IfFlagSet(ZFlag.P), param, _) => + case ZLine(RET, IfFlagSet(ZFlag.P), _, _) => writeByte(bank, index, 0xe8) index + 1 - case ZLine(RET, IfFlagSet(ZFlag.S), param, _) => + case ZLine(RET, IfFlagSet(ZFlag.S), _, _) => writeByte(bank, index, 0xf8) index + 1 diff --git a/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala b/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala index 22296d8f..3ea0868e 100644 --- a/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala +++ b/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala @@ -1,6 +1,6 @@ package millfork.parser -import millfork.{CompilationFlag, CompilationOptions} +import millfork.CompilationOptions import millfork.assembly.mos.AssemblyLine /** @@ -13,7 +13,7 @@ class MosSourceLoadingQueue(initialFilenames: List[String], override def createParser(filename: String, src: String, parentDir: String): MfParser[AssemblyLine] = MosParser(filename, src, parentDir, options) def enqueueStandardModules(): Unit = { - if (options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (options.zpRegisterSize > 0) { moduleQueue.enqueue(() => parseModule("zp_reg", includePath, Left(None))) } } diff --git a/src/test/scala/millfork/test/StackVarSuite.scala b/src/test/scala/millfork/test/StackVarSuite.scala index 0ad2569c..b2670382 100644 --- a/src/test/scala/millfork/test/StackVarSuite.scala +++ b/src/test/scala/millfork/test/StackVarSuite.scala @@ -1,6 +1,7 @@ package millfork.test -import millfork.test.emu.{EmuBenchmarkRun, EmuCmosBenchmarkRun} +import millfork.Cpu +import millfork.test.emu.{EmuCmosBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuZ80BenchmarkRun} import org.scalatest.{FunSuite, Matchers} /** @@ -9,7 +10,7 @@ import org.scalatest.{FunSuite, Matchers} class StackVarSuite extends FunSuite with Matchers { test("Basic stack assignment") { - EmuCmosBenchmarkRun(""" + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(""" | byte output @$c000 | void main () { | stack byte a @@ -23,7 +24,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Stack byte addition") { - EmuCmosBenchmarkRun(""" + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(""" | byte output @$c000 | void main () { | stack byte a @@ -40,7 +41,7 @@ class StackVarSuite extends FunSuite with Matchers { """.stripMargin)(_.readWord(0xc000) should equal(0x77)) } - test("Complex expressions involving stack variables") { + test("Complex expressions involving stack variables (6502)") { EmuCmosBenchmarkRun(""" | byte output @$c000 | void main () { @@ -54,6 +55,20 @@ class StackVarSuite extends FunSuite with Matchers { """.stripMargin)(_.readWord(0xc000) should equal(21)) } + test("Complex expressions involving stack variables (Z80)") { + EmuZ80BenchmarkRun(""" + | byte output @$c000 + | void main () { + | stack byte a + | a = 7 + | output = f(a) + f(a) + f(a) + | } + | asm byte f(byte a) { + | ret + | } + """.stripMargin)(_.readWord(0xc000) should equal(21)) + } + // ERROR: (8:9) Right-hand-side expression is too complex // test("Stack byte subtraction") { // EmuUnoptimizedRun(""" @@ -74,7 +89,7 @@ class StackVarSuite extends FunSuite with Matchers { // } test("Stack word addition") { - EmuCmosBenchmarkRun(""" + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(""" | word output @$c000 | void main () { | stack word a @@ -92,7 +107,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Recursion") { - EmuCmosBenchmarkRun(""" + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(""" | array output [6] @$c000 | byte fails @$c010 | void main () { @@ -129,7 +144,7 @@ class StackVarSuite extends FunSuite with Matchers { test("Indexing") { - EmuCmosBenchmarkRun(""" + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(""" | array output [200] @$c000 | void main () { | stack byte a diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala index 37b392a2..9633f4b4 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala @@ -11,6 +11,8 @@ object EmuOptimizedCmosRun extends EmuRun( OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + OptimizationPresets.Good ++ CmosOptimizations.All ++ OptimizationPresets.Good ++ CmosOptimizations.All ++ OptimizationPresets.Good ++ ZeropageRegisterOptimizations.All ++ diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala index 068e77f8..4dde2c22 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala @@ -11,6 +11,8 @@ object EmuOptimizedInlinedRun extends EmuRun( OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + OptimizationPresets.Good ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ ZeropageRegisterOptimizations.All ++ diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala index b8db7e2c..f6d8b7db 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala @@ -12,6 +12,8 @@ object EmuOptimizedRun extends EmuRun( OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + OptimizationPresets.Good ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ ZeropageRegisterOptimizations.All ++ diff --git a/src/test/scala/millfork/test/emu/EmuPlatform.scala b/src/test/scala/millfork/test/emu/EmuPlatform.scala index eb368798..a1a7a813 100644 --- a/src/test/scala/millfork/test/emu/EmuPlatform.scala +++ b/src/test/scala/millfork/test/emu/EmuPlatform.scala @@ -18,6 +18,7 @@ object EmuPlatform { Map("default" -> new VariableAllocator( if (CpuFamily.forType(cpu) == CpuFamily.M6502) pointers else Nil, new AfterCodeByteAllocator(0xff00))), + if (CpuFamily.forType(cpu) == CpuFamily.M6502) 2 else 0, pointers, ".bin", false, diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 83117c7f..bd34326f 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -17,7 +17,7 @@ import millfork.node.StandardCallGraph import millfork.node.opt.NodeOptimization import millfork.output.{MemoryBank, MosAssembler} import millfork.parser.MosParser -import millfork.{CompilationFlag, CompilationOptions} +import millfork.{CompilationFlag, CompilationOptions, CpuFamily} import org.scalatest.Matchers import scala.collection.JavaConverters._ @@ -104,7 +104,6 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], CompilationFlag.InlineFunctions -> this.inline, CompilationFlag.InterproceduralOptimization -> true, CompilationFlag.CompactReturnDispatchParams -> true, - CompilationFlag.ZeropagePseudoregister -> true, CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu), CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen), CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02), @@ -112,12 +111,11 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], CompilationFlag.OptimizeForSpeed -> blastProcessing, CompilationFlag.OptimizeForSonicSpeed -> blastProcessing // CompilationFlag.CheckIndexOutOfBounds -> true, - ), None) + ), None, 2) ErrorReporting.hasErrors = false ErrorReporting.verbosity = 999 var effectiveSource = source if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}" - if (!source.contains("__reg")) effectiveSource += "\n pointer __reg" if (source.contains("import zp_reg")) effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "") val parserF = MosParser("", effectiveSource, "", options) @@ -129,7 +127,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], // prepare val program = nodeOptimizations.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt, options)) val callGraph = new StandardCallGraph(program) - val env = new Environment(None, "") + val env = new Environment(None, "", CpuFamily.M6502) env.collectDeclarations(program, options) val hasOptimizations = assemblyOptimizations.nonEmpty @@ -149,7 +147,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], // compile - val env2 = new Environment(None, "") + val env2 = new Environment(None, "", CpuFamily.M6502) env2.collectDeclarations(program, options) val assembler = new MosAssembler(program, env2, platform) val output = assembler.assemble(callGraph, assemblyOptimizations, options) diff --git a/src/test/scala/millfork/test/emu/EmuZ80Run.scala b/src/test/scala/millfork/test/emu/EmuZ80Run.scala index 7eef1c0e..e25e45ca 100644 --- a/src/test/scala/millfork/test/emu/EmuZ80Run.scala +++ b/src/test/scala/millfork/test/emu/EmuZ80Run.scala @@ -12,7 +12,7 @@ import millfork.node.StandardCallGraph import millfork.node.opt.NodeOptimization import millfork.output.{MemoryBank, MosAssembler, Z80Assembler} import millfork.parser.Z80Parser -import millfork.CompilationOptions +import millfork.{CompilationOptions, CpuFamily} import millfork.compiler.z80.Z80Compiler import org.scalatest.Matchers @@ -21,8 +21,6 @@ import org.scalatest.Matchers */ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], assemblyOptimizations: List[AssemblyOptimization[ZLine]]) extends Matchers { - private val variableLength = Set(0x10, 0x30, 0x50, 0x70, 0x90, 0xb0, 0xd0, 0xf0) - private val TooManyCycles: Long = 1000000 def apply2(source: String): (Timings, MemoryBank) = { @@ -30,7 +28,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio Console.err.flush() println(source) val platform = EmuPlatform.get(cpu) - val options = CompilationOptions(platform, millfork.Cpu.defaultFlags(cpu).map(_ -> true).toMap, None) + val options = CompilationOptions(platform, millfork.Cpu.defaultFlags(cpu).map(_ -> true).toMap, None, 0) ErrorReporting.hasErrors = false ErrorReporting.verbosity = 999 var effectiveSource = source @@ -44,7 +42,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio // prepare val program = nodeOptimizations.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt, options)) val callGraph = new StandardCallGraph(program) - val env = new Environment(None, "") + val env = new Environment(None, "", CpuFamily.I80) env.collectDeclarations(program, options) val hasOptimizations = assemblyOptimizations.nonEmpty @@ -64,7 +62,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio // compile - val env2 = new Environment(None, "") + val env2 = new Environment(None, "", CpuFamily.I80) env2.collectDeclarations(program, options) val assembler = new Z80Assembler(program, env2, platform) val output = assembler.assemble(callGraph, assemblyOptimizations, options)