mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-11 12:29:46 +00:00
Use zero page locations as as pseudo-registers; 8-bit multiplication and 16-bit shifts
This commit is contained in:
parent
656dbef184
commit
15dbaad6d1
@ -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`.
|
||||
|
@ -8,16 +8,26 @@ Syntax:
|
||||
|
||||
* `<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)
|
||||
|
||||
* `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.
|
||||
|
||||
* `<return_type>` is a valid return type, see [Types](./types.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`
|
||||
|
||||
|
15
include/zp_reg.mfk
Normal file
15
include/zp_reg.mfk
Normal 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
|
||||
// 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,
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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, _)) =>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
115
src/main/scala/millfork/compiler/PseudoregisterBuiltIns.scala
Normal file
115
src/main/scala/millfork/compiler/PseudoregisterBuiltIns.scala
Normal file
@ -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 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) =>
|
||||
|
13
src/main/scala/millfork/env/Environment.scala
vendored
13
src/main/scala/millfork/env/Environment.scala
vendored
@ -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)))
|
||||
}
|
||||
|
1
src/main/scala/millfork/env/Thing.scala
vendored
1
src/main/scala/millfork/env/Thing.scala
vendored
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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(_())
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user