mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
amiga: custom now includes interrupt vector offset symbols, cpuVBR and isPAL (set in grab_system)
This commit is contained in:
1 parent
df659d285b
commit
ffb0a4d603
10 files changed
+274
-1060
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
[93mWARN [0mfile:///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
|
||||
|
||||
@@ -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<Position>`
|
||||
(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.
|
||||
@@ -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
|
||||
`<INIT>` 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 `<original>_init_value` containing
|
||||
the raw init bytes as a `ubyte[N]` array in the `<INIT>` section.
|
||||
2. Clears the original ZP variable's init value so it moves to the
|
||||
`<NOINIT>` 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.
|
||||
@@ -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
|
||||
ldy #>varname
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<varname_init_value
|
||||
ldy #>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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
File renamed without changes.
@@ -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.
|
||||
@@ -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 |
|
||||
Reference in new issue
Block a user