diff --git a/docs/abi/variable-storage.md b/docs/abi/variable-storage.md index 47db5506..607e0b98 100644 --- a/docs/abi/variable-storage.md +++ b/docs/abi/variable-storage.md @@ -50,7 +50,11 @@ The implementation depends on the target architecture: * on 6502, the stack pointer is transferred into the X register and used as a base -* on 65818, the native stack-based addressing mode is used, similar to 6502, just without clobbering X +* on 65816, the native stack-based addressing mode is used, similar to 6502, just without clobbering X + +* alternatively, on both 6502 and 65816, you can use a software-based stack for stack variables: + it's implemented as a separate 256-byte memory area plus a single byte for the stack pointer, + exclusively for local stack variables; using it can help against stack overflows * on 8080 and LR35902, the address is calculated from the stackpointer into the HL register pair diff --git a/docs/api/command-line.md b/docs/api/command-line.md index ef6b0d25..7832eebc 100644 --- a/docs/api/command-line.md +++ b/docs/api/command-line.md @@ -126,6 +126,10 @@ Allow using the IX register for other purposes. Allow using the IY register for other purposes. `.ini` equivalent: `iy_scratch`. Default: no. +* `-fsoftware-stack`, `-fno-software-stack` – +Use a software stack for stack variables. +`.ini` equivalent: `software_stack`. Default: no. + ## Optimization options * `-O0` – Disable all optimizations. diff --git a/docs/api/custom-platform.md b/docs/api/custom-platform.md index 959afcf8..c47c5b79 100644 --- a/docs/api/custom-platform.md +++ b/docs/api/custom-platform.md @@ -80,6 +80,8 @@ Default: the same as `encoding`. * `iy_scratch` – allow using the IY register for other purposes, default is `false` + * `software_stach` – use software stack for stack variables, default is `false` + * `output_intel_syntax` – use Intel syntax instead of Zilog syntax, default is `true` for Intel 8080 and `false` otherwise diff --git a/docs/lang/preprocessor.md b/docs/lang/preprocessor.md index 35c211f8..de6da597 100644 --- a/docs/lang/preprocessor.md +++ b/docs/lang/preprocessor.md @@ -60,6 +60,8 @@ The following features are defined based on the chosen CPU and compilation optio * `USES_SHADOW_REGISTERS` – 1 if interrupts preserve old registers in the shadow registers, 0 otherwise +* `USES_SOFTWARE_STACK` – 1 if using software stack for variables, 0 otherwise + ### Commonly used features * `WIDESCREEN` – 1 if the horizontal screen resolution, ignoring borders, is greater than 256, 0 otherwise diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index 5b8482cb..5857500a 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -34,7 +34,7 @@ case class CompilationOptions(platform: Platform, if (CpuFamily.forType(platform.cpu) != CpuFamily.M6502) invalids ++= Set( EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, - PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, LUnixRelocatableCode, RorWarning) + PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, LUnixRelocatableCode, RorWarning, SoftwareStack) if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set( EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitIntel8080Opcodes, EmitEZ80Opcodes, @@ -189,6 +189,7 @@ case class CompilationOptions(platform: Platform, "USES_ZPREG" -> toLong(platform.cpuFamily == CpuFamily.M6502 && zpRegisterSize > 0), "USES_IX_STACK" -> toLong(flag(CompilationFlag.UseIxForStack)), "USES_IY_STACK" -> toLong(flag(CompilationFlag.UseIyForStack)), + "USES_SOFTWARE_STACK" -> toLong(flag(CompilationFlag.SoftwareStack)), "USES_SHADOW_REGISTERS" -> toLong(flag(CompilationFlag.UseShadowRegistersForInterrupts)), "ZPREG_SIZE" -> (if (platform.cpuFamily == CpuFamily.M6502) zpRegisterSize.toLong else 0) ) @@ -306,7 +307,7 @@ object CompilationFlag extends Enumeration { EmitIllegals, DecimalMode, ReadOnlyArrays, LenientTextEncoding, LineNumbersInAssembly, // compilation options for MOS: EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, - PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, + PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack, // compilation options for I80 EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, UseShadowRegistersForInterrupts, @@ -347,6 +348,7 @@ object CompilationFlag extends Enumeration { "iy_stack" -> UseIyForStack, "ix_scratch" -> UseIxForScratch, "iy_scratch" -> UseIyForScratch, + "software_stack" -> SoftwareStack, "use_shadow_registers_for_irq" -> UseShadowRegistersForInterrupts, "output_intel_syntax" -> UseIntelSyntaxForOutput, "input_intel_syntax" -> UseIntelSyntaxForInput, diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 450debbc..4a2c98fb 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -403,6 +403,9 @@ object Main { flag("-fno-use-index-for-stack").action { c => c.changeFlag(CompilationFlag.UseIyForStack, false).changeFlag(CompilationFlag.UseIxForStack, false) }.description("Don't use either IX or IY as base pointer for stack variables (Z80 only)") + boolean("-fsoftware-stack", "-fno-software-stack").action { (c, v) => + c.changeFlag(CompilationFlag.SoftwareStack, v) + }.description("Use software stack for stack variables (6502 only)") fluff("", "Optimization options:", "") diff --git a/src/main/scala/millfork/assembly/mos/AssemblyLine.scala b/src/main/scala/millfork/assembly/mos/AssemblyLine.scala index 66f769c8..44a75b38 100644 --- a/src/main/scala/millfork/assembly/mos/AssemblyLine.scala +++ b/src/main/scala/millfork/assembly/mos/AssemblyLine.scala @@ -421,11 +421,7 @@ object AssemblyLine { List(AssemblyLine.zeropage(opcode, v.toAddress + offset)) case v: VariableInMemory => List(AssemblyLine.absolute(opcode, v.toAddress + offset)) case v: StackVariable => - if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { - List(AssemblyLine.stackRelative(opcode, v.baseOffset + offset + ctx.extraStackOffset)) - } else { - List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(opcode, v.baseOffset + offset + ctx.extraStackOffset)) - } + AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, opcode, v, offset) } } @@ -460,6 +456,35 @@ object AssemblyLine { def absoluteX(opcode: Opcode.Value, addr: Int) = AssemblyLine(opcode, AddrMode.AbsoluteX, NumericConstant(addr, 2)) + def dataStackX(ctx: CompilationContext, opcode: Opcode.Value, v: StackVariable): AssemblyLine = + dataStackX(ctx, opcode, v.baseOffset) + + def dataStackX(ctx: CompilationContext, opcode: Opcode.Value, v: StackVariable, offset: Int): AssemblyLine = + dataStackX(ctx, opcode, v.baseOffset + offset) + + def dataStackX(ctx: CompilationContext, opcode: Opcode.Value, offset: Int): AssemblyLine = + if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + val stack = ctx.env.get[ThingInMemory]("__stack") + if (offset == 0x108) { + println() + } + AssemblyLine.absoluteX(opcode, stack.toAddress + (offset - 0x100)) + } else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { + AssemblyLine.stackRelative(opcode, offset + ctx.extraStackOffset) + } else { + AssemblyLine.absoluteX(opcode, offset + ctx.extraStackOffset) + } + + def tsx(ctx: CompilationContext): List[AssemblyLine] = + if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + val sp = ctx.env.get[ThingInMemory]("__sp") + List(AssemblyLine.absolute(LDX, sp)) + } else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { + Nil + } else { + List(AssemblyLine.implied(TSX)) + } + def absoluteX(opcode: Opcode.Value, addr: Constant) = AssemblyLine(opcode, AddrMode.AbsoluteX, addr) diff --git a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala index a7694121..b570cb61 100644 --- a/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/AlwaysGoodOptimizations.scala @@ -306,7 +306,7 @@ object AlwaysGoodOptimizations { (HasOpcode(PHA)) ~ (Linear & Not(ChangesA) & Not(ChangesStack) & Not(ChangesMemory)).* ~ - (Elidable & XContainsStackPointer & HasOpcode(STA) & HasAddrMode(AbsoluteX) & HasParameterWhere(p => p.quickSimplify match { + (Elidable & XContainsHardwareStackPointer & HasOpcode(STA) & HasAddrMode(AbsoluteX) & HasParameterWhere(p => p.quickSimplify match { case NumericConstant(n, _) => n == 0x101 case _ => false })) ~~> (_.init), @@ -423,14 +423,14 @@ object AlwaysGoodOptimizations { (HasOpcodeIn(LDX, TAX, TSX, INX, DEX) & Elidable) ~ (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_XF))).* ~ HasOpcode(DISCARD_XF) ~~> (_.tail), (HasOpcodeIn(LDY, TAY, INY, DEY) & Elidable) ~ (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & Not(HasOpcode(DISCARD_YF))).* ~ HasOpcode(DISCARD_YF) ~~> (_.tail), (HasOpcode(LDX) & Elidable & MatchAddrMode(3) & MatchParameter(4)) ~ - (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & DoesntChangeMemoryAt(3, 4) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~ + (LinearOrLabel & Not(ConcernsX) & Not(ReadsNOrZ) & DoesntChangeMemoryAtAssumingNonchangingIndices(3, 4) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~ (HasOpcode(TXA) & Elidable) ~ ((LinearOrLabel & Not(ConcernsX) & Not(HasOpcode(DISCARD_XF))).* ~ HasOpcode(DISCARD_XF)).capture(2) ~~> { (c, ctx) => ctx.get[List[AssemblyLine]](1) ++ (c.head.copy(opcode = LDA) :: ctx.get[List[AssemblyLine]](2)) }, (HasOpcode(LDY) & Elidable & MatchAddrMode(3) & MatchParameter(4)) ~ - (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & DoesntChangeMemoryAt(3, 4) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~ + (LinearOrLabel & Not(ConcernsY) & Not(ReadsNOrZ) & DoesntChangeMemoryAtAssumingNonchangingIndices(3, 4) & DoesntChangeIndexingInAddrMode(3)).*.capture(1) ~ (HasOpcode(TYA) & Elidable) ~ ((LinearOrLabel & Not(ConcernsY) & Not(HasOpcode(DISCARD_YF))).* ~ HasOpcode(DISCARD_YF)).capture(2) ~~> { (c, ctx) => @@ -606,19 +606,19 @@ object AlwaysGoodOptimizations { }, (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(PHA)) ~ - (Linear & Not(ConcernsStack) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ConcernsStack) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(PLA)) ~~> { code => code.head :: (code.drop(2).init :+ code.head) }, (Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(PHX)) ~ - (Linear & Not(ConcernsStack) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ConcernsStack) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(PLX)) ~~> { code => code.head :: (code.drop(2).init :+ code.head) }, (Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~ (Elidable & HasOpcode(PHY)) ~ - (Linear & Not(ConcernsStack) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ConcernsStack) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(PLY)) ~~> { code => code.head :: (code.drop(2).init :+ code.head) }, @@ -976,16 +976,16 @@ object AlwaysGoodOptimizations { val LoadingOfJustWrittenValue = jvmFix(new RuleBasedAssemblyOptimization("Loading of just written value", needsFlowInfo = FlowInfoRequirement.BothFlows, - (HasOpcode(STA) & XContainsStackPointer & HasAddrMode(AbsoluteX) & MatchAddrMode(0) & MatchParameter(1) & MatchA(2) & HasParameterWhere(_ match { + (HasOpcode(STA) & XContainsHardwareStackPointer & 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) | + XContainsHardwareStackPointer & HasOpcodeIn(INC, DEC, ASL, LSR) & MatchAddrMode(0) & MatchParameter(1) | + Linear & XContainsHardwareStackPointer & HasAddrMode(AbsoluteX) & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory)) | + Linear & HasAddrMode(AbsoluteX) & Not(MatchParameter(1) & XContainsHardwareStackPointer) & 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) => + (Elidable & XContainsHardwareStackPointer & 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) => @@ -1003,20 +1003,20 @@ object AlwaysGoodOptimizations { (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))).* ~ + Linear & Not(ChangesS) & DoesntChangeMemoryAt(0, 1) & Not(HasAddrMode(Stack)) & Not(MatchAddrMode(0) & MatchParameter(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)) }, (HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1) & MatchA(2)) ~ - (Linear & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (LinearOrBranch & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1) & Not(MatchAddrMode(0) & MatchParameter(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)) }, (HasOpcode(PHA)) ~ (Linear & Not(ChangesA) & Not(ChangesStack) & Not(ChangesMemory)).* ~ - (Elidable & XContainsStackPointer & HasOpcode(LDA) & DoesntMatterWhatItDoesWith(State.N, State.Z) & HasAddrMode(AbsoluteX) & HasParameterWhere(p => p.quickSimplify match { + (Elidable & XContainsHardwareStackPointer & HasOpcode(LDA) & DoesntMatterWhatItDoesWith(State.N, State.Z) & HasAddrMode(AbsoluteX) & HasParameterWhere(p => p.quickSimplify match { case NumericConstant(n, _) => n == 0x101 case _ => false })) ~~> (_.init), @@ -1030,7 +1030,7 @@ object AlwaysGoodOptimizations { (HasOpcode(PHA)) ~ (Linear & Not(ChangesA) & Not(ChangesStack) & Not(ChangesMemory)).* ~ - (Elidable & XContainsStackPointer & HasOpcode(LDA) & HasAddrMode(AbsoluteX) & HasParameterWhere(p => p.quickSimplify match { + (Elidable & XContainsHardwareStackPointer & HasOpcode(LDA) & HasAddrMode(AbsoluteX) & HasParameterWhere(p => p.quickSimplify match { case NumericConstant(n, _) => n == 0x101 case _ => false })) ~~> (code => code.init :+ AssemblyLine.immediate(ORA, 0)), @@ -1040,7 +1040,13 @@ object AlwaysGoodOptimizations { (Elidable & HasOpcode(LDA) & HasAddrMode(Stack) & HasParameterWhere(p => p.quickSimplify match { case NumericConstant(n, _) => n == 1 case _ => false - })) ~~> (code => code.init :+ AssemblyLine.immediate(ORA, 0)) + })) ~~> (code => code.init :+ AssemblyLine.immediate(ORA, 0)), + + (HasOpcode(LDX) & RefersTo("__sp", 0) + & XContainsSoftwareStackPointer & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~~> (_ => Nil), + + (HasOpcodeIn(LAX, LDA) & RefersTo("__sp", 0) + & XContainsSoftwareStackPointer) ~~> (_ => List(AssemblyLine.implied(TXA))), )) val PointlessStackStore = new RuleBasedAssemblyOptimization("Pointless stack store", @@ -1048,13 +1054,13 @@ object AlwaysGoodOptimizations { // TODO: check if JSR is OK - (Elidable & HasOpcode(STA) & HasAddrMode(AbsoluteX) & XContainsStackPointer & HasParameterWhere(_ match { + (Elidable & HasOpcode(STA) & HasAddrMode(AbsoluteX) & XContainsHardwareStackPointer & 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))).* ~ + HasAddrMode(AbsoluteX) & XContainsHardwareStackPointer & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory))).* ~ HasOpcodeIn(RTS, RTI) ~~> (_.tail), (Elidable & HasOpcode(STA) & HasAddrMode(AbsoluteX) & HasParameterWhere(_ match { @@ -1075,14 +1081,14 @@ object AlwaysGoodOptimizations { (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 { + (HasOpcode(STA) & XContainsHardwareStackPointer & 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 & XContainsHardwareStackPointer & 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), + (Elidable & XContainsHardwareStackPointer & HasOpcode(STA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.tail), ) val IdempotentDuplicateRemoval = new RuleBasedAssemblyOptimization("Idempotent duplicate operation", diff --git a/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala index 8845eb3c..3f9ce313 100644 --- a/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/CoarseFlowAnalyzer.scala @@ -35,6 +35,7 @@ object CoarseFlowAnalyzer { while (changed) { changed = false var currentStatus: CpuStatus = functionStartStatus + var staSpWasLast = false for (i <- codeArray.indices) { import millfork.assembly.mos.Opcode._ import millfork.assembly.mos.AddrMode._ @@ -43,6 +44,7 @@ object CoarseFlowAnalyzer { changed = true flagArray(i) = currentStatus } + var staSpIsNow = false codeArray(i) match { case AssemblyLine0(LABEL, _, MemoryAddressConstant(Label(l))) => val L = l @@ -57,6 +59,7 @@ object CoarseFlowAnalyzer { ah = if (niceFunctionProperties(DoesntChangeAH -> th.name)) currentStatus.ah else AnyStatus, x = if (niceFunctionProperties(DoesntChangeX -> th.name)) currentStatus.x else AnyStatus, eqSX = if (niceFunctionProperties(DoesntChangeX -> th.name)) currentStatus.eqSX else false, + eqSpX = if (niceFunctionProperties(DoesntChangeX -> th.name)) currentStatus.eqSpX else false, y = if (niceFunctionProperties(DoesntChangeY -> th.name)) currentStatus.y else AnyStatus, iz = if (niceFunctionProperties(DoesntChangeIZ -> th.name)) currentStatus.iz else AnyStatus, c = if (niceFunctionProperties(DoesntChangeC -> th.name)) currentStatus.c else AnyStatus @@ -65,26 +68,44 @@ object CoarseFlowAnalyzer { case AssemblyLine0(JSR | BYTE, _, _) => currentStatus = initialStatus + case AssemblyLine0(TAX, _, _) if staSpWasLast => + currentStatus = currentStatus.copy( + x = currentStatus.a, + eqSX = false, + eqSpX = true, + n = currentStatus.a.n(), + z = currentStatus.a.z(), + src = SourceOfNZ.AX) + case AssemblyLine0(op, Implied, _) if FlowAnalyzerForImplied.hasDefinition(op) => currentStatus = FlowAnalyzerForImplied.get(op)(currentStatus) case AssemblyLine0(op, Immediate | WordImmediate, NumericConstant(nn, _)) if FlowAnalyzerForImmediate.hasDefinition(op) => currentStatus = FlowAnalyzerForImmediate.get(op)(nn.toInt, currentStatus) + case AssemblyLine0(STA, _, MemoryAddressConstant(th: Thing)) + if th.name == "__sp" => + currentStatus = FlowAnalyzerForTheRest.get(STA)(currentStatus, None, true) + staSpIsNow = true + + case AssemblyLine0(op, _, MemoryAddressConstant(th: Thing)) + if th.name == "__sp" && FlowAnalyzerForTheRest.hasDefinition(op) => + currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, None, true) + case AssemblyLine0(op, _, MemoryAddressConstant(th: Thing)) if th.name == "__reg" && FlowAnalyzerForTheRest.hasDefinition(op) => - currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, Some(0)) + currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, Some(0), false) case AssemblyLine0(op, _, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th: Thing), NumericConstant(n, _))) if th.name == "__reg" && FlowAnalyzerForTheRest.hasDefinition(op) => - currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, Some(n.toInt)) + currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, Some(n.toInt), false) case AssemblyLine0(op, _, _) if FlowAnalyzerForTheRest.hasDefinition(op) => - currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, None) + currentStatus = FlowAnalyzerForTheRest.get(op)(currentStatus, None, false) case AssemblyLine0(opcode, addrMode, _) => currentStatus = currentStatus.copy(src = AnyStatus) - if (OpcodeClasses.ChangesX(opcode)) currentStatus = currentStatus.copy(x = AnyStatus, eqSX = false) + if (OpcodeClasses.ChangesX(opcode)) currentStatus = currentStatus.copy(x = AnyStatus, eqSX = false, eqSpX = false) if (OpcodeClasses.ChangesY(opcode)) currentStatus = currentStatus.copy(y = AnyStatus) if (OpcodeClasses.ChangesAAlways(opcode)) currentStatus = currentStatus.copy(a = AnyStatus, a0 = AnyStatus, a7 = AnyStatus) if (addrMode == Implied && OpcodeClasses.ChangesAIfImplied(opcode)) currentStatus = currentStatus.copy(a = AnyStatus, a0 = AnyStatus, a7 = AnyStatus) @@ -95,6 +116,7 @@ object CoarseFlowAnalyzer { if (OpcodeClasses.ChangesV(opcode)) currentStatus = currentStatus.copy(v = AnyStatus) if (OpcodeClasses.ChangesStack(opcode) || OpcodeClasses.ChangesS(opcode)) currentStatus = currentStatus.copy(eqSX = false) } + staSpWasLast = staSpIsNow } // flagArray.zip(codeArray).foreach{ // case (fl, y) => if (y.isPrintable) println(f"$fl%-32s $y%-32s") diff --git a/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala b/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala index e20a5e43..3208c226 100644 --- a/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala +++ b/src/main/scala/millfork/assembly/mos/opt/CpuStatus.scala @@ -53,6 +53,8 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, r3: Status[Int] = UnknownStatus, src: Status[SourceOfNZ] = UnknownStatus, eqSX: Boolean = false, + eqSpA: Boolean = false, + eqSpX: Boolean = false, z: Status[Boolean] = UnknownStatus, n: Status[Boolean] = UnknownStatus, c: Status[Boolean] = UnknownStatus, @@ -61,6 +63,8 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, m: Status[Boolean] = UnknownStatus, w: Status[Boolean] = UnknownStatus ) { + def overwriteSp(sp: Boolean): CpuStatus = if (sp && eqSpX) this.copy(eqSpX = false) else this + // assert(a ne null) // assert(ah ne null) // assert(x ne null) @@ -96,7 +100,9 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, 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; R0=$r0,R1=$r1,R2=$r2,R3=$r3; A7=$a7,A0=$a0,NZ:$src" + (if (eqSX) "; S=X" - else /* */ " ") + else /*-*/ " ") + + (if (eqSpX) "; SP=X" + else /*--*/ " ") def aw: Status[Int] = (ah, a) match { case (SingleStatus(h), SingleStatus(l)) => SingleStatus(h.&(0xff).<<(8).+(l&0xff)) @@ -123,6 +129,7 @@ case class CpuStatus(a: Status[Int] = UnknownStatus, iz = this.iz ~ that.iz, src = this.src ~ that.src, eqSX = this.eqSX && that.eqSX, + eqSpX = this.eqSpX && that.eqSpX, z = this.z ~ that.z, n = this.n ~ that.n, c = this.c ~ that.c, diff --git a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImmediate.scala b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImmediate.scala index c25d1343..e9f4f09d 100644 --- a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImmediate.scala +++ b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImmediate.scala @@ -15,7 +15,7 @@ object FlowAnalyzerForImmediate { if ((nn & 1) != 0) currentStatus = currentStatus.copy(c = Status.SingleFalse) if ((nn & 2) != 0) currentStatus = currentStatus.copy(z = Status.SingleFalse) if ((nn & 8) != 0) currentStatus = currentStatus.copy(d = Status.SingleFalse) - if ((nn & 0x10) != 0) currentStatus = currentStatus.copy(w = Status.SingleFalse, eqSX = false) + if ((nn & 0x10) != 0) currentStatus = currentStatus.copy(w = Status.SingleFalse, eqSX = false, eqSpX = false) if ((nn & 0x20) != 0) currentStatus = currentStatus.copy(m = Status.SingleFalse) if ((nn & 0x40) != 0) currentStatus = currentStatus.copy(v = Status.SingleFalse) if ((nn & 0x80) != 0) currentStatus = currentStatus.copy(n = Status.SingleFalse) @@ -26,7 +26,7 @@ object FlowAnalyzerForImmediate { if ((nn & 1) != 0) currentStatus = currentStatus.copy(c = Status.SingleTrue) if ((nn & 2) != 0) currentStatus = currentStatus.copy(z = Status.SingleTrue) if ((nn & 8) != 0) currentStatus = currentStatus.copy(d = Status.SingleTrue) - if ((nn & 0x10) != 0) currentStatus = currentStatus.copy(w = Status.SingleTrue, eqSX = false) + if ((nn & 0x10) != 0) currentStatus = currentStatus.copy(w = Status.SingleTrue, eqSX = false, eqSpX = false) if ((nn & 0x20) != 0) currentStatus = currentStatus.copy(m = Status.SingleTrue) if ((nn & 0x40) != 0) currentStatus = currentStatus.copy(v = Status.SingleTrue) if ((nn & 0x80) != 0) currentStatus = currentStatus.copy(n = Status.SingleTrue) @@ -34,7 +34,7 @@ object FlowAnalyzerForImmediate { }, LDX -> {(nn, currentStatus) => val n = nn & 0xff - currentStatus.nz(n).copy(x = SingleStatus(n), src = SourceOfNZ.X, eqSX = false) + currentStatus.nz(n).copy(x = SingleStatus(n), src = SourceOfNZ.X, eqSX = false, eqSpX = false) }, LDY -> {(nn, currentStatus) => val n = nn & 0xff @@ -54,7 +54,7 @@ object FlowAnalyzerForImmediate { }, LDX_W -> {(nn, currentStatus) => val n = nn & 0xff - currentStatus.nzw(nn).copy(x = SingleStatus(n), src = AnyStatus, eqSX = false) + currentStatus.nzw(nn).copy(x = SingleStatus(n), src = AnyStatus, eqSX = false, eqSpX = false) }, LDY_W -> {(nn, currentStatus) => val n = nn & 0xff @@ -260,6 +260,7 @@ object FlowAnalyzerForImmediate { z = newX.z(), c = AnyStatus, eqSX = false, + eqSpX = false, src = SourceOfNZ.X) } ) diff --git a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala index e542953d..88afc35d 100644 --- a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala +++ b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForImplied.scala @@ -15,7 +15,7 @@ object FlowAnalyzerForImplied { RTI -> identity, SEI -> identity, CLI -> identity, - TXS -> (_.copy(eqSX = true)), + TXS -> (_.copy(eqSX = true, eqSpX = false)), PHP -> (_.copy(eqSX = false)), PHA -> (_.copy(eqSX = false)), PHA_W -> (_.copy(eqSX = false)), @@ -33,12 +33,12 @@ object FlowAnalyzerForImplied { SEC -> (_.copy(c = Status.SingleTrue)), CLV -> (_.copy(v = Status.SingleFalse)), CLA -> (currentStatus => currentStatus.copy(a = Status.SingleZero, src = currentStatus.src.butNotA)), - CLX -> (currentStatus => currentStatus.copy(x = Status.SingleZero, src = currentStatus.src.butNotX)), + CLX -> (currentStatus => currentStatus.copy(x = Status.SingleZero, src = currentStatus.src.butNotX, eqSX = false, eqSpX = false)), CLY -> (currentStatus => currentStatus.copy(y = Status.SingleZero, src = currentStatus.src.butNotY)), XCE -> (_.copy(c = AnyStatus, m = AnyStatus, w = AnyStatus, x = AnyStatus, y = AnyStatus, eqSX = false)), PLA -> (_.copy(src = SourceOfNZ.A, a = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), - PLX -> (_.copy(src = SourceOfNZ.X, x = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), - PLX_W -> (_.copy(src = SourceOfNZ.X, x = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), + PLX -> (_.copy(src = SourceOfNZ.X, x = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false, eqSpX = false)), + PLX_W -> (_.copy(src = SourceOfNZ.X, x = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false, eqSpX = false)), PLY -> (_.copy(src = SourceOfNZ.Y, y = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), PLY_W -> (_.copy(src = SourceOfNZ.Y, y = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), PLZ -> (_.copy(src = SourceOfNZ.Z, iz = AnyStatus, n = AnyStatus, z = AnyStatus, eqSX = false)), @@ -57,6 +57,7 @@ object FlowAnalyzerForImplied { z = newX.z(), x = newX, eqSX = false, + eqSpX = false, src = SourceOfNZ.X) }), DEX -> (currentStatus => { @@ -66,6 +67,7 @@ object FlowAnalyzerForImplied { z = newX.z(), x = newX, eqSX = false, + eqSpX = false, src = SourceOfNZ.X) }), INY -> (currentStatus => { @@ -137,6 +139,7 @@ object FlowAnalyzerForImplied { z = newX.z().withHiddenHi, x = newX, eqSX = false, + eqSpX = false, src = AnyStatus) }), DEX_W -> (currentStatus => { @@ -146,6 +149,7 @@ object FlowAnalyzerForImplied { z = newX.z().withHiddenHi, x = newX, eqSX = false, + eqSpX = false, src = AnyStatus) }), INY_W -> (currentStatus => { @@ -192,6 +196,7 @@ object FlowAnalyzerForImplied { currentStatus.copy( x = currentStatus.a, eqSX = false, + eqSpX = false, n = currentStatus.a.n(), z = currentStatus.a.z(), src = SourceOfNZ.AX) @@ -232,6 +237,7 @@ object FlowAnalyzerForImplied { currentStatus.copy( x = currentStatus.y, eqSX = false, + eqSpX = false, n = currentStatus.y.n(), z = currentStatus.y.z(), src = SourceOfNZ.XY) @@ -258,6 +264,7 @@ object FlowAnalyzerForImplied { a0 = currentStatus.x.bit0, a7 = currentStatus.x.bit7, eqSX = false, + eqSpX = false, x = currentStatus.a) }), SAY -> (currentStatus => { @@ -271,6 +278,7 @@ object FlowAnalyzerForImplied { currentStatus.copy( y = currentStatus.x, eqSX = false, + eqSpX = false, x = currentStatus.y) }), ASL -> (currentStatus => { @@ -376,6 +384,7 @@ object FlowAnalyzerForImplied { currentStatus.copy( x = AnyStatus, eqSX = true, + eqSpX = false, src = SourceOfNZ.X) }), ) diff --git a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForTheRest.scala b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForTheRest.scala index a03468cd..fe965bc6 100644 --- a/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForTheRest.scala +++ b/src/main/scala/millfork/assembly/mos/opt/FlowAnalyzerForTheRest.scala @@ -8,35 +8,38 @@ import millfork.assembly.opt.{AnyStatus, SingleStatus, Status} * @author Karol Stasiak */ object FlowAnalyzerForTheRest { - private val map: Map[Opcode.Value, (CpuStatus, Option[Int]) => CpuStatus] = Map( - STA -> ((c,zpreg) => c.setReg(zpreg, c.a)), - STX -> ((c,zpreg) => c.setReg(zpreg, c.x)), - STY -> ((c,zpreg) => c.setReg(zpreg, c.y)), - STZ -> ((c,zpreg) => c.setReg(zpreg, c.iz)), - SAX -> ((c,zpreg) => c.setReg(zpreg, (c.a <*> c.x)(_ & _))), - NOP -> ((x,_) => x), - DISCARD_AF -> ((x,_) => x), - DISCARD_XF -> ((x,_) => x), - DISCARD_YF -> ((x,_) => x), - REP -> ((currentStatus, zpreg) => { + private val map: Map[Opcode.Value, (CpuStatus, Option[Int], Boolean) => CpuStatus] = Map( + STA -> ((c, zpreg, sp) => c.setReg(zpreg, c.a).overwriteSp(sp)), + STX -> ((c, zpreg, sp) => { + if (sp) c.copy(eqSpX = true) + else c.setReg(zpreg, c.x) + }), + STY -> ((c, zpreg, sp) => c.setReg(zpreg, c.y).overwriteSp(sp)), + STZ -> ((c, zpreg, sp) => c.setReg(zpreg, c.iz).overwriteSp(sp)), + SAX -> ((c, zpreg, sp) => c.setReg(zpreg, (c.a <*> c.x) (_ & _)).copy(eqSpX = false)), + NOP -> ((x, _, _) => x), + DISCARD_AF -> ((x, _, _) => x), + DISCARD_XF -> ((x, _, _) => x), + DISCARD_YF -> ((x, _, _) => x), + REP -> ((currentStatus, zpreg, sp) => { currentStatus.copy(c = AnyStatus, d = AnyStatus, n = AnyStatus, z= AnyStatus, v = AnyStatus, m = AnyStatus, w = AnyStatus) }), - SEP -> ((currentStatus, zpreg) => { + SEP -> ((currentStatus, zpreg, sp) => { currentStatus.copy(c = AnyStatus, d = AnyStatus, n = AnyStatus, z= AnyStatus, v = AnyStatus, m = AnyStatus, w = AnyStatus) }), - BCC -> ((currentStatus, zpreg) => { + BCC -> ((currentStatus, zpreg, sp) => { currentStatus.copy(c = Status.SingleTrue) }), - BCS -> ((currentStatus, zpreg) => { + BCS -> ((currentStatus, zpreg, sp) => { currentStatus.copy(c = Status.SingleFalse) }), - BVS -> ((currentStatus, zpreg) => { + BVS -> ((currentStatus, zpreg, sp) => { currentStatus.copy(v = Status.SingleFalse) }), - BVC -> ((currentStatus, zpreg) => { + BVC -> ((currentStatus, zpreg, sp) => { currentStatus.copy(v = Status.SingleTrue) }), - BMI -> ((c, zpreg) => { + BMI -> ((c, zpreg,_) => { var currentStatus = c currentStatus = currentStatus.copy(n = Status.SingleFalse) if (currentStatus.src.isFromA) { @@ -44,7 +47,7 @@ object FlowAnalyzerForTheRest { } currentStatus }), - BPL -> ((c, zpreg) => { + BPL -> ((c, zpreg,_) => { var currentStatus = c currentStatus = currentStatus.copy(n = Status.SingleTrue) if (currentStatus.src.isFromA) { @@ -52,10 +55,10 @@ object FlowAnalyzerForTheRest { } currentStatus }), - BEQ -> ((currentStatus, zpreg) => { + BEQ -> ((currentStatus, zpreg, sp) => { currentStatus.copy(z = Status.SingleFalse) }), - BNE -> ((c, zpreg) => { + BNE -> ((c, zpreg,_) => { var currentStatus = c currentStatus = currentStatus.copy(z = Status.SingleTrue) if (currentStatus.src.isFromA) { @@ -79,15 +82,17 @@ object FlowAnalyzerForTheRest { } currentStatus }), - LDX -> ((currentStatus, zpreg) => { + LDX -> ((currentStatus, zpreg, sp) => { val newX = currentStatus.getReg(zpreg) currentStatus.copy( x = newX, n = newX.n(), z = newX.z(), + eqSX = false, + eqSpX = sp, src = SourceOfNZ.X) }), - LDY -> ((currentStatus, zpreg) => { + LDY -> ((currentStatus, zpreg, sp) => { val newY = currentStatus.getReg(zpreg) currentStatus.copy( y = newY, @@ -95,7 +100,7 @@ object FlowAnalyzerForTheRest { z = newY.z(), src = SourceOfNZ.Y) }), - LDA -> ((currentStatus, zpreg) => { + LDA -> ((currentStatus, zpreg, sp) => { val newA = currentStatus.getReg(zpreg) currentStatus.copy( a = newA, @@ -105,7 +110,7 @@ object FlowAnalyzerForTheRest { z = newA.z(), src = SourceOfNZ.A) }), - LDZ -> ((currentStatus, zpreg) => { + LDZ -> ((currentStatus, zpreg, sp) => { val newZ = currentStatus.getReg(zpreg) currentStatus.copy( iz = newZ, @@ -113,7 +118,7 @@ object FlowAnalyzerForTheRest { z = newZ.z(), src = SourceOfNZ.Z) }), - LAX -> ((currentStatus, zpreg) => { + LAX -> ((currentStatus, zpreg, sp) => { val newA = currentStatus.getReg(zpreg) currentStatus.copy( x = newA, @@ -122,9 +127,11 @@ object FlowAnalyzerForTheRest { a0 = newA.bit0, n = newA.n(), z = newA.z(), + eqSX = false, + eqSpX = sp, src = SourceOfNZ.AX) }), - LDA_W -> ((currentStatus, zpreg) => { + LDA_W -> ((currentStatus, zpreg, sp) => { val newA = currentStatus.getReg(zpreg) val newAH = currentStatus.getRegHi(zpreg) currentStatus.copy( @@ -136,21 +143,23 @@ object FlowAnalyzerForTheRest { z = AnyStatus, src = SourceOfNZ.AW) }), - LDX_W -> ((currentStatus, zpreg) => { + LDX_W -> ((currentStatus, zpreg, sp) => { currentStatus.copy( x = AnyStatus, n = AnyStatus, z = AnyStatus, + eqSX = false, + eqSpX = false, src = AnyStatus) }), - LDY_W -> ((currentStatus, zpreg) => { + LDY_W -> ((currentStatus, zpreg, sp) => { currentStatus.copy( y = AnyStatus, n = AnyStatus, z = AnyStatus, src = AnyStatus) }), - ADC -> ((currentStatus, zpreg) => { + ADC -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) val newA: Status[Int]= if (currentStatus.d.contains(false)) Status.flatMap3(currentStatus.a, r, currentStatus.c) { case (m, n, false) => SingleStatus((m + n) & 0xff) @@ -173,7 +182,7 @@ object FlowAnalyzerForTheRest { n = newA.n(), src = currentStatus.d.flatMap((dec: Boolean) => if (dec) AnyStatus else SourceOfNZ.A)) }), - SBC -> ((currentStatus, zpreg) => { + SBC -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) val newA: Status[Int]= if (currentStatus.d.contains(false)) Status.flatMap3(currentStatus.a, r, currentStatus.c) { case (m, n, false) => SingleStatus((m - n + 0xff) & 0xff) @@ -192,7 +201,7 @@ object FlowAnalyzerForTheRest { n = newA.n(), src = currentStatus.d.flatMap((dec: Boolean) => if (dec) AnyStatus else SourceOfNZ.A)) }), - AND -> ((currentStatus, zpreg) => { + AND -> ((currentStatus, zpreg, sp) => { val newA: Status[Int] = currentStatus.a.flatMap(v => if ((v & 0xff) == 0) Status.SingleZero else AnyStatus) | (currentStatus.a <*> currentStatus.getReg(zpreg))(_ & _) @@ -204,7 +213,7 @@ object FlowAnalyzerForTheRest { z = newA.z(), src = SourceOfNZ.A) }), - ORA -> ((currentStatus, zpreg) => { + ORA -> ((currentStatus, zpreg, sp) => { val newA: Status[Int] = currentStatus.a.flatMap(v => if ((v & 0xff) == 0xff) Status.SingleFF else AnyStatus) | (currentStatus.a <*> currentStatus.getReg(zpreg))(_ | _) @@ -216,7 +225,7 @@ object FlowAnalyzerForTheRest { z = newA.z(), src = SourceOfNZ.A) }), - SLO -> ((currentStatus, zpreg) => { + SLO -> ((currentStatus, zpreg, sp) => { val newA: Status[Int] = currentStatus.a.flatMap(v => if ((v & 0xff) == 0xff) Status.SingleFF else AnyStatus) currentStatus.copy( c = AnyStatus, @@ -227,7 +236,7 @@ object FlowAnalyzerForTheRest { z = newA.z(), src = SourceOfNZ.A).setReg(zpreg, AnyStatus) }), - EOR -> ((currentStatus, zpreg) => { + EOR -> ((currentStatus, zpreg, sp) => { val newA: Status[Int] = (currentStatus.a <*> currentStatus.getReg(zpreg))(_ ^ _) currentStatus.copy( n = newA.n(), @@ -237,7 +246,7 @@ object FlowAnalyzerForTheRest { a0 = newA.bit0, src = SourceOfNZ.A).setReg(zpreg, AnyStatus) }), - SRE -> ((currentStatus, zpreg) => { + SRE -> ((currentStatus, zpreg, sp) => { currentStatus.copy( c = AnyStatus, n = AnyStatus, @@ -245,9 +254,9 @@ object FlowAnalyzerForTheRest { a = AnyStatus, a7 = AnyStatus, a0 = AnyStatus, - src = SourceOfNZ.A).setReg(zpreg, AnyStatus) + src = SourceOfNZ.A).setReg(zpreg, AnyStatus).overwriteSp(sp) }), - RLA -> ((currentStatus, zpreg) => { + RLA -> ((currentStatus, zpreg, sp) => { currentStatus.copy( c = AnyStatus, n = AnyStatus, @@ -255,9 +264,9 @@ object FlowAnalyzerForTheRest { a = AnyStatus, a7 = AnyStatus, a0 = AnyStatus, - src = SourceOfNZ.A).setReg(zpreg, AnyStatus) + src = SourceOfNZ.A).setReg(zpreg, AnyStatus).overwriteSp(sp) }), - RRA -> ((currentStatus, zpreg) => { + RRA -> ((currentStatus, zpreg, sp) => { currentStatus.copy( c = AnyStatus, n = AnyStatus, @@ -266,17 +275,17 @@ object FlowAnalyzerForTheRest { a7 = AnyStatus, a0 = AnyStatus, z = AnyStatus, - src = currentStatus.d.flatMap(dec => if (dec) AnyStatus else SourceOfNZ.A)).setReg(zpreg, AnyStatus) + src = currentStatus.d.flatMap(dec => if (dec) AnyStatus else SourceOfNZ.A)).setReg(zpreg, AnyStatus).overwriteSp(sp) }), - DCP -> ((currentStatus, zpreg) => { + DCP -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy( c = AnyStatus, n = AnyStatus, z = AnyStatus, - src = AnyStatus).setReg(zpreg, r.map(n => (n - 1) & 0xff)) + src = AnyStatus).setReg(zpreg, r.map(n => (n - 1) & 0xff)).overwriteSp(sp) }), - ISC -> ((currentStatus, zpreg) => { + ISC -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy( c = AnyStatus, @@ -286,61 +295,61 @@ object FlowAnalyzerForTheRest { a7 = AnyStatus, a0 = AnyStatus, z = AnyStatus, - src = currentStatus.d.flatMap(dec => if (dec) AnyStatus else SourceOfNZ.A)).setReg(zpreg, r.map(n => (n + 1) & 0xff)) + src = currentStatus.d.flatMap(dec => if (dec) AnyStatus else SourceOfNZ.A)).setReg(zpreg, r.map(n => (n + 1) & 0xff)).overwriteSp(sp) }), - ROL -> ((currentStatus, zpreg) => { + ROL -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy(c = r.bit7, n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, currentStatus.c.flatMap { c => r.map(n => (n << 1) & 0xff | (if (c) 1 else 0)) - }) + }).overwriteSp(sp) }), - ROR -> ((currentStatus, zpreg) => { + ROR -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy(c = r.bit0, n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, currentStatus.c.flatMap { c => r.map(n => (n >> 1) & 0x7f | (if (c) 0x80 else 0)) - }) + }).overwriteSp(sp) }), - ASL -> ((currentStatus, zpreg) => { + ASL -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) - currentStatus.copy(c = r.bit7, n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n << 1) & 0xff)) + currentStatus.copy(c = r.bit7, n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n << 1) & 0xff)).overwriteSp(sp) }), - LSR -> ((currentStatus, zpreg) => { + LSR -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) - currentStatus.copy(c = r.bit0, n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n >> 1) & 0xff)) + currentStatus.copy(c = r.bit0, n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n >> 1) & 0xff)).overwriteSp(sp) }), - INC -> ((currentStatus, zpreg) => { + INC -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) - currentStatus.copy(n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n + 1) & 0xff)) + currentStatus.copy(n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n + 1) & 0xff)).overwriteSp(sp) }), - DEC -> ((currentStatus, zpreg) => { + DEC -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) - currentStatus.copy(n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n - 1) & 0xff)) + currentStatus.copy(n = AnyStatus, z = AnyStatus, src = AnyStatus).setReg(zpreg, r.map(n => (n - 1) & 0xff)).overwriteSp(sp) }), - CMP -> ((currentStatus, zpreg) => { + CMP -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy(c = AnyStatus, n = AnyStatus, z = (currentStatus.a <*> r)(_==_), src = if (r.contains(0)) SourceOfNZ.A else AnyStatus) }), - CPX -> ((currentStatus, zpreg) => { + CPX -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy(c = AnyStatus, n = AnyStatus, z = (currentStatus.x <*> r)(_==_), src = if (r.contains(0)) SourceOfNZ.X else AnyStatus) }), - CPY -> ((currentStatus, zpreg) => { + CPY -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy(c = AnyStatus, n = AnyStatus, z = (currentStatus.y <*> r)(_==_), src = if (r.contains(0)) SourceOfNZ.Y else AnyStatus) }), - CPZ -> ((currentStatus, zpreg) => { + CPZ -> ((currentStatus, zpreg, sp) => { val r = currentStatus.getReg(zpreg) currentStatus.copy(c = AnyStatus, n = AnyStatus, z = (currentStatus.iz <*> r)(_==_), src = if (r.contains(0)) SourceOfNZ.Z else AnyStatus) }), - BIT -> ((currentStatus, zpreg) => currentStatus.copy(v = AnyStatus, n = AnyStatus, z = AnyStatus, src = AnyStatus)), - TRB -> ((currentStatus, zpreg) => currentStatus.copy(z = AnyStatus, src = AnyStatus).setReg(zpreg, AnyStatus)), - TSB -> ((currentStatus, zpreg) => currentStatus.copy(z = AnyStatus, src = AnyStatus).setReg(zpreg, AnyStatus)), - JMP -> ((_,_) => CpuStatus()), - BRA -> ((_,_) => CpuStatus()), - BRL -> ((_,_) => CpuStatus()), + BIT -> ((currentStatus, zpreg, sp) => currentStatus.copy(v = AnyStatus, n = AnyStatus, z = AnyStatus, src = AnyStatus)), + TRB -> ((currentStatus, zpreg, sp) => currentStatus.copy(z = AnyStatus, src = AnyStatus).setReg(zpreg, AnyStatus)), + TSB -> ((currentStatus, zpreg, sp) => currentStatus.copy(z = AnyStatus, src = AnyStatus).setReg(zpreg, AnyStatus)), + JMP -> ((_,_,_) => CpuStatus()), + BRA -> ((_,_,_) => CpuStatus()), + BRL -> ((_,_,_) => CpuStatus()), ) def hasDefinition(opcode: Opcode.Value): Boolean = map.contains(opcode) - def get(opcode: Opcode.Value): (CpuStatus, Option[Int]) => CpuStatus = map(opcode) + def get(opcode: Opcode.Value): (CpuStatus, Option[Int], Boolean) => CpuStatus = map(opcode) } diff --git a/src/main/scala/millfork/assembly/mos/opt/LaterOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/LaterOptimizations.scala index 116510eb..2d6096a0 100644 --- a/src/main/scala/millfork/assembly/mos/opt/LaterOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/LaterOptimizations.scala @@ -36,19 +36,19 @@ object LaterOptimizations { TwoDifferentLoadsWhoseFlagsWillNotBeChecked(LDY, Not(ChangesY), LDA, TYA), (HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Linear & Not(ChangesA) & Not(HasOpcode(LDX)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ChangesA) & Not(HasOpcode(LDX)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(LDX) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init :+ AssemblyLine.implied(TAX)), (HasOpcodeIn(Set(LDA, STA)) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Linear & Not(ChangesA) & Not(HasOpcode(LDY)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ChangesA) & Not(HasOpcode(LDY)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(LDY) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init :+ AssemblyLine.implied(TAY)), (HasOpcodeIn(Set(LDX, STX)) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Linear & Not(ChangesX) & Not(HasOpcode(LDA)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ChangesX) & Not(HasOpcode(LDA)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init :+ AssemblyLine.implied(TXA)), (HasOpcodeIn(Set(LDY, STY)) & MatchAddrMode(0) & MatchParameter(1)) ~ - (Linear & Not(ChangesY) & Not(HasOpcode(LDA)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~ + (Linear & Not(ChangesY) & Not(HasOpcode(LDA)) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAtAssumingNonchangingIndices(0, 1)).* ~ (Elidable & HasOpcode(LDA) & MatchAddrMode(0) & MatchParameter(1)) ~~> (code => code.init :+ AssemblyLine.implied(TYA)), ) diff --git a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala index ece59c92..2a39d24d 100644 --- a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala @@ -256,7 +256,7 @@ object HelperCheckers { private val badAddrModes = Set(IndexedX, IndexedY, IndexedZ, LongIndexedY, LongIndexedZ, IndexedSY, Indirect, TripleAbsolute, Stack) private val goodAddrModes = Set(Implied, Immediate, WordImmediate, Relative, LongRelative) - def memoryAccessDoesntOverlap(l1: AssemblyLine, l2: AssemblyLine): Boolean = { + def memoryAccessDoesntOverlap(l1: AssemblyLine, l2: AssemblyLine, assumeSameIndices: Boolean = false): Boolean = { val a1 = l1.addrMode val a2 = l2.addrMode if (goodAddrModes(a1) || goodAddrModes(a2)) return true @@ -270,14 +270,17 @@ object HelperCheckers { def distinctThings(a: String, b: String): Boolean = { if (a == "__reg") return b != "__reg" if (b == "__reg") return a != "__reg" + if (a == "__sp") return b != "__sp" + if (b == "__sp") return a != "__sp" a.takeWhile(_ != '.') != b.takeWhile(_ != '.') } def handleKnownDistance(distance: Short): Boolean = { // `distance` is the distance between the first byte that can be addressed by l1 (b1) and the first byte that can be addressed by l2 (b2): (b2-b1) val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY, LongAbsoluteX) - val a1Indexing = indexingAddrModes(a1) - val a2Indexing = indexingAddrModes(a2) + val indicesCancelOut = assumeSameIndices && a1 == a2 + val a1Indexing = indexingAddrModes(a1) && !indicesCancelOut + val a2Indexing = indexingAddrModes(a2) && !indicesCancelOut (a1Indexing, a2Indexing) match { case (false, false) => distance != 0 && (distance != 1 || !w1) && (distance != -1 || !w2) case (true, false) => distance > 255 || distance < 0 && (distance != 256 || !w1) && (distance != -1 || !w2) @@ -624,7 +627,7 @@ case class HasSet(state: State.Value) extends AssemblyLinePattern { flowInfo.hasSet(state) } -case object XContainsStackPointer extends AssemblyLinePattern { +case object XContainsHardwareStackPointer extends AssemblyLinePattern { override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = FlowInfoRequirement.assertForward(needsFlowInfo) @@ -632,6 +635,14 @@ case object XContainsStackPointer extends AssemblyLinePattern { flowInfo.statusBefore.eqSX } +case object XContainsSoftwareStackPointer extends AssemblyLinePattern { + override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = + FlowInfoRequirement.assertForward(needsFlowInfo) + + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = + flowInfo.statusBefore.eqSpX +} + case class HasSourceOfNZ(state: State.Value) extends AssemblyLinePattern { override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit = FlowInfoRequirement.assertForward(needsFlowInfo) @@ -1008,6 +1019,23 @@ case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int, opcode: Opcode.Valu } } +case class DoesntChangeMemoryAtAssumingNonchangingIndices(addrMode1: Int, param1: Int, opcode: Opcode.Value = Opcode.NOP) extends AssemblyLinePattern { + override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = { + import AddrMode._ + import Opcode._ + line match { + case AssemblyLine0(JSR | BSR, Absolute | LongAbsolute, MemoryAddressConstant(th)) => !ctx.functionChangesMemory(th.name) + case _ => + val p1 = ctx.get[Constant](param1) + val a1 = ctx.get[AddrMode.Value](addrMode1) + val changesSomeMemory = OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode) + // TODO: NOP + // this will break if the actual instruction was 16-bit + !changesSomeMemory || HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(opcode, a1, p1), line, assumeSameIndices = true) + } + } +} + case object ConcernsMemory extends AssemblyLinePattern { override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = ReadsMemory.matchLineTo(ctx, flowInfo, line) || ChangesMemory.matchLineTo(ctx, flowInfo, line) diff --git a/src/main/scala/millfork/compiler/CompilationContext.scala b/src/main/scala/millfork/compiler/CompilationContext.scala index 4b7b7ef1..c5c88cf6 100644 --- a/src/main/scala/millfork/compiler/CompilationContext.scala +++ b/src/main/scala/millfork/compiler/CompilationContext.scala @@ -1,6 +1,6 @@ package millfork.compiler -import millfork.env.{Environment, Label, NormalFunction} +import millfork.env.{Environment, Label, NormalFunction, NormalParamSignature} import millfork.error.Logger import millfork.node.NiceFunctionProperty import millfork.{CompilationFlag, CompilationOptions, JobContext} @@ -42,4 +42,11 @@ case class CompilationContext(env: Environment, def log: Logger = options.log @inline def nextLabel: LabelGenerator = options.nextLabel + + def prologueShouldAvoidA: Boolean = { + function.params match { + case NormalParamSignature(List(param)) => param.typ.size == 1 + case _ => false + } + } } diff --git a/src/main/scala/millfork/compiler/mos/MosCompiler.scala b/src/main/scala/millfork/compiler/mos/MosCompiler.scala index 3b36a73a..8eaea63e 100644 --- a/src/main/scala/millfork/compiler/mos/MosCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosCompiler.scala @@ -25,14 +25,17 @@ object MosCompiler extends AbstractCompiler[AssemblyLine] { case _ => Nil }).map(_.position(ctx.function.position)) val phReg = - if (zpRegisterSize > 0) { + (if (zpRegisterSize > 0) { val reg = ctx.env.get[VariableInMemory]("__reg") (0 until zpRegisterSize).flatMap { i => List( AssemblyLine.zeropage(LDA, reg, i), AssemblyLine.implied(PHA)) }.toList.map(_.position(ctx.function.position)) - } else Nil + } else Nil) ++ ( + if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + List(AssemblyLine.absolute(LDA, ctx.env.get[ThingInMemory]("__sp")), AssemblyLine.implied(PHA)) + } else Nil) val prefix = storeParamsFromRegisters ++ (if (ctx.function.interrupt) { @@ -124,16 +127,36 @@ object MosCompiler extends AbstractCompiler[AssemblyLine] { def stackPointerFixAtBeginning(ctx: CompilationContext): List[AssemblyLine] = { val m = ctx.function - if (m.stackVariablesSize == 0) return Nil - if (ctx.options.flag(CompilationFlag.EmitIllegals)) { - if (m.stackVariablesSize > 4) - return List( - AssemblyLine.implied(TSX), - AssemblyLine.immediate(LDA, 0xff), - AssemblyLine.immediate(SBX, m.stackVariablesSize), - AssemblyLine.implied(TXS)).map(_.position(m.position)) // this TXS is fine, it won't appear in 65816 code + if (m.stackVariablesSize == 0 && m.name != "main") return Nil + if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + val stackPointer = ctx.env.get[ThingInMemory]("__sp") + if (m.name == "main") { + List( + AssemblyLine.immediate(LDX, 0xff - m.stackVariablesSize), + AssemblyLine.absolute(STX, stackPointer)).map(_.position(m.position)) + } else if (m.stackVariablesSize < 3 || m.stackVariablesSize == 3 && ctx.prologueShouldAvoidA) { + List.fill(m.stackVariablesSize)(AssemblyLine.absolute(DEC, stackPointer)).map(_.position(m.position)) + } else { + List(AssemblyLine.absolute(LDA, stackPointer), + AssemblyLine.implied(SEC), + AssemblyLine.immediate(SBC, m.stackVariablesSize), + AssemblyLine.absolute(STA, stackPointer)).map(_.position(m.position)) + } + } else { + if (ctx.options.flag(CompilationFlag.EmitIllegals)) { + // TODO + if (m.stackVariablesSize > 4 && !ctx.prologueShouldAvoidA) + return List( + AssemblyLine.implied(TSX), + AssemblyLine.immediate(LDA, 0xff), + AssemblyLine.immediate(SBX, m.stackVariablesSize), + AssemblyLine.implied(TXS)).map(_.position(m.position)) // this TXS is fine, it won't appear in 65816 code + } + if (ctx.prologueShouldAvoidA && ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { + return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PHX)).map(_.position(m.position)) + } + List.fill(m.stackVariablesSize)(AssemblyLine.implied(PHA)).map(_.position(m.position)) } - List.fill(m.stackVariablesSize)(AssemblyLine.implied(PHA)).map(_.position(m.position)) } } diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index c0eaf958..98201994 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -57,24 +57,24 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case StackVariable(_, t, offset) => t.size match { case 0 => Nil - case 1 => List( + case 1 => AssemblyLine.tsx(ctx) ++ List( + AssemblyLine.immediate(LDA, expr.loByte), + AssemblyLine.dataStackX(ctx, STA, offset)) + case 2 => AssemblyLine.tsx(ctx) ++ List( AssemblyLine.implied(TSX), AssemblyLine.immediate(LDA, expr.loByte), - AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset)) - case 2 => List( - AssemblyLine.implied(TSX), - AssemblyLine.immediate(LDA, expr.loByte), - AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, STA, offset), AssemblyLine.immediate(LDA, expr.hiByte), - AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset + 1)) - case s => AssemblyLine.implied(TSX) :: List.tabulate(s)(i => List( + AssemblyLine.absoluteX(STA, offset + 1)) + case s => AssemblyLine.tsx(ctx) ++ List.tabulate(s)(i => List( AssemblyLine.immediate(LDA, expr.subbyte(i)), - AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset + i))).flatten + AssemblyLine.dataStackX(ctx, STA, offset + i))).flatten } } } def fixTsx(code: List[AssemblyLine]): List[AssemblyLine] = code match { + case (access@AssemblyLine0(_, Stack | IndexedSY, p)) :: xs => access.copy(parameter = (p + 1).quickSimplify) :: fixTsx(xs) case (tsx@AssemblyLine0(TSX, _, _)) :: xs => tsx :: AssemblyLine.implied(INX) :: fixTsx(xs) case (txs@AssemblyLine0(TXS, _, _)) :: xs => ??? case x :: xs => x :: fixTsx(xs) @@ -218,12 +218,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case 1 => v match { case mv: VariableInMemory => AssemblyLine.variable(ctx, store, mv) - case sv@StackVariable(_, _, offset) => - if (ctx.options.flags(CompilationFlag.EmitEmulation65816Opcodes)) { - AssemblyLine.implied(transferToA) :: AssemblyLine.stackRelative(STA, offset + ctx.extraStackOffset) :: Nil - } else { - AssemblyLine.implied(transferToA) :: AssemblyLine.implied(TSX) :: AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset) :: Nil - } + case sv: StackVariable => + AssemblyLine.implied(transferToA) :: (AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, sv)) } case s if s > 1 => v match { @@ -231,11 +227,13 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { AssemblyLine.variable(ctx, store, mv) ++ List(AssemblyLine.immediate(LDA, 0)) ++ List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, mv, i + 1)).flatten - case sv@StackVariable(_, _, offset) => - AssemblyLine.implied(transferToA) :: - AssemblyLine.implied(TSX) :: - AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset) :: - List.tabulate(s - 1)(i => AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset + i + 1)) + case sv: StackVariable => + AssemblyLine.implied(transferToA) :: ( + AssemblyLine.tsx(ctx) ++ + List(AssemblyLine.dataStackX(ctx, STA, sv), AssemblyLine.immediate(LDA, 0)) ++ + List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, sv, i + 1)) + ) + } } case IndexedExpression(arrayName, indexExpr) => @@ -440,104 +438,99 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil } else { - val copy = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) :+ AssemblyLine.absoluteX(STA, target.baseOffset + ctx.extraStackOffset + i)) + val copy = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) :+ AssemblyLine.dataStackX(ctx, STA, target, i)) val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) { - signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.absoluteX(STA, target.baseOffset + ctx.extraStackOffset + i + exprType.size)) + signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size)) } else { AssemblyLine.immediate(LDA, 0) :: - List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.absoluteX(STA, target.baseOffset + ctx.extraStackOffset + i + exprType.size)) + List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size)) } - AssemblyLine.implied(TSX) :: (copy.flatten ++ extend) + AssemblyLine.tsx(ctx) ++ copy.flatten ++ extend } } case source@StackVariable(_, sourceType, offset) => target match { - case RegisterVariable(MosRegister.A, _) => List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset)) - case RegisterVariable(MosRegister.X, _) => List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset), AssemblyLine.implied(TAX)) - case RegisterVariable(MosRegister.Y, _) => List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(LDY, offset + ctx.extraStackOffset)) + case RegisterVariable(MosRegister.A, _) => AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, LDA, offset) + case RegisterVariable(MosRegister.X, _) => AssemblyLine.tsx(ctx) ++ List(AssemblyLine.dataStackX(ctx, LDA, offset), AssemblyLine.implied(TAX)) + case RegisterVariable(MosRegister.Y, _) => AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, LDY, offset) case RegisterVariable(MosRegister.AX, _) => - exprType.size match { + AssemblyLine.tsx(ctx) ++ (exprType.size match { case 1 => if (exprType.isSigned) { List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, LDA, offset), AssemblyLine.implied(PHA)) ++ signExtendA(ctx) ++ List( AssemblyLine.implied(TAX), AssemblyLine.implied(PLA)) } else List( AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, LDA, offset), AssemblyLine.immediate(LDX, 0)) case 2 => List( AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, LDA, offset), AssemblyLine.implied(PHA), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset + 1), + AssemblyLine.dataStackX(ctx, LDA, offset + 1), AssemblyLine.implied(TAX), AssemblyLine.implied(PLA)) - } + }) case RegisterVariable(MosRegister.AY, _) => - exprType.size match { + AssemblyLine.tsx(ctx) ++ (exprType.size match { case 1 => if (exprType.isSigned) { val label = ctx.nextLabel("sx") ??? // TODO } else { List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, LDA, offset), AssemblyLine.immediate(LDY, 0)) } case 2 => List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset), - AssemblyLine.absoluteX(LDY, offset + ctx.extraStackOffset + 1)) - } + AssemblyLine.dataStackX(ctx, LDA, offset), + AssemblyLine.dataStackX(ctx, LDY, offset + 1)) + }) case RegisterVariable(MosRegister.XA, _) => ??? // TODO case RegisterVariable(MosRegister.YA, _) => - exprType.size match { + AssemblyLine.tsx(ctx) ++ (exprType.size match { case 1 => if (exprType.isSigned) { val label = ctx.nextLabel("sx") ??? // TODO } else { List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDY, offset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, LDY, offset), AssemblyLine.immediate(LDA, 0)) } case 2 => List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(LDY, offset + ctx.extraStackOffset), - AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset + 1)) - } + AssemblyLine.dataStackX(ctx, LDY, offset), + AssemblyLine.dataStackX(ctx, LDA, offset + 1)) + }) case target: VariableInMemory => if (exprType.size > target.typ.size) { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil } else { - val copy = List.tabulate(exprType.size)(i => AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset + i) :: AssemblyLine.variable(ctx, STA, target, i)) + val copy = List.tabulate(exprType.size)(i => AssemblyLine.dataStackX(ctx, LDA, offset + i) :: AssemblyLine.variable(ctx, STA, target, i)) val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) { signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.variable(ctx, STA, target, i + exprType.size)).flatten } else { AssemblyLine.immediate(LDA, 0) :: List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.variable(ctx, STA, target, i + exprType.size)).flatten } - AssemblyLine.implied(TSX) :: (copy.flatten ++ extend) + AssemblyLine.tsx(ctx) ++ copy.flatten ++ extend } case target: StackVariable => if (exprType.size > target.typ.size) { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil } else { - val copyFromLo = List.tabulate(exprType.size)(i => List(AssemblyLine.absoluteX(LDA, offset + ctx.extraStackOffset + i), AssemblyLine.absoluteX(STA, target.baseOffset + i))) + val copyFromLo = List.tabulate(exprType.size)(i => List(AssemblyLine.dataStackX(ctx, LDA, offset + i), AssemblyLine.dataStackX(ctx, STA, target, i))) val copy = if (shouldCopyFromHiToLo(NumericConstant(source.baseOffset, 2), NumericConstant(target.baseOffset, 2))) copyFromLo.reverse else copyFromLo val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) { - signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.absoluteX(STA, target.baseOffset + ctx.extraStackOffset + i + exprType.size)) + signExtendA(ctx) ++ List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size)) } else { AssemblyLine.immediate(LDA, 0) :: - List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.absoluteX(STA, target.baseOffset + ctx.extraStackOffset + i + exprType.size)) + List.tabulate(target.typ.size - exprType.size)(i => AssemblyLine.dataStackX(ctx, STA, target, i + exprType.size)) } - AssemblyLine.implied(TSX) :: (copy.flatten ++ extend) + AssemblyLine.tsx(ctx) ++ copy.flatten ++ extend } } case source@ConstantThing(_, value, _) => @@ -710,8 +703,13 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { ctx.log.error(s"Variable `$target.name` cannot hold a word", expr.position) Nil case 2 => - compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset)), branches) ++ - compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset + 1)), branches) + if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset)), branches) ++ + compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + 1)), branches) + } else { + compile(ctx, l, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset)), branches) ++ + compile(ctx, h, Some(b -> StackVariable("", b, target.baseOffset + ctx.extraStackOffset + 1)), branches) + } } } } @@ -1257,21 +1255,19 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case (t, v: StackVariable) => t.size match { case 1 => v.typ.size match { case 1 => - List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset)) + AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, v) case s if s > 1 => - if (t.isSigned) { + AssemblyLine.tsx(ctx) ++ (if (t.isSigned) { List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset)) ++ + AssemblyLine.dataStackX(ctx, STA, v.baseOffset)) ++ signExtendA(ctx) ++ - List.tabulate(s - 1)(i => AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset + i + 1)) + List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1)) } else { List( - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset), + AssemblyLine.dataStackX(ctx, STA, v.baseOffset), AssemblyLine.immediate(LDA, 0)) ++ - List.tabulate(s - 1)(i => AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset + i + 1)) - } + List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1)) + }) } case 2 => v.typ.size match { case 1 => @@ -1280,11 +1276,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case 2 => List( AssemblyLine.implied(TAY), - AssemblyLine.implied(TXA), - AssemblyLine.implied(TSX), - AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset + 1), + AssemblyLine.implied(TXA)) ++ AssemblyLine.tsx(ctx) ++ List( + AssemblyLine.dataStackX(ctx, STA, v, 1), AssemblyLine.implied(TYA), - AssemblyLine.absoluteX(STA, v.baseOffset + ctx.extraStackOffset)) + AssemblyLine.dataStackX(ctx, STA, v)) case s if s > 2 => ??? } } diff --git a/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala b/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala index d6d58933..5b211552 100644 --- a/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosStatementCompiler.scala @@ -35,14 +35,18 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { val w = env.get[Type]("word") val zpRegisterSize = ctx.options.zpRegisterSize lazy val plReg = - if (zpRegisterSize > 0) { + (if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + List( + AssemblyLine.implied(PLA), + AssemblyLine.absolute(STA, ctx.env.get[ThingInMemory]("__sp"))) + } else Nil) ++ (if (zpRegisterSize > 0) { val reg = env.get[VariableInMemory]("__reg") - (zpRegisterSize.-(1) to 0 by (-1)).flatMap{ i=> + (zpRegisterSize.-(1) to 0 by (-1)).flatMap { i => List( AssemblyLine.implied(PLA), AssemblyLine.zeropage(STA, reg,i)) }.toList - } else Nil + } 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)) @@ -228,9 +232,9 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { ctx.log.error("Cannot return anything from a void function", statement.position) stackPointerFixBeforeReturn(ctx) ++ returnInstructions case 1 => - MosExpressionCompiler.compile(ctx, e, someRegisterA, NoBranching) ++ stackPointerFixBeforeReturn(ctx) ++ returnInstructions + MosExpressionCompiler.compile(ctx, e, someRegisterA, NoBranching) ++ stackPointerFixBeforeReturn(ctx, preserveA = true) ++ returnInstructions case 2 => - MosExpressionCompiler.compile(ctx, e, someRegisterAX, NoBranching) ++ stackPointerFixBeforeReturn(ctx) ++ returnInstructions + MosExpressionCompiler.compile(ctx, e, someRegisterAX, NoBranching) ++ stackPointerFixBeforeReturn(ctx, preserveA = true, preserveX = true) ++ returnInstructions case _ => MosExpressionCompiler.compileAssignment(ctx, e, VariableExpression(ctx.function.name + "`return")) ++ stackPointerFixBeforeReturn(ctx) ++ returnInstructions @@ -242,10 +246,10 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { ctx.log.error("Cannot return anything from a void function", statement.position) stackPointerFixBeforeReturn(ctx) ++ List(AssemblyLine.discardAF(), AssemblyLine.discardXF(), AssemblyLine.discardYF()) ++ returnInstructions case 1 => - MosExpressionCompiler.compile(ctx, e, someRegisterA, NoBranching) ++ stackPointerFixBeforeReturn(ctx) ++ List(AssemblyLine.discardXF(), AssemblyLine.discardYF()) ++ returnInstructions + MosExpressionCompiler.compile(ctx, e, someRegisterA, NoBranching) ++ stackPointerFixBeforeReturn(ctx, preserveA = true) ++ List(AssemblyLine.discardXF(), AssemblyLine.discardYF()) ++ returnInstructions case 2 => // TODO: ??? - val stackPointerFix = stackPointerFixBeforeReturn(ctx) + val stackPointerFix = stackPointerFixBeforeReturn(ctx, preserveA = true, preserveY = true) if (stackPointerFix.isEmpty) { MosExpressionCompiler.compile(ctx, e, someRegisterAX, NoBranching) ++ List(AssemblyLine.discardYF()) ++ returnInstructions } else { @@ -276,38 +280,80 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] { }).map(_.positionIfEmpty(statement.position)) } - def stackPointerFixBeforeReturn(ctx: CompilationContext): List[AssemblyLine] = { + private def stackPointerFixBeforeReturn(ctx: CompilationContext, preserveA: Boolean = false, preserveX: Boolean = false, preserveY: Boolean = false): List[AssemblyLine] = { val m = ctx.function - if (m.stackVariablesSize == 0) return Nil - - if (m.returnType.size == 0 && m.stackVariablesSize <= 2) - return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLA)) - - if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { - if (m.returnType.size == 1 && m.stackVariablesSize <= 2) { - return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLX)) - } - if (m.returnType.size == 2 && m.stackVariablesSize <= 2) { - return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLY)) - } - } - - if (ctx.options.flag(CompilationFlag.EmitIllegals)) { - if (m.returnType.size == 0 && m.stackVariablesSize > 4) - return List( - AssemblyLine.implied(TSX), - AssemblyLine.immediate(LDA, 0xff), - AssemblyLine.immediate(SBX, 256 - m.stackVariablesSize), - AssemblyLine.implied(TXS)) // this TXS is fine, it won't appear in 65816 code - if (m.returnType.size == 1 && m.stackVariablesSize > 6) - return List( - AssemblyLine.implied(TAY), - AssemblyLine.implied(TSX), - AssemblyLine.immediate(LDA, 0xff), - AssemblyLine.immediate(SBX, 256 - m.stackVariablesSize), - AssemblyLine.implied(TXS), // this TXS is fine, it won't appear in 65816 code + if (m.stackVariablesSize == 0 && m.name != "main") return Nil + if (ctx.options.flag(CompilationFlag.SoftwareStack)) { + // TODO + val stackPointer = ctx.env.get[ThingInMemory]("__sp") + if (m.name == "main") { + List( + AssemblyLine.immediate(LDY, 0xff), + AssemblyLine.absolute(STY, stackPointer)) + } else if (m.stackVariablesSize < 3) { + List.fill(m.stackVariablesSize)(AssemblyLine.absolute(INC, stackPointer)) + } else if (!preserveA) { + List(AssemblyLine.absolute(LDA, stackPointer), + AssemblyLine.implied(CLC), + AssemblyLine.immediate(ADC, m.stackVariablesSize), + AssemblyLine.absolute(STA, stackPointer)) + } else if (!preserveY) { + List(AssemblyLine.implied(TAY), + AssemblyLine.absolute(LDA, stackPointer), + AssemblyLine.implied(CLC), + AssemblyLine.immediate(ADC, m.stackVariablesSize), + AssemblyLine.absolute(STA, stackPointer), AssemblyLine.implied(TYA)) + } else if (!preserveX) { + List(AssemblyLine.implied(TAY), + AssemblyLine.absolute(LDA, stackPointer), + AssemblyLine.implied(CLC), + AssemblyLine.immediate(ADC, m.stackVariablesSize), + AssemblyLine.absolute(STA, stackPointer), + AssemblyLine.implied(TYA)) + } else ??? + } else { + if (!preserveA && m.stackVariablesSize <= 2) + return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLA)) + + if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { + if (!preserveX && m.stackVariablesSize <= 2) { + return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLX)) + } + if (!preserveY && m.stackVariablesSize <= 2) { + return List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLY)) + } + } + + if (ctx.options.flag(CompilationFlag.EmitIllegals) && !preserveX) { + // TODO + if (!preserveA && m.stackVariablesSize > 4) + return List( + AssemblyLine.implied(TSX), + AssemblyLine.immediate(LDA, 0xff), + AssemblyLine.immediate(SBX, 256 - m.stackVariablesSize), + AssemblyLine.implied(TXS)) // this TXS is fine, it won't appear in 65816 code + if (!preserveY && m.stackVariablesSize > 6) + return List( + AssemblyLine.implied(TAY), + AssemblyLine.implied(TSX), + AssemblyLine.immediate(LDA, 0xff), + AssemblyLine.immediate(SBX, 256 - m.stackVariablesSize), + AssemblyLine.implied(TXS), // this TXS is fine, it won't appear in 65816 code + AssemblyLine.implied(TYA)) + } + if (!preserveX) { + AssemblyLine.implied(TSX) :: (List.fill(m.stackVariablesSize)(AssemblyLine.implied(INX)) :+ AssemblyLine.implied(TXS)) // this TXS is fine, it won't appear in 65816 code + } else { + // TODO: figure out if there's nothing better + if (!preserveA) { + List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLA)) + } else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes) && !preserveY) { + List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLY)) + } else if (!preserveY) { + AssemblyLine.implied(TAY) :: (List.fill(m.stackVariablesSize)(AssemblyLine.implied(PLA)) :+ AssemblyLine.implied(TYA)) + } else ??? + } } - AssemblyLine.implied(TSX) :: (List.fill(m.stackVariablesSize)(AssemblyLine.implied(INX)) :+ AssemblyLine.implied(TXS)) // this TXS is fine, it won't appear in 65816 code } } diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 12c70fb5..9f8a4fd8 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -1,7 +1,7 @@ package millfork.env import millfork.assembly.BranchingOpcodeMapping -import millfork._ +import millfork.{env, _} import millfork.assembly.mos.Opcode import millfork.assembly.z80.{IfFlagClear, IfFlagSet, ZFlag} import millfork.compiler.LabelGenerator @@ -1077,11 +1077,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa if (stmt.register && stmt.address.isDefined) log.error(s"`$name` cannot by simultaneously at an address and in a register", position) if (stmt.stack) { val v = StackVariable(prefix + name, typ, this.baseStackOffset) - baseStackOffset += typ.size addThing(v, stmt.position) for((suffix, offset, t) <- getSubvariables(typ)) { addThing(StackVariable(prefix + name + suffix, t, baseStackOffset + offset), stmt.position) } + baseStackOffset += typ.size } else { val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({ val alloc = @@ -1230,6 +1230,12 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa if (!things.contains("__constant8")) { things("__constant8") = InitializedArray("__constant8", None, List(LiteralExpression(8, 1)), declaredBank = None, b, b, NoAlignment) } + if (options.flag(CompilationFlag.SoftwareStack)) { + if (!things.contains("__sp")) { + things("__sp") = UninitializedMemoryVariable("__sp", b, VariableAllocationMethod.Auto, None, NoAlignment) + things("__stack") = UninitializedArray("__stack", 256, None, b, b, WithinPageAlignment) + } + } } } diff --git a/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala b/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala index 542aa3cf..cf98bd30 100644 --- a/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala +++ b/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala @@ -47,7 +47,7 @@ object UnusedGlobalVariables extends NodeOptimization { } def getAllReadVariables(expressions: List[Node]): List[String] = expressions.flatMap { - case s: VariableDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.initialValue.toList) + case s: VariableDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.initialValue.toList) ++ (if (s.stack) List("__sp", "__stack") else Nil) case s: ArrayDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.elements.toList) case s: ArrayContents => getAllReadVariables(s.getAllExpressions) case s: FunctionDeclarationStatement => getAllReadVariables(s.address.toList) ++ getAllReadVariables(s.statements.getOrElse(Nil)) diff --git a/src/test/scala/millfork/test/StackVarSuite.scala b/src/test/scala/millfork/test/StackVarSuite.scala index c162228e..01970341 100644 --- a/src/test/scala/millfork/test/StackVarSuite.scala +++ b/src/test/scala/millfork/test/StackVarSuite.scala @@ -1,7 +1,7 @@ package millfork.test import millfork.Cpu -import millfork.test.emu.{EmuCmosBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuZ80BenchmarkRun} +import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuOptimizedSoftwareStackRun, EmuSoftwareStackBenchmarkRun, EmuUnoptimizedZ80Run} import org.scalatest.{FunSuite, Matchers} /** @@ -10,7 +10,7 @@ import org.scalatest.{FunSuite, Matchers} class StackVarSuite extends FunSuite with Matchers { test("Basic stack assignment") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | byte output @$c000 | void main () { | stack byte a @@ -24,7 +24,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Stack byte addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | byte output @$c000 | void main () { | stack byte a @@ -42,7 +42,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Complex expressions involving stack variables (6502)") { - EmuCmosBenchmarkRun(""" + EmuSoftwareStackBenchmarkRun(""" | byte output @$c000 | void main () { | stack byte a @@ -89,7 +89,7 @@ class StackVarSuite extends FunSuite with Matchers { // } test("Stack word addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | stack word a @@ -107,7 +107,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Recursion") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | array output [6] @$c000 | byte fails @$c010 | void main () { @@ -143,7 +143,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Recursion 2") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | array output [6] @$c000 | byte fails @$c010 | void main () { @@ -175,7 +175,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Complex stack-related stuff") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | byte output @$c000 | array id = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | void main() { @@ -193,7 +193,7 @@ class StackVarSuite extends FunSuite with Matchers { test("Indexing") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | array output [200] @$c000 | void main () { | stack byte a @@ -206,7 +206,7 @@ class StackVarSuite extends FunSuite with Matchers { } test("Double array with stack variables") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)( + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)( """ | array output[5]@$c001 | array input = [0,1,4,9,16,25,36,49] @@ -222,4 +222,33 @@ class StackVarSuite extends FunSuite with Matchers { m.readByte(0xc005) should equal (50) } } + + test("Complex large stacks") { + EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)( +// val m = EmuUnoptimizedZ80Run( + """ + | array output[5]@$c000 + | noinline byte f(byte y) { + | stack int56 param + | param = y + | param -= 1 + | if param == 0 { + | return 1 + | } + | return f(param.b0) + 1 + | } + | void main () { + | output[0] = f(7) + | output[1] = f(8) + | output[2] = f(9) + | output[3] = f(2) + | } + | void _panic(){while(true){}} + """.stripMargin) { m => + m.readByte(0xc000) should equal(7) + m.readByte(0xc001) should equal(8) + m.readByte(0xc002) should equal(9) + m.readByte(0xc003) should equal(2) + } + } } diff --git a/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala b/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala index d6fd8d09..de782249 100644 --- a/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala +++ b/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala @@ -25,6 +25,30 @@ object EmuBenchmarkRun { } } +object EmuSoftwareStackBenchmarkRun { + def apply(source: String)(verifier: MemoryBank => Unit): Unit = { + val (Timings(t0, _), m0) = EmuUnoptimizedRun.apply2(source) + val (Timings(t1, _), m1) = EmuOptimizedRun.apply2(source) + val (Timings(t2, _), m2) = EmuOptimizedInlinedRun.apply2(source) + val (Timings(t3, _), m3) = EmuOptimizedSoftwareStackRun.apply2(source) + println(f"Before optimization: $t0%7d") + println(f"After optimization: $t1%7d") + println(f"After inlining: $t2%7d") + println(f"After software stack: $t3%7d") + println(f"Gain: ${(100L * (t0 - t1) / t0.toDouble).round}%7d%%") + println(f"Gain with inlining: ${(100L * (t0 - t2) / t0.toDouble).round}%7d%%") + println(f"Gain with SW stack: ${(100L * (t0 - t3) / t0.toDouble).round}%7d%%") + println(f"Running 6502 unoptimized") + verifier(m0) + println(f"Running 6502 optimized") + verifier(m1) + println(f"Running 6502 optimized inlined") + verifier(m2) + println(f"Running 6502 optimized with software stack") + verifier(m3) + } +} + object EmuZ80BenchmarkRun { def apply(source: String)(verifier: MemoryBank => Unit): Unit = { val (Timings(t0, _), m0) = EmuUnoptimizedZ80Run.apply2(source) @@ -90,6 +114,9 @@ object EmuCrossPlatformBenchmarkRun { if (platforms.contains(millfork.Cpu.Mos)) { EmuBenchmarkRun.apply(source)(verifier) } + if (platforms.contains(millfork.Cpu.StrictMos)) { + EmuBenchmarkRun.apply(source)(verifier) + } if (platforms.contains(millfork.Cpu.Ricoh)) { verifier(EmuUndocumentedRun.apply(source)) } diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala index 6256ea51..54ef5867 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala @@ -43,6 +43,25 @@ object EmuSizeOptimizedRun extends EmuRun( override def optimizeForSize = true } +object EmuOptimizedSoftwareStackRun extends EmuRun( + Cpu.StrictMos, + OptimizationPresets.NodeOpt, + OptimizationPresets.AssOpt ++ + ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + OptimizationPresets.Good ++ + OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ + OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ + ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + ZeropageRegisterOptimizations.All ++ + OptimizationPresets.Good ++ + OptimizationPresets.Good) { + override def softwareStack = true +} + object EmuOptimizedZ80Run extends EmuZ80Run(Cpu.Z80, OptimizationPresets.NodeOpt, Z80OptimizationPresets.GoodForZ80) diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 98fb05f4..40be3784 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -63,6 +63,8 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], def optimizeForSize = false + def softwareStack = false + private val timingNmos = Array[Int]( 7, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, @@ -131,6 +133,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], CompilationFlag.InlineFunctions -> this.inline, CompilationFlag.InterproceduralOptimization -> true, CompilationFlag.CompactReturnDispatchParams -> true, + CompilationFlag.SoftwareStack -> softwareStack, CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu), CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen), CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02),