1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-31 18:41:30 +00:00

Compare commits

...

56 Commits

Author SHA1 Message Date
Karol Stasiak
d8c11a9c50 Minor optimizations 2023-02-03 14:46:01 +01:00
Karol Stasiak
ee47ccef5a fix z80 indexing in hardwritten assembly (#137) 2023-01-27 18:27:53 +01:00
Karol Stasiak
24de2f7530 enable tests of identity page 2023-01-27 18:19:19 +01:00
Karol Stasiak
1beb695151 more tests 2023-01-27 18:17:41 +01:00
Karol Stasiak
9229092309 random minor stuff 2023-01-27 18:16:25 +01:00
Karol Stasiak
ca7166d1ae oops 2023-01-27 18:16:02 +01:00
Karol Stasiak
1594d63a9a Add a command line flag for identity page 2023-01-27 18:15:40 +01:00
Karol Stasiak
75cc34663c Z80: fix an optimization 2023-01-27 18:15:10 +01:00
Karol Stasiak
f4d2fdd370 6502: use identity page for maths 2023-01-27 18:14:50 +01:00
Karol Stasiak
29c1e3f2a6 6502 and Z80: Optimize optimizations 2023-01-27 18:13:21 +01:00
Karol Stasiak
e9cfec54b5 Various 6502 optimization fixes 2023-01-27 17:34:09 +01:00
Karol Stasiak
1bc1ab3539 Fix some codegen bugs on 6502 2023-01-27 17:00:29 +01:00
Karol Stasiak
6f294d6dec Fix -fbounds-checking 2022-02-12 02:13:30 +01:00
Karol Stasiak
9866c974ad Don't apply Constant index offset propagation in decimal mode 2022-02-12 02:12:55 +01:00
Karol Stasiak
ef2f5b5918 Add test 2022-02-11 21:48:24 +01:00
Karol Stasiak
a70a1c0e6b Consider kernal_interrupt functions as entry points 2022-02-11 21:48:13 +01:00
Karol Stasiak
790c836771 Fix handling of non-top-level return dispatch statements 2022-02-11 21:47:39 +01:00
Karol Stasiak
6af84d1628 Optimize the optimizer 2022-02-08 14:42:30 +01:00
Karol Stasiak
7b205a2754 Allow multiple -D options 2022-02-08 13:21:26 +01:00
Karol Stasiak
d23fe1756e Switch to snapshot versioning 2022-02-08 13:20:15 +01:00
Karol Stasiak
3b3ee1bc55 Version 0.3.30 2021-12-15 16:01:46 +01:00
Karol Stasiak
fb30075ea5 Update CHANGELOG 2021-12-15 15:54:00 +01:00
Karol Stasiak
b5084cd180 More tests 2021-12-15 15:53:49 +01:00
Karol Stasiak
b1a2be5574 6809: Fix constant condition compilation 2021-11-22 01:58:09 +01:00
Karol Stasiak
a260d0a806 CBM BASIC loader should use the actual address of main (fixes #111) 2021-11-12 02:45:53 +01:00
Karol Stasiak
38ad919ed9 Show the positions of unevaluated constants 2021-11-12 02:44:58 +01:00
Karol Stasiak
34ef8b8de9 Better evaluation of the if function in constants 2021-11-12 02:44:20 +01:00
Karol Stasiak
f676e74e38 Macro improvements:
– allow local constants in macros
– allow untyped macro parameters with void
– treat the name of a function as a pointer to it
– add this.function local alias (#118)
2021-11-12 02:10:07 +01:00
Karol Stasiak
c9313e5dbe Add support for ld65 label file format (#128) 2021-11-12 00:47:12 +01:00
Karol Stasiak
73a223b9b6 Add support for Mesen label file format by tracking file-relative positions of segments (#128) 2021-11-03 21:48:45 +01:00
Karol Stasiak
b168818bab Report every hole in the output 4 bytes or larger; count total free bytes in the entire bank 2021-09-21 00:43:29 +02:00
Karol Stasiak
effa723863 Never commit .labels files again 2021-09-21 00:21:21 +02:00
Karol Stasiak
b2ab3dbeab Clean accidentally commited labels file 2021-09-21 00:20:54 +02:00
Karol Stasiak
c9ef5e636b Add raw label file format 2021-09-21 00:09:59 +02:00
Karol Stasiak
fc2f0782c5 Update Notepad++ syntax 2021-09-20 00:56:04 +02:00
Karol Stasiak
f0e22f02a6
Merge pull request #126 from mookiexl/master
Update x16_experimental.ini
2021-09-18 14:23:02 +02:00
Karol Stasiak
b2291d1cb2
Merge pull request #122 from retrac0/master
gb library code updates to current syntax
2021-09-18 13:21:15 +02:00
Karol Stasiak
166acf2b18 R800 support 2021-09-18 00:36:16 +02:00
Karol Stasiak
7530b382a8 Fix array fields in certain contexts 2021-09-17 22:19:39 +02:00
Karol Stasiak
b66435d06b Fix EasyFlash switch_hirom (fixes #121) 2021-09-13 15:10:11 +02:00
Karol Stasiak
0c8951d015 Fix unused variable elimination in for-array statements (fixes #125) 2021-09-13 09:27:34 +02:00
Karol Stasiak
84dde8589c Allow spaces after the array keyword (fixes #120) 2021-09-13 09:26:54 +02:00
Karol Stasiak
3b0aa9a425 Fixes for flag boolean types (fixes #123) 2021-09-13 09:26:27 +02:00
mookiexl
1b8de990a5 Update x16_experimental.ini 2021-08-19 14:37:20 +02:00
mookiexl
b060fc599b Update x16_experimental.ini
$00-$01 are used for bank switching.
Unintentional write to those will crash the machine.
2021-08-16 16:06:28 +02:00
Karol Stasiak
90e5360bfd Related to #119:
– Detection of simple byte overflow cases.
– Optimization of 8×8→16 multiplication on 6809.
– Multiplication optimizations on Z80.
2021-08-06 21:01:03 +02:00
Joel Heikkila
bbf430a1c7 update gb lib code to current syntax for example/ 2021-07-11 18:15:39 -04:00
Karol Stasiak
7f6a0c6b0d Fix documentation of .loword and .hiword 2021-06-29 02:39:23 +02:00
Karol Stasiak
7f0def54bc Improvements for 65CE02 assembly (fixes #116) 2021-06-29 02:29:30 +02:00
Karol Stasiak
faf97cee1f Don't call valid control characters invalid 2021-06-29 02:29:12 +02:00
Karol Stasiak
da862069a7 Fix for volatile fields 2021-06-29 02:28:32 +02:00
Karol Stasiak
431a25d325 Internal support for pointers to volatile objects; add volatile fields (closes #112) 2021-06-21 14:20:24 +02:00
Karol Stasiak
73beafd65e Support for expressions in file() (fixes #114) 2021-06-21 14:18:17 +02:00
Karol Stasiak
307ad90ecf 6809: Improve flow analysis and add few more optimizations 2021-06-21 14:15:40 +02:00
Karol Stasiak
2e592a2331 Correct address for sid_v1_sr (fixing #115) 2021-06-21 14:12:33 +02:00
Karol Stasiak
91c9b42f3d Switch to snapshot versioning 2021-06-21 14:11:56 +02:00
139 changed files with 3482 additions and 1037 deletions

3
.gitignore vendored
View File

@ -31,9 +31,12 @@ issue*.mfk
*.seq
*.asm
*.lbl
*.labels
*.nl
*.fns
*.sym
*.mlb
*.dbg
*.deb
*.xex
*.nes

View File

@ -1,5 +1,39 @@
# Change log
## 0.3.30 (2021-12-15)
* Added volatile structure fields (#112).
* Added `this.function` as the alias for the current function (#118).
* Added support for constant evaluation in `file` expressions (#114).
* Allowed declaring local constants and passing untyped parameters for macros.
* Allowed treating bare function name as a pointer to it.
* Added Mesen, ld65 and "raw" label file formats (#128).
* Commodore: the address used by SYS is now determined automatically instead of hardcoded (#111).
* C64: Fixed address for `sid_v1_sr` (#115).
* EasyFlash: Fixed address for `switch_hirom` (#121).
* GB: Fixed standard library (thanks to @retrac0).
* Commander X16: Updated platform definition file (thanks to @mookiexl).
* 65CE02: Full assembly support.
* R800: Full assembly support.
* Various miscompilation fixes (#123, #125) and parser fixes (e.g. #120).
* 6809: Various optimizations.
* Improvements related to constant evaluation.
## 0.3.28 (2021-05-24)
* Officially deprecated decimal operators with apostrophes.

View File

@ -5,7 +5,7 @@
A middle-level programming language targeting 6502-based, 8080-based, Z80-based and 6809-based microcomputers.
For binary releases, see: [https://github.com/KarolS/millfork/releases](https://github.com/KarolS/millfork/releases)
(latest: 0.3.28).
(latest: 0.3.30).
For build instructions, see [Build instructions](./COMPILING.md).
## Features

View File

@ -1,6 +1,6 @@
name := "millfork"
version := "0.3.28"
version := "0.3.31-SNAPSHOT"
// keep it at 2.12.11 for GraalVM native image compatibility!
scalaVersion := "2.12.11"

View File

@ -14,6 +14,8 @@ It implies the following:
* cannot contain variable or array declarations
* but can contain scalar constant declarations; the constants are scoped to the particular macro invocation
* can be `asm` - in this case, they should **not** end with a return instruction
* do not have an address
@ -35,7 +37,13 @@ It implies the following:
* `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
* macros do not have their own scope (they reuse the scope from their invocations) exceptions:
* the parameters
* the local labels defined in assembly
* the local constants
* control-flow statements (`break`, `continue`, `return`, `goto`, `label`) are run as if places in the caller function

View File

@ -47,6 +47,12 @@ The extension and the file format are platform-dependent.
* `-G sym` format used by the WLA DX assembler. The extension is `.sym`.
* `-G fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `-G mesen` format used by the Mesen emulator. The extension is `.mlb`.
* `-G ld65` a simplified version of the format used by the `ld65` linker (used by CC65 and CA65). The extension is `.dbg`.
* `-G raw` Millfork-specific format. The extension is '.labels'. Each row contains bank number, start address, end address (if known), object type, and Millfork-specific object identifier.
* `-fbreakpoints`, `-fno-breakpoints`
Whether the compiler should use the `breakpoint` macro.

View File

@ -2,6 +2,11 @@
### A note about Commodore 64
#### Unusual main function location
If you're creating a prg file and your `main` function may be located at address $2710 hex (10000 decimal) or higher,
you might need to define the `DISPLACED_MAIN=1` preprocessor feature.
#### Multifile programs
A multifile program is a program stored on a disk that consists of the main program file that is executed first

View File

@ -37,6 +37,8 @@ if a line ends with a backslash character, the value continues to the next line.
* `z80` (Zilog Z80)
* `strictz80` (Z80 without illegal instructions)
* `r800` (R800)
* `z80next` (Z80 core from ZX Spectrum Next)
Note: Millfork version 0.3.18 and earlier uses the name `zx80next` for this architecture.
@ -80,6 +82,8 @@ This list cannot contain module template instantiations.
* `emit_x80` whether the compiler should emit instructions present on Sharp LR35902 and Z80, but absent on Intel 8080, default is `true` on compatible processors and `false` elsewhere
* `emit_z80` whether the compiler should emit Zilog Z80 instructions not covered by `emit_x80`, default is `true` on compatible processors and `false` elsewhere
* `emit_r800` whether the compiler should emit R800 instructions, default is `true` on compatible processors and `false` elsewhere
* `prevent_jmp_indirect_bug` whether the compiler should try to avoid the indirect JMP bug,
default is `false` on 65C02-compatible or non-6502 processors and `true` elsewhere
@ -263,3 +267,7 @@ Default: `main,*`
* `sym` format used by the WLA/DX assembler. The extension is `.sym`.
* `fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `mesen` format used by the Mesen emulator. The extension is `.mlb`.
* `ld65` format used by the `ld65` linker. The extension is `.dbg`.

View File

@ -25,3 +25,5 @@
* `byte segment.N.bank` the value of `segment_N_bank` from the platform definition
* `byte segment.N.fill` the value of `segment_N_fill` from the platform definition
* `this.function` the alias of the current function (in macros, it resolves to the actual non-macro function that called the macro)

View File

@ -103,6 +103,8 @@ Some libraries may require that some of these be defined.
* `KEYBOARD` 1 if the target has a keyboard, 0 otherwise
* `DISPLACED_MAIN` set this to 1 if the `main` function is in a very unusual location for the target
* `USE_MOUSE_MBM` set this to 1 if you want to enable middle button support for the mouse.
* `JOYSTICKS` the maximum number of joysticks using standard hardware configurations, may be 0

View File

@ -10,9 +10,9 @@ These suffixes can be only applied to arithmetic or pointer variables:
* `.hi` the most significant byte of a two-byte variable (word, pointer) (use `hi(_)` for arbitrary expressions)
* `.loword` the least significant byte of a three- or four-byte variable
* `.loword` the least significant word of a three- or four-byte variable
* `.hiword` the most significant byte of a three- or four-byte variable
* `.hiword` the most significant word of a three- or four-byte variable
* `.b0`, `.b1` etc. the given byte of the multi-byte arithmetic variable, with `.b0` being the least significant byte

View File

@ -3,6 +3,12 @@
The examples showcased here are designed to compile with a compiler built from the newest sources.
If you are using a release version of the compiler, consider browsing the older versions of the examples:
* [for version 0.3.28](https://github.com/KarolS/millfork/tree/v0.3.28/examples)
* [for version 0.3.26](https://github.com/KarolS/millfork/tree/v0.3.26/examples)
* [for version 0.3.24](https://github.com/KarolS/millfork/tree/v0.3.24/examples)
* [for version 0.3.22](https://github.com/KarolS/millfork/tree/v0.3.22/examples)
* [for version 0.3.18](https://github.com/KarolS/millfork/tree/v0.3.18/examples)

View File

@ -2,7 +2,7 @@ import err
inline asm void switch_hirom(byte register(a) bank) {
? and #$3F
! sta $DE01
! sta $DE00
? rts
}

View File

@ -10,7 +10,7 @@ word sid_v1_freq @$D400
word sid_v1_pulse @$D402
byte sid_v1_cr @$D404
byte sid_v1_ad @$D405
byte sid_v1_sr @$D409
byte sid_v1_sr @$D406
word sid_v2_freq @$D407
word sid_v2_pulse @$D409

View File

@ -0,0 +1,31 @@
#template $ADDR$
#if not(CBM)
#warn cbm/basic_loader module should be only used on Commodore targets
#endif
const array _basic_loader @ $ADDR$ = [
#if DISPLACED_MAIN
@word_le [
$ADDR$ + 0xB
],
#else
@word_le [
$ADDR$ + if(main.addr >= 10000, 1/0, 0xA) // use -D DISPLACED_MAIN=1 if you get an error here
],
#endif
10,
0,
$9e,
#if DISPLACED_MAIN
$30 + (main.addr/10000)%%10,
#endif
$30 + (main.addr/1000)%%10,
$30 + (main.addr/100)%%10,
$30 + (main.addr/10)%%10,
$30 + (main.addr/1)%%10,
0,
0,
0
]

View File

@ -4,7 +4,7 @@
#pragma zilog_syntax
array __header @ $100 = [
const array __header @ $100 = [
$00, $C3, $50, $01, $CE, $ED, $66, $66, $CC, $0D, $00, $0B, $03, $73, $00, $83,
$00, $0C, $00, $0D, $00, $08, $11, $1F, $88, $89, $00, $0E, $DC, $CC, $6E, $E6,
$DD, $DD, $D9, $99, $BB, $BB, $67, $63, $6E, $0E, $EC, $CC, $DD, $DC, $99, $9F,

View File

@ -38,9 +38,9 @@ void read_joy() {
ld a,(reg_joypad)
ld a,(reg_joypad)
}
byte tmp
tmp = reg_joypad ^ $ff
input_a = (tmp & 1) >> 0
input_a = (tmp & 1) // >> 0
input_b = (tmp & 2) >> 1
input_select = (tmp & 4) >> 2
input_start = (tmp & 8) >> 3

View File

@ -11,6 +11,12 @@ inline asm byte __mul_u8u8u8() {
? LD A, E
? RET
}
#elseif CPUFEATURE_R800
inline asm byte __mul_u8u8u8() {
? MULUB A,D
? LD A,L
? RET
}
#elseif CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
//A = A * D
noinline asm byte __mul_u8u8u8() {
@ -89,6 +95,16 @@ __divmod_u16u8u16u8_skip:
? RET
}
#if CPUFEATURE_R800
inline asm word __mul_u16u8u16() {
? LD L,A
? LD H,0
? MULUW HL,DE
? RET
}
#else
// HL=A*DE
noinline asm word __mul_u16u8u16() {
? LD HL,0
? LD B,8
@ -113,8 +129,17 @@ __mul_u16u8u16_skip:
#endif
? RET
}
#endif
#if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
#if CPUFEATURE_R800
inline asm word __mul_u16u16u16() {
? EX DE,HL
? MULUW HL,BC
? RET
}
#elseif CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
// HL=BC*DE
noinline asm word __mul_u16u16u16() {
LD HL,0
LD A,16

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_0401 module should be only used on Commodore targets
#endif
const array _basic_loader @$401 = [
$0b,
4,
10,
0,
$9e,
$31,
$30,
$33,
$37,
0,
0,
0
]
import cbm/basic_loader<$401>

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_0801 module should be only used on Commodore targets
#endif
const array _basic_loader @$801 = [
$0b,
$08,
10,
0,
$9e,
$32,
$30,
$36,
$31,
0,
0,
0
]
import cbm/basic_loader<$801>

View File

@ -22,4 +22,5 @@ asm void __init_16bit() @$80D {
clc
xce
sep #$30
}
jmp main
}

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_1001 module should be only used on Commodore targets
#endif
const array _basic_loader @$1001 = [
$0b,
$10,
10,
0,
$9e,
$34,
$31,
$30,
$39,
0,
0,
0
]
import cbm/basic_loader<$1001>

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_1201 module should be only used on Commodore targets
#endif
const array _basic_loader @$1201 = [
$0b,
$12,
10,
0,
$9e,
$34,
$36,
$32,
$31,
0,
0,
0
]
import cbm/basic_loader<$1201>

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_1c01 module should be only used on Commodore targets
#endif
const array _basic_loader @$1C01 = [
$0b,
$1C,
10,
0,
$9e,
$37,
$31,
$38,
$31,
0,
0,
0
]
import cbm/basic_loader<$1c01>

View File

@ -3,7 +3,7 @@
#warn m6809_math module should be used only on 6809-like targets
#endif
noinline asm word __mul_u16u16u16(word register(x) x, word register(d) d) {
noinline asm word __mul_u16u16u16(word register(x) x, word register(d) d) !preserves_y !preserves_u {
pshs d,x
exg d,x
lda ,s
@ -26,7 +26,7 @@ noinline asm word __mul_u16u16u16(word register(x) x, word register(d) d) {
}
// returns p/q: quotient in A, remainder in B
noinline asm word __divmod_u8u8u8u8(word register(a) p, word register(b) q) {
noinline asm word __divmod_u8u8u8u8(word register(a) p, word register(b) q) !preserves_x !preserves_y !preserves_u {
pshs b,cc
ldb #8
stb ,-s
@ -47,7 +47,7 @@ noinline asm word __divmod_u8u8u8u8(word register(a) p, word register(b) q) {
}
// returns p/q: quotient in X, remainder in D
noinline asm word __divmod_u16u16u16u16(word register(x) p, word register(d) q) {
noinline asm word __divmod_u16u16u16u16(word register(x) p, word register(d) q) !preserves_y !preserves_u {
pshs x,d,cc
ldb #16
pshs b

View File

@ -11,14 +11,21 @@ modules=loader_0801,x16_kernal,x16_hardware,c64_panic,stdlib
[allocation]
; Let's not use the BASIC:
zp_pointers=0-$7F
segments=default,himem_00,himem_ff
; $00-$01 are used for bank switching
zp_pointers=$02-$7F
segments=default,user,himem_00,himem_ff
default_code_segment=default
segment_default_start=$80D
segment_default_codeend=$9eff
segment_default_datastart=after_code
segment_default_end=$9eff
;1KB user space
segment_user_start=$0400
segment_user_codeend=$07ff
segment_user_datastart=after_code
segment_user_end=$07ff
segment_himem_00_start=$a000
segment_himem_00_codeend=$bfff
segment_himem_00_datastart=after_code

View File

@ -19,11 +19,15 @@
+= -= *= &gt;&gt;= &lt;&lt;=
+&apos; -&apos; *&apos; &lt;&lt;&apos; &gt;&gt;&apos;
+&apos;= -&apos;= *&apos;= &lt;&lt;&apos;= &gt;&gt;&apos;=
$+ $- $* $&lt;&lt; $&gt;&gt;
$+= $-= $*= $&lt;&lt;= $&gt;&gt;=
= ?
/ %% /= %%=
&gt; &gt;= &lt; &lt;= != ==
| || |= ^ ^= &amp; &amp;&amp; &amp;=
( ) { } @ [ ] : # </Keywords>
( ) { } @ [ ] : #
; .
</Keywords>
<Keywords name="Operators2"></Keywords>
<Keywords name="Folders in code1, open">{</Keywords>
<Keywords name="Folders in code1, middle"></Keywords>
@ -38,10 +42,14 @@
void bool byte sbyte ubyte word farword pointer farpointer long word_be word_le long_be long_le file
int8 int16 int24 int32 int40 int48 int56 int64
int72 int80 int88 int96 int104 int112 int120 int128
signed8
signed8 signed16 signed24 signed32 signed40 signed48 signed56 signed64
signed72 signed80 signed88 signed96 signed104 signed112 signed120 signed128
unsigned8 unsigned16 unsigned24 unsigned32 unsigned40 unsigned48 unsigned56 unsigned64
unsigned72 unsigned80 unsigned88 unsigned96 unsigned104 unsigned112 unsigned120 unsigned128
array addr fast</Keywords>
array addr fast
clear_carry clear_zero clear_overflow clear_negative
set_carry set_zero set_overflow set_negative
</Keywords>
<Keywords name="Keywords2">
import segment
if else for return while do asm extern break continue default goto label
@ -54,9 +62,11 @@
utf8 utf16le utf16be latin0 latin9 iso8859_15 zx80 zx81 vectrex koi7n2 short_koi msx_intl msx_us msx_uk msx_de msx_fr msx_es msx_ru msx_jp msx_br
utf8z utf16lez utf16bez latin0z latin9z iso8859_15z zx80z zx81z vectrexz koi7n2z short_koiz msx_intlz msx_usz msx_ukz msx_dez msx_frz msx_esz msx_ruz msx_jpz msx_brz
until to downto parallelto paralleluntil
function
static stack ref const volatile inline noinline macro register kernal_interrupt interrupt align reentrant
hi lo sin cos tan call nonet
false true nullptr</Keywords>
false true nullptr nullchar nullchar_scr
</Keywords>
<Keywords name="Keywords4">&quot;sta &quot; &quot;lda &quot; &quot;jmp &quot; &quot;bit &quot; &quot;eor &quot; &quot;adc &quot; &quot;sbc &quot; &quot;ora &quot; &quot;and &quot; &quot;ldx &quot; &quot;ldy &quot; &quot;stx &quot; &quot;sty &quot; &quot;tax&quot; &quot;tay&quot; &quot;tya&quot; &quot;txa&quot; &quot;txs&quot; &quot;tsx&quot; &quot;sei&quot; &quot;cli&quot; &quot;clv&quot; &quot;clc&quot; &quot;cld&quot; &quot;sed&quot; &quot;sec&quot; &quot;bra &quot; &quot;beq &quot; &quot;bne &quot; &quot;bmi &quot; &quot;bpl &quot; &quot;bcc &quot; &quot;bcs &quot; &quot;bvs &quot; bvc &quot; &quot;jsr &quot; rts&quot; &quot;rti&quot; &quot;brk&quot; &quot;rol&quot; &quot;ror&quot; &quot;asl&quot; &quot;lsr&quot; &quot;inc &quot; &quot;dec &quot; &quot;cmp &quot; &quot;cpx &quot; &quot;cpy &quot; inx iny dex dey pla pha plp hp phx plx phy ply &quot;stz &quot; &quot;ldz &quot; tza taz &quot;tsb &quot; &quot;trb &quot; ra txy tyx pld plb phb phd phk xce&#x000D;&#x000A;&#x000D;&#x000A;&quot;STA &quot; &quot;LDA &quot; &quot;JMP &quot; &quot;BIT &quot; &quot;EOR &quot; &quot;ADC &quot; &quot;SBC &quot; &quot;ORA &quot; &quot;AND &quot; &quot;LDX &quot; &quot;LDY &quot; &quot;STX &quot; &quot;STY &quot; &quot;TAX&quot; &quot;TAY&quot; &quot;TYA&quot; &quot;TXA&quot; &quot;TXS&quot; &quot;TSX&quot; &quot;SEI&quot; &quot;CLI&quot; &quot;CLV&quot; &quot;CLC&quot; &quot;CLD&quot; &quot;SED&quot; &quot;SEC&quot; &quot;BEQ &quot; &quot;BRA &quot; &quot;BNE &quot; &quot;BMI &quot; &quot;BPL &quot; &quot;BCC &quot; &quot;BCS &quot; &quot;BVS &quot; BVC &quot; &quot;JSR &quot; RTS&quot; &quot;RTI&quot; &quot;BRK&quot; &quot;ROL&quot; &quot;ROR&quot; &quot;ASL&quot; &quot;LSR&quot; &quot;INC &quot; &quot;DEC &quot; &quot;CMP &quot; &quot;CPX &quot; &quot;CPY &quot; INX INY DEX DEY PLA PHA PLP HP PHX PLX PHY PLY &quot;STZ &quot; &quot;LDZ &quot; TZA TAZ &quot;TSB &quot; &quot;TRB &quot; RA TXY TYX PLD PLB PHB PHD PHK XCE</Keywords>
<Keywords name="Keywords5">&quot;sbx &quot; &quot;isc &quot; &quot;dcp &quot; &quot;lax &quot; &quot;sax &quot; &quot;anc &quot; &quot;alr &quot; &quot;arr &quot; &quot;rra &quot; &quot;rla &quot; &quot;lxa &quot; &quot;ane &quot; &quot;xaa &quot;&#x000D;&#x000A;&quot;SBX &quot; &quot;ISC &quot; &quot;DCP &quot; &quot;LAX &quot; &quot;SAX &quot; &quot;ANC &quot; &quot;ALR &quot; &quot;ARR &quot; &quot;RRA &quot; &quot;RLA &quot; &quot;LXA &quot; &quot;ANE &quot; &quot;XAA &quot;</Keywords>
<Keywords name="Keywords6"></Keywords>

View File

@ -45,7 +45,7 @@ case class CompilationOptions(platform: Platform,
EmitIntel8085Opcodes, EmitIntel8080Opcodes, UseIxForStack, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set(
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes, EmitZ80NextOpcodes,
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes, EmitZ80NextOpcodes, EmitR800Opcodes,
UseIyForStack, UseIxForScratch, UseIyForScratch, UseShadowRegistersForInterrupts)
if (CpuFamily.forType(platform.cpu) != CpuFamily.M6809) invalids ++= Set(
@ -172,6 +172,11 @@ case class CompilationOptions(platform: Platform,
log.error("Extended 8080-like opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitR800Opcodes)) {
if (platform.cpu != R800) {
log.error("R800 opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitIntel8080Opcodes)) {
if (!Intel8080Compatible(platform.cpu)) {
log.error("Intel 8080 opcodes enabled for architecture that doesn't support them")
@ -220,6 +225,7 @@ case class CompilationOptions(platform: Platform,
"OPTIMIZE_IPO" -> toLong(flag(CompilationFlag.InterproceduralOptimization)),
"CPUFEATURE_DECIMAL_MODE" -> toLong(flag(CompilationFlag.DecimalMode)),
"CPUFEATURE_Z80" -> toLong(flag(CompilationFlag.EmitZ80Opcodes)),
"CPUFEATURE_R800" -> toLong(flag(CompilationFlag.EmitR800Opcodes)),
"CPUFEATURE_EZ80" -> toLong(flag(CompilationFlag.EmitEZ80Opcodes)),
"CPUFEATURE_8080" -> toLong(flag(CompilationFlag.EmitIntel8080Opcodes)),
"CPUFEATURE_8085" -> toLong(flag(CompilationFlag.EmitIntel8085Opcodes)),
@ -290,7 +296,7 @@ object CpuFamily extends Enumeration {
import Cpu._
cpu match {
case Mos | StrictMos | Ricoh | StrictRicoh | Cmos | SC02 | Rockwell | Wdc | HuC6280 | CE02 | Sixteen => M6502
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | EZ80 | Z80Next => I80
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | R800 | EZ80 | Z80Next => I80
case Intel8086 | Intel80186 => I86
case Cpu.Motorola6809 => M6809
}
@ -368,6 +374,10 @@ object Cpu extends Enumeration {
* The Zilog Z80 processor, without illegal instructions
*/
val StrictZ80: Cpu.Value = Value
/**
* The R800 CPU (used in MSX Turbo-R)
*/
val R800: Cpu.Value = Value
/**
* The Zilog eZ80 processor
*/
@ -400,11 +410,11 @@ object Cpu extends Enumeration {
/**
* Processors that can run code for Zilog Z80
*/
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, EZ80, Z80Next)
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, R800, EZ80, Z80Next)
/**
* Processors that can run code for Intel 8080
*/
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, EZ80, Z80Next)
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, R800, EZ80, Z80Next)
/**
* Processors that can run code for Intel 8085
*/
@ -421,6 +431,7 @@ object Cpu extends Enumeration {
EnableBreakpoints,
UseOptimizationHints,
GenericWarnings,
ByteOverflowWarning,
UselessCodeWarning,
BuggyCodeWarning,
FallbackValueUseWarning,
@ -467,6 +478,8 @@ object Cpu extends Enumeration {
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitIntel8085Opcodes, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
case StrictZ80 | Z80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts)
case R800 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitR800Opcodes)
case Z80Next =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitIllegals, EmitZ80NextOpcodes)
case EZ80 =>
@ -513,6 +526,7 @@ object Cpu extends Enumeration {
case "strict2a07" => StrictRicoh
case "z80" => Z80
case "strictz80" => Z80
case "r800" => R800
case "zx80next" => Z80Next
case "z80next" => Z80Next
// disabled for now:
@ -561,9 +575,9 @@ object CompilationFlag extends Enumeration {
EmitIllegals, DecimalMode, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly, EnableBreakpoints,
// compilation options for MOS:
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitSC02Opcodes, EmitRockwellOpcodes, EmitWdcOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack, IdentityPage,
// compilation options for I80
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, EmitZ80NextOpcodes,
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitR800Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, EmitZ80NextOpcodes,
UseShadowRegistersForInterrupts,
UseIxForStack, UseIyForStack,
UseIxForScratch, UseIyForScratch,
@ -585,6 +599,7 @@ object CompilationFlag extends Enumeration {
SingleThreaded,
// warning options
GenericWarnings,
ByteOverflowWarning,
UselessCodeWarning,
BuggyCodeWarning,
DeprecationWarning,
@ -603,6 +618,7 @@ object CompilationFlag extends Enumeration {
val allWarnings: Set[CompilationFlag.Value] = Set(
GenericWarnings,
ByteOverflowWarning,
UselessCodeWarning,
BuggyCodeWarning,
DeprecationWarning,
@ -625,6 +641,7 @@ object CompilationFlag extends Enumeration {
"emit_65ce02" -> Emit65CE02Opcodes,
"emit_huc6280" -> EmitHudsonOpcodes,
"emit_z80" -> EmitZ80Opcodes,
"emit_r800" -> EmitR800Opcodes,
"emit_ez80" -> EmitEZ80Opcodes,
"emit_x80" -> EmitExtended80Opcodes,
"emit_8080" -> EmitIntel8080Opcodes,
@ -637,6 +654,7 @@ object CompilationFlag extends Enumeration {
"u_stack" -> UseUForStack,
"y_stack" -> UseYForStack,
"software_stack" -> SoftwareStack,
"identity_page" -> IdentityPage,
"use_shadow_registers_for_irq" -> UseShadowRegistersForInterrupts,
"output_intel_syntax" -> UseIntelSyntaxForOutput,
"input_intel_syntax" -> UseIntelSyntaxForInput,

View File

@ -46,10 +46,24 @@ case class Context(errorReporting: Logger,
if (isFlagSet(CompilationFlag.EmitEZ80Opcodes)) {
addons += CompilationFlag.EmitZ80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitZ80Opcodes) || isFlagSet(CompilationFlag.EmitSharpOpcodes)) {
if (isFlagSet(CompilationFlag.EmitZ80NextOpcodes)) {
addons += CompilationFlag.EmitZ80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitR800Opcodes)) {
addons += CompilationFlag.EmitZ80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitEZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80NextOpcodes)
|| isFlagSet(CompilationFlag.EmitR800Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitSharpOpcodes)) {
addons += CompilationFlag.EmitExtended80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitZ80Opcodes) || isFlagSet(CompilationFlag.EmitIntel8085Opcodes)) {
if (isFlagSet(CompilationFlag.EmitEZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80NextOpcodes)
|| isFlagSet(CompilationFlag.EmitR800Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitIntel8085Opcodes)) {
addons += CompilationFlag.EmitIntel8080Opcodes -> true
}
if (isFlagSet(CompilationFlag.OptimizeForSpeed)) {

View File

@ -1,23 +1,37 @@
package millfork
import millfork.output.{BankLayoutInFile, FormattableLabel}
import java.util.regex.Pattern
import scala.collection.mutable
import scala.util.control.Breaks.{break, breakable}
/**
* @author Karol Stasiak
*/
object DebugOutputFormat {
val map: Map[String, DebugOutputFormat] = Map(
"raw" -> RawDebugOutputFormat,
"vice" -> ViceDebugOutputFormat,
"nesasm" -> NesasmDebugOutputFormat,
"fns" -> NesasmDebugOutputFormat,
"fceux" -> FceuxDebugOutputFormat,
"nl" -> FceuxDebugOutputFormat,
"mlb" -> MesenOutputFormat,
"mesen" -> MesenOutputFormat,
"asm6f" -> MesenOutputFormat,
"ld65" -> Ld65OutputFormat,
"ca65" -> Ld65OutputFormat,
"cc65" -> Ld65OutputFormat,
"dbg" -> Ld65OutputFormat,
"sym" -> SymDebugOutputFormat)
}
sealed trait DebugOutputFormat {
def formatAll(labels: Seq[(String, (Int, Int))], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLineTupled).mkString("\n") + "\n"
def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLine).mkString("\n") + "\n"
if (breakpoints.isEmpty) {
labelPart
} else {
@ -25,9 +39,7 @@ sealed trait DebugOutputFormat {
}
}
final def formatLineTupled(labelAndValue: (String, (Int, Int))): String = formatLine(labelAndValue._1, labelAndValue._2._1, labelAndValue._2._2)
def formatLine(label: String, bank: Int, value: Int): String
def formatLine(label: FormattableLabel): String
final def formatBreakpointTupled(value: (Int, Int)): Seq[String] = formatBreakpoint(value._1, value._2).toSeq
@ -45,10 +57,25 @@ sealed trait DebugOutputFormat {
def breakpointsHeader: String = ""
}
object RawDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: FormattableLabel): String = {
f"${label.bankNumber}%02X:${label.startValue}%04X:${label.endValue.fold("")(_.formatted("%04X"))}%s:${label.category}%s:$label%s"
}
override def fileExtension(bank: Int): String = ".labels"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
override def formatBreakpoint(bank: Int, value: Int): Option[String] =
Some(f"$bank%02X:$value%04X::b:<breakpoint@$value%04X>")
}
object ViceDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
val normalized = label.replace('$', '_').replace('.', '_')
s"al ${value.toHexString} .$normalized"
override def formatLine(label: FormattableLabel): String = {
val normalized = label.labelName.replace('$', '_').replace('.', '_')
s"al ${label.startValue.toHexString} .$normalized"
}
override def fileExtension(bank: Int): String = ".lbl"
@ -61,8 +88,8 @@ object ViceDebugOutputFormat extends DebugOutputFormat {
}
object NesasmDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
label + " = $" + value.toHexString
override def formatLine(label: FormattableLabel): String = {
label.labelName + " = $" + label.startValue.toHexString
}
override def fileExtension(bank: Int): String = ".fns"
@ -75,8 +102,8 @@ object NesasmDebugOutputFormat extends DebugOutputFormat {
}
object SymDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
f"$bank%02x:$value%04x $label%s"
override def formatLine(label: FormattableLabel): String = {
f"${label.bankNumber}%02x:${label.startValue}%04x ${label.labelName}%s"
}
override def fileExtension(bank: Int): String = ".sym"
@ -93,8 +120,8 @@ object SymDebugOutputFormat extends DebugOutputFormat {
}
object FceuxDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
f"$$$value%04x#$label%s#"
override def formatLine(label: FormattableLabel): String = {
f"$$${label.startValue}%04x#${label.labelName}%s#"
}
override def fileExtension(bank: Int): String = if (bank == 0xff) ".ram.nl" else s".$bank.nl"
@ -105,3 +132,97 @@ object FceuxDebugOutputFormat extends DebugOutputFormat {
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
}
object MesenOutputFormat extends DebugOutputFormat {
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val allStarts = labels.groupBy(_.bankName).mapValues(_.map(_.startValue).toSet)
labels.flatMap{ l =>
val mesenShift = b.getMesenShift(l.bankName)
val shiftedStart = l.startValue + mesenShift
var shiftedEnd = l.endValue.map(_ + mesenShift).filter(_ != shiftedStart)
l.endValue match {
case None =>
case Some(e) =>
// Mesen does not like labels of form XXX-XXX, where both ends are equal
breakable {
for (i <- l.startValue.+(1) to e) {
if (allStarts.getOrElse(l.bankName, Set.empty).contains(i)) {
shiftedEnd = None
break
}
}
}
}
if (shiftedStart >= 0 && shiftedEnd.forall(_ >= 0)) {
val normalized = l.labelName.replace('$', '_').replace('.', '_')
val comment = (l.category match {
case 'F' => "function "
case 'A' => "initialized array "
case 'V' => "initialized variable "
case 'a' => "array "
case 'v' => "variable "
case _ => ""
}) + l.labelName
Some(f"${l.mesenSymbol}%s:${shiftedStart}%04X${shiftedEnd.fold("")(e => f"-$e%04X")}%s:$normalized%s:$comment%s")
} else {
None
}
}.mkString("\n")
}
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
override def fileExtension(bank: Int): String = ".mlb"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
}
object Ld65OutputFormat extends DebugOutputFormat {
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
override def fileExtension(bank: Int): String = ".dbg"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = true
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val q = '"'
val allBanksInFile = b.allBanks()
val allBanks = (labels.map(_.bankName).distinct ++ allBanksInFile).distinct
val result = mutable.ListBuffer[String]()
result += "version\tmajor=2,minor=0"
result += s"info\tcsym=0,file=0,lib=0,line=0,mod=0,scope=1,seg=${allBanks.size},span=0,sym=${labels.size},type=4"
for ((bank, ix) <- allBanks.zipWithIndex) {
val common = s"seg\tid=${ix},name=$q${bank}$q,start=0x${b.getStart(bank).toHexString},size=0x0,addrsize=absolute"
if (allBanksInFile.contains(bank)) {
result += common + s",ooffs=${b.ld65Offset(bank)}"
} else {
result += common
}
}
result += "scope\tid=0,name=\"\""
for ((label, ix) <- labels.sortBy(l => (l.bankName, l.startValue, l.labelName)).zipWithIndex) {
val name = label.labelName.replace('$', '@').replace('.', '@')
val sb = new StringBuilder
sb ++= s"sym\tid=${ix},name=$q${name}$q,addrsize=absolute,"
label.endValue match {
case Some(e) =>
if (!labels.exists(l => l.ne(label) && l.startValue >= label.startValue && l.startValue <= e)) {
sb ++= s"size=${ e - label.startValue + 1 },"
}
case None =>
}
sb ++= s"scope=0,val=0x${label.startValue.toHexString},seg=${allBanks.indexOf(label.bankName)},type=lab"
result += sb.toString
}
result.mkString("\n")
}
}

View File

@ -119,25 +119,35 @@ object Main {
else if (l.startsWith("__")) 7
else 0
}
val sortedLabels = result.labels.groupBy(_._2).values.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2)
val sortedLabels: Seq[FormattableLabel] =
result.labels.groupBy(_._2).values
.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2)
.map { case (l, (b, s)) =>
val bankNumber = options.platform.bankNumbers.getOrElse(b, 0)
val mesenCategory = options.platform.getMesenLabelCategory(b, s)
result.endLabels.get(l) match {
case Some((c, e)) => FormattableLabel(l, b, bankNumber, s, Some(e), c, mesenCategory)
case _ => FormattableLabel(l, b, bankNumber, s, None, 'x', mesenCategory)
}
}
val sortedBreakpoints = result.breakpoints
val format = c.outputLabelsFormatOverride.getOrElse(platform.outputLabelsFormat)
val basename = if (format.addOutputExtension) output + platform.fileExtension else output
if (format.filePerBank) {
val banks = sortedLabels.map(_._2._1).toSet ++ sortedBreakpoints.map(_._2).toSet
val banks: Set[Int] = sortedLabels.map(_.bankNumber).toSet ++ sortedBreakpoints.map(_._1).toSet
banks.foreach{ bank =>
val labels = sortedLabels.filter(_._2._1.==(bank))
val labels = sortedLabels.filter(_.bankNumber.==(bank))
val breakpoints = sortedBreakpoints.filter(_._1.==(bank))
val labelOutput = basename + format.fileExtension(bank)
val path = Paths.get(labelOutput)
errorReporting.debug("Writing labels to " + path.toAbsolutePath)
Files.write(path, format.formatAll(labels, breakpoints).getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(result.bankLayoutInFile, labels, breakpoints).getBytes(StandardCharsets.UTF_8))
}
} else {
val labelOutput = basename + format.fileExtension(0)
val path = Paths.get(labelOutput)
errorReporting.debug("Writing labels to " + path.toAbsolutePath)
Files.write(path, format.formatAll(sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(result.bankLayoutInFile, sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
}
}
val defaultPrgOutput = if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension
@ -165,7 +175,7 @@ object Main {
f"${path.getFileName}%s ${start}%04X ${start}%04X ${codeLength}%04X".getBytes(StandardCharsets.UTF_8))
}
}
errorReporting.debug(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
errorReporting.info(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
c.runFileName.foreach{ program =>
if (File.separatorChar == '\\') {
if (!new File(program).exists() && !new File(program + ".exe").exists()) {
@ -283,6 +293,7 @@ object Main {
if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.All else Nil,
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil,
if (options.flag(CompilationFlag.EmitNative65816Opcodes)) SixteenOptimizations.AllForNative else Nil,
if (options.flag(CompilationFlag.IdentityPage)) IdentityPageOptimizations.All else Nil,
if (options.flag(CompilationFlag.DangerousOptimizations)) DangerousOptimizations.All else Nil
).flatten
val goodCycle = List.fill(optLevel - 2)(OptimizationPresets.Good ++ goodExtras).flatten
@ -321,7 +332,9 @@ object Main {
val assemblyOptimizations = optLevel match {
case 0 => Nil
case _ =>
if (options.flag(CompilationFlag.EmitZ80Opcodes))
if (options.flag(CompilationFlag.EmitR800Opcodes))
Z80OptimizationPresets.GoodForR800
else if (options.flag(CompilationFlag.EmitZ80Opcodes))
Z80OptimizationPresets.GoodForZ80
else if (options.flag(CompilationFlag.EmitIntel8080Opcodes))
Z80OptimizationPresets.GoodForIntel8080
@ -427,7 +440,7 @@ object Main {
p.toLowerCase(Locale.ROOT),
errorReporting.fatal("Invalid label file format: " + p))
c.copy(outputLabels = true, outputLabelsFormatOverride = Some(f))
}.description("Generate also the label file in the given format. Available options: vice, nesasm, sym.")
}.description("Generate also the label file in the given format. Available options: vice, nesasm, sym, raw.")
boolean("-fbreakpoints", "-fno-breakpoints").action((c,v) =>
c.changeFlag(CompilationFlag.EnableBreakpoints, v)
@ -487,7 +500,7 @@ object Main {
} else {
errorReporting.fatal("Invalid syntax for -D option")
}
}.description("Define a feature value for the preprocessor.")
}.description("Define a feature value for the preprocessor.").maxCount(Integer.MAX_VALUE)
boolean("-finput_intel_syntax", "-finput_zilog_syntax").repeatable().action((c,v) =>
c.changeFlag(CompilationFlag.UseIntelSyntaxForInput, v)
@ -650,7 +663,7 @@ object Main {
c.changeFlag(CompilationFlag.CompactReturnDispatchParams, v)
}.description("Whether parameter values in return dispatch statements may overlap other objects. Enabled by default.")
boolean("-fbounds-checking", "-fno-bounds-checking").action { (c, v) =>
c.changeFlag(CompilationFlag.VariableOverlap, v)
c.changeFlag(CompilationFlag.CheckIndexOutOfBounds, v)
}.description("Whether should insert bounds checking on array access.")
boolean("-flenient-encoding", "-fno-lenient-encoding").action { (c, v) =>
c.changeFlag(CompilationFlag.LenientTextEncoding, v)
@ -745,6 +758,9 @@ object Main {
boolean("-fregister-variables", "-fno-register-variables").action { (c, v) =>
c.changeFlag(CompilationFlag.RegisterVariables, v)
}.description("Allow moving local variables into CPU registers. Enabled by default.")
boolean("-fidentity-page", "-fno-identity-page").action { (c, v) =>
c.changeFlag(CompilationFlag.IdentityPage, v)
}.description("Whether should use an identity page to optimize certain operations.")
flag("-Os", "--size").repeatable().action { c =>
c.changeFlag(CompilationFlag.OptimizeForSize, true).
changeFlag(CompilationFlag.OptimizeForSpeed, false).
@ -757,6 +773,7 @@ object Main {
}.description("Prefer faster code even if it is slightly bigger (experimental). Implies -finline.")
flag("-Ob", "--blast-processing").repeatable().action { c =>
c.changeFlag(CompilationFlag.OptimizeForSize, false).
changeFlag(CompilationFlag.IdentityPage, true).
changeFlag(CompilationFlag.OptimizeForSpeed, true).
changeFlag(CompilationFlag.OptimizeForSonicSpeed, true)
}.description("Prefer faster code even if it is much bigger (experimental). Implies -finline.")
@ -818,6 +835,10 @@ object Main {
c.changeFlag(CompilationFlag.RorWarning, v)
}.description("Whether should warn about the ROR instruction (6502 only). Default: disabled.")
boolean("-Woverflow", "-Wno-overflow").repeatable().action { (c, v) =>
c.changeFlag(CompilationFlag.ByteOverflowWarning, v)
}.description("Whether should warn about byte overflow. Default: enabled.")
boolean("-Wuseless", "-Wno-useless").repeatable().action { (c, v) =>
c.changeFlag(CompilationFlag.UselessCodeWarning, v)
}.description("Whether should warn about code that does nothing. Default: enabled.")

View File

@ -42,6 +42,7 @@ class Platform(
val outputLabelsFormat: DebugOutputFormat,
val outputStyle: OutputStyle.Value
) {
def hasZeroPage: Boolean = cpuFamily == CpuFamily.M6502
def cpuFamily: CpuFamily.Value = CpuFamily.forType(this.cpu)
@ -60,6 +61,18 @@ class Platform(
val e2 = bankEndBefore(bank2)
e2 > s1 && s2 < e1
}
lazy val banksExplicitlyWrittenToOutput: Set[String] = bankNumbers.keySet.filter(b => defaultOutputPackager.writes(b)).toSet
def getMesenLabelCategory(bank: String, address: Int): Char = {
// see: https://www.mesen.ca/docs/debugging/debuggerintegration.html#mesen-label-files-mlb
val start = bankStart(bank)
val end = bankEndBefore(bank)
if (start > address || address >= end) 'G'
else if (banksExplicitlyWrittenToOutput(bank)) 'P'
else if (bank == "default") 'R'
else 'W' // TODO: distinguish between W and S
}
}
object Platform {

View File

@ -2,7 +2,7 @@ package millfork.assembly
import millfork.{CompilationFlag, CompilationOptions}
import millfork.compiler.LabelGenerator
import millfork.env.{NormalFunction, ThingInMemory}
import millfork.env.{Constant, NormalFunction, ThingInMemory}
import millfork.error.Logger
import millfork.node.NiceFunctionProperty
@ -10,8 +10,9 @@ import millfork.node.NiceFunctionProperty
* @author Karol Stasiak
*/
case class OptimizationContext(options: CompilationOptions,
labelMap: Map[String, (Int, Int)],
labelMap: Map[String, (String, Int)],
zreg: Option[ThingInMemory],
identityPage: Constant,
niceFunctionProperties: Set[(NiceFunctionProperty, String)]) {
@inline
def log: Logger = options.log
@ -25,4 +26,6 @@ trait AssemblyOptimization[T <: AbstractCode] {
def optimize(f: NormalFunction, code: List[T], context: OptimizationContext): List[T]
def requiredFlags: Set[CompilationFlag.Value] = Set.empty
def minimumRequiredLines: Int
}

View File

@ -254,9 +254,10 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
case MUL => overlaps(D)
case ABX => reg == X || overlaps(B)
case NOP | SWI | SWI2 | SWI3 | SYNC => false
case LDB | LDA | LDX | LDY | LDU => false
case INC | DEC | ROL | ROR | ASL | ASR | LSR | CLR | COM | NEG | TST => false // variants for A and B handled before
case op if Branching(op) => false
case JMP => false
case JMP | RTS => false
case _ => true // TODO
}
}

View File

@ -3,7 +3,7 @@ package millfork.assembly.m6809.opt
import millfork.assembly.AssemblyOptimization
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, InherentA, InherentB, LongRelative, MAddrMode, MLine, MState, PostIncremented, RegisterSet}
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, Indexed, InherentA, InherentB, LongRelative, MAddrMode, MLine, MState, PostIncremented, RegisterSet}
import millfork.env.{CompoundConstant, Constant, MathOperator}
import millfork.node.M6809Register
@ -253,6 +253,36 @@ object AlwaysGoodMOptimizations {
)
val SimplifiableAddressing = new RuleBasedAssemblyOptimization("Simplifiable addressing",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(NotFixed & MatchX(0) & HasAddrMode(Indexed(M6809Register.X, indirect = false)) & Not(HasOpcodeIn(LEAX, LEAY, LEAS, LEAU))) ~~> { (code, ctx) =>
val x = ctx.get[Constant](0)
code.map(l => l.copy(addrMode = Absolute(indirect = false), parameter = (x + l.parameter).quickSimplify))
},
(NotFixed & MatchY(0) & HasAddrMode(Indexed(M6809Register.Y, indirect = false)) & Not(HasOpcodeIn(LEAX, LEAY, LEAS, LEAU))) ~~> { (code, ctx) =>
val y = ctx.get[Constant](0)
code.map(l => l.copy(addrMode = Absolute(indirect = false), parameter = (y + l.parameter).quickSimplify))
},
(NotFixed & MatchX(0) & HasAddrMode(Indexed(M6809Register.X, indirect = false)) & HasOpcodeIn(LEAX, LEAY, LEAS, LEAU)) ~~> { (code, ctx) =>
val x = ctx.get[Constant](0)
code.map(l => l.copy(opcode = l.opcode match {
case LEAX => LDX
case LEAY => LDY
case LEAU => LDU
case LEAS => LDS
}, addrMode = Immediate, parameter = (x + l.parameter).quickSimplify))
},
(NotFixed & MatchY(0) & HasAddrMode(Indexed(M6809Register.X, indirect = false)) & HasOpcodeIn(LEAX, LEAY, LEAS, LEAU)) ~~> { (code, ctx) =>
val y = ctx.get[Constant](0)
code.map(l => l.copy(opcode = l.opcode match {
case LEAX => LDX
case LEAY => LDY
case LEAU => LDU
case LEAS => LDS
}, addrMode = Immediate, parameter = (y + l.parameter).quickSimplify))
},
)
val UnusedLabelRemoval = new RuleBasedAssemblyOptimization("Unused label removal",
needsFlowInfo = FlowInfoRequirement.JustLabels,
(Elidable & HasOpcode(LABEL) & HasCallerCount(0) & ParameterIsLocalLabel) ~~> (_ => Nil)
@ -263,6 +293,7 @@ object AlwaysGoodMOptimizations {
PointlessLoad,
PointlessCompare,
PointlessRegisterTransfers,
SimplifiableAddressing,
SimplifiableArithmetics,
SimplifiableComparison,
SimplifiableJumps,

View File

@ -35,6 +35,11 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
def nzB(i: Long): CpuStatus =
this.copy(n = SingleStatus((i & 0x80) != 0), z = SingleStatus((i & 0xff) == 0))
def nzB(i: Status[Int]): CpuStatus = i match {
case SingleStatus(j) => this.copy(n = SingleStatus((j & 0x80) != 0), z = SingleStatus((j & 0xff) == 0))
case _ => this.copy(n = AnyStatus, z = AnyStatus)
}
def nzW(i: Long): CpuStatus =
this.copy(n = SingleStatus((i & 0x8000) != 0), z = SingleStatus((i & 0xffff) == 0))
@ -44,6 +49,11 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
case _ => this.nz
}
def nzW(i: Status[Int]): CpuStatus = i match {
case SingleStatus(j) => this.copy(n = SingleStatus((j & 0x8000) != 0), z = SingleStatus((j & 0xffff) == 0))
case _ => this.copy(n = AnyStatus, z = AnyStatus)
}
def ~(that: CpuStatus) = new CpuStatus(
a = this.a ~ that.a,
b = this.b ~ that.b,

View File

@ -2,10 +2,11 @@ package millfork.assembly.m6809.opt
import millfork.CompilationFlag
import millfork.assembly.OptimizationContext
import millfork.assembly.m6809.{Immediate, MLine, MLine0}
import millfork.assembly.m6809.{Absolute, Immediate, Inherent, InherentA, InherentB, MLine, MLine0, TwoRegisters}
import millfork.assembly.opt.Status.SingleFalse
import millfork.assembly.opt.{AnyStatus, FlowCache, SingleStatus, Status}
import millfork.env._
import millfork.node.M6809NiceFunctionProperty.{DoesntChangeA, DoesntChangeB, DoesntChangeU, DoesntChangeX, DoesntChangeY}
import millfork.node.{M6809Register, NiceFunctionProperty}
import scala.util.control.Breaks._
@ -62,33 +63,108 @@ object ForwardFlowAnalysis {
case _ => None
}).fold(currentStatus)(_ ~ _)
case MLine0(JSR, _, MemoryAddressConstant(th)) =>
case MLine0(JSR, am, MemoryAddressConstant(th)) =>
var prU = bpInU
var prY = bpInY
var prA = false
var prB = false
var prX = false
(am, th) match {
case (Absolute(false), fun: FunctionInMemory) =>
val nfp = optimizationContext.niceFunctionProperties
val fn = fun.name
if (nfp(DoesntChangeX -> fn)) prX = true
if (nfp(DoesntChangeY -> fn)) prY = true
if (nfp(DoesntChangeU -> fn)) prU = true
if (nfp(DoesntChangeA -> fn)) prA = true
if (nfp(DoesntChangeB -> fn)) prB = true
case _ =>
}
currentStatus = initialStatus.copy(
memStack = currentStatus.memStack,
u = if (bpInU) currentStatus.u else AnyStatus,
y = if (bpInY) currentStatus.y else AnyStatus
u = if (prU) currentStatus.u else AnyStatus,
x = if (prX) currentStatus.x else AnyStatus,
y = if (prY) currentStatus.y else AnyStatus,
a = if (prA) currentStatus.a else AnyStatus,
b = if (prB) currentStatus.b else AnyStatus
)
case MLine0(JSR | BYTE, _, _) =>
currentStatus = initialStatus
case MLine0(NOP, _, _) =>
()
case MLine0(op, Immediate, constant) if ForwardFlowAnalysisForImmediate.hasDefinition(op) =>
ForwardFlowAnalysisForImmediate.get(op)(constant, currentStatus)
case MLine0(op, Inherent, _) if ForwardFlowAnalysisForInherent.hasDefinition(op) =>
ForwardFlowAnalysisForInherent.get(op)(currentStatus)
case MLine0(op, InherentA, _) if ForwardFlowAnalysisForInherentA.hasDefinition(op) =>
ForwardFlowAnalysisForInherentA.get(op)(currentStatus)
case MLine0(op, InherentB, _) if ForwardFlowAnalysisForInherentB.hasDefinition(op) =>
ForwardFlowAnalysisForInherentB.get(op)(currentStatus)
case MLine0(LDA, Immediate, NumericConstant(n, _)) =>
currentStatus = currentStatus.copy(a = SingleStatus(n.toInt & 0xff), v = SingleFalse).nzB(n)
case MLine0(LDB, Immediate, NumericConstant(n, _)) =>
currentStatus = currentStatus.copy(b = SingleStatus(n.toInt & 0xff), v = SingleFalse).nzB(n)
case MLine0(LDD, Immediate, NumericConstant(n, _)) =>
currentStatus = currentStatus.copy(
a = SingleStatus(n.toInt.>>(8) & 0xff),
b = SingleStatus(n.toInt & 0xff),
v = SingleFalse
).nzW(n)
case MLine0(LDX, Immediate, c) =>
currentStatus = currentStatus.copy(x = SingleStatus(c), v = SingleFalse).nzW(c)
case MLine0(LDY, Immediate, c) =>
currentStatus = currentStatus.copy(y = SingleStatus(c), v = SingleFalse).nzW(c)
case MLine0(LDA, _, _) =>
currentStatus = currentStatus.copy(a = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDB, _, _) =>
currentStatus = currentStatus.copy(b = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDD, _, _) =>
currentStatus = currentStatus.copy(a = AnyStatus, b = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDX, _, _) =>
currentStatus = currentStatus.copy(x = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDY, _, _) =>
currentStatus = currentStatus.copy(y = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(STA | STB | STD | STX | STU | STY | STS, _, _) =>
// don't change
case MLine0(TFR, TwoRegisters(source, target), _) =>
import M6809Register._
(source, target) match {
case (A, B) => currentStatus = currentStatus.copy(b = currentStatus.a)
case (B, A) => currentStatus = currentStatus.copy(a = currentStatus.b)
case (CC, A) => currentStatus = currentStatus.copy(a = AnyStatus)
case (CC, B) => currentStatus = currentStatus.copy(b = AnyStatus)
case (A, CC) | (B, CC) => currentStatus = currentStatus.copy(c = AnyStatus, z = AnyStatus, n = AnyStatus, v = AnyStatus)
case (S, D) => currentStatus = currentStatus.copy(a = AnyStatus, b = AnyStatus)
case (S, X) => currentStatus = currentStatus.copy(x = AnyStatus)
case (S, Y) => currentStatus = currentStatus.copy(y = AnyStatus)
case (S, U) => currentStatus = currentStatus.copy(u = AnyStatus)
case (D, X) => currentStatus = currentStatus.copy(x = currentStatus.d.map(n => NumericConstant(n, 2)))
case (D, Y) => currentStatus = currentStatus.copy(y = currentStatus.d.map(n => NumericConstant(n, 2)))
case (D, U) => currentStatus = currentStatus.copy(u = currentStatus.d.map(n => NumericConstant(n, 2)))
case (X, Y) => currentStatus = currentStatus.copy(y = currentStatus.x)
case (X, U) => currentStatus = currentStatus.copy(u = currentStatus.x)
case (Y, X) => currentStatus = currentStatus.copy(x = currentStatus.y)
case (Y, U) => currentStatus = currentStatus.copy(u = currentStatus.y)
case (U, X) => currentStatus = currentStatus.copy(x = currentStatus.u)
case (U, Y) => currentStatus = currentStatus.copy(y = currentStatus.u)
case (X, D) =>
val (h, l) = currentStatus.x.toHiLo
currentStatus = currentStatus.copy(a = h, b = l)
case (Y, D) =>
val (h, l) = currentStatus.y.toHiLo
currentStatus = currentStatus.copy(a = h, b = l)
case (U, D) =>
val (h, l) = currentStatus.u.toHiLo
currentStatus = currentStatus.copy(a = h, b = l)
case _ => currentStatus = initialStatus
}
case MLine0(EXG, TwoRegisters(source, target), _) =>
import M6809Register._
(source, target) match {
case (A, B) | (B, A) => currentStatus = currentStatus.copy(a = currentStatus.b, b = currentStatus.a)
case (A, CC) | (CC, A) => currentStatus = currentStatus.copy(a = AnyStatus, c = AnyStatus, v = AnyStatus, n = AnyStatus, z = AnyStatus)
case (B, CC) | (CC, B) => currentStatus = currentStatus.copy(b = AnyStatus, c = AnyStatus, v = AnyStatus, n = AnyStatus, z = AnyStatus)
case (X, Y) | (Y, X) => currentStatus = currentStatus.copy(y = currentStatus.x, x = currentStatus.y)
case (U, Y) | (Y, U) => currentStatus = currentStatus.copy(y = currentStatus.u, u = currentStatus.y)
case (X, U) | (U, X) => currentStatus = currentStatus.copy(u = currentStatus.x, x = currentStatus.u)
case (X, D) | (D, X) =>
val (h, l) = currentStatus.x.toHiLo
currentStatus = currentStatus.copy(a = h, b = l, x = currentStatus.d.map(n => NumericConstant(n, 2)))
case (Y, D) | (D, Y) =>
val (h, l) = currentStatus.y.toHiLo
currentStatus = currentStatus.copy(a = h, b = l, y = currentStatus.d.map(n => NumericConstant(n, 2)))
case (U, D) | (D, U) =>
val (h, l) = currentStatus.u.toHiLo
currentStatus = currentStatus.copy(a = h, b = l, u = currentStatus.d.map(n => NumericConstant(n, 2)))
case _ => currentStatus = initialStatus
}
case MLine0(opcode, addrMode, _) =>
// TODO

View File

@ -0,0 +1,80 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.{AnyStatus, SingleStatus, Status}
import millfork.assembly.opt.Status.SingleFalse
import millfork.env.Constant
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForImmediate {
private val map: Map[MOpcode.Value, (Constant, CpuStatus) => CpuStatus] = Map(
LDX -> {(c, currentStatus) =>
currentStatus.copy(x = SingleStatus(c), v = SingleFalse).nzW(c)
},
LDY -> {(c, currentStatus) =>
currentStatus.copy(y = SingleStatus(c), v = SingleFalse).nzW(c)
},
LDU -> {(c, currentStatus) =>
currentStatus.copy(u = SingleStatus(c), v = SingleFalse).nzW(c)
},
LDA -> {(c, currentStatus) =>
val value = Status.fromByte(c)
currentStatus.copy(a = value, v = SingleFalse).nzB(value)
},
LDB -> {(c, currentStatus) =>
val value = Status.fromByte(c)
currentStatus.copy(a = value, v = SingleFalse).nzB(value)
},
LDD -> {(c, currentStatus) =>
val value = Status.fromByte(c)
currentStatus.copy(a = value.hi, b = value.lo, v = SingleFalse).nzW(value)
},
ORA -> { (c, currentStatus) =>
val newValue = (currentStatus.a <*> Status.fromByte(c)) { (x, y) => (x | y) & 0xff }
currentStatus.copy(a = newValue, v = SingleFalse).nzB(newValue)
},
ORB -> { (c, currentStatus) =>
val newValue = (currentStatus.b <*> Status.fromByte(c)) { (x, y) => (x | y) & 0xff }
currentStatus.copy(b = newValue, v = SingleFalse).nzB(newValue)
},
EORA -> { (c, currentStatus) =>
val newValue = (currentStatus.a <*> Status.fromByte(c)) { (x, y) => (x ^ y) & 0xff }
currentStatus.copy(a = newValue, v = SingleFalse).nzB(newValue)
},
EORB -> { (c, currentStatus) =>
val newValue = (currentStatus.b <*> Status.fromByte(c)) { (x, y) => (x ^ y) & 0xff }
currentStatus.copy(b = newValue, v = SingleFalse).nzB(newValue)
},
ANDA -> { (c, currentStatus) =>
val newValue = (currentStatus.a <*> Status.fromByte(c)) { (x, y) => (x & y) & 0xff }
currentStatus.copy(a = newValue, v = SingleFalse).nzB(newValue)
},
ANDB -> { (c, currentStatus) =>
val newValue = (currentStatus.b <*> Status.fromByte(c)) { (x, y) => (x & y) & 0xff }
currentStatus.copy(b = newValue, v = SingleFalse).nzB(newValue)
},
ADDA -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.a.adc(Status.fromByte(c), SingleFalse)
currentStatus.copy(a = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
ADDB -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.b.adc(Status.fromByte(c), SingleFalse)
currentStatus.copy(b = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
ADCA -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.a.adc(Status.fromByte(c), currentStatus.c)
currentStatus.copy(a = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
ADCB -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.b.adc(Status.fromByte(c), currentStatus.c)
currentStatus.copy(b = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): (Constant, CpuStatus) => CpuStatus = map(opcode)
}

View File

@ -0,0 +1,28 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.Status.SingleFalse
import millfork.assembly.opt.{AnyStatus, SingleStatus, Status}
import millfork.env.Constant
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForInherent {
private val map: Map[MOpcode.Value, CpuStatus => CpuStatus] = Map(
NOP -> identity,
MUL -> { currentStatus =>
val newD = (currentStatus.a <*> currentStatus.b) { (a, b) => ((a & 0xff) * (b & 0xff)) & 0xff }
currentStatus.copy(c = AnyStatus, z = AnyStatus, a = newD.hi, b = newD.hi)
},
SEX -> { currentStatus =>
val newA = currentStatus.b.map{ n => if (n.&(0x80) == 0) 0 else 0xff }
currentStatus.copy(v = Status.SingleFalse, n = AnyStatus, z = AnyStatus, a = newA)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): CpuStatus => CpuStatus = map(opcode)
}

View File

@ -0,0 +1,47 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.{AnyStatus, SingleStatus, Status}
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForInherentA {
private val map: Map[MOpcode.Value, CpuStatus => CpuStatus] = Map(
ASL -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.<<(1).&(0xff))
currentStatus.copy(a = newValue, c = currentStatus.a.bit7, v = AnyStatus).nzB(newValue)
},
LSR -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.>>(1).&(0x7f))
currentStatus.copy(a = newValue, c = currentStatus.a.bit0, v = AnyStatus).nzB(newValue)
},
CLR -> { currentStatus =>
currentStatus.copy(a = Status.SingleZero, c = Status.SingleFalse, v = Status.SingleFalse, n = Status.SingleFalse, z = Status.SingleFalse)
},
COM -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.^(0xff).&(0xff))
currentStatus.copy(a = newValue, c = Status.SingleTrue, v = Status.SingleFalse).nzB(newValue)
},
DEC -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.+(1).&(0xff))
currentStatus.copy(a = newValue, v = AnyStatus).nzB(newValue)
},
INC -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.-(1).&(0xff))
currentStatus.copy(a = newValue, v = AnyStatus).nzB(newValue)
},
NEG -> { currentStatus =>
val newValue = currentStatus.a.map(n => (-n).&(0xff))
currentStatus.copy(a = newValue, c = AnyStatus, v = AnyStatus).nzB(newValue)
},
TST -> { currentStatus =>
currentStatus.copy(v = Status.SingleFalse).nzB(currentStatus.a)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): CpuStatus => CpuStatus = map(opcode)
}

View File

@ -0,0 +1,47 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.{AnyStatus, Status}
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForInherentB {
private val map: Map[MOpcode.Value, CpuStatus => CpuStatus] = Map(
ASL -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.<<(1).&(0xff))
currentStatus.copy(b = newValue, c = currentStatus.b.bit7, v = AnyStatus).nzB(newValue)
},
LSR -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.>>(1).&(0x7f))
currentStatus.copy(b = newValue, c = currentStatus.b.bit0, v = AnyStatus).nzB(newValue)
},
CLR -> { currentStatus =>
currentStatus.copy(b = Status.SingleZero, c = Status.SingleFalse, v = Status.SingleFalse, n = Status.SingleFalse, z = Status.SingleFalse)
},
COM -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.^(0xff).&(0xff))
currentStatus.copy(b = newValue, c = Status.SingleTrue, v = Status.SingleFalse).nzB(newValue)
},
DEC -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.+(1).&(0xff))
currentStatus.copy(b = newValue, v = AnyStatus).nzB(newValue)
},
INC -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.-(1).&(0xff))
currentStatus.copy(b = newValue, v = AnyStatus).nzB(newValue)
},
NEG -> { currentStatus =>
val newValue = currentStatus.b.map(n => (-n).&(0xff))
currentStatus.copy(b = newValue, c = AnyStatus, v = AnyStatus).nzB(newValue)
},
TST -> { currentStatus =>
currentStatus.copy(v = Status.SingleFalse).nzB(currentStatus.b)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): CpuStatus => CpuStatus = map(opcode)
}

View File

@ -1,6 +1,7 @@
package millfork.assembly.m6809.opt
import jdk.jfr.BooleanFlag
import millfork.CompilationFlag
import millfork.assembly._
import millfork.assembly.m6809.{Absolute, MLine, MLine0, MOpcode, MState}
import millfork.assembly.opt.FlowCache
@ -82,6 +83,7 @@ object ReverseFlowAnalyzer {
val readsB: Set[String] = Set("call")
val readsX: Set[String] = Set("call")
val readsY: Set[String] = Set("")
val preservesX: Set[String] = Set("__divmod_u8u8u8u8")
val cache = new FlowCache[MLine, CpuImportance]("m6809 reverse")
private val importanceBeforeJsr: CpuImportance = CpuImportance(
@ -97,7 +99,7 @@ object ReverseFlowAnalyzer {
hf = Unimportant)
private val finalImportance: CpuImportance = CpuImportance(
a = Important, b = Important,
x = Important, y = Important, u = Important,
x = Important, y = Important, u = Important, // TODO: correctly check which registers are important given the function and the compilation options
cf = Unimportant, vf = Unimportant, hf = Unimportant, zf = Unimportant, nf = Unimportant)
//noinspection RedundantNewCaseClass
@ -109,11 +111,23 @@ object ReverseFlowAnalyzer {
var changed = true
changed = true
val actualFinalImportance = f.returnType match {
case FlagBooleanType(_, _, _) => finalImportance.copy(cf = Important, zf = Important, nf = Important, vf = Important)
case t if t.size == 1 => finalImportance.copy(a = Unimportant)
case t if t.size == 0 => finalImportance.copy(a = Unimportant, b = Unimportant)
case _ => finalImportance
val actualFinalImportance = {
var tmp = f.returnType match {
case FlagBooleanType(_, _, _) => finalImportance.copy(cf = Important, zf = Important, nf = Important, vf = Important)
case t if t.size == 1 => finalImportance.copy(a = Unimportant)
case t if t.size == 0 => finalImportance.copy(a = Unimportant, b = Unimportant)
case _ => finalImportance
}
if (!f.inAssembly) {
tmp = tmp.copy(x = Unimportant)
if (!optimizationContext.options.flag(CompilationFlag.UseUForStack)) {
tmp = tmp.copy(u = Unimportant)
}
if (!optimizationContext.options.flag(CompilationFlag.UseYForStack)) {
tmp = tmp.copy(y = Unimportant)
}
}
tmp
}
while (changed) {
changed = false
@ -179,6 +193,7 @@ object ReverseFlowAnalyzer {
if (readsB(fun.name)) result = result.copy(b = Important)
if (readsX(fun.name)) result = result.copy(x = Important)
if (readsY(fun.name)) result = result.copy(y = Important)
if (preservesX(fun.name)) result = result.copy(x = currentImportance.x)
currentImportance = result.copy(
a = if (niceFunctionProperties(DoesntChangeA -> fun.name)) currentImportance.a ~ result.a else result.a,
b = if (niceFunctionProperties(DoesntChangeB -> fun.name)) currentImportance.b ~ result.b else result.b,

View File

@ -30,42 +30,52 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[MLine] {
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[MLine], optimizationContext: OptimizationContext): List[MLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, MLine)], optimizationContext: OptimizationContext): (Boolean, List[MLine]) = {
final def optimizeImpl(f: NormalFunction, code: List[MLine], taggedCode: List[(FlowInfo, MLine)], optimizationContext: OptimizationContext): List[MLine] = {
val log = optimizationContext.log
code match {
case Nil => false -> Nil
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
optimizationContext.niceFunctionProperties,
head._1.labelUseCount(_)
)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, MLine)]) =>
val matchedChunkToOptimize: List[MLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[MLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -74,34 +84,40 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}
class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
val labelMap: Map[String, (Int, Int)],
val labelMap: Map[String, (String, Int)],
val niceFunctionProperties: Set[(NiceFunctionProperty, String)],
val labelUseCount: String => Int) {
@inline
@ -211,10 +227,14 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
case class AssemblyRule(pattern: AssemblyPattern, result: (List[MLine], AssemblyMatchingContext) => List[MLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).sum
}
trait AssemblyPattern {
@ -235,6 +255,8 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
object HelperCheckers {
private def badAddrModes(am: MAddrMode): Boolean = am match {
@ -337,6 +359,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = 0
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -349,6 +373,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = 0
}
@ -358,6 +384,8 @@ case class Where(predicate: AssemblyMatchingContext => Boolean) extends Assembly
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -380,6 +408,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: MLinePattern) extends AssemblyPattern {
@ -405,6 +435,8 @@ case class Many(rule: MLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: MLinePattern, atLeastOneIsThis: MLinePattern) extends AssemblyPattern {
@ -439,6 +471,8 @@ case class ManyWhereAtLeastOne(rule: MLinePattern, atLeastOneIsThis: MLinePatter
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = 1
}
case class Opt(rule: MLinePattern) extends AssemblyPattern {
@ -461,6 +495,8 @@ case class Opt(rule: MLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait MLinePattern extends AssemblyPattern {
@ -488,6 +524,8 @@ trait MLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
//noinspection ScalaUnnecessaryParentheses
@ -509,6 +547,8 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext
val s2s = ctx.get[List[MLine]](ix2)
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1, s2)))) Some(code) else None
}
override def minimumRequiredLines: Int = 0
}
case class MatchA(i: Int) extends MLinePattern {
@ -754,6 +794,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends MLinePattern {
@ -1274,6 +1316,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: MLinePattern, lastLineP
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsNotALabelUsedManyTimes extends MLinePattern {

View File

@ -693,6 +693,8 @@ case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var para
parameter match {
case StructureConstant(_, List(a,b)) => s" $opcode $a,$b"
}
} else if (addrMode == LongRelative && opcode != BSR) { // BSR on Hudson is always 8-bit short, and on 65CE02 it's always 16-bit
s" L$opcode ${AddrMode.addrModeToString(addrMode, parameter.toString)}"
} else if (addrMode == ImmediateWithAbsolute || addrMode == ImmediateWithZeroPage) {
parameter match {
case StructureConstant(_, List(a,b)) => s" $opcode #$a,$b"

View File

@ -180,20 +180,31 @@ object Opcode extends Enumeration {
case "AXA" => AHX
case "AXS" => SBX // could mean SAX
case "BCC" => BCC
case "LBCC" => BCC
case "BCS" => BCS
case "LBCS" => BCS
case "BEQ" => BEQ
case "LBEQ" => BEQ
case "BIT" => BIT
case "BMI" => BMI
case "LBMI" => BMI
case "BNE" => BNE
case "LBNE" => BNE
case "BPL" => BPL
case "LBPL" => BPL
case "BRA" => BRA
case "LBRA" => BRA
case "BRK" => BRK
case "BRL" => BRL
case "BSR" => BSR
case "LBSR" => BSR
case "BVC" => BVC
case "LBVC" => BVC
case "BVS" => BVS
case "LBVS" => BVS
case "CLC" => CLC
case "CLD" => CLD
case "CLE" => CLE
case "CLI" => CLI
case "CLV" => CLV
case "CLX" => CLX
@ -232,6 +243,7 @@ object Opcode extends Enumeration {
case "LSE" => SRE
case "LSR" => LSR
case "LXA" => LXA
case "MAP" => MAP
case "NEG" => NEG
case "NOP" => NOP
case "OAL" => LXA
@ -247,17 +259,19 @@ object Opcode extends Enumeration {
case "PHW" => PHW
case "PHX" => PHX
case "PHY" => PHY
case "PHZ" => PHZ
case "PLA" => PLA
case "PLB" => PLB
case "PLD" => PLD
case "PLP" => PLP
case "PLX" => PLX
case "PLY" => PLY
case "PLZ" => PLZ
case "REP" => REP
case "RLA" => RLA
case "ROL" => ROL
case "ROR" => ROR
case "ROW" => ROR_W // TODO: is this correct?
case "ROW" => ROL_W
case "RRA" => RRA
case "RTI" => RTI
case "RTL" => RTL
@ -268,6 +282,7 @@ object Opcode extends Enumeration {
case "SBX" => SBX
case "SEC" => SEC
case "SED" => SED
case "SEE" => SEE
case "SEI" => SEI
case "SEP" => SEP
case "SET" => SET

View File

@ -995,16 +995,10 @@ object AlwaysGoodOptimizations {
val ConstantFlowAnalysis = new RuleBasedAssemblyOptimization("Constant flow analysis",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Elidable & HasParameterWhere({
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
})) ~~> { (code, ctx) =>
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Not(HasOpcode(BIT)) & Elidable & HasIdentityPageParameter) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Immediate, parameter = NumericConstant(ctx.get[Int](0), 1)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable & HasParameterWhere({
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
})) ~~> { (code, ctx) =>
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable & HasIdentityPageParameter) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Immediate, parameter = NumericConstant(ctx.get[Int](0), 1)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>

View File

@ -1,10 +1,11 @@
package millfork.assembly.mos.opt
import millfork.assembly.mos.{AssemblyLine, AssemblyLine0, OpcodeClasses}
import millfork.assembly.mos.{AddrMode, AssemblyLine, AssemblyLine0, OpcodeClasses}
import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext}
import millfork.env.NormalFunction
import millfork.error.Logger
import scala.annotation.tailrec
import scala.util.control.TailCalls.{TailRec, done, tailcall}
/**
@ -34,20 +35,22 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
override def name = "Changing index registers"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val addressingModesUsingX = Set(AbsoluteX, ZeroPageX, IndexedX)
val addressingModesUsingY = Set(AbsoluteY, ZeroPageY, IndexedY, LongIndexedY, IndexedSY)
val usesX = code.exists(l =>
OpcodeClasses.ReadsXAlways(l.opcode) ||
OpcodeClasses.ReadsYAlways(l.opcode) ||
OpcodeClasses.ChangesX(l.opcode) ||
OpcodeClasses.ChangesY(l.opcode) ||
Set(AbsoluteX, AbsoluteY, ZeroPageY, ZeroPageX, IndexedX, IndexedY, LongIndexedY, IndexedSY)(l.addrMode)
addressingModesUsingX(l.addrMode)
)
val usesY = code.exists(l =>
OpcodeClasses.ReadsXAlways(l.opcode) ||
OpcodeClasses.ReadsYAlways(l.opcode) ||
OpcodeClasses.ChangesX(l.opcode) ||
val usesY = code.exists(l => {
OpcodeClasses.ReadsYAlways(l.opcode) ||
OpcodeClasses.ChangesY(l.opcode) ||
Set(AbsoluteX, AbsoluteY, ZeroPageY, ZeroPageX, IndexedX, IndexedY, LongIndexedY, IndexedSY)(l.addrMode)
addressingModesUsingY(l.addrMode)
}
)
if (!usesX && !usesY) {
return code
@ -101,21 +104,25 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
}
//noinspection OptionEqualsSome
private def canOptimize(code: List[AssemblyLine], dir: IndexDirection, loaded: Option[IndexReg]): Boolean = code match {
@tailrec
private def canOptimize(code: List[AssemblyLine], dir: IndexDirection, loaded: Option[IndexReg]): Boolean = {
val notX = loaded != Some(X)
val notY = loaded != Some(Y)
code match {
case AssemblyLine0(INC | DEC | ASL | ROL | ROR | LSR | STZ | LDZ | BIT, AbsoluteX | ZeroPageX, _) :: xs if dir == X2Y => false
case AssemblyLine0(LDY | STY, AbsoluteX | ZeroPageX, _) :: xs => false
case AssemblyLine0(LDX | STX, AbsoluteY | ZeroPageY, _) :: xs => false
case AssemblyLine0(_, AbsoluteY, _) :: xs if loaded != Some(Y) => false
case AssemblyLine0(_, ZeroPageY, _) :: xs if loaded != Some(Y) => false
case AssemblyLine0(_, IndexedY, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine0(_, LongIndexedY, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine0(_, IndexedSY, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine0(_, AbsoluteX, _) :: xs if loaded != Some(X) => false
case AssemblyLine0(_, LongAbsoluteX, _) :: xs if loaded != Some(X) => false
case AssemblyLine0(_, ZeroPageX, _) :: xs if loaded != Some(X) => false
case AssemblyLine0(_, IndexedX, _) :: xs if dir == X2Y || loaded != Some(X) => false
case AssemblyLine0(_, AbsoluteY, _) :: xs if notY => false
case AssemblyLine0(_, ZeroPageY, _) :: xs if notY => false
case AssemblyLine0(_, IndexedY, _) :: xs if dir == Y2X || notY => false
case AssemblyLine0(_, LongIndexedY, _) :: xs if dir == Y2X || notY => false
case AssemblyLine0(_, IndexedSY, _) :: xs if dir == Y2X || notY => false
case AssemblyLine0(_, AbsoluteX, _) :: xs if notX => false
case AssemblyLine0(_, LongAbsoluteX, _) :: xs if notX => false
case AssemblyLine0(_, ZeroPageX, _) :: xs if notX => false
case AssemblyLine0(_, IndexedX | ImmediateWithAbsoluteX | ImmediateWithZeroPageX, _) :: xs if dir == X2Y || notX => false
case AssemblyLine0(_, AbsoluteIndexedX, _) :: xs if dir == X2Y => false
case AssemblyLine0(SHX | SHY | AHX | TAS | LAS, _, _) :: xs => false
case AssemblyLine(TXY, _, _, e, _) :: xs => (e == Elidability.Elidable || e == Elidability.Volatile) && loaded == Some(X) && canOptimize(xs, dir, Some(Y))
@ -155,11 +162,12 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
(e == Elidability.Elidable || e == Elidability.Volatile || dir == X2Y) && loaded == Some(Y) && canOptimize(xs, dir, Some(Y))
case AssemblyLine0(SAX | TXS | SBX, _, _) :: xs => dir == Y2X && loaded == Some(X) && canOptimize(xs, dir, Some(X))
case AssemblyLine0(TSX, _, _) :: xs => dir == Y2X && loaded != Some(Y) && canOptimize(xs, dir, Some(X))
case AssemblyLine0(TSX, _, _) :: xs => dir == Y2X && notY && canOptimize(xs, dir, Some(X))
case _ :: xs => canOptimize(xs, dir, loaded)
case Nil => true
}
}
private def switchX2Y(code: List[AssemblyLine])(implicit log: Logger): TailRec[List[AssemblyLine]] = code match {

View File

@ -56,7 +56,8 @@ object CoarseFlowAnalyzer {
changed = false
var currentStatus: CpuStatus = functionStartStatus
var staSpWasLast = false
for (i <- codeArray.indices) {
var i = 0
while (i < codeArray.length) {
import millfork.assembly.mos.Opcode._
import millfork.assembly.mos.AddrMode._
import millfork.node.MosNiceFunctionProperty._
@ -184,6 +185,7 @@ object CoarseFlowAnalyzer {
}
}
tFlag = codeArray(i).opcode == SET
i += 1;
}
// flagArray.zip(codeArray).foreach{
// case (fl, y) => if (y.isPrintable) println(f"$fl%-32s $y%-32s")

View File

@ -160,11 +160,6 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
}
def hasSet(state: State.Value): Boolean = state match {
case State.A => false
case State.AH => false
case State.X => false
case State.Y => false
case State.IZ => false
case State.Z => z.contains(true)
case State.N => n.contains(true)
case State.C => c.contains(true)

View File

@ -15,42 +15,42 @@ object DangerousOptimizations {
// TODO: try to guess when overflow can happen
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(CLC)).? ~
(Elidable & HasClear(State.C) & HasOpcode(ADC) & MatchImmediate(0) & DoesntMatterWhatItDoesWith(State.V, State.C)) ~
(Elidable & HasClear(State.C) & HasClear(State.D) & HasOpcode(ADC) & MatchImmediate(0) & DoesntMatterWhatItDoesWith(State.V, State.C)) ~
(
(HasOpcode(TAY) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
(Linear & Not(ConcernsY)).*
).capture(1) ~
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
ctx.get[List[AssemblyLine]](1) :+ last.copy(parameter = last.parameter.+(ctx.get[Constant](0)).quickSimplify)
},
(Elidable & HasOpcode(CLC)).? ~
(Elidable & HasClear(State.C) & HasOpcode(ADC) & MatchImmediate(0) & DoesntMatterWhatItDoesWith(State.V, State.C)) ~
(Elidable & HasClear(State.C) & HasClear(State.D) & HasOpcode(ADC) & MatchImmediate(0) & DoesntMatterWhatItDoesWith(State.V, State.C)) ~
(
(HasOpcode(TAX) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
(Linear & Not(ConcernsX)).*
).capture(1) ~
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
ctx.get[List[AssemblyLine]](1) :+ last.copy(parameter = last.parameter.+(ctx.get[Constant](0)).quickSimplify)
},
(Elidable & HasOpcode(INY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(1).quickSimplify))
},
(Elidable & HasOpcode(DEY) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteY) & DoesntMatterWhatItDoesWith(State.Y) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(-1).quickSimplify))
},
(Elidable & HasOpcode(INX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(1).quickSimplify))
},
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
(Elidable & HasAddrMode(AbsoluteX) & DoesntMatterWhatItDoesWith(State.X) & HasSmallParameter) ~~> { (code, ctx) =>
val last = code.last
List(last.copy(parameter = last.parameter.+(-1).quickSimplify))
},

View File

@ -16,6 +16,8 @@ import scala.collection.{immutable, mutable}
object EmptyMemoryStoreRemoval extends AssemblyOptimization[AssemblyLine] {
override def name = "Removing pointless stores to automatic variables"
override def minimumRequiredLines: Int = 2
private val storeAddrModes = Set(Absolute, ZeroPage, AbsoluteX, AbsoluteY, ZeroPageX, ZeroPageY)
private val directStorageOpcodes = Set(
STA, STX, STY, SAX, STZ, SHX, SHY, AHX,

View File

@ -8,12 +8,16 @@ import millfork.assembly.{AssemblyOptimization, Elidability, OptimizationContext
import millfork.env._
import millfork.error.ConsoleLogger
import scala.collection.mutable
/**
* @author Karol Stasiak
*/
object EmptyParameterStoreRemoval extends AssemblyOptimization[AssemblyLine] {
override def name = "Removing pointless stores to foreign variables"
override def minimumRequiredLines: Int = 2
private val storeInstructions = Set(STA, STX, STY, SAX, STZ, STA_W, STX_W, STY_W, STZ_W)
private val storeAddrModes = Set(Absolute, ZeroPage, AbsoluteX, AbsoluteY, ZeroPageX, ZeroPageY, LongAbsolute, LongAbsoluteX)
@ -23,48 +27,52 @@ object EmptyParameterStoreRemoval extends AssemblyOptimization[AssemblyLine] {
case AssemblyLine0(JSR | BSR | JMP, _, NumericConstant(addr, _)) => Some("$" + addr.toHexString)
case _ => None
}.toSet
val foreignVariables = f.environment.root.things.values.flatMap {
val foreignVariables = mutable.Set[String]()
f.environment.root.things.values.foreach {
case other: NormalFunction if !other.name.endsWith(".trampoline") =>
val address = other.address match {
case Some(NumericConstant(addr, _)) => "$" + addr.toHexString
case _ => ""
}
if (other.name == f.name || usedFunctions(other.name) || usedFunctions(address)) {
Nil
// do nothing
} else {
val params = other.params match {
case NormalParamSignature(ps) => ps.map(_.name)
case _ => Nil
other.params match {
case NormalParamSignature(ps) =>
ps.foreach(p => foreignVariables += p.name)
case _ =>
}
val locals = other.environment.things.values.flatMap{
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Auto => Some(th.name)
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Zeropage => Some(th.name) // TODO: ???
case _ => None
other.environment.things.values.foreach {
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Auto =>
foreignVariables += th.name
case th: MemoryVariable if th.alloc == VariableAllocationMethod.Zeropage =>
foreignVariables += th.name // TODO: ???
case _ =>
}
if (other.returnType.size > Cpu.getMaxSizeReturnableViaRegisters(optimizationContext.options.platform.cpu, optimizationContext.options)) {
other.name + ".return" :: (params ++ locals)
} else {
params ++ locals
foreignVariables += other.name + ".return"
}
}
case _ => Nil
}.toSet
val stillReadOrStoredVariables = code.flatMap {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => Some(th.name)
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => Some(th.name)
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => Some(th.name)
case _ => None
}.toSet
val stillReadVariables = code.flatMap {
case _ =>
}
val stillReadOrStoredVariables = mutable.Set[String]()
code.foreach {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => stillReadOrStoredVariables += th.name
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => stillReadOrStoredVariables += th.name
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => stillReadOrStoredVariables += th.name
case _ =>
}
val stillReadVariables = mutable.Set[String]()
code.foreach {
case AssemblyLine(op, am, MemoryAddressConstant(th), Elidability.Elidable, _)
if storeInstructions(op) && storeAddrModes(am) => Nil
if storeInstructions(op) && storeAddrModes(am) =>
case AssemblyLine(op, am, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(_, _)), Elidability.Elidable, _)
if storeInstructions(op) && storeAddrModes(am) => Nil
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => Some(th.name)
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => Some(th.name)
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => Some(th.name)
case _ => None
}.toSet
if storeInstructions(op) && storeAddrModes(am) =>
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => stillReadVariables += th.name
case AssemblyLine0(_, _, CompoundConstant(_, MemoryAddressConstant(th), _)) => stillReadVariables += th.name
case AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)) => stillReadVariables += th.name
case _ =>
}
val unusedForeignVariables = (foreignVariables & stillReadOrStoredVariables) -- stillReadVariables
if (unusedForeignVariables.isEmpty) {

View File

@ -21,12 +21,16 @@ case class FlowInfo(holder: FlowHolder, index: Int, _labelUseCountMap: () => Opt
lazy val importanceAfter: CpuImportance = holder.importanceAfter(index)
lazy val labelUseCountMap: Option[Map[String, Int]] = _labelUseCountMap()
@inline
def hasClear(state: State.Value): Boolean = statusBefore.hasClear(state)
@inline
def hasSet(state: State.Value): Boolean = statusBefore.hasSet(state)
@inline
def isUnimportant(state: State.Value): Boolean = importanceAfter.isUnimportant(state)
@inline
def labelUseCount(label: String): Int = labelUseCountMap.map(_.getOrElse(label, 0)).getOrElse(-1)
override def toString: String = holder.toString(index)

View File

@ -0,0 +1,50 @@
package millfork.assembly.mos.opt
import millfork.assembly.mos.AddrMode._
import millfork.assembly.mos.{AssemblyLine, State}
import millfork.assembly.mos.Opcode._
import millfork.env.NumericConstant
object IdentityPageOptimizations {
val SimplifiableAccess = new RuleBasedAssemblyOptimization("Simplifiable identity page access",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(LDA) & HasAddrMode(AbsoluteX) & HasIdentityPageParameter) ~~> { c =>
List(AssemblyLine.implied(TXA))
},
(Elidable & HasOpcode(LDA) & HasAddrMode(AbsoluteY) & HasIdentityPageParameter) ~~> { c =>
List(AssemblyLine.implied(TYA))
},
)
val UseInsteadOfStack = new RuleBasedAssemblyOptimization("Use identity page instead of stack",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(PHA)) ~
(Not(ConcernsX) & Not(ConcernsStack)).*.capture(0) ~
(Elidable & HasOpcode(TSX) & HasAddrMode(AbsoluteX) & HasIdentityPageParameter) ~
(Elidable & HasAddrMode(AbsoluteX) & HasParameterWhere{
case NumericConstant(0x101, _) => true
case _ => false
}).capture(1) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS) & DoesntMatterWhatItDoesWith(State.X)) ~~> { (code, ctx) =>
List(AssemblyLine.implied(TAX)) ++ ctx.get[List[AssemblyLine]](0) ++ List(ctx.get[AssemblyLine](1).copy(parameter = ctx.identityPage))
},
(Elidable & HasOpcode(PHA)) ~
(Not(ConcernsY) & Not(ConcernsStack)).*.capture(0) ~
(Elidable & HasOpcode(TSX) & HasAddrMode(AbsoluteX) & HasIdentityPageParameter) ~
(Elidable & HasAddrMode(AbsoluteX) & HasParameterWhere{
case NumericConstant(0x101, _) => true
case _ => false
}).capture(1) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS) & DoesntMatterWhatItDoesWith(State.Y)) ~~> { (code, ctx) =>
List(AssemblyLine.implied(TAY)) ++ ctx.get[List[AssemblyLine]](0) ++ List(ctx.get[AssemblyLine](1).copy(parameter = ctx.identityPage, addrMode = AbsoluteY))
},
)
val All: List[RuleBasedAssemblyOptimization] = List(
SimplifiableAccess,
UseInsteadOfStack)
}

View File

@ -145,8 +145,8 @@ object LaterOptimizations {
//noinspection ZeroIndexToHead
private def InterleavedLoads(load: Opcode.Value, store: Opcode.Value) = {
(Elidable & HasOpcode(load) & MatchAddrMode(0) & MatchParameter(1)).capture(12) ~
(Elidable & HasOpcode(store)).+.capture(10) ~
(Elidable & HasOpcode(load) & MatchAddrMode(2) & MatchParameter(3) & DoesNotConcernMemoryAt(0, 1)).capture(13) ~
(Elidable & HasOpcode(store) & MatchAddrMode(20) & MatchParameter(21)).+.capture(10) ~
(Elidable & HasOpcode(load) & MatchAddrMode(2) & MatchParameter(3) & DoesNotConcernMemoryAt(0, 1) & DoesNotConcernMemoryAt(20, 21)).capture(13) ~
(Elidable & HasOpcode(store) & DoesNotConcernMemoryAt(0, 1) & DoesNotConcernMemoryAt(2, 3)).+.capture(11) ~
(Elidable & HasOpcode(load) & MatchAddrMode(0) & MatchParameter(1)) ~
WhereNoMemoryAccessOverlapBetweenTwoLineLists(10, 11) ~~> { (_, ctx) =>

View File

@ -17,6 +17,8 @@ object LocalVariableReadOptimization extends AssemblyOptimization[AssemblyLine]
override def name: String = "Local variable read optimization"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val stillUsedVariables = code.flatMap {

View File

@ -18,6 +18,8 @@ import scala.util.control.TailCalls.tailcall
case class RepeatedIndexCalculationOptimization(forX: Boolean) extends AssemblyOptimization[AssemblyLine] {
override def name: String = "Repeated index calculation into " + (if (forX) "X" else "Y")
override def minimumRequiredLines: Int = 4
override def optimize(f: NormalFunction, code: List[AssemblyLine], context: OptimizationContext): List[AssemblyLine] = {
val log = context.log
val allRuns = findAllRuns(code, 0, None).result

View File

@ -30,43 +30,54 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[AssemblyLine] {
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, AssemblyLine)], optimizationContext: OptimizationContext): (Boolean, List[AssemblyLine]) = {
final def optimizeImpl(f: NormalFunction, code: List[AssemblyLine], taggedCode: List[(FlowInfo, AssemblyLine)], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val log = optimizationContext.log
code match {
case Nil => false -> Nil
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
optimizationContext.zreg,
optimizationContext.identityPage,
optimizationContext.niceFunctionProperties,
head._1.labelUseCount(_)
)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, AssemblyLine)]) =>
val matchedChunkToOptimize: List[AssemblyLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[AssemblyLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -75,35 +86,42 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}
class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
val labelMap: Map[String, (Int, Int)],
val labelMap: Map[String, (String, Int)],
val zeropageRegister: Option[ThingInMemory],
val identityPage: Constant,
val niceFunctionProperties: Set[(NiceFunctionProperty, String)],
val labelUseCount: String => Int) {
@inline
@ -131,7 +149,17 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
def functionReadsMemory(name: String): Boolean = !niceFunctionProperties(NiceFunctionProperty.DoesntReadMemory -> name)
private val map = new mutable.HashMap[Int, Any]()
private var m_map: mutable.HashMap[Int, Any] = _
@inline
private def map = {
var m = m_map
if (m eq null) {
m = new mutable.HashMap[Int, Any]()
m_map = m
}
m
}
override def toString: String = if (map.isEmpty) "<empty context>" else map.mkString(", ")
@ -235,10 +263,14 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
case class AssemblyRule(pattern: AssemblyPattern, result: (List[AssemblyLine], AssemblyMatchingContext) => List[AssemblyLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).min
}
trait AssemblyPattern {
@ -259,6 +291,8 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
object HelperCheckers {
import AddrMode._
@ -367,6 +401,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -379,6 +415,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
@ -388,6 +426,8 @@ case class Where(predicate: AssemblyMatchingContext => Boolean) extends Assembly
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -410,6 +450,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -435,6 +477,8 @@ case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: AssemblyLinePattern) extends AssemblyPattern {
@ -469,6 +513,8 @@ case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: Asse
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = rule.minimumRequiredLines
}
case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -491,6 +537,8 @@ case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait AssemblyLinePattern extends AssemblyPattern {
@ -518,6 +566,8 @@ trait AssemblyLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
//noinspection ScalaUnnecessaryParentheses
@ -531,6 +581,8 @@ case class Match(predicate: AssemblyMatchingContext => Boolean) extends Assembly
override def toString: String = "Match(...)"
override def hitRate: Double = 0.5 // ?
override def minimumRequiredLines: Int = 0
}
case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) extends AssemblyPattern {
@ -539,6 +591,8 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext
val s2s = ctx.get[List[AssemblyLine]](ix2)
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1, s2)))) Some(code) else None
}
override def minimumRequiredLines: Int = 0
}
case class MatchA(i: Int) extends AssemblyLinePattern {
@ -859,6 +913,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends AssemblyLinePattern {
@ -1459,6 +1515,27 @@ case class HasParameterWhere(predicate: Constant => Boolean) extends TrivialAsse
override def hitRate: Double = 0.332
}
case object HasSmallParameter extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = line.parameter match {
case MemoryAddressConstant(th : VariableInMemory) => th.typ.size < 255
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th : VariableInMemory), NumericConstant(_, 1)) => th.typ.size < 255
case MemoryAddressConstant(th : MfArray) => th.sizeInBytes < 255
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th : MfArray), NumericConstant(_, 1)) => th.sizeInBytes < 255
case _ => false
}
override def hitRate: Double = 0.332
}
case object HasIdentityPageParameter extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = line.parameter match {
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
}
override def hitRate: Double = 0.04
}
case class MatchObject(i: Int, f: Function[AssemblyLine, Any]) extends AssemblyLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
ctx.addObject(i, f(line))
@ -1601,6 +1678,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: AssemblyLinePattern, la
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsZeroPage extends AssemblyLinePattern {

View File

@ -15,6 +15,8 @@ import scala.collection.mutable.ListBuffer
*/
object SingleAssignmentVariableOptimization extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
private val BadOpcodes = Set(RTS, JSR, JMP, RTI)
private val GoodOpcodes = Set(
LDA, LDX, LAX, LDY, EOR, AND, ORA, ADC, SBC, CMP, CPX, CPY, BIT,

View File

@ -14,6 +14,8 @@ import scala.collection.mutable
*/
object SuperOptimizer extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
override def optimize(m: NormalFunction, code: List[AssemblyLine], optimizationContext: OptimizationContext): List[AssemblyLine] = {
val options = optimizationContext.options
val log = optimizationContext.log

View File

@ -17,6 +17,8 @@ import scala.util.control.TailCalls.{TailRec, done, tailcall}
*/
object TwoVariablesToIndexRegistersOptimization extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
override def requiredFlags: Set[CompilationFlag.Value] = Set(CompilationFlag.RegisterVariables)
object CyclesAndBytes {

View File

@ -35,4 +35,6 @@ object UnusedLabelRemoval extends AssemblyOptimization[AssemblyLine] {
}
override def name = "Unused label removal"
override def minimumRequiredLines: Int = 2
}

View File

@ -16,6 +16,8 @@ object UseAccumulatorInsteadOfXRegister extends UseAccumulatorInsteadOfIndexRegi
class UseAccumulatorInsteadOfIndexRegister(doY: Boolean) extends AssemblyOptimization[AssemblyLine] {
override def name: String = if (doY) "Use accumulator instead of the Y register" else "Use accumulator instead of the X register"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[AssemblyLine], context: OptimizationContext): List[AssemblyLine] = {
val log = context.log
if (f.params.length == 1) return code

View File

@ -1,6 +1,6 @@
package millfork.assembly.mos.opt
import millfork.assembly.mos.{AssemblyLine, AssemblyLine0, OpcodeClasses, State}
import millfork.assembly.mos.{AddrMode, AssemblyLine, AssemblyLine0, OpcodeClasses, State}
import millfork.env._
import millfork.error.ConsoleLogger
import millfork.node.NiceFunctionProperty
@ -12,10 +12,10 @@ object VariableLifetime {
// This only works for non-stack variables.
def apply(variableName: String, code: List[AssemblyLine], expandToIncludeIndexing: Boolean = false, expandToIncludeUsesOfLoadedIndices: Option[Set[(NiceFunctionProperty, String)]] = None): Range = {
val flags = code.map(_.parameter match {
val flags = code.map(line => line.parameter match {
case MemoryAddressConstant(MemoryVariable(n, _, _)) if n == variableName => true
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(MemoryVariable(n, _, _)), NumericConstant(_, 1)) if n == variableName => true
case _ => false
case p => line.addrMode == AddrMode.IndexedX && p.refersTo(variableName)
})
if (flags.forall(!_)) return Range(0, 0)
var min = flags.indexOf(true)

View File

@ -9,6 +9,7 @@ import millfork.env._
import millfork.error.Logger
import millfork.node.MosNiceFunctionProperty
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.util.control.TailCalls.{TailRec, done, tailcall}
@ -17,6 +18,8 @@ import scala.util.control.TailCalls.{TailRec, done, tailcall}
*/
object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine] {
override def minimumRequiredLines: Int = 2
override def requiredFlags: Set[CompilationFlag.Value] = Set(CompilationFlag.RegisterVariables)
object CyclesAndBytes {
@ -27,14 +30,14 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case class FeaturesForIndexRegisters(
blastProcessing: Boolean,
izIsAlwaysZero: Boolean,
indexRegisterTransfers: Boolean,
functionsSafeForX: Set[String],
functionsSafeForY: Set[String],
functionsSafeForZ: Set[String],
identityArray: Constant,
log: Logger)
useIdentityPage: Boolean,
izIsAlwaysZero: Boolean,
indexRegisterTransfers: Boolean,
functionsSafeForX: Set[String],
functionsSafeForY: Set[String],
functionsSafeForZ: Set[String],
identityArray: Constant,
log: Logger)
case class FeaturesForAccumulator(
cmos: Boolean,
@ -147,24 +150,26 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
// assembly functions do not get this optimization
return code
}
val stillUsedVariables = code.flatMap {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => Some(th.name)
val stillUsedVariables = mutable.Set[String]()
val variablesWithAddressesTaken = mutable.Set[String]()
code.foreach {
case AssemblyLine0(_, _, MemoryAddressConstant(th)) => stillUsedVariables += th.name
case AssemblyLine0(_, _, SubbyteConstant(MemoryAddressConstant(th), _)) => variablesWithAddressesTaken += th.name
case _ => None
}.toSet
val variablesWithAddressesTaken = code.flatMap {
case AssemblyLine0(_, _, SubbyteConstant(MemoryAddressConstant(th), _)) => Some(th.name)
case _ => None
}.toSet
val localVariables = f.environment.getAllLocalVariables.filter {
case v@MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
}
val localVariables = mutable.Set[Variable]()
val variablesWithRegisterHint = mutable.Set[String]()
f.environment.getAllLocalVariables.foreach {
case v@MemoryVariable(name, typ, alloc@(VariableAllocationMethod.Auto | VariableAllocationMethod.Register)) =>
if (typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile){
localVariables += v
if (alloc == VariableAllocationMethod.Register) {
variablesWithRegisterHint += v.name
}
}
case _ => false
}
val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter {
case v@MemoryVariable(name, typ, VariableAllocationMethod.Register) =>
typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) && !v.isVolatile
case _ => false
}.map(_.name).toSet
val variablesWithLifetimes = localVariables.map(v =>
v.name -> VariableLifetime.apply(v.name, code)
@ -173,11 +178,11 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
val removeVariablesForReal = !options.flag(CompilationFlag.InternalCurrentlyOptimizingForMeasurement)
val costFunction: CyclesAndBytes => Int = if (options.flag(CompilationFlag.OptimizeForSpeed)) _.cycles else _.bytes
val importances = ReverseFlowAnalyzer.analyze(f, code, optimizationContext)
val blastProcessing = options.flag(CompilationFlag.OptimizeForSonicSpeed)
val identityArray = f.environment.maybeGet[ThingInMemory]("identity$").map(MemoryAddressConstant).getOrElse(Constant.Zero)
val useIdentityPage = options.flag(CompilationFlag.IdentityPage)
val identityArray = f.environment.identityPage
val izIsAlwaysZero = !options.flag(CompilationFlag.Emit65CE02Opcodes)
val featuresForIndices = FeaturesForIndexRegisters(
blastProcessing = blastProcessing,
useIdentityPage = useIdentityPage,
izIsAlwaysZero = izIsAlwaysZero,
indexRegisterTransfers = options.flag(CompilationFlag.EmitEmulation65816Opcodes),
functionsSafeForX = optimizationContext.niceFunctionProperties.filter(x => x._1 == MosNiceFunctionProperty.DoesntChangeX).map(_._2),
@ -187,10 +192,12 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
log = log
)
val labelsUsedOnce: Set[String] = code.flatMap {
case AssemblyLine0(op, _, MemoryAddressConstant(Label(l))) if op != Opcode.LABEL => Some(l)
case _ => None
}.groupBy(identity).filter(_._2.size == 1).keySet
val tmpUsedLabelList = mutable.ListBuffer[String]()
code.foreach{
case AssemblyLine0(op, _, MemoryAddressConstant(Label(l))) if op != Opcode.LABEL => tmpUsedLabelList += l
case _ =>
}
val labelsUsedOnce: Set[String] = tmpUsedLabelList.groupBy(identity).filter(_._2.size == 1).keySet
val featuresForAcc = FeaturesForAccumulator(
cmos = options.flag(CompilationFlag.EmitCmosOpcodes),
@ -378,14 +385,26 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
val vx = xCandidate.getOrElse("-")
val vy = yCandidate.getOrElse("-")
val vz = zCandidate.getOrElse("-")
val accessingX = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vx
case _ => false
}
val accessingY = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vy
case _ => false
}
val accessingZ = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vz
case _ => false
}
lines match {
case (AssemblyLine0(_, Immediate, MemoryAddressConstant(th)), _) :: xs
if th.name == vx || th.name == vy || th.name == vz =>
if accessingX || accessingY || accessingZ =>
// if an address of a variable is used, then that variable cannot be assigned to a register
None
case (AssemblyLine0(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _)), _) :: xs
if th.name == vx || th.name == vy || th.name == vz =>
if accessingX || accessingY || accessingZ =>
// if an address of a variable is used, then that variable cannot be assigned to a register
None
@ -405,7 +424,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
Indirect | LongIndirect |
AbsoluteIndexedX, MemoryAddressConstant(th)), _) :: xs =>
// if a variable is used as an array or a pointer, then it cannot be assigned to a register
if (th.name == vx || th.name == vy || th.name == vz) {
if (accessingX || accessingY || accessingZ) {
None
} else {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
@ -417,56 +436,56 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine0(SEP | REP, _, _), _) :: xs => None
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vx && (features.indexRegisterTransfers) =>
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingX && (features.indexRegisterTransfers) =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vy && (features.indexRegisterTransfers) =>
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingY && (features.indexRegisterTransfers) =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if th.name == vx && f.y == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if accessingX && f.y == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if th.name == vy && f.x == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if accessingY && f.x == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(SEC | CLC, _, _), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if th.name == vx && f.y == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteY, _), f) :: xs if accessingX && f.y == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(AssemblyLine0(SEC | CLC, _, _), _) ::
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if th.name == vy && f.x == Unimportant =>
(AssemblyLine0(LDA | STA | ADC | SBC | ORA | EOR | AND | CMP, AbsoluteX, _), f) :: xs if accessingY && f.x == Unimportant =>
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vx => None
case (AssemblyLine0(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingX => None
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if th.name == vy => None
case (AssemblyLine0(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs if accessingY => None
case (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs
if opcodesIdentityTable(op) && features.blastProcessing =>
if (th.name == vx || th.name == vy) {
if opcodesIdentityTable(op) && features.useIdentityPage =>
if (accessingX || accessingY) {
if (elidability == Elidability.Elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 0, cycles = -1))
else None
} else {
if (th.name == vz) None
if (accessingZ) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if th.name == vx && (opcode == LDY || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
if accessingX && (opcode == LDY || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
case (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if th.name == vy && (opcode == LDX || opcode == LAX || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
if accessingY && (opcode == LDX || opcode == LAX || opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
case (AssemblyLine0(opcode, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if th.name == vz && (opcode == LDX || opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
if accessingZ && (opcode == LDX || opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
@ -474,7 +493,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if xCandidate.isDefined =>
// if a register is populated with a different variable, then this variable cannot be assigned to that register
// removing LDX saves 3 cycles
if (elidability == Elidability.Elidable && th.name == vx) {
if (elidability == Elidability.Elidable && accessingX) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
} else {
@ -488,7 +507,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if xCandidate.isDefined =>
// LAX = LDX-LDA, and since LDX simplifies to nothing and LDA simplifies to TXA,
// LAX simplifies to TXA, saving two bytes
if (elidability == Elidability.Elidable && th.name == vx) {
if (elidability == Elidability.Elidable && accessingX) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
} else {
None
@ -498,7 +517,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
// if a register is populated with a different variable, then this variable cannot be assigned to that register
// removing LDX saves 3 bytes
// sometimes that LDX has to be converted into CPX#0
if (elidability == Elidability.Elidable && th.name == vy) {
if (elidability == Elidability.Elidable && accessingY) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
@ -509,7 +528,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), imp) :: xs if zCandidate.isDefined =>
if (elidability == Elidability.Elidable && th.name == vz) {
if (elidability == Elidability.Elidable && accessingZ) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
@ -548,7 +567,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
else None
} else {
if (th.name == vz) None
if (accessingZ) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
@ -556,7 +575,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if xCandidate.isDefined =>
// a variable cannot be inlined if there is TAX not after LDA of that variable
// but LDA-TAX can be simplified to TXA
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && th.name == vx) {
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && accessingX) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
None
@ -566,7 +585,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if yCandidate.isDefined =>
// a variable cannot be inlined if there is TAY not after LDA of that variable
// but LDA-TAY can be simplified to TYA
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && th.name == vy) {
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && accessingY) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
None
@ -576,7 +595,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
if zCandidate.isDefined =>
// a variable cannot be inlined if there is TAZ not after LDA of that variable
// but LDA-TAZ can be simplified to TZA
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && th.name == vy) {
if (elidability == Elidability.Elidable && elidability2 == Elidability.Elidable && accessingY) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 3, cycles = 4))
} else {
None
@ -584,7 +603,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine(LDA | STA, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
if (th.name == vy || th.name == vx || th.name == vz) {
if (accessingY || accessingX || accessingZ) {
if (elidability == Elidability.Elidable) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 2))
} else {
@ -596,7 +615,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine(INC | DEC, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
if (th.name == vy || th.name == vx || th.name == vz) {
if (accessingY || accessingX || accessingZ) {
if (elidability == Elidability.Elidable) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 2, cycles = 4))
} else {
@ -608,7 +627,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidability, _), _) :: xs =>
// changing STZ->LDX saves 1 byte
if (th.name == vy || th.name == vx) {
if (accessingY || accessingX) {
if (elidability == Elidability.Elidable && features.izIsAlwaysZero) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + CyclesAndBytes(bytes = 1, cycles = 2))
} else {
@ -883,37 +902,53 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
val vy = yCandidate.getOrElse("-")
val vz = zCandidate.getOrElse("-")
val va = aCandidate.getOrElse("-")
val accessingX = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vx
case _ => false
}
val accessingY = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vy
case _ => false
}
val accessingZ = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == vz
case _ => false
}
val accessingA = lines match {
case (AssemblyLine0(_, _, MemoryAddressConstant(th)), _) :: _ => th.name == va
case _ => false
}
lines match {
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map( AssemblyLine.implied(INX).pos(s) :: _)
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(INY).pos(s) :: _)
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(INZ).pos(s) :: _)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(DEX).pos(s) :: _)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(DEY).pos(s) :: _)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(DEZ).pos(s) :: _)
case (AssemblyLine(opcode@(DEC | INC | ROL | ROR | ASL | LSR), Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(opcode).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == vx =>
if accessingX =>
if (imp.z == Unimportant && imp.n == Unimportant) {
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs))
} else {
@ -921,22 +956,22 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (l@AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if opcodesIdentityTable(op) && th.name == vx =>
if opcodesIdentityTable(op) && accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l.copy(addrMode = AbsoluteX, parameter = features.identityArray) :: _)
case (l@AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if opcodesIdentityTable(op) && th.name == vy =>
if opcodesIdentityTable(op) && accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l.copy(addrMode = AbsoluteY, parameter = features.identityArray) :: _)
case (l@AssemblyLine0(LDA | TYA | TXA | TZA | CLA, _, _), _) :: xs if aCandidate.isDefined && isReturn(xs) =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l :: _)
case (l@AssemblyLine0(LDA, _, _), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
if opcodesCommutative(op) && th.name == va =>
if opcodesCommutative(op) && accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l.copy(opcode = op) :: _)
case (l@AssemblyLine0(LDA, _, _), _) :: (clc@AssemblyLine0(CLC, _, _), _) :: (AssemblyLine0(op, Absolute | ZeroPage, MemoryAddressConstant(th)), _) :: xs
@ -960,7 +995,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: clc :: l.copy(opcode = op) :: _)
case (AssemblyLine(LDA | STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == va =>
if accessingA =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
@ -968,11 +1003,11 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == vy =>
if accessingY =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
@ -980,7 +1015,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), imp) :: xs
if th.name == vz =>
if accessingZ =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
@ -988,64 +1023,64 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
}
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAX, _, _, Elidability.Elidable, _), _) :: xs
if th.name == vx =>
if accessingX =>
// these TXA's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAY, _, _, Elidability.Elidable, _), _) :: xs
if th.name == vy =>
if accessingY =>
// these TYA's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TAZ, _, _, Elidability.Elidable, _), _) :: xs
if th.name == vz =>
if accessingZ =>
// these TZA's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s), _) :: (AssemblyLine(TXA, _, _, Elidability.Elidable, _), _) :: xs
if th.name == va =>
if accessingA =>
// these TAX's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, _), _) :: (AssemblyLine(TYA, _, _, Elidability.Elidable, _), _) :: xs
if th.name == va =>
if accessingA =>
// these TAY's may get optimized away by a different optimization
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAY) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s1), _) :: (AssemblyLine(CMP, am, param, Elidability.Elidable, s2), _) :: xs
if th.name == vx && CpxyzAddrModes(am) && isNot(vx, param) =>
if accessingX && CpxyzAddrModes(am) && isNot(vx, param) =>
// ditto
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s1) :: AssemblyLine(CPX, am, param).pos(s2) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s1), _) :: (AssemblyLine(CMP, am, param, Elidability.Elidable, s2), _) :: xs
if th.name == vy && CpxyzAddrModes(am) && isNot(vx, param) =>
if accessingY && CpxyzAddrModes(am) && isNot(vx, param) =>
// ditto
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s1) :: AssemblyLine(CPY, am, param).pos(s2) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s1), _) :: (AssemblyLine(CMP, am, param, Elidability.Elidable, s2), _) :: xs
if th.name == vy && CpxyzAddrModes(am) && isNot(vx, param) =>
if accessingY && CpxyzAddrModes(am) && isNot(vx, param) =>
// ditto
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s1) :: AssemblyLine(CPZ, am, param).pos(s2) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx && features.indexRegisterTransfers =>
if accessingX && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXY).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy && features.indexRegisterTransfers =>
if accessingY && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYX).pos(s) :: _)
case (l0@AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteY, _), f):: xs
if th.name == vx =>
if accessingX =>
if (l1.opcode != STA || f.n == Unimportant && f.z == Unimportant) {
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l1.copy(addrMode = AbsoluteX) :: _)
} else if (f.c == Unimportant) {
@ -1057,7 +1092,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (l0@AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteX, _), f):: xs
if th.name == vy =>
if accessingY =>
if (l1.opcode != STA || f.n == Unimportant && f.z == Unimportant) {
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l1.copy(addrMode = AbsoluteY) :: _)
} else if (f.c == Unimportant) {
@ -1069,31 +1104,31 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
case (l0@AssemblyLine0(LDY, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l5@AssemblyLine0(SEC | CLC, _, _), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteY, _), _):: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l5 :: l1.copy(addrMode = AbsoluteX) :: _)
case (l0@AssemblyLine0(LDX, Absolute | ZeroPage, MemoryAddressConstant(th)), _) ::
(l5@AssemblyLine0(SEC | CLC, _, _), _) ::
(l1@AssemblyLine0(LDA | STA | ADC | SBC | AND | ORA | EOR | CMP, AbsoluteX, _), _):: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(l5 :: l1.copy(addrMode = AbsoluteY) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx => features.log.fatal("Unexpected LDY")
if accessingX => features.log.fatal("Unexpected LDY")
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy => features.log.fatal("Unexpected LDX")
if accessingY => features.log.fatal("Unexpected LDX")
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s) :: _)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAY).pos(s) :: _)
case (AssemblyLine(LDA, am, param, Elidability.Elidable, s1), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), Elidability.Elidable, s2), _) :: xs
@ -1112,51 +1147,51 @@ object VariableToRegisterOptimization extends AssemblyOptimization[AssemblyLine]
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine(LDZ, am, param).pos(s1, s2) :: AssemblyLine.implied(TZA).pos(s1, s2) :: _)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAX).pos(s) :: _)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAY).pos(s) :: _)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vz =>
if accessingZ =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TAZ).pos(s) :: _)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXA).pos(s) :: _)
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYA).pos(s) :: _)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy && features.indexRegisterTransfers =>
if accessingY && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TXY).pos(s) :: _)
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx && features.indexRegisterTransfers =>
if accessingX && features.indexRegisterTransfers =>
tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TYX).pos(s) :: _)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy => features.log.fatal("Unexpected STX")
if accessingY => features.log.fatal("Unexpected STX")
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx => features.log.fatal("Unexpected STY")
if accessingX => features.log.fatal("Unexpected STY")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vx =>
if accessingX =>
if (features.izIsAlwaysZero) tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(LDX, 0).pos(s) :: _)
else features.log.fatal("Unexpected STZ")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == vy =>
if accessingY =>
if (features.izIsAlwaysZero) tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(LDY, 0).pos(s) :: _)
else features.log.fatal("Unexpected STZ")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _, s), _) :: xs
if th.name == va =>
if accessingA =>
if (features.izIsAlwaysZero) tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.immediate(LDA, 0).pos(s) :: _)
else tailcall(inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)).map(AssemblyLine.implied(TZA).pos(s) :: _)

View File

@ -1,6 +1,7 @@
package millfork.assembly.opt
import millfork.assembly.mos.opt.SourceOfNZ
import millfork.env.{Constant, NumericConstant}
/**
* @author Karol Stasiak
@ -78,6 +79,16 @@ object Status {
val SingleZero: Status[Int] = SingleStatus(0)
val SingleFF: Status[Int] = SingleStatus(0xff)
def fromByte(c: Constant): Status[Int] = c.loByte match {
case NumericConstant(n, _) => SingleStatus(n.toInt & 0xff)
case _ => AnyStatus
}
def fromWord(c: Constant): Status[Int] = c match {
case NumericConstant(n, _) => SingleStatus(n.toInt & 0xffff)
case _ => AnyStatus
}
@inline
private def wrapBool(b: Boolean): Status[Boolean] = if (b) SingleTrue else SingleFalse
@ -109,6 +120,13 @@ object Status {
case x => x
}
}
implicit class ConstantStatusOps(val inner: Status[Constant]) extends AnyVal {
def toHiLo: (Status[Int], Status[Int]) = inner match {
case SingleStatus(NumericConstant(n, _)) => SingleStatus(n.toInt.>>(8).&(0xff)) -> SingleStatus(n.toInt.&(0xff))
case _ => AnyStatus -> AnyStatus
}
}
implicit class SourceOfNZStatusOps(val inner: Status[SourceOfNZ]) extends AnyVal {
def isFromA: Boolean = inner match {
case SingleStatus(v) => v.a

View File

@ -953,7 +953,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
if (result.contains("???")) s" ??? (${this.toString.stripPrefix(" ")})" else result
}
def readsRegister(r: ZRegister.Value): Boolean = {
def readsRegister(r: ZRegister.Value): Boolean = { // TODO: optimize
import ZOpcode._
import ZRegister._
r match {
@ -1067,6 +1067,17 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LHLX | RLDE => r == D || r == E
case RRHL => r == H || r == L
case MULUB => r == A || (registers match {
case TwoRegisters(p, q) => r == q || r == p
case _ => true
})
case MULUW => r == H || r == L || (registers match {
case TwoRegisters(_, BC) => r == B || r == C
case TwoRegisters(_, DE) => r == D || r == E
case TwoRegisters(_, SP) => r == SP
case _ => true
})
case _ => true // TODO
}
}
@ -1230,6 +1241,9 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LHLX | RRHL | DSUB => r == H || r == L
case SHLX => false
case MULUB => r == H || r == L
case MULUW => r == H || r == L || r == D || r == E
case _ => true // TODO
}
}

View File

@ -29,6 +29,8 @@ object ZOpcode extends Enumeration {
LD_DESP, LD_DEHL, RRHL, RLDE, DSUB, RSTV, LHLX, SHLX,
//sharp:
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, SWAP, LDH_DA, LDH_AD, LDH_CA, LDH_AC, LD_HLSP, ADD_SP, STOP,
// R800:
MULUB, MULUW,
// next:
LDIX, LDWS, LDIRX, LDDX, LDDRX, LDPIRX, OUTINB, MUL, SWAPNIB, MIRROR, NEXTREG, PIXELDN, PIXELAD, SETAE, TEST,
DISCARD_A, DISCARD_F, DISCARD_HL, DISCARD_BC, DISCARD_DE, DISCARD_IX, DISCARD_IY, CHANGED_MEM,
@ -55,6 +57,7 @@ object ZOpcodeClasses {
val CbInstructions: Set[ZOpcode.Value] = Set(SLA, SRA, SRL, SLL, RLC, RRC, RL, RR) ++ BIT ++ RES ++ SET
val EdInstructions: Set[ZOpcode.Value] = Set(NEG, RETN, RETI, IM, RRD, RLD,
MULUB, MULUW,
INI, INIR, OUTI, OUTIR, IND, INDR, OUTD, OUTDR,
LDI, LDIR, LDD, LDDR, CPI, CPIR, CPD, CPDR) ++ BIT ++ RES ++ SET
@ -63,6 +66,7 @@ object ZOpcodeClasses {
val ChangesAFAlways: Set[ZOpcode.Value] = Set( // TODO: !
DAA, ADD, ADC, SUB, SBC, XOR, OR, AND, INC, DEC,
SCF, CCF, NEG, RIM,
MULUB, MULUW,
LDH_AC, LDH_AD, LD_AHLI, LD_AHLD,
ADD_16, ADC_16, SBC_16, INC_16, DEC_16,
INI, INIR, OUTI, OUTIR, IND, INDR, OUTD, OUTDR,
@ -74,6 +78,7 @@ object ZOpcodeClasses {
LDIX, LDIRX, LDDX, LDDRX, LDPIRX,
EXX, CALL, JR, JP, LABEL, DJNZ)
val ChangesHLAlways: Set[ZOpcode.Value] = Set(
MULUB, MULUW,
INI, INIR, OUTI, OUTIR, IND, INDR, OUTD, OUTDR,
LDI, LDIR, LDD, LDDR, CPI, CPIR, CPD, CPDR,
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, LD_HLSP, DSUB,
@ -81,6 +86,7 @@ object ZOpcodeClasses {
LDWS, LDIX, LDIRX, LDDX, LDDRX, LDPIRX, PIXELAD, PIXELDN, OUTINB,
EXX, EX_DE_HL, CALL, JR, JP, LABEL)
val ChangesDEAlways: Set[ZOpcode.Value] = Set(
MULUW,
LDI, LDIR, LDD, LDDR,
LD_DESP, LD_DEHL, RLDE,
LDWS, LDIX, LDIRX, LDDX, LDDRX, LDPIRX, MUL,
@ -88,6 +94,7 @@ object ZOpcodeClasses {
val ChangesOnlyRegister: Set[ZOpcode.Value] = Set(INC, DEC, INC_16, DEC_16, POP, EX_SP, IN_C, IN_IMM, RL, RR, RLC, RRC, SLA, SRA, SRL, SLL) ++ SET ++ RES
val ChangesFirstRegister: Set[ZOpcode.Value] = Set(LD, LD_16, ADD_16, SBC_16)
val ChangesAAlways: Set[ZOpcode.Value] = Set(
MULUB,
DAA, ADD, ADC, SUB, SBC, XOR, OR, AND, LD_AHLI, LD_AHLD, RIM,
MIRROR, SETAE,
)

View File

@ -0,0 +1,24 @@
package millfork.assembly.z80.opt
import millfork.assembly.AssemblyOptimization
import millfork.assembly.z80.ZLine
import millfork.assembly.z80.ZOpcode.MULUB
import millfork.assembly.z80.ZOpcode.MULUW
import millfork.node.ZRegister
/**
* Optimizations valid for R800
* @author Karol Stasiak
*/
object AlwaysGoodR800Optimizations {
val UnusedR800Instructions = new RuleBasedAssemblyOptimization("Simplifiable maths (R800)",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(MULUB) & DoesntMatterWhatItDoesWith(ZRegister.H, ZRegister.L) & DoesntMatterWhatItDoesWithFlags) ~~> (_ => Nil),
(Elidable & HasOpcode(MULUW) & DoesntMatterWhatItDoesWith(ZRegister.H, ZRegister.L, ZRegister.D, ZRegister.E) & DoesntMatterWhatItDoesWithFlags) ~~> (_ => Nil),
)
val All: List[AssemblyOptimization[ZLine]] = List[AssemblyOptimization[ZLine]](
UnusedR800Instructions,
)
}

View File

@ -17,6 +17,8 @@ object ByteVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
override def name = "Allocating variables to single registers"
override def minimumRequiredLines: Int = 3
object CyclesAndBytes {
val Zero = CyclesAndBytes(0, 0)
}

View File

@ -17,6 +17,8 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
import millfork.node.ZRegister._
override def minimumRequiredLines: Int = 3
case class Loaded(b: Boolean = false, c: Boolean = false, d: Boolean = false, e: Boolean = false) {
def load(register: ZRegister.Value): Loaded = register match {
case B => copy(b = true, d = false, e = false)
@ -172,6 +174,11 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
case (x@ZLine0(_, TwoRegisters(r, BC), _)) :: xs =>
x.copy(registers = TwoRegisters(r, DE)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(MEM_BC, r), _)) :: xs =>
x.copy(registers = TwoRegisters(MEM_DE, r)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(r, MEM_BC), _)) :: xs =>
x.copy(registers = TwoRegisters(r, MEM_DE)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(B, r), _)) :: xs =>
x.copy(registers = TwoRegisters(D, r)) :: switchBC2DE(xs)
case (x@ZLine0(_, TwoRegisters(r, B), _)) :: xs =>
@ -220,6 +227,11 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
case (x@ZLine0(_, TwoRegisters(r, DE), _)) :: xs =>
x.copy(registers = TwoRegisters(r, BC)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(MEM_DE, r), _)) :: xs =>
x.copy(registers = TwoRegisters(MEM_BC, r)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(r, MEM_DE), _)) :: xs =>
x.copy(registers = TwoRegisters(r, MEM_BC)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(D, r), _)) :: xs =>
x.copy(registers = TwoRegisters(B, r)) :: switchDE2BC(xs)
case (x@ZLine0(_, TwoRegisters(r, D), _)) :: xs =>

View File

@ -361,6 +361,38 @@ object CoarseFlowAnalyzer {
case ZLine0(RIM, _, _) =>
currentStatus = currentStatus.copy(a = AnyStatus)
case ZLine0(MULUB, TwoRegisters(ZRegister.A, r@(ZRegister.B | ZRegister.C | ZRegister.D | ZRegister.E)), _) =>
val hl = (currentStatus.a<*>currentStatus.getRegister(r, 0)){(a,b) => (a*b)&0xff}
currentStatus = currentStatus.copy(
h = hl.hi,
l = hl.lo,
hl = hl.map(NumericConstant(_, 2)),
cf = AnyStatus,
nf = AnyStatus,
hf = AnyStatus,
zf = AnyStatus,
sf = AnyStatus,
pf = AnyStatus
)
case ZLine0(MULUW, TwoRegisters(ZRegister.HL, ZRegister.BC), _) =>
val hl = (currentStatus.h<*>currentStatus.l)(currentStatus.mergeBytes)
val bc = (currentStatus.b<*>currentStatus.c)(currentStatus.mergeBytes)
val hi = (hl<*>bc){(a,b) => (a*b).>>(16).&(0xffff)}
val lo = (hl<*>bc){(a,b) => (a*b).&(0xffff)}
currentStatus = currentStatus.copy(
d = hi.hi,
e = hi.lo,
h = lo.hi,
l = lo.lo,
hl = lo.map(NumericConstant(_, 2)),
cf = AnyStatus,
nf = AnyStatus,
hf = AnyStatus,
zf = AnyStatus,
sf = AnyStatus,
pf = AnyStatus
)
case ZLine0(opcode, registers, _) =>
currentStatus = currentStatus.copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus)
if (ZOpcodeClasses.ChangesAAlways(opcode)) currentStatus = currentStatus.copy(a = AnyStatus)

View File

@ -12,6 +12,8 @@ import millfork.node.ZRegister
object CompactStackFrame extends AssemblyOptimization[ZLine] {
override def name: String = "Compacting the stack frame"
override def minimumRequiredLines: Int = 9
override def optimize(f: NormalFunction, code: List[ZLine], context: OptimizationContext): List[ZLine] = {
val register =
if (context.options.flag(CompilationFlag.UseIxForStack)) ZRegister.IX

View File

@ -14,6 +14,8 @@ import scala.collection.mutable
object EmptyMemoryStoreRemoval extends AssemblyOptimization[ZLine] {
override def name = "Removing pointless stores to automatic variables"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val vs = VariableStatus(f, code, optimizationContext, _ => true, allowParams = true).getOrElse(return code)
if (vs.localVariables.isEmpty) {

View File

@ -12,6 +12,8 @@ import millfork.env._
object EmptyParameterStoreRemoval extends AssemblyOptimization[ZLine] {
override def name = "Removing pointless stores to foreign variables"
override def minimumRequiredLines: Int = 2
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val usedFunctions = code.flatMap {
case ZLine0(CALL | JP | JR, _, MemoryAddressConstant(th)) => Some(th.name)

View File

@ -30,36 +30,47 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[ZLine]{
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[ZLine], optimizationContext: OptimizationContext): List[ZLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, ZLine)], optimizationContext: OptimizationContext): (Boolean, List[ZLine]) = {
final def optimizeImpl(f: NormalFunction, code:List[ZLine], taggedCode: List[(FlowInfo, ZLine)], optimizationContext: OptimizationContext): List[ZLine] = {
val log = optimizationContext.log
code match {
case Nil => (false, Nil)
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(optimizationContext.options)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, ZLine)]) =>
val matchedChunkToOptimize: List[ZLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[ZLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -68,30 +79,34 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
if (log.traceEnabled) {
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
}
if (log.traceEnabled) {
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}
@ -240,11 +255,11 @@ object HelperCheckers {
case LD | LD_16 | ADD_16 | ADC_16 | SBC_16 => l.registers match {
case TwoRegisters(MEM_HL | MEM_IX_D | MEM_IY_D | MEM_BC | MEM_DE, _) => true
case TwoRegisters(_, MEM_HL | MEM_IX_D | MEM_IY_D | MEM_BC | MEM_DE) => true
case TwoRegisters(_, _) => false
case _ => false
}
case ADD | SUB | SBC | ADC | XOR | CP | OR | AND => l.registers match {
case OneRegister(MEM_HL | MEM_IX_D | MEM_IY_D | MEM_BC | MEM_DE) => true
case OneRegister(_) => false
case _ => false
}
case CHANGED_MEM => true
case POP | PUSH => false
@ -288,10 +303,14 @@ object HelperCheckers {
}
case class AssemblyRule(pattern: AssemblyPattern, result: (List[ZLine], AssemblyMatchingContext) => List[ZLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).min
}
trait AssemblyPattern {
@ -312,6 +331,7 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -324,6 +344,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class CaptureLine(i: Int, pattern: AssemblyLinePattern) extends AssemblyLinePattern {
@ -335,6 +357,8 @@ case class CaptureLine(i: Int, pattern: AssemblyLinePattern) extends AssemblyLin
}
override def hitRate: Double = 0.025
override def minimumRequiredLines: Int = 0
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -347,6 +371,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
@ -356,6 +382,8 @@ case class Where(predicate: (AssemblyMatchingContext => Boolean)) extends Assemb
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -378,6 +406,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -403,6 +433,8 @@ case class Many(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: AssemblyLinePattern) extends AssemblyPattern {
@ -437,6 +469,8 @@ case class ManyWhereAtLeastOne(rule: AssemblyLinePattern, atLeastOneIsThis: Asse
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = rule.minimumRequiredLines
}
case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
@ -459,6 +493,8 @@ case class Opt(rule: AssemblyLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait AssemblyLinePattern extends AssemblyPattern {
@ -488,6 +524,8 @@ trait AssemblyLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
trait TrivialAssemblyLinePattern extends AssemblyLinePattern with (ZLine => Boolean) {
@ -926,6 +964,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends AssemblyLinePattern {
@ -1337,6 +1377,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: AssemblyLinePattern, la
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsNotALabelUsedManyTimes extends AssemblyLinePattern {

View File

@ -19,6 +19,8 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
override def name = "Allocating variables to register pairs"
override def minimumRequiredLines: Int = 3
object CyclesAndBytes {
val Zero = CyclesAndBytes(0, 0)
}
@ -565,7 +567,7 @@ object WordVariableToRegisterOptimization extends AssemblyOptimization[ZLine] {
} else if (de != "") {
tailcall(inlineVars(hl, bc, de, xs)).map(ZLine.register(PUSH, DE).pos(s) :: x :: ZLine.register(POP, DE).pos(s) :: _)
} else {
throw new IllegalStateException()
tailcall(inlineVars(hl, bc, de, xs)).map(x :: _)
}

View File

@ -22,6 +22,21 @@ object Z80OptimizationPresets {
).flatten
}
val GoodForR800: List[AssemblyOptimization[ZLine]] = {
List.fill(5)(
List.fill(5)(
AlwaysGoodI80Optimizations.All ++
AlwaysGoodZ80Optimizations.All ++
AlwaysGoodR800Optimizations.All ++
List(
EmptyParameterStoreRemoval,
EmptyMemoryStoreRemoval)
).flatten ++
List(ChangeRegisterPairPreferringDE, WordVariableToRegisterOptimization, ByteVariableToRegisterOptimization, ChangeRegisterPairPreferringBC, CompactStackFrame) ++
LaterIntel8080Optimizations.All ++ LaterI80Optimizations.All
).flatten
}
val GoodForIntel8080: List[AssemblyOptimization[ZLine]] = {
List.fill(5)(
List.fill(5)(

View File

@ -7,6 +7,8 @@ import millfork.error.{ConsoleLogger, Logger}
import millfork.assembly.AbstractCode
import millfork.output.NoAlignment
import scala.collection.mutable.ListBuffer
/**
* @author Karol Stasiak
*/
@ -14,6 +16,33 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
def getExpressionType(ctx: CompilationContext, expr: Expression): Type = AbstractExpressionCompiler.getExpressionType(ctx, expr)
def extractWordExpandedBytes(ctx: CompilationContext, params:List[Expression]): Option[List[Expression]] = {
val result = ListBuffer[Expression]()
for(param <- params) {
if (ctx.env.eval(param).isDefined) return None
AbstractExpressionCompiler.getExpressionType(ctx, param) match {
case t: PlainType if t.size == 1 && !t.isSigned =>
result += param
case t: PlainType if t.size == 2 =>
param match {
case FunctionCallExpression(functionName, List(inner)) =>
AbstractExpressionCompiler.getExpressionType(ctx, inner) match {
case t: PlainType if t.size == 1 && !t.isSigned =>
ctx.env.maybeGet[Type](functionName) match {
case Some(tw: PlainType) if tw.size == 2 =>
result += inner
case _ => return None
}
case _ => return None
}
case _ => return None
}
case _ => return None
}
}
Some(result.toList)
}
def assertAllArithmetic(ctx: CompilationContext,expressions: List[Expression], booleanHint: String = ""): Unit = {
for(e <- expressions) {
val typ = getExpressionType(ctx, e)
@ -227,6 +256,10 @@ object AbstractExpressionCompiler {
def getExpressionTypeLoosely(ctx: CompilationContext, expr: Expression): Type = {
getExpressionTypeImpl(ctx.env, ctx.log, expr, loosely = true)
}
@inline
def getExpressionTypeForMacro(ctx: CompilationContext, expr: Expression): Type = {
getExpressionTypeImpl(ctx.env, ctx.log, expr, loosely = false, failWithVoid = true)
}
@inline
def getExpressionType(env: Environment, log: Logger, expr: Expression): Type = getExpressionTypeImpl(env, log, expr, loosely = false)
@ -234,7 +267,7 @@ object AbstractExpressionCompiler {
@inline
def getExpressionTypeLoosely(env: Environment, log: Logger, expr: Expression): Type = getExpressionTypeImpl(env, log, expr, loosely = true)
def getExpressionTypeImpl(env: Environment, log: Logger, expr: Expression, loosely: Boolean): Type = {
def getExpressionTypeImpl(env: Environment, log: Logger, expr: Expression, loosely: Boolean, failWithVoid: Boolean = false): Type = {
if (expr.typeCache ne null) return expr.typeCache
val b = env.get[Type]("byte")
val bool = env.get[Type]("bool$")
@ -322,6 +355,11 @@ object AbstractExpressionCompiler {
b
}
}
} else if (failWithVoid) {
env.maybeGet[TypedThing](name) match {
case Some(t) => t.typ
case None => VoidType
}
} else {
env.get[TypedThing](name, expr.position).typ
}
@ -332,7 +370,7 @@ object AbstractExpressionCompiler {
env.getPointy(name).elementType
case DerefDebuggingExpression(_, 1) => b
case DerefDebuggingExpression(_, 2) => w
case DerefExpression(_, _, typ) => typ
case DerefExpression(_, _, _, typ) => typ
case IndirectFieldExpression(inner, firstIndices, fieldPath) =>
var currentType = inner match {
case VariableExpression(arrName) =>
@ -378,12 +416,12 @@ object AbstractExpressionCompiler {
log.error(s"Type `$targetType` doesn't have field named `$actualFieldName`", expr.position)
ok = false
} else {
if (tuples.head.arrayIndexTypeAndSize.isDefined) ??? // TODO
val subv = tuples.head
pointerWrap match {
case 0 =>
currentType = tuples.head.typ
currentType = if (subv.arrayIndexTypeAndSize.isDefined) env.get[Type]("pointer." + subv.typ) else subv.typ
case 1 =>
currentType = env.get[Type]("pointer." + tuples.head.typ)
currentType = env.get[Type]("pointer." + subv.typ)
case 2 =>
currentType = env.get[Type]("pointer")
case 10 | 11 =>

View File

@ -150,6 +150,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
case _ =>
}
new OverflowDetector(ctx).detectOverflow(stmt)
stmt match {
case Assignment(ve@VariableExpression(v), arg) if trackableVars(v) =>
cv = search(arg, cv)
@ -175,7 +176,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
Assignment(DerefExpression(
FunctionCallExpression("pointer", List(VariableExpression(target.name).pos(pos))).pos(pos) #+#
FunctionCallExpression("<<", List(optimizeExpr(target.index, cv), LiteralExpression(1, 1))).pos(pos),
0, env.getPointy(target.name).elementType).pos(pos), optimizeExpr(arg, cv)).pos(pos) -> cv
0, false, env.getPointy(target.name).elementType).pos(pos), optimizeExpr(arg, cv)).pos(pos) -> cv
case Assignment(target:IndexedExpression, arg) =>
cv = search(arg, cv)
cv = search(target, cv)
@ -347,7 +348,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
case HalfWordExpression(arg, _) => search(arg, cv)
case IndexedExpression(_, arg) => search(arg, cv)
case DerefDebuggingExpression(arg, _) => search(arg, cv)
case DerefExpression(arg, _, _) => search(arg, cv)
case DerefExpression(arg, _, _, _) => search(arg, cv)
case _ => cv // TODO
}
}
@ -435,7 +436,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
false
}
if (zero) {
DerefExpression(result, 0, targetType)
DerefExpression(result, 0, false, targetType)
} else {
val indexType = AbstractExpressionCompiler.getExpressionType(env, env.log, index)
val (newResult, constantOffset) = result match {
@ -449,11 +450,11 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
if (constantOffset + (targetType.alignedSize * n) <= 127) {
DerefExpression(
("pointer." + targetType.name) <| newResult,
constantOffset + targetType.alignedSize * n.toInt, targetType)
constantOffset + targetType.alignedSize * n.toInt, false, targetType)
} else {
DerefExpression(
("pointer." + targetType.name) <| result,
targetType.alignedSize * n.toInt, targetType)
targetType.alignedSize * n.toInt, false, targetType)
}
case _ =>
val small = smallArraySizeInBytes.isDefined || (indexType.size == 1 && !indexType.isSigned)
@ -468,12 +469,12 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
}
DerefExpression(("pointer." + targetType.name) <| (
("pointer" <| newResult) #+# scaledIndexWithOffset
), 0, targetType)
), 0, false, targetType)
case _ =>
val scaledIndex: Expression = scaleIndexForArrayAccess(index, targetType, smallArraySizeInBytes)
DerefExpression(("pointer." + targetType.name) <| (
("pointer" <| result) #+# optimizeExpr(scaledIndex, Map())
), 0, targetType)
), 0, false, targetType)
}
}
}
@ -492,18 +493,18 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
case Some(NumericConstant(n, _)) if n >= 0 && (targetType.alignedSize * n) <= 127 =>
x match {
case _: PointerType =>
DerefExpression(result, targetType.alignedSize * n.toInt, targetType)
DerefExpression(result, targetType.alignedSize * n.toInt, false, targetType)
case _ =>
DerefExpression(
("pointer." + targetType.name) <| result,
targetType.alignedSize * n.toInt, targetType)
targetType.alignedSize * n.toInt, false, targetType)
}
case _ =>
val scaledIndex: Expression = scaleIndexForArrayAccess(index, targetType, arraySizeInBytes)
// TODO: re-cast pointer type
DerefExpression(("pointer." + targetType.name) <| (
result #+# optimizeExpr(scaledIndex, Map())
), 0, targetType)
), 0, false, targetType)
}
case _ =>
ctx.log.error("Not a pointer type on the left-hand side of `[`", pos)
@ -519,9 +520,9 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
for ((dot, fieldName, indices) <- fieldPath) {
if (dot && ok) {
val pointer = result match {
case DerefExpression(inner, 0, _) =>
case DerefExpression(inner, 0, _, _) =>
optimizeExpr(inner, currentVarValues).pos(pos)
case DerefExpression(inner, offset, targetType) =>
case DerefExpression(inner, offset, _, targetType) =>
if (offset == 0) {
("pointer." + targetType.name) <| ("pointer" <| optimizeExpr(inner, currentVarValues).pos(pos))
} else {
@ -597,7 +598,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
currentArraySizeInBytes = None
pointerWrap match {
case 0 =>
DerefExpression(inner, fieldOffset, fieldType)
DerefExpression(inner, fieldOffset, subvariable.isVolatile, fieldType)
case 1 =>
if (fieldOffset == 0) {
("pointer." + fieldType.name) <| ("pointer" <| inner)
@ -648,9 +649,9 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
}
result
case DerefDebuggingExpression(inner, 1) =>
DerefExpression(optimizeExpr(inner, currentVarValues, optimizeSum = true), 0, env.get[VariableType]("byte")).pos(pos)
DerefExpression(optimizeExpr(inner, currentVarValues, optimizeSum = true), 0, false, env.get[VariableType]("byte")).pos(pos)
case DerefDebuggingExpression(inner, 2) =>
DerefExpression(optimizeExpr(inner, currentVarValues, optimizeSum = true), 0, env.get[VariableType]("word")).pos(pos)
DerefExpression(optimizeExpr(inner, currentVarValues, optimizeSum = true), 0, false, env.get[VariableType]("word")).pos(pos)
case e@TextLiteralExpression(characters) =>
val name = ctx.env.getTextLiteralArrayName(e)
VariableExpression(name).pos(pos)
@ -731,11 +732,11 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
if (pointy.isArray) {
DerefExpression(
"pointer" <| VariableExpression(name).pos(pos),
o.toInt, pointy.elementType).pos(pos)
o.toInt, false, pointy.elementType).pos(pos)
} else {
DerefExpression(
VariableExpression(name).pos(pos),
o.toInt, pointy.elementType).pos(pos)
o.toInt, false, pointy.elementType).pos(pos)
}
case _ =>
val arraySizeInBytes = pointy match {
@ -769,7 +770,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
}
DerefExpression(
("pointer" <| VariableExpression(name).pos(pos)) #+# optimizeExpr(scaledIndex, Map()),
0, pointy.elementType).pos(pos)
0, false, pointy.elementType).pos(pos)
}
}
case _ => expr // TODO

View File

@ -4,9 +4,12 @@ import millfork.assembly.AbstractCode
import millfork.assembly.m6809.MOpcode
import millfork.assembly.mos._
import millfork.assembly.z80.ZOpcode
import millfork.env
import millfork.env._
import millfork.node._
import scala.collection.mutable
/**
* @author Karol Stasiak
*/
@ -104,6 +107,7 @@ abstract class MacroExpander[T <: AbstractCode] {
def inlineFunction(ctx: CompilationContext, i: MacroFunction, actualParams: List[Expression], position: Option[Position]): (List[T], List[ExecutableStatement]) = {
var paramPreparation = List[T]()
var actualCode = i.code
var actualConstants = i.constants
i.params match {
case AssemblyOrMacroParamSignature(params) =>
params.foreach{ param =>
@ -133,6 +137,7 @@ abstract class MacroExpander[T <: AbstractCode] {
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))
actualConstants = actualConstants.map(_.replaceVariableInInitialValue(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))
@ -142,6 +147,21 @@ abstract class MacroExpander[T <: AbstractCode] {
}
}
var flattenedConstants = mutable.MutableList[VariableDeclarationStatement]()
while(actualConstants.nonEmpty) {
val constant = actualConstants.head
flattenedConstants += constant
actualConstants = actualConstants.tail.map(_.replaceVariableInInitialValue(constant.name.stripPrefix(i.environment.prefix), constant.initialValue.get))
}
for (constant <- flattenedConstants) {
val valueExpr = constant.initialValue.get
ctx.env.eval(valueExpr) match {
case Some(c) =>
actualCode = actualCode.map(stmt => replaceVariableX(stmt, constant.name.stripPrefix(i.environment.prefix), valueExpr))
case None =>
ctx.log.error("Not a constant", constant.position)
}
}
// fix local labels:
// TODO: do it even if the labels are in an inline assembly block inside a Millfork function
val localLabels = actualCode.flatMap {

View File

@ -2,11 +2,12 @@ package millfork.compiler.m6809
import java.util.concurrent.AbstractExecutorService
import millfork.CompilationFlag
import millfork.assembly.Elidability
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, Indexed, InherentB, MLine, MLine0, MOpcode, RegisterSet, TwoRegisters}
import millfork.compiler.{AbstractExpressionCompiler, BranchIfFalse, BranchIfTrue, BranchSpec, ComparisonType, CompilationContext, NoBranching}
import millfork.node.{DerefExpression, Expression, FunctionCallExpression, GeneratedConstantExpression, IndexedExpression, LhsExpression, LiteralExpression, M6809Register, SeparateBytesExpression, SumExpression, VariableExpression}
import millfork.assembly.m6809.MOpcode._
import millfork.env.{AssemblyOrMacroParamSignature, BuiltInBooleanType, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FlagBooleanType, FunctionInMemory, FunctionPointerType, KernalInterruptPointerType, Label, M6809RegisterVariable, MacroFunction, MathOperator, MemoryAddressConstant, MemoryVariable, NonFatalCompilationException, NormalFunction, NormalParamSignature, NumericConstant, StackOffsetThing, StackVariable, StackVariablePointy, StructureConstant, Thing, ThingInMemory, Type, Variable, VariableInMemory, VariableLikeThing, VariablePointy, VariableType}
import millfork.env.{AssemblyOrMacroParamSignature, BuiltInBooleanType, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FlagBooleanType, FunctionInMemory, FunctionPointerType, KernalInterruptPointerType, Label, M6809RegisterVariable, MacroFunction, MathOperator, MemoryAddressConstant, MemoryVariable, NonFatalCompilationException, NormalFunction, NormalParamSignature, NumericConstant, PlainType, StackOffsetThing, StackVariable, StackVariablePointy, StructureConstant, Thing, ThingInMemory, Type, Variable, VariableInMemory, VariableLikeThing, VariablePointy, VariableType}
import scala.collection.GenTraversableOnce
@ -79,10 +80,22 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
}
env.eval(expr) match {
case Some(c) =>
return target match {
return (target match {
case MExpressionTarget.NOTHING => Nil
case _ => List(MLine.immediate(toLd(target), c))
}
}) ++ (branches match {
case BranchIfTrue(l) if c.isProvablyNonZero => List(MLine(JMP, Absolute(false), Label(l).toAddress))
case BranchIfTrue(l) =>
// TODO: ??
if (target == MExpressionTarget.NOTHING) List(MLine.immediate(LDB, c), MLine.longBranch(BNE, l))
else List(MLine.longBranch(BNE, l))
case BranchIfFalse(l) if c.isProvablyZero => List(MLine(JMP, Absolute(false), Label(l).toAddress))
case BranchIfFalse(l) =>
// TODO: ??
if (target == MExpressionTarget.NOTHING) List(MLine.immediate(LDB, c), MLine.longBranch(BEQ, l))
else List(MLine.longBranch(BEQ, l))
case NoBranching => Nil
})
case None =>
}
expr match {
@ -117,17 +130,18 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
case MExpressionTarget.NOTHING => Nil
case _ => List(MLine.immediate(MExpressionTarget.toLd(target), NumericConstant(c, MExpressionTarget.size(target))))
}
case DerefExpression(inner, offset, _) =>
case DerefExpression(inner, offset, vol, _) =>
val (i, o) = if (offset == 0) {
extractConstantOffset(ctx, inner)
} else (inner, offset)
val el = if (vol) Elidability.Volatile else Elidability.Elidable
compileToX(ctx, i) match {
case List(l@MLine0(LDX, Immediate, _)) =>
List(l.copy(opcode = toLd(target), addrMode = Absolute(false), parameter = l.parameter + o))
List(l.copy(opcode = toLd(target), addrMode = Absolute(false), parameter = l.parameter + o, elidability = el))
case List(l@MLine0(LDX, addrMode, _)) if addrMode.isDeferenceable && o == 0 =>
List(l.copy(opcode = toLd(target), addrMode = addrMode.dereference()))
List(l.copy(opcode = toLd(target), addrMode = addrMode.dereference(), elidability = el))
case other =>
other :+ MLine(toLd(target), Indexed(M6809Register.X, indirect = false), NumericConstant(o, 2))
other :+ MLine(toLd(target), Indexed(M6809Register.X, indirect = false), NumericConstant(o, 2), elidability = el)
}
case IndexedExpression(name, index) =>
env.getPointy(name) match {
@ -290,7 +304,13 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
assertSizesForMultiplication(ctx, params, inPlace = false)
getArithmeticParamMaxSize(ctx, params) match {
case 1 => M6809MulDiv.compileByteMultiplication(ctx, params, updateDerefX = false) ++ targetifyB(ctx, target, isSigned = false)
case 2 => M6809MulDiv.compileWordMultiplication(ctx, params, updateDerefX = false) ++ targetifyD(ctx, target)
case 2 =>
extractWordExpandedBytes(ctx, params) match {
case Some(byteParams) if byteParams.size == 2 =>
M6809MulDiv.compileByteMultiplication(ctx, byteParams, updateDerefX = false) ++ targetifyD(ctx, target)
case _ =>
M6809MulDiv.compileWordMultiplication(ctx, params, updateDerefX = false) ++ targetifyD(ctx, target)
}
case 0 => Nil
case _ =>
ctx.log.error("Multiplication of variables larger than 2 bytes is not supported", expr.position)
@ -680,7 +700,20 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
case FatBooleanType => compileToB(ctx, expr)
case t: ConstantBooleanType =>
List(MLine.immediate(MOpcode.LDB, if (t.value) 1 else 0))
case BuiltInBooleanType | _: FlagBooleanType =>
case t: FlagBooleanType =>
val condition = compile(ctx, expr, MExpressionTarget.NOTHING, NoBranching)
condition ++ (t.jumpIfFalse.m6809 match {
case BCC => List(MLine.immediate(LDB, 0), MLine.inherentB(ROL))
case BCS => List(MLine.immediate(LDB, 0), MLine.inherentB(ROL), MLine.immediate(EORB, 1))
case o =>
val label = ctx.env.nextLabel("bo")
List(
MLine.immediate(LDB, 0),
MLine.shortBranch(o, label),
MLine.inherentB(INC),
MLine.label(label))
})
case BuiltInBooleanType =>
val label = ctx.env.nextLabel("bo")
val condition = compile(ctx, expr, MExpressionTarget.NOTHING, BranchIfFalse(label))
val conditionWithoutJump = condition.init
@ -782,8 +815,8 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
case VariableExpression(name) =>
val variable = ctx.env.get[Variable](name)
List(MLine.variable(ctx, store, variable))
case DerefExpression(inner, offset, _) =>
stashIfNeeded(ctx, compileToX(ctx, inner)) :+ MLine.indexedX(store, NumericConstant(offset, 2))
case DerefExpression(inner, offset, vol, _) =>
stashIfNeeded(ctx, compileToX(ctx, inner)) :+ MLine.indexedX(store, NumericConstant(offset, 2)).copy(elidability = if(vol) Elidability.Volatile else Elidability.Elidable)
case IndexedExpression(name, index) =>
ctx.env.getPointy(name) match {
case p: ConstantPointy =>
@ -837,14 +870,15 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
val sh = storeA(ctx, hi)
val sl = storeB(ctx, lo)
stashBIfNeeded(ctx, sh) ++ sl // TODO: optimize
case DerefExpression(inner, offset, _) =>
case DerefExpression(inner, offset, vol, _) =>
val el = if(vol) Elidability.Volatile else Elidability.Elidable
compileToX(ctx, inner) match {
case List(MLine0(LDX, Immediate, constAddr)) =>
List(MLine(STD, Absolute(false), constAddr + offset))
List(MLine(STD, Absolute(false), constAddr + offset, elidability = el))
case List(MLine0(LDX, Absolute(false), constAddr)) if offset == 0 =>
List(MLine(STD, Absolute(true), constAddr))
List(MLine(STD, Absolute(true), constAddr, elidability = el))
case xs =>
stashDIfNeeded(ctx, xs) :+ MLine(STD, Indexed(M6809Register.X, indirect = false), NumericConstant(offset, 2))
stashDIfNeeded(ctx, xs) :+ MLine(STD, Indexed(M6809Register.X, indirect = false), NumericConstant(offset, 2), elidability = el)
}
}
}
@ -893,8 +927,8 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
List(MLine.variable(ctx, LEAX, variable))
}
case DerefExpression(inner, offset, _) =>
compileToX(ctx, inner) :+ MLine.indexedX(MOpcode.LEAX, Constant(offset))
case DerefExpression(inner, offset, vol, _) =>
compileToX(ctx, inner) :+ MLine.indexedX(MOpcode.LEAX, Constant(offset)).copy(elidability = if(vol) Elidability.Volatile else Elidability.Elidable)
case IndexedExpression(aname, index) =>
ctx.env.getPointy(aname) match {
case p: VariablePointy => compileToD(ctx, index #*# p.elementType.alignedSize) ++ List(MLine.absolute(ADDD, p.addr), MLine.tfr(M6809Register.D, M6809Register.X))
@ -1046,9 +1080,10 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
List(calculateStackAddressToD(ctx, sot.offset), List(MLine.tfr(M6809Register.A, M6809Register.B)))
}
case e:DerefExpression =>
val el = if(e.isVolatile) Elidability.Volatile else Elidability.Elidable
List.tabulate(targetSize)(i =>
if (i == 0) compileAddressToX(ctx, e) :+ MLine.indexedX(LDB, 0)
else List(MLine.indexedX(LDB, i))
if (i == 0) compileAddressToX(ctx, e) :+ MLine.indexedX(LDB, 0).copy(elidability = el)
else List(MLine.indexedX(LDB, i).copy(elidability = el))
)
case e:FunctionCallExpression =>
ctx.env.maybeGet[NormalFunction](e.functionName) match {

View File

@ -15,9 +15,9 @@ import scala.collection.mutable.ListBuffer
*/
object M6809MulDiv {
def compileByteMultiplication(ctx: CompilationContext, params: List[Expression], updateDerefX: Boolean): List[MLine] = {
def compileByteMultiplication(ctx: CompilationContext, params: List[Expression], updateDerefX: Boolean, forceMul: Boolean = false): List[MLine] = {
var constant = Constant.One
val variablePart = params.flatMap { p =>
val variablePart = if(forceMul) params else params.flatMap { p =>
ctx.env.eval(p) match {
case Some(c) =>
constant = CompoundConstant(MathOperator.Times, constant, c).quickSimplify

View File

@ -74,11 +74,17 @@ object BuiltIns {
case FunctionCallExpression(name, List(param)) if env.maybeGet[Type](name).isDefined =>
return simpleOperation(opcode, ctx, param, indexChoice, preserveA, commutative, decimal)
case _: FunctionCallExpression | _: SumExpression if commutative =>
val code = MosExpressionCompiler.compileToA(ctx, source)
if (ctx.options.flags(CompilationFlag.IdentityPage)
&& !code.exists(_.concernsX)
&& AssemblyLine.treatment(code, State.X) == Treatment.Unchanged) {
return List(AssemblyLine.implied(TAX)) ++ code ++ wrapInSedCldIfNeeded(decimal, List(AssemblyLine.absoluteX(opcode, env.identityPage)))
}
// TODO: is it ok?
if (ctx.options.zpRegisterSize >= 1) {
val reg = ctx.env.get[ThingInMemory]("__reg")
return List(AssemblyLine.implied(PHA)) ++
MosExpressionCompiler.compileToA(ctx, source) ++
MosExpressionCompiler.fixTsx(code) ++
List(AssemblyLine.zeropage(STA, reg), AssemblyLine.implied(PLA)) ++
wrapInSedCldIfNeeded(decimal, List(AssemblyLine.zeropage(opcode, reg)))
} else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
@ -336,6 +342,7 @@ object BuiltIns {
val reg = ctx.env.get[VariableInMemory]("__reg")
lhs match {
case dx: DerefExpression =>
val el = if(dx.isVolatile) Elidability.Volatile else Elidability.Elidable
if (ctx.options.zpRegisterSize < 4) {
ctx.log.error("Unsupported shift operation. Consider increasing the size of the zeropage register or simplifying the left hand side expression.", lhs.position)
return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, rhs)
@ -348,19 +355,19 @@ object BuiltIns {
val loadToR2 =
List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.zeropage(STA, reg, 3))
val storeFromR2 =
List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.zeropage(LDA, reg, 2),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.zeropage(LDA, reg, 3),
AssemblyLine.indexedY(STA, ptr))
AssemblyLine.indexedY(STA, ptr).copy(elidability = el))
val shiftR2 =
if (aslRatherThanLsr) List(AssemblyLine.zeropage(ASL, reg, 2), AssemblyLine.zeropage(ROL, reg, 3))
else List(AssemblyLine.zeropage(LSR, reg, 3), AssemblyLine.zeropage(ROR, reg, 2))
@ -369,23 +376,23 @@ object BuiltIns {
case Some(1) if aslRatherThanLsr =>
List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.implied(ASL),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.implied(ROL),
AssemblyLine.indexedY(STA, ptr))
AssemblyLine.indexedY(STA, ptr).copy(elidability = el))
case Some(1) if !aslRatherThanLsr =>
List(
AssemblyLine.immediate(LDY, offset + 1),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.implied(LSR),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(DEY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.implied(ROR),
AssemblyLine.indexedY(STA, ptr))
AssemblyLine.indexedY(STA, ptr).copy(elidability = el))
case Some(n) if n >= 1 && n <= 7 => // TODO: pick optimal
loadToR2 ++ List.fill(n)(shiftR2).flatten ++ storeFromR2
case _ =>
@ -412,9 +419,9 @@ object BuiltIns {
AssemblyLine.implied(CLC),
AssemblyLine.immediate(LDY, 0),
AssemblyLine.label(innerLoopLabel),
AssemblyLine.indexedY(LDA, reg),
AssemblyLine.indexedY(LDA, reg).copy(elidability = el),
AssemblyLine.implied(ROL),
AssemblyLine.indexedY(STA, reg),
AssemblyLine.indexedY(STA, reg).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.immediate(CPY, size),
AssemblyLine.relative(BNE, innerLoopLabel))
@ -423,9 +430,9 @@ object BuiltIns {
AssemblyLine.implied(CLC),
AssemblyLine.immediate(LDY, size - 1),
AssemblyLine.label(innerLoopLabel),
AssemblyLine.indexedY(LDA, reg),
AssemblyLine.indexedY(LDA, reg).copy(elidability = el),
AssemblyLine.implied(ROR),
AssemblyLine.indexedY(STA, reg),
AssemblyLine.indexedY(STA, reg).copy(elidability = el),
AssemblyLine.implied(DEY),
AssemblyLine.relative(BPL, innerLoopLabel))
}
@ -436,7 +443,7 @@ object BuiltIns {
AssemblyLine.immediate(LDY, size - 1),
AssemblyLine.label(innerLoopLabel),
AssemblyLine.immediate(LDA, 0),
AssemblyLine.indexedY(STA, reg),
AssemblyLine.indexedY(STA, reg).copy(elidability = el),
AssemblyLine.implied(DEY),
AssemblyLine.relative(BPL, innerLoopLabel))
case Some(1) => singleShift
@ -1195,6 +1202,7 @@ object BuiltIns {
def compileInPlaceWordMultiplication(ctx: CompilationContext, v: LhsExpression, addend: Expression): List[AssemblyLine] = {
v match {
case dx: DerefExpression =>
val el = if(dx.isVolatile) Elidability.Volatile else Elidability.Elidable
// this is ugly, needs a rewrite
return handleWordOrLongInPlaceModificationViaDeref(ctx, dx, addend, fromMsb = false){(ptr, reg, offset, r) =>
val constR = r match {
@ -1210,27 +1218,27 @@ object BuiltIns {
case Some(0) => List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(STA, ptr))
AssemblyLine.indexedY(STA, ptr).copy(elidability = el))
case Some(2) => List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.implied(ASL),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.implied(ROL),
AssemblyLine.indexedY(STA, ptr))
AssemblyLine.indexedY(STA, ptr).copy(elidability = el))
// TODO: other powers of two
case _ if reg.toAddress == ptr => List(
AssemblyLine.implied(PHA),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, reg),
AssemblyLine.indexedY(LDA, reg).copy(elidability = el),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, reg),
AssemblyLine.indexedY(LDA, reg).copy(elidability = el),
AssemblyLine.zeropage(STA, reg, 3),
AssemblyLine.implied(PLA),
AssemblyLine.implied(TAY),
@ -1248,26 +1256,26 @@ object BuiltIns {
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.zeropage(LDA, reg, 2),
AssemblyLine.indexedY(STA, reg),
AssemblyLine.indexedY(STA, reg).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.implied(TXA),
AssemblyLine.indexedY(STA, reg),
AssemblyLine.indexedY(STA, reg).copy(elidability = el),
)
case _ => List(
AssemblyLine.zeropage(STA, reg),
AssemblyLine.zeropage(STX, reg, 1),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.zeropage(STA, reg, 3),
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("__mul_u16u16u16")),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.implied(TXA),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
)
}
}(Left({size => ???}))
@ -1585,6 +1593,7 @@ object BuiltIns {
}
lhs match {
case dx: DerefExpression =>
val el = if(dx.isVolatile) Elidability.Volatile else Elidability.Elidable
if (subtract && ctx.options.zpRegisterSize < 3) {
ctx.log.error("Too complex left hand side. Consider increasing the size of the zeropage register.", lhs.position)
return compileInPlaceWordOrLongAddition(ctx, lhs, addend, subtract = false, decimal = false)
@ -1593,14 +1602,14 @@ object BuiltIns {
AssemblyLine.immediate(LDY, offset),
AssemblyLine.implied(SEC),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.zeropage(SBC, reg, 2),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.zeropage(STX, reg, 2),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.indexedY(LDA, ptr).copy(elidability = el),
AssemblyLine.zeropage(SBC, reg, 2),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
)))(Right({(iy, r, last) =>
val reg = ctx.env.get[VariableInMemory]("__reg")
val result = ListBuffer[AssemblyLine]()
@ -1611,7 +1620,7 @@ object BuiltIns {
}
result += AssemblyLine.immediate(LDY, iy)
result += AssemblyLine.zeropage(STA, reg, 2)
result += AssemblyLine.indexedY(LDA, reg)
result += AssemblyLine.indexedY(LDA, reg).copy(elidability = el)
if (iy == 0) {
result += AssemblyLine.implied(SEC)
}
@ -1627,12 +1636,12 @@ object BuiltIns {
})) else handleWordOrLongInPlaceModificationViaDeref(ctx, dx, addend, fromMsb = false)((ptr, _, offset, _) => wrapInSedCldIfNeeded(decimal, List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.implied(CLC),
AssemblyLine.indexedY(ADC, ptr),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(ADC, ptr).copy(elidability = el),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(ADC, ptr),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(ADC, ptr).copy(elidability = el),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
)))(Right({(iy, r, last) =>
val reg = ctx.env.get[VariableInMemory]("__reg")
val result = ListBuffer[AssemblyLine]()
@ -2004,14 +2013,16 @@ object BuiltIns {
def compileInPlaceWordOrLongBitOp(ctx: CompilationContext, lhs: LhsExpression, param: Expression, operation: Opcode.Value): List[AssemblyLine] = {
lhs match {
case dx: DerefExpression => return handleWordOrLongInPlaceModificationViaDeref(ctx, dx, param, fromMsb = false)((ptr, _, offset, _) => List(
case dx: DerefExpression =>
val el = if(dx.isVolatile) Elidability.Volatile else Elidability.Elidable
return handleWordOrLongInPlaceModificationViaDeref(ctx, dx, param, fromMsb = false)((ptr, _, offset, _) => List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(operation, ptr),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(operation, ptr).copy(elidability = el),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(operation, ptr),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.indexedY(operation, ptr).copy(elidability = el),
AssemblyLine.indexedY(STA, ptr).copy(elidability = el),
))(Right({ (iy, r, last) =>
val reg = ctx.env.get[VariableInMemory]("__reg")
r ++ List(

View File

@ -72,6 +72,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) :: (AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8)
} else AssemblyLine.tsx(ctx) ++ List(
// TODO: ???
AssemblyLine.implied(TSX),
AssemblyLine.immediate(LDA, expr.loByte),
AssemblyLine.dataStackX(ctx, STA, offset),
@ -155,10 +156,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
val cmos = ctx.options.flag(CompilationFlag.EmitCmosOpcodes)
if (register == MosRegister.AX && !code.exists(_.concernsX)) {
if (register == MosRegister.AX && !code.exists(_.concernsX) && code.forall(_.treatment(State.X).==(Treatment.Unchanged))) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (register == MosRegister.AY && !code.exists(_.concernsY)) {
if (register == MosRegister.AY && !code.exists(_.concernsY) && code.forall(_.treatment(State.Y).==(Treatment.Unchanged))) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (states.exists(state => AssemblyLine.treatment(code, state) != Treatment.Unchanged)) {
@ -431,21 +432,22 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
ctx.log.error("Invalid index for writing", indexExpr.position)
Nil
}
case DerefExpression(inner, offset, targetType) =>
case DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner)
if (targetType.size == 1) {
fast match {
case Some((fastBase, fastAddrMode, fastIndex)) =>
return preserveRegisterIfNeeded(ctx, MosRegister.A, fastIndex(offset)) ++ List(AssemblyLine(STA, fastAddrMode, fastBase))
return preserveRegisterIfNeeded(ctx, MosRegister.A, fastIndex(offset)) ++ List(AssemblyLine(STA, fastAddrMode, fastBase, elidability = el))
case _ =>
}
}
val lo = preserveRegisterIfNeeded(ctx, MosRegister.A, prepare) ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(STA, am, addr))
val lo = preserveRegisterIfNeeded(ctx, MosRegister.A, prepare) ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(STA, am, addr, elidability = el))
if (targetType.size == 1) {
lo
} else {
lo ++ List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(targetType.size - 1)(i => List(AssemblyLine.implied(INY), AssemblyLine(STA, am, addr))).flatten
List.tabulate(targetType.size - 1)(i => List(AssemblyLine.implied(INY), AssemblyLine(STA, am, addr, elidability = el))).flatten
}
}
}
@ -475,7 +477,20 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
compileToA(ctx, expr)
case t: ConstantBooleanType =>
List(AssemblyLine.immediate(LDA, if (t.value) 1 else 0))
case _: FlagBooleanType | BuiltInBooleanType =>
case t: FlagBooleanType =>
val condition = compile(ctx, expr, None, NoBranching)
condition ++ (t.jumpIfFalse.mosOpcode match {
case BCC => List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL))
case BCS => List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL), AssemblyLine.immediate(EOR, 1))
case o =>
val label = env.nextLabel("bo")
List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.relative(o, label),
AssemblyLine.immediate(LDA, 1),
AssemblyLine.label(label))
})
case BuiltInBooleanType =>
val label = env.nextLabel("bo")
val condition = compile(ctx, expr, None, BranchIfFalse(label))
if (condition.isEmpty) {
@ -563,13 +578,13 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
def getPhysicalPointerForDeref(ctx: CompilationContext, pointerExpression: Expression): (List[AssemblyLine], Constant, AddrMode.Value, Option[(Constant, AddrMode.Value, Int => List[AssemblyLine])]) = {
pointerExpression match {
case VariableExpression(name) =>
val p = ctx.env.get[ThingInMemory](name)
val p = ctx.env.maybeGet[ThingInMemory](name)
p match {
case array: MfArray => return (Nil, p.toAddress, AddrMode.AbsoluteY,
if (array.sizeInBytes <= 256) Some((p.toAddress, AbsoluteY, (i: Int) => List(AssemblyLine.immediate(LDY, i)))) else None)
case Some(array: MfArray) => return (Nil, array.toAddress, AddrMode.AbsoluteY,
if (array.sizeInBytes <= 256) Some((array.toAddress, AbsoluteY, (i: Int) => List(AssemblyLine.immediate(LDY, i)))) else None)
case Some(q) => if (q.zeropage) return (Nil, q.toAddress, AddrMode.IndexedY, None)
case _ =>
}
if (p.zeropage) return (Nil, p.toAddress, AddrMode.IndexedY, None)
case _ =>
}
ctx.env.eval(pointerExpression) match {
@ -658,7 +673,11 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
case RegisterVariable(MosRegister.Y, _) => actualOffset match {
case 0 => List(tsx)
case 1 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(TAY), AssemblyLine.implied(INY))
case 1 =>
if (ctx.options.flag(CompilationFlag.IdentityPage))
List(tsx, AssemblyLine.absoluteX(LDY, ctx.env.identityPage), AssemblyLine.implied(INY))
else
List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(TAY), AssemblyLine.implied(INY))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset), AssemblyLine.implied(TAY))
}
case _ =>
@ -1145,43 +1164,44 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
result ++ List(AssemblyLine.implied(PHA), AssemblyLine.implied(XBA), AssemblyLine.implied(PLA)) ++ signExtendA(ctx) ++ List(AssemblyLine.implied(XBA))
}
}
case DerefExpression(inner, offset, targetType) =>
case DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner)
(fast, targetType.size, am) match {
case (Some((fastBase, fastAddrMode, fastIndex)), 1, _) =>
fastIndex(offset) ++ List(AssemblyLine(LDA, fastAddrMode, fastBase)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
fastIndex(offset) ++ List(AssemblyLine(LDA, fastAddrMode, fastBase, elidability = el)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
case (_, 1, AbsoluteY) =>
prepare ++ List(AssemblyLine.absolute(LDA, addr + offset)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
prepare ++ List(AssemblyLine.absolute(LDA, addr + offset).copy(elidability = el)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
case (_, 1, _) =>
prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr, elidability = el)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
case (Some((fastBase, AbsoluteY, fastIndex)), 2, _) =>
fastIndex(offset) ++ List(
AssemblyLine.absoluteY(LDA, fastBase),
AssemblyLine.absoluteY(LDA, fastBase).copy(elidability = el),
AssemblyLine.implied(INY),
AssemblyLine.absoluteY(LDX, fastBase)) ++
AssemblyLine.absoluteY(LDX, fastBase).copy(elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case (Some((fastBase, fastAddrMode, fastIndex)), 2, _) =>
fastIndex(offset) ++ List(
AssemblyLine.implied(INY),
AssemblyLine(LDA, fastAddrMode, fastBase),
AssemblyLine(LDA, fastAddrMode, fastBase).copy(elidability = el),
AssemblyLine.implied(TAX),
AssemblyLine.implied(DEY),
AssemblyLine(LDA, fastAddrMode, fastBase)) ++
AssemblyLine(LDA, fastAddrMode, fastBase).copy(elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case (_, 2, AbsoluteY) =>
prepare ++
List(
AssemblyLine.absolute(LDA, addr + offset),
AssemblyLine.absolute(LDX, addr + offset + 1)) ++
AssemblyLine.absolute(LDA, addr + offset).copy(elidability = el),
AssemblyLine.absolute(LDX, addr + offset + 1).copy(elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case (_, 2, _) =>
prepare ++
List(
AssemblyLine.immediate(LDY, offset+1),
AssemblyLine(LDA, am, addr),
AssemblyLine(LDA, am, addr, elidability = el),
AssemblyLine.implied(TAX),
AssemblyLine.implied(DEY),
AssemblyLine(LDA, am, addr)) ++
AssemblyLine(LDA, am, addr, elidability = el)) ++
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
case _ =>
exprTypeAndVariable match {
@ -2229,7 +2249,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case SeparateBytesExpression(_, _) =>
ctx.log.error("Invalid left-hand-side use of `:`")
Nil
case DerefExpression(inner, offset, targetType) =>
case DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val (prepare, addr, am, fastTarget) = getPhysicalPointerForDeref(ctx, inner)
env.eval(source) match {
case Some(constant) =>
@ -2237,7 +2258,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case AbsoluteY =>
prepare ++ (0 until targetType.size).flatMap(i => List(
AssemblyLine.immediate(LDA, constant.subbyte(i)),
AssemblyLine.absolute(STA, addr + offset + i)))
AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el)))
case _ =>
fastTarget match {
case Some((constAddr, fastAddrMode, initializeY)) =>
@ -2245,13 +2266,13 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
val load = List(AssemblyLine.immediate(LDA, constant.subbyte(i)))
load ++ (if (i == 0) List(AssemblyLine(STA, fastAddrMode, constAddr)) else List(
AssemblyLine.implied(INY),
AssemblyLine(STA, fastAddrMode, constAddr)))
AssemblyLine(STA, fastAddrMode, constAddr, elidability = el)))
}
case _ =>
prepare ++ (0 until targetType.size).flatMap(i => List(
if (i == 0) AssemblyLine.immediate(LDY, offset) else AssemblyLine.implied(INY),
AssemblyLine.immediate(LDA, constant.subbyte(i)),
AssemblyLine(STA, am, addr)))
AssemblyLine(STA, am, addr, elidability = el)))
}
}
case None =>
@ -2262,7 +2283,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case (1, AbsoluteY) =>
prepare ++
AssemblyLine.variable(ctx, LDA, variable) :+
AssemblyLine.absolute(STA, addr + offset)
AssemblyLine.absolute(STA, addr + offset).copy(elidability = el)
case (1, _) if fastTarget.isEmpty =>
prepare ++
AssemblyLine.variable(ctx, LDA, variable) ++ List(
@ -2272,7 +2293,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
prepare ++ (0 until targetType.size).flatMap { i =>
val load = loadSingleByteAssumingAPreserved(ctx, sourceType, variable, i)
load ++ List(
AssemblyLine.absolute(STA, addr + offset + i))
AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el))
}
case (_, _) =>
fastTarget match {
@ -2281,37 +2302,38 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
val load = loadSingleByteAssumingAPreserved(ctx, sourceType, variable, i)
load ++ (if (i == 0) List(AssemblyLine(STA, fastAddrMode, constAddr)) else List(
AssemblyLine.implied(INY),
AssemblyLine(STA, fastAddrMode, constAddr)))
AssemblyLine(STA, fastAddrMode, constAddr, elidability = el)))
}
case _ =>
prepare ++ (0 until targetType.size).flatMap { i =>
val load = loadSingleByteAssumingAPreserved(ctx, sourceType, variable, i)
load ++ List(
if (i == 0) AssemblyLine.immediate(LDY, offset) else AssemblyLine.implied(INY),
AssemblyLine(STA, am, addr))
AssemblyLine(STA, am, addr).copy(elidability = el))
}
}
case _ =>
ctx.log.error("Cannot assign to a large object indirectly", target.position)
Nil
}
case DerefExpression(innerSource, sourceOffset, _) =>
case DerefExpression(innerSource, sourceOffset, sourceVolatile, _) =>
val sEl = if (sourceVolatile) Elidability.Volatile else Elidability.Elidable
val (prepareSource, addrSource, amSource, fastSource) = getPhysicalPointerForDeref(ctx, innerSource)
(am, amSource) match {
case (AbsoluteY, AbsoluteY) =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
loadSingleByteAssumingAPreserved(ctx, sourceType, addrSource + sourceOffset, i) :+ AssemblyLine.absolute(STA, addr + offset + i)
loadSingleByteAssumingAPreserved(ctx, sourceType, addrSource + sourceOffset, i, sEl) :+ AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el)
}
case (AbsoluteY, _) =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
loadSingleByteIndexedCountingYAssumingAPreserved(ctx, sourceType, amSource, addrSource, sourceOffset, i) :+
AssemblyLine.absolute(STA, addr + offset + i)
loadSingleByteIndexedCountingYAssumingAPreserved(ctx, sourceType, amSource, addrSource, sourceOffset, i, sEl) :+
AssemblyLine.absolute(STA, addr + offset + i).copy(elidability = el)
}
case (_, AbsoluteY) =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteAssumingAPreserved(ctx, sourceType, addrSource + sourceOffset, i) :+
AssemblyLine(STA, am, addr))
(loadSingleByteAssumingAPreserved(ctx, sourceType, addrSource + sourceOffset, i, sEl) :+
AssemblyLine(STA, am, addr, elidability = el))
}
case (IndexedY, IndexedY) =>
val reg = env.get[ThingInMemory]("__reg.loword")
@ -2327,8 +2349,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.zeropage(STA, reg, 3)) ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress, i) :+
AssemblyLine.indexedY(STA, reg, 2))
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress, i, elidability = sEl) :+
AssemblyLine.indexedY(STA, reg, 2).copy(elidability = el))
}
case (false, true) =>
prepareSource ++ List(
@ -2337,8 +2359,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.zeropage(STA, reg, 3)) ++ prepare ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress + 2, i) :+
AssemblyLine.indexedY(STA, reg))
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress + 2, i, elidability = sEl) :+
AssemblyLine.indexedY(STA, reg).copy(elidability = el))
}
case _ =>
prepare ++ List(
@ -2351,15 +2373,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, 3)) ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress, i) :+
AssemblyLine.indexedY(STA, reg, 2))
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, reg.toAddress, i, sEl) :+
AssemblyLine.indexedY(STA, reg, 2).copy(elidability = el))
}
}
case (MemoryAddressConstant(thT: MemoryVariable), MemoryAddressConstant(thS: MemoryVariable)) if thT.name != thS.name =>
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
(if (i == 0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY)) ::
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, thS.toAddress, i) :+
AssemblyLine.indexedY(STA, thT))
(loadSingleByteIndexedAssumingAPreserved(ctx, sourceType, IndexedY, thS.toAddress, i, sEl) :+
AssemblyLine.indexedY(STA, thT).copy(elidability = el))
}
case _ =>
???
@ -2376,17 +2398,17 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
// TODO: optimize if prepare is empty
if (prepare.isEmpty) {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.absolute(STA, addr + offset),
AssemblyLine.absolute(STX, addr + offset + 1))
AssemblyLine.absolute(STA, addr + offset).copy(elidability = el),
AssemblyLine.absolute(STX, addr + offset + 1).copy(elidability = el))
} else {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(PHA)) ++ prepare ++ List(
AssemblyLine.implied(PLA),
AssemblyLine.absolute(STA, addr + offset + 1),
AssemblyLine.absolute(STA, addr + offset + 1).copy(elidability = el),
AssemblyLine.implied(PLA),
AssemblyLine.absolute(STA, addr + offset))
AssemblyLine.absolute(STA, addr + offset).copy(elidability = el))
}
case (2, _) =>
val someTuple = Some(targetType, RegisterVariable(MosRegister.AX, targetType))
@ -2395,18 +2417,18 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
compile(ctx, source, someTuple, BranchSpec.None) ++
preserveRegisterIfNeeded(ctx, MosRegister.AX, initializeY(offset)) ++
List(
AssemblyLine(STA, fastAddrMode, baseOffset),
AssemblyLine(STA, fastAddrMode, baseOffset, elidability = el),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine(STA, fastAddrMode, baseOffset))
AssemblyLine(STA, fastAddrMode, baseOffset, elidability = el))
case _ =>
if (prepare.isEmpty) {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(STA, addr),
AssemblyLine.indexedY(STA, addr).copy(elidability = el),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(STA, addr))
AssemblyLine.indexedY(STA, addr).copy(elidability = el))
} else {
compile(ctx, source, someTuple, BranchSpec.None) ++ List(
AssemblyLine.implied(PHA),
@ -2414,10 +2436,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
AssemblyLine.implied(PHA)) ++ prepare ++ List(
AssemblyLine.immediate(LDY, offset+1),
AssemblyLine.implied(PLA),
AssemblyLine.indexedY(STA, addr),
AssemblyLine.indexedY(STA, addr).copy(elidability = el),
AssemblyLine.implied(PLA),
AssemblyLine.implied(DEY),
AssemblyLine.indexedY(STA, addr))
AssemblyLine.indexedY(STA, addr).copy(elidability = el))
}
}
case _ =>
@ -2443,25 +2465,25 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
else AssemblyLine.variable(ctx, LDA, variable, i)
}
private def loadSingleByteAssumingAPreserved(ctx: CompilationContext, sourceType: Type, varAddr: Constant, i: Int) = {
private def loadSingleByteAssumingAPreserved(ctx: CompilationContext, sourceType: Type, varAddr: Constant, i: Int, elidability: Elidability.Value) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else List(AssemblyLine.absolute(LDA, varAddr + i))
else List(AssemblyLine(LDA, Absolute, varAddr + i, elidability))
}
private def loadSingleByteIndexedAssumingAPreserved(ctx: CompilationContext, sourceType: Type, addrMode: AddrMode.Value, baseAddr: Constant, i: Int) = {
private def loadSingleByteIndexedAssumingAPreserved(ctx: CompilationContext, sourceType: Type, addrMode: AddrMode.Value, baseAddr: Constant, i: Int, elidability: Elidability.Value) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else List(AssemblyLine(LDA, addrMode, baseAddr))
else List(AssemblyLine(LDA, addrMode, baseAddr, elidability))
}
private def loadSingleByteIndexedCountingYAssumingAPreserved(ctx: CompilationContext, sourceType: Type, addrMode: AddrMode.Value, baseAddr: Constant, sourceOffset:Int, i: Int) = {
private def loadSingleByteIndexedCountingYAssumingAPreserved(ctx: CompilationContext, sourceType: Type, addrMode: AddrMode.Value, baseAddr: Constant, sourceOffset:Int, i: Int, elidability: Elidability.Value) = {
if (i == sourceType.size) {
if (sourceType.isSigned) signExtendA(ctx) else List(AssemblyLine.immediate(LDA, 0))
} else if (i > sourceType.size) Nil
else List(if(i==0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY), AssemblyLine(LDA, addrMode, baseAddr))
else List(if(i==0) AssemblyLine.immediate(LDY, sourceOffset) else AssemblyLine.implied(INY), AssemblyLine(LDA, addrMode, baseAddr, elidability))
}
def arrayBoundsCheck(ctx: CompilationContext, pointy: Pointy, register: MosRegister.Value, index: Expression): List[AssemblyLine] = {

View File

@ -164,14 +164,14 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] {
val c: Constant = compileParameterForAssemblyStatement(env, o, x)
val actualAddrMode = a match {
case Absolute if OpcodeClasses.ShortBranching(o) => Relative
case Absolute if (o == ROR_W || o == ASL_W) && ctx.options.flag(CompilationFlag.Emit65CE02Opcodes) =>
case Absolute if (o == ROL_W || o == ASL_W) && ctx.options.flag(CompilationFlag.Emit65CE02Opcodes) =>
Absolute
case Absolute if OpcodeClasses.SupportsZeropage(o) && c.fitsProvablyIntoByte => ZeroPage
case ImmediateWithAbsolute if (c match {
case StructureConstant(_, List(a, b)) => b.fitsProvablyIntoByte
}) => ImmediateWithZeroPage
case IndexedX if o == JMP => AbsoluteIndexedX
case Indirect if o != JMP => IndexedZ
case IndexedX if o == JMP || o == JSR => AbsoluteIndexedX
case Indirect if o != JMP && o != JSR => IndexedZ
case _ => a
}
List(AssemblyLine(o, actualAddrMode, c, e)) -> Nil
@ -191,7 +191,7 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] {
case Some(_) =>
params.flatMap(p => MosExpressionCompiler.compile(ctx, p, None, NoBranching))-> Nil
case None =>
env.lookupFunction(name, params.map(p => MosExpressionCompiler.getExpressionType(ctx, p) -> p)) match {
env.lookupFunction(name, params.map(p => AbstractExpressionCompiler.getExpressionTypeForMacro(ctx, p) -> p)) match {
case Some(i: MacroFunction) =>
val (paramPreparation, inlinedStatements) = MosMacroExpander.inlineFunction(ctx, i, params, e.position)
paramPreparation ++ compile(ctx.withInlinedEnv(i.environment, ctx.nextLabel("en")), inlinedStatements)._1 -> Nil

View File

@ -730,11 +730,23 @@ object PseudoregisterBuiltIns {
case (1, 1) => // ok
case _ => ctx.log.fatal("Invalid code path", param2.position)
}
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
val reg = ctx.env.get[VariableInMemory]("__reg")
if (!storeInRegLo && param1OrRegister.isDefined) {
(ctx.env.eval(param1OrRegister.get), ctx.env.eval(param2)) match {
case (Some(l), Some(r)) =>
val product = CompoundConstant(MathOperator.Times, l, r).quickSimplify
return List(AssemblyLine.immediate(LDA, product.loByte), AssemblyLine.immediate(LDX, product.hiByte))
case (Some(NumericConstant(2, _)), _) =>
val evalParam2 = MosExpressionCompiler.compile(ctx, param2, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None)
val label = ctx.nextLabel("sh")
return evalParam2 ++ List(
AssemblyLine.implied(ASL),
AssemblyLine.immediate(LDX, 0),
AssemblyLine.relative(BCC, label),
AssemblyLine.implied(INX),
AssemblyLine.label(label))
case (Some(NumericConstant(c, _)), _) if isPowerOfTwoUpTo15(c)=>
return compileWordShiftOps(left = true, ctx, param2, LiteralExpression(java.lang.Long.bitCount(c - 1), 1))
case (_, Some(NumericConstant(c, _))) if isPowerOfTwoUpTo15(c)=>
@ -742,9 +754,6 @@ object PseudoregisterBuiltIns {
case _ =>
}
}
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
val reg = ctx.env.get[VariableInMemory]("__reg")
val load: List[AssemblyLine] = param1OrRegister match {
case Some(param1) =>
val code1 = MosExpressionCompiler.compile(ctx, param1, Some(w -> RegisterVariable(MosRegister.AX, w)), BranchSpec.None)

View File

@ -27,7 +27,18 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case FatBooleanType => compileToA(ctx, expression)
case t: ConstantBooleanType =>
List(ZLine.ldImm8(ZRegister.A, if (t.value) 1 else 0))
case BuiltInBooleanType | _: FlagBooleanType =>
case t: FlagBooleanType =>
val condition = Z80ExpressionCompiler.compile(ctx, expression, ZExpressionTarget.NOTHING, NoBranching)
condition ++ (t.jumpIfFalse.z80Flags match {
case o =>
val label = ctx.env.nextLabel("bo")
List(
ZLine.ldImm8(ZRegister.A, 0),
ZLine.jumpR(ctx, label, o),
ZLine.register(INC, ZRegister.A),
ZLine.label(label))
})
case BuiltInBooleanType =>
// TODO optimize if using CARRY
// TODO: helper functions to convert flags to booleans, to make code smaller
val label = ctx.env.nextLabel("bo")
@ -623,70 +634,71 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case ZExpressionTarget.DE => xxx(ZRegister.D, ZRegister.E, allowRedirect = true)
case ZExpressionTarget.BC => xxx(ZRegister.B, ZRegister.C, allowRedirect = true)
}
case e@DerefExpression(inner, offset, targetType) =>
case e@DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if (isVolatile) Elidability.Volatile else Elidability.Elidable
compileDerefPointer(ctx, e) ++ (targetType.size match {
case 1 => target match {
case ZExpressionTarget.A => List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL))
case ZExpressionTarget.A => List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el))
case ZExpressionTarget.DEHL =>
if (targetType.isSigned) {
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL), ZLine.ld8(ZRegister.L, ZRegister.A)) ++
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el), ZLine.ld8(ZRegister.L, ZRegister.A)) ++
signExtendHighestByte(ctx, ZRegister.A) ++
List(ZLine.ld8(ZRegister.H, ZRegister.A), ZLine.ld8(ZRegister.E, ZRegister.A), ZLine.ld8(ZRegister.D, ZRegister.A))
} else {
List(ZLine.ld8(ZRegister.L, ZRegister.MEM_HL), ZLine.ldImm8(ZRegister.H, 0), ZLine.ldImm8(ZRegister.E, 0), ZLine.ldImm8(ZRegister.D, 0))
List(ZLine.ld8(ZRegister.L, ZRegister.MEM_HL).copy(elidability = el), ZLine.ldImm8(ZRegister.H, 0), ZLine.ldImm8(ZRegister.E, 0), ZLine.ldImm8(ZRegister.D, 0))
}
case ZExpressionTarget.HL =>
if (targetType.isSigned) {
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL), ZLine.ld8(ZRegister.L, ZRegister.A)) ++ signExtendHighestByte(ctx, ZRegister.H)
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el), ZLine.ld8(ZRegister.L, ZRegister.A)) ++ signExtendHighestByte(ctx, ZRegister.H)
} else {
List(ZLine.ld8(ZRegister.L, ZRegister.MEM_HL), ZLine.ldImm8(ZRegister.H, 0))
List(ZLine.ld8(ZRegister.L, ZRegister.MEM_HL).copy(elidability = el), ZLine.ldImm8(ZRegister.H, 0))
}
case ZExpressionTarget.BC =>
if (targetType.isSigned) {
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL), ZLine.ld8(ZRegister.C, ZRegister.A)) ++ signExtendHighestByte(ctx, ZRegister.B)
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el), ZLine.ld8(ZRegister.C, ZRegister.A)) ++ signExtendHighestByte(ctx, ZRegister.B)
} else {
List(ZLine.ld8(ZRegister.C, ZRegister.MEM_HL), ZLine.ldImm8(ZRegister.B, 0))
List(ZLine.ld8(ZRegister.C, ZRegister.MEM_HL).copy(elidability = el), ZLine.ldImm8(ZRegister.B, 0))
}
case ZExpressionTarget.DE =>
if (targetType.isSigned) {
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL), ZLine.ld8(ZRegister.E, ZRegister.A)) ++ signExtendHighestByte(ctx, ZRegister.D)
List(ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el), ZLine.ld8(ZRegister.E, ZRegister.A)) ++ signExtendHighestByte(ctx, ZRegister.D)
} else {
List(ZLine.ld8(ZRegister.E, ZRegister.MEM_HL), ZLine.ldImm8(ZRegister.D, 0))
List(ZLine.ld8(ZRegister.E, ZRegister.MEM_HL).copy(elidability = el), ZLine.ldImm8(ZRegister.D, 0))
}
case ZExpressionTarget.NOTHING => Nil
}
case 2 => target match {
case ZExpressionTarget.DEHL =>
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.A),
ZLine.ldImm8(ZRegister.E, 0),
ZLine.ldImm8(ZRegister.D, 0)) // TODO
case ZExpressionTarget.EHL =>
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.A),
ZLine.ldImm8(ZRegister.E, 0)) // TODO
case ZExpressionTarget.HL =>
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.A))
case ZExpressionTarget.BC =>
List(
ZLine.ld8(ZRegister.C, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.C, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.B, ZRegister.MEM_HL))
ZLine.ld8(ZRegister.B, ZRegister.MEM_HL).copy(elidability = el))
case ZExpressionTarget.DE =>
List(
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL))
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL).copy(elidability = el))
case ZExpressionTarget.NOTHING => Nil
}
case 3 => target match {
@ -694,20 +706,20 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case ZExpressionTarget.EHL | ZExpressionTarget.DEHL =>
if (ctx.options.flag(CompilationFlag.EmitIntel8080Opcodes)) {
List(
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.L, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.L, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ldImm8(ZRegister.H, 0),
ZLine.implied(EX_DE_HL)) // TODO
} else {
List(
ZLine.ld8(ZRegister.C, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.C, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.B, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.B, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.C),
ZLine.ld8(ZRegister.H, ZRegister.B),
ZLine.ldImm8(ZRegister.D, 0)) // TODO
@ -718,24 +730,24 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case ZExpressionTarget.DEHL =>
if (ctx.options.flag(CompilationFlag.EmitIntel8080Opcodes)) {
List(
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.H, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.A),
ZLine.implied(EX_DE_HL)) // TODO
} else {
List(
ZLine.ld8(ZRegister.C, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.C, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.B, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.B, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.E, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.D, ZRegister.MEM_HL).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.C),
ZLine.ld8(ZRegister.H, ZRegister.B)) // TODO
}
@ -1590,11 +1602,15 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
prepareA ++ fillUpperBytes
}
private def signExtendHighestByte(ctx: CompilationContext, sourceHiRegister: ZRegister.Value, targetHiRegister: ZRegister.Value = ZRegister.A, signedSource: Boolean = true): List[ZLine] = {
private def signExtendHighestByte(ctx: CompilationContext,
sourceHiRegister: ZRegister.Value,
targetHiRegister: ZRegister.Value = ZRegister.A,
signedSource: Boolean = true,
elidability: Elidability.Value = Elidability.Elidable): List[ZLine] = {
if (!signedSource) {
return List(ZLine.ldImm8(targetHiRegister, 0))
}
val prefix = if (sourceHiRegister == ZRegister.A) Nil else List(ZLine.ld8(ZRegister.A, sourceHiRegister))
val prefix = if (sourceHiRegister == ZRegister.A) Nil else List(ZLine.ld8(ZRegister.A, sourceHiRegister).copy(elidability = elidability))
val label = ctx.nextLabel("sx")
val resultInA = if (ctx.options.flag(CompilationFlag.EmitIntel8080Opcodes)) {
prefix ++ List(
@ -1613,7 +1629,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
} else {
throw new IllegalStateException()
}
if (targetHiRegister == ZRegister.A) resultInA else resultInA :+ ZLine.ld8(targetHiRegister, ZRegister.A)
if (targetHiRegister == ZRegister.A) resultInA else resultInA :+ ZLine.ld8(targetHiRegister, ZRegister.A).copy(elidability = elidability)
}
def signExtendViaIX(ctx: CompilationContext, targetOffset: Int, hiRegister: ZRegister.Value, bytes: Int, signedSource: Boolean): List[ZLine] = {
@ -1868,17 +1884,18 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
List(ZLine.ld8(ZRegister.E, ZRegister.A)) ++ stashDEIfChanged(ctx, code) :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.E)
} else code :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)
}
case e@DerefExpression(_, _, targetType) =>
val lo = stashAFIfChanged(ctx, compileDerefPointer(ctx, e)) :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)
case e@DerefExpression(_, _, vol, targetType) =>
val el = if (vol) Elidability.Volatile else Elidability.Elidable
val lo = stashAFIfChanged(ctx, compileDerefPointer(ctx, e)) :+ ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el)
if (targetType.size == 1) lo
else if (targetType.size == 2) {
lo ++ List(ZLine.register(INC_16, ZRegister.HL)) ++
signExtendHighestByte(ctx, ZRegister.A, signedSource = signedSource) ++
List(ZLine.ld8(ZRegister.MEM_HL, ZRegister.A))
List(ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el))
} else {
lo ++ signExtendHighestByte(ctx, ZRegister.A, signedSource = signedSource) ++ List.tabulate(targetType.size - 1)(_ => List(
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el)
)).flatten
}
//TODO
@ -1889,10 +1906,11 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
def storeConstantWord(ctx: CompilationContext, target: LhsExpression, source: Constant, signedSource: Boolean): List[ZLine] = {
target match {
case e: DerefExpression =>
val el = if(e.isVolatile) Elidability.Volatile else Elidability.Elidable
compileDerefPointer(ctx, e) ++ List(
ZLine.ldImm8(ZRegister.MEM_HL, source.loByte),
ZLine.ldImm8(ZRegister.MEM_HL, source.loByte).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ldImm8(ZRegister.MEM_HL, source.hiByte))
ZLine.ldImm8(ZRegister.MEM_HL, source.hiByte).copy(elidability = el))
case _ => ZLine.ldImm16(ZRegister.HL, source) :: storeHL(ctx, target, signedSource)
}
}
@ -1958,6 +1976,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
Z80ExpressionCompiler.stashHLIfChanged(ctx, ZLine.ld8(ZRegister.A, ZRegister.L) :: storeA(ctx, lo, signedSource)) ++
(ZLine.ld8(ZRegister.A, ZRegister.H) :: storeA(ctx, hi, signedSource))
case e:DerefExpression =>
val el = if(e.isVolatile) Elidability.Volatile else Elidability.Elidable
if (ctx.options.flag(CompilationFlag.EmitIntel8085Opcodes) && ctx.options.flag(CompilationFlag.EmitIllegals)) {
List(ZLine.register(PUSH, ZRegister.HL)) ++ fixTsx(ctx, compileDerefPointer(ctx, e)) ++ List(
ZLine.register(POP, ZRegister.DE),
@ -1968,20 +1987,20 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
List(ZLine.register(INC_16, DE)) ++
List.fill(e.targetType.size - 2)(List(
ZLine.register(INC_16, DE),
ZLine.ld8(MEM_DE, A)
ZLine.ld8(MEM_DE, A).copy(elidability = el)
)).flatten
else Nil)
} else {
List(ZLine.register(PUSH, ZRegister.HL)) ++ fixTsx(ctx, compileDerefPointer(ctx, e)) ++ List(
ZLine.register(POP, ZRegister.BC),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.C),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.C).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.B)) ++
ZLine.ld8(ZRegister.MEM_HL, ZRegister.B).copy(elidability = el)) ++
(if (e.targetType.size > 2)
signExtendHighestByte(ctx, B) ++
List.fill(e.targetType.size - 2)(List(
ZLine.register(INC_16, HL),
ZLine.ld8(MEM_HL, A)
ZLine.ld8(MEM_HL, A).copy(elidability = el)
)).flatten
else Nil)
}
@ -2298,16 +2317,17 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case _ => List(ZLine.ldImm8(ZRegister.A, 0)) // TODO: signed large types?
}
}
case e@DerefExpression(_, _, _) =>
case e: DerefExpression =>
val el = if(e.isVolatile) Elidability.Volatile else Elidability.Elidable
import ZRegister._
val prepareHL = compileDerefPointer(ctx, e)
List.tabulate(size) { i =>
if (i == 0) {
prepareHL :+ ZLine.ld8(A, MEM_HL)
prepareHL :+ ZLine.ld8(A, MEM_HL).copy(elidability = el)
} else if (i < e.targetType.size) {
List(ZLine.register(INC_16, HL), ZLine.ld8(A, MEM_HL))
List(ZLine.register(INC_16, HL), ZLine.ld8(A, MEM_HL).copy(elidability = el))
} else if (e.targetType.isSigned) {
signExtendHighestByte(ctx, ZRegister.MEM_HL)
signExtendHighestByte(ctx, ZRegister.MEM_HL, elidability = el)
} else {
List(ZLine.ldImm8(A, 0))
}
@ -2383,15 +2403,16 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
Nil
}
}
case de@DerefExpression(inner, offset, targetType) =>
case de@DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
import ZRegister._
val prepareHL = compileDerefPointer(ctx, de)
List.tabulate(size) { i =>
if (i == 0) {
prepareHL :+ ZLine.ld8(MEM_HL, A)
prepareHL :+ ZLine.ld8(MEM_HL, A).copy(elidability = el)
} else if (i < targetType.size) {
if (includeStep) List(ZLine.register(INC_16, HL), ZLine.ld8(MEM_HL, A))
else List(ZLine.ld8(MEM_HL, A))
if (includeStep) List(ZLine.register(INC_16, HL), ZLine.ld8(MEM_HL, A).copy(elidability = el))
else List(ZLine.ld8(MEM_HL, A).copy(elidability = el))
} else Nil
}
case _ =>

View File

@ -326,8 +326,14 @@ object Z80Multiply {
case (1, 1) => // ok
case _ => ctx.log.fatal("Invalid code path", l.position)
}
ctx.env.eval(r) match {
case Some(c) =>
(ctx.env.eval(l), ctx.env.eval(r)) match {
case (Some(p), Some(q)) =>
List(ZLine.ldImm16(ZRegister.HL, CompoundConstant(MathOperator.Times, p, q).quickSimplify))
case (Some(NumericConstant(c, _)), _) if isPowerOfTwoUpTo15(c) =>
Z80ExpressionCompiler.compileToHL(ctx, l) ++ List.fill(Integer.numberOfTrailingZeros(c.toInt))(ZLine.registers(ZOpcode.ADD_16, ZRegister.HL, ZRegister.HL))
case (_, Some(NumericConstant(c, _))) if isPowerOfTwoUpTo15(c) =>
Z80ExpressionCompiler.compileToHL(ctx, l) ++ List.fill(Integer.numberOfTrailingZeros(c.toInt))(ZLine.registers(ZOpcode.ADD_16, ZRegister.HL, ZRegister.HL))
case (_, Some(c)) =>
Z80ExpressionCompiler.compileToDE(ctx, l) ++ List(ZLine.ldImm8(ZRegister.A, c)) ++ multiplication16And8(ctx)
case _ =>
val lw = Z80ExpressionCompiler.compileToDE(ctx, l)

View File

@ -241,7 +241,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
}
}
val registers = (reg, offset) match {
case (OneRegister(r), Some(o)) => env.evalForAsm(expression) match {
case (OneRegister(r), Some(o)) => env.evalForAsm(o) match {
case Some(NumericConstant(v, _)) => OneRegisterOffset(r, v.toInt)
case Some(_) =>
ctx.log.error("Non-numeric constant", o.position)
@ -250,7 +250,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
ctx.log.error("Inlining failed due to non-constant things", o.position)
reg
}
case (TwoRegisters(t, s), Some(o)) => env.evalForAsm(expression) match {
case (TwoRegisters(t, s), Some(o)) => env.evalForAsm(o) match {
case Some(NumericConstant(v, _)) => TwoRegistersOffset(t, s, v.toInt)
case Some(_) =>
ctx.log.error("Non-numeric constant", o.position)

View File

@ -5,6 +5,7 @@ import millfork.compiler.CompilationContext
import millfork.node._
import ZOpcode._
import millfork.CompilationFlag
import millfork.assembly.Elidability
import millfork.env._
import millfork.error.ConsoleLogger
@ -485,24 +486,25 @@ object ZBuiltIns {
def performLongInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, opcodeFirst: ZOpcode.Value, opcodeLater: ZOpcode.Value, size: Int, decimal: Boolean = false): List[ZLine] = {
lhs match {
case dx@DerefExpression(inner, offset, targetType) =>
case dx@DerefExpression(inner, offset, isVolatile, targetType) =>
val el = if(isVolatile) Elidability.Volatile else Elidability.Elidable
val env = ctx.env
if (targetType.size == 2) {
if (opcodeFirst == ADD && !decimal && ctx.options.flag(CompilationFlag.EmitZ80Opcodes)) {
val l = Z80ExpressionCompiler.compileToBC(ctx, inner #+# offset)
val r = Z80ExpressionCompiler.compileToDE(ctx, rhs)
val s = List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_BC),
ZLine.ld8(ZRegister.A, ZRegister.MEM_BC).copy(elidability = el),
ZLine.ld8(ZRegister.L, ZRegister.A),
ZLine.register(INC_16, ZRegister.BC),
ZLine.ld8(ZRegister.A, ZRegister.MEM_BC),
ZLine.ld8(ZRegister.A, ZRegister.MEM_BC).copy(elidability = el),
ZLine.ld8(ZRegister.H, ZRegister.A),
ZLine.registers(ADD_16, ZRegister.HL, ZRegister.DE),
ZLine.ld8(ZRegister.A, ZRegister.H),
ZLine.ld8(ZRegister.MEM_BC, ZRegister.A),
ZLine.ld8(ZRegister.MEM_BC, ZRegister.A).copy(elidability = el),
ZLine.register(DEC_16, ZRegister.BC),
ZLine.ld8(ZRegister.A, ZRegister.L),
ZLine.ld8(ZRegister.MEM_BC, ZRegister.A),
ZLine.ld8(ZRegister.MEM_BC, ZRegister.A).copy(elidability = el),
)
if (!r.exists(Z80ExpressionCompiler.changesBC)) {
return l ++ r ++ s
@ -517,15 +519,15 @@ object ZBuiltIns {
val s = if (opcodeFirst == SUB && decimal) {
if (ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)) {
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(SUB, ZRegister.E),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(SBC, ZRegister.D),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el),
)
} else {
ctx.log.error("Decimal subtraction from such a complex LHS is not yet supported", dx.position)
@ -533,25 +535,25 @@ object ZBuiltIns {
}
} else if (opcodeFirst == ADD && decimal) {
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(ADD, ZRegister.E),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(ADC, ZRegister.D),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el),
)
} else {
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(opcodeFirst, ZRegister.E),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el),
ZLine.register(opcodeLater, ZRegister.D),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el),
)
}
if (!r.exists(Z80ExpressionCompiler.changesHL)) {
@ -575,17 +577,17 @@ object ZBuiltIns {
result ++= sourceBytes(i)
result += ZLine.ld8(ZRegister.E, ZRegister.A)
if (i != 0) result += ZLine.register(POP, ZRegister.AF)
result += ZLine.ld8(ZRegister.A, ZRegister.MEM_HL)
result += ZLine.ld8(ZRegister.A, ZRegister.MEM_HL).copy(elidability = el)
result += ZLine.register(if (i == 0) opcodeFirst else opcodeLater, ZRegister.E)
result += ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)
result += ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el)
if (i != size - 1) result += ZLine.register(INC_16, ZRegister.HL)
}
} else {
for (i <- 0 until size) {
result ++= sourceBytes(i)
result += ZLine.register(if (i==0) opcodeFirst else opcodeLater, ZRegister.MEM_HL)
result += ZLine.register(if (i==0) opcodeFirst else opcodeLater, ZRegister.MEM_HL).copy(elidability = el)
if (decimal) result += ZLine.implied(DAA)
result += ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)
result += ZLine.ld8(ZRegister.MEM_HL, ZRegister.A).copy(elidability = el)
if (i != size -1) result += ZLine.register(INC_16, ZRegister.HL)
}
}

View File

@ -28,6 +28,7 @@ sealed trait Constant {
def isQuiteNegative: Boolean = false
def isProvablyZero: Boolean = false
def isProvablyNonZero: Boolean = false
def isProvably(value: Int): Boolean = false
def isProvablyInRange(startInclusive: Int, endInclusive: Int): Boolean = false
def isProvablyNonnegative: Boolean = false
@ -183,6 +184,7 @@ case class AssertByte(c: Constant) extends Constant {
override def isQuiteNegative: Boolean = c.isQuiteNegative
override def isProvablyGreaterOrEqualThan(other: Constant): Boolean = c.isProvablyGreaterOrEqualThan(other)
override def isProvablyZero: Boolean = c.isProvablyZero
override def isProvablyNonZero: Boolean = c.isProvablyNonZero
override def isProvably(i: Int): Boolean = c.isProvably(i)
override def isProvablyNonnegative: Boolean = c.isProvablyNonnegative
override def isProvablyNegative(asType: Type): Boolean = c.isProvablyNegative(asType)
@ -221,7 +223,7 @@ case class StructureConstant(typ: StructType, fields: List[Constant]) extends Co
override def subbyte(index: Int): Constant = {
var offset = 0
for ((fv, ResolvedFieldDesc(ft, _, arrayIndexTypeAndSize)) <- fields.zip(typ.mutableFieldsWithTypes)) {
for ((fv, ResolvedFieldDesc(ft, _, _, arrayIndexTypeAndSize)) <- fields.zip(typ.mutableFieldsWithTypes)) {
// TODO: handle array members?
val fs = ft.size
if (index < offset + fs) {
@ -234,7 +236,7 @@ case class StructureConstant(typ: StructType, fields: List[Constant]) extends Co
}
override def subbyteBe(index: Int, totalSize: Int): Constant = {
var offset = 0
for ((fv, ResolvedFieldDesc(ft, _, arrayIndexTypeAndSize)) <- fields.zip(typ.mutableFieldsWithTypes)) {
for ((fv, ResolvedFieldDesc(ft, _, _, arrayIndexTypeAndSize)) <- fields.zip(typ.mutableFieldsWithTypes)) {
// TODO: handle array members?
val fs = ft.size
if (index < offset + fs) {
@ -277,6 +279,7 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
})
override def isProvablyInRange(startInclusive: Int, endInclusive: Int): Boolean = value >= startInclusive && value <= endInclusive
override def isProvablyZero: Boolean = value == 0
override def isProvablyNonZero: Boolean = value.&(0x1L.<<(8 * requiredSize - 1)) != 0
override def isProvably(i: Int): Boolean = value == i
override def isProvablyNonnegative: Boolean = value >= 0
override def isProvablyNegative(asType: Type): Boolean = {
@ -440,6 +443,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
object MathOperator extends Enumeration {
val Plus, Minus, Times, Shl, Shr, Shl9, Shr9, Plus9, DecimalPlus9,
DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShl9, DecimalShr,
Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual,
Minimum, Maximum,
Divide, Modulo,
And, Or, Exor = Value
@ -599,7 +603,7 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
case MathOperator.Exor => (lv ^ rv) & bitmask
case MathOperator.Or => lv | rv
case MathOperator.And => lv & rv & bitmask
case MathOperator.Divide if lv >= 0 && rv >= 0 => lv / rv
case MathOperator.Divide if lv >= 0 && rv > 0 => lv / rv
case MathOperator.Modulo if lv >= 0 && rv >= 0 => lv % rv
case MathOperator.DecimalPlus if ls == 1 && rs == 1 =>
asDecimal(lv & 0xff, rv & 0xff, _ + _) & 0xff
@ -692,6 +696,12 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
case Exor => s"$plhs ^ $prhs"
case Divide => s"$plhs / $prhs"
case Modulo => s"$plhs %% $prhs"
case Equal => s"$plhs == $prhs"
case NotEqual => s"$plhs != $prhs"
case Greater => s"$plhs > $prhs"
case GreaterEqual => s"$plhs >= $prhs"
case Less=> s"$plhs < $prhs"
case LessEqual=> s"$plhs <= $prhs"
}
}
@ -783,3 +793,20 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
override def extractLabels: List[String] = lhs.extractLabels ++ rhs.extractLabels
}
case class IfConstant(cond: Constant, ifTrue: Constant, ifFalse: Constant) extends Constant {
override def toIntelString: String = s"if(${cond.toIntelString},${ifTrue.toIntelString},${ifFalse.toIntelString})"
override def toString: String = s"if(${cond.toString},${ifTrue.toString},${ifFalse.toString})"
override def requiredSize: Int = ifTrue.requiredSize max ifFalse.requiredSize
override def isRelatedTo(v: Thing): Boolean = cond.isRelatedTo(v) || ifTrue.isRelatedTo(v) || ifFalse.isRelatedTo(v)
override def refersTo(name: String): Boolean = cond.refersTo(name) || ifTrue.refersTo(name) || ifFalse.refersTo(name)
override def extractLabels: List[String] = cond.extractLabels ++ ifTrue.extractLabels ++ ifFalse.extractLabels
override def rootThingName: String = "?"
}

View File

@ -11,6 +11,7 @@ import millfork.node._
import millfork.output._
import org.apache.commons.lang3.StringUtils
import java.nio.file.Files
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
@ -116,7 +117,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
callGraph: CallGraph,
allocators: Map[String, VariableAllocator],
options: CompilationOptions,
onEachVariable: (String, (Int, Int)) => Unit,
onEachVariable: (String, (String, Int)) => Unit,
onEachVariableEnd: (String, (Char, Int)) => Unit,
pass: Int,
forZpOnly: Boolean): Unit = {
if (forZpOnly && !options.platform.hasZeroPage) {
@ -182,7 +184,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val addr =
allocators(bank).allocateBytes(bank0, callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Zeropage, alignment = m.alignment)
if (log.traceEnabled) log.trace("addr $" + addr.toHexString)
onEachVariable(m.name, bank0.index -> addr)
onEachVariable(m.name, bank -> addr)
onEachVariableEnd(m.name, (if (m.isInstanceOf[MfArray])'a' else 'v') -> (addr + m.sizeInBytes - 1))
List(
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
)
@ -205,7 +208,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case None => Nil
case Some(addr) =>
if (log.traceEnabled) log.trace("addr $" + addr.toHexString)
onEachVariable(m.name, bank0.index -> addr)
onEachVariable(m.name, bank -> addr)
onEachVariableEnd(m.name, (if (m.isInstanceOf[MfArray])'a' else 'v') -> (addr + m.sizeInBytes - 1))
List(
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
)
@ -216,7 +220,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
} else {
val addr = allocators(bank).allocateBytes(bank0, callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Either, alignment = m.alignment)
if (log.traceEnabled) log.trace("addr $" + addr.toHexString)
onEachVariable(m.name, bank0.index -> addr)
onEachVariable(m.name, bank -> addr)
onEachVariableEnd(m.name, (if (m.isInstanceOf[MfArray])'a' else 'v') -> (addr + m.sizeInBytes - 1))
List(
ConstantThing(graveName, NumericConstant(addr, 2), p)
)
@ -224,7 +229,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
case f: NormalFunction =>
f.environment.allocateVariables(Some(f), mem, callGraph, allocators, options, onEachVariable, pass, forZpOnly)
f.environment.allocateVariables(Some(f), mem, callGraph, allocators, options, onEachVariable, onEachVariableEnd, pass, forZpOnly)
Nil
case _ => Nil
}.toList
@ -290,18 +295,20 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
private def removeVariableImpl(str: String): Unit = {
def extractThingName(fullName: String): String = {
var result = fullName.takeWhile(_ != '.')
if (result.length == fullName.length) return result
val suffix = fullName.drop(result.length)
val ix = fullName.indexOf('.')
if (ix < 0) return fullName
var result = fullName.substring(0, ix)
val suffix = fullName.substring(ix)
if (suffix == ".return" || suffix.startsWith(".return.")) {
result += ".return"
}
result
}
val strWithoutPrefix = str.stripPrefix(prefix)
val toRemove = things.keys.filter { n =>
val baseName = extractThingName(n)
baseName == str || baseName == str.stripPrefix(prefix)
baseName == str || baseName == strWithoutPrefix
}.toSet
removedThings ++= toRemove.map(_.stripPrefix(prefix))
things --= toRemove
@ -332,11 +339,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
t.asInstanceOf[T]
} else {
t match {
case Alias(_, target, deprectated) =>
case Alias(_, target, deprectated, local) =>
if (deprectated && options.flag(CompilationFlag.DeprecationWarning)) {
log.warn(s"Alias `$name` is deprecated, use `$target` instead", position)
}
root.get[T](target)
if (local) get[T](target) else root.get[T](target)
case _ => throw IdentifierHasWrongTypeOfThingException(clazz, name, position)
}
}
@ -360,11 +367,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val t: Thing = things(name)
val clazz = implicitly[Manifest[T]].runtimeClass
t match {
case Alias(_, target, deprectated) =>
case Alias(_, target, deprectated, local) =>
if (deprectated && options.flag(CompilationFlag.DeprecationWarning)) {
log.warn(s"Alias `$name` is deprecated, use `$target` instead")
}
root.maybeGet[T](target)
if (local) maybeGet[T](target) else root.maybeGet[T](target)
case _ =>
if ((t ne null) && clazz.isInstance(t)) {
Some(t.asInstanceOf[T])
@ -388,6 +395,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
@inline
final def identityPage: Constant = maybeGet[MfArray]("identity$").fold(Constant.Zero)(_.toAddress)
def getPointy(name: String): Pointy = {
InitializedMemoryVariable
UninitializedMemoryVariable
@ -549,8 +559,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
BranchingOpcodeMapping(Opcode.BPL, IfFlagClear(ZFlag.S), MOpcode.BPL),
BranchingOpcodeMapping(Opcode.BMI, IfFlagSet(ZFlag.S), MOpcode.BMI)),
None)
val byte_and_pointer$ = StructType("byte_and_pointer$", List(FieldDesc("byte", "zp", None), FieldDesc("pointer", "branch", None)), NoAlignment)
val hudson_transfer$ = StructType("hudson_transfer$", List(FieldDesc("word", "a", None), FieldDesc("word", "b", None), FieldDesc("word", "c", None)), NoAlignment)
val byte_and_pointer$ = StructType("byte_and_pointer$", List(FieldDesc("byte", "zp", false, None), FieldDesc("pointer", "branch", false, None)), NoAlignment)
val hudson_transfer$ = StructType("hudson_transfer$", List(FieldDesc("word", "a", false, None), FieldDesc("word", "b", false, None), FieldDesc("word", "c", false, None)), NoAlignment)
addThing(byte_and_pointer$, None)
addThing(hudson_transfer$, None)
Environment.constOnlyBuiltinFunction.foreach(n => addThing(ConstOnlyCallable(n), None))
@ -715,7 +725,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (name.startsWith(".")) return Some(MemoryAddressConstant(Label(prefix + name)))
vv match {
case Some(m) if m.contains(name) => Some(m(name))
case _ => maybeGet[ConstantThing](name).map(_.value)
case _ => maybeGet[ConstantLikeThing](name).map {
case x: ConstantThing => x.value
case x: FunctionInMemory => x.toAddress
}
}
case IndexedExpression(arrName, index) =>
getPointy(arrName) match {
@ -832,7 +845,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case Some(c) =>
if (c.isProvablyGreaterOrEqualThan(1)) evalImpl(params(1), vv)
else if (c.isProvablyZero) evalImpl(params(2), vv)
else None
else (evalImpl(params(1), vv), evalImpl(params(2), vv)) match {
case (Some(t), Some(f)) => Some(IfConstant(c, t, f))
case _ => None
}
case _ => None
}
} else {
@ -879,15 +895,17 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
constantOperation(MathOperator.Exor, params, vv)
case "||" | "|" =>
constantOperation(MathOperator.Or, params, vv)
case ">" => evalComparisons(params, vv, _ > _)
case "<" => evalComparisons(params, vv, _ < _)
case ">=" => evalComparisons(params, vv, _ >= _)
case "<=" => evalComparisons(params, vv, _ <= _)
case "==" => evalComparisons(params, vv, _ == _)
case ">" => evalComparisons(params, vv, MathOperator.Greater, _ > _)
case "<" => evalComparisons(params, vv, MathOperator.Less,_ < _)
case ">=" => evalComparisons(params, vv, MathOperator.GreaterEqual,_ >= _)
case "<=" => evalComparisons(params, vv, MathOperator.LessEqual,_ <= _)
case "==" => evalComparisons(params, vv, MathOperator.Equal,_ == _)
case "!=" =>
sequence(params.map(p => evalImpl(p, vv))) match {
case Some(List(NumericConstant(n1, _), NumericConstant(n2, _))) =>
Some(if (n1 != n2) Constant.One else Constant.Zero)
case Some(List(c1, c2)) =>
Some(CompoundConstant(MathOperator.NotEqual, c1, c2))
case _ => None
}
case _ =>
@ -942,14 +960,22 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
private def evalComparisons(params: List[Expression], vv: Option[Map[String, Constant]], cond: (Long, Long) => Boolean): Option[Constant] = {
private def evalComparisons(params: List[Expression], vv: Option[Map[String, Constant]], operator: MathOperator.Value, cond: (Long, Long) => Boolean): Option[Constant] = {
if (params.size < 2) return None
val numbers = sequence(params.map{ e =>
evalImpl(e, vv) match {
case Some(NumericConstant(n, _)) => Some(n)
case _ => None
}
val paramsEvaluated = params.map { e =>
evalImpl(e, vv)
}
val numbers = sequence(paramsEvaluated.map {
case Some(NumericConstant(n, _)) => Some(n)
case _ => None
})
if (numbers.isEmpty) {
paramsEvaluated match {
case List(Some(c1), Some(c2)) =>
return Some(CompoundConstant(operator, c1, c2))
case _ =>
}
}
numbers.map { ns =>
if (ns.init.zip(ns.tail).forall(cond.tupled)) Constant.One else Constant.Zero
}
@ -1155,7 +1181,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case s: CompoundVariableType =>
if (s.mutableAlignment ne null) return s.mutableAlignment
var alignment = s.baseAlignment
for( ResolvedFieldDesc(fieldType, _, _) <- s.mutableFieldsWithTypes) {
for( ResolvedFieldDesc(fieldType, _, _, _) <- s.mutableFieldsWithTypes) {
val a = getTypeAlignment(fieldType, path + name)
if (a eq null) return null
alignment = alignment & a
@ -1175,7 +1201,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
else {
val newPath = path + name
var sum = 0
for( ResolvedFieldDesc(fieldType, _, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
for( ResolvedFieldDesc(fieldType, _, _, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
val fieldSize = getTypeSize(fieldType, newPath) * indexTypeAndCount.fold(1)(_._2)
if (fieldSize < 0) return -1
sum = fieldType.alignment.roundSizeUp(sum)
@ -1188,7 +1214,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
val b = get[Type]("byte")
var offset = 0
for( ResolvedFieldDesc(fieldType, fieldName, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
for( ResolvedFieldDesc(fieldType, fieldName, _, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
offset = fieldType.alignment.roundSizeUp(offset)
addThing(ConstantThing(s"$name.$fieldName.offset", NumericConstant(offset, 1), b), None)
offset += getTypeSize(fieldType, newPath) * indexTypeAndCount.fold(1)(_._2)
@ -1201,7 +1227,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
else {
val newPath = path + name
var max = 0
for( ResolvedFieldDesc(fieldType, _, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
for( ResolvedFieldDesc(fieldType, _, _, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
val fieldSize = getTypeSize(fieldType, newPath) * indexTypeAndCount.fold(1)(_._2)
if (fieldSize < 0) return -1
max = max max fieldSize
@ -1211,7 +1237,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
log.error(s"Union `$name` is larger than 255 bytes")
}
val b = get[Type]("byte")
for (ResolvedFieldDesc(fieldType, fieldName, _) <- s.mutableFieldsWithTypes) {
for (ResolvedFieldDesc(fieldType, fieldName, _, _) <- s.mutableFieldsWithTypes) {
addThing(ConstantThing(s"$name.$fieldName.offset", NumericConstant(0, 1), b), None)
}
max
@ -1304,6 +1330,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (hasReturnVariable) {
registerVariable(VariableDeclarationStatement(stmt.name + ".return", stmt.resultType, None, global = true, stack = false, constant = false, volatile = false, register = false, None, None, Set.empty, None), options, isPointy = false)
}
val constants = mutable.MutableList[VariableDeclarationStatement]()
stmt.statements match {
case None =>
stmt.address match {
@ -1326,10 +1353,17 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
case Some(statements) =>
statements.foreach {
case v: VariableDeclarationStatement => env.registerVariable(v, options, pointies(v.name))
case a: ArrayDeclarationStatement => env.registerArray(a, options)
case _ => ()
if (stmt.isMacro) {
statements.foreach {
case v: VariableDeclarationStatement => constants += v
case _ => ()
}
} else {
statements.foreach {
case v: VariableDeclarationStatement => env.registerVariable(v, options, pointies(v.name))
case a: ArrayDeclarationStatement => env.registerArray(a, options)
case _ => ()
}
}
def scanForLabels(statement: Statement): Unit = statement match {
case c: CompoundStatement => c.getChildStatements.foreach(scanForLabels)
@ -1422,6 +1456,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
params.asInstanceOf[AssemblyOrMacroParamSignature],
stmt.assembly,
env,
constants.toList,
executableStatements
)
addThing(mangled, stmt.position)
@ -1456,6 +1491,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
}
if (!stmt.isMacro) {
val alias = Alias("this.function", name, local = true)
env.addThing("this.function", alias, None)
env.expandAlias(alias)
}
}
private def getFunctionPointerType(f: FunctionInMemory) = f.params.types match {
@ -1613,9 +1653,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
addThing(v, stmt.position)
registerAddressConstant(v, stmt.position, options, Some(typ))
val addr = v.toAddress
for(Subvariable(suffix, offset, t, arraySize) <- getSubvariables(typ)) {
for(Subvariable(suffix, offset, vol, t, arraySize) <- getSubvariables(typ)) {
if (arraySize.isDefined) ??? // TODO
val subv = RelativeVariable(v.name + suffix, addr + offset, t, zeropage = zp, None, isVolatile = v.isVolatile)
val subv = RelativeVariable(v.name + suffix, addr + offset, t, zeropage = zp, None, isVolatile = v.isVolatile || vol)
addThing(subv, stmt.position)
registerAddressConstant(subv, stmt.position, options, Some(t))
}
@ -1657,7 +1697,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
List.fill(tt.size)(LiteralExpression(0, 1))
} else {
tt.fields.zip(fieldValues).flatMap {
case (FieldDesc(fieldTypeName, _, count), expr) =>
case (FieldDesc(fieldTypeName, _, _, count), expr) =>
// TODO: handle array fields
if (count.isDefined) ???
extractStructArrayContents(expr, Some(get[Type](fieldTypeName)))
@ -1694,7 +1734,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
List.fill(tt.size)(LiteralExpression(0, 1))
} else {
tt.fields.zip(fieldValues).flatMap {
case (FieldDesc(fieldTypeName, _, count), expr) =>
case (FieldDesc(fieldTypeName, _, _, count), expr) =>
// TODO: handle array fields
if (count.isDefined) ???
extractStructArrayContents(expr, Some(get[Type](fieldTypeName)))
@ -1738,7 +1778,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case IndexedExpression(a, i) =>
constantsThatShouldHaveBeenImportedEarlier.addBinding(a, node.position)
markAsConstantsThatShouldHaveBeenImportedEarlier(i)
case DerefExpression(p, _, _) =>
case DerefExpression(p, _, _, _) =>
markAsConstantsThatShouldHaveBeenImportedEarlier(p)
case DerefDebuggingExpression(p, _) =>
markAsConstantsThatShouldHaveBeenImportedEarlier(p)
@ -1749,6 +1789,38 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def extractArrayContents(contents1: ArrayContents): List[Expression] = contents1 match {
case LiteralContents(xs) => xs
case FileChunkContents(filePath, startE, lengthE) =>
val data = Files.readAllBytes(filePath)
val p = contents1.position
val slice = (eval(startE).map(_.quickSimplify), lengthE.map(l => eval(l).map(_.quickSimplify))) match {
case (Some(NumericConstant(start, _)), Some(Some(NumericConstant(length, _)))) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", p)
Array.fill(length.toInt)(0.toByte)
} else if (data.length < start + length) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset plus length ${start + length}", p)
Array.fill(length.toInt)(0.toByte)
} else {
data.slice(start.toInt, start.toInt + length.toInt)
}
case (Some(NumericConstant(start, _)), None) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", p)
Array[Byte](0)
} else {
data.drop(start.toInt)
}
case (None, Some(Some(_))) =>
log.error(s"Start offset is not a constant", p)
Array[Byte](0)
case (_, Some(None)) =>
log.error(s"Length is not a constant", p)
Array[Byte](0)
case (None, Some(None)) =>
log.error(s"Start offset and length are not constants", p)
Array[Byte](0)
}
slice.map(c => LiteralExpression(c & 0xff, 1)).toList
case CombinedContents(xs) => xs.flatMap(extractArrayContents)
case pc@ProcessedContents("struct", xs: CombinedContents) =>
checkIfArrayContentsAreSimple(xs)
@ -1896,6 +1968,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
def registerArray(stmt: ArrayDeclarationStatement, options: CompilationOptions): Unit = {
new OverflowDetector(this, options).detectOverflow(stmt)
if (options.flag(CompilationFlag.LUnixRelocatableCode) && stmt.alignment.exists(_.isMultiplePages)) {
log.error("Invalid alignment for LUnix code", stmt.position)
}
@ -2057,6 +2130,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
def registerVariable(stmt: VariableDeclarationStatement, options: CompilationOptions, isPointy: Boolean): Unit = {
new OverflowDetector(this, options).detectOverflow(stmt)
val name = stmt.name
val position = stmt.position
if (name == "" || name.contains(".") && !name.contains(".return")) {
@ -2097,7 +2171,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val constantValue = rawConstantValue.fitInto(typ)
if (constantValue.requiredSize > typ.size) log.error(s"`$name` is has an invalid value: not in the range of `$typ`", position)
addThing(ConstantThing(prefix + name, constantValue, typ), stmt.position)
for(Subvariable(suffix, offset, t, arraySize) <- getSubvariables(typ)) {
for(Subvariable(suffix, offset, vol, t, arraySize) <- getSubvariables(typ)) {
if (arraySize.isDefined) {
log.error(s"Constants of type ${t.name} that contains array fields are not supported", stmt.position)
} else {
@ -2178,7 +2252,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
variable match {
case v: StackVariable =>
addThing(localName, v, position)
for (Subvariable(suffix, offset, t, arraySize) <- getSubvariables(v.typ)) {
for (Subvariable(suffix, offset, vol, t, arraySize) <- getSubvariables(v.typ)) {
if (arraySize.isDefined) {
log.error(s"Cannot create a stack variable $localName of compound type ${v.typ.name} that contains an array member", position)
} else {
@ -2187,10 +2261,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
case v: MemoryVariable =>
addThing(localName, v, position)
for (Subvariable(suffix, offset, t, arrayIndexTypeAndSize) <- getSubvariables(v.typ)) {
for (Subvariable(suffix, offset, vol, t, arrayIndexTypeAndSize) <- getSubvariables(v.typ)) {
arrayIndexTypeAndSize match {
case None =>
val subv = RelativeVariable(prefix + localName + suffix, v.toAddress + offset, t, zeropage = v.zeropage, declaredBank = v.declaredBank, isVolatile = v.isVolatile)
val subv = RelativeVariable(prefix + localName + suffix, v.toAddress + offset, t, zeropage = v.zeropage, declaredBank = v.declaredBank, isVolatile = v.isVolatile || vol)
addThing(subv, position)
registerAddressConstant(subv, position, options, Some(t))
case Some((indexType, elemCount)) =>
@ -2202,10 +2276,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case v: VariableInMemory =>
addThing(localName, v, position)
addThing(ConstantThing(v.name + "`", v.toAddress, get[Type]("word")), position)
for (Subvariable(suffix, offset, t, arrayIndexTypeAndSize) <- getSubvariables(v.typ)) {
for (Subvariable(suffix, offset, vol, t, arrayIndexTypeAndSize) <- getSubvariables(v.typ)) {
arrayIndexTypeAndSize match {
case None =>
val subv = RelativeVariable(prefix + localName + suffix, v.toAddress + offset, t, zeropage = v.zeropage, declaredBank = v.declaredBank, isVolatile = v.isVolatile)
val subv = RelativeVariable(prefix + localName + suffix, v.toAddress + offset, t, zeropage = v.zeropage, declaredBank = v.declaredBank, isVolatile = v.isVolatile || vol)
addThing(subv, position)
registerAddressConstant(subv, position, options, Some(t))
case Some((indexType, elemCount)) =>
@ -2218,6 +2292,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
//noinspection NameBooleanParameters
def getSubvariables(typ: Type): List[Subvariable] = {
val b = get[VariableType]("byte")
val w = get[VariableType]("word")
@ -2225,109 +2300,109 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (options.isBigEndian) {
throw new IllegalArgumentException("__reg$type on 6809???")
}
return Subvariable(".lo", 0, b) ::
Subvariable(".hi", 1, b) ::
Subvariable(".loword", 0, w) ::
Subvariable(".loword.lo", 0, b) ::
Subvariable(".loword.hi", 1, b) ::
Subvariable(".b2b3", 2, w) ::
Subvariable(".b2b3.lo", 2, b) ::
Subvariable(".b2b3.hi", 3, b) ::
List.tabulate(typ.size) { i => Subvariable(".b" + i, i, b) }
return Subvariable(".lo", 0, false, b) ::
Subvariable(".hi", 1, false, b) ::
Subvariable(".loword", 0, false, w) ::
Subvariable(".loword.lo", 0, false, b) ::
Subvariable(".loword.hi", 1, false, b) ::
Subvariable(".b2b3", 2, false, w) ::
Subvariable(".b2b3.lo", 2, false, b) ::
Subvariable(".b2b3.hi", 3, false, b) ::
List.tabulate(typ.size) { i => Subvariable(".b" + i, i, false, b) }
}
typ match {
case _: PlainType => typ.size match {
case 2 => if (options.isBigEndian) List(
Subvariable(".lo", 1, b),
Subvariable(".hi", 0, b)
Subvariable(".lo", 1, false, b),
Subvariable(".hi", 0, false, b)
) else List(
Subvariable(".lo", 0, b),
Subvariable(".hi", 1, b))
Subvariable(".lo", 0, false, b),
Subvariable(".hi", 1, false, b))
case 3 => if (options.isBigEndian) List(
Subvariable(".loword", 1, w),
Subvariable(".loword.lo", 2, b),
Subvariable(".loword.hi", 1, b),
Subvariable(".hiword", 0, w),
Subvariable(".hiword.lo", 1, b),
Subvariable(".hiword.hi", 0, b),
Subvariable(".lo", 2, b),
Subvariable(".b0", 2, b),
Subvariable(".b1", 1, b),
Subvariable(".b2", 0, b)
Subvariable(".loword", 1, false, w),
Subvariable(".loword.lo", 2, false, b),
Subvariable(".loword.hi", 1, false, b),
Subvariable(".hiword", 0, false, w),
Subvariable(".hiword.lo", 1, false, b),
Subvariable(".hiword.hi", 0, false, b),
Subvariable(".lo", 2, false, b),
Subvariable(".b0", 2, false, b),
Subvariable(".b1", 1, false, b),
Subvariable(".b2", 0, false, b)
) else List(
Subvariable(".loword", 0, w),
Subvariable(".loword.lo", 0, b),
Subvariable(".loword.hi", 1, b),
Subvariable(".hiword", 1, w),
Subvariable(".hiword.lo", 1, b),
Subvariable(".hiword.hi", 2, b),
Subvariable(".lo", 0, b),
Subvariable(".b0", 0, b),
Subvariable(".b1", 1, b),
Subvariable(".b2", 2, b))
Subvariable(".loword", 0, false, w),
Subvariable(".loword.lo", 0, false, b),
Subvariable(".loword.hi", 1, false, b),
Subvariable(".hiword", 1, false, w),
Subvariable(".hiword.lo", 1, false, b),
Subvariable(".hiword.hi", 2, false, b),
Subvariable(".lo", 0, false, b),
Subvariable(".b0", 0, false, b),
Subvariable(".b1", 1, false, b),
Subvariable(".b2", 2, false, b))
case 4 => if (options.isBigEndian) List(
Subvariable(".loword", 2, w),
Subvariable(".hiword", 0, w),
Subvariable(".loword.lo", 3, b),
Subvariable(".loword.hi", 2, b),
Subvariable(".hiword.lo", 1, b),
Subvariable(".hiword.hi", 0, b),
Subvariable(".lo", 3, b),
Subvariable(".b0", 3, b),
Subvariable(".b1", 2, b),
Subvariable(".b2", 1, b),
Subvariable(".b3", 0, b)
Subvariable(".loword", 2, false, w),
Subvariable(".hiword", 0, false, w),
Subvariable(".loword.lo", 3, false, b),
Subvariable(".loword.hi", 2, false, b),
Subvariable(".hiword.lo", 1, false, b),
Subvariable(".hiword.hi", 0, false, b),
Subvariable(".lo", 3, false, b),
Subvariable(".b0", 3, false, b),
Subvariable(".b1", 2, false, b),
Subvariable(".b2", 1, false, b),
Subvariable(".b3", 0, false, b)
) else List(
Subvariable(".loword", 0, w),
Subvariable(".hiword", 2, w),
Subvariable(".loword.lo", 0, b),
Subvariable(".loword.hi", 1, b),
Subvariable(".hiword.lo", 2, b),
Subvariable(".hiword.hi", 3, b),
Subvariable(".lo", 0, b),
Subvariable(".b0", 0, b),
Subvariable(".b1", 1, b),
Subvariable(".b2", 2, b),
Subvariable(".b3", 3, b)
Subvariable(".loword", 0, false, w),
Subvariable(".hiword", 2, false, w),
Subvariable(".loword.lo", 0, false, b),
Subvariable(".loword.hi", 1, false, b),
Subvariable(".hiword.lo", 2, false, b),
Subvariable(".hiword.hi", 3, false, b),
Subvariable(".lo", 0, false, b),
Subvariable(".b0", 0, false, b),
Subvariable(".b1", 1, false, b),
Subvariable(".b2", 2, false, b),
Subvariable(".b3", 3, false, b)
)
case sz if sz > 4 =>
if (options.isBigEndian) {
Subvariable(".lo", sz - 1, b) ::
Subvariable(".loword", sz - 2, w) ::
Subvariable(".loword.lo", sz - 1, b) ::
Subvariable(".loword.hi", sz - 2, b) ::
List.tabulate(sz){ i => Subvariable(".b" + i, sz - 1 - i, b) }
Subvariable(".lo", sz - 1, false, b) ::
Subvariable(".loword", sz - 2, false, w) ::
Subvariable(".loword.lo", sz - 1, false, b) ::
Subvariable(".loword.hi", sz - 2, false, b) ::
List.tabulate(sz){ i => Subvariable(".b" + i, sz - 1 - i, false, b) }
} else {
Subvariable(".lo", 0, b) ::
Subvariable(".loword", 0, w) ::
Subvariable(".loword.lo", 0, b) ::
Subvariable(".loword.hi", 1, b) ::
List.tabulate(sz){ i => Subvariable(".b" + i, i, b) }
Subvariable(".lo", 0, false, b) ::
Subvariable(".loword", 0, false, w) ::
Subvariable(".loword.lo", 0, false, b) ::
Subvariable(".loword.hi", 1, false, b) ::
List.tabulate(sz){ i => Subvariable(".b" + i, i, false, b) }
}
case _ => Nil
}
case InterruptPointerType | _: FunctionPointerType | _: PointerType => if (options.isBigEndian) List(
Subvariable(".raw", 0, get[VariableType]("pointer")),
Subvariable(".raw.lo", 1, b),
Subvariable(".raw.hi", 0, b),
Subvariable(".lo", 1, b),
Subvariable(".hi", 0, b)
Subvariable(".raw", 0, false, get[VariableType]("pointer")),
Subvariable(".raw.lo", 1, false, b),
Subvariable(".raw.hi", 0, false, b),
Subvariable(".lo", 1, false, b),
Subvariable(".hi", 0, false, b)
) else List(
Subvariable(".raw", 0, get[VariableType]("pointer")),
Subvariable(".raw.lo", 0, b),
Subvariable(".raw.hi", 1, b),
Subvariable(".lo", 0, b),
Subvariable(".hi", 1, b))
Subvariable(".raw", 0, false, get[VariableType]("pointer")),
Subvariable(".raw.lo", 0, false, b),
Subvariable(".raw.hi", 1, false, b),
Subvariable(".lo", 0, false, b),
Subvariable(".hi", 1, false, b))
case s: StructType =>
val builder = new ListBuffer[Subvariable]
var offset = 0
for(ResolvedFieldDesc(typ, fieldName, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
for(ResolvedFieldDesc(typ, fieldName, vol, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
offset = getTypeAlignment(typ, Set()).roundSizeUp(offset)
val suffix = "." + fieldName
builder += Subvariable(suffix, offset, typ, indexTypeAndCount)
builder += Subvariable(suffix, offset, vol, typ, indexTypeAndCount)
if (indexTypeAndCount.isEmpty) {
builder ++= getSubvariables(typ).map {
case Subvariable(innerSuffix, innerOffset, innerType, innerSize) => Subvariable(suffix + innerSuffix, offset + innerOffset, innerType, innerSize)
case Subvariable(innerSuffix, innerOffset, innerVolatile, innerType, innerSize) => Subvariable(suffix + innerSuffix, offset + innerOffset, vol || innerVolatile, innerType, innerSize)
}
}
offset += typ.size * indexTypeAndCount.fold(1)(_._2)
@ -2336,13 +2411,12 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
builder.toList
case s: UnionType =>
val builder = new ListBuffer[Subvariable]
for(FieldDesc(typeName, fieldName, arraySize) <- s.fields) {
val typ = get[VariableType](typeName)
for(ResolvedFieldDesc(typ, fieldName, vol1, indexTypeAndCount) <- s.mutableFieldsWithTypes) {
val suffix = "." + fieldName
builder += Subvariable(suffix, 0, typ)
if (arraySize.isEmpty) {
builder += Subvariable(suffix, 0, vol1, typ)
if (indexTypeAndCount.isEmpty) {
builder ++= getSubvariables(typ).map {
case Subvariable(innerSuffix, innerOffset, innerType, innerSize) => Subvariable(suffix + innerSuffix, innerOffset, innerType, innerSize)
case Subvariable(innerSuffix, innerOffset, vol2, innerType, innerSize) => Subvariable(suffix + innerSuffix, innerOffset, vol1 || vol2, innerType, innerSize)
}
}
}
@ -2419,19 +2493,28 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
private def expandAliases(): Unit = {
val aliasesToAdd = mutable.ListBuffer[Alias]()
things.values.foreach{
case Alias(aliasName, target, deprecated) =>
val prefix = target + "."
things.foreach{
case (thingName, thing) =>
if (thingName.startsWith(prefix)) {
aliasesToAdd += Alias(aliasName + "." + thingName.stripPrefix(prefix), thingName, deprecated)
}
}
case a:Alias => aliasesToAdd ++= expandAliasImpl(a)
case _ => ()
}
aliasesToAdd.foreach(a => things += a.name -> a)
}
private def expandAliasImpl(a: Alias): Seq[Alias] = {
val aliasesToAdd = mutable.ListBuffer[Alias]()
val prefix = a.target + "."
root.things.foreach {
case (thingName, thing) =>
if (thingName.startsWith(prefix)) {
aliasesToAdd += Alias(a.name + "." + thingName.stripPrefix(prefix), thingName, a.deprecated, a.local)
}
}
aliasesToAdd
}
private def expandAlias(a: Alias): Unit = {
expandAliasImpl(a).foreach(a => things += a.name -> a)
}
def fixStructAlignments(): Unit = {
val allStructTypes: Iterable[CompoundVariableType] = things.values.flatMap {
case s@StructType(name, _, _) => Some(s)
@ -2526,11 +2609,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
things.values.foreach {
case st@StructType(_, fields, _) =>
st.mutableFieldsWithTypes = fields.map {
case FieldDesc(tn, name, arraySize) => ResolvedFieldDesc(get[VariableType](tn), name, arraySize.map(getArrayFieldIndexTypeAndSize))
case FieldDesc(tn, name, vol, arraySize) => ResolvedFieldDesc(get[VariableType](tn), name, vol, arraySize.map(getArrayFieldIndexTypeAndSize))
}
case ut@UnionType(_, fields, _) =>
ut.mutableFieldsWithTypes = fields.map {
case FieldDesc(tn, name, arraySize) => ResolvedFieldDesc(get[VariableType](tn), name, arraySize.map(getArrayFieldIndexTypeAndSize))
case FieldDesc(tn, name, vol, arraySize) => ResolvedFieldDesc(get[VariableType](tn), name, vol, arraySize.map(getArrayFieldIndexTypeAndSize))
}
case _ => ()
}
@ -2540,7 +2623,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def collectDeclarations(program: Program, options: CompilationOptions): Unit = {
val b = get[VariableType]("byte")
val v = get[Type]("void")
if (options.flag(CompilationFlag.OptimizeForSonicSpeed)) {
if (options.flag(CompilationFlag.IdentityPage)) {
addThing(InitializedArray("identity$", None, IndexedSeq.tabulate(256)(n => LiteralExpression(n, 1)), declaredBank = None, b, b, readOnly = true, Set.empty, defaultArrayAlignment(options, 256)), None)
}
program.declarations.foreach {
@ -2605,7 +2688,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
if (!things.contains("memory_barrier")) {
things("memory_barrier") = MacroFunction("memory_barrier", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, CpuFamily.forType(options.platform.cpu) match {
things("memory_barrier") = MacroFunction("memory_barrier", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, Nil, 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))
@ -2617,7 +2700,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, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, CpuFamily.forType(options.platform.cpu) match {
things("breakpoint") = MacroFunction("breakpoint", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, Nil, 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))
@ -2625,7 +2708,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case _ => ???
})
} else {
things("breakpoint") = MacroFunction("breakpoint", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, Nil)
things("breakpoint") = MacroFunction("breakpoint", v, AssemblyOrMacroParamSignature(Nil), isInAssembly = true, this, Nil, Nil)
}
}
}
@ -2713,7 +2796,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
nameCheck(index)
case DerefDebuggingExpression(inner, _) =>
nameCheck(inner)
case DerefExpression(inner, _, _) =>
case DerefExpression(inner, _, _, _) =>
nameCheck(inner)
case IndirectFieldExpression(inner, firstIndices, fields) =>
nameCheck(inner)
@ -2745,7 +2828,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def getAliases: Map[String, String] = {
things.values.flatMap {
case Alias(a, b, _) => Some(a -> b)
case Alias(a, b, _, _) => Some(a -> b)
case _ => None
}.toMap ++ parent.map(_.getAliases).getOrElse(Map.empty)
}

View File

@ -0,0 +1,134 @@
package millfork.env
import millfork.{CompilationFlag, CompilationOptions}
import millfork.compiler.{AbstractExpressionCompiler, CompilationContext}
import millfork.error.Logger
import millfork.node._
/**
* @author Karol Stasiak
*/
class OverflowDetector(env: Environment, options: CompilationOptions) {
def this(ctx: CompilationContext) {
this(ctx.env, ctx.options)
}
private def log: Logger = options.log
private def isWord(e: Expression): Boolean =
AbstractExpressionCompiler.getExpressionType(env, log, e) match {
case t: PlainType => t.size == 2
case _ => false
}
private def isWord(typeName: String): Boolean =
env.maybeGet[Thing](typeName) match {
case Some(t: PlainType) => t.size == 2
case _ => false
}
private def isWord(typ: Type): Boolean =
typ match {
case t: PlainType => t.size == 2
case _ => false
}
private def isByte(e: Expression): Boolean =
AbstractExpressionCompiler.getExpressionType(env, log, e) match {
case t: PlainType => t.size == 1
case _ => false
}
def warnConstantOverflow(e: Expression, op: String): Unit = {
if (options.flag(CompilationFlag.ByteOverflowWarning)) {
log.warn(s"Constant byte overflow. Consider wrapping one of the arguments of $op with word( )", e.position)
}
}
def warnDynamicOverflow(e: Expression, op: String): Unit = {
if (options.flag(CompilationFlag.ByteOverflowWarning)) {
log.warn(s"Potential byte overflow. Consider wrapping one of the arguments of $op with word( )", e.position)
}
}
def scanExpression(e: Expression, willBeAssignedToWord: Boolean): Unit = {
if (willBeAssignedToWord) {
e match {
case FunctionCallExpression("<<", List(l, r)) =>
if (isByte(l) && isByte(r)) {
(env.eval(l), env.eval(r)) match {
case (Some(NumericConstant(lc, 1)), Some(NumericConstant(rc, 1))) =>
if (lc >= 0 && rc >= 0 && (lc << rc) > 255) {
warnConstantOverflow(e, "<<")
}
case (_, Some(NumericConstant(0, _))) =>
case _ =>
warnDynamicOverflow(e, "<<")
}
}
case FunctionCallExpression("*", List(l, r)) =>
if (isByte(l) && isByte(r)) {
(env.eval(l), env.eval(r)) match {
case (Some(NumericConstant(lc, 1)), Some(NumericConstant(rc, 1))) =>
if (lc >= 0 && rc >= 0 && (lc * rc) > 255) {
warnConstantOverflow(e, "*")
}
case (_, Some(NumericConstant(0, _))) =>
case (_, Some(NumericConstant(1, _))) =>
case (Some(NumericConstant(0, _)), _) =>
case (Some(NumericConstant(1, _)), _) =>
case _ =>
warnDynamicOverflow(e, "*")
}
}
case FunctionCallExpression("word" | "unsigned16" | "signed16" | "pointer", List(SumExpression(expressions, _))) =>
if (expressions.map(_._2).forall(isByte)) {
}
case _ =>
}
}
e match {
case SumExpression(expressions, decimal) =>
if (willBeAssignedToWord && !decimal && isByte(e)) env.eval(e) match {
case Some(NumericConstant(n, _)) if n < -128 || n > 255 =>
warnConstantOverflow(e, "+")
case _ =>
}
for ((_, e) <- expressions) {
scanExpression(e, willBeAssignedToWord = willBeAssignedToWord)
}
case FunctionCallExpression("word" | "unsigned16" | "signed16" | "pointer", expressions) =>
expressions.foreach(x => scanExpression(x, willBeAssignedToWord = true))
case FunctionCallExpression("|" | "^" | "&" | "not", expressions) =>
expressions.foreach(x => scanExpression(x, willBeAssignedToWord = false))
case FunctionCallExpression(fname, expressions) =>
env.maybeGet[Thing](fname) match {
case Some(f: FunctionInMemory) if f.params.length == expressions.length =>
for ((e, t) <- expressions zip f.params.types) {
scanExpression(e, willBeAssignedToWord = isWord(t))
}
case _ =>
for (e <- expressions) {
scanExpression(e, willBeAssignedToWord = false)
}
}
case _ =>
}
}
def detectOverflow(stmt: Statement): Unit = {
stmt match {
case Assignment(lhs, rhs) =>
if (isWord(lhs)) scanExpression(rhs, willBeAssignedToWord = true)
case v: VariableDeclarationStatement =>
v.initialValue match {
case Some(e) => scanExpression(e, willBeAssignedToWord = isWord(v.typ))
case _ =>
}
case s =>
s.getAllExpressions.foreach(e => scanExpression(e, willBeAssignedToWord = false))
}
}
}

View File

@ -1,7 +1,7 @@
package millfork.env
import millfork.assembly.BranchingOpcodeMapping
import millfork.{CompilationFlag, CompilationOptions, CpuFamily}
import millfork.{CompilationFlag, CompilationOptions, CpuFamily, env}
import millfork.node._
import millfork.output.{MemoryAlignment, NoAlignment}
@ -10,7 +10,7 @@ sealed trait Thing {
def rootName: String = name
}
case class Alias(name: String, target: String, deprecated: Boolean = false) extends Thing
case class Alias(name: String, target: String, deprecated: Boolean = false, local: Boolean = false) extends Thing
sealed trait CallableThing extends Thing
@ -55,7 +55,7 @@ sealed trait VariableType extends Type {
}
case class Subvariable(suffix: String, offset: Int, typ: VariableType, arrayIndexTypeAndSize: Option[(VariableType, Int)] = None)
case class Subvariable(suffix: String, offset: Int, isVolatile: Boolean, typ: VariableType, arrayIndexTypeAndSize: Option[(VariableType, Int)] = None)
case object VoidType extends Type {
def size = 0
@ -69,6 +69,18 @@ case object VoidType extends Type {
override def alignment: MemoryAlignment = NoAlignment
}
case class InvalidType(nonce: Long) extends Type {
def size = 0
def alignedSize = 0
def isSigned = false
override def name = "$invalid"
override def alignment: MemoryAlignment = NoAlignment
}
sealed trait PlainType extends VariableType {
override def isCompatible(other: Type): Boolean = this == other || this.isSubtypeOf(other) || other.isSubtypeOf(this)
@ -248,6 +260,10 @@ sealed trait TypedThing extends Thing {
def typ: Type
}
sealed trait ConstantLikeThing extends TypedThing {
}
sealed trait ThingInMemory extends Thing {
def zeropage: Boolean
@ -490,6 +506,8 @@ sealed trait MangledFunction extends CallableThing {
def interrupt: Boolean
def kernalInterrupt: Boolean
def isConstPure: Boolean
def canBePointedTo: Boolean
@ -504,6 +522,8 @@ case class EmptyFunction(name: String,
override def interrupt = false
override def kernalInterrupt: Boolean = false
override def isConstPure = false
override def canBePointedTo: Boolean = false
@ -514,15 +534,18 @@ case class MacroFunction(name: String,
params: AssemblyOrMacroParamSignature,
isInAssembly: Boolean,
environment: Environment,
constants: List[VariableDeclarationStatement],
code: List[ExecutableStatement]) extends MangledFunction {
override def interrupt = false
override def kernalInterrupt: Boolean = false
override def isConstPure = false
override def canBePointedTo: Boolean = false
}
sealed trait FunctionInMemory extends MangledFunction with ThingInMemory {
sealed trait FunctionInMemory extends MangledFunction with ThingInMemory with TypedThing with VariableLikeThing with ConstantLikeThing {
def environment: Environment
override def isFar(compilationOptions: CompilationOptions): Boolean =
@ -538,6 +561,20 @@ sealed trait FunctionInMemory extends MangledFunction with ThingInMemory {
def optimizationHints: Set[String]
override def hasOptimizationHints: Boolean = optimizationHints.nonEmpty
override lazy val typ: Type = {
if (interrupt || kernalInterrupt) {
InvalidType(name.hashCode())
} else {
val paramType = params.types match {
case Nil => VoidType
case List(t) => t
case _ => InvalidType(params.types.hashCode())
}
val typeName = "function." + paramType.name + ".to." + returnType.name
FunctionPointerType(typeName, paramType.name, returnType.name, Some(paramType), Some(returnType))
}
}
}
case class ExternFunction(name: String,
@ -551,6 +588,8 @@ case class ExternFunction(name: String,
override def interrupt = false
override def kernalInterrupt: Boolean = false
override def isConstPure = false
override def zeropage: Boolean = false
@ -582,7 +621,7 @@ case class NormalFunction(name: String,
override def isVolatile: Boolean = false
}
case class ConstantThing(name: String, value: Constant, typ: Type) extends TypedThing with VariableLikeThing with IndexableThing {
case class ConstantThing(name: String, value: Constant, typ: Type) extends TypedThing with VariableLikeThing with IndexableThing with ConstantLikeThing {
def map(f: Constant => Constant) = ConstantThing("", f(value), typ)
}

View File

@ -48,7 +48,7 @@ abstract class CallGraph(program: Program, log: Logger) {
case f: FunctionDeclarationStatement =>
allFunctions += f.name
allFunctions += f.name + ".trampoline" // TODO: ???
if (f.address.isDefined || f.interrupt) entryPoints += f.name
if (f.address.isDefined || f.interrupt || f.kernalInterrupt) entryPoints += f.name
f.statements.getOrElse(Nil).foreach(s => this.add(Some(f.name), Nil, s))
case s: Statement =>
s.getAllExpressions.foreach(e => add(currentFunction, callingFunctions, e))

View File

@ -17,7 +17,7 @@ abstract class FloatFormat(val name: String, val hasInfinity: Boolean, val manti
val alignment = env.get[Type]("word").alignment
val size = format(0.0).size
StructDefinitionStatement("float." + name,
List.tabulate(size)(i => FieldDesc("byte", "b" + i, None)),
List.tabulate(size)(i => FieldDesc("byte", "b" + i, false, None)),
Some(if (size % 2 == 0) alignment else NoAlignment))
}
}

View File

@ -8,11 +8,13 @@ import millfork.assembly.z80.{NoRegisters, OneRegister, ZOpcode, ZRegisters}
import millfork.env.{Constant, EnumType, ParamPassingConvention, Type, VariableType}
import millfork.output.MemoryAlignment
import java.nio.file.Path
case class Position(moduleName: String, line: Int, column: Int, cursor: Int)
case class FieldDesc(typeName:String, fieldName: String, arraySize: Option[Expression])
case class FieldDesc(typeName:String, fieldName: String, isVolatile: Boolean, arraySize: Option[Expression])
case class ResolvedFieldDesc(typ:VariableType, fieldName: String, arrayIndexTypeAndSize: Option[(VariableType, Int)])
case class ResolvedFieldDesc(typ:VariableType, fieldName: String, isVolatile: Boolean, arrayIndexTypeAndSize: Option[(VariableType, Int)])
sealed trait Node {
var position: Option[Position] = None
@ -171,18 +173,31 @@ case class SumExpression(expressions: List[(Boolean, Expression)], decimal: Bool
case class FunctionCallExpression(functionName: String, expressions: List[Expression]) extends Expression {
override def renameVariable(variable: String, newVariable: String): Expression =
FunctionCallExpression(functionName, expressions.map {
FunctionCallExpression(if (functionName == variable) newVariable else functionName, expressions.map {
_.renameVariable(variable, newVariable)
}).pos(position)
override def replaceVariable(variable: String, actualParam: Expression): Expression =
FunctionCallExpression(functionName, expressions.map {
_.replaceVariable(variable, actualParam)
}).pos(position)
override def replaceIndexedExpression(predicate: IndexedExpression => Boolean, replacement: IndexedExpression => Expression): Expression =
override def replaceVariable(variable: String, actualParam: Expression): Expression = {
if (variable == functionName) {
actualParam match {
case VariableExpression(v) =>
FunctionCallExpression(v, expressions.map {
_.replaceVariable(variable, actualParam)
}).pos(position)
case _ =>
??? // TODO
}
} else {
FunctionCallExpression(functionName, expressions.map {
_.replaceVariable(variable, actualParam)
}).pos(position)
}
}
override def replaceIndexedExpression(predicate: IndexedExpression => Boolean, replacement: IndexedExpression => Expression): Expression =
FunctionCallExpression(functionName, expressions.map {
_.replaceIndexedExpression(predicate, replacement)
}).pos(position)
override def containsVariable(variable: String): Boolean = expressions.exists(_.containsVariable(variable))
override def containsVariable(variable: String): Boolean = variable == functionName || expressions.exists(_.containsVariable(variable))
override def getPointies: Seq[String] = expressions.flatMap(_.getPointies)
override def isPure: Boolean = false // TODO
override def getAllIdentifiers: Set[String] = expressions.map(_.getAllIdentifiers).fold(Set[String]())(_ ++ _) + functionName
@ -225,8 +240,8 @@ object MosNiceFunctionProperty {
case object DoesntConcernD extends NiceFunctionProperty("D")
case object DoesntChangeZpRegister extends NiceFunctionProperty("reg")
case class SetsSourceOfNZ(sourceOfNZ: SourceOfNZ) extends NiceFunctionProperty(sourceOfNZ + "NZ")
case class SetsXTo(value: Int) extends NiceFunctionProperty("Y=" + value)
case class SetsYTo(value: Int) extends NiceFunctionProperty("Z=" + value)
case class SetsXTo(value: Int) extends NiceFunctionProperty("X=" + value)
case class SetsYTo(value: Int) extends NiceFunctionProperty("Y=" + value)
case class SetsATo(value: Int) extends NiceFunctionProperty("A=" + value)
case class Bit0OfA(value: Boolean) extends NiceFunctionProperty("A0=" + value)
case class Bit7OfA(value: Boolean) extends NiceFunctionProperty("A7=" + value)
@ -419,12 +434,12 @@ case class DerefDebuggingExpression(inner: Expression, preferredSize: Int) exten
override def prettyPrint: String = s"¥deref(${inner.prettyPrint})"
}
case class DerefExpression(inner: Expression, offset: Int, targetType: Type) extends LhsExpression {
override def renameVariable(variable: String, newVariable: String): Expression = DerefExpression(inner.renameVariable(variable, newVariable), offset, targetType)
override def replaceVariable(variable: String, actualParam: Expression): Expression = DerefExpression(inner.replaceVariable(variable, actualParam), offset, targetType)
case class DerefExpression(inner: Expression, offset: Int, isVolatile: Boolean, targetType: Type) extends LhsExpression {
override def renameVariable(variable: String, newVariable: String): Expression = DerefExpression(inner.renameVariable(variable, newVariable), offset, isVolatile, targetType)
override def replaceVariable(variable: String, actualParam: Expression): Expression = DerefExpression(inner.replaceVariable(variable, actualParam), offset, isVolatile, targetType)
override def replaceIndexedExpression(predicate: IndexedExpression => Boolean, replacement: IndexedExpression => Expression): Expression =
DerefExpression(inner.replaceIndexedExpression(predicate, replacement), offset, targetType)
DerefExpression(inner.replaceIndexedExpression(predicate, replacement), offset, isVolatile, targetType)
override def containsVariable(variable: String): Boolean = inner.containsVariable(variable)
@ -475,6 +490,11 @@ case class VariableDeclarationStatement(name: String,
override def getAllExpressions: List[Expression] = List(initialValue, address).flatten
override def withChangedBank(bank: String): BankedDeclarationStatement = copy(bank = Some(bank))
def replaceVariableInInitialValue(variable: String, expression: Expression): VariableDeclarationStatement = initialValue match {
case Some(v) => copy(initialValue = Some(v.replaceVariable(variable, expression)))
case None => this
}
}
trait ArrayContents extends Node {
@ -483,6 +503,21 @@ trait ArrayContents extends Node {
def replaceVariable(variableToReplace: String, expression: Expression): ArrayContents
}
case class FileChunkContents(filePath: Path, start: Expression, length: Option[Expression]) extends ArrayContents {
def getAllExpressions(bigEndian: Boolean): List[Expression] = length match {
case Some(l) => List(start, l)
case None => List(start)
}
def renameVariable(variableToRename: String, newVariable: String): ArrayContents =
FileChunkContents(filePath,
start.renameVariable(variableToRename, newVariable),
length.map(_.renameVariable(variableToRename, newVariable)))
def replaceVariable(variableToReplace: String, expression: Expression): ArrayContents =
FileChunkContents(filePath,
start.replaceVariable(variableToReplace, expression),
length.map(_.replaceVariable(variableToReplace, expression)))
}
case class LiteralContents(contents: List[Expression]) extends ArrayContents {
override def getAllExpressions(bigEndian: Boolean): List[Expression] = contents
@ -687,7 +722,7 @@ case class StandardReturnDispatchLabel(labels:List[Expression]) extends ReturnDi
}
case class ReturnDispatchBranch(label: ReturnDispatchLabel, function: Expression, params: List[Expression]) extends Node {
def getAllExpressions: List[Expression] = label.getAllExpressions ++ params
def getAllExpressions: List[Expression] = label.getAllExpressions ++ params :+ function
}
case class ReturnDispatchStatement(indexer: Expression, params: List[LhsExpression], branches: List[ReturnDispatchBranch]) extends ExecutableStatement {
@ -796,7 +831,7 @@ case class MemsetStatement(start: Expression, size: Constant, value: Expression,
}
case class ForEachStatement(variable: String, pointerVariable: Option[String], values: Either[Expression, List[Expression]], body: List[ExecutableStatement]) extends CompoundStatement {
override def getAllExpressions: List[Expression] = VariableExpression(variable) :: (values.fold[List[Expression]](_ => Nil, identity) ++ body.flatMap(_.getAllExpressions))
override def getAllExpressions: List[Expression] = VariableExpression(variable) :: (pointerVariable.map(VariableExpression).toList ++ values.fold[List[Expression]](List(_), identity) ++ body.flatMap(_.getAllExpressions))
override def getChildStatements: Seq[Statement] = body
override def flatMap(f: ExecutableStatement => Option[ExecutableStatement]): Option[ExecutableStatement] = {

View File

@ -42,7 +42,7 @@ object UnusedLocalVariables extends NodeOptimization {
case SumExpression(xs, _) => getAllReadVariables(xs.map(_._2))
case FunctionCallExpression(_, xs) => getAllReadVariables(xs)
case IndexedExpression(arr, index) => arr :: getAllReadVariables(List(index))
case DerefExpression(inner, _, _) => getAllReadVariables(List(inner))
case DerefExpression(inner, _, _, _) => getAllReadVariables(List(inner))
case DerefDebuggingExpression(inner, _) => getAllReadVariables(List(inner))
case IndirectFieldExpression(inner, firstIndices, fields) => getAllReadVariables(List(inner) ++ firstIndices ++ fields.flatMap(_._3))
case SeparateBytesExpression(h, l) => getAllReadVariables(List(h, l))

View File

@ -19,7 +19,12 @@ import scala.collection.mutable.ArrayBuffer
* @author Karol Stasiak
*/
case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, (Int, Int))], breakpoints: List[(Int, Int)])
case class AssemblerOutput(code: Map[String, Array[Byte]],
bankLayoutInFile: BankLayoutInFile,
asm: Array[String],
labels: List[(String, (String, Int))],
endLabels: Map[String, (Char, Int)],
breakpoints: List[(Int, Int)])
abstract class AbstractAssembler[T <: AbstractCode](private val program: Program,
private val rootEnv: Environment,
@ -33,8 +38,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
var initializedVariablesSize: Int = 0
protected val log: Logger = rootEnv.log
val labelMap: mutable.Map[String, (Int, Int)] = mutable.Map()
val unimportantLabelMap: mutable.Map[String, (Int, Int)] = mutable.Map()
val labelMap: mutable.Map[String, (String, Int)] = mutable.Map()
val endLabelMap: mutable.Map[String, (Char, Int)] = mutable.Map()
val unimportantLabelMap: mutable.Map[String, (String, Int)] = mutable.Map()
val mem = new CompiledMemory(platform.bankNumbers.toList, platform.bankFill, platform.isBigEndian, labelMap, log)
val breakpointSet: mutable.Set[(Int, Int)] = mutable.Set()
private val bytesToWriteLater = mutable.ListBuffer[(String, Int, Constant, Option[Position])]()
@ -105,6 +111,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
c match {
case NumericConstant(v, _) => v
case IfConstant(c, t, f) =>
if (deepConstResolve(c) != 0) deepConstResolve(t) else deepConstResolve(f)
case AssertByte(inner) =>
val value = deepConstResolve(inner)
if (value.toByte == value) value else {
@ -115,7 +123,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
try {
if (labelMap.contains(th.name)) return labelMap(th.name)._2
if (labelMap.contains(th.name + "`")) return labelMap(th.name)._2
if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[(Int, Int)](th.name, labelMap(th.name + ".array"))._2
if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[(String, Int)](th.name, labelMap(th.name + ".array"))._2
if (unimportantLabelMap.contains(th.name)) return labelMap(th.name)._2
val x1 = env.maybeGet[ConstantThing](th.name).map(_.value)
val x2 = env.maybeGet[ConstantThing](th.name + "`").map(_.value)
@ -178,6 +186,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case MathOperator.DecimalShl => asDecimal(l, 1 << r, _ * _)
case MathOperator.DecimalShl9 => asDecimal(l, 1 << r, _ * _) & 0x1ff
case MathOperator.DecimalShr => asDecimal(l, 1 << r, _ / _)
case MathOperator.Equal => if (l == r) 1 else 0
case MathOperator.NotEqual => if (l != r) 1 else 0
case MathOperator.Less => if (l < r) 1 else 0
case MathOperator.LessEqual => if (l <= r) 1 else 0
case MathOperator.Greater => if (l > r) 1 else 0
case MathOperator.GreaterEqual => if (l >= r) 1 else 0
case MathOperator.And => l & r
case MathOperator.Exor => l ^ r
case MathOperator.Or => l | r
@ -246,30 +260,38 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case tim: VariableInMemory =>
tim.toAddress match {
case NumericConstant(n, _) =>
val m = mem.banks(tim.bank(options))
val bank = tim.bank(options)
val m = mem.banks(bank)
for (i <- 0 until tim.typ.size) {
m.occupied(i + n.toInt) = true
}
labelMap.put(tim.name, m.index -> n.toInt)
labelMap.put(tim.name, bank -> n.toInt)
endLabelMap.put(tim.name,
(if (tim.isInstanceOf[InitializedMemoryVariable]) 'V' else 'v') ->
(n.toInt + tim.typ.size - 1))
case _ =>
}
case arr: MfArray =>
arr.toAddress match {
case NumericConstant(n, _) =>
val m = mem.banks(arr.bank(options))
val bank = arr.bank(options)
val m = mem.banks(bank)
for (i <- 0 until arr.sizeInBytes) {
m.occupied(i + n.toInt) = true
}
labelMap.put(arr.name, m.index -> n.toInt)
labelMap.put(arr.name, bank -> n.toInt)
endLabelMap.put(arr.name,
(if (arr.isInstanceOf[InitializedArray]) 'A' else 'a') ->
(n.toInt + arr.sizeInBytes - 1))
case _ =>
}
case _ =>
}
}
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 1, forZpOnly = true)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 2, forZpOnly = true)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 3, forZpOnly = true)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, endLabelMap.put, 1, forZpOnly = true)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, endLabelMap.put, 2, forZpOnly = true)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, endLabelMap.put, 3, forZpOnly = true)
var inlinedFunctions = Map[String, List[T]]()
val compiledFunctions = mutable.Map[String, CompiledFunction[T]]()
@ -306,8 +328,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val labelMapImm = labelMap.toMap
val niceFunctionPropertiesImm = niceFunctionProperties.toSet
val extraOptimizedCode = veryLateOptimizations(thisFunctionNiceProperties, options).foldLeft(code) { (c, opt) =>
val ocode = opt.optimize(function, c, OptimizationContext(options, labelMapImm, env.maybeGet[ThingInMemory]("__reg"), niceFunctionPropertiesImm))
if (ocode eq c) code else quickSimplify(ocode)
if (c.length < opt.minimumRequiredLines) {
c
} else {
val ocode = opt.optimize(function, c, OptimizationContext(options, labelMapImm, env.maybeGet[ThingInMemory]("__reg"), env.identityPage, niceFunctionPropertiesImm))
if (ocode eq c) c else quickSimplify(ocode)
}
}
compiledFunctions(f) = NormalCompiledFunction(
function.declaredBank.getOrElse(platform.defaultCodeBank),
@ -373,7 +399,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
env.eval(item) match {
case Some(c) =>
for(i <- 0 until elementType.alignedSize) {
writeByte(bank, index, subbyte(c, i, elementType.size))(None)
writeByte(bank, index, subbyte(c, i, elementType.size))(item.position)
bank0.occupied(index) = true
bank0.initialized(index) = true
bank0.writeable(index) = true
@ -394,8 +420,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val index = f.address.get.asInstanceOf[NumericConstant].value.toInt
compiledFunctions(f.name) match {
case NormalCompiledFunction(_, functionCode, _, _, _) =>
labelMap(f.name) = bank0.index -> index
labelMap(f.name) = bank -> index
val end = outputFunction(bank, functionCode, index, assembly, options)
endLabelMap(f.name) = 'F' -> (end - 1)
for (i <- index until end) {
bank0.occupied(index) = true
bank0.initialized(index) = true
@ -443,7 +470,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val size = functionCode.map(_.sizeInBytes).sum
val bank0 = mem.banks(bank)
val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
labelMap(name) = bank -> index
endLabelMap(name) = 'F' -> (index + size - 1)
justAfterCode += bank -> outputFunction(bank, functionCode, index, assembly, options)
case _ =>
}
@ -477,7 +505,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
if (bank != "default") ???
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size + 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = m.alignment)
labelMap(name) = bank0.index -> (index + 1)
labelMap(name) = bank -> (index + 1)
val altName = m.name.stripPrefix(env.prefix) + "`"
val thing = if (name.endsWith(".addr")) env.get[ThingInMemory](name.stripSuffix(".addr")) else env.get[ThingInMemory](name + ".array")
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
@ -528,7 +556,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, thing.sizeInBytes, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
labelMap(name) = bank -> index
endLabelMap(name) = 'A' -> (index + thing.sizeInBytes - 1)
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + thing.sizeInBytes)
@ -540,7 +569,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
env.eval(item) match {
case Some(c) =>
for (i <- 0 until elementType.size) {
writeByte(bank, index, subbyte(c, i, elementType.size))(None)
writeByte(bank, index, subbyte(c, i, elementType.size))(item.position)
index += 1
}
case None =>
@ -558,7 +587,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.alignedSize, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
labelMap(name) = bank -> index
endLabelMap(name) = 'V' -> (index + typ.size - 1)
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + typ.alignedSize)
@ -570,7 +600,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
env.eval(value) match {
case Some(c) =>
for (i <- 0 until typ.size) {
writeByte(bank, index, subbyte(c, i, typ.size))(None)
writeByte(bank, index, subbyte(c, i, typ.size))(value.position)
assembly.append(" " + bytePseudoopcode + " " + subbyte(c, i, typ.size).quickSimplify)
index += 1
}
@ -599,9 +629,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
if (size < 0) log.fatal("Negative writable memory size. It's a compiler bug.")
val ivAddr = codeAllocators(ivBank).allocateBytes(ib, options, size, initialized = true, writeable = false, AllocationLocation.High, NoAlignment)
unimportantLabelMap += "__rwdata_init_start" -> (ib.index -> ivAddr)
unimportantLabelMap += "__rwdata_init_end" -> (ib.index -> (ivAddr + size))
unimportantLabelMap += "__rwdata_size" -> (ib.index -> size)
unimportantLabelMap += "__rwdata_init_start" -> (ivBank -> ivAddr)
unimportantLabelMap += "__rwdata_init_end" -> (ivBank -> (ivAddr + size))
unimportantLabelMap += "__rwdata_size" -> (ivBank -> size)
for (i <- 0 until size) {
ib.output(ivAddr + i) = db.output(rwDataStart + i)
db.outputted(rwDataStart + i) = true
@ -632,37 +662,38 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
variableAllocators("default").notifyAboutEndOfData(rwDataEnd)
}
variableAllocators.foreach { case (b, a) => a.notifyAboutEndOfCode(justAfterCode(b)) }
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 2, forZpOnly = false)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 3, forZpOnly = false)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, endLabelMap.put, 2, forZpOnly = false)
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, endLabelMap.put, 3, forZpOnly = false)
val defaultBank = mem.banks("default").index
if (platform.freeZpBytes.nonEmpty) {
// TODO: reimplement indexOf/LastIndexOf for speed
val zpUsageOffset = platform.freeZpBytes.min
val zeropageOccupation = zpOccupied.slice(zpUsageOffset, platform.freeZpBytes.max + 1)
unimportantLabelMap += "__zeropage_usage" -> (defaultBank, zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1)
unimportantLabelMap += "__zeropage_first" -> (defaultBank, zpUsageOffset + (zeropageOccupation.indexOf(true) max 0))
unimportantLabelMap += "__zeropage_last" -> (defaultBank, zpUsageOffset + (zeropageOccupation.lastIndexOf(true) max 0))
unimportantLabelMap += "__zeropage_end" -> (defaultBank, zpUsageOffset + zeropageOccupation.lastIndexOf(true) + 1)
unimportantLabelMap += "__zeropage_usage" -> ("default", zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1)
unimportantLabelMap += "__zeropage_first" -> ("default", zpUsageOffset + (zeropageOccupation.indexOf(true) max 0))
unimportantLabelMap += "__zeropage_last" -> ("default", zpUsageOffset + (zeropageOccupation.lastIndexOf(true) max 0))
unimportantLabelMap += "__zeropage_end" -> ("default", zpUsageOffset + zeropageOccupation.lastIndexOf(true) + 1)
} else {
unimportantLabelMap += "__zeropage_usage" -> (defaultBank -> 0)
unimportantLabelMap += "__zeropage_first" -> (defaultBank -> 3)
unimportantLabelMap += "__zeropage_last" -> (defaultBank -> 2)
unimportantLabelMap += "__zeropage_end" -> (defaultBank -> 3)
unimportantLabelMap += "__zeropage_usage" -> ("default" -> 0)
unimportantLabelMap += "__zeropage_first" -> ("default" -> 3)
unimportantLabelMap += "__zeropage_last" -> ("default" -> 2)
unimportantLabelMap += "__zeropage_end" -> ("default" -> 3)
}
unimportantLabelMap += "__rwdata_start" -> (defaultBank -> rwDataStart)
unimportantLabelMap += "__rwdata_end" -> (defaultBank -> rwDataEnd)
unimportantLabelMap += "__heap_start" -> (defaultBank -> variableAllocators("default").heapStart)
unimportantLabelMap += "__rwdata_start" -> ("default" -> rwDataStart)
unimportantLabelMap += "__rwdata_end" -> ("default" -> rwDataEnd)
unimportantLabelMap += "__heap_start" -> ("default" -> variableAllocators("default").heapStart)
for (segment <- platform.bankNumbers.keys) {
val variableAllocator = options.platform.variableAllocators(segment)
val codeAllocator = options.platform.codeAllocators(segment)
unimportantLabelMap += s"segment.$segment.start" -> (defaultBank -> codeAllocator.startAt)
unimportantLabelMap += s"segment.$segment.codeend" -> (defaultBank -> (codeAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.datastart" -> (defaultBank -> variableAllocator.startAt)
unimportantLabelMap += s"segment.$segment.heapstart" -> (defaultBank -> variableAllocator.heapStart)
unimportantLabelMap += s"segment.$segment.end" -> (defaultBank -> (variableAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.length" -> (defaultBank -> (variableAllocator.endBefore - codeAllocator.startAt))
unimportantLabelMap += s"segment.$segment.bank" -> (defaultBank -> platform.bankNumbers(segment))
unimportantLabelMap += s"segment.$segment.fill" -> (defaultBank -> platform.bankFill(segment))
unimportantLabelMap += s"segment.$segment.start" -> ("default" -> codeAllocator.startAt)
unimportantLabelMap += s"segment.$segment.codeend" -> ("default" -> (codeAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.datastart" -> ("default" -> variableAllocator.startAt)
unimportantLabelMap += s"segment.$segment.heapstart" -> ("default" -> variableAllocator.heapStart)
unimportantLabelMap += s"segment.$segment.end" -> ("default" -> (variableAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.length" -> ("default" -> (variableAllocator.endBefore - codeAllocator.startAt))
unimportantLabelMap += s"segment.$segment.bank" -> ("default" -> platform.bankNumbers(segment))
unimportantLabelMap += s"segment.$segment.fill" -> ("default" -> platform.bankFill(segment))
}
env = rootEnv.allThings
@ -692,9 +723,35 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
for (bank <- mem.banks.keys.toSeq.sorted) {
val b = mem.banks(bank)
if (b.start >= 0 && b.end >= 0) {
val (allotedStart, allotedEndBefore) = {
val al1 = variableAllocators(bank)
val al2 = codeAllocators(bank)
(al1.startAt max al2.startAt) -> (al1.endBefore max al2.endBefore)
}
val holes = (b.start to b.end).count(i => !b.occupied(i))
val freeMemory = (allotedStart until allotedEndBefore).count(i => !b.occupied(i))
val largeHoles = {
val builder = Seq.newBuilder[(Int, Int)]
var i = b.start
while(i < b.end) {
if (b.occupied(i)) {
i+= 1
} else {
val holeSize = b.occupied.segmentLength(!_, i)
if (holeSize >= 4) {
builder += i -> (i + holeSize - 1)
}
i += holeSize
}
}
builder.result()
}
val size = b.end - b.start + 1
log.info(f"Segment ${bank}%s: $$${b.start}%04x-$$${b.end}%04x, size: $size%d B ($holes%d B unused)")
log.info(f"Segment ${bank}%s: $$${b.start}%04x-$$${b.end}%04x, size: $size%d B ($freeMemory B free, of which unused gaps $holes%d B)")
if (largeHoles.nonEmpty) {
log.info("Unused gaps: " + largeHoles.map{ case (s,e) => f"$$$s%04x-$$$e%04x"}.mkString(", "))
}
// TODO: report holes:
}
}
if (platform.cpuFamily == CpuFamily.M6502) {
@ -706,19 +763,29 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val allLabelList = labelMap.toList ++ unimportantLabelMap.toList
allLabelList.sorted.foreach { case (l, (_, v)) =>
assembly += f"$l%-30s = $$$v%04X"
endLabelMap.get(l) match {
case Some((category, end)) =>
assembly += f"$l%-30s = $$$v%04X ;-$$$end%04X $category%s"
case _ =>
assembly += f"$l%-30s = $$$v%04X"
}
}
allLabelList.sortBy { case (a, (_, v)) => v -> a }.foreach { case (l, (_, v)) =>
assembly += f" ; $$$v%04X = $l%s"
}
var bankLayoutInFile = BankLayoutInFile.empty
// TODO:
val code = (platform.outputStyle match {
case OutputStyle.Single | OutputStyle.LUnix => List("default")
case OutputStyle.PerBank => platform.bankNumbers.keys.toList
}).map{b =>
val outputPackager = platform.outputPackagers.getOrElse(b, platform.defaultOutputPackager)
b -> outputPackager.packageOutput(mem, b)
val tuple = outputPackager.packageOutputAndLayout(mem, b)
if (b == "default") {
bankLayoutInFile = tuple._2
}
b -> tuple._1
}.toMap
if (options.flag(CompilationFlag.DataMissingInOutputWarning)) {
for (bank <- mem.banks.keys.toSeq.sorted) {
@ -729,7 +796,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
}
}
AssemblerOutput(code, assembly.toArray, labelMap.toList, breakpointSet.toList.sorted)
AssemblerOutput(code, bankLayoutInFile, assembly.toArray, labelMap.toList, endLabelMap.toMap, breakpointSet.toList.sorted)
}
private def printArrayToAssemblyOutput(assembly: ArrayBuffer[String], name: String, elementType: Type, items: Seq[Expression]): Unit = {
@ -762,13 +829,13 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
}
def injectLabels(labelMap: Map[String, (Int, Int)], code: List[T]): List[T]
def injectLabels(labelMap: Map[String, (String, Int)], code: List[T]): List[T]
private def compileFunction(f: NormalFunction,
optimizations: Seq[AssemblyOptimization[T]],
options: CompilationOptions,
inlinedFunctions: Map[String, List[T]],
labelMap: Map[String, (Int, Int)],
labelMap: Map[String, (String, Int)],
niceFunctionProperties: Set[(NiceFunctionProperty, String)]): List[T] = {
log.debug("Compiling: " + f.name, f.position)
val unoptimized: List[T] =
@ -779,8 +846,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
unoptimizedCodeSize += unoptimized.map(_.sizeInBytes).sum
// unoptimized.foreach(l => log.trace(l.toString))
val code = optimizations.foldLeft(quickSimplify(unoptimized)) { (c, opt) =>
val ocode = opt.optimize(f, c, OptimizationContext(options, labelMap, env.maybeGet[ThingInMemory]("__reg"), niceFunctionProperties))
if (ocode eq c) ocode else quickSimplify(ocode)
if (c.length < opt.minimumRequiredLines) {
c
} else {
val ocode = opt.optimize(f, c, OptimizationContext(options, labelMap, env.maybeGet[ThingInMemory]("__reg"), env.identityPage, niceFunctionProperties))
if (ocode eq c) ocode else quickSimplify(ocode)
}
}
performFinalOptimizationPass(f, optimizations.nonEmpty, options, code)
}

Some files were not shown because too many files have changed in this diff Show More