diff --git a/doc/api/command-line.md b/doc/api/command-line.md index baadc554..b83bce21 100644 --- a/doc/api/command-line.md +++ b/doc/api/command-line.md @@ -56,6 +56,11 @@ Default: native if targeting 65816, no otherwise. * `-fjmp-fix`, `-fno-jmp-fix` – Whether should prevent indirect JMP bug on page boundary. `.ini` equivalent: `prevent_jmp_indirect_bug`. Default: no if targeting a 65C02-compatible architecture, yes otherwise. + +* `-fzp-register`, `-fno-zp-register` – Whether should reserve 2 bytes of zero page as a pseudoregister. +Increases language features. +`.ini` equivalent: `zeropage_register`. +Default: yes. * `-fdecimal-mode`, `-fno-decimal-mode` – Whether decimal mode should be available. `.ini` equivalent: `decimal_mode`. diff --git a/doc/lang/functions.md b/doc/lang/functions.md index 0aac6e29..6b2d231b 100644 --- a/doc/lang/functions.md +++ b/doc/lang/functions.md @@ -8,16 +8,26 @@ Syntax: * ``: zero or more of the following: - * `asm` – the function is written in assembly, not in Millfork (doesn't matter for `extern` functions), + * `asm` – the function is written in assembly, not in Millfork (obligatory for `extern` functions), see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions) * `macro` – the function is a macro, see [Macros_and inlining#Macros](../abi/inlining.md#macros) - * `inline` and `noinline` – the function should preferably/should never be inlined + * `inline` – the function should preferably be inlined see [Macros_and inlining#Inlining](../abi/inlining.md#automatic_inlining.md) - * `interrupt` – the function is a hardware interrupt handler + * `noinline` – the function should never be inlined + + * `interrupt` – the function is a hardware interrupt handler. + You are not allowed to call such functions directly. + The function cannot have parameters and the retrn type should be `void`. + + * `kernal_interrupt` – the function is an interrupt handler called from a generic vendor-provider hardware interrupt handler. + The hardware instruction handler is assumed to have preserved the CPU registers, + so this function only has to preserve the zeropage pseudoregisters. + An example is the Commodore 64 interrupt handler that calls the function at an address read from $314/$315. + Unline hardware handlers with `interrupt`, you can treat functions with `kernal_interrupt` like normal functions. * `` is a valid return type, see [Types](./types.md) diff --git a/doc/lang/operators.md b/doc/lang/operators.md index c8004046..32bd754e 100644 --- a/doc/lang/operators.md +++ b/doc/lang/operators.md @@ -6,6 +6,10 @@ Most expressions involving single bytes compile, but for larger types usually you need to use in-place modification operators. Further improvements to the compiler may increase the number of acceptable combinations. +Certain expressions require the commandline flag `-fzp-register` (`.ini` equivalent: `zeropage_register`) to be enabled. +They will be marked with (zpreg) next to them. +The flag is enabled by default, but you can disable it if you need it. + ## Precedence Millfork has different operator precedence compared to most other languages. From highest to lowest it goes: @@ -68,7 +72,8 @@ If and only if both `h` and `l` are assignable expressions, then `h:l` is also a `byte * constant byte` `constant byte * byte` `constant word * constant word` -`constant long * constant long` +`constant long * constant long` +`byte * byte` (zpreg) There are no division, remainder or modulo operators. @@ -81,6 +86,7 @@ There are no division, remainder or modulo operators. * `<<`, `>>`: bit shifting; shifting pads the result with zeroes `byte << constant byte` +`word << constant byte` (zpreg) `constant word << constant byte` `constant long << constant byte` diff --git a/include/zp_reg.mfk b/include/zp_reg.mfk new file mode 100644 index 00000000..55edcd68 --- /dev/null +++ b/include/zp_reg.mfk @@ -0,0 +1,15 @@ + +inline asm byte __mul_u8u8u8() { + ? LDA #0 + ? JMP start +add: + CLC + ADC __reg.lo +loop: + ASL __reg.lo +start: + LSR __reg.hi + BCS add + BNE loop + ? RTS +} diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index 3153685b..9da344f6 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -119,7 +119,7 @@ object CompilationFlag extends Enumeration { val // compilation options: EmitIllegals, EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, - DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, + ZeropagePseudoregister, DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, // optimization options: DetailedFlowAnalysis, DangerousOptimizations, InlineFunctions, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed, // memory allocation options @@ -138,6 +138,7 @@ object CompilationFlag extends Enumeration { "emit_cmos" -> EmitCmosOpcodes, "emit_65ce02" -> Emit65CE02Opcodes, "emit_huc6280" -> EmitHudsonOpcodes, + "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 edacef05..75a0c0f1 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -98,6 +98,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, ).flatten val extras = List( if (options.flag(CompilationFlag.EmitIllegals)) UndocumentedOptimizations.All else Nil, @@ -228,6 +229,9 @@ object Main { boolean("-fillegals", "-fno-illegals").action { (c, v) => 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) + }.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) }.description("Whether should prevent indirect JMP bug on page boundary.") diff --git a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala index 84c23cc2..46eac344 100644 --- a/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/opt/RuleBasedAssemblyOptimization.scala @@ -777,6 +777,48 @@ case class HasOpcode(op: Opcode.Value) extends TrivialAssemblyLinePattern { override def toString: String = op.toString } +case class RefersTo(identifier: String, offset: Int) extends TrivialAssemblyLinePattern { + override def apply(line: AssemblyLine): Boolean = { + (line.addrMode == AddrMode.ZeroPage || line.addrMode == AddrMode.Absolute || line.addrMode == AddrMode.LongAbsolute) && (line.parameter match { + case MemoryAddressConstant(th) => + offset == 0 && th.name == identifier + case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(nn, _)) => + offset == nn && th.name == identifier + case CompoundConstant(MathOperator.Plus, NumericConstant(nn, _), MemoryAddressConstant(th)) => + offset == nn && th.name == identifier + case _ => false + }) + } + + override def toString: String = s"<$identifier+$offset>" +} + +case class CallsAnyOf(identifiers: Set[String]) extends TrivialAssemblyLinePattern { + override def apply(line: AssemblyLine): Boolean = { + (line.addrMode == AddrMode.Absolute || + line.addrMode == AddrMode.LongAbsolute || + line.addrMode == AddrMode.LongRelative) && (line.parameter match { + case MemoryAddressConstant(th) => identifiers(th.name) + case _ => false + }) + } + + override def toString: String = identifiers.mkString("(JSR {", ",", "})") +} + +case class CallsAnyExcept(identifiers: Set[String]) extends TrivialAssemblyLinePattern { + override def apply(line: AssemblyLine): Boolean = { + (line.addrMode == AddrMode.Absolute || + line.addrMode == AddrMode.LongAbsolute || + line.addrMode == AddrMode.LongRelative) && (line.parameter match { + case MemoryAddressConstant(th) => !identifiers(th.name) + case _ => false + }) + } + + override def toString: String = identifiers.mkString("(JSR ¬{", ",", "})") +} + case class HasOpcodeIn(ops: Set[Opcode.Value]) extends TrivialAssemblyLinePattern { override def apply(line: AssemblyLine): Boolean = ops(line.opcode) diff --git a/src/main/scala/millfork/assembly/opt/SuperOptimizer.scala b/src/main/scala/millfork/assembly/opt/SuperOptimizer.scala index c50b268d..ebcf2298 100644 --- a/src/main/scala/millfork/assembly/opt/SuperOptimizer.scala +++ b/src/main/scala/millfork/assembly/opt/SuperOptimizer.scala @@ -24,6 +24,21 @@ object SuperOptimizer extends AssemblyOptimization { } else { allOptimizers ++= LaterOptimizations.Nmos } + if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { + allOptimizers ++= SixteenOptimizations.AllForEmulation + } + if (options.flag(CompilationFlag.EmitNative65816Opcodes)) { + allOptimizers ++= SixteenOptimizations.AllForNative + } + if (options.flag(CompilationFlag.EmitHudsonOpcodes)) { + allOptimizers ++= HudsonOptimizations.All + } + if (options.flag(CompilationFlag.Emit65CE02Opcodes)) { + allOptimizers ++= CE02Optimizations.All + } + if (options.flag(CompilationFlag.ZeropagePseudoregister)) { + allOptimizers ++= ZeropageRegisterOptimizations.All + } allOptimizers ++= List( VariableToRegisterOptimization, ChangeIndexRegisterOptimizationPreferringX2Y, diff --git a/src/main/scala/millfork/assembly/opt/ZeropageRegisterOptimizations.scala b/src/main/scala/millfork/assembly/opt/ZeropageRegisterOptimizations.scala new file mode 100644 index 00000000..0ca3752e --- /dev/null +++ b/src/main/scala/millfork/assembly/opt/ZeropageRegisterOptimizations.scala @@ -0,0 +1,74 @@ +package millfork.assembly.opt + +import millfork.assembly.Opcode._ +import millfork.assembly.AddrMode._ +import millfork.assembly.AssemblyLine +import millfork.env.{CompoundConstant, Constant, MathOperator} + +/** + * @author Karol Stasiak + */ +object ZeropageRegisterOptimizations { + + private val functionsThatUsePseudoregisterAsInput = Set("__mul_u8u8u8") + + val ConstantMultiplication = new RuleBasedAssemblyOptimization("Constant multiplication", + needsFlowInfo = FlowInfoRequirement.ForwardFlow, + (HasOpcode(STA) & RefersTo("__reg", 0) & MatchAddrMode(0) & MatchParameter(1) & MatchA(4)) ~ + (Linear & Not(RefersTo("__reg", 1)) & DoesntChangeMemoryAt(0, 1)).* ~ + (HasOpcode(STA) & RefersTo("__reg", 1) & MatchA(5)) ~ + (Elidable & HasOpcode(JSR) & RefersTo("__mul_u8u8u8", 0)) ~~> { (code, ctx) => + val product = ctx.get[Int](4) * ctx.get[Int](5) + code.init :+ AssemblyLine.immediate(LDA, product & 0xff) + }, + + (Elidable & HasOpcode(STA) & RefersTo("__reg", 0) & MatchAddrMode(0) & MatchParameter(1)) ~ + (Linear & Not(RefersTo("__reg", 1)) & DoesntChangeMemoryAt(0, 1)).* ~ + (HasOpcode(STA) & RefersTo("__reg", 1) & MatchA(4)) ~ + Where(ctx => { + val constant = ctx.get[Int](4) + (constant & (constant - 1)) == 0 + }) ~ + (Elidable & HasOpcode(JSR) & RefersTo("__mul_u8u8u8", 0)) ~~> { (code, ctx) => + val constant = ctx.get[Int](4) + if (constant == 0) { + code.init :+ AssemblyLine.immediate(LDA, 0) + } else { + code.init ++ (code.head.copy(opcode = LDA) :: List.fill(Integer.numberOfTrailingZeros(constant))(AssemblyLine.implied(ASL))) + } + }, + + (HasOpcode(STA) & RefersTo("__reg", 0) & MatchAddrMode(0) & MatchParameter(1) & MatchA(4)) ~ + Where(ctx => { + val constant = ctx.get[Int](4) + (constant & (constant - 1)) == 0 + }) ~ + (Linear & Not(RefersTo("__reg", 1)) & DoesntChangeMemoryAt(0, 1)).* ~ + (HasOpcode(STA) & RefersTo("__reg", 1)) ~ + (Elidable & HasOpcode(JSR) & RefersTo("__mul_u8u8u8", 0)) ~~> { (code, ctx) => + val constant = ctx.get[Int](4) + if (constant == 0) { + code.init :+ AssemblyLine.immediate(LDA, 0) + } else { + code.init ++ List.fill(Integer.numberOfTrailingZeros(constant))(AssemblyLine.implied(ASL)) + } + }, + ) + + // TODO: do this in a smarter way + val DeadRegStore = new RuleBasedAssemblyOptimization("Dead zeropage register store", + needsFlowInfo = FlowInfoRequirement.NoRequirement, + (Elidable & HasOpcode(STA) & RefersTo("__reg", 0) & MatchAddrMode(0) & MatchParameter(1)) ~ + (LinearOrLabel & DoesNotConcernMemoryAt(0, 1)).* ~ + (HasOpcodeIn(Set(RTS, RTL)) | CallsAnyExcept(functionsThatUsePseudoregisterAsInput)) ~~> (_.tail), + (Elidable & HasOpcode(STA) & RefersTo("__reg", 1) & MatchAddrMode(0) & MatchParameter(1)) ~ + (LinearOrLabel & DoesNotConcernMemoryAt(0, 1)).* ~ + (HasOpcodeIn(Set(RTS, RTL)) | CallsAnyExcept(functionsThatUsePseudoregisterAsInput)) ~~> (_.tail), + ) + + val All: List[AssemblyOptimization] = List( + ConstantMultiplication, + DeadRegStore, + ) + +} diff --git a/src/main/scala/millfork/compiler/BuiltIns.scala b/src/main/scala/millfork/compiler/BuiltIns.scala index 3e73e0b7..97fba5d3 100644 --- a/src/main/scala/millfork/compiler/BuiltIns.scala +++ b/src/main/scala/millfork/compiler/BuiltIns.scala @@ -524,8 +524,7 @@ object BuiltIns { case Some(NumericConstant(x, _)) => compileByteMultiplication(ctx, v, x.toInt) ++ ExpressionCompiler.compileByteStorage(ctx, Register.A, v) case _ => - ErrorReporting.error("Multiplying by not a constant not supported", v.position) - Nil + PseudoregisterBuiltIns.compileByteMultiplication(ctx, Some(v), addend, storeInRegLo = false) ++ ExpressionCompiler.compileByteStorage(ctx, Register.A, v) } } @@ -554,15 +553,20 @@ object BuiltIns { result.toList } + //noinspection ZeroIndexToHead def compileByteMultiplication(ctx: CompilationContext, params: List[Expression]): List[AssemblyLine] = { val (constants, variables) = params.map(p => p -> ctx.env.eval(p)).partition(_._2.exists(_.isInstanceOf[NumericConstant])) val constant = constants.map(_._2.get.asInstanceOf[NumericConstant].value).foldLeft(1L)(_ * _).toInt variables.length match { case 0 => List(AssemblyLine.immediate(LDA, constant & 0xff)) - case 1 =>compileByteMultiplication(ctx, variables.head._1, constant) + case 1 => compileByteMultiplication(ctx, variables.head._1, constant) case 2 => - ErrorReporting.error("Multiplying by not a constant not supported", params.head.position) - Nil + if (constant == 1) + PseudoregisterBuiltIns.compileByteMultiplication(ctx, Some(variables(0)._1), variables(1)._1, storeInRegLo = false) + else + PseudoregisterBuiltIns.compileByteMultiplication(ctx, Some(variables(0)._1), variables(1)._1, storeInRegLo = true) ++ + compileByteMultiplication(ctx, VariableExpression("__reg.lo"), constant) + case _ => ??? // TODO } } diff --git a/src/main/scala/millfork/compiler/ExpressionCompiler.scala b/src/main/scala/millfork/compiler/ExpressionCompiler.scala index b3a27a82..7f3050dc 100644 --- a/src/main/scala/millfork/compiler/ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/ExpressionCompiler.scala @@ -46,8 +46,12 @@ object ExpressionCompiler { case FunctionCallExpression("|", params) => b case FunctionCallExpression("&", params) => b case FunctionCallExpression("^", params) => b - case FunctionCallExpression("<<", params) => b - case FunctionCallExpression(">>", params) => b + case FunctionCallExpression("<<", List(a1, a2)) => + if (getExpressionType(ctx, a2).size > 1) ErrorReporting.error("Shift amount too large", a2.position) + getExpressionType(ctx, a1) + case FunctionCallExpression(">>", List(a1, a2)) => + if (getExpressionType(ctx, a2).size > 1) ErrorReporting.error("Shift amount too large", a2.position) + getExpressionType(ctx, a1) case FunctionCallExpression("<<'", params) => b case FunctionCallExpression(">>'", params) => b case FunctionCallExpression(">>>>", params) => b @@ -771,13 +775,27 @@ object ExpressionCompiler { val (l, r, 1) = assertBinary(ctx, params) BuiltIns.compileNonetLeftShift(ctx, l, r) case "<<" => - assertAllBytes("Long shift ops not supported", ctx, params) - val (l, r, 1) = assertBinary(ctx, params) - BuiltIns.compileShiftOps(ASL, ctx, l, r) + val (l, r, size) = assertBinary(ctx, params) + size match { + case 1 => + BuiltIns.compileShiftOps(ASL, ctx, l, r) + case 2 => + PseudoregisterBuiltIns.compileWordShiftOps(left = true, ctx, l, r) + case _ => + ErrorReporting.error("Long shift ops not supported", l.position) + Nil + } case ">>" => - assertAllBytes("Long shift ops not supported", ctx, params) - val (l, r, 1) = assertBinary(ctx, params) - BuiltIns.compileShiftOps(LSR, ctx, l, r) + val (l, r, size) = assertBinary(ctx, params) + size match { + case 1 => + BuiltIns.compileShiftOps(LSR, ctx, l, r) + case 2 => + PseudoregisterBuiltIns.compileWordShiftOps(left = false, ctx, l, r) + case _ => + ErrorReporting.error("Long shift ops not supported", l.position) + Nil + } case "<<'" => assertAllBytes("Long shift ops not supported", ctx, params) val (l, r, 1) = assertBinary(ctx, params) diff --git a/src/main/scala/millfork/compiler/MfCompiler.scala b/src/main/scala/millfork/compiler/MfCompiler.scala index 833fcf22..8a0a4529 100644 --- a/src/main/scala/millfork/compiler/MfCompiler.scala +++ b/src/main/scala/millfork/compiler/MfCompiler.scala @@ -26,37 +26,63 @@ object MfCompiler { def compile(ctx: CompilationContext): List[AssemblyLine] = { ctx.env.nameCheck(ctx.function.code) val chunk = StatementCompiler.compile(ctx, ctx.function.code) + + val phReg = + if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + val reg = ctx.env.get[VariableInMemory]("__reg") + List( + AssemblyLine.zeropage(LDA, reg), + AssemblyLine.implied(PHA), + AssemblyLine.zeropage(LDA, reg, 1), + AssemblyLine.implied(PHA) + ) + } else Nil + val prefix = (if (ctx.function.interrupt) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { - List( - AssemblyLine.implied(PHB), - AssemblyLine.implied(PHD), - AssemblyLine.immediate(REP, 0x30), - AssemblyLine.implied(PHA), - AssemblyLine.implied(PHX), - AssemblyLine.implied(PHY), - AssemblyLine.immediate(SEP, 0x30)) + if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + 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)) + } else { + List( + AssemblyLine.implied(PHB), + AssemblyLine.implied(PHD), + AssemblyLine.immediate(REP, 0x30), + AssemblyLine.implied(PHA), + AssemblyLine.implied(PHX), + AssemblyLine.implied(PHY), + AssemblyLine.immediate(SEP, 0x30)) + } } else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { List( AssemblyLine.implied(PHB), AssemblyLine.implied(PHD), AssemblyLine.implied(PHA), AssemblyLine.implied(PHX), - AssemblyLine.implied(PHY)) + AssemblyLine.implied(PHY)) ++ phReg } else if (ctx.options.flag(CompilationFlag.Emit65CE02Opcodes)) { List( AssemblyLine.implied(PHA), AssemblyLine.implied(PHX), AssemblyLine.implied(PHY), AssemblyLine.implied(PHZ), - AssemblyLine.implied(CLD)) + AssemblyLine.implied(CLD)) ++ phReg } else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { List( AssemblyLine.implied(PHA), AssemblyLine.implied(PHX), AssemblyLine.implied(PHY), - AssemblyLine.implied(CLD)) + AssemblyLine.implied(CLD)) ++ phReg } else { List( AssemblyLine.implied(PHA), @@ -64,8 +90,16 @@ object MfCompiler { AssemblyLine.implied(PHA), AssemblyLine.implied(TYA), AssemblyLine.implied(PHA), - AssemblyLine.implied(CLD)) + AssemblyLine.implied(CLD)) ++ phReg } + } else if (ctx.function.kernalInterrupt && ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + List( + AssemblyLine.accu16, + AssemblyLine.zeropage(LDA_W, ctx.env.get[VariableInMemory]("__reg")), + AssemblyLine.implied(PHA_W), + AssemblyLine.accu8) + } else phReg } else Nil) ++ stackPointerFixAtBeginning(ctx) val label = AssemblyLine.label(Label(ctx.function.name)).copy(elidable = false) label :: (prefix ++ chunk) diff --git a/src/main/scala/millfork/compiler/PseudoregisterBuiltIns.scala b/src/main/scala/millfork/compiler/PseudoregisterBuiltIns.scala new file mode 100644 index 00000000..6bf0648f --- /dev/null +++ b/src/main/scala/millfork/compiler/PseudoregisterBuiltIns.scala @@ -0,0 +1,115 @@ +package millfork.compiler + +import millfork.CompilationFlag +import millfork.assembly.AssemblyLine +import millfork.env._ +import millfork.error.ErrorReporting +import millfork.node._ +import millfork.assembly.Opcode +import millfork.assembly.Opcode._ +import millfork.assembly.AddrMode._ + +/** + * @author Karol Stasiak + */ +object PseudoregisterBuiltIns { + + def compileWordShiftOps(left: Boolean, ctx: CompilationContext, l: Expression, r: Expression): List[AssemblyLine] = { + if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + ErrorReporting.error("Word shifting requires the zeropage pseudoregister", l.position) + return Nil + } + val b = ctx.env.get[Type]("byte") + val w = ctx.env.get[Type]("word") + val reg = ctx.env.get[VariableInMemory]("__reg") + val firstParamCompiled = ExpressionCompiler.compile(ctx, l, Some(w -> reg), NoBranching) + ctx.env.eval(r) match { + case Some(NumericConstant(0, _)) => + Nil + case Some(NumericConstant(v, _)) if v > 0 => + if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + firstParamCompiled ++ + List(AssemblyLine.accu16) ++ + List.fill(v.toInt)(if (left) AssemblyLine.zeropage(ASL, reg) else AssemblyLine.zeropage(LSR, reg)) ++ + List(AssemblyLine.accu8, AssemblyLine.zeropage(LDA, reg), AssemblyLine.zeropage(LDX, reg, 1)) + } else { + val cycle = + if (left) List(AssemblyLine.zeropage(ASL, reg), AssemblyLine.zeropage(ROL, reg, 1)) + else List(AssemblyLine.zeropage(LSR, reg, 1), AssemblyLine.zeropage(ROR, reg)) + firstParamCompiled ++ List.fill(v.toInt)(cycle).flatten ++ List(AssemblyLine.zeropage(LDA, reg), AssemblyLine.zeropage(LDX, reg, 1)) + } + case _ => + ErrorReporting.error("Cannot shift by a non-constant amount") + Nil + } + } + + def compileByteMultiplication(ctx: CompilationContext, param1OrRegister: Option[Expression], param2: Expression, storeInRegLo: Boolean): List[AssemblyLine] = { + if (!ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + ErrorReporting.error("Variable byte multiplication requires the zeropage pseudoregister", param1OrRegister.flatMap(_.position)) + return Nil + } + val b = ctx.env.get[Type]("byte") + val w = ctx.env.get[Type]("word") + val reg = ctx.env.get[VariableInMemory]("__reg") + val load: List[AssemblyLine] = param1OrRegister match { + case Some(param1) => + val code1 = ExpressionCompiler.compile(ctx, param1, Some(b -> RegisterVariable(Register.A, b)), BranchSpec.None) + val code2 = ExpressionCompiler.compile(ctx, param2, Some(b -> RegisterVariable(Register.A, b)), BranchSpec.None) + if (!usesRegLo(code2)) { + code1 ++ List(AssemblyLine.zeropage(STA, reg)) ++ code2 ++ List(AssemblyLine.zeropage(STA, reg, 1)) + } else if (!usesRegLo(code1)) { + code2 ++ List(AssemblyLine.zeropage(STA, reg)) ++ code1 ++ List(AssemblyLine.zeropage(STA, reg, 1)) + } else { + code1 ++ List(AssemblyLine.implied(PHA)) ++ code2 ++ List( + AssemblyLine.zeropage(STA, reg), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, 1) + ) + } + case None => + val code2 = ExpressionCompiler.compile(ctx, param2, Some(b -> RegisterVariable(Register.A, b)), BranchSpec.None) + if (!usesRegLo(code2)) { + List(AssemblyLine.zeropage(STA, reg)) ++ code2 ++ List(AssemblyLine.zeropage(STA, reg, 1)) + } else if (!usesRegHi(code2)) { + List(AssemblyLine.zeropage(STA, reg, 1)) ++ code2 ++ List(AssemblyLine.zeropage(STA, reg)) + } else { + List(AssemblyLine.implied(PHA)) ++ code2 ++ List( + AssemblyLine.zeropage(STA, reg), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, 1) + ) + } + } + val calculate = AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[FunctionInMemory]("__mul_u8u8u8"), ctx.options) :: + (if (storeInRegLo) List(AssemblyLine.zeropage(STA, reg)) else Nil) + load ++ calculate + } + + private def simplicity(env: Environment, expr: Expression): Char = { + val constPart = env.eval(expr) match { + case Some(NumericConstant(_, _)) => 'Z' + case Some(_) => 'Y' + case None => expr match { + case VariableExpression(_) => 'V' + case IndexedExpression(_, LiteralExpression(_, _)) => 'K' + case IndexedExpression(_, VariableExpression(_)) => 'J' + case IndexedExpression(_, _) => 'I' + case _ => 'A' + } + } + constPart + } + + def usesRegLo(code: List[AssemblyLine]): Boolean = code.forall{ + case AssemblyLine(JSR | BSR | TCD | TDC, _, _, _) => true + case AssemblyLine(_, _, MemoryAddressConstant(th), _) if th.name == "__reg" => true + case _ => false + } + + def usesRegHi(code: List[AssemblyLine]): Boolean = code.forall{ + case AssemblyLine(JSR | BSR | TCD | TDC, _, _, _) => true + case AssemblyLine(_, _, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _)), _) if th.name == "__reg" => true + case _ => false + } +} diff --git a/src/main/scala/millfork/compiler/StatementCompiler.scala b/src/main/scala/millfork/compiler/StatementCompiler.scala index d284503b..3280d90f 100644 --- a/src/main/scala/millfork/compiler/StatementCompiler.scala +++ b/src/main/scala/millfork/compiler/StatementCompiler.scala @@ -31,22 +31,45 @@ object StatementCompiler { 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 reg = env.get[VariableInMemory]("__reg") + List( + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, 1), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg) + ) + } else Nil val someRegisterA = Some(b, RegisterVariable(Register.A, b)) val someRegisterAX = Some(w, RegisterVariable(Register.AX, w)) val someRegisterYA = Some(w, RegisterVariable(Register.YA, w)) val returnInstructions = if (m.interrupt) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { - List( - AssemblyLine.immediate(REP, 0x30), - AssemblyLine.implied(PLY), - AssemblyLine.implied(PLX), - AssemblyLine.implied(PLA), - AssemblyLine.implied(PLD), - AssemblyLine.implied(PLB), - AssemblyLine.implied(RTI)) + 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)) + } else { + List( + AssemblyLine.immediate(REP, 0x30), + AssemblyLine.implied(PLY), + AssemblyLine.implied(PLX), + AssemblyLine.implied(PLA), + AssemblyLine.implied(PLD), + AssemblyLine.implied(PLB), + AssemblyLine.implied(RTI)) + } } else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { - List( + plReg ++ List( AssemblyLine.implied(PLY), AssemblyLine.implied(PLX), AssemblyLine.implied(PLA), @@ -54,20 +77,20 @@ object StatementCompiler { AssemblyLine.implied(PLB), AssemblyLine.implied(RTI)) } else if (ctx.options.flag(CompilationFlag.Emit65CE02Opcodes)) { - List( + plReg ++ List( AssemblyLine.implied(PLZ), AssemblyLine.implied(PLY), AssemblyLine.implied(PLX), AssemblyLine.implied(PLA), AssemblyLine.implied(RTI)) } else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { - List( + plReg ++ List( AssemblyLine.implied(PLY), AssemblyLine.implied(PLX), AssemblyLine.implied(PLA), AssemblyLine.implied(RTI)) } else { - List( + plReg ++ List( AssemblyLine.implied(PLA), AssemblyLine.implied(TAY), AssemblyLine.implied(PLA), @@ -75,10 +98,20 @@ object StatementCompiler { AssemblyLine.implied(PLA), AssemblyLine.implied(RTI)) } - } else if (m.isFar(ctx.options)) { - List(AssemblyLine.implied(RTL)) } else { - List(AssemblyLine.implied(RTS)) + (if (m.kernalInterrupt && ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) { + if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + List( + AssemblyLine.accu16, + AssemblyLine.implied(PLA_W), + AssemblyLine.zeropage(STA_W, env.get[VariableInMemory]("__reg")), + AssemblyLine.accu8) + } else plReg + } else Nil) ++ (if (m.isFar(ctx.options)) { + List(AssemblyLine.implied(RTL)) + } else { + List(AssemblyLine.implied(RTS)) + }) } statement match { case AssemblyStatement(o, a, x, e) => diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 62162205..7d6a236d 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -474,6 +474,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { stmt.address.map(a => this.eval(a).getOrElse(Constant.error(s"Address of `${stmt.name}` is not a constant"))), executableStatements ++ (if (needsExtraRTS) List(ReturnStatement(None)) else Nil), interrupt = stmt.interrupt, + kernalInterrupt = stmt.kernalInterrupt, reentrant = stmt.reentrant, position = stmt.position ) @@ -732,6 +733,18 @@ class Environment(val parent: Option[Environment], val prefix: String) { case a: ArrayDeclarationStatement => registerArray(a) case i: ImportStatement => () } + if (options.flag(CompilationFlag.ZeropagePseudoregister) && !things.contains("__reg")) { + registerVariable(VariableDeclarationStatement( + name = "__reg", + typ = "pointer", + global = true, + stack = false, + constant = false, + volatile = false, + register = false, + initialValue = None, + address = None), options) + } if (!things.contains("__constant8")) { things("__constant8") = InitializedArray("__constant8", None, List(NumericConstant(8, 1))) } diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 70297fdc..b9fd0bc4 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -253,6 +253,7 @@ case class NormalFunction(name: String, address: Option[Constant], code: List[ExecutableStatement], interrupt: Boolean, + kernalInterrupt: Boolean, reentrant: Boolean, position: Option[Position]) extends FunctionInMemory with PreallocableThing { override def shouldGenerate = true diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 43933ef3..75af0773 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -126,6 +126,7 @@ case class FunctionDeclarationStatement(name: String, inlinable: Option[Boolean], assembly: Boolean, interrupt: Boolean, + kernalInterrupt: Boolean, reentrant: Boolean) extends DeclarationStatement { override def getAllExpressions: List[Expression] = address.toList ++ statements.getOrElse(Nil).flatMap(_.getAllExpressions) } diff --git a/src/main/scala/millfork/node/opt/UnusedFunctions.scala b/src/main/scala/millfork/node/opt/UnusedFunctions.scala index f4f9d23d..f5637b66 100644 --- a/src/main/scala/millfork/node/opt/UnusedFunctions.scala +++ b/src/main/scala/millfork/node/opt/UnusedFunctions.scala @@ -17,7 +17,10 @@ object UnusedFunctions extends NodeOptimization { case _ => Nil }.toSet val allCalledFunctions = getAllCalledFunctions(nodes).toSet - val unusedFunctions = allNormalFunctions -- allCalledFunctions + var unusedFunctions = allNormalFunctions -- allCalledFunctions + if (allCalledFunctions.contains("*") && options.flag(CompilationFlag.ZeropagePseudoregister)) { + unusedFunctions -= "__mul_u8u8u8" + } if (unusedFunctions.nonEmpty) { ErrorReporting.debug("Removing unused functions: " + unusedFunctions.mkString(", ")) optimize(removeFunctionsFromProgram(nodes, unusedFunctions), options) diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index 97c733a8..91956ad9 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -456,7 +456,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def functionDefinition: P[DeclarationStatement] = for { p <- position() - flags <- flags("asm", "inline", "interrupt", "macro", "noinline", "reentrant") ~ HWS + flags <- flags("asm", "inline", "interrupt", "macro", "noinline", "reentrant", "kernal_interrupt") ~ HWS returnType <- identifier ~ SWS name <- identifier ~ HWS params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS @@ -464,7 +464,9 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass } yield { if (flags("interrupt") && flags("macro")) ErrorReporting.error(s"Interrupt function `$name` cannot be macros", Some(p)) + if (flags("kernal_interrupt") && flags("macro")) ErrorReporting.error(s"Kernal interrupt function `$name` cannot be macros", Some(p)) if (flags("interrupt") && flags("reentrant")) ErrorReporting.error("Interrupt function `$name` cannot be reentrant", Some(p)) + if (flags("interrupt") && flags("kernal_interrupt")) ErrorReporting.error("Interrupt function `$name` cannot be a Kernal interrupt", Some(p)) if (flags("macro") && flags("reentrant")) ErrorReporting.error("Reentrant and macro exclude each other", Some(p)) if (flags("inline") && flags("noinline")) ErrorReporting.error("Noinline and inline exclude each other", Some(p)) if (flags("macro") && flags("noinline")) ErrorReporting.error("Noinline and macro exclude each other", Some(p)) @@ -505,6 +507,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None, flags("asm"), flags("interrupt"), + flags("kernal_interrupt"), flags("reentrant")).pos(p) } diff --git a/src/main/scala/millfork/parser/SourceLoadingQueue.scala b/src/main/scala/millfork/parser/SourceLoadingQueue.scala index fc0d3d10..9d98168d 100644 --- a/src/main/scala/millfork/parser/SourceLoadingQueue.scala +++ b/src/main/scala/millfork/parser/SourceLoadingQueue.scala @@ -3,7 +3,7 @@ package millfork.parser import java.nio.file.{Files, Paths} import fastparse.core.Parsed.{Failure, Success} -import millfork.CompilationOptions +import millfork.{CompilationFlag, CompilationOptions} import millfork.error.ErrorReporting import millfork.node.{ImportStatement, Position, Program} @@ -26,6 +26,9 @@ class SourceLoadingQueue(val initialFilenames: List[String], val includePath: Li options.platform.startingModules.foreach {m => moduleQueue.enqueue(() => parseModule(m, includePath, Left(None), options)) } + if (options.flag(CompilationFlag.ZeropagePseudoregister)) { + moduleQueue.enqueue(() => parseModule("zp_reg", includePath, Left(None), options)) + } while (moduleQueue.nonEmpty) { moduleQueue.dequeueAll(_ => true).par.foreach(_()) } diff --git a/src/test/scala/millfork/test/ByteMathSuite.scala b/src/test/scala/millfork/test/ByteMathSuite.scala index 067743e2..9e2754e8 100644 --- a/src/test/scala/millfork/test/ByteMathSuite.scala +++ b/src/test/scala/millfork/test/ByteMathSuite.scala @@ -1,6 +1,6 @@ package millfork.test -import millfork.test.emu.EmuBenchmarkRun +import millfork.test.emu.{EmuBenchmarkRun, EmuUltraBenchmarkRun} import org.scalatest.{FunSuite, Matchers} /** @@ -155,4 +155,80 @@ class ByteMathSuite extends FunSuite with Matchers { """. stripMargin)(_.readByte(0xc000) should equal(x * y)) } + + test("Byte multiplication 2") { + EmuUltraBenchmarkRun( + """ + | import zp_reg + | byte output1 @$c001 + | byte output2 @$c002 + | void main () { + | calc1() + | crash_if_bad() + | calc2() + | crash_if_bad() + | calc3() + | crash_if_bad() + | } + | + | byte three() { return 3 } + | byte four() { return 4 } + | noinline byte five() { return 5 } + | + | noinline void calc1() { + | output1 = five() * four() + | output2 = 3 * three() * three() + | } + | + | noinline void calc2() { + | output2 = 3 * three() * three() + | output1 = five() * four() + | } + | + | noinline void calc3() { + | output2 = 3 * three() * three() + | output1 = four() * five() + | } + | + | noinline void crash_if_bad() { + | if output1 != 20 { asm { lda $bfff }} + | if output2 != 27 { asm { lda $bfff }} + | } + """.stripMargin){m => + m.readByte(0xc002) should equal(27) + m.readByte(0xc001) should equal(20) + } + } + + test("Byte multiplication 3") { + multiplyCase3(0, 0) + multiplyCase3(0, 1) + multiplyCase3(0, 2) + multiplyCase3(0, 5) + multiplyCase3(1, 0) + multiplyCase3(5, 0) + multiplyCase3(7, 0) + multiplyCase3(2, 5) + multiplyCase3(7, 2) + multiplyCase3(100, 2) + multiplyCase3(54, 4) + multiplyCase3(2, 100) + multiplyCase3(4, 54) + } + + private def multiplyCase3(x: Int, y: Int): Unit = { + EmuBenchmarkRun( + s""" + | import zp_reg + | byte output @$$c000 + | void main () { + | byte a + | a = f() + | output = a * g() + | } + | byte f() {return $x} + | byte g() {return $y} + """. + stripMargin)(_.readByte(0xc000) should equal(x * y)) + } } diff --git a/src/test/scala/millfork/test/ShiftSuite.scala b/src/test/scala/millfork/test/ShiftSuite.scala index 0c9857fa..e93701fc 100644 --- a/src/test/scala/millfork/test/ShiftSuite.scala +++ b/src/test/scala/millfork/test/ShiftSuite.scala @@ -60,4 +60,15 @@ class ShiftSuite extends FunSuite with Matchers { | } """.stripMargin)(_.readLong(0xc000) should equal(0x1010301)) } + + test("Word shifting via pseudoregister") { + EmuBenchmarkRun(""" + | word output @$c000 + | void main () { + | output = identity(three() << 7) + | } + | word three() { return 3 } + | word identity(word w) { return w } + """.stripMargin)(_.readWord(0xc000) should equal(0x180)) + } } diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala index 91aed3e5..17ce76a1 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedCmosRun.scala @@ -1,6 +1,6 @@ package millfork.test.emu -import millfork.assembly.opt.CmosOptimizations +import millfork.assembly.opt.{CmosOptimizations, ZeropageRegisterOptimizations} import millfork.{Cpu, OptimizationPresets} /** @@ -10,8 +10,10 @@ object EmuOptimizedCmosRun extends EmuRun( Cpu.Cmos, OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ + ZeropageRegisterOptimizations.All ++ CmosOptimizations.All ++ OptimizationPresets.Good ++ CmosOptimizations.All ++ OptimizationPresets.Good ++ + ZeropageRegisterOptimizations.All ++ CmosOptimizations.All ++ OptimizationPresets.Good, false) diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala index 03ca0d3c..31dd40af 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedInlinedRun.scala @@ -1,6 +1,6 @@ package millfork.test.emu -import millfork.assembly.opt.LaterOptimizations +import millfork.assembly.opt.{LaterOptimizations, ZeropageRegisterOptimizations} import millfork.{Cpu, OptimizationPresets} /** @@ -10,8 +10,10 @@ object EmuOptimizedInlinedRun extends EmuRun( Cpu.StrictMos, OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ + ZeropageRegisterOptimizations.All ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ + ZeropageRegisterOptimizations.All ++ OptimizationPresets.Good, false) { override def inline: Boolean = true diff --git a/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala b/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala index 3c6f8dc0..e4632471 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimizedRun.scala @@ -1,6 +1,6 @@ package millfork.test.emu -import millfork.assembly.opt.LaterOptimizations +import millfork.assembly.opt.{LaterOptimizations, ZeropageRegisterOptimizations} import millfork.{Cpu, OptimizationPresets} /** @@ -10,8 +10,10 @@ object EmuOptimizedRun extends EmuRun( Cpu.StrictMos, OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ + ZeropageRegisterOptimizations.All ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ + ZeropageRegisterOptimizations.All ++ OptimizationPresets.Good, false) diff --git a/src/test/scala/millfork/test/emu/EmuQuantumOptimizedRun.scala b/src/test/scala/millfork/test/emu/EmuQuantumOptimizedRun.scala index dbdf4007..731956bf 100644 --- a/src/test/scala/millfork/test/emu/EmuQuantumOptimizedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuQuantumOptimizedRun.scala @@ -1,6 +1,6 @@ package millfork.test.emu -import millfork.assembly.opt.LaterOptimizations +import millfork.assembly.opt.{LaterOptimizations, ZeropageRegisterOptimizations} import millfork.{Cpu, OptimizationPresets} /** @@ -10,8 +10,10 @@ object EmuQuantumOptimizedRun extends EmuRun( Cpu.StrictMos, OptimizationPresets.NodeOpt, OptimizationPresets.AssOpt ++ + ZeropageRegisterOptimizations.All ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ + ZeropageRegisterOptimizations.All ++ OptimizationPresets.Good, true) diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 3daaa33b..aff9be38 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -1,5 +1,8 @@ package millfork.test.emu +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Paths} + import com.grapeshot.halfnes.{CPU, CPURAM} import com.loomcom.symon.InstructionTable.CpuBehavior import com.loomcom.symon.{Bus, Cpu, CpuState} @@ -14,6 +17,7 @@ import millfork.output.{Assembler, MemoryBank} import millfork.parser.MfParser import millfork.{CompilationFlag, CompilationOptions} import org.scalatest.Matchers +import scala.collection.JavaConverters._ /** * @author Karol Stasiak @@ -97,6 +101,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], CompilationFlag.DetailedFlowAnalysis -> quantum, CompilationFlag.InlineFunctions -> this.inline, 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), @@ -106,8 +111,12 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], )) ErrorReporting.hasErrors = false ErrorReporting.verbosity = 999 - val sourceWithPanic = if (source.contains("_panic")) source else source + "\n void _panic(){while(true){}}" - val parserF = MfParser("", sourceWithPanic, "", options) + 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 = MfParser("", effectiveSource, "", options) parserF.toAst match { case Success(unoptimized, _) => ErrorReporting.assertNoErrors("Parse failed")