mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
226
Commits
v12.2-beta
...
v12.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dedc27e5b | ||
|
|
5ba494c5e5 | ||
|
|
5c16ebe377 | ||
|
|
b5dfc0cd97 | ||
|
|
d276fb9ed5 | ||
|
|
ec9567a669 | ||
|
|
70d81298c1 | ||
|
|
1de1c10b41 | ||
|
|
25f883bb85 | ||
|
|
adc6d6626a | ||
|
|
90ff904b6e | ||
|
|
ba54601eab | ||
|
|
1c71f702f4 | ||
|
|
3d3c567ea6 | ||
|
|
539c75bbf6 | ||
|
|
2683b6388a | ||
|
|
a919ea63e4 | ||
|
|
07910fc543 | ||
|
|
13e032e1a3 | ||
|
|
c3b5ee3dfd | ||
|
|
b0e0058310 | ||
|
|
9cbbf85a85 | ||
|
|
9936a855ab | ||
|
|
bb1d1e2007 | ||
|
|
0aa6c130e8 | ||
|
|
51257e47da | ||
|
|
67ddd97f3d | ||
|
|
abea7bf329 | ||
|
|
bc3f49dc13 | ||
|
|
cf2a5ae4f9 | ||
|
|
e5865a1446 | ||
|
|
5b5f553d37 | ||
|
|
b9fe1f2057 | ||
|
|
cfd1f951f0 | ||
|
|
b0e55eb25b | ||
|
|
b405ffb8bd | ||
|
|
0fa08310e7 | ||
|
|
968d9819db | ||
|
|
f85c59b12c | ||
|
|
34eedd1fe7 | ||
|
|
2c10e2b102 | ||
|
|
05bea61a69 | ||
|
|
2a8b24074b | ||
|
|
1f184a14ae | ||
|
|
8be1cd1090 | ||
|
|
c3ec400ee7 | ||
|
|
5130a74754 | ||
|
|
e61db5ed3f | ||
|
|
cc5ef21fd6 | ||
|
|
f1f925ecee | ||
|
|
ab422c0ebc | ||
|
|
05a13463f4 | ||
|
|
19238cc366 | ||
|
|
e3d4401d99 | ||
|
|
5da4efc2a3 | ||
|
|
c0c44156f1 | ||
|
|
8645833319 | ||
|
|
6a867d98eb | ||
|
|
58363ff4e4 | ||
|
|
2555aa72ca | ||
|
|
3b94fd4c4b | ||
|
|
95f500c32f | ||
|
|
870c2e8827 | ||
|
|
af2360648f | ||
|
|
bb95d40d6d | ||
|
|
55ea599fca | ||
|
|
fd5fdf3d13 | ||
|
|
ca129edce4 | ||
|
|
c1694a4b08 | ||
|
|
a348bbbebe | ||
|
|
e6b0a89d64 | ||
|
|
9a4821d1e3 | ||
|
|
71583a5b93 | ||
|
|
407ba34c72 | ||
|
|
fc4782a35b | ||
|
|
7a83b7db1b | ||
|
|
a827f4c4ce | ||
|
|
01c5794799 | ||
|
|
1c093cca2d | ||
|
|
a85b267872 | ||
|
|
5e0c1c9794 | ||
|
|
887df44359 | ||
|
|
a5fc162c71 | ||
|
|
9d3fc99d71 | ||
|
|
a871713907 | ||
|
|
2a19e5682a | ||
|
|
22d4fa73d6 | ||
|
|
2ed95c52ef | ||
|
|
73fb04aaf5 | ||
|
|
f97a7f426c | ||
|
|
c1189617f3 | ||
|
|
78d43c3cb6 | ||
|
|
86f9b6aa69 | ||
|
|
8df2bb8d2e | ||
|
|
33f30d925e | ||
|
|
c8c51245af | ||
|
|
db227679a8 | ||
|
|
02110de844 | ||
|
|
7d77a0eca6 | ||
|
|
0f3475128a | ||
|
|
e6e4714b02 | ||
|
|
d83ece397d | ||
|
|
a3c768bc6b | ||
|
|
a3d14003cb | ||
|
|
d451953e40 | ||
|
|
d505c42d15 | ||
|
|
8c92663824 | ||
|
|
10d86eb4d8 | ||
|
|
ee5e734f23 | ||
|
|
6b233b7da3 | ||
|
|
a140519374 | ||
|
|
2b118f2e42 | ||
|
|
be9d9fe377 | ||
|
|
d1345aafec | ||
|
|
d5932a7a57 | ||
|
|
5751beb72f | ||
|
|
02194a735a | ||
|
|
b5fc25b2f4 | ||
|
|
5a593b9ef3 | ||
|
|
b411914624 | ||
|
|
0f80b695e7 | ||
|
|
63c425f247 | ||
|
|
276627abcb | ||
|
|
34225d10aa | ||
|
|
69261c51b3 | ||
|
|
63d7ef7105 | ||
|
|
88a3e5b23f | ||
|
|
282c3aa00b | ||
|
|
19f3b03ac3 | ||
|
|
1b5efe1e5d | ||
|
|
2b5a15a178 | ||
|
|
a9f22d9375 | ||
|
|
f9b3514f2a | ||
|
|
be12fb507e | ||
|
|
b2dcd02ee4 | ||
|
|
5c23471254 | ||
|
|
6cb6f37871 | ||
|
|
67e284b680 | ||
|
|
1e5ba48fe0 | ||
|
|
8e6a27d7c1 | ||
|
|
d0edc0b484 | ||
|
|
ccd3d94ec0 | ||
|
|
98355d3850 | ||
|
|
0e53113b85 | ||
|
|
e9a3ed457e | ||
|
|
6001da30d8 | ||
|
|
c55f5e4b50 | ||
|
|
e983c0c2b9 | ||
|
|
532bf13057 | ||
|
|
de706e79de | ||
|
|
a19f337ebe | ||
|
|
041a904e44 | ||
|
|
131134d24b | ||
|
|
97fb5298a8 | ||
|
|
b580a177d4 | ||
|
|
a0b1ca5d9f | ||
|
|
3a291c56b4 | ||
|
|
ed41d5dbc5 | ||
|
|
628bd41740 | ||
|
|
de988f72c6 | ||
|
|
869c5ef544 | ||
|
|
17ee233e8b | ||
|
|
f2d1dffbe0 | ||
|
|
4008280a62 | ||
|
|
2e7235ec02 | ||
|
|
a4330f5048 | ||
|
|
f65aeff231 | ||
|
|
505243aff7 | ||
|
|
d0f2fc13ba | ||
|
|
53d51dcd38 | ||
|
|
c1aa0e49fe | ||
|
|
e1e72d4317 | ||
|
|
feec343cbc | ||
|
|
5cdc4233f3 | ||
|
|
1ae89d18cc | ||
|
|
eb19d00a1e | ||
|
|
e01ee10ef0 | ||
|
|
f4b3cb12e1 | ||
|
|
77ced60ad6 | ||
|
|
6fad90af6e | ||
|
|
3076318d7e | ||
|
|
9c99110738 | ||
|
|
1457d36997 | ||
|
|
43fb9296dd | ||
|
|
8bf744cc4a | ||
|
|
f1f5f070cf | ||
|
|
191a240d0d | ||
|
|
cafb2e355b | ||
|
|
c99c5751f8 | ||
|
|
435b7e56fa | ||
|
|
14f13fac53 | ||
|
|
f8b5037924 | ||
|
|
d2870cb02e | ||
|
|
8e43fc426c | ||
|
|
ba1b6e127a | ||
|
|
4f36a6958c | ||
|
|
df8f08b4ab | ||
|
|
fd5746d3fc | ||
|
|
6ab74fd51d | ||
|
|
245082ca1d | ||
|
|
845e58ea10 | ||
|
|
2a5dbc1ef7 | ||
|
|
02a1deb1e0 | ||
|
|
3ba8e5de10 | ||
|
|
819d1b4af7 | ||
|
|
cdd3d37376 | ||
|
|
d0bc01d618 | ||
|
|
ffab73334a | ||
|
|
83aeb8846c | ||
|
|
3a2087310b | ||
|
|
61b616701e | ||
|
|
bddf872906 | ||
|
|
48214a6fdb | ||
|
|
b7340856df | ||
|
|
2685854113 | ||
|
|
ad92f39469 | ||
|
|
d3fe041e01 | ||
|
|
f32bac5004 | ||
|
|
9a8e4ac105 | ||
|
|
78857e5c22 | ||
|
|
1bb828875d | ||
|
|
82aa67ffda | ||
|
|
7c44835772 | ||
|
|
8b9c63fe90 | ||
|
|
e77fcc8c16 | ||
|
|
08c12131d4 |
No files matched your search
@@ -1,23 +1,29 @@
|
||||
---
|
||||
name: asm6502-coder
|
||||
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
description: Writing or understanding 6502/65C02 assembly code using 64tass syntax, for use as stand alone .asm or inline within Prog8 programs
|
||||
---
|
||||
|
||||
# 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.
|
||||
You are an expert MOS 6502 assembly development assistant. Keep responses concise and practical — prefer short, correct code examples over lengthy prose.
|
||||
You are working with **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.
|
||||
|
||||
## Assembler: 64tass Syntax
|
||||
### Formatting Rules
|
||||
- Labels start in the first column of the line.
|
||||
- Instructions are indented at least 4 spaces.
|
||||
- Two spaces between the instruction opcode and its operand (e.g., `lda #1`, `sta $d020`).
|
||||
- End-of-line comments are preceded by two spaces before the `;` (e.g., `sta $d020 ; border color`).
|
||||
- If a comment is the only thing on a line, it starts in the first column (no indentation).
|
||||
- Opcodes and operands are written in lowercase (e.g., `lda`, not `LDA`; `$d020`, not `$D020`).
|
||||
|
||||
- **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.
|
||||
- **Symbol aliases**: inside a `.proc`, use `_name = P8ZP_SCRATCH_B1` to give a scratch variable a descriptive name.
|
||||
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line.
|
||||
- **CRITICAL DISTINCTION**: Label **definitions** are always just a single `+` or `-`. Label **references** use `+`, `++`, `+++` to refer to the 1st, 2nd, 3rd upcoming forward label, and `-`, `--`, `---` for the 1st, 2nd, 3rd preceding backward label.
|
||||
- `+` 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:
|
||||
@@ -28,27 +34,51 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
+ dex ; forward label '+'
|
||||
bne + ; branch to this same '+' (forward)
|
||||
sta $400
|
||||
+ lda #0 ; second '+'
|
||||
bne ++ ; branch to the '++' below
|
||||
+ lda #0 ; second '+' label definition (just '+')
|
||||
bne ++ ; branch to the '++' below (third forward '+')
|
||||
rts
|
||||
++ inc $d020 ; third forward label
|
||||
+ inc $d020 ; third forward label definition (just '+', referred to as '++')
|
||||
```
|
||||
- Data directives: `.byte`, `.word`, `.dword`
|
||||
- Data directives: `.byte`, `.word`, `.dword`, `.fill` (allocate space)
|
||||
- Equates: `label = value` (not `label .equ value` or `#define`)
|
||||
- **Number Literals**: Hex `$1234`, Binary `%10101010`, Decimal `123`.
|
||||
- Zero-page variables defined with `=`
|
||||
- `.text` for inline string data
|
||||
- **Conditional assembly**: `.if`, `.elsif`, `.else`, `.endif`
|
||||
- **Memory Sections**: `.section <name>`, `.send <name>` (common sections: `CODE`, `DATA`, `BSS`, `BSS_NOCLEAR`)
|
||||
|
||||
## Instructions
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr` (and `inc`, `dec` on 65C02) require an explicit operand for accumulator**: write `rol a`, `inc a`, etc., not just `rol` or `inc`.
|
||||
- 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
|
||||
|
||||
### Instruction Side Effects (Flags)
|
||||
- **`Z` (Zero)**: Set if the result of an operation is 0.
|
||||
- **`N` (Negative)**: Set if bit 7 of the result is 1.
|
||||
- **`C` (Carry)**: Used for unsigned overflow and shifts. `cmp` sets `C` if `Register >= Operand`.
|
||||
- **`V` (Overflow)**: Set if a signed arithmetic operation overflowed.
|
||||
- **Commonly affected by**: `lda`, `ldx`, `ldy`, `inx`, `dex`, `tax`, `tay`, `txa`, `tya`, `and`, `ora`, `eor`, `asl`, `lsr`, `rol`, `ror`, `adc`, `sbc`, `cmp`, `cpx`, `cpy`, `bit`.
|
||||
- **Note**: `lda`, `ldx`, `ldy` do NOT affect the Carry flag. Only `adc`, `sbc`, `cmp`, and shift/rotate instructions affect Carry.
|
||||
|
||||
## 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`
|
||||
- **CX16 target only**: can use WDC 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `inc a`, `dec a`, `bit #imm`, `bit zp,x`, `bit abs,x`, `jmp (abs,x)`, and `(zp)` indirect addressing mode (e.g., `lda (zp)`).
|
||||
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra` etc.
|
||||
- **Note**: The Rockwell/bit-manipulation instructions (`rmb`, `smb`, `bbr`, `bbs`) are **NOT** available.
|
||||
- Check the target before using 65C02-specific instructions
|
||||
- **6502 / 65C02 instruction reference table**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=4 (provides exact instruction details for all opcodes: operation, addressing modes, byte length, and cycle count).
|
||||
|
||||
## Commander X16 Memory Map
|
||||
```
|
||||
Bank Offset Content
|
||||
$0000-$9EFF Fixed/System RAM
|
||||
$9F00-$9FFF I/O Area (VIA, VERA, YM2151)
|
||||
$00-$FF:$A000-$BFFF Banked RAM (max 2 MiB, often 512 KiB) (256x8K banks)
|
||||
$00-$1F:$C000-$FFFF System ROM (512 KiB) (32x16K banks)
|
||||
```
|
||||
- Bank registers (in zeropage): `$00` selects the current RAM bank (0-255), `$01` selects the current ROM bank (0-31). For JSRFAR/banked calls from Prog8, use `extsub @bank ...` instead of manipulating these directly.
|
||||
- Full hardware specs at https://ayce.dev/emptyx16.html#emptyx16---x16-hardware-specs
|
||||
|
||||
## Calling Convention / Register Conventions
|
||||
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
|
||||
@@ -70,8 +100,9 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
- 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
|
||||
- **CRITICAL**: Parameter names in `asmsub` are **documentation only**. You MUST use the actual registers in your assembly code, NOT the parameter names (unless you create aliases yourself).
|
||||
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
|
||||
- Accessing Prog8 parameters if they were NOT mapped to registers: use `p8v_paramname`. (Mapping to registers is preferred for speed).
|
||||
|
||||
Example:
|
||||
```prog8
|
||||
@@ -126,15 +157,38 @@ All Prog8 symbols are prefixed when accessed from assembly:
|
||||
- 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
|
||||
- **CRITICAL: NEVER use hardcoded zeropage addresses** (like `$1e`, `$22`, `$7c`, etc.) to reference P8ZP_SCRATCH registers or cx16 virtual registers. Their addresses differ per compilation target. **ALWAYS** use the symbolic names (`P8ZP_SCRATCH_W1`, `cx16.r14`, `cx16.r14+1`, etc.) so the assembler resolves the correct address for the target.
|
||||
|
||||
## Common 6502 Patterns
|
||||
|
||||
### Common Branch Logic (Comparisons)
|
||||
| Logic | Unsigned | Signed |
|
||||
|-------|----------|--------|
|
||||
| `A == imm` | `cmp #imm`, `beq label` | (Same) |
|
||||
| `A != imm` | `cmp #imm`, `bne label` | (Same) |
|
||||
| `A < imm` | `cmp #imm`, `bcc label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bmi label` |
|
||||
| `A >= imm` | `cmp #imm`, `bcs label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bpl label` |
|
||||
| `A <= imm` | `beq label`, `bcc label` | (Use complex signed logic or reorder) |
|
||||
| `A > imm` | `beq +`, `bcs label`, `+` | (Use complex signed logic or reorder) |
|
||||
|
||||
### Looping (downto with BNE)
|
||||
```asm
|
||||
ldx #count
|
||||
loop ; do work here
|
||||
dex
|
||||
bne loop ; loop while X != 0
|
||||
bne loop ; loop while X != 0 (runs 'count' times)
|
||||
```
|
||||
|
||||
### Self-Modifying Code (SMC) — Avoid When Possible
|
||||
**Do NOT use self-modifying code unless absolutely necessary** — self-modifying code writes to the code segment at runtime, which means it **cannot run from ROM** and is generally fragile, hard to debug, and violates the expected behavior of modern tooling (emulators, debuggers, etc.). Prefer lookup tables, indirect jumps via a vector in RAM, or alternative algorithms that keep code in place.
|
||||
|
||||
### Self-Modifying Code (SMC) Detection
|
||||
Look for `sta`, `stx`, or `sty` pointing into code labels:
|
||||
```asm
|
||||
lda #$42
|
||||
sta _target+1 ; Modifies the immediate operand of the LDA at _target
|
||||
...
|
||||
_target lda #$00 ; This #$00 will be replaced by #$42 at runtime
|
||||
```
|
||||
|
||||
### Indirect indexed read (table of data)
|
||||
@@ -164,3 +218,74 @@ loop ; do work here
|
||||
lda p8v_myword ; loads LSB
|
||||
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
|
||||
```
|
||||
|
||||
## CPU Quirks and Pitfalls
|
||||
|
||||
### JMP ($xxFF) Page Wrap Bug (NMOS 6502)
|
||||
- **Problem**: On original 6502 CPUs, `jmp ($caff)` will fetch the LSB from `$caff` but the MSB from `$ca00` (instead of `$cb00`).
|
||||
- **Target**: Affects C64, C128, PET32.
|
||||
- **Solution**: Avoid placing indirect jump vectors on a page boundary, or use the CX16 (65C02) which fixed this bug.
|
||||
|
||||
### BRK Instruction and the "Signature Byte"
|
||||
- **Behavior**: After a `BRK` instruction, the return address on the stack is incremented by **2**. This means the CPU skips the byte immediately following the `BRK` opcode.
|
||||
- **Usage**: This skipped byte is often used as a "signature" or parameter byte for the BRK handler.
|
||||
|
||||
### BIT Instruction Flags
|
||||
- **Absolute/Zero-page**: `bit $1234` copies bit 7 of the memory value to the **N** flag and bit 6 to the **V** flag. The **Z** flag is set based on `A AND memory`.
|
||||
- **Immediate (65C02 only)**: `bit #$01` only affects the **Z** flag; it does **NOT** modify N or V.
|
||||
- **NMOS 6502**: Does **not** support `bit #imm`.
|
||||
|
||||
### The "B" (Break) Flag
|
||||
- **Quirk**: The B flag (bit 4 of the status register) doesn't actually exist in the hardware status register. It only exists on the stack after a `PHP` or `BRK` instruction (set to 1) or a hardware IRQ/NMI (set to 0).
|
||||
- **Detection**: To tell if an interrupt was caused by `BRK` or a hardware IRQ, your handler must `pla`, `and #$10`, and check the result.
|
||||
|
||||
### Decimal Mode Flag (D) Persistence
|
||||
- **Pitfall**: On NMOS 6502, the `D` flag is **not** cleared on interrupt. Always use `cld` in IRQ handlers. On 65C02, it is cleared automatically, but `cld` is still good practice.
|
||||
- **ADC/SBC**: Be extremely careful with arithmetic if you haven't explicitly set or cleared the `D` flag, as its state might be unknown.
|
||||
|
||||
## Optimization Tips
|
||||
- **`stz` (65C02 only)**: Saves cycles and bytes compared to `lda #0`, `sta ...`.
|
||||
- **`bra` (65C02 only)**: Shorter and usually faster than `jmp`.
|
||||
- **Avoid `clc` before `bcc`**: `cmp` already sets the carry flag correctly for `bcc`/`bcs`.
|
||||
- **`inx` / `dex` vs `clc`+`adc #1`**: Incrementing/decrementing is faster and doesn't affect the carry flag.
|
||||
- **Zero-page usage**: Accessing variables in zeropage is 1 cycle faster and 1 byte shorter than absolute addressing.
|
||||
- **Cycle Counting**: Most instructions take 2-4 cycles. `jsr` takes 6, `rts` takes 6. Branches take 2 (no branch), 3 (branch taken), or 4 (branch taken across page boundary).
|
||||
|
||||
## 64tass Macros
|
||||
Define macros for common tasks:
|
||||
```asm
|
||||
pushax .macro
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
.endm
|
||||
|
||||
popax .macro
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
.endm
|
||||
```
|
||||
Call them with `#pushax` and `#popax`.
|
||||
|
||||
## IRQ Handler Best Practices
|
||||
- **Clear the Decimal Flag (`cld`)**: On the original 6502, the decimal flag (`D`) is **not** automatically cleared when an interrupt occurs. If the interrupted code was in decimal mode, your handler will also run in decimal mode, causing arithmetic errors. Always call `cld` at the beginning of your handler. The 65C02 clears it automatically, but `cld` is still recommended for portability.
|
||||
|
||||
## Invoking the Assembler (64tass)
|
||||
If you need to manually invoke `64tass` to assemble a generated `.asm` file, you should be aware of the default arguments that `prog8c` supplies to ensure compatibility with the generated code:
|
||||
|
||||
- `--ascii`: **CRITICAL.** Prog8 generates character and string data in ASCII. Without this flag, `64tass` defaults to PETSCII, which will garble your strings.
|
||||
- `--case-sensitive`: Prog8 is case-sensitive and expects the assembler to be as well.
|
||||
- `--long-branch`: Enables automatic conversion of relative branches (`beq`, `bne`, etc.) to absolute jumps if the target is out of range. Prog8 relies on this.
|
||||
- `-Wno-implied-reg`: Suppresses warnings when the accumulator `a` is omitted from instructions like `rol`, `lsr`, etc. (though the skill recommends always using `rol a` for clarity).
|
||||
- `-Wall`: Enables all warnings.
|
||||
- `--cbm-prg` (or `--atari-xex` / `--nostart`): Sets the output format and adds the appropriate load address header.
|
||||
|
||||
### Optional but Recommended for Debugging:
|
||||
- `--vice-labels --labels=labels.txt`: Generates a label file that can be loaded into the VICE monitor (`load_labels "labels.txt"`) to see your Prog8 symbol names while debugging.
|
||||
- `--list=listing.txt`: Generates a full assembly listing file with addresses and opcodes.
|
||||
|
||||
### Example manual invocation:
|
||||
```bash
|
||||
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg -o myprogram.prg myprogram.asm
|
||||
```
|
||||
@@ -1,30 +1,23 @@
|
||||
---
|
||||
name: prog8-coder
|
||||
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
description: Writing or understanding Prog8 programs
|
||||
---
|
||||
|
||||
# Prog8 Coder Skill
|
||||
|
||||
You are an expert retro system development assistant, versed in the Prog8 programming language. Keep responses concise and practical — prefer short, correct code examples over lengthy prose.
|
||||
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
|
||||
- `.p8ir` files are the **Intermediate Representation** — target-independent, executable via `prog8c -vm file.p8ir`
|
||||
- **Library search**: `prog8c -libsearch <regex>` to find routines in the embedded stdlib (e.g., `prog8c -libsearch "txt\."`)
|
||||
- **Library dump**: `prog8c -libdump <dir>` to extract all embedded library source files
|
||||
- **`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 ...`
|
||||
- **CX16 programs**: add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`
|
||||
|
||||
## Datatypes & Variables
|
||||
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
|
||||
@@ -32,17 +25,31 @@ Follow ALL the rules below carefully.
|
||||
- **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`
|
||||
- To point a typed pointer at a `memory()` block, assign the uword directly: `^^MyStruct ptr = memory("name", size)`. The `^^Type:expression` syntax (below) only works with array literals, not general uword expressions.
|
||||
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable). This `^^Type:[...]` syntax does NOT work with variables or `memory()` — only literal arrays.
|
||||
- Struct definitions must be inside a block, not at file level.
|
||||
- Struct fields: simple types, `str`, and fixed-size arrays. Array fields: `ubyte[108] name`. `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
|
||||
- `@shared`, `@zp`, `@requirezp`, `@dirty`, `@nosplit` are **tags** that go on variable declarations. **Place them after the datatype (and array specifiers), before the variable name(s):**
|
||||
```
|
||||
private ubyte[8] @shared vera_storage ; single var with tag
|
||||
ubyte @zp @shared varname ; multiple tags allowed
|
||||
ubyte @requirezp var1 ; require zeropage
|
||||
ubyte[] @shared names ; array with tag
|
||||
```
|
||||
The grammar is: `[private] datatype [arraydims] [tags...] identifierlist`
|
||||
- `@shared` marks variables as "used by external code" (assembly), prevents the optimizer from removing them
|
||||
- `@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()`
|
||||
- Virtual registers (`cx16.r0`–`cx16.r15`): global 16-bit, NOT preserved across calls.
|
||||
- **WARNING: Virtual registers in ISR/IRQ handlers**: The virtual registers R0-R15 are *not preserved* across the IRQ handler call. If your handler uses them, it will corrupt the interrupted program's state. Either avoid using them in the handler, or save/restore with `cx16.save_virtual_registers()` / `cx16.restore_virtual_registers()`. This applies to all targets, not just CX16.
|
||||
- **WARNING: Long operations clobber R12-R15**: Some operations on `long` values use R12-R15 as temporary storage and will silently overwrite them. Do not rely on R12-R15 values when working with longs, and avoid using R12-R15 explicitly if your code uses long arithmetic.
|
||||
- **WARNING: VERA registers in ISR handlers (CX16)**: If your IRQ handler reads or writes VERA control registers (e.g., `cx16.VERA_DATA0`, `cx16.VERA_ADDR_L`, etc.), you must save and restore the VERA context around the handler's work using `cx16.save_vera_context()` / `cx16.restore_vera_context()`. Without this, the handler will corrupt any VERA operations (tilemap updates, sprite positioning, etc.) happening in the interrupted main program.
|
||||
- **IRQ handler best practices**: Keep handlers extremely short and fast — they run with interrupts disabled and steal cycles from the main program. Do NOT do lengthy processing, I/O, or complex subroutine calls inside the handler. Instead, set a boolean flag or semaphore that the main loop checks periodically, and do the actual work there.
|
||||
- Math performance: integer trig (`math.sin8`, `math.cos8`) uses fast LUTs; float trig (`floats.sin`/`cos`) is much slower
|
||||
|
||||
## Recursion & Stack Management
|
||||
@@ -62,6 +69,7 @@ No call stack for variable storage — recursion overwrites locals. To handle it
|
||||
- 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`
|
||||
- Assigning a uword address to a `str` field of a struct is done by direct assignment. Compute the address in a uword variable first, then assign: `cx16.r0 = &namebufs + offset; entry.name = cx16.r0`. The `^^ubyte:(expr)` cast syntax does not parse — use a temp uword instead.
|
||||
- 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
|
||||
@@ -74,12 +82,24 @@ No call stack for variable storage — recursion overwrites locals. To handle it
|
||||
- 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
|
||||
- `defer` defers statement execution until scope exit
|
||||
- **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. Multiple defers fire in **reverse registration order** (LIFO / stack order — last deferred runs first). A defer is only registered if execution reaches that statement — conditional paths that skip the `defer` line will not register it.
|
||||
- `goto`, labels, jump lists allowed
|
||||
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
|
||||
|
||||
### The when Statement
|
||||
The `when` statement is a control flow construct that enables you to execute a specific action based on the value of an expression. It is generally more readable and often more efficient than a sequence of `if-else if` statements, as the compiler can optimize it into more efficient branching structures, such as a jump table.
|
||||
- **Expression**: Evaluates an expression and compares it against case values.
|
||||
- **Cases**: Defined by a value followed by the `->` operator.
|
||||
- **Blocks**: Use `{ }` to enclose multiple statements for a case.
|
||||
- **Else Clause**: Serves as a default handler; mandatory unless the expression type is fully covered.
|
||||
- **Efficiency**: Recommended for handling sets of fixed choices as it typically results in better assembly code.
|
||||
|
||||
## Loop Constructs
|
||||
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
|
||||
|
||||
@@ -157,43 +177,53 @@ unroll 80 {
|
||||
```
|
||||
|
||||
## Subroutines & Return Values
|
||||
- Symbols public by default; use `private` to restrict to block
|
||||
- **Don't use `private` on subroutines and variables** (including nested ones) unless the user asks for it. Everything is public by default in Prog8 — follow that convention.
|
||||
- `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
|
||||
- No **function overloading** (except builtins) and no **polymorphism** in general. This means you must call specific routines for different types (e.g., `txt.print_ub(val)` vs `txt.print_w(val)` instead of a generic `print(val)`). 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 kernel (ROM) routines or low-level assembly that gets arguments via registers.
|
||||
- Parameter passing via registers: `@A` (accu), `@X`, `@Y`, `@AX`/`@AY` (16-bit), `@R0`-`@R15` (virtual regs), `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
|
||||
- Return value: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use)
|
||||
- Clobbers: `clobbers (A, X, Y)` — list all modified registers
|
||||
- Parameter names are documentation only — use registers in assembly, not parameter names
|
||||
- Create symbolic aliases at assembly start: `x1 = cx16.r0`, `x1_lo = cx16.r0L`
|
||||
- Example:
|
||||
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.
|
||||
|
||||
## 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
|
||||
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
|
||||
}}
|
||||
}
|
||||
; 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
|
||||
```
|
||||
- Symbol prefixes in assembly: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- Fully qualified: `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 only: use 65C02 instructions (STZ etc). Others: 6502 only
|
||||
- Assembly syntax: 64tass assembler. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- Instructions like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`, not just `rol`
|
||||
- Anonymous labels: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
|
||||
## Inline Assembly
|
||||
When writing inline assembly (`%asm {{ }}` blocks or `asmsub` routines), load the **asm6502-coder** skill for formatting rules, symbol references, anonymous labels, and 64tass syntax details.
|
||||
|
||||
|
||||
|
||||
## 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()`)
|
||||
- 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()`). **Note: Prog8 has no function overloading**, so you cannot use `txt.print(number)` — you must call the specific routine for the type (e.g., `txt.print_ub(val)` for an unsigned byte).
|
||||
- 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
|
||||
@@ -207,15 +237,12 @@ For kernel (ROM) routines or low-level assembly that gets arguments via register
|
||||
- **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()`)
|
||||
- **Test programs for CBM targets must only use lowercase text in string literals** (the C64/C128/PET32/CX16 targets all use PETSCII). In PETSCII, uppercase letters A-Z are the *graphics* symbols, so any uppercase text you put in a string literal is rendered as unpredictable graphics characters instead of readable letters. Use only lowercase (a-z) for any printed test output, labels, or expected-match strings on these targets; reserve uppercase for intentional PETSCII graphics. The `virtual` target (ISO) is unaffected and may use either case.
|
||||
- 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
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. 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
|
||||
@@ -10,32 +10,46 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: build and install recent 64tass
|
||||
- name: install 64tass
|
||||
run: |
|
||||
sudo apt-get install -y make build-essential
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
sudo apt update
|
||||
sudo apt install 64tass
|
||||
64tass --version
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Build and test with Gradle
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Compile
|
||||
run: ./gradlew compileKotlin compileTestKotlin --console=plain
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew test --continue --console=plain
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon
|
||||
./gradlew shadowJar --console=plain
|
||||
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: |
|
||||
compiler/build/libs/*-all.jar
|
||||
compiler/build/libs/hash.txt
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/build/test-results/test/
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@
|
||||
.idea/shelf/
|
||||
.junie/
|
||||
.junie/memory/
|
||||
.serena
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
@@ -16,6 +17,7 @@ out-old/
|
||||
*.prg
|
||||
*.bin
|
||||
*.p8ir
|
||||
*.bankedcalls
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
@@ -38,7 +40,6 @@ parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
sd*.img
|
||||
*.d64
|
||||
|
||||
Generated
+11
-1
@@ -2,6 +2,16 @@
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="prog8.codeGenExperimental" target="17" />
|
||||
<module name="prog8.codeGenExperimental.main" target="17" />
|
||||
<module name="prog8.codeGenExperimental.test" target="17" />
|
||||
<module name="prog8.codeGenM68k" target="17" />
|
||||
<module name="prog8.codeGenM68k.main" target="17" />
|
||||
<module name="prog8.codeGenM68k.test" target="17" />
|
||||
<module name="prog8.codeGenNew6502" target="17" />
|
||||
<module name="prog8.codeGenNew6502.main" target="17" />
|
||||
<module name="prog8.codeGenNew6502.test" target="17" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/codeCore" />
|
||||
<option value="$PROJECT_DIR$/codeGenCpu6502" />
|
||||
<option value="$PROJECT_DIR$/codeGenIntermediate" />
|
||||
<option value="$PROJECT_DIR$/codeOptimizers" />
|
||||
<option value="$PROJECT_DIR$/compiler" />
|
||||
<option value="$PROJECT_DIR$/compilerAst" />
|
||||
<option value="$PROJECT_DIR$/intermediate" />
|
||||
<option value="$PROJECT_DIR$/languageServer" />
|
||||
<option value="$PROJECT_DIR$/parser" />
|
||||
<option value="$PROJECT_DIR$/simpleAst" />
|
||||
<option value="$PROJECT_DIR$/virtualmachine" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myGradleHome" value="/usr/share/java/gradle" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-4
@@ -7,13 +7,14 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.3" />
|
||||
<option name="languageVersion" value="2.3" />
|
||||
<option name="apiVersion" value="2.4" />
|
||||
<option name="languageVersion" value="2.4" />
|
||||
</component>
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.3.20" />
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.10" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20" />
|
||||
<CLASSES>
|
||||
<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>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-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.20/kotlin-stdlib-jdk7-2.3.20-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-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.20/kotlin-stdlib-jdk7-2.3.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-13
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
|
||||
<CLASSES>
|
||||
<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 />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-14
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.irmen.ksim65" type="repository">
|
||||
<properties maven-id="com.github.irmen:ksim65:v2.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.0/ksim65-v2.0.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-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.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>
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.framework.datatest" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<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.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.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.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>
|
||||
</component>
|
||||
Generated
+1
@@ -20,6 +20,7 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
|
||||
Generated
-14
@@ -3,22 +3,8 @@
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<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" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -30,7 +30,7 @@
|
||||
- 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.
|
||||
- Kotlin version 2.4 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
|
||||
@@ -127,6 +127,12 @@ Steps when encountering a compiler crash:
|
||||
### 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.
|
||||
|
||||
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
|
||||
```kotlin
|
||||
val bill = $$"The value is $3.99"
|
||||
```
|
||||
|
||||
|
||||
## 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.
|
||||
@@ -153,18 +159,6 @@ When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir`
|
||||
- **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
|
||||
@@ -202,9 +196,10 @@ These instructions apply specifically to the Junie agent:
|
||||
- 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
|
||||
- **Before final verification**: 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
|
||||
- **When working on a standalone codegen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification.
|
||||
|
||||
### Build command summary
|
||||
|
||||
@@ -225,6 +220,7 @@ These instructions apply specifically to the Junie agent:
|
||||
- **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**.
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
|
||||
|
||||
### Compilation Output Files
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
@@ -238,6 +234,10 @@ These instructions apply specifically to the Junie agent:
|
||||
- `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
|
||||
|
||||
### Library Tools
|
||||
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- Never assume your changes simply work, always test!
|
||||
@@ -284,6 +284,25 @@ gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
|
||||
- **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**
|
||||
|
||||
### Compiler Unit Test Snippets
|
||||
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
|
||||
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
|
||||
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
|
||||
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
|
||||
- **Set `optimize=false`** by default for these snippets
|
||||
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
|
||||
|
||||
### Testing Assembly Output
|
||||
When testing generated assembly in Kotlin tests:
|
||||
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
|
||||
- **Instead, parse into lines and check specific properties:**
|
||||
```kotlin
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
lines.any { it == "lda #42" } shouldBe true
|
||||
lines.count { it.startsWith("jsr") } shouldBe 0
|
||||
```
|
||||
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -12,9 +12,9 @@ endif
|
||||
|
||||
all:
|
||||
$(GRADLE) installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
test:
|
||||
$(GRADLE) build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
@@ -15,10 +15,12 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
|
||||
**Want to buy me a coffee or a pizza perhaps?**
|
||||
|
||||
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
|
||||
This project was created over the last couple of years by dedicating a huge amoung of my free time to it, to make it the best I possibly can.
|
||||
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
|
||||
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
|
||||
|
||||
[](https://jb.gg/OpenSource)
|
||||
I am using JetBrains products to develop the Prog8 compiler.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -63,6 +65,7 @@ What does Prog8 provide?
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
@@ -110,8 +113,8 @@ Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
|
||||
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.3.20"
|
||||
kotlin("jvm") version "2.4.10"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -52,8 +52,8 @@ subprojects {
|
||||
// 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.0")
|
||||
testImplementation(platform("io.kotest:kotest-bom:6.2.3"))
|
||||
// implementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
// Common sourceSets configuration
|
||||
|
||||
@@ -8,5 +8,4 @@ dependencies {
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,17 +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" />
|
||||
<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>
|
||||
@@ -111,7 +111,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"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" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), 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)),
|
||||
@@ -120,7 +120,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"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" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), 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)),
|
||||
@@ -140,19 +140,19 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"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" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), 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" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), 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" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), 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)),
|
||||
@@ -173,6 +173,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"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)),
|
||||
|
||||
@@ -25,7 +25,6 @@ class CompilationOptions(val output: OutputType,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var dumpVariables: Boolean = false,
|
||||
var dumpSymbols: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var varsGolden: Boolean = false,
|
||||
var slabsHighBank: Int? = null,
|
||||
@@ -66,7 +65,6 @@ class CompilationOptions(val output: OutputType,
|
||||
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
|
||||
@@ -97,7 +95,6 @@ class CompilationOptions(val output: OutputType,
|
||||
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 }
|
||||
@@ -113,7 +110,7 @@ class CompilationOptions(val output: OutputType,
|
||||
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,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, varsHighBank, varsGolden,
|
||||
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
profilingInstrumentation, symbolDefs
|
||||
)
|
||||
|
||||
@@ -98,3 +98,24 @@ fun String.unescape(): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
// === Symbol name prefixing ===
|
||||
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
|
||||
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
|
||||
// Scoped names (containing dots) have each part prefixed according to its role.
|
||||
|
||||
fun prefixScopedName(name: String, type: Char): String {
|
||||
val generatedPrefix = "p8_label_gen_"
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(generatedPrefix))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
@@ -32,8 +32,8 @@ enum class BaseDataType {
|
||||
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.isArray -> other.isByteOrBool
|
||||
this == STR -> other.isByteOrBool
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
@@ -73,6 +73,9 @@ val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, Ba
|
||||
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.isUnsigned get() = !this.isSigned
|
||||
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
|
||||
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
|
||||
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
|
||||
|
||||
@@ -2,10 +2,48 @@ package prog8.code.core
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65C02,
|
||||
VIRTUAL
|
||||
/**
|
||||
* Identifies the target CPU type and its key characteristics that affect IR semantics.
|
||||
*
|
||||
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
|
||||
* status-bit contract (see IRInstructions.kt for the full spec):
|
||||
*
|
||||
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
|
||||
* branches that depend on the result of a previous "status-setting" instruction
|
||||
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
|
||||
* Z and N based on the full multi-byte value." Backends for these targets must
|
||||
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
|
||||
* the 16-bit value, so the IR can rely on it).
|
||||
*
|
||||
* - When `false`, multi-byte operations on this target do NOT set status bits
|
||||
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
|
||||
* low byte of a 16-bit decrement). The IR generator must therefore always emit
|
||||
* an explicit CMP/CMPI before any branch that depends on the result. Backends
|
||||
* for these targets should NOT add `ora` postambles to set Z; instead they
|
||||
* rely on the explicit CMP/CMPI emitted by the IR generator.
|
||||
*
|
||||
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
|
||||
* cascade are misleading: Z is set based on the LAST byte operated on, not
|
||||
* the full value. So we treat the contract as "no instruction sets status
|
||||
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
|
||||
* explicit compares. This avoids the trap of "this works for BYTE but
|
||||
* silently breaks for WORD/LONG".
|
||||
*/
|
||||
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
|
||||
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
|
||||
CPU6502(false),
|
||||
|
||||
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
|
||||
CPU65C02(false),
|
||||
|
||||
/**
|
||||
* Virtual machine target. We use `false` here so the IR generator always
|
||||
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
|
||||
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
|
||||
* existing documentation (VirtualMachine.kt top comment) and keeps the
|
||||
* behavior consistent across all current targets.
|
||||
*/
|
||||
VIRTUAL(false)
|
||||
}
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
@@ -14,7 +52,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
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
|
||||
val BSSHIGHRAM_START: UInt
|
||||
@@ -28,6 +65,8 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val customLauncher: List<String>
|
||||
val additionalAssemblerOptions: List<String>
|
||||
val defaultOutputType: OutputType
|
||||
val defaultLauncherType: CbmPrgLauncherType
|
||||
val supportsBankedCalls: Boolean
|
||||
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
@@ -13,9 +13,10 @@ import kotlin.io.path.Path
|
||||
|
||||
object ImportFileSystem {
|
||||
|
||||
val userHome: Path get() = Path.of(System.getProperty("user.home"))
|
||||
|
||||
fun expandTilde(path: String): String = if (path.startsWith("~")) {
|
||||
val userHome = System.getProperty("user.home")
|
||||
userHome + path.drop(1)
|
||||
userHome.toString() + path.drop(1)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
@@ -35,6 +36,11 @@ object ImportFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun retrieveSourceLine(position: Position): String {
|
||||
if(SourceCode.isLibraryResource(position.file)) {
|
||||
val key = SourceCode.withoutPrefix(position.file)
|
||||
|
||||
@@ -11,11 +11,13 @@ class C128Target: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c128"
|
||||
@@ -27,7 +29,6 @@ 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.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@ class C64Target: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c64"
|
||||
@@ -30,7 +32,6 @@ 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.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
|
||||
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
|
||||
|
||||
@@ -15,16 +15,17 @@ import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
class ConfigFileTarget(
|
||||
override val name: String,
|
||||
override val supportsBankedCalls: Boolean,
|
||||
override val defaultEncoding: Encoding,
|
||||
override val cpu: CpuType,
|
||||
override val PROGRAM_LOAD_ADDRESS: UInt,
|
||||
override val PROGRAM_MEMTOP_ADDRESS: UInt,
|
||||
override val STARTUP_CODE_RESERVED_SIZE: UInt,
|
||||
override val BSSHIGHRAM_START: UInt,
|
||||
override val BSSHIGHRAM_END: UInt,
|
||||
override val BSSGOLDENRAM_START: UInt,
|
||||
override val BSSGOLDENRAM_END: UInt,
|
||||
override val defaultOutputType: OutputType,
|
||||
override val defaultLauncherType: CbmPrgLauncherType,
|
||||
override val libraryPath: Path,
|
||||
override val customLauncher: List<String>,
|
||||
override val additionalAssemblerOptions: List<String>,
|
||||
@@ -112,19 +113,26 @@ class ConfigFileTarget(
|
||||
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
|
||||
val outputTypeString = props.getProperty("output_type", "PRG")
|
||||
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
|
||||
val launcherString = props.getProperty("launcher", "BASIC")
|
||||
val defaultLauncherType = try {
|
||||
CbmPrgLauncherType.valueOf(launcherString.uppercase())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
CbmPrgLauncherType.BASIC
|
||||
}
|
||||
|
||||
return ConfigFileTarget(
|
||||
configfile.nameWithoutExtension,
|
||||
props.getProperty("supports_banked_calls", "false").toBoolean(),
|
||||
Encoding.entries.first { it.prefix==props.getString("encoding") },
|
||||
cpuType,
|
||||
props.getInteger("load_address"),
|
||||
props.getInteger("memtop"),
|
||||
0u, // used only in a very specific error condition check in a certain scenario...
|
||||
props.getInteger("bss_highram_start"),
|
||||
props.getInteger("bss_highram_end"),
|
||||
props.getInteger("bss_goldenram_start"),
|
||||
props.getInteger("bss_goldenram_end"),
|
||||
defaultOutputType,
|
||||
defaultLauncherType,
|
||||
libraryPath,
|
||||
customLauncher,
|
||||
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
|
||||
|
||||
@@ -11,11 +11,13 @@ class Cx16Target: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "cx16"
|
||||
@@ -27,7 +29,6 @@ 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.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
|
||||
dt.isFloat -> floatsize * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isPointer -> 2 // pointer is just a uword
|
||||
dt.isStructInstance -> dt.subType!!.memsize(this)
|
||||
dt.isStructInstance -> dt.subType?.memsize(this) ?: 0
|
||||
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,13 @@ class PETTarget: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "pet32"
|
||||
@@ -26,7 +28,6 @@ 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.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ class VMTarget: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "virtual"
|
||||
@@ -29,7 +31,6 @@ 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.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
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.io.CharConversionException
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to Unicode
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
|
||||
@@ -32,7 +32,10 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0xb1u..0xbau) // TODO more?
|
||||
// Only scratch register area is safe for transient use.
|
||||
// $b5 and $ba are clobbered by PET BASIC/Kernal calls like CHROUT ($ffd2).
|
||||
// User variables requiring persistent storage must NOT be placed here.
|
||||
free.addAll(listOf(0xb1u, 0xb2u, 0xb3u, 0xb4u, 0xb6u, 0xb7u, 0xb8u, 0xb9u, 0xfbu, 0xfcu, 0xfdu, 0xfeu))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
@@ -83,4 +86,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class ErrorReporterForTests(
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
val printedErrors = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
@@ -47,7 +48,7 @@ class ErrorReporterForTests(
|
||||
!errors.any { ":${position.line}:" in it }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
// prints nothing in tests
|
||||
printedErrors.add(errormessage)
|
||||
}
|
||||
|
||||
override fun report() {
|
||||
|
||||
@@ -229,6 +229,16 @@ class TestDataType: FunSpec({
|
||||
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.BOOL).shouldBeTrue()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UWORD).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.POINTER).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.LONG).shouldBeFalse()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.BOOL).shouldBeTrue()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UWORD).shouldBeFalse()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.STR).shouldBeFalse()
|
||||
}
|
||||
|
||||
test("BaseDataType.equalsSize") {
|
||||
@@ -236,6 +246,11 @@ class TestDataType: FunSpec({
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
|
||||
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.STR.equalsSize(BaseDataType.STR).shouldBeTrue()
|
||||
BaseDataType.STR.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.STR.equalsSize(BaseDataType.BYTE).shouldBeFalse()
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.STR).shouldBeFalse()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -256,16 +256,17 @@ class TestZeropageFreeList: FunSpec({
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have minimal locations") {
|
||||
test("BASICSAFE should have minimal free 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
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After scratch removal, $fb-$fe remain usable.
|
||||
zp.free.size shouldBeLessThan 20
|
||||
zp.free.contains(0xfbu) shouldBe true
|
||||
zp.free.contains(0xfcu) shouldBe true
|
||||
zp.free.contains(0xfdu) shouldBe true
|
||||
zp.free.contains(0xfeu) shouldBe true
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
@@ -279,6 +280,9 @@ class TestZeropageFreeList: FunSpec({
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for PET
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
floatZp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE should exclude IRQ locations") {
|
||||
@@ -628,13 +632,17 @@ class TestZeropageFreeList: FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
|
||||
test("BASICSAFE - extra fb-fe free after scratch removal") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After removing scratch registers from the pool,
|
||||
// only $fb-$fe remain free.
|
||||
zp.free.size shouldBeLessThan 15
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE - excludes IRQ locations") {
|
||||
|
||||
@@ -9,5 +9,4 @@ dependencies {
|
||||
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,19 +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" />
|
||||
<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="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<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>
|
||||
@@ -15,7 +15,10 @@ import kotlin.io.path.writeLines
|
||||
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
||||
|
||||
class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSequenceNr: Int): ICodeGeneratorBackend {
|
||||
class AsmGen6502(val prefixSymbols: Boolean,
|
||||
private val lastGeneratedLabelSequenceNr: Int,
|
||||
private val preassignedCallSiteIds: Map<PtAsmSub, UByte> = emptyMap()
|
||||
): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
@@ -23,319 +26,29 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
errors: IErrorReporter,
|
||||
): IAssemblyProgram? {
|
||||
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors, lastGeneratedLabelSequenceNr)
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors, lastGeneratedLabelSequenceNr, preassignedCallSiteIds)
|
||||
return asmgen.compileToAssembly()
|
||||
}
|
||||
|
||||
private fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
|
||||
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val expressionsToFixSubtype = mutableListOf<PtExpression>()
|
||||
val variablesToFixSubtype = mutableListOf<IPtVariable>()
|
||||
|
||||
fun prefixNamedNode(node: PtNamedNode) {
|
||||
when(node) {
|
||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||
is PtBlock -> node.name = "p8b_${node.name}"
|
||||
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // only prefix user defined labels
|
||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||
is PtStructDecl -> node.name = "p8t_${node.name}"
|
||||
}
|
||||
}
|
||||
|
||||
fun prefixSymbols(node: PtNode) {
|
||||
when(node) {
|
||||
is PtAsmSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
|
||||
if(node.address?.varbank!=null) {
|
||||
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
|
||||
}
|
||||
}
|
||||
is PtSub -> prefixNamedNode(node)
|
||||
is PtFunctionCall -> {
|
||||
if(node.builtin) {
|
||||
// could be a struct instance creation
|
||||
if(node.name=="prog8_lib_structalloc") {
|
||||
val struct = node.type.subType!!
|
||||
if(struct is StStruct) {
|
||||
// update link to active symboltable node
|
||||
node.type.subType = st.lookup(struct.scopedNameString) as StStruct
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if (stNode.astNode?.definingBlock()?.options?.noSymbolPrefixing != true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
// check if the identifier is part of a pointer dereference (which means you cannot look it up in the symboltable because it's a struct field)
|
||||
val pexpr = node.parent as? PtBinaryExpression
|
||||
if(pexpr?.operator==".") {
|
||||
if(pexpr.left is PtArrayIndexer) {
|
||||
val arrayvar = (pexpr.left as PtArrayIndexer).variable!!
|
||||
if(arrayvar.type.subType!=null) {
|
||||
// don't prefix a struct field name here, we take care of that at asm generation time, which was a lot easier
|
||||
//nodesToPrefix += node.parent to node.parent.children.indexOf(node)
|
||||
return
|
||||
} else
|
||||
throw AssemblyError("can only use deref expression on struct type")
|
||||
} else {
|
||||
TODO("handle other left operand ${pexpr.left }in dereferencing of struct fields ${node.position}")
|
||||
}
|
||||
}
|
||||
// normal (non pointer deref) expression
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?:
|
||||
throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
is PtBlock -> prefixNamedNode(node)
|
||||
is PtConstant -> node.name = "p8c_${node.name}"
|
||||
is PtLabel -> prefixNamedNode(node)
|
||||
is PtMemMapped -> prefixNamedNode(node)
|
||||
is PtSubroutineParameter -> prefixNamedNode(node)
|
||||
is PtVariable -> {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
is PtStructDecl -> prefixNamedNode(node) // note: field names are not prefixed here, we take care of that at asm generation time, which was a lot easier
|
||||
else -> { }
|
||||
}
|
||||
|
||||
if(node is IPtVariable) {
|
||||
if(node.type.subType!=null)
|
||||
variablesToFixSubtype.add(node)
|
||||
} else if(node is PtExpression) {
|
||||
if(node.type.subType!=null)
|
||||
expressionsToFixSubtype.add(node)
|
||||
}
|
||||
|
||||
node.children.forEach { prefixSymbols(it) }
|
||||
}
|
||||
|
||||
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
|
||||
if(node is PtFunctionCall && !node.builtin) {
|
||||
// function calls to subroutines defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the call site
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
else if (node is PtIdentifier) {
|
||||
// identifier references to things defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the referencing point
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
if (!block.options.noSymbolPrefixing) {
|
||||
prefixSymbols(block)
|
||||
} else {
|
||||
maybePrefixFunctionCallsAndIdentifierReferences(block)
|
||||
}
|
||||
}
|
||||
|
||||
nodesToPrefix.forEach { (parent, index) ->
|
||||
when(val node = parent.children[index]) {
|
||||
is PtIdentifier -> parent.setChild(index, node.prefix(parent, st))
|
||||
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
||||
is PtVariable -> parent.setChild(index, node.prefix(parent, st))
|
||||
else -> throw AssemblyError("weird node to prefix $node")
|
||||
}
|
||||
}
|
||||
|
||||
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
|
||||
functionCallsToPrefix.reversed().forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
if(node is PtFunctionCall) {
|
||||
val prefixedName = PtIdentifier(node.name, DataType.UNDEFINED, node.position).prefix(parent, st)
|
||||
val prefixedNode = node.withNewName(prefixedName.name)
|
||||
parent.setChild(index, prefixedNode)
|
||||
} else {
|
||||
throw AssemblyError("expected PtFunctionCall")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val updatedSt = SymbolTableMaker(program, options).make()
|
||||
|
||||
fun findSubtypeReplacement(sub: ISubType): StStruct? {
|
||||
if(updatedSt.lookup(sub.scopedNameString)!=null)
|
||||
return null
|
||||
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
|
||||
|
||||
val prefixed = ArrayDeque<String>()
|
||||
var node: StNode = old
|
||||
while(node.type!= StNodeType.GLOBAL) {
|
||||
val typeChar = typePrefixChar(node.type)
|
||||
prefixed.addFirst("p8${typeChar}_${node.name}")
|
||||
node=node.parent
|
||||
}
|
||||
return updatedSt.lookup(prefixed.joinToString(".")) as StStruct
|
||||
}
|
||||
|
||||
expressionsToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
variablesToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
|
||||
return updatedSt
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun prefixScopedName(name: String, type: Char): String {
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(GENERATED_LABEL_PREFIX))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||
// the parts in between are assumed to be subroutine scopes.
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
|
||||
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
name = prefixScopedName(name, 'v')
|
||||
if(value==null)
|
||||
return this
|
||||
|
||||
val arrayValue = value as? PtArray
|
||||
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber && it !is PtBool } ) {
|
||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||
arrayValue.children.forEach { elt ->
|
||||
when(elt) {
|
||||
is PtBool -> newValue.add(elt)
|
||||
is PtNumber -> newValue.add(elt)
|
||||
is PtConstant -> newValue.add(elt)
|
||||
is PtAddressOf -> {
|
||||
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
newValue.add(elt)
|
||||
else {
|
||||
val newAddr = PtAddressOf(elt.type, false, elt.position)
|
||||
newAddr.add(elt.identifier!!.prefix(newAddr, st))
|
||||
if (elt.arrayIndexExpr != null)
|
||||
newAddr.add(elt.arrayIndexExpr!!)
|
||||
newAddr.parent = arrayValue
|
||||
newValue.add(newAddr)
|
||||
}
|
||||
}
|
||||
is PtFunctionCall if elt.builtin -> {
|
||||
if (elt.name != "prog8_lib_structalloc")
|
||||
throw AssemblyError("weird array value element $elt")
|
||||
else
|
||||
newValue.add(elt)
|
||||
}
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
else this
|
||||
}
|
||||
|
||||
//private fun PtFunctionCall.prefix(targetType: Char): PtFunctionCall {
|
||||
// val newName = prefixScopedName(name, targetType)
|
||||
// return this.withNewName(newName)
|
||||
//}
|
||||
|
||||
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
|
||||
val call = PtFunctionCall(name, builtin, hasNoSideEffects, returntypes, position)
|
||||
call.addAll(children)
|
||||
call.children.forEach { it.parent = call }
|
||||
call.parent = parent
|
||||
return call
|
||||
}
|
||||
|
||||
private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
|
||||
val targetNt: StNodeType
|
||||
val target = st.lookup(name)
|
||||
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
|
||||
if(target==null) {
|
||||
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
|
||||
val target2 = st.lookup(name.dropLast(4))
|
||||
if(target2?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
targetNt = target2!!.type
|
||||
} else {
|
||||
// if no target found, assume that the identifier is a struct field
|
||||
targetNt = StNodeType.STATICVAR
|
||||
}
|
||||
} else {
|
||||
targetNt = target.type
|
||||
}
|
||||
|
||||
val prefixType = typePrefixChar(targetNt)
|
||||
val newName = prefixScopedName(name, prefixType)
|
||||
val node = PtIdentifier(newName, type, position)
|
||||
node.parent = parent
|
||||
return node
|
||||
}
|
||||
|
||||
private fun typePrefixChar(targetNt: StNodeType): Char {
|
||||
return when(targetNt) {
|
||||
StNodeType.BLOCK -> 'b'
|
||||
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
|
||||
StNodeType.LABEL -> 'l'
|
||||
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
|
||||
StNodeType.CONSTANT -> 'c'
|
||||
StNodeType.BUILTINFUNC -> 's'
|
||||
StNodeType.MEMORYSLAB -> 'v'
|
||||
StNodeType.STRUCT -> 't'
|
||||
StNodeType.STRUCTINSTANCE -> 'i'
|
||||
else -> '?'
|
||||
return prog8.code.prefixSymbols(program, options, st)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AsmGen6502Internal (
|
||||
val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter,
|
||||
private var generatedLabelSequenceNumber: Int
|
||||
private var generatedLabelSequenceNumber: Int,
|
||||
private val preassignedCallSiteIds: Map<PtAsmSub, UByte> = emptyMap()
|
||||
) {
|
||||
|
||||
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
private val allocator = VariableAllocator(symbolTable, options, errors)
|
||||
private val allocator = VariableAllocator(symbolTable, options, errors, program)
|
||||
private val assembly = mutableListOf<String>()
|
||||
private val breakpointLabels = mutableListOf<String>()
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(this, zeropage)
|
||||
@@ -348,6 +61,7 @@ class AsmGen6502Internal (
|
||||
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, pointerGen, assignmentAsmGen, errors)
|
||||
private val ifExpressionAsmgen = IfExpressionAsmGen(this, pointerGen, assignmentAsmGen, errors)
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, assignmentAsmGen, this, pointerGen, allocator)
|
||||
internal val extsubCallSiteIds: MutableMap<PtAsmSub, UByte> = preassignedCallSiteIds.toMutableMap()
|
||||
|
||||
init {
|
||||
assignmentAsmGen.augmentableAsmGen = augmentableAsmGen
|
||||
@@ -366,7 +80,7 @@ class AsmGen6502Internal (
|
||||
|
||||
if(errors.noErrors()) {
|
||||
val output = options.outputDir.resolve("${program.name}.asm")
|
||||
val asmLines = assembly.flatMapTo(mutableListOf()) { it.split('\n') }
|
||||
val asmLines = assembly.toMutableList()
|
||||
if(options.compTarget.name==Cx16Target.NAME) {
|
||||
scanInvalid65816instructions(asmLines)
|
||||
if(!errors.noErrors()) {
|
||||
@@ -375,9 +89,7 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
if(options.optimize) {
|
||||
while(optimizeAssembly(asmLines, options.compTarget, symbolTable)>0) {
|
||||
// optimize the assembly source code
|
||||
}
|
||||
optimizeAssembly(asmLines, options.compTarget, symbolTable)
|
||||
output.writeLines(asmLines)
|
||||
} else {
|
||||
// write the unmodified code
|
||||
@@ -471,14 +183,21 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
internal fun out(str: String, splitlines: Boolean = true) {
|
||||
val fragment = (if(splitlines && " | " in str) str.replace("|", "\n") else str).trim('\r', '\n')
|
||||
if (splitlines) {
|
||||
for (line in fragment.splitToSequence('\n')) {
|
||||
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
|
||||
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
||||
assembly.add(trimmed)
|
||||
if ('\n' in str || '|' in str) {
|
||||
val fragment = if (" | " in str) str.replace("|", "\n").trim('\r', '\n') else str.trim('\r', '\n')
|
||||
val lines = fragment.split('\n')
|
||||
for (i in lines.indices) {
|
||||
val line = lines[i]
|
||||
assembly.add(if (line.startsWith(' ')) "\t${line.trim()}" else line)
|
||||
}
|
||||
} else {
|
||||
val trimmed = str.trim()
|
||||
assembly.add(if (str.startsWith(' ')) "\t$trimmed" else trimmed)
|
||||
}
|
||||
} else assembly.add(fragment)
|
||||
} else {
|
||||
assembly.add(str.trim('\r', '\n'))
|
||||
}
|
||||
}
|
||||
|
||||
fun asmSymbolName(regs: RegisterOrPair): String =
|
||||
@@ -1024,7 +743,7 @@ class AsmGen6502Internal (
|
||||
assignExpressionToRegister(value, target.register!!)
|
||||
} else {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AX)
|
||||
pointerGen.assignWordReg(PtrTarget(target), RegisterOrPair.AX)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AX)
|
||||
}
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPassByRef -> {
|
||||
@@ -1120,7 +839,43 @@ class AsmGen6502Internal (
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted long into pointer ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else throw AssemblyError("weird casted type")
|
||||
} else if(value.value.type.isFloat) {
|
||||
// first evaluate the float expression into FAC1
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.FAC1)
|
||||
// then convert float in FAC1 to long and store to target
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta ${target.asmVarname}
|
||||
lda floats.FAC_ADDR+3
|
||||
sta ${target.asmVarname}+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val startreg = target.register!!.startregname()
|
||||
out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta cx16.$startreg
|
||||
lda floats.FAC_ADDR+3
|
||||
sta cx16.$startreg+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta cx16.$startreg+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta cx16.$startreg+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign typecasted float to long array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted float to long pointer ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else throw AssemblyError("weird casted type ${value.value.type} as ${value.type} at $value")
|
||||
} else {
|
||||
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
|
||||
}
|
||||
@@ -1175,6 +930,7 @@ class AsmGen6502Internal (
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
iterations == 1 -> translate(stmt.statements)
|
||||
iterations > 65536 -> repeatLongCount(iterations.toLong(), stmt)
|
||||
iterations !in 0..65536 -> throw AssemblyError("invalid number of iterations")
|
||||
iterations <= 256 -> repeatByteCount(iterations, stmt)
|
||||
else -> repeatWordCount(iterations, stmt)
|
||||
@@ -1186,26 +942,36 @@ class AsmGen6502Internal (
|
||||
val name = asmVariableName(stmt.count as PtIdentifier)
|
||||
when {
|
||||
vardecl.type.isByte -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position)
|
||||
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
|
||||
repeatCountInY(stmt, endLabel)
|
||||
}
|
||||
vardecl.type.isWord -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position)
|
||||
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
|
||||
repeatWordCountInAY(endLabel, stmt)
|
||||
}
|
||||
vardecl.type.isLong -> {
|
||||
val reg = CombinedLongRegisters.last()
|
||||
assignVariableToRegister(name, reg, stmt.definingISub(), (stmt.count as PtIdentifier).position, vardecl.type.isSigned)
|
||||
repeatLongCountInReg(reg, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype ${vardecl.type}")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when {
|
||||
stmt.count.type.isByte -> {
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.Y)
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.Y, stmt.count.type.isSigned)
|
||||
repeatCountInY(stmt, endLabel)
|
||||
}
|
||||
stmt.count.type.isWord -> {
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.AY)
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.AY, stmt.count.type.isSigned)
|
||||
repeatWordCountInAY(endLabel, stmt)
|
||||
}
|
||||
stmt.count.type.isLong -> {
|
||||
val reg = CombinedLongRegisters.last()
|
||||
assignExpressionToRegister(stmt.count, reg, stmt.count.type.isSigned)
|
||||
repeatLongCountInReg(reg, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype ${stmt.count.type}")
|
||||
}
|
||||
}
|
||||
@@ -1213,6 +979,75 @@ class AsmGen6502Internal (
|
||||
|
||||
loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun repeatLongCount(iterations: Long, stmt: PtRepeatLoop) {
|
||||
require(iterations > 65536) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createTempVarReused(BaseDataType.LONG, true, stmt)
|
||||
|
||||
val bytes = mutableListOf<Int>()
|
||||
var currentN = iterations
|
||||
while (currentN > 0) {
|
||||
val b = (currentN % 256).toInt()
|
||||
bytes.add(b)
|
||||
currentN = if (b == 0) currentN / 256 else currentN / 256 + 1
|
||||
if (currentN == 1L) break
|
||||
}
|
||||
|
||||
for (i in bytes.indices) {
|
||||
val v = bytes[i]
|
||||
val suffix = if (i > 0) "+$i" else ""
|
||||
if (v == 0 && isTargetCpu(CpuType.CPU65C02)) {
|
||||
out(" stz $counterVar$suffix")
|
||||
} else {
|
||||
out(" lda #$v")
|
||||
out(" sta $counterVar$suffix")
|
||||
}
|
||||
}
|
||||
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
|
||||
for (i in bytes.indices) {
|
||||
val suffix = if (i > 0) "+$i" else ""
|
||||
out(" dec $counterVar$suffix")
|
||||
out(" bne $repeatLabel")
|
||||
}
|
||||
}
|
||||
|
||||
private fun repeatLongCountInReg(reg: RegisterOrPair, endLabel: String, stmt: PtRepeatLoop) {
|
||||
require(reg in CombinedLongRegisters)
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val regName = "cx16." + reg.startregname()
|
||||
val d1 = makeLabel("skip_dec1")
|
||||
val d2 = makeLabel("skip_dec2")
|
||||
val d3 = makeLabel("skip_dec3")
|
||||
out("""
|
||||
lda $regName
|
||||
ora $regName+1
|
||||
ora $regName+2
|
||||
ora $regName+3
|
||||
beq $endLabel
|
||||
$repeatLabel""")
|
||||
translate(stmt.statements)
|
||||
out("""
|
||||
lda $regName
|
||||
bne $d3
|
||||
lda $regName+1
|
||||
bne $d2
|
||||
lda $regName+2
|
||||
bne $d1
|
||||
dec $regName+3
|
||||
$d1 dec $regName+2
|
||||
$d2 dec $regName+1
|
||||
$d3 dec $regName
|
||||
lda $regName
|
||||
ora $regName+1
|
||||
ora $regName+2
|
||||
ora $regName+3
|
||||
bne $repeatLabel""")
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
|
||||
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
|
||||
|
||||
@@ -13,39 +13,42 @@ import prog8.code.core.ICompilationTarget
|
||||
// TrimmedLine has .value (original), .trimmed (trimStart), and .instruction (instructionPart).
|
||||
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable): Int {
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable) {
|
||||
val pretrimmed = lines.map { it.trimStart() }.toMutableList()
|
||||
|
||||
/** Runs an optimization pass, applies modifications if any, and recomputes line windows. */
|
||||
fun runPass(
|
||||
mods: List<Modification>,
|
||||
windowSize: Int,
|
||||
currentLines: Sequence<List<TrimmedLine>>
|
||||
): Sequence<List<TrimmedLine>> {
|
||||
if (mods.isNotEmpty()) {
|
||||
apply(mods, lines, pretrimmed)
|
||||
numberOfOptimizations++
|
||||
return getLinesBy(pretrimmed, lines, windowSize)
|
||||
while(true) {
|
||||
var modified = false
|
||||
|
||||
/** Runs a single optimization pass, applies modifications if any, and recomputes line windows. */
|
||||
fun runPass(
|
||||
mods: List<Modification>,
|
||||
windowSize: Int,
|
||||
currentLines: Sequence<List<TrimmedLine>>
|
||||
): Sequence<List<TrimmedLine>> {
|
||||
if (mods.isNotEmpty()) {
|
||||
apply(mods, lines, pretrimmed)
|
||||
modified = true
|
||||
return getLinesBy(pretrimmed, lines, windowSize)
|
||||
}
|
||||
return currentLines
|
||||
}
|
||||
return currentLines
|
||||
|
||||
var linesByFour = getLinesBy(pretrimmed, lines, 4)
|
||||
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
|
||||
|
||||
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
|
||||
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
|
||||
|
||||
if(!modified)
|
||||
break
|
||||
}
|
||||
|
||||
var linesByFour = getLinesBy(pretrimmed, lines, 4)
|
||||
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
|
||||
|
||||
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
|
||||
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
internal fun String.isBranch() = this.startsWith("b")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSubroutineParameter
|
||||
import prog8.code.core.CombinedLongRegisters
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub, argComplexity: List<Boolean>): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) combined cx16 virtual registers (longs),
|
||||
@@ -15,6 +17,8 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// 4) single CPU registers (order Y,X,A),
|
||||
// 5) floating point registers (FAC1, FAC2),
|
||||
// 6) CPU Carry status flag
|
||||
// Within each group, complex expressions are evaluated first (they may clobber registers,
|
||||
// so running them before simple expressions avoids needing to save/restore already-loaded values).
|
||||
val args = sub.parameters.withIndex()
|
||||
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
|
||||
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
@@ -23,12 +27,17 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
|
||||
|
||||
combinedLongRegs.forEach { order += it.index }
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
combinedLongRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
cx16regs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
pairedRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
singleCpuRegs
|
||||
.sortedWith(
|
||||
compareByDescending<IndexedValue<Pair<RegisterOrStatusflag, PtSubroutineParameter>>> { argComplexity[it.index] }
|
||||
.thenByDescending { it.value.first.registerOrPair!!.asCpuRegister() }
|
||||
)
|
||||
.forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
floatRegs.forEach { order += it.index }
|
||||
floatRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
rest.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
|
||||
|
||||
@@ -136,9 +136,27 @@ internal class AssemblyProgram(
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(assemblerCommand)
|
||||
if(!options.quiet)
|
||||
proc.inheritIO()
|
||||
val process = proc.start()
|
||||
.redirectErrorStream(true)
|
||||
|
||||
if (options.quiet) {
|
||||
proc.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
}
|
||||
|
||||
val process = try {
|
||||
proc.start()
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: assembler failed to start: ${e.message}")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!options.quiet) {
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.forEachLine {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = process.waitFor()
|
||||
if (result == 0) {
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
|
||||
@@ -275,8 +275,14 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.assignWordOperandsToAYAndVar(fcall.args[1], fcall.args[0], "P8ZP_SCRATCH_W1")
|
||||
// math.divmod_uw_asm: -- divide two unsigned words (16 bit each) into 16 bit results
|
||||
// input: P8ZP_SCRATCH_W1 in ZP: 16-bit number, A/Y: 16 bit divisor
|
||||
// output: cx16.r15: 16-bit remainder, A/Y: 16 bit division result
|
||||
// output: P8ZP_SCRATCH_W2: 16-bit remainder, A/Y: 16 bit division result
|
||||
asmgen.out(" jsr prog8_math.divmod_uw_asm")
|
||||
// copy remainder from P8ZP_SCRATCH_W2 to R15 (needed as second return value)
|
||||
asmgen.out("""
|
||||
ldx P8ZP_SCRATCH_W2
|
||||
stx cx16.r15L
|
||||
ldx P8ZP_SCRATCH_W2+1
|
||||
stx cx16.r15H""".trimIndent())
|
||||
return arrayOf(RegisterOrPair.AY, RegisterOrPair.R15)
|
||||
}
|
||||
|
||||
@@ -396,14 +402,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcCallFar(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
if(targetName !in arrayOf("cx16", "c64", "c128"))
|
||||
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
|
||||
|
||||
val jsrfar = when(targetName) {
|
||||
"cx16" -> "cx16.JSRFAR"
|
||||
"c64" -> "c64.x16jsrfar"
|
||||
"c128" -> "c128.x16jsrfar"
|
||||
else -> TODO("jsrfar routine")
|
||||
else -> "$targetName.x16jsrfar"
|
||||
}
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
@@ -494,83 +497,115 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
val arg2 = fcall.args[1]
|
||||
if(arg1.type.isByte) {
|
||||
if(arg2.type.isByte) {
|
||||
val op = if (arg1.type.isSigned || arg2.type.isSigned) "sec | sbc" else "cmp"
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
|
||||
asmgen.out(" $op ${asmgen.asmVariableName(arg2)}")
|
||||
}
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.number.toInt()}")
|
||||
asmgen.out(" $op #${arg2.number.toInt()}")
|
||||
}
|
||||
is PtBool -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.asInt()}")
|
||||
asmgen.out(" $op #${arg2.asInt()}")
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
if(arg2.address is PtNumber) {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
|
||||
asmgen.out(" $op ${arg2.address.asConstInteger()!!.toHex()}")
|
||||
} else {
|
||||
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" $op P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" $op P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
} else if(arg1.type.isWord) {
|
||||
if(arg2.type.isWord) {
|
||||
if(arg1.type.isSigned) {
|
||||
if(arg1.type.isSigned || arg2.type.isSigned) {
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc ${asmgen.asmVariableName(arg2)}
|
||||
sbc $var2
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
sbc ${asmgen.asmVariableName(arg2)}+1""")
|
||||
sbc $var2+1
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
is PtBool -> TODO("word compare against bool ${arg2.position}")
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val num = arg2.number.toInt()
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${arg2.number.toInt()}
|
||||
sbc #<($num)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
sbc #>${arg2.number.toInt()}""")
|
||||
sbc #>($num)
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W1+1""")
|
||||
sbc P8ZP_SCRATCH_W1+1
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
asmgen.out("""
|
||||
cpy ${asmgen.asmVariableName(arg2)}+1
|
||||
cpy $var2+1
|
||||
bne +
|
||||
cmp ${asmgen.asmVariableName(arg2)}
|
||||
+""")
|
||||
cmp $var2
|
||||
+""")
|
||||
}
|
||||
is PtBool -> TODO("word compare against bool ${arg2.position}")
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val num = arg2.number.toInt()
|
||||
asmgen.out("""
|
||||
cpy #>${arg2.number.toInt()}
|
||||
cpy #>($num)
|
||||
bne +
|
||||
cmp #<${arg2.number.toInt()}
|
||||
+""")
|
||||
cmp #<($num)
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
|
||||
@@ -578,7 +613,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
+""")
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,12 +628,25 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
sec
|
||||
lda $var1
|
||||
sbc #$${hex.substring(6, 8)}
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+1
|
||||
sbc #$${hex.substring(4, 6)}
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+2
|
||||
sbc #$${hex.substring(2, 4)}
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+3
|
||||
sbc #$${hex.take(2)}""")
|
||||
sbc #$${hex.take(2)}
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
} else if(arg1 is PtIdentifier && arg2 is PtIdentifier) {
|
||||
val var1 = asmgen.asmVariableName(arg1)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
@@ -606,26 +654,55 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
sec
|
||||
lda $var1
|
||||
sbc $var2
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+1
|
||||
sbc $var2+1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+2
|
||||
sbc $var2+2
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+3
|
||||
sbc $var2+3""")
|
||||
sbc $var2+3
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
} else {
|
||||
// cmp() doesn't return a value and as such can't be used in an expression, so no need to save the temp registers' original values
|
||||
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R14R15, true)
|
||||
if(!assignAsmGen.isRightTrivial(arg1))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignAsmGen.assignExpressionToRegister(arg1, RegisterOrPair.R12R13, true)
|
||||
if(!assignAsmGen.isRightTrivial(arg1))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda cx16.r12
|
||||
sbc cx16.r14
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r12+1
|
||||
sbc cx16.r14+1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r12+2
|
||||
sbc cx16.r14+2
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r12+3
|
||||
sbc cx16.r14+3""")
|
||||
sbc cx16.r14+3
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
@@ -683,7 +760,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror2 byte ptr indexing ${what.position}")
|
||||
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
@@ -709,7 +786,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror2 word ptr indexing ${what.position}")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
@@ -761,7 +838,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror byte ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" ror ${varname},x")
|
||||
}
|
||||
@@ -803,7 +880,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
|
||||
@@ -870,7 +947,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol2 byte ptr indexing ${what.position}")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
|
||||
@@ -896,7 +973,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
is PtArrayIndexer -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol2 word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
|
||||
@@ -944,7 +1021,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol byte ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" rol ${varname},x")
|
||||
}
|
||||
@@ -986,7 +1063,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
|
||||
@@ -1118,19 +1195,16 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(arg is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
when(val dt = arg.type.base) {
|
||||
BaseDataType.UBYTE -> asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
|
||||
BaseDataType.BYTE -> asmgen.out(" lda $varname | jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.UWORD -> asmgen.out(" lda $varname+1 | jsr prog8_lib.func_sign_ub_into_A")
|
||||
BaseDataType.WORD -> asmgen.out(" lda $varname+1 | jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.LONG -> asmgen.out(" lda $varname+3 | jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.FLOAT -> {
|
||||
translateArguments(fcall, null, scope)
|
||||
asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
BaseDataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_b_into_A")
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// result in A
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
|
||||
when (val dt = arg.type.base) {
|
||||
@@ -1846,7 +1920,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(tryOptimizedLoopCopyWithY("", "$address"))
|
||||
return emptyArray()
|
||||
|
||||
asmgen.assignVariableToRegister(address.toString(), RegisterOrPair.R14R15, null, fcall.position)
|
||||
asmgen.assignVariableToRegister(address.toString(), RegisterOrPair.R14R15, null, fcall.position, true)
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
}
|
||||
|
||||
@@ -1875,7 +1949,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(tryOptimizedLoopCopyWithY("lda #<$varname+$offset | sta P8ZP_SCRATCH_W1 | lda #>$varname+$offset | sta P8ZP_SCRATCH_W1+1", "(P8ZP_SCRATCH_W1)"))
|
||||
return emptyArray()
|
||||
|
||||
asmgen.assignVariableToRegister("$varname+$offset", RegisterOrPair.R14R15, null, fcall.position)
|
||||
asmgen.assignVariableToRegister("$varname+$offset", RegisterOrPair.R14R15, null, fcall.position, true)
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
} else if(result.second is PtIdentifier) {
|
||||
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
|
||||
@@ -1996,7 +2070,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R12R13, true) // left
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R14R15, true) // right
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out("""
|
||||
lda cx16.r12
|
||||
cmp cx16.r14
|
||||
@@ -2095,7 +2173,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R12R13, true) // left
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R14R15, true) // right
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out("""
|
||||
lda cx16.r12
|
||||
cmp cx16.r14
|
||||
|
||||
@@ -42,12 +42,129 @@ internal class ForLoopsAsmGen(
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
|
||||
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1 || stepsize == -1)
|
||||
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
else
|
||||
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
|
||||
// Use temporary variable to store the limit
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1) {
|
||||
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
|
||||
asmgen.out("""
|
||||
inc $toValueVar
|
||||
bne +
|
||||
inc $toValueVar+1
|
||||
bne +
|
||||
inc $toValueVar+2
|
||||
bne +
|
||||
inc $toValueVar+3
|
||||
+
|
||||
""")
|
||||
} else {
|
||||
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
|
||||
val label1 = asmgen.makeLabel("dec_1")
|
||||
val label2 = asmgen.makeLabel("dec_2")
|
||||
val label3 = asmgen.makeLabel("dec_3")
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
bne $label1
|
||||
lda $toValueVar+1
|
||||
bne $label2
|
||||
lda $toValueVar+2
|
||||
bne $label3
|
||||
dec $toValueVar+3
|
||||
$label3: dec $toValueVar+2
|
||||
$label2: dec $toValueVar+1
|
||||
$label1: dec $toValueVar
|
||||
""")
|
||||
}
|
||||
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
|
||||
if (stepsize == 1) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
bne +
|
||||
inc $varname+1
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
// TODO: implement 32-bit loop
|
||||
asmgen.romableError("for loops over long ranges with step != 1 not yet fully implemented", forloop.position)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
|
||||
@@ -9,6 +9,16 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
|
||||
|
||||
/*
|
||||
* CRITICAL: `inline asmsub` subroutines MUST be inlined at the call site.
|
||||
* They are NOT called with jsr - their assembly body is copied directly into the caller.
|
||||
* This means: no .proc/.pend, no rts, no jsr. The return value is left in the appropriate
|
||||
* CPU register (A/X/Y) and the caller picks it up.
|
||||
*
|
||||
* This is handled at line 68: `if (sub.inline) { ... }`.
|
||||
* Any new codegen backend MUST replicate this behavior.
|
||||
*/
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||
@@ -33,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)!!
|
||||
val symbol = asmgen.symbolTable.lookup(call.name) ?: asmgen.symbolTable.lookupUnscoped(call.name) ?: throw IllegalStateException("Symbol not found: ${call.name}")
|
||||
if(symbol.type == StNodeType.LABEL) {
|
||||
require(call.void)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
|
||||
@@ -44,6 +54,26 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
val varBank = sub.address?.varbank
|
||||
var varbank: String? = null
|
||||
var bankFixLabel: String? = null
|
||||
if (varBank != null) {
|
||||
// the bank variable can also be the name of a subroutine that should be called to return the bank byte dynamically
|
||||
val bankSymbol = asmgen.symbolTable.lookup(varBank.name) ?: asmgen.symbolTable.lookupUnscoped(varBank.name)
|
||||
if (bankSymbol?.type == StNodeType.SUBROUTINE || bankSymbol?.type == StNodeType.EXTSUB) {
|
||||
val extsubId = asmgen.extsubCallSiteIds[sub]
|
||||
if (extsubId != null) {
|
||||
asmgen.out(" lda #${extsubId.toInt()}")
|
||||
}
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(varBank.name)}")
|
||||
// store bank byte directly into the JSRFAR instruction data (forward label reference)
|
||||
bankFixLabel = asmgen.makeLabel("bank")
|
||||
asmgen.out(" sta $bankFixLabel")
|
||||
} else {
|
||||
varbank = asmgen.asmVariableName(varBank)
|
||||
}
|
||||
}
|
||||
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline) {
|
||||
// inline the subroutine. (regardless of optimization settings!)
|
||||
@@ -56,54 +86,57 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
} else {
|
||||
val bank = sub.address?.constbank?.toString()
|
||||
if(bank==null) {
|
||||
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
|
||||
if(varbank!=null) {
|
||||
if(varbank!=null || bankFixLabel!=null) {
|
||||
if(asmgen.options.romable)
|
||||
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
|
||||
|
||||
// variable bank case: save/restore registers around the self-modifying sta
|
||||
if(varbank!=null) {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp""")
|
||||
}
|
||||
val label = bankFixLabel ?: "+"
|
||||
|
||||
// self-modifying code: set jsrfar bank argument
|
||||
when(asmgen.options.compTarget.name) {
|
||||
"cx16" -> {
|
||||
// JSRFAR can jump to a banked RAM address as well!
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr cx16.JSRFAR
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c64.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c128.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
@@ -133,7 +166,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +277,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||
val argComplexity = call.args.map { usesOtherRegistersWhileEvaluating(it) }
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub, argComplexity)
|
||||
optimalEvalOrder.forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
@@ -343,7 +387,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
|
||||
}
|
||||
val src = if(value.type.isPassByRef) {
|
||||
if(value is PtIdentifier) {
|
||||
if(value is PtIdentifier || value is PtPointerDeref) {
|
||||
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
|
||||
addr.add(value)
|
||||
addr.parent = scope as PtNode
|
||||
@@ -382,6 +426,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(argType.isUnsignedWord && paramType.isString)
|
||||
return true
|
||||
|
||||
if (argType.isArray && (paramType.isString || paramType.isPointerToByte))
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1580,7 +1580,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
lda $opL+3
|
||||
sbc $varR+3""")
|
||||
} else {
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $opL
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
expr.type.isWord || expr.type.isString -> {
|
||||
expr.type.isWord || expr.type.isString || expr.type.isPointer -> {
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(trueLabel)
|
||||
@@ -656,7 +656,13 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
lda $opL+3
|
||||
sbc $varR+3""")
|
||||
} else {
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $opL
|
||||
|
||||
@@ -372,13 +372,14 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun asmTypeString(dt: DataType): String {
|
||||
val effectiveDt = if(dt.isArray) dt.elementType() else dt
|
||||
return when {
|
||||
dt.isBool || dt.isUnsignedByte -> ".byte"
|
||||
dt.isSignedByte -> ".char"
|
||||
dt.isUnsignedWord || dt.isPointer -> ".word"
|
||||
dt.isSignedWord -> ".sint"
|
||||
dt.isLong -> ".dint"
|
||||
dt.isFloat -> ".byte"
|
||||
effectiveDt.isBool || effectiveDt.isUnsignedByte -> ".byte"
|
||||
effectiveDt.isSignedByte -> ".char"
|
||||
effectiveDt.isUnsignedWord || effectiveDt.isPointer -> ".word"
|
||||
effectiveDt.isSignedWord -> ".sint"
|
||||
effectiveDt.isLong -> ".dint"
|
||||
effectiveDt.isFloat -> ".byte"
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -387,42 +388,56 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private fun structInstances2asm() {
|
||||
|
||||
fun initValues(instance: StStructInstance): List<String> {
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
|
||||
if(field.first.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
"["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
} else {
|
||||
when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(field.first.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(field.first.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
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")
|
||||
}
|
||||
fun formatValue(value: StArrayElement, dt: DataType): String {
|
||||
if(dt.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
return "["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
}
|
||||
return when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(dt.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(dt.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
fun flatInitValues(instance: StStructInstance, structtype: StStruct): List<String> {
|
||||
var valueIndex = 0
|
||||
return structtype.fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
(0 until count).map {
|
||||
val value = instance.initialValues[valueIndex++]
|
||||
formatValue(value, elemDt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asmgen.out("; struct types")
|
||||
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
|
||||
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
|
||||
val paramFields = structtype.fields.filter { !it.isArray }
|
||||
val structargs = paramFields.indices.joinToString(",") { "f$it" }
|
||||
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
|
||||
structtype.fields.withIndex().forEach { (index, field) ->
|
||||
val dt = field.first
|
||||
val varname = "f${index}"
|
||||
val type = asmTypeString(dt)
|
||||
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
|
||||
var paramIdx = 0
|
||||
for(field in structtype.fields) {
|
||||
val type = asmTypeString(field.type)
|
||||
if(field.isArray) {
|
||||
val anonymous = List(field.arraySize!!) { "?" }.joinToString(",")
|
||||
asmgen.out("p8v_${field.name} $type $anonymous")
|
||||
} else {
|
||||
asmgen.out("p8v_${field.name} $type \\f$paramIdx")
|
||||
paramIdx++
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endstruct\n")
|
||||
}
|
||||
@@ -433,14 +448,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
|
||||
instancesNoInit.forEach {
|
||||
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
|
||||
val zerovalues = structtype.fields.map { field ->
|
||||
if(field.first.isFloat) {
|
||||
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
|
||||
"[${floatbytes.joinToString(",")}]"
|
||||
}
|
||||
else "?"
|
||||
}
|
||||
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
|
||||
asmgen.out("${it.name} .fill ${structtype.size}\n")
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send BSS\n")
|
||||
@@ -448,9 +456,28 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; struct instances with initialization values\n")
|
||||
asmgen.out(" .section STRUCTINSTANCES\n")
|
||||
asmgen.out("$StStructInstanceBlockName .block\n")
|
||||
instances.forEach {
|
||||
val instancename = it.name.substringAfter('.')
|
||||
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
|
||||
instances.forEach { instance ->
|
||||
val instancename = instance.name.substringAfter('.')
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
val values = flatInitValues(instance, structtype)
|
||||
val hasArrays = structtype.fields.any { fld -> fld.isArray }
|
||||
if(hasArrays) {
|
||||
asmgen.out(instancename)
|
||||
var valueIndex = 0
|
||||
for(field in structtype.fields) {
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
val type = asmTypeString(elemDt)
|
||||
val fieldValues = (0 until count).joinToString(",") {
|
||||
formatValue(instance.initialValues[valueIndex++], elemDt)
|
||||
}
|
||||
asmgen.out(" $type $fieldValues")
|
||||
}
|
||||
asmgen.out("\n")
|
||||
} else {
|
||||
asmgen.out("$instancename .dstruct ${instance.structName}, ${values.joinToString(",")}\n")
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send STRUCTINSTANCES\n")
|
||||
@@ -617,18 +644,26 @@ internal class ProgramAndVarsGen(
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val size = it.alloc.size
|
||||
val name = asmgen.asmVariableName(it.name)
|
||||
asmgen.out("""
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
lda #<$size
|
||||
ldy #>$size
|
||||
jsr sys.memcopy""")
|
||||
if(it.alloc.dt.isSplitWordArray) {
|
||||
val halfSize = size / 2
|
||||
if(halfSize>0) {
|
||||
asmgen.out("""
|
||||
ldx #${halfSize-1}
|
||||
- lda ${name}_init_value_lsb,x
|
||||
sta ${name}_lsb,x
|
||||
lda ${name}_init_value_msb,x
|
||||
sta ${name}_msb,x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldx #${size-1}
|
||||
- lda ${name}_init_value,x
|
||||
sta ${name},x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
}
|
||||
asmgen.out(" jmp +")
|
||||
}
|
||||
@@ -874,14 +909,14 @@ internal class ProgramAndVarsGen(
|
||||
dt.isSplitWordArray -> {
|
||||
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
} else {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
}
|
||||
}
|
||||
dt.isUnsignedWordArray -> {
|
||||
|
||||
@@ -3,17 +3,21 @@ package prog8.codegen.cpu6502
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onOk
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
private val errors: IErrorReporter,
|
||||
private val program: PtProgram
|
||||
) {
|
||||
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||
private val allStaticVariables by lazy { collectAllVariables(symboltable) }
|
||||
private val usageScores by lazy { computeUsageScores(allStaticVariables) }
|
||||
|
||||
init {
|
||||
allocateZeropageVariables()
|
||||
@@ -46,7 +50,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
val allVariables = collectAllVariables(symboltable)
|
||||
val allVariables = allStaticVariables
|
||||
|
||||
val numberOfAllocatableVariables = allVariables.size
|
||||
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
@@ -90,9 +94,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
}
|
||||
|
||||
// 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? ...? Simply sorting by size (bytes prioritized) has BAD results
|
||||
// Prioritize by usage frequency: sort by computed hotness score (access frequency × type weight × loop depth)
|
||||
if(errors.noErrors()) {
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { usageScores[it.scopedNameString] ?: 0 }
|
||||
for (variable in sortedList) {
|
||||
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
@@ -129,4 +133,33 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
collect(st)
|
||||
return vars.sortedBy { it.dt.base }
|
||||
}
|
||||
|
||||
private fun computeUsageScores(vars: Collection<StStaticVariable>): Map<String, Int> {
|
||||
val scores = mutableMapOf<String, Int>()
|
||||
val varInfo = vars.associateBy { it.scopedNameString }
|
||||
var loopDepth = 0
|
||||
|
||||
fun walk(node: PtNode) {
|
||||
val isLoopEnter = node is PtForLoop || node is PtRepeatLoop
|
||||
if(isLoopEnter) loopDepth++
|
||||
|
||||
if(node is PtIdentifier) {
|
||||
val v = varInfo[node.name]
|
||||
if(v != null) {
|
||||
val weight = when {
|
||||
v.dt.isPointer || v.dt.isWord -> 2
|
||||
else -> 1
|
||||
}
|
||||
scores[node.name] = (scores[node.name] ?: 0) + weight * (loopDepth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
node.children.forEach { walk(it) }
|
||||
|
||||
if(isLoopEnter) loopDepth--
|
||||
}
|
||||
|
||||
walk(program)
|
||||
return scores
|
||||
}
|
||||
}
|
||||
@@ -125,10 +125,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
|
||||
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
// C64/PET32 ROM T-variant entries skip CONUPK and need arisgn+Z flag set up beforehand.
|
||||
// CX16 ROM entries are already fixed and handle this internally.
|
||||
val tSuffix = if(asmgen.options.compTarget.name in listOf(C64Target.NAME, PETTarget.NAME)) "_NZ" else ""
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FADDT")
|
||||
asmgen.out(" jsr floats.FADDT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
@@ -140,13 +143,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
"*" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FMULTT")
|
||||
asmgen.out(" jsr floats.FMULTT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"/" -> {
|
||||
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||
asmgen.out(" jsr floats.FDIVT")
|
||||
asmgen.out(" jsr floats.FDIVT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -224,7 +227,7 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
targetDt.isByte -> assignVariableByte(assign.target, variable)
|
||||
targetDt.isSignedWord -> assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
targetDt.isUnsignedWord -> {
|
||||
targetDt.isUnsignedWord || targetDt.isPointer -> {
|
||||
if(assign.source.datatype.isPassByRef)
|
||||
assignAddressOf(assign.target, variable, false, assign.source.datatype, assign.source.array ?: PtNumber(BaseDataType.UBYTE, 0.0, assign.position))
|
||||
else
|
||||
@@ -233,7 +236,6 @@ internal class AssignmentAsmGen(
|
||||
targetDt.isLong -> assignVariableLong(assign.target, variable, assign.source.datatype)
|
||||
targetDt.isFloat -> assignVariableFloat(assign.target, variable)
|
||||
targetDt.isString -> assignVariableString(assign.target, variable)
|
||||
targetDt.isPointer -> assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype} ${assign.position}")
|
||||
}
|
||||
}
|
||||
@@ -242,13 +244,217 @@ internal class AssignmentAsmGen(
|
||||
val elementDt = assign.source.datatype
|
||||
val valueVar = value.variable
|
||||
if(valueVar==null) {
|
||||
TODO("translate assignment of indexed ${elementDt} pointer ${value.position}")
|
||||
val pointerDeref = value.pointerderef!!
|
||||
val constIndex = value.index.asConstInteger()
|
||||
val eltSize = program.memsizer.memorySize(elementDt, null)
|
||||
|
||||
if(constIndex != null) {
|
||||
val (zpPtrVar, structOffset) = pointergen.deref(pointerDeref, false)
|
||||
val totalOffset = eltSize * constIndex + structOffset.toInt()
|
||||
|
||||
when {
|
||||
elementDt.isByteOrBool -> {
|
||||
if(totalOffset > 255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out(" lda $zpPtrVar+1 | clc | adc #>($totalOffset) | sta $zpPtrVar+1 | ldy #<($totalOffset) | lda ($zpPtrVar),y")
|
||||
} else {
|
||||
asmgen.out(" lda $zpPtrVar | sta P8ZP_SCRATCH_PTR | lda $zpPtrVar+1 | clc | adc #>($totalOffset) | sta P8ZP_SCRATCH_PTR+1 | ldy #<($totalOffset) | lda (P8ZP_SCRATCH_PTR),y")
|
||||
}
|
||||
} else {
|
||||
asmgen.loadIndirectByte(zpPtrVar, totalOffset.toUByte())
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt.isSigned, false)
|
||||
}
|
||||
elementDt.isWord -> {
|
||||
if(totalOffset > 255) {
|
||||
val yOffset = totalOffset and 0xff
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta $zpPtrVar+1
|
||||
ldy #$yOffset
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #$yOffset
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
tax
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
} else {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, totalOffset.toUByte())
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
elementDt.isLong -> {
|
||||
if(totalOffset > 255) {
|
||||
val yOffset = totalOffset and 0xff
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta $zpPtrVar+1
|
||||
ldy #$yOffset
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+3""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #$yOffset
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1+2
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1+3""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$totalOffset
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+3""")
|
||||
}
|
||||
assignVariableLong(assign.target, "P8ZP_SCRATCH_W1", DataType.LONG)
|
||||
}
|
||||
elementDt.isFloat -> {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<($totalOffset)
|
||||
pha
|
||||
lda $zpPtrVar+1
|
||||
adc #>($totalOffset)
|
||||
tay
|
||||
pla""")
|
||||
assignFloatFromAY(assign.target)
|
||||
}
|
||||
else ->
|
||||
throw AssemblyError("weird array element type for pointer indexed read $elementDt")
|
||||
}
|
||||
} else {
|
||||
val (zpPtrVar, _) = pointergen.deref(pointerDeref, addOffsetToPointer = true)
|
||||
|
||||
when {
|
||||
elementDt.isByteOrBool -> {
|
||||
val idx = value.index
|
||||
if(idx is PtIdentifier) {
|
||||
if(eltSize > 1) {
|
||||
TODO("non-byte element size $eltSize for pointer array index read with variable index ${value.position}")
|
||||
}
|
||||
val indexVarName = asmgen.asmVariableName(idx)
|
||||
asmgen.out(" ldy $indexVarName | lda ($zpPtrVar),y")
|
||||
} else if(idx.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, idx)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda ($zpPtrVar),y")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, idx)
|
||||
asmgen.out(" pla | clc | adc $zpPtrVar+1 | sta $zpPtrVar+1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out(" lda ($zpPtrVar),y")
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt.isSigned, true)
|
||||
}
|
||||
elementDt.isWord -> {
|
||||
val idx = value.index
|
||||
if(idx is PtIdentifier) {
|
||||
if(eltSize > 1) {
|
||||
TODO("non-byte element size $eltSize for pointer array index read with variable index ${value.position}")
|
||||
}
|
||||
val indexVarName = asmgen.asmVariableName(idx)
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
} else if(idx.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, idx)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, idx)
|
||||
asmgen.out(" pla | clc | adc $zpPtrVar+1 | sta $zpPtrVar+1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
elementDt.isLong -> {
|
||||
TODO("variable index pointer array read of long ${value.position}")
|
||||
}
|
||||
elementDt.isFloat -> {
|
||||
TODO("variable index pointer array read of float ${value.position}")
|
||||
}
|
||||
else ->
|
||||
throw AssemblyError("weird array element type for pointer indexed read $elementDt")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
val arrayVarName = asmgen.asmVariableName(valueVar)
|
||||
|
||||
if(valueVar.type.isPointer) {
|
||||
pointergen.assignIndexedPointer(assign.target, arrayVarName, value, valueVar.type)
|
||||
pointergen.assignIndexedPointer(assign.target, arrayVarName, value)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -490,13 +696,7 @@ internal class AssignmentAsmGen(
|
||||
val identifier = value.identifier!!
|
||||
val source = asmgen.symbolTable.lookup(identifier.name)
|
||||
require(source !is StConstant) { "addressOf of a constant should have been rewritten to a simple addition expression" }
|
||||
val sourceName =
|
||||
if (value.isMsbForSplitArray)
|
||||
asmgen.asmSymbolName(identifier) + "_msb"
|
||||
else if (identifier.type.isSplitWordArray)
|
||||
asmgen.asmSymbolName(identifier) + "_lsb" // the _lsb split array comes first in memory
|
||||
else
|
||||
asmgen.asmSymbolName(identifier)
|
||||
val sourceName = asmgen.asmSymbolName(identifier)
|
||||
assignAddressOf(assign.target, sourceName, value.isMsbForSplitArray, identifier.type, value.arrayIndexExpr)
|
||||
} else {
|
||||
val ptrderef = value.dereference!!
|
||||
@@ -867,12 +1067,8 @@ internal class AssignmentAsmGen(
|
||||
|
||||
return if(translatedOk)
|
||||
true
|
||||
else {
|
||||
if(expr.type.isLong && expr.operator in "/%")
|
||||
TODO("long division - use floats for now ${expr.position} ")
|
||||
|
||||
else
|
||||
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizedComparison(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
@@ -1161,6 +1357,11 @@ internal class AssignmentAsmGen(
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1198,6 +1399,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
|
||||
}
|
||||
}
|
||||
@@ -1242,7 +1454,11 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
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
|
||||
@@ -2179,13 +2395,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
|
||||
}
|
||||
}
|
||||
@@ -2363,8 +2583,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) {
|
||||
@@ -2774,6 +2998,21 @@ $endLabel""")
|
||||
asmgen.out(" jsr prog8_lib.containment_linearwordarray")
|
||||
}
|
||||
}
|
||||
dt.isLongArray -> {
|
||||
asmgen.assignExpressionToRegister(containment.needle, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r14+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r15
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda cx16.r15+1
|
||||
sta P8ZP_SCRATCH_W2+1""")
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_PTR"), symbolName, false, null, null)
|
||||
asmgen.out(" ldy #$numElements")
|
||||
asmgen.out(" jsr prog8_lib.containment_longarray")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
@@ -3209,7 +3448,19 @@ $endLabel""")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> TODO("cast float to long")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta $targetAsmVarName
|
||||
lda floats.FAC_ADDR+3
|
||||
sta $targetAsmVarName+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta $targetAsmVarName+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta $targetAsmVarName+3
|
||||
""".trimIndent())
|
||||
}
|
||||
else -> throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
}
|
||||
@@ -3634,17 +3885,26 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
|
||||
var actualSourceName = sourceName
|
||||
var actualMsb = msb
|
||||
if (arrayDt?.isSplitWordArray == true) {
|
||||
if (!sourceName.endsWith("_lsb") && !sourceName.endsWith("_msb")) {
|
||||
actualSourceName = if (msb) sourceName + "_msb" else sourceName + "_lsb"
|
||||
}
|
||||
actualMsb = false
|
||||
}
|
||||
|
||||
if(arrayIndexExpr!=null) {
|
||||
if(arrayDt?.isPointer==true) {
|
||||
require(!msb)
|
||||
return pointergen.assignAddressOfIndexedPointer(target, sourceName, arrayDt, arrayIndexExpr)
|
||||
require(!actualMsb)
|
||||
return pointergen.assignAddressOfIndexedPointer(target, actualSourceName, arrayDt, arrayIndexExpr)
|
||||
}
|
||||
val constIndex = arrayIndexExpr.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
if (arrayDt!!.isUnsignedWord) {
|
||||
// using a UWORD pointer with array indexing, always bytes
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
require(!actualMsb)
|
||||
assignVariableToRegister(actualSourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
if(constIndex in 1..255)
|
||||
asmgen.out("""
|
||||
clc
|
||||
@@ -3666,9 +3926,9 @@ $endLabel""")
|
||||
else {
|
||||
if(constIndex>0) {
|
||||
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
|
||||
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
|
||||
asmgen.out(" lda #<($actualSourceName + $offset) | ldy #>($actualSourceName + $offset)")
|
||||
} else {
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
asmgen.out(" lda #<$actualSourceName | ldy #>$actualSourceName")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
@@ -3676,8 +3936,8 @@ $endLabel""")
|
||||
} else {
|
||||
if (arrayDt!!.isUnsignedWord) {
|
||||
// using a UWORD pointer with array indexing, always bytes
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
require(!actualMsb)
|
||||
assignVariableToRegister(actualSourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(arrayIndexExpr.type.isWord) {
|
||||
@@ -3730,9 +3990,9 @@ $endLabel""")
|
||||
)
|
||||
} else throw AssemblyError("weird type $subtype")
|
||||
asmgen.out("""
|
||||
ldy #>$sourceName
|
||||
ldy #>$actualSourceName
|
||||
clc
|
||||
adc #<$sourceName
|
||||
adc #<$actualSourceName
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
@@ -3743,12 +4003,12 @@ $endLabel""")
|
||||
}
|
||||
|
||||
// address of a normal variable
|
||||
require(!msb)
|
||||
require(!actualMsb)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
lda #<$actualSourceName
|
||||
ldy #>$actualSourceName
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1""")
|
||||
}
|
||||
@@ -3756,25 +4016,25 @@ $endLabel""")
|
||||
throw AssemblyError("can't store word into memory byte")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
asmgen.out(" lda #<$actualSourceName | ldy #>$actualSourceName")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" ldx #>$sourceName | lda #<$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
|
||||
RegisterOrPair.AX -> asmgen.out(" ldx #>$actualSourceName | lda #<$actualSourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" ldy #>$actualSourceName | lda #<$actualSourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy #>$actualSourceName | ldx #<$actualSourceName")
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
lda #<$actualSourceName
|
||||
ldy #>$actualSourceName
|
||||
sta cx16.${target.register.toString().lowercase()}
|
||||
sty cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
else -> throw AssemblyError("can only load address into 16 bit register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignAddressOf(PtrTarget(target), sourceName)
|
||||
TargetStorageKind.POINTER -> pointergen.assignAddressOf(PtrTarget(target), actualSourceName)
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
@@ -3942,7 +4202,7 @@ $endLabel""")
|
||||
lda $arrayVarName+3,y
|
||||
sta ${target.asmVarname}+3,x""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignIndexedPointer(target, arrayVarName, index, DataType.arrayFor(BaseDataType.LONG))
|
||||
TargetStorageKind.POINTER -> pointergen.assignIndexedPointer(target, arrayVarName, index)
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
|
||||
+136
-14
@@ -318,14 +318,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
SourceStorageKind.EXPRESSION ->
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modification ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modification ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,24 +576,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
clc
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
adc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
adc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
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
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
sec
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
sbc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
sbc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
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
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
@@ -667,6 +682,53 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to target
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to target
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $targetVar
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $targetVar+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $targetVar+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
else -> {
|
||||
TODO("in-place modify LONG with variable, operator=$operator")
|
||||
}
|
||||
@@ -1021,6 +1083,65 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
// long /= constant - use division routine
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
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.div_longs
|
||||
; Copy result from R14R15 back to variable
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $variable,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
// long %= constant - use division routine, remainder is in P8ZP_SCRATCH_W1/W2
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
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.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to variable
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $variable
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $variable+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $variable+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $variable+3""")
|
||||
}
|
||||
"<<" -> if (value > 0) inplaceLongShiftLeft()
|
||||
">>" -> if (value > 0) inplaceLongShiftRight()
|
||||
"|" -> {
|
||||
@@ -3149,6 +3270,7 @@ $shortcutLabel:""")
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
oweroff
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
|
||||
@@ -490,15 +490,275 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignWord(target: IndexedPtrTarget, word: Int) {
|
||||
TODO("array ptr assign const word ${target.position}")
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
|
||||
TODO("array ptr assign const long ${target.position}")
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
val hex = long.toLongHex()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
val hex = long.toLongHex()
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
|
||||
TODO("array ptr assign const float ${target.position}")
|
||||
// For now, only the constant-index case is supported
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<$offset
|
||||
sta $zpPtrVar
|
||||
lda $zpPtrVar+1
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1""")
|
||||
asmgen.storeIndirectFloat(float, zpPtrVar, 0.toUByte())
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<$offset
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
asmgen.storeIndirectFloat(float, "P8ZP_SCRATCH_PTR", 0.toUByte())
|
||||
}
|
||||
} else {
|
||||
asmgen.storeIndirectFloat(float, zpPtrVar, offset.toUByte())
|
||||
}
|
||||
} else {
|
||||
TODO("array ptr assign const float with variable index not yet supported ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFAC1(target: IndexedPtrTarget) {
|
||||
@@ -2560,7 +2820,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer, arrayDt: DataType) {
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer) {
|
||||
// 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
|
||||
|
||||
@@ -30,45 +30,45 @@ class TestAsmOptimizer: FunSpec({
|
||||
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)
|
||||
fun optimize(lines: MutableList<String>) {
|
||||
optimizeAssembly(lines, machine, symbolTable)
|
||||
}
|
||||
|
||||
// --- optimizeIncDec ---
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence") {
|
||||
val lines = mutableListOf(" iny", " dey", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes inx+dex sequence") {
|
||||
val lines = mutableListOf(" inx", " dex", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dey+iny sequence") {
|
||||
val lines = mutableListOf(" dey", " iny", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dex+inx sequence") {
|
||||
val lines = mutableListOf(" dex", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: preserves non-canceling sequences") {
|
||||
val lines = mutableListOf(" iny", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" iny", " inx", " rts", " nop")
|
||||
}
|
||||
|
||||
@@ -76,42 +76,37 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X") {
|
||||
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phx + plx") {
|
||||
val lines = mutableListOf(" nop", " phx", " plx", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phy + ply") {
|
||||
val lines = mutableListOf(" nop", " phy", " ply", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " lda myvar", " rts")
|
||||
}
|
||||
|
||||
@@ -119,50 +114,49 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp") {
|
||||
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+bra") {
|
||||
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
}
|
||||
|
||||
@@ -170,26 +164,26 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeUselessPushPop: removes phy+ldy+pla") {
|
||||
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
|
||||
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
@@ -197,14 +191,14 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
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
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
|
||||
}
|
||||
|
||||
@@ -212,14 +206,14 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
|
||||
}
|
||||
|
||||
@@ -231,7 +225,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" lda #1", " ldy #0", " sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" sta var2", " sty var2+1",
|
||||
@@ -246,7 +240,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" 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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
@@ -260,7 +254,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
|
||||
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
|
||||
@@ -271,7 +265,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
@@ -280,19 +274,19 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("handles empty input") {
|
||||
val lines = mutableListOf<String>()
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("handles single line input") {
|
||||
val lines = mutableListOf(" rts")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
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
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #1", " sta var", " rts")
|
||||
}
|
||||
|
||||
@@ -547,7 +541,9 @@ class TestAsmOptimizer: FunSpec({
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should be removed since "ora" doesn't modify Y
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora myvar", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("actual Y-modifying instructions should still be detected") {
|
||||
@@ -557,7 +553,11 @@ class TestAsmOptimizer: FunSpec({
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should NOT be removed since "tay" modifies Y
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" ldy #0", " lda (ptr),y", " tay",
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
|
||||
@@ -584,8 +584,13 @@ class TestAsmOptimizer: FunSpec({
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
val count = optimize(lines)
|
||||
count shouldBe 0
|
||||
optimize(lines)
|
||||
lines.size shouldBe 8 // all instructions preserved
|
||||
lines shouldBe listOf(
|
||||
" 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"
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -170,7 +170,7 @@ class TestCodegen: FunSpec({
|
||||
val st = SymbolTable(program)
|
||||
st.add(StMemVar("zpmemvar", DataType.WORD, 0x20u, null, null))
|
||||
st.add(StMemVar("normalmemvar", DataType.WORD, 0x9000u, null, null))
|
||||
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests())
|
||||
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests(), program)
|
||||
allocator.isZpVar("zpmemvar") shouldBe true
|
||||
allocator.isZpVar("normalmemvar") shouldBe false
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
}
|
||||
@@ -1,18 +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="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,46 +0,0 @@
|
||||
package prog8.codegen.experimental
|
||||
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram {
|
||||
|
||||
// you could write a code generator directly on the PtProgram AST,
|
||||
// but you can also use the Intermediate Representation to build a codegen on:
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irProgram = irCodeGen.generate()
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
// this stub only writes the IR program to disk but doesn't generate anything else.
|
||||
IRFileWriter(irProgram, null).write()
|
||||
|
||||
if(!options.quiet)
|
||||
println("** experimental codegen stub: no assembly generated **")
|
||||
return EmptyProgram
|
||||
}
|
||||
}
|
||||
|
||||
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 **")
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,5 +11,4 @@ dependencies {
|
||||
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,20 +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" />
|
||||
<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="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<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>
|
||||
@@ -33,10 +33,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
extsub.returns.zip(assignmentTargets).forEach { pair ->
|
||||
val returns = pair.first
|
||||
val target = pair.second as PtAssignTarget
|
||||
// consume the register even for void targets to keep indices aligned
|
||||
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
|
||||
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
|
||||
if (!target.void) {
|
||||
// Determine if this return uses an FP register or CPU register
|
||||
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
|
||||
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(!target.void) {
|
||||
val reg = it.second.first
|
||||
val regnum = codeGen.registers.next(irType(it.second.second))
|
||||
val p = StExtSubParameter(reg, it.second.second)
|
||||
val p = StExtSubParameter("", it.second.second, reg)
|
||||
result += assignCpuRegister(p, regnum, target)
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(!target.void) {
|
||||
val reg = it.second.first
|
||||
val regnum = codeGen.registers.next(irType(it.second.second))
|
||||
val p = StExtSubParameter(reg, it.second.second)
|
||||
val p = StExtSubParameter("", it.second.second, reg)
|
||||
result += assignCpuRegister(p, regnum, target)
|
||||
}
|
||||
}
|
||||
@@ -91,12 +91,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
private fun assignCpuRegister(returns: StExtSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
when(returns.register.registerOrPair) {
|
||||
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum), null)
|
||||
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum), null)
|
||||
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum), null)
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=0), null)
|
||||
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=1), null)
|
||||
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=2), null)
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=3), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=4), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=5), null)
|
||||
in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null)
|
||||
in CombinedLongRegisters -> {
|
||||
require(returns.type.isLong)
|
||||
@@ -224,6 +224,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
}
|
||||
"or=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
@@ -232,6 +234,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
}
|
||||
"and=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
@@ -385,7 +389,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
val arrayVariableName = array.variable!!.name
|
||||
val skipCarryLabel = codeGen.createLabelName()
|
||||
if(constIndex!=null) {
|
||||
val negLsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = negLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = negLsbReg, immediate = 0), null)
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel), null)
|
||||
addInstr(result, IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null)
|
||||
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), skipCarryLabel)
|
||||
@@ -1029,6 +1037,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel)
|
||||
}
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -1289,6 +1299,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel)
|
||||
}
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -1477,11 +1489,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(constValue==1) {
|
||||
addInstr(result, IRInstruction(Opcode.DECM, eltDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
} else {
|
||||
val valueReg=codeGen.registers.next(eltDt)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue)
|
||||
it += IRInstruction(Opcode.SUBM, eltDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.SUBIM, eltDt, immediate = constValue, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1604,8 +1612,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.DECM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediateFp = constValue, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediateFp = constValue, labelSymbol = symbol), null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, if(constAddress!=null)
|
||||
@@ -1619,8 +1631,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.DECM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediate = constValue.toInt(), address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediate = constValue.toInt(), labelSymbol = symbol), null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, if(constAddress!=null)
|
||||
@@ -1712,6 +1728,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
|
||||
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
|
||||
}
|
||||
@@ -1793,11 +1811,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(constValue==1) {
|
||||
addInstr(result, IRInstruction(Opcode.INCM, elementDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
} else {
|
||||
val valueReg=codeGen.registers.next(elementDt)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, elementDt, reg1=valueReg, immediate = constValue)
|
||||
it += IRInstruction(Opcode.ADDM, elementDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.ADDIM, elementDt, immediate = constValue, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1923,8 +1937,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) , null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if (constAddress != null)
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediateFp = constValue, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediateFp = constValue, labelSymbol = symbol) , null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, if (constAddress != null)
|
||||
@@ -1938,8 +1956,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) , null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if (constAddress != null)
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediate = constValue.toInt(), address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediate = constValue.toInt(), labelSymbol = symbol) , null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
if (constAddress != null)
|
||||
@@ -1960,8 +1982,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
|
||||
if(constValue==1) {
|
||||
val skip = codeGen.createLabelName()
|
||||
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
|
||||
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else {
|
||||
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=resultvalueReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=resultvalueReg, immediate=4), null)
|
||||
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -256,12 +256,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
// 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.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=0)
|
||||
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.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=4)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
}
|
||||
} else throw AssemblyError("invalid type for DIVMOD")
|
||||
@@ -300,12 +300,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val needsExplicitCmpi = !codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
when (sourceDt.base) {
|
||||
BaseDataType.BYTE -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -317,6 +320,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -328,6 +333,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -468,40 +475,30 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val type = irType(call.type)
|
||||
require(type != IRDataType.FLOAT)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
val minimumTr = exprGen.translateExpression(call.args[1])
|
||||
val maximumTr = exprGen.translateExpression(call.args[2])
|
||||
result += valueTr.chunks
|
||||
result += minimumTr.chunks
|
||||
result += maximumTr.chunks
|
||||
if(type==IRDataType.FLOAT) {
|
||||
result += codeGen.makeSyscall(
|
||||
IMSyscall.CLAMP_FLOAT, listOf(
|
||||
valueTr.dt to valueTr.resultFpReg,
|
||||
minimumTr.dt to minimumTr.resultFpReg,
|
||||
maximumTr.dt to maximumTr.resultFpReg,
|
||||
), type to valueTr.resultFpReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, -1, valueTr.resultFpReg)
|
||||
} else {
|
||||
val syscall = when(call.type.base) {
|
||||
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
|
||||
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
|
||||
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
|
||||
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to valueTr.resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
|
||||
val syscall = when(call.type.base) {
|
||||
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
|
||||
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
|
||||
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
|
||||
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to valueTr.resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
|
||||
@@ -701,7 +698,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
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)
|
||||
addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
@@ -762,9 +759,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val arr = (arg as? PtArrayIndexer)
|
||||
val index = arr?.index?.asConstInteger()
|
||||
if(arr!=null && index!=null) {
|
||||
if(arr.variable==null)
|
||||
TODO("support for ptr indexing ${arr.position}")
|
||||
if(arr!=null && index!=null && arr.variable!=null) {
|
||||
val variable = arr.variable!!.name
|
||||
if(arr.splitWords) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -828,11 +823,34 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
if(target.splitWords) {
|
||||
if(target.variable==null) {
|
||||
// pointer-based array indexing: compute memory address and store byte
|
||||
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
|
||||
val pointerTr = exprGen.translateExpression(target.pointerderef!!)
|
||||
addToResult(result, pointerTr, pointerTr.resultReg, -1)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex != null) {
|
||||
val offset = eltSize * constIndex + if(msb) eltSize - 1 else 0
|
||||
if(offset > 0)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = offset), null)
|
||||
} else {
|
||||
val (code, indexWordReg) = codeGen.loadIndexReg(target.index, eltSize, true, false)
|
||||
result += code
|
||||
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = pointerTr.resultReg, reg2 = indexWordReg), null)
|
||||
if(msb)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = eltSize - 1), null)
|
||||
}
|
||||
if(isConstZeroValue) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerTr.resultReg, immediate = 0), null)
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
}
|
||||
else if(target.splitWords) {
|
||||
// lsb/msb in split arrays, element index 'size' is always 1
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(target.variable==null)
|
||||
TODO("support for ptr indexing ${target.position}")
|
||||
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
|
||||
@@ -96,11 +96,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtBinaryExpression -> translate(expr)
|
||||
is PtIfExpression -> translate(expr)
|
||||
is PtBranchCondExpression -> translate(expr)
|
||||
is PtFunctionCall -> translate(expr)
|
||||
is PtFunctionCall -> translate(expr, true)
|
||||
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) {
|
||||
@@ -212,6 +211,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
|
||||
result += instructions
|
||||
|
||||
// For inline array fields (not pointer fields), return the address (ptr+offset) instead of loading from it
|
||||
if(deref.type.isArray && !deref.derefLast) {
|
||||
if(offset > 0u)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = offset.toInt()), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, pointerReg, -1)
|
||||
}
|
||||
|
||||
return if(deref.type.isFloat) {
|
||||
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = RegisterNum(resultReg), reg1 = pointerReg, immediate = offset.toInt()), null)
|
||||
@@ -239,24 +245,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(ifExpr.condition is PtBinaryExpression) {
|
||||
val useBIT = checkIfConditionCanUseBIT(ifExpr.condition as PtBinaryExpression)
|
||||
if(useBIT!=null) {
|
||||
// use a BIT instruction to test for bit 7 or 6 set/clear
|
||||
val (testBitSet, variable, bitmask) = useBIT
|
||||
val bitBranchOpcode = when(testBitSet) {
|
||||
true -> when(bitmask) {
|
||||
64 -> Opcode.BSTVC
|
||||
128 -> Opcode.BSTPOS
|
||||
else -> throw AssemblyError("need bit 6 or 7")
|
||||
}
|
||||
false -> when(bitmask) {
|
||||
64 -> Opcode.BSTVS
|
||||
128 -> Opcode.BSTNEG
|
||||
else -> throw AssemblyError("need bit 6 or 7")
|
||||
}
|
||||
}
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name)
|
||||
it += IRInstruction(bitBranchOpcode, labelSymbol = falseLabel)
|
||||
}
|
||||
// use a BITTST instruction to test any single bit
|
||||
val (testBitSet, expr, bitmask) = useBIT
|
||||
val bitPos = Integer.numberOfTrailingZeros(bitmask)
|
||||
val tr = translateExpression(expr)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.BITTST, tr.dt, reg1 = tr.resultReg, immediate = bitPos), null)
|
||||
if(testBitSet)
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = falseLabel), null)
|
||||
addToResult(result, trueTr, trueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
result += IRCodeChunk(falseLabel, null)
|
||||
@@ -270,9 +268,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// Use status flags from condition expression when possible, avoiding redundant CMPI #0
|
||||
val condTr = translateExpression(ifExpr.condition)
|
||||
addToResult(result, condTr, condTr.resultReg, -1)
|
||||
// Check if the last instruction already sets status flags - if so, skip the CMPI #0
|
||||
// Only skip the CMPI if the target honors the "multi-byte ops set Z based on full
|
||||
// value" contract. On 8-bit targets (6502/65C02) this is false, so we always emit
|
||||
// an explicit CMPI - a multi-byte op may have set Z based on only the last byte.
|
||||
// See CpuType.statusBitsOnMultiByteOps for the rationale.
|
||||
val lastInstr = condTr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val skipCmpi = lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
val skipCmpi = codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
&& lastInstr != null
|
||||
&& lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
@@ -538,7 +541,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
val syscall = if(haystackVar.type.isSplitWordArray) IMSyscall.SPLIT_WORDARRAY_CONTAINS else IMSyscall.WORDARRAY_CONTAINS
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
haystackVar.type.isLongArray -> {
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(haystackVar)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -924,7 +941,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
internal fun translate(fcall: PtFunctionCall, resultUsedAsExpression: Boolean = false): ExpressionCodeResult {
|
||||
if(fcall.builtin)
|
||||
return codeGen.translateBuiltinFunc(fcall)
|
||||
|
||||
@@ -962,11 +979,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val paramDt = irType(parameter.type)
|
||||
if(parameter.register==null) {
|
||||
val tr = translateExpression(arg)
|
||||
result += tr.chunks
|
||||
// Note: explicit setCpuRegister is not needed here because the CALL instruction
|
||||
// carries the same slot information in its argument specs, and the code generator
|
||||
// backend uses those to load the CPU registers directly.
|
||||
result += tr.chunks
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null, null)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null, null)))
|
||||
} else {
|
||||
require(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
|
||||
@@ -985,7 +1005,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, RegisterNum(codeGen.registers.next(returnIrType)), null)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), null, null)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
@@ -1012,12 +1032,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
val paramDt = irType(parameter.type)
|
||||
val tr = translateExpression(arg)
|
||||
val (slot, flag) = registerOrStatusflagToSlotAndFlag(parameter.register)
|
||||
val argName = if(slot==null && flag==null) {
|
||||
val reg = parameter.register.registerOrPair
|
||||
if(reg != null && reg in Cx16VirtualRegisters) {
|
||||
// CX16 virtual register parameter - use the cx16 symbol name
|
||||
// so the codegen can resolve it to the correct address
|
||||
"cx16.${reg.toString().lowercase()}"
|
||||
} else {
|
||||
parameter.name
|
||||
}
|
||||
} else ""
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), parameter.register)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(argName, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), slot, flag)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), parameter.register)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(argName, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), slot, flag)))
|
||||
result += tr.chunks
|
||||
result += codeGen.setCpuRegister(parameter.register, paramDt, tr.resultReg, tr.resultFpReg)
|
||||
}
|
||||
|
||||
if(callTarget.returns.size>1)
|
||||
@@ -1030,44 +1060,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else {
|
||||
val returns = callTarget.returns[0]
|
||||
val returnIrType = irType(returns.type)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), returns.register)
|
||||
val (retSlot, retFlag) = registerOrStatusflagToSlotAndFlag(returns.register)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), retSlot, retFlag)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
val returnRegs = if(returnRegSpec==null) emptyList() else listOf(returnRegSpec)
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
else {
|
||||
val address = callTarget.address!!
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
} else if(address.constbank!=null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt()
|
||||
)
|
||||
} else {
|
||||
val tr = translateExpression(address.varbank!!)
|
||||
require(tr.dt==IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = tr.resultReg
|
||||
)
|
||||
}
|
||||
}
|
||||
val call = emitExtSubCall(callTarget, fcall, FunctionCallArgs(argRegisters, returnRegs), result)
|
||||
addInstr(result, call, null)
|
||||
var finalReturnRegister = returnRegSpec?.registerNum?.value ?: -1
|
||||
|
||||
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
|
||||
if(resultUsedAsExpression) {
|
||||
// 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
|
||||
val statusFlagResult = returnRegSpec?.statusflag
|
||||
if(statusFlagResult!=null) {
|
||||
// assign status flag bit to the return value register
|
||||
finalReturnRegister = returnRegSpec.registerNum.value
|
||||
@@ -1128,24 +1133,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkIfConditionCanUseBIT(condition: PtBinaryExpression): Triple<Boolean, PtIdentifier, Int>? {
|
||||
// test for occurrence of: x & 64 != 0 (or 128) , this can be performed with a BIT instruction
|
||||
data class BitTestResult(val testBitSet: Boolean, val expression: PtExpression, val bitmask: Int)
|
||||
|
||||
internal fun checkIfConditionCanUseBIT(condition: PtBinaryExpression): BitTestResult? {
|
||||
// test for occurrence of: x & power_of_2 != 0 (or == 0) , this can be performed with a BITTST instruction
|
||||
if(condition.operator == "==" || condition.operator == "!=") {
|
||||
if (condition.right.asConstInteger() == 0) {
|
||||
val and = condition.left as? PtBinaryExpression
|
||||
if (and != null && and.operator == "&" && and.type.isUnsignedByte) {
|
||||
val bitmask = and.right.asConstInteger()
|
||||
if(bitmask==128 || bitmask==64) {
|
||||
val variable = and.left as? PtIdentifier
|
||||
if (variable != null && variable.type.isByte) {
|
||||
return Triple(condition.operator=="!=", variable, bitmask)
|
||||
}
|
||||
val typecast = and.left as? PtTypeCast
|
||||
if (typecast != null && typecast.type.isUnsignedByte) {
|
||||
val castedVariable = typecast.value as? PtIdentifier
|
||||
if(castedVariable!=null && castedVariable.type.isByte)
|
||||
return Triple(condition.operator=="!=", castedVariable, bitmask)
|
||||
}
|
||||
if (and != null && and.operator == "&") {
|
||||
val bitmask = and.right.asConstInteger() ?: return null
|
||||
if(bitmask > 0 && bitmask and (bitmask - 1) == 0) { // power of two
|
||||
val expr = and.left
|
||||
if(expr is PtIdentifier || expr is PtTypeCast || expr is PtArrayIndexer)
|
||||
return BitTestResult(condition.operator=="!=", expr, bitmask)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1162,29 +1162,67 @@ 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), RegisterNum(regnum), it.register)
|
||||
val (slot, flag) = registerOrStatusflagToSlotAndFlag(it.register)
|
||||
FunctionCallArgs.RegSpec(irType(it.type), RegisterNum(regnum), slot, flag)
|
||||
}
|
||||
// create the call
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
|
||||
else {
|
||||
val address = callTarget.address!!
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
|
||||
)
|
||||
}
|
||||
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
|
||||
}
|
||||
val call = emitExtSubCall(callTarget, fcall, FunctionCallArgs(argRegisters, returnRegisters), result)
|
||||
addInstr(result, call, null)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun emitExtSubCall(
|
||||
callTarget: StExtSub,
|
||||
fcall: PtFunctionCall,
|
||||
fcallArgs: FunctionCallArgs,
|
||||
result: MutableList<IRCodeChunkBase>
|
||||
): IRInstruction {
|
||||
val address = callTarget.address
|
||||
return if (address == null) {
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = fcallArgs)
|
||||
} else if (address.constbank == null && address.varbank == null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = fcallArgs
|
||||
)
|
||||
} else if (address.constbank != null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt(),
|
||||
fcallArgs = fcallArgs
|
||||
)
|
||||
} else {
|
||||
val varBank = address.varbank!!
|
||||
val target = codeGen.symbolTable.lookup(varBank.name) ?: codeGen.symbolTable.lookupUnscoped(varBank.name)
|
||||
val bankReg = if (target?.type == StNodeType.SUBROUTINE || target?.type == StNodeType.EXTSUB) {
|
||||
// generate a call to the bank subroutine with the call ID as argument
|
||||
val bankFcall = PtFunctionCall(varBank.name, false, false, arrayOf(DataType.UBYTE), varBank.position)
|
||||
val callId = codeGen.extsubCallSiteIds[fcall.name]
|
||||
if (callId != null) {
|
||||
bankFcall.add(PtNumber(BaseDataType.UBYTE, callId.toDouble(), varBank.position))
|
||||
}
|
||||
val tr = translate(bankFcall, true)
|
||||
result += tr.chunks
|
||||
tr.resultReg
|
||||
} else {
|
||||
val tr = translateExpression(varBank)
|
||||
require(tr.dt == IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
tr.resultReg
|
||||
}
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = bankReg,
|
||||
fcallArgs = fcallArgs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun operatorGreaterThan(
|
||||
binExpr: PtBinaryExpression,
|
||||
vmDt: IRDataType,
|
||||
@@ -1278,7 +1316,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val leftTr = translateExpression(binExpr.left)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val skipCmpi = rightConst.toInt() == 0 && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
// Only skip the CMPI if the target honors the "multi-byte ops set Z
|
||||
// based on full value" contract. On 8-bit targets a previous op like
|
||||
// DEC only set Z from the last byte, so the explicit CMPI is required.
|
||||
// See CpuType.statusBitsOnMultiByteOps for the rationale.
|
||||
val skipCmpi = rightConst.toInt() == 0
|
||||
&& codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
&& lastInstr != null
|
||||
&& lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null)
|
||||
}
|
||||
@@ -1820,6 +1865,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg, immediate = fieldinfo.second.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerOrStatusflagToSlotAndFlag(reg: RegisterOrStatusflag): Pair<CallingConventionSlot?, Statusflag?> {
|
||||
if (reg.statusflag != null) return null to reg.statusflag
|
||||
if (reg.registerOrPair == null) return null to null
|
||||
if (reg.registerOrPair in Cx16VirtualRegisters || reg.registerOrPair in CombinedLongRegisters) return null to null
|
||||
return when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> CallingConventionSlot(0) to null
|
||||
RegisterOrPair.X -> CallingConventionSlot(1) to null
|
||||
RegisterOrPair.Y -> CallingConventionSlot(2) to null
|
||||
RegisterOrPair.AX -> CallingConventionSlot(3) to null
|
||||
RegisterOrPair.AY -> CallingConventionSlot(4) to null
|
||||
RegisterOrPair.XY -> CallingConventionSlot(5) to null
|
||||
RegisterOrPair.FAC1 -> CallingConventionSlot(6) to null
|
||||
RegisterOrPair.FAC2 -> CallingConventionSlot(7) to null
|
||||
else -> throw AssemblyError("unsupported register $reg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,30 +3,44 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.*
|
||||
import kotlin.io.path.readBytes
|
||||
|
||||
// NOTE: All symbol names in the generated IR are fully scoped (entire dotted path)
|
||||
// and carry type prefixes: p8b_ (block), p8s_ (sub), p8v_ (variable), p8c_ (const),
|
||||
// p8l_ (label), p8t_ (struct), p8i_ (instance). Any backend consuming the IR
|
||||
// can rely on these already being present and does not need to add them itself.
|
||||
|
||||
|
||||
class IRCodeGen(
|
||||
internal val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal var symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter,
|
||||
internal val retainSSA: Boolean
|
||||
internal val retainSSA: Boolean,
|
||||
preassignedCallSiteIds: Map<String, UByte> = emptyMap()
|
||||
) {
|
||||
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
||||
private val assignmentGen = AssignmentGen(this, expressionEval)
|
||||
internal val registers = RegisterPool()
|
||||
internal val extsubCallSiteIds: MutableMap<String, UByte> = preassignedCallSiteIds.toMutableMap()
|
||||
|
||||
fun generate(): IRProgram {
|
||||
// The pure "virtual" (VM) target doesn't need symbol prefixing because the VM
|
||||
// doesn't emit assembly and has no risk of symbol clashes. Any other target
|
||||
// (that goes through assembly code generation) MUST prefix symbols to avoid
|
||||
// clashes with 64tass reserved names, BASIC tokens, and other toolchain symbols.
|
||||
if(options.compTarget.name!=VMTarget.NAME)
|
||||
symbolTable = prefixSymbols(program, options, symbolTable)
|
||||
makeAllNodenamesScoped(program)
|
||||
moveAllNestedSubroutinesToBlockScope(program)
|
||||
verifyNameScoping(program, symbolTable)
|
||||
changeGlobalVarInits(symbolTable)
|
||||
|
||||
val irSymbolTable = convertStToIRSt(symbolTable)
|
||||
val irSymbolTable = convertStToIRSt(symbolTable, options.romable)
|
||||
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
|
||||
|
||||
// collect global variables initializers
|
||||
@@ -39,6 +53,17 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
// in romable mode, generate global init code for declaration-initialized variables
|
||||
// and clear their init value so they go into the BSS section
|
||||
if(options.romable) {
|
||||
generateRomableInits(irProg)
|
||||
}
|
||||
|
||||
// generate global init code for string/array-initialized variables
|
||||
// (the existing mechanisms only handle numeric inits; string/array data stays
|
||||
// in the INIT section declarations but no runtime copy instructions are emitted)
|
||||
generateStringArrayInits(irProg)
|
||||
|
||||
irProg.addAsmSymbols(options.symbolDefs)
|
||||
|
||||
for (block in program.allBlocks())
|
||||
@@ -63,6 +88,11 @@ class IRCodeGen(
|
||||
fun registerTypes(): Map<RegisterNum, IRDataType> = registers.getTypes()
|
||||
|
||||
private fun changeGlobalVarInits(symbolTable: SymbolTable) {
|
||||
// In romable mode, don't pull initializers into the symbol table;
|
||||
// they stay as assignments in the AST and become global init code naturally.
|
||||
if(options.romable)
|
||||
return
|
||||
|
||||
// Normally, block level (global) variables that have a numeric initialization value
|
||||
// are initialized via an assignment statement.
|
||||
val initsToRemove = mutableListOf<Pair<PtBlock, PtAssignment>>()
|
||||
@@ -95,6 +125,153 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRomableInits(irProg: IRProgram) {
|
||||
// For romable mode, declaration-initialized variables marked inBss need
|
||||
// runtime init code in the globalInits chunk, and their initializationValue
|
||||
// must be cleared so they appear in the NOINIT (BSS) section of the IR file.
|
||||
val replacements = mutableListOf<IRStStaticVariable>()
|
||||
for(variable in irProg.st.allVariables()) {
|
||||
if(variable.inBss && variable.initializationValue != null) {
|
||||
when(val initValue = variable.initializationValue) {
|
||||
is IRVariableInitializer.Numeric -> {
|
||||
val dt = irType(variable.dt)
|
||||
val tempReg = registers.next(dt)
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
chunk += IRInstruction(Opcode.LOAD, dt, reg1 = tempReg, immediate = initValue.value.toInt())
|
||||
chunk += IRInstruction(Opcode.STOREM, dt, reg1 = tempReg, labelSymbol = variable.name)
|
||||
irProg.addGlobalInits(chunk)
|
||||
// replace with uninitialized version so it goes to NOINIT section
|
||||
replacements += IRStStaticVariable(
|
||||
variable.name, variable.dt, null, variable.length,
|
||||
variable.zpwish, variable.align, variable.dirty,
|
||||
variable.inBss, variable.readonly
|
||||
)
|
||||
}
|
||||
is IRVariableInitializer.Str, is IRVariableInitializer.Array -> {
|
||||
// Strings and arrays: keep inline data but now marked readonly.
|
||||
// They can serve as ROM-based read-only data.
|
||||
}
|
||||
null -> {} // uninitialized, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
replacements.forEach { irProg.st.add(it) }
|
||||
}
|
||||
|
||||
private fun generateStringArrayInits(irProg: IRProgram) {
|
||||
// ZP variables with string/array init values need runtime copy code.
|
||||
// The VM's varsToMemory already initializes them from the INIT section,
|
||||
// but a 6502 backend needs explicit init instructions.
|
||||
//
|
||||
// Create a non-ZP shadow variable holding the init bytes, clear the
|
||||
// original variable's init value so it becomes uninitialized/BSS, then
|
||||
// emit a single MEMCOPY from the shadow to the ZP variable at startup.
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
for(variable in irProg.st.allVariables().toList()) {
|
||||
if(variable.initializationValue == null)
|
||||
continue
|
||||
if(variable.inBss)
|
||||
continue
|
||||
if(variable.zpwish == ZeropageWish.DONTCARE || variable.zpwish == ZeropageWish.NOT_IN_ZEROPAGE)
|
||||
continue
|
||||
if(variable.dt.isSplitWordArray)
|
||||
continue
|
||||
if(variable.initializationValue is IRVariableInitializer.Numeric)
|
||||
continue
|
||||
|
||||
val initBytes = when(val initVal = variable.initializationValue) {
|
||||
is IRVariableInitializer.Str -> {
|
||||
irProg.encoding.encodeString(initVal.text, initVal.encoding) + 0u
|
||||
}
|
||||
is IRVariableInitializer.Array -> {
|
||||
val elemDt = variable.dt.elementType()
|
||||
val elemByteSize = when {
|
||||
elemDt.isByte || elemDt.isBool -> 1
|
||||
elemDt.isWord || elemDt.isPointer -> 2
|
||||
else -> -1
|
||||
}
|
||||
if(elemByteSize < 0)
|
||||
continue
|
||||
val bytes = mutableListOf<UByte>()
|
||||
for(element in initVal.elements) {
|
||||
when(element) {
|
||||
is IRStSymbolicReference.Numeric -> {
|
||||
val value = element.value.toInt()
|
||||
when(elemByteSize) {
|
||||
1 -> bytes += value.toUByte()
|
||||
2 -> {
|
||||
bytes += (value and 0xFF).toUByte()
|
||||
bytes += ((value shr 8) and 0xFF).toUByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
is IRStSymbolicReference.BoolValue -> {
|
||||
bytes += (if(element.value) 1u else 0u).toUByte()
|
||||
}
|
||||
is IRStSymbolicReference.Symbol -> {
|
||||
bytes.clear()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if(bytes.isEmpty())
|
||||
continue
|
||||
bytes
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
|
||||
if(initBytes.isEmpty())
|
||||
continue
|
||||
|
||||
val shadowName = variable.name + "_init_value"
|
||||
val shadowInit = IRVariableInitializer.Array(initBytes.map { IRStSymbolicReference.Numeric(it.toDouble()) })
|
||||
val shadowVar = IRStStaticVariable(
|
||||
shadowName,
|
||||
DataType.arrayFor(BaseDataType.UBYTE, false),
|
||||
shadowInit,
|
||||
initBytes.size.toUInt(),
|
||||
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||
0u,
|
||||
false,
|
||||
inBss = false,
|
||||
readonly = false
|
||||
)
|
||||
irProg.st.add(shadowVar)
|
||||
|
||||
// Clear the original variable's init value so it becomes uninitialized (BSS/NOINIT).
|
||||
// The runtime copy from the shadow variable will provide the actual initial bytes.
|
||||
val clearedVar = IRStStaticVariable(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
null,
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align,
|
||||
variable.dirty,
|
||||
variable.inBss,
|
||||
variable.readonly
|
||||
)
|
||||
irProg.st.add(clearedVar)
|
||||
|
||||
val sourceReg = registers.next(IRDataType.WORD)
|
||||
val destReg = registers.next(IRDataType.WORD)
|
||||
val countReg = registers.next(IRDataType.WORD)
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = sourceReg, labelSymbol = shadowName)
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = destReg, labelSymbol = variable.name)
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = countReg, immediate = initBytes.size)
|
||||
val args = listOf(
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(sourceReg), null, null)),
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(destReg), null, null)),
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(countReg), null, null))
|
||||
)
|
||||
chunk += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.MEMCOPY.number, fcallArgs = FunctionCallArgs(args, emptyList()))
|
||||
}
|
||||
|
||||
if(chunk.isNotEmpty())
|
||||
irProg.addGlobalInits(chunk)
|
||||
}
|
||||
|
||||
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
|
||||
fun verifyPtNode(node: PtNode) {
|
||||
when (node) {
|
||||
@@ -116,9 +293,16 @@ class IRCodeGen(
|
||||
is PtPointerDeref -> require('.' in node.startpointer.name) { "node $node name is not scoped: ${node.startpointer.name}" }
|
||||
is PtIdentifier -> {
|
||||
if('.' !in node.name) {
|
||||
// there is 1 case where the identifier is not scoped: if it's the value field name after a pointer array indexing.
|
||||
// there are 2 cases where the identifier is not scoped:
|
||||
// 1) it is the value field name after a pointer array indexing.
|
||||
// 2) it is the operand of a PtAddressOf referring to a block name.
|
||||
val expr = node.parent as? PtBinaryExpression
|
||||
if (expr?.operator != "." || expr.right !== node || expr.left !is PtArrayIndexer || (!expr.left.type.isPointer && !expr.left.type.isStructInstance))
|
||||
val isFieldOfPointerArrayElement = expr?.operator == "." && expr.right === node
|
||||
&& expr.left is PtArrayIndexer
|
||||
&& (expr.left.type.isPointer || expr.left.type.isStructInstance)
|
||||
val isAddressOfBlock = node.parent is PtAddressOf
|
||||
&& symbolTable.lookup(node.name)?.type == StNodeType.BLOCK
|
||||
if(!isFieldOfPointerArrayElement && !isAddressOfBlock)
|
||||
require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
}
|
||||
}
|
||||
@@ -204,8 +388,9 @@ class IRCodeGen(
|
||||
val old = it.first.instructions[it.second]
|
||||
val formats = instructionFormats.getValue(old.opcode)
|
||||
val format = formats.getOrElse(old.type) { throw IllegalArgumentException("type ${old.type} invalid for ${old.opcode}") }
|
||||
val immediateValue = if(format.immediate) it.third.toInt() else null
|
||||
val addressValue: UInt? = if(format.immediate) null else it.third
|
||||
val addressUsed = format.address != OperandDirection.UNUSED
|
||||
val immediateValue = if(addressUsed) old.immediate else if(format.immediate) it.third.toInt() else null
|
||||
val addressValue: UInt? = if(addressUsed) it.third else null
|
||||
|
||||
it.first.instructions[it.second] = IRInstruction(
|
||||
old.opcode,
|
||||
@@ -216,6 +401,7 @@ class IRCodeGen(
|
||||
old.fpReg1,
|
||||
old.fpReg2,
|
||||
immediate = immediateValue,
|
||||
immediateFp = old.immediateFp,
|
||||
address = addressValue?.toAddress()
|
||||
)
|
||||
}
|
||||
@@ -230,7 +416,7 @@ class IRCodeGen(
|
||||
is PtAssignment -> assignmentGen.translate(node)
|
||||
is PtAugmentedAssign -> assignmentGen.translate(node)
|
||||
is PtNodeGroup -> translateGroup(node.children)
|
||||
is PtFunctionCall -> expressionEval.translate(node).chunks // it's not an expression so no result value
|
||||
is PtFunctionCall -> expressionEval.translate(node, false).chunks // it's not an expression so no result value
|
||||
is PtNop -> emptyList()
|
||||
is PtReturn -> translate(node)
|
||||
is PtJump -> translate(node)
|
||||
@@ -508,6 +694,12 @@ class IRCodeGen(
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
|
||||
result += IRCodeChunk(loopLabel, null).also {
|
||||
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
|
||||
// Emit explicit CMPI #0 before the BSTEQ branch on 8-bit targets
|
||||
// (where LOADX doesn't reliably set Z for multi-byte results).
|
||||
// See CpuType.statusBitsOnMultiByteOps.
|
||||
if(!options.compTarget.cpu.statusBitsOnMultiByteOps) {
|
||||
it += IRInstruction(Opcode.CMPI, elementDt, reg1 = tmpReg, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
|
||||
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
@@ -607,6 +799,12 @@ class IRCodeGen(
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
|
||||
// Emit explicit CMPI #0 before the BSTNE branch on 8-bit targets where
|
||||
// LOADM doesn't reliably set Z for multi-byte results. Skip on targets
|
||||
// that honor the contract (e.g. M68000). See CpuType.statusBitsOnMultiByteOps.
|
||||
if(!options.compTarget.cpu.statusBitsOnMultiByteOps) {
|
||||
it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
@@ -700,6 +898,14 @@ class IRCodeGen(
|
||||
}
|
||||
else if(iterable.step==-1 && iterable.last==1) {
|
||||
// downto 1 optimization (byte and word)
|
||||
// On 8-bit targets the preceding addConstMem emitted a DECM which doesn't
|
||||
// reliably set Z; we must explicitly test the loop var. Skip the explicit
|
||||
// CMPI on targets that honor the contract (e.g. M68000).
|
||||
// See CpuType.statusBitsOnMultiByteOps.
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
if(!options.compTarget.cpu.statusBitsOnMultiByteOps) {
|
||||
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = 0)
|
||||
}
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
} else {
|
||||
// downto some other value
|
||||
@@ -775,20 +981,35 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val valueReg = registers.next(dt)
|
||||
if(value>0) {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = -value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
// float loop variables are not currently supported; keep the load+addm/subm form
|
||||
val valueReg = registers.next(dt)
|
||||
if(value>0) {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = -value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
}
|
||||
} else {
|
||||
if(value>0) {
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ADDIM, dt, immediate = value, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDIM, dt, immediate = value, labelSymbol = symbol)
|
||||
} else {
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.SUBIM, dt, immediate = -value, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBIM, dt, immediate = -value, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,8 +1088,8 @@ class IRCodeGen(
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = registers.next(dt)
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
val pow2reg = registers.next(IRDataType.BYTE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, address = knownAddress.toAddress())
|
||||
else
|
||||
@@ -985,8 +1206,8 @@ class IRCodeGen(
|
||||
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
|
||||
} else {
|
||||
// just shift multiple bits (signed)
|
||||
val pow2reg = registers.next(dt)
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
|
||||
val pow2reg = registers.next(IRDataType.BYTE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = pow2reg, immediate = pow2)
|
||||
code += if (knownAddress != null)
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress.toAddress())
|
||||
else
|
||||
@@ -1002,8 +1223,8 @@ class IRCodeGen(
|
||||
}
|
||||
else {
|
||||
// just shift multiple bits (unsigned)
|
||||
val pow2reg = registers.next(dt)
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
val pow2reg = registers.next(IRDataType.BYTE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress.toAddress())
|
||||
else
|
||||
@@ -1115,9 +1336,17 @@ class IRCodeGen(
|
||||
// Fallback: materialize expression and branch on result
|
||||
val tr = expressionEval.translateExpression(condition)
|
||||
result += tr.chunks
|
||||
// Check if the last instruction already sets status flags - if so, skip the CMPI #0
|
||||
// Only skip the CMPI #0 if BOTH:
|
||||
// 1) The previous instruction is documented to set status bits (Z/N), AND
|
||||
// 2) The target CPU honors the "multi-byte ops set Z based on full value" contract
|
||||
// (see CpuType.statusBitsOnMultiByteOps). For 8-bit targets this contract
|
||||
// is false: e.g. a 16-bit DEC only sets Z from the low byte's dec, not the
|
||||
// full 16-bit value, so we MUST emit an explicit CMPI here.
|
||||
val lastInstr = tr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val skipCmpi = lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
val targetHonorsContract = options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
val skipCmpi = targetHonorsContract
|
||||
&& lastInstr != null
|
||||
&& lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0), null)
|
||||
}
|
||||
@@ -1135,14 +1364,17 @@ class IRCodeGen(
|
||||
private fun translateIntegerComparison(condition: PtBinaryExpression, onTrueLabel: String?, onTrueAddress: MemoryAddress?, onFalseLabel: String?, onFalseAddress: MemoryAddress?, result: MutableList<IRCodeChunkBase>) {
|
||||
val useBIT = expressionEval.checkIfConditionCanUseBIT(condition)
|
||||
if (useBIT != null) {
|
||||
val (testBitSet, variable, bitmask) = useBIT
|
||||
addInstr(result, IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name), null)
|
||||
val (testBitSet, expr, bitmask) = useBIT
|
||||
val bitPos = Integer.numberOfTrailingZeros(bitmask)
|
||||
val leftTr = expressionEval.translateExpression(expr)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.BITTST, leftTr.dt, reg1 = leftTr.resultReg, immediate = bitPos), null)
|
||||
if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) {
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
} else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) {
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, !testBitSet), labelSymbol = onFalseLabel, address = onFalseAddress), null)
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(!testBitSet), labelSymbol = onFalseLabel, address = onFalseAddress), null)
|
||||
} else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) {
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null)
|
||||
}
|
||||
return
|
||||
@@ -1157,7 +1389,15 @@ class IRCodeGen(
|
||||
if (number != null) {
|
||||
val isComparingWithZero = number == 0
|
||||
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val canSkipCmpi = isComparingWithZero && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
// Skip the CMPI only if the target honors the "multi-byte ops set Z correctly"
|
||||
// contract. On 8-bit targets a 16/32-bit op like DEC only sets Z from the last
|
||||
// byte, so we cannot rely on the previous instruction's status bits - the
|
||||
// CMPI is required to test the full multi-byte value.
|
||||
// See CpuType.statusBitsOnMultiByteOps for details.
|
||||
val canSkipCmpi = isComparingWithZero
|
||||
&& options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
&& lastInstr != null
|
||||
&& lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
|
||||
if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) {
|
||||
var (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, false, signed)
|
||||
@@ -1211,12 +1451,8 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBitBranchOpcode(bitmask: Int, wantSet: Boolean): Opcode {
|
||||
return if (wantSet) {
|
||||
if (bitmask == 64) Opcode.BSTVS else Opcode.BSTNEG
|
||||
} else {
|
||||
if (bitmask == 64) Opcode.BSTVC else Opcode.BSTPOS
|
||||
}
|
||||
private fun getBitBranchOpcode(wantSet: Boolean): Opcode {
|
||||
return if (wantSet) Opcode.BSTNE else Opcode.BSTEQ
|
||||
}
|
||||
|
||||
private fun invertOperator(operator: String) = when (operator) {
|
||||
@@ -1327,13 +1563,22 @@ class IRCodeGen(
|
||||
val skipRepeatLabel = createLabelName()
|
||||
val constRepeats = repeat.count.asConstInteger()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
// The DEC instruction only sets Z correctly for BYTE (or for the full multi-byte
|
||||
// value if the target honors the multi-byte status-bits contract, e.g. M68000).
|
||||
// On 8-bit targets we must emit an explicit CMPI #0 before the BSTNE branch
|
||||
// because DEC only sets Z based on the last byte.
|
||||
// See CpuType.statusBitsOnMultiByteOps for the rationale.
|
||||
val needsExplicitCmpi = !options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
if(constRepeats==65536) {
|
||||
// make use of the word wrap around to count to 65536
|
||||
val resultRegister = registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, immediate = 0), null)
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1 = resultRegister) // sets status bits
|
||||
it += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1 = resultRegister)
|
||||
if (needsExplicitCmpi) {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1 = resultRegister, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
} else {
|
||||
@@ -1341,12 +1586,20 @@ class IRCodeGen(
|
||||
val countTr = expressionEval.translateExpression(repeat.count)
|
||||
addToResult(result, countTr, countTr.resultReg, -1)
|
||||
if (repeat.count.asConstValue() == null) {
|
||||
// check if the counter is already zero
|
||||
// check if the counter is already zero. On 8-bit targets we need an
|
||||
// explicit CMPI #0 because the previous load (from expressionEval) does
|
||||
// not reliably set Z for multi-byte values.
|
||||
if (needsExplicitCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = countTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
|
||||
}
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
|
||||
if (needsExplicitCmpi) {
|
||||
it += IRInstruction(Opcode.CMPI, irDt, reg1 = countTr.resultReg, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
}
|
||||
@@ -1409,7 +1662,7 @@ class IRCodeGen(
|
||||
?: throw AssemblyError("expected function call for multi-value return ${ret.position}")
|
||||
|
||||
// Translate the function call (this generates the CALL instruction)
|
||||
val callResult = expressionEval.translate(fcall)
|
||||
val callResult = expressionEval.translate(fcall, false)
|
||||
result += callResult.chunks
|
||||
|
||||
// Get the return register specs for the called function
|
||||
@@ -1520,7 +1773,11 @@ class IRCodeGen(
|
||||
is PtNop -> { /* nothing */ }
|
||||
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
|
||||
is PtVariable, is PtConstant, is PtMemMapped, is PtMemorySlabReservation -> { /* vars should be looked up via symbol table */ }
|
||||
is PtAlign -> TODO("ir support for inline %align ${child.position}")
|
||||
is PtAlign -> {
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
chunk += IRInstruction(Opcode.ALIGN, immediate = child.align.toInt())
|
||||
irBlock += chunk
|
||||
}
|
||||
is PtSub -> {
|
||||
val sub = IRSubroutine(child.name, translateParameters(child.signature.children), child.signature.returns, child.position)
|
||||
for (subchild in child.children) {
|
||||
@@ -1529,11 +1786,15 @@ class IRCodeGen(
|
||||
irBlock += sub
|
||||
}
|
||||
is PtAsmSub -> {
|
||||
if(child.address!=null) {
|
||||
// extmsub. No codegen needed: calls to this are jumping straight to the address.
|
||||
val addr = child.address
|
||||
if(addr!=null) {
|
||||
// extsub: emit as inline assembly equate so that other tools (vm, 6502 codegen) can reference the symbol as an address label
|
||||
require(child.children.isEmpty()) {
|
||||
"extsub should be empty at ${child.position}"
|
||||
}
|
||||
val bank = if(addr.constbank!=null) " ; @bank ${addr.constbank}" else ""
|
||||
val asm = "${child.name} = ${addr.address.toHex()}$bank"
|
||||
irBlock += IRInlineAsmChunk(null, asm, false, null)
|
||||
} else {
|
||||
// regular asmsub
|
||||
if(child.children.mapTo(mutableSetOf()) { (it as PtInlineAssembly).isIR }.size>1)
|
||||
@@ -1550,7 +1811,8 @@ class IRCodeGen(
|
||||
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
|
||||
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
|
||||
asmChunk,
|
||||
child.position
|
||||
child.position,
|
||||
child.inline
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1635,9 +1897,10 @@ class IRCodeGen(
|
||||
} else {
|
||||
val reg = it.register
|
||||
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = it.register!!.asScopedNameVirtualReg(it.type).joinToString(".")
|
||||
val targetVar = symbolTable.lookup(regname) as StMemVar
|
||||
result += IRSubroutine.IRParam(regname, targetVar.dt)
|
||||
//val regname = it.register!!.asScopedNameVirtualReg(it.type).joinToString(".")
|
||||
require('.' in it.name) { "even parameter names should have been made fully scoped by now" }
|
||||
val targetVar = symbolTable.lookup(it.name) as StMemVar
|
||||
result += IRSubroutine.IRParam(it.name, targetVar.dt)
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -1660,10 +1923,10 @@ class IRCodeGen(
|
||||
internal fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
|
||||
return IRCodeChunk(label, null).also {
|
||||
val args = params.map { (dt, reg)->
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, RegisterNum(reg), null))
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, RegisterNum(reg), null, null))
|
||||
}
|
||||
// for now, syscalls have 0 or 1 return value
|
||||
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, RegisterNum(returns.second), null))
|
||||
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, RegisterNum(returns.second), null, null))
|
||||
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
|
||||
}
|
||||
}
|
||||
@@ -1671,12 +1934,12 @@ class IRCodeGen(
|
||||
internal fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
when(registerOrFlag.registerOrPair) {
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=0)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=1)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=2)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=3)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=4)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=5)
|
||||
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg))
|
||||
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg))
|
||||
in Cx16VirtualRegisters -> {
|
||||
@@ -1701,12 +1964,12 @@ class IRCodeGen(
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
val irType = irType(fromType)
|
||||
when(registerOrFlag.registerOrPair) {
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=tempReg)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=tempReg)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=tempReg)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=tempReg)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=tempReg)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=tempReg)
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=0)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=1)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=2)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=3)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=4)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=5)
|
||||
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(tempReg))
|
||||
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(tempReg))
|
||||
in Cx16VirtualRegisters -> {
|
||||
@@ -1795,5 +2058,4 @@ class IRCodeGen(
|
||||
result += multiplyByConst(DataType.UBYTE, byteIndexTr.resultReg, itemsize)
|
||||
return Pair(result, byteIndexTr.resultReg)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,14 +3,6 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.intermediate.*
|
||||
|
||||
/**
|
||||
* Represents what value a register currently holds for LOADR forwarding optimization.
|
||||
*/
|
||||
private sealed class RegisterContent {
|
||||
data class Constant(val value: Any) : RegisterContent() // Int or Double
|
||||
data class Register(val originalReg: Int) : RegisterContent()
|
||||
}
|
||||
|
||||
class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: Boolean) {
|
||||
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
|
||||
if(!optimizationsEnabled)
|
||||
@@ -65,6 +57,9 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA:
|
||||
|| removeNeedlessLoads(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)
|
||||
}
|
||||
@@ -344,23 +339,28 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA:
|
||||
}
|
||||
|
||||
private fun removeNeedlessCompares(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// a CMPI with 0, after an instruction like LOAD that already sets the status bits, can be removed.
|
||||
// but only if the instruction after it is not using the Carry bit because that won't be set by a LOAD instruction etc.
|
||||
// A CMPI #0 after an instruction that already sets the status bits can be removed.
|
||||
// The optimization is ONLY safe if the target CPU honors the "multi-byte ops set
|
||||
// status bits based on the full value" contract (CpuType.statusBitsOnMultiByteOps).
|
||||
// On 8-bit targets like 6502/65C02 a 16/32-bit DEC/INC/AND/OR/XOR/LOAD only sets
|
||||
// Z from the last byte, so a following CMPI #0 is NOT redundant - it tests the
|
||||
// full multi-byte value. In that case we keep the explicit CMPI.
|
||||
// See CpuType.statusBitsOnMultiByteOps for the rationale.
|
||||
val targetHonorsContract = irprog.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
if (!targetHonorsContract) return false
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) {
|
||||
val previous = indexedInstructions[idx-1].value
|
||||
if(previous.reg1==ins.reg1) {
|
||||
if (previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
|
||||
if (previous.opcode in OpcodesThatSetStatusbits) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if (previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
|
||||
val next = indexedInstructions[idx + 1].value
|
||||
if (next.opcode !in OpcodesThatDependOnCarry) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// Note: the OpcodesThatSetStatusbitsButNotCarry set is empty under
|
||||
// the strict contract (see IRInstructions.kt) so we don't need a
|
||||
// second branch here. Only CMP/CMPI/SGN/BITTST are recognized as
|
||||
// setting flags.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,7 +452,7 @@ jump p8_label_gen_2
|
||||
}
|
||||
}
|
||||
Opcode.AND -> {
|
||||
when (ins.immediate) {
|
||||
when (val imm = ins.immediate) {
|
||||
0 -> {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
|
||||
changed = true
|
||||
@@ -470,23 +470,54 @@ jump p8_label_gen_2
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// convert AND with all-ones-except-one-bit into BITCLR
|
||||
if(!changed) {
|
||||
val imm = ins.immediate ?: return@forEach
|
||||
val clearedBits = when(ins.type) {
|
||||
IRDataType.BYTE -> imm xor 0xFF
|
||||
IRDataType.WORD -> imm xor 0xFFFF
|
||||
IRDataType.LONG -> imm xor -1
|
||||
else -> 0
|
||||
}
|
||||
if(clearedBits > 0 && clearedBits and (clearedBits - 1) == 0) {
|
||||
val bitPos = Integer.numberOfTrailingZeros(clearedBits)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.BITCLR, ins.type, reg1 = ins.reg1, immediate = bitPos)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode.OR -> {
|
||||
if (ins.immediate == 0) {
|
||||
val imm = ins.immediate
|
||||
if (imm == null) return@forEach
|
||||
if (imm == 0) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if ((ins.immediate == 255 && ins.type == IRDataType.BYTE) ||
|
||||
(ins.immediate == 65535 && ins.type == IRDataType.WORD) ||
|
||||
(ins.immediate == -1 && ins.type == IRDataType.LONG)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = ins.immediate)
|
||||
} else if ((imm == 255 && ins.type == IRDataType.BYTE) ||
|
||||
(imm == 65535 && ins.type == IRDataType.WORD) ||
|
||||
(imm == -1 && ins.type == IRDataType.LONG)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = imm)
|
||||
changed = true
|
||||
}
|
||||
// convert OR with power-of-2 into BITSET
|
||||
if(!changed && imm > 0 && imm and (imm - 1) == 0) {
|
||||
val bitPos = Integer.numberOfTrailingZeros(imm)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.BITSET, ins.type, reg1 = ins.reg1, immediate = bitPos)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.XOR -> {
|
||||
if (ins.immediate == 0) {
|
||||
val imm = ins.immediate
|
||||
if (imm == null) return@forEach
|
||||
if (imm == 0) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
// convert XOR with power-of-2 into BITTOG
|
||||
if(!changed && imm > 0 && imm and (imm - 1) == 0) {
|
||||
val bitPos = Integer.numberOfTrailingZeros(imm)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.BITTOG, ins.type, reg1 = ins.reg1, immediate = bitPos)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -544,6 +575,98 @@ jump p8_label_gen_2
|
||||
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 -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
@@ -686,18 +809,6 @@ jump p8_label_gen_2
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun isRegisterRead(ins: IRInstruction, reg: Int): Boolean {
|
||||
// Check if the instruction reads from the given register
|
||||
val formats = instructionFormats.getValue(ins.opcode)
|
||||
val format = formats[ins.type] ?: formats[null]
|
||||
|
||||
val reg1Read = (format?.reg1 == OperandDirection.READ || format?.reg1 == OperandDirection.READWRITE) && (ins.reg1 == reg || ins.fpReg1?.value == reg)
|
||||
val reg2Read = (format?.reg2 == OperandDirection.READ || format?.reg2 == OperandDirection.READWRITE) && (ins.reg2 == reg || ins.fpReg2?.value == reg)
|
||||
val reg3Read = (format?.reg3 == OperandDirection.READ || format?.reg3 == OperandDirection.READWRITE) && (ins.reg3 == reg)
|
||||
|
||||
return reg1Read || reg2Read || reg3Read
|
||||
}
|
||||
|
||||
//private fun removeLoadrForwarding(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// Forward LOADR instructions to their original source.
|
||||
// Example:
|
||||
|
||||
@@ -76,17 +76,31 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
// remove stray loads
|
||||
val readRegs = mutableSetOf<Int>()
|
||||
val readFpRegs = mutableSetOf<Int>()
|
||||
instructions.forEach { ins ->
|
||||
val readRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val readFpRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val writeRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val writeFpRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val regsTypes = mutableMapOf<RegisterNum, IRDataType>()
|
||||
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null)
|
||||
readRegs.addAll(readRegsCounts.keys.map { it.value })
|
||||
readFpRegs.addAll(readFpRegsCounts.keys.map { it.value })
|
||||
}
|
||||
instructions.toTypedArray().forEach { ins ->
|
||||
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
|
||||
if(ins.reg1!=0) {
|
||||
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
|
||||
val reg1 = ins.reg1
|
||||
val fpReg1 = ins.fpReg1
|
||||
if(reg1!=null) {
|
||||
if(reg1 !in readRegs) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
else if(ins.fpReg1!=null) {
|
||||
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
|
||||
else if(fpReg1!=null) {
|
||||
if(fpReg1.value !in readFpRegs) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
@@ -193,10 +207,15 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
|
||||
|
||||
private val entrypointNames = setOf("p8b_main.p8s_start", "main.start")
|
||||
private val mainBlockNames = setOf("p8b_main", "main")
|
||||
|
||||
|
||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||
val entrypointSub = irprog.blocks.single { it.label=="main" }
|
||||
.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||
val entrypointSub = irprog.blocks.single { it.label in mainBlockNames }
|
||||
.children.single { it is IRSubroutine && it.label in entrypointNames }
|
||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||
reachable.add(irprog.globalInits)
|
||||
|
||||
@@ -269,7 +288,7 @@ class IRUnusedCodeRemover(
|
||||
linkedChunks += it.branchTarget!!
|
||||
}
|
||||
}
|
||||
if (chunk.label == "main.start")
|
||||
if (chunk.label in entrypointNames)
|
||||
linkedChunks += chunk
|
||||
}
|
||||
|
||||
@@ -290,6 +309,14 @@ class IRUnusedCodeRemover(
|
||||
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
|
||||
var numRemoved = 0
|
||||
irprog.foreachSub { sub ->
|
||||
// don't remove stuff in library modules or with %forceoutput
|
||||
// TODO this is needed to keep the subroutine body of sqrt_long from being wiped !??! Sounds like the bad solution to the problem: why is the *body* being cleared , making an empty sqrt_long subrotuine end up in the p8ir output (and eventually in the .asm, which crashes the resulting program)
|
||||
// TODO this causes other stuff to end up in the output IR that is not used......
|
||||
// note: this wasn't a problem on the virtual target, but it is a problem when using the expericodegen target (because on the virtual target, the VM simply executes the SQRT instruction directly without needing a library routine)
|
||||
val block = irprog.blocks.first { b -> b.children.any { it === sub } }
|
||||
if (block.library || block.options.forceOutput)
|
||||
return@foreachSub
|
||||
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
if (chunk !in linkedChunks) {
|
||||
if (chunk === sub.chunks[0]) {
|
||||
@@ -315,4 +342,4 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.ast.PtStructField
|
||||
import prog8.intermediate.*
|
||||
import kotlin.collections.flatMap
|
||||
|
||||
/**
|
||||
* Converter object for transforming SymbolTable nodes to IR symbol table nodes.
|
||||
* Groups all conversion functions together for better organization.
|
||||
*/
|
||||
private object StToIrConverter {
|
||||
private class StToIrConverter(val romable: Boolean) {
|
||||
fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
IRStStructDef(struct.scopedNameString, makeIrStructFieldsFrom(struct.fields), struct.size)
|
||||
|
||||
private fun makeIrStructFieldsFrom(fields: List<PtStructField>): List<IRStStructField> =
|
||||
fields.map { IRStStructField(it.type, it.name, it.arraySize) }
|
||||
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStSymbolicReference {
|
||||
return when(elt) {
|
||||
@@ -72,16 +76,21 @@ private object StToIrConverter {
|
||||
}
|
||||
|
||||
val name = if('.' in variable.name) variable.name else variable.scopedNameString
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty)
|
||||
val hasInit = initValue != null
|
||||
val inBss = !hasInit || (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
|
||||
variable.length,
|
||||
readonly
|
||||
)
|
||||
} else {
|
||||
val scopedName = try {
|
||||
@@ -89,7 +98,7 @@ private object StToIrConverter {
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
variable.name
|
||||
}
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length, readonly)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,10 +131,17 @@ private object StToIrConverter {
|
||||
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) ->
|
||||
fun convert(instance: StStructInstance, fields: Iterable<PtStructField>): IRStStructInstance {
|
||||
val expanded = fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
if(arraySz!=null)
|
||||
List(arraySz) { field.type.sub!! }
|
||||
else
|
||||
listOf(field.type.base)
|
||||
}
|
||||
val values = expanded.zip(instance.initialValues).map { (baseType, value) ->
|
||||
val elt = convertArrayElt(value)
|
||||
IRStructInitValue(field.first.base, elt)
|
||||
IRStructInitValue(baseType, elt)
|
||||
}
|
||||
return if('.' in instance.name)
|
||||
IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
@@ -134,33 +150,34 @@ private object StToIrConverter {
|
||||
}
|
||||
}
|
||||
|
||||
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
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(StToIrConverter.convert(it.value as StStaticVariable))
|
||||
StNodeType.MEMVAR -> st.add(StToIrConverter.convert(it.value as StMemVar))
|
||||
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 = StToIrConverter.convert(slab)
|
||||
val converted = converter.convert(slab)
|
||||
st.add(converted)
|
||||
}
|
||||
st.add(StToIrConverter.convert(constant))
|
||||
st.add(converter.convert(constant))
|
||||
}
|
||||
StNodeType.MEMORYSLAB -> {
|
||||
val slab = it.value as StMemorySlab
|
||||
val converted = StToIrConverter.convert(slab)
|
||||
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(StToIrConverter.convert(instance, struct.fields))
|
||||
st.add(converter.convert(instance, struct.fields))
|
||||
}
|
||||
StNodeType.STRUCT -> st.add(StToIrConverter.convert(it.value as StStruct))
|
||||
StNodeType.STRUCT -> st.add(converter.convert(it.value as StStruct))
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
/**
|
||||
* Prefixes all symbol names in the given program's AST with type-specific prefixes
|
||||
* (p8b_ for blocks, p8s_ for subroutines, p8v_ for variables, p8c_ for constants,
|
||||
* p8l_ for labels, p8t_ for structs).
|
||||
*
|
||||
* The AST should have simple (unscoped) names for all named nodes at this point.
|
||||
* Identifiers and function calls may already have dotted scoped names.
|
||||
* After prefixing, call [makeAllNodenamesScoped] to add scoping to the prefix names.
|
||||
*
|
||||
* Blocks with `noSymbolPrefixing=true` keep their own names intact,
|
||||
* but their references to symbols in prefixed blocks still get prefixed.
|
||||
*
|
||||
* @return A new [SymbolTable] rebuilt from the now-prefixed AST.
|
||||
*/
|
||||
fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
|
||||
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val expressionsToFixSubtype = mutableListOf<PtExpression>()
|
||||
val variablesToFixSubtype = mutableListOf<IPtVariable>()
|
||||
|
||||
fun prefixNamedNode(node: PtNamedNode) {
|
||||
when(node) {
|
||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||
is PtBlock -> node.name = "p8b_${node.name}"
|
||||
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}"
|
||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||
is PtStructDecl -> node.name = "p8t_${node.name}"
|
||||
}
|
||||
}
|
||||
|
||||
fun prefixSymbols(node: PtNode) {
|
||||
when(node) {
|
||||
is PtAsmSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
|
||||
val address = node.address
|
||||
if (address?.varbank != null) {
|
||||
address.varbank = address.varbank!!.prefixIdentifierOrThis(node, st)
|
||||
}
|
||||
}
|
||||
is PtSub -> prefixNamedNode(node)
|
||||
is PtFunctionCall -> {
|
||||
if(node.builtin) {
|
||||
if(node.name=="prog8_lib_structalloc") {
|
||||
val struct = node.type.subType!!
|
||||
if(struct is StStruct) {
|
||||
node.type.subType = st.lookup(struct.scopedNameString) as StStruct
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if (stNode.astNode?.definingBlock()?.options?.noSymbolPrefixing != true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val pexpr = node.parent as? PtBinaryExpression
|
||||
if(pexpr?.operator==".") {
|
||||
if(pexpr.left is PtArrayIndexer) {
|
||||
val arrayvar = (pexpr.left as PtArrayIndexer).variable!!
|
||||
if(arrayvar.type.subType!=null) {
|
||||
return
|
||||
} else
|
||||
throw AssemblyError("can only use deref expression on struct type")
|
||||
} else {
|
||||
TODO("handle other left operand ${pexpr.left }in dereferencing of struct fields ${node.position}")
|
||||
}
|
||||
}
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?:
|
||||
throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
is PtBlock -> prefixNamedNode(node)
|
||||
is PtConstant -> node.name = "p8c_${node.name}"
|
||||
is PtLabel -> prefixNamedNode(node)
|
||||
is PtMemMapped -> prefixNamedNode(node)
|
||||
is PtSubroutineParameter -> prefixNamedNode(node)
|
||||
is PtVariable -> {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
is PtStructDecl -> prefixNamedNode(node)
|
||||
else -> { }
|
||||
}
|
||||
|
||||
if(node is IPtVariable) {
|
||||
if(node.type.subType!=null)
|
||||
variablesToFixSubtype.add(node)
|
||||
} else if(node is PtExpression) {
|
||||
if(node.type.subType!=null)
|
||||
expressionsToFixSubtype.add(node)
|
||||
}
|
||||
|
||||
node.children.forEach { prefixSymbols(it) }
|
||||
}
|
||||
|
||||
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
|
||||
if(node is PtFunctionCall && !node.builtin) {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
else if (node is PtIdentifier) {
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
if (!block.options.noSymbolPrefixing) {
|
||||
prefixSymbols(block)
|
||||
} else {
|
||||
maybePrefixFunctionCallsAndIdentifierReferences(block)
|
||||
}
|
||||
}
|
||||
|
||||
nodesToPrefix.forEach { (parent, index) ->
|
||||
when(val node = parent.children[index]) {
|
||||
is PtIdentifier -> parent.setChild(index, node.prefixIdentifierOrThis(parent, st))
|
||||
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
||||
is PtVariable -> parent.setChild(index, node.prefixVariableOrThis(parent, st))
|
||||
else -> throw AssemblyError("weird node to prefix $node")
|
||||
}
|
||||
}
|
||||
|
||||
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
|
||||
functionCallsToPrefix.reversed().forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
if(node is PtFunctionCall) {
|
||||
val prefixedName = PtIdentifier(node.name, DataType.UNDEFINED, node.position).prefixIdentifierOrThis(parent, st)
|
||||
val prefixedNode = node.withNewName(prefixedName.name)
|
||||
parent.setChild(index, prefixedNode)
|
||||
} else {
|
||||
throw AssemblyError("expected PtFunctionCall")
|
||||
}
|
||||
}
|
||||
|
||||
val updatedSt = SymbolTableMaker(program, options).make()
|
||||
|
||||
fun findSubtypeReplacement(sub: ISubType): StStruct? {
|
||||
if(updatedSt.lookup(sub.scopedNameString)!=null)
|
||||
return null
|
||||
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
|
||||
|
||||
val prefixed = ArrayDeque<String>()
|
||||
var node: StNode = old
|
||||
while(node.type!= StNodeType.GLOBAL) {
|
||||
val typeChar = typePrefixChar(node.type)
|
||||
prefixed.addFirst("p8${typeChar}_${node.name}")
|
||||
node=node.parent
|
||||
}
|
||||
return updatedSt.lookup(prefixed.joinToString(".")) as StStruct
|
||||
}
|
||||
|
||||
expressionsToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
variablesToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
|
||||
return updatedSt
|
||||
}
|
||||
|
||||
|
||||
internal fun prefixScopedName(name: String, type: Char): String {
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(GENERATED_LABEL_PREFIX))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
|
||||
|
||||
internal fun typePrefixChar(targetNt: StNodeType): Char {
|
||||
return when(targetNt) {
|
||||
StNodeType.BLOCK -> 'b'
|
||||
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
|
||||
StNodeType.LABEL -> 'l'
|
||||
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
|
||||
StNodeType.CONSTANT -> 'c'
|
||||
StNodeType.BUILTINFUNC -> 's'
|
||||
StNodeType.MEMORYSLAB -> 'v'
|
||||
StNodeType.STRUCT -> 't'
|
||||
StNodeType.STRUCTINSTANCE -> 'i'
|
||||
else -> '?'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun PtVariable.prefixVariableOrThis(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
name = prefixScopedName(name, 'v')
|
||||
if(value==null)
|
||||
return this
|
||||
|
||||
val arrayValue = value as? PtArray
|
||||
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber && it !is PtBool } ) {
|
||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||
arrayValue.children.forEach { elt ->
|
||||
when(elt) {
|
||||
is PtBool -> newValue.add(elt)
|
||||
is PtNumber -> newValue.add(elt)
|
||||
is PtConstant -> newValue.add(elt)
|
||||
is PtAddressOf -> {
|
||||
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
newValue.add(elt)
|
||||
else {
|
||||
val newAddr = PtAddressOf(elt.type, false, elt.position)
|
||||
newAddr.add(elt.identifier!!.prefixIdentifierOrThis(newAddr, st))
|
||||
if (elt.arrayIndexExpr != null)
|
||||
newAddr.add(elt.arrayIndexExpr!!)
|
||||
newAddr.parent = arrayValue
|
||||
newValue.add(newAddr)
|
||||
}
|
||||
}
|
||||
is PtFunctionCall if elt.builtin -> {
|
||||
if (elt.name != "prog8_lib_structalloc")
|
||||
throw AssemblyError("weird array value element $elt")
|
||||
else
|
||||
newValue.add(elt)
|
||||
}
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
else this
|
||||
}
|
||||
|
||||
|
||||
private fun PtIdentifier.prefixIdentifierOrThis(parent: PtNode, st: SymbolTable): PtIdentifier {
|
||||
val targetNt: StNodeType
|
||||
val target = st.lookup(name)
|
||||
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
|
||||
if(target==null) {
|
||||
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
|
||||
val target2 = st.lookup(name.dropLast(4))
|
||||
if(target2?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
targetNt = target2!!.type
|
||||
} else {
|
||||
targetNt = StNodeType.STATICVAR
|
||||
}
|
||||
} else {
|
||||
targetNt = target.type
|
||||
}
|
||||
|
||||
val prefixType = typePrefixChar(targetNt)
|
||||
val newName = prefixScopedName(name, prefixType)
|
||||
val node = PtIdentifier(newName, type, position)
|
||||
node.parent = parent
|
||||
return node
|
||||
}
|
||||
|
||||
|
||||
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
|
||||
val call = PtFunctionCall(name, builtin, hasNoSideEffects, returntypes, position)
|
||||
call.addAll(children)
|
||||
call.children.forEach { it.parent = call }
|
||||
call.parent = parent
|
||||
return call
|
||||
}
|
||||
@@ -10,14 +10,16 @@ import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
import prog8.intermediate.IRProgram
|
||||
|
||||
class VmCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
class VmCodeGen(val retainSSA: Boolean,
|
||||
private val preassignedCallSiteIds: Map<String, UByte> = emptyMap()
|
||||
): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram {
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA, preassignedCallSiteIds)
|
||||
val irProgram = irCodeGen.generate()
|
||||
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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.*
|
||||
@@ -8,9 +11,9 @@ import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
class TestIRPeepholeOpt: FunSpec({
|
||||
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||
require(chunks.first().label=="main.start")
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY)
|
||||
require(chunks.first().label=="p8b_main.p8s_start")
|
||||
val block = IRBlock("p8b_main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val sub = IRSubroutine("p8b_main.p8s_start", emptyList(), emptyList(), Position.DUMMY)
|
||||
chunks.forEach { sub += it }
|
||||
block += sub
|
||||
val target = VMTarget()
|
||||
@@ -28,7 +31,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
|
||||
val chunk = IRCodeChunk("main.start", null)
|
||||
val chunk = IRCodeChunk("p8b_main.p8s_start", null)
|
||||
instructions.forEach { chunk += it }
|
||||
return makeIRProgram(listOf(chunk))
|
||||
}
|
||||
@@ -48,7 +51,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove jmp to label below but keep labels") {
|
||||
val c1 = IRCodeChunk("main.start", null)
|
||||
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
|
||||
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
|
||||
val c2 = IRCodeChunk("label", null)
|
||||
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
|
||||
@@ -65,7 +68,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val chunks = irProg.chunks()
|
||||
chunks.size shouldBe 4
|
||||
chunks[0].label shouldBe "main.start"
|
||||
chunks[0].label shouldBe "p8b_main.p8s_start"
|
||||
chunks[1].label shouldBe "label"
|
||||
chunks[2].label shouldBe "label2"
|
||||
chunks[3].label shouldBe "label3"
|
||||
@@ -289,4 +292,71 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
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
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,318 @@
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.codegen.intermediate.prefixScopedName
|
||||
import prog8.codegen.intermediate.prefixSymbols
|
||||
import prog8.codegen.intermediate.typePrefixChar
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
|
||||
class TestSymbolPrefixer : FunSpec({
|
||||
|
||||
// --- prefixScopedName tests ---
|
||||
|
||||
test("prefixScopedName single name with type char") {
|
||||
prefixScopedName("main", 'b') shouldBe "p8b_main"
|
||||
prefixScopedName("start", 's') shouldBe "p8s_start"
|
||||
prefixScopedName("x", 'v') shouldBe "p8v_x"
|
||||
prefixScopedName("SIZE", 'c') shouldBe "p8c_SIZE"
|
||||
prefixScopedName("label", 'l') shouldBe "p8l_label"
|
||||
prefixScopedName("Point", 't') shouldBe "p8t_Point"
|
||||
}
|
||||
|
||||
test("prefixScopedName dotted scoped name") {
|
||||
prefixScopedName("main.start", 's') shouldBe "p8b_main.p8s_start"
|
||||
prefixScopedName("main.start.x", 'v') shouldBe "p8b_main.p8s_start.p8v_x"
|
||||
prefixScopedName("main.start.looplabel", 'l') shouldBe "p8b_main.p8s_start.p8l_looplabel"
|
||||
prefixScopedName("main.SIZE", 'c') shouldBe "p8b_main.p8c_SIZE"
|
||||
prefixScopedName("lib.module.func", 's') shouldBe "p8b_lib.p8s_module.p8s_func"
|
||||
}
|
||||
|
||||
test("prefixScopedName generated label is not prefixed") {
|
||||
val genLabel = "p8_label_gen_42"
|
||||
prefixScopedName(genLabel, 's') shouldBe genLabel
|
||||
prefixScopedName("main.start.$genLabel", 'v') shouldBe "p8b_main.p8s_start.$genLabel"
|
||||
}
|
||||
|
||||
// --- typePrefixChar tests ---
|
||||
|
||||
test("typePrefixChar maps all StNodeTypes correctly") {
|
||||
typePrefixChar(StNodeType.BLOCK) shouldBe 'b'
|
||||
typePrefixChar(StNodeType.SUBROUTINE) shouldBe 's'
|
||||
typePrefixChar(StNodeType.EXTSUB) shouldBe 's'
|
||||
typePrefixChar(StNodeType.LABEL) shouldBe 'l'
|
||||
typePrefixChar(StNodeType.STATICVAR) shouldBe 'v'
|
||||
typePrefixChar(StNodeType.MEMVAR) shouldBe 'v'
|
||||
typePrefixChar(StNodeType.CONSTANT) shouldBe 'c'
|
||||
typePrefixChar(StNodeType.BUILTINFUNC) shouldBe 's'
|
||||
typePrefixChar(StNodeType.MEMORYSLAB) shouldBe 'v'
|
||||
typePrefixChar(StNodeType.STRUCT) shouldBe 't'
|
||||
typePrefixChar(StNodeType.STRUCTINSTANCE) shouldBe 'i'
|
||||
}
|
||||
|
||||
// --- full program prefixing tests ---
|
||||
|
||||
test("prefixSymbols prefixes a simple program") {
|
||||
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)
|
||||
val varX = PtVariable("x", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY)
|
||||
sub.add(varX)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
|
||||
val updatedSt = prefixSymbols(program, options, st)
|
||||
|
||||
// AST names should be prefixed
|
||||
block.name shouldBe "p8b_main"
|
||||
sub.name shouldBe "p8s_start"
|
||||
varX.name shouldBe "p8v_x"
|
||||
|
||||
// symbol table should have prefixed entries
|
||||
updatedSt.lookup("p8b_main") shouldNotBe null
|
||||
updatedSt.lookup("p8b_main.p8s_start") shouldNotBe null
|
||||
updatedSt.lookup("p8b_main.p8s_start.p8v_x") shouldNotBe null
|
||||
}
|
||||
|
||||
test("prefixSymbols handles subroutine parameters") {
|
||||
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)
|
||||
val param = PtSubroutineParameter("arg", DataType.UBYTE, null, Position.DUMMY)
|
||||
sub.add(param)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
param.name shouldBe "p8v_arg"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles constants") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val constant = PtConstant("SIZE", DataType.UWORD, 100.0, null, Position.DUMMY)
|
||||
block.add(constant)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
constant.name shouldBe "p8c_SIZE"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles labels") {
|
||||
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)
|
||||
val label = PtLabel("mylabel", Position.DUMMY)
|
||||
sub.add(label)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
label.name shouldBe "p8l_mylabel"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles generated labels without prefixing them") {
|
||||
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)
|
||||
val genLabel = PtLabel("p8_label_gen_42", Position.DUMMY)
|
||||
sub.add(genLabel)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
genLabel.name shouldBe "p8_label_gen_42"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles struct declarations") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val struct = PtStructDecl("Point", listOf(
|
||||
PtStructField(DataType.UBYTE, "x", null),
|
||||
PtStructField(DataType.UBYTE, "y", null)
|
||||
), Position.DUMMY)
|
||||
block.add(struct)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
struct.name shouldBe "p8t_Point"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles asmsub") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val asmsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(asmsub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
asmsub.name shouldBe "p8s_routine"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles memmapped variables") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val memvar = PtMemMapped("reg", DataType.UBYTE, 0xd020u, null, Position.DUMMY)
|
||||
block.add(memvar)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
memvar.name shouldBe "p8v_reg"
|
||||
}
|
||||
|
||||
// --- noSymbolPrefixing tests ---
|
||||
|
||||
test("noSymbolPrefixing blocks keep their names intact") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val libOptions = PtBlock.Options(noSymbolPrefixing = true)
|
||||
val libBlock = PtBlock("lib", false, SourceCode.Generated("test"), libOptions, Position.DUMMY)
|
||||
val libSub = PtSub("func", emptyList(), emptyList(), Position.DUMMY)
|
||||
libBlock.add(libSub)
|
||||
program.add(libBlock)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
libBlock.name shouldBe "lib" // no prefix
|
||||
libSub.name shouldBe "func" // no prefix
|
||||
}
|
||||
|
||||
test("references from user block to noSymbolPrefixing block are not prefixed") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
|
||||
// Library block with no symbol prefixing
|
||||
val libOptions = PtBlock.Options(noSymbolPrefixing = true)
|
||||
val libBlock = PtBlock("lib", false, SourceCode.Generated("test"), libOptions, Position.DUMMY)
|
||||
val libSub = PtSub("print", emptyList(), emptyList(), Position.DUMMY)
|
||||
libBlock.add(libSub)
|
||||
program.add(libBlock)
|
||||
|
||||
// User block (regular)
|
||||
val userBlock = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val userSub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
userBlock.add(userSub)
|
||||
program.add(userBlock)
|
||||
|
||||
val options = basicTestOptions()
|
||||
|
||||
// Now add a function call referencing the library sub - this must happen AFTER making the st
|
||||
// because the st is needed for lookup during prefixing
|
||||
val call = PtFunctionCall("lib.print", false, false, emptyArray(), Position.DUMMY)
|
||||
userSub.add(call)
|
||||
|
||||
// Rebuild st after adding the call
|
||||
val st2 = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st2)
|
||||
|
||||
// The call site should NOT be prefixed (lib has noSymbolPrefixing)
|
||||
call.name shouldBe "lib.print"
|
||||
}
|
||||
|
||||
test("references from noSymbolPrefixing block to regular block get prefixed") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
|
||||
// Regular user block
|
||||
val userBlock = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val userSub = PtSub("helper", emptyList(), emptyList(), Position.DUMMY)
|
||||
userBlock.add(userSub)
|
||||
program.add(userBlock)
|
||||
|
||||
// Library block with no symbol prefixing
|
||||
val libOptions = PtBlock.Options(noSymbolPrefixing = true)
|
||||
val libBlock = PtBlock("lib", false, SourceCode.Generated("test"), libOptions, Position.DUMMY)
|
||||
val libSub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val call = PtFunctionCall("main.helper", false, false, emptyArray(), Position.DUMMY)
|
||||
libSub.add(call)
|
||||
libBlock.add(libSub)
|
||||
program.add(libBlock)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
// The call site from lib to main should get prefixed (new node, get from parent)
|
||||
val newCall = libSub.children.find { it is PtFunctionCall } as PtFunctionCall
|
||||
newCall.name shouldBe "p8b_main.p8s_helper"
|
||||
}
|
||||
|
||||
test("identifier references are prefixed according to their target type") {
|
||||
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)
|
||||
val varX = PtVariable("x", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY)
|
||||
sub.add(varX)
|
||||
|
||||
// Add an identifier reference to the variable
|
||||
val idRef = PtIdentifier("main.start.x", DataType.UBYTE, Position.DUMMY)
|
||||
sub.add(idRef)
|
||||
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
// The identifier reference should have been prefixed (new node, get from parent)
|
||||
val newIdRef = sub.children.find { it is PtIdentifier } as PtIdentifier
|
||||
newIdRef.name shouldBe "p8b_main.p8s_start.p8v_x"
|
||||
}
|
||||
|
||||
test("prefixSymbols exception for unknown identifier") {
|
||||
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)
|
||||
val idRef = PtIdentifier("nonexistent", DataType.UBYTE, Position.DUMMY)
|
||||
sub.add(idRef)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
|
||||
shouldThrow<AssemblyError> {
|
||||
prefixSymbols(program, options, st)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun basicTestOptions(): CompilationOptions {
|
||||
val target = prog8.code.target.VMTarget()
|
||||
return CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(ZeropageType.DONTUSE)
|
||||
.floats(true)
|
||||
.compilerVersion("99.99")
|
||||
.build()
|
||||
}
|
||||
@@ -628,11 +628,40 @@ class TestVmCodeGen: FunSpec({
|
||||
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
|
||||
val irSub = result.irProgram.blocks.first().children.filterIsInstance<IRSubroutine>().single()
|
||||
val irChunks = irSub.chunks
|
||||
irChunks.size shouldBe 1
|
||||
irChunks[0].instructions.size shouldBe 1
|
||||
val callInstr = irChunks[0].instructions[0]
|
||||
callInstr.opcode shouldBe Opcode.CALL
|
||||
callInstr.address shouldBe 0x5000u.toAddress()
|
||||
}
|
||||
|
||||
test("ir codegen for target 'virtual' produces no prefixed names") {
|
||||
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("x", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY))
|
||||
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 irProg = result.irProgram
|
||||
|
||||
// Block label should not be prefixed
|
||||
val irBlock = irProg.blocks.first()
|
||||
irBlock.label shouldBe "main"
|
||||
|
||||
// Subroutine label should be scoped but not prefixed
|
||||
val irSub = irBlock.children.single() as IRSubroutine
|
||||
irSub.label shouldBe "main.start"
|
||||
|
||||
// Variable in IR symbol table should not have prefixed scoped name
|
||||
val irVar = irProg.st.allVariables().single()
|
||||
irVar.name shouldBe "main.start.x"
|
||||
}
|
||||
})
|
||||
@@ -1,16 +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="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -66,21 +66,6 @@ class ConstExprEvaluator {
|
||||
return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
@@ -90,7 +75,7 @@ class ConstExprEvaluator {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() or right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||
@@ -105,12 +90,27 @@ class ConstExprEvaluator {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() and right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
}
|
||||
|
||||
private fun logicalAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral =
|
||||
NumericLiteral.fromBoolean(left.asBooleanValue and right.asBooleanValue, left.position)
|
||||
|
||||
@@ -355,33 +355,10 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun evalString(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
|
||||
return when(func.target.nameInSource[1]) {
|
||||
"isdigit" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 48..57, func.position)
|
||||
}
|
||||
"isupper" -> {
|
||||
// shifted petscii has 2 ranges that contain the upper case letters... 97-122 and 193-218
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 97..122 || char in 193..218, func.position)
|
||||
}
|
||||
"islower" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 65..90, func.position)
|
||||
}
|
||||
"isletter" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 65..90 || char in 97..122 || char in 193..218, func.position)
|
||||
}
|
||||
"isspace" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in setOf(32, 13, 9, 10, 141, 160), func.position)
|
||||
}
|
||||
"isprint" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 32..127 || char>=160, func.position)
|
||||
}
|
||||
else -> null
|
||||
return if (func.target.nameInSource[1] == "isdigit") {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 48..57, func.position)
|
||||
}
|
||||
else null
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,11 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<AstModification> {
|
||||
// @( &thing ) --> thing (but only if thing is a byte type!)
|
||||
// @( &thing ) --> thing (but only if thing is unsigned byte or bool, this prevents sign extension errors)
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null) {
|
||||
if(addrOf.identifier?.inferType(program)?.isBytes==true)
|
||||
val it = addrOf.identifier?.inferType(program)
|
||||
if(it?.isUnsignedByte==true || it?.isBool==true)
|
||||
return listOf(AstReplaceNode(memread, addrOf.identifier!!, parent))
|
||||
}
|
||||
return noModifications
|
||||
|
||||
@@ -32,6 +32,12 @@ class VarConstantValueTypeAdjuster(
|
||||
|
||||
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)
|
||||
@@ -47,95 +53,95 @@ 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 || decl.datatype.isPointer))
|
||||
return noModifications
|
||||
if (decl.value != null && !isConstantInitializer(decl.value!!))
|
||||
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 as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
|
||||
}
|
||||
|
||||
// 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 (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 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) {
|
||||
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
|
||||
}
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
val declValue = if (decl.value != null) getConstantInitializer(decl.value!!) else null
|
||||
if (declValue != null && 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(
|
||||
AstReplaceNode(decl, const, parent)
|
||||
)
|
||||
}
|
||||
|
||||
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 && isConstantInitializer(singleAssignment.value) && 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
|
||||
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(singleAssignment.value)
|
||||
.value(declValue)
|
||||
.build()
|
||||
decl.value = null
|
||||
return listOf(
|
||||
AstReplaceNode(decl, const, parent),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
AstReplaceNode(decl, const, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
} 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
|
||||
@@ -148,10 +154,11 @@ class VarConstantValueTypeAdjuster(
|
||||
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
|
||||
|| 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
|
||||
}
|
||||
@@ -340,6 +347,12 @@ internal class ConstantIdentifierReplacer(
|
||||
override fun before(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
|
||||
val constValue = addressOf.constValue(program)
|
||||
if(constValue!=null) {
|
||||
// don't replace &constVar (without array index); leave it for AstChecker to reject as invalid address-of.
|
||||
// But still allow &constArray[idx] (those are valid address-of operations).
|
||||
val target = addressOf.identifier?.targetVarDecl()
|
||||
if(target?.type==VarDeclType.CONST && addressOf.arrayIndex==null) {
|
||||
return noModifications
|
||||
}
|
||||
return listOf(AstReplaceNode(addressOf, constValue, parent))
|
||||
}
|
||||
return noModifications
|
||||
@@ -348,7 +361,7 @@ internal class ConstantIdentifierReplacer(
|
||||
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 || identifier.parent is AddressOf)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if (forloop == null)
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlin.math.floor
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter, private val options: CompilationOptions) : AstWalker() {
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
|
||||
val mods = mutableListOf<AstModification>()
|
||||
|
||||
@@ -177,7 +177,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
// X <= Y-1 ---> X<Y , X >= Y+1 ---> X>Y
|
||||
if(leftDt.isInteger && rightDt.isInteger) {
|
||||
// Only for signed integers to avoid unsigned wraparound issues.
|
||||
if(leftDt.isSignedInteger && rightDt.isSignedInteger) {
|
||||
val rightExpr = expr.right as? BinaryExpression
|
||||
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
|
||||
if (expr.operator == "<=" && rightExpr.operator == "-") {
|
||||
@@ -463,12 +464,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
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)
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -476,12 +477,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
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)
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -489,12 +490,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> constFalse
|
||||
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
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> expr.right
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -679,23 +680,23 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return null
|
||||
// absorption laws: a or (a and b) --> a, a and (a or b) --> a
|
||||
if(expr.operator=="or" && rightB.operator=="and") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && rightB.operator=="or") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="or" && rightB.operator=="or") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// a or (a or b) -> a or b
|
||||
return expr.right
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && rightB.operator=="and") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// a and (a and b) -> a and b
|
||||
return expr.right
|
||||
}
|
||||
@@ -707,23 +708,23 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return null
|
||||
// absorption laws: (a and b) or a --> a, (a or b) and a --> a
|
||||
if(expr.operator=="or" && leftB.operator=="and") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return expr.right
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && leftB.operator=="or") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return expr.right
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="or" && leftB.operator=="or") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
// (a or b) or a -> a or b
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && leftB.operator=="and") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
// (a and b) or a -> a and b
|
||||
return expr.left
|
||||
}
|
||||
@@ -844,7 +845,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
if(expr.left.isSameAs(expr.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// optimize X+X into X *2
|
||||
expr.operator = "*"
|
||||
expr.right = NumericLiteral.optimalInteger(2, expr.right.position)
|
||||
@@ -879,7 +880,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
if(expr.left.isSameAs(expr.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// optimize X-X into 0
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
@@ -924,12 +925,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val cv = rightVal?.number?.toInt()?.toDouble()
|
||||
when (expr.operator) {
|
||||
"%" -> {
|
||||
if (cv == 1.0) {
|
||||
if (cv == 1.0 && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteral(idt.getOrUndef().base, 0.0, expr.position)
|
||||
} else if (cv in powersOfTwoFloat) {
|
||||
} else if (cv in powersOfTwoFloat && expr.left.inferType(program).getOrUndef().isUnsignedInteger) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
@@ -969,7 +970,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
256.0 -> {
|
||||
when(leftDt.base) {
|
||||
BaseDataType.UBYTE -> return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)
|
||||
BaseDataType.UBYTE -> if (!expr.left.hasSideEffects(options.compTarget)) return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position) else return null
|
||||
BaseDataType.BYTE -> return null // is either 0 or -1 we cannot tell here
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> {
|
||||
// just use: msb(value) as type
|
||||
@@ -992,11 +993,11 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
if (leftDt.isUnsignedByte) {
|
||||
if (abs(rightConst.number) >= 256.0) {
|
||||
if (abs(rightConst.number) >= 256.0 && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
} else if (leftDt.isUnsignedWord) {
|
||||
if (abs(rightConst.number) >= 65536.0) {
|
||||
if (abs(rightConst.number) >= 65536.0 && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
@@ -1006,8 +1007,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteral(leftVal.type, 0.0, expr.position)
|
||||
// 0 (only if not float and no side effects)
|
||||
if (!expr.inferType(program).isFloat && !expr.right.hasSideEffects(options.compTarget))
|
||||
return NumericLiteral(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1030,8 +1032,11 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return PrefixExpression("-", leftValue, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteral(rightConst.type, 0.0, expr.position)
|
||||
// 0 (only if not float and no side effects)
|
||||
if (!expr.inferType(program).isFloat && !leftValue.hasSideEffects(options.compTarget))
|
||||
return NumericLiteral(rightConst.type, 0.0, expr.position)
|
||||
else
|
||||
return null
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.ParentSentinel
|
||||
import prog8.ast.Program
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
@@ -25,6 +26,7 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
|
||||
optimizer.visit(this)
|
||||
var tries=0
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0 && tries++ < 100000) {
|
||||
optimizer.linkAffectedParents(namespace)
|
||||
optimizer.visit(this)
|
||||
}
|
||||
require(tries<100000) { "endless loop in constantfold" }
|
||||
@@ -32,13 +34,14 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
|
||||
if (errors.noErrors()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
replacer.linkAffectedParents(namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.noErrors())
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
namespace.linkParents(ParentSentinel) // re-link in final configuration
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +52,7 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
val optimizer = StatementOptimizer(this, errors, functions, options)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
optimizer.linkAffectedParents(this.namespace)
|
||||
|
||||
return optimizationCount
|
||||
}
|
||||
@@ -61,11 +63,15 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
|
||||
|
||||
val inliner = Inliner(this, options)
|
||||
inliner.visit(this)
|
||||
return inliner.applyModifications()
|
||||
val mods = inliner.applyModifications()
|
||||
inliner.linkAffectedParents(namespace)
|
||||
return mods
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors)
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors, options)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
val mods = opti.applyModifications()
|
||||
opti.linkAffectedParents(namespace)
|
||||
return mods
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.*
|
||||
@@ -14,10 +11,26 @@ import prog8.code.target.VMTarget
|
||||
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.isEmpty()
|
||||
|
||||
/**
|
||||
* The Inliner performs subroutine inlining on the AST.
|
||||
*
|
||||
* It supports both manual inlining (subroutines marked with the 'inline' keyword)
|
||||
* and automatic inlining for very simple subroutines.
|
||||
*
|
||||
* Current restrictions and considerations:
|
||||
* - Automatic inlining is currently restricted to subroutines with ZERO parameters.
|
||||
* - Inlining subroutines with parameters is complicated because it requires careful
|
||||
* parameter substitution to avoid side effects and name collisions.
|
||||
* - Handling parameterized inlining can easily lead to infinite loops or recursion
|
||||
* in the optimizer if not done VERY CAREFULLY (e.g. self-referential subroutines).
|
||||
* - Subroutines must have a single functional statement in their body to be auto-inlined.
|
||||
* - Multi-return value subroutines are not yet supported for auto-inlining.
|
||||
*/
|
||||
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
|
||||
|
||||
class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
inner class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
private val modifications = mutableListOf<AstModification>()
|
||||
private val visitedSubroutines = java.util.Collections.newSetFromMap(java.util.IdentityHashMap<Subroutine, Boolean>())
|
||||
|
||||
init {
|
||||
visit(program)
|
||||
@@ -25,30 +38,50 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
modifications.clear()
|
||||
}
|
||||
|
||||
private fun isRecursive(sub: Subroutine): Boolean {
|
||||
var recursive = false
|
||||
val visitor = object : IAstVisitor {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
if (!recursive && functionCallExpr.target.targetSubroutine() == sub) recursive = true
|
||||
if (!recursive) super.visit(functionCallExpr)
|
||||
}
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
if (!recursive && functionCallStatement.target.targetSubroutine() == sub) recursive = true
|
||||
if (!recursive) super.visit(functionCallStatement)
|
||||
}
|
||||
}
|
||||
sub.statements.forEach { if (!recursive) it.accept(visitor) }
|
||||
return recursive
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if (!visitedSubroutines.add(subroutine)) return
|
||||
|
||||
fun isBodyInlineable(stmt: Return): Boolean {
|
||||
if (stmt.values.isEmpty())
|
||||
return true
|
||||
|
||||
return stmt.values.all { value ->
|
||||
when (value) {
|
||||
is NumericLiteral -> true
|
||||
is IdentifierReference -> {
|
||||
makeFullyScoped(value)
|
||||
true
|
||||
}
|
||||
is FunctionCallExpression -> {
|
||||
if (value.args.size <= 1 && value.args.all {
|
||||
it is NumericLiteral || it is IdentifierReference
|
||||
}) {
|
||||
makeFullyScoped(value)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
if (isSimpleReturnExpression(value)) {
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
|
||||
makeFullyScoped(identifier)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
value.accept(walker, stmt)
|
||||
true
|
||||
} else if (value is FunctionCallExpression) {
|
||||
if (value.args.size <= 1 && value.args.all {
|
||||
it is NumericLiteral || it is IdentifierReference
|
||||
}) {
|
||||
makeFullyScoped(value)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,28 +139,16 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
stmt.target is NumericLiteral
|
||||
}
|
||||
|
||||
if (!subroutine.isAsmSubroutine) {
|
||||
// NOTE: We allow subroutines with ANY number of parameters to be considered for inlining.
|
||||
// For void calls where the body doesn't use the parameters, inlining is trivial -
|
||||
// we just remove the call entirely (no parameter substitution needed).
|
||||
// For calls that return values or use parameters in the body, we need simple arguments
|
||||
// (NumericLiteral or IdentifierReference) for parameter substitution to work.
|
||||
// See canInlineAtCallSite() for the argument simplicity check.
|
||||
|
||||
val containsSubsOrVariables = subroutine.statements.any {
|
||||
it is Subroutine || (it is VarDecl && it.origin != VarDeclOrigin.SUBROUTINEPARAM)
|
||||
}
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.size <= 1) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is Subroutine || (it is VarDecl && it.origin != VarDeclOrigin.SUBROUTINEPARAM) }
|
||||
if (!containsSubsOrVariables) {
|
||||
// For subroutines with parameters, there will be a VarDecl(SUBROUTINEPARAM) per parameter
|
||||
// plus the body statement(s)
|
||||
val expectedMaxStmts = subroutine.parameters.size + 1 // param VarDecls + 1 body statement
|
||||
val hasOnlyBodyStatements = subroutine.statements.size == expectedMaxStmts ||
|
||||
(subroutine.statements.size == expectedMaxStmts + 1 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true)
|
||||
val functionalStatements = subroutine.statements.filter { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val hasOnlyBodyStatements = functionalStatements.size == 1 ||
|
||||
(functionalStatements.size == 2 && functionalStatements.lastOrNull()?.let(::isEmptyReturn) == true)
|
||||
|
||||
if (hasOnlyBodyStatements) {
|
||||
if (subroutine !== program.entrypoint) {
|
||||
// Find the first non-parameter statement (the actual body)
|
||||
val bodyStmt = subroutine.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val bodyStmt = functionalStatements.firstOrNull()
|
||||
val isAutoInlineable =
|
||||
when (val stmt = bodyStmt) {
|
||||
is Return -> isBodyInlineable(stmt)
|
||||
@@ -136,16 +157,15 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
is Jump -> isBodyInlineable(stmt)
|
||||
else -> false
|
||||
}
|
||||
if (isAutoInlineable)
|
||||
if (isAutoInlineable && !isRecursive(subroutine))
|
||||
subroutine.inline = true
|
||||
}
|
||||
}
|
||||
|
||||
if (subroutine.inline && subroutine.statements.size > 1) {
|
||||
if (subroutine.inline && functionalStatements.size > 1) {
|
||||
// Remove trailing return if it's empty and there's another body statement
|
||||
val bodyStmts = subroutine.statements.filter { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
if (bodyStmts.size > 1 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true) {
|
||||
subroutine.statements.removeLast()
|
||||
if (functionalStatements.lastOrNull()?.let(::isEmptyReturn) == true) {
|
||||
subroutine.statements.remove(functionalStatements.last())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +174,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(identifier: IdentifierReference) {
|
||||
if (identifier.nameInSource.singleOrNull() in program.builtinFunctions.names) return
|
||||
identifier.targetStatement()?.let { target ->
|
||||
if (target is VarDecl && target.origin == VarDeclOrigin.SUBROUTINEPARAM) return@let
|
||||
val scoped = (target as INamedStatement).scopedName
|
||||
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
||||
modifications += AstReplaceNode(identifier, scopedIdent, identifier.parent)
|
||||
@@ -223,17 +245,19 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
private fun canInlineAtCallSiteWithReason(sub: Subroutine, fcall: IFunctionCall): Pair<Boolean, String> {
|
||||
if (!sub.inline)
|
||||
return false to "subroutine not marked as inlineable"
|
||||
|
||||
// Check parameter argument complexity
|
||||
if (sub.parameters.isNotEmpty()) {
|
||||
// Only allow inlining for void statement calls, not expression context
|
||||
if (fcall !is FunctionCallStatement || !fcall.void)
|
||||
return false to "expression context calls with parameters not supported yet"
|
||||
|
||||
// Only inline if all arguments are simple (literals or identifiers)
|
||||
if (!fcall.args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
val complexArgs = fcall.args.filterNot { it is NumericLiteral || it is IdentifierReference }
|
||||
return false to "complex arguments (${complexArgs.size}x): ${complexArgs.map { it::class.simpleName }.joinToString(", ")}"
|
||||
|
||||
if (sub.isAsmSubroutine && sub.parameters.isNotEmpty()) {
|
||||
return false to "asmsub with parameters cannot be inlined"
|
||||
}
|
||||
|
||||
if (sub.parameters.size > 1) {
|
||||
return false to "subroutines with more than 1 parameter cannot be inlined in this compiler version"
|
||||
}
|
||||
|
||||
if (sub.parameters.size == 1) {
|
||||
val arg = fcall.args.first()
|
||||
if (!arg.isSimple) {
|
||||
return false to "argument is too complex to inline (might have side effects)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +303,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
|
||||
fun inlineFunctionBody(toInline: Return): Iterable<AstModification> {
|
||||
// call site is an expression, so we have to have a Return here in the inlined sub to provide the values
|
||||
// note that we don't have to process any args, because we are currently only inlining parameterless subroutines.
|
||||
return if(toInline.values.size==1 && functionCallExpr!==toInline.values[0]) {
|
||||
// println(">>> INLINER: INLINED expression '${sub.name}' at ${functionCallExpr.position} (return value substituted)")
|
||||
sub.hasBeenInlined =true
|
||||
listOf(AstReplaceNode(functionCallExpr, toInline.values[0].copy(), parent))
|
||||
}
|
||||
else
|
||||
return if (toInline.values.size == 1 && functionCallExpr !== toInline.values[0]) {
|
||||
sub.hasBeenInlined = true
|
||||
val substitutedReturn = substituteParameters(sub, functionCallExpr, toInline.values[0]) as Expression
|
||||
listOf(AstReplaceNode(functionCallExpr, substitutedReturn, parent))
|
||||
} else
|
||||
noModifications
|
||||
}
|
||||
|
||||
@@ -303,17 +325,17 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
private fun possiblyInlineFunctioncallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<AstModification> {
|
||||
|
||||
fun possiblyShortCircuitFunctionCall(toInline: Return): Iterable<AstModification> {
|
||||
val functionCalls = toInline.values.filterIsInstance<FunctionCallExpression>()
|
||||
// Substitute parameters in the return values first
|
||||
val substitutedReturn = substituteParameters(sub, origNode as IFunctionCall, toInline) as Return
|
||||
val functionCalls = substitutedReturn.values.filterIsInstance<FunctionCallExpression>()
|
||||
|
||||
if (functionCalls.isEmpty()) {
|
||||
// No function calls in the return values - the void call has no side effects and can be removed.
|
||||
// println(">>> INLINER: INLINED void call to '${sub.name}' at ${functionCallStatement.position} (removed, no side effects)")
|
||||
sub.hasBeenInlined = true
|
||||
return listOf(AstRemove(origNode as Statement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
// There are function calls in the return values - convert each to a void statement.
|
||||
// println(">>> INLINER: INLINED void call to '${sub.name}' at ${functionCallStatement.position} (converted ${functionCalls.size} inner call(s) to void)")
|
||||
sub.hasBeenInlined = true
|
||||
|
||||
val voidStatements: MutableList<Statement> = functionCalls.map { fcall ->
|
||||
@@ -331,22 +353,16 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
fun possiblyInlineFunctionBody(toInline: Statement): Iterable<AstModification> {
|
||||
// For void calls with parameters, we already verified in canInlineAtCallSiteWithReason()
|
||||
// that the parameter is unused in the body (so no substitution needed).
|
||||
// For expression context calls with parameters, those are rejected in canInlineAtCallSiteWithReason()
|
||||
// so we never reach here.
|
||||
// Just copy the statement - parameters are unused so no substitution needed.
|
||||
val inlinedStatement = toInline.copy()
|
||||
|
||||
// println(">>> INLINER: INLINED '${sub.name}' at ${functionCallStatement.position} (body inserted, ${sub.parameters.size} param(s))")
|
||||
return if(origNode !== toInline) {
|
||||
val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as Statement
|
||||
|
||||
return if (origNode !== toInline) {
|
||||
sub.hasBeenInlined = true
|
||||
listOf(AstReplaceNode(origNode, inlinedStatement, parent))
|
||||
} else
|
||||
noModifications
|
||||
}
|
||||
|
||||
if(sub.inline && sub.parameters.size >= 0) {
|
||||
if(sub.inline) {
|
||||
// Get the first non-parameter statement (skip VarDecl with SUBROUTINEPARAM origin)
|
||||
val bodyStmt = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val hasOnlyBodyAndReturn = bodyStmt != null && (
|
||||
@@ -361,7 +377,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
sub.hasBeenInlined=true
|
||||
listOf(AstReplaceNode(origNode, bodyStmt.copy(), parent))
|
||||
} else {
|
||||
// note that we don't have to process any args, because we only inline parameterless subroutines.
|
||||
when (val toInline = bodyStmt) {
|
||||
is Return -> possiblyShortCircuitFunctionCall(toInline)
|
||||
else -> possiblyInlineFunctionBody(toInline)
|
||||
@@ -371,6 +386,19 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
private fun isSimpleReturnExpression(expr: Expression): Boolean {
|
||||
return when (expr) {
|
||||
is NumericLiteral -> true
|
||||
is IdentifierReference -> true
|
||||
is PrefixExpression -> isSimpleReturnExpression(expr.expression)
|
||||
is BinaryExpression ->
|
||||
(expr.left is NumericLiteral || expr.left is IdentifierReference) &&
|
||||
(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitutes parameter references in a statement with argument values.
|
||||
*/
|
||||
@@ -382,22 +410,25 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
val sub = fcall.target.targetStatement(program.builtinFunctions) as? Subroutine
|
||||
?: return noModifications
|
||||
|
||||
// Only handle parameterless subroutines with simple returns
|
||||
if (!sub.inline || sub.parameters.isNotEmpty() || !canInlineAtCallSite(sub, fcall))
|
||||
// Only handle inlining if sub is marked as such and call site is okay
|
||||
if (!sub.inline || !canInlineAtCallSite(sub, fcall))
|
||||
return noModifications
|
||||
|
||||
val toInline = sub.statements.firstOrNull() as? Return ?: return noModifications
|
||||
val toInline = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } as? Return ?: return noModifications
|
||||
|
||||
// Check return count matches target count
|
||||
if (toInline.values.size != multiTargets.size)
|
||||
return noModifications
|
||||
|
||||
// Only allow simple return values (literals or identifiers, no function calls)
|
||||
if (!toInline.values.all { it is NumericLiteral || it is IdentifierReference })
|
||||
// Only allow simple return values to prevent code bloat on 6502
|
||||
if (!toInline.values.all { isSimpleReturnExpression(it) })
|
||||
return noModifications
|
||||
|
||||
// Substitute parameters in all return values
|
||||
val substitutedReturn = substituteParameters(sub, fcall, toInline) as Return
|
||||
|
||||
// Create multiple single assignments, skipping void targets (they discard the value)
|
||||
val newAssignments = multiTargets.zip(toInline.values)
|
||||
val newAssignments = multiTargets.zip(substitutedReturn.values)
|
||||
.filter { (target, _) -> !target.void }
|
||||
.map { (target, value) ->
|
||||
Assignment(
|
||||
@@ -420,4 +451,104 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
val scope = AnonymousScope(newAssignments.toMutableList(), assignment.position)
|
||||
return listOf(AstReplaceNode(assignment, scope, parent))
|
||||
}
|
||||
|
||||
private fun substituteParameters(sub: Subroutine, fcall: IFunctionCall, node: Node): Node {
|
||||
val paramVarDecls = sub.parameters.map { param ->
|
||||
sub.statements.filterIsInstance<VarDecl>().find { it.origin == VarDeclOrigin.SUBROUTINEPARAM && it.name == param.name }
|
||||
?: throw FatalAstException("parameter ${param.name} not found in subroutine ${sub.name}")
|
||||
}
|
||||
val paramMap: Map<VarDecl, Expression> = paramVarDecls.zip(fcall.args).associate { it.first to it.second }
|
||||
|
||||
fun substitute(n: Node): Node {
|
||||
if (n is IdentifierReference) {
|
||||
val target = n.targetStatement(program.builtinFunctions)
|
||||
if (target is VarDecl) {
|
||||
val arg = paramMap[target]
|
||||
if (arg != null) return arg.copy()
|
||||
}
|
||||
}
|
||||
|
||||
return when (n) {
|
||||
is BinaryExpression -> BinaryExpression(substitute(n.left) as Expression, n.operator, substitute(n.right) as Expression, n.position)
|
||||
is PrefixExpression -> PrefixExpression(n.operator, substitute(n.expression) as Expression, n.position)
|
||||
is DirectMemoryRead -> DirectMemoryRead(substitute(n.addressExpression) as Expression, n.position)
|
||||
is ArrayIndexedExpression -> ArrayIndexedExpression(
|
||||
n.plainarrayvar?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
subId
|
||||
},
|
||||
n.nestedArray?.let { substitute(it) as ArrayIndexedExpression },
|
||||
n.pointerderef?.let { substitute(it) as PtrDereference },
|
||||
substitute(n.indexer) as ArrayIndex,
|
||||
n.position
|
||||
)
|
||||
is ArrayIndex -> ArrayIndex(substitute(n.indexExpr) as Expression, n.position)
|
||||
is Return -> Return(n.values.map { substitute(it) as Expression }.toTypedArray(), n.position)
|
||||
is Assignment -> {
|
||||
val newTarget = n.target.copy()
|
||||
newTarget.identifier = n.target.identifier?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
subId
|
||||
}
|
||||
newTarget.memoryAddress = n.target.memoryAddress?.let { DirectMemoryWrite(substitute(it.addressExpression) as Expression, it.position) }
|
||||
newTarget.arrayindexed = n.target.arrayindexed?.let { substitute(it) as ArrayIndexedExpression }
|
||||
newTarget.pointerDereference = n.target.pointerDereference?.let { substitute(it) as PtrDereference }
|
||||
newTarget.arrayIndexedDereference = n.target.arrayIndexedDereference?.let {
|
||||
ArrayIndexedPtrDereference(
|
||||
it.chain.map { (name, index) -> name to index?.let { substitute(it) as ArrayIndex } },
|
||||
it.derefLast,
|
||||
it.position
|
||||
)
|
||||
}
|
||||
|
||||
Assignment(
|
||||
newTarget,
|
||||
substitute(n.value) as Expression,
|
||||
n.origin,
|
||||
n.position
|
||||
)
|
||||
}
|
||||
is FunctionCallExpression -> FunctionCallExpression(
|
||||
substitute(n.target).let {
|
||||
if (it !is IdentifierReference) throw FatalAstException("parameter substituted by ${it::class.simpleName} but an identifier reference was expected")
|
||||
it
|
||||
},
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
n.position
|
||||
)
|
||||
is FunctionCallStatement -> FunctionCallStatement(
|
||||
substitute(n.target).let {
|
||||
if (it !is IdentifierReference) throw FatalAstException("parameter substituted by ${it::class.simpleName} but an identifier reference was expected")
|
||||
it
|
||||
},
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
n.void,
|
||||
n.position
|
||||
)
|
||||
is AnonymousScope -> AnonymousScope(
|
||||
n.statements.map { substitute(it) as Statement }.toMutableList(),
|
||||
n.position
|
||||
)
|
||||
is Jump -> Jump(substitute(n.target) as Expression, n.position)
|
||||
is TypecastExpression -> TypecastExpression(substitute(n.expression) as Expression, n.type, n.implicit, n.position)
|
||||
is AddressOf -> AddressOf(
|
||||
n.identifier?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
subId
|
||||
},
|
||||
n.arrayIndex?.let { substitute(it) as ArrayIndex },
|
||||
n.dereference?.let { substitute(it) as PtrDereference },
|
||||
n.msb,
|
||||
n.typed,
|
||||
n.position
|
||||
)
|
||||
else -> n.copy()
|
||||
}
|
||||
}
|
||||
|
||||
return substitute(node)
|
||||
}
|
||||
}
|
||||
@@ -313,30 +313,21 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
val rNum = (rExpr.right as? NumericLiteral)?.number
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
// A = A +/- B + N ---> A = A +/- B ; A = A + N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val addConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
AstReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
AstInsert.after(assignment, addConstant, parent as IStatementContainer))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N ---> A = A +/- B ; A = A - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val subConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
AstReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
AstInsert.after(assignment, subConstant, parent as IStatementContainer))
|
||||
}
|
||||
if ((op1 == "+" || op1 == "-") && (op2 == "+" || op2 == "-")) {
|
||||
// A = A op1 (B op2 N) ---> A = A op1 B ; A = A secondOp N
|
||||
// only valid for additive operators (op2 must be + or -).
|
||||
// secondOp keeps the combined expression equivalent:
|
||||
// A+(B+N)=A+N, A+(B-N)=A-N, A-(B+N)=A-N, A-(B-N)=A+N
|
||||
val secondOp = if (op1 == "+") op2 else if (op2 == "+") "-" else "+"
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val opConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), secondOp, rExpr.right, binExpr.position),
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
AstReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
AstInsert.after(assignment, opConstant, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,7 +357,7 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target isSameAs assignment.value && !assignment.target.hasSideEffects(options.compTarget) && !assignment.value.hasSideEffects(options.compTarget)) {
|
||||
// remove assignment to self
|
||||
return listOf(AstRemove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
@@ -380,7 +371,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number
|
||||
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left && !assignment.target.hasSideEffects(options.compTarget) && !bexpr.left.hasSideEffects(options.compTarget)) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
|
||||
@@ -255,7 +255,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
|
||||
if(assignment.target isSameAs assignment.value)
|
||||
if(assignment.target isSameAs assignment.value && !assignment.target.hasSideEffects(options.compTarget) && !assignment.value.hasSideEffects(options.compTarget))
|
||||
return listOf(AstRemove(assignment, parent as IStatementContainer))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
plugins {
|
||||
id("application")
|
||||
kotlin("jvm")
|
||||
id("com.gradleup.shadow") version "9.3.2"
|
||||
id("com.peterabeles.gversion") version "1.10.3"
|
||||
id("com.gradleup.shadow") version "9.6.1"
|
||||
id("com.peterabeles.gversion") version "1.11.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -12,7 +12,6 @@ dependencies {
|
||||
implementation(project(":compilerAst"))
|
||||
implementation(project(":codeGenCpu6502"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
implementation(project(":codeGenExperimental"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":virtualmachine"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.6")
|
||||
@@ -22,8 +21,7 @@ dependencies {
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation(project(":intermediate"))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
testImplementation("com.github.irmen:ksim65:v2.0")
|
||||
testImplementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
// Exclude transitive antlr4 dependency (we only need it in parser module)
|
||||
@@ -43,6 +41,7 @@ application {
|
||||
tasks.shadowJar {
|
||||
archiveBaseName.set("prog8c")
|
||||
archiveVersion.set(version.toString())
|
||||
duplicatesStrategy = DuplicatesStrategy.WARN
|
||||
// minimize()
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ gversion {
|
||||
className = "Version"
|
||||
language = "kotlin"
|
||||
debug = false
|
||||
annotate = true
|
||||
annotate = ""
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -1,29 +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$/res" type="java-resource" />
|
||||
<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="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<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" />
|
||||
<orderEntry type="library" scope="TEST" name="github.irmen.ksim65" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -25,15 +25,16 @@ smallringbuffer {
|
||||
}
|
||||
|
||||
sub free() -> ubyte {
|
||||
return 255-fill
|
||||
return 255 - fill
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=254
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 254
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill<=1
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
@@ -92,11 +93,12 @@ smallstack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp <= 1
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==255
|
||||
return sp == 255
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -150,11 +152,12 @@ stack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp == 0 or sp > 8191
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==8191
|
||||
return sp == 8191
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -207,15 +210,16 @@ ringbuffer {
|
||||
}
|
||||
|
||||
sub free() -> uword {
|
||||
return 8191-fill
|
||||
return 8192-fill
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill==0
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=8191
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 8191
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
|
||||
@@ -534,6 +534,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -539,3 +539,43 @@ swap_floats .proc
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
; Wrappers for the ROM's bare T-variant math routines (FADDT, FMULTT, FDIVT, FPWRT).
|
||||
; These ROM entries skip CONUPK which normally sets the Z flag and arisgn byte.
|
||||
; Callers that have not just come through CONUPK must set these up themselves
|
||||
; or the result can be wrong (heisenbug with some values).
|
||||
; The corresponding entries on the X16 (via its KERNAL jump table) do this correctly,
|
||||
; so these wrappers are only needed on C64 and PET32.
|
||||
FADDT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FADDT
|
||||
.pend
|
||||
|
||||
FMULTT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FMULTT
|
||||
.pend
|
||||
|
||||
FDIVT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FDIVT
|
||||
.pend
|
||||
|
||||
FPWRT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FPWRT
|
||||
.pend
|
||||
|
||||
@@ -51,15 +51,15 @@ extsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
extsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
extsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
|
||||
extsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
extsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FADDT_NZ wrapper
|
||||
extsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
extsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
extsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands ; (FSUBT does NOT need the Z flag hack, it already does the setup internally)
|
||||
extsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
extsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
extsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FMULTT_NZ wrapper
|
||||
extsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
extsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
extsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FDIVT_NZ wrapper
|
||||
extsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
extsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
extsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FPWRT_NZ wrapper
|
||||
extsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
extsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
|
||||
@@ -349,16 +349,28 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled.
|
||||
; Overdraws horizontal lines unfortunately.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -367,6 +379,10 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -446,4 +462,3 @@ _y_lookup_hi .byte >_plot_y_values
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -509,6 +509,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -27,11 +27,12 @@ smallringbuffer {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=254
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 254
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill<=1
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
@@ -90,11 +91,12 @@ smallstack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp <= 1
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==255
|
||||
return sp == 255
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -150,11 +152,12 @@ stack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp<=$a001
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp <= $a000
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==$bfff
|
||||
return sp == $bfff
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -227,15 +230,16 @@ ringbuffer {
|
||||
}
|
||||
|
||||
sub free() -> uword {
|
||||
return $1fff-fill
|
||||
return 8192-fill
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill<=1
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=8191
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 8191 or head == $bfff
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
|
||||
Loaded 100 of 333 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user