mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
283
Commits
v12.1
..
v12.2-beta2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02a1deb1e0 | ||
|
|
3ba8e5de10 | ||
|
|
819d1b4af7 | ||
|
|
cdd3d37376 | ||
|
|
d0bc01d618 | ||
|
|
ffab73334a | ||
|
|
83aeb8846c | ||
|
|
3a2087310b | ||
|
|
61b616701e | ||
|
|
bddf872906 | ||
|
|
48214a6fdb | ||
|
|
b7340856df | ||
|
|
2685854113 | ||
|
|
ad92f39469 | ||
|
|
d3fe041e01 | ||
|
|
f32bac5004 | ||
|
|
9a8e4ac105 | ||
|
|
78857e5c22 | ||
|
|
1bb828875d | ||
|
|
82aa67ffda | ||
|
|
7c44835772 | ||
|
|
8b9c63fe90 | ||
|
|
e77fcc8c16 | ||
|
|
08c12131d4 | ||
|
|
5a3ddf705a | ||
|
|
20b29fb9b1 | ||
|
|
fd3d59e159 | ||
|
|
6d1dfd2a09 | ||
|
|
7095b14bcb | ||
|
|
1fe5ff18b1 | ||
|
|
793b2a703b | ||
|
|
53948be68f | ||
|
|
3d0dbb71ce | ||
|
|
3dfa804e52 | ||
|
|
fd93d103f6 | ||
|
|
28977996e2 | ||
|
|
e867cb88a7 | ||
|
|
1ba8dfcc8b | ||
|
|
cd2d30c2c7 | ||
|
|
a47d2fa40d | ||
|
|
6be70af79f | ||
|
|
15a28814d5 | ||
|
|
049acf0e98 | ||
|
|
df0d9e567f | ||
|
|
78920aa115 | ||
|
|
1bb9ad5f46 | ||
|
|
1f30a06429 | ||
|
|
5560c342db | ||
|
|
e073c007ad | ||
|
|
9d1fa856d4 | ||
|
|
7a26bb972f | ||
|
|
e72a4723f9 | ||
|
|
96f0d112d2 | ||
|
|
eac1b64f81 | ||
|
|
0011dd8f4c | ||
|
|
5ce463d133 | ||
|
|
5926473281 | ||
|
|
0a1d06b7f2 | ||
|
|
f98e7d6ad9 | ||
|
|
37896aeed4 | ||
|
|
b175dd0265 | ||
|
|
310d17d8bb | ||
|
|
371bd4b120 | ||
|
|
3965d77813 | ||
|
|
e0ed761661 | ||
|
|
51d5261776 | ||
|
|
b85ac38b3b | ||
|
|
6ff9153d9b | ||
|
|
e0d2e407e4 | ||
|
|
0117ab410b | ||
|
|
b78a0cf076 | ||
|
|
722d52c939 | ||
|
|
162e5ffe3c | ||
|
|
7383ffae46 | ||
|
|
1b4635d27e | ||
|
|
1c376aa9d1 | ||
|
|
91ff17f666 | ||
|
|
5a81021ddc | ||
|
|
9fdd9254f1 | ||
|
|
ae4c4ca160 | ||
|
|
7ab4880c4a | ||
|
|
5c16a40f33 | ||
|
|
6f9f31cd8a | ||
|
|
7cd70acc01 | ||
|
|
2d3c20d347 | ||
|
|
b05bc9ac0e | ||
|
|
2589f953c3 | ||
|
|
a40f9de9e6 | ||
|
|
e7491a56ef | ||
|
|
2bf5d1406d | ||
|
|
c32e7ee57b | ||
|
|
a633b5a4b5 | ||
|
|
a5c078be14 | ||
|
|
547d21a42b | ||
|
|
e2e0992d02 | ||
|
|
10fd321537 | ||
|
|
fb26a7af3b | ||
|
|
8592a3a200 | ||
|
|
930697dad5 | ||
|
|
a60ba27bad | ||
|
|
87480b150a | ||
|
|
0cb9d408ff | ||
|
|
c784fd03fd | ||
|
|
5dcc11d67e | ||
|
|
e36a589e23 | ||
|
|
fe6a8f63a0 | ||
|
|
60e2a38060 | ||
|
|
d808818506 | ||
|
|
0f72bafeae | ||
|
|
c794d2a50b | ||
|
|
db84c375f1 | ||
|
|
03fa6422b3 | ||
|
|
ad34633ca9 | ||
|
|
d5eef79892 | ||
|
|
d5a53c5d21 | ||
|
|
bd51296a33 | ||
|
|
c0103f149e | ||
|
|
86174e49c9 | ||
|
|
0be336c8a8 | ||
|
|
d836f48f38 | ||
|
|
952d7b3162 | ||
|
|
5638eb1c53 | ||
|
|
d3f19bb438 | ||
|
|
be60021edc | ||
|
|
b2ed3b5934 | ||
|
|
59d0f7d49c | ||
|
|
47700918cf | ||
|
|
db7edde2aa | ||
|
|
e2702d3e22 | ||
|
|
74475d023b | ||
|
|
9ae0623ad0 | ||
|
|
33b34b990e | ||
|
|
6dc96bb8f0 | ||
|
|
aeca6350da | ||
|
|
6a340aa59d | ||
|
|
207650fbc2 | ||
|
|
4fb3df977e | ||
|
|
9f742e1806 | ||
|
|
aca6c22098 | ||
|
|
4b45dae68a | ||
|
|
0b68481fda | ||
|
|
bba08f55de | ||
|
|
9e5df3d2d4 | ||
|
|
5c1aa2307d | ||
|
|
3129f5e071 | ||
|
|
04a8181c05 | ||
|
|
27bf9c9a4c | ||
|
|
7658a0ab12 | ||
|
|
42d3340fe4 | ||
|
|
1100ab0d22 | ||
|
|
96eb3f0fb2 | ||
|
|
1ed618c474 | ||
|
|
e6c26587d6 | ||
|
|
4739bd087a | ||
|
|
92751d9285 | ||
|
|
0792571b28 | ||
|
|
511564dd1e | ||
|
|
3da3d63eb4 | ||
|
|
3c859512a5 | ||
|
|
45c05522d5 | ||
|
|
db417ab9ce | ||
|
|
c17a3aff5c | ||
|
|
6184a5004c | ||
|
|
07772bddfd | ||
|
|
15bf4c7692 | ||
|
|
f38c701658 | ||
|
|
10ce992b35 | ||
|
|
331e2b8f2b | ||
|
|
05093e35c8 | ||
|
|
5fd11ca036 | ||
|
|
5330069b53 | ||
|
|
5408a3ca68 | ||
|
|
cecf49b8e3 | ||
|
|
65c17b920a | ||
|
|
7666b72fea | ||
|
|
bcd12a0fc7 | ||
|
|
dd0828a1fc | ||
|
|
770532a039 | ||
|
|
400149d5a3 | ||
|
|
d6ed40af36 | ||
|
|
570ea50a3b | ||
|
|
04b9f8e749 | ||
|
|
2bd7f3e210 | ||
|
|
00bae6e746 | ||
|
|
f6d5e88f31 | ||
|
|
8d44a1b226 | ||
|
|
a47be61812 | ||
|
|
68226a8fcd | ||
|
|
2016883f6c | ||
|
|
d4dd2f02e8 | ||
|
|
bc959435ec | ||
|
|
e5c6e4b854 | ||
|
|
4294658d71 | ||
|
|
0a7352de99 | ||
|
|
bb907aa2af | ||
|
|
e5d74d55b5 | ||
|
|
c291d7d4a8 | ||
|
|
d2633187a6 | ||
|
|
ca56cf26c5 | ||
|
|
f72bec3c4a | ||
|
|
06b4ec600a | ||
|
|
410e330889 | ||
|
|
11f187bda8 | ||
|
|
a53e7c7050 | ||
|
|
e56ccdbc3a | ||
|
|
51264267ad | ||
|
|
8be0cd3c19 | ||
|
|
decfbfc8cc | ||
|
|
652da4c692 | ||
|
|
ce58f0e759 | ||
|
|
8ba8efbc3e | ||
|
|
276121bfb3 | ||
|
|
5c9c6bd023 | ||
|
|
86f331aa39 | ||
|
|
e61328b6d6 | ||
|
|
9502a6f590 | ||
|
|
01748517ae | ||
|
|
3890191981 | ||
|
|
c20e5b788b | ||
|
|
fb3a001a99 | ||
|
|
04910ffbc0 | ||
|
|
caa991e478 | ||
|
|
f9767a5b1b | ||
|
|
3b68741208 | ||
|
|
b8ef64139a | ||
|
|
1ad9e0fc46 | ||
|
|
ce244906ef | ||
|
|
63ab79157f | ||
|
|
9ac0cfd14c | ||
|
|
02c544bb62 | ||
|
|
998d1ad446 | ||
|
|
89e38c6d42 | ||
|
|
a326c6f079 | ||
|
|
acf70d68ca | ||
|
|
17850d3bc1 | ||
|
|
d03e49a92f | ||
|
|
62c5b531c5 | ||
|
|
8911f58bd2 | ||
|
|
da6e47d422 | ||
|
|
10d80d0274 | ||
|
|
23b9a2792d | ||
|
|
0a52cd2cee | ||
|
|
fedf342cee | ||
|
|
2bafc161d6 | ||
|
|
d0c5e35f0a | ||
|
|
859a02fb0a | ||
|
|
2a53dbc1f3 | ||
|
|
63cba4825a | ||
|
|
08e3652924 | ||
|
|
10796c09c7 | ||
|
|
a3cbf4c3db | ||
|
|
bdbdf6e5cd | ||
|
|
1b5dd980df | ||
|
|
f6b59c6832 | ||
|
|
fe8d904792 | ||
|
|
9736ac19d1 | ||
|
|
14abc1f0b7 | ||
|
|
effc7f1b56 | ||
|
|
093be9f2dd | ||
|
|
9aff85f08a | ||
|
|
f2077cb56f | ||
|
|
2a9ac45cad | ||
|
|
e15452b110 | ||
|
|
a64b586a43 | ||
|
|
2613d45cd5 | ||
|
|
c25fbbccfb | ||
|
|
77f0cf8e89 | ||
|
|
74ca190ce0 | ||
|
|
6a0b22bb64 | ||
|
|
07be7f0154 | ||
|
|
d1383813d2 | ||
|
|
3bd2064c3a | ||
|
|
f95f568319 | ||
|
|
5eb873b722 | ||
|
|
3f181b0ede | ||
|
|
d6c8fc0c61 | ||
|
|
709ced1bca | ||
|
|
3b16d08246 | ||
|
|
b9a6deea25 | ||
|
|
099514a2c0 | ||
|
|
ed02b84f7d | ||
|
|
7d6e67e251 | ||
|
|
4aa364925d |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
name: asm6502-coder
|
||||
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
---
|
||||
|
||||
# 65(C)02 Assembly Coder Skill
|
||||
|
||||
You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
|
||||
|
||||
## Debugging Generated Assembly
|
||||
- The prog8 compiler outputs `*.asm` (assembly source) and (with -asmlist option) also `*.list` (full listing with address/symbols) when compiling. Inspect these to debug generated code, verify optimizations, and trace instruction sequences.
|
||||
|
||||
## Assembler: 64tass Syntax
|
||||
- **NOT ca65/cc65** or other assemblers. Key differences:
|
||||
- `.proc` / `.pend` for procedures (scoping)
|
||||
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
|
||||
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line. Reference them in branches using `+`, `++`, `+++` etc. (first/second/third *upcoming* anonymous forward label) and `-`, `--`, `---` etc. (first/second/third *preceding* anonymous backward label).
|
||||
- Crucial: `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
|
||||
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
|
||||
- When a `+` label is passed, the forward-reference count resets (so `++` then refers to the next one after that new label)
|
||||
- Example:
|
||||
```asm
|
||||
- dex ; backward label '-'
|
||||
bne - ; branch to most recent '-' (the dex above)
|
||||
ldx #5
|
||||
+ dex ; forward label '+'
|
||||
bne + ; branch to this same '+' (forward)
|
||||
sta $400
|
||||
+ lda #0 ; second '+'
|
||||
bne ++ ; branch to the '++' below
|
||||
rts
|
||||
++ inc $d020 ; third forward label
|
||||
```
|
||||
- Data directives: `.byte`, `.word`, `.dword`
|
||||
- Equates: `label = value` (not `label .equ value` or `#define`)
|
||||
- Zero-page variables defined with `=`
|
||||
- `.text` for inline string data
|
||||
|
||||
## Instructions
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
|
||||
- Standard 6502 addressing modes: implied, immediate (`#`), zero-page (`zp`), zero-page,X (`zp,x`), absolute (`abs`), absolute,X (`abs,x`), absolute,Y (`abs,y`), indirect (`(abs)`), indirect,X (`(zp,x)`), indirect,Y (`(zp),y`), relative (branches), accumulator
|
||||
- Branches: `bne`, `beq`, `bmi`, `bpl`, `bcs`, `bcc`, `bvs`, `bvc` (relative, max +127/-128 bytes)
|
||||
- Jumps: `jmp` (absolute or indirect), `jsr`/`rts` (subroutine call/return)
|
||||
- No `push`/`pop` mnemonics — use `pha`/`pla` (byte) and `txa`/`phx`/`plx`/`tay`/`phy`/`ply` for registers
|
||||
|
||||
## 6502 vs 65C02
|
||||
- **CX16 target only**: can use 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `clr`, `ina`, `dea`, `cmp (zp)`, `dec`/`inc abs,x` etc.
|
||||
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra`
|
||||
- Check the target before using 65C02-specific instructions
|
||||
- **6502 / 65C02 instruction reference**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2
|
||||
|
||||
## Calling Convention / Register Conventions
|
||||
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
|
||||
- **X register**: 8-bit, often used for indexing, loop counters
|
||||
- **Y register**: 8-bit, often used for indirect addressing index
|
||||
- **Processor Status (P)**: flags — carry (C), zero (Z), negative/N (sign bit 7), overflow (V), decimal (D), interrupt (I), break (B)
|
||||
- **No caller-saved vs callee-saved convention** — list all modified registers in `clobbers (A, X, Y)` when writing `asmsub`
|
||||
- The CPU stack (SP, $0100-$01FF) is limited (usually ~128 bytes free). Do not overflow it
|
||||
|
||||
## Assembly within Prog8 Programs
|
||||
|
||||
### `%asm {{ }}` blocks
|
||||
- Embed arbitrary 64tass assembly directly in your Prog8 source
|
||||
- Access Prog8 symbols using their prefixed names (see below)
|
||||
- Can be placed inside subroutines or at block level
|
||||
|
||||
### `asmsub` (assembly subroutine)
|
||||
- For kernel (ROM) routines or low-level assembly
|
||||
- Parameters passed via registers: `@A`, `@X`, `@Y`, `@AX` (A low, X high), `@AY` (A low, Y high), `@R0`-`@R15`, `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
|
||||
- Return value: `-> type @register` — also via `@Pz`/`@Pc` for flags
|
||||
- Clobbers: `clobbers (A, X, Y)` — MUST list all modified registers
|
||||
- **Parameter names are documentation only** — use the actual registers in assembly, NOT parameter names
|
||||
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
|
||||
|
||||
Example:
|
||||
```prog8
|
||||
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
|
||||
%asm {{
|
||||
x1 = cx16.r0
|
||||
x2 = cx16.r1
|
||||
lda x1 ; use alias, not "_x1"
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
### `asmsub` parameter annotation reference
|
||||
| Annotation | Register | Size |
|
||||
|------------|----------|------|
|
||||
| `@A` | Accumulator | 8-bit |
|
||||
| `@X` | X register | 8-bit |
|
||||
| `@Y` | Y register | 8-bit |
|
||||
| `@AX` | A (low) + X (high) | 16-bit |
|
||||
| `@AY` | A (low) + Y (high) | 16-bit |
|
||||
| `@R0`-`@R15` | cx16 virtual registers | 16-bit each |
|
||||
| `@FAC1`/`@FAC2` | Floating-point accumulators | 5-byte float |
|
||||
| `@Pc` | Carry flag | bool |
|
||||
| `@Pz` | Zero flag | bool |
|
||||
|
||||
## Accessing Prog8 Symbols from Assembly
|
||||
All Prog8 symbols are prefixed when accessed from assembly:
|
||||
|
||||
| Prefix | Refers to | Example |
|
||||
|--------|-----------|---------|
|
||||
| `p8v_` | Variables, parameters | `p8v_myvar` |
|
||||
| `p8s_` | Subroutines | `p8s_mysub` |
|
||||
| `p8b_` | Blocks | `p8b_myblock` |
|
||||
| `p8c_` | Constants, enum members | `p8c_myconst`, `p8c_MyEnum_Member` |
|
||||
| `p8l_` | Labels | `p8l_mylabel` |
|
||||
| `p8t_` | Struct types | `p8t_MyStruct` |
|
||||
| `p8_` | Other symbols | |
|
||||
|
||||
- **Fully qualified**: e.g., `p8b_myblock.p8v_myvar`, `p8b_myblock.p8s_mysub.p8v_localvar`
|
||||
- Within a `.proc` (subroutine) in your assembly, short names often work (assembler scoping)
|
||||
- **`%option no_symbol_prefixing`**: disables all prefixes. Stdlib modules (`cbm`, `cx16`, `txt`) use this — you can write `cbm.CHROUT` directly
|
||||
- **Split word arrays**: two separate byte arrays — append `_lsb` and `_msb` to the name: `p8v_myarray_lsb`, `p8v_myarray_msb`
|
||||
|
||||
## Zeropage Usage
|
||||
- Do NOT use arbitrary zeropage locations. Only use these predefined scratch variables:
|
||||
- `P8ZP_SCRATCH_B1` (byte)
|
||||
- `P8ZP_SCRATCH_REG` (byte)
|
||||
- `P8ZP_SCRATCH_W1` (word)
|
||||
- `P8ZP_SCRATCH_W2` (word)
|
||||
- `P8ZP_SCRATCH_PTR` (word)
|
||||
- On CX16: virtual registers `cx16.r0`-`cx16.r15` are in zeropage (and their low/high bytes: `cx16.r0L`, `cx16.r0H`, etc.)
|
||||
- Virtual registers `cx16.r0`-`cx16.r15` are available on ALL targets, but only on CX16 in zeropage
|
||||
- Assume the scratch variables are not consecutive in zeropage. The CX16 virtual registers ARE consecutive in memory though.
|
||||
- For additional temporary storage, allocate regular variables in BSS
|
||||
|
||||
## Common 6502 Patterns
|
||||
|
||||
### Looping (downto with BNE)
|
||||
```asm
|
||||
ldx #count
|
||||
loop ; do work here
|
||||
dex
|
||||
bne loop ; loop while X != 0
|
||||
```
|
||||
|
||||
### Indirect indexed read (table of data)
|
||||
```asm
|
||||
ldy #index
|
||||
lda (ptr),y ; read byte at address stored in zp ptr + Y
|
||||
```
|
||||
|
||||
### 16-bit arithmetic (word add)
|
||||
```asm
|
||||
clc
|
||||
lda word1_lo
|
||||
adc word2_lo
|
||||
sta result_lo
|
||||
lda word1_hi
|
||||
adc word2_hi
|
||||
sta result_hi
|
||||
```
|
||||
|
||||
### Calling a Prog8 subroutine from assembly
|
||||
```asm
|
||||
jsr p8s_myblock.p8s_mysub
|
||||
```
|
||||
|
||||
### Reading a word variable
|
||||
```asm
|
||||
lda p8v_myword ; loads LSB
|
||||
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
|
||||
```
|
||||
@@ -0,0 +1,253 @@
|
||||
---
|
||||
name: prog8-coder
|
||||
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
---
|
||||
|
||||
# Prog8 Coder Skill
|
||||
|
||||
You are working with **Prog8** source code (`.p8` files) or its Intermediate Representation (`.p8ir` files). Prog8 targets 8-bit systems (C64, CX16, C128, PET32) with the 6502 CPU, plus a `virtual` target for testing.
|
||||
|
||||
Follow ALL the rules below carefully.
|
||||
|
||||
## General & Setup
|
||||
- A program = a `main` block containing a `start` subroutine entry point, plus optional other subroutines/blocks
|
||||
- Add `%zeropage basicsafe` at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)
|
||||
- Module imports: `%import modulename` — no `as` aliasing. Use the module's defined prefix (e.g., `%import textio` → `txt.xxx`)
|
||||
- **Virtual target for testing**: Use `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir` for fast testing
|
||||
- **Fast syntax check**: `prog8c -check input.p8` — does NOT produce output files (.prg, .asm, etc.), just errors
|
||||
- **Output directory**: `prog8c -out outdir input.p8` (default: same dir as source)
|
||||
- **Compilation outputs**: `*.prg` (program binary), `*.asm` (assembly listing), `*.list` (full listing), `*.p8ir` (IR for VM), `*.vice-mon-list` (Vice debug symbols)
|
||||
- `.p8ir` files contain the **Intermediate Representation** — a sequence of named chunks, each containing typed instructions and virtual registers. This is a target-independent representation of the program, executable by the built-in Virtual Machine via `prog8c -vm file.p8ir`. Useful for debugging the compiler's code generation path without involving 6502 assembly.
|
||||
- **Debugging switches**: `-noopt` (disable optimizations), `-printast1` (parsed AST), `-printast2` (optimized simple AST), `-compareir` (compare IR outputs)
|
||||
- **TODO list**: Check `docs/source/todo.rst` to see what features are NOT yet implemented
|
||||
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
|
||||
- **`sys` module**: always available, no import needed
|
||||
- **CX16 debugging**: Add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`. For emulator: `x16emu -echo iso -run -prg input.prg 2>&1 | grep ...`
|
||||
|
||||
## Datatypes & Variables
|
||||
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
|
||||
- `ubyte`/`uword` = unsigned; `long` = signed 4-byte; `float` = 5-byte MS format; `str` = 0-terminated ubytes (max 255 chars)
|
||||
- **float requires `%import floats`** at top of file, else compiler errors
|
||||
- Arrays: max 256 bytes (512 for split word arrays). For larger data, use `memory()` + pointers
|
||||
- `memory(name, size)` returns a `uword` address to a statically reserved block of memory
|
||||
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable)
|
||||
- Struct fields: only simple types + `str` allowed. NO arrays as fields. `str` in struct = `^^ubyte`
|
||||
- Word/pointer arrays split into LSB/MSB by default. Override with `@nosplit`
|
||||
- **No call stack**: all variables statically allocated. No recursion without manual stack management
|
||||
- Variables zero-initialized (globals at start, locals on subroutine entry)
|
||||
- `@shared` marks variables as "used by external code" (assembly), prevents optimization
|
||||
- `@zp`/`@requirezp`: use sparingly — only for pointers (limited zeropage space)
|
||||
- Pointer-like typed pointers (`^^type`) support C-style scaled arithmetic; `uword` pointers always treat element as 1 byte
|
||||
- `&` = untyped address (uword); `&&` = typed pointer
|
||||
- Available zeropage scratch: `P8ZP_SCRATCH_B1`, `P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, `P8ZP_SCRATCH_PTR` — and cx16 virtual registers R0-R15 on all targets
|
||||
- Virtual registers (`cx16.r0`–`cx16.r15`): global 16-bit, NOT preserved across calls. R12-R15 may be clobbered by long ops. Save/restore in IRQs with `cx16.save_virtual_registers()`/`cx16.restore_virtual_registers()`
|
||||
- Math performance: integer trig (`math.sin8`, `math.cos8`) uses fast LUTs; float trig (`floats.sin`/`cos`) is much slower
|
||||
|
||||
## Recursion & Stack Management
|
||||
No call stack for variable storage — recursion overwrites locals. To handle it:
|
||||
1. **CPU hardware stack**: `push()`/`pushw()`/`pushl()`/`pushf()` and `pop()`/`popw()`/`popl()`/`popf()`. Save/restore locals around recursive calls
|
||||
2. **Software stacks**: `buffers.stack` (uword) / `buffers.smallstack` (ubyte). Both provide `push_b()`/`push_w()` and `pop_b()`/`pop_w()`
|
||||
3. **Iterative rewrite (preferred)**: Many recursive algorithms work as `repeat` loops with explicit bounds — avoids all stack overhead
|
||||
|
||||
## Strings, Arrays & Pointers
|
||||
- `str` / array: max 256 bytes. `long[]` limited to 64 entries (64x4=256). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
|
||||
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size still ≤ 256 bytes
|
||||
- str/array passed as pointer to subroutine (receiving subroutine gets `^^ubyte` or `^^element`)
|
||||
- **No const pointers** or pointer-to-pointer currently supported
|
||||
- **Parsing limitation**: `pointer[index].field` as assignment target needs `^^`: `pointer[index]^^.field = value`
|
||||
- **Pre-allocate buffers**: `str buffer = "." * 50` (empty `""` allocates nothing — `strings.append()` will fail)
|
||||
- **No reassignment**: can't `buffer = "new text"` after declaration. Use `strings.copy()`/`strings.ncopy()`
|
||||
- String concat is expensive on 6502. Prefer separate prints over concatenation
|
||||
- **Efficient buffer iteration**: prefer `ptr++` + `@(ptr)` over `@(buffer + offset)`. Exception: if offset is a `ubyte` (≤ 255), `buffer[offset]` works fine
|
||||
- `@(ptr)` = peek/poke byte. For words: `peekw`/`pokew`, longs: `peekl`/`pokel`, floats: `peekf`/`pokef`, bools: `peekbool`/`pokebool`
|
||||
- Use `len(array)` instead of hardcoded sizes
|
||||
- Array indexing is 0-based: `arr[0]` is first element
|
||||
- Static memory only — real dynamic allocation impossible, but can emulate with a simple arena allocator over a `memory()` slab
|
||||
|
||||
## Logic & Control Flow
|
||||
- Logical operators (short-circuit, bool only): `and`, `or`, `xor`, `not`
|
||||
- In `a and b`: if `a` is false, `b` is NOT evaluated
|
||||
- In `a or b`: if `a` is true, `b` is NOT evaluated
|
||||
- Important when `b` has side effects
|
||||
- Bitwise operators: `&`, `|`, `^`, `~`, `<<`, `>>`
|
||||
- Bit rotation: `rol()`/`ror()` (through carry), `rol2()`/`ror2()` (no carry)
|
||||
- CPU status flag branches: `if_cs`, `if_cc`, `if_z`, `if_nz` (compile to single 6502 branch instructions)
|
||||
- Use `when` with choice blocks instead of multiple `if`
|
||||
- If-expressions for simple value assignments based on a choice
|
||||
- **Optional braces in if/else**: when the `if` or `else` body is a single statement, the `{ }` can be omitted. Place the statement on the next line, indented. Example:
|
||||
```
|
||||
if x < 5
|
||||
txt.print("small")
|
||||
```
|
||||
- `defer` defers statement execution until scope exit
|
||||
- `goto`, labels, jump lists allowed
|
||||
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
|
||||
|
||||
## Loop Constructs
|
||||
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
|
||||
|
||||
### `for` loop — iterate over a range or array
|
||||
- Loop variable **must be declared separately** before the `for` statement
|
||||
- Works with `ubyte`, `byte`, `uword`, `word`, `long`, pointer types (NOT `float`)
|
||||
- Iterates over ranges (`start to end`), descending ranges (`start downto end`), or arrays/strings
|
||||
- Optional `step <constant>` for non-unit step sizes
|
||||
- Loop variable value **after the loop is undefined** — don't rely on it
|
||||
- Descending loops with `downto` usually produce more efficient 6502 code
|
||||
```
|
||||
ubyte i
|
||||
for i in 20 to 155 {
|
||||
; body
|
||||
break ; exit loop
|
||||
continue ; next iteration
|
||||
}
|
||||
|
||||
; descending
|
||||
for i in 155 downto 20 {}
|
||||
for i in 155 to 20 step -1 {}
|
||||
|
||||
; iterate over array elements
|
||||
uword[] fib = [0, 1, 1, 2, 3, 5, 8, 13]
|
||||
uword num
|
||||
for num in fib {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### `while` loop — repeat while condition is true
|
||||
```
|
||||
while condition {
|
||||
; body
|
||||
break
|
||||
continue
|
||||
}
|
||||
```
|
||||
|
||||
### `do`-`until` loop — always executes body at least once
|
||||
```
|
||||
do {
|
||||
; body
|
||||
break
|
||||
continue
|
||||
} until condition
|
||||
```
|
||||
|
||||
### `repeat` loop — repeat a fixed number of times (most efficient)
|
||||
- Most efficient code generation — prefer over `for` when loop variable not needed
|
||||
- Omit count for infinite loop (still supports `break`)
|
||||
```
|
||||
repeat 15 {
|
||||
; body
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
; infinite:
|
||||
repeat {
|
||||
; body
|
||||
break if x==5
|
||||
}
|
||||
```
|
||||
|
||||
### `unroll` loop — compile-time code duplication
|
||||
- Not a real loop — duplicates body N times at compile time
|
||||
- No `break`/`continue` allowed
|
||||
- Only simple statements (assignments, calls) in body
|
||||
- Constant iteration count required
|
||||
```
|
||||
unroll 80 {
|
||||
cx16.VERA_DATA0 = 255
|
||||
}
|
||||
```
|
||||
|
||||
## Subroutines & Return Values
|
||||
- Symbols public by default; use `private` to restrict to block
|
||||
- `inline` keyword for subroutines to suggest inlining
|
||||
- No function overloading (except builtins). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
|
||||
- Can return 0, 1, or multiple values: `a, b, c = routine()`. Use `void` to skip: `void routine()`, `a, void, c = routine()`
|
||||
- Nested subroutines access parent scope variables directly
|
||||
|
||||
## Assembly Subroutines (asmsub)
|
||||
For low-level assembly that gets arguments via registers and returns values in registers.
|
||||
- **Syntax**: `[private] [inline] asmsub subname(params) [clobbers(regs)] [-> returns] { %asm {{ ... }} }`
|
||||
- **Body Restriction**: The body of an `asmsub` **must only contain** a single `%asm {{ ... }}` node. Regular Prog8 statements or nested blocks are NOT allowed.
|
||||
- **Parameters**: `type name @register` (e.g., `ubyte val @A`, `uword addr @AX`, `float f @FAC1`).
|
||||
- **Return Values**: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use).
|
||||
- **Registers**:
|
||||
- **8-bit**: `@A`, `@X`, `@Y`
|
||||
- **16-bit**: `@AX`, `@AY`, `@XY` (register pairs)
|
||||
- **Float**: `@FAC1`, `@FAC2` (Floating Point Accumulators)
|
||||
- **Virtual**: `@R0`–`@R15` (16-bit), `@R0R1`–`@R14R15` (32-bit combined)
|
||||
- **Status Flags (for returns)**: `@Pc` (Carry), `@Pz` (Zero), `@Pv` (Overflow), `@Pn` (Negative)
|
||||
- **Clobbers**: `clobbers (A, X, Y)` — list all hardware registers modified by the routine.
|
||||
- **Parameter names** are for documentation and type checking only. Use the registers in your assembly code.
|
||||
- **Inlining**: `inline asmsub` will paste the assembly code directly at the call site, avoiding `jsr`/`rts` overhead.
|
||||
- **Symbol prefixes** in assembly: `p8v_` (vars), `p8s_` (subs), `p8b_` (blocks), etc.
|
||||
|
||||
## External Subroutines (extsub)
|
||||
Used to call routines at fixed memory addresses (like ROM KERNAL routines or third-party drivers).
|
||||
- **Syntax**: `[private] extsub [@bank <value>] address = subname(params) [clobbers(regs)] [-> returns]`
|
||||
- **Address**: Can be a hex literal (`$C000`) or a constant expression.
|
||||
- **Bank (optional)**: `@bank <integer>` (constant bank) or `@bank <identifier>` (variable bank).
|
||||
- **No Body**: Unlike `asmsub`, `extsub` has no `{ }` body; it just maps a signature to an address.
|
||||
- **Example**:
|
||||
```prog8
|
||||
; CX16 KERNAL CHROUT
|
||||
extsub $FFD2 = chrout(ubyte char @A) clobbers(A, X, Y)
|
||||
|
||||
; Routine in a specific RAM bank
|
||||
extsub @bank 10 $C09F = audio_init() clobbers(A, X, Y) -> bool @Pc
|
||||
```
|
||||
|
||||
## Assembly Programming Details
|
||||
- **Symbol prefixes**: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- **Fully qualified names**: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
|
||||
- **Within a `.proc`**, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
|
||||
- **Split word arrays**: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
|
||||
- **CX16 target**: use 65C02 instructions (STZ, PHX, etc). Others: 6502 only
|
||||
- **Assembly syntax**: 64tass. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- **Instructions** like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`
|
||||
- **Anonymous labels**: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
- **Register Aliases**: It's common to define aliases at the start of an `asmsub`:
|
||||
```prog8
|
||||
asmsub my_routine(uword ptr @AX) {
|
||||
%asm {{
|
||||
ptr_lo = p8zp_scratch_w1
|
||||
ptr_hi = p8zp_scratch_w1+1
|
||||
sta ptr_lo
|
||||
stx ptr_hi
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
## Standard Library
|
||||
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
|
||||
- Online location: https://prog8.readthedocs.io/en/latest/libraries.html#low-fi-variable-and-subroutine-definitions-in-all-available-library-modules they are linked there 1 for each compilation target
|
||||
- Structure: builtin functions, then module sections with variables/constants (`type name`) and subroutines (`name (params) -> returntype`)
|
||||
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`)
|
||||
- Math: `math` module — integer trig (`sin8`, `cos8`) via fast LUTs; `math.rnd()` for random numbers
|
||||
- String conversion: `conv` module (`str_uword`, `str2word`, etc.) — for printing numbers use txt routines instead
|
||||
- Char operations: `strings` module (`isdigit`, `isxdigit`, `isupper`, `islower`, `isletter`, `isspace`, `isprint`) — use these instead of manual ASCII/PETSCII arithmetic
|
||||
- String functions return useful lengths — capture them: `len = strings.copy(dest, src)`, `len = strings.append(buf, text)`, `len = strings.upper(mystr)`
|
||||
|
||||
## Syntax & Formatting
|
||||
- Hex: `$FF` (not `0xFF`); Binary: `%1010` (not `0b1010`). Underscores for readability: `25_000_000`
|
||||
- 4-digit hex `$0000` = uword. No type suffixes (no `0L`). Cast: `expr as type`
|
||||
- Augmented assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
|
||||
- **`;` starts a comment to end of line** — NOT a statement separator. One statement per line
|
||||
- **No `elif`**: use nested `else { if ... }`
|
||||
- Type casting: `expression as type` (e.g., `bytevar as word`). `as` has very low precedence (lower than arithmetic)
|
||||
- **No automatic type widening**: `byte*byte=byte` (overflow possible!). Cast explicitly
|
||||
- **No block scope**: `for`/`if` don't introduce scope. Only subroutines do. Variables in blocks are hoisted to subroutine level
|
||||
- **No bare `{ }` blocks** like C/Java
|
||||
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
|
||||
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
|
||||
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
|
||||
- Avoid `globals.XXXX` — move constants closer to where they're used
|
||||
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
|
||||
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
|
||||
- **No block scope** — variables in `if`/`for`/`repeat` blocks are hoisted to subroutine level
|
||||
- **No reassigning strings** after declaration — use `strings.copy()`/`strings.ncopy()`
|
||||
- **Pre-allocate string buffers** — `""` allocates nothing
|
||||
@@ -0,0 +1,45 @@
|
||||
# EditorConfig for Prog8 projects
|
||||
# Ensures consistent formatting across editors and team members
|
||||
# See https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# All files
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Prog8 source files (.p8)
|
||||
[*.p8]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Assembly files (.asm) - 64tass syntax
|
||||
[*.asm]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Kotlin compiler source files
|
||||
[*.kt]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Documentation files
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rst]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Gradle build files
|
||||
[*.gradle.kts]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Git ignore files
|
||||
[.gitignore]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: build and install recent 64tass
|
||||
run: |
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
sudo make install
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: |
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
.idea/developer-tools.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf/
|
||||
.junie/
|
||||
.junie/memory/
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
@@ -22,6 +24,7 @@ parser/**/*.interp
|
||||
parser/**/*.tokens
|
||||
parser/**/*.java
|
||||
compiler/src/prog8/buildversion/*
|
||||
languageServer/src/prog8/buildversion/*
|
||||
*.py[cod]
|
||||
*.egg
|
||||
*.egg-info
|
||||
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JunieProjectTechnologies">
|
||||
<option name="explicitlyEnabledTechs">
|
||||
<set>
|
||||
<option value="junie.python" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
|
||||
<option name="name" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-5
@@ -7,13 +7,13 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.2" />
|
||||
<option name="languageVersion" value="2.2" />
|
||||
<option name="apiVersion" value="2.3" />
|
||||
<option name="languageVersion" value="2.3" />
|
||||
</component>
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.2.0" />
|
||||
<option name="version" value="2.3.21" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
+10
-10
@@ -1,23 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0" />
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.0/kotlin-stdlib-jdk8-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.0/kotlin-stdlib-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.0/kotlin-stdlib-jdk7-2.3.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+5
-5
@@ -1,11 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/1.0.0/org.eclipse.lsp4j-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/1.0.0/org.eclipse.lsp4j.jsonrpc-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.irmen.ksim65" type="repository">
|
||||
<properties maven-id="com.github.irmen:ksim65:v2.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.1/ksim65-v2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+7
-7
@@ -1,19 +1,19 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" />
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
|
||||
Generated
-1
@@ -2,7 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/beanshell/beanshell.iml" filepath="$PROJECT_DIR$/beanshell/beanshell.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Agent Instructions
|
||||
|
||||
- The file `AGENTS.md` MUST be loaded as the very first step of every session, if it isn't already loaded by the system.
|
||||
- Follow all project-specific guidelines and information provided in `AGENTS.md`.
|
||||
- Ensure all automated tasks and code generations adhere to the language rules and architectural decisions described in `AGENTS.md`.
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"context": {
|
||||
"fileName": "AGENTS.md"
|
||||
},
|
||||
"$version": 3,
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(prog8c *)",
|
||||
"Bash(gradle *)",
|
||||
"Bash(mkdir *)",
|
||||
"Bash(ls *)",
|
||||
"Bash(cat *)",
|
||||
"Bash(find *)",
|
||||
"Bash(sed *)",
|
||||
"Bash(grep *)",
|
||||
"Bash(xargs *)",
|
||||
"Bash(wc *)",
|
||||
"Bash(diff *)",
|
||||
"Bash(paste *)",
|
||||
"Bash(echo *)",
|
||||
"Bash(cp *)",
|
||||
"Bash(time)",
|
||||
"Bash(kotlinc *)",
|
||||
"Bash(python3 *)",
|
||||
"Bash(jar *)",
|
||||
"Bash(od *)",
|
||||
"Bash(xxd *)",
|
||||
"Bash(gcc *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
# CRITICAL RULES - MUST FOLLOW
|
||||
|
||||
## RESPONSES
|
||||
|
||||
- Keep responses concise and to the point - unless the user asks otherwise
|
||||
|
||||
## PLANNING MODE
|
||||
|
||||
- Always ask clarifying questions
|
||||
- Never assume design, tech stack or features
|
||||
- Use deep-dive sub-agents to assist with research
|
||||
- Use deep-dive sub-agents to review the different aspects of your plan before presenting to the user
|
||||
|
||||
## CHANGE / EDIT MODE
|
||||
|
||||
- Never implement features yourself when possible - use sub-agents!
|
||||
- Identify changes from the plan that can be implemented in parallel, and use sub-agents to implement the features efficiently
|
||||
- When using sub-agents to implement features, act as a coordinator only
|
||||
- Use the best model for the task - premium models for complex tasks (like coding) and mid-tier models for simpler tasks, like documentation
|
||||
- After completing features (large or small), always run commands like lint, type check and next build to check code quality
|
||||
|
||||
## Project Overview
|
||||
- This project is a compiler for the Prog8 programming language.
|
||||
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
|
||||
- The compiler has a 6502 code generator backend, and an IR code generator.
|
||||
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
|
||||
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
|
||||
- Prog8 source files have .p8 extension – these are *not* LUA or PICO-8 source files in this case!
|
||||
- Prog8 source files are a "module" that can contain one or more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
|
||||
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
|
||||
- Kotlin version 2.3 is used for the compiler implementation.
|
||||
- Java 17 is used as Java runtime version.
|
||||
- ANTLR4 version 4.13 is used for the parser implementation.
|
||||
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
|
||||
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
|
||||
|
||||
## Compilation Flow (High-Level)
|
||||
```
|
||||
Source → parseMainModule() → processAst() → optimizeAst() → postprocessAst()
|
||||
→ Simple AST → Code Generator → Assembly → .prg
|
||||
```
|
||||
|
||||
**Key phases in `compiler/src/prog8/compiler/Compiler.kt`:**
|
||||
- `parseMainModule()` - Import modules, parse to Compiler AST
|
||||
- `processAst()` - Semantic analysis, constant folding, type casting, validation
|
||||
- `optimizeAst()` - Dead code elimination, inlining, statement optimization
|
||||
- `postprocessAst()` - Memory layout, final transformations
|
||||
- Code generation – Simple AST → IR or 6502 assembly
|
||||
|
||||
**Understanding this order is important:** For example, `ConstantIdentifierReplacer` runs during `processAst()`, so by the time `AstChecker` runs at the end of that phase, identifier references have already been replaced with their actual values.
|
||||
|
||||
### Target Differences
|
||||
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
|
||||
- When writing or understanding assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
|
||||
|
||||
|
||||
## DEBUGGING TIP: Use `-noopt` to isolate problems
|
||||
When investigating a possible code generation problem (both IR and 6502), **FIRST try compiling the program with the `-noopt` switch** to disable most of the compiler optimizations.
|
||||
- **Problem gone with `-noopt`**: Issue is in **optimization phases** (`optimizeAst()`, `UnusedCodeRemover`, `Inliner`, etc.)
|
||||
- **Problem persists with `-noopt`**: Issue is in **parsing, semantic analysis, symboltable, or the regular code generation path**.
|
||||
|
||||
This way you can determine if the problem is caused by a faulty optimization step, or just occurs in the regular code generation path.
|
||||
|
||||
## DEBUGGING TIP: Use `-compareir` to see what changed
|
||||
When investigating optimization-related issues or tracking regressions:
|
||||
```bash
|
||||
# Compile without optimizations (baseline)
|
||||
prog8c -target virtual -noopt -out dir program.p8
|
||||
|
||||
# Compile with optimizations and compare
|
||||
prog8c -target virtual -compareir dir/program_noopt.p8ir program.p8
|
||||
```
|
||||
**Output shows:**
|
||||
- Instruction/chunk/register count changes with percentages
|
||||
- First 10 actual instruction differences
|
||||
- Helps identify which optimization transformed the code
|
||||
|
||||
## DEBUGGING TIP: Use `-vmtrace` to trace execution
|
||||
When debugging VM execution or control flow issues:
|
||||
```bash
|
||||
prog8c -target virtual -vm program.p8ir -vmtrace
|
||||
prog8c -target virtual -emu -vmtrace program.p8
|
||||
```
|
||||
**Output format:** `[chunkName:instructionIndex] instruction`
|
||||
- Shows each executed IR instruction with location
|
||||
- Useful for understanding control flow and finding where execution diverges
|
||||
- **Only works on virtual target**
|
||||
|
||||
## Typical debugging workflow:
|
||||
1. **Quick check:** `-check` for syntax errors
|
||||
2. **Isolate:** `-noopt` to determine if problem is optimizer-related
|
||||
3. **Compare:** `-compareir` to see what instructions changed
|
||||
4. **Trace:** `-vmtrace` to watch actual execution flow
|
||||
5. **Deep dive:** `-printast1` / `-printast2` for compiler internals
|
||||
|
||||
## IMPORTANT: SymbolTable cachedFlat cache
|
||||
The `SymbolTable` class has a **cached `flat` property** for fast symbol lookups.
|
||||
|
||||
**CRITICAL:** If you modify the AST **after** SymbolTable construction, you **MUST** call `symbolTable.resetCachedFlat()` or lookups will fail!
|
||||
|
||||
## IMPORTANT: Virtual Target (IR Codegen) Issues
|
||||
For problems that **ONLY occur with the 'virtual' target**, **ONLY modify these modules**:
|
||||
- `codeGenIntermediate` - IR code generator
|
||||
- `intermediate` - IR representation and file I/O
|
||||
- `virtualmachine` - VM that executes IR code
|
||||
|
||||
**DO NOT modify** `compilerAst`, `simpleAst`, `codeGenCpu6502`, etc. The IR codegen has its own separate handling for symbol tables, AST transformations, and unused code removal.
|
||||
|
||||
## IMPORTANT: Compiler Crashes Must Be Fixed in the Compiler
|
||||
A Kotlin/Java crash in the compiler itself (as opposed to a compilation error in the user's program) indicates a bug in the compiler code. **Such crashes should NEVER be worked around by modifying the Prog8 source file that triggered them.**
|
||||
|
||||
Instead, always fix the bug in the compiler's Kotlin code. The user's program is correct - the compiler needs to handle it properly.
|
||||
|
||||
Steps when encountering a compiler crash:
|
||||
1. Create a minimal reproduction case of the Prog8 source that triggers the crash
|
||||
2. Debug the compiler to find the root cause in the Kotlin code
|
||||
3. Fix the compiler bug, not the user program
|
||||
|
||||
## CRITICAL: NO FORMATTING
|
||||
- DO NOT change indentation and formatting of lines that are not being modified. NEVER run formatters (black, ruff, prettier, etc.) after edits,
|
||||
- .editorconfig handles basic formatting (indentation, line endings, whitespace)
|
||||
- Make ONLY the requested changes, touch nothing else
|
||||
- **NO EMOJI in user documentation**. Do not use emoji or decorative unicode symbols in documentation files. Functional unicode symbols are acceptable when they serve a clear purpose (e.g., → for arrows, ± for plus-minus, × for multiplication, ° for degrees). Avoid decorative emoji like ❌ ✅ ⚠️ 🎉 etc.
|
||||
|
||||
### Code Style Guidelines
|
||||
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
|
||||
|
||||
## Prog8 language information
|
||||
|
||||
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
|
||||
|
||||
## Project Module Descriptions
|
||||
- `compiler` - Main compiler entrypoint (src/prog8/CompilerMain.kt)
|
||||
- `compilerAst` - Complex AST where most optimizations run
|
||||
- `simpleAst` - Simplified AST used by code generator backends
|
||||
- `parser` - ANTLR4 parser implementation
|
||||
- `codeCore` - Core code generation utilities
|
||||
- `codeGenCpu6502` - 6502 assembly code generator backend
|
||||
- `codeGenIntermediate` - Intermediate representation (IR) code generator
|
||||
- `codeGenVirtual` - Virtual machine code generator backend
|
||||
- `codeOptimizers` - Optimization passes
|
||||
- `intermediate` - IR components
|
||||
- `virtualmachine` - VM that executes IR code
|
||||
- `languageServer` - Language Server Protocol implementation
|
||||
- `docs` - Documentation files
|
||||
- `examples` - Example Prog8 programs
|
||||
|
||||
## Key Information
|
||||
- never read the files and directories that are ignored via the .aiignore and .gitignore files (EXCEPT when it is a folder for agent config such as .junie, .opencode, .qwen or .agent)
|
||||
- never perform any git source control write/update/add/commit/branch operations. Read and status operations are allowed.
|
||||
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
## Junie Agent Commitments
|
||||
These instructions apply specifically to the Junie agent:
|
||||
|
||||
* **Code Discovery and Navigation**: Use the `get_file_structure` tool to understand the API and structure of files before reading or editing them.
|
||||
* **Symbol-Aware Searching**: Use the `search_project` tool as the primary method for finding symbol definitions (classes, methods, variables) across the project.
|
||||
* **Project-Wide Refactoring**: Exclusively use the `rename_element` tool for any symbol renames to ensure all references, imports, and documentation are updated correctly.
|
||||
* **Structured Test Execution**: Use the `run_test` tool for rapid, isolated verification of Kotlin logic changes.
|
||||
* **Contextual Exploration**: Use the `open` tool with specific `line_number` parameters to navigate directly to relevant code sections.
|
||||
* **Gradle Console Output**: Always use the `--console=plain` flag with `gradle` commands. Fancy console output (like progress bars) will mess up the coding agent's text interface.
|
||||
|
||||
**Note on Terminal usage**: Junie uses `gradle` and terminal commands primarily for project lifecycle tasks (e.g., `gradle installdist` to re-embed standard library resources), manual compiler verification with `prog8c`, and full regression testing before submission. When using `gradle`, always append `--console=plain`.
|
||||
|
||||
# Dev environment tips
|
||||
|
||||
## Development Workflows
|
||||
|
||||
**1. Testing your own Prog8 programs** (no compiler changes):
|
||||
- No rebuild needed - just run `prog8c` directly
|
||||
- Edit your `.p8` file → compile/run → check stdout output
|
||||
- Example: `prog8c -target virtual -emu myprogram.p8`
|
||||
|
||||
**2. Testing compiler or standard library changes**:
|
||||
- Rebuild required after every change to `.kt`, `.p8`, or `.asm` files in the compiler project
|
||||
- Workflow: edit source → `gradle installdist installshadowdist` → test with `prog8c`
|
||||
- Example: fix bug in `CodeGen6502.kt` → rebuild → `prog8c -target cx16 test.p8`
|
||||
|
||||
## Building and Installing the Compiler
|
||||
- use the system installed gradle command instead of the gradle wrapper.
|
||||
|
||||
### Quick compile check (NO tests)
|
||||
- **After changing compiler Kotlin source (.kt files)**: Use `gradle :compiler:compileKotlin --console=plain` to quickly check for syntax/compile errors
|
||||
- This compiles the compiler but **skips running tests**, this is much faster than `gradle build`
|
||||
- **Note:** This does NOT install the compiler - use `gradle installdist installshadowdist --console=plain` after to actually use your changes
|
||||
|
||||
### When you MUST rebuild AND reinstall the compiler
|
||||
|
||||
**After ANY change to:**
|
||||
1. **Kotlin compiler source code** (`.kt` files in any module, or the `.g4` grammar file)
|
||||
2. **Standard library Prog8 files** (`compiler/res/prog8lib/**/*.p8`)
|
||||
3. **Standard library assembly files** (`compiler/res/prog8lib/**/*.asm`)
|
||||
|
||||
**Run:** `gradle installdist installshadowdist --console=plain`
|
||||
|
||||
**Why:** The standard library files (.p8 and .asm) are embedded into the compiler JAR during the build. Changes to these files are NOT picked up by simply running `prog8c` - you MUST rebuild and reinstall for your changes to take effect.
|
||||
|
||||
**Without this step, your changes will NOT be reflected when running `prog8c`!**
|
||||
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
|
||||
|
||||
### When to run full build with tests
|
||||
- **Before committing changes**: Always run `gradle build` to ensure all tests pass
|
||||
- **After major refactoring**: Run `gradle build` to catch regressions
|
||||
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
|
||||
|
||||
### Build command summary
|
||||
|
||||
| Command | When to use | Time |
|
||||
|---------|-------------|------|
|
||||
| `gradle :compiler:compileKotlin --console=plain` | Quick syntax check (no tests) | ~10-20s |
|
||||
| `gradle installdist installshadowdist --console=plain` | After compiler changes (to use them) | ~10-20s |
|
||||
| `gradle build --console=plain` | Before commits, after major changes | ~45-60s |
|
||||
| `gradle test --tests "*Name*" --console=plain` | Run specific tests | ~5-30s |
|
||||
|
||||
**Note:** Use system `gradle` command, not wrapper. Run `gradle clean` if you suspect stale artifacts.
|
||||
|
||||
## Using the Compiler (prog8c)
|
||||
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
|
||||
- **the `-check` switch performs a quick syntax/semantic check only; it will NOT produce any output files (no .prg, .asm, etc.)**. Use it only for fast error checking during development.
|
||||
- **the `-noopt` switch DISABLES all optimizations** - useful for debugging to determine if a problem is caused by the optimizer. **Optimizations are ENABLED by default** (no flag needed).
|
||||
- **prog8c uses single-dash command line options** (e.g., `-target`, `-noopt`, `-check`), NOT double-dash (`--target` is invalid).
|
||||
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
|
||||
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
|
||||
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the same directory as the source file**.
|
||||
|
||||
### Compilation Output Files
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
- `*.asm` - Generated assembly code from the Prog8 source
|
||||
- `*.list` - Generated full assembly listing file from the Prog8 source
|
||||
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
|
||||
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
|
||||
|
||||
### Execution Examples
|
||||
- `prog8c -target targetname input.p8` - Compile a Prog8 source file "input.p8" for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- Never assume your changes simply work, always test!
|
||||
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
|
||||
|
||||
### Automated Tests (gradle)
|
||||
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
|
||||
- `gradle build --console=plain` - Full build including all tests (about 50s)
|
||||
- `gradle :compiler:test --console=plain` - Run only compiler tests (faster)
|
||||
- `gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
|
||||
|
||||
Note: By default, Gradle only shows failed tests. Passed and skipped tests are silent.
|
||||
|
||||
**⚠️ CRITICAL: Test Filtering Patterns - Read This First!**
|
||||
|
||||
Gradle's `--tests` filter has **strict rules** that are easy to get wrong:
|
||||
|
||||
| Pattern | Example | Works? |
|
||||
|---------|---------|---------------|
|
||||
| **Full class name** | `--tests "prog8tests.compiler.TestOptimization"` | **USE THIS** |
|
||||
| Wildcard at END | `--tests "prog8tests.compiler.Test*"` | Yes |
|
||||
| Wildcard on package | `--tests "prog8tests.compiler.*"` | Yes |
|
||||
| **Wildcard at START** | `--tests "*TestOptimization"` | FAILS |
|
||||
| Test description | `--tests "*Optimization*inline*"` | FAILS |
|
||||
|
||||
**Why the restrictions?** Gradle's `--tests` filter matches **fully qualified class names only**. Wildcards work as **suffixes** (e.g., `Test*`) but NOT as prefixes (e.g., `*Test`). KoTest test names like `"inline multi-value void"` are _descriptions_, not method names, so you cannot filter by them.
|
||||
|
||||
**To find failing tests after a run:**
|
||||
```bash
|
||||
# Check XML results (reliable)
|
||||
grep "<failure" compiler/build/test-results/test/*.xml
|
||||
|
||||
# Or check HTML report (most reliable)
|
||||
cat compiler/build/reports/tests/test/index.html
|
||||
|
||||
# For test compilation errors
|
||||
gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
|
||||
```
|
||||
|
||||
**Additional notes:**
|
||||
- Unit tests use KoTest (FunSpec style)
|
||||
- Tests are in the "compiler" module's `test` directory (and some other modules)
|
||||
- Test config is in root `build.gradle.kts`; tests run in parallel
|
||||
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit`
|
||||
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
|
||||
|
||||
### Manual Verification & Emulators
|
||||
|
||||
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
|
||||
|
||||
**CX16 output verification**: Use `x16emu -echo iso -run -prg input.prg` to echo screen output to stdout **and auto-start the program**. The `-run` flag is **critical**: without it, the program loads but doesn't execute, so you'll see no output. Pipe through `strings` or `grep` to filter: `x16emu -echo iso -run -prg input.prg 2>&1 | grep -E "(PASS|FAIL)"`.
|
||||
**IMPORTANT**: Always add `%encoding iso` at the top of your source file and call `txt.iso()` in `start()`. This prevents PETSCII→ISO charset translation errors that garble uppercase/special characters and make output unreadable:
|
||||
```prog8
|
||||
%encoding iso
|
||||
%import textio
|
||||
main { sub start() { txt.iso(); txt.print("PASS\n") } }
|
||||
```
|
||||
**IMPORTANT: Always use `sys.poweroff_system()` to exit the CX16 emulator cleanly!** Add `sys.poweroff_system()` at the end of your main program block - this exits x16emu automatically in most cases.
|
||||
**Note:** The `sys` module is always available, there is no need to import it ever.
|
||||
|
||||
**Commodore 64 (x64sc)**: `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator. Ignore any errors and warnings, because the emulator doesn't produce any output on STDOUT.
|
||||
|
||||
### actual 6502 CPU simulation tests
|
||||
For high-fidelity functional verification of generated 6502 code without a full emulator, you can use the `ksim65` simulator in your unit tests. This allows executing the compiled machine code and asserting on memory or register states.
|
||||
- Use the `simulate()` extension function on a `CompilationResult`.
|
||||
- See `prog8tests.codegeneration.TestExecution6502` for examples.
|
||||
- Both 6502 and 65C02 CPUs can be simulated.
|
||||
- The simulator supports capturing serial output and handling hardware reset/poweroff signals.
|
||||
- There are various assertion helper methods to test on the state of CPU registers, memory contents, etc.
|
||||
- Using the simulator is much faster and way more controllable than using a full emulator to run the code.
|
||||
- The simulator DOES NOT represent an actual real world machine and has VERY LIMITED hardware devices so it can generally not be used to run full example prorams on.
|
||||
|
||||
## Git Operations for File Moves/Deletes
|
||||
|
||||
**When renaming or moving git-tracked files, ALWAYS use `git mv`:**
|
||||
```bash
|
||||
# CORRECT - preserves git history
|
||||
git mv old/path/File.kt new/path/File.kt
|
||||
|
||||
# WRONG - git sees this as delete + add (loses history)
|
||||
mv old/path/File.kt new/path/File.kt
|
||||
```
|
||||
|
||||
**When deleting git-tracked files, ALWAYS use `git rm`:**
|
||||
```bash
|
||||
# CORRECT - properly stages the deletion
|
||||
git rm path/to/File.kt
|
||||
|
||||
# WRONG - git sees this as unstaged deletion
|
||||
rm path/to/File.kt
|
||||
```
|
||||
|
||||
**Why this matters:** `git mv` and `git rm` properly stage the changes and preserve file history. Plain `mv`/`rm` requires git to detect renames heuristically, which may not always work correctly.
|
||||
|
||||
## TODO Items
|
||||
The file `docs/source/todo.rst` contains a comprehensive list of things that still have to be fixed, implemented, or optimized. **Use this to understand what features are NOT yet available** in the compiler or Prog8 language: if a user asks for something that's on the TODO list, you'll know it's not implemented yet and can explain the limitation.
|
||||
@@ -1,12 +1,20 @@
|
||||
# super simple Makefile to lauch the main gradle targets to build and/or test the prog8 compiler
|
||||
# super simple Makefile to launch the main gradle targets to build and/or test the prog8 compiler
|
||||
|
||||
.PHONY: all test
|
||||
|
||||
# Use system gradle if available, otherwise fall back to gradlew wrapper
|
||||
# Windows uses 'where' and gradlew.bat, Unix uses 'command -v' and ./gradlew
|
||||
ifeq ($(OS),Windows_NT)
|
||||
GRADLE = $(shell where gradle >nul 2>&1 && echo gradle || echo gradlew.bat)
|
||||
else
|
||||
GRADLE = $(shell command -v gradle >/dev/null 2>&1 && echo gradle || echo ./gradlew)
|
||||
endif
|
||||
|
||||
all:
|
||||
gradle installdist installshadowdist
|
||||
$(GRADLE) installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
|
||||
test:
|
||||
gradle build
|
||||
$(GRADLE) build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
|
||||
@@ -65,6 +65,7 @@ What does Prog8 provide?
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
- floating point math is supported on certain targets
|
||||
- access to most Kernal ROM routines as external subroutine definitions you can call normally
|
||||
- tight control over Zeropage usage
|
||||
@@ -205,3 +206,10 @@ Here is a performance comparison with various C compilers for the 6502/C64 so yo
|
||||
an idea of how Prog8 stacks up.
|
||||
|
||||
[comparison](benchmark-c/benchmark.md)
|
||||
|
||||
|
||||
Agent context instructions
|
||||
--------------------------
|
||||
|
||||
[Agent context instructions](AGENTS.md)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/lib/bsh-3.0.0-SNAPSHOT.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,65 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("application")
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
val serverMainClassName = "prog8lsp.MainKt"
|
||||
val applicationName = "prog8-beanshell"
|
||||
|
||||
application {
|
||||
mainClass.set(serverMainClassName)
|
||||
description = "Code completions, diagnostics and more for Prog8"
|
||||
// applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version")
|
||||
applicationDistribution.into("bin") {
|
||||
filePermissions {
|
||||
user {
|
||||
read=true
|
||||
execute=true
|
||||
write=true
|
||||
}
|
||||
other.execute = true
|
||||
group.execute = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(files("lib/bsh-3.0.0-SNAPSHOT.jar"))
|
||||
}
|
||||
|
||||
configurations.forEach { config ->
|
||||
config.resolutionStrategy {
|
||||
preferProjectModules()
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDir("src")
|
||||
resources.srcDir("resources")
|
||||
}
|
||||
|
||||
tasks.startScripts {
|
||||
applicationName = "prog8-beanshell"
|
||||
}
|
||||
|
||||
tasks.register<Exec>("fixFilePermissions") {
|
||||
// When running on macOS or Linux the start script
|
||||
// needs executable permissions to run.
|
||||
|
||||
onlyIf { !System.getProperty("os.name").lowercase().contains("windows") }
|
||||
commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-beanshell")
|
||||
}
|
||||
|
||||
tasks.installDist {
|
||||
finalizedBy("fixFilePermissions")
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
finalizedBy("installDist")
|
||||
}
|
||||
Binary file not shown.
@@ -1,48 +0,0 @@
|
||||
package prog8beanshell
|
||||
|
||||
import java.io.FilterReader
|
||||
import java.io.Reader
|
||||
|
||||
|
||||
class CommandLineReader(val input: Reader): FilterReader(input) {
|
||||
private val normal = 0
|
||||
private val lastCharNL = 1
|
||||
private val sentSemi = 2
|
||||
private var state = lastCharNL
|
||||
|
||||
override fun read(): Int {
|
||||
if (state == sentSemi) {
|
||||
this.state = lastCharNL
|
||||
return 10
|
||||
} else {
|
||||
var b = input.read()
|
||||
while(b==13) b = input.read()
|
||||
|
||||
if (b == 10) {
|
||||
if (this.state == lastCharNL) {
|
||||
b = 59
|
||||
this.state = sentSemi
|
||||
} else {
|
||||
this.state = lastCharNL
|
||||
}
|
||||
} else {
|
||||
this.state = normal
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun read(buff: CharArray, off: Int, len: Int): Int {
|
||||
val b = read()
|
||||
if (b == -1) {
|
||||
return -1
|
||||
} else {
|
||||
buff[off] = b.toChar()
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package prog8beanshell
|
||||
|
||||
import bsh.FileReader
|
||||
import bsh.Interpreter
|
||||
|
||||
|
||||
class BeanshellInterpreter {
|
||||
|
||||
fun run(symbols: Map<String, Any>) {
|
||||
val interpreter = Interpreter(CommandLineReader(FileReader(System.`in`)), System.out, System.err, true)
|
||||
interpreter.setExitOnEOF(false)
|
||||
symbols.forEach { (name, value) -> interpreter.set(name, value) }
|
||||
interpreter.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val i = BeanshellInterpreter()
|
||||
i.run(mapOf(
|
||||
"env" to System.getenv(),
|
||||
"args" to args
|
||||
))
|
||||
}
|
||||
@@ -113,14 +113,14 @@ btree {
|
||||
sub count_node(^^Node r) {
|
||||
count++
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
count_node(r.left)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
count_node(r.right)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,15 +179,15 @@ btree {
|
||||
|
||||
sub process_tree(^^Node r) {
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
process_tree(r.left)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
cx16.r0 = r.value
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
pushw(r)
|
||||
process_tree(r.right)
|
||||
r = sys.popw()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,18 +200,18 @@ btree {
|
||||
sub process_tree(^^Node r, ubyte depth) {
|
||||
cx16.r0 = r.value
|
||||
if r.left!=0 {
|
||||
sys.pushw(r)
|
||||
sys.push(depth)
|
||||
pushw(r)
|
||||
push(depth)
|
||||
process_tree(r.left, depth+1)
|
||||
depth = sys.pop()
|
||||
r = sys.popw()
|
||||
depth = pop()
|
||||
r = popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
sys.pushw(r)
|
||||
sys.push(depth)
|
||||
pushw(r)
|
||||
push(depth)
|
||||
process_tree(r.right, depth+1)
|
||||
depth = sys.pop()
|
||||
r = sys.popw()
|
||||
depth = pop()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ life {
|
||||
math.rndseed(12345,9999)
|
||||
uword offset = STRIDE+1
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in 0 to HEIGHT-1 {
|
||||
repeat HEIGHT {
|
||||
for x in 0 to WIDTH-1 {
|
||||
active_world[offset+x] = math.rnd() & 1
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ queens {
|
||||
if could_place(row, col) {
|
||||
board[row] = col
|
||||
; we need to save the local variables row and col.
|
||||
sys.push(row)
|
||||
sys.push(col)
|
||||
push(row)
|
||||
push(col)
|
||||
continue_running = place_queen(row + 1)
|
||||
; restore the local variables after the recursive call.
|
||||
col = sys.pop()
|
||||
row = sys.pop()
|
||||
col = pop()
|
||||
row = pop()
|
||||
board[row] = 0
|
||||
|
||||
if not continue_running
|
||||
|
||||
+74
-7
@@ -1,11 +1,9 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
|
||||
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.3.0"
|
||||
kotlin("jvm") version "2.3.21"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -14,12 +12,13 @@ allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf()
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
|
||||
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
|
||||
}
|
||||
@@ -32,7 +31,75 @@ allprojects {
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Common Configuration for All Subprojects
|
||||
// ============================================================================
|
||||
// This avoids duplication in each module's build.gradle.kts
|
||||
// ============================================================================
|
||||
|
||||
subprojects {
|
||||
// Common dependency exclusions for all subprojects
|
||||
// Note: antlr4 exclusion is done per-module since parser module needs it
|
||||
configurations.all {
|
||||
exclude(group = "com.ibm.icu", module = "icu4j")
|
||||
}
|
||||
|
||||
// Common test dependencies via Kotest BOM (Bill of Materials)
|
||||
// This manages all Kotest module versions centrally
|
||||
dependencies {
|
||||
testImplementation(platform("io.kotest:kotest-bom:5.9.1"))
|
||||
// implementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
// Common sourceSets configuration
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDir("${project.projectDir}/src")
|
||||
resources.srcDir("${project.projectDir}/res")
|
||||
}
|
||||
test {
|
||||
java.srcDir("${project.projectDir}/test")
|
||||
}
|
||||
}
|
||||
|
||||
// Common test configuration
|
||||
tasks.withType<Test>().configureEach {
|
||||
// Enable JUnit 5 (required for Kotest)
|
||||
useJUnitPlatform()
|
||||
|
||||
// Enable concurrent test execution for Kotest
|
||||
// Set parallelism to number of CPU cores
|
||||
jvmArgs("-Dkotest.framework.parallelism=${Runtime.getRuntime().availableProcessors()}")
|
||||
|
||||
// Disable Kotest autoscan warning
|
||||
jvmArgs("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
|
||||
|
||||
// Enable Gradle's parallel test execution (runs multiple test classes concurrently)
|
||||
// Use 50% of available processors to avoid over-subscription
|
||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
|
||||
// Allow filtering tests via -PtestFilter="*TestName*" from command line
|
||||
// Example: gradle test -PtestFilter="*TestLookup*"
|
||||
val testFilter = project.findProperty("testFilter")?.toString()
|
||||
if(testFilter != null) {
|
||||
filter {
|
||||
includeTestsMatching(testFilter)
|
||||
}
|
||||
}
|
||||
|
||||
// Show test results - only show failures to reduce noise
|
||||
testLogging {
|
||||
events("failed")
|
||||
// Show full exception details for failures
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,12 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
`java-test-fixtures`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// should have no dependencies to other modules
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
}
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("${project.projectDir}/src")
|
||||
}
|
||||
resources {
|
||||
srcDir("${project.projectDir}/res")
|
||||
}
|
||||
}
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
@@ -4,11 +4,14 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
@@ -29,4 +29,23 @@ fun Path.sanitize(): Path {
|
||||
} catch (iox: IOException) {
|
||||
throw FileSystemException(this.toFile()).also { it.initCause(iox) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object SymbolNames {
|
||||
/**
|
||||
* Strips Prog8 symbol prefixes (p8b_, p8t_, p8s_, p8v_, p8l_, p8c_) from a scoped name.
|
||||
* Example: "p8b_plane.p8t_Point" -> "plane.Point"
|
||||
* Used to compare names across different contexts where prefixes may or may not be applied.
|
||||
* See https://github.com/irmen/prog8/issues/198
|
||||
*/
|
||||
fun stripPrefixes(scopedName: String): String {
|
||||
return scopedName.split('.')
|
||||
.map { part ->
|
||||
if(part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
|
||||
part.drop(4)
|
||||
else part
|
||||
}
|
||||
.joinToString(".")
|
||||
}
|
||||
}
|
||||
@@ -30,40 +30,48 @@ private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, Ba
|
||||
|
||||
|
||||
class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val returnType: BaseDataType?,
|
||||
val returnTypes: Array<BaseDataType>,
|
||||
vararg val parameters: FParam) {
|
||||
|
||||
fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention {
|
||||
val returns: ReturnConvention = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
|
||||
null -> ReturnConvention(null, null)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
val returns: ReturnConvention
|
||||
if(returnTypes.isEmpty())
|
||||
returns = ReturnConvention(null, null)
|
||||
else if(returnTypes.size==1) {
|
||||
val returnType = returnTypes[0]
|
||||
returns = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// note: this path is currently never hit because the builtin functions with multiple return values have been handled in specialized optimized codegens already earlier.
|
||||
TODO("multiple return types from builtin function")
|
||||
}
|
||||
|
||||
return when {
|
||||
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||
actualParamTypes.size==1 -> {
|
||||
// One parameter goes via register/registerpair.
|
||||
// One parameter goes via register/registerpair (except longs)
|
||||
// this avoids repeated code for every caller to store the value in the subroutine's argument variable.
|
||||
// (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site).
|
||||
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, RegisterOrPair.R14R15, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, null, true)
|
||||
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
|
||||
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ParamConvention(paramType, null, false)
|
||||
@@ -89,96 +97,102 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
}
|
||||
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
|
||||
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
|
||||
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_structalloc" to FSignature(true, BaseDataType.UWORD),
|
||||
"prog8_lib_copylong" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, null, FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
|
||||
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.LONG)),
|
||||
"sqrt__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
|
||||
"swap" to FSignature(false, null, FParam("var1", *NumericDatatypes + BaseDataType.BOOL), FParam("var2", *NumericDatatypes + BaseDataType.BOOL)),
|
||||
"swap__byte" to FSignature(false, null, FParam("var1", BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL), FParam("var2",BaseDataType.BYTE, BaseDataType.UBYTE, BaseDataType.BOOL)),
|
||||
"swap__word" to FSignature(false, null, FParam("var1", BaseDataType.WORD, BaseDataType.UWORD), FParam("var2",BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"swap__long" to FSignature(false, null, FParam("var1", BaseDataType.LONG), FParam("var2",BaseDataType.LONG)),
|
||||
"swap__float" to FSignature(false, null, FParam("var1", BaseDataType.FLOAT), FParam("var2",BaseDataType.FLOAT)),
|
||||
"divmod" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)),
|
||||
"divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)),
|
||||
"lsb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"lsb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
|
||||
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
|
||||
"lsw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
|
||||
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
|
||||
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
|
||||
"peekl" to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)),
|
||||
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
|
||||
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
|
||||
"rsave" to FSignature(false, null),
|
||||
"rrestore" to FSignature(false, null),
|
||||
"memory" to FSignature(true, BaseDataType.UWORD, FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"callfar" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
|
||||
"setlsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"rol" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"rol2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"cmp" to FSignature(false, emptyArray(), FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
|
||||
"prog8_lib_stringcompare" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
|
||||
"prog8_lib_square_byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"prog8_lib_square_word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_square_long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
|
||||
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"abs" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"abs__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"sqrt__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
|
||||
"divmod" to FSignature(false, arrayOf(BaseDataType.UNDEFINED, BaseDataType.UNDEFINED), FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"divmod__ubyte" to FSignature(false, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE)),
|
||||
"divmod__uword" to FSignature(false, arrayOf(BaseDataType.UWORD, BaseDataType.UWORD), FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD)),
|
||||
"divmod__byte" to FSignature(false, arrayOf(BaseDataType.BYTE, BaseDataType.BYTE), FParam("dividend", BaseDataType.BYTE), FParam("divisor", BaseDataType.BYTE)),
|
||||
"divmod__word" to FSignature(false, arrayOf(BaseDataType.WORD, BaseDataType.WORD), FParam("dividend", BaseDataType.WORD), FParam("divisor", BaseDataType.WORD)),
|
||||
"lmh" to FSignature(true, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"lsb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"lsb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"msb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"lsw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, emptyArray(), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD)),
|
||||
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD)),
|
||||
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
|
||||
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD)),
|
||||
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD)),
|
||||
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
|
||||
"rsave" to FSignature(false, emptyArray()),
|
||||
"rrestore" to FSignature(false, emptyArray()),
|
||||
"memory" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"memory__ref" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR)),
|
||||
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
|
||||
"push" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)),
|
||||
"pushw" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER)),
|
||||
"pushl" to FSignature(false, emptyArray(), FParam("value", BaseDataType.LONG)),
|
||||
"pushf" to FSignature(false, emptyArray(), FParam("value", BaseDataType.FLOAT)),
|
||||
"pop" to FSignature(false, arrayOf(BaseDataType.UBYTE)),
|
||||
"popw" to FSignature(false, arrayOf(BaseDataType.UWORD)),
|
||||
"popl" to FSignature(false, arrayOf(BaseDataType.LONG)),
|
||||
"popf" to FSignature(false, arrayOf(BaseDataType.FLOAT)),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
"setlsb", "setmsb",
|
||||
"rol", "ror", "rol2", "ror2",
|
||||
"divmod", "divmod__ubyte", "divmod__uword",
|
||||
"swap", "swap__byte", "swap__word", "swap__long", "swap__float"
|
||||
"rol", "ror", "rol2", "ror2"
|
||||
)
|
||||
|
||||
val SimpleBuiltinFunctions = setOf(
|
||||
"msb", "lsb", "msw", "lsw",
|
||||
"mkword", "mklong", "mklong2",
|
||||
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
@@ -34,6 +34,7 @@ class CompilationOptions(val output: OutputType,
|
||||
var ignoreFootguns: Boolean = false,
|
||||
var outputDir: Path = Path(""),
|
||||
var quiet: Boolean = false,
|
||||
var profilingInstrumentation: Boolean = false,
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
) {
|
||||
init {
|
||||
@@ -42,5 +43,81 @@ class CompilationOptions(val output: OutputType,
|
||||
|
||||
companion object {
|
||||
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
|
||||
|
||||
fun builder(compTarget: ICompilationTarget) = Builder(compTarget)
|
||||
|
||||
class Builder(private val compTarget: ICompilationTarget) {
|
||||
private var output: OutputType = compTarget.defaultOutputType
|
||||
private var launcher: CbmPrgLauncherType = CbmPrgLauncherType.NONE
|
||||
private var zeropage: ZeropageType = ZeropageType.DONTUSE
|
||||
private var zpReserved: List<UIntRange> = emptyList()
|
||||
private var zpAllowed: List<UIntRange> = AllZeropageAllowed
|
||||
private var floats: Boolean = false
|
||||
private var noSysInit: Boolean = false
|
||||
private var romable: Boolean = false
|
||||
private var compilerVersion: String = "unknown"
|
||||
private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS
|
||||
private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS
|
||||
private var warnSymbolShadowing: Boolean = false
|
||||
private var warnImplicitTypeCast: Boolean = false
|
||||
private var optimize: Boolean = false
|
||||
private var asmQuiet: Boolean = false
|
||||
private var asmListfile: Boolean = false
|
||||
private var includeSourcelines: Boolean = false
|
||||
private var dumpVariables: Boolean = false
|
||||
private var dumpSymbols: Boolean = false
|
||||
private var experimentalCodegen: Boolean = false
|
||||
private var varsHighBank: Int? = null
|
||||
private var varsGolden: Boolean = false
|
||||
private var slabsHighBank: Int? = null
|
||||
private var slabsGolden: Boolean = false
|
||||
private var breakpointCpuInstruction: String? = null
|
||||
private var ignoreFootguns: Boolean = false
|
||||
private var outputDir: Path = Path("")
|
||||
private var quiet: Boolean = false
|
||||
private var profilingInstrumentation: Boolean = false
|
||||
private var symbolDefs: Map<String, String> = emptyMap()
|
||||
|
||||
fun output(output: OutputType) = apply { this.output = output }
|
||||
fun launcher(launcher: CbmPrgLauncherType) = apply { this.launcher = launcher }
|
||||
fun zeropage(zeropage: ZeropageType) = apply { this.zeropage = zeropage }
|
||||
fun zpReserved(zpReserved: List<UIntRange>) = apply { this.zpReserved = zpReserved }
|
||||
fun zpAllowed(zpAllowed: List<UIntRange>) = apply { this.zpAllowed = zpAllowed }
|
||||
fun floats(floats: Boolean) = apply { this.floats = floats }
|
||||
fun noSysInit(noSysInit: Boolean) = apply { this.noSysInit = noSysInit }
|
||||
fun romable(romable: Boolean) = apply { this.romable = romable }
|
||||
fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion }
|
||||
fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress }
|
||||
fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress }
|
||||
fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing }
|
||||
fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast }
|
||||
fun optimize(optimize: Boolean) = apply { this.optimize = optimize }
|
||||
fun asmQuiet(asmQuiet: Boolean) = apply { this.asmQuiet = asmQuiet }
|
||||
fun asmListfile(asmListfile: Boolean) = apply { this.asmListfile = asmListfile }
|
||||
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
|
||||
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
|
||||
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
|
||||
fun experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen }
|
||||
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
|
||||
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
|
||||
fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank }
|
||||
fun slabsGolden(slabsGolden: Boolean) = apply { this.slabsGolden = slabsGolden }
|
||||
fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction }
|
||||
fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns }
|
||||
fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir }
|
||||
fun quiet(quiet: Boolean) = apply { this.quiet = quiet }
|
||||
fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation }
|
||||
fun symbolDefs(symbolDefs: Map<String, String>) = apply { this.symbolDefs = symbolDefs }
|
||||
|
||||
fun build(): CompilationOptions {
|
||||
return CompilationOptions(
|
||||
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
|
||||
loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden,
|
||||
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
profilingInstrumentation, symbolDefs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Base data types supported by the Prog8 compiler.
|
||||
* These represent the fundamental types that can be used in Prog8 programs.
|
||||
*/
|
||||
enum class BaseDataType {
|
||||
UBYTE, // pass by value 8 bits unsigned
|
||||
BYTE, // pass by value 8 bits signed
|
||||
UWORD, // pass by value 16 bits unsigned
|
||||
WORD, // pass by value 16 bits signed
|
||||
LONG, // pass by value 32 bits signed
|
||||
FLOAT, // pass by value machine dependent
|
||||
BOOL, // pass by value bit 0 of an 8-bit byte
|
||||
STR, // pass by reference
|
||||
ARRAY, // pass by reference, subtype is the element type
|
||||
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
|
||||
POINTER, // typed pointer, subtype is whatever type is pointed to
|
||||
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
|
||||
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
|
||||
UNDEFINED;
|
||||
|
||||
|
||||
fun largerSizeThan(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> false
|
||||
this.isByteOrBool -> false
|
||||
this.isWord -> other.isByteOrBool
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other != FLOAT
|
||||
this == STR -> other != FLOAT
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun equalsSize(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> true
|
||||
this.isArray && other.isArray -> true
|
||||
this.isByteOrBool -> other.isByteOrBool
|
||||
this.isWord -> other.isWord || other.isPointer
|
||||
this.isPointer -> other.isWord
|
||||
this == STR && other== UWORD || this== UWORD && other== STR -> true
|
||||
this == STR && other.isArray -> true
|
||||
this.isArray && other == STR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// BaseDataType Extension Properties
|
||||
// ============================================================================
|
||||
|
||||
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
|
||||
val BaseDataType.isUnsignedByte get() = this == BaseDataType.UBYTE
|
||||
val BaseDataType.isSignedByte get() = this == BaseDataType.BYTE
|
||||
val BaseDataType.isBool get() = this == BaseDataType.BOOL
|
||||
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isUnsignedWord get() = this == BaseDataType.UWORD
|
||||
val BaseDataType.isSignedWord get() = this == BaseDataType.WORD
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
|
||||
/**
|
||||
* Interface for types that can be used as subtypes in DataType.
|
||||
* Primarily used for struct types.
|
||||
*/
|
||||
interface ISubType {
|
||||
val scopedNameString: String
|
||||
fun memsize(sizer: IMemSizer): Int
|
||||
fun sameas(other: ISubType): Boolean
|
||||
fun getFieldType(name: String): DataType?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a complete data type in Prog8, including base type and optional subtype.
|
||||
*
|
||||
* DataType is immutable after construction. Use the companion object factory methods
|
||||
* to create instances.
|
||||
*
|
||||
* @property base The base data type (e.g., UBYTE, WORD, ARRAY, POINTER)
|
||||
* @property sub The subtype for arrays and strings (e.g., UBYTE for byte arrays)
|
||||
* @property subType The structured subtype for pointers and struct instances
|
||||
* @property subTypeFromAntlr Deferred subtype resolution from parser
|
||||
*/
|
||||
class DataType private constructor(
|
||||
val base: BaseDataType,
|
||||
val sub: BaseDataType?,
|
||||
var subType: ISubType?,
|
||||
var subTypeFromAntlr: List<String>? = null
|
||||
) {
|
||||
|
||||
init {
|
||||
when {
|
||||
base.isPointerArray -> {
|
||||
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
|
||||
}
|
||||
base.isArray -> {
|
||||
require(sub != null && subType==null && subTypeFromAntlr==null)
|
||||
if(base.isSplitWordArray)
|
||||
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
}
|
||||
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
|
||||
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
|
||||
else -> {
|
||||
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
|
||||
"sub and subtype can't both be set"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DataType) return false
|
||||
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, subType)
|
||||
|
||||
fun setActualSubType(actualSubType: ISubType) {
|
||||
subType = actualSubType
|
||||
subTypeFromAntlr = null
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Companion Object - Factory Methods
|
||||
// ============================================================================
|
||||
|
||||
companion object {
|
||||
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
|
||||
|
||||
private val simpletypes = mapOf(
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
|
||||
)
|
||||
|
||||
fun forDt(dt: BaseDataType): DataType {
|
||||
if(dt.isStructInstance)
|
||||
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
|
||||
return simpletypes.getValue(dt)
|
||||
}
|
||||
|
||||
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
|
||||
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
|
||||
return if(splitwordarray && actualElementDt.isWord)
|
||||
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
|
||||
else {
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
DataType(BaseDataType.ARRAY, actualElementDt, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
}
|
||||
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
|
||||
fun pointer(dt: DataType): DataType = if(dt.isBasic)
|
||||
DataType(BaseDataType.POINTER, dt.base, null)
|
||||
else
|
||||
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// DataType Methods
|
||||
// ============================================================================
|
||||
|
||||
fun elementToArray(splitwords: Boolean = true): DataType {
|
||||
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
|
||||
else arrayFor(base, false)
|
||||
}
|
||||
|
||||
fun elementType(): DataType =
|
||||
when {
|
||||
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
|
||||
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
|
||||
else -> throw IllegalArgumentException("not an array")
|
||||
}
|
||||
|
||||
fun typeForAddressOf(msb: Boolean): DataType {
|
||||
if (isUndefined)
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
|
||||
else {
|
||||
if (isBasic)
|
||||
return pointer(base)
|
||||
if (isString)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
if (isPointer)
|
||||
return UWORD
|
||||
if (isArray) {
|
||||
if (msb || isSplitWordArray)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
val elementDt = elementType()
|
||||
require(elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
}
|
||||
if (subType != null)
|
||||
return pointer(this)
|
||||
return UWORD
|
||||
}
|
||||
}
|
||||
|
||||
fun dereference(): DataType {
|
||||
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type ${this}"}
|
||||
return when {
|
||||
isUnsignedWord -> forDt(BaseDataType.UBYTE)
|
||||
sub!=null -> forDt(sub)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
|
||||
else -> throw IllegalArgumentException("cannot dereference this pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = when(base) {
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.BOOL -> "bool[]"
|
||||
BaseDataType.FLOAT -> "float[]"
|
||||
BaseDataType.BYTE -> "byte[]"
|
||||
BaseDataType.WORD -> "word[]"
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.WORD -> "word[] (split)"
|
||||
BaseDataType.UWORD -> "uword[] (split)"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
|
||||
}
|
||||
else -> base.name.lowercase()
|
||||
}
|
||||
|
||||
fun sourceString(): String = when (base) {
|
||||
BaseDataType.BOOL -> "bool"
|
||||
BaseDataType.UBYTE -> "ubyte"
|
||||
BaseDataType.BYTE -> "byte"
|
||||
BaseDataType.UWORD -> "uword"
|
||||
BaseDataType.WORD -> "word"
|
||||
BaseDataType.LONG -> "long"
|
||||
BaseDataType.FLOAT -> "float"
|
||||
BaseDataType.STR -> "str"
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}"
|
||||
subType!=null -> "^^${subType!!.scopedNameString}"
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
when {
|
||||
sub!=null -> sub.name.lowercase()
|
||||
subType!=null -> subType!!.scopedNameString
|
||||
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}["
|
||||
subType!=null -> "^^${subType!!.scopedNameString}["
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else -> "????? ["
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.UBYTE -> "ubyte["
|
||||
BaseDataType.UWORD -> "@nosplit uword["
|
||||
BaseDataType.BOOL -> "bool["
|
||||
BaseDataType.BYTE -> "byte["
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.UWORD -> "uword["
|
||||
BaseDataType.WORD -> "word["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type is assignable to the given target type (perhaps via a typecast)
|
||||
* without loss of precision.
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(base) {
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
targetType.isPointer -> this.isUnsignedWord || this == targetType
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
|
||||
BaseDataType.ARRAY_POINTER -> false
|
||||
BaseDataType.UNDEFINED -> false
|
||||
}
|
||||
|
||||
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
|
||||
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
|
||||
|
||||
/**
|
||||
* Returns the memory size in bytes.
|
||||
* Note: for pointer types, size() doesn't return the size of the pointer itself
|
||||
* but the size of the thing it points to.
|
||||
*/
|
||||
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
|
||||
memsizer.memorySize(sub)
|
||||
} else if(subType!=null) {
|
||||
subType!!.memsize(memsizer)
|
||||
} else {
|
||||
memsizer.memorySize(base)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Properties
|
||||
// ============================================================================
|
||||
|
||||
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
|
||||
val isUndefined = base == BaseDataType.UNDEFINED
|
||||
val isByte = base.isByte
|
||||
val isUnsignedByte = base == BaseDataType.UBYTE
|
||||
val isSignedByte = base == BaseDataType.BYTE
|
||||
val isByteOrBool = base.isByteOrBool
|
||||
val isWord = base.isWord
|
||||
val isUnsignedWord = base == BaseDataType.UWORD
|
||||
val isSignedWord = base == BaseDataType.WORD
|
||||
val isInteger = base.isInteger
|
||||
val isWordOrByteOrBool = base.isWordOrByteOrBool
|
||||
val isIntegerOrBool = base.isIntegerOrBool
|
||||
val isNumeric = base.isNumeric
|
||||
val isNumericOrBool = base.isNumericOrBool
|
||||
val isSigned = base.isSigned
|
||||
val isUnsigned = !base.isSigned
|
||||
val isSignedInteger = isSigned && isInteger
|
||||
val isUnsignedInteger = isUnsigned && isInteger
|
||||
val isArray = base.isArray
|
||||
val isPointer = base.isPointer
|
||||
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
|
||||
val isPointerToWord = base.isPointer && sub?.isWord==true
|
||||
val isStructInstance = base.isStructInstance
|
||||
val isPointerArray = base.isPointerArray
|
||||
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
|
||||
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
|
||||
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isLongArray = base.isArray && sub == BaseDataType.LONG
|
||||
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
val isFloat = base == BaseDataType.FLOAT
|
||||
val isLong = base == BaseDataType.LONG
|
||||
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
val isSplitWordArray = base.isSplitWordArray
|
||||
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isIterable = base.isIterable
|
||||
val isPassByRef = base.isPassByRef
|
||||
val isPassByValue = base.isPassByValue
|
||||
}
|
||||
@@ -1,388 +1,5 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.util.*
|
||||
|
||||
enum class BaseDataType {
|
||||
UBYTE, // pass by value 8 bits unsigned
|
||||
BYTE, // pass by value 8 bits signed
|
||||
UWORD, // pass by value 16 bits unsigned
|
||||
WORD, // pass by value 16 bits signed
|
||||
LONG, // pass by value 32 bits signed
|
||||
FLOAT, // pass by value machine dependent
|
||||
BOOL, // pass by value bit 0 of an 8-bit byte
|
||||
STR, // pass by reference
|
||||
ARRAY, // pass by reference, subtype is the element type
|
||||
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
|
||||
POINTER, // typed pointer, subtype is whatever type is pointed to
|
||||
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
|
||||
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
|
||||
UNDEFINED;
|
||||
|
||||
|
||||
fun largerSizeThan(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> false
|
||||
this.isByteOrBool -> false
|
||||
this.isWord -> other.isByteOrBool
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other != FLOAT
|
||||
this == STR -> other != FLOAT
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun equalsSize(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> true
|
||||
this.isArray && other.isArray -> true
|
||||
this.isByteOrBool -> other.isByteOrBool
|
||||
this.isWord -> other.isWord || other.isPointer
|
||||
this.isPointer -> other.isWord
|
||||
this == STR && other== UWORD || this== UWORD && other== STR -> true
|
||||
this == STR && other.isArray -> true
|
||||
this.isArray && other == STR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
|
||||
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
|
||||
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
|
||||
interface ISubType {
|
||||
val scopedNameString: String
|
||||
fun memsize(sizer: IMemSizer): Int
|
||||
fun sameas(other: ISubType): Boolean
|
||||
fun getFieldType(name: String): DataType?
|
||||
}
|
||||
|
||||
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, var subType: ISubType?, var subTypeFromAntlr: List<String>?=null) {
|
||||
|
||||
init {
|
||||
when {
|
||||
base.isPointerArray -> {
|
||||
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
|
||||
}
|
||||
base.isArray -> {
|
||||
require(sub != null && subType==null && subTypeFromAntlr==null)
|
||||
if(base.isSplitWordArray)
|
||||
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
}
|
||||
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
|
||||
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
|
||||
else -> {
|
||||
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
|
||||
"sub and subtype can't both be set"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DataType) return false
|
||||
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, subType)
|
||||
|
||||
fun setActualSubType(actualSubType: ISubType) {
|
||||
subType = actualSubType
|
||||
subTypeFromAntlr = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
|
||||
|
||||
private val simpletypes = mapOf(
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
|
||||
)
|
||||
|
||||
fun forDt(dt: BaseDataType): DataType {
|
||||
if(dt.isStructInstance)
|
||||
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
|
||||
return simpletypes.getValue(dt)
|
||||
}
|
||||
|
||||
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
|
||||
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
|
||||
return if(splitwordarray && actualElementDt.isWord)
|
||||
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
|
||||
else {
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
DataType(BaseDataType.ARRAY, actualElementDt, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
}
|
||||
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
|
||||
fun pointer(dt: DataType): DataType = if(dt.isBasic)
|
||||
DataType(BaseDataType.POINTER, dt.base, null)
|
||||
else
|
||||
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
}
|
||||
|
||||
|
||||
fun elementToArray(splitwords: Boolean = true): DataType {
|
||||
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
|
||||
else arrayFor(base, false)
|
||||
}
|
||||
|
||||
fun elementType(): DataType =
|
||||
when {
|
||||
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
|
||||
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
|
||||
else -> throw IllegalArgumentException("not an array")
|
||||
}
|
||||
|
||||
fun typeForAddressOf(msb: Boolean): DataType {
|
||||
if (isUndefined)
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
|
||||
else {
|
||||
if (isBasic)
|
||||
return pointer(base)
|
||||
if (isString)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
if (isPointer)
|
||||
return UWORD
|
||||
if (isArray) {
|
||||
if (msb || isSplitWordArray)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
val elementDt = elementType()
|
||||
require(elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
}
|
||||
if (subType != null)
|
||||
return pointer(this)
|
||||
return UWORD
|
||||
}
|
||||
}
|
||||
|
||||
fun dereference(): DataType {
|
||||
require(isPointer || isUnsignedWord)
|
||||
return when {
|
||||
isUnsignedWord -> forDt(BaseDataType.UBYTE)
|
||||
sub!=null -> forDt(sub)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr)
|
||||
else -> throw IllegalArgumentException("cannot dereference this pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = when(base) {
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.BOOL -> "bool[]"
|
||||
BaseDataType.FLOAT -> "float[]"
|
||||
BaseDataType.BYTE -> "byte[]"
|
||||
BaseDataType.WORD -> "word[]"
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.WORD -> "word[] (split)"
|
||||
BaseDataType.UWORD -> "uword[] (split)"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
|
||||
}
|
||||
else -> base.name.lowercase()
|
||||
}
|
||||
|
||||
fun sourceString(): String = when (base) {
|
||||
BaseDataType.BOOL -> "bool"
|
||||
BaseDataType.UBYTE -> "ubyte"
|
||||
BaseDataType.BYTE -> "byte"
|
||||
BaseDataType.UWORD -> "uword"
|
||||
BaseDataType.WORD -> "word"
|
||||
BaseDataType.LONG -> "long"
|
||||
BaseDataType.FLOAT -> "float"
|
||||
BaseDataType.STR -> "str"
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}"
|
||||
subType!=null -> "^^${subType!!.scopedNameString}"
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
when {
|
||||
sub!=null -> sub.name.lowercase()
|
||||
subType!=null -> subType!!.scopedNameString
|
||||
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}["
|
||||
subType!=null -> "^^${subType!!.scopedNameString}["
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else -> "????? ["
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.UBYTE -> "ubyte["
|
||||
BaseDataType.UWORD -> "@nosplit uword["
|
||||
BaseDataType.BOOL -> "bool["
|
||||
BaseDataType.BYTE -> "byte["
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.UWORD -> "uword["
|
||||
BaseDataType.WORD -> "word["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
|
||||
}
|
||||
|
||||
// is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(base) {
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
targetType.isPointer -> this.isUnsignedWord || this == targetType
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
|
||||
BaseDataType.ARRAY_POINTER -> false
|
||||
BaseDataType.UNDEFINED -> false
|
||||
}
|
||||
|
||||
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
|
||||
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
|
||||
|
||||
// note: for pointer types, size() doesn't return the size of the pointer itself but the size of the thing it points to
|
||||
fun size(memsizer: IMemSizer): Int = if(sub!=null) {
|
||||
memsizer.memorySize(sub)
|
||||
} else if(subType!=null) {
|
||||
subType!!.memsize(memsizer)
|
||||
} else {
|
||||
memsizer.memorySize(base)
|
||||
}
|
||||
|
||||
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
|
||||
val isUndefined = base == BaseDataType.UNDEFINED
|
||||
val isByte = base.isByte
|
||||
val isUnsignedByte = base == BaseDataType.UBYTE
|
||||
val isSignedByte = base == BaseDataType.BYTE
|
||||
val isByteOrBool = base.isByteOrBool
|
||||
val isWord = base.isWord
|
||||
val isUnsignedWord = base == BaseDataType.UWORD
|
||||
val isSignedWord = base == BaseDataType.WORD
|
||||
val isInteger = base.isInteger
|
||||
val isWordOrByteOrBool = base.isWordOrByteOrBool
|
||||
val isIntegerOrBool = base.isIntegerOrBool
|
||||
val isNumeric = base.isNumeric
|
||||
val isNumericOrBool = base.isNumericOrBool
|
||||
val isSigned = base.isSigned
|
||||
val isUnsigned = !base.isSigned
|
||||
val isArray = base.isArray
|
||||
val isPointer = base.isPointer
|
||||
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
|
||||
val isPointerToWord = base.isPointer && sub?.isWord==true
|
||||
val isStructInstance = base.isStructInstance
|
||||
val isPointerArray = base.isPointerArray
|
||||
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
|
||||
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
|
||||
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isLongArray = base.isArray && sub == BaseDataType.LONG
|
||||
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
val isFloat = base == BaseDataType.FLOAT
|
||||
val isLong = base == BaseDataType.LONG
|
||||
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
val isSplitWordArray = base.isSplitWordArray
|
||||
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
|
||||
val isIterable = base.isIterable
|
||||
val isPassByRef = base.isPassByRef
|
||||
val isPassByValue = base.isPassByValue
|
||||
}
|
||||
|
||||
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
@@ -406,7 +23,7 @@ enum class RegisterOrPair {
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
|
||||
|
||||
companion object {
|
||||
val names by lazy { entries.map { it.toString()} }
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
|
||||
return when(cpu) {
|
||||
CpuRegister.A -> A
|
||||
@@ -440,12 +57,13 @@ enum class RegisterOrPair {
|
||||
}
|
||||
|
||||
fun asScopedNameVirtualReg(type: DataType?): List<String> {
|
||||
require(this in Cx16VirtualRegisters)
|
||||
require(this in Cx16VirtualRegisters || this in CombinedLongRegisters)
|
||||
val suffix = when(type?.base) {
|
||||
BaseDataType.UBYTE, BaseDataType.BOOL -> "L"
|
||||
BaseDataType.BYTE -> "sL"
|
||||
BaseDataType.WORD -> "s"
|
||||
BaseDataType.UWORD, BaseDataType.POINTER, null -> ""
|
||||
BaseDataType.LONG -> "sl"
|
||||
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
|
||||
}
|
||||
return listOf("cx16", name.lowercase()+suffix)
|
||||
@@ -463,7 +81,7 @@ enum class Statusflag {
|
||||
Pn; // don't use
|
||||
|
||||
companion object {
|
||||
val names by lazy { entries.map { it.toString()} }
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,14 +100,14 @@ enum class BranchCondition {
|
||||
VC
|
||||
}
|
||||
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
val Cx16VirtualRegisters = setOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||
)
|
||||
|
||||
val CombinedLongRegisters = arrayOf(
|
||||
val CombinedLongRegisters = setOf(
|
||||
RegisterOrPair.R0R1,
|
||||
RegisterOrPair.R2R3,
|
||||
RegisterOrPair.R4R5,
|
||||
@@ -500,7 +118,7 @@ val CombinedLongRegisters = arrayOf(
|
||||
RegisterOrPair.R14R15
|
||||
)
|
||||
|
||||
val CpuRegisters = arrayOf(
|
||||
val CpuRegisters = setOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
)
|
||||
@@ -536,4 +154,4 @@ enum class ZeropageWish {
|
||||
enum class SplitWish {
|
||||
DONTCARE,
|
||||
NOSPLIT
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
val FLOAT_MEM_SIZE: UInt
|
||||
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val PROGRAM_MEMTOP_ADDRESS: UInt
|
||||
@@ -24,7 +24,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
|
||||
val cpu: CpuType
|
||||
var zeropage: Zeropage
|
||||
var golden: GoldenRam
|
||||
val libraryPath: Path?
|
||||
val customLauncher: List<String>
|
||||
val additionalAssemblerOptions: List<String>
|
||||
|
||||
@@ -28,26 +28,21 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
abstract val SCRATCH_PTR : UInt // temp storage for a pointer
|
||||
|
||||
|
||||
// the variables allocated into Zeropage.
|
||||
// Collections for zeropage allocation (single-threaded usage)
|
||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||
val allocatedVariables = mutableMapOf<String, VarAllocation>()
|
||||
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
fun removeReservedFromFreePool() {
|
||||
synchronized(this) {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
}
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
}
|
||||
|
||||
fun retainAllowed() {
|
||||
synchronized(this) {
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
|
||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||
@@ -65,7 +60,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
||||
require(name.isEmpty() || !allocatedVariables.containsKey(name)) {"name can't be allocated twice"}
|
||||
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return Err(MemAllocationError("zero page usage has been disabled"))
|
||||
@@ -95,19 +90,17 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
if(free.isNotEmpty()) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
}
|
||||
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
|
||||
}
|
||||
if(free.isNotEmpty()) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
}
|
||||
return Ok(VarAllocation(makeAllocation(free.first(), 1, datatype, name), datatype,1))
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,43 +124,12 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
return address
|
||||
}
|
||||
|
||||
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
|
||||
private fun oneSeparateByteFree(address: UInt) =
|
||||
address in free &&
|
||||
(address == 0u || address-1u !in free) &&
|
||||
address+1u !in free
|
||||
private fun sequentialFree(address: UInt, size: Int): Boolean {
|
||||
require(size>0)
|
||||
return free.containsAll((address until address+size.toUInt()).toList())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: this class is not yet used
|
||||
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||
private var nextLocation: UInt = region.first
|
||||
|
||||
override fun allocate(
|
||||
name: String,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
val size: Int =
|
||||
when {
|
||||
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
|
||||
datatype.isString -> numElements!!
|
||||
datatype.isArray -> options.compTarget.memorySize(datatype, numElements!!)
|
||||
datatype.isFloat -> {
|
||||
if (options.floats) {
|
||||
options.compTarget.memorySize(DataType.FLOAT, null)
|
||||
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||
}
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
|
||||
val result = Ok(VarAllocation(nextLocation, datatype, size))
|
||||
nextLocation += size.toUInt()
|
||||
result
|
||||
} else
|
||||
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package prog8.code.core
|
||||
|
||||
val AssociativeOperators = arrayOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
|
||||
val ComparisonOperators = arrayOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = arrayOf("and", "or", "xor", "not", "in")
|
||||
val BitwiseOperators = arrayOf("&", "|", "^", "~")
|
||||
val PrefixOperators = arrayOf("+", "-", "~", "not")
|
||||
val CommutativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not commutative because of Shortcircuit/McCarthy evaluation
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
val PrefixOperators = setOf("+", "-", "~", "not")
|
||||
|
||||
fun invertedComparisonOperator(operator: String) =
|
||||
when (operator) {
|
||||
|
||||
@@ -5,14 +5,17 @@ import prog8.code.source.SourceCode
|
||||
import java.nio.file.InvalidPathException
|
||||
import kotlin.io.path.Path
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol}-${endCol}]"
|
||||
|
||||
init {
|
||||
if(!file.startsWith('~') ||!file.endsWith('~'))
|
||||
require(line>0 && startCol>=0 && endCol>=startCol) {
|
||||
"Invalid position: $this"
|
||||
}
|
||||
/**
|
||||
* Source code position.
|
||||
*/
|
||||
data class Position(
|
||||
val file: String,
|
||||
val line: Int, // line number (1-based)
|
||||
val startCol: Int, // start column (1-based, tab-expanded)
|
||||
val endCol: Int // end column (1-based, tab-expanded)
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "[$file: line $line col ${startCol}-${endCol}]"
|
||||
}
|
||||
|
||||
fun toClickableStr(): String {
|
||||
@@ -21,8 +24,8 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
|
||||
if(SourceCode.isLibraryResource(file))
|
||||
return "$file:$line:$startCol:"
|
||||
return try {
|
||||
val path = Path(file).sanitize().toString()
|
||||
"file://$path:$line:$startCol:"
|
||||
val path = Path(file).sanitize().toUri().toString()
|
||||
"$path:$line:$startCol:"
|
||||
} catch(_: InvalidPathException) {
|
||||
// this can occur on Windows when the source origin contains "invalid" characters such as ':'
|
||||
"file://$file:$line:$startCol:"
|
||||
@@ -32,4 +35,4 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
|
||||
companion object {
|
||||
val DUMMY = Position("~dummy~", 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,16 @@ package prog8.code.source
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.sanitize
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
// Resource caching "filesystem".
|
||||
// Note that it leaves the decision to load a resource or an actual disk file to the caller.
|
||||
// Thread-safe: uses ConcurrentHashMap for all caches.
|
||||
|
||||
object ImportFileSystem {
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun expandTilde(path: String): String = if (path.startsWith("~")) {
|
||||
val userHome = System.getProperty("user.home")
|
||||
userHome + path.drop(1)
|
||||
@@ -28,45 +24,45 @@ object ImportFileSystem {
|
||||
|
||||
fun getFile(path: Path, isLibrary: Boolean=false): SourceCode {
|
||||
val normalized = path.sanitize()
|
||||
val cached = cache[normalized.toString()]
|
||||
if (cached != null)
|
||||
return cached
|
||||
val file = SourceCode.File(normalized, isLibrary)
|
||||
cache[normalized.toString()] = file
|
||||
return file
|
||||
return cache.computeIfAbsent(normalized.toString().lowercase()) {
|
||||
SourceCode.File(normalized, isLibrary)
|
||||
}
|
||||
}
|
||||
|
||||
fun getResource(name: String): SourceCode {
|
||||
val cached = cache[name]
|
||||
if (cached != null) return cached
|
||||
val resource = SourceCode.Resource(name)
|
||||
cache[name] = resource
|
||||
return resource
|
||||
return cache.computeIfAbsent(name.lowercase()) {
|
||||
SourceCode.Resource(name)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun retrieveSourceLine(position: Position): String {
|
||||
if(SourceCode.isLibraryResource(position.file)) {
|
||||
val cached = cache[SourceCode.withoutPrefix(position.file)]
|
||||
val key = SourceCode.withoutPrefix(position.file)
|
||||
val cached = cache[key.lowercase()]
|
||||
?: runCatching { getResource(key) }.getOrNull()
|
||||
if(cached != null)
|
||||
return getLine(cached, position.line)
|
||||
}
|
||||
val cached = cache[position.file]
|
||||
val cached = cache[position.file.lowercase()]
|
||||
if(cached != null)
|
||||
return getLine(cached, position.line)
|
||||
val path = Path(position.file).sanitize()
|
||||
val cached2 = cache[path.toString()]
|
||||
val cached2 = cache[path.toString().lowercase()]
|
||||
if(cached2 != null)
|
||||
return getLine(cached2, position.line)
|
||||
throw NoSuchElementException("cannot get source line $position, with path $path")
|
||||
}
|
||||
|
||||
private fun getLine(code: SourceCode, lineIndex: Int): String {
|
||||
var spans = lineSpanCache[code]
|
||||
if(spans==null) {
|
||||
val spans = lineSpanCache.computeIfAbsent(code) {
|
||||
val lineSpans = Regex("^", RegexOption.MULTILINE).findAll(code.text).map { it.range.first }
|
||||
val ends = lineSpans.drop(1) + code.text.length
|
||||
spans = lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
|
||||
lineSpanCache[code] = spans
|
||||
lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
|
||||
}
|
||||
val span = spans[lineIndex - 1]
|
||||
return code.text.substring(span.start, span.end).trim()
|
||||
@@ -74,6 +70,6 @@ object ImportFileSystem {
|
||||
|
||||
private class LineSpan(val start: Int, val end: Int)
|
||||
|
||||
private val cache = TreeMap<String, SourceCode>(String.CASE_INSENSITIVE_ORDER)
|
||||
private val lineSpanCache = mutableMapOf<SourceCode, Array<LineSpan>>()
|
||||
}
|
||||
private val cache = ConcurrentHashMap<String, SourceCode>()
|
||||
private val lineSpanCache = ConcurrentHashMap<SourceCode, Array<LineSpan>>()
|
||||
}
|
||||
@@ -26,18 +26,17 @@ class C128Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO address?
|
||||
override val BSSHIGHRAM_END = 0u // TODO address?
|
||||
override val BSSGOLDENRAM_START = 0u // TODO address?
|
||||
override val BSSGOLDENRAM_END = 0u // TODO address?
|
||||
override val BSSHIGHRAM_START = 0u // no high ram
|
||||
override val BSSHIGHRAM_END = 0u // no high ram
|
||||
override val BSSGOLDENRAM_START = 0x1300u
|
||||
override val BSSGOLDENRAM_END = 0x1bdfu // note: $1be0 - $1bff contains the 16 virtual registers
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -71,11 +70,10 @@ class C128Target: ICompilationTarget,
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu || address in 0xff00u..0xff04u
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C128Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class C64Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
|
||||
@@ -41,7 +41,6 @@ class C64Target: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_END = 0u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -87,7 +86,6 @@ class C64Target: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -145,10 +145,9 @@ class ConfigFileTarget(
|
||||
// TODO floats are not yet supported here, just enter some values
|
||||
override val FLOAT_MAX_POSITIVE = 9.999999999e97
|
||||
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
|
||||
override val FLOAT_MEM_SIZE = 8
|
||||
override val FLOAT_MEM_SIZE = 8u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam // TODO this is not yet used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("floats")
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
|
||||
@@ -168,6 +167,5 @@ class ConfigFileTarget(
|
||||
zpFullsafe,
|
||||
compilerOptions
|
||||
)
|
||||
// note: there's no golden ram yet
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class Cx16Target: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
|
||||
@@ -37,7 +37,6 @@ class Cx16Target: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_END = 0x07ffu
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -87,7 +86,6 @@ class Cx16Target: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
|
||||
val flt = num.toDouble()
|
||||
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $flt")
|
||||
if (flt == 0.0)
|
||||
return zero
|
||||
|
||||
@@ -40,7 +40,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
|
||||
return when {
|
||||
exponent < 0 -> zero // underflow, use zero instead
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $flt")
|
||||
exponent == 0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
|
||||
@@ -25,7 +25,7 @@ class PETTarget: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
|
||||
@@ -36,7 +36,6 @@ class PETTarget: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_END = 0u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
@@ -74,7 +73,6 @@ class PETTarget: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = PETZeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class VMTarget: ICompilationTarget,
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
|
||||
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
|
||||
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE
|
||||
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
|
||||
@@ -38,7 +38,6 @@ class VMTarget: ICompilationTarget,
|
||||
override val BSSGOLDENRAM_START = 0u // not actually used
|
||||
override val BSSGOLDENRAM_END = 0u // not actually used
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
override lateinit var golden: GoldenRam // not actually used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
// little endian binary representation
|
||||
@@ -69,6 +68,10 @@ class VMTarget: ICompilationTarget,
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
|
||||
launchEmulatorWithTrace(programNameWithPath, quiet, traceEnabled = false)
|
||||
}
|
||||
|
||||
fun launchEmulatorWithTrace(programNameWithPath: Path, quiet: Boolean, traceEnabled: Boolean) {
|
||||
if(!quiet)
|
||||
println("\nStarting Virtual Machine...")
|
||||
|
||||
@@ -76,7 +79,7 @@ class VMTarget: ICompilationTarget,
|
||||
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
|
||||
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
|
||||
if(withExt.isReadable())
|
||||
vm.runProgram(withExt.readText(), quiet)
|
||||
vm.runProgram(withExt.readText(), quiet, traceEnabled)
|
||||
else
|
||||
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
|
||||
}
|
||||
@@ -85,14 +88,13 @@ class VMTarget: ICompilationTarget,
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = VirtualZeropage(compilerOptions)
|
||||
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface IVirtualMachineRunner {
|
||||
fun runProgram(irSource: String, quiet: Boolean)
|
||||
fun runProgram(irSource: String, quiet: Boolean, traceEnabled: Boolean = false)
|
||||
}
|
||||
|
||||
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
|
||||
|
||||
@@ -43,13 +43,13 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u
|
||||
))
|
||||
|
||||
// if(options.zeropage==ZeropageType.BASICSAFE) {
|
||||
// can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0
|
||||
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
|
||||
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
|
||||
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
|
||||
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
|
||||
// }
|
||||
// C128 doesn't support floating point calculations in Prog8, so we can use the FP zeropage locations
|
||||
// for general variables in both BASICSAFE and FLOATSAFE modes.
|
||||
// These addresses are normally used by the C128 BASIC floating point routines.
|
||||
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
|
||||
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
|
||||
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
|
||||
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
@@ -63,7 +63,9 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
// C128 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
|
||||
// C128 has limited zeropage space in BASICSAFE/FLOATSAFE modes.
|
||||
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
override val SCRATCH_REG = 0x03u // temp storage for a register byte, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
||||
override val SCRATCH_PTR = 0x9bu // temp storage for a pointer $9b+$9c
|
||||
override val SCRATCH_PTR = 0x04u // temp storage for a pointer $04+$05
|
||||
|
||||
|
||||
init {
|
||||
@@ -57,7 +57,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(arrayOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||
0x92, 0x96, 0x9c, 0x9e, 0x9f, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
@@ -72,7 +72,10 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
// C64 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
|
||||
// C64 has limited zeropage space compared to CX16. In BASICSAFE and FLOATSAFE modes,
|
||||
// there isn't enough contiguous space for all 32 bytes of virtual register variants.
|
||||
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,41 +22,47 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
synchronized(this) {
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.FLOATSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xd4u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
// Initialize free list - no synchronization needed during construction
|
||||
// (object reference doesn't escape until fully constructed)
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.FLOATSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xd4u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
val distinctFree = free.distinct()
|
||||
free.clear()
|
||||
free.addAll(distinctFree)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
allocateCx16VirtualRegisters()
|
||||
retainAllowed()
|
||||
}
|
||||
|
||||
private fun allocateCx16VirtualRegisters() {
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
|
||||
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
// CX16 DESIGN: Virtual registers are ALWAYS allocated in zeropage for all ZeropageType modes.
|
||||
// The Commander X16 has ample zeropage space (0x22-0x7F in BASICSAFE, up to 0x22-0xFF in FULL),
|
||||
// so virtual registers can always fit. This ensures consistent behavior across all modes.
|
||||
// Virtual registers occupy addresses 0x02-0x21 (32 bytes for r0-r15 variants).
|
||||
//
|
||||
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped*
|
||||
// elsewhere to fixed addresses. However, to be able for the compiler to "see" them as zeropage
|
||||
// variables, we have to register them here as well. This is important because the compiler
|
||||
// sometimes treats ZP variables more efficiently (for example if it's a pointer).
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
|
||||
@@ -46,7 +46,9 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
removeReservedFromFreePool()
|
||||
|
||||
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
|
||||
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
|
||||
// PET DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
|
||||
// PET has very limited zeropage space in BASICSAFE/FLOATSAFE modes (only 0xB1-0xBA).
|
||||
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
|
||||
allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package prog8tests.helpers
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
/**
|
||||
* Dummy IMemSizer implementation for testing.
|
||||
* Returns standard memory sizes for all data types.
|
||||
*/
|
||||
object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if (dt.isPointerArray)
|
||||
return 2 * numElements!!
|
||||
else if (dt.isArray) {
|
||||
require(numElements != null)
|
||||
return when (dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT -> numElements * 5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dummy IStringEncoding implementation for testing.
|
||||
* Returns empty list/string for all operations.
|
||||
*/
|
||||
object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ASCII IStringEncoding implementation for testing.
|
||||
* Encodes/decodes using simple ASCII mapping.
|
||||
*/
|
||||
object AsciiStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> =
|
||||
str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String =
|
||||
bytes.joinToString("") { it.toInt().toChar().toString() }
|
||||
}
|
||||
+24
-9
@@ -3,7 +3,17 @@ package prog8tests.helpers
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||
|
||||
/**
|
||||
* Test implementation of IErrorReporter that collects errors/warnings/infos in memory.
|
||||
*
|
||||
* @param throwExceptionAtReportIfErrors If true, report() will throw an exception when errors exist
|
||||
* @param keepMessagesAfterReporting If false, clear() is called after report()
|
||||
*/
|
||||
class ErrorReporterForTests(
|
||||
private val throwExceptionAtReportIfErrors: Boolean = true,
|
||||
private val keepMessagesAfterReporting: Boolean = false
|
||||
) : IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
@@ -11,19 +21,19 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in errors)
|
||||
if (text !in errors)
|
||||
errors.add(text)
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in warnings)
|
||||
if (text !in warnings)
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in infos)
|
||||
if (text !in infos)
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
@@ -31,17 +41,22 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
|
||||
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun noErrorForLine(position: Position): Boolean =
|
||||
!errors.any { ":${position.line}:" in it }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
// prints nothing in tests
|
||||
}
|
||||
|
||||
override fun report() {
|
||||
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
if (throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
if (!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,109 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.escape
|
||||
import prog8.code.core.toHex
|
||||
import prog8.code.core.unescape
|
||||
|
||||
/**
|
||||
* Unit tests for conversion functions in codeCore.
|
||||
* Focus on toHex boundaries and escape/unescape roundtrips.
|
||||
*/
|
||||
class TestConversions: FunSpec({
|
||||
|
||||
// ============================================================================
|
||||
// toHex() Tests
|
||||
// ============================================================================
|
||||
|
||||
test("toHex for 0-15 (decimal output)") {
|
||||
0.toHex() shouldBe "0"
|
||||
10.toHex() shouldBe "10"
|
||||
15.toHex() shouldBe "15"
|
||||
}
|
||||
|
||||
test("toHex for 16-255 (hex with $)") {
|
||||
16.toHex() shouldBe "\$10"
|
||||
255.toHex() shouldBe "\$ff"
|
||||
}
|
||||
|
||||
test("toHex for 256-65535 (4 digit hex)") {
|
||||
256.toHex() shouldBe "\$0100"
|
||||
65535.toHex() shouldBe "\$ffff"
|
||||
}
|
||||
|
||||
test("toHex for larger numbers (8 digit hex)") {
|
||||
65536.toHex() shouldBe "\$00010000"
|
||||
0x12345678.toHex() shouldBe "\$12345678"
|
||||
}
|
||||
|
||||
test("toHex for negative numbers") {
|
||||
(-1).toHex() shouldBe "-1"
|
||||
(-255).toHex() shouldBe "-\$ff"
|
||||
Int.MIN_VALUE.toHex() shouldBe "\$80000000"
|
||||
}
|
||||
|
||||
test("UInt.toHex") {
|
||||
0u.toHex() shouldBe "0"
|
||||
15u.toHex() shouldBe "15"
|
||||
255u.toHex() shouldBe "\$ff"
|
||||
65535u.toHex() shouldBe "\$ffff"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// escape() Tests
|
||||
// ============================================================================
|
||||
|
||||
test("escape for plain string") {
|
||||
"hello".escape() shouldBe "hello"
|
||||
}
|
||||
|
||||
test("escape for tab and newline") {
|
||||
"hello\tworld".escape() shouldBe "hello\\tworld"
|
||||
"hello\nworld".escape() shouldBe "hello\\nworld"
|
||||
}
|
||||
|
||||
test("escape for double quote") {
|
||||
"hello\"world".escape() shouldBe "hello\\\"world"
|
||||
}
|
||||
|
||||
test("escape for unicode outside passthrough range") {
|
||||
"€".escape() shouldBe "\\u20ac"
|
||||
}
|
||||
|
||||
test("escape for passthrough characters (\\u8000-\\u80ff)") {
|
||||
"\u8000".escape() shouldBe "\\x00"
|
||||
"\u80ff".escape() shouldBe "\\xff"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// unescape() Tests
|
||||
// ============================================================================
|
||||
|
||||
test("unescape for plain string") {
|
||||
"hello".unescape() shouldBe "hello"
|
||||
}
|
||||
|
||||
test("unescape for tab and newline") {
|
||||
"hello\\tworld".unescape() shouldBe "hello\tworld"
|
||||
"hello\\nworld".unescape() shouldBe "hello\nworld"
|
||||
}
|
||||
|
||||
test("unescape for double quote") {
|
||||
"hello\\\"world".unescape() shouldBe "hello\"world"
|
||||
}
|
||||
|
||||
test("unescape for unicode") {
|
||||
"\\u0041".unescape() shouldBe "A"
|
||||
"\\u20ac".unescape() shouldBe "€"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Roundtrip Tests
|
||||
// ============================================================================
|
||||
|
||||
test("escape then unescape roundtrip") {
|
||||
val original = "hello\t\nworld\"test"
|
||||
original.escape().unescape() shouldBe original
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,296 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.booleans.shouldBeFalse
|
||||
import io.kotest.matchers.booleans.shouldBeTrue
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.core.*
|
||||
|
||||
/**
|
||||
* Unit tests for DataType and BaseDataType classes in codeCore.
|
||||
* Focus on type predicates, relationships, and key operations.
|
||||
*/
|
||||
class TestDataType: FunSpec({
|
||||
|
||||
val dummyMemSizer = object : IMemSizer {
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if (dt.isPointerArray) return 2 * numElements!!
|
||||
if (dt.isArray || dt.isSplitWordArray) {
|
||||
require(numElements != null)
|
||||
return when (dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
|
||||
BaseDataType.LONG -> numElements * 4
|
||||
BaseDataType.FLOAT -> numElements * 5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BaseDataType Extension Property Tests
|
||||
// ============================================================================
|
||||
|
||||
test("isByte extension") {
|
||||
BaseDataType.UBYTE.isByte.shouldBeTrue()
|
||||
BaseDataType.BYTE.isByte.shouldBeTrue()
|
||||
BaseDataType.UWORD.isByte.shouldBeFalse()
|
||||
BaseDataType.WORD.isByte.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isByteOrBool extension") {
|
||||
BaseDataType.UBYTE.isByteOrBool.shouldBeTrue()
|
||||
BaseDataType.BYTE.isByteOrBool.shouldBeTrue()
|
||||
BaseDataType.BOOL.isByteOrBool.shouldBeTrue()
|
||||
BaseDataType.UWORD.isByteOrBool.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isWord extension") {
|
||||
BaseDataType.UWORD.isWord.shouldBeTrue()
|
||||
BaseDataType.WORD.isWord.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isWord.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isInteger extension") {
|
||||
BaseDataType.UBYTE.isInteger.shouldBeTrue()
|
||||
BaseDataType.WORD.isInteger.shouldBeTrue()
|
||||
BaseDataType.LONG.isInteger.shouldBeTrue()
|
||||
BaseDataType.FLOAT.isInteger.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isNumeric extension") {
|
||||
BaseDataType.UBYTE.isNumeric.shouldBeTrue()
|
||||
BaseDataType.FLOAT.isNumeric.shouldBeTrue()
|
||||
BaseDataType.BOOL.isNumeric.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isSigned extension") {
|
||||
BaseDataType.BYTE.isSigned.shouldBeTrue()
|
||||
BaseDataType.WORD.isSigned.shouldBeTrue()
|
||||
BaseDataType.LONG.isSigned.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isSigned.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isArray extension") {
|
||||
BaseDataType.ARRAY.isArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY_SPLITW.isArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY_POINTER.isArray.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isArray.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isPointer extension") {
|
||||
BaseDataType.POINTER.isPointer.shouldBeTrue()
|
||||
BaseDataType.UBYTE.isPointer.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("isSplitWordArray extension") {
|
||||
BaseDataType.ARRAY_SPLITW.isSplitWordArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY_POINTER.isSplitWordArray.shouldBeTrue()
|
||||
BaseDataType.ARRAY.isSplitWordArray.shouldBeFalse()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Companion Object Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.forDt creates simple types") {
|
||||
DataType.forDt(BaseDataType.UBYTE) shouldBe DataType.UBYTE
|
||||
DataType.forDt(BaseDataType.WORD) shouldBe DataType.WORD
|
||||
DataType.forDt(BaseDataType.LONG) shouldBe DataType.LONG
|
||||
DataType.forDt(BaseDataType.FLOAT) shouldBe DataType.FLOAT
|
||||
}
|
||||
|
||||
test("DataType.forDt throws for struct instance") {
|
||||
shouldThrow<NotImplementedError> {
|
||||
DataType.forDt(BaseDataType.STRUCT_INSTANCE)
|
||||
}
|
||||
}
|
||||
|
||||
test("DataType.arrayFor creates array types") {
|
||||
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE)
|
||||
ubyteArray.base shouldBe BaseDataType.ARRAY
|
||||
ubyteArray.sub shouldBe BaseDataType.UBYTE
|
||||
|
||||
val wordArraySplit = DataType.arrayFor(BaseDataType.WORD)
|
||||
wordArraySplit.base shouldBe BaseDataType.ARRAY_SPLITW
|
||||
wordArraySplit.sub shouldBe BaseDataType.WORD
|
||||
}
|
||||
|
||||
test("DataType.pointer creates pointer types") {
|
||||
val ptrByte = DataType.pointer(BaseDataType.UBYTE)
|
||||
ptrByte.base shouldBe BaseDataType.POINTER
|
||||
ptrByte.sub shouldBe BaseDataType.UBYTE
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Property Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.isBasic property") {
|
||||
DataType.UBYTE.isBasic.shouldBeTrue()
|
||||
DataType.WORD.isBasic.shouldBeTrue()
|
||||
DataType.STR.isBasic.shouldBeFalse()
|
||||
DataType.arrayFor(BaseDataType.UBYTE).isBasic.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("DataType.isByte property") {
|
||||
DataType.UBYTE.isByte.shouldBeTrue()
|
||||
DataType.BYTE.isByte.shouldBeTrue()
|
||||
DataType.WORD.isByte.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("DataType.isNumeric property") {
|
||||
DataType.UBYTE.isNumeric.shouldBeTrue()
|
||||
DataType.FLOAT.isNumeric.shouldBeTrue()
|
||||
DataType.BOOL.isNumeric.shouldBeFalse()
|
||||
DataType.STR.isNumeric.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("DataType.isArray property") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE).isArray.shouldBeTrue()
|
||||
DataType.UBYTE.isArray.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("DataType.isPointer property") {
|
||||
DataType.pointer(BaseDataType.UBYTE).isPointer.shouldBeTrue()
|
||||
DataType.UBYTE.isPointer.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("DataType.isString property") {
|
||||
DataType.STR.isString.shouldBeTrue()
|
||||
DataType.UBYTE.isString.shouldBeFalse()
|
||||
}
|
||||
|
||||
test("DataType.isPassByRef property") {
|
||||
DataType.STR.isPassByRef.shouldBeTrue()
|
||||
DataType.arrayFor(BaseDataType.UBYTE).isPassByRef.shouldBeTrue()
|
||||
DataType.UBYTE.isPassByRef.shouldBeFalse()
|
||||
DataType.pointer(BaseDataType.UBYTE).isPassByRef.shouldBeFalse()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Method Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.elementType returns element type for arrays") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE).elementType() shouldBe DataType.UBYTE
|
||||
DataType.arrayFor(BaseDataType.WORD).elementType() shouldBe DataType.WORD
|
||||
DataType.STR.elementType() shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
test("DataType.elementType throws for non-arrays") {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
DataType.UBYTE.elementType()
|
||||
}
|
||||
}
|
||||
|
||||
test("DataType.dereference for pointers") {
|
||||
DataType.pointer(BaseDataType.UBYTE).dereference() shouldBe DataType.UBYTE
|
||||
DataType.pointer(BaseDataType.WORD).dereference() shouldBe DataType.WORD
|
||||
DataType.UWORD.dereference() shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
test("DataType.size with memsizer") {
|
||||
DataType.UBYTE.size(dummyMemSizer) shouldBe 1
|
||||
DataType.WORD.size(dummyMemSizer) shouldBe 2
|
||||
DataType.LONG.size(dummyMemSizer) shouldBe 4
|
||||
DataType.FLOAT.size(dummyMemSizer) shouldBe 5
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Comparison Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType equals for same types") {
|
||||
DataType.UBYTE shouldBe DataType.UBYTE
|
||||
DataType.WORD shouldBe DataType.WORD
|
||||
DataType.STR shouldBe DataType.STR
|
||||
}
|
||||
|
||||
test("DataType equals for different types") {
|
||||
DataType.UBYTE shouldNotBe DataType.BYTE
|
||||
DataType.UBYTE shouldNotBe DataType.WORD
|
||||
}
|
||||
|
||||
test("BaseDataType.largerSizeThan") {
|
||||
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
|
||||
}
|
||||
|
||||
test("BaseDataType.equalsSize") {
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
|
||||
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType.isAssignableTo Tests
|
||||
// ============================================================================
|
||||
|
||||
test("bool assignability") {
|
||||
DataType.BOOL.isAssignableTo(DataType.BOOL).shouldBeTrue()
|
||||
DataType.BOOL.isAssignableTo(DataType.UBYTE).shouldBeFalse()
|
||||
}
|
||||
|
||||
test("ubyte assignability") {
|
||||
DataType.UBYTE.isAssignableTo(DataType.UBYTE).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
DataType.UBYTE.isAssignableTo(DataType.FLOAT).shouldBeTrue()
|
||||
}
|
||||
|
||||
test("byte assignability") {
|
||||
DataType.BYTE.isAssignableTo(DataType.BYTE).shouldBeTrue()
|
||||
DataType.BYTE.isAssignableTo(DataType.WORD).shouldBeTrue()
|
||||
DataType.BYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
}
|
||||
|
||||
test("uword assignability") {
|
||||
DataType.UWORD.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
DataType.UWORD.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
DataType.UWORD.isAssignableTo(DataType.FLOAT).shouldBeTrue()
|
||||
}
|
||||
|
||||
test("string assignability") {
|
||||
DataType.STR.isAssignableTo(DataType.STR).shouldBeTrue()
|
||||
DataType.STR.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
}
|
||||
|
||||
test("pointer assignability") {
|
||||
val ptr = DataType.pointer(BaseDataType.UBYTE)
|
||||
ptr.isAssignableTo(DataType.UWORD).shouldBeTrue()
|
||||
ptr.isAssignableTo(DataType.LONG).shouldBeTrue()
|
||||
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)).shouldBeTrue()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType.toString Tests
|
||||
// ============================================================================
|
||||
|
||||
test("DataType.toString for basic types") {
|
||||
DataType.UBYTE.toString() shouldBe "ubyte"
|
||||
DataType.WORD.toString() shouldBe "word"
|
||||
DataType.LONG.toString() shouldBe "long"
|
||||
DataType.FLOAT.toString() shouldBe "float"
|
||||
}
|
||||
|
||||
test("DataType.toString for arrays") {
|
||||
DataType.arrayFor(BaseDataType.UBYTE).toString() shouldBe "ubyte[]"
|
||||
DataType.arrayFor(BaseDataType.WORD).toString() shouldBe "word[] (split)"
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,721 @@
|
||||
package prog8tests.codecore
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.booleans.shouldBeTrue
|
||||
import io.kotest.matchers.collections.shouldBeEmpty
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||
import io.kotest.matchers.maps.shouldContainKey
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C128Target
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.PETTarget
|
||||
import prog8.code.target.zp.C128Zeropage
|
||||
import prog8.code.target.zp.C64Zeropage
|
||||
import prog8.code.target.zp.CX16Zeropage
|
||||
import prog8.code.target.zp.PETZeropage
|
||||
|
||||
|
||||
/**
|
||||
* Comprehensive tests for Zeropage free list configurations across all targets.
|
||||
*
|
||||
* Tests verify that for each target and ZeropageType combination:
|
||||
* - The exact list of free locations is correct
|
||||
* - Scratch locations are properly reserved
|
||||
* - Virtual registers are allocated when appropriate
|
||||
*/
|
||||
class TestZeropageFreeList: FunSpec({
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
fun createOptions(target: ICompilationTarget, zpType: ZeropageType, floats: Boolean = false): CompilationOptions {
|
||||
return CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(zpType)
|
||||
.floats(floats)
|
||||
.compilerVersion("test")
|
||||
.build()
|
||||
}
|
||||
|
||||
fun verifyScratchLocationsReserved(zp: Zeropage) {
|
||||
// Verify scratch locations are not in free list
|
||||
zp.free.contains(zp.SCRATCH_B1) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_REG) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W1) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W1 + 1u) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W2) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_W2 + 1u) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_PTR) shouldBe false
|
||||
zp.free.contains(zp.SCRATCH_PTR + 1u) shouldBe false
|
||||
}
|
||||
|
||||
fun verifyVirtualRegistersAllocated(zp: Zeropage, expected: Boolean) {
|
||||
if (expected) {
|
||||
// Check that virtual registers are allocated
|
||||
for (reg in 0..15) {
|
||||
zp.allocatedVariables shouldContainKey "cx16.r${reg}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// C64 Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("C64Zeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have specific free locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// BASICSAFE includes specific BASIC-safe locations
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
|
||||
// Verify scratch locations are reserved
|
||||
verifyScratchLocationsReserved(zp)
|
||||
|
||||
// Virtual registers should be allocated for BASICSAFE
|
||||
verifyVirtualRegistersAllocated(zp, expected = false) // C64 doesn't allocate virtual regs for BASICSAFE
|
||||
}
|
||||
|
||||
test("KERNALSAFE should have larger free list than BASICSAFE") {
|
||||
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
|
||||
// KERNALSAFE has more locations than BASICSAFE
|
||||
val basicsafeOptions = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val basicsafeZp = C64Zeropage(basicsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true) // KERNALSAFE gets virtual registers
|
||||
}
|
||||
|
||||
test("FLOATSAFE should exclude float-used locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
|
||||
// FLOATSAFE should have fewer locations than KERNALSAFE due to float exclusions
|
||||
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
|
||||
// Some float-related locations should not be free (exact set depends on implementation)
|
||||
// Just verify the size difference is significant
|
||||
(kernalsafeZp.free.size - zp.free.size) shouldBeGreaterThan 10
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have maximum free locations minus IRQ") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FULL)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// FULL should have the most free locations
|
||||
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0xa0u) shouldBe false
|
||||
zp.free.contains(0xa1u) shouldBe false
|
||||
zp.free.contains(0xa2u) shouldBe false
|
||||
zp.free.contains(0x91u) shouldBe false
|
||||
zp.free.contains(0xc0u) shouldBe false
|
||||
zp.free.contains(0xc5u) shouldBe false
|
||||
zp.free.contains(0xcbu) shouldBe false
|
||||
zp.free.contains(0xf5u) shouldBe false
|
||||
zp.free.contains(0xf6u) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CX16 Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("CX16Zeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have 0x22-0x7F") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 should be allocated, not free
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free shouldNotBe addr
|
||||
}
|
||||
|
||||
// Free should be 0x22-0x7F minus scratch locations
|
||||
for (addr in 0x22u..0x7Fu) {
|
||||
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
|
||||
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("KERNALSAFE should have 0x22-0x7F and 0xA9-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
val basicsafeOptions = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val basicsafeZp = CX16Zeropage(basicsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
|
||||
|
||||
// Should include high memory range
|
||||
zp.free.contains(0xa9u) shouldBe true
|
||||
zp.free.contains(0xffu) shouldBe true
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FLOATSAFE should have 0x22-0x7F and 0xD4-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Should include 0xD4-0xFF range
|
||||
zp.free.contains(0xd4u) shouldBe true
|
||||
zp.free.contains(0xffu) shouldBe true
|
||||
|
||||
// FLOATSAFE has different high range than KERNALSAFE (0xD4-0xFF vs 0xA9-0xFF)
|
||||
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
|
||||
|
||||
// FLOATSAFE should have fewer locations than KERNALSAFE (smaller high range)
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have 0x22-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FULL)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// FULL should have the most free locations
|
||||
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
|
||||
|
||||
// Should include all locations from 0x22 to 0xFF (minus scratch)
|
||||
for (addr in 0x80u..0xA8u) {
|
||||
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
|
||||
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PET Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("PETZeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have minimal locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA per code, minus scratch locations)
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
// After scratch removal, some locations in 0xb1-0xba range should still be free
|
||||
// Just verify the free list is small but non-empty
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
zp.free.size shouldBeLessThan 20
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FLOATSAFE should be same as BASICSAFE") {
|
||||
val floatOptions = createOptions(PETTarget(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val floatZp = PETZeropage(floatOptions)
|
||||
|
||||
val basicOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val basicZp = PETZeropage(basicOptions)
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for PET
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
}
|
||||
|
||||
test("KERNALSAFE should exclude IRQ locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0x8du) shouldBe false
|
||||
zp.free.contains(0x8eu) shouldBe false
|
||||
zp.free.contains(0x8fu) shouldBe false
|
||||
zp.free.contains(0x97u) shouldBe false
|
||||
zp.free.contains(0x98u) shouldBe false
|
||||
zp.free.contains(0x99u) shouldBe false
|
||||
zp.free.contains(0x9au) shouldBe false
|
||||
zp.free.contains(0x9bu) shouldBe false
|
||||
zp.free.contains(0x9eu) shouldBe false
|
||||
zp.free.contains(0xa7u) shouldBe false
|
||||
zp.free.contains(0xa8u) shouldBe false
|
||||
zp.free.contains(0xa9u) shouldBe false
|
||||
zp.free.contains(0xaau) shouldBe false
|
||||
zp.free.contains(0xaaau) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have all locations minus IRQ") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.FULL)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// FULL should have most locations
|
||||
zp.free.size shouldBeGreaterThan 100
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0x8du) shouldBe false
|
||||
zp.free.contains(0x97u) shouldBe false
|
||||
zp.free.contains(0xa7u) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// C128 Zeropage Tests
|
||||
// ============================================================================
|
||||
|
||||
context("C128Zeropage") {
|
||||
|
||||
test("DONTUSE should have no free locations") {
|
||||
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
zp.free.shouldBeEmpty()
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have specific safe locations") {
|
||||
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
|
||||
// Should include some specific BASIC-safe locations (excluding scratch)
|
||||
// 0x0b is SCRATCH_PTR, 0x74 is SCRATCH_B1 for C128, so they're not free
|
||||
// Check for other locations that should be free
|
||||
zp.free.contains(0x0du) shouldBe true
|
||||
zp.free.contains(0x1bu) shouldBe true // In BASICSAFE range
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FLOATSAFE should be same as BASICSAFE for C128") {
|
||||
val floatOptions = createOptions(C128Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val floatZp = C128Zeropage(floatOptions)
|
||||
|
||||
val basicOptions = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val basicZp = C128Zeropage(basicOptions)
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for C128
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
}
|
||||
|
||||
test("KERNALSAFE should have BASIC variables area") {
|
||||
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// Should include 0x0A-0x8F (BASIC variables area) minus scratch/virtual reg locations
|
||||
// 0x0a is where virtual registers start for C128, so not free
|
||||
// 0x8fu should be free
|
||||
zp.free.contains(0x8fu) shouldBe true
|
||||
|
||||
// Should include additional specific locations
|
||||
zp.free.contains(0x92u) shouldBe true
|
||||
// 0xb6u is SCRATCH_W1 for C128 - won't be free
|
||||
zp.free.contains(0x96u) shouldBe true // Alternative location
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
|
||||
test("FULL should have maximum locations minus IRQ") {
|
||||
val options = createOptions(C128Target(), ZeropageType.FULL)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// FULL should have many locations (0x0A-0xFF minus IRQ and scratch)
|
||||
// 0x0a is where virtual registers start for C128, so not free
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
|
||||
// IRQ locations should not be free
|
||||
zp.free.contains(0x90u) shouldBe false
|
||||
zp.free.contains(0x91u) shouldBe false
|
||||
zp.free.contains(0xa0u) shouldBe false
|
||||
zp.free.contains(0xa1u) shouldBe false
|
||||
zp.free.contains(0xa2u) shouldBe false
|
||||
zp.free.contains(0xc0u) shouldBe false
|
||||
zp.free.contains(0xccu) shouldBe false
|
||||
zp.free.contains(0xcdu) shouldBe false
|
||||
zp.free.contains(0xd0u) shouldBe false
|
||||
zp.free.contains(0xd1u) shouldBe false
|
||||
zp.free.contains(0xd2u) shouldBe false
|
||||
zp.free.contains(0xd3u) shouldBe false
|
||||
zp.free.contains(0xd4u) shouldBe false
|
||||
zp.free.contains(0xd5u) shouldBe false
|
||||
zp.free.contains(0xf7u) shouldBe false
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
verifyVirtualRegistersAllocated(zp, expected = true)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cross-Target Comparison Tests
|
||||
// ============================================================================
|
||||
|
||||
context("Cross-Target Comparisons") {
|
||||
|
||||
test("all targets should have empty free list for DONTUSE") {
|
||||
// C64
|
||||
val c64Options = createOptions(C64Target(), ZeropageType.DONTUSE)
|
||||
C64Zeropage(c64Options).free.shouldBeEmpty()
|
||||
|
||||
// CX16
|
||||
val cx16Options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
|
||||
CX16Zeropage(cx16Options).free.shouldBeEmpty()
|
||||
|
||||
// PET
|
||||
val petOptions = createOptions(PETTarget(), ZeropageType.DONTUSE)
|
||||
PETZeropage(petOptions).free.shouldBeEmpty()
|
||||
|
||||
// C128
|
||||
val c128Options = createOptions(C128Target(), ZeropageType.DONTUSE)
|
||||
C128Zeropage(c128Options).free.shouldBeEmpty()
|
||||
}
|
||||
|
||||
test("scratch locations differ between targets") {
|
||||
val c64Options = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val c64Zp = C64Zeropage(c64Options)
|
||||
|
||||
val cx16Options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val cx16Zp = CX16Zeropage(cx16Options)
|
||||
|
||||
val petOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val petZp = PETZeropage(petOptions)
|
||||
|
||||
val c128Options = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val c128Zp = C128Zeropage(c128Options)
|
||||
|
||||
// Each target has different scratch locations
|
||||
c64Zp.SCRATCH_B1 shouldBe 0x02u
|
||||
cx16Zp.SCRATCH_B1 shouldBe 0x7au
|
||||
petZp.SCRATCH_B1 shouldBe 0xb3u
|
||||
c128Zp.SCRATCH_B1 shouldBe 0x74u
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Exhaustive Zeropage Address Tests - All 256 locations
|
||||
// ============================================================================
|
||||
|
||||
context("Exhaustive C64 Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - exact free address set") {
|
||||
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// BASICSAFE: specific BASIC-safe locations plus virtual registers at 0x04-0x23
|
||||
// Scratch locations are removed from free list
|
||||
// Just verify key properties
|
||||
zp.free.size shouldBeGreaterThan 10
|
||||
|
||||
// Scratch locations should NOT be free
|
||||
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Some known free locations
|
||||
zp.free.contains(0x06u) shouldBe true
|
||||
zp.free.contains(0x92u) shouldBe true
|
||||
}
|
||||
|
||||
test("KERNALSAFE - exact free address set") {
|
||||
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// KERNALSAFE: larger set including more locations, plus virtual registers at 0x04-0x23
|
||||
// Scratch locations (0x02,0x03,0x04,0x05,0xFB,0xFC,0xFD,0xFE) are removed
|
||||
// Virtual registers 0x04-0x23 are allocated (not free)
|
||||
|
||||
// Just verify key properties since exact set is complex
|
||||
zp.free.size shouldBeGreaterThan 50
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Scratch locations should NOT be free
|
||||
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("FLOATSAFE - excludes float-used locations") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// FLOATSAFE is KERNALSAFE minus float-used locations
|
||||
zp.free.size shouldBeGreaterThan 20
|
||||
|
||||
// FLOATSAFE should have fewer locations than KERNALSAFE
|
||||
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
}
|
||||
|
||||
test("FULL - maximum free locations minus IRQ") {
|
||||
val options = createOptions(C64Target(), ZeropageType.FULL)
|
||||
val zp = C64Zeropage(options)
|
||||
|
||||
// FULL should have the most locations
|
||||
zp.free.size shouldBeGreaterThan 100
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Scratch locations should NOT be free
|
||||
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("Exhaustive CX16 Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - 0x22-0x7F minus scratch and virtual registers") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 are allocated (not free)
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// BASICSAFE: 0x22-0x7F minus scratch
|
||||
for (addr in 0x22u..0x7Fu) {
|
||||
val isScratch = addr in setOf(0x7Au, 0x7Bu, 0x7Cu, 0x7Du, 0x7Eu, 0x7Fu, 0x22u, 0x23u)
|
||||
zp.free.contains(addr) shouldBe !isScratch
|
||||
}
|
||||
|
||||
// Above 0x7F should not be free for BASICSAFE
|
||||
for (addr in 0x80u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE - 0x22-0x7F and 0xA9-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 are allocated (not free)
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Should include 0xA9-0xFF range
|
||||
zp.free.contains(0xA9u) shouldBe true
|
||||
zp.free.contains(0xFFu) shouldBe true
|
||||
}
|
||||
|
||||
test("FLOATSAFE - 0x22-0x7F and 0xD4-0xFF") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Should include 0xD4-0xFF range
|
||||
zp.free.contains(0xD4u) shouldBe true
|
||||
zp.free.contains(0xFFu) shouldBe true
|
||||
|
||||
// Should NOT include 0xA9-0xD3 range that KERNALSAFE has
|
||||
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
|
||||
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
|
||||
|
||||
zp.free.size shouldBeLessThan kernalsafeZp.free.size
|
||||
}
|
||||
|
||||
test("FULL - 0x22-0xFF minus scratch") {
|
||||
val options = createOptions(Cx16Target(), ZeropageType.FULL)
|
||||
val zp = CX16Zeropage(options)
|
||||
|
||||
// Virtual registers 0x02-0x21 are allocated (not free)
|
||||
for (addr in 0x02u..0x21u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// FULL should have most locations from 0x22-0xFF
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
}
|
||||
}
|
||||
|
||||
context("Exhaustive PET Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
zp.free.size shouldBeLessThan 15
|
||||
}
|
||||
|
||||
test("KERNALSAFE - excludes IRQ locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0x8Du, 0x8Eu, 0x8Fu, 0x97u, 0x98u, 0x99u, 0x9Au, 0x9Bu, 0x9Eu,
|
||||
0xA7u, 0xA8u, 0xA9u, 0xAAu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("FULL - all minus IRQ locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.FULL)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// FULL should have most locations
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0x8Du, 0x97u, 0xA7u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("Exhaustive C128 Zeropage Tests") {
|
||||
|
||||
test("DONTUSE - no addresses should be free") {
|
||||
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
for (addr in 0x00u..0xFFu) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - specific safe locations") {
|
||||
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// BASICSAFE has specific locations
|
||||
zp.free.size shouldBeGreaterThan 50
|
||||
|
||||
// Note: Virtual registers are NOT allocated for BASICSAFE on C128
|
||||
// (only for FULL and KERNALSAFE modes)
|
||||
}
|
||||
|
||||
test("KERNALSAFE - BASIC variables area 0x0A-0x8F") {
|
||||
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// Virtual registers at 0x0A-0x29 are allocated (not free)
|
||||
for (addr in 0x0Au..0x29u) {
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
|
||||
// Should include some locations in BASIC variables area
|
||||
zp.free.contains(0x8Fu) shouldBe true
|
||||
}
|
||||
|
||||
test("FULL - 0x0A-0xFF minus IRQ") {
|
||||
val options = createOptions(C128Target(), ZeropageType.FULL)
|
||||
val zp = C128Zeropage(options)
|
||||
|
||||
// FULL should have many locations
|
||||
zp.free.size shouldBeGreaterThan 150
|
||||
|
||||
// IRQ locations should NOT be free
|
||||
setOf(0x90u, 0x91u, 0xA0u, 0xA1u, 0xA2u, 0xC0u, 0xCCu, 0xCDu,
|
||||
0xD0u, 0xD1u, 0xD2u, 0xD3u, 0xD4u, 0xD5u, 0xF7u).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Helper infix function for readability
|
||||
infix fun Int.shouldBeLessThan(value: Int) {
|
||||
if (this >= value) {
|
||||
throw AssertionError("Expected $this to be less than $value")
|
||||
}
|
||||
}
|
||||
@@ -5,39 +5,9 @@ plugins {
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
|
||||
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
setSrcDirs(listOf("$projectDir/src"))
|
||||
}
|
||||
resources {
|
||||
setSrcDirs(listOf("$projectDir/res"))
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
setSrcDirs(listOf("$projectDir/test"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn("cleanTest")
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events("skipped", "failed")
|
||||
}
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -17,6 +17,10 @@ internal class AssemblyProgram(
|
||||
outputDir: Path,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
|
||||
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -737,18 +737,18 @@ $loopLabel sty $indexVar
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
when {
|
||||
iterableDt.isByteArray -> {
|
||||
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
|
||||
1, -1 -> {
|
||||
throw AssemblyError("step 1 and -1 should have been handled specifically $range ${stmt.position}")
|
||||
}
|
||||
2 -> {
|
||||
if(range.last==255 || range.last==254) {
|
||||
@@ -805,10 +805,9 @@ $loopLabel""")
|
||||
}
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
|
||||
// loop over word range via loopvar, step >= 2 or <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
1, -1 -> {
|
||||
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
|
||||
}
|
||||
else -> {
|
||||
// word, step >= 2 or <= -2
|
||||
@@ -826,7 +825,6 @@ $loopLabel""")
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #>${range.last}
|
||||
bne +
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
clc
|
||||
@@ -843,11 +841,54 @@ $loopLabel""")
|
||||
iterableDt.isLongArray -> {
|
||||
// loop over long range via loopvar, step >= 2 or <= -2
|
||||
when(range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
1, -1 -> {
|
||||
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
|
||||
}
|
||||
else -> {
|
||||
TODO("for long range. Report this error please, and use word loops in the meantime ${stmt.position}")
|
||||
// long, step >= 2 or <= -2
|
||||
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
|
||||
val from = range.first.toLongHex()
|
||||
val to = range.last.toLongHex()
|
||||
val step = range.step.toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
lda #$${from.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda #$${from.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda #$${from.take(2)}
|
||||
sta $varname+3
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #$${to.substring(6, 8)}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #$${to.substring(4, 6)}
|
||||
bne +
|
||||
lda $varname+2
|
||||
cmp #$${to.substring(2, 4)}
|
||||
bne +
|
||||
lda $varname+3
|
||||
cmp #$${to.take(2)}
|
||||
beq $endLabel
|
||||
+ lda $varname
|
||||
clc
|
||||
adc #$${step.substring(6, 8)}
|
||||
sta $varname
|
||||
lda $varname+1
|
||||
adc #$${step.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda $varname+2
|
||||
adc #$${step.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda $varname+3
|
||||
adc #$${step.take(2)}
|
||||
sta $varname+3""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -987,12 +1028,106 @@ $loopLabel""")
|
||||
|
||||
private fun translateForSimpleLongRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==1)
|
||||
TODO("for long range step +1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
val from = range.first.toLongHex()
|
||||
val to = range.last.toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
lda #$${from.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda #$${from.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda #$${from.take(2)}
|
||||
sta $varname+3
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #$${to.substring(6, 8)}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #$${to.substring(4, 6)}
|
||||
bne +
|
||||
lda $varname+2
|
||||
cmp #$${to.substring(2, 4)}
|
||||
bne +
|
||||
lda $varname+3
|
||||
cmp #$${to.take(2)}
|
||||
beq $endLabel
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1
|
||||
bne $loopLabel
|
||||
inc $varname+2
|
||||
bne $loopLabel
|
||||
inc $varname+3""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun translateForSimpleLongRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
require(range.step==-1)
|
||||
TODO("for long range step -1. Report this error please, and use nested word loops in the meantime ${stmt.position}")
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
val from = range.first.toLongHex()
|
||||
val to = range.last.toLongHex()
|
||||
asmgen.out("""
|
||||
lda #$${from.substring(6, 8)}
|
||||
sta $varname
|
||||
lda #$${from.substring(4, 6)}
|
||||
sta $varname+1
|
||||
lda #$${from.substring(2, 4)}
|
||||
sta $varname+2
|
||||
lda #$${from.take(2)}
|
||||
sta $varname+3
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(range.last==0) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne ++++
|
||||
lda $varname+1
|
||||
bne +++
|
||||
lda $varname+2
|
||||
bne ++
|
||||
lda $varname+3
|
||||
beq $endLabel""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #$${to.substring(6, 8)}
|
||||
bne +
|
||||
lda $varname+1
|
||||
cmp #$${to.substring(4, 6)}
|
||||
bne +
|
||||
lda $varname+2
|
||||
cmp #$${to.substring(2, 4)}
|
||||
bne +
|
||||
lda $varname+3
|
||||
cmp #$${to.take(2)}
|
||||
beq $endLabel""")
|
||||
}
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
|
||||
|
||||
@@ -203,8 +203,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
|
||||
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
|
||||
return when(arg) {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
|
||||
is PtFunctionCall if arg.builtin -> {
|
||||
if (arg.name in setOf("lsb", "msb", "lsw", "msw"))
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
else if (arg.name == "mkword" || arg.name == "mklong2")
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||
@@ -268,8 +268,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
|
||||
val reg = parameter.register
|
||||
if(reg!=null) {
|
||||
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val varName = "cx16.${reg.name.lowercase()}"
|
||||
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val varName = reg.asScopedNameVirtualReg(value.type).joinToString(".")
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type)
|
||||
} else {
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
|
||||
@@ -333,7 +333,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
|
||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type.base)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, value.position)
|
||||
} else {
|
||||
val scope = value.definingISub()
|
||||
val target: AsmAssignTarget =
|
||||
@@ -344,7 +344,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
}
|
||||
val src = if(value.type.isPassByRef) {
|
||||
if(value is PtIdentifier) {
|
||||
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY)
|
||||
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
|
||||
addr.add(value)
|
||||
addr.parent = scope as PtNode
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -352,7 +352,7 @@ internal class ProgramAndVarsGen(
|
||||
val varsInBlock = getVars(scope)
|
||||
|
||||
// Zeropage Variables
|
||||
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
|
||||
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
|
||||
zeropagevars2asm(varnames)
|
||||
|
||||
// MemDefs and Consts
|
||||
@@ -391,20 +391,23 @@ internal class ProgramAndVarsGen(
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
|
||||
if(field.first.isFloat) {
|
||||
"["+compTarget.getFloatAsmBytes(value.number!!)+"]"
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
"["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
} else {
|
||||
when {
|
||||
value.number!=null -> {
|
||||
when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(field.first.isPointer)
|
||||
"$"+value.number!!.toInt().toString(16)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(field.first.isInteger)
|
||||
value.number!!.toInt().toString()
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.number.toString()
|
||||
value.value.toString()
|
||||
}
|
||||
value.addressOfSymbol!=null -> value.addressOfSymbol!!
|
||||
value.boolean!=null -> if(value.boolean==true) "1" else "0"
|
||||
else -> throw AssemblyError("weird struct initial value $value")
|
||||
is StArrayElement.AddressOf -> value.symbol
|
||||
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
|
||||
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
|
||||
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,7 +505,7 @@ internal class ProgramAndVarsGen(
|
||||
val varsInSubroutine = getVars(scope)
|
||||
|
||||
// Zeropage Variables
|
||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
|
||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
|
||||
zeropagevars2asm(varnames)
|
||||
|
||||
// MemDefs and Consts
|
||||
@@ -739,7 +742,7 @@ internal class ProgramAndVarsGen(
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
}
|
||||
alignedStrings.sortedBy { it.align }.forEach {
|
||||
outputStringvar(
|
||||
@@ -748,22 +751,22 @@ internal class ProgramAndVarsGen(
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
}
|
||||
|
||||
notAlignedOther.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
if(it.dt.isArray)
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
else
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
|
||||
}
|
||||
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
if(it.dt.isArray)
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
|
||||
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
|
||||
else
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
|
||||
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -914,7 +917,9 @@ internal class ProgramAndVarsGen(
|
||||
dt.isFloatArray -> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
val floatFills = array.map {
|
||||
compTarget.getFloatAsmBytes(it.number!!)
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float array")
|
||||
compTarget.getFloatAsmBytes(numValue)
|
||||
}
|
||||
asmgen.out(varname)
|
||||
for (f in array.zip(floatFills))
|
||||
@@ -925,11 +930,7 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun zeroFilledArray(numElts: Int): StArray {
|
||||
val values = mutableListOf<StArrayElement>()
|
||||
repeat(numElts) {
|
||||
values.add(StArrayElement(0.0, null, null,null,null))
|
||||
}
|
||||
return values
|
||||
return List(numElts) { StArrayElement.Number(0.0) }
|
||||
}
|
||||
|
||||
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
||||
@@ -937,10 +938,17 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
||||
}
|
||||
consts.sortedBy { it.name }.forEach {
|
||||
val nameWithoutColons = it.name.replace("::", "_")
|
||||
if(it.dt.isFloat)
|
||||
asmgen.out(" ${it.name} = ${it.value}")
|
||||
else
|
||||
asmgen.out(" ${it.name} = ${it.value.toHex()}")
|
||||
asmgen.out(" ${nameWithoutColons} = ${it.value}")
|
||||
else {
|
||||
if(it.value!=null)
|
||||
asmgen.out(" ${nameWithoutColons} = ${it.value!!.toHex()}")
|
||||
else if(it.memorySlab!=null)
|
||||
asmgen.out(" ${nameWithoutColons} = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
|
||||
else
|
||||
TODO("asm gen for ${nameWithoutColons} = memory alloc?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,41 +978,35 @@ internal class ProgramAndVarsGen(
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
return when {
|
||||
dt.isBoolArray ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
if(it.boolean!=null)
|
||||
if(it.boolean==true) "1" else "0"
|
||||
else {
|
||||
val number = it.number!!
|
||||
if(number==0.0) "0" else "1"
|
||||
when(it) {
|
||||
is StArrayElement.BoolValue -> if(it.value) "1" else "0"
|
||||
is StArrayElement.Number -> if(it.value==0.0) "0" else "1"
|
||||
else -> throw AssemblyError("unexpected array element type for bool array")
|
||||
}
|
||||
}
|
||||
dt.isUnsignedByteArray ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = it.number!!.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for ubyte array")
|
||||
"$"+numValue.toInt().toString(16).padStart(2, '0')
|
||||
}
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
|
||||
if(it.number!=null) {
|
||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
else if(it.addressOfSymbol!=null) {
|
||||
val addrOfSymbol = it.addressOfSymbol!!
|
||||
val symbol = symboltable.lookup(addrOfSymbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
|
||||
asmgen.asmSymbolName(addrOfSymbol+"_lsb") // the _lsb split array comes first in memory
|
||||
else
|
||||
asmgen.asmSymbolName(addrOfSymbol)
|
||||
}
|
||||
else if(it.structInstance!=null) {
|
||||
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
|
||||
}
|
||||
else if(it.structInstanceUninitialized!=null) {
|
||||
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
|
||||
}
|
||||
else {
|
||||
throw AssemblyError("weird array elt")
|
||||
when(it) {
|
||||
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
|
||||
is StArrayElement.AddressOf -> {
|
||||
val symbol = symboltable.lookup(it.symbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
|
||||
asmgen.asmSymbolName(it.symbol+"_lsb")
|
||||
else
|
||||
asmgen.asmSymbolName(it.symbol)
|
||||
}
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
is StArrayElement.StructInstance -> {
|
||||
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
|
||||
asmgen.asmSymbolName("$prefix.${it.name}")
|
||||
}
|
||||
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
@@ -1014,41 +1016,66 @@ internal class ProgramAndVarsGen(
|
||||
private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
|
||||
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
|
||||
return when {
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
dt.isUnsignedByteArray ->
|
||||
array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for ubyte array")
|
||||
val number = numValue.toInt()
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
dt.isSignedByteArray ->
|
||||
array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val numValue = (it as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for byte array")
|
||||
val number = numValue.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
if(number>=0) "$$hexnum" else "-$$hexnum"
|
||||
}
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) ->
|
||||
array.map {
|
||||
when(it) {
|
||||
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
|
||||
is StArrayElement.AddressOf -> {
|
||||
val symbol = symboltable.lookup(it.symbol)!!
|
||||
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
|
||||
asmgen.asmSymbolName(it.symbol+"_lsb")
|
||||
else
|
||||
asmgen.asmSymbolName(it.symbol)
|
||||
}
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
is StArrayElement.StructInstance -> {
|
||||
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
|
||||
asmgen.asmSymbolName("$prefix.${it.name}")
|
||||
}
|
||||
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
|
||||
}
|
||||
}
|
||||
dt.isSignedWordArray ->
|
||||
array.map {
|
||||
when(it) {
|
||||
is StArrayElement.Number -> {
|
||||
val number = it.value.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if (number >= 0) "$$hexnum" else "-$$hexnum"
|
||||
}
|
||||
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
else -> throw AssemblyError("unexpected array element type for signed word array")
|
||||
}
|
||||
}
|
||||
dt.isLongArray ->
|
||||
array.map {
|
||||
when(it) {
|
||||
is StArrayElement.Number -> {
|
||||
val number = it.value.toInt()
|
||||
val hexnum = number.absoluteValue.toLongHex()
|
||||
if (number >= 0) "$$hexnum" else "-$$hexnum"
|
||||
}
|
||||
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
|
||||
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
|
||||
else -> throw AssemblyError("unexpected array element type for long array")
|
||||
}
|
||||
}
|
||||
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
"$" + number.toString(16).padStart(4, '0')
|
||||
}
|
||||
dt.isSignedWordArray -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
dt.isLongArray -> array.map {
|
||||
val number = it.number!!.toInt()
|
||||
val hexnum = number.absoluteValue.toLongHex()
|
||||
if(number>=0)
|
||||
"$$hexnum"
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onSuccess
|
||||
import com.github.michaelbull.result.onOk
|
||||
import prog8.code.*
|
||||
import prog8.code.core.*
|
||||
|
||||
@@ -85,12 +85,12 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.astNode?.position ?: Position.DUMMY,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
result.onOk { numVariablesAllocatedInZP++ }
|
||||
// no need to check for allocation error, if there is one, just allocate in normal system ram.
|
||||
}
|
||||
|
||||
// try to allocate the "don't care" interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? Simply sorting by size (bytes prioritized) has BAD results
|
||||
if(errors.noErrors()) {
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
|
||||
for (variable in sortedList) {
|
||||
@@ -105,7 +105,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.astNode?.position ?: Position.DUMMY,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
result.onOk { numVariablesAllocatedInZP++ }
|
||||
}
|
||||
} else
|
||||
numberOfNonIntegerVariables++
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
|
||||
|
||||
@@ -49,8 +51,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
}
|
||||
|
||||
init {
|
||||
if(register!=null && !datatype.isNumericOrBool)
|
||||
throw AssemblyError("must be numeric type")
|
||||
if(register!=null) {
|
||||
if(datatype.isPointer)
|
||||
throw AssemblyError("register target type must be uword, not pointer $datatype $position")
|
||||
else if(!datatype.isNumericOrBool)
|
||||
throw AssemblyError("target type must be numeric not $datatype $position")
|
||||
}
|
||||
if(kind==TargetStorageKind.REGISTER)
|
||||
require(register!=null)
|
||||
else
|
||||
@@ -192,6 +198,19 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
|
||||
if(parameter?.definingAsmSub() != null)
|
||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||
|
||||
// Check if this identifier is a constant with a value - if so, use the value directly instead of reading from memory
|
||||
val symbol = asmgen.symbolTable.lookup(value.name)
|
||||
if(symbol is StConstant && symbol.value != null) {
|
||||
val constValue = symbol.value!!
|
||||
val baseType = when(symbol.dt.base) {
|
||||
BaseDataType.POINTER -> BaseDataType.UWORD
|
||||
else -> symbol.dt.base
|
||||
}
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, symbol.dt,
|
||||
number = PtNumber(baseType, constValue, value.position))
|
||||
}
|
||||
|
||||
val varName=asmgen.asmVariableName(value)
|
||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||
if(value.type.isUnsignedWord && varName.lowercase().startsWith("cx16.r")) {
|
||||
@@ -208,19 +227,20 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
is PtArrayIndexer -> {
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
}
|
||||
is PtFunctionCall -> {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val returnType =
|
||||
if(sub is PtSub && sub.signature.returns.size>1)
|
||||
DataType.UNDEFINED // TODO list of types instead?
|
||||
else
|
||||
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
if(value.builtin) {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
} else {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name} ${value.position}")
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val returnType =
|
||||
if(sub is PtSub && sub.signature.returns.size>1)
|
||||
DataType.UNDEFINED // TODO list of types instead?
|
||||
else
|
||||
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
@@ -262,7 +282,7 @@ internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
|
||||
}
|
||||
|
||||
val target: AsmAssignTarget
|
||||
get() = targets.single()
|
||||
get() = targets.singleOrNull() ?: throw AssemblyError("code assumes single target but assignment is multi-value $position")
|
||||
}
|
||||
|
||||
internal class AsmAssignment(source: AsmAssignSource,
|
||||
|
||||
@@ -18,6 +18,9 @@ internal class AssignmentAsmGen(
|
||||
) {
|
||||
lateinit var augmentableAsmGen: AugmentableAssignmentAsmGen
|
||||
|
||||
internal fun isRightTrivial(expr: PtExpression): Boolean =
|
||||
expr is PtNumber || expr is PtBool || expr is PtIdentifier
|
||||
|
||||
fun translate(assignment: PtAssignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment.target, assignment.definingISub(), asmgen)
|
||||
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
|
||||
@@ -38,96 +41,79 @@ internal class AssignmentAsmGen(
|
||||
val values = assignment.value as? PtFunctionCall
|
||||
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
|
||||
|
||||
val extsub = asmgen.symbolTable.lookup(values.name) as? StExtSub
|
||||
if(extsub!=null) {
|
||||
require(extsub.returns.size>=2)
|
||||
if(extsub.returns.any { it.type.isFloat })
|
||||
TODO("deal with (multiple?) FP return registers ${assignment.position}")
|
||||
val thing = asmgen.symbolTable.lookup(values.name)
|
||||
if(thing?.type== StNodeType.EXTSUB) {
|
||||
val extsub = thing as StExtSub
|
||||
require(extsub.returns.size>=2) {
|
||||
"extsub ${extsub.name} must return at least 2 values but got ${extsub.returns.size} ${assignment.position}"
|
||||
}
|
||||
|
||||
asmgen.translate(values)
|
||||
|
||||
val assignmentTargets = assignment.children.dropLast(1)
|
||||
if(extsub.returns.size==assignmentTargets.size) {
|
||||
// because we can only handle integer results right now we can just zip() it all up
|
||||
val (statusFlagResults, registersResults) = extsub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
|
||||
if (statusFlagResults.isEmpty())
|
||||
// Filter out void targets first, then separate by result type
|
||||
val allResults = extsub.returns.zip(assignmentTargets)
|
||||
val floatResults = allResults.filter { it.first.type.isFloat && !(it.second as PtAssignTarget).void }
|
||||
val nonVoidResults = allResults.filter { !(it.second as PtAssignTarget).void && !it.first.type.isFloat }
|
||||
val (statusFlagResults, registersResults) = nonVoidResults.partition { it.first.register.statusflag!=null }
|
||||
val saveFlags = statusFlagResults.size > 1
|
||||
|
||||
// Check if we need to save A register before extracting status flags
|
||||
// (flag extraction functions like assignCarryFlagResult overwrite A)
|
||||
val hasByteInA = registersResults.any { (ret, _) ->
|
||||
ret.type.isByteOrBool && ret.register.registerOrPair == RegisterOrPair.A
|
||||
}
|
||||
if(hasByteInA && statusFlagResults.isNotEmpty()) {
|
||||
asmgen.out(" pha") // Save A (contains return value) before flag extraction
|
||||
}
|
||||
|
||||
// Save status flags first (before float MOVMF or other ops clobber them)
|
||||
if(statusFlagResults.isNotEmpty()) {
|
||||
if(saveFlags) asmgen.out(" php")
|
||||
statusFlagResults.forEach { (returns, target) ->
|
||||
when(returns.register.statusflag) {
|
||||
Statusflag.Pc -> assignCarryFlagResult(target as PtAssignTarget)
|
||||
Statusflag.Pz -> assignZeroFlagResult(target as PtAssignTarget, saveFlags)
|
||||
Statusflag.Pn -> assignNegativeFlagResult(target as PtAssignTarget, saveFlags)
|
||||
Statusflag.Pv -> assignOverflowFlagResult(target as PtAssignTarget)
|
||||
else -> throw AssemblyError("unknown status flag")
|
||||
}
|
||||
}
|
||||
if(saveFlags) asmgen.out(" plp")
|
||||
}
|
||||
|
||||
// Save float results (these use ldx/ldy/jsr which clobber flags)
|
||||
floatResults.forEach { (returns, target) ->
|
||||
val asmTarget = AsmAssignTarget.fromAstAssignment(target as PtAssignTarget, target.definingISub(), asmgen)
|
||||
if(returns.register.registerOrPair == RegisterOrPair.FAC1) {
|
||||
assignFAC1float(asmTarget)
|
||||
} else if(returns.register.registerOrPair == RegisterOrPair.FAC2) {
|
||||
assignFAC2float(asmTarget)
|
||||
} else {
|
||||
throw AssemblyError("float result must be in FAC1 or FAC2")
|
||||
}
|
||||
}
|
||||
|
||||
// Restore A if we saved it for flag extraction
|
||||
if(hasByteInA && statusFlagResults.isNotEmpty()) {
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
|
||||
// Handle non-float register results (not status flags)
|
||||
if(registersResults.isNotEmpty()) {
|
||||
assignRegisterResults(registersResults)
|
||||
else if(registersResults.isEmpty())
|
||||
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
|
||||
else
|
||||
assignStatusFlagsAndRegistersResults(statusFlagResults, registersResults)
|
||||
}
|
||||
} else {
|
||||
throw AssemblyError("number of values and targets don't match")
|
||||
}
|
||||
} else {
|
||||
val sub = asmgen.symbolTable.lookup(values.name) as? StSub
|
||||
if(sub!=null) {
|
||||
val scope = assignment.definingISub()
|
||||
val source = AsmAssignSource.fromAstSource(assignment.children.last() as PtExpression, program, asmgen)
|
||||
val targets = AsmAssignTarget.fromAstAssignmentMulti(assignment.children.dropLast(1).map { it as PtAssignTarget }, scope, asmgen)
|
||||
val asmassign = AsmAssignment(source, targets, program.memsizer, assignment.position)
|
||||
assignExpression(asmassign, scope)
|
||||
}
|
||||
else throw AssemblyError("expected extsub or normal sub")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignStatusFlagsAndRegistersResults(
|
||||
statusFlagResults: List<Pair<StExtSubParameter, PtNode>>,
|
||||
registersResults: List<Pair<StExtSubParameter, PtNode>>
|
||||
) {
|
||||
|
||||
fun needsToSaveA(registersResults: List<Pair<StExtSubParameter, PtNode>>): Boolean =
|
||||
if(registersResults.isEmpty())
|
||||
false
|
||||
else if(registersResults.all { (it.second as PtAssignTarget).identifier!=null})
|
||||
false
|
||||
else
|
||||
true
|
||||
|
||||
if(registersResults.all {
|
||||
val tgt = it.second as PtAssignTarget
|
||||
tgt.void || tgt.identifier!=null})
|
||||
{
|
||||
// all other results are just stored into identifiers directly so first handle those
|
||||
// (simple store instructions that don't modify the carry flag)
|
||||
assignRegisterResults(registersResults)
|
||||
assignOnlyTheStatusFlagsResults(false, statusFlagResults)
|
||||
} else {
|
||||
val saveA = needsToSaveA(registersResults)
|
||||
assignOnlyTheStatusFlagsResults(saveA, statusFlagResults)
|
||||
assignRegisterResults(registersResults)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignOnlyTheStatusFlagsResults(saveA: Boolean, statusFlagResults: List<Pair<StExtSubParameter, PtNode>>) {
|
||||
// assigning flags to their variables targets requires load-instructions that destroy flags
|
||||
// so if there's more than 1, we need to save and restore the flags
|
||||
val saveFlags = statusFlagResults.size>1
|
||||
|
||||
fun hasFlag(statusFlagResults: List<Pair<StExtSubParameter, PtNode>>, flag: Statusflag): PtAssignTarget? {
|
||||
for ((returns, target) in statusFlagResults) {
|
||||
if(returns.register.statusflag!! == flag)
|
||||
return target as PtAssignTarget
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val targetCarry = hasFlag(statusFlagResults, Statusflag.Pc)
|
||||
val targetZero = hasFlag(statusFlagResults, Statusflag.Pz)
|
||||
val targetNeg = hasFlag(statusFlagResults, Statusflag.Pn)
|
||||
val targetOverflow = hasFlag(statusFlagResults, Statusflag.Pv)
|
||||
|
||||
if(saveA) asmgen.out(" pha")
|
||||
if(targetZero!=null && !targetZero.void)
|
||||
assignZeroFlagResult(targetZero, saveFlags)
|
||||
if(targetNeg!=null && !targetNeg.void)
|
||||
assignNegativeFlagResult(targetNeg, saveFlags)
|
||||
if(targetCarry!=null && !targetCarry.void)
|
||||
assignCarryFlagResult(targetCarry)
|
||||
if(targetOverflow!=null && !targetOverflow.void)
|
||||
assignOverflowFlagResult(targetOverflow)
|
||||
if(saveA) asmgen.out(" pla")
|
||||
} else if(thing?.type==StNodeType.SUBROUTINE || thing?.type==StNodeType.BUILTINFUNC) {
|
||||
val scope = assignment.definingISub()
|
||||
val source = AsmAssignSource.fromAstSource(assignment.children.last() as PtExpression, program, asmgen)
|
||||
val targets = AsmAssignTarget.fromAstAssignmentMulti(assignment.children.dropLast(1).map { it as PtAssignTarget }, scope, asmgen)
|
||||
val asmassign = AsmAssignment(source, targets, program.memsizer, assignment.position)
|
||||
assignExpression(asmassign, scope)
|
||||
} else throw AssemblyError("expected extsub or normal sub or builtinfunc")
|
||||
}
|
||||
|
||||
private fun assignRegisterResults(registersResults: List<Pair<StExtSubParameter, PtNode>>) {
|
||||
@@ -259,13 +245,13 @@ internal class AssignmentAsmGen(
|
||||
val elementDt = assign.source.datatype
|
||||
val valueVar = value.variable
|
||||
if(valueVar==null) {
|
||||
TODO("translate assignment of pointer ${value.position}")
|
||||
TODO("translate assignment of indexed ${elementDt} pointer ${value.position}")
|
||||
return
|
||||
}
|
||||
val arrayVarName = asmgen.asmVariableName(valueVar)
|
||||
|
||||
if(valueVar.type.isPointer) {
|
||||
pointergen.assignIndexedPointer(assign.target, arrayVarName, value.index, valueVar.type)
|
||||
pointergen.assignIndexedPointer(assign.target, arrayVarName, value, valueVar.type)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -376,7 +362,11 @@ internal class AssignmentAsmGen(
|
||||
if(result!=null) {
|
||||
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
|
||||
if(addressOfIdentifier!=null) {
|
||||
val varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
var varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
if(addressOfIdentifier.type.isSplitWordArray) {
|
||||
val addrOf = result.first as PtAddressOf
|
||||
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
|
||||
}
|
||||
if(result.second is PtNumber) {
|
||||
val offset = (result.second as PtNumber).number.toInt()
|
||||
asmgen.out(" lda $varname+$offset")
|
||||
@@ -537,7 +527,6 @@ internal class AssignmentAsmGen(
|
||||
is PtMemoryByte -> throw AssemblyError("source kind should have been memory")
|
||||
is PtTypeCast -> assignTypeCastedValue(assign.target, value.type, value.value, value)
|
||||
is PtFunctionCall -> assignFunctionCall(assign, value)
|
||||
is PtBuiltinFunctionCall -> assignBuiltinFunctionCall(assign.target, value)
|
||||
is PtPrefix -> assignPrefixExpr(assign, value, scope)
|
||||
is PtContainmentCheck -> {
|
||||
containmentCheckIntoA(value)
|
||||
@@ -552,8 +541,12 @@ internal class AssignmentAsmGen(
|
||||
asmgen.assignRegister(RegisterOrPair.A, assign.target)
|
||||
}
|
||||
dt.isWord ||dt.isPointer -> {
|
||||
asmgen.loadIndirectWord(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, assign.target)
|
||||
if(assign.target.register in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
asmgen.loadIndirectWordIntoRegisters(zpPtrVar, offset, assign.target.register!!)
|
||||
} else {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, assign.target)
|
||||
}
|
||||
}
|
||||
dt.isFloat -> {
|
||||
asmgen.loadIndirectFloat(zpPtrVar, offset)
|
||||
@@ -576,6 +569,12 @@ internal class AssignmentAsmGen(
|
||||
is PtIfExpression -> asmgen.assignIfExpression(assign.target, value)
|
||||
is PtBranchCondExpression -> asmgen.assignBranchCondExpression(assign.target, value)
|
||||
is PtPointerDeref -> pointergen.assignPointerDerefExpression(assign.target, value)
|
||||
is PtConstant -> {
|
||||
val slab = value.memorySlab
|
||||
require(slab != null) { "remaining PtConstant in asmgen as assignment value can only be a memory slab reference at ${value.position}" }
|
||||
val label = "$StMemorySlabBlockName.${slab.name}"
|
||||
assignAddressOf(assign.target, label, false, DataType.UWORD, null)
|
||||
}
|
||||
else -> throw AssemblyError("weird assignment value type $value")
|
||||
}
|
||||
}
|
||||
@@ -674,83 +673,99 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignBuiltinFunctionCall(target: AsmAssignTarget, value: PtBuiltinFunctionCall) {
|
||||
|
||||
if(value.name=="peekl" && target.kind==TargetStorageKind.VARIABLE) {
|
||||
val arg = value.args[0]
|
||||
if(arg is PtNumber) {
|
||||
val address = arg.number.toInt()
|
||||
asmgen.out("""
|
||||
lda $address
|
||||
sta ${target.asmVarname}
|
||||
lda $address+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $address+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $address+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
return
|
||||
}
|
||||
else if(arg is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
if(asmgen.isZpVar(arg)) {
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}
|
||||
iny
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}+1
|
||||
iny
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}+2
|
||||
iny
|
||||
lda ($varname),y
|
||||
sta ${target.asmVarname}+3""")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val returnDt = asmgen.translateBuiltinFunctionCallExpression(value, target.register)
|
||||
if(target.register==null) {
|
||||
// still need to assign the result to the target variable/etc.
|
||||
when {
|
||||
returnDt?.isByteOrBool==true -> assignRegisterByte(target, CpuRegister.A, returnDt.isSigned, false) // function's byte result is in A
|
||||
returnDt?.isWord==true -> assignRegisterpairWord(target, RegisterOrPair.AY) // function's word result is in AY
|
||||
returnDt==BaseDataType.STR -> {
|
||||
if (target.datatype.isUnsignedWord) assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
else throw AssemblyError("str return value type mismatch with target")
|
||||
}
|
||||
returnDt== BaseDataType.LONG -> {
|
||||
// longs are in R14:R15 (r14=lsw, r15=msw)
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
}
|
||||
returnDt==BaseDataType.FLOAT -> {
|
||||
// float result from function sits in FAC1
|
||||
assignFAC1float(target)
|
||||
}
|
||||
else -> throw AssemblyError("weird result type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignFunctionCall(assign: AsmAssignment, value: PtFunctionCall) {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
if(symbol!!.type==StNodeType.BUILTINFUNC) {
|
||||
// builtin function call
|
||||
|
||||
if (value.type.isLong && assign.targets.single().kind == TargetStorageKind.VARIABLE) {
|
||||
|
||||
val targetVarName = assign.targets.single().asmVarname
|
||||
|
||||
fun handleResultInRegister(resultReg: Array<RegisterOrPair>) {
|
||||
resultReg.singleOrNull()?.let {
|
||||
asmgen.out(" ;--REG TO VAR")
|
||||
asmgen.assignRegister(it, assign.targets[0])
|
||||
asmgen.out(" ;--REG TO VAR DONE")
|
||||
}
|
||||
}
|
||||
|
||||
when(value.name) {
|
||||
"peekl" -> {
|
||||
val resultInRegister = asmgen.builtinFunctionsAsmGen.funcPeekL(value, targetVarName)
|
||||
return handleResultInRegister(resultInRegister)
|
||||
}
|
||||
"mklong" -> {
|
||||
val resultInRegister = asmgen.builtinFunctionsAsmGen.funcMklong(value, RegisterOrPair.R14R15, targetVarName)
|
||||
return handleResultInRegister(resultInRegister)
|
||||
}
|
||||
"mklong2" -> {
|
||||
val resultInRegister = asmgen.builtinFunctionsAsmGen.funcMklong(value, RegisterOrPair.R14R15, targetVarName)
|
||||
return handleResultInRegister(resultInRegister)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(value.name=="lmh") {
|
||||
// avoid temporary variable use when assigning lmh results
|
||||
val arg = value.args.single()
|
||||
if(arg is PtIdentifier) {
|
||||
val (targetL, targetM, targetH) = assign.targets
|
||||
val acceptedTargets = arrayOf(TargetStorageKind.VARIABLE, TargetStorageKind.VOID)
|
||||
if(targetL.kind in acceptedTargets && targetM.kind in acceptedTargets && targetH.kind in acceptedTargets) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
if(targetL.kind==TargetStorageKind.VARIABLE) asmgen.out(" lda $varname | sta ${targetL.asmVarname}")
|
||||
if(targetM.kind==TargetStorageKind.VARIABLE) asmgen.out(" lda $varname+1 | sta ${targetM.asmVarname}")
|
||||
if(targetH.kind==TargetStorageKind.VARIABLE) asmgen.out(" lda $varname+2 | sta ${targetH.asmVarname}")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO optimized float functions into variable? (to avoid needless FAC1 register copying?)
|
||||
|
||||
val firstTarget = assign.targets.firstOrNull()
|
||||
val actualResultRegisters = asmgen.translateBuiltinFunctionCallExpression(value, firstTarget?.register)
|
||||
|
||||
require(actualResultRegisters.size==assign.targets.size) { "builtin function call should have same number return values as assignment targets ${value.position}" }
|
||||
// assign.targets.zip(actualResultRegisters).forEach { (target, register) ->
|
||||
// if(target.kind==TargetStorageKind.REGISTER) {
|
||||
// if(target.register!! != register) {
|
||||
// println("OPTIMIZE: ${value.name} builtin func output register mismatch ${target.register} got $register ${value.position}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if(assign.targets.size>1) {
|
||||
assign.targets.zip(actualResultRegisters)
|
||||
.filter { it.first.kind != TargetStorageKind.VOID }
|
||||
.forEach { target -> asmgen.assignRegister(target.second, target.first) }
|
||||
} else if(actualResultRegisters.isNotEmpty() && assign.targets.single().kind != TargetStorageKind.VOID) {
|
||||
val target = assign.targets.single()
|
||||
if(target.kind != TargetStorageKind.VOID) {
|
||||
asmgen.assignRegister(actualResultRegisters.single(), target)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// regular subroutine call
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val returns = sub.returnsWhatWhere()
|
||||
asmgen.translateFunctionCall(value)
|
||||
if(sub is PtSub && sub.signature.returns.size>1) {
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// (this allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val returnRegs = sub.returnsWhatWhere()
|
||||
assign.targets.zip(returnRegs).forEach { target ->
|
||||
assign.targets.zip(returns).forEach { target ->
|
||||
if(target.first.kind != TargetStorageKind.VOID) {
|
||||
asmgen.assignRegister(target.second.first.registerOrPair!!, target.first)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val target = assign.target
|
||||
val returnValue = sub.returnsWhatWhere().singleOrNull { it.first.registerOrPair!=null } ?: sub.returnsWhatWhere().single { it.first.statusflag!=null }
|
||||
if(target.kind==TargetStorageKind.VOID)
|
||||
return
|
||||
val returnValue = returns.singleOrNull { it.first.registerOrPair!=null } ?: returns.single { it.first.statusflag!=null }
|
||||
when {
|
||||
returnValue.second.isString -> {
|
||||
val targetDt = target.datatype
|
||||
@@ -855,12 +870,8 @@ internal class AssignmentAsmGen(
|
||||
|
||||
return if(translatedOk)
|
||||
true
|
||||
else {
|
||||
if(expr.type.isLong && expr.operator in "*/%")
|
||||
TODO("long multiplication or division - use floats for now ${expr.position} ")
|
||||
|
||||
else
|
||||
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizedComparison(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
@@ -992,12 +1003,12 @@ internal class AssignmentAsmGen(
|
||||
&& expr.right.type.isWord) {
|
||||
// word X>Y -> X<Y, X<=Y -> Y>=X , easier to do in 6502 (codegen also expects these to no longe exist!)
|
||||
exprClone = PtBinaryExpression(if(expr.operator==">") "<" else ">=", expr.type, expr.position)
|
||||
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
} else {
|
||||
exprClone = PtBinaryExpression(expr.operator, expr.type, expr.position)
|
||||
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
}
|
||||
ifelse.add(exprClone)
|
||||
ifelse.add(ifPart)
|
||||
@@ -1080,12 +1091,12 @@ internal class AssignmentAsmGen(
|
||||
&& expr.right.type.isWord) {
|
||||
// word X>Y -> X<Y, X<=Y -> Y>=X , easier to do in 6502 (codegen also expects these to no longe exist!)
|
||||
exprClone = PtBinaryExpression(if(expr.operator==">") "<" else ">=", expr.type, expr.position)
|
||||
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
} else {
|
||||
exprClone = PtBinaryExpression(expr.operator, expr.type, expr.position)
|
||||
exprClone.children.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
exprClone.children.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[0]) // doesn't seem to need a deep clone
|
||||
exprClone.add(expr.children[1]) // doesn't seem to need a deep clone
|
||||
}
|
||||
ifelse.add(exprClone)
|
||||
ifelse.add(ifPart)
|
||||
@@ -1100,7 +1111,7 @@ internal class AssignmentAsmGen(
|
||||
is PtIdentifier -> true
|
||||
is PtIrRegister -> true
|
||||
is PtNumber -> true
|
||||
is PtBuiltinFunctionCall -> expr.name in arrayOf("lsb", "msb")
|
||||
is PtFunctionCall -> expr.builtin && expr.name in arrayOf("lsb", "msb")
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -1125,6 +1136,35 @@ internal class AssignmentAsmGen(
|
||||
assignVariableWord(target, "P8ZP_SCRATCH_W2", DataType.UWORD)
|
||||
return true
|
||||
}
|
||||
expr.type.isSignedByte -> {
|
||||
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", expr.right.position)
|
||||
return true
|
||||
// TODO implement the signed remainder asm routine
|
||||
// assignExpressionToRegister(expr.left, RegisterOrPair.A, true)
|
||||
// if(!directIntoY(expr.right)) asmgen.out(" pha")
|
||||
// assignExpressionToRegister(expr.right, RegisterOrPair.Y, true)
|
||||
// if(!directIntoY(expr.right)) asmgen.out(" pla")
|
||||
// asmgen.out(" jsr prog8_math.divmod_b_asm")
|
||||
// if(target.register==RegisterOrPair.A)
|
||||
// asmgen.out(" cmp #0") // fix the status register
|
||||
// else
|
||||
// assignRegisterByte(target, CpuRegister.A, true, true)
|
||||
// return true
|
||||
}
|
||||
expr.type.isSignedWord -> {
|
||||
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", expr.right.position)
|
||||
return true
|
||||
// TODO implement the signed remainder asm routine
|
||||
// asmgen.assignWordOperandsToAYAndVar(expr.right, expr.left, "P8ZP_SCRATCH_W1")
|
||||
// asmgen.out(" jsr prog8_math.divmod_w_asm")
|
||||
// assignVariableWord(target, "cx16.r15", DataType.WORD)
|
||||
// return true
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", expr.right.position)
|
||||
return true
|
||||
// TODO implement the signed long remainder asm routine
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
@@ -1162,6 +1202,17 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out(" jsr prog8_math.div_longs")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
@@ -1204,6 +1255,17 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
return true
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out(" jsr prog8_math.multiply_longs")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
} else {
|
||||
@@ -1267,6 +1329,17 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
// long * constant
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
|
||||
asmgen.out(" lda #<${value} | sta cx16.r14")
|
||||
asmgen.out(" lda #>${value} | sta cx16.r14+1")
|
||||
asmgen.out(" lda #<(${value}>>16) | sta cx16.r15")
|
||||
asmgen.out(" lda #>(${value}>>16) | sta cx16.r15+1")
|
||||
asmgen.out(" jsr prog8_math.multiply_longs")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
@@ -1486,8 +1559,30 @@ internal class AssignmentAsmGen(
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getIdentForNoopCastOrIdent(expr: PtExpression): Pair<String, DataType>? {
|
||||
// Returns the variable name and type if the expression is:
|
||||
// 1. A plain identifier (returns its name and type)
|
||||
// 2. A no-op type cast (word<->uword) - unwraps and returns the inner identifier
|
||||
// Otherwise returns null
|
||||
val identifier = when (expr) {
|
||||
is PtIdentifier -> expr
|
||||
is PtTypeCast -> {
|
||||
// Check if this is a no-op cast (word<->uword)
|
||||
if (expr.value is PtIdentifier &&
|
||||
((expr.type.isWord && expr.value.type.isUnsignedWord) ||
|
||||
(expr.type.isUnsignedWord && expr.value.type.isWord) ||
|
||||
(expr.type.isByte && expr.value.type.isByte))) {
|
||||
expr.value as PtIdentifier
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
return Pair(asmgen.asmVariableName(identifier), identifier.type)
|
||||
}
|
||||
|
||||
private fun optimizedPlusMinExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
|
||||
// TODO can this use the target directly as intermediate, when it's a variable? To save the final assignRegister call.
|
||||
val dt = expr.type
|
||||
val left = expr.left
|
||||
val right = expr.right
|
||||
@@ -1583,32 +1678,42 @@ internal class AssignmentAsmGen(
|
||||
} else if(dt.isWord || dt.isPointer) {
|
||||
|
||||
fun doAddOrSubWordExpr() {
|
||||
if(expr.operator=="+" && expr.left.type.isWord && expr.left is PtIdentifier) {
|
||||
val symname = asmgen.asmVariableName(expr.left as PtIdentifier)
|
||||
val leftAddr = asmgen.getStaticAddressLowHigh(expr.left)
|
||||
val rightAddr = asmgen.getStaticAddressLowHigh(expr.right)
|
||||
|
||||
if (expr.operator == "+" && leftAddr != null) {
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.AY, expr.right.type.isSigned)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $symname
|
||||
adc ${leftAddr.first}
|
||||
tax
|
||||
tya
|
||||
adc $symname+1
|
||||
adc ${leftAddr.second}
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
else if(expr.operator=="-" && expr.right.type.isWord && expr.right is PtIdentifier) {
|
||||
val symname = asmgen.asmVariableName(expr.right as PtIdentifier)
|
||||
} else if (expr.operator == "+" && rightAddr != null) {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.left.type.isSigned)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc ${rightAddr.first}
|
||||
tax
|
||||
tya
|
||||
adc ${rightAddr.second}
|
||||
tay
|
||||
txa""")
|
||||
} else if (expr.operator == "-" && rightAddr != null) {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.AY, expr.left.type.isSigned)
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc $symname
|
||||
sbc ${rightAddr.first}
|
||||
tax
|
||||
tya
|
||||
sbc $symname+1
|
||||
sbc ${rightAddr.second}
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
|
||||
if(expr.operator=="+")
|
||||
if (expr.operator == "+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
@@ -1630,6 +1735,66 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
|
||||
// Handle when left is PtAddressOf of a split word array
|
||||
val leftAddressOf = left as? PtAddressOf
|
||||
if(leftAddressOf!=null && leftAddressOf.identifier!=null && !leftAddressOf.isFromArrayElement && leftAddressOf.dereference==null) {
|
||||
if(leftAddressOf.identifier!!.type.isSplitWordArray) {
|
||||
var symbol = asmgen.asmVariableName(leftAddressOf.identifier!!)
|
||||
symbol = if(leftAddressOf.isMsbForSplitArray) symbol+"_msb" else symbol+"_lsb"
|
||||
assignExpressionToRegister(right, RegisterOrPair.AY, right.type.isSigned)
|
||||
if(expr.operator=="+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc #<$symbol
|
||||
tax
|
||||
tya
|
||||
adc #>$symbol
|
||||
tay
|
||||
txa""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<$symbol
|
||||
tax
|
||||
tya
|
||||
sbc #>$symbol
|
||||
tay
|
||||
txa""")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Memory-to-memory optimization: when both operands and target are static variables
|
||||
// Also handles no-op type casts (word<->uword) by unwrapping them
|
||||
if(target.kind == TargetStorageKind.VARIABLE) {
|
||||
val leftInfo = getIdentForNoopCastOrIdent(expr.left)
|
||||
val rightInfo = getIdentForNoopCastOrIdent(expr.right)
|
||||
if(leftInfo!=null && rightInfo!=null) {
|
||||
val targetSym = target.asmVarname
|
||||
if(expr.operator=="+") {
|
||||
asmgen.out("""
|
||||
lda ${leftInfo.first}
|
||||
clc
|
||||
adc ${rightInfo.first}
|
||||
sta $targetSym
|
||||
lda ${leftInfo.first}+1
|
||||
adc ${rightInfo.first}+1
|
||||
sta $targetSym+1""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda ${leftInfo.first}
|
||||
sec
|
||||
sbc ${rightInfo.first}
|
||||
sta $targetSym
|
||||
lda ${leftInfo.first}+1
|
||||
sbc ${rightInfo.first}+1
|
||||
sta $targetSym+1""")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
when (right) {
|
||||
is PtAddressOf -> {
|
||||
if(right.isFromArrayElement) {
|
||||
@@ -1688,6 +1853,34 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
is PtConstant -> {
|
||||
val addr = asmgen.getStaticAddressLowHigh(right)
|
||||
if (addr != null) {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt.isSigned)
|
||||
if (expr.operator == "+")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc ${addr.first}
|
||||
tax
|
||||
tya
|
||||
adc ${addr.second}
|
||||
tay
|
||||
txa""")
|
||||
else
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc ${addr.first}
|
||||
tax
|
||||
tya
|
||||
sbc ${addr.second}
|
||||
tay
|
||||
txa""")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
doAddOrSubWordExpr()
|
||||
return true
|
||||
}
|
||||
is PtNumber -> {
|
||||
assignExpressionToRegister(left, RegisterOrPair.AY, dt.isSigned)
|
||||
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
@@ -2005,13 +2198,17 @@ internal class AssignmentAsmGen(
|
||||
else
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 2)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, expr.left.type.isSigned)
|
||||
// save left operand because right eval may clobber R14R15
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13, expr.right.type.isSigned)
|
||||
// restore left operand
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "cx16.r12")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(targetreg==RegisterOrPair.R14R15)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
else
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 2)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 2)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2189,8 +2386,12 @@ internal class AssignmentAsmGen(
|
||||
else if(varname!=null)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, varname)
|
||||
else {
|
||||
// TODO: preserve R14:R15 on stack here? (the function does't do it for us)
|
||||
augmentableAsmGen.inplacemodificationLongWithExpression("cx16.r14", expr.operator, expr.right)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13, expr.right.type.isSigned)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "cx16.r12")
|
||||
}
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(targetreg!=RegisterOrPair.R14R15) {
|
||||
@@ -2231,7 +2432,32 @@ internal class AssignmentAsmGen(
|
||||
|
||||
assignExpressionToRegister(left, RegisterOrPair.A, false)
|
||||
when(right) {
|
||||
is PtBool -> throw AssemblyError("bool literal in logical expr should have been optimized away")
|
||||
is PtBool -> {
|
||||
// Handle boolean literals that weren't optimized away (e.g., with -noopt)
|
||||
when {
|
||||
operator == "and" && right.value -> {
|
||||
// left AND true = left (already in A, do nothing)
|
||||
}
|
||||
operator == "and" && !right.value -> {
|
||||
// left AND false = false
|
||||
asmgen.out(" lda #0")
|
||||
}
|
||||
operator == "or" && right.value -> {
|
||||
// left OR true = true
|
||||
asmgen.out(" lda #1")
|
||||
}
|
||||
operator == "or" && !right.value -> {
|
||||
// left OR false = left (already in A, do nothing)
|
||||
}
|
||||
operator == "xor" && right.value -> {
|
||||
// left XOR true = NOT left (invert low bit)
|
||||
asmgen.out(" eor #1")
|
||||
}
|
||||
operator == "xor" && !right.value -> {
|
||||
// left XOR false = left (already in A, do nothing)
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(right)
|
||||
when (operator) {
|
||||
@@ -2278,10 +2504,13 @@ internal class AssignmentAsmGen(
|
||||
fun requiresCmp(expr: PtExpression) =
|
||||
when (expr) {
|
||||
is PtFunctionCall -> {
|
||||
val function = asmgen.symbolTable.lookup(expr.name)
|
||||
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
|
||||
if(expr.builtin)
|
||||
true
|
||||
else {
|
||||
val function = asmgen.symbolTable.lookup(expr.name)
|
||||
function is StExtSub // don't assume the extsub/asmsub has set the cpu flags correctly on exit, add an explicit cmp
|
||||
}
|
||||
}
|
||||
is PtBuiltinFunctionCall -> true
|
||||
is PtIfExpression -> true
|
||||
else -> false
|
||||
}
|
||||
@@ -2968,7 +3197,7 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun assignCastWordViaLsbFunc(value: PtExpression, target: AsmAssignTarget) {
|
||||
val lsb = PtBuiltinFunctionCall("lsb", false, true, DataType.UBYTE, value.position)
|
||||
val lsb = PtFunctionCall("lsb", true, true,arrayOf(DataType.UBYTE), value.position)
|
||||
lsb.parent = value.parent
|
||||
lsb.add(value)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
|
||||
@@ -2977,7 +3206,7 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun assignCastViaLswFunc(value: PtExpression, target: AsmAssignTarget) {
|
||||
val lsb = PtBuiltinFunctionCall("lsw", false, true, DataType.UWORD, value.position)
|
||||
val lsb = PtFunctionCall("lsw", true, true, arrayOf(DataType.UWORD), value.position)
|
||||
lsb.parent = value.parent
|
||||
lsb.add(value)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = lsb)
|
||||
@@ -3517,7 +3746,7 @@ $endLabel""")
|
||||
asmgen.out(" asl a")
|
||||
}
|
||||
} else if(subtype==BaseDataType.FLOAT) {
|
||||
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5)
|
||||
if(asmgen.options.compTarget.FLOAT_MEM_SIZE != 5u)
|
||||
TODO("support float size other than 5 ${arrayIndexExpr.position}")
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_REG
|
||||
@@ -3610,15 +3839,13 @@ $endLabel""")
|
||||
TODO("ubyte to long var ${target.position}")
|
||||
}
|
||||
DataType.LONG -> {
|
||||
// Long to long variable copy - use loop
|
||||
asmgen.out("""
|
||||
lda $varName
|
||||
sta ${target.asmVarname}
|
||||
lda $varName+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $varName+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $varName+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
ldy #3
|
||||
- lda $varName,y
|
||||
sta ${target.asmVarname},y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
else -> throw AssemblyError("wrong dt ${target.position}")
|
||||
}
|
||||
@@ -3756,7 +3983,12 @@ $endLabel""")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return
|
||||
}
|
||||
require(sourceDt.isWord || sourceDt.isUnsignedByte || sourceDt.isBool || sourceDt.isPointer) { "weird source dt for word variable" }
|
||||
require(sourceDt.isWord || sourceDt.isUnsignedByte || sourceDt.isBool || sourceDt.isPointer) {
|
||||
if(sourceDt.isString)
|
||||
"str source type for word variable should have been uword or ^^ubyte, a && is likely missing on the source variable $sourceDt ${target.position}"
|
||||
else
|
||||
"weird source dt for word variable: $sourceDt ${target.position}"
|
||||
}
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(sourceDt.isUnsignedByte || sourceDt.isBool) {
|
||||
@@ -4237,14 +4469,11 @@ $endLabel""")
|
||||
if(pairedRegisters in CombinedLongRegisters) {
|
||||
val startreg = pairedRegisters.startregname()
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
sta ${target.asmVarname}
|
||||
lda cx16.$startreg+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda cx16.$startreg+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda cx16.$startreg+3
|
||||
sta ${target.asmVarname}+3""")
|
||||
ldy #3
|
||||
- lda cx16.$startreg,y
|
||||
sta ${target.asmVarname},y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
else throw AssemblyError("only combined vreg allowed as long target ${target.position}")
|
||||
}
|
||||
@@ -4252,6 +4481,7 @@ $endLabel""")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, CpuRegister.Y)
|
||||
val arrayVarName = asmgen.asmSymbolName(target.array.variable!!)
|
||||
val startreg = pairedRegisters.startregname()
|
||||
// Unrolled copy to preserve array index in Y
|
||||
asmgen.out("""
|
||||
lda cx16.$startreg
|
||||
sta $arrayVarName,y
|
||||
@@ -4269,15 +4499,13 @@ $endLabel""")
|
||||
if(targetreg!=pairedRegisters) {
|
||||
val sourceStartReg = pairedRegisters.startregname()
|
||||
val targetStartReg = targetreg.startregname()
|
||||
// Loop-based copy between register pairs
|
||||
asmgen.out("""
|
||||
lda cx16.$sourceStartReg
|
||||
sta cx16.$targetStartReg
|
||||
lda cx16.$sourceStartReg+1
|
||||
sta cx16.$targetStartReg+1
|
||||
lda cx16.$sourceStartReg+2
|
||||
sta cx16.$targetStartReg+2
|
||||
lda cx16.$sourceStartReg+3
|
||||
sta cx16.$targetStartReg+3""")
|
||||
ldy #3
|
||||
- lda cx16.$sourceStartReg,y
|
||||
sta cx16.$targetStartReg,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
@@ -5312,7 +5540,11 @@ $endLabel""")
|
||||
} else if(addressOf.dereference!=null) {
|
||||
throw AssemblyError("write &dereference, makes no sense at ${addressOf.position}")
|
||||
} else {
|
||||
asmgen.out(" sta ${asmgen.asmSymbolName(addressOf.identifier!!)}")
|
||||
var symbolName = asmgen.asmSymbolName(addressOf.identifier!!)
|
||||
if(addressOf.identifier!!.type.isSplitWordArray) {
|
||||
symbolName = if(addressOf.isMsbForSplitArray) symbolName+"_msb" else symbolName+"_lsb"
|
||||
}
|
||||
asmgen.out(" sta $symbolName")
|
||||
}
|
||||
}
|
||||
addressExpr is PtIdentifier -> {
|
||||
@@ -5323,7 +5555,11 @@ $endLabel""")
|
||||
if(result!=null) {
|
||||
val addressOfIdentifier = (result.first as? PtAddressOf)?.identifier
|
||||
if(addressOfIdentifier!=null) {
|
||||
val varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
var varname = asmgen.asmVariableName(addressOfIdentifier)
|
||||
if(addressOfIdentifier.type.isSplitWordArray) {
|
||||
val addrOf = result.first as PtAddressOf
|
||||
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
|
||||
}
|
||||
if(result.second is PtNumber) {
|
||||
val offset = (result.second as PtNumber).number.toInt()
|
||||
asmgen.out(" sta $varname+$offset")
|
||||
@@ -5364,7 +5600,7 @@ $endLabel""")
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean, scope: IPtSubroutine?, pos: Position) {
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, pos, null, asmgen)
|
||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
||||
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, Position.DUMMY)
|
||||
val assign = AsmAssignment(src, listOf(tgt), program.memsizer, pos)
|
||||
translateNormalAssignment(assign, scope)
|
||||
}
|
||||
|
||||
|
||||
+121
-67
@@ -44,7 +44,14 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
"^=", "xor=" -> inplaceModification(assign.target, "^", assign.source)
|
||||
"<<=" -> inplaceModification(assign.target, "<<", assign.source)
|
||||
">>=" -> inplaceModification(assign.target, ">>", assign.source)
|
||||
"%=" -> inplaceModification(assign.target, "%", assign.source)
|
||||
"%=" -> {
|
||||
if(assign.source.datatype.isSigned) {
|
||||
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", assign.position)
|
||||
// TODO implement the signed remainder asm routine
|
||||
}
|
||||
else
|
||||
inplaceModification(assign.target, "%", assign.source)
|
||||
}
|
||||
"==" -> inplaceModification(assign.target, "==", assign.source)
|
||||
"!=" -> inplaceModification(assign.target, "!=", assign.source)
|
||||
"<" -> inplaceModification(assign.target, "<", assign.source)
|
||||
@@ -572,34 +579,22 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
"+" -> {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $targetVar
|
||||
adc $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
adc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
adc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
adc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
}
|
||||
"-" -> {
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $targetVar
|
||||
sbc $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
sbc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
sbc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
sbc $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
sbc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
@@ -625,51 +620,55 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
ora $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
ora $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
ora $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
ora $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
ora $sourceVar,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
and $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
and $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
and $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
and $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
and $sourceVar,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
lda $targetVar
|
||||
eor $sourceVar
|
||||
sta $targetVar
|
||||
lda $targetVar+1
|
||||
eor $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
eor $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
eor $sourceVar+3
|
||||
sta $targetVar+3""")
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
eor $sourceVar,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (multiplicand)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (multiplier)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.multiply_longs
|
||||
; Copy result from R14R15 back to target
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
else -> {
|
||||
TODO("in-place modify LONG with variable")
|
||||
TODO("in-place modify LONG with variable, operator=$operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -994,6 +993,34 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
// long *= constant - use multiplication routine with loop-based copies
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (multiplicand)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (multiplier)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.multiply_longs
|
||||
; Copy result from R14R15 back to variable
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $variable,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"<<" -> if (value > 0) inplaceLongShiftLeft()
|
||||
">>" -> if (value > 0) inplaceLongShiftRight()
|
||||
"|" -> {
|
||||
@@ -1135,9 +1162,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
val assignValue = AsmAssignment(value,
|
||||
listOf(
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.UBYTE,
|
||||
address.definingISub(), Position.DUMMY, register = RegisterOrPair.A)
|
||||
address.definingISub(), address.position, register = RegisterOrPair.A)
|
||||
),
|
||||
program.memsizer, Position.DUMMY)
|
||||
program.memsizer, address.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assignValue, address.definingISub()) // calculate value into A
|
||||
}
|
||||
|
||||
@@ -1220,6 +1247,33 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
sbc cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
"|" -> {
|
||||
asmgen.out("""
|
||||
lda ${arrayVar}_lsb+$index
|
||||
ora cx16.r0L
|
||||
sta ${arrayVar}_lsb+$index
|
||||
lda ${arrayVar}_msb+$index
|
||||
ora cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
"&" -> {
|
||||
asmgen.out("""
|
||||
lda ${arrayVar}_lsb+$index
|
||||
and cx16.r0L
|
||||
sta ${arrayVar}_lsb+$index
|
||||
lda ${arrayVar}_msb+$index
|
||||
and cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
"^" -> {
|
||||
asmgen.out("""
|
||||
lda ${arrayVar}_lsb+$index
|
||||
eor cx16.r0L
|
||||
sta ${arrayVar}_lsb+$index
|
||||
lda ${arrayVar}_msb+$index
|
||||
eor cx16.r0H
|
||||
sta ${arrayVar}_msb+$index""")
|
||||
}
|
||||
else -> TODO("inplace split word array value $operator")
|
||||
}
|
||||
}
|
||||
@@ -1528,7 +1582,7 @@ $shortcutLabel:""")
|
||||
}
|
||||
|
||||
private fun inplacemodificationRegisterAwithVariable(operator: String, variable: String, signed: Boolean) {
|
||||
if(operator in "+-" && variable in arrayOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
|
||||
if(operator in "+-" && variable in setOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
if(operator=="+")
|
||||
asmgen.out(" inc a")
|
||||
@@ -1710,8 +1764,8 @@ $shortcutLabel:""")
|
||||
private fun inplacemodificationRegisterAwithVariableWithSwappedOperands(operator: String, variable: String, signed: Boolean) {
|
||||
// A = variable <operator> A
|
||||
|
||||
if(operator in AssociativeOperators)
|
||||
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for associative operators
|
||||
if(operator in CommutativeOperators)
|
||||
return inplacemodificationRegisterAwithVariable(operator, variable, signed) // just reuse existing code for commutative operators
|
||||
|
||||
// now implement the non-assiciative operators...
|
||||
when (operator) {
|
||||
|
||||
@@ -242,14 +242,26 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
|
||||
val (zpPtrVar, offset) = deref(value)
|
||||
val (zpPtrVar, offset1) = deref(value)
|
||||
val offset: UByte
|
||||
if(value.derefLast) {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, offset1)
|
||||
asmgen.out(" sta $zpPtrVar | sty $zpPtrVar+1")
|
||||
offset = 0.toUByte()
|
||||
} else {
|
||||
offset = offset1
|
||||
}
|
||||
if(value.type.isByteOrBool) {
|
||||
asmgen.loadIndirectByte(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
}
|
||||
else if(value.type.isWord || value.type.isPointer) {
|
||||
asmgen.loadIndirectWord(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
if(target.register in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
asmgen.loadIndirectWordIntoRegisters(zpPtrVar, offset, target.register!!)
|
||||
} else {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, offset)
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
}
|
||||
}
|
||||
else if(value.type.isFloat) {
|
||||
asmgen.loadIndirectFloat(zpPtrVar, offset)
|
||||
@@ -270,15 +282,17 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
sta ${target.asmVarname}+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta ${target.asmVarname}+3
|
||||
""")
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register!! in CombinedLongRegisters)
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, target.register)
|
||||
}
|
||||
TargetStorageKind.POINTER -> {
|
||||
// TODO optimize this so it doesn't need a temporary long var all the time
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15, target)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadIndirectLongIntoCombinedLongRegister(zpPtrVar, offset, RegisterOrPair.R14R15)
|
||||
asmgen.assignRegister(RegisterOrPair.R14R15, target)
|
||||
}
|
||||
@@ -1738,7 +1752,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
|
||||
fun multiply() {
|
||||
// on entry here: number placed in routine argument variable
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
jsr prog8_math.multiply_words
|
||||
tax
|
||||
@@ -1807,7 +1821,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number in powersOfTwoInt)
|
||||
throw AssemblyError("divide by power of two should have been a shift $value.position")
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1818,7 +1832,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1830,7 +1844,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val register = value.register!!
|
||||
require(register.isWord())
|
||||
val regname = asmgen.asmSymbolName(register)
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1839,7 +1853,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
divide(target.dt.isSigned)
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AY)
|
||||
divide(target.dt.isSigned)
|
||||
@@ -1869,7 +1883,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val number = value.number!!.number.toInt()
|
||||
if(number in powersOfTwoInt)
|
||||
throw AssemblyError("divide by power of two should have been a shift $value.position")
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1880,7 +1894,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
require(value.datatype.isWord)
|
||||
val varname = value.asmVarname
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1892,7 +1906,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
val register = value.register!!
|
||||
require(register.isWord())
|
||||
val regname = asmgen.asmSymbolName(register)
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -1901,7 +1915,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
modulus()
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
asmgen.loadIndirectWord(zpPtrVar, 0u)
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, 0u)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
asmgen.assignExpressionToRegister(value.expression!!, RegisterOrPair.AY)
|
||||
modulus()
|
||||
@@ -2546,46 +2560,42 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, index: PtExpression, arrayDt: DataType) {
|
||||
TODO("assign indexed pointer from array $arrayVarName at ${target.position}")
|
||||
// val ptrZp = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, target.scope, target.position, variableAsmName="P8ZP_SCRATCH_PTR")
|
||||
// assignAddressOfIndexedPointer(ptrZp, arrayVarName, arrayDt, index)
|
||||
// when {
|
||||
// target.datatype.isByteOrBool -> {
|
||||
// asmgen.out("""
|
||||
// ldy #0
|
||||
// lda (P8ZP_SCRATCH_PTR),y""")
|
||||
// asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
// }
|
||||
// target.datatype.isWord || target.datatype.isPointer -> {
|
||||
// if(asmgen.isTargetCpu(CpuType.CPU65C02))
|
||||
// asmgen.out("""
|
||||
// ldy #1
|
||||
// lda (P8ZP_SCRATCH_PTR),y
|
||||
// tax
|
||||
// lda (P8ZP_SCRATCH_PTR)""")
|
||||
// else
|
||||
// asmgen.out("""
|
||||
// ldy #1
|
||||
// lda (P8ZP_SCRATCH_PTR),y
|
||||
// tax
|
||||
// dey
|
||||
// lda (P8ZP_SCRATCH_PTR),y""")
|
||||
// asmgen.assignRegister(RegisterOrPair.AX, target)
|
||||
// }
|
||||
// target.datatype.isLong -> {
|
||||
// TODO("assign long from pointer to $target ${target.position}")
|
||||
// }
|
||||
// target.datatype.isFloat -> {
|
||||
// // TODO optimize the float copying to avoid having to go through FAC1
|
||||
// asmgen.out("""
|
||||
// lda P8ZP_SCRATCH_PTR
|
||||
// ldy P8ZP_SCRATCH_PTR+1
|
||||
// jsr floats.MOVFM""")
|
||||
// asmgen.assignRegister(RegisterOrPair.FAC1, target)
|
||||
// }
|
||||
// else -> throw AssemblyError("weird dt ${target.datatype}")
|
||||
// }
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer, arrayDt: DataType) {
|
||||
// Load value from array[index] and store to pointer target
|
||||
if(target.datatype.isLong) {
|
||||
// Get address of array[index] into AY, then 4-byte copy loop to target pointer
|
||||
val index = arrayIndexer.index
|
||||
asmgen.assignExpressionToRegister(index, RegisterOrPair.AY, false)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_REG")
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") // *2
|
||||
asmgen.out(" asl a | rol P8ZP_SCRATCH_REG") // *4
|
||||
asmgen.out(" ldy P8ZP_SCRATCH_REG")
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $arrayVarName
|
||||
pha
|
||||
tya
|
||||
adc $arrayVarName+1
|
||||
tay
|
||||
pla""")
|
||||
// Now AY = address of array[index], save to scratch pointer
|
||||
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
|
||||
// Get target pointer address
|
||||
target.pointer?.let { ptr ->
|
||||
val (ptrVar, offset) = deref(ptr)
|
||||
// 4-byte copy loop from (P8ZP_SCRATCH_W1) to (ptrVar)+offset
|
||||
asmgen.out("""
|
||||
ldx #4
|
||||
ldy #$offset
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta ($ptrVar),y
|
||||
iny
|
||||
dex
|
||||
bne -""")
|
||||
} ?: throw AssemblyError("expected pointer target ${target.position}")
|
||||
} else {
|
||||
throw AssemblyError("unsupported type for indexed pointer assignment ${target.datatype} ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveOnStack(regs: RegisterOrPair) {
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if(dt.isPointerArray)
|
||||
return 2 * numElements!!
|
||||
else if(dt.isArray) {
|
||||
require(numElements != null)
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
|
||||
BaseDataType.LONG -> numElements*4
|
||||
BaseDataType.FLOAT -> numElements*5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in errors)
|
||||
errors.add(text)
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in warnings)
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in infos)
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
|
||||
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
|
||||
|
||||
override fun report() {
|
||||
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
errors.clear()
|
||||
warnings.clear()
|
||||
infos.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.codegen.cpu6502.*
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
|
||||
class TestAsmOptimizer: FunSpec({
|
||||
|
||||
val machine = C64Target()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val symbolTable = SymbolTable(program)
|
||||
|
||||
// Add test variables with known non-IO addresses so the optimizer can resolve them
|
||||
// Non-IO addresses in typical C64 RAM range
|
||||
symbolTable.add(StMemVar("myvar", DataType.UBYTE, 0x0300u, null, null))
|
||||
symbolTable.add(StMemVar("var", DataType.UBYTE, 0x0301u, null, null))
|
||||
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0302u, null, null))
|
||||
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0304u, null, null))
|
||||
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0306u, null, null))
|
||||
symbolTable.add(StMemVar("P8ZP_SCRATCH_PTR", DataType.UWORD, 0x02u, null, null))
|
||||
symbolTable.add(StMemVar("P8ZP_SCRATCH_W1", DataType.UWORD, 0x04u, null, null))
|
||||
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0310u, null, null))
|
||||
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0312u, null, null))
|
||||
|
||||
fun optimize(lines: MutableList<String>): Int {
|
||||
return optimizeAssembly(lines, machine, symbolTable)
|
||||
}
|
||||
|
||||
// --- optimizeIncDec ---
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence") {
|
||||
val lines = mutableListOf(" iny", " dey", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence with comments") {
|
||||
val lines = mutableListOf(" iny ; increment Y", " dey ; decrement Y", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes inx+dex sequence") {
|
||||
val lines = mutableListOf(" inx", " dex", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dey+iny sequence") {
|
||||
val lines = mutableListOf(" dey", " iny", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dex+inx sequence") {
|
||||
val lines = mutableListOf(" dex", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: preserves non-canceling sequences") {
|
||||
val lines = mutableListOf(" iny", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
lines shouldBe listOf(" iny", " inx", " rts", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeStoreLoadSame ---
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X") {
|
||||
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" lda #42", " sta myvar", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X with comments") {
|
||||
// Note: comments don't affect the optimization, but the optimizer checks lines[1] and lines[2]
|
||||
// and the comment is part of the line, so "sta myvar ; store it" doesn't match "sta myvar"
|
||||
// The optimizer uses substring(4) which includes the comment
|
||||
val lines = mutableListOf(" nop", " sta myvar", " lda myvar", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" nop", " sta myvar", " rts ; return")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes pha + pla") {
|
||||
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
|
||||
val lines = mutableListOf(" nop", " pha", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phx + plx") {
|
||||
val lines = mutableListOf(" nop", " phx", " plx", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phy + ply") {
|
||||
val lines = mutableListOf(" nop", " phy", " ply", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes lda X + sta X") {
|
||||
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
|
||||
val lines = mutableListOf(" nop", " lda myvar", " sta myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" nop", " lda myvar", " rts")
|
||||
}
|
||||
|
||||
// --- optimizeJsrRtsAndOtherCombinations ---
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp") {
|
||||
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" jmp mysub", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" jsr mysub ; call subroutine", " rts ; return", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" jmp mysub ; call subroutine", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+jmp") {
|
||||
val lines = mutableListOf(" rts", " jmp somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+bra") {
|
||||
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0") {
|
||||
val lines = mutableListOf(" lda myvar", " cmp #0", " beq skip", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" lda myvar", " beq skip", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0 with comments") {
|
||||
val lines = mutableListOf(" lda myvar ; load variable", " cmp #0 ; compare to zero", " beq skip ; branch if equal", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" lda myvar ; load variable", " beq skip ; branch if equal", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes bra+jmp") {
|
||||
val lines = mutableListOf(" bra somewhere", " jmp elsewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" bra somewhere", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: preserves jsr+rts for floats.pushFAC") {
|
||||
val lines = mutableListOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeUselessPushPopStack ---
|
||||
|
||||
test("optimizeUselessPushPop: removes phy+ldy+pla") {
|
||||
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" tya", " ldy #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes phx+ldx+pla") {
|
||||
val lines = mutableListOf(" phx", " ldx #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" txa", " ldx #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+lda+pla") {
|
||||
// Note: optimizer removes all three: pha, lda, and pla
|
||||
val lines = mutableListOf(" pha", " lda #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
|
||||
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
// --- optimizeUnneededTempvarInAdd ---
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add") {
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
|
||||
}
|
||||
|
||||
// --- optimizeTSBtoRegularOr ---
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts")
|
||||
}
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
|
||||
}
|
||||
|
||||
// --- optimizeSameAssignments ---
|
||||
|
||||
test("optimizeSameAssignments: removes duplicate lda/ldy/sta/sty word init") {
|
||||
val lines = mutableListOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" lda #1", " ldy #0", " sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
}
|
||||
|
||||
// --- optimizeSamePointerIndexingAndUselessBeq ---
|
||||
|
||||
test("optimizeSamePointerIndexing: removes redundant ldy in pointer indexing") {
|
||||
val lines = mutableListOf(
|
||||
" ldy #0", " lda (ptr),y", " ora #3", " ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore ---
|
||||
|
||||
test("optimizeAddWordToSameVariable: optimizes P8ZP_SCRATCH_PTR += AY") {
|
||||
val lines = mutableListOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " pha", " tya",
|
||||
" adc P8ZP_SCRATCH_PTR+1", " tay", " pla",
|
||||
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
|
||||
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
}
|
||||
|
||||
test("optimizeExtraRegisterLoadInWordStore: removes ldx+sta+txa+ldy+sta") {
|
||||
val lines = mutableListOf(" ldx #42", " sta myvar", " txa", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
// --- Edge cases ---
|
||||
|
||||
test("handles empty input") {
|
||||
val lines = mutableListOf<String>()
|
||||
optimize(lines) shouldBe 0
|
||||
lines shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("handles single line input") {
|
||||
val lines = mutableListOf(" rts")
|
||||
optimize(lines) shouldBe 0
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("multiple optimizations in one pass") {
|
||||
val lines = mutableListOf(" iny", " dey", " lda #1", " sta var", " lda var", " rts")
|
||||
optimize(lines) shouldBe 2
|
||||
lines shouldBe listOf(" lda #1", " sta var", " rts")
|
||||
}
|
||||
|
||||
// --- Helper function tests ---
|
||||
|
||||
// hasInstr tests
|
||||
test("hasInstr: exact match") {
|
||||
"iny".hasInstr("iny") shouldBe true
|
||||
"pla".hasInstr("pla") shouldBe true
|
||||
"rts".hasInstr("rts") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: starts with mnemonic and space") {
|
||||
"lda #1".hasInstr("lda") shouldBe true
|
||||
"sta myvar".hasInstr("sta") shouldBe true
|
||||
"ldy #0".hasInstr("ldy") shouldBe true
|
||||
"inc a".hasInstr("inc") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: contains mnemonic after label") {
|
||||
"mylabel: lda #1".hasInstr("lda") shouldBe true
|
||||
"loop: iny".hasInstr("iny") shouldBe true
|
||||
"skip: rts".hasInstr("rts") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: does not match named labels without colon") {
|
||||
// Named labels require a colon to distinguish from instructions
|
||||
"mylabel lda #1".hasInstr("lda") shouldBe false
|
||||
"loop iny".hasInstr("iny") shouldBe false
|
||||
"skip rts".hasInstr("rts") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: handles 64tass anonymous labels with colon") {
|
||||
"+: lda #1".hasInstr("lda") shouldBe true
|
||||
"++: lda #1".hasInstr("lda") shouldBe true
|
||||
"-: lda #1".hasInstr("lda") shouldBe true
|
||||
"--: lda #1".hasInstr("lda") shouldBe true
|
||||
"+: rts".hasInstr("rts") shouldBe true
|
||||
"+++: iny".hasInstr("iny") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: handles 64tass anonymous labels without colon") {
|
||||
"+ lda #1".hasInstr("lda") shouldBe true
|
||||
"++ lda #1".hasInstr("lda") shouldBe true
|
||||
"- lda #1".hasInstr("lda") shouldBe true
|
||||
"-- lda #1".hasInstr("lda") shouldBe true
|
||||
"+ rts".hasInstr("rts") shouldBe true
|
||||
"+++ iny".hasInstr("iny") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: handles scoped labels with periods") {
|
||||
"mysub.loop: lda #1".hasInstr("lda") shouldBe true
|
||||
"_private: rts".hasInstr("rts") shouldBe true
|
||||
// Without colon, named labels are not recognized
|
||||
"mysub.loop lda #1".hasInstr("lda") shouldBe false
|
||||
"_private rts".hasInstr("rts") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: does not match numbers as labels") {
|
||||
"1 lda #1".hasInstr("lda") shouldBe false
|
||||
"+1 lda #1".hasInstr("lda") shouldBe false
|
||||
"-1 lda #1".hasInstr("lda") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: does not match partial mnemonics") {
|
||||
"lda #1".hasInstr("ld") shouldBe false
|
||||
"sta var".hasInstr("st") shouldBe false
|
||||
"iny".hasInstr("in") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: does not match mnemonic in operand") {
|
||||
// Note: hasInstr uses contains(" mnemonic") which can match operands
|
||||
// This is a known limitation - the optimizer only calls hasInstr on
|
||||
// trimmed lines where the instruction is at the start or after a label
|
||||
"lda #iny".hasInstr("iny") shouldBe false
|
||||
// "sta pla" would match due to contains(" pla") - this is expected behavior
|
||||
// in the optimizer context where lines are properly formatted
|
||||
}
|
||||
|
||||
test("hasInstr: handles inc a variants") {
|
||||
"inc a".hasInstr("inc a") shouldBe true
|
||||
"inc a".hasInstr("inc a") shouldBe true
|
||||
"inc var".hasInstr("inc a") shouldBe false
|
||||
}
|
||||
|
||||
test("hasInstr: handles lines with trailing comments") {
|
||||
"iny ; increment Y".hasInstr("iny") shouldBe true
|
||||
"lda #1 ; load immediate".hasInstr("lda") shouldBe true
|
||||
"mylabel: rts ; return".hasInstr("rts") shouldBe true
|
||||
// Note: hasInstr expects trimmed lines, so leading whitespace must be removed first
|
||||
"dey ; decrement Y".hasInstr("dey") shouldBe true
|
||||
}
|
||||
|
||||
test("hasInstr: handles dec a variants") {
|
||||
"dec a".hasInstr("dec a") shouldBe true
|
||||
"dec a".hasInstr("dec a") shouldBe true
|
||||
"dec var".hasInstr("dec a") shouldBe false
|
||||
}
|
||||
|
||||
// isBranch tests
|
||||
test("isBranch: matches branch instructions") {
|
||||
"beq skip".isBranch() shouldBe true
|
||||
"bne loop".isBranch() shouldBe true
|
||||
"bcc target".isBranch() shouldBe true
|
||||
"bcs target".isBranch() shouldBe true
|
||||
"bmi neg".isBranch() shouldBe true
|
||||
"bpl pos".isBranch() shouldBe true
|
||||
"bvc noov".isBranch() shouldBe true
|
||||
"bvs ov".isBranch() shouldBe true
|
||||
"bra somewhere".isBranch() shouldBe true
|
||||
}
|
||||
|
||||
test("isBranch: matches branch instructions with trailing comments") {
|
||||
"beq skip ; branch if equal".isBranch() shouldBe true
|
||||
"bne loop ; branch if not equal".isBranch() shouldBe true
|
||||
"bra target ; always branch".isBranch() shouldBe true
|
||||
}
|
||||
|
||||
test("isBranch: does not match non-branch instructions") {
|
||||
"lda #1".isBranch() shouldBe false
|
||||
"sta var".isBranch() shouldBe false
|
||||
"rts".isBranch() shouldBe false
|
||||
"nop".isBranch() shouldBe false
|
||||
}
|
||||
|
||||
// isStoreReg tests
|
||||
test("isStoreReg: matches store instructions") {
|
||||
"sta var".isStoreReg() shouldBe true
|
||||
"sty var".isStoreReg() shouldBe true
|
||||
"stx var".isStoreReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isStoreReg: matches store instructions with trailing comments") {
|
||||
"sta var ; store accumulator".isStoreReg() shouldBe true
|
||||
"sty myvar ; store Y".isStoreReg() shouldBe true
|
||||
"stx counter ; store X".isStoreReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isStoreReg: does not match non-store instructions") {
|
||||
"lda var".isStoreReg() shouldBe false
|
||||
"ldy var".isStoreReg() shouldBe false
|
||||
"ldx var".isStoreReg() shouldBe false
|
||||
"sta".isStoreReg() shouldBe false // no space after
|
||||
}
|
||||
|
||||
// isStoreRegOrZero tests
|
||||
test("isStoreRegOrZero: matches store and stz") {
|
||||
"sta var".isStoreRegOrZero() shouldBe true
|
||||
"sty var".isStoreRegOrZero() shouldBe true
|
||||
"stx var".isStoreRegOrZero() shouldBe true
|
||||
"stz var".isStoreRegOrZero() shouldBe true
|
||||
}
|
||||
|
||||
test("isStoreRegOrZero: does not match non-store") {
|
||||
"lda var".isStoreRegOrZero() shouldBe false
|
||||
"stz".isStoreRegOrZero() shouldBe false // no space after
|
||||
}
|
||||
|
||||
// isLoadReg tests
|
||||
test("isLoadReg: matches load instructions") {
|
||||
"lda var".isLoadReg() shouldBe true
|
||||
"ldy var".isLoadReg() shouldBe true
|
||||
"ldx var".isLoadReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isLoadReg: matches load instructions with trailing comments") {
|
||||
"lda var ; load accumulator".isLoadReg() shouldBe true
|
||||
"ldy #5 ; load Y".isLoadReg() shouldBe true
|
||||
"ldx counter ; load X".isLoadReg() shouldBe true
|
||||
}
|
||||
|
||||
test("isLoadReg: does not match non-load instructions") {
|
||||
"sta var".isLoadReg() shouldBe false
|
||||
"sty var".isLoadReg() shouldBe false
|
||||
"stx var".isLoadReg() shouldBe false
|
||||
"lda".isLoadReg() shouldBe false // no space after
|
||||
}
|
||||
|
||||
// haslabel tests
|
||||
test("haslabel: detects labels") {
|
||||
haslabel("mylabel:") shouldBe true
|
||||
haslabel("loop:") shouldBe true
|
||||
haslabel("mylabel: lda #1") shouldBe true
|
||||
haslabel("loop: iny") shouldBe true
|
||||
}
|
||||
|
||||
test("haslabel: detects labels with trailing comments") {
|
||||
haslabel("mylabel: ; this is a label") shouldBe true
|
||||
haslabel("loop: lda #1 ; load immediate") shouldBe true
|
||||
haslabel("skip: ; skip ahead") shouldBe true
|
||||
}
|
||||
|
||||
test("haslabel: does not detect non-labels") {
|
||||
haslabel(" lda #1") shouldBe false
|
||||
haslabel(" iny") shouldBe false
|
||||
haslabel("; comment") shouldBe false
|
||||
haslabel("") shouldBe false
|
||||
}
|
||||
|
||||
test("haslabel: handles labels with leading whitespace") {
|
||||
haslabel(" label:") shouldBe true
|
||||
haslabel("\tlabel:") shouldBe true
|
||||
}
|
||||
|
||||
// keeplabel tests
|
||||
test("keeplabel: extracts label from line") {
|
||||
keeplabel("mylabel: lda #1") shouldBe "mylabel:"
|
||||
keeplabel("loop: iny") shouldBe "loop:"
|
||||
keeplabel("mylabel:") shouldBe "mylabel:"
|
||||
}
|
||||
|
||||
test("keeplabel: extracts label from line with trailing comment") {
|
||||
keeplabel("mylabel: lda #1 ; load immediate") shouldBe "mylabel:"
|
||||
keeplabel("loop: ; just a label") shouldBe "loop:"
|
||||
keeplabel("skip: rts ; return") shouldBe "skip:"
|
||||
}
|
||||
|
||||
test("keeplabel: returns colon-only for non-label lines with leading whitespace") {
|
||||
// keeplabel splits on first whitespace and returns first part + ":"
|
||||
// For " lda #1", first part is empty string, so returns ":"
|
||||
keeplabel(" lda #1") shouldBe ":"
|
||||
keeplabel(" iny") shouldBe ":"
|
||||
}
|
||||
|
||||
test("keeplabel: handles tab-separated labels") {
|
||||
keeplabel("mylabel\tlda #1") shouldBe "mylabel:"
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// KNOWN BUGS - These tests demonstrate current optimizer defects.
|
||||
// See AsmOptimizer.kt header for the full list.
|
||||
// ========================================================================
|
||||
|
||||
test("float copy init to different destinations should not be removed") {
|
||||
val lines = mutableListOf(
|
||||
// Lines 0-6: first float copy
|
||||
" lda #1", " ldy #0", " sta floatA", " sty floatA+1",
|
||||
" lda #1", " ldy #0", " jsr floats.copy_float",
|
||||
// Lines 7-13: second float copy to DIFFERENT destination
|
||||
" lda #1", " ldy #0", " sta floatB", " sty floatB+1",
|
||||
" lda #1", " ldy #0", " jsr floats.copy_float"
|
||||
)
|
||||
val beforeOpt = lines.toList()
|
||||
optimize(lines)
|
||||
lines shouldBe beforeOpt
|
||||
}
|
||||
|
||||
test("variable name containing 'y' should not trigger false positive") {
|
||||
val lines = mutableListOf(
|
||||
" ldy #0", " lda (ptr),y", " ora myvar", // 'y' in "myvar" no longer triggers false positive
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should be removed since "ora" doesn't modify Y
|
||||
optimize(lines) shouldBe 1
|
||||
}
|
||||
|
||||
test("actual Y-modifying instructions should still be detected") {
|
||||
val lines = mutableListOf(
|
||||
" ldy #0", " lda (ptr),y", " tay", // tay modifies Y, so ldy cannot be removed
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should NOT be removed since "tay" modifies Y
|
||||
optimize(lines) shouldBe 0
|
||||
}
|
||||
|
||||
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
|
||||
" ldy #0".modifiesYRegister() shouldBe true
|
||||
" tay".modifiesYRegister() shouldBe true
|
||||
" iny".modifiesYRegister() shouldBe true
|
||||
" dey".modifiesYRegister() shouldBe true
|
||||
" tya".modifiesYRegister() shouldBe true
|
||||
" phy".modifiesYRegister() shouldBe true
|
||||
" ply".modifiesYRegister() shouldBe true
|
||||
" sty var".modifiesYRegister() shouldBe true
|
||||
" ora myvar".modifiesYRegister() shouldBe false // false positive fixed
|
||||
" lda #0".modifiesYRegister() shouldBe false
|
||||
" cpy #0".modifiesYRegister() shouldBe false // compare doesn't modify
|
||||
" jsr func".modifiesYRegister() shouldBe false
|
||||
}
|
||||
|
||||
test("compiler-prefixed memory-mapped IO variable accesses are not optimized away") {
|
||||
symbolTable.add(StMemVar("p8b_main.p8v_io_data", DataType.UBYTE, 0xd021u, null, null))
|
||||
symbolTable.add(StMemVar("p8b_main.p8v_result", DataType.UBYTE, 0x0300u, null, null))
|
||||
val lines = mutableListOf(
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
val count = optimize(lines)
|
||||
count shouldBe 0
|
||||
lines.size shouldBe 8 // all instructions preserved
|
||||
}
|
||||
})
|
||||
@@ -14,6 +14,9 @@ import prog8.code.source.SourceCode
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.codegen.cpu6502.AsmGen6502
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
|
||||
@@ -21,20 +24,13 @@ class TestCodegen: FunSpec({
|
||||
|
||||
fun getTestOptions(): CompilationOptions {
|
||||
val target = C64Target()
|
||||
return CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
return CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(ZeropageType.DONTUSE)
|
||||
.floats(true)
|
||||
.compilerVersion("99.99")
|
||||
.memtopAddress(0xffffu)
|
||||
.build()
|
||||
}
|
||||
|
||||
test("augmented assign on arrays") {
|
||||
|
||||
@@ -7,20 +7,5 @@ dependencies {
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir(file("${project.projectDir}/src"))
|
||||
}
|
||||
resources {
|
||||
srcDir(file("${project.projectDir}/res"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
@@ -34,6 +34,9 @@ class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
|
||||
private object EmptyProgram : IAssemblyProgram {
|
||||
override val name = "<Empty Program>"
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
if(!options.quiet)
|
||||
println("** nothing assembled **")
|
||||
|
||||
@@ -7,39 +7,9 @@ dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
|
||||
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("${project.projectDir}/src")
|
||||
}
|
||||
resources {
|
||||
srcDir("${project.projectDir}/res")
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDir("${project.projectDir}/test")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn("cleanTest")
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events("skipped", "failed")
|
||||
}
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,5 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StMemorySlabBlockName
|
||||
import prog8.code.StStructInstanceBlockName
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
@@ -12,14 +11,17 @@ import prog8.intermediate.*
|
||||
|
||||
internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
fun translate(call: PtFunctionCall): ExpressionCodeResult {
|
||||
require(call.builtin)
|
||||
return when(call.name) {
|
||||
"abs__byte", "abs__word", "abs__long", "abs__float" -> funcAbs(call)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call)
|
||||
"sqrt__ubyte", "sqrt__uword", "sqrt__long", "sqrt__float" -> funcSqrt(call)
|
||||
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE)
|
||||
"divmod__uword" -> funcDivmod(call, IRDataType.WORD)
|
||||
"divmod__ubyte" -> funcDivmod(call, IRDataType.BYTE, false)
|
||||
"divmod__uword" -> funcDivmod(call, IRDataType.WORD, false)
|
||||
"divmod__byte" -> funcDivmod(call, IRDataType.BYTE, true)
|
||||
"divmod__word" -> funcDivmod(call, IRDataType.WORD, true)
|
||||
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
|
||||
"callfar" -> funcCallfar(call)
|
||||
"callfar2" -> funcCallfar2(call)
|
||||
@@ -30,7 +32,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"msb__long" -> funcMsb(call, true)
|
||||
"lsb" -> funcLsb(call, false)
|
||||
"lsb__long" -> funcLsb(call, true)
|
||||
"memory" -> funcMemory(call)
|
||||
"lmh" -> funcLmh(call)
|
||||
"peek" -> funcPeek(call, IRDataType.BYTE)
|
||||
"peekbool" -> funcPeek(call, IRDataType.BYTE)
|
||||
"peekw" -> funcPeek(call, IRDataType.WORD)
|
||||
@@ -57,20 +59,85 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(call)
|
||||
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
|
||||
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
|
||||
"prog8_lib_square_long" -> funcSquare(call, IRDataType.LONG)
|
||||
"prog8_lib_structalloc" -> funcStructAlloc(call)
|
||||
"prog8_lib_copylong" -> funcCopyFromPointer1ToPointer2(call, IRDataType.LONG)
|
||||
"prog8_lib_copyfloat" -> funcCopyFromPointer1ToPointer2(call, IRDataType.FLOAT)
|
||||
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant")
|
||||
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant")
|
||||
"swap__byte" -> funcSwap(call)
|
||||
"swap__word" -> funcSwap(call)
|
||||
"swap__long" -> funcSwap(call)
|
||||
"swap__float" -> funcSwap(call)
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
"push" -> funcPush(call)
|
||||
"pushw" -> funcPushW(call)
|
||||
"pushl" -> funcPushL(call)
|
||||
"pushf" -> funcPushF(call)
|
||||
"pop" -> funcPop()
|
||||
"popw" -> funcPopW()
|
||||
"popl" -> funcPopL()
|
||||
"popf" -> funcPopF()
|
||||
"sizeof" -> throw AssemblyError("sizeof must have been replaced with a constant ${call.position}")
|
||||
"offsetof" -> throw AssemblyError("offsetof must have been replaced with a constant ${call.position}")
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name} ${call.position}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCopyFromPointer1ToPointer2(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
private fun funcPush(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPushW(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPushL(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.LONG, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPushF(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPop(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
|
||||
private fun funcPopW(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
|
||||
private fun funcPopL(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.LONG, reg1=resultRegister), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
|
||||
private fun funcPopF(): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = RegisterNum(resultRegister)), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister)
|
||||
}
|
||||
|
||||
private fun funcCopyFromPointer1ToPointer2(call: PtFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val trSourceAddr = exprGen.translateExpression(call.args[0])
|
||||
val trTargetAddr = exprGen.translateExpression(call.args[1])
|
||||
@@ -80,15 +147,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
IRDataType.LONG -> {
|
||||
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg)
|
||||
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg, immediate = 0)
|
||||
}
|
||||
}
|
||||
IRDataType.FLOAT -> {
|
||||
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, type, fpReg1=intermediateReg, reg1=trSourceAddr.resultReg)
|
||||
it += IRInstruction(Opcode.STOREI, type, fpReg1=intermediateReg, reg1=trTargetAddr.resultReg)
|
||||
it += IRInstruction(Opcode.LOADI, type, fpReg1=RegisterNum(intermediateReg), reg1=trSourceAddr.resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.STOREI, type, fpReg1=RegisterNum(intermediateReg), reg1=trTargetAddr.resultReg, immediate = 0)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid type $type")
|
||||
@@ -96,13 +163,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
||||
private fun funcSquare(call: PtFunctionCall, resultType: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
return if(resultType==IRDataType.FLOAT) {
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = resultFpReg, fpReg2 = valueTr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(valueTr.resultFpReg)), null)
|
||||
ExpressionCodeResult(result, resultType, -1, resultFpReg)
|
||||
}
|
||||
else {
|
||||
@@ -112,7 +179,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCall(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcCall(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
@@ -126,7 +193,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcCallfar(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val bankTr = exprGen.translateExpression(call.args[0])
|
||||
val addressTr = exprGen.translateExpression(call.args[1])
|
||||
@@ -138,7 +205,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcCallfar2(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcCallfar2(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val bankTr = exprGen.translateExpression(call.args[0])
|
||||
val addressTr = exprGen.translateExpression(call.args[1])
|
||||
@@ -160,16 +227,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcDivmod(call: PtBuiltinFunctionCall, type: IRDataType): ExpressionCodeResult {
|
||||
private fun funcDivmod(call: PtFunctionCall, type: IRDataType, signed: Boolean): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val number = call.args[0]
|
||||
val divident = call.args[1]
|
||||
val divisionReg: Int
|
||||
val remainderReg: Int
|
||||
val divmodOpcode = if(signed) Opcode.SDIVMOD else Opcode.DIVMOD
|
||||
val divmodrOpcode = if(signed) Opcode.SDIVMODR else Opcode.DIVMODR
|
||||
if(divident is PtNumber) {
|
||||
val tr = exprGen.translateExpression(number)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.DIVMOD, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
|
||||
addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
|
||||
divisionReg = tr.resultReg
|
||||
remainderReg = codeGen.registers.next(type)
|
||||
} else {
|
||||
@@ -177,19 +246,29 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, numTr, numTr.resultReg, -1)
|
||||
val dividentTr = exprGen.translateExpression(divident)
|
||||
addToResult(result, dividentTr, dividentTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.DIVMODR, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
|
||||
addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
|
||||
divisionReg = numTr.resultReg
|
||||
remainderReg = dividentTr.resultReg
|
||||
}
|
||||
// DIVMOD result convention: on value stack, division and remainder on top.
|
||||
// DIVMOD instruction result convention: on value stack, division and remainder on top.
|
||||
addInstr(result, IRInstruction(Opcode.POP, type, reg1=remainderReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.POP, type, reg1=divisionReg), null)
|
||||
result += assignRegisterTo(call.args[2], divisionReg)
|
||||
result += assignRegisterTo(call.args[3], remainderReg)
|
||||
// return the 2 result values: division and remainder division in AY, remainder in R15
|
||||
if(type==IRDataType.BYTE) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=divisionReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
}
|
||||
} else if(type==IRDataType.WORD) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=divisionReg)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
}
|
||||
} else throw AssemblyError("invalid type for DIVMOD")
|
||||
return ExpressionCodeResult(result, type, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcStringCompare(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcStringCompare(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val left = exprGen.translateExpression(call.args[0])
|
||||
val right = exprGen.translateExpression(call.args[1])
|
||||
@@ -200,7 +279,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcCmp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcCmp(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
@@ -213,7 +292,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcAbs(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val sourceDt = call.args.single().type
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(sourceDt.isUnsignedWord)
|
||||
@@ -257,14 +336,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)), null)
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSgn(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcSgn(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
@@ -272,7 +351,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
if(tr.dt==IRDataType.FLOAT) {
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = tr.resultFpReg)
|
||||
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = RegisterNum(tr.resultFpReg))
|
||||
}
|
||||
} else {
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -283,7 +362,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcSqrt(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcSqrt(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
val dt = call.args[0].type
|
||||
@@ -316,7 +395,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = resultFpReg, fpReg2 = tr.resultFpReg)
|
||||
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg))
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
@@ -324,7 +403,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcMkword(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
if((call.args[0] as? PtNumber)?.number == 0.0) {
|
||||
@@ -342,7 +421,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMklong(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcMklong(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val resultReg = codeGen.registers.next(IRDataType.LONG)
|
||||
if(call.args.size==2) {
|
||||
@@ -388,7 +467,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.LONG, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcClamp(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val type = irType(call.type)
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
@@ -425,7 +504,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMin(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val type = irType(call.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftTr = exprGen.translateExpression(call.args[0])
|
||||
@@ -444,7 +523,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMax(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcMax(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val type = irType(call.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftTr = exprGen.translateExpression(call.args[0])
|
||||
@@ -463,74 +542,55 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcSwap(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
require(call.args[0].type == call.args[1].type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
val dt = irType(call.args[0].type)
|
||||
val t1 = exprGen.translateExpression(call.args[0])
|
||||
val t2 = exprGen.translateExpression(call.args[1])
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addToResult(result, t1, -1, t1.resultFpReg)
|
||||
addToResult(result, t2, -1, t2.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.SWAP, dt, fpReg1=t1.resultFpReg, fpReg2=t2.resultFpReg), null)
|
||||
result += assignFpRegisterTo(call.args[0], t1.resultFpReg)
|
||||
result += assignFpRegisterTo( call.args[1], t2.resultFpReg)
|
||||
} else {
|
||||
addToResult(result, t1, t1.resultReg, -1)
|
||||
addToResult(result, t2, t2.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.SWAP, dt, reg1=t1.resultReg, reg2=t2.resultReg), null)
|
||||
result += assignRegisterTo(call.args[0], t1.resultReg)
|
||||
result += assignRegisterTo( call.args[1], t2.resultReg)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
private fun funcPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, dt, address = address)
|
||||
it += IRInstruction(Opcode.STOREZM, dt, address = address.toAddress())
|
||||
}
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg)
|
||||
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
|
||||
if(address!=null) {
|
||||
val tr = exprGen.translateExpression(address)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = offset!!), null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = 0), null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
if (call.args[0] is PtNumber) {
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
val tr = exprGen.translateExpression(call.args[1])
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, dt, fpReg1 = tr.resultFpReg, address = address)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, dt, fpReg1 = RegisterNum(valueTr.resultFpReg), address = address.toAddress()), null)
|
||||
} else {
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, dt, reg1 = tr.resultReg, address = address)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, dt, reg1 = valueTr.resultReg, address = address.toAddress()), null)
|
||||
}
|
||||
} else {
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addToResult(result, valueTr, -1, valueTr.resultFpReg)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = valueTr.resultFpReg)
|
||||
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
|
||||
if(address!=null) {
|
||||
val addressTr = exprGen.translateExpression(address)
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = offset!!), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = offset!!), null)
|
||||
}
|
||||
} else {
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = 0), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,109 +598,77 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
private fun funcPeek(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
return if(dt==IRDataType.FLOAT) {
|
||||
if(call.args[0] is PtNumber) {
|
||||
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
|
||||
}
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), address = address.toAddress()), null)
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = RegisterNum(resultFpReg), immediate = 0), null)
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
|
||||
}
|
||||
} else {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val resultRegister = codeGen.registers.next(dt)
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
|
||||
}
|
||||
val address = (call.args[0] as PtNumber).number.toUInt()
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address.toAddress()), null)
|
||||
ExpressionCodeResult(result, dt, resultRegister, -1)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(dt)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg, immediate = 0), null)
|
||||
ExpressionCodeResult(result, dt, resultReg, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcPokemon(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val address = call.args[0]
|
||||
val newValue = call.args[1]
|
||||
|
||||
fun pokeM(result: MutableList<IRCodeChunkBase>, address: Int, value: PtExpression) {
|
||||
if(codeGen.isZero(value)) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
|
||||
}
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(value)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
|
||||
}
|
||||
}
|
||||
}
|
||||
val newValueTr = if (codeGen.isZero(newValue)) null else exprGen.translateExpression(newValue)
|
||||
|
||||
fun pokeI(result: MutableList<IRCodeChunkBase>, register: Int, value: PtExpression) {
|
||||
if(codeGen.isZero(value)) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = register)
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = register)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if(address is PtNumber) {
|
||||
return if (address is PtNumber) {
|
||||
val addressNum = address.number.toUInt()
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
val addressNum = address.number.toInt()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum)
|
||||
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum.toAddress()), null)
|
||||
if (newValueTr == null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = addressNum.toAddress()), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newValueTr.resultReg, address = addressNum.toAddress()), null)
|
||||
}
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
pokeM(result, addressNum, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
} else {
|
||||
val addressTr = exprGen.translateExpression(address)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg)
|
||||
val (baseaddress, offset) = exprGen.getAddressAndOffset(address)
|
||||
val addrExpr = baseaddress ?: address
|
||||
val finalOffset = if (baseaddress != null) offset!! else 0
|
||||
|
||||
val addressTr = exprGen.translateExpression(addrExpr)
|
||||
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
|
||||
if (newValueTr == null) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressTr.resultReg, immediate = finalOffset), null)
|
||||
} else {
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = newValueTr.resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
|
||||
}
|
||||
// TODO first evaluate the expression value to store, then the address (easier peephole optimization later)
|
||||
pokeI(result, addressTr.resultReg, call.args[1])
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val code = IRCodeChunk(null, null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "$StMemorySlabBlockName.memory_$name")
|
||||
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcStructAlloc(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcStructAlloc(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val code = IRCodeChunk(null, null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
val labelname = SymbolTable.labelnameForStructInstance(call)
|
||||
@@ -648,7 +676,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcLsb(call: PtBuiltinFunctionCall, fromLong: Boolean): ExpressionCodeResult {
|
||||
private fun funcLsb(call: PtFunctionCall, fromLong: Boolean): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -661,7 +689,23 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcLsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcLmh(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
// TODO this can be more optimal if the argument is a variable or memory address
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
// low byte returned in A, mid in R15, high (bank) in R14
|
||||
val byteReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r14"), null)
|
||||
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null)
|
||||
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1 = byteReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcLsw(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -670,7 +714,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall, fromLong: Boolean): ExpressionCodeResult {
|
||||
private fun funcMsb(call: PtFunctionCall, fromLong: Boolean): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -683,7 +727,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMsw(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcMsw(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val tr = exprGen.translateExpression(call.args.single())
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -692,7 +736,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcRolRor(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
private fun funcRolRor(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val arg = call.args[0]
|
||||
val vmDt = irType(arg.type)
|
||||
@@ -710,9 +754,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
val memAddr = (arg as? PtMemoryByte)?.address?.asConstInteger()
|
||||
val memAddr: UInt? = (arg as? PtMemoryByte)?.address?.asConstInteger()?.toUInt()
|
||||
if(memAddr!=null) {
|
||||
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr), null)
|
||||
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr.toAddress()), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
@@ -754,29 +798,24 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null)
|
||||
if(saveCarry)
|
||||
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
|
||||
result += assignRegisterTo(arg, tr.resultReg)
|
||||
result += codeGen.assignRegisterTo(arg, tr.resultReg)
|
||||
if(saveCarry)
|
||||
addInstr(result, IRInstruction(Opcode.POPST), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcSetLsbMsb(call: PtBuiltinFunctionCall, msb: Boolean): ExpressionCodeResult {
|
||||
private fun funcSetLsbMsb(call: PtFunctionCall, msb: Boolean): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val target = call.args[0]
|
||||
val isConstZeroValue = call.args[1].asConstInteger()==0
|
||||
when(target) {
|
||||
is PtIdentifier -> {
|
||||
val offset = if (msb) { if(target.type.isLong) 3 else 1 } else 0
|
||||
if(isConstZeroValue) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg)
|
||||
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg, immediate = offset)
|
||||
}
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
@@ -784,13 +823,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
|
||||
if (msb) {
|
||||
it += if(target.type.isLong)
|
||||
IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = 3)
|
||||
else
|
||||
IRInstruction(Opcode.INC, IRDataType.WORD, reg1 = pointerReg)
|
||||
}
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg)
|
||||
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg, immediate = offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -893,28 +926,4 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(false, target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtIrRegister(register, target.type, target.position))
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += codeGen.translateNode(assignment)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun assignFpRegisterTo(target: PtExpression, fpRegister: Int): IRCodeChunks {
|
||||
require(target.type.isFloat)
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(false, target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtIrRegister(fpRegister, DataType.FLOAT, target.position))
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += codeGen.translateNode(assignment)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number)
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = RegisterNum(resultFpRegister), immediateFp = expr.number)
|
||||
ExpressionCodeResult(code, vmDt,-1, resultFpRegister)
|
||||
}
|
||||
else {
|
||||
@@ -54,16 +54,28 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val code = IRCodeChunk(null, null)
|
||||
if (expr.type.isPassByValue) {
|
||||
// Check if this identifier is a constant - if so, use LOAD instead of LOADM to get the constant value directly
|
||||
val stNode = codeGen.symbolTable.lookup(expr.name)
|
||||
val isConstant = stNode is StConstant && stNode.value != null
|
||||
|
||||
if (isConstant || expr.type.isPassByValue) {
|
||||
val vmDt = irType(expr.type)
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = expr.name)
|
||||
if (isConstant) {
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = RegisterNum(resultFpRegister), labelSymbol = expr.name)
|
||||
} else {
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, fpReg1 = RegisterNum(resultFpRegister), labelSymbol = expr.name)
|
||||
}
|
||||
ExpressionCodeResult(code, vmDt, -1, resultFpRegister)
|
||||
}
|
||||
else {
|
||||
val resultRegister = codeGen.registers.next(vmDt)
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
|
||||
if (isConstant) {
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
|
||||
} else {
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
|
||||
}
|
||||
ExpressionCodeResult(code, vmDt, resultRegister, -1)
|
||||
}
|
||||
} else {
|
||||
@@ -84,16 +96,53 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtBinaryExpression -> translate(expr)
|
||||
is PtIfExpression -> translate(expr)
|
||||
is PtBranchCondExpression -> translate(expr)
|
||||
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
|
||||
is PtFunctionCall -> translate(expr)
|
||||
is PtContainmentCheck -> translate(expr)
|
||||
is PtPointerDeref -> translate(expr)
|
||||
is PtConstant -> {
|
||||
val name = expr.name
|
||||
val slab = expr.memorySlab
|
||||
// Check if the constant has a numeric value or is a memory slab reference
|
||||
if(slab != null) {
|
||||
val code = IRCodeChunk(null, null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.WORD)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StMemorySlabBlockName}.${slab.name}")
|
||||
ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
|
||||
} else if(expr.value != null) {
|
||||
TODO("didn't expect PtConstant to appear here ${expr.position}")
|
||||
// val vmDt = irType(expr.type)
|
||||
// val code = IRCodeChunk(null, null)
|
||||
// val resultRegister = codeGen.registers.next(vmDt)
|
||||
// code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = expr.value!!.toInt())
|
||||
// ExpressionCodeResult(code, vmDt, resultRegister, -1)
|
||||
} else {
|
||||
// Regular constant reference - look up in symbol table
|
||||
TODO("didn't expect PtConstant to appear here ${expr.position}")
|
||||
// val vmDt = irType(expr.type)
|
||||
// val code = IRCodeChunk(null, null)
|
||||
// val resultRegister = codeGen.registers.next(vmDt)
|
||||
// code += IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = name)
|
||||
// ExpressionCodeResult(code, vmDt, resultRegister, -1)
|
||||
}
|
||||
}
|
||||
is PtRange,
|
||||
is PtArray,
|
||||
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getAddressAndOffset(expression: PtExpression): Pair<PtExpression?, Int?> {
|
||||
// if the expression is of the form address + offset (where offset<=65535), return those components
|
||||
if(expression is PtBinaryExpression && expression.operator=="+") {
|
||||
val offset=expression.right as? PtNumber
|
||||
if(offset!=null && offset.number.toInt()<=65535) {
|
||||
return expression.left to offset.number.toInt()
|
||||
}
|
||||
}
|
||||
return null to null
|
||||
}
|
||||
|
||||
|
||||
private fun translate(deref: PtPointerDeref): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
var pointerReg: Int
|
||||
@@ -162,28 +211,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
|
||||
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
|
||||
result += instructions
|
||||
if(offset<=0u) {
|
||||
val irdt = irType(deref.type)
|
||||
return if(deref.type.isFloat) {
|
||||
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg), null)
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
|
||||
} else {
|
||||
val resultReg = codeGen.registers.next(irdt)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg), null)
|
||||
ExpressionCodeResult(result, irdt, resultReg, -1)
|
||||
}
|
||||
}
|
||||
|
||||
// load field with offset
|
||||
return if(deref.type.isFloat) {
|
||||
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADFIELD, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg, immediate = offset.toInt()), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = RegisterNum(resultReg), reg1 = pointerReg, immediate = offset.toInt()), null)
|
||||
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
|
||||
} else {
|
||||
val irdt = irType(deref.type)
|
||||
val resultReg = codeGen.registers.next(irdt)
|
||||
addInstr(result, IRInstruction(Opcode.LOADFIELD, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
|
||||
ExpressionCodeResult(result, irdt, resultReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -231,10 +267,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
|
||||
// TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does
|
||||
// Use status flags from condition expression when possible, avoiding redundant CMPI #0
|
||||
val condTr = translateExpression(ifExpr.condition)
|
||||
addToResult(result, condTr, condTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
|
||||
// Check if the last instruction already sets status flags - if so, skip the CMPI #0
|
||||
val lastInstr = condTr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val skipCmpi = lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null)
|
||||
|
||||
if (irDt != IRDataType.FLOAT) {
|
||||
@@ -346,8 +387,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
require(!expr.isMsbForSplitArray)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
val ptr = codeGen.symbolTable.lookup(identifier.name)
|
||||
it += if(ptr is StConstant)
|
||||
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value.toInt())
|
||||
it += if(ptr is StConstant) {
|
||||
if(ptr.value != null)
|
||||
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, immediate = ptr.value!!.toInt())
|
||||
else
|
||||
TODO("LOAD memory() address? ${ptr}")
|
||||
}
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = identifier.name)
|
||||
it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=resultRegister, reg2=indexWordReg)
|
||||
@@ -398,7 +443,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
|
||||
val constAddress = mem.address as? PtNumber
|
||||
if(constAddress!=null) {
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, address = constAddress.number.toInt()), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=resultRegister, address = constAddress.number.toUInt().toAddress()), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
|
||||
@@ -411,7 +456,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val pointerReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = pointerReg, labelSymbol = ptrName)
|
||||
it += IRInstruction(Opcode.LOADFIELD, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
|
||||
}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
@@ -420,7 +465,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
|
||||
val tr = translateExpression(mem.address)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
}
|
||||
|
||||
@@ -569,7 +614,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val memOffset = index * eltSize
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=resultFpRegister, labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1=RegisterNum(resultFpRegister), labelSymbol = arrayVarSymbol, symbolOffset = memOffset), null)
|
||||
}
|
||||
else {
|
||||
resultRegister = codeGen.registers.next(vmDt)
|
||||
@@ -582,7 +627,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
result += code
|
||||
if(vmDt==IRDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = resultFpRegister, reg1=indexByteReg, labelSymbol = arrayVarSymbol), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexByteReg, labelSymbol = arrayVarSymbol), null)
|
||||
}
|
||||
else {
|
||||
resultRegister = codeGen.registers.next(vmDt)
|
||||
@@ -620,11 +665,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
|
||||
if(resultDt==IRDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=resultFpRegister, reg1=pointerReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=RegisterNum(resultFpRegister), reg1=pointerReg, immediate = 0), null)
|
||||
}
|
||||
else {
|
||||
resultRegister = codeGen.registers.next(resultDt)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, resultDt, reg1=resultRegister, reg2=pointerReg, immediate = 0), null)
|
||||
}
|
||||
|
||||
return ExpressionCodeResult(result, resultDt, resultRegister, resultFpRegister)
|
||||
@@ -639,7 +684,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
"+" -> { }
|
||||
"-" -> {
|
||||
if(vmDt==IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.NEG, vmDt, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.NEG, vmDt, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.NEG, vmDt, reg1 = tr.resultReg), null)
|
||||
}
|
||||
@@ -681,7 +726,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
valueDt.isFloat -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.SGN, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg)
|
||||
it += IRInstruction(Opcode.SGN, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg))
|
||||
it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=actualResultReg2, immediate = 1)
|
||||
}
|
||||
}
|
||||
@@ -703,7 +748,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.FTOUB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
@@ -723,7 +768,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSB, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
@@ -749,7 +794,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.FTOUW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
actualResultReg2 = tr.resultReg
|
||||
@@ -778,7 +823,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSW, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
@@ -809,7 +854,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
BaseDataType.FLOAT -> {
|
||||
actualResultReg2 = codeGen.registers.next(IRDataType.LONG)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.FTOSL, IRDataType.FLOAT, reg1=actualResultReg2, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast $valueDt to long ${cast.position}")
|
||||
}
|
||||
@@ -818,19 +863,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
actualResultFpReg2 = codeGen.registers.next(IRDataType.FLOAT)
|
||||
when(valueDt.base) {
|
||||
BaseDataType.BOOL, BaseDataType.UBYTE -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
addInstr(result, IRInstruction(Opcode.FFROMUB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSB, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMUW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
addInstr(result, IRInstruction(Opcode.FFROMUW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSW, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = actualResultFpReg2), null)
|
||||
addInstr(result, IRInstruction(Opcode.FFROMSL, IRDataType.FLOAT, reg1=tr.resultReg, fpReg1 = RegisterNum(actualResultFpReg2)), null)
|
||||
}
|
||||
else -> throw AssemblyError("weird cast value type ${cast.position}")
|
||||
}
|
||||
@@ -860,7 +905,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
"-" -> operatorMinus(binExpr, vmDt)
|
||||
"*" -> operatorMultiply(binExpr, binExpr.left.type)
|
||||
"/" -> operatorDivide(binExpr, binExpr.left.type)
|
||||
"%" -> operatorModulo(binExpr, vmDt)
|
||||
"%" -> operatorModulo(binExpr, vmDt, binExpr.left.type)
|
||||
"|" -> operatorOr(binExpr, vmDt, true)
|
||||
"&" -> operatorAnd(binExpr, vmDt, true)
|
||||
"^", "xor" -> operatorXor(binExpr, vmDt)
|
||||
@@ -880,12 +925,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
if(fcall.builtin)
|
||||
return codeGen.translateBuiltinFunc(fcall)
|
||||
|
||||
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
|
||||
|
||||
if(callTarget.scopedNameString in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
|
||||
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
|
||||
return translateStackFunctions(fcall, callTarget)
|
||||
}
|
||||
when(callTarget.scopedNameString) {
|
||||
"sys.clear_carry" -> {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
@@ -920,11 +964,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val tr = translateExpression(arg)
|
||||
result += tr.chunks
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, null)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null)))
|
||||
} else {
|
||||
require(parameter.register in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
|
||||
require(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
|
||||
val assign = PtAssignment(fcall.position)
|
||||
val target = PtAssignTarget(false, fcall.position)
|
||||
@@ -941,7 +985,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
|
||||
callTarget.returns.map {
|
||||
val returnIrType = irType(it)
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), null)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), null)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
@@ -952,9 +996,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else if(returnRegSpecs.size==1) {
|
||||
val returnRegSpec = returnRegSpecs.single()
|
||||
if (fcall.type.isFloat)
|
||||
ExpressionCodeResult(result, returnRegSpec.dt, -1, returnRegSpec.registerNum)
|
||||
ExpressionCodeResult(result, returnRegSpec.dt, -1, returnRegSpec.registerNum.value)
|
||||
else
|
||||
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum, -1)
|
||||
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum.value, -1)
|
||||
} else {
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// so the actual result of the expression here is 'void' (doesn't use IR virtual registers at all)
|
||||
@@ -969,9 +1013,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val paramDt = irType(parameter.type)
|
||||
val tr = translateExpression(arg)
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, tr.resultFpReg, parameter.register)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), parameter.register)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), parameter.register)))
|
||||
result += tr.chunks
|
||||
result += codeGen.setCpuRegister(parameter.register, paramDt, tr.resultReg, tr.resultFpReg)
|
||||
}
|
||||
@@ -986,7 +1030,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else {
|
||||
val returns = callTarget.returns[0]
|
||||
val returnIrType = irType(returns.type)
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), returns.register)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), returns.register)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
@@ -999,12 +1043,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toInt(),
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
} else if(address.constbank!=null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toInt(),
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt()
|
||||
)
|
||||
} else {
|
||||
@@ -1013,20 +1057,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
result += tr.chunks
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toInt(),
|
||||
address = address.address.toAddress(),
|
||||
reg1 = tr.resultReg
|
||||
)
|
||||
}
|
||||
}
|
||||
addInstr(result, call, null)
|
||||
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
|
||||
var finalReturnRegister = returnRegSpec?.registerNum?.value ?: -1
|
||||
|
||||
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
|
||||
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
|
||||
val statusFlagResult = returnRegSpec?.cpuRegister?.statusflag
|
||||
if(statusFlagResult!=null) {
|
||||
// assign status flag bit to the return value register
|
||||
finalReturnRegister = returnRegSpec.registerNum
|
||||
finalReturnRegister = returnRegSpec.registerNum.value
|
||||
if(finalReturnRegister<0)
|
||||
finalReturnRegister = codeGen.registers.next(returnRegSpec.dt)
|
||||
when(statusFlagResult) {
|
||||
@@ -1109,52 +1153,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
|
||||
val chunk = mutableListOf<IRCodeChunkBase>()
|
||||
when(callTarget.scopedNameString) {
|
||||
"sys.push" -> {
|
||||
// push byte
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
"sys.pushw" -> {
|
||||
// push word
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, -1, -1)
|
||||
}
|
||||
"sys.pop" -> {
|
||||
// pop byte
|
||||
val popReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.BYTE, popReg, -1)
|
||||
}
|
||||
"sys.popw" -> {
|
||||
// pop word
|
||||
val popReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
|
||||
}
|
||||
"floats.push" -> {
|
||||
// push float
|
||||
val tr = translateExpression(fcall.args.single())
|
||||
chunk += tr.chunks
|
||||
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.FLOAT, -1, -1)
|
||||
}
|
||||
"floats.pop" -> {
|
||||
// pop float
|
||||
val popReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = popReg), null)
|
||||
return ExpressionCodeResult(chunk, IRDataType.FLOAT, -1, resultFpReg = popReg)
|
||||
}
|
||||
else -> throw AssemblyError("unknown stack subroutine called")
|
||||
}
|
||||
}
|
||||
|
||||
private fun callExtSubWithMultipleReturnValues(
|
||||
callTarget: StExtSub,
|
||||
fcall: PtFunctionCall,
|
||||
@@ -1164,7 +1162,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// return multiple values
|
||||
val returnRegisters = callTarget.returns.map {
|
||||
val regnum = codeGen.registers.next(irType(it.type))
|
||||
FunctionCallArgs.RegSpec(irType(it.type), regnum, it.register)
|
||||
FunctionCallArgs.RegSpec(irType(it.type), RegisterNum(regnum), it.register)
|
||||
}
|
||||
// create the call
|
||||
val call =
|
||||
@@ -1175,15 +1173,15 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toInt(),
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
|
||||
)
|
||||
}
|
||||
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
|
||||
}
|
||||
addInstr(result, call, null)
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
|
||||
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value}
|
||||
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum.value}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs)
|
||||
}
|
||||
|
||||
@@ -1200,29 +1198,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
|
||||
val other = codeGen.createLabelName()
|
||||
val after = codeGen.createLabelName()
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
// TODO can this be done more efficiently? also see operatorLessThan
|
||||
if(greaterEquals) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = other)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
|
||||
} else {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.BSTNEG, labelSymbol = other)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = other)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 0), other)
|
||||
}
|
||||
result += IRCodeChunk(after, null)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
|
||||
val branch = if(greaterEquals) Opcode.BGES else Opcode.BGTS
|
||||
val resultReg = loadBooleanStatusAsByte(branch, resultRegister, result)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
} else {
|
||||
if(binExpr.left.type.isString || binExpr.right.type.isString) {
|
||||
@@ -1256,29 +1234,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultRegister, immediate = 0), null)
|
||||
val other = codeGen.createLabelName()
|
||||
val after = codeGen.createLabelName()
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
// TODO can this be done more efficiently? also see operatorGreaterThan
|
||||
if(lessEquals) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.BSTNEG, labelSymbol = other)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = other)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
|
||||
} else {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.BSTNEG, labelSymbol = other)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
|
||||
}
|
||||
result += IRCodeChunk(after, null)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=resultRegister, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
|
||||
val branch = if(lessEquals) Opcode.BLES else Opcode.BLTS
|
||||
val resultReg = loadBooleanStatusAsByte(branch, resultRegister, result)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
} else {
|
||||
if(binExpr.left.type.isString || binExpr.right.type.isString) {
|
||||
@@ -1306,21 +1264,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
|
||||
val valueReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val label = codeGen.createLabelName()
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 0)
|
||||
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0)
|
||||
it += if (notEquals)
|
||||
IRInstruction(Opcode.BSTEQ, labelSymbol = label)
|
||||
else
|
||||
IRInstruction(Opcode.BSTNE, labelSymbol = label)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultRegister, immediate = 1)
|
||||
}
|
||||
result += IRCodeChunk(label, null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
|
||||
addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=valueReg, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=valueReg, immediate = 0), null)
|
||||
val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
} else {
|
||||
if(binExpr.left.type.isString || binExpr.right.type.isString) {
|
||||
throw AssemblyError("str compares should have been replaced with builtin function call to do the compare")
|
||||
@@ -1329,7 +1277,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
return if(rightConst!=null) {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null)
|
||||
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val skipCmpi = rightConst.toInt() == 0 && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null)
|
||||
}
|
||||
val resultReg = loadStatusAsBooleanResult(if(notEquals) Opcode.BSTNE else Opcode.BSTEQ, result)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
} else {
|
||||
@@ -1346,32 +1298,50 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
private fun loadStatusAsBooleanResult(branchForTrue: Opcode, result: MutableList<IRCodeChunkBase>): Int {
|
||||
// TODO this used to be a single instruction like SCC, SCS, SZ etc
|
||||
val other = codeGen.createLabelName()
|
||||
val after = codeGen.createLabelName()
|
||||
val labelTrue = codeGen.createLabelName()
|
||||
val labelDone = codeGen.createLabelName()
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(branchForTrue, labelSymbol = other)
|
||||
it += IRInstruction(branchForTrue, labelSymbol = labelTrue)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
|
||||
result += IRCodeChunk(after, null)
|
||||
result += IRCodeChunk(labelTrue, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
|
||||
}
|
||||
result += IRCodeChunk(labelDone, null)
|
||||
return resultReg
|
||||
}
|
||||
|
||||
private fun compareRegisterAsBooleanResult(branchForTrue: Opcode, dt: IRDataType, reg1: Int, reg2: Int, result: MutableList<IRCodeChunkBase>): Int {
|
||||
// TODO this used to be a single instruction like SCC, SCS, SZ etc
|
||||
val other = codeGen.createLabelName()
|
||||
val after = codeGen.createLabelName()
|
||||
val labelTrue = codeGen.createLabelName()
|
||||
val labelDone = codeGen.createLabelName()
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(branchForTrue, dt, reg1=reg1, reg2=reg2, labelSymbol = other)
|
||||
it += IRInstruction(branchForTrue, dt, reg1 = reg1, reg2 = reg2, labelSymbol = labelTrue)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=resultReg, immediate = 1), other)
|
||||
result += IRCodeChunk(after, null)
|
||||
result += IRCodeChunk(labelTrue, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
|
||||
}
|
||||
result += IRCodeChunk(labelDone, null)
|
||||
return resultReg
|
||||
}
|
||||
|
||||
private fun loadBooleanStatusAsByte(branchForTrue: Opcode, reg1: Int, result: MutableList<IRCodeChunkBase>): Int {
|
||||
val labelTrue = codeGen.createLabelName()
|
||||
val labelDone = codeGen.createLabelName()
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(branchForTrue, IRDataType.BYTE, reg1 = reg1, immediate = 0, labelSymbol = labelTrue)
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.JUMP, labelSymbol = labelDone)
|
||||
}
|
||||
result += IRCodeChunk(labelTrue, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = resultReg, immediate = 1)
|
||||
}
|
||||
result += IRCodeChunk(labelDone, null)
|
||||
return resultReg
|
||||
}
|
||||
|
||||
@@ -1503,20 +1473,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
|
||||
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: IRDataType, dt: DataType): ExpressionCodeResult {
|
||||
require(vmDt!=IRDataType.FLOAT) {"floating-point modulo not supported ${binExpr.position}"}
|
||||
val isSigned = dt.isSigned
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
return if(binExpr.right is PtNumber) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.MOD, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
|
||||
val opcode = if(isSigned) Opcode.MODS else Opcode.MOD
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
|
||||
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, rightTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.MODR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
|
||||
val opcode = if(isSigned) Opcode.MODSR else Opcode.MODR
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
|
||||
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -1538,9 +1511,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
addInstr(result, if(dt.isSigned)
|
||||
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
|
||||
IRInstruction(Opcode.DIVSR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2=RegisterNum(rightTr.resultFpReg))
|
||||
else
|
||||
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2=rightTr.resultFpReg)
|
||||
IRInstruction(Opcode.DIVR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2=RegisterNum(rightTr.resultFpReg))
|
||||
, null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
|
||||
}
|
||||
@@ -1600,7 +1573,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.MULSR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
|
||||
}
|
||||
} else {
|
||||
@@ -1634,21 +1607,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.DEC, vmDt, fpReg1 = tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.DEC, vmDt, fpReg1 = RegisterNum(tr.resultFpReg)), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
}
|
||||
else {
|
||||
return if(binExpr.right is PtNumber) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
|
||||
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), immediateFp = (binExpr.right as PtNumber).number), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.SUBR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.SUBR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
|
||||
}
|
||||
}
|
||||
@@ -1683,27 +1656,27 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if((binExpr.left as? PtNumber)?.number==1.0) {
|
||||
val tr = translateExpression(binExpr.right)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=RegisterNum(tr.resultFpReg)), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
}
|
||||
else if((binExpr.right as? PtNumber)?.number==1.0) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=tr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.INC, vmDt, fpReg1=RegisterNum(tr.resultFpReg)), null)
|
||||
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
}
|
||||
else {
|
||||
return if(binExpr.right is PtNumber) {
|
||||
val tr = translateExpression(binExpr.left)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = RegisterNum(tr.resultFpReg), immediateFp = (binExpr.right as PtNumber).number), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
|
||||
} else {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, -1, leftTr.resultFpReg)
|
||||
val rightTr = translateExpression(binExpr.right)
|
||||
addToResult(result, rightTr, -1, rightTr.resultFpReg)
|
||||
addInstr(result, IRInstruction(Opcode.ADDR, vmDt, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.ADDR, vmDt, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null)
|
||||
ExpressionCodeResult(result, vmDt, -1, leftTr.resultFpReg)
|
||||
}
|
||||
}
|
||||
@@ -1793,37 +1766,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
else
|
||||
resultReg = codeGen.registers.next(fieldVmDt)
|
||||
if(fieldOffset==0) {
|
||||
// no offset, can use current pointer directly
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += if (fieldVmDt == IRDataType.FLOAT)
|
||||
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
|
||||
else
|
||||
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
|
||||
}
|
||||
} else {
|
||||
// actually have to add an offset
|
||||
if(fieldOffset<=255) {
|
||||
if(fieldVmDt==IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADFIELD, fieldVmDt, fpReg1 = resultFpReg, reg1 = pointerReg, immediate = fieldOffset), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.LOADFIELD, fieldVmDt, reg1 = resultReg, reg2 = pointerReg, immediate = fieldOffset), null)
|
||||
} else {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldOffset)
|
||||
it += if (fieldVmDt == IRDataType.FLOAT)
|
||||
IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultFpReg, reg1 = pointerReg)
|
||||
else
|
||||
IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
require(fieldOffset<=65535)
|
||||
if(fieldVmDt==IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, fieldVmDt, fpReg1 = RegisterNum(resultFpReg), reg1 = pointerReg, immediate = fieldOffset), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, fieldVmDt, reg1 = resultReg, reg2 = pointerReg, immediate = fieldOffset), null)
|
||||
return ExpressionCodeResult(result, fieldVmDt, resultReg, resultFpReg)
|
||||
}
|
||||
|
||||
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, UByte> {
|
||||
// returns instructions to calculate the pointer address, and the offset into the struct it points to
|
||||
// so that LOADFIELD and STOREFIELD opcodes can be used instead of having an explicit extra ADD
|
||||
// so that LOADI and STOREFIELD opcodes can be used instead of having an explicit extra ADD
|
||||
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
|
||||
@@ -1838,12 +1791,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
for(deref in targetPointerDeref.chain.dropLast(1)) {
|
||||
val fieldinfo = struct!!.getField(deref, codeGen.program.memsizer)
|
||||
struct = fieldinfo.first.subType as StStruct
|
||||
// get new pointer from field
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt())
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg)
|
||||
}
|
||||
result += updatePointerFromField(fieldinfo, pointerReg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1852,10 +1800,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(fieldinfo.second>0u) {
|
||||
if(targetPointerDeref.derefLast) {
|
||||
require(fieldinfo.first.isPointer)
|
||||
// add the field offset
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
|
||||
result += updatePointerFromField(fieldinfo, pointerReg)
|
||||
return result to 0u
|
||||
} else {
|
||||
return result to fieldinfo.second
|
||||
@@ -1864,17 +1809,24 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(targetPointerDeref.derefLast) {
|
||||
require(fieldinfo.first.isPointer)
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg, immediate = 0), null)
|
||||
}
|
||||
return result to 0u
|
||||
}
|
||||
|
||||
private fun updatePointerFromField(fieldinfo: Pair<DataType, UByte>, pointerReg: Int): IRCodeChunk {
|
||||
return IRCodeChunk(null, null).also {
|
||||
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg, immediate = fieldinfo.second.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun addInstr(code: MutableList<IRCodeChunkBase>, instr: IRInstruction, label: String?) {
|
||||
code += IRCodeChunk(label, null).also {
|
||||
it += instr
|
||||
}
|
||||
val chunk = IRCodeChunk(label, null)
|
||||
chunk.instructions.add(instr)
|
||||
code += chunk
|
||||
}
|
||||
|
||||
internal fun addToResult(
|
||||
@@ -1890,7 +1842,7 @@ internal fun addToResult(
|
||||
codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, codeResult.dt, reg1=requiredResultReg, reg2=codeResult.resultReg)
|
||||
}
|
||||
if(requiredResultFpReg>=0 && requiredResultFpReg!=codeResult.resultFpReg) {
|
||||
codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1 = requiredResultFpReg, fpReg2 = codeResult.resultFpReg)
|
||||
codeResult.chunks.last().instructions += IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1 = RegisterNum(requiredResultFpReg), fpReg2 = RegisterNum(codeResult.resultFpReg))
|
||||
}
|
||||
result += codeResult.chunks
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -55,7 +55,11 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA:
|
||||
|| cleanupPushPop(chunk1, indexedInstructions)
|
||||
|| simplifyConstantReturns(chunk1, indexedInstructions)
|
||||
|| removeNeedlessLoads(chunk1, indexedInstructions)
|
||||
|| loadfieldsAndStorefields(chunk1, indexedInstructions)
|
||||
|| removeDeadStores(chunk1, indexedInstructions)
|
||||
// || removeLoadrForwarding(chunk1, indexedInstructions) // DISABLED - needs debugging
|
||||
|| removeSelfIdentityOps(chunk1, indexedInstructions)
|
||||
|| simplifyShiftByZero(chunk1, indexedInstructions)
|
||||
|| cancelAdjacentOps(chunk1, indexedInstructions)
|
||||
|| removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop
|
||||
} while (changed)
|
||||
}
|
||||
@@ -482,7 +486,7 @@ jump p8_label_gen_2
|
||||
else -> {}
|
||||
}
|
||||
|
||||
fun optimizeImmediateLoadAssociative(replacementOpcode: Opcode) {
|
||||
fun optimizeImmediateLoad(replacementOpcode: Opcode, isCommutative: Boolean) {
|
||||
|
||||
fun getImmediateLoad(reg: Int): Pair<Int, Int>? {
|
||||
// look if the given register gets an immediate value 1 or 2 istructions back
|
||||
@@ -501,32 +505,126 @@ jump p8_label_gen_2
|
||||
}
|
||||
|
||||
if(ins.reg1!=null) {
|
||||
val immediate1 = getImmediateLoad(ins.reg1!!)
|
||||
if(immediate1!=null) {
|
||||
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second)
|
||||
chunk.instructions.removeAt(immediate1.first)
|
||||
changed=true
|
||||
} else {
|
||||
val immediate2 = getImmediateLoad(ins.reg2!!)
|
||||
if (immediate2 != null) {
|
||||
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second)
|
||||
chunk.instructions.removeAt(immediate2.first)
|
||||
changed=true
|
||||
if (isCommutative) {
|
||||
val immediate1 = getImmediateLoad(ins.reg1!!)
|
||||
if (immediate1 != null) {
|
||||
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg2, immediate = immediate1.second)
|
||||
chunk.instructions.removeAt(immediate1.first)
|
||||
changed = true
|
||||
return
|
||||
}
|
||||
}
|
||||
val immediate2 = getImmediateLoad(ins.reg2!!)
|
||||
if (immediate2 != null) {
|
||||
chunk.instructions[idx] = IRInstruction(replacementOpcode, ins.type, reg1 = ins.reg1, immediate = immediate2.second)
|
||||
chunk.instructions.removeAt(immediate2.first)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to use immediate arithmetic instruction if possible
|
||||
when(ins.opcode) {
|
||||
Opcode.ADDR -> optimizeImmediateLoadAssociative(Opcode.ADD)
|
||||
Opcode.MULR -> optimizeImmediateLoadAssociative(Opcode.MUL)
|
||||
Opcode.MULSR -> optimizeImmediateLoadAssociative(Opcode.MULS)
|
||||
// Opcode.SUBR -> TODO("ir peephole Subr")
|
||||
// Opcode.DIVR -> TODO("ir peephole Divr")
|
||||
// Opcode.DIVSR -> TODO("ir peephole Divsr")
|
||||
// Opcode.MODR -> TODO("ir peephole Modr")
|
||||
// Opcode.DIVMODR -> TODO("ir peephole DivModr")
|
||||
Opcode.ADDR -> optimizeImmediateLoad(Opcode.ADD, true)
|
||||
Opcode.MULR -> optimizeImmediateLoad(Opcode.MUL, true)
|
||||
Opcode.MULSR -> optimizeImmediateLoad(Opcode.MULS, true)
|
||||
Opcode.SUBR -> optimizeImmediateLoad(Opcode.SUB, false)
|
||||
Opcode.DIVR -> optimizeImmediateLoad(Opcode.DIV, false)
|
||||
Opcode.DIVSR -> optimizeImmediateLoad(Opcode.DIVS, false)
|
||||
Opcode.MODR -> optimizeImmediateLoad(Opcode.MOD, false)
|
||||
// Opcode.DIVMODR - skipped, no immediate DIVMOD variant exists
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeSelfIdentityOps(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
when(ins.opcode) {
|
||||
Opcode.LOADR -> {
|
||||
if((ins.reg1 != null && ins.reg1 == ins.reg2) || (ins.fpReg1 != null && ins.fpReg1 == ins.fpReg2)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.ANDR, Opcode.ORR -> {
|
||||
if(ins.reg1 == ins.reg2) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.XORR -> {
|
||||
if(ins.reg1 == ins.reg2) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun simplifyShiftByZero(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx>0 && ins.opcode in setOf(Opcode.LSLN, Opcode.LSRN, Opcode.ASRN) && ins.reg2 != null) {
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode == Opcode.LOAD && prev.reg1 == ins.reg2 && prev.immediate == 0) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions.removeAt(idx-1)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun cancelAdjacentOps(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx >= chunk.instructions.size - 1)
|
||||
return@forEach
|
||||
val insAfter = chunk.instructions[idx+1]
|
||||
|
||||
when(ins.opcode) {
|
||||
Opcode.INV -> {
|
||||
if(insAfter.opcode == Opcode.INV && insAfter.reg1 == ins.reg1) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.NEG -> {
|
||||
val sameReg = (ins.reg1 != null && ins.reg1 == insAfter.reg1) || (ins.fpReg1 != null && ins.fpReg1 == insAfter.fpReg1)
|
||||
if(insAfter.opcode == Opcode.NEG && sameReg) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.EXT, Opcode.EXTS -> {
|
||||
if(insAfter.opcode == ins.opcode && insAfter.reg1 == ins.reg1 && insAfter.type == ins.type) {
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.INC -> {
|
||||
if(insAfter.opcode == Opcode.DEC && insAfter.reg1 == ins.reg1) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.DEC -> {
|
||||
if(insAfter.opcode == Opcode.INC && insAfter.reg1 == ins.reg1) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -595,21 +693,96 @@ jump p8_label_gen_2
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun loadfieldsAndStorefields(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if (ins.opcode == Opcode.LOADFIELD && ins.immediate==0) {
|
||||
val loadi = IRInstruction(Opcode.LOADI, ins.type, ins.reg1!!, ins.reg2!!)
|
||||
chunk.instructions[idx] = loadi
|
||||
changed = true
|
||||
private fun removeDeadStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// Detect and remove dead stores: when a register is written but never read before being overwritten.
|
||||
// Example:
|
||||
// LOAD r1, #5 <- dead store (r1 overwritten before use)
|
||||
// LOAD r1, #10
|
||||
// USE r1
|
||||
|
||||
// Track for each register: (index of last write, whether value was read since)
|
||||
val pendingWrites = mutableMapOf<Int, Pair<Int, Boolean>>() // reg -> (writeIdx, wasRead)
|
||||
val deadStores = mutableSetOf<Int>()
|
||||
|
||||
indexedInstructions.forEach { (idx, ins) ->
|
||||
if (ins.opcode in OpcodesWithSideEffects) {
|
||||
// Instruction has side effects, must never be removed as dead store,
|
||||
// AND it also counts as a "read" of all current pending registers to avoid them being removed
|
||||
pendingWrites.clear()
|
||||
return@forEach
|
||||
}
|
||||
else if (ins.opcode == Opcode.STOREFIELD && ins.immediate==0) {
|
||||
val loadi = IRInstruction(Opcode.STOREI, ins.type, ins.reg1!!, ins.reg2!!)
|
||||
chunk.instructions[idx] = loadi
|
||||
val formats = instructionFormats.getValue(ins.opcode)
|
||||
val format = formats[ins.type] ?: formats[null]
|
||||
|
||||
// First, check if this instruction READS any registers
|
||||
if(format?.reg1 == OperandDirection.READ || format?.reg1 == OperandDirection.READWRITE) {
|
||||
val reg = ins.reg1 ?: ins.fpReg1?.value
|
||||
if(reg != null) {
|
||||
val existing = pendingWrites[reg]
|
||||
if(existing != null) {
|
||||
pendingWrites[reg] = existing.first to true
|
||||
}
|
||||
}
|
||||
}
|
||||
if(format?.reg2 == OperandDirection.READ || format?.reg2 == OperandDirection.READWRITE) {
|
||||
val reg = ins.reg2 ?: ins.fpReg2?.value
|
||||
if(reg != null) {
|
||||
val existing = pendingWrites[reg]
|
||||
if(existing != null) {
|
||||
pendingWrites[reg] = existing.first to true
|
||||
}
|
||||
}
|
||||
}
|
||||
if(format?.reg3 == OperandDirection.READ || format?.reg3 == OperandDirection.READWRITE) {
|
||||
val reg = ins.reg3
|
||||
if(reg != null) {
|
||||
val existing = pendingWrites[reg]
|
||||
if(existing != null) {
|
||||
pendingWrites[reg] = existing.first to true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then, check if this instruction WRITES to any registers
|
||||
if(format?.reg1 == OperandDirection.WRITE || format?.reg1 == OperandDirection.READWRITE) {
|
||||
val reg = ins.reg1 ?: ins.fpReg1?.value
|
||||
if(reg != null) {
|
||||
// Check if previous write to this reg was dead (never read before this overwrite)
|
||||
val existing = pendingWrites[reg]
|
||||
if(existing != null && !existing.second) {
|
||||
deadStores.add(existing.first)
|
||||
}
|
||||
// Record this new write as pending (not yet read)
|
||||
pendingWrites[reg] = idx to false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any remaining pending writes that were never read are also dead
|
||||
// (unless they're the final value needed - but we can't know that here)
|
||||
// Actually, we should NOT remove these because they might be the final value
|
||||
|
||||
// Remove dead stores (in reverse order to preserve indices)
|
||||
var changed = false
|
||||
deadStores.sortedDescending().forEach { idx ->
|
||||
if(idx < chunk.instructions.size) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
}
|
||||
|
||||
//private fun removeLoadrForwarding(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// Forward LOADR instructions to their original source.
|
||||
// Example:
|
||||
// LOAD r1, #5
|
||||
// LOADR r2, r1 -> LOAD r2, #5
|
||||
// LOADR r3, r2 -> LOAD r3, #5
|
||||
|
||||
// todo: This needs more careful implementation considering:
|
||||
// - Cross-chunk register usage
|
||||
// - Function call side effects
|
||||
// - Proper invalidation of register tracking
|
||||
//}
|
||||
}
|
||||
@@ -40,10 +40,10 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
irprog.st.allVariables().forEach { variable ->
|
||||
val initValue = variable.onetimeInitializationArrayValue
|
||||
if(!initValue.isNullOrEmpty()) {
|
||||
if(initValue.any {
|
||||
it.addressOfSymbol?.startsWith(blockLabel)==true
|
||||
val initValue = variable.initializationValue
|
||||
if(initValue is IRVariableInitializer.Array) {
|
||||
if(initValue.elements.any {
|
||||
it is IRStSymbolicReference.Symbol && it.name.startsWith(blockLabel)
|
||||
})
|
||||
return // symbol occurs in an initializer value (address-of this symbol)_
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class IRUnusedCodeRemover(
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
else if(ins.fpReg1!=0) {
|
||||
else if(ins.fpReg1!=null) {
|
||||
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
@@ -106,12 +106,16 @@ class IRUnusedCodeRemover(
|
||||
irprog.blocks.forEach { block ->
|
||||
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
|
||||
if(sub.isEmpty()) {
|
||||
if(!block.options.ignoreUnused) {
|
||||
errors.info("unused subroutine '${sub.label}'", sub.position)
|
||||
// Don't remove subroutines with @shared variables
|
||||
val hasVariables = irprog.st.allVariables().any { it.name.startsWith(sub.label + ".") }
|
||||
if(!hasVariables) {
|
||||
if(!block.options.ignoreUnused) {
|
||||
errors.info("unused subroutine '${sub.label}'", sub.position)
|
||||
}
|
||||
block.children.remove(sub)
|
||||
irprog.st.removeTree(sub.label)
|
||||
numRemoved++
|
||||
}
|
||||
block.children.remove(sub)
|
||||
irprog.st.removeTree(sub.label)
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,10 +204,11 @@ class IRUnusedCodeRemover(
|
||||
irprog.st.allVariables()
|
||||
.filter { !it.uninitialized }
|
||||
.forEach {
|
||||
it.onetimeInitializationArrayValue?.let { array ->
|
||||
array.forEach {elt ->
|
||||
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
|
||||
reachable.add(irprog.getChunkWithLabel(elt.addressOfSymbol!!))
|
||||
val initValue = it.initializationValue
|
||||
if(initValue is IRVariableInitializer.Array) {
|
||||
initValue.elements.forEach {elt ->
|
||||
if(elt is IRStSymbolicReference.Symbol && irprog.st.lookup(elt.name)==null)
|
||||
reachable.add(irprog.getChunkWithLabel(elt.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,10 +251,11 @@ class IRUnusedCodeRemover(
|
||||
irprog.st.allVariables()
|
||||
.filter { !it.uninitialized }
|
||||
.forEach {
|
||||
it.onetimeInitializationArrayValue?.let { array ->
|
||||
array.forEach {elt ->
|
||||
if(elt.addressOfSymbol!=null && irprog.st.lookup(elt.addressOfSymbol!!)==null)
|
||||
linkedChunks += irprog.getChunkWithLabel(elt.addressOfSymbol!!)
|
||||
if(it.initializationValue is IRVariableInitializer.Array) {
|
||||
val initValue = it.initializationValue as IRVariableInitializer.Array
|
||||
initValue.elements.forEach {elt ->
|
||||
if(elt is IRStSymbolicReference.Symbol && irprog.st.lookup(elt.name)==null)
|
||||
linkedChunks += irprog.getChunkWithLabel(elt.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,33 @@ import prog8.code.ast.*
|
||||
|
||||
internal fun makeAllNodenamesScoped(program: PtProgram) {
|
||||
val renames = mutableListOf<Pair<PtNamedNode, String>>()
|
||||
val constRenames = mutableListOf<Pair<PtConstant, String>>()
|
||||
fun computeScopedName(node: PtNode): String {
|
||||
val name = if(node is PtNamedNode) node.name else (node as? PtConstant)?.name ?: return ""
|
||||
var parent = node.parent
|
||||
while(true) {
|
||||
when(parent) {
|
||||
is PtNamedNode -> return "${parent.scopedName}.$name"
|
||||
is PtConstant -> return "${parent.name}.$name"
|
||||
is PtProgram -> return name
|
||||
else -> {}
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
}
|
||||
fun recurse(node: PtNode) {
|
||||
node.children.forEach {
|
||||
if(it is PtNamedNode)
|
||||
renames.add(it to it.scopedName)
|
||||
when(it) {
|
||||
is PtNamedNode -> renames.add(it to it.scopedName)
|
||||
is PtConstant -> constRenames.add(it to computeScopedName(it))
|
||||
else -> {}
|
||||
}
|
||||
recurse(it)
|
||||
}
|
||||
}
|
||||
recurse(program)
|
||||
renames.forEach { it.first.name = it.second }
|
||||
constRenames.forEach { it.first.name = it.second }
|
||||
}
|
||||
|
||||
internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
|
||||
@@ -22,12 +40,14 @@ internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
|
||||
|
||||
fun moveToBlock(block: PtBlock, parent: PtSub, asmsub: PtAsmSub) {
|
||||
block.add(asmsub)
|
||||
parent.children.remove(asmsub)
|
||||
parent.removeChild(asmsub)
|
||||
}
|
||||
|
||||
fun moveToBlock(block: PtBlock, parent: PtSub, sub: PtSub) {
|
||||
// First recursively move nested subroutines
|
||||
sub.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, sub, subsub) }
|
||||
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, sub, asmsubsub) }
|
||||
|
||||
movedSubs += Pair(block, sub)
|
||||
removedSubs += Pair(parent, sub)
|
||||
}
|
||||
@@ -42,6 +62,6 @@ internal fun moveAllNestedSubroutinesToBlockScope(program: PtProgram) {
|
||||
}
|
||||
}
|
||||
|
||||
removedSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
|
||||
removedSubs.forEach { (parent, sub) -> parent.removeChild(sub) }
|
||||
movedSubs.forEach { (block, sub) -> block.add(sub) }
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.intermediate.IRDataType
|
||||
import prog8.intermediate.RegisterNum
|
||||
|
||||
internal class RegisterPool {
|
||||
// everything from 99000 onwards is reserved for special purposes:
|
||||
@@ -10,17 +11,17 @@ internal class RegisterPool {
|
||||
// 99200 - 99299 : LONG registers for syscall arguments and response value(s)
|
||||
|
||||
private var nextRegister: Int=1
|
||||
private val registerTypes: MutableMap<Int, IRDataType> = mutableMapOf()
|
||||
private val registerTypes: MutableMap<RegisterNum, IRDataType> = mutableMapOf()
|
||||
|
||||
fun getTypes(): Map<Int, IRDataType> = registerTypes
|
||||
fun getTypes(): Map<RegisterNum, IRDataType> = registerTypes
|
||||
|
||||
init {
|
||||
for(i in 99000..99099)
|
||||
registerTypes[i] = IRDataType.WORD
|
||||
registerTypes[RegisterNum(i)] = IRDataType.WORD
|
||||
for(i in 99100..99199)
|
||||
registerTypes[i] = IRDataType.BYTE
|
||||
registerTypes[RegisterNum(i)] = IRDataType.BYTE
|
||||
for(i in 99200..99299)
|
||||
registerTypes[i] = IRDataType.LONG
|
||||
registerTypes[RegisterNum(i)] = IRDataType.LONG
|
||||
}
|
||||
|
||||
fun next(type: IRDataType): Int {
|
||||
@@ -28,7 +29,7 @@ internal class RegisterPool {
|
||||
throw AssemblyError("register pool depleted")
|
||||
val result = nextRegister
|
||||
nextRegister++
|
||||
registerTypes[result] = type
|
||||
registerTypes[RegisterNum(result)] = type
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,169 @@ import prog8.code.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.intermediate.*
|
||||
|
||||
/**
|
||||
* Converter object for transforming SymbolTable nodes to IR symbol table nodes.
|
||||
* Groups all conversion functions together for better organization.
|
||||
*/
|
||||
private class StToIrConverter(val romable: Boolean) {
|
||||
fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
|
||||
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStSymbolicReference {
|
||||
return when(elt) {
|
||||
is StArrayElement.BoolValue -> IRStSymbolicReference.BoolValue(elt.value)
|
||||
is StArrayElement.Number -> IRStSymbolicReference.Numeric(elt.value)
|
||||
// Memory slabs and address-of are both stored as symbol references in the IR
|
||||
is StArrayElement.MemorySlab -> IRStSymbolicReference.Symbol("$StMemorySlabBlockName.${elt.name}")
|
||||
is StArrayElement.AddressOf -> IRStSymbolicReference.Symbol(elt.symbol)
|
||||
is StArrayElement.StructInstance -> {
|
||||
// Struct instances in arrays are stored as symbol references to the struct instance block
|
||||
val symbol = StStructInstanceBlockName + "." + (if(elt.uninitialized) elt.name else elt.name)
|
||||
IRStSymbolicReference.Symbol(symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeInitializer(
|
||||
variable: StStaticVariable,
|
||||
array: List<StArrayElement>?
|
||||
): IRVariableInitializer? {
|
||||
if(array==null && variable.initializationNumericValue==null && variable.initializationStringValue==null)
|
||||
return null
|
||||
|
||||
return when {
|
||||
variable.initializationStringValue != null -> {
|
||||
val str = variable.initializationStringValue!!
|
||||
IRVariableInitializer.Str(str.first, str.second)
|
||||
}
|
||||
variable.initializationNumericValue != null -> {
|
||||
IRVariableInitializer.Numeric(variable.initializationNumericValue!!)
|
||||
}
|
||||
array != null -> {
|
||||
val scopedArray = array.map {
|
||||
when(it) {
|
||||
is StArrayElement.AddressOf -> {
|
||||
val target = variable.lookup(it.symbol) ?:
|
||||
throw NoSuchElementException("can't find variable ${it.symbol}")
|
||||
IRStSymbolicReference.Symbol(target.scopedNameString)
|
||||
}
|
||||
else -> convertArrayElt(it)
|
||||
}
|
||||
}
|
||||
IRVariableInitializer.Array(scopedArray)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
val initValue = if('.' in variable.name) {
|
||||
when {
|
||||
variable.initializationStringValue != null -> IRVariableInitializer.Str(variable.initializationStringValue!!.first, variable.initializationStringValue!!.second)
|
||||
variable.initializationNumericValue != null -> IRVariableInitializer.Numeric(variable.initializationNumericValue!!)
|
||||
variable.initializationArrayValue != null -> IRVariableInitializer.Array(variable.initializationArrayValue!!.map { convertArrayElt(it) })
|
||||
else -> null
|
||||
}
|
||||
} else {
|
||||
makeInitializer(variable, variable.initializationArrayValue)
|
||||
}
|
||||
|
||||
val name = if('.' in variable.name) variable.name else variable.scopedNameString
|
||||
val hasInit = initValue != null
|
||||
val inBss = romable && hasInit
|
||||
val readonly = romable && hasInit
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly)
|
||||
}
|
||||
|
||||
fun convert(variable: StMemVar): IRStMemVar {
|
||||
val readonly = romable && variable.address > 255u
|
||||
if('.' in variable.name) {
|
||||
return IRStMemVar(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
variable.address,
|
||||
variable.length,
|
||||
readonly
|
||||
)
|
||||
} else {
|
||||
val scopedName = try {
|
||||
variable.scopedNameString
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
variable.name
|
||||
}
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length, readonly)
|
||||
}
|
||||
}
|
||||
|
||||
fun convert(constant: StConstant): IRStConstant {
|
||||
val scopedName = if('.' in constant.name) {
|
||||
constant.name
|
||||
} else {
|
||||
try {
|
||||
constant.scopedNameString
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
constant.name
|
||||
}
|
||||
}
|
||||
if(constant.value != null)
|
||||
return IRStConstant(scopedName, constant.dt, constant.value)
|
||||
else if(constant.memorySlab != null) {
|
||||
// For memory() constants, store reference to slab name
|
||||
// The actual address will be resolved during code generation
|
||||
val slabName = constant.memorySlab!!.name
|
||||
return IRStConstant(scopedName, constant.dt, null, slabName)
|
||||
}
|
||||
else
|
||||
TODO("constant without value or memory slab: $constant")
|
||||
}
|
||||
|
||||
fun convert(mem: StMemorySlab): IRStMemorySlab {
|
||||
return if('.' in mem.name)
|
||||
IRStMemorySlab(mem.name, mem.size, mem.align)
|
||||
else
|
||||
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
|
||||
}
|
||||
|
||||
fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
|
||||
val values = fields.zip(instance.initialValues).map { (field, value) ->
|
||||
val elt = convertArrayElt(value)
|
||||
IRStructInitValue(field.first.base, elt)
|
||||
}
|
||||
return if('.' in instance.name)
|
||||
IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
else
|
||||
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertStToIRSt(sourceSt: SymbolTable?, romable: Boolean = false): IRSymbolTable {
|
||||
val st = IRSymbolTable()
|
||||
val converter = StToIrConverter(romable)
|
||||
if (sourceSt != null) {
|
||||
sourceSt.flat.forEach {
|
||||
when(it.value.type) {
|
||||
StNodeType.STATICVAR -> st.add(convert(it.value as StStaticVariable))
|
||||
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
|
||||
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
|
||||
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
|
||||
StNodeType.STATICVAR -> st.add(converter.convert(it.value as StStaticVariable))
|
||||
StNodeType.MEMVAR -> st.add(converter.convert(it.value as StMemVar))
|
||||
StNodeType.CONSTANT -> {
|
||||
val constant = it.value as StConstant
|
||||
// If the constant has a memory() slab, add the slab to the IR symbol table first
|
||||
constant.memorySlab?.let { slab ->
|
||||
val converted = converter.convert(slab)
|
||||
st.add(converted)
|
||||
}
|
||||
st.add(converter.convert(constant))
|
||||
}
|
||||
StNodeType.MEMORYSLAB -> {
|
||||
val slab = it.value as StMemorySlab
|
||||
val converted = converter.convert(slab)
|
||||
st.add(converted)
|
||||
}
|
||||
StNodeType.STRUCTINSTANCE -> {
|
||||
val instance = it.value as StStructInstance
|
||||
val struct = sourceSt.lookup(instance.structName) as StStruct
|
||||
st.add(convert(instance, struct.fields))
|
||||
st.add(converter.convert(instance, struct.fields))
|
||||
}
|
||||
StNodeType.STRUCT -> st.add(convert(it.value as StStruct))
|
||||
StNodeType.STRUCT -> st.add(converter.convert(it.value as StStruct))
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
@@ -27,12 +174,12 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
st.validate()
|
||||
|
||||
st.allVariables().forEach { variable ->
|
||||
variable.onetimeInitializationArrayValue?.let {
|
||||
it.forEach { arrayElt ->
|
||||
val addrOfSymbol = arrayElt.addressOfSymbol
|
||||
if (addrOfSymbol != null) {
|
||||
require(addrOfSymbol.contains('.')) {
|
||||
"pointer var in array should be properly scoped: $addrOfSymbol in ${variable.name}"
|
||||
if(variable.initializationValue is IRVariableInitializer.Array) {
|
||||
val initValue = variable.initializationValue as IRVariableInitializer.Array
|
||||
initValue.elements.forEach { arrayElt ->
|
||||
if (arrayElt is IRStSymbolicReference.Symbol) {
|
||||
require(arrayElt.name.contains('.')) {
|
||||
"pointer var in array should be properly scoped: ${arrayElt.name} in ${variable.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,115 +188,3 @@ fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
|
||||
private fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
|
||||
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement {
|
||||
return if (elt.boolean != null)
|
||||
IRStArrayElement(elt.boolean, null, null)
|
||||
else if(elt.number!=null)
|
||||
IRStArrayElement(null, elt.number, null)
|
||||
else {
|
||||
val symbol = elt.addressOfSymbol ?: (StStructInstanceBlockName + "." + (elt.structInstance ?: elt.structInstanceUninitialized))
|
||||
IRStArrayElement(null, null, symbol)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
|
||||
if('.' in variable.name) {
|
||||
return IRStStaticVariable(variable.name,
|
||||
variable.dt,
|
||||
variable.initializationNumericValue,
|
||||
variable.initializationStringValue,
|
||||
variable.initializationArrayValue?.map { convertArrayElt(it) },
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align,
|
||||
variable.dirty)
|
||||
} else {
|
||||
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
|
||||
if(array==null)
|
||||
return null
|
||||
val newArray = mutableListOf<IRStArrayElement>()
|
||||
array.forEach {
|
||||
if(it.addressOfSymbol!=null) {
|
||||
val target = variable.lookup(it.addressOfSymbol!!) ?:
|
||||
throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
|
||||
newArray.add(IRStArrayElement(null, null, target.scopedNameString))
|
||||
} else {
|
||||
newArray.add(convertArrayElt(it))
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
val scopedName = variable.scopedNameString
|
||||
return IRStStaticVariable(scopedName,
|
||||
variable.dt,
|
||||
variable.initializationNumericValue,
|
||||
variable.initializationStringValue,
|
||||
fixupAddressOfInArray(variable.initializationArrayValue),
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align,
|
||||
variable.dirty
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StMemVar): IRStMemVar {
|
||||
if('.' in variable.name) {
|
||||
return IRStMemVar(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
variable.address,
|
||||
variable.length
|
||||
)
|
||||
} else {
|
||||
val scopedName = try {
|
||||
variable.scopedNameString
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
variable.name
|
||||
}
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convert(constant: StConstant): IRStConstant {
|
||||
val scopedName = if('.' in constant.name) {
|
||||
constant.name
|
||||
} else {
|
||||
try {
|
||||
constant.scopedNameString
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
constant.name
|
||||
}
|
||||
}
|
||||
return IRStConstant(scopedName, constant.dt, constant.value)
|
||||
}
|
||||
|
||||
|
||||
private fun convert(mem: StMemorySlab): IRStMemorySlab {
|
||||
return if('.' in mem.name)
|
||||
IRStMemorySlab(mem.name, mem.size, mem.align)
|
||||
else
|
||||
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
|
||||
}
|
||||
|
||||
|
||||
private fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
|
||||
val values = fields.zip(instance.initialValues).map { (field, value) ->
|
||||
val elt = convertArrayElt(value)
|
||||
IRStructInitValue(field.first.base, elt)
|
||||
}
|
||||
return if('.' in instance.name)
|
||||
IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
else
|
||||
IRStStructInstance("${StStructInstanceBlockName}.${instance.name}", instance.structName, values, instance.size)
|
||||
}
|
||||
@@ -32,6 +32,15 @@ internal class VmAssemblyProgram(
|
||||
internal val irProgram: IRProgram
|
||||
): IAssemblyProgram {
|
||||
|
||||
override val irInstructionCount: Int
|
||||
get() = irProgram.countCodeElements().first
|
||||
|
||||
override val irChunkCount: Int
|
||||
get() = irProgram.countCodeElements().second
|
||||
|
||||
override val irRegisterCount: Int
|
||||
get() = irProgram.countUsedRegisters()
|
||||
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
// the VM reads the IR file from disk.
|
||||
IRFileWriter(irProgram, null).write()
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType, numElements: Int?): Int {
|
||||
if(dt.isPointerArray)
|
||||
return 2 * numElements!!
|
||||
else if(dt.isArray || dt.isSplitWordArray) {
|
||||
require(numElements!=null)
|
||||
return when(dt.sub) {
|
||||
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> numElements*2
|
||||
BaseDataType.LONG -> numElements*4
|
||||
BaseDataType.FLOAT -> numElements*5
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
return when {
|
||||
dt.isByteOrBool -> 1 * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isFloat -> 5 * (numElements ?: 1)
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(dt: BaseDataType): Int {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
}
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override val defaultEncoding: Encoding = Encoding.ISO
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in errors)
|
||||
errors.add(text)
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in warnings)
|
||||
warnings.add(text)
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
if(text !in infos)
|
||||
infos.add(text)
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
|
||||
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
|
||||
|
||||
override fun report() {
|
||||
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size, infos.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
errors.clear()
|
||||
warnings.clear()
|
||||
infos.clear()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.OutputType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.intermediate.IRPeepholeOptimizer
|
||||
import prog8.intermediate.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
class TestIRPeepholeOpt: FunSpec({
|
||||
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||
@@ -13,20 +17,12 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
chunks.forEach { sub += it }
|
||||
block += sub
|
||||
val target = VMTarget()
|
||||
val options = CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
emptyList(),
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
val options = CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(ZeropageType.DONTUSE)
|
||||
.noSysInit(true)
|
||||
.compilerVersion("99.99")
|
||||
.build()
|
||||
val prog = IRProgram("test", IRSymbolTable(), options, target)
|
||||
prog.addBlock(block)
|
||||
prog.linkChunks()
|
||||
@@ -152,28 +148,31 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove useless div/mul, add/sub") {
|
||||
// Use different registers for each test case to avoid dead store elimination removing them
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 2),
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0)
|
||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=1, immediate = 1),
|
||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=2, immediate = 1),
|
||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=3, immediate = 1),
|
||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=4, immediate = 1),
|
||||
IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=5, immediate = 2),
|
||||
IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=6, immediate = 2),
|
||||
IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=7, immediate = 2),
|
||||
IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=8, immediate = 2),
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=9, immediate = 0),
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=10, immediate = 0)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 10
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
// First 4 are removed (div/mul/mod by 1), last 2 become no-ops (add/sub 0)
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace add/sub 1 by inc/dec") {
|
||||
// Use different registers for each test case to avoid dead store elimination removing them
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=1, immediate = 1),
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=2, immediate = 1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
@@ -185,30 +184,51 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove useless and/or/xor") {
|
||||
// Use different registers for each test case to avoid dead store elimination removing them
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 255),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 65535),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 200),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 60000),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 1),
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1)
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=4, immediate = 255),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=5, immediate = 65535),
|
||||
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=6, immediate = 2147483647),
|
||||
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=7, immediate = -1),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=8, immediate = 0),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=9, immediate = 255),
|
||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=10, immediate = 65535),
|
||||
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=11, immediate = 2147483647),
|
||||
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=12, immediate = -1),
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=13, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=14, immediate = 200),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=15, immediate = 60000),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=16, immediate = 1),
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=17, immediate = 1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 8
|
||||
irProg.chunks().single().instructions.size shouldBe 17
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
// After optimization:
|
||||
// - AND #0 (3x) -> LOAD #0
|
||||
// - AND #max (2x) -> removed
|
||||
// - AND #other (3x) -> stays
|
||||
// - OR #0 (1x) -> removed
|
||||
// - OR #max (3x) -> LOAD #max
|
||||
// - XOR #0 (1x) -> removed
|
||||
// - other (3x) -> stays
|
||||
// Total: 17 - 5 (removed) = 12, with 6 LOAD instructions
|
||||
irProg.chunks().single().instructions.size shouldBe 12
|
||||
irProg.chunks().single().instructions.count { it.opcode == Opcode.LOAD } shouldBe 6
|
||||
}
|
||||
|
||||
test("replace and/or/xor by constant number") {
|
||||
// Use different registers for each test case to avoid dead store elimination removing them
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=42, immediate = 0),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 255),
|
||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535),
|
||||
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=42, immediate = -1)
|
||||
IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=1, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.WORD, reg1=2, immediate = 0),
|
||||
IRInstruction(Opcode.AND, IRDataType.LONG, reg1=3, immediate = 0),
|
||||
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=4, immediate = 255),
|
||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=5, immediate = 65535),
|
||||
IRInstruction(Opcode.OR, IRDataType.LONG, reg1=6, immediate = -1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 6
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
@@ -228,4 +248,115 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
instr[4].immediate shouldBe 65535
|
||||
instr[5].immediate shouldBe -1
|
||||
}
|
||||
})
|
||||
|
||||
test("replace register arithmetic by immediate operations") {
|
||||
// Test the new optimization: LOAD + SUBR/DIVR/MODR → immediate operation
|
||||
// Pattern: LOAD r1, #const followed by SUBR r2, r1 → SUB r2, #const
|
||||
|
||||
// SUBR optimization
|
||||
val subrProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate = 5),
|
||||
IRInstruction(Opcode.SUBR, IRDataType.BYTE, reg1=2, reg2=1)
|
||||
))
|
||||
subrProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt1 = IRPeepholeOptimizer(subrProg, false)
|
||||
opt1.optimize(true, ErrorReporterForTests())
|
||||
val subInstr = subrProg.chunks().single().instructions
|
||||
subInstr.size shouldBe 1
|
||||
subInstr[0].opcode shouldBe Opcode.SUB
|
||||
subInstr[0].immediate shouldBe 5
|
||||
|
||||
// DIVR optimization
|
||||
val divrProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=10, immediate = 100),
|
||||
IRInstruction(Opcode.DIVR, IRDataType.WORD, reg1=20, reg2=10)
|
||||
))
|
||||
divrProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt2 = IRPeepholeOptimizer(divrProg, false)
|
||||
opt2.optimize(true, ErrorReporterForTests())
|
||||
val divInstr = divrProg.chunks().single().instructions
|
||||
divInstr.size shouldBe 1
|
||||
divInstr[0].opcode shouldBe Opcode.DIV
|
||||
divInstr[0].immediate shouldBe 100
|
||||
|
||||
// MODR optimization
|
||||
val modrProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=30, immediate = 7),
|
||||
IRInstruction(Opcode.MODR, IRDataType.BYTE, reg1=40, reg2=30)
|
||||
))
|
||||
modrProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt3 = IRPeepholeOptimizer(modrProg, false)
|
||||
opt3.optimize(true, ErrorReporterForTests())
|
||||
val modInstr = modrProg.chunks().single().instructions
|
||||
modInstr.size shouldBe 1
|
||||
modInstr[0].opcode shouldBe Opcode.MOD
|
||||
modInstr[0].immediate shouldBe 7
|
||||
}
|
||||
|
||||
test("remove self identity operations") {
|
||||
// LOADR with same fp register is allowed (constructor only blocks integer reg1==reg2)
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(10), fpReg2=RegisterNum(10)),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr[0].opcode shouldBe Opcode.LOAD
|
||||
instr[0].reg1 shouldBe 99
|
||||
instr[0].immediate shouldBe 42
|
||||
}
|
||||
|
||||
test("remove shift by zero") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0),
|
||||
IRInstruction(Opcode.LSLN, IRDataType.BYTE, reg1=1, reg2=10),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=11, immediate=0),
|
||||
IRInstruction(Opcode.LSRN, IRDataType.BYTE, reg1=2, reg2=11),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=12, immediate=0),
|
||||
IRInstruction(Opcode.ASRN, IRDataType.BYTE, reg1=3, reg2=12),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 7
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr[0].opcode shouldBe Opcode.LOAD
|
||||
instr[0].reg1 shouldBe 99
|
||||
instr[0].immediate shouldBe 42
|
||||
}
|
||||
|
||||
test("cancel adjacent ops") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1),
|
||||
IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1),
|
||||
IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2),
|
||||
IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2),
|
||||
IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33),
|
||||
IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33),
|
||||
IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44),
|
||||
IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44),
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=5),
|
||||
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=5),
|
||||
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=6),
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=6),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 13
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 3
|
||||
instr[0].opcode shouldBe Opcode.EXT
|
||||
instr[0].reg1 shouldBe 3
|
||||
instr[0].reg2 shouldBe 33
|
||||
instr[1].opcode shouldBe Opcode.EXTS
|
||||
instr[1].reg1 shouldBe 4
|
||||
instr[1].reg2 shouldBe 44
|
||||
instr[2].opcode shouldBe Opcode.LOAD
|
||||
instr[2].immediate shouldBe 42
|
||||
}
|
||||
})
|
||||
@@ -10,25 +10,21 @@ import prog8.codegen.vm.VmAssemblyProgram
|
||||
import prog8.codegen.vm.VmCodeGen
|
||||
import prog8.intermediate.IRSubroutine
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.toAddress
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
class TestVmCodeGen: FunSpec({
|
||||
|
||||
fun getTestOptions(): CompilationOptions {
|
||||
val target = VMTarget()
|
||||
return CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
compilerVersion="99.99",
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
)
|
||||
return CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(ZeropageType.DONTUSE)
|
||||
.floats(true)
|
||||
.compilerVersion("99.99")
|
||||
.build()
|
||||
}
|
||||
|
||||
test("augmented assigns") {
|
||||
@@ -148,6 +144,75 @@ class TestVmCodeGen: FunSpec({
|
||||
irChunks.size shouldBe 1
|
||||
}
|
||||
|
||||
test("augmented assigns with PtMemoryByte target - all register opcodes") {
|
||||
// Tests the fix for using ADDR/SUBR/ANDR/ORR/XORR (register-to-register)
|
||||
// instead of ADD/SUB/AND/OR/XOR (register+immediate) when the target is a
|
||||
// PtMemoryByte with a non-constant address and the operand is a variable.
|
||||
//
|
||||
// Pattern: @(ptr+offset) = @(ptr+offset) + val i.e. ptr[offset] += val
|
||||
// Non-zero offsets are used to avoid potential optimization of the address expression.
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
sub.add(PtVariable("ptr", DataType.pointer(BaseDataType.UBYTE), ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY))
|
||||
sub.add(PtVariable("val", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY))
|
||||
|
||||
fun makeMemByteAssign(op: String, offset: Int): PtAugmentedAssign {
|
||||
// Use non-zero offset to avoid optimization
|
||||
val addrExpr = PtBinaryExpression("+", DataType.UWORD, Position.DUMMY)
|
||||
addrExpr.add(PtIdentifier("main.start.ptr", DataType.pointer(BaseDataType.UBYTE), Position.DUMMY))
|
||||
addrExpr.add(PtNumber(BaseDataType.UWORD, offset.toDouble(), Position.DUMMY))
|
||||
val memByte = PtMemoryByte(Position.DUMMY)
|
||||
memByte.add(addrExpr)
|
||||
val target = PtAssignTarget(false, Position.DUMMY)
|
||||
target.add(memByte)
|
||||
val assign = PtAugmentedAssign(op, Position.DUMMY)
|
||||
assign.add(target)
|
||||
assign.add(PtIdentifier("main.start.val", DataType.UBYTE, Position.DUMMY))
|
||||
return assign
|
||||
}
|
||||
|
||||
sub.add(makeMemByteAssign("+=", 3))
|
||||
sub.add(makeMemByteAssign("-=", 5))
|
||||
sub.add(makeMemByteAssign("|=", 7))
|
||||
sub.add(makeMemByteAssign("&=", 11))
|
||||
sub.add(makeMemByteAssign("^=", 13))
|
||||
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = getTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
irChunks.size shouldBe 1
|
||||
val instructions = irChunks[0].instructions
|
||||
|
||||
// Verify correct register-to-register opcodes are used (not immediate variants)
|
||||
val addrCount = instructions.count { it.opcode == Opcode.ADDR }
|
||||
val subrCount = instructions.count { it.opcode == Opcode.SUBR }
|
||||
val orrCount = instructions.count { it.opcode == Opcode.ORR }
|
||||
val andrCount = instructions.count { it.opcode == Opcode.ANDR }
|
||||
val xorrCount = instructions.count { it.opcode == Opcode.XORR }
|
||||
|
||||
addrCount shouldBe 1
|
||||
subrCount shouldBe 1
|
||||
orrCount shouldBe 1
|
||||
andrCount shouldBe 1
|
||||
xorrCount shouldBe 1
|
||||
|
||||
// Verify that the immediate-only variants are NOT used with reg2
|
||||
val wrongAdd = instructions.any { it.opcode == Opcode.ADD && it.reg2 != null }
|
||||
val wrongSub = instructions.any { it.opcode == Opcode.SUB && it.reg2 != null }
|
||||
val wrongOr = instructions.any { it.opcode == Opcode.OR && it.reg2 != null }
|
||||
val wrongAnd = instructions.any { it.opcode == Opcode.AND && it.reg2 != null }
|
||||
val wrongXor = instructions.any { it.opcode == Opcode.XOR && it.reg2 != null }
|
||||
|
||||
(wrongAdd || wrongSub || wrongOr || wrongAnd || wrongXor) shouldBe false
|
||||
}
|
||||
|
||||
test("float comparison expressions against zero") {
|
||||
//main {
|
||||
// sub start() {
|
||||
@@ -554,7 +619,7 @@ class TestVmCodeGen: FunSpec({
|
||||
val extsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(extsub)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val call = PtFunctionCall("main.routine", true, DataType.UNDEFINED, Position.DUMMY)
|
||||
val call = PtFunctionCall("main.routine", false, false, emptyArray(), Position.DUMMY)
|
||||
sub.add(call)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
@@ -568,6 +633,6 @@ class TestVmCodeGen: FunSpec({
|
||||
irChunks[0].instructions.size shouldBe 1
|
||||
val callInstr = irChunks[0].instructions[0]
|
||||
callInstr.opcode shouldBe Opcode.CALL
|
||||
callInstr.address shouldBe 0x5000
|
||||
callInstr.address shouldBe 0x5000u.toAddress()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -5,20 +5,5 @@ plugins {
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":compilerAst"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("${project.projectDir}/src")
|
||||
}
|
||||
resources {
|
||||
srcDir("${project.projectDir}/res")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
@@ -262,6 +262,8 @@ class ConstExprEvaluator {
|
||||
"round" -> round(args[0].number)
|
||||
"floor" -> floor(args[0].number)
|
||||
"ceil" -> ceil(args[0].number)
|
||||
"sqrt" -> sqrt(args[0].number)
|
||||
"abs" -> abs(args[0].number)
|
||||
"minf", "min" -> min(args[0].number, args[1].number)
|
||||
"maxf", "max" -> max(args[0].number, args[1].number)
|
||||
"clampf", "clamp" -> {
|
||||
@@ -373,7 +375,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
"isspace" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in arrayOf(32, 13, 9, 10, 141, 160), func.position)
|
||||
NumericLiteral.fromBoolean(char in setOf(32, 13, 9, 10, 141, 160), func.position)
|
||||
}
|
||||
"isprint" -> {
|
||||
val char = args[0].number.toInt()
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.FatalAstException
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.maySwapOperandOrder
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.AssociativeOperators
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.isInteger
|
||||
import prog8.ast.walk.*
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
@@ -19,22 +15,22 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
|
||||
private val evaluator = ConstExprEvaluator()
|
||||
|
||||
override fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
|
||||
val constAddr = addressOf.constValue(program) ?: return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(addressOf, constAddr, parent))
|
||||
return listOf(AstReplaceNode(addressOf, constAddr, parent))
|
||||
}
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<AstModification> {
|
||||
// @( &thing ) --> thing (but only if thing is a byte type!)
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null) {
|
||||
if(addrOf.identifier?.inferType(program)?.isBytes==true)
|
||||
return listOf(IAstModification.ReplaceNode(memread, addrOf.identifier!!, parent))
|
||||
return listOf(AstReplaceNode(memread, addrOf.identifier!!, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<AstModification> {
|
||||
|
||||
if(numLiteral.type==BaseDataType.LONG) {
|
||||
// see if LONG values may be reduced to something smaller
|
||||
@@ -42,7 +38,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(smaller.type!=BaseDataType.LONG) {
|
||||
if(parent !is Assignment || !parent.target.inferType(program).isLong) {
|
||||
// do NOT reduce the type if the target of the assignment is a long
|
||||
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
|
||||
return listOf(AstReplaceNode(numLiteral, smaller, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,23 +48,35 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(iDt.isKnown && !iDt.isBool && !(iDt issimpletype numLiteral.type)) {
|
||||
val casted = numLiteral.cast(iDt.getOrUndef().base, true)
|
||||
if(casted.isValid) {
|
||||
return listOf(IAstModification.ReplaceNode(numLiteral, casted.valueOrZero(), parent))
|
||||
return listOf(AstReplaceNode(numLiteral, casted.valueOrZero(), parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<AstModification> {
|
||||
val result = containment.constValue(program)
|
||||
if(result!=null)
|
||||
return listOf(IAstModification.ReplaceNode(containment, result, parent))
|
||||
return listOf(AstReplaceNode(containment, result, parent))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<AstModification> {
|
||||
val constValue = expr.constValue(program) ?: return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(expr, constValue, parent))
|
||||
return listOf(AstReplaceNode(expr, constValue, parent))
|
||||
}
|
||||
|
||||
private fun getPointerConstValue(expr: Expression): NumericLiteral? {
|
||||
if (expr.inferType(program).isPointer) {
|
||||
if (expr is IdentifierReference) {
|
||||
val target = expr.definingScope.lookup(expr.nameInSource) as? VarDecl
|
||||
if (target?.type == VarDeclType.CONST) {
|
||||
return target.value?.constValue(program)
|
||||
}
|
||||
}
|
||||
}
|
||||
return expr.constValue(program)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -88,10 +96,41 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
* (X / c1) * c2 -> X / (c2/c1)
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<AstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
// pointer arithmetic: ptr + offset, ptr - offset
|
||||
if (leftDt.isPointer && (expr.operator == "+" || expr.operator == "-") && !rightDt.isPointer) {
|
||||
val leftPtrConst = getPointerConstValue(expr.left)
|
||||
val rightOffsetConst = expr.right.constValue(program)
|
||||
if (leftPtrConst != null && rightOffsetConst != null) {
|
||||
val ptrDt = leftDt.getOrUndef()
|
||||
val subDt = ptrDt.sub
|
||||
val scale = if(subDt is BaseDataType) program.memsizer.memorySize(subDt) else ptrDt.size(program.memsizer)
|
||||
val offset = rightOffsetConst.number.toInt() * scale
|
||||
val result = if (expr.operator == "+") leftPtrConst.number + offset else leftPtrConst.number - offset
|
||||
return listOf(AstReplaceNode(expr, NumericLiteral(BaseDataType.UWORD, result, expr.position), parent))
|
||||
}
|
||||
}
|
||||
// commutative pointer arithmetic: offset + ptr
|
||||
if (rightDt.isPointer && expr.operator == "+" && !leftDt.isPointer) {
|
||||
val rightPtrConst = getPointerConstValue(expr.right)
|
||||
val leftOffsetConst = expr.left.constValue(program)
|
||||
if (rightPtrConst != null && leftOffsetConst != null) {
|
||||
val ptrDt = rightDt.getOrUndef()
|
||||
val subDt = ptrDt.sub
|
||||
val scale = if(subDt is BaseDataType) program.memsizer.memorySize(subDt) else ptrDt.size(program.memsizer)
|
||||
val offset = leftOffsetConst.number.toInt() * scale
|
||||
val result = rightPtrConst.number + offset
|
||||
return listOf(AstReplaceNode(expr, NumericLiteral(BaseDataType.UWORD, result, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
val modifications = mutableListOf<AstModification>()
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
@@ -108,7 +147,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
leftString.encoding)
|
||||
}
|
||||
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
|
||||
return listOf(AstReplaceNode(expr, concatStr, parent))
|
||||
}
|
||||
else if (expr.operator=="*" && expr.left is StringLiteral) {
|
||||
if (rightconst != null) {
|
||||
@@ -122,7 +161,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
errors.err("can only multiply string by integer constant", rightconst.position)
|
||||
else {
|
||||
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
|
||||
return listOf(AstReplaceNode(expr, newStr, parent))
|
||||
}
|
||||
}
|
||||
// other errors are reported later
|
||||
@@ -148,7 +187,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
part.value.forEach { tmp += it.copy() }
|
||||
}
|
||||
val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newArray, parent))
|
||||
return listOf(AstReplaceNode(expr, newArray, parent))
|
||||
}
|
||||
} else {
|
||||
val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl()
|
||||
@@ -173,16 +212,16 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// X + С1 == C2 --> X == C2 - C1
|
||||
val newRightConst = NumericLiteral(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
AstReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
AstReplaceNode(expr.right, newRightConst, expr)
|
||||
)
|
||||
}
|
||||
"-" -> {
|
||||
// X - С1 == C2 --> X == C2 + C1
|
||||
val newRightConst = NumericLiteral(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
AstReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
AstReplaceNode(expr.right, newRightConst, expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -216,7 +255,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// if in a chained comparison, that one has to be desugared first though.
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
modifications += IAstModification.ReplaceNode(expr, result, parent)
|
||||
modifications += AstReplaceNode(expr, result, parent)
|
||||
}
|
||||
|
||||
if(leftconst==null && rightconst!=null && rightconst.number<0.0) {
|
||||
@@ -224,13 +263,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// X - -1 ---> X + 1
|
||||
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
|
||||
val plusExpr = BinaryExpression(expr.left, "+", posNumber, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, plusExpr, parent))
|
||||
}
|
||||
else if (expr.operator == "+") {
|
||||
// X + -1 ---> X - 1
|
||||
val posNumber = NumericLiteral.optimalNumeric(rightconst.type, null, -rightconst.number, rightconst.position)
|
||||
val plusExpr = BinaryExpression(expr.left, "-", posNumber, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, plusExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, plusExpr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +287,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val operator = if(leftBinExpr.operator=="+") expr.operator else if(expr.operator=="-") "+" else "-"
|
||||
val constants = BinaryExpression(c2, operator, rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, leftBinExpr.operator, constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,13 +298,13 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// (X * C2) * rightConst --> X * (rightConst*C2)
|
||||
val constants = BinaryExpression(rightconst, "*", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, newExpr, parent))
|
||||
} else if (leftBinExpr.operator=="/") {
|
||||
if(expr.inferType(program) issimpletype BaseDataType.FLOAT) {
|
||||
// (X / C2) * rightConst --> X * (rightConst/C2) only valid for floating point
|
||||
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +316,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// NOTE: do not optimize (X * C1) / C2 for integers, --> X * (C1/C2) because this causes precision loss on integers
|
||||
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +331,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val c3 = evaluator.evaluate(c1, expr.operator, c2)
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "+", c3, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
modifications += AstReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
|
||||
@@ -301,7 +340,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
val c3 = evaluator.evaluate(c1, expr.operator, c2)
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "-", c3, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
modifications += AstReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
|
||||
@@ -312,7 +351,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(xDt==yDt) {
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
modifications += AstReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +361,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(array: ArrayLiteral, parent: Node): Iterable<AstModification> {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
@@ -335,26 +374,26 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(vardeclType!=null) {
|
||||
val newArray = array.cast(vardeclType)
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
return listOf(AstReplaceNode(array, newArray, parent))
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if (arrayDt.isKnown) {
|
||||
val newArray = array.cast(arrayDt.getOrUndef())
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
return listOf(AstReplaceNode(array, newArray, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<AstModification> {
|
||||
if(parent is VarDecl && parent.parent is Block) {
|
||||
// only block level (global) initializers are considered here, because they're run just once at program startup
|
||||
|
||||
val const = arrayIndexedExpression.constValue(program)
|
||||
if (const != null)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, const, parent))
|
||||
return listOf(AstReplaceNode(arrayIndexedExpression, const, parent))
|
||||
|
||||
val constIndex = arrayIndexedExpression.indexer.constIndex()
|
||||
if (constIndex != null) {
|
||||
@@ -365,7 +404,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(array!=null) {
|
||||
val value = array.value[constIndex].constValue(program)
|
||||
if(value!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, value, parent))
|
||||
return listOf(AstReplaceNode(arrayIndexedExpression, value, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,20 +416,54 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
|
||||
val constValues = functionCallExpr.constValues(program)
|
||||
if (constValues != null) {
|
||||
if (constValues.size == 1) {
|
||||
return listOf(AstReplaceNode(functionCallExpr, constValues[0], parent))
|
||||
} else {
|
||||
// Multi-value return
|
||||
val container = parent.parent as? IStatementContainer ?: return noModifications
|
||||
if (parent is Assignment && parent.target.multi?.size == constValues.size) {
|
||||
val mods = mutableListOf<AstModification>()
|
||||
for (i in constValues.indices.reversed()) {
|
||||
val assign = Assignment(parent.target.multi!![i], constValues[i], AssignmentOrigin.USERCODE, parent.position)
|
||||
mods.add(AstInsert.after(parent, assign, container))
|
||||
}
|
||||
mods.add(AstRemove(parent, container))
|
||||
return mods
|
||||
} else if (parent is VarDecl && parent.names.size == constValues.size) {
|
||||
val mods = mutableListOf<AstModification>()
|
||||
for (i in constValues.indices.reversed()) {
|
||||
val newDecl = VarDecl.builder(DataType.forDt(constValues[i].type), parent.position)
|
||||
.copyFrom(parent)
|
||||
.names(parent.names[i])
|
||||
.type(VarDeclType.CONST)
|
||||
.value(constValues[i])
|
||||
.hasExplicitInitializer(true)
|
||||
.build()
|
||||
newDecl.allowInitializeWithZero = false
|
||||
mods.add(AstInsert.after(parent, newDecl, container))
|
||||
}
|
||||
mods.add(AstRemove(parent, container))
|
||||
return mods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val constvalue = functionCallExpr.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
|
||||
listOf(AstReplaceNode(functionCallExpr, constvalue, parent))
|
||||
else {
|
||||
val const2 = evaluator.evaluate(functionCallExpr, program)
|
||||
if(const2!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, const2, parent))
|
||||
listOf(AstReplaceNode(functionCallExpr, const2, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<AstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: BaseDataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
|
||||
val fromCast = rangeFrom.cast(targetDt, true)
|
||||
val toCast = rangeTo.cast(targetDt, true)
|
||||
@@ -427,7 +500,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// attempt to translate the iterable into ubyte values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
BaseDataType.BYTE -> {
|
||||
@@ -435,7 +508,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// attempt to translate the iterable into byte values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
@@ -443,7 +516,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// attempt to translate the iterable into uword values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
BaseDataType.WORD -> {
|
||||
@@ -451,7 +524,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
// attempt to translate the iterable into word values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvarSimpleDt, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
return listOf(AstReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
else -> { /* nothing for floats, these are not allowed in for loops and will give an error elsewhere */ }
|
||||
@@ -460,7 +533,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
|
||||
if (decl.type == VarDeclType.CONST && decl.datatype.isPointer) {
|
||||
val sizer = program.memsizer
|
||||
if (decl.datatype.size(sizer) > 1) {
|
||||
errors.err("due to internal compiler complexity, currently pointer variables with data type size > 1 cannot be const", decl.position)
|
||||
}
|
||||
}
|
||||
|
||||
val numval = decl.value as? NumericLiteral
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
@@ -470,14 +550,14 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(!(valueDt istype decl.datatype)) {
|
||||
val cast = numval.cast(decl.datatype.base, true)
|
||||
if (cast.isValid) {
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
return listOf(AstReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<AstModification> {
|
||||
val count = (repeatLoop.iterations as? NumericLiteral)?.number
|
||||
if(count!=null && floor(count)!=count) {
|
||||
val integer = NumericLiteral.optimalInteger(count.toInt(), repeatLoop.position)
|
||||
@@ -487,12 +567,12 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
|
||||
val constValue = typecast.constValue(program) ?: return noModifications
|
||||
return listOf(IAstModification.ReplaceNode(typecast, constValue, parent))
|
||||
return listOf(AstReplaceNode(typecast, constValue, parent))
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<AstModification> {
|
||||
val address = subroutine.asmAddress?.address
|
||||
if(address!=null) {
|
||||
val constAddress = address.constValue(program)
|
||||
@@ -504,47 +584,30 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private class ShuffleOperands(val expr: BinaryExpression,
|
||||
val exprOperator: String?,
|
||||
val subExpr: BinaryExpression,
|
||||
val newExprLeft: Expression?,
|
||||
val newExprRight: Expression?,
|
||||
val newSubexprLeft: Expression?,
|
||||
val newSubexprRight: Expression?
|
||||
): IAstModification {
|
||||
override fun perform() {
|
||||
if(exprOperator!=null) expr.operator = exprOperator
|
||||
if(newExprLeft!=null) expr.left = newExprLeft
|
||||
if(newExprRight!=null) expr.right = newExprRight
|
||||
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
|
||||
if(newSubexprRight!=null) subExpr.right = newSubexprRight
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupTwoFloatConstsTogether(expr: BinaryExpression,
|
||||
subExpr: BinaryExpression,
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
subrightIsConst: Boolean): AstModification?
|
||||
{
|
||||
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
|
||||
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
|
||||
// If associative, we can simply shuffle the const operands around to optimize.
|
||||
if(expr.operator in AssociativeOperators && maySwapOperandOrder(expr)) {
|
||||
// If commutative, we can simply shuffle the const operands around to optimize.
|
||||
if(expr.operator in CommutativeOperators && expr.maySwapOperandOrder()) {
|
||||
return if(leftIsConst) {
|
||||
if(subleftIsConst)
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||
AstShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||
else
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
|
||||
AstShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
|
||||
} else {
|
||||
if(subleftIsConst)
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
AstShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
else
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
|
||||
AstShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,9 +615,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
if(expr.operator=="-" || expr.operator=="/") {
|
||||
if(leftIsConst) {
|
||||
return if (subleftIsConst) {
|
||||
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
|
||||
AstShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
AstReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position),
|
||||
@@ -562,9 +625,9 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
AstShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
AstReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
@@ -594,7 +657,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"/",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1*V)/C2 -> (C1/C2)*V
|
||||
@@ -609,7 +672,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*" && subExpr.operator=="/" && subExpr.inferType(program) issimpletype BaseDataType.FLOAT) {
|
||||
@@ -628,7 +691,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1/V)*C2 -> (C1*C2)/V
|
||||
@@ -643,7 +706,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="+" && subExpr.operator=="-") {
|
||||
@@ -661,7 +724,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1-v)+c2 -> (c1+c2)-v
|
||||
@@ -676,7 +739,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="-" && subExpr.operator=="+") {
|
||||
@@ -694,7 +757,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"-",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1+v)-c2 -> v+(c1-c2)
|
||||
@@ -709,7 +772,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
return AstReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,3 +780,27 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom modification to shuffle operands in binary expressions.
|
||||
* This is specific to constant folding optimization.
|
||||
*/
|
||||
private class AstShuffleOperands(
|
||||
val expr: BinaryExpression,
|
||||
val exprOperator: String?,
|
||||
val subExpr: BinaryExpression,
|
||||
val newExprLeft: Expression?,
|
||||
val newExprRight: Expression?,
|
||||
val newSubexprLeft: Expression?,
|
||||
val newSubexprRight: Expression?
|
||||
) : AstModification() {
|
||||
override val affectedNodes = listOf(expr, subExpr)
|
||||
|
||||
override fun perform() {
|
||||
if(exprOperator!=null) expr.operator = exprOperator
|
||||
if(newExprLeft!=null) expr.left = newExprLeft
|
||||
if(newExprRight!=null) expr.right = newExprRight
|
||||
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
|
||||
if(newSubexprRight!=null) subExpr.right = newSubexprRight
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@ package prog8.optimizer
|
||||
import prog8.ast.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.*
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.CallGraph
|
||||
|
||||
@@ -18,12 +17,28 @@ class VarConstantValueTypeAdjuster(
|
||||
|
||||
private lateinit var callGraph : CallGraph
|
||||
|
||||
override fun before(program: Program) : Iterable<IAstModification> {
|
||||
override fun before(program: Program) : Iterable<AstModification> {
|
||||
callGraph = CallGraph(program)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
private fun getConstantInitializer(expr: Expression): Expression? {
|
||||
val cv = expr.constValue(program)
|
||||
if (cv != null) return cv
|
||||
if (expr is MemorySlabRef) return expr
|
||||
if (expr is IFunctionCall && expr.isMemoryCall) return expr
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isConstantInitializer(expr: Expression): Boolean = getConstantInitializer(expr) != null
|
||||
|
||||
private fun isMemorySlabInit(expr: Expression): Boolean =
|
||||
expr is MemorySlabRef || (expr is IFunctionCall && expr.isMemoryCall)
|
||||
|
||||
private fun requiresZp(decl: VarDecl): Boolean =
|
||||
decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
|
||||
|
||||
if(decl.parent is AnonymousScope)
|
||||
throw FatalAstException("vardecl may no longer occur in anonymousscope ${decl.position}")
|
||||
@@ -38,99 +53,117 @@ class VarConstantValueTypeAdjuster(
|
||||
}
|
||||
|
||||
// replace variables by constants, if possible
|
||||
if(options.optimize) {
|
||||
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE || !decl.datatype.isNumericOrBool)
|
||||
return noModifications
|
||||
if (decl.value != null && decl.value!!.constValue(program) == null)
|
||||
return noModifications
|
||||
val usages = callGraph.usages(decl)
|
||||
val (writes, reads) = usages
|
||||
.partition {
|
||||
it is InlineAssembly // can't really tell if it's written to or only read, assume the worst
|
||||
|| it.parent is AssignTarget
|
||||
|| it.parent is ForLoop
|
||||
|| it.parent is AddressOf
|
||||
|| (it.parent as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
|
||||
}
|
||||
|
||||
var singleAssignment: Assignment? = null
|
||||
val singleWrite=writes.singleOrNull()
|
||||
if(singleWrite!=null) {
|
||||
singleAssignment = singleWrite.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
singleAssignment = singleWrite.parent.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
// we could be part of a multi-assign
|
||||
if(singleWrite.parent is AssignTarget && singleWrite.parent.parent is AssignTarget)
|
||||
singleAssignment = singleWrite.parent.parent.parent as? Assignment
|
||||
}
|
||||
}
|
||||
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE
|
||||
|| !(decl.datatype.isNumericOrBool || decl.datatype.isPointer))
|
||||
return noModifications
|
||||
if (decl.value != null && !isConstantInitializer(decl.value!!))
|
||||
return noModifications
|
||||
if (decl.value != null && isMemorySlabInit(decl.value!!))
|
||||
return noModifications // a pointer to a memory slab is usually accessed often and/or in ways that benefit more from the pointer being an actual variable (preferrably in zeropage)
|
||||
val usages = callGraph.usages(decl)
|
||||
val (writes, reads) = usages
|
||||
.partition {
|
||||
it is InlineAssembly // can't really tell if it's written to or only read, assume the worst
|
||||
|| it.parent is AssignTarget
|
||||
|| it.parent is ForLoop
|
||||
|| (it.parent as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
|
||||
}
|
||||
|
||||
if (singleAssignment == null) {
|
||||
if (writes.isEmpty()) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether
|
||||
if ("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
val declValue = decl.value?.constValue(program)
|
||||
if (declValue != null) {
|
||||
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.splitwordarray, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.alignment, decl.dirty, decl.position)
|
||||
decl.value = null
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, const, parent)
|
||||
)
|
||||
// If the address of the variable is taken, we cannot replace it with a constant.
|
||||
if (reads.any { it.parent is AddressOf })
|
||||
return noModifications
|
||||
|
||||
var singleAssignment: Assignment? = null
|
||||
val singleWrite=writes.singleOrNull()
|
||||
if(singleWrite!=null) {
|
||||
singleAssignment = singleWrite.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
singleAssignment = singleWrite.parent.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
// we could be part of a multi-assign
|
||||
if(singleWrite.parent is AssignTarget && singleWrite.parent.parent is AssignTarget)
|
||||
singleAssignment = singleWrite.parent.parent.parent as? Assignment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (singleAssignment == null) {
|
||||
if (writes.isEmpty()) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether
|
||||
if ("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(AstRemove(decl, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (singleAssignment.origin == AssignmentOrigin.VARINIT && singleAssignment.value.constValue(program) != null) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether, including the single assignment
|
||||
if("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer),
|
||||
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// variable only has a single write, and it is the initialization value, so it can be replaced with a constant, but only IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
|
||||
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.splitwordarray, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.alignment, decl.dirty, decl.position)
|
||||
val declValue = if (decl.value != null) getConstantInitializer(decl.value!!) else null
|
||||
if (declValue != null && !requiresZp(decl) && canBeMadeConst(decl, usages)) {
|
||||
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
|
||||
val const = VarDecl.builder(decl.datatype, decl.position)
|
||||
.copyFrom(decl)
|
||||
.type(VarDeclType.CONST)
|
||||
.value(declValue)
|
||||
.build()
|
||||
decl.value = null
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, const, parent),
|
||||
IAstModification.Remove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
AstReplaceNode(decl, const, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
/*
|
||||
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
|
||||
if(writes.size==2) {
|
||||
val firstAssignment = writes[0].parent as? Assignment
|
||||
val secondAssignment = writes[1].parent as? Assignment
|
||||
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
|
||||
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
|
||||
} else {
|
||||
if (singleAssignment.origin == AssignmentOrigin.VARINIT && isConstantInitializer(singleAssignment.value) && !isMemorySlabInit(singleAssignment.value) && !requiresZp(decl) && canBeMadeConst(decl, usages)) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether, including the single assignment
|
||||
if("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
AstRemove(decl, parent as IStatementContainer),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// variable only has a single write, and it is the initialization value, so it can be replaced with a constant, but only IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
|
||||
val const = VarDecl.builder(decl.datatype, decl.position)
|
||||
.copyFrom(decl)
|
||||
.type(VarDeclType.CONST)
|
||||
.value(singleAssignment.value)
|
||||
.build()
|
||||
return listOf(
|
||||
AstReplaceNode(decl, const, parent),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
|
||||
private fun canBeMadeConst(decl: VarDecl, usages: List<Node>): Boolean {
|
||||
if (decl.datatype.isPointer) {
|
||||
if (decl.datatype.size(program.memsizer) > 1) return false
|
||||
val derefCount = usages.count {
|
||||
val p = it.parent
|
||||
val gp = p.parent
|
||||
it is PtrDereference || it is ArrayIndexedPtrDereference
|
||||
|| p is ArrayIndexedExpression || p is DirectMemoryRead || p is DirectMemoryWrite
|
||||
|| gp is DirectMemoryRead || gp is DirectMemoryWrite
|
||||
}
|
||||
if (derefCount > 2) return false
|
||||
if (derefCount > 0 && (decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpression, parent: Node): Iterable<AstModification> {
|
||||
val from = range.from.constValue(program)?.number
|
||||
val to = range.to.constValue(program)?.number
|
||||
val step = range.step.constValue(program)?.number
|
||||
@@ -160,7 +193,7 @@ class VarConstantValueTypeAdjuster(
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
|
||||
// choose specific builtin function for the given types
|
||||
val func = functionCallExpr.target.nameInSource
|
||||
if(func==listOf("clamp")) {
|
||||
@@ -179,7 +212,7 @@ class VarConstantValueTypeAdjuster(
|
||||
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
|
||||
return noModifications
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
@@ -209,7 +242,7 @@ class VarConstantValueTypeAdjuster(
|
||||
errors.err("expected numeric arguments", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
@@ -224,14 +257,14 @@ class VarConstantValueTypeAdjuster(
|
||||
dt.isLong -> "abs__long"
|
||||
dt.isFloat -> "abs__float"
|
||||
dt.isUnsignedByte || dt.isUnsignedWord -> {
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, functionCallExpr.args[0], parent))
|
||||
}
|
||||
else -> {
|
||||
errors.err("expected numeric argument", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
@@ -247,17 +280,21 @@ class VarConstantValueTypeAdjuster(
|
||||
dt.isLong -> {
|
||||
val value = functionCallExpr.args[0].constValue(program)?.number
|
||||
if(value!=null && value<0) {
|
||||
errors.err("expected unsigned or float numeric argument", functionCallExpr.args[0].position)
|
||||
errors.err("expected positive integer or float numeric argument", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
"sqrt__long"
|
||||
}
|
||||
dt.isSigned -> {
|
||||
errors.err("expected unsigned (positive) numeric argument", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
else -> {
|
||||
errors.err("expected numeric argument", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
@@ -266,21 +303,15 @@ class VarConstantValueTypeAdjuster(
|
||||
val t1 = functionCallExpr.args[0].inferType(program)
|
||||
if(t1.isLong) {
|
||||
val replaceFunc = func[0]+"__long"
|
||||
return listOf(IAstModification.SetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
// choose specific builtin function for the given types
|
||||
val func = functionCallStatement.target.nameInSource
|
||||
if(func==listOf("divmod")) {
|
||||
val argTypes = functionCallStatement.args.map {it.inferType(program)}.toSet()
|
||||
val argTypes = functionCallExpr.args.mapTo(mutableSetOf()) { it.inferType(program) }
|
||||
if(argTypes.size!=1) {
|
||||
errors.err("expected all ubyte or all uword arguments", functionCallStatement.args[0].position)
|
||||
errors.err("expected all ubyte/uword or all byte/word arguments", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
val t1 = argTypes.single()
|
||||
@@ -289,33 +320,16 @@ class VarConstantValueTypeAdjuster(
|
||||
val replaceFunc = when {
|
||||
dt.isUnsignedByte -> "divmod__ubyte"
|
||||
dt.isUnsignedWord -> "divmod__uword"
|
||||
dt.isSignedByte -> "divmod__byte"
|
||||
dt.isSignedWord -> "divmod__word"
|
||||
else -> {
|
||||
errors.err("expected all ubyte or all uword arguments", functionCallStatement.args[0].position)
|
||||
errors.err("expected all ubyte/uword or all byte/word arguments", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
|
||||
functionCallStatement))
|
||||
}
|
||||
}
|
||||
else if(func==listOf("swap")) {
|
||||
val t1 = functionCallStatement.args[0].inferType(program)
|
||||
if(t1.isKnown) {
|
||||
val dt = t1.getOrElse { throw InternalCompilerException("invalid dt") }
|
||||
val replaceFunc = when {
|
||||
dt.isByteOrBool -> "swap__byte"
|
||||
dt.isWord || dt.isPointer -> "swap__word"
|
||||
dt.isLong -> "swap__long"
|
||||
dt.isFloat -> "swap__float"
|
||||
else -> {
|
||||
errors.err("expected numeric arguments", functionCallStatement.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.SetExpression({functionCallStatement.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallStatement.target.position),
|
||||
functionCallStatement))
|
||||
return listOf(AstSetExpression({functionCallExpr.target = it as IdentifierReference},
|
||||
IdentifierReference(listOf(replaceFunc), functionCallExpr.target.position),
|
||||
functionCallExpr))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -330,31 +344,81 @@ internal class ConstantIdentifierReplacer(
|
||||
private val errors: IErrorReporter
|
||||
) : AstWalker() {
|
||||
|
||||
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
|
||||
val constValue = addressOf.constValue(program)
|
||||
if(constValue!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(addressOf, constValue, parent))
|
||||
return listOf(AstReplaceNode(addressOf, constValue, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if(identifier.parent is AssignTarget || identifier.parent is Alias)
|
||||
if (identifier.parent is AssignTarget || identifier.parent is Alias)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
if (forloop == null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
if (forloop != null && identifier === forloop.loopVar)
|
||||
return noModifications
|
||||
|
||||
val dt = identifier.inferType(program)
|
||||
if(!dt.isKnown || !dt.isNumeric && !dt.isBool)
|
||||
if (!dt.isKnown || !(dt.isNumeric || dt.isBool || dt.isPointer))
|
||||
return noModifications
|
||||
|
||||
// Special case: CONST variable holding memory() slab - replace with the MemorySlabRef itself
|
||||
// This must be checked BEFORE constValue() since memory() returns null from constValue()
|
||||
// It also handles the pre-desugared state (memory() still as FunctionCallExpression)
|
||||
// because CodeDesugarer runs after ConstantIdentifierReplacer in processAst.
|
||||
val targetNode = identifier.definingScope.lookup(identifier.nameInSource)
|
||||
if (targetNode is VarDecl && targetNode.type == VarDeclType.CONST &&
|
||||
identifier.parent !is ArrayIndexedExpression
|
||||
) {
|
||||
val slabName = when (val value = targetNode.value) {
|
||||
is MemorySlabRef -> value.slabName
|
||||
is FunctionCallExpression -> {
|
||||
if (value.isMemoryCall) {
|
||||
val str = value.args[0] as? StringLiteral
|
||||
str?.value
|
||||
} else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (slabName != null) {
|
||||
val newVal = MemorySlabRef(slabName, identifier.position)
|
||||
return listOf(
|
||||
AstReplaceNode(
|
||||
identifier,
|
||||
newVal,
|
||||
identifier.parent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// handle const pointers
|
||||
if (dt.isPointer) {
|
||||
if (isPartOfPointerArithmetic(identifier, dt))
|
||||
return noModifications // ConstantFoldingOptimizer will take care of this further
|
||||
|
||||
if (targetNode is VarDecl && targetNode.type == VarDeclType.CONST) {
|
||||
val pointerValue = targetNode.value?.constValue(program)
|
||||
if (pointerValue != null) {
|
||||
return listOf(
|
||||
AstReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(BaseDataType.UWORD, pointerValue.number, identifier.position),
|
||||
parent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
val arrayIdx = identifier.parent as? ArrayIndexedExpression
|
||||
|
||||
if(arrayIdx!=null && cval.type.isNumeric) {
|
||||
// special case when the identifier is used as a pointer var
|
||||
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
|
||||
@@ -363,10 +427,10 @@ internal class ConstantIdentifierReplacer(
|
||||
return if(arrayIdx.parent is AssignTarget) {
|
||||
val memwrite = DirectMemoryWrite(add, identifier.position)
|
||||
val assignTarget = AssignTarget(null, null, memwrite, null, false, position = identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
listOf(AstReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
|
||||
} else {
|
||||
val memread = DirectMemoryRead(add, identifier.position)
|
||||
listOf(IAstModification.ReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||
listOf(AstReplaceNode(arrayIdx, memread, arrayIdx.parent))
|
||||
}
|
||||
}
|
||||
when {
|
||||
@@ -374,19 +438,41 @@ internal class ConstantIdentifierReplacer(
|
||||
if(parent is AddressOf)
|
||||
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(
|
||||
AstReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(cval.type, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
)
|
||||
}
|
||||
cval.type.isPointer -> {
|
||||
return listOf(
|
||||
AstReplaceNode(
|
||||
identifier,
|
||||
NumericLiteral(BaseDataType.UWORD, cval.number, identifier.position),
|
||||
identifier.parent
|
||||
)
|
||||
)
|
||||
}
|
||||
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
private fun isPartOfPointerArithmetic(identifier: IdentifierReference, dt: InferredTypes.InferredType): Boolean {
|
||||
if(dt.isPointer) {
|
||||
if (identifier.parent is ArrayIndexedExpression || identifier.parent is AddressOf)
|
||||
return true
|
||||
|
||||
val binexpr = identifier.parent as? BinaryExpression
|
||||
if (binexpr != null)
|
||||
return binexpr.operator == "+" || binexpr.operator == "-"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
@@ -396,7 +482,7 @@ internal class ConstantIdentifierReplacer(
|
||||
if(decl.isArray && decl.type==VarDeclType.MEMORY && decl.value !is IdentifierReference) {
|
||||
val memaddr = decl.value?.constValue(program)
|
||||
if(memaddr!=null && memaddr !== decl.value) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
return listOf(AstSetExpression(
|
||||
{ decl.value = it }, memaddr, decl
|
||||
))
|
||||
}
|
||||
@@ -409,7 +495,7 @@ internal class ConstantIdentifierReplacer(
|
||||
// for arrays that have no size specifier attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteral
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
return listOf(AstSetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteral.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
@@ -424,13 +510,13 @@ internal class ConstantIdentifierReplacer(
|
||||
val litval = decl.value as? NumericLiteral
|
||||
if (litval!=null && litval.type.isIntegerOrBool) {
|
||||
val newValue = NumericLiteral(BaseDataType.FLOAT, litval.number, litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
return listOf(AstReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
decl.datatype.isArray -> {
|
||||
val replacedArrayInitializer = createConstArrayInitializerValue(decl)
|
||||
if(replacedArrayInitializer!=null)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, replacedArrayInitializer, decl))
|
||||
return listOf(AstReplaceNode(decl.value!!, replacedArrayInitializer, decl))
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
@@ -441,18 +527,21 @@ internal class ConstantIdentifierReplacer(
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
|
||||
// convert a range expression that is assigned to an array, to an array literal instead.
|
||||
val range = assignment.value as? RangeExpression
|
||||
if(range!=null) {
|
||||
val targetDatatype = assignment.target.inferType(program)
|
||||
if(targetDatatype.isArray) {
|
||||
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOrUndef(),
|
||||
ZeropageWish.DONTCARE, SplitWish.NOSPLIT, null, "dummy", emptyList(),
|
||||
assignment.value, false, 0u, false, Position.DUMMY)
|
||||
val decl = VarDecl.builder(targetDatatype.getOrUndef(), assignment.value.position)
|
||||
.names("dummy")
|
||||
.origin(VarDeclOrigin.ARRAYLITERAL)
|
||||
.splitwordarray(SplitWish.NOSPLIT)
|
||||
.value(assignment.value)
|
||||
.build()
|
||||
val replaceValue = createConstArrayInitializerValue(decl)
|
||||
if(replaceValue!=null) {
|
||||
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
|
||||
return listOf(AstReplaceNode(assignment.value, replaceValue, assignment))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.*
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.floor
|
||||
@@ -14,15 +13,15 @@ import kotlin.math.log2
|
||||
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
|
||||
val mods = mutableListOf<AstModification>()
|
||||
|
||||
// try to statically convert a literal value into one of the desired type
|
||||
val literal = typecast.expression as? NumericLiteral
|
||||
if (literal != null && typecast.type.isBasic) {
|
||||
val newLiteral = literal.cast(typecast.type.base, typecast.implicit)
|
||||
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) {
|
||||
mods += IAstModification.ReplaceNode(typecast, newLiteral.valueOrZero(), parent)
|
||||
mods += AstReplaceNode(typecast, newLiteral.valueOrZero(), parent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,35 +30,35 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (subTypecast != null) {
|
||||
// remove the sub-typecast if its datatype is larger than the outer typecast
|
||||
if(subTypecast.type.largerSizeThan(typecast.type)) {
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
mods += AstReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
}
|
||||
} else {
|
||||
if (typecast.expression.inferType(program) istype typecast.type) {
|
||||
// remove duplicate cast
|
||||
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||
mods += AstReplaceNode(typecast, typecast.expression, parent)
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<AstModification> {
|
||||
val truepart = ifElse.truepart
|
||||
val elsepart = ifElse.elsepart
|
||||
if(truepart.isNotEmpty() && elsepart.isNotEmpty()) {
|
||||
if(truepart.statements.singleOrNull() is Jump) {
|
||||
return listOf(
|
||||
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
|
||||
AstInsert.after(ifElse, elsepart, parent as IStatementContainer),
|
||||
AstReplaceNode(elsepart, AnonymousScope.empty(), ifElse)
|
||||
)
|
||||
}
|
||||
if(elsepart.statements.singleOrNull() is Jump) {
|
||||
val invertedCondition = invertCondition(ifElse.condition, program)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
|
||||
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
|
||||
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
|
||||
AstReplaceNode(ifElse.condition, invertedCondition, ifElse),
|
||||
AstInsert.after(ifElse, truepart, parent as IStatementContainer),
|
||||
AstReplaceNode(elsepart, AnonymousScope.empty(), ifElse),
|
||||
AstReplaceNode(truepart, elsepart, ifElse)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -73,16 +72,16 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
|
||||
val bytevalue = NumericLiteral(BaseDataType.UBYTE, (rightNum.number.toInt() shr 8).toDouble(), booleanCondition.right.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(booleanCondition.left, msb, booleanCondition),
|
||||
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
|
||||
AstReplaceNode(booleanCondition.left, msb, booleanCondition),
|
||||
AstReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
|
||||
}
|
||||
else if ((rightNum.number.toInt() and 0xff00) == 0) {
|
||||
// if WORD & $00ff -> if lsb(WORD) & $ff
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), booleanCondition.left.position), mutableListOf(booleanCondition.left), booleanCondition.left.position)
|
||||
val bytevalue = NumericLiteral(BaseDataType.UBYTE, rightNum.number, booleanCondition.right.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(booleanCondition.left, lsb, booleanCondition),
|
||||
IAstModification.ReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
|
||||
AstReplaceNode(booleanCondition.left, lsb, booleanCondition),
|
||||
AstReplaceNode(booleanCondition.right, bytevalue, booleanCondition))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,12 +89,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<AstModification> {
|
||||
if(expr.operator==".")
|
||||
return noModifications
|
||||
val newExpr = applyAbsorptionLaws(expr)
|
||||
if(newExpr!=null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
return listOf(AstReplaceNode(expr, newExpr, parent))
|
||||
|
||||
val leftVal = expr.left.constValue(program)
|
||||
val rightVal = expr.right.constValue(program)
|
||||
@@ -107,7 +106,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
|
||||
// X + (-A) --> X - A
|
||||
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
return listOf(AstReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "-", (expr.right as PrefixExpression).expression, expr.position),
|
||||
parent
|
||||
@@ -116,7 +115,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
|
||||
// (-A) + X --> X - A
|
||||
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
return listOf(AstReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.right, "-", (expr.left as PrefixExpression).expression, expr.position),
|
||||
parent
|
||||
@@ -125,7 +124,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
|
||||
// X - (-A) --> X + A
|
||||
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
return listOf(AstReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "+", (expr.right as PrefixExpression).expression, expr.position),
|
||||
parent
|
||||
@@ -149,7 +148,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (y != null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteral(leftDt.base, 1.0, y.position), y.position)
|
||||
val replacement = BinaryExpression(x, "*", yPlus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
|
||||
return listOf(AstReplaceNode(expr, replacement, parent))
|
||||
}
|
||||
} else {
|
||||
// Y*X - X -> X*(Y - 1)
|
||||
@@ -159,7 +158,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (y != null) {
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteral(leftDt.base, 1.0, y.position), y.position)
|
||||
val replacement = BinaryExpression(x, "*", yMinus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
|
||||
return listOf(AstReplaceNode(expr, replacement, parent))
|
||||
}
|
||||
}
|
||||
} else if (rightBinExpr?.operator == "*") {
|
||||
@@ -171,7 +170,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (y != null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteral.optimalInteger(1, y.position), y.position)
|
||||
val replacement = BinaryExpression(x, "*", yPlus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
|
||||
return listOf(AstReplaceNode(expr, replacement, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,10 +182,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
|
||||
if (expr.operator == "<=" && rightExpr.operator == "-") {
|
||||
expr.operator = "<"
|
||||
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
return listOf(AstReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
} else if (expr.operator == ">=" && rightExpr.operator == "+") {
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
return listOf(AstReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,45 +193,45 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(!leftDt.isFloat && expr.operator == ">=" && rightVal?.number == 1.0) {
|
||||
// for integers: x >= 1 --> x > 0
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
return listOf(AstReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
// for signed integers: X <= -1 => X<0 , X > -1 => X>=0
|
||||
if(leftDt.isSigned && !leftDt.isFloat && rightVal?.number==-1.0) {
|
||||
if(expr.operator=="<=") {
|
||||
expr.operator = "<"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
|
||||
return listOf(AstReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
|
||||
} else if(expr.operator==">") {
|
||||
expr.operator = ">="
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
|
||||
return listOf(AstReplaceNode(expr.right, NumericLiteral(rightDt.base, 0.0, expr.right.position), expr))
|
||||
}
|
||||
}
|
||||
|
||||
if (leftDt.isUnsignedByte || leftDt.isUnsignedWord) {
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
||||
return listOf(AstReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
else if(expr.operator == ">" && rightVal?.number == 0.0) {
|
||||
// unsigned > 0 --> unsigned != 0
|
||||
return listOf(IAstModification.SetExpression({expr.operator="!="}, expr, parent))
|
||||
return listOf(AstSetExpression({expr.operator="!="}, expr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(!leftDt.isFloat && expr.operator == "<" && rightVal?.number == 1.0) {
|
||||
// for integers: x < 1 --> x <= 0
|
||||
expr.operator = "<="
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
return listOf(AstReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if (leftDt.isUnsignedByte || leftDt.isUnsignedWord) {
|
||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
||||
return listOf(AstReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
else if(expr.operator == "<=" && rightVal?.number == 0.0) {
|
||||
// unsigned <= 0 --> unsigned==0
|
||||
return listOf(IAstModification.SetExpression({expr.operator="=="}, expr, parent))
|
||||
return listOf(AstSetExpression({expr.operator="=="}, expr, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +240,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(rightDt.isBool && leftDt.isBool) {
|
||||
val rightConstBool = rightVal?.asBooleanValue
|
||||
if(rightConstBool==true) {
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
return listOf(AstReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
}
|
||||
if (rightVal?.number == 1.0) {
|
||||
@@ -249,7 +248,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
return listOf(AstReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,7 +257,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
return listOf(AstReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +266,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(rightDt.isBool && leftDt.isBool) {
|
||||
val rightConstBool = rightVal?.asBooleanValue
|
||||
if(rightConstBool==false) {
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
listOf(AstReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
}
|
||||
if (rightVal?.number == 1.0) {
|
||||
@@ -275,7 +274,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
return listOf(AstReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +283,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
val right = NumericLiteral(dt, rightVal.number, rightVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
return listOf(AstReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,7 +306,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
else -> throw FatalAstException("weird op")
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(expr, result, parent))
|
||||
return listOf(AstReplaceNode(expr, result, parent))
|
||||
}
|
||||
else if(rightVal!=null) {
|
||||
val result = if(rightVal.asBooleanValue) {
|
||||
@@ -325,7 +324,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
else -> throw FatalAstException("weird op")
|
||||
}
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(expr, result, parent))
|
||||
return listOf(AstReplaceNode(expr, result, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +335,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// msb(word<<8) --> lsb(word)
|
||||
val msb = expr.parent as? IFunctionCall
|
||||
if(msb?.target?.nameInSource == listOf("msb"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
}
|
||||
@@ -349,14 +348,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
|
||||
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, lsb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
16.0 -> {
|
||||
// msw(long<<16) --> lsw(long)
|
||||
val msw = expr.parent as? IFunctionCall
|
||||
if(msw?.target?.nameInSource == listOf("msw"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
@@ -365,14 +364,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(msb?.singleOrNull() in arrayOf("msb__long", "msb")) {
|
||||
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
24.0 -> {
|
||||
// msb(long<<24) --> lsb__long(long)
|
||||
val msb = (expr.parent as? IFunctionCall)?.target?.nameInSource
|
||||
if(msb?.singleOrNull() in arrayOf("msb__long", "msb"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("lsb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
}
|
||||
@@ -387,13 +386,13 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// word>>8 as ubyte --> msb(word)
|
||||
val lsb = expr.parent as? IFunctionCall
|
||||
if(lsb?.target?.nameInSource == listOf("lsb"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isByte==true)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
}
|
||||
@@ -405,14 +404,14 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// lsb(long>>8) --> msb(lsw(long))
|
||||
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isByte==true) {
|
||||
// long>>8 as ubyte --> msb(lsw(long))
|
||||
val lsw = FunctionCallExpression(IdentifierReference(listOf("lsw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(lsw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
16.0 -> {
|
||||
@@ -420,13 +419,13 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// long>>16 as uword --> msw(long)
|
||||
val lsw = expr.parent as? IFunctionCall
|
||||
if(lsw?.target?.nameInSource == listOf("lsw"))
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isWord==true)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent,
|
||||
return listOf(AstReplaceNode(expr.parent,
|
||||
FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position),
|
||||
expr.parent.parent))
|
||||
|
||||
@@ -436,7 +435,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// long>>16 as ubyte --> msb(long)
|
||||
val msw = FunctionCallExpression(IdentifierReference(listOf("msw"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(msw), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, lsb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, lsb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
24.0 -> {
|
||||
@@ -444,13 +443,13 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(lsb?.singleOrNull() in arrayOf("lsb__long", "lsb")) {
|
||||
// lsb(long>>24) --> msb(long)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
val castbyte = expr.parent as? TypecastExpression
|
||||
if(castbyte?.type?.isByte==true) {
|
||||
// long>>24 as ubyte --> msb(long)
|
||||
val msb = FunctionCallExpression(IdentifierReference(listOf("msb__long"), expr.position), mutableListOf(expr.left.copy()), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
return listOf(AstReplaceNode(expr.parent, msb, expr.parent.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,8 +465,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -477,8 +478,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -488,8 +491,10 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
rightVal?.number==0.0 -> constFalse
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> expr.right
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -508,15 +513,15 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(expr.operator=="==" || expr.operator=="!=") {
|
||||
val test = if (expr.operator == "==") rightVal.asBooleanValue else !rightVal.asBooleanValue
|
||||
return if (test) {
|
||||
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
listOf(AstReplaceNode(expr, expr.left, parent))
|
||||
} else {
|
||||
listOf(IAstModification.ReplaceNode(expr, invertCondition(expr.left, program), parent))
|
||||
listOf(AstReplaceNode(expr, invertCondition(expr.left, program), parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(newExpr2 != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr2, parent))
|
||||
return listOf(AstReplaceNode(expr, newExpr2, parent))
|
||||
|
||||
if (rightVal!=null && (expr.operator == "==" || expr.operator == "!=")) {
|
||||
val bitwise = expr.left as? BinaryExpression
|
||||
@@ -526,11 +531,11 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(expr.operator=="==") {
|
||||
// x & const == const -> x & const != 0 (if const has only a single bit set!)
|
||||
val notZero = BinaryExpression(bitwise, "!=", NumericLiteral(rightDt.base, 0.0, rightVal.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, notZero, parent))
|
||||
return listOf(AstReplaceNode(expr, notZero, parent))
|
||||
} else {
|
||||
// x & const != const -> x & const == 0 (if const has only a single bit set!)
|
||||
val equalsZero = BinaryExpression(bitwise, "==", NumericLiteral(rightDt.base, 0.0, rightVal.position), expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, equalsZero, parent))
|
||||
return listOf(AstReplaceNode(expr, equalsZero, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,9 +548,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val bytevalue = NumericLiteral(BaseDataType.UBYTE, (andNum shr 8).toDouble(), bitwise.right.position)
|
||||
val rightvalByte = NumericLiteral(BaseDataType.UBYTE, (rightVal.number.toInt() shr 8).toDouble(), rightVal.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(bitwise.left, msb, bitwise),
|
||||
IAstModification.ReplaceNode(bitwise.right, bytevalue, bitwise),
|
||||
IAstModification.ReplaceNode(expr.right, rightvalByte, expr)
|
||||
AstReplaceNode(bitwise.left, msb, bitwise),
|
||||
AstReplaceNode(bitwise.right, bytevalue, bitwise),
|
||||
AstReplaceNode(expr.right, rightvalByte, expr)
|
||||
)
|
||||
} else if((andNum and 0xff00) == 0) {
|
||||
// (WORD & $00xx)==y -> (lsb(WORD) & $xx)==y
|
||||
@@ -553,9 +558,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val bytevalue = NumericLiteral(BaseDataType.UBYTE, andNum.toDouble(), bitwise.right.position)
|
||||
val rightvalByte = NumericLiteral(BaseDataType.UBYTE, (rightVal.number.toInt() and 255).toDouble(), rightVal.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(bitwise.left, lsb, bitwise),
|
||||
IAstModification.ReplaceNode(bitwise.right, bytevalue, bitwise),
|
||||
IAstModification.ReplaceNode(expr.right, rightvalByte, expr)
|
||||
AstReplaceNode(bitwise.left, lsb, bitwise),
|
||||
AstReplaceNode(bitwise.right, bytevalue, bitwise),
|
||||
AstReplaceNode(expr.right, rightvalByte, expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -628,7 +633,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<AstModification> {
|
||||
if(arrayIndexedExpression.indexer.constIndex()==0) {
|
||||
if(arrayIndexedExpression.plainarrayvar!=null) {
|
||||
val binexprParent = arrayIndexedExpression.parent as? BinaryExpression
|
||||
@@ -637,7 +642,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(dt.isPointer) {
|
||||
// pointer[0] --> pointer^^
|
||||
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource, true, arrayIndexedExpression.plainarrayvar!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression,deref, parent))
|
||||
return listOf(AstReplaceNode(arrayIndexedExpression,deref, parent))
|
||||
}
|
||||
} else if(arrayIndexedExpression.pointerderef==null) {
|
||||
// possibly pointer[0].field --> pointer.field
|
||||
@@ -646,7 +651,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val field = (binexprParent.right as? IdentifierReference)?.nameInSource
|
||||
if(field!=null) {
|
||||
val deref = PtrDereference(arrayIndexedExpression.plainarrayvar!!.nameInSource + field, false, arrayIndexedExpression.plainarrayvar!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
|
||||
return listOf(AstReplaceNode(arrayIndexedExpression.parent, deref, arrayIndexedExpression.parent.parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,7 +662,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(dt.isPointer) {
|
||||
// ptr1.ptr2[0] --> ptr1.ptr2^^
|
||||
val deref = PtrDereference(ptrDeref.chain, true, ptrDeref.position)
|
||||
return listOf(IAstModification.ReplaceNode(arrayIndexedExpression, deref, parent))
|
||||
return listOf(AstReplaceNode(arrayIndexedExpression, deref, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -727,7 +732,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return null
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
|
||||
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
|
||||
if(functionCallExpr.args.isEmpty())
|
||||
return noModifications
|
||||
@@ -736,12 +741,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt issimpletype BaseDataType.UBYTE) {
|
||||
// useless lsb() of ubyte value
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg.expression, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, arg.expression, parent))
|
||||
}
|
||||
else if (valueDt issimpletype BaseDataType.BYTE) {
|
||||
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
|
||||
val cast = TypecastExpression(arg.expression, DataType.UBYTE, true, arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, cast, parent))
|
||||
}
|
||||
} else if(arg is FunctionCallExpression && arg.target.nameInSource == listOf("msw")) {
|
||||
// lsb(msw(longvar)) --> @(&longvar+2) ; get the bank byte from a long variable
|
||||
@@ -750,24 +755,24 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
|
||||
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 2.0, functionCallExpr.position), functionCallExpr.position)
|
||||
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, memread, parent))
|
||||
}
|
||||
} else {
|
||||
if(arg is IdentifierReference && arg.nameInSource.size==2
|
||||
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
|
||||
// lsb(cx16.r0) -> cx16.r0L
|
||||
val highReg = IdentifierReference(listOf("cx16", arg.nameInSource[1]+'L'), arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, highReg, parent))
|
||||
}
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt issimpletype BaseDataType.UBYTE) {
|
||||
// useless lsb() of byte value
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, arg, parent))
|
||||
}
|
||||
else if (argDt issimpletype BaseDataType.BYTE) {
|
||||
// useless lsb() of byte value, but as lsb() returns unsigned, we have to cast now.
|
||||
val cast = TypecastExpression(arg, DataType.UBYTE, true, arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, cast, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,7 +784,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt issimpletype BaseDataType.BYTE || valueDt issimpletype BaseDataType.UBYTE) {
|
||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
return listOf(AstReplaceNode(
|
||||
functionCallExpr,
|
||||
NumericLiteral(valueDt.getOr(DataType.UBYTE).base, 0.0, arg.expression.position),
|
||||
parent))
|
||||
@@ -791,19 +796,19 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val address = AddressOf(longvar, null, null, false, false, functionCallExpr.position)
|
||||
val plus2 = BinaryExpression(address, "+", NumericLiteral(BaseDataType.UWORD, 1.0, functionCallExpr.position), functionCallExpr.position)
|
||||
val memread = DirectMemoryRead(plus2, functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, memread, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, memread, parent))
|
||||
}
|
||||
} else {
|
||||
if(arg is IdentifierReference && arg.nameInSource.size==2
|
||||
&& arg.nameInSource[0]=="cx16" && arg.nameInSource[1].uppercase() in RegisterOrPair.names) {
|
||||
// msb(cx16.r0) -> cx16.r0H
|
||||
val highReg = IdentifierReference(listOf("cx16", arg.nameInSource[1]+'H'), arg.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, highReg, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, highReg, parent))
|
||||
}
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt issimpletype BaseDataType.BYTE || argDt issimpletype BaseDataType.UBYTE) {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
return listOf(AstReplaceNode(
|
||||
functionCallExpr,
|
||||
NumericLiteral(argDt.getOr(DataType.UBYTE).base, 0.0, arg.position),
|
||||
parent))
|
||||
@@ -815,7 +820,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(functionCallExpr.args[0].constValue(program)?.number==0.0) {
|
||||
// just cast the lsb to uword
|
||||
val cast = TypecastExpression(functionCallExpr.args[1], DataType.UWORD, true, functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, cast, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr, cast, parent))
|
||||
}
|
||||
}
|
||||
else if(functionCallExpr.target.nameInSource == listOf("strings", "contains")) {
|
||||
@@ -823,7 +828,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(target?.value is StringLiteral) {
|
||||
errors.info("for actual strings, use a regular containment check instead: 'char in string'", functionCallExpr.position)
|
||||
val contains = ContainmentCheck(functionCallExpr.args[1], functionCallExpr.args[0], functionCallExpr.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr as Node, contains, parent))
|
||||
return listOf(AstReplaceNode(functionCallExpr as Node, contains, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,7 +855,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
|
||||
val (expr2, _, rightVal2) = reorderCommutativeWithConstant(expr, leftVal)
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteral = rightVal2
|
||||
@@ -1014,7 +1019,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if (leftVal == null && rightVal == null)
|
||||
return null
|
||||
|
||||
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
|
||||
val (expr2, _, rightVal2) = reorderCommutativeWithConstant(expr, leftVal)
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val leftValue: Expression = expr2.left
|
||||
@@ -1177,8 +1182,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return null
|
||||
}
|
||||
|
||||
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteral?): BinExprWithConstants {
|
||||
if (expr.operator in AssociativeOperators && leftVal != null && maySwapOperandOrder(expr)) {
|
||||
private fun reorderCommutativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteral?): BinExprWithConstants {
|
||||
if (expr.operator in CommutativeOperators && leftVal != null && expr.maySwapOperandOrder()) {
|
||||
// swap left and right so that right is always the constant
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
@@ -1190,4 +1195,4 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
|
||||
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteral?, val rightVal: NumericLiteral?)
|
||||
|
||||
}
|
||||
}
|
||||
Loaded 100 of 380 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user