Changes to macros and parameter list syntax:

* non-asm macros can now take `const` and `call` parameters
* register parameters to asm functions and macros can be given names if annotated explicitly
This commit is contained in:
Karol Stasiak 2020-03-30 19:23:48 +02:00
parent 5cdc599b1d
commit 63ff28e94e
50 changed files with 465 additions and 191 deletions

View File

@ -18,9 +18,20 @@ It implies the following:
* their invocations cannot be used as expressions
* in case of `asm` macros, the parameters must be defined as either `const` (compile-time constants) or `ref` (variables)
* in case of `asm` macros, the parameters:
* in case of non-`asm` macros, the parameters must be variables; exceptionally, their type may be declared as `void`
* must be defined as either `const` (compile-time constants), `ref` (variables) or `register(XX)` (registers, where XX is the register you want to use)
* at most one parameter can be defined as a register
* in case of non-`asm` macros, the parameters
* must be defined as either `ref` (variables; default, may be omitted) `const` (compile-time constants), or `call` (expressions, which are evaluated every time they are used)
* `ref` parameters exceptionally can have their type declared as `void`; such parameters accept variables of any type
* `call` parameters exceptionally can have their type declared as `void`;
such parameters accept expressions of any type, including `void`, however, you cannot assign from those expressions
* macros do not have their own scope (they reuse the scope from their invocations) exceptions: the parameters and the local labels defined in assembly
@ -28,9 +39,12 @@ It implies the following:
When invoking a macro, you need to pass variables as arguments to parameters annotated with `ref` and constants as arguments annotated with `const`.
Invoking a non-`asm` macro requires the types of passed variables to match precisely. No type conversions are performed.
Invoking a non-`asm` macro requires the types of variables via `ref` parameters to match precisely.
No type conversions are performed.
Exception: parameters of type `void` can accept a variable of any type.
For parameters defined as `const`, `register(XX)` or `call`, the usual type conversions are performed.
You can invoke a macro from assembly, by preceding the invocation with `+`
Examples:
@ -52,6 +66,14 @@ Examples:
inc(x)
return x
}
macro void perform_twice(void call f) {
f
f
}
byte add_two_3(byte x) {
perform_twice(inc(x))
return x
}
macro void add(byte b, byte v) {
b += v
@ -59,7 +81,7 @@ Examples:
macro void retu(byte result) {
return result
}
byte add_two_3(byte x) {
byte add_two_4(byte x) {
add(x, 2)
retu(x)
}
@ -68,13 +90,26 @@ Examples:
LDA b
CLC
ADC #v
STA b
// no RTS!
}
byte add_two_4(byte x) {
byte add_two_5(byte x) {
add_asm(x, 2)
return x
}
macro asm byte add_asm_2(byte ref b, byte register(x) v) {
TXA
CLC
ADC b
STA b
// no RTS!
}
byte add_two_6(byte x) {
add_asm_2(x, 2)
return x
}
## Automatic inlining

View File

@ -151,9 +151,11 @@ Non-macro functions can only have their parameters passed via registers:
* `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; any 2-byte type can be used
* the above, but written more explicitly: `byte register(a) paramname`, `byte register(x) paramname`, `word register(ax) paramname` etc.
For example, this piece of code:
asm void f(word ax) @F_ADDR extern
asm void f(word register(ax) value) @F_ADDR extern
f(5)
@ -179,7 +181,7 @@ Macro assembly functions can have maximum one parameter passed via a register.
An external function should be declared with a defined memory address
and the `extern` keyword instead of the body:
asm void putchar(byte a) @$FFD2 extern
asm void putchar(byte register(a) char) @$FFD2 extern
## Safe assembly

View File

@ -120,6 +120,8 @@ Non-macro functions can only have their parameters passed via registers:
* `word d`, `word x`, `word y`: a 2-byte word byte passed via given 16-bit register; any 2-byte type can be used
* the above, but written more explicitly: `byte register(a) paramname`, `byte register(b) paramname`, `word register(x) paramname` etc.
Parameters passed via other registers (`U`, `S` etc.) or combinations of registers do not work yet.
**Work in progress**:

View File

@ -160,6 +160,8 @@ Non-macro functions can only have their parameters passed via registers:
* `word hl`, `word bc`, `word de`: a 2-byte word byte passed via given 16-bit register; any 2-byte type can be used
* the above, but written more explicitly: `byte register(a) paramname`, `byte register(b) paramname`, `word register(hl) paramname` etc.
Parameters passed via other registers (`I`, `IX`, `IY`, `IXH` etc.) or combinations of registers do not work yet.
**Work in progress**:

View File

@ -14,7 +14,7 @@ Examples:
void do_nothing() { }
inline byte two() = 2
extern asm void chkout(byte a) @ $FFD2
extern asm void chkout(byte register(a) char) @ $FFD2
segment(prgrom0) void main_loop(word w, byte x) align(fast) { // body omitted

View File

@ -7,10 +7,10 @@
To call an external function, you need to declare it as `asm extern`. For example:
```
asm void putchar(byte a) @$FFD2 extern
asm void putchar(byte register(a) char) @$FFD2 extern
```
The function parameter will be passed via the accumulator,
In this example, the function parameter will be passed via the accumulator,
the function itself is located in ROM at $FFD2. A call like this:
```
@ -36,7 +36,7 @@ To call a function that has its address calculated dynamically,
you just need to do the same as what you would do in assembly; 6502 example:
```
asm void call_function(byte a) {
asm void call_function(byte register(a) param) {
JMP (function_address)
}
```

View File

@ -41,7 +41,7 @@ Get joypad2's state as a byte.
Simulates a hardware reset by jumping to the reset vector,
which then calls main().
#### `void ppu_set_addr(word ax)`
#### `void ppu_set_addr(word register(ax) address)`
Sets the PPU to point at the VRAM address at ax, usually in preparation
for a write via ppu_write_data().
@ -50,18 +50,18 @@ for a write via ppu_write_data().
Gets the PPU status byte.
#### `void ppu_set_scroll(byte a, byte x)`
#### `void ppu_set_scroll(byte register(a) xscroll, byte register(x) yscroll)`
Sets the PPU scroll register. Parameter a defines the horizontal
(X-axis) scroll value, and parameter x defines the vertical (Y-axis)
scroll value.
#### `void ppu_write_data(byte a)`
#### `void ppu_write_data(byte register(a) data)`
Writes a byte to the PPU's VRAM at the address the PPU
is currently pointing to. Usually used after a call to ppu_set_addr().
#### `void ppu_oam_dma_write(byte a)`
#### `void ppu_oam_dma_write(byte register(a) page)`
Initiates a DMA transfer of 256 bytes from CPU memory address $xx00-$xxFF
to PPU OAM memory, where xx is the hexadecimal representation of parameter a.
@ -71,18 +71,18 @@ to PPU OAM memory, where xx is the hexadecimal representation of parameter a.
The `nes_mmc4` module is imported automatically on the NES MMC4 target
and contains routines related to MMC4 bankswitching.
#### `void set_prg_bank(byte a)`
#### `void set_prg_bank(byte register(a) bank)`
Changes the $8000-$BFFF PRG bank.
#### `void set_chr_bank0(byte a)`
#### `void set_chr_bank0(byte register(a) bank)`
Changes the CHR bank 0 ($0000-$0fff in the PPU memory space).
The high nibble (0 or 1) selects between `chrrom0` and `chrrom1` segments.
The low nibble L (0-$F) selects a 4K-aligned address in the segment ($L000).
#### `void set_chr_bank1(byte a)`
#### `void set_chr_bank1(byte register(a) bank)`
Changes the CHR bank 1 ($1000-$1fff in the PPU memory space).

View File

@ -4,7 +4,7 @@
The `stdlib` module is automatically imported on most targets.
#### `macro asm void poke(word const addr, byte a)`
#### `macro asm void poke(word const addr, byte register(a) value)`
Stores a byte at given constant address. Will not be optimized away by the optimizer.
@ -20,11 +20,11 @@ Disables interrupts.
Enables interrupts.
#### `byte hi_nibble_to_hex(byte a)`
#### `byte hi_nibble_to_hex(byte register(a) value)`
Returns an ASCII representation of the upper nibble of the given byte.
#### `byte lo_nibble_to_hex(byte a)`
#### `byte lo_nibble_to_hex(byte register(a) value)`
Returns an ASCII representation of the lower nibble of the given byte.

View File

@ -3,7 +3,7 @@
#warn a8_kernel module should be used only on Atari computer-compatible targets
#endif
noinline asm void putchar(byte a) {
noinline asm void putchar(byte register(a) char) {
? tax
lda $347
pha

View File

@ -8,5 +8,5 @@ array hires_page_2 [$2000] @$4000
asm void bell() @$FBE4 extern
asm void putchar(byte a) @$FDED extern
asm void putchar(byte register(a) char) @$FDED extern
asm void new_line() @$FC62 extern

View File

@ -5,7 +5,7 @@
// OSASCI. Write a character (to screen) from A plus LF if (A)=&0D
// Input: A = Byte to write.
asm void putchar(byte a) @$FFE3 extern
asm void putchar(byte register(a) char) @$FFE3 extern
inline void new_line() {
putchar(13)

View File

@ -6,9 +6,9 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte a) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 extern
asm void putchar(byte a) {
asm void putchar(byte register(a) char) {
JSR chrout
LDA #0
STA $F4

View File

@ -8,7 +8,7 @@ import c264_kernal
import err
// print a 16-bit number on the standard output
asm void putword_basic(word xa) @$A45F extern
asm void putword_basic(word register(xa) num) @$A45F extern
alias putword = putword_basic!

View File

@ -5,9 +5,9 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte a) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 extern
asm void putchar(byte a) {
asm void putchar(byte register(a) char) {
JSR chrout
LDA #0
STA $CB

View File

@ -8,7 +8,7 @@ import c64_kernal
import err
// print a 16-bit number on the standard output
asm void putword_basic(word xa) @$BDCD extern
asm void putword_basic(word register(xa) num) @$BDCD extern
alias putword = putword_basic!

View File

@ -1,6 +1,6 @@
import err
inline asm void switch_hirom(byte a) {
inline asm void switch_hirom(byte register(a) bank) {
? and #$3F
! sta $DE01
? rts
@ -25,17 +25,17 @@ inline asm void switch_hirom(byte a) {
#endif
// address, value, returns true if ok
asm clear_carry eapi_write_flash(pointer xy, byte a) @ $DF80 extern
asm clear_carry eapi_write_flash(pointer register(xy) address, byte register(a) value) @ $DF80 extern
// bank, page ($80, $A0 or $E0), returns true if ok
asm clear_carry eapi_erase_sector(byte a, byte y) @ $DF83 extern
asm clear_carry eapi_erase_sector(byte register(a) bank, byte register(y) page) @ $DF83 extern
// bank
asm void eapi_set_bank(byte a) @ $DF86 extern
asm void eapi_set_bank(byte register(a) bank) @ $DF86 extern
// returns bank
asm byte eapi_get_bank() @ $DF89 extern
// bank mode, address
asm void eapi_set_ptr(byte a, pointer xy) @ $DF8C extern
asm void eapi_set_ptr(byte register(a) bank_mode, pointer register(xy) address) @ $DF8C extern
// length.b2, length.loword
asm void eapi_set_len(byte a, word xy) @ $DF8F extern
asm void eapi_set_len(byte register(a) length_b2, word register(xy) length_loword) @ $DF8F extern
asm byte eapi_read_flash_inc() @ $DF92 extern
asm clear_carry eapi_write_flash_inc(byte a) @ $DF95 extern
asm void eapi_set_slot(byte a) @ $DF98 extern

View File

@ -2,7 +2,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte a) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 extern
// CHRIN. Read byte from default input (for keyboard, read a line from the screen). (If not keyboard, must call OPEN and CHKIN beforehands.)
// Output: A = Byte read.
@ -11,11 +11,11 @@ alias getchar = chrin
// CHKIN. Define file as default input. (Must call OPEN beforehands.)
// Input: X = Logical number.
asm void chkin(byte x) @$FFC6 extern
asm void chkin(byte register(x) fd) @$FFC6 extern
// CHKOUT. Define file as default output. (Must call OPEN beforehands.)
// Input: X = Logical number.
asm void chkout(byte x) @$FFC9 extern
asm void chkout(byte register(x) fd) @$FFC9 extern
// CLRCHN. Close default input/output files (for serial bus, send UNTALK and/or UNLISTEN); restore default input/output to keyboard/screen.
asm void clrchn() @$FFCC extern
@ -24,7 +24,7 @@ asm void clrchn() @$FFCC extern
// Output: A = Device status.
asm byte readst() @$FFB7 extern
asm void putchar(byte a) {
asm void putchar(byte register(a) char) {
JSR chrout
LDA #0
STA $D4
@ -40,24 +40,24 @@ asm void open() @$FFC0 extern
// CLOSE. Close file.
// Input: A = Logical number.
asm void close(byte a) @$FFC3 extern
asm void close(byte register(a) fd) @$FFC3 extern
// SETLFS. Set file parameters.
// Input: A = Logical number; X = Device number; Y = Secondary address.
asm void setlfs(byte a, byte x, byte y) @$FFBA extern
asm void setlfs(byte register(a) fd, byte register(x) device, byte register(y) secondary) @$FFBA extern
// SETNAM. Set file name parameters.
// Input: A = File name length; X/Y = Pointer to file name.
asm void setnam(word xy, byte a) @$FFBD extern
asm void setnam(pointer register(xy) filename, byte register(a) len) @$FFBD extern
// LOAD. Load or verify file. (Must call SETLFS and SETNAM beforehands.)
// Input: A: 0 = Load, 1-255 = Verify; X/Y = Load address (if secondary address = 0).
// Output: Carry: 0 = No errors, 1 = Error; A = KERNAL error code (if Carry = 1); X/Y = Address of last byte loaded/verified (if Carry = 0).
asm clear_carry load(byte a, word xy) @$FFD5 extern
asm clear_carry load(bool register(a) verify, pointer register(xy) address) @$FFD5 extern
// SAVE. Save file. (Must call SETLFS and SETNAM beforehands.)
// Input: A = Address of zero page register holding start address of memory area to save; X/Y = End address of memory area plus 1.
// Output: Carry: 0 = No errors, 1 = Error; A = KERNAL error code (if Carry = 1).
asm clear_carry save(byte a, word xy) @$FFD8 extern
asm clear_carry save(byte register(a) address, word register(xy) past_end) @$FFD8 extern
word irq_pointer @$314

View File

@ -3,7 +3,7 @@
#warn cpc module should be only used on Amstrad CPC-compatible targets
#endif
asm void putchar(byte a) @$BB5A extern
asm void putchar(byte register(a) char) @$BB5A extern
inline void new_line() {
putchar(13)

View File

@ -1,18 +1,18 @@
#if ENCODING_SAME
#if ARCH_6502
inline byte __byte_identity(byte a) { ? rts }
inline void __pointer_to_void(pointer ax) { ? rts }
inline byte __byte_identity(byte register(a) value) { ? rts }
inline void __pointer_to_void(pointer register(ax) value) { ? rts }
#elseif ARCH_I80
#pragma zilog_syntax
inline byte __byte_identity(byte a) { ? ret }
inline void __pointer_to_void(pointer hl) { ? ret }
inline byte __byte_identity(byte register(a) value) { ? ret }
inline void __pointer_to_void(pointer register(hl) value) { ? ret }
#elseif ARCH_M6809
inline byte __byte_identity(byte b) { ? rts }
inline void __pointer_to_void(pointer d) { ? rts }
inline byte __byte_identity(byte register(b) value) { ? rts }
inline void __pointer_to_void(pointer register(d) value) { ? rts }
#else
inline byte __byte_identity(byte a) = a
inline void __pointer_to_void(pointer a) { }
inline byte __byte_identity(byte register(a) value) = a
inline void __pointer_to_void(pointer register(a) value) { }
#endif
alias from_screencode = __byte_identity
@ -89,7 +89,7 @@ void strz_to_screencode(pointer p) {
#if ARCH_6502
asm byte petscii_to_petscr(byte a) {
asm byte petscii_to_petscr(byte register(a) char) {
cmp #$20
bcc __petscii_to_petscr_ddRev
cmp #$60
@ -121,7 +121,7 @@ __petscii_to_petscr_ddRev:
rts
}
asm byte petscr_to_petscii(byte a) {
asm byte petscr_to_petscii(byte register(a) char) {
cmp #$20
bcs __petscr_to_petscii_40
ora #$40
@ -150,7 +150,7 @@ __petscr_to_petscii_c0:
rts
}
asm byte atascii_to_atasciiscr(byte a) {
asm byte atascii_to_atasciiscr(byte register(a) char) {
and #$7f
cmp #$20
bcs __atascii_to_atasciiscr_60
@ -166,7 +166,7 @@ __atascii_to_atasciiscr_end:
}
asm byte atasciiscr_to_atascii(byte a) {
asm byte atasciiscr_to_atascii(byte register(a) char) {
and #$7f
cmp #$40
bcs __atascii_to_atasciiscr_60

View File

@ -5,7 +5,7 @@
#pragma zilog_syntax
asm void putchar(byte a) @$00a2 extern
asm void putchar(byte register(a) char) @$00a2 extern
inline void new_line() {
putchar(13)

View File

@ -61,7 +61,7 @@ macro asm void simulate_reset() {
JMP (reset_vector.addr)
}
inline asm void ppu_set_addr(word ax) {
inline asm void ppu_set_addr(word register(ax) address) {
! STX ppu_addr
! STA ppu_addr
?RTS
@ -72,19 +72,19 @@ inline asm byte read_ppu_status() {
? RTS
}
inline asm void ppu_set_scroll(byte a, byte x) {
inline asm void ppu_set_scroll(byte register(a) xscroll, byte register(x) yscroll) {
BIT ppu_status
! STA ppu_scroll
! STX ppu_scroll
? RTS
}
inline asm void ppu_write_data(byte a) {
inline asm void ppu_write_data(byte register(a) data) {
! STA ppu_data
? RTS
}
inline asm void ppu_oam_dma_write(byte a) {
inline asm void ppu_oam_dma_write(byte register(a) page) {
! STA oam_dma
? RTS
}

View File

@ -2,20 +2,20 @@
#warn nes_mmc4 module should be only used on NES/Famicom targets
#endif
asm inline void set_prg_bank(byte a) {
asm inline void set_prg_bank(byte register(a) bank) {
STA $A000
? RTS
}
// calling with with e.g. $1E will set $0000-$0fff to $E000-$EFFF in chrrom1
asm inline void set_chr_bank0(byte a) {
asm inline void set_chr_bank0(byte register(a) bank) {
STA $B000
STA $C000
? RTS
}
// calling with with e.g. $1E will set $1000-$1fff to $E000-$EFFF in chrrom1
asm inline void set_chr_bank1(byte a) {
asm inline void set_chr_bank1(byte register(a) bank) {
STA $D000
STA $E000
? RTS

View File

@ -4,7 +4,7 @@
#warn pc88 module should be only used on PC-88 targets
#endif
asm void putchar(byte a) @$3e0d extern
asm void putchar(byte register(a) char) @$3e0d extern
inline void new_line() {
putchar(13)

View File

@ -6,7 +6,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void putchar(byte a) @$FFD2 extern
asm void putchar(byte register(a) char) @$FFD2 extern
inline void new_line() {
putchar(13)

View File

@ -8,7 +8,7 @@ word nmi_routine_addr @$FFFA
word reset_routine_addr @$FFFC
word irq_routine_addr @$FFFE
macro asm void poke(word const addr, byte a) {
macro asm void poke(word const addr, byte register(a) value) {
! STA addr
}
@ -24,7 +24,7 @@ macro asm void enable_irq() {
CLI
}
asm byte hi_nibble_to_hex(byte a) {
asm byte hi_nibble_to_hex(byte register(a) value) {
LSR
LSR
LSR
@ -32,7 +32,7 @@ asm byte hi_nibble_to_hex(byte a) {
? JMP lo_nibble_to_hex
}
asm byte lo_nibble_to_hex(byte a) {
asm byte lo_nibble_to_hex(byte register(a) value) {
! AND #$F
CLC
ADC #$30

View File

@ -8,7 +8,7 @@
import i80_math
macro asm void poke(word const addr, byte a) {
macro asm void poke(word const addr, byte register(a) value) {
! LD (addr), A
}
@ -24,7 +24,7 @@ macro asm void enable_irq() {
EI
}
asm byte hi_nibble_to_hex(byte a) {
asm byte hi_nibble_to_hex(byte register(a) value) {
#if CPUFEATURE_GAMEBOY
SWAP A
#else
@ -36,7 +36,7 @@ asm byte hi_nibble_to_hex(byte a) {
? JP lo_nibble_to_hex
}
asm byte lo_nibble_to_hex(byte a) {
asm byte lo_nibble_to_hex(byte register(a) value) {
AND $F
ADD A,$30
CP $3A

View File

@ -6,9 +6,9 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte a) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 extern
asm void putchar(byte a) {
asm void putchar(byte register(a) char) {
JSR chrout
LDA #0
STA $D4

View File

@ -157,12 +157,12 @@ struct vera_sprite_data {
byte ctrl1
}
inline asm void set_ram_bank(byte a) {
inline asm void set_ram_bank(byte register(a) bank) {
! STA $9F61
? RTS
}
inline asm void set_rom_bank(byte a) {
inline asm void set_rom_bank(byte register(a) bank) {
! STA $9F60
? RTS
}

View File

@ -5,7 +5,7 @@
#pragma zilog_syntax
inline asm void putchar(byte a) {
inline asm void putchar(byte register(a) char) {
rst $10
? ret
}
@ -14,7 +14,7 @@ inline void new_line() {
putchar(13)
}
inline asm void set_border(byte a) {
inline asm void set_border(byte register(a) colour) {
out (254),a
? ret
}

View File

@ -135,7 +135,7 @@ object ReverseFlowAnalyzer {
// this case has to be handled first, because the generic JSR importance handler is too conservative
var result = importanceBeforeJsr
fun.params match {
case AssemblyParamSignature(params) =>
case AssemblyOrMacroParamSignature(params) =>
params.foreach(_.variable match {
case M6809RegisterVariable(M6809Register.A, _) =>
result = result.copy(a = Important)

View File

@ -192,7 +192,7 @@ object ReverseFlowAnalyzer {
// this case has to be handled first, because the generic JSR importance handler is too conservative
var result = importanceBeforeJsr
fun.params match {
case AssemblyParamSignature(params) =>
case AssemblyOrMacroParamSignature(params) =>
params.foreach(_.variable match {
case RegisterVariable(MosRegister.A, _) =>
result = result.copy(a = Important)

View File

@ -2,7 +2,7 @@ package millfork.assembly.z80.opt
import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext}
import millfork.assembly.z80._
import millfork.env.{AssemblyParam, AssemblyParamSignature, FunctionInMemory, MemoryAddressConstant, NormalFunction, NormalParamSignature, NumericConstant, ParamSignature, ZRegisterVariable}
import millfork.env.{AssemblyOrMacroParam, AssemblyOrMacroParamSignature, FunctionInMemory, MemoryAddressConstant, NormalFunction, NormalParamSignature, NumericConstant, ParamSignature, ZRegisterVariable}
import millfork.error.Logger
import millfork.node.ZRegister
@ -45,7 +45,7 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
override def name = "Changing registers pairs"
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
if (f.params.isInstanceOf[AssemblyParamSignature]) return code
if (f.params.isInstanceOf[AssemblyOrMacroParamSignature]) return code
val usesBC = code.exists(l => (l.readsRegister(BC) || l.changesRegister(BC)) && l.opcode != CALL)
val usesDE = code.exists(l => (l.readsRegister(DE) || l.changesRegister(DE)) && l.opcode != CALL)
val canDE2BC = f.returnType.size < 3 && canOptimize(code, DE2BC, Loaded()) && canOptimize2(code, DE2BC) && (f.params match {
@ -102,8 +102,8 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
private def readsBCorDE(params: ParamSignature): Boolean = {
import ZRegister._
params match {
case AssemblyParamSignature(ps) => ps.exists {
case AssemblyParam(_, ZRegisterVariable(B | C | D | E | BC | DE, _), _) => true
case AssemblyOrMacroParamSignature(ps) => ps.exists {
case AssemblyOrMacroParam(_, ZRegisterVariable(B | C | D | E | BC | DE, _), _) => true
case _ => false
}
case NormalParamSignature(List(p)) => p.typ.size == 3 || p.typ.size == 4

View File

@ -435,7 +435,7 @@ object ReverseFlowAnalyzer {
sf = Unimportant,
hf = Unimportant
)
case NormalParamSignature(_) | AssemblyParamSignature(Nil) =>
case NormalParamSignature(_) | AssemblyOrMacroParamSignature(Nil) =>
currentImportance = currentImportance.copy(
a = if (readsA(n)) Important else Unimportant,
b = if (readsB(n)) Important else Unimportant,

View File

@ -1,6 +1,7 @@
package millfork.compiler
import millfork.assembly.AbstractCode
import millfork.assembly.m6809.MOpcode
import millfork.assembly.mos._
import millfork.assembly.z80.ZOpcode
import millfork.env._
@ -11,8 +12,13 @@ import millfork.node._
*/
abstract class MacroExpander[T <: AbstractCode] {
def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyParam], params: List[Expression], code: List[ExecutableStatement]): (List[T], List[ExecutableStatement])
def stmtPreprocess(ctx: CompilationContext, stmts: List[ExecutableStatement]): List[ExecutableStatement]
def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyOrMacroParam], params: List[Expression], code: List[ExecutableStatement]): (List[T], List[ExecutableStatement])
def replaceVariableX(stmt: Statement, paramName: String, target: Expression): ExecutableStatement = {
replaceVariable(stmt, paramName, target).asInstanceOf[ExecutableStatement]
}
def replaceVariable(stmt: Statement, paramName: String, target: Expression): Statement = {
val paramNamePeriod = paramName + "."
def f[S <: Expression](e: S) = e.replaceVariable(paramName, target)
@ -52,6 +58,10 @@ abstract class MacroExpander[T <: AbstractCode] {
}).pos(stmt.position)
}
def renameVariableX(stmt: Statement, paramName: String, target: String): ExecutableStatement = {
renameVariable(stmt, paramName, target).asInstanceOf[ExecutableStatement]
}
def renameVariable(stmt: Statement, paramName: String, target: String): Statement = {
val paramNamePeriod = paramName + "."
def f[S <: Expression](e: S) = e.renameVariable(paramName, target)
@ -91,24 +101,41 @@ abstract class MacroExpander[T <: AbstractCode] {
}).pos(stmt.position)
}
def inlineFunction(ctx: CompilationContext, i: MacroFunction, params: List[Expression], position: Option[Position]): (List[T], List[ExecutableStatement]) = {
def inlineFunction(ctx: CompilationContext, i: MacroFunction, actualParams: List[Expression], position: Option[Position]): (List[T], List[ExecutableStatement]) = {
var paramPreparation = List[T]()
var actualCode = i.code
i.params match {
case AssemblyParamSignature(assParams) =>
val pair = prepareAssemblyParams(ctx, assParams, params, i.code)
paramPreparation = pair._1
actualCode = pair._2
case NormalParamSignature(normalParams) =>
if (params.length != normalParams.length) {
case AssemblyOrMacroParamSignature(params) =>
params.foreach{ param =>
val vName = param.variable.name
i.environment.removeVariable(vName)
ctx.env.maybeGet[Thing](vName) match {
case Some(thing) => i.environment.things += vName -> thing
case None =>
}
}
if (actualParams.length != params.length) {
ctx.log.error(s"Invalid number of params for macro function ${i.name}", position)
} else if (i.isInAssembly) {
val pair = prepareAssemblyParams(ctx, params, actualParams, i.code)
paramPreparation = pair._1
actualCode = pair._2
} else {
normalParams.foreach(param => i.environment.removeVariable(param.name))
params.zip(normalParams).foreach {
case (v@VariableExpression(_), MemoryVariable(paramName, paramType, _)) =>
actualCode = actualCode.map(stmt => renameVariable(stmt, paramName.stripPrefix(i.environment.prefix), v.name).asInstanceOf[ExecutableStatement])
case (v@IndexedExpression(_, _), MemoryVariable(paramName, paramType, _)) =>
actualCode = actualCode.map(stmt => replaceVariable(stmt, paramName.stripPrefix(i.environment.prefix), v).asInstanceOf[ExecutableStatement])
actualParams.zip(params).foreach {
case (v@VariableExpression(_), AssemblyOrMacroParam(_, paramVariable, AssemblyParameterPassingBehaviour.ByReference)) =>
actualCode = actualCode.map(stmt => renameVariableX(stmt, paramVariable.name.stripPrefix(i.environment.prefix), v.name))
case (v@IndexedExpression(_, _), AssemblyOrMacroParam(_, paramVariable, AssemblyParameterPassingBehaviour.ByReference)) =>
actualCode = actualCode.map(stmt => replaceVariableX(stmt, paramVariable.name.stripPrefix(i.environment.prefix), v))
case (expr, AssemblyOrMacroParam(_, _, AssemblyParameterPassingBehaviour.ByReference)) =>
ctx.log.error("By-reference parameters to macro functions have to be variables", expr.position)
case (expr, AssemblyOrMacroParam(_, paramVariable, AssemblyParameterPassingBehaviour.ByConstant)) =>
if (ctx.env.eval(expr).isEmpty) {
ctx.log.error("Const parameters to macro functions have to be constants", expr.position)
}
actualCode = actualCode.map(stmt => replaceVariableX(stmt, paramVariable.name.stripPrefix(i.environment.prefix), expr))
case (expr, AssemblyOrMacroParam(paramType, paramVariable, AssemblyParameterPassingBehaviour.Eval)) =>
val castParam = FunctionCallExpression(paramType.name, List(expr))
actualCode = actualCode.map(stmt => replaceVariableX(stmt, paramVariable.name.stripPrefix(i.environment.prefix), castParam))
case _ =>
ctx.log.error(s"Parameters to macro functions have to be variables", position)
}
@ -120,14 +147,18 @@ abstract class MacroExpander[T <: AbstractCode] {
val localLabels = actualCode.flatMap {
case MosAssemblyStatement(Opcode.LABEL, _, VariableExpression(l), _) => Some(l)
case Z80AssemblyStatement(ZOpcode.LABEL, _, _, VariableExpression(l), _) => Some(l)
case M6809AssemblyStatement(MOpcode.LABEL, _, VariableExpression(l), _) => Some(l)
case _ => None
}.toSet
val labelPrefix = ctx.nextLabel("il")
actualCode = stmtPreprocess(ctx, actualCode)
paramPreparation -> actualCode.map {
case s@MosAssemblyStatement(_, _, VariableExpression(v), _) if localLabels(v) =>
s.copy(expression = VariableExpression(labelPrefix + v))
case s@Z80AssemblyStatement(_, _, _, VariableExpression(v), _) if localLabels(v) =>
s.copy(expression = VariableExpression(labelPrefix + v))
case s@M6809AssemblyStatement(_, _, VariableExpression(v), _) if localLabels(v) =>
s.copy(expression = VariableExpression(labelPrefix + v))
case s => s
}
}

View File

@ -5,7 +5,7 @@ import millfork.assembly.m6809.{DAccumulatorIndexed, Immediate, Indexed, Inheren
import millfork.compiler.{AbstractExpressionCompiler, BranchIfFalse, BranchIfTrue, BranchSpec, ComparisonType, CompilationContext, NoBranching}
import millfork.node.{DerefExpression, Expression, FunctionCallExpression, GeneratedConstantExpression, IndexedExpression, LhsExpression, LiteralExpression, M6809Register, SumExpression, VariableExpression}
import millfork.assembly.m6809.MOpcode._
import millfork.env.{AssemblyParamSignature, BuiltInBooleanType, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FlagBooleanType, FunctionInMemory, FunctionPointerType, Label, M6809RegisterVariable, MacroFunction, MathOperator, MemoryAddressConstant, MemoryVariable, NonFatalCompilationException, NormalFunction, NormalParamSignature, NumericConstant, StackVariablePointy, ThingInMemory, Type, Variable, VariableInMemory, VariablePointy}
import millfork.env.{AssemblyOrMacroParamSignature, BuiltInBooleanType, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FlagBooleanType, FunctionInMemory, FunctionPointerType, Label, M6809RegisterVariable, MacroFunction, MathOperator, MemoryAddressConstant, MemoryVariable, NonFatalCompilationException, NormalFunction, NormalParamSignature, NumericConstant, StackVariablePointy, ThingInMemory, Type, Variable, VariableInMemory, VariablePointy}
import scala.collection.GenTraversableOnce
@ -428,7 +428,7 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
???
}
}
case AssemblyParamSignature(signature) =>
case AssemblyOrMacroParamSignature(signature) =>
params.zip(signature).flatMap { case (e, a) =>
val compiled = a.variable match {
case M6809RegisterVariable(M6809Register.A, _) => compileToA(ctx, e)

View File

@ -13,12 +13,14 @@ import millfork.node._
*/
object M6809MacroExpander extends MacroExpander[MLine] {
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyParam], params: List[Expression], code: List[ExecutableStatement]): (List[MLine], List[ExecutableStatement]) = {
override def stmtPreprocess(ctx: CompilationContext, stmts: List[ExecutableStatement]): List[ExecutableStatement] = new M6809StatementPreprocessor(ctx, stmts)()
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyOrMacroParam], params: List[Expression], code: List[ExecutableStatement]): (List[MLine], List[ExecutableStatement]) = {
var paramPreparation = List[MLine]()
var actualCode = code
var hadRegisterParam = false
assParams.zip(params).foreach {
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
case (AssemblyOrMacroParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
actualParam match {
case VariableExpression(vname) =>
ctx.env.get[ThingInMemory](vname)
@ -33,14 +35,14 @@ object M6809MacroExpander extends MacroExpander[MLine] {
a.copy(expression = expr.replaceVariable(ph, actualParam))
case x => x
}
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
case (AssemblyOrMacroParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
ctx.env.eval(actualParam).getOrElse(ctx.env.errorConstant("Non-constant expression was passed to an inlineable function as a `const` parameter", actualParam.position))
actualCode = actualCode.map {
case a@Z80AssemblyStatement(_, _, _, expr, _) =>
a.copy(expression = expr.replaceVariable(ph, actualParam))
case x => x
}
case (AssemblyParam(typ, v@M6809RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
case (AssemblyOrMacroParam(typ, v@M6809RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
if (hadRegisterParam) {
ctx.log.error("Only one macro assembly function parameter can be passed via a register", actualParam.position)
}
@ -56,10 +58,10 @@ object M6809MacroExpander extends MacroExpander[MLine] {
ctx.log.error(s"Invalid parameter for macro: ${typ.name} ${register.toString.toLowerCase(Locale.ROOT)}", actualParam.position)
Nil
}
case (AssemblyParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
case (AssemblyOrMacroParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
???
case (_, actualParam) =>
}
paramPreparation -> actualCode
paramPreparation -> stmtPreprocess(ctx, actualCode)
}
}

View File

@ -1745,15 +1745,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case _ => ()
}
val result = function.params match {
case AssemblyParamSignature(paramConvs) =>
case AssemblyOrMacroParamSignature(paramConvs) =>
val pairs = params.zip(paramConvs)
val secondViaMemory = pairs.flatMap {
case (paramExpr, AssemblyParam(typ, paramVar: VariableInMemory, AssemblyParameterPassingBehaviour.Copy)) =>
case (paramExpr, AssemblyOrMacroParam(typ, paramVar: VariableInMemory, AssemblyParameterPassingBehaviour.Copy)) =>
compile(ctx, paramExpr, Some(typ -> paramVar), NoBranching)
case _ => Nil
}
val thirdViaRegisters = pairs.flatMap {
case (paramExpr, AssemblyParam(typ, paramVar@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy)) =>
case (paramExpr, AssemblyOrMacroParam(typ, paramVar@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy)) =>
Some(register -> compile(ctx, paramExpr, Some(typ -> paramVar), NoBranching))
case _ => Nil
} match {

View File

@ -11,12 +11,14 @@ import millfork.node._
*/
object MosMacroExpander extends MacroExpander[AssemblyLine] {
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyParam], params: List[Expression], code: List[ExecutableStatement]): (List[AssemblyLine], List[ExecutableStatement]) = {
override def stmtPreprocess(ctx: CompilationContext, stmts: List[ExecutableStatement]): List[ExecutableStatement] = new MosStatementPreprocessor(ctx, stmts)()
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyOrMacroParam], params: List[Expression], code: List[ExecutableStatement]): (List[AssemblyLine], List[ExecutableStatement]) = {
var paramPreparation = List[AssemblyLine]()
var actualCode = code
var hadRegisterParam = false
assParams.zip(params).foreach {
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
case (AssemblyOrMacroParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
actualParam match {
case VariableExpression(vname) =>
ctx.env.get[ThingInMemory](vname)
@ -31,23 +33,23 @@ object MosMacroExpander extends MacroExpander[AssemblyLine] {
a.copy(expression = expr.replaceVariable(ph, actualParam))
case x => x
}
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
case (AssemblyOrMacroParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
ctx.env.eval(actualParam).getOrElse(ctx.env.errorConstant("Non-constant expression was passed to an inlineable function as a `const` parameter", actualParam.position))
actualCode = actualCode.map {
case a@MosAssemblyStatement(_, _, expr, _) =>
a.copy(expression = expr.replaceVariable(ph, actualParam))
case x => x
}
case (AssemblyParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
case (AssemblyOrMacroParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
if (hadRegisterParam) {
ctx.log.error("Only one macro assembly function parameter can be passed via a register", actualParam.position)
}
hadRegisterParam = true
paramPreparation = MosExpressionCompiler.compile(ctx, actualParam, Some(typ, v), BranchSpec.None)
case (AssemblyParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
case (AssemblyOrMacroParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
???
case (_, actualParam) =>
}
paramPreparation -> actualCode
paramPreparation -> stmtPreprocess(ctx, actualCode)
}
}

View File

@ -1236,34 +1236,34 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case _ => ()
}
val result = function.params match {
case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(ZRegister.A, typ2), AssemblyParameterPassingBehaviour.Copy)))
case AssemblyOrMacroParamSignature(List(AssemblyOrMacroParam(typ1, ZRegisterVariable(ZRegister.A, typ2), AssemblyParameterPassingBehaviour.Copy)))
if typ1.size == 1 && typ2.size == 1 =>
compileToA(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress)
case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(
case AssemblyOrMacroParamSignature(List(AssemblyOrMacroParam(typ1, ZRegisterVariable(
register@(ZRegister.B | ZRegister.C | ZRegister.D | ZRegister.E | ZRegister.H | ZRegister.L),
typ2), AssemblyParameterPassingBehaviour.Copy)))
if typ1.size == 1 && typ2.size == 1 =>
compile8BitTo(ctx, params.head, register) :+ ZLine(CALL, NoRegisters, function.toAddress)
case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(ZRegister.HL, typ2), AssemblyParameterPassingBehaviour.Copy)))
case AssemblyOrMacroParamSignature(List(AssemblyOrMacroParam(typ1, ZRegisterVariable(ZRegister.HL, typ2), AssemblyParameterPassingBehaviour.Copy)))
if typ1.size == 2 && typ2.size == 2 =>
compileToHL(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress)
case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(ZRegister.DE, typ2), AssemblyParameterPassingBehaviour.Copy)))
case AssemblyOrMacroParamSignature(List(AssemblyOrMacroParam(typ1, ZRegisterVariable(ZRegister.DE, typ2), AssemblyParameterPassingBehaviour.Copy)))
if typ1.size == 2 && typ2.size == 2 =>
compileToDE(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress)
case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(ZRegister.BC, typ2), AssemblyParameterPassingBehaviour.Copy)))
case AssemblyOrMacroParamSignature(List(AssemblyOrMacroParam(typ1, ZRegisterVariable(ZRegister.BC, typ2), AssemblyParameterPassingBehaviour.Copy)))
if typ1.size == 2 && typ2.size == 2 =>
compileToBC(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress)
case AssemblyParamSignature(Nil) =>
case AssemblyOrMacroParamSignature(Nil) =>
List(ZLine(CALL, NoRegisters, function.toAddress))
case AssemblyParamSignature(paramConvs) =>val pairs = params.zip(paramConvs)
case AssemblyOrMacroParamSignature(paramConvs) =>val pairs = params.zip(paramConvs)
val viaMemory = pairs.flatMap {
case (paramExpr, AssemblyParam(typ, paramVar: VariableInMemory, AssemblyParameterPassingBehaviour.Copy)) =>
case (paramExpr, AssemblyOrMacroParam(typ, paramVar: VariableInMemory, AssemblyParameterPassingBehaviour.Copy)) =>
ctx.log.error("Variable parameters to assembly functions are not supported", expression.position)
Nil
case _ => Nil
}
val viaRegisters = pairs.flatMap {
case (paramExpr, AssemblyParam(typ, paramVar@ZRegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy)) =>
case (paramExpr, AssemblyOrMacroParam(typ, paramVar@ZRegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy)) =>
if (typ.size != ZRegister.registerSize(register)) {
ctx.log.error(s"Type ${typ.name} and register $register are of different sizes", expression.position)
}

View File

@ -13,12 +13,14 @@ import millfork.node._
*/
object Z80MacroExpander extends MacroExpander[ZLine] {
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyParam], params: List[Expression], code: List[ExecutableStatement]): (List[ZLine], List[ExecutableStatement]) = {
override def stmtPreprocess(ctx: CompilationContext, stmts: List[ExecutableStatement]): List[ExecutableStatement] = new Z80StatementPreprocessor(ctx, stmts)()
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyOrMacroParam], params: List[Expression], code: List[ExecutableStatement]): (List[ZLine], List[ExecutableStatement]) = {
var paramPreparation = List[ZLine]()
var actualCode = code
var hadRegisterParam = false
assParams.zip(params).foreach {
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
case (AssemblyOrMacroParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
actualParam match {
case VariableExpression(vname) =>
ctx.env.get[ThingInMemory](vname)
@ -33,14 +35,14 @@ object Z80MacroExpander extends MacroExpander[ZLine] {
a.copy(expression = expr.replaceVariable(ph, actualParam))
case x => x
}
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
case (AssemblyOrMacroParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
ctx.env.eval(actualParam).getOrElse(ctx.env.errorConstant("Non-constant expression was passed to an inlineable function as a `const` parameter", actualParam.position))
actualCode = actualCode.map {
case a@Z80AssemblyStatement(_, _, _, expr, _) =>
a.copy(expression = expr.replaceVariable(ph, actualParam))
case x => x
}
case (AssemblyParam(typ, v@ZRegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
case (AssemblyOrMacroParam(typ, v@ZRegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
if (hadRegisterParam) {
ctx.log.error("Only one macro assembly function parameter can be passed via a register", actualParam.position)
}
@ -56,10 +58,10 @@ object Z80MacroExpander extends MacroExpander[ZLine] {
ctx.log.error(s"Invalid parameter for macro: ${typ.name} ${register.toString.toLowerCase(Locale.ROOT)}", actualParam.position)
Nil
}
case (AssemblyParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
case (AssemblyOrMacroParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
???
case (_, actualParam) =>
}
paramPreparation -> actualCode
paramPreparation -> stmtPreprocess(ctx, actualCode)
}
}

View File

@ -1175,23 +1175,25 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
new Environment(Some(this), name + "$", cpuFamily, options)
}
stmt.params.foreach(p => env.registerParameter(p, options, pointies))
def params: ParamSignature = if (stmt.assembly) {
AssemblyParamSignature(stmt.params.map {
def params: ParamSignature = if (stmt.assembly || stmt.isMacro) {
AssemblyOrMacroParamSignature(stmt.params.map {
pd =>
val typ = env.get[Type](pd.typ)
pd.assemblyParamPassingConvention match {
case ByVariable(vn) =>
AssemblyParam(typ, env.get[MemoryVariable](vn), AssemblyParameterPassingBehaviour.Copy)
AssemblyOrMacroParam(typ, env.get[MemoryVariable](vn), AssemblyParameterPassingBehaviour.Copy)
case ByMosRegister(reg) =>
AssemblyParam(typ, RegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
AssemblyOrMacroParam(typ, RegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
case ByZRegister(reg) =>
AssemblyParam(typ, ZRegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
AssemblyOrMacroParam(typ, ZRegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
case ByM6809Register(reg) =>
AssemblyParam(typ, M6809RegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
AssemblyOrMacroParam(typ, M6809RegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy)
case ByConstant(vn) =>
AssemblyParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByConstant)
AssemblyOrMacroParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByConstant)
case ByReference(vn) =>
AssemblyParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByReference)
AssemblyOrMacroParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByReference)
case ByLazilyEvaluableExpressionVariable(vn) =>
AssemblyOrMacroParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.Eval)
}
})
} else {
@ -1318,7 +1320,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val mangled = MacroFunction(
name,
resultType,
params,
params.asInstanceOf[AssemblyOrMacroParamSignature],
stmt.assembly,
env,
executableStatements
)
@ -1488,6 +1491,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
addThing(subv, stmt.position)
registerAddressConstant(subv, stmt.position, options, Some(t))
}
case ByLazilyEvaluableExpressionVariable(_) => ()
case ByMosRegister(_) => ()
case ByZRegister(_) => ()
case ByM6809Register(_) => ()
@ -2097,20 +2101,34 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
function.params match {
case NormalParamSignature(params) =>
function.params.types.zip(actualParams).zip(params).foreach { case ((required, (actual, expr)), m) =>
if (function.isInstanceOf[MacroFunction]) {
if (required != VoidType && actual != required) {
log.error(s"Invalid argument type for parameter `${m.name}` of macro function `$name`: required: ${required.name}, actual: ${actual.name}", expr.position)
}
} else {
if (!actual.isAssignableTo(required)) {
log.error(s"Invalid value for parameter `${m.name}` of function `$name`", expr.position)
}
if (!actual.isAssignableTo(required)) {
log.error(s"Invalid value for parameter `${m.name}` of function `$name`", expr.position)
}
}
case AssemblyParamSignature(params) =>
function.params.types.zip(actualParams).zipWithIndex.foreach { case ((required, (actual, expr)), ix) =>
if (!actual.isAssignableTo(required)) {
log.error(s"Invalid value for parameter ${ix + 1} of function `$name`", expr.position)
case AssemblyOrMacroParamSignature(params) =>
params.zip(actualParams).zipWithIndex.foreach { case ((AssemblyOrMacroParam(requiredType, variable, behaviour), (actual, expr)), ix) =>
function match {
case m: MacroFunction =>
behaviour match {
case AssemblyParameterPassingBehaviour.ByReference =>
if (!m.isInAssembly) {
if (requiredType != VoidType && actual != requiredType) {
log.error(s"Invalid argument type for parameter `${variable.name}` of macro function `$name`: required: ${requiredType.name}, actual: ${actual.name}", expr.position)
}
}
case AssemblyParameterPassingBehaviour.Copy if m.isInAssembly =>
if (!actual.isAssignableTo(requiredType)) {
log.error(s"Invalid value for parameter #${ix + 1} of macro function `$name`", expr.position)
}
case _ =>
if (!actual.isAssignableTo(requiredType)) {
log.error(s"Invalid value for parameter #${ix + 1} `${variable.name}` of macro function `$name`", expr.position)
}
}
case _ =>
if (!actual.isAssignableTo(requiredType)) {
log.error(s"Invalid value for parameter #${ix + 1} `${variable.name}` of function `$name`", expr.position)
}
}
}
}
@ -2249,7 +2267,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
if (!things.contains("memory_barrier")) {
things("memory_barrier") = MacroFunction("memory_barrier", v, NormalParamSignature(Nil), this, CpuFamily.forType(options.platform.cpu) match {
things("memory_barrier") = MacroFunction("memory_barrier", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, CpuFamily.forType(options.platform.cpu) match {
case CpuFamily.M6502 => List(MosAssemblyStatement(Opcode.CHANGED_MEM, AddrMode.DoesNotExist, LiteralExpression(0, 1), Elidability.Fixed))
case CpuFamily.I80 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, LiteralExpression(0, 1), Elidability.Fixed))
case CpuFamily.I86 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, LiteralExpression(0, 1), Elidability.Fixed))
@ -2261,7 +2279,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (!things.contains("breakpoint")) {
val p = get[VariableType]("pointer")
if (options.flag(CompilationFlag.EnableBreakpoints)) {
things("breakpoint") = MacroFunction("breakpoint", v, NormalParamSignature(Nil), this, CpuFamily.forType(options.platform.cpu) match {
things("breakpoint") = MacroFunction("breakpoint", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, CpuFamily.forType(options.platform.cpu) match {
case CpuFamily.M6502 => List(MosAssemblyStatement(Opcode.CHANGED_MEM, AddrMode.DoesNotExist, VariableExpression("..brk"), Elidability.Fixed))
case CpuFamily.I80 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, VariableExpression("..brk"), Elidability.Fixed))
case CpuFamily.I86 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, VariableExpression("..brk"), Elidability.Fixed))
@ -2269,7 +2287,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case _ => ???
})
} else {
things("breakpoint") = MacroFunction("breakpoint", v, NormalParamSignature(Nil), this, Nil)
things("breakpoint") = MacroFunction("breakpoint", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, Nil)
}
}
}

View File

@ -396,7 +396,8 @@ case class EmptyFunction(name: String,
case class MacroFunction(name: String,
returnType: Type,
params: ParamSignature,
params: AssemblyOrMacroParamSignature,
isInAssembly: Boolean,
environment: Environment,
code: List[ExecutableStatement]) extends MangledFunction {
override def interrupt = false
@ -525,6 +526,12 @@ case class ByVariable(name: String) extends ParamPassingConvention {
override def inNonInlinedOnly = true
}
case class ByLazilyEvaluableExpressionVariable(name: String) extends ParamPassingConvention {
override def inInlinedOnly = true
override def inNonInlinedOnly = false
}
case class ByConstant(name: String) extends ParamPassingConvention {
override def inInlinedOnly = true
@ -538,10 +545,10 @@ case class ByReference(name: String) extends ParamPassingConvention {
}
object AssemblyParameterPassingBehaviour extends Enumeration {
val Copy, ByReference, ByConstant = Value
val Copy, Eval, ByReference, ByConstant = Value
}
case class AssemblyParam(typ: Type, variable: TypedThing, behaviour: AssemblyParameterPassingBehaviour.Value) {
case class AssemblyOrMacroParam(typ: Type, variable: TypedThing, behaviour: AssemblyParameterPassingBehaviour.Value) {
def canBePointedTo: Boolean = behaviour == AssemblyParameterPassingBehaviour.Copy && (variable match {
case RegisterVariable(MosRegister.A | MosRegister.AX, _) => true
case ZRegisterVariable(ZRegister.A | ZRegister.HL, _) => true
@ -550,7 +557,7 @@ case class AssemblyParam(typ: Type, variable: TypedThing, behaviour: AssemblyPar
}
case class AssemblyParamSignature(params: List[AssemblyParam]) extends ParamSignature {
case class AssemblyOrMacroParamSignature(params: List[AssemblyOrMacroParam]) extends ParamSignature {
override def length: Int = params.length
override def types: List[Type] = params.map(_.typ)

View File

@ -135,9 +135,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case _: StackOverflowError =>
log.fatal("Stack overflow " + c)
}
case UnexpandedConstant(name, _) =>
case badThing@UnexpandedConstant(name, _) =>
if (labelMap.contains(name)) labelMap(name)._2
else ???
else {
println(badThing)
???
}
case SubbyteConstant(cc, i) => deepConstResolve(cc).>>>(i * 8).&(0xff)
case s: StructureConstant =>
try {

View File

@ -23,7 +23,7 @@ case class M6809Parser(filename: String,
override def allowIntelHexAtomsInAssembly: Boolean = false
val appcSimple: P[ParamPassingConvention] = P(("a" | "b" | "d" | "x" | "y" | "u" | "s" | "dp") ~ !letterOrDigit).!.map {
override val appcRegister: P[ParamPassingConvention] = P(("a" | "b" | "d" | "x" | "y" | "u" | "s" | "dp") ~ !letterOrDigit).!.map {
case "a" => ByM6809Register(M6809Register.A)
case "b" => ByM6809Register(M6809Register.B)
case "d" => ByM6809Register(M6809Register.D)
@ -35,7 +35,7 @@ case class M6809Parser(filename: String,
override val asmParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS
appc <- appcSimple | appcComplex
appc <- appcRegister | appcComplex
} yield ParameterDeclaration(typ, appc).pos(p)
def fastAlignmentForArrays: MemoryAlignment = WithinPageAlignment

View File

@ -186,6 +186,9 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
typ <- identifier ~/ SWS ~/ Pass
name <- identifier ~/ Pass
} yield {
if (name == "register" || name == "const" || name == "ref" || name == "call") {
log.error(s"Invalid parameter name: `$name`. Did you mean writing a macro?", Some(p))
}
ParameterDeclaration(typ, ByVariable(name)).pos(p)
}
@ -206,14 +209,53 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
asmExpression.map(_ -> false)
)).map { case (p, e) => e._1.pos(p) -> e._2 }
val appcComplex: P[ParamPassingConvention] = P((("const" | "ref").! ~/ AWS).? ~ AWS ~ identifier) map {
case (None, name) => ByVariable(name)
case (Some("const"), name) => ByConstant(name)
case (Some("ref"), name) => ByReference(name)
case x => log.fatal(s"Unknown assembly parameter passing convention: `$x`")
}
def appcRegister: P[ParamPassingConvention]
def asmParamDefinition: P[ParameterDeclaration]
val appcComplex: P[ParamPassingConvention] =
for {
pos <- position("passing method")
keyword <- (("const" | "ref" | "register" | "call").! ~ !letterOrDigit ~/ Pass).? ~/ Pass
_ <- if (keyword.contains("call")) {log.error(s"Invalid assembly macro parameter passing convention: `call`", Some(pos)) ; Pass } else Pass
if !keyword.contains("call")
_ <- position("register name")
register <- keyword match {
case Some("register") => (AWS ~ "(" ~/ AWS ~ appcRegister ~/ AWS ~ ")" ~/ AWS).map(Some(_))
case Some(_) => SWS.map(_ => None)
case None => Pass.map(_ => None)
}
_ <- position("parameter name")
ident <- identifier
} yield ((keyword, register, ident) match {
case (None, _, name) => ByVariable(name)
case (Some("const"), _, name) => ByConstant(name)
case (Some("ref"), _, name) => ByReference(name)
case (Some("register"), Some(reg), _) => reg
case x => log.fatal(s"Unknown assembly parameter passing convention: `$x`")
})
val asmParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS ~/ Pass
appc <- appcRegister | appcComplex
} yield ParameterDeclaration(typ, appc).pos(p)
val macroParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS ~/ Pass
pos <- position("passing method")
keyword <- ( ("const" | "ref" | "call" | "register").! ~ !letterOrDigit ~/ Pass).?
_ <- if (keyword.contains("register")) {log.error(s"Invalid non-assembly macro parameter passing convention: `register`. Did you forget `asm`?", Some(pos)) ; Pass } else Pass
if !keyword.contains("register")
_ <- if (keyword.isDefined) SWS else Pass
_ <- position("parameter name")
name <- identifier
} yield ParameterDeclaration(typ, (keyword match {
case None => ByReference(name)
case Some("ref") => ByReference(name)
case Some("const") => ByConstant(name)
case Some("call") => ByLazilyEvaluableExpressionVariable(name)
case Some(x) => log.fatal(s"Invalid non-assembly macro parameter passing convention: `$x`")
})).pos(p)
def arrayListElement: P[ArrayContents] = arrayStringContents | arrayProcessedContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel, false).map(e => LiteralContents(List(e)))
@ -578,7 +620,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
returnType <- identifier ~ SWS
if !Environment.neverValidTypeIdentifiers(returnType)
name <- identifier ~ HWS
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else if (flags("macro")) macroParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
alignment <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ AWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).?.opaque("<address>") ~/ AWS
statements <- (externFunctionBody | (if (flags("asm")) asmStatements else mfFunctionBody).map(l => Some(l))) ~/ Pass

View File

@ -117,7 +117,7 @@ case class MosParser(filename: String, input: String, currentDirectory: String,
val asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
val appcSimple: P[ParamPassingConvention] = P(("xy" | "yx" | "ax" | "ay" | "xa" | "ya" | "stack" | "a" | "x" | "y") ~ !letterOrDigit).!.map {
override val appcRegister: P[ParamPassingConvention] = P(("xy" | "yx" | "ax" | "ay" | "xa" | "ya" | "a" | "x" | "y") ~ !letterOrDigit).!.map {
case "xy" => ByMosRegister(MosRegister.XY)
case "yx" => ByMosRegister(MosRegister.YX)
case "ax" => ByMosRegister(MosRegister.AX)
@ -130,12 +130,6 @@ case class MosParser(filename: String, input: String, currentDirectory: String,
case x => log.fatal(s"Unknown assembly parameter passing convention: `$x`")
}
override val asmParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS
appc <- appcSimple | appcComplex
} yield ParameterDeclaration(typ, appc).pos(p)
def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]]): Unit = {
if (!options.flag(CompilationFlag.BuggyCodeWarning)) return
statements match {

View File

@ -31,7 +31,7 @@ case class Z80Parser(filename: String,
private val zero = LiteralExpression(0, 1)
val appcSimple: P[ParamPassingConvention] = (P("hl" | "bc" | "de" | "a" | "b" | "c" | "d" | "e" | "h" | "l").! ~ !letterOrDigit).map {
override val appcRegister: P[ParamPassingConvention] = (P("hl" | "bc" | "de" | "a" | "b" | "c" | "d" | "e" | "h" | "l").! ~ !letterOrDigit).map {
case "a" => ByZRegister(ZRegister.A)
case "b" => ByZRegister(ZRegister.B)
case "c" => ByZRegister(ZRegister.C)
@ -48,7 +48,7 @@ case class Z80Parser(filename: String,
override val asmParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS
appc <- appcSimple | appcComplex
appc <- appcRegister | appcComplex
} yield ParameterDeclaration(typ, appc).pos(p)
// TODO: label and instruction in one line

View File

@ -1,14 +1,12 @@
package millfork.test
import millfork.assembly.mos.opt.DangerousOptimizations
import millfork.test.emu.EmuBenchmarkRun
import millfork.{Cpu, OptimizationPresets}
import org.scalatest.{FunSuite, Matchers}
import millfork.test.emu.{EmuBenchmarkRun, EmuUnoptimizedRun, ShouldNotParse}
import org.scalatest.{AppendedClues, FunSuite, Matchers}
/**
* @author Karol Stasiak
*/
class AssemblyMacroSuite extends FunSuite with Matchers {
class AssemblyMacroSuite extends FunSuite with Matchers with AppendedClues {
test("Poke test 1") {
EmuBenchmarkRun(
@ -99,4 +97,42 @@ class AssemblyMacroSuite extends FunSuite with Matchers {
m.readByte(0xc000) should equal(3)
}
}
test("All assembly macro param types") {
val m = EmuUnoptimizedRun(
"""
| array output [3] @$c000
|
| macro asm void test0(byte register(a) value) {
| sta $c000
| }
| macro asm void test1(byte ref variable) {
| lda variable
| sta $c001
| }
| macro asm void test2(byte const constant) {
| lda #constant
| sta $c002
| }
|
| void main() {
| byte variable
| variable = 42
| test0(42)
| test1(variable)
| test2(42)
| }
|""".stripMargin)
m.readByte(0xc000) should equal(42) withClue "$c000"
m.readByte(0xc001) should equal(42) withClue "$c001"
m.readByte(0xc002) should equal(42) withClue "$c002"
}
test("Invalid assembly macro param types") {
ShouldNotParse(
"""
| macro asm void test0(byte call value) {
| }
|""".stripMargin)
}
}

View File

@ -1,6 +1,6 @@
package millfork.test
import millfork.Cpu
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuOptimizedCmosRun, EmuOptimizedHudsonRun, EmuOptimizedRun, EmuUndocumentedRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedHudsonRun, ShouldNotCompile}
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuOptimizedCmosRun, EmuOptimizedHudsonRun, EmuOptimizedRun, EmuUndocumentedRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedHudsonRun, EmuUnoptimizedRun, ShouldNotCompile}
import org.scalatest.{AppendedClues, FunSuite, Matchers}
/**
@ -362,4 +362,21 @@ class AssemblySuite extends FunSuite with Matchers with AppendedClues {
|""".stripMargin, Set(Cpu.Mos))
}
test("Compile params properly") {
val m = EmuUnoptimizedRun(
"""
|noinline asm void f(byte register(a) v0, byte register(x) v1) {
| sta $c000
| stx $c001
| rts
|}
|
|void main() {
| f(9,3)
|}
|""".stripMargin)
m.readByte(0xc000) should equal(9)
m.readByte(0xc001) should equal(3)
}
}

View File

@ -2,13 +2,13 @@ package millfork.test
import millfork.Cpu
import millfork.output.MemoryBank
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuOptimizedAccordingToLevelRun, EmuOptimizedRun, EmuUnoptimizedCrossPlatformRun, ShouldNotCompile}
import org.scalatest.{FunSuite, Matchers}
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuOptimizedAccordingToLevelRun, EmuOptimizedRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun, ShouldNotCompile, ShouldNotParse}
import org.scalatest.{AppendedClues, FunSuite, Matchers}
/**
* @author Karol Stasiak
*/
class MacroSuite extends FunSuite with Matchers {
class MacroSuite extends FunSuite with Matchers with AppendedClues {
test("Most basic test") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086, Cpu.Motorola6809)(
@ -215,4 +215,83 @@ class MacroSuite extends FunSuite with Matchers {
assertAtLeastOneTrueInZeroPage(EmuOptimizedAccordingToLevelRun(2)(src))
assertAtLeastOneTrueInZeroPage(EmuOptimizedAccordingToLevelRun(3)(src))
}
test("All non-assembly macro param types") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
"""
| array(word) output [3] @$c000
|
| macro void test0(byte const constant) {
| output[0] = constant
| }
| macro void test1(byte ref variable) {
| variable += 1
| output[1] = variable
| }
| macro void test2(byte call tooMuch) {
| output[2] = tooMuch
| output[2].lo &= $7f
| }
|
| macro void twice(void call expr) {
| expr
| expr
| }
|
| inline void inc0() {
| output[0] += 1
| }
| void main() {
| byte variable
| variable = 41
| output[0]=0
| output[1]=0
| output[2]=0
| test0(40)
| twice(inc0())
| test1(variable)
| test2(sbyte($80 | 42))
| }
|""".stripMargin) { m =>
m.readWord(0xc000) should equal(42) withClue "$c000"
m.readWord(0xc002) should equal(42) withClue "$c002"
m.readWord(0xc004) should equal(42) withClue "$c004"
}
}
test("Macros as params to void call macros") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
"""
| byte output @$c000
| macro void inc_x() {
| x += 1
| }
| macro void inc(void ref x) {
| inc_x()
| }
| macro void perform_twice(void call f) {
| f
| f
| }
| byte add_two_3(byte x) {
| perform_twice(inc(x))
| return x
| }
|
| void main() {
| output = add_two_3(40)
| }
|
|""".stripMargin) { m =>
m.readByte(0xc000) should equal(42)
}
}
test("Invalid non-assembly macro param types") {
ShouldNotParse(
"""
| macro void test0(byte register(a) value) {
| }
|""".stripMargin)
}
}