1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-30 06:29:31 +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

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.
`.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`.

View File

@ -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)

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.
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
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
}

View File

@ -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,

View File

@ -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.")

View File

@ -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)

View File

@ -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,

View File

@ -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,
)
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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)

View 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
}
}

View File

@ -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) =>

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"))),
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)))
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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(_())
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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")