diff --git a/docs/abi/inlining.md b/docs/abi/inlining.md index 0238b02c..23fb81ee 100644 --- a/docs/abi/inlining.md +++ b/docs/abi/inlining.md @@ -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 diff --git a/docs/lang/assembly.md b/docs/lang/assembly.md index 0a273ade..fc7a29b5 100644 --- a/docs/lang/assembly.md +++ b/docs/lang/assembly.md @@ -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 diff --git a/docs/lang/assembly6809.md b/docs/lang/assembly6809.md index c95185e8..78108e45 100644 --- a/docs/lang/assembly6809.md +++ b/docs/lang/assembly6809.md @@ -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**: diff --git a/docs/lang/assemblyz80.md b/docs/lang/assemblyz80.md index 740e72e1..889a48f7 100644 --- a/docs/lang/assemblyz80.md +++ b/docs/lang/assemblyz80.md @@ -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**: diff --git a/docs/lang/functions.md b/docs/lang/functions.md index 5771fefc..16160111 100644 --- a/docs/lang/functions.md +++ b/docs/lang/functions.md @@ -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 diff --git a/docs/lang/interfacing.md b/docs/lang/interfacing.md index 669b1de8..2ade76ee 100644 --- a/docs/lang/interfacing.md +++ b/docs/lang/interfacing.md @@ -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) } ``` diff --git a/docs/stdlib/nes.md b/docs/stdlib/nes.md index 683007a4..bac7d39d 100644 --- a/docs/stdlib/nes.md +++ b/docs/stdlib/nes.md @@ -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). diff --git a/docs/stdlib/stdlib.md b/docs/stdlib/stdlib.md index f027fa2e..8d86e00c 100644 --- a/docs/stdlib/stdlib.md +++ b/docs/stdlib/stdlib.md @@ -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. diff --git a/include/a8_kernel.mfk b/include/a8_kernel.mfk index 4c047297..b5fb5412 100644 --- a/include/a8_kernel.mfk +++ b/include/a8_kernel.mfk @@ -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 diff --git a/include/apple2_kernel.mfk b/include/apple2_kernel.mfk index 36a95d03..9b699c93 100644 --- a/include/apple2_kernel.mfk +++ b/include/apple2_kernel.mfk @@ -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 diff --git a/include/bbc_kernal.mfk b/include/bbc_kernal.mfk index b8a57d4b..c6ee7f4e 100644 --- a/include/bbc_kernal.mfk +++ b/include/bbc_kernal.mfk @@ -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) diff --git a/include/c128_kernal.mfk b/include/c128_kernal.mfk index 767c7bb3..9fd33920 100644 --- a/include/c128_kernal.mfk +++ b/include/c128_kernal.mfk @@ -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 diff --git a/include/c264_basic.mfk b/include/c264_basic.mfk index 9f4b7ab8..97d7984e 100644 --- a/include/c264_basic.mfk +++ b/include/c264_basic.mfk @@ -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! diff --git a/include/c264_kernal.mfk b/include/c264_kernal.mfk index 912e2e7f..db55d492 100644 --- a/include/c264_kernal.mfk +++ b/include/c264_kernal.mfk @@ -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 diff --git a/include/c64_basic.mfk b/include/c64_basic.mfk index 4275559e..b6e8bfb5 100644 --- a/include/c64_basic.mfk +++ b/include/c64_basic.mfk @@ -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! diff --git a/include/c64_easyflash.mfk b/include/c64_easyflash.mfk index 3b507429..08d68075 100644 --- a/include/c64_easyflash.mfk +++ b/include/c64_easyflash.mfk @@ -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 diff --git a/include/c64_kernal.mfk b/include/c64_kernal.mfk index 82abf81e..40c1c1ac 100644 --- a/include/c64_kernal.mfk +++ b/include/c64_kernal.mfk @@ -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 diff --git a/include/cpc.mfk b/include/cpc.mfk index 65c86791..070c8ccf 100644 --- a/include/cpc.mfk +++ b/include/cpc.mfk @@ -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) diff --git a/include/encconv.mfk b/include/encconv.mfk index e66492ed..c0f2aa39 100644 --- a/include/encconv.mfk +++ b/include/encconv.mfk @@ -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 diff --git a/include/msx.mfk b/include/msx.mfk index d86c5f87..3f34602b 100644 --- a/include/msx.mfk +++ b/include/msx.mfk @@ -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) diff --git a/include/nes_hardware.mfk b/include/nes_hardware.mfk index 03984036..dd79b3f0 100644 --- a/include/nes_hardware.mfk +++ b/include/nes_hardware.mfk @@ -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 } diff --git a/include/nes_mmc4.mfk b/include/nes_mmc4.mfk index 6b0a26a1..02ea9a33 100644 --- a/include/nes_mmc4.mfk +++ b/include/nes_mmc4.mfk @@ -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 diff --git a/include/pc88.mfk b/include/pc88.mfk index 3970b7bd..ef3808c1 100644 --- a/include/pc88.mfk +++ b/include/pc88.mfk @@ -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) diff --git a/include/pet_kernal.mfk b/include/pet_kernal.mfk index cedc40bf..59376250 100644 --- a/include/pet_kernal.mfk +++ b/include/pet_kernal.mfk @@ -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) diff --git a/include/stdlib_6502.mfk b/include/stdlib_6502.mfk index 2b69ee94..9f6b05c0 100644 --- a/include/stdlib_6502.mfk +++ b/include/stdlib_6502.mfk @@ -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 diff --git a/include/stdlib_i80.mfk b/include/stdlib_i80.mfk index cb85fcf8..d78a80c0 100644 --- a/include/stdlib_i80.mfk +++ b/include/stdlib_i80.mfk @@ -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 diff --git a/include/vic20_kernal.mfk b/include/vic20_kernal.mfk index 1de29e51..689c5aa6 100644 --- a/include/vic20_kernal.mfk +++ b/include/vic20_kernal.mfk @@ -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 diff --git a/include/x16_hardware.mfk b/include/x16_hardware.mfk index ff0ee72c..4eec1dc2 100644 --- a/include/x16_hardware.mfk +++ b/include/x16_hardware.mfk @@ -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 } diff --git a/include/zxspectrum.mfk b/include/zxspectrum.mfk index 1fcd63db..502ff185 100644 --- a/include/zxspectrum.mfk +++ b/include/zxspectrum.mfk @@ -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 } diff --git a/src/main/scala/millfork/assembly/m6809/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/m6809/opt/ReverseFlowAnalyzer.scala index be9f64b2..9016f15e 100644 --- a/src/main/scala/millfork/assembly/m6809/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/m6809/opt/ReverseFlowAnalyzer.scala @@ -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) diff --git a/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala index a19169c9..6c076597 100644 --- a/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/mos/opt/ReverseFlowAnalyzer.scala @@ -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) diff --git a/src/main/scala/millfork/assembly/z80/opt/ChangeRegisterPair.scala b/src/main/scala/millfork/assembly/z80/opt/ChangeRegisterPair.scala index 6bc37104..98b827f3 100644 --- a/src/main/scala/millfork/assembly/z80/opt/ChangeRegisterPair.scala +++ b/src/main/scala/millfork/assembly/z80/opt/ChangeRegisterPair.scala @@ -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 diff --git a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala index 91458e76..ba8bece6 100644 --- a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala @@ -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, diff --git a/src/main/scala/millfork/compiler/MacroExpander.scala b/src/main/scala/millfork/compiler/MacroExpander.scala index 981ccd4d..7df5a696 100644 --- a/src/main/scala/millfork/compiler/MacroExpander.scala +++ b/src/main/scala/millfork/compiler/MacroExpander.scala @@ -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 } } diff --git a/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala b/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala index 2f6d8efe..fedfc8dc 100644 --- a/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/m6809/M6809ExpressionCompiler.scala @@ -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) diff --git a/src/main/scala/millfork/compiler/m6809/M6809MacroExpander.scala b/src/main/scala/millfork/compiler/m6809/M6809MacroExpander.scala index e1f3ce0a..e43c90de 100644 --- a/src/main/scala/millfork/compiler/m6809/M6809MacroExpander.scala +++ b/src/main/scala/millfork/compiler/m6809/M6809MacroExpander.scala @@ -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) } } diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index 5e582daf..61f282fc 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -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 { diff --git a/src/main/scala/millfork/compiler/mos/MosMacroExpander.scala b/src/main/scala/millfork/compiler/mos/MosMacroExpander.scala index 1a61b23c..e0b2f63d 100644 --- a/src/main/scala/millfork/compiler/mos/MosMacroExpander.scala +++ b/src/main/scala/millfork/compiler/mos/MosMacroExpander.scala @@ -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) } } diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index e764c10f..1c7e0238 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -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) } diff --git a/src/main/scala/millfork/compiler/z80/Z80MacroExpander.scala b/src/main/scala/millfork/compiler/z80/Z80MacroExpander.scala index 59bb112a..435a80ab 100644 --- a/src/main/scala/millfork/compiler/z80/Z80MacroExpander.scala +++ b/src/main/scala/millfork/compiler/z80/Z80MacroExpander.scala @@ -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) } } diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 8a1f5d12..5edcae02 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -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) } } } diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 2586352e..1fe9caa6 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -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) diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index 2b09381d..2c51e2bd 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -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 { diff --git a/src/main/scala/millfork/parser/M6809Parser.scala b/src/main/scala/millfork/parser/M6809Parser.scala index 222fa7ff..0b154ca0 100644 --- a/src/main/scala/millfork/parser/M6809Parser.scala +++ b/src/main/scala/millfork/parser/M6809Parser.scala @@ -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 diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index edfe4ff4..767e305f 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -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("
") ~/ AWS statements <- (externFunctionBody | (if (flags("asm")) asmStatements else mfFunctionBody).map(l => Some(l))) ~/ Pass diff --git a/src/main/scala/millfork/parser/MosParser.scala b/src/main/scala/millfork/parser/MosParser.scala index 92cb2f4e..358ffc5c 100644 --- a/src/main/scala/millfork/parser/MosParser.scala +++ b/src/main/scala/millfork/parser/MosParser.scala @@ -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 { diff --git a/src/main/scala/millfork/parser/Z80Parser.scala b/src/main/scala/millfork/parser/Z80Parser.scala index 17699c57..f10b4b15 100644 --- a/src/main/scala/millfork/parser/Z80Parser.scala +++ b/src/main/scala/millfork/parser/Z80Parser.scala @@ -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 diff --git a/src/test/scala/millfork/test/AssemblyMacroSuite.scala b/src/test/scala/millfork/test/AssemblyMacroSuite.scala index e034dc1c..a74c1d3f 100644 --- a/src/test/scala/millfork/test/AssemblyMacroSuite.scala +++ b/src/test/scala/millfork/test/AssemblyMacroSuite.scala @@ -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) + } } diff --git a/src/test/scala/millfork/test/AssemblySuite.scala b/src/test/scala/millfork/test/AssemblySuite.scala index 9dc838b7..fd2cf55e 100644 --- a/src/test/scala/millfork/test/AssemblySuite.scala +++ b/src/test/scala/millfork/test/AssemblySuite.scala @@ -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) + } + } diff --git a/src/test/scala/millfork/test/MacroSuite.scala b/src/test/scala/millfork/test/MacroSuite.scala index 3247dd1b..cbc26c36 100644 --- a/src/test/scala/millfork/test/MacroSuite.scala +++ b/src/test/scala/millfork/test/MacroSuite.scala @@ -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) + } }