1
0
mirror of https://github.com/KarolS/millfork.git synced 2026-04-25 19:17:54 +00:00

Use zero page locations as as pseudo-registers; 8-bit multiplication and 16-bit shifts

This commit is contained in:
Karol Stasiak
2018-03-05 12:05:37 +01:00
parent 656dbef184
commit 15dbaad6d1
27 changed files with 559 additions and 55 deletions
+5
View File
@@ -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. * `-fjmp-fix`, `-fno-jmp-fix` Whether should prevent indirect JMP bug on page boundary.
`.ini` equivalent: `prevent_jmp_indirect_bug`. `.ini` equivalent: `prevent_jmp_indirect_bug`.
Default: no if targeting a 65C02-compatible architecture, yes otherwise. 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. * `-fdecimal-mode`, `-fno-decimal-mode` Whether decimal mode should be available.
`.ini` equivalent: `decimal_mode`. `.ini` equivalent: `decimal_mode`.
+13 -3
View File
@@ -8,16 +8,26 @@ Syntax:
* `<modifiers>`: zero or more of the following: * `<modifiers>`: 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) see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions)
* `macro` the function is a macro, * `macro` the function is a macro,
see [Macros_and inlining#Macros](../abi/inlining.md#macros) 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) 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.
* `<return_type>` is a valid return type, see [Types](./types.md) * `<return_type>` is a valid return type, see [Types](./types.md)
+7 -1
View File
@@ -6,6 +6,10 @@ Most expressions involving single bytes compile,
but for larger types usually you need to use in-place modification operators. 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. 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 ## Precedence
Millfork has different operator precedence compared to most other languages. From highest to lowest it goes: 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` `byte * constant byte`
`constant byte * byte` `constant byte * byte`
`constant word * constant word` `constant word * constant word`
`constant long * constant long` `constant long * constant long`
`byte * byte` (zpreg)
There are no division, remainder or modulo operators. 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 * `<<`, `>>`: bit shifting; shifting pads the result with zeroes
`byte << constant byte` `byte << constant byte`
`word << constant byte` (zpreg)
`constant word << constant byte` `constant word << constant byte`
`constant long << constant byte` `constant long << constant byte`
+15
View File
@@ -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
}
@@ -119,7 +119,7 @@ object CompilationFlag extends Enumeration {
val val
// compilation options: // compilation options:
EmitIllegals, EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, EmitIllegals, EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, ZeropagePseudoregister, DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator,
// optimization options: // optimization options:
DetailedFlowAnalysis, DangerousOptimizations, InlineFunctions, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed, DetailedFlowAnalysis, DangerousOptimizations, InlineFunctions, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed,
// memory allocation options // memory allocation options
@@ -138,6 +138,7 @@ object CompilationFlag extends Enumeration {
"emit_cmos" -> EmitCmosOpcodes, "emit_cmos" -> EmitCmosOpcodes,
"emit_65ce02" -> Emit65CE02Opcodes, "emit_65ce02" -> Emit65CE02Opcodes,
"emit_huc6280" -> EmitHudsonOpcodes, "emit_huc6280" -> EmitHudsonOpcodes,
"zeropage_register" -> ZeropagePseudoregister,
"decimal_mode" -> DecimalMode, "decimal_mode" -> DecimalMode,
"ro_arrays" -> ReadOnlyArrays, "ro_arrays" -> ReadOnlyArrays,
"ror_warn" -> RorWarning, "ror_warn" -> RorWarning,
+4
View File
@@ -98,6 +98,7 @@ object Main {
val goodExtras = List( val goodExtras = List(
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil, if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil,
if (options.flag(CompilationFlag.EmitNative65816Opcodes)) SixteenOptimizations.AllForNative else Nil, if (options.flag(CompilationFlag.EmitNative65816Opcodes)) SixteenOptimizations.AllForNative else Nil,
if (options.flag(CompilationFlag.ZeropagePseudoregister)) ZeropageRegisterOptimizations.All else Nil,
).flatten ).flatten
val extras = List( val extras = List(
if (options.flag(CompilationFlag.EmitIllegals)) UndocumentedOptimizations.All else Nil, if (options.flag(CompilationFlag.EmitIllegals)) UndocumentedOptimizations.All else Nil,
@@ -228,6 +229,9 @@ object Main {
boolean("-fillegals", "-fno-illegals").action { (c, v) => boolean("-fillegals", "-fno-illegals").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitIllegals, v) c.changeFlag(CompilationFlag.EmitIllegals, v)
}.description("Whether should emit illegal (undocumented) NMOS opcodes. Requires -O2 or higher to have an effect.") }.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) => boolean("-fjmp-fix", "-fno-jmp-fix").action { (c, v) =>
c.changeFlag(CompilationFlag.PreventJmpIndirectBug, v) c.changeFlag(CompilationFlag.PreventJmpIndirectBug, v)
}.description("Whether should prevent indirect JMP bug on page boundary.") }.description("Whether should prevent indirect JMP bug on page boundary.")
@@ -777,6 +777,48 @@ case class HasOpcode(op: Opcode.Value) extends TrivialAssemblyLinePattern {
override def toString: String = op.toString 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 { case class HasOpcodeIn(ops: Set[Opcode.Value]) extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = override def apply(line: AssemblyLine): Boolean =
ops(line.opcode) ops(line.opcode)
@@ -24,6 +24,21 @@ object SuperOptimizer extends AssemblyOptimization {
} else { } else {
allOptimizers ++= LaterOptimizations.Nmos 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( allOptimizers ++= List(
VariableToRegisterOptimization, VariableToRegisterOptimization,
ChangeIndexRegisterOptimizationPreferringX2Y, ChangeIndexRegisterOptimizationPreferringX2Y,
@@ -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,
)
}
@@ -524,8 +524,7 @@ object BuiltIns {
case Some(NumericConstant(x, _)) => case Some(NumericConstant(x, _)) =>
compileByteMultiplication(ctx, v, x.toInt) ++ ExpressionCompiler.compileByteStorage(ctx, Register.A, v) compileByteMultiplication(ctx, v, x.toInt) ++ ExpressionCompiler.compileByteStorage(ctx, Register.A, v)
case _ => case _ =>
ErrorReporting.error("Multiplying by not a constant not supported", v.position) PseudoregisterBuiltIns.compileByteMultiplication(ctx, Some(v), addend, storeInRegLo = false) ++ ExpressionCompiler.compileByteStorage(ctx, Register.A, v)
Nil
} }
} }
@@ -554,15 +553,20 @@ object BuiltIns {
result.toList result.toList
} }
//noinspection ZeroIndexToHead
def compileByteMultiplication(ctx: CompilationContext, params: List[Expression]): List[AssemblyLine] = { 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 (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 val constant = constants.map(_._2.get.asInstanceOf[NumericConstant].value).foldLeft(1L)(_ * _).toInt
variables.length match { variables.length match {
case 0 => List(AssemblyLine.immediate(LDA, constant & 0xff)) 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 => case 2 =>
ErrorReporting.error("Multiplying by not a constant not supported", params.head.position) if (constant == 1)
Nil 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
} }
} }
@@ -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("^", params) => b
case FunctionCallExpression("<<", params) => b case FunctionCallExpression("<<", List(a1, a2)) =>
case FunctionCallExpression(">>", params) => b 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 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) val (l, r, 1) = assertBinary(ctx, params)
BuiltIns.compileNonetLeftShift(ctx, l, r) BuiltIns.compileNonetLeftShift(ctx, l, r)
case "<<" => case "<<" =>
assertAllBytes("Long shift ops not supported", ctx, params) val (l, r, size) = assertBinary(ctx, params)
val (l, r, 1) = assertBinary(ctx, params) size match {
BuiltIns.compileShiftOps(ASL, ctx, l, r) 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 ">>" => case ">>" =>
assertAllBytes("Long shift ops not supported", ctx, params) val (l, r, size) = assertBinary(ctx, params)
val (l, r, 1) = assertBinary(ctx, params) size match {
BuiltIns.compileShiftOps(LSR, ctx, l, r) 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 "<<'" => case "<<'" =>
assertAllBytes("Long shift ops not supported", ctx, params) assertAllBytes("Long shift ops not supported", ctx, params)
val (l, r, 1) = assertBinary(ctx, params) val (l, r, 1) = assertBinary(ctx, params)
@@ -26,37 +26,63 @@ object MfCompiler {
def compile(ctx: CompilationContext): List[AssemblyLine] = { def compile(ctx: CompilationContext): List[AssemblyLine] = {
ctx.env.nameCheck(ctx.function.code) ctx.env.nameCheck(ctx.function.code)
val chunk = StatementCompiler.compile(ctx, 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) { val prefix = (if (ctx.function.interrupt) {
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
List( if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) {
AssemblyLine.implied(PHB), List(
AssemblyLine.implied(PHD), AssemblyLine.implied(PHB),
AssemblyLine.immediate(REP, 0x30), AssemblyLine.implied(PHD),
AssemblyLine.implied(PHA), AssemblyLine.immediate(REP, 0x30),
AssemblyLine.implied(PHX), AssemblyLine.implied(PHA_W),
AssemblyLine.implied(PHY), AssemblyLine.implied(PHX_W),
AssemblyLine.immediate(SEP, 0x30)) 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)) { } else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
List( List(
AssemblyLine.implied(PHB), AssemblyLine.implied(PHB),
AssemblyLine.implied(PHD), AssemblyLine.implied(PHD),
AssemblyLine.implied(PHA), AssemblyLine.implied(PHA),
AssemblyLine.implied(PHX), AssemblyLine.implied(PHX),
AssemblyLine.implied(PHY)) AssemblyLine.implied(PHY)) ++ phReg
} else if (ctx.options.flag(CompilationFlag.Emit65CE02Opcodes)) { } else if (ctx.options.flag(CompilationFlag.Emit65CE02Opcodes)) {
List( List(
AssemblyLine.implied(PHA), AssemblyLine.implied(PHA),
AssemblyLine.implied(PHX), AssemblyLine.implied(PHX),
AssemblyLine.implied(PHY), AssemblyLine.implied(PHY),
AssemblyLine.implied(PHZ), AssemblyLine.implied(PHZ),
AssemblyLine.implied(CLD)) AssemblyLine.implied(CLD)) ++ phReg
} else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { } else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) {
List( List(
AssemblyLine.implied(PHA), AssemblyLine.implied(PHA),
AssemblyLine.implied(PHX), AssemblyLine.implied(PHX),
AssemblyLine.implied(PHY), AssemblyLine.implied(PHY),
AssemblyLine.implied(CLD)) AssemblyLine.implied(CLD)) ++ phReg
} else { } else {
List( List(
AssemblyLine.implied(PHA), AssemblyLine.implied(PHA),
@@ -64,8 +90,16 @@ object MfCompiler {
AssemblyLine.implied(PHA), AssemblyLine.implied(PHA),
AssemblyLine.implied(TYA), AssemblyLine.implied(TYA),
AssemblyLine.implied(PHA), 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) } else Nil) ++ stackPointerFixAtBeginning(ctx)
val label = AssemblyLine.label(Label(ctx.function.name)).copy(elidable = false) val label = AssemblyLine.label(Label(ctx.function.name)).copy(elidable = false)
label :: (prefix ++ chunk) label :: (prefix ++ chunk)
@@ -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
}
}
@@ -31,22 +31,45 @@ object StatementCompiler {
val m = ctx.function val m = ctx.function
val b = env.get[Type]("byte") val b = env.get[Type]("byte")
val w = env.get[Type]("word") 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 someRegisterA = Some(b, RegisterVariable(Register.A, b))
val someRegisterAX = Some(w, RegisterVariable(Register.AX, w)) val someRegisterAX = Some(w, RegisterVariable(Register.AX, w))
val someRegisterYA = Some(w, RegisterVariable(Register.YA, w)) val someRegisterYA = Some(w, RegisterVariable(Register.YA, w))
val returnInstructions = if (m.interrupt) { val returnInstructions = if (m.interrupt) {
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
List( if (ctx.options.flag(CompilationFlag.ZeropagePseudoregister)) {
AssemblyLine.immediate(REP, 0x30), List(
AssemblyLine.implied(PLY), AssemblyLine.immediate(REP, 0x30),
AssemblyLine.implied(PLX), AssemblyLine.implied(PLA_W),
AssemblyLine.implied(PLA), AssemblyLine.zeropage(STA_W, env.get[VariableInMemory]("__reg")),
AssemblyLine.implied(PLD), AssemblyLine.implied(PLY),
AssemblyLine.implied(PLB), AssemblyLine.implied(PLX),
AssemblyLine.implied(RTI)) 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 } else
if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) { if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
List( plReg ++ List(
AssemblyLine.implied(PLY), AssemblyLine.implied(PLY),
AssemblyLine.implied(PLX), AssemblyLine.implied(PLX),
AssemblyLine.implied(PLA), AssemblyLine.implied(PLA),
@@ -54,20 +77,20 @@ object StatementCompiler {
AssemblyLine.implied(PLB), AssemblyLine.implied(PLB),
AssemblyLine.implied(RTI)) AssemblyLine.implied(RTI))
} else if (ctx.options.flag(CompilationFlag.Emit65CE02Opcodes)) { } else if (ctx.options.flag(CompilationFlag.Emit65CE02Opcodes)) {
List( plReg ++ List(
AssemblyLine.implied(PLZ), AssemblyLine.implied(PLZ),
AssemblyLine.implied(PLY), AssemblyLine.implied(PLY),
AssemblyLine.implied(PLX), AssemblyLine.implied(PLX),
AssemblyLine.implied(PLA), AssemblyLine.implied(PLA),
AssemblyLine.implied(RTI)) AssemblyLine.implied(RTI))
} else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) { } else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) {
List( plReg ++ List(
AssemblyLine.implied(PLY), AssemblyLine.implied(PLY),
AssemblyLine.implied(PLX), AssemblyLine.implied(PLX),
AssemblyLine.implied(PLA), AssemblyLine.implied(PLA),
AssemblyLine.implied(RTI)) AssemblyLine.implied(RTI))
} else { } else {
List( plReg ++ List(
AssemblyLine.implied(PLA), AssemblyLine.implied(PLA),
AssemblyLine.implied(TAY), AssemblyLine.implied(TAY),
AssemblyLine.implied(PLA), AssemblyLine.implied(PLA),
@@ -75,10 +98,20 @@ object StatementCompiler {
AssemblyLine.implied(PLA), AssemblyLine.implied(PLA),
AssemblyLine.implied(RTI)) AssemblyLine.implied(RTI))
} }
} else if (m.isFar(ctx.options)) {
List(AssemblyLine.implied(RTL))
} else { } 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 { statement match {
case AssemblyStatement(o, a, x, e) => case AssemblyStatement(o, a, x, e) =>
+13
View File
@@ -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"))), 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), executableStatements ++ (if (needsExtraRTS) List(ReturnStatement(None)) else Nil),
interrupt = stmt.interrupt, interrupt = stmt.interrupt,
kernalInterrupt = stmt.kernalInterrupt,
reentrant = stmt.reentrant, reentrant = stmt.reentrant,
position = stmt.position position = stmt.position
) )
@@ -732,6 +733,18 @@ class Environment(val parent: Option[Environment], val prefix: String) {
case a: ArrayDeclarationStatement => registerArray(a) case a: ArrayDeclarationStatement => registerArray(a)
case i: ImportStatement => () 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")) { if (!things.contains("__constant8")) {
things("__constant8") = InitializedArray("__constant8", None, List(NumericConstant(8, 1))) things("__constant8") = InitializedArray("__constant8", None, List(NumericConstant(8, 1)))
} }
+1
View File
@@ -253,6 +253,7 @@ case class NormalFunction(name: String,
address: Option[Constant], address: Option[Constant],
code: List[ExecutableStatement], code: List[ExecutableStatement],
interrupt: Boolean, interrupt: Boolean,
kernalInterrupt: Boolean,
reentrant: Boolean, reentrant: Boolean,
position: Option[Position]) extends FunctionInMemory with PreallocableThing { position: Option[Position]) extends FunctionInMemory with PreallocableThing {
override def shouldGenerate = true override def shouldGenerate = true
+1
View File
@@ -126,6 +126,7 @@ case class FunctionDeclarationStatement(name: String,
inlinable: Option[Boolean], inlinable: Option[Boolean],
assembly: Boolean, assembly: Boolean,
interrupt: Boolean, interrupt: Boolean,
kernalInterrupt: Boolean,
reentrant: Boolean) extends DeclarationStatement { reentrant: Boolean) extends DeclarationStatement {
override def getAllExpressions: List[Expression] = address.toList ++ statements.getOrElse(Nil).flatMap(_.getAllExpressions) override def getAllExpressions: List[Expression] = address.toList ++ statements.getOrElse(Nil).flatMap(_.getAllExpressions)
} }
@@ -17,7 +17,10 @@ object UnusedFunctions extends NodeOptimization {
case _ => Nil case _ => Nil
}.toSet }.toSet
val allCalledFunctions = getAllCalledFunctions(nodes).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) { if (unusedFunctions.nonEmpty) {
ErrorReporting.debug("Removing unused functions: " + unusedFunctions.mkString(", ")) ErrorReporting.debug("Removing unused functions: " + unusedFunctions.mkString(", "))
optimize(removeFunctionsFromProgram(nodes, unusedFunctions), options) optimize(removeFunctionsFromProgram(nodes, unusedFunctions), options)
@@ -456,7 +456,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
def functionDefinition: P[DeclarationStatement] = for { def functionDefinition: P[DeclarationStatement] = for {
p <- position() 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 returnType <- identifier ~ SWS
name <- identifier ~ HWS name <- identifier ~ HWS
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS 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 statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass
} yield { } yield {
if (flags("interrupt") && flags("macro")) ErrorReporting.error(s"Interrupt function `$name` cannot be macros", Some(p)) 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("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("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("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)) 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, if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None,
flags("asm"), flags("asm"),
flags("interrupt"), flags("interrupt"),
flags("kernal_interrupt"),
flags("reentrant")).pos(p) flags("reentrant")).pos(p)
} }
@@ -3,7 +3,7 @@ package millfork.parser
import java.nio.file.{Files, Paths} import java.nio.file.{Files, Paths}
import fastparse.core.Parsed.{Failure, Success} import fastparse.core.Parsed.{Failure, Success}
import millfork.CompilationOptions import millfork.{CompilationFlag, CompilationOptions}
import millfork.error.ErrorReporting import millfork.error.ErrorReporting
import millfork.node.{ImportStatement, Position, Program} import millfork.node.{ImportStatement, Position, Program}
@@ -26,6 +26,9 @@ class SourceLoadingQueue(val initialFilenames: List[String], val includePath: Li
options.platform.startingModules.foreach {m => options.platform.startingModules.foreach {m =>
moduleQueue.enqueue(() => parseModule(m, includePath, Left(None), options)) 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) { while (moduleQueue.nonEmpty) {
moduleQueue.dequeueAll(_ => true).par.foreach(_()) moduleQueue.dequeueAll(_ => true).par.foreach(_())
} }
@@ -1,6 +1,6 @@
package millfork.test package millfork.test
import millfork.test.emu.EmuBenchmarkRun import millfork.test.emu.{EmuBenchmarkRun, EmuUltraBenchmarkRun}
import org.scalatest.{FunSuite, Matchers} import org.scalatest.{FunSuite, Matchers}
/** /**
@@ -155,4 +155,80 @@ class ByteMathSuite extends FunSuite with Matchers {
""". """.
stripMargin)(_.readByte(0xc000) should equal(x * y)) 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))
}
} }
@@ -60,4 +60,15 @@ class ShiftSuite extends FunSuite with Matchers {
| } | }
""".stripMargin)(_.readLong(0xc000) should equal(0x1010301)) """.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))
}
} }
@@ -1,6 +1,6 @@
package millfork.test.emu package millfork.test.emu
import millfork.assembly.opt.CmosOptimizations import millfork.assembly.opt.{CmosOptimizations, ZeropageRegisterOptimizations}
import millfork.{Cpu, OptimizationPresets} import millfork.{Cpu, OptimizationPresets}
/** /**
@@ -10,8 +10,10 @@ object EmuOptimizedCmosRun extends EmuRun(
Cpu.Cmos, Cpu.Cmos,
OptimizationPresets.NodeOpt, OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++ OptimizationPresets.AssOpt ++
ZeropageRegisterOptimizations.All ++
CmosOptimizations.All ++ OptimizationPresets.Good ++ CmosOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ OptimizationPresets.Good ++ CmosOptimizations.All ++ OptimizationPresets.Good ++
ZeropageRegisterOptimizations.All ++
CmosOptimizations.All ++ OptimizationPresets.Good, CmosOptimizations.All ++ OptimizationPresets.Good,
false) false)
@@ -1,6 +1,6 @@
package millfork.test.emu package millfork.test.emu
import millfork.assembly.opt.LaterOptimizations import millfork.assembly.opt.{LaterOptimizations, ZeropageRegisterOptimizations}
import millfork.{Cpu, OptimizationPresets} import millfork.{Cpu, OptimizationPresets}
/** /**
@@ -10,8 +10,10 @@ object EmuOptimizedInlinedRun extends EmuRun(
Cpu.StrictMos, Cpu.StrictMos,
OptimizationPresets.NodeOpt, OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++ OptimizationPresets.AssOpt ++
ZeropageRegisterOptimizations.All ++
OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++
OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++
ZeropageRegisterOptimizations.All ++
OptimizationPresets.Good, OptimizationPresets.Good,
false) { false) {
override def inline: Boolean = true override def inline: Boolean = true
@@ -1,6 +1,6 @@
package millfork.test.emu package millfork.test.emu
import millfork.assembly.opt.LaterOptimizations import millfork.assembly.opt.{LaterOptimizations, ZeropageRegisterOptimizations}
import millfork.{Cpu, OptimizationPresets} import millfork.{Cpu, OptimizationPresets}
/** /**
@@ -10,8 +10,10 @@ object EmuOptimizedRun extends EmuRun(
Cpu.StrictMos, Cpu.StrictMos,
OptimizationPresets.NodeOpt, OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++ OptimizationPresets.AssOpt ++
ZeropageRegisterOptimizations.All ++
OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++
OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++
ZeropageRegisterOptimizations.All ++
OptimizationPresets.Good, OptimizationPresets.Good,
false) false)
@@ -1,6 +1,6 @@
package millfork.test.emu package millfork.test.emu
import millfork.assembly.opt.LaterOptimizations import millfork.assembly.opt.{LaterOptimizations, ZeropageRegisterOptimizations}
import millfork.{Cpu, OptimizationPresets} import millfork.{Cpu, OptimizationPresets}
/** /**
@@ -10,8 +10,10 @@ object EmuQuantumOptimizedRun extends EmuRun(
Cpu.StrictMos, Cpu.StrictMos,
OptimizationPresets.NodeOpt, OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++ OptimizationPresets.AssOpt ++
ZeropageRegisterOptimizations.All ++
OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++
OptimizationPresets.Good ++ LaterOptimizations.Nmos ++ OptimizationPresets.Good ++ LaterOptimizations.Nmos ++
ZeropageRegisterOptimizations.All ++
OptimizationPresets.Good, OptimizationPresets.Good,
true) true)
+11 -2
View File
@@ -1,5 +1,8 @@
package millfork.test.emu package millfork.test.emu
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}
import com.grapeshot.halfnes.{CPU, CPURAM} import com.grapeshot.halfnes.{CPU, CPURAM}
import com.loomcom.symon.InstructionTable.CpuBehavior import com.loomcom.symon.InstructionTable.CpuBehavior
import com.loomcom.symon.{Bus, Cpu, CpuState} import com.loomcom.symon.{Bus, Cpu, CpuState}
@@ -14,6 +17,7 @@ import millfork.output.{Assembler, MemoryBank}
import millfork.parser.MfParser import millfork.parser.MfParser
import millfork.{CompilationFlag, CompilationOptions} import millfork.{CompilationFlag, CompilationOptions}
import org.scalatest.Matchers import org.scalatest.Matchers
import scala.collection.JavaConverters._
/** /**
* @author Karol Stasiak * @author Karol Stasiak
@@ -97,6 +101,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
CompilationFlag.DetailedFlowAnalysis -> quantum, CompilationFlag.DetailedFlowAnalysis -> quantum,
CompilationFlag.InlineFunctions -> this.inline, CompilationFlag.InlineFunctions -> this.inline,
CompilationFlag.CompactReturnDispatchParams -> true, CompilationFlag.CompactReturnDispatchParams -> true,
CompilationFlag.ZeropagePseudoregister -> true,
CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu), CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu),
CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen), CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen),
CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02), CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02),
@@ -106,8 +111,12 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
)) ))
ErrorReporting.hasErrors = false ErrorReporting.hasErrors = false
ErrorReporting.verbosity = 999 ErrorReporting.verbosity = 999
val sourceWithPanic = if (source.contains("_panic")) source else source + "\n void _panic(){while(true){}}" var effectiveSource = source
val parserF = MfParser("", sourceWithPanic, "", options) 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 { parserF.toAst match {
case Success(unoptimized, _) => case Success(unoptimized, _) =>
ErrorReporting.assertNoErrors("Parse failed") ErrorReporting.assertNoErrors("Parse failed")