diff --git a/compiler/res/prog8lib/amiga500/custom.p8 b/compiler/res/prog8lib/amiga500/custom.p8 index ef917b010..85c0492a4 100644 --- a/compiler/res/prog8lib/amiga500/custom.p8 +++ b/compiler/res/prog8lib/amiga500/custom.p8 @@ -14,6 +14,7 @@ exec_AttnFlags = 296 gfx_ActiView = $22 gfx_copinit = $26 gfx_LOFlist = $32 +gfx_DisplayFlags = $dc GfxLoadView = -222 GfxWaitTOF = -270 IRQ1 = $64 @@ -30,9 +31,13 @@ IRQ7 = $7C beq.s 1$ ; On 68000 no VBR (always zero) lea.l _S_GetVBR(PC),a5 ; Function to call as supervisor jsr exec.Supervisor(a6) ; Call supervisor function in A5 - move.l d0,S_VBR ; Store the returned VBR contents + move.l d0,custom.cpuVBR ; Store the returned VBR contents 1$ move.l sys.GfxBase,a6 ; A6 = Graphics base + btst.b #0,gfx_DisplayFlags(a6) ; Check NTSC bit (bit 0) + bne.s .isNTSC + move.b #-1,custom.isPAL +.isNTSC: move.l gfx_ActiView(a6),-(sp) ; Store current View pointer sub.l a1,a1 ; NULL view = default settings jsr GfxLoadView(a6) ; Load the view @@ -46,36 +51,31 @@ IRQ7 = $7C or.w d0,(sp) ; SET/CLR set to SET move.w custom.ADKCONR,-(sp) ; Audio, disk and UART or.w d0,(sp) ; SET/CLR set to SET - move.w custom.VPOSR,d0 ; Vertical pos and Agnus ID - btst #13,d0 ; When set: NTSC, when clear: PAL - bne.s 2$ ; Leave value 0 for NTSC - move.w #$FFFF,S_PAL ; Set all bits for PAL -2$ btst.b #14-8,custom.DMACONR ; Dummy read + btst.b #14-8,custom.DMACONR ; Dummy read 3$ btst.b #14-8,custom.DMACONR ; Blitter still busy? bne.s 3$ ; If yes, wait a bit move.w #$01FF,custom.DMACON ; Disable all DMA move.w #$3FFF,custom.INTENA ; Disable all interrupts - move.l S_VBR,a0 ; A0 = Pointer to vector base + move.l custom.cpuVBR,a0 ; A0 = Pointer to vector base move.l IRQ1(a0),-(sp) ; Store IRQ1 vector move.l IRQ3(a0),-(sp) ; Store IRQ3 vector move.l IRQ4(a0),-(sp) ; Store IRQ4 vector bra.s _skip _S_GetVBR: dc.l $4E7A0801 ; MOVEC VBR,d0 - privileged instruction - rte ; Return from supervisor mode + rte _skip: - SECTION .bss,bss -S_VBR: ds.l 1 -S_PAL: ds.w 1 - - SECTION .text,code }} } + pointer @shared cpuVBR ; after calling grab_system: contains CPU VBR register + bool @shared isPAL ; after calling grab_system: is true when system is PAL, or false if NTSC TODO doesn't work yet + + asmsub waitvsync() { %asm {{ ; synchronize to the screen's vsync, this routine doesn't require the OS to be active (sys.waitvsync is the OS-compatible version of this routine) @@ -109,7 +109,7 @@ S_PAL: ds.w 1 move.w #$01FF,custom.DMACON ; Disable all DMA move.w #$3FFF,custom.INTENA ; Disable all interrupts - move.l S_VBR,a0 ; A0 = Pointer to vector base + move.l custom.cpuVBR,a0 ; A0 = Pointer to vector base move.l (sp)+,IRQ4(a0) ; Restore IRQ4 vector move.l (sp)+,IRQ3(a0) ; Restore IRQ3 vector move.l (sp)+,IRQ1(a0) ; Restore IRQ1 vector diff --git a/docs/source/_static/symboldumps/skeletons-amiga500.txt b/docs/source/_static/symboldumps/skeletons-amiga500.txt index a0d6fb81d..2a84e1b03 100644 --- a/docs/source/_static/symboldumps/skeletons-amiga500.txt +++ b/docs/source/_static/symboldumps/skeletons-amiga500.txt @@ -1,11 +1,10 @@ Prog8 compiler v14.0-SNAPSHOT by Irmen de Jong (irmen@razorvine.net) -Prerelease version from git commit d870c39a in branch new-codegens +Prerelease version from git commit df659d28 in branch new-codegens This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html Compiling program import-all-amiga500.p8 Compiler target: amiga500 -WARN file:///home/irmen/Projects/prog8/docs/import-all-amiga500.p8:1:54: floating-point code requires a 68020 or better CPU with an 68881/68882 FPU; assembler options set to -m68020 -m68881. This program will NOT run on an Amiga 500/600 or 1200 without fp coprocessor! BUILTIN FUNCTIONS @@ -265,6 +264,7 @@ blitter { line_draw (uword x1 @D0, uword y1 @D1, uword x2 @D2, uword y2 @D3, uword screen_mod @D4, long screen_ptr @A1) clobbers (D0,D1,D2,D3,D5,D6,A0,A1) line_init (uword mask @D0, uword pixel @D1, uword pattern @D2, uword screen_mod @D4) clobbers (D0,D1,D2,D4,A0,A1) masked_blit (long src @A0, long mask @A1, long dst @A2, uword width_words @D0, uword height @D1, uword src_mod @D2, uword mask_mod @D3, uword dst_mod @D4) clobbers (D0,D1,D2,D3,D4,A0,A1,A2) + set_blitpri (bool hasprio @D0) clobbers (D0) wait () } @@ -775,6 +775,10 @@ custom { &uword VSSTOP &uword VSSTRT &uword VTOTAL + long @shared cpuVBR + bool @shared isPAL + filter_off () + filter_on () grab_system () left_button () -> bool middle_button () -> bool @@ -4250,7 +4254,6 @@ LIBRARY MODULE NAME: syslib --------------------------- sys { - ^^ubyte @shared arguments long @shared DOSBase long @shared GfxBase long @shared IFFParseBase @@ -4277,6 +4280,7 @@ sys { const ubyte SIZEOF_WORD long @shared TimerBase long @shared UtilityBase + ^^ubyte @shared arguments const ubyte target clear_carry () cpuAtLeast68020 () clobbers (D0,A6) -> bool @Pz diff --git a/docs/source/design/codegen-design.md b/docs/source/design/codegen-design.md deleted file mode 100644 index b9b7d7f03..000000000 --- a/docs/source/design/codegen-design.md +++ /dev/null @@ -1,687 +0,0 @@ -# Code Generator Backend Design (IR-based) - -This document describes the design of a code generator backend for the Prog8 compiler, -targeting 6502 (and eventually 68000). It focuses on the abstract concepts and conventions -that a new IR-based code generator should implement. - -> **Warning:** -> -> The **old** 6502 code generator walks the SimpleAST (`PtProgram`, `PtAssignment`, -> `PtFunctionCall`, etc.) to emit assembly. **A new code generator MUST NOT do this.** -> -> Instead, it must work exclusively from the **.p8ir IR file**. The IR is fundamentally -> different from the AST -- it is a linear sequence of instructions (load, store, add, -> branch, call, etc.) grouped into chunks, operating on virtual registers and memory -> locations. The code generator's job is to translate each IR instruction into the -> target's equivalent instructions, not to walk AST nodes. -> -> The .p8ir file contains every detail needed to generate equivalent assembly: -> memory layout, symbol definitions, subroutine chunks with their instruction bodies, -> register allocations, and data sections. The IR code generator and virtual machine -> demonstrate this approach -- study those modules rather than the old SimpleAST-based -> code generator. - -## 1. Entry Point - -The code generator implements the `ICodeGeneratorBackend` interface: - -```kotlin -interface ICodeGeneratorBackend { - fun generate(program: PtProgram, - symbolTable: SymbolTable, - options: CompilationOptions, - errors: IErrorReporter - ): IAssemblyProgram? -} -``` - -It receives the full program AST (already analyzed and optimized), a symbol table, -compilation options, and an error reporter. It produces an `IAssemblyProgram` -which can be assembled into a binary. - -> **Note:** -> The above interface is used by the **old** SimpleAST-based code generators. -> A new IR-based code generator will likely need a different entry point that takes -> the parsed IR file (`IntermediateProgram`) instead of a `PtProgram` AST. - -## 2. Memory Sections - -The generated assembly organizes data into these sections: - -**Constants** - Read-only values embedded in the code section. Includes numeric constants - (simple value equates), float constants (emitted as raw bytes), and memory - slab data blocks. - -**Zero-Page Variables** - Fast-access zero-page RAM (addresses $02-$FF). Used for frequently accessed - scalar variables. Allocation decisions are based on: - - Explicit `%zeropage` directive (`require` / `prefer` / `dontcare` / `not`) - - Usage frequency scoring (weighted by type size and loop nesting depth) - - Split-word arrays of 2-byte elements can be stored as two separate byte - arrays (LSB and MSB halves) for efficient indexed access with no scaling. - -**BSS (Uninitialized Data)** - Zero-initialized variables in regular RAM. This section is cleared at program - startup. Includes: - - Module-level and local variables without initializers - - The virtual register file (`p8_regfile`, 400 bytes for 200 word-sized registers) - - `dirty` variables (not required to be consistent across subroutine calls) - - Memory slabs (runtime-allocated blocks, in sub-section `BSS_SLABS`) - -**BSS_NOCLEAR** - A sub-section of BSS that is NOT zeroed at startup or on subroutine entry. - Used for: - - `dirty` variables (explicitly marked as undefined on subroutine entry) - - Float evaluation temporary variables - - Any variable where the runtime cost of zeroing is not justified - - The memory is still allocated in RAM, but its initial content is undefined. - -**BSS Relocation (Golden RAM / High Bank)** - BSS variables can be relocated to alternative memory regions: - - **Golden RAM**: A special RAM area on some targets (e.g., C128) that persists - across system resets - - **High Bank**: Extended RAM bank on targets that support banked memory (e.g., CX16) - - When relocation is active via `options.varsGolden` / `options.varsHighBank` - and `options.slabsGolden` / `options.slabsHighBank`: - - `BSS_NOCLEAR` and `BSS_SLABS` are emitted at the non-relocated location - - `prog8_program_end` is emitted before the relocated sections - - An `* = relocatedStart` directive switches to the relocated area - - `BSS` is emitted in the relocated area with an overflow check - - A `PROG8_VARSHIGH_RAMBANK` equate defines the active RAM bank - -**Initialized Data** - Data with compile-time initial values. Only arrays and strings can have - non-zero initializers; scalar numeric variables with non-zero initial values - are initialized via runtime assignment statements. - -**Struct Instances** - Statically allocated struct instances, with optional initial values. - - Instances **without** initial values go into the `BSS` section (zeroed at startup) - - Instances **with** initial values go into the `STRUCTINSTANCES` section - (initialized data, inline with the program) - - Struct type definitions (`IRStStructDef` from the IR symbol table) are emitted - as 64tass `.struct` blocks with named fields and parameterized constructors, - enabling `.dstruct` directives in data emission - -**Variable Alignment** - Variables may have an alignment constraint (`align: UInt` in - `IRStStaticVariable` and `IRStMemorySlab`). When `align > 1`, - a `.align N` directive is emitted before the variable allocation. - This ensures the variable starts at an address that is a multiple of N. - Alignment only applies to non-zero-page variables (strings and arrays). - -**Split-Word Arrays** - Arrays of 2-byte elements (words, pointers) may be stored in "split" layout - (`DataType.isSplitWordArray`) as two separate byte arrays: - - `variable_lsb`: the low bytes of each element - - `variable_msb`: the high bytes of each element - - This enables indexed access with a simple 8-bit index (no scaling by 2). - In the generated assembly, split-word arrays with initial values emit a - helper array directive (`_array_$varname := ...`) followed by `_lsb` - and `_msb` labels with `<` / `>` byte-selectors. - Uninitialized split-word arrays are emitted as two `.fill` allocations. - -**Memory Slabs** - Runtime-allocated memory blocks (`IRStMemorySlab`). Emitted in the - `BSS_SLABS` section (which is part of BSS but separated for optional - relocation). Each slab gets a `.fill` directive with its size. - -**Scratch Space** - A small set of fixed zero-page locations reserved for temporary values during - expression evaluation (e.g., scratch bytes, scratch words, a scratch pointer). - -## 3. Symbol Naming - -Symbols are prefixed to avoid collisions with assembler built-in symbols and -to encode their kind: - -| Kind | Prefix | Example | -|----------|--------|--------------------------| -| Block | p8b_ | `p8b_main` | -| Sub | p8s_ | `p8b_main.p8s_start` | -| Variable | p8v_ | `p8b_main.p8v_x` | -| Constant | p8c_ | `p8c_MYCONST` | -| Label | p8l_ | `p8l_mylabel` | -| Struct | p8t_ | `p8t_point` | -| Instance | p8i_ | `p8i_screen` | - -**Scoped names** use the target assembler's dot notation (e.g., `p8b_main.p8s_start`) -to create nested symbol scopes. When generating code inside a subroutine, local -references should use relative/abbreviated names to make the assembly listing -more readable. - -Symbol prefixing is performed before code generation by walking the AST and -modifying node names, then constructing a new `SymbolTable` from the prefixed AST. - -## 4. Label Naming - -Control flow labels use a monotonically increasing sequence number appended to -a descriptive prefix. The sequence counter is preserved across the entire -compilation unit to guarantee uniqueness. - -Common label prefixes: - - `for_loop_N`, `for_end_N` -- for-loop body and exit - - `repeat_N`, `repeatend_N` -- repeat-loop body and exit - - `for_modified_N` -- self-modifying comparison values - - `if_else_N`, `if_end_N` -- conditional branches - - `short_circuit_skip_N`, `short_circuit_end_N` -- short-circuit evaluation - -A label stack tracks the current innermost loop's exit label, used by -`break` / `exit` statements. - -## 5. Startup and Initialization - -The generated program's entry point performs these steps in order: - -**Phase 1: Program Header (`emitHeader` + `emitLauncher`)** - - Set CPU type directive and encoding - - Emit zero-page scratch register definitions as equates - - Emit user-supplied symbol definitions from command line (`-esa`) - - Generate launcher stub based on `options.output` and `options.launcher`: - - - `PRG` with `BASIC` launcher: generates a BASIC SYS line with - `prog8_entrypoint` label, `.word` / `.null` directives for the - BASIC stub - - `PRG` with `NONE` launcher: raw header with load address - - `RAW`: raw header with load address - - `XEX`: Atari XEX header with load address - - `LIBRARY`: minimal header with `jmp main.start` - - - Emit `prog8_program_start` label at the beginning of the program - - Emit startup sequence (`emitStartupSequence`): - - - `cld` instruction - - Save stack pointer for `sys.exit()` (`tsx` / `stx prog8_lib.orig_stackpointer`) - - Call `init_system` and `init_system_phase2` (unless `noSysInit`) - - Call `prog8_lib.program_startup_clear_bss` to zero the BSS section - - Call `run_global_inits` for block-level variable initializers - - Call the user's `main.start` subroutine - - On return, jump to `cleanup_at_exit` which calls `sys.poweroff_system` - -**Phase 2: Main Entry Point (generated inside the `main.start` subroutine)** - - Clear the BSS section (zero all uninitialized variables) - - For each block, call its block-level variable initializer subroutine - - Block initializers contain the runtime assignment statements for variables - with non-zero initial values - - For zero-page arrays/strings with initial values, copy initial data from - ROM-stored blocks into the zero-page locations - - Execute the user's program statements - -**Phase 3: Program Cleanup** - - After main returns, jump to cleanup routine (resets system, etc.) - - A `prog8_program_end` label is emitted at the very end of the generated - output (after code, data, and BSS sections). This label marks the end of - the program in memory and is used by the `progend()` library routine and - memory layout calculations (e.g., `MEMORY TOP`). - - A **memtop overflow check** is emitted after `prog8_program_end` using - 64tass's `.cerror` directive: - `.cerror * >= memtopAddress, "Program too long..."` - This catches program overflow at assembly time. - -### 5.1 Subroutine Entry Initialization - -There is no dynamic stack frame in the traditional sense. Local variables are -statically allocated in BSS (not on the stack). Their initial values (if any) -are established by assignment statements within the subroutine body. -`Dirty` local variables are explicitly documented as being undefined on -subroutine entry (they get whatever value was left in memory). - -## 6. Subroutine Calling Convention - -### 6.1 Argument Passing - -Arguments are passed via CPU registers when possible, falling back to -passing through static parameter variable slots. - -**Optimized register passing (simple integer types):** - - 1 byte argument: passed in accumulator (A) - - 2 byte arguments: first in A, second in Y - - 1 word argument: passed in AY (A=lo, Y=hi) - - 1 long argument: passed in combined register pair (e.g., virtual registers) - -**General case (more arguments or complex types):** - - Arguments are assigned to the subroutine's statically allocated parameter - variables before the `jsr` call - - Complex expressions that might clobber registers are evaluated first, - simpler register-based arguments last - -**External assembly subroutines (`PtAsmSub`) with explicit register specs:** - - Arguments follow the declared register specification - - Evaluation order is optimized: combined long regs -> virtual word regs -> - paired CPU regs -> single CPU regs -> float regs -> carry flag - -### 6.2 Return Values - -| Type | Return Convention | -|----------------------------------------|------------------------------------------------| -| Byte | Accumulator (A) | -| Word | Accumulator + Y register (A=lo, Y=hi) | -| Long | Combined register pair (e.g., virtual registers)| -| Float | Floating-point accumulator (FAC1) | -| Pointer (address of array/string) | Accumulator + Y register (A=lo, Y=hi) | - -**Multi-value returns:** - - First return value goes in A (byte) or AY (word) - - Subsequent return values use virtual registers (assigned downwards: - R15, R14, ...) - - Float returns via FAC1 are assigned *last* to avoid clobbering by - other return value computations - - Status flag returns (Carry, Zero, Negative, Overflow) are extracted - from CPU flags and converted to byte values - -### 6.3 Caller/Callee Responsibilities - -- **Caller** is responsible for saving/restoring registers that must be - preserved across the call (notably the X register, which is often used - as an index base pointer) -- **Callee** may freely use A, X, Y registers, scratch zero-page locations, - and floating-point accumulators; it is NOT responsible for saving them -- **Static parameters** are assigned by the caller before `jsr`; on entry - the callee can assume they hold the correct values - -### 6.4 Subroutine Types - -- **Regular subroutines (`PtSub`)**: Generated as standard `jsr` targets - with a `rts` return -- **External assembly subroutines (`PtAsmSub`)**: May have explicit register - parameter specifications; optional `inline` flag causes the body to be - emitted directly at the call site instead of via `jsr` -- **Banked calls (`JSRFAR`)**: Subroutine address and bank are embedded - as data words/bytes after the `jsr` instruction - -## 7. Expression Evaluation - -Expression evaluation uses a tiered strategy: - -**Tier 1: Direct (register-based)** - Simple expressions involving constants, variables, and basic operators - are evaluated directly using CPU registers and scratch zero-page - locations. Results remain in registers for immediate use. - -**Tier 2: Stack-based fallback** - Complex expressions (e.g., nested arithmetic, mixed types, float operations) - are evaluated using a stack discipline on the 6502 hardware stack or via - pre-allocated temporary variables. The exact strategy depends on the - target's register set and addressing modes. - -**Tier 3: Augmented (in-place) assignments** - Compound assignment operators (`+=`, `-=`, `*=`, etc.) on variables, - array elements, memory-mapped locations, and pointer dereferences are - handled as read-modify-write operations targeting the storage location - directly, avoiding unnecessary temporary copies. - -## 8. Control Flow - -**If/Else:** - Conditional branches test a comparison result (byte, word, long, float) - against zero. The comparison operator (`==`, `!=`, `<`, `<=`, `>`, - `>=`) generates the appropriate conditional branch instructions. - Branch inversion optimization is used: a `beq` over a `jmp` can be - replaced with `bne` to the target. - -**Loops (For, Repeat):** - - For-loops over numeric ranges generate an index variable, a loop body - label, an increment/decrement, and a conditional back-branch to the - loop top - - For-loops iterating over arrays/strings generate index and pointer - variables with appropriate bounds checks - - Repeat-loops are simpler: a loop body label with a back-branch - (unconditional for `repeat`, conditional for `repeat while/until`) - - Self-modifying code may be used for comparison immediates in some - targets (when not in ROMable mode) - -**Ternary expressions (`condition ? trueval : falseval`):** - Generated as a conditional branch around the false branch, with the - result stored in a common destination register or variable. - -## 9. Floating Point - -Floating-point operations use the target machine's ROM-based floating-point -routines. Each target platform provides (or must provide) entry points for: - - Float addition, subtraction, multiplication, division - - Float-to-integer and integer-to-float conversion - - Float comparison and sign manipulation - - The floating-point accumulator (FAC1) holds the primary operand and result - -The compiler emits `jsr` calls to these platform-provided routines. -Float constants are emitted as raw bytes matching the target's -floating-point byte format. - -## 10. Memory-Mapped I/O - -Memory-mapped variables (for hardware registers, etc.) are generated as -simple address equates. Reading from them translates to a load from that -address; writing to them translates to a store to that address. - -## 11. Struct Handling - -Struct types are emitted as assembler struct definitions (or equivalent), -allowing field access by name with automatic offset calculation. -Struct instances are allocated linearly in memory; fields are laid out -sequentially. Pointer dereferencing through a struct chain (e.g., -`ptr.field.subfield`) walks the chain of offsets using a scratch pointer -register. - -## 12. Multi-Target Considerations (6502 vs. 68000) - -The design should accommodate multiple CPU targets: - -**Common abstractions:** - - `Zeropage` or `Fast RAM` concept -- a small, fast address range for - frequently accessed variables - - Scratch registers -- fixed temporary storage for expression evaluation - - CPU register set -- the primary working registers exposed by the ISA - - Addressing modes -- how memory operands are specified - -**Target-specific differences to abstract:** - - Size and availability of CPU registers (6502: A, X, Y, flags; 68000: - D0-D7, A0-A7) - - Stack discipline (6502: hardware stack is limited; 68000: full - general-purpose stack) - - Instruction set capabilities (e.g., `STZ` on 65C02, `MUL` on 68000) - - Floating-point support (ROM routines vs. FPU) - - Addressing modes (6502: limited; 68000: rich orthogonal set) - -### 12.1 6502 vs. 65C02 Instruction Set Differences - -The 65C02 is a superset of the 6502. A new code generator should prefer 65C02 -instructions when targeting that CPU (the CX16 target uses 65C02). Below are the -instruction choices that differ: - -**Zero stores:** - - 6502: `lda #0` / `sta addr` (2 instructions) - - 65C02: `stz addr` (1 instruction, does not affect flags) - -**Register save/restore (X and Y):** - - 6502: `txa` / `pha` / `pla` / `tax` (4 instructions) - - 65C02: `phx` / `plx` (2 instructions, likewise for Y with `phy`/`ply`) - -**Accumulator increment/decrement:** - - 6502: `clc` / `adc #1` (2 instructions, clobbers carry) - - 65C02: `ina` / `dea` (1 instruction, does not clobber carry) - -**Unconditional branch (short range):** - - 6502: `jmp addr` (3 bytes, 3 cycles) - - 65C02: `bra addr` (2 bytes, 3 cycles, limited range) - -**Zero-page indirect addressing (without Y offset):** - - 6502: `lda ($zp),y` with `ldy #0` (2 instructions, clobbers Y) - - 65C02: `lda ($zp)` (1 instruction, direct zero-page indirect) - -**Bit manipulation (test and reset/set):** - - 6502: `lda mask` / `and mask` / `sta addr` or `ora` - - 65C02: `trb addr` (test and reset bits), `tsb addr` (test and set bits) - -**Stack-relative addressing (local variable access):** - - 6502: requires manual stack pointer with `tsx` / `lda $0100,x` - - 65C02: `lda $NN,s` / `sta $NN,s` (stack-relative addressing built-in) - -**65816 compatibility note:** The Commander X16 can use a 65816 CPU which is -mostly 65C02 compatible but lacks the `rmb`, `smb`, `bbs`, `bbr` -instructions. The code generator should avoid these instructions when targeting -CX16 even if a 65C02 CPU is selected, or add a post-generation scan to reject -them. - -A code generator should check `options.compTarget.cpu` at the point of -emitting these instructions and select the appropriate variant, rather than -relying on a post-processing pass. - -## 13. Lessons from the Existing 6502 Code Generator - -This section documents design decisions, pitfalls, and notable patterns from the -existing 6502 code generator that a new IR-based backend should consider. - -### 13.1 Assignment Engine as Central Abstraction - -The existing code generator models all data movement as `AsmAssignment` -objects connecting an `AsmAssignSource` to one or more `AsmAssignTarget` -instances. This decouples "what value to move" from "how to move it" and -provides a clean dispatch table for all (source, target) type combinations. -A new backend should adopt a similar model. - -Source kinds: literal number, literal boolean, variable, array element, -memory address, CPU register, expression (complex). - -Target kinds: variable, array element, memory address, CPU register, pointer -dereference, void (discard result). - -### 13.2 Static Variable Allocation, No Stack Frames - -All variables are statically allocated. There is no runtime stack frame for -local variables -- they live in BSS or zero-page and are initialized by -explicit assignments in the subroutine body. Parameters are also statically -allocated. This avoids stack depth issues on the 6502 (which has a 256-byte -hardware stack) but means recursion is not supported. - -Dirty variables (`%option dirty`) are documented as undefined on subroutine -entry; the optimizer omits their re-initialization at subroutine entry. This is -worth preserving as an optimization hint. - -### 13.3 Zero-Page Scratch Variables - -A small set of fixed zero-page locations is reserved for temporary values -during expression evaluation: - -| Name | Size | Purpose | -|---------------|--------|-----------------------------------| -| SCRATCH_B1 | 1 byte | General-purpose byte temp | -| SCRATCH_REG | 1 byte | Register save/restore temp | -| SCRATCH_W1 | 2 words| General-purpose word temp (2) | -| SCRATCH_PTR | 2 bytes| Pointer for indirect access | - -These are defined in the header and used throughout the generated code. -A new backend should reserve equivalent scratch space and use it consistently. -Note that the byte-sized `SCRATCH_REG` aliases the low byte of a word scratch -location -- this is a known fragility to be handled carefully. - -### 13.4 Zero-Page vs. Non-Zero-Page Code Paths - -Many operations have two code paths depending on whether a variable is in -zero-page or not. For example, pointer indirection through a zero-page -variable uses `(zp),y` addressing directly, while a non-ZP pointer must -first be copied to a scratch pointer in zero-page. A new backend should track -this distinction and generate different code accordingly. - -The `isZpVar()` check is used pervasively. In an IR-based backend, this -information should be available as a property of the variable's storage -allocation. - -### 13.5 Floating-Point is Entirely Library-Driven - -All floating-point operations call into target-specific ROM routines via -`jsr`. The compiler does not generate inline FP code. The floating-point -accumulator (FAC1) is the primary operand register, FAC2 is secondary. -Float-to-integer conversion, comparison, and arithmetic all go through these -library entry points. - -Float constants are deduplicated globally across the entire program and -emitted as raw bytes in the target's float byte format (typically 5 bytes). - -### 13.6 Self-Modifying Code for Non-ROMable Targets - -When `romable=false` (the default), the code generator may use self-modifying -code patterns (e.g., writing comparison immediates into `cmp #xx` operands) -to avoid temporary variable overhead. This is incompatible with ROM-based -programs and must be disabled when `romable=true`. A new backend should -check the romable flag before emitting self-modifying patterns. - -### 13.7 Assembly-Level Peephole Optimization is Necessary - -The existing code generator runs a post-generation peephole optimizer on the -assembly text. Key optimizations to preserve in a new backend: - -- **Store-load elimination:** `sta X / lda X` -> remove the redundant load -- **Load-load elimination:** `lda V / sta X / lda V` (same value) -> remove -- **Push-pop elimination:** `pha / pla` -> remove both -- **JSR+RTS to JMP:** `jsr Sub / rts` -> `jmp Sub` -- **Comparison with zero:** `lda X / cmp #0` -> remove `cmp` (flags already set) -- **Branch inversion:** `beq L / jmp A / L:` -> `bne A` (shorter) -- **Canceled increments:** `iny / dey` -> remove both - -**Critical safety concern:** The optimizer must NOT eliminate loads from -I/O-mapped addresses (reading hardware registers has side effects). The -code generator should track which addresses are I/O and communicate this -to the optimizer, or accept that certain loads cannot be eliminated. - -### 13.8 Symbol Prefixing is Done Before Code Generation - -All user symbols are renamed with type prefixes (`p8b_`, `p8s_`, etc.) -by walking the AST and modifying node names *before* code generation. A new -`SymbolTable` is then constructed from the prefixed AST. This means the -symbol table used during code generation is NOT the same instance produced -by earlier compilation phases. - -A new backend should either adopt the same approach or work with unprefixed -symbols and let the assembler output handle name mangling differently. -Key implications: -- Struct field names are **not** prefixed during the prefix pass (they are - handled lazily at code generation time). -- After renaming, all `subType` references in the AST must be updated to - point to entries in the new symbol table. -- The `SymbolTable`'s cached `flat` property becomes stale after renaming - and must be rebuilt. - -### 13.9 CPU Register Model Abstraction - -The existing code uses a rich `RegisterOrPair` enum to model registers: - -| Register | Composition | Notes | -|----------------|----------------|----------------------------------| -| A | Single byte | Accumulator | -| X | Single byte | Index register | -| Y | Single byte | Index register | -| AY | Word | A (lo), Y (hi) | -| AX | Word | A (lo), X (hi) | -| FAC1 | Float (5 byte) | FP accumulator | -| FAC2 | Float (5 byte) | FP secondary | -| R0-R15 | Byte/Word | CX16 virtual registers (memory-mapped I/O) | -| R0R1-R14R15 | Long (4 byte) | Paired virtual registers | -| Status flags | Bit | Carry, Zero, Negative, Overflow | - -A new backend should define a similar abstract register set that maps onto -the target CPU's actual registers, with size and pairing information. - -### 13.10 Fixed-Point Scratch Registers on CX16 - -On the Commander X16 target, there are 16 virtual registers (R0-R15) at fixed -memory-mapped I/O addresses. These are used as fast temporary storage for -intermediate values, multi-word operations, and multi-value returns. -R14/R15 are reserved for long return values; R15 alone is used for secondary -word return values. A new backend should define which (if any) target-specific -fast registers are available and how they are allocated. - -### 13.11 Struct Instance Initialization Quirk - -Zero-page string and array variables with compile-time initial values require -special handling: their init data is stored in a ROM block (labeled with a -`_init_value` suffix), and a runtime `memcopy` copies the data from ROM -into the zero-page locations at startup. This is necessary because zero-page -locations cannot have static initializers in most binary formats. - -A new backend should replicate this pattern for any variables in a "fast" -memory region that lacks static initialization support. - -### 13.12 Float Eval Result Variables - -Per-subroutine temporary float variables (`subroutineFloatEvalResultVar1` -and `subroutineFloatEvalResultVar2`, 5 bytes each) are allocated in -BSS_NOCLEAR for intermediate float expression results. A new backend should -provide equivalent scratch space for float temporaries, sized according to -the target's float byte format. - -The `dirty` attribute is set on these temporaries because they don't need -initialization. - -### 13.13 Pitfall: `TODO` and `AssemblyError` for Unimplemented Paths - -The existing code generator uses `throw AssemblyError(...)` and `TODO()` -for type combinations or edge cases that are not yet implemented. This is a -placeholder for incomplete features. A new backend should either implement -these paths cleanly or define a clear error-reporting strategy for unsupported -operations. - -Several of these TODOs have been fixed in the IR codegen's builtin function -handling (`BuiltinFuncGen.kt`): `rol`, `ror`, `rol2`, `ror2`, `setlsb`, and -`setmsb` on pointer-deref arrays now fall through to address-computation -paths instead of throwing. The corresponding TODOs remain in the 6502 codegen -(`BuiltinFunctionsAsmGen.kt`). - -### 13.14 Source Line References in Assembly Output - -The generated assembly should include comments referencing the original -`.p8` source file and line number for each generated block of instructions. -This is controlled by the `includeSourcelines` compilation option. - -In the old code generator (AST-based), each AST node carries a `Position` -object with file, line, and column information. The code generator tracks -the last emitted source line and inserts `; source: filename.p8:NN` -comments whenever the line changes. - -For an IR-based code generator, source positions are stored at the -`IRCodeChunk` level via `sourceLinesPositions: List` -(rather than per-instruction). The code generator should emit a source -comment at the beginning of each chunk (or at subroutine entry) using -the first non-dummy position from this list. A `lastSourceLine` tracker -avoids duplicate consecutive comments. - -The comment format should be: - -``` -; source: filename.p8:42 -``` - -### 13.15 Output Type and Launcher Handling - -The code generator must handle multiple output formats: -- `PRG` with `BASIC` launcher: emits a BASIC SYS stub with a - `prog8_entrypoint` label, `.word` / `.null` directives -- `PRG` with `NONE` launcher: raw header at load address -- `RAW`: raw binary at load address -- `XEX`: Atari XEX format header -- `LIBRARY`: minimal `jmp main.start` header - -All formats emit `prog8_program_start` at the entry point and share a -common startup sequence: `cld`, stack save via `tsx`/`stx prog8_lib.orig_stackpointer`, -system initialization calls, BSS clearing, and jump to `main.start`. -After `main.start` returns, execution jumps to `cleanup_at_exit` -which calls `sys.poweroff_system`. - -The `includeSourcelines` option and user-supplied symbol definitions -(`options.symbolDefs` / `-esa`) are also emitted in the header. - -### 13.16 The `prog8_program_end` Label - -The code generator must emit a `prog8_program_end` label at the very end -of the generated output (after all code, data, and BSS sections). This label -marks where the program ends in memory and is used by: - -- The `progend()` library routine -- Memory layout calculations (`MEMORY TOP`) -- Determining available space between program end and hardware boundaries - -The old codegen emits this label at the end of its `footer()` method, -positioned after all sections. A new code generator should place it as the -last thing before assembly symbols and final output. - -### 13.17 BSS_NOCLEAR and BSS Section Organization - -Variables in BSS are split into two categories: -- **Clean** (`dirty == false`): cleared to zero at program startup. - These go into the `BSS` section. -- **Dirty** (`dirty == true`): cleared at program startup but NOT at each - subroutine entry. These go into `BSS_NOCLEAR`. The runtime cost of - zeroing on every call is avoided while still having deterministic initial - state at program start. - -The virtual register file (`p8_regfile`) always lives in `BSS` (cleared). -Memory slabs go into a separate `BSS_SLABS` sub-section to allow selective -relocation (e.g., to golden RAM or a high memory bank). - -A `PROG8_VARSHIGH_RAMBANK` equate documents which RAM bank is active for -relocated BSS sections. When relocation is active, non-relocatable sections -(`BSS_NOCLEAR`, `BSS_SLABS`) are emitted before the `* =` directive -that switches to the relocated area. diff --git a/docs/source/design/initglobals-ir-issue.md b/docs/source/design/initglobals-ir-issue.md deleted file mode 100644 index d33a2fedf..000000000 --- a/docs/source/design/initglobals-ir-issue.md +++ /dev/null @@ -1,91 +0,0 @@ -# INITGLOBALS Chunk and ZP String/Array Initialization in the IR - -## The Problem - -The IR codegen pipeline never emitted runtime init instructions for non-BSS variables -(such as zero-page variables) that have string or array initialization values. -The existing mechanisms only handled numeric init values: - -- **Non-romable mode**: Numeric init values are pulled into the symbol table - (`changeGlobalVarInits`) and the corresponding AST assignment is removed. - String/array init values remain as AST assignments at the block level, BUT - these are NOT separate `PtAssignment` nodes -- they are embedded in the - variable declaration itself. So the loop at `IRCodeGen.kt:35-42`, which - collects block-level `PtAssignment` nodes and translates them into - `globalInits` IR instructions, finds nothing for string/array init values. - -- **Romable mode**: BSS variables with numeric init values get `LOAD`+`STOREM` - instructions in `globalInits`. BSS variables with string/array init values - are left as readonly inline data (no init instructions). Non-BSS variables - (including ZP) are not processed at all. - -The Virtual Machine works around this by reading init values directly from the -`` section variable declarations during `varsToMemory()`. For a future -6502 backend built on IR, no such mechanism would exist -- zero-page variables -with string/array init values would silently have zeroed/bogus data at runtime. - -## Affected Variables - -Variables that are: -- NOT in BSS (`inBss == false`) -- Have a ZP wish (`zpwish == PREFER_ZEROPAGE` or `REQUIRE_ZEROPAGE`) -- Have a non-numeric init value (`IRVariableInitializer.Str` or `.Array`) -- Are NOT split word arrays (those are handled as separate `_lsb`/`_msb` vars) - -These are typically block-level variables declared with the `@zp` tag and an -initialization value such as: -```prog8 -str @zp msg = "hello from zp" -ubyte[5] @zp counts = [10, 20, 30, 40, 50] -uword[3] @zp values = [1000, 2000, 3000] -``` - -## Current Fix - -The `generateStringArrayInits()` method in `IRCodeGen.kt` iterates over all -IR symbol table variables matching the criteria above. For each one it: - -1. Creates a non-ZP "shadow" variable named `_init_value` containing - the raw init bytes as a `ubyte[N]` array in the `` section. -2. Clears the original ZP variable's init value so it moves to the - `` section and is zeroed at startup. -3. Emits a `SYSCALL MEMCOPY` into the `INITGLOBALS` chunk to copy the shadow - data into the ZP variable at runtime. - -For example, a 14-byte ZP string `"hello from zp"` generates: -```asm -load.w r1,main.msg_init_value -load.w r2,main.msg -load.w r3,#14 -syscall $1019(r1.w,r2.w,r3.w) -``` - -This mirrors the approach used in the 6502 code generator -(`ProgramAndVarsGen.kt:628-682`), where shadow `_init_value` data arrays are -copied into zero-page variables at program startup. - -### Instruction Count - -For a ZP array of N bytes, this generates 4 instructions (3 `LOAD.w` + 1 -`SYSCALL`). Since all ZP variables combined can occupy at most ~200 bytes, the -worst-case overhead is ~8 instructions. Typical programs add 4-20 instructions, -a large reduction from the previous per-byte approach. - -## Implementation Notes - -- The shadow variable is added to the IR symbol table during code generation, - before the IR program is returned. This ensures it is present when the VM's - `VmVariableAllocator` builds its address map. -- The original variable's init value is cleared so a future 6502 backend can - place it in zero-page RAM rather than treating it as ROM-initialized data. -- `IRUnusedCodeRemover.removeBlockInits()` had to be fixed so that its - "stray load" cleanup computes register uses from `fcallArgs` as well as - from `reg1`/`reg2`. Without this fix, the `LOAD.w` instructions feeding the - `SYSCALL MEMCOPY` were incorrectly identified as dead and removed, taking - the referenced variables with them. - -## Future Work - -- Consider adding a `zpAddress` field to `IRStStaticVariable` (see - `zp-ir-issue.md`) so that ZP allocation is explicit in the IR and the - backend doesn't need to re-derive it from the variable name. diff --git a/docs/source/design/zp-ir-issue.md b/docs/source/design/zp-ir-issue.md deleted file mode 100644 index 0e5464e7f..000000000 --- a/docs/source/design/zp-ir-issue.md +++ /dev/null @@ -1,131 +0,0 @@ -# Zero-Page Variable Initialization in IR-based Code Generation - -## The Problem - -Zero-page variables (allocated to addresses $02-$FF for fast access) that have -compile-time initial values (strings, arrays) need special handling at program -startup. The old 6502 code generator handles this, but the gap between what -the IR provides and what the code generator needs makes this hard to implement -in an IR-based backend. - -## How the Old 6502 Codegen Handles It - -The old codegen (`ProgramAndVarsGen.kt`) has an `entrypointInitialization()` -method inside the `main.start` subroutine that: - -1. **Determines which ZP variables have init values** by querying the - `VariableAllocator.zeropageVars` map -- the actual allocation result from - the ZP allocator that runs during code generation. - -2. **Emits runtime copy code** for each such variable: - - Stores the initial value data at a ``_init_value`` label (in ROM/data section) - - Generates code to copy from that ROM label into the ZP address at startup - - Uses `strcpy` for strings, `sys.memcopy` for arrays - -3. **Example emitted code:** - ```asm - ; zp str and array initializations - lda #varname - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #varname_init_value - jsr prog8_lib.strcpy - ``` - -## What the IR Provides - -The IR symbol table has `IRStStaticVariable` with these relevant fields: - -- `zpwish: ZeropageWish` -- the programmer's intent (REQUIRE, PREFER, DONTCARE, - NOT_IN_ZEROPAGE), written at compile time -- `initializationValue: IRVariableInitializer?` -- the init value (Str, Array, - or Numeric) -- `inBss: Boolean` -- whether the variable is in BSS (zero-page vars are NOT - in BSS) -- `dirty: Boolean` -- whether the variable needs re-initialization - -Notably **missing** from the IR: - -- **No `isZp` or `zpAddress` field** -- the actual ZP allocation decision is not - recorded. The IR only knows the programmer's *wish*, not the final allocation. -- **No relationship between ZP status and initialization value** -- the IR - doesn't express "this ZP variable needs a runtime init copy from ROM". - -## Why This Is Hard - -### 1. ZP Allocation is a Backend Responsibility - -In the old codegen, `VariableAllocator` makes the final ZP allocation decision -at assembly-generation time. It scores variables by usage frequency, type size, -and loop depth, then assigns actual ZP addresses. This allocator runs inside -the 6502 backend, not in the shared IR generation pipeline. - -An IR-based code generator would need to either: -- **Reimplement ZP allocation** (duplicating the allocator logic per backend) -- **Have the IR pipeline perform ZP allocation** and store results in the IR - symbol table (adding `zpAddress` etc.) - -### 2. The Init Copy Code is Generated by the Backend - -The `jsr prog8_lib.strcpy` / `jsr sys.memcopy` approach is target-specific -and uses ZP scratch registers (P8ZP_SCRATCH_W1). This code is emitted by the -backend inside `main.start`, not by the IR pipeline. The IR `globalInits` -chunk only handles numeric init values (converted to LOAD+STOREM instructions -in `changeGlobalVarInits()`). - -### 3. No Precedent in the IR Pipeline - -Looking at `IRCodeGen.kt` lines 111-139 (`generateRomableInits`): - -```kotlin -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. -} -``` - -For romable mode, strings/arrays with init values are left as inline data -(readonly). Non-romable mode doesn't handle them at all -- they stay as -assignment statements in the AST which become regular instructions. -Neither path encodes the ZP-copy-init pattern into the IR. - -## Possible Solutions - -### Option A: Add ZP allocation info to the IR - -Add fields to `IRStStaticVariable`: -```kotlin -val zpAddress: UInt? = null // set if allocated in zero-page -``` - -The IR pipeline would need to run ZP allocation (or the IR file would need -to record the allocation from the earlier AST-based allocator). The backend -can then check `zpAddress != null && initializationValue != null` to emit -the copy code. - -### Option B: Emit the copy pattern as IR instructions - -Have the IR codegen emit LOAD+STOREM or a new `COPY` instruction for each -ZP string/array init, using temporary registers and scratch ZP addresses. -This would make the copy visible in the IR and any backend can translate it. - -### Option C: Leave it to each backend - -Each backend reimplements ZP allocation and emits the copy pattern. -Simple but duplicates logic across backends. - -### Option D: Record the init as a data section entry - -Instead of runtime copy, store the initialized ZP variables' values in a -special data section with metadata (target ZP address, size). The backend -emits a startup copy loop for all entries in this section. This is more -generic but less efficient. - -## Current Status - -The new6502gen code generator does NOT handle ZP string/array init. All -zero-page scratch registers are hardcoded to fixed addresses, and no ZP -allocation scoring is implemented. Any program using `%zeropage` with -initialized strings/arrays in ZP will get incorrect behavior. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 44e9d4997..59f184e6d 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -7,14 +7,13 @@ TODO Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ -- DataType.ARRAY_POINTER depends on the compilation target to be either a split word array or not. This is horrible because now we have to check with the compilation target everywhere to see if a DataType enumeration value is split word array, and PtVariable and PtArrayIndexer need an explicit boolean to tell us if this is the case +- DataType.ARRAY_POINTER depends on the compilation target to be either a split word array or not. This is horrible because now we have to check with the compilation target everywhere to see if a DataType enumeration value is split word array, and PtVariable and PtArrayIndexer need an explicit boolean to tell us if this is the case. See ideas/remove_array_pointer_plan.md for the plan. - m68k codegen: make use of scaling factors in the indexed instructions on 68020+ ? see ideas/scaled-indexing-IR.md - support arrays-of-structs, see ideas/arrays-of-structs.md - split up AssignmentAsmGen.kt in codeGenCpu6502 it is by far the largest file 6000+ lines - make enums strongly typed instead of just syntactic sugar for ints (see ideas/enum-strong-type.md for the plan) - symboldump: some sort of javadocs generated from the p8 source files (instead of just the function signatures). Use markdown for formatting, not html. - if implementing unsigned longs: remove the (multiple?) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast -- struct/ptr: implement the remaining TODOs in PointerAssignmentsGen (see ``ideas/ptr-assignment-todo.md`` for the severity breakdown). - struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid between IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator" - struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field) - add documentation for more library modules instead of just linking to the source code diff --git a/examples/m68k/amiga3d.p8 b/examples/m68k/amiga3d.p8 index b9bcecfb0..b262ae67b 100644 --- a/examples/m68k/amiga3d.p8 +++ b/examples/m68k/amiga3d.p8 @@ -21,7 +21,6 @@ main { pointer back_bitplane = bitplane2 setup_copper(current_bitplane, copper_list) - blitter.set_blitpri(true) ; make blitter take all dma cycles const ubyte TEXT_Y = 240 ; Copy the generated monochrome message into the reserved bottom strip of BOTH bitplanes. diff --git a/parser/NEW-POINTERDEREF-PLANS.md b/ideas/NEW-POINTERDEREF-PLANS.md similarity index 100% rename from parser/NEW-POINTERDEREF-PLANS.md rename to ideas/NEW-POINTERDEREF-PLANS.md diff --git a/ideas/ptr-assignment-todo.md b/ideas/ptr-assignment-todo.md deleted file mode 100644 index d2e137b48..000000000 --- a/ideas/ptr-assignment-todo.md +++ /dev/null @@ -1,131 +0,0 @@ -# Pointer Assignment TODOs - -`codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt` -contains many active `TODO()` calls. These are generally compiler crash paths, -not runtime fallbacks: valid source code reaching one of them fails during 6502 -code generation with `NotImplementedError`. - -## Highest Impact - -### Pointer dereference assignment helpers - -- Line 47: assign a float from AY to a pointer dereference. -- Lines 813-850: assign float, byte, word, and long values to indexed pointer dereferences. - -These helpers are still explicit `TODO()` paths, but representative source -cases such as `pointers[idx]^^ = value` currently compile successfully for the -C64 and CX16 targets. Existing pointer tests also cover byte, word, long, and -float indexed assignments. The actual reachable source construct for these -helpers has not been identified, so their severity is currently unverified -rather than a confirmed compiler crash. - -### Pointer-array index scaling - -- Lines 503, 520, 592, 610, 709, and 734: multiply an index by the element - size for pointer-indexed byte, word, long, or struct arrays. - -Representative variable-index assignments for byte, word, and long pointees -compile successfully for C64 and CX16 with and without optimization. The -reachable source construct for these helpers has not been identified, so their -severity is currently unverified rather than a confirmed compiler crash. - -### Pointer augmented multiplication and division - -- Lines 390 and 399: byte `*=` and `/=` through pointers. -- Lines 393 and 402: long `*=` and `/=` through pointers. -- Line 410: byte `%=` through a pointer. - -Representative direct pointer dereference operations for byte and long values, -including `*=`, `/=`, and byte `%=`, compile successfully for C64 and CX16 with -and without optimization. The reachable source construct for these helpers has -not been identified, so their severity is currently unverified rather than a -confirmed compiler crash. - -### Pointer augmented float operations - -- Lines 2005-2007: variable, expression, and register operands for float `+=` - and `*=`. -- Lines 2258-2260: variable, expression, and register operands for float `-=` - and `/=`. - -Representative direct pointer dereferences using shared variable operands for -`+=`, `*=`, `-=`, and `/=` compile successfully for C64 and CX16 with and -without optimization. The reachable source construct for these helpers has -not been identified, so their severity is currently unverified rather than a -confirmed compiler crash. - -## Medium-High Impact - -### Pointer augmented operations with register operands - -- Line 2056: word subtraction from a register. -- Lines 2348 and 2397: byte addition and subtraction from a register. -- Lines 2440, 2484, 2527, 2571, 2614, and 2658: byte and word XOR, OR, and - AND from a register. - -The corresponding literal, variable, or expression paths are implemented in -many cases. Severity: medium-high, compiler crash for register-source paths. - -### Long pointer augmented operations - -- Lines 1849 and 1919: unsupported source kinds for long add and subtract. -- Lines 2729, 2800, and 2871: long AND, OR, and XOR from a register. - -Severity: medium-high, compiler crash for the affected source forms. - -### Struct and large-offset dereferences - -- Line 1079: read through an address-of dereference when the final offset is - greater than 255. -- Line 1100: obtain a struct pointer from a pointer dereference before field - access. - -Small offsets and simpler pointer chains work. Severity: high for large -structures or nested pointer/struct access. - -### Pointer comparisons - -- Lines 942 and 1065: byte pointer comparisons using `<`, `<=`, `>`, and `>=`. - -Equality and inequality are implemented. Severity: medium, compiler crash for -relational comparisons. - -### Signed byte pointer shifts - -- Line 1543: signed byte right shift through a pointer. - -Unsigned shifts and other shift widths have implementations. Severity: medium, -compiler crash for signed `>>=` cases. - -## Lower Impact or Non-Functional TODOs - -- Lines 2943 and 2978: save and restore combined long register pairs on the - stack. This is likely an internal register-allocation path and is less likely - to be reached by ordinary source code, but still crashes if selected. -- Line 472: comment that the zeropage scratch-variable detection is not robust. - This is a code-quality concern, not an unimplemented operation. -- Line 1122: comment about avoiding an unnecessary zeropage scratch register. - This is an optimization opportunity, not a correctness issue. -- Lines 412-413: commented-out float and long modulo TODOs. They are inactive - and have no current effect. - -## Overall Assessment - -The file contains many explicit `TODO()` calls, so any genuinely reachable -path would fail during compilation rather than silently generating incorrect -machine code. However, representative tests for indexed pointer assignments, -pointer-array index scaling, pointer byte/long multiplication and division, and -pointer float augmented operations all compile successfully for C64 and CX16, -with and without optimization. Those entries are therefore currently latent or -unreachable from the tested source forms, not confirmed user-facing crashes. - -The remaining struct, large-offset, comparison, register-source, and stack -paths still need targeted reachability tests before their severity can be -assessed reliably. - -Recommended implementation order: - -1. Identify reachable source constructs for the remaining TODOs. -2. Add regression tests for confirmed failures. -3. Implement confirmed pointer and struct gaps. -4. Reassess or remove TODOs that remain unreachable. diff --git a/ideas/remove_array_pointer_plan.md b/ideas/remove_array_pointer_plan.md new file mode 100644 index 000000000..ea19e3c2e --- /dev/null +++ b/ideas/remove_array_pointer_plan.md @@ -0,0 +1,252 @@ +# Implementation Plan: Remove `ARRAY_POINTER` from Type System + +## Overview + +**Goal**: Remove the target-dependent `ARRAY_POINTER` type and replace it with `ARRAY` using a pointer element type, making the type system target-independent. + +**Current Problem**: `ARRAY_POINTER` has different memory layouts on different targets (split word on 6502, regular long on m68k/virtual), requiring target checks throughout the codebase. This is described as "horrible" in `todo.rst`. + +**Solution**: Use `ARRAY(sub=POINTER, ...)` with a new field to store the pointee base type, making the layout decision explicit at allocation/codegen time. + +**Decisions**: +1. Remove `ARRAY_POINTER` entirely (no deprecated alias) +2. No backward compatibility for old `.p8ir` files +3. Implement incrementally, phase by phase with verification + +--- + +## New Type Representation + +### Current +```kotlin +DataType(ARRAY_POINTER, sub=UBYTE) // array of pointers to ubyte +DataType(ARRAY_POINTER, sub=STRUCT, subType=X) // array of pointers to struct X +``` + +### New +```kotlin +DataType(ARRAY, sub=POINTER, pointeeSub=UBYTE) // array of pointers to ubyte +DataType(ARRAY, sub=POINTER, pointeeSub=STRUCT, subType=X) // array of pointers to struct X +``` + +**Key Change**: Add `pointeeSub: BaseDataType?` field to `DataType` to store what the pointers point to. + +--- + +## Phase 1: Core Type System Changes + +**File**: `codeCore/src/prog8/code/core/DataTypes.kt` + +### Changes: +1. **Remove `ARRAY_POINTER` from `BaseDataType` enum** (line 23) +2. **Add `pointeeSub: BaseDataType?` field to `DataType`** (after line 196) +3. **Update `DataType` constructor and `copy()` method** to include new field +4. **Remove `arrayOfPointersTo()` factory methods** (lines 218-222) - replace with new versions that create `ARRAY` with `POINTER` subtype +5. **Update `elementType()`** (line 252-259): + - For `ARRAY` with `sub=POINTER`: return `DataType(POINTER, sub=pointeeSub, subType=subType)` +6. **Update `isSplitWordArray()`** (line 476-482): + - Check `sub==POINTER && memsizer.POINTER_MEM_SIZE<=2u` instead of `base==ARRAY_POINTER` +7. **Remove `isPointerArray` alias from `BaseDataType`** (line 93) +8. **Add `DataType.isPointerArray` extension property**: `isArray && sub==POINTER` +9. **Update `isAssignableTo()`** (lines 396-409): + - Replace `ARRAY_POINTER` cases with `ARRAY` + `sub=POINTER` checks +10. **Update `toString()` and `sourceString()`** (lines 323-364): + - Handle `ARRAY` with `sub=POINTER` to print as `^^subtype[]` + +### Verification: +- Compile `codeCore` module: `gradle :codeCore:compileKotlin --console=plain` +- Run `codeCore` tests: `gradle :codeCore:test --console=plain` + +--- + +## Phase 2: Type Creation + +### File: `compilerAst/src/prog8/ast/antlr/Antlr2KotlinVisitor.kt` +**Line 232-233**: Update parser to create `ARRAY` with `POINTER` subtype instead of `ARRAY_POINTER` + +### File: `compilerAst/src/prog8/ast/expressions/AstExpressions.kt` +**Lines 1284-1292**: Update array literal type inference to create `ARRAY` with `POINTER` subtype + +### Verification: +- Compile `compilerAst` module: `gradle :compilerAst:compileKotlin --console=plain` + +--- + +## Phase 3: Type Checking and Validation + +### File: `compiler/src/prog8/compiler/astprocessing/AstChecker.kt` +**8 references** (lines 306, 992, 1050, 1146, 1258, 1525, 1688, 2540): +- Replace `isPointerArray` checks with `datatype.isPointerArray` (using new extension property) +- Or inline as `datatype.isArray && datatype.sub==POINTER` + +### File: `compiler/src/prog8/compiler/astprocessing/ImplicitForIteratorDecls.kt` +**Lines 61-62**: Update for-loop iterator type checking + +### Verification: +- Compile `compiler` module: `gradle :compiler:compileKotlin --console=plain` + +--- + +## Phase 4: AST Transformations + +### File: `compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt` +**Lines 132, 1015**: Update prefix operator and struct dereference checks + +### File: `compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt` +**Lines 34, 96, 145-152**: Update type compatibility and `@nosplit` handling + +### File: `compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVarsAndRecombineIdentifiers.kt` +**Line 88**: Update struct field resolution for pointer arrays + +### Verification: +- Compile `compiler` module: `gradle :compiler:compileKotlin --console=plain` + +--- + +## Phase 5: Memory Sizing + +### File: `codeCore/src/prog8/code/target/NormalMemSizer.kt` +**Lines 13-14**: Update memory size calculation for `ARRAY` with `POINTER` subtype + +### File: `codeCore/testFixtures/kotlin/prog8tests/helpers/Dummies.kt` +**Lines 15-16**: Update test memory sizer + +### File: `compiler/test/helpers/Dummies.kt` +**Lines 38-39**: Update compiler test memory sizer + +### Verification: +- Compile and test `codeCore`: `gradle :codeCore:test --console=plain` + +--- + +## Phase 6: IR Handling + +### File: `intermediate/src/prog8/intermediate/Utils.kt` +**Lines 18-22**: Update IR type string serialization +- Change `^^` prefix format to regular array format with pointer subtype + +### File: `intermediate/src/prog8/intermediate/IRFileReader.kt` +**Lines 601-611**: Update IR file deserialization +- Parse new format and create `ARRAY` with `POINTER` subtype + +### Verification: +- Compile `intermediate` module: `gradle :intermediate:compileKotlin --console=plain` +- Run `intermediate` tests if any + +--- + +## Phase 7: Code Generation + +### File: `codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt` +**Lines 831-832**: Update uninitialized pointer array emission for 6502 + +### File: `codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt` +**Lines 908-910**: Update typecast to pointer array (currently TODO) + +### File: `codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt` +**Line 752**: Update for-loop iteration to use new `isSplitWordArray` logic + +### Verification: +- Compile codegen modules: `gradle :codeGenCpu6502:compileKotlin :codeGenIntermediate:compileKotlin --console=plain` + +--- + +## Phase 8: VM Updates + +### File: `virtualmachine/src/prog8/vm/VmProgramLoader.kt` +**Lines 245-247, 447-456**: Update variable zeroing and array initialization + +### Verification: +- Compile `virtualmachine` module: `gradle :virtualmachine:compileKotlin --console=plain` + +--- + +## Phase 9: Builtin Functions + +### File: `codeCore/src/prog8/code/core/BuiltinFunctions.kt` +**Line 27**: Update `IterableDatatypes` array (remove `ARRAY_POINTER`) + +--- + +## Phase 10: Tests and Documentation + +### Test Files: +- `codeCore/test/prog8tests/codecore/TestDataType.kt` (lines 96, 110-112) +- `compiler/test/ast/TestVariousCompilerAst.kt` (line 1557) + +### Documentation: +- `docs/source/todo.rst` (line 10): Remove TODO item +- `vm32.md` (lines 133-142): Update dev notes + +### Verification: +- Run all tests: `gradle build --console=plain` + +--- + +## Verification Strategy + +After each phase: +1. Compile the affected module(s) +2. Run module-specific tests +3. After all phases: run full build with `gradle build --console=plain` + +### Manual Testing: +Create test programs with pointer arrays: +```prog8 +%zeropage basicsafe +%option no_sysinit + +main { + ^ubyte[3] ptrs + ^structDef[2] structPtrs + + sub start() { + ; test pointer array operations + } +} +``` + +Compile for multiple targets: +- `prog8c -target cx16 test.p8` +- `prog8c -target virtual test.p8` +- `prog8c -target c64 test.p8` + +--- + +## Risk Mitigation + +1. **Incremental implementation**: Complete and verify each phase before moving to next +2. **Type safety**: Ensure `pointeeSub` field is properly propagated through all type operations +3. **Memory layout**: Verify split-word logic works correctly on all targets + +--- + +## Estimated Effort + +- **Phase 1-2**: 2-3 hours (core type system) +- **Phase 3-4**: 2-3 hours (validation and transforms) +- **Phase 5-6**: 1-2 hours (memory and IR) +- **Phase 7-8**: 2-3 hours (codegen and VM) +- **Phase 9-10**: 1-2 hours (tests and docs) +- **Total**: 8-13 hours + +--- + +## Files Affected Summary + +**Total**: ~18 Kotlin source files + 2 documentation files, ~60 reference points + +### By Category: +| Category | Files | References | +|----------|-------|------------| +| Type system | DataTypes.kt | 15+ | +| AST validation | AstChecker.kt | 8 | +| AST transforms | CodeDesugarer.kt, VariousCleanups.kt, LiteralsToAutoVars.kt | 7 | +| IR handling | Utils.kt, IRFileReader.kt | 10 | +| Code generation | ProgramAndVarsGen.kt, ExpressionGen.kt, IRCodeGen.kt | 3 | +| VM | VmProgramLoader.kt | 2 | +| Memory sizing | NormalMemSizer.kt, Dummies.kt (2 files) | 3 | +| Type creation | Antlr2KotlinVisitor.kt, AstExpressions.kt | 2 | +| Builtin functions | BuiltinFunctions.kt | 1 | +| Tests | TestDataType.kt, TestVariousCompilerAst.kt | 3 | +| Documentation | todo.rst, vm32.md | 2 |