1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-25 19:29:49 +00:00

Tons of things:

– changed `inline` to `macro`
– added support for parameters for macros written in Millfork
– added `inline`, `noinline`, `register` hints
– added <<<< operator
– pointer dereference expressions are now supported more widely
– C64 library fixes
– added `-O1` command line option as an alias for `-O`
This commit is contained in:
Karol Stasiak 2018-02-01 22:39:38 +01:00
parent c599db0068
commit 0ca1be0c00
29 changed files with 281 additions and 131 deletions

View File

@ -2,10 +2,20 @@
## Current version ## Current version
* **Breaking change!** Renamed `inline` to `macro`
* Added support for parameters for macros written in Millfork
* Added optimizer hints: `inline`, `noinline`, `register`
* Added `*'=` and `<<<<` operators
* Added return dispatch statements. * Added return dispatch statements.
* Fixed several optimization bugs. * Fixed several optimization bugs.
* Fixed several C64 library bugs.
* Other minor improvements. * Other minor improvements.
## 0.1 ## 0.1

View File

@ -1,9 +1,11 @@
# Function inlining # Macros and inlining
## Explicit inlining ## Macros
`inline` keyword `macro` keyword
## Automatic inlining ## Automatic inlining
`--inline` command-line option `--inline` command-line option
`inline` and `noinline` keyword

View File

@ -55,6 +55,8 @@ Some small automatic variables may be inlined to index registers.
They are not automatically initialized before reading, reading them before initialization yields an undefined value. They are not automatically initialized before reading, reading them before initialization yields an undefined value.
Automatic local variables are not safe to use with reentrant functions, see the [relevant documentation](../lang/reentrancy.md) for more details. Automatic local variables are not safe to use with reentrant functions, see the [relevant documentation](../lang/reentrancy.md) for more details.
Automatic variables defined with the `register` keyword will have the priority when it comes to register allocation.
## Parameters ## Parameters
Automatic variables have lifetime starting with the beginning Automatic variables have lifetime starting with the beginning

View File

@ -54,15 +54,15 @@ Currently there is no way to insert raw bytes into inline assembly
## Assembly functions ## Assembly functions
Assembly functions can be declared as `inline` or not. Assembly functions can be declared as `macro` or not.
An inline assembly function is inserted into the calling function like an inline assembly block, A macro assembly function is inserted into the calling function like an inline assembly block,
and therefore usually it shouldn't end with `RTS` or `RTI`. and therefore usually it shouldn't end with `RTS` or `RTI`.
A non-inline assembly function should end with `RTS`, `JMP` or `RTI` as appropriate, A non-macro assembly function should end with `RTS`, `JMP` or `RTI` as appropriate,
or it should be an external function. or it should be an external function.
For both inline and non-inline assembly functions, For both macro and non-macro assembly functions,
the return type can be any valid return type, like for Millfork functions. the return type can be any valid return type, like for Millfork functions.
If the size of the return type is one byte, If the size of the return type is one byte,
then the result is passed via the accumulator. then the result is passed via the accumulator.
@ -76,7 +76,7 @@ and the high byte of the result is passed via the X register.
An assembly function can have parameters. An assembly function can have parameters.
They differ from what is used by Millfork functions. They differ from what is used by Millfork functions.
Inline assembly functions can have the following parameter types: Macro assembly functions can have the following parameter types:
* reference parameters: `byte ref paramname`: every occurrence of the parameter will be replaced with the variable given as an argument * reference parameters: `byte ref paramname`: every occurrence of the parameter will be replaced with the variable given as an argument
@ -98,13 +98,13 @@ and call `increase(score, 10)`, the entire call will compile into:
ADC #10 ADC #10
STA score STA score
Non-inline functions can only have their parameters passed via registers: Non-macro functions can only have their parameters passed via registers:
* `byte a`, `byte x`, `byte y`: a single byte passed via the given CPU register * `byte a`, `byte x`, `byte y`: a single byte passed via the given CPU register
* `word xa`, `word ax`, `word ay`, `word ya`, `word xy`, `word yx`: a 2-byte word byte passed via given two CPU registers, with the high byte passed through the first register and the low byte passed through the second register * `word xa`, `word ax`, `word ay`, `word ya`, `word xy`, `word yx`: a 2-byte word byte passed via given two CPU registers, with the high byte passed through the first register and the low byte passed through the second register
Inline assembly functions can have maximum one parameter passed via a register. Macro assembly functions can have maximum one parameter passed via a register.
### External functions ### External functions

View File

@ -11,8 +11,11 @@ Syntax:
* `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 (doesn't matter 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)
* `inline` the function should be always inlined, * `macro` the function is a macro,
see [Function inlining#Explicit inlining](../abi/inlining.md#explicit-inlining) see [Macros_and inlining#Macros](../abi/inlining.md#macros)
* `inline` and `noinline` the function should preferably/should never be inlined
see [Macros_and inlining#Inlining](../abi/inlining.md#automatic_inlining.md)
* `interrupt` the function is a hardware interrupt handler * `interrupt` the function is a hardware interrupt handler

View File

@ -12,7 +12,7 @@ Millfork has different operator precedence compared to most other languages. Fro
* `*`, `*'` * `*`, `*'`
* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>` * `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>`, `<<<<`
* `:` * `:`
@ -87,6 +87,9 @@ There are no division, remainder or modulo operators.
* `>>>>`: shifting a 9-bit value and returning a byte; `a >>>> b` is equivalent to `(a & $1FF) >> b`, but the latter doesn't compile yet * `>>>>`: shifting a 9-bit value and returning a byte; `a >>>> b` is equivalent to `(a & $1FF) >> b`, but the latter doesn't compile yet
`word >>>> constant byte` `word >>>> constant byte`
* `>>>>`: shifting a byte and returning a 9-bit value; `a >>>> b` is equivalent to `(a << b) & 0x1ff` if there was no overflow, but the latter doesn't compile yet
`byte <<<< constant byte`
## Decimal arithmetic operators ## Decimal arithmetic operators
These operators work using the decimal arithmetic and will not work on Ricoh CPU's. These operators work using the decimal arithmetic and will not work on Ricoh CPU's.

View File

@ -20,7 +20,8 @@ Syntax:
`[<storage>] <type> <name> [@<address>] [= <initial_value>]` `[<storage>] <type> <name> [@<address>] [= <initial_value>]`
* `<storage>` can be only specified for local variables. It can be either `stack`, `static` or nothing. * `<storage>` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing.
`register` is only a hint for the optimizer.
See [the description of variable storage](../abi/variable-storage.md). See [the description of variable storage](../abi/variable-storage.md).
* `<address>` is a constant expression that defines where in the memory the variable will be located. * `<address>` is a constant expression that defines where in the memory the variable will be located.
@ -119,7 +120,7 @@ return [i] (param1, param2) {
Return dispatch calculates the value of an index, picks the correct branch, Return dispatch calculates the value of an index, picks the correct branch,
assigns some global variables and jumps to another function. assigns some global variables and jumps to another function.
The index has to evaluate to a byte. The functions cannot be `inline` and shouldn't have parameters. The index has to evaluate to a byte. The functions cannot be `macro` and shouldn't have parameters.
Jumping to a function with parameters gives those parameters undefined values. Jumping to a function with parameters gives those parameters undefined values.
The functions are not called, so they don't return to the function the return dispatch statement is in, but to its caller. The functions are not called, so they don't return to the function the return dispatch statement is in, but to its caller.

View File

@ -10,7 +10,7 @@ byte cia2_prb @$DD01
byte cia2_ddra @$DD02 byte cia2_ddra @$DD02
byte cia2_ddrb @$DD03 byte cia2_ddrb @$DD03
inline asm void cia_disable_irq() { macro asm void cia_disable_irq() {
LDA #$7f LDA #$7f
LDA $dc0d LDA $dc0d
LDA $dd0d LDA $dd0d
@ -19,22 +19,22 @@ inline asm void cia_disable_irq() {
} }
inline void vic_bank_0000() { macro void vic_bank_0000() {
cia2_ddra = $C0 cia2_ddra = 3
cia2_pra = $C0 cia2_pra = 3
} }
inline void vic_bank_4000() { macro void vic_bank_4000() {
cia2_ddra = $C0 cia2_ddra = 3
cia2_pra = $80 cia2_pra = 2
} }
inline void vic_bank_8000() { macro void vic_bank_8000() {
cia2_ddra = $C0 cia2_ddra = 3
cia2_pra = $40 cia2_pra = 1
} }
inline void vic_bank_C000() { macro void vic_bank_C000() {
cia2_ddra = $C0 cia2_ddra = 3
cia2_pra = $00 cia2_pra = 0
} }

View File

@ -5,37 +5,37 @@ import cpu6510
array c64_color_ram [1000] @$D800 array c64_color_ram [1000] @$D800
inline void c64_ram_only() { macro void c64_ram_only() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 0 cpu6510_port = 0
} }
inline void c64_ram_io() { macro void c64_ram_io() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 5 cpu6510_port = 5
} }
inline void c64_ram_io_kernal() { macro void c64_ram_io_kernal() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 6 cpu6510_port = 6
} }
inline void c64_ram_io_basic() { macro void c64_ram_io_basic() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 7 cpu6510_port = 7
} }
inline void c64_ram_charset() { macro void c64_ram_charset() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 1 cpu6510_port = 1
} }
inline void c64_ram_charset_kernal() { macro void c64_ram_charset_kernal() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 2 cpu6510_port = 2
} }
inline void c64_ram_charset_basic() { macro void c64_ram_charset_basic() {
cpu6510_ddr = 7 cpu6510_ddr = 7
cpu6510_port = 3 cpu6510_port = 3
} }

View File

@ -52,77 +52,77 @@ byte vic_spr7_color @$D02E
array vic_spr_coord [16] @$D000 array vic_spr_coord [16] @$D000
array vic_spr_color [8] @$D027 array vic_spr_color [8] @$D027
inline void vic_enable_multicolor() { macro void vic_enable_multicolor() {
vic_cr2 |= 0x10 vic_cr2 |= 0x10
} }
inline void vic_disable_multicolor() { macro void vic_disable_multicolor() {
vic_cr2 &= 0xEF vic_cr2 &= 0xEF
} }
inline void vic_enable_bitmap() { macro void vic_enable_bitmap() {
vic_cr1 |= 0x20 vic_cr1 |= 0x20
} }
inline void vic_disable_bitmap() { macro void vic_disable_bitmap() {
vic_cr1 &= 0xDF vic_cr1 &= 0xDF
} }
inline void vic_24_rows() { macro void vic_24_rows() {
vic_cr1 &= 0xF7 vic_cr1 &= 0xF7
} }
inline void vic_25_rows() { macro void vic_25_rows() {
vic_cr1 |= 8 vic_cr1 |= 8
} }
inline void vic_38_columns() { macro void vic_38_columns() {
vic_cr2 &= 0xF7 vic_cr2 &= 0xF7
} }
inline void vic_40_columns() { macro void vic_40_columns() {
vic_cr2 |= 8 vic_cr2 |= 8
} }
inline void vic_disable_irq() { macro void vic_disable_irq() {
vic_irq_ena = 0 vic_irq_ena = 0
vic_irq += 1 vic_irq += 1
} }
// base: divisible by $400, $0000-$3C00 allowed // base: divisible by $400, $0000-$3C00 allowed
//inline void vic_screen(word const base) { //macro void vic_screen(word const base) {
// vic_mem = (vic_mem & $0F) | (base >> 6) // vic_mem = (vic_mem & $0F) | (base >> 6)
//} //}
inline void vic_charset_0000() { macro void vic_charset_0000() {
vic_mem = (vic_mem & $F1) vic_mem = (vic_mem & $F1)
} }
inline void vic_charset_0800() { macro void vic_charset_0800() {
vic_mem = (vic_mem & $F1) | 2 vic_mem = (vic_mem & $F1) | 2
} }
inline void vic_charset_1000() { macro void vic_charset_1000() {
vic_mem = (vic_mem & $F1) | 4 vic_mem = (vic_mem & $F1) | 4
} }
inline void vic_charset_1800() { macro void vic_charset_1800() {
vic_mem = (vic_mem & $F1) | 6 vic_mem = (vic_mem & $F1) | 6
} }
inline void vic_charset_2000() { macro void vic_charset_2000() {
vic_mem = (vic_mem & $F1) | 8 vic_mem = (vic_mem & $F1) | 8
} }
inline void vic_charset_2800() { macro void vic_charset_2800() {
vic_mem = (vic_mem & $F1) | $A vic_mem = (vic_mem & $F1) | $A
} }
inline void vic_charset_3000() { macro void vic_charset_3000() {
vic_mem = (vic_mem & $F1) | $C vic_mem = (vic_mem & $F1) | $C
} }
inline void vic_charset_3800() { macro void vic_charset_3800() {
vic_mem = (vic_mem & $F1) | $E vic_mem = (vic_mem & $F1) | $E
} }
inline void vic_bitmap_0000() { macro void vic_bitmap_0000() {
vic_mem &= $F7 vic_mem &= $F7
} }
inline void vic_bitmap_2000() { macro void vic_bitmap_2000() {
vic_mem |= 8 vic_mem |= 8
} }

View File

@ -4,19 +4,19 @@ word nmi_routine_addr @$FFFA
word reset_routine_addr @$FFFC word reset_routine_addr @$FFFC
word irq_routine_addr @$FFFE word irq_routine_addr @$FFFE
inline asm void poke(word const addr, byte a) { macro asm void poke(word const addr, byte a) {
STA addr STA addr
} }
inline asm byte peek(word const addr) { macro asm byte peek(word const addr) {
?LDA addr ?LDA addr
} }
inline asm void disable_irq() { macro asm void disable_irq() {
SEI SEI
} }
inline asm void enable_irq() { macro asm void enable_irq() {
CLI CLI
} }
@ -39,7 +39,7 @@ _lo_nibble_to_hex_lbl:
RTS RTS
} }
inline asm void panic() { macro asm void panic() {
JSR _panic JSR _panic
} }

View File

@ -213,12 +213,12 @@ object Main {
assertNone(c.optimizationLevel, "Optimization level already defined") assertNone(c.optimizationLevel, "Optimization level already defined")
c.copy(optimizationLevel = Some(1)) c.copy(optimizationLevel = Some(1))
}.description("Optimize code.") }.description("Optimize code.")
for (i <- 2 to 9) { for (i <- 1 to 9) {
val f = flag("-O" + i).action { c => val f = flag("-O" + i).action { c =>
assertNone(c.optimizationLevel, "Optimization level already defined") assertNone(c.optimizationLevel, "Optimization level already defined")
c.copy(optimizationLevel = Some(i)) c.copy(optimizationLevel = Some(i))
}.description("Optimize code even more.") }.description("Optimize code even more.")
if (i > 3) f.hidden() if (i == 1 || i > 3) f.hidden()
} }
flag("--inline").action { c => flag("--inline").action { c =>
c.changeFlag(CompilationFlag.InlineFunctions, true) c.changeFlag(CompilationFlag.InlineFunctions, true)

View File

@ -74,10 +74,15 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
case _ => None case _ => None
}.toSet }.toSet
val localVariables = f.environment.getAllLocalVariables.filter { val localVariables = f.environment.getAllLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Auto) => case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case _ => false case _ => false
} }
val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter {
case MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name)
case _ => false
}.map(_.name).toSet
val variablesWithLifetimes = localVariables.map(v => val variablesWithLifetimes = localVariables.map(v =>
v.name -> VariableLifetime.apply(v.name, code) v.name -> VariableLifetime.apply(v.name, code)
@ -90,7 +95,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
importances(range.start).x != Important importances(range.start).x != Important
}.flatMap { }.flatMap {
case (vName, range) => case (vName, range) =>
canBeInlined(Some(vName), None, code.zip(importances).slice(range.start, range.end)).map(score => (vName, range, score)) canBeInlined(Some(vName), None, code.zip(importances).slice(range.start, range.end)).map { score =>
(vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score)
}
} }
val yCandidates = variablesWithLifetimes.filter { val yCandidates = variablesWithLifetimes.filter {
@ -98,7 +105,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
importances(range.start).y != Important importances(range.start).y != Important
}.flatMap { }.flatMap {
case (vName, range) => case (vName, range) =>
canBeInlined(None, Some(vName), code.zip(importances).slice(range.start, range.end)).map(score => (vName, range, score)) canBeInlined(None, Some(vName), code.zip(importances).slice(range.start, range.end)).map { score =>
(vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score)
}
} }
val aCandidates = variablesWithLifetimes.filter { val aCandidates = variablesWithLifetimes.filter {
@ -111,7 +120,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
start = true, start = true,
synced = false, synced = false,
vName, vName,
code.zip(importances).slice(range.start, range.end)).map(score => (vName, range, score)) code.zip(importances).slice(range.start, range.end)).map { score =>
(vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score)
}
} }
// println(s"X: $xCandidates") // println(s"X: $xCandidates")
// println(s"Y: $yCandidates") // println(s"Y: $yCandidates")

View File

@ -92,11 +92,14 @@ object BuiltIns {
case IndexChoice.PreferY => case IndexChoice.PreferY =>
val array = env.getArrayOrPointer(arrayName) val array = env.getArrayOrPointer(arrayName)
val calculateIndex = MfCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching) val calculateIndex = MfCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching)
val baseAddress = array match { array match {
case c: ConstantThing => c.value case c: ConstantThing =>
case a: MfArray => a.toAddress calculateIndex -> List(AssemblyLine.absoluteY(opcode, c.value))
case a: MfArray =>
calculateIndex -> List(AssemblyLine.absoluteY(opcode, a.toAddress))
case v: VariableInMemory if v.typ.name == "pointer" =>
calculateIndex -> List(AssemblyLine.indexedY(opcode, v.toAddress))
} }
calculateIndex -> List(AssemblyLine.absoluteY(opcode, baseAddress))
} }
case FunctionCallExpression(name, List(param)) if env.maybeGet[Type](name).isDefined => case FunctionCallExpression(name, List(param)) if env.maybeGet[Type](name).isDefined =>
return simpleOperation(opcode, ctx, param, indexChoice, preserveA, commutative, decimal) return simpleOperation(opcode, ctx, param, indexChoice, preserveA, commutative, decimal)
@ -250,6 +253,16 @@ object BuiltIns {
} }
} }
def compileNonetLeftShift(ctx: CompilationContext, lhs: Expression, rhs: Expression): List[AssemblyLine] = {
val label = MfCompiler.nextLabel("sh")
compileShiftOps(ASL, ctx, lhs, rhs) ++ List(
AssemblyLine.immediate(LDX, 0),
AssemblyLine.relative(BCC, label),
AssemblyLine.implied(INX),
AssemblyLine.label(label)
)
}
def compileInPlaceByteShiftOps(opcode: Opcode.Value, ctx: CompilationContext, lhs: LhsExpression, rhs: Expression): List[AssemblyLine] = { def compileInPlaceByteShiftOps(opcode: Opcode.Value, ctx: CompilationContext, lhs: LhsExpression, rhs: Expression): List[AssemblyLine] = {
val env = ctx.env val env = ctx.env
val b = env.get[Type]("byte") val b = env.get[Type]("byte")

View File

@ -7,6 +7,12 @@ import millfork.env.{Environment, MangledFunction, NormalFunction}
* @author Karol Stasiak * @author Karol Stasiak
*/ */
case class CompilationContext(env: Environment, function: NormalFunction, extraStackOffset: Int, options: CompilationOptions){ case class CompilationContext(env: Environment, function: NormalFunction, extraStackOffset: Int, options: CompilationOptions){
def withInlinedEnv(environment: Environment): CompilationContext = {
val newEnv = new Environment(Some(env), MfCompiler.nextLabel("en"))
newEnv.things ++= environment.things
copy(env = newEnv)
}
def addStack(i: Int): CompilationContext = this.copy(extraStackOffset = extraStackOffset + i) def addStack(i: Int): CompilationContext = this.copy(extraStackOffset = extraStackOffset + i)

View File

@ -112,6 +112,7 @@ object MfCompiler {
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) => w
case FunctionCallExpression("&&", params) => bool case FunctionCallExpression("&&", params) => bool
case FunctionCallExpression("||", params) => bool case FunctionCallExpression("||", params) => bool
case FunctionCallExpression("^^", params) => bool case FunctionCallExpression("^^", params) => bool
@ -390,7 +391,7 @@ object MfCompiler {
def callingContext(ctx: CompilationContext, v: MemoryVariable): CompilationContext = { def callingContext(ctx: CompilationContext, v: MemoryVariable): CompilationContext = {
val result = new Environment(Some(ctx.env), "") val result = new Environment(Some(ctx.env), "")
result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, initialValue = None, address = None), ctx.options) result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, register = false, initialValue = None, address = None), ctx.options)
ctx.copy(env = result) ctx.copy(env = result)
} }
@ -866,6 +867,10 @@ object MfCompiler {
case v: LhsExpression => case v: LhsExpression =>
BuiltIns.compileNonetOps(ctx, v, r) BuiltIns.compileNonetOps(ctx, v, r)
} }
case "<<<<" =>
assertAllBytes("Long shift ops not supported", ctx, params)
val (l, r, 1) = assertBinary(ctx, params)
BuiltIns.compileNonetLeftShift(ctx, l, r)
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)
@ -1104,8 +1109,8 @@ object MfCompiler {
// fallthrough to the lookup below // fallthrough to the lookup below
} }
lookupFunction(ctx, f) match { lookupFunction(ctx, f) match {
case function: InlinedFunction => case function: MacroFunction =>
val (paramPreparation, statements) = inlineFunction(ctx, function, params) val (paramPreparation, statements) = inlineFunction(ctx, function, params, expr.position)
paramPreparation ++ statements.map { paramPreparation ++ statements.map {
case AssemblyStatement(opcode, addrMode, expression, elidable) => case AssemblyStatement(opcode, addrMode, expression, elidable) =>
val param = env.evalForAsm(expression).getOrElse { val param = env.evalForAsm(expression).getOrElse {
@ -1344,7 +1349,29 @@ object MfCompiler {
SequenceChunk(statements.map(s => compile(ctx, s))) SequenceChunk(statements.map(s => compile(ctx, s)))
} }
def inlineFunction(ctx: CompilationContext, i: InlinedFunction, params: List[Expression]): (List[AssemblyLine], List[ExecutableStatement]) = { def replaceVariable(stmt: Statement, paramName: String, target: Expression): Statement = {
def f[T <: Expression](e:T) = e.replaceVariable(paramName, target)
def fx[T <: Expression](e:T) = e.replaceVariable(paramName, target).asInstanceOf[LhsExpression]
def g[T <: Statement](s:T) = replaceVariable(s, paramName, target)
def gx[T <: ExecutableStatement](s:T) = replaceVariable(s, paramName, target).asInstanceOf[ExecutableStatement]
def h(s:String) = if (s == paramName) target.asInstanceOf[VariableExpression].name else s
stmt match {
case ExpressionStatement(e) => ExpressionStatement(e.replaceVariable(paramName, target))
case ReturnStatement(e) => ReturnStatement(e.map(f))
case ReturnDispatchStatement(i,ps, bs) => ReturnDispatchStatement(i.replaceVariable(paramName, target), ps.map(fx), bs.map{
case ReturnDispatchBranch(l, fu, pps) => ReturnDispatchBranch(l, f(fu), pps.map(f))
})
case WhileStatement(c, b) => WhileStatement(f(c), b.map(gx))
case DoWhileStatement(b, c) => DoWhileStatement(b.map(gx), f(c))
case ForStatement(v, start, end, dir, body) => ForStatement(h(v), f(start), f(end), dir, body.map(gx))
case IfStatement(c, t, e) => IfStatement(f(c), t.map(gx), e.map(gx))
case s:AssemblyStatement => s.copy(expression = f(s.expression))
case Assignment(d,s) => Assignment(fx(d), f(s))
case _ => ???
}
}
def inlineFunction(ctx: CompilationContext, i: MacroFunction, params: List[Expression], position: Option[Position]): (List[AssemblyLine], List[ExecutableStatement]) = {
var paramPreparation = List[AssemblyLine]() var paramPreparation = List[AssemblyLine]()
var actualCode = i.code var actualCode = i.code
i.params match { i.params match {
@ -1375,7 +1402,7 @@ object MfCompiler {
} }
case (AssemblyParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) => case (AssemblyParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
if (hadRegisterParam) { if (hadRegisterParam) {
ErrorReporting.error("Only one inline assembly function parameter can be passed via a register") ErrorReporting.error("Only one macro assembly function parameter can be passed via a register", position)
} }
hadRegisterParam = true hadRegisterParam = true
paramPreparation = compile(ctx, actualParam, Some(typ, v), BranchSpec.None) paramPreparation = compile(ctx, actualParam, Some(typ, v), BranchSpec.None)
@ -1383,8 +1410,18 @@ object MfCompiler {
??? ???
case (_, actualParam) => case (_, actualParam) =>
} }
case NormalParamSignature(Nil) => case NormalParamSignature(normalParams) =>
case NormalParamSignature(normalParams) => ??? if (params.length != normalParams.length) {
ErrorReporting.error(s"Invalid number of params for macro function ${i.name}", position)
} else {
params.zip(normalParams).foreach{
case (v@VariableExpression(_), MemoryVariable(paramName, paramType, _)) =>
actualCode = actualCode.map(stmt => replaceVariable(stmt, paramName, v).asInstanceOf[ExecutableStatement])
case _ =>
ErrorReporting.error(s"Parameters to macro functions have to be variables", position)
}
}
} }
// fix local labels: // fix local labels:
// TODO: do it even if the labels are in an inline assembly block inside a Millfork function // TODO: do it even if the labels are in an inline assembly block inside a Millfork function
@ -1501,9 +1538,9 @@ object MfCompiler {
LinearChunk(compileAssignment(ctx, source, dest)) LinearChunk(compileAssignment(ctx, source, dest))
case ExpressionStatement(e@FunctionCallExpression(name, params)) => case ExpressionStatement(e@FunctionCallExpression(name, params)) =>
env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match { env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match {
case Some(i: InlinedFunction) => case Some(i: MacroFunction) =>
val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params) val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params, e.position)
SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx, inlinedStatements))) SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx.withInlinedEnv(i.environment), inlinedStatements)))
case _ => case _ =>
LinearChunk(compile(ctx, e, None, NoBranching)) LinearChunk(compile(ctx, e, None, NoBranching))
} }

View File

@ -148,7 +148,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
} }
object MathOperator extends Enumeration { object MathOperator extends Enumeration {
val Plus, Minus, Times, Shl, Shr, val Plus, Minus, Times, Shl, Shr, Shl9, Shr9,
DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShr, DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShr,
And, Or, Exor = Value And, Or, Exor = Value
} }
@ -166,6 +166,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
case MathOperator.Times => lv * rv case MathOperator.Times => lv * rv
case MathOperator.Shl => lv << rv case MathOperator.Shl => lv << rv
case MathOperator.Shr => lv >> rv case MathOperator.Shr => lv >> rv
case MathOperator.Shl9 => (lv << rv) & 0x1ff
case MathOperator.Shr9 => (lv & 0x1ff) >> rv
case MathOperator.Exor => lv ^ rv case MathOperator.Exor => lv ^ rv
case MathOperator.Or => lv | rv case MathOperator.Or => lv | rv
case MathOperator.And => lv & rv case MathOperator.And => lv & rv
@ -229,6 +231,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
case Times => f"$plhs * $prhs" case Times => f"$plhs * $prhs"
case Shl => f"$plhs << $prhs" case Shl => f"$plhs << $prhs"
case Shr => f"$plhs >> $prhs" case Shr => f"$plhs >> $prhs"
case Shl9 => f"$plhs <<<< $prhs"
case Shr9 => f"$plhs >>>> $prhs"
case DecimalPlus => f"$plhs +' $prhs" case DecimalPlus => f"$plhs +' $prhs"
case DecimalMinus => f"$plhs -' $prhs" case DecimalMinus => f"$plhs -' $prhs"
case DecimalTimes => f"$plhs *' $prhs" case DecimalTimes => f"$plhs *' $prhs"

View File

@ -33,7 +33,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
val allThings: Map[String, Thing] = things.values.map { val allThings: Map[String, Thing] = things.values.map {
case m: FunctionInMemory => case m: FunctionInMemory =>
m.environment.getAllPrefixedThings m.environment.getAllPrefixedThings
case m: InlinedFunction => case m: MacroFunction =>
m.environment.getAllPrefixedThings m.environment.getAllPrefixedThings
case _ => Map[String, Thing]() case _ => Map[String, Thing]()
}.fold(things.toMap)(_ ++ _) }.fold(things.toMap)(_ ++ _)
@ -63,7 +63,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
def allConstants: List[ConstantThing] = things.values.flatMap { def allConstants: List[ConstantThing] = things.values.flatMap {
case m: NormalFunction => m.environment.allConstants case m: NormalFunction => m.environment.allConstants
case m: InlinedFunction => m.environment.allConstants case m: MacroFunction => m.environment.allConstants
case m: ConstantThing => List(m) case m: ConstantThing => List(m)
case _ => Nil case _ => Nil
}.toList }.toList
@ -105,7 +105,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
) )
} }
case VariableAllocationMethod.Auto | VariableAllocationMethod.Static => case VariableAllocationMethod.Auto | VariableAllocationMethod.Register | VariableAllocationMethod.Static =>
m.sizeInBytes match { m.sizeInBytes match {
case 0 => Nil case 0 => Nil
case 2 => case 2 =>
@ -278,6 +278,10 @@ class Environment(val parent: Option[Environment], val prefix: String) {
constantOperation(MathOperator.Shr, params) constantOperation(MathOperator.Shr, params)
case "<<" => case "<<" =>
constantOperation(MathOperator.Shl, params) constantOperation(MathOperator.Shl, params)
case "<<<<" =>
constantOperation(MathOperator.Shl9, params)
case ">>>>" =>
constantOperation(MathOperator.Shr9, params)
case "*'" => case "*'" =>
constantOperation(MathOperator.DecimalTimes, params) constantOperation(MathOperator.DecimalTimes, params)
case "*" => case "*" =>
@ -366,20 +370,19 @@ class Environment(val parent: Option[Environment], val prefix: String) {
if (stmt.reentrant && stmt.interrupt) ErrorReporting.error(s"Reentrant function `$name` cannot be an interrupt handler", stmt.position) if (stmt.reentrant && stmt.interrupt) ErrorReporting.error(s"Reentrant function `$name` cannot be an interrupt handler", stmt.position)
if (stmt.reentrant && stmt.params.nonEmpty) ErrorReporting.error(s"Reentrant function `$name` cannot have parameters", stmt.position) if (stmt.reentrant && stmt.params.nonEmpty) ErrorReporting.error(s"Reentrant function `$name` cannot have parameters", stmt.position)
if (stmt.interrupt && stmt.params.nonEmpty) ErrorReporting.error(s"Interrupt function `$name` cannot have parameters", stmt.position) if (stmt.interrupt && stmt.params.nonEmpty) ErrorReporting.error(s"Interrupt function `$name` cannot have parameters", stmt.position)
if (stmt.inlined) { if (stmt.isMacro) {
if (!stmt.assembly) { if (!stmt.assembly) {
if (stmt.params.nonEmpty) ErrorReporting.error(s"Inline non-assembly function `$name` cannot have parameters", stmt.position) // TODO: ??? if (resultType != VoidType) ErrorReporting.error(s"Macro non-assembly function `$name` must return void", stmt.position)
if (resultType != VoidType) ErrorReporting.error(s"Inline non-assembly function `$name` must return void", stmt.position)
} }
if (stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly)) if (stmt.assembly && stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly))
ErrorReporting.error(s"Inline function `$name` cannot have by-variable parameters", stmt.position) ErrorReporting.error(s"Macro function `$name` cannot have by-variable parameters", stmt.position)
} else { } else {
if (!stmt.assembly) { if (!stmt.assembly) {
if (stmt.params.exists(!_.assemblyParamPassingConvention.isInstanceOf[ByVariable])) if (stmt.params.exists(!_.assemblyParamPassingConvention.isInstanceOf[ByVariable]))
ErrorReporting.error(s"Non-assembly function `$name` cannot have non-variable parameters", stmt.position) ErrorReporting.error(s"Non-assembly function `$name` cannot have non-variable parameters", stmt.position)
} }
if (stmt.params.exists(_.assemblyParamPassingConvention.inInlinedOnly)) if (stmt.params.exists(_.assemblyParamPassingConvention.inInlinedOnly))
ErrorReporting.error(s"Non-inline function `$name` cannot have inlinable parameters", stmt.position) ErrorReporting.error(s"Non-macro function `$name` cannot have inlinable parameters", stmt.position)
} }
val env = new Environment(Some(this), name + "$") val env = new Environment(Some(this), name + "$")
@ -432,9 +435,9 @@ class Environment(val parent: Option[Environment], val prefix: String) {
case e: ExecutableStatement => Some(e) case e: ExecutableStatement => Some(e)
case _ => None case _ => None
} }
val needsExtraRTS = !stmt.inlined && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement]) val needsExtraRTS = !stmt.isMacro && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement])
if (stmt.inlined) { if (stmt.isMacro) {
val mangled = InlinedFunction( val mangled = MacroFunction(
name, name,
resultType, resultType,
params, params,
@ -603,6 +606,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
} }
if (stmt.constant) { if (stmt.constant) {
if (stmt.stack) ErrorReporting.error(s"`$name` is a constant and cannot be on stack", position) if (stmt.stack) ErrorReporting.error(s"`$name` is a constant and cannot be on stack", position)
if (stmt.register) ErrorReporting.error(s"`$name` is a constant and cannot be in a register", position)
if (stmt.address.isDefined) ErrorReporting.error(s"`$name` is a constant and cannot have an address", position) if (stmt.address.isDefined) ErrorReporting.error(s"`$name` is a constant and cannot have an address", position)
if (stmt.initialValue.isEmpty) ErrorReporting.error(s"`$name` is a constant and requires a value", position) if (stmt.initialValue.isEmpty) ErrorReporting.error(s"`$name` is a constant and requires a value", position)
val constantValue: Constant = stmt.initialValue.flatMap(eval).getOrElse(Constant.error(s"`$name` has a non-constant value", position)) val constantValue: Constant = stmt.initialValue.flatMap(eval).getOrElse(Constant.error(s"`$name` has a non-constant value", position))
@ -614,8 +618,12 @@ class Environment(val parent: Option[Environment], val prefix: String) {
} }
} else { } else {
if (stmt.stack && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be on stack", position) if (stmt.stack && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be on stack", position)
if (stmt.register && typ.size != 1) ErrorReporting.error(s"A register variable `$name` is too large", position)
if (stmt.register && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be in a register", position)
if (stmt.register && stmt.stack) ErrorReporting.error(s"`$name` cannot be simultaneously on stack and in a register", position)
if (stmt.initialValue.isDefined && parent.isDefined) ErrorReporting.error(s"`$name` is local and not a constant and therefore cannot have a value", position) if (stmt.initialValue.isDefined && parent.isDefined) ErrorReporting.error(s"`$name` is local and not a constant and therefore cannot have a value", position)
if (stmt.initialValue.isDefined && stmt.address.isDefined) ErrorReporting.warn(s"`$name` has both address and initial value - this may not work as expected!", options, position) if (stmt.initialValue.isDefined && stmt.address.isDefined) ErrorReporting.warn(s"`$name` has both address and initial value - this may not work as expected!", options, position)
if (stmt.register && stmt.address.isDefined) ErrorReporting.error(s"`$name` cannot by simultaneously at an address and in a register", position)
if (stmt.stack) { if (stmt.stack) {
val v = StackVariable(prefix + name, typ, this.baseStackOffset) val v = StackVariable(prefix + name, typ, this.baseStackOffset)
baseStackOffset += typ.size baseStackOffset += typ.size
@ -626,7 +634,11 @@ class Environment(val parent: Option[Environment], val prefix: String) {
} }
} else { } else {
val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({ val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({
val alloc = if (typ.name == "pointer") VariableAllocationMethod.Zeropage else if (stmt.global) VariableAllocationMethod.Static else VariableAllocationMethod.Auto val alloc =
if (typ.name == "pointer") VariableAllocationMethod.Zeropage
else if (stmt.global) VariableAllocationMethod.Static
else if (stmt.register) VariableAllocationMethod.Register
else VariableAllocationMethod.Auto
if (alloc != VariableAllocationMethod.Static && stmt.initialValue.isDefined) { if (alloc != VariableAllocationMethod.Static && stmt.initialValue.isDefined) {
ErrorReporting.error(s"`$name` cannot be preinitialized`", position) ErrorReporting.error(s"`$name` cannot be preinitialized`", position)
} }

View File

@ -117,7 +117,7 @@ sealed trait UninitializedMemory extends ThingInMemory {
} }
object VariableAllocationMethod extends Enumeration { object VariableAllocationMethod extends Enumeration {
val Auto, Static, Zeropage, None = Value val Auto, Register, Static, Zeropage, None = Value
} }
case class StackVariable(name: String, typ: Type, baseOffset: Int) extends Variable { case class StackVariable(name: String, typ: Type, baseOffset: Int) extends Variable {
@ -188,11 +188,11 @@ case class EmptyFunction(name: String,
override def interrupt = false override def interrupt = false
} }
case class InlinedFunction(name: String, case class MacroFunction(name: String,
returnType: Type, returnType: Type,
params: ParamSignature, params: ParamSignature,
environment: Environment, environment: Environment,
code: List[ExecutableStatement]) extends MangledFunction { code: List[ExecutableStatement]) extends MangledFunction {
override def interrupt = false override def interrupt = false
} }

View File

@ -97,6 +97,7 @@ case class VariableDeclarationStatement(name: String,
stack: Boolean, stack: Boolean,
constant: Boolean, constant: Boolean,
volatile: Boolean, volatile: Boolean,
register: Boolean,
initialValue: Option[Expression], initialValue: Option[Expression],
address: Option[Expression]) extends DeclarationStatement { address: Option[Expression]) extends DeclarationStatement {
override def getAllExpressions: List[Expression] = List(initialValue, address).flatten override def getAllExpressions: List[Expression] = List(initialValue, address).flatten
@ -121,7 +122,8 @@ case class FunctionDeclarationStatement(name: String,
params: List[ParameterDeclaration], params: List[ParameterDeclaration],
address: Option[Expression], address: Option[Expression],
statements: Option[List[Statement]], statements: Option[List[Statement]],
inlined: Boolean, isMacro: Boolean,
inlinable: Option[Boolean],
assembly: Boolean, assembly: Boolean,
interrupt: Boolean, interrupt: Boolean,
reentrant: Boolean) extends DeclarationStatement { reentrant: Boolean) extends DeclarationStatement {

View File

@ -145,9 +145,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment)
val assembly = mutable.ArrayBuffer[String]() val assembly = mutable.ArrayBuffer[String]()
val potentiallyInlineable: Map[String, Int] = val potentiallyInlineable: Map[String, Int] =
if (options.flags(CompilationFlag.InlineFunctions)) InliningCalculator.getPotentiallyInlineableFunctions(program, options.flags(CompilationFlag.InlineFunctions))
InliningCalculator.getPotentiallyInlineableFunctions(program)
else Map()
var inlinedFunctions = Map[String, List[AssemblyLine]]() var inlinedFunctions = Map[String, List[AssemblyLine]]()
val compiledFunctions = mutable.Map[String, List[AssemblyLine]]() val compiledFunctions = mutable.Map[String, List[AssemblyLine]]()

View File

@ -14,10 +14,14 @@ object InliningCalculator {
private val sizes = Seq(64, 64, 8, 6, 5, 5, 4) private val sizes = Seq(64, 64, 8, 6, 5, 5, 4)
def getPotentiallyInlineableFunctions(program: Program): Map[String, Int] = { def getPotentiallyInlineableFunctions(program: Program,
inlineByDefault: Boolean,
aggressivenessForNormal: Double = 1.0,
aggressivenessForRecommended: Double = 1.2): Map[String, Int] = {
val callCount = mutable.Map[String, Int]().withDefaultValue(0) val callCount = mutable.Map[String, Int]().withDefaultValue(0)
val allFunctions = mutable.Set[String]() val allFunctions = mutable.Set[String]()
val badFunctions = mutable.Set[String]() val badFunctions = mutable.Set[String]()
val recommendedFunctions = mutable.Set[String]()
getAllCalledFunctions(program.declarations).foreach{ getAllCalledFunctions(program.declarations).foreach{
case (name, true) => badFunctions += name case (name, true) => badFunctions += name
case (name, false) => callCount(name) += 1 case (name, false) => callCount(name) += 1
@ -25,7 +29,11 @@ object InliningCalculator {
program.declarations.foreach{ program.declarations.foreach{
case f:FunctionDeclarationStatement => case f:FunctionDeclarationStatement =>
allFunctions += f.name allFunctions += f.name
if (f.inlined if (f.inlinable.contains(true)) {
recommendedFunctions += f.name
}
if (f.isMacro
|| f.inlinable.contains(false)
|| f.address.isDefined || f.address.isDefined
|| f.interrupt || f.interrupt
|| f.reentrant || f.reentrant
@ -34,7 +42,12 @@ object InliningCalculator {
case _ => case _ =>
} }
allFunctions --= badFunctions allFunctions --= badFunctions
allFunctions.map(f => f -> sizes(callCount(f) min (sizes.size - 1))).toMap recommendedFunctions --= badFunctions
(if (inlineByDefault) allFunctions else recommendedFunctions).map(f => f -> {
val size = sizes(callCount(f) min (sizes.size - 1))
val aggressiveness = if (recommendedFunctions(f)) aggressivenessForRecommended else aggressivenessForNormal
(size * aggressiveness).floor.toInt
}).toMap
} }
private def getAllCalledFunctions(expressions: List[Node]): List[(String, Boolean)] = expressions.flatMap { private def getAllCalledFunctions(expressions: List[Node]): List[(String, Boolean)] = expressions.flatMap {
@ -71,6 +84,9 @@ object InliningCalculator {
if (code.isEmpty) return None if (code.isEmpty) return None
if (code.last.opcode != RTS) return None if (code.last.opcode != RTS) return None
var result = code.init var result = code.init
while (result.nonEmpty && OpcodeClasses.NoopDiscardsFlags(result.last.opcode)) {
result = result.init
}
if (result.head.opcode == LABEL && result.head.parameter == Label(fname).toAddress) result = result.tail if (result.head.opcode == LABEL && result.head.parameter == Label(fname).toAddress) result = result.tail
if (result.exists(l => badOpcodes(l.opcode))) return None if (result.exists(l => badOpcodes(l.opcode))) return None
Some(result) Some(result)

View File

@ -27,6 +27,9 @@ sealed trait ByteAllocator {
if (counter == 0) { if (counter == 0) {
lastFree = i lastFree = i
} }
if (count == 0) {
return lastFree
}
counter += 1 counter += 1
if (counter == count) { if (counter == count) {
return lastFree return lastFree

View File

@ -106,7 +106,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
List("&&"), List("&&"),
List("==", "<=", ">=", "!=", "<", ">"), List("==", "<=", ">=", "!=", "<", ">"),
List(":"), List(":"),
List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"), List("+'", "-'", "<<'", ">>'", "<<<<", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"),
List("*'", "*")) List("*'", "*"))
val nonStatementLevel = 1 // everything but not `=` val nonStatementLevel = 1 // everything but not `=`
@ -116,7 +116,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
def variableDefinition(implicitlyGlobal: Boolean): P[DeclarationStatement] = for { def variableDefinition(implicitlyGlobal: Boolean): P[DeclarationStatement] = for {
p <- position() p <- position()
flags <- flags("const", "static", "volatile", "stack") ~ HWS flags <- flags("const", "static", "volatile", "stack", "register") ~ HWS
typ <- identifier ~ SWS typ <- identifier ~ SWS
name <- identifier ~/ HWS ~/ Pass name <- identifier ~/ HWS ~/ Pass
addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~ HWS addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~ HWS
@ -128,6 +128,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
stack = flags("stack"), stack = flags("stack"),
constant = flags("const"), constant = flags("const"),
volatile = flags("volatile"), volatile = flags("volatile"),
register = flags("register"),
initialValue, addr).pos(p) initialValue, addr).pos(p)
} }
@ -332,11 +333,11 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
def statement: P[Statement] = (position() ~ P(keywordStatement | variableDefinition(false) | expressionStatement)).map { case (p, s) => s.pos(p) } def statement: P[Statement] = (position() ~ P(keywordStatement | variableDefinition(false) | expressionStatement)).map { case (p, s) => s.pos(p) }
def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS ~/ asmStatement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS ~/ asmStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
def statements: P[List[Statement]] = ("{" ~/ AWS ~ statement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) def statements: P[List[Statement]] = ("{" ~/ AWS ~ statement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
def executableStatements: P[Seq[ExecutableStatement]] = "{" ~/ AWS ~/ executableStatement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~ "}" def executableStatements: P[Seq[ExecutableStatement]] = "{" ~/ AWS ~/ executableStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~ "}"
def dispatchLabel: P[ReturnDispatchLabel] = def dispatchLabel: P[ReturnDispatchLabel] =
("default" ~ !letterOrDigit ~/ AWS ~/ ("(" ~/ position("default branch range") ~ AWS ~/ mlExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?).map{ ("default" ~ !letterOrDigit ~/ AWS ~/ ("(" ~/ position("default branch range") ~ AWS ~/ mlExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?).map{
@ -406,16 +407,19 @@ 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", "reentrant") ~ HWS flags <- flags("asm", "inline", "interrupt", "macro", "noinline", "reentrant") ~ 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
addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~/ AWS addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~/ AWS
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("inline")) ErrorReporting.error(s"Interrupt function `$name` cannot be inline", Some(p)) if (flags("interrupt") && flags("macro")) ErrorReporting.error(s"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("inline") && flags("reentrant")) ErrorReporting.error("Reentrant and inline 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("macro") && flags("noinline")) ErrorReporting.error("Noinline and macro exclude each other", Some(p))
if (flags("inline") && flags("macro")) ErrorReporting.error("Macro and inline exclude each other", Some(p))
if (flags("interrupt") && returnType != "void") ErrorReporting.error("Interrupt function `$name` has to return void", Some(p)) if (flags("interrupt") && returnType != "void") ErrorReporting.error("Interrupt function `$name` has to return void", Some(p))
if (addr.isEmpty && statements.isEmpty) ErrorReporting.error("Extern function `$name` must have an address", Some(p)) if (addr.isEmpty && statements.isEmpty) ErrorReporting.error("Extern function `$name` must have an address", Some(p))
if (statements.isEmpty && !flags("asm") && params.nonEmpty) ErrorReporting.error("Extern non-asm function `$name` cannot have parameters", Some(p)) if (statements.isEmpty && !flags("asm") && params.nonEmpty) ErrorReporting.error("Extern non-asm function `$name` cannot have parameters", Some(p))
@ -433,14 +437,14 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
case _ => false case _ => false
}) ErrorReporting.warn("Assembly non-interrupt function `$name` contains RTI, did you mean RTS?", options, Some(p)) }) ErrorReporting.warn("Assembly non-interrupt function `$name` contains RTI, did you mean RTS?", options, Some(p))
} }
if (!flags("inline")) { if (!flags("macro")) {
xs.last match { xs.last match {
case AssemblyStatement(Opcode.RTS, _, _, _) => () // OK case AssemblyStatement(Opcode.RTS, _, _, _) => () // OK
case AssemblyStatement(Opcode.RTI, _, _, _) => () // OK case AssemblyStatement(Opcode.RTI, _, _, _) => () // OK
case AssemblyStatement(Opcode.JMP, _, _, _) => () // OK case AssemblyStatement(Opcode.JMP, _, _, _) => () // OK
case _ => case _ =>
val validReturn = if (flags("interrupt")) "RTI" else "RTS" val validReturn = if (flags("interrupt")) "RTI" else "RTS"
ErrorReporting.warn(s"Non-inline assembly function `$name` should end in " + validReturn, options, Some(p)) ErrorReporting.warn(s"Non-macro assembly function `$name` should end in " + validReturn, options, Some(p))
} }
} }
case None => () case None => ()
@ -448,7 +452,8 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
FunctionDeclarationStatement(name, returnType, params.toList, FunctionDeclarationStatement(name, returnType, params.toList,
addr, addr,
statements, statements,
flags("inline"), flags("macro"),
if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None,
flags("asm"), flags("asm"),
flags("interrupt"), flags("interrupt"),
flags("reentrant")).pos(p) flags("reentrant")).pos(p)

View File

@ -8,12 +8,12 @@ import org.scalatest.{FunSuite, Matchers}
/** /**
* @author Karol Stasiak * @author Karol Stasiak
*/ */
class InlineAssemblyFunctionsSuite extends FunSuite with Matchers { class AssemblyMacroSuite extends FunSuite with Matchers {
test("Poke test 1") { test("Poke test 1") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| inline asm void poke(word ref addr, byte a) { | macro asm void poke(word ref addr, byte a) {
| STA addr | STA addr
| } | }
| |
@ -28,7 +28,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
test("Peek test 1") { test("Peek test 1") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| inline asm byte peek(word ref addr) { | macro asm byte peek(word ref addr) {
| ?LDA addr | ?LDA addr
| } | }
| |
@ -45,7 +45,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
test("Poke test 2") { test("Poke test 2") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| inline asm void poke(word const addr, byte a) { | macro asm void poke(word const addr, byte a) {
| STA addr | STA addr
| } | }
| |
@ -62,7 +62,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
test("Peek test 2") { test("Peek test 2") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| inline asm byte peek(word const addr) { | macro asm byte peek(word const addr) {
| ?LDA addr | ?LDA addr
| } | }
| |
@ -80,7 +80,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
test("Labels test") { test("Labels test") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| inline asm void doNothing () { | macro asm void doNothing () {
| JMP label | JMP label
| label: | label:
| } | }

View File

@ -41,7 +41,7 @@ class AssemblyOptimizationSuite extends FunSuite with Matchers {
""" """
| array output [100] @$C000 | array output [100] @$C000
| void main () { | void main () {
| byte i | register byte i
| i = 1 | i = 1
| while (i<50) { | while (i<50) {
| output[i] = i | output[i] = i

View File

@ -61,7 +61,7 @@ class AssemblySuite extends FunSuite with Matchers {
""".stripMargin)(_.readByte(0xc000) should equal(10)) """.stripMargin)(_.readByte(0xc000) should equal(10))
} }
test("Inline asm functions") { test("Macro asm functions") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| byte output @$c000 | byte output @$c000
@ -70,14 +70,14 @@ class AssemblySuite extends FunSuite with Matchers {
| f() | f()
| f() | f()
| } | }
| inline asm void f() { | macro asm void f() {
| inc $c000 | inc $c000
| rts | rts
| } | }
""".stripMargin)(_.readByte(0xc000) should equal(1)) """.stripMargin)(_.readByte(0xc000) should equal(1))
} }
test("Inline asm functions 2") { test("macro asm functions 2") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """
| byte output @$c000 | byte output @$c000
@ -86,7 +86,7 @@ class AssemblySuite extends FunSuite with Matchers {
| add(output, 5) | add(output, 5)
| add(output, 5) | add(output, 5)
| } | }
| inline asm void add(byte ref v, byte const c) { | macro asm void add(byte ref v, byte const c) {
| lda v | lda v
| clc | clc
| adc #c | adc #c
@ -104,7 +104,7 @@ class AssemblySuite extends FunSuite with Matchers {
| output = 0 | output = 0
| add256(output) | add256(output)
| } | }
| inline asm void add256(word ref v) { | macro asm void add256(word ref v) {
| inc v+1 | inc v+1
| } | }
""".stripMargin)(_.readWord(0xc000) should equal(0x100)) """.stripMargin)(_.readWord(0xc000) should equal(0x100))

View File

@ -23,7 +23,7 @@ class BasicSymonTest extends FunSuite with Matchers {
| void main () { | void main () {
| panic() | panic()
| } | }
| inline asm void panic() { | macro asm void panic() {
| JSR _panic | JSR _panic
| } | }
| void _panic() { | void _panic() {

View File

@ -26,4 +26,26 @@ class NonetSuite extends FunSuite with Matchers {
m.readByte(0xc001) should equal(0x88) m.readByte(0xc001) should equal(0x88)
m.readByte(0xc002) should equal(0x44) m.readByte(0xc002) should equal(0x44)
} }
test("Nonet left shift") {
val m = EmuUnoptimizedRun(
"""
| word output0 @$c000
| word output1 @$c002
| word output2 @$c004
| word output3 @$c006
| void main () {
| byte a
| a = 3
| output0 = a <<<< 1
| output1 = a <<<< 2
| output2 = a <<<< 6
| output3 = a <<<< 7
| }
""".stripMargin)
m.readWord(0xc000) should equal(0x06)
m.readWord(0xc002) should equal(0x0C)
m.readWord(0xc004) should equal(0xC0)
m.readWord(0xc006) should equal(0x180)
}
} }