mirror of
https://github.com/KarolS/millfork.git
synced 2025-02-06 01:30:13 +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:
parent
c599db0068
commit
0ca1be0c00
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,10 +2,20 @@
|
||||
|
||||
## 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.
|
||||
|
||||
* Fixed several optimization bugs.
|
||||
|
||||
* Fixed several C64 library bugs.
|
||||
|
||||
* Other minor improvements.
|
||||
|
||||
## 0.1
|
||||
|
@ -1,9 +1,11 @@
|
||||
# Function inlining
|
||||
# Macros and inlining
|
||||
|
||||
## Explicit inlining
|
||||
## Macros
|
||||
|
||||
`inline` keyword
|
||||
`macro` keyword
|
||||
|
||||
## Automatic inlining
|
||||
|
||||
`--inline` command-line option
|
||||
|
||||
`inline` and `noinline` keyword
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
Automatic variables have lifetime starting with the beginning
|
||||
|
@ -54,15 +54,15 @@ Currently there is no way to insert raw bytes into inline assembly
|
||||
|
||||
## 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`.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
If the size of the return type is one byte,
|
||||
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.
|
||||
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
|
||||
|
||||
@ -98,13 +98,13 @@ and call `increase(score, 10)`, the entire call will compile into:
|
||||
ADC #10
|
||||
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
|
||||
|
||||
* `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
|
||||
|
||||
|
@ -11,8 +11,11 @@ Syntax:
|
||||
* `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)
|
||||
|
||||
* `inline` – the function should be always inlined,
|
||||
see [Function inlining#Explicit inlining](../abi/inlining.md#explicit-inlining)
|
||||
* `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
|
||||
see [Macros_and inlining#Inlining](../abi/inlining.md#automatic_inlining.md)
|
||||
|
||||
* `interrupt` – the function is a hardware interrupt handler
|
||||
|
||||
|
@ -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
|
||||
`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
|
||||
|
||||
These operators work using the decimal arithmetic and will not work on Ricoh CPU's.
|
||||
|
@ -20,7 +20,8 @@ Syntax:
|
||||
|
||||
`[<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).
|
||||
|
||||
* `<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,
|
||||
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.
|
||||
|
||||
The functions are not called, so they don't return to the function the return dispatch statement is in, but to its caller.
|
||||
|
@ -10,7 +10,7 @@ byte cia2_prb @$DD01
|
||||
byte cia2_ddra @$DD02
|
||||
byte cia2_ddrb @$DD03
|
||||
|
||||
inline asm void cia_disable_irq() {
|
||||
macro asm void cia_disable_irq() {
|
||||
LDA #$7f
|
||||
LDA $dc0d
|
||||
LDA $dd0d
|
||||
@ -19,22 +19,22 @@ inline asm void cia_disable_irq() {
|
||||
}
|
||||
|
||||
|
||||
inline void vic_bank_0000() {
|
||||
cia2_ddra = $C0
|
||||
cia2_pra = $C0
|
||||
macro void vic_bank_0000() {
|
||||
cia2_ddra = 3
|
||||
cia2_pra = 3
|
||||
}
|
||||
|
||||
inline void vic_bank_4000() {
|
||||
cia2_ddra = $C0
|
||||
cia2_pra = $80
|
||||
macro void vic_bank_4000() {
|
||||
cia2_ddra = 3
|
||||
cia2_pra = 2
|
||||
}
|
||||
|
||||
inline void vic_bank_8000() {
|
||||
cia2_ddra = $C0
|
||||
cia2_pra = $40
|
||||
macro void vic_bank_8000() {
|
||||
cia2_ddra = 3
|
||||
cia2_pra = 1
|
||||
}
|
||||
|
||||
inline void vic_bank_C000() {
|
||||
cia2_ddra = $C0
|
||||
cia2_pra = $00
|
||||
macro void vic_bank_C000() {
|
||||
cia2_ddra = 3
|
||||
cia2_pra = 0
|
||||
}
|
@ -5,37 +5,37 @@ import cpu6510
|
||||
|
||||
array c64_color_ram [1000] @$D800
|
||||
|
||||
inline void c64_ram_only() {
|
||||
macro void c64_ram_only() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 0
|
||||
}
|
||||
|
||||
inline void c64_ram_io() {
|
||||
macro void c64_ram_io() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 5
|
||||
}
|
||||
|
||||
inline void c64_ram_io_kernal() {
|
||||
macro void c64_ram_io_kernal() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 6
|
||||
}
|
||||
|
||||
inline void c64_ram_io_basic() {
|
||||
macro void c64_ram_io_basic() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 7
|
||||
}
|
||||
|
||||
inline void c64_ram_charset() {
|
||||
macro void c64_ram_charset() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 1
|
||||
}
|
||||
|
||||
inline void c64_ram_charset_kernal() {
|
||||
macro void c64_ram_charset_kernal() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 2
|
||||
}
|
||||
|
||||
inline void c64_ram_charset_basic() {
|
||||
macro void c64_ram_charset_basic() {
|
||||
cpu6510_ddr = 7
|
||||
cpu6510_port = 3
|
||||
}
|
@ -52,77 +52,77 @@ byte vic_spr7_color @$D02E
|
||||
array vic_spr_coord [16] @$D000
|
||||
array vic_spr_color [8] @$D027
|
||||
|
||||
inline void vic_enable_multicolor() {
|
||||
macro void vic_enable_multicolor() {
|
||||
vic_cr2 |= 0x10
|
||||
}
|
||||
|
||||
inline void vic_disable_multicolor() {
|
||||
macro void vic_disable_multicolor() {
|
||||
vic_cr2 &= 0xEF
|
||||
}
|
||||
|
||||
inline void vic_enable_bitmap() {
|
||||
macro void vic_enable_bitmap() {
|
||||
vic_cr1 |= 0x20
|
||||
}
|
||||
|
||||
inline void vic_disable_bitmap() {
|
||||
macro void vic_disable_bitmap() {
|
||||
vic_cr1 &= 0xDF
|
||||
}
|
||||
|
||||
inline void vic_24_rows() {
|
||||
macro void vic_24_rows() {
|
||||
vic_cr1 &= 0xF7
|
||||
}
|
||||
|
||||
inline void vic_25_rows() {
|
||||
macro void vic_25_rows() {
|
||||
vic_cr1 |= 8
|
||||
}
|
||||
|
||||
inline void vic_38_columns() {
|
||||
macro void vic_38_columns() {
|
||||
vic_cr2 &= 0xF7
|
||||
}
|
||||
|
||||
inline void vic_40_columns() {
|
||||
macro void vic_40_columns() {
|
||||
vic_cr2 |= 8
|
||||
}
|
||||
|
||||
inline void vic_disable_irq() {
|
||||
macro void vic_disable_irq() {
|
||||
vic_irq_ena = 0
|
||||
vic_irq += 1
|
||||
}
|
||||
|
||||
// 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)
|
||||
//}
|
||||
|
||||
inline void vic_charset_0000() {
|
||||
macro void vic_charset_0000() {
|
||||
vic_mem = (vic_mem & $F1)
|
||||
}
|
||||
inline void vic_charset_0800() {
|
||||
macro void vic_charset_0800() {
|
||||
vic_mem = (vic_mem & $F1) | 2
|
||||
}
|
||||
inline void vic_charset_1000() {
|
||||
macro void vic_charset_1000() {
|
||||
vic_mem = (vic_mem & $F1) | 4
|
||||
}
|
||||
inline void vic_charset_1800() {
|
||||
macro void vic_charset_1800() {
|
||||
vic_mem = (vic_mem & $F1) | 6
|
||||
}
|
||||
inline void vic_charset_2000() {
|
||||
macro void vic_charset_2000() {
|
||||
vic_mem = (vic_mem & $F1) | 8
|
||||
}
|
||||
inline void vic_charset_2800() {
|
||||
macro void vic_charset_2800() {
|
||||
vic_mem = (vic_mem & $F1) | $A
|
||||
}
|
||||
inline void vic_charset_3000() {
|
||||
macro void vic_charset_3000() {
|
||||
vic_mem = (vic_mem & $F1) | $C
|
||||
}
|
||||
inline void vic_charset_3800() {
|
||||
macro void vic_charset_3800() {
|
||||
vic_mem = (vic_mem & $F1) | $E
|
||||
}
|
||||
|
||||
inline void vic_bitmap_0000() {
|
||||
macro void vic_bitmap_0000() {
|
||||
vic_mem &= $F7
|
||||
}
|
||||
inline void vic_bitmap_2000() {
|
||||
macro void vic_bitmap_2000() {
|
||||
vic_mem |= 8
|
||||
}
|
||||
|
||||
|
@ -4,19 +4,19 @@ word nmi_routine_addr @$FFFA
|
||||
word reset_routine_addr @$FFFC
|
||||
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
|
||||
}
|
||||
|
||||
inline asm byte peek(word const addr) {
|
||||
macro asm byte peek(word const addr) {
|
||||
?LDA addr
|
||||
}
|
||||
|
||||
inline asm void disable_irq() {
|
||||
macro asm void disable_irq() {
|
||||
SEI
|
||||
}
|
||||
|
||||
inline asm void enable_irq() {
|
||||
macro asm void enable_irq() {
|
||||
CLI
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ _lo_nibble_to_hex_lbl:
|
||||
RTS
|
||||
}
|
||||
|
||||
inline asm void panic() {
|
||||
macro asm void panic() {
|
||||
JSR _panic
|
||||
}
|
||||
|
||||
|
@ -213,12 +213,12 @@ object Main {
|
||||
assertNone(c.optimizationLevel, "Optimization level already defined")
|
||||
c.copy(optimizationLevel = Some(1))
|
||||
}.description("Optimize code.")
|
||||
for (i <- 2 to 9) {
|
||||
for (i <- 1 to 9) {
|
||||
val f = flag("-O" + i).action { c =>
|
||||
assertNone(c.optimizationLevel, "Optimization level already defined")
|
||||
c.copy(optimizationLevel = Some(i))
|
||||
}.description("Optimize code even more.")
|
||||
if (i > 3) f.hidden()
|
||||
if (i == 1 || i > 3) f.hidden()
|
||||
}
|
||||
flag("--inline").action { c =>
|
||||
c.changeFlag(CompilationFlag.InlineFunctions, true)
|
||||
|
@ -74,10 +74,15 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
|
||||
case _ => None
|
||||
}.toSet
|
||||
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)
|
||||
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 =>
|
||||
v.name -> VariableLifetime.apply(v.name, code)
|
||||
@ -90,7 +95,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
|
||||
importances(range.start).x != Important
|
||||
}.flatMap {
|
||||
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 {
|
||||
@ -98,7 +105,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
|
||||
importances(range.start).y != Important
|
||||
}.flatMap {
|
||||
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 {
|
||||
@ -111,7 +120,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
|
||||
start = true,
|
||||
synced = false,
|
||||
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"Y: $yCandidates")
|
||||
|
@ -92,11 +92,14 @@ object BuiltIns {
|
||||
case IndexChoice.PreferY =>
|
||||
val array = env.getArrayOrPointer(arrayName)
|
||||
val calculateIndex = MfCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching)
|
||||
val baseAddress = array match {
|
||||
case c: ConstantThing => c.value
|
||||
case a: MfArray => a.toAddress
|
||||
array match {
|
||||
case c: ConstantThing =>
|
||||
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 =>
|
||||
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] = {
|
||||
val env = ctx.env
|
||||
val b = env.get[Type]("byte")
|
||||
|
@ -7,6 +7,12 @@ import millfork.env.{Environment, MangledFunction, NormalFunction}
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
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)
|
||||
|
||||
|
@ -112,6 +112,7 @@ object MfCompiler {
|
||||
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
|
||||
@ -390,7 +391,7 @@ object MfCompiler {
|
||||
|
||||
def callingContext(ctx: CompilationContext, v: MemoryVariable): CompilationContext = {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -866,6 +867,10 @@ object MfCompiler {
|
||||
case v: LhsExpression =>
|
||||
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 "<<" =>
|
||||
assertAllBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertBinary(ctx, params)
|
||||
@ -1104,8 +1109,8 @@ object MfCompiler {
|
||||
// fallthrough to the lookup below
|
||||
}
|
||||
lookupFunction(ctx, f) match {
|
||||
case function: InlinedFunction =>
|
||||
val (paramPreparation, statements) = inlineFunction(ctx, function, params)
|
||||
case function: MacroFunction =>
|
||||
val (paramPreparation, statements) = inlineFunction(ctx, function, params, expr.position)
|
||||
paramPreparation ++ statements.map {
|
||||
case AssemblyStatement(opcode, addrMode, expression, elidable) =>
|
||||
val param = env.evalForAsm(expression).getOrElse {
|
||||
@ -1344,7 +1349,29 @@ object MfCompiler {
|
||||
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 actualCode = i.code
|
||||
i.params match {
|
||||
@ -1375,7 +1402,7 @@ object MfCompiler {
|
||||
}
|
||||
case (AssemblyParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
|
||||
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
|
||||
paramPreparation = compile(ctx, actualParam, Some(typ, v), BranchSpec.None)
|
||||
@ -1383,8 +1410,18 @@ object MfCompiler {
|
||||
???
|
||||
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:
|
||||
// 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))
|
||||
case ExpressionStatement(e@FunctionCallExpression(name, params)) =>
|
||||
env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match {
|
||||
case Some(i: InlinedFunction) =>
|
||||
val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params)
|
||||
SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx, inlinedStatements)))
|
||||
case Some(i: MacroFunction) =>
|
||||
val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params, e.position)
|
||||
SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx.withInlinedEnv(i.environment), inlinedStatements)))
|
||||
case _ =>
|
||||
LinearChunk(compile(ctx, e, None, NoBranching))
|
||||
}
|
||||
|
6
src/main/scala/millfork/env/Constant.scala
vendored
6
src/main/scala/millfork/env/Constant.scala
vendored
@ -148,7 +148,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
|
||||
}
|
||||
|
||||
object MathOperator extends Enumeration {
|
||||
val Plus, Minus, Times, Shl, Shr,
|
||||
val Plus, Minus, Times, Shl, Shr, Shl9, Shr9,
|
||||
DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShr,
|
||||
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.Shl => 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.Or => 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 Shl => f"$plhs << $prhs"
|
||||
case Shr => f"$plhs >> $prhs"
|
||||
case Shl9 => f"$plhs <<<< $prhs"
|
||||
case Shr9 => f"$plhs >>>> $prhs"
|
||||
case DecimalPlus => f"$plhs +' $prhs"
|
||||
case DecimalMinus => f"$plhs -' $prhs"
|
||||
case DecimalTimes => f"$plhs *' $prhs"
|
||||
|
38
src/main/scala/millfork/env/Environment.scala
vendored
38
src/main/scala/millfork/env/Environment.scala
vendored
@ -33,7 +33,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
val allThings: Map[String, Thing] = things.values.map {
|
||||
case m: FunctionInMemory =>
|
||||
m.environment.getAllPrefixedThings
|
||||
case m: InlinedFunction =>
|
||||
case m: MacroFunction =>
|
||||
m.environment.getAllPrefixedThings
|
||||
case _ => Map[String, Thing]()
|
||||
}.fold(things.toMap)(_ ++ _)
|
||||
@ -63,7 +63,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
|
||||
def allConstants: List[ConstantThing] = things.values.flatMap {
|
||||
case m: NormalFunction => m.environment.allConstants
|
||||
case m: InlinedFunction => m.environment.allConstants
|
||||
case m: MacroFunction => m.environment.allConstants
|
||||
case m: ConstantThing => List(m)
|
||||
case _ => Nil
|
||||
}.toList
|
||||
@ -105,7 +105,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
|
||||
)
|
||||
}
|
||||
case VariableAllocationMethod.Auto | VariableAllocationMethod.Static =>
|
||||
case VariableAllocationMethod.Auto | VariableAllocationMethod.Register | VariableAllocationMethod.Static =>
|
||||
m.sizeInBytes match {
|
||||
case 0 => Nil
|
||||
case 2 =>
|
||||
@ -278,6 +278,10 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
constantOperation(MathOperator.Shr, params)
|
||||
case "<<" =>
|
||||
constantOperation(MathOperator.Shl, params)
|
||||
case "<<<<" =>
|
||||
constantOperation(MathOperator.Shl9, params)
|
||||
case ">>>>" =>
|
||||
constantOperation(MathOperator.Shr9, params)
|
||||
case "*'" =>
|
||||
constantOperation(MathOperator.DecimalTimes, params)
|
||||
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.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.inlined) {
|
||||
if (stmt.isMacro) {
|
||||
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"Inline non-assembly function `$name` must return void", stmt.position)
|
||||
if (resultType != VoidType) ErrorReporting.error(s"Macro non-assembly function `$name` must return void", stmt.position)
|
||||
}
|
||||
if (stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly))
|
||||
ErrorReporting.error(s"Inline function `$name` cannot have by-variable parameters", stmt.position)
|
||||
if (stmt.assembly && stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly))
|
||||
ErrorReporting.error(s"Macro function `$name` cannot have by-variable parameters", stmt.position)
|
||||
} else {
|
||||
if (!stmt.assembly) {
|
||||
if (stmt.params.exists(!_.assemblyParamPassingConvention.isInstanceOf[ByVariable]))
|
||||
ErrorReporting.error(s"Non-assembly function `$name` cannot have non-variable parameters", stmt.position)
|
||||
}
|
||||
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 + "$")
|
||||
@ -432,9 +435,9 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
case e: ExecutableStatement => Some(e)
|
||||
case _ => None
|
||||
}
|
||||
val needsExtraRTS = !stmt.inlined && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement])
|
||||
if (stmt.inlined) {
|
||||
val mangled = InlinedFunction(
|
||||
val needsExtraRTS = !stmt.isMacro && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement])
|
||||
if (stmt.isMacro) {
|
||||
val mangled = MacroFunction(
|
||||
name,
|
||||
resultType,
|
||||
params,
|
||||
@ -603,6 +606,7 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
}
|
||||
if (stmt.constant) {
|
||||
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.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))
|
||||
@ -614,8 +618,12 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
}
|
||||
} else {
|
||||
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 && 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) {
|
||||
val v = StackVariable(prefix + name, typ, this.baseStackOffset)
|
||||
baseStackOffset += typ.size
|
||||
@ -626,7 +634,11 @@ class Environment(val parent: Option[Environment], val prefix: String) {
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
ErrorReporting.error(s"`$name` cannot be preinitialized`", position)
|
||||
}
|
||||
|
12
src/main/scala/millfork/env/Thing.scala
vendored
12
src/main/scala/millfork/env/Thing.scala
vendored
@ -117,7 +117,7 @@ sealed trait UninitializedMemory extends ThingInMemory {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -188,11 +188,11 @@ case class EmptyFunction(name: String,
|
||||
override def interrupt = false
|
||||
}
|
||||
|
||||
case class InlinedFunction(name: String,
|
||||
returnType: Type,
|
||||
params: ParamSignature,
|
||||
environment: Environment,
|
||||
code: List[ExecutableStatement]) extends MangledFunction {
|
||||
case class MacroFunction(name: String,
|
||||
returnType: Type,
|
||||
params: ParamSignature,
|
||||
environment: Environment,
|
||||
code: List[ExecutableStatement]) extends MangledFunction {
|
||||
override def interrupt = false
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,7 @@ case class VariableDeclarationStatement(name: String,
|
||||
stack: Boolean,
|
||||
constant: Boolean,
|
||||
volatile: Boolean,
|
||||
register: Boolean,
|
||||
initialValue: Option[Expression],
|
||||
address: Option[Expression]) extends DeclarationStatement {
|
||||
override def getAllExpressions: List[Expression] = List(initialValue, address).flatten
|
||||
@ -121,7 +122,8 @@ case class FunctionDeclarationStatement(name: String,
|
||||
params: List[ParameterDeclaration],
|
||||
address: Option[Expression],
|
||||
statements: Option[List[Statement]],
|
||||
inlined: Boolean,
|
||||
isMacro: Boolean,
|
||||
inlinable: Option[Boolean],
|
||||
assembly: Boolean,
|
||||
interrupt: Boolean,
|
||||
reentrant: Boolean) extends DeclarationStatement {
|
||||
|
@ -145,9 +145,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment)
|
||||
val assembly = mutable.ArrayBuffer[String]()
|
||||
|
||||
val potentiallyInlineable: Map[String, Int] =
|
||||
if (options.flags(CompilationFlag.InlineFunctions))
|
||||
InliningCalculator.getPotentiallyInlineableFunctions(program)
|
||||
else Map()
|
||||
InliningCalculator.getPotentiallyInlineableFunctions(program, options.flags(CompilationFlag.InlineFunctions))
|
||||
|
||||
var inlinedFunctions = Map[String, List[AssemblyLine]]()
|
||||
val compiledFunctions = mutable.Map[String, List[AssemblyLine]]()
|
||||
|
@ -14,10 +14,14 @@ object InliningCalculator {
|
||||
|
||||
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 allFunctions = mutable.Set[String]()
|
||||
val badFunctions = mutable.Set[String]()
|
||||
val recommendedFunctions = mutable.Set[String]()
|
||||
getAllCalledFunctions(program.declarations).foreach{
|
||||
case (name, true) => badFunctions += name
|
||||
case (name, false) => callCount(name) += 1
|
||||
@ -25,7 +29,11 @@ object InliningCalculator {
|
||||
program.declarations.foreach{
|
||||
case f:FunctionDeclarationStatement =>
|
||||
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.interrupt
|
||||
|| f.reentrant
|
||||
@ -34,7 +42,12 @@ object InliningCalculator {
|
||||
case _ =>
|
||||
}
|
||||
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 {
|
||||
@ -71,6 +84,9 @@ object InliningCalculator {
|
||||
if (code.isEmpty) return None
|
||||
if (code.last.opcode != RTS) return None
|
||||
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.exists(l => badOpcodes(l.opcode))) return None
|
||||
Some(result)
|
||||
|
@ -27,6 +27,9 @@ sealed trait ByteAllocator {
|
||||
if (counter == 0) {
|
||||
lastFree = i
|
||||
}
|
||||
if (count == 0) {
|
||||
return lastFree
|
||||
}
|
||||
counter += 1
|
||||
if (counter == count) {
|
||||
return lastFree
|
||||
|
@ -106,7 +106,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
|
||||
List("&&"),
|
||||
List("==", "<=", ">=", "!=", "<", ">"),
|
||||
List(":"),
|
||||
List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"),
|
||||
List("+'", "-'", "<<'", ">>'", "<<<<", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"),
|
||||
List("*'", "*"))
|
||||
|
||||
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 {
|
||||
p <- position()
|
||||
flags <- flags("const", "static", "volatile", "stack") ~ HWS
|
||||
flags <- flags("const", "static", "volatile", "stack", "register") ~ HWS
|
||||
typ <- identifier ~ SWS
|
||||
name <- identifier ~/ HWS ~/ Pass
|
||||
addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~ HWS
|
||||
@ -128,6 +128,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
|
||||
stack = flags("stack"),
|
||||
constant = flags("const"),
|
||||
volatile = flags("volatile"),
|
||||
register = flags("register"),
|
||||
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 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] =
|
||||
("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 {
|
||||
p <- position()
|
||||
flags <- flags("asm", "inline", "interrupt", "reentrant") ~ HWS
|
||||
flags <- flags("asm", "inline", "interrupt", "macro", "noinline", "reentrant") ~ HWS
|
||||
returnType <- identifier ~ SWS
|
||||
name <- identifier ~ HWS
|
||||
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
|
||||
addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("<address>") ~/ AWS
|
||||
statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass
|
||||
} 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("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 (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))
|
||||
@ -433,14 +437,14 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
|
||||
case _ => false
|
||||
}) 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 {
|
||||
case AssemblyStatement(Opcode.RTS, _, _, _) => () // OK
|
||||
case AssemblyStatement(Opcode.RTI, _, _, _) => () // OK
|
||||
case AssemblyStatement(Opcode.JMP, _, _, _) => () // OK
|
||||
case _ =>
|
||||
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 => ()
|
||||
@ -448,7 +452,8 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
|
||||
FunctionDeclarationStatement(name, returnType, params.toList,
|
||||
addr,
|
||||
statements,
|
||||
flags("inline"),
|
||||
flags("macro"),
|
||||
if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None,
|
||||
flags("asm"),
|
||||
flags("interrupt"),
|
||||
flags("reentrant")).pos(p)
|
||||
|
@ -8,12 +8,12 @@ import org.scalatest.{FunSuite, Matchers}
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
class AssemblyMacroSuite extends FunSuite with Matchers {
|
||||
|
||||
test("Poke test 1") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm void poke(word ref addr, byte a) {
|
||||
| macro asm void poke(word ref addr, byte a) {
|
||||
| STA addr
|
||||
| }
|
||||
|
|
||||
@ -28,7 +28,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
test("Peek test 1") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm byte peek(word ref addr) {
|
||||
| macro asm byte peek(word ref addr) {
|
||||
| ?LDA addr
|
||||
| }
|
||||
|
|
||||
@ -45,7 +45,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
test("Poke test 2") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm void poke(word const addr, byte a) {
|
||||
| macro asm void poke(word const addr, byte a) {
|
||||
| STA addr
|
||||
| }
|
||||
|
|
||||
@ -62,7 +62,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
test("Peek test 2") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm byte peek(word const addr) {
|
||||
| macro asm byte peek(word const addr) {
|
||||
| ?LDA addr
|
||||
| }
|
||||
|
|
||||
@ -80,7 +80,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
test("Labels test") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm void doNothing () {
|
||||
| macro asm void doNothing () {
|
||||
| JMP label
|
||||
| label:
|
||||
| }
|
@ -41,7 +41,7 @@ class AssemblyOptimizationSuite extends FunSuite with Matchers {
|
||||
"""
|
||||
| array output [100] @$C000
|
||||
| void main () {
|
||||
| byte i
|
||||
| register byte i
|
||||
| i = 1
|
||||
| while (i<50) {
|
||||
| output[i] = i
|
||||
|
@ -61,7 +61,7 @@ class AssemblySuite extends FunSuite with Matchers {
|
||||
""".stripMargin)(_.readByte(0xc000) should equal(10))
|
||||
}
|
||||
|
||||
test("Inline asm functions") {
|
||||
test("Macro asm functions") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| byte output @$c000
|
||||
@ -70,14 +70,14 @@ class AssemblySuite extends FunSuite with Matchers {
|
||||
| f()
|
||||
| f()
|
||||
| }
|
||||
| inline asm void f() {
|
||||
| macro asm void f() {
|
||||
| inc $c000
|
||||
| rts
|
||||
| }
|
||||
""".stripMargin)(_.readByte(0xc000) should equal(1))
|
||||
}
|
||||
|
||||
test("Inline asm functions 2") {
|
||||
test("macro asm functions 2") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| byte output @$c000
|
||||
@ -86,7 +86,7 @@ class AssemblySuite extends FunSuite with Matchers {
|
||||
| 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
|
||||
| clc
|
||||
| adc #c
|
||||
@ -104,7 +104,7 @@ class AssemblySuite extends FunSuite with Matchers {
|
||||
| output = 0
|
||||
| add256(output)
|
||||
| }
|
||||
| inline asm void add256(word ref v) {
|
||||
| macro asm void add256(word ref v) {
|
||||
| inc v+1
|
||||
| }
|
||||
""".stripMargin)(_.readWord(0xc000) should equal(0x100))
|
||||
|
@ -23,7 +23,7 @@ class BasicSymonTest extends FunSuite with Matchers {
|
||||
| void main () {
|
||||
| panic()
|
||||
| }
|
||||
| inline asm void panic() {
|
||||
| macro asm void panic() {
|
||||
| JSR _panic
|
||||
| }
|
||||
| void _panic() {
|
||||
|
@ -26,4 +26,26 @@ class NonetSuite extends FunSuite with Matchers {
|
||||
m.readByte(0xc001) should equal(0x88)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user