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:
parent
c599db0068
commit
0ca1be0c00
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
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 {
|
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"
|
||||||
|
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|
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 {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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]]()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
| }
|
| }
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user