todo cleanup

This commit is contained in:
Irmen de Jong committed 2026-10-14 01:29:22 +02:00
1 parent 05d8acc4bb
commit da7ea294ef
3 files changed
+31 -85

No files matched your search

+3 -1
View File
@@ -46,6 +46,8 @@ Missing VM Implementations (VirtualMachine.kt)
- ``IRInlineAsmChunk`` with ``isIR=true`` (IR-form inline asm) is not executable: ``VmProgramLoader.kt`` only converts the trivial ``"clc"``/``"sec"`` chunks and throws on everything else. This is the designed-for home of IR-form asm, since ``AstChecker`` restricts ``asm { %ir }`` to the virtual target only; the VM should parse and run the IR instruction lines contained in such chunks. Raw (non-IR) inline asm and ``IRAsmSubroutine`` should NOT be made executable (the VM has no 6502/68k CPU): instead, reject non-IR ``asm`` blocks and ``asmsub`` with a frontend error on the virtual target (currently they pass ``AstChecker`` and only crash at VM load). ``IRInlineBinaryChunk`` (``%binary`` include) stays deferred: the program is not loaded as data, so an embedded blob has no place.
Source line tracking in new codegen backends
- Improve source line tracking across the IR into the generated assembly code in the new codegen backends (new6502, m68k). Currently, the IR preserves some source position information, but this is not consistently propagated through to the final assembly output. Better tracking would improve debugging experience (e.g., in monitor/debugger tools) and make it easier to correlate generated assembly back to the original Prog8 source code. Consider adding source location metadata to IR instructions and ensuring code generators emit appropriate ``.line`` directives or comments in the assembly output.
Recursion-sound defers via framebase-as-parameter
- Push ``(id, framebase)`` pairs on the defer handler stack, pass the parent's bottom-of-frame address as a hidden trailing parameter, lower parent-local accesses to indirect framebase-relative accesses. Lifts the current rejection of recursive deferring subs on m68k targets. VM parity only (not needed for m68k): running such IR on the VM would also need VM activation records in linear memory so saved frame bases are addresses (currently won't-do, see "Won't do: VM stack frames" above).
Libraries
@@ -61,7 +63,7 @@ Optimizations
- Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once new 6502 codegen is done from IR code, those 6502 only optimizations should probably be removed
- M68K: replace prog8 routines in math library by optimized 68000 assembly versions.
- M68K stack/frame follow-ups (see ``ideas/m68k-frames-followups.md``): the stack-based memory model for locals and parameters is implemented, including frame slots for locals, parameters in the caller-pushed incoming area (word-packed argument slots are now the default convention), per-activation virtual registers for re-entrant subroutines, prologue zero-store coalescing, and diagnostics/polish items. Remaining optimization work: register argument passing (section 2), reserved outgoing-argument area (section 3), recursion-sound defers via framebase-as-parameter (section 4), and loop-aware liveness for frame slot reuse (section 6). See the user-facing description in ``technical.rst``.
- M68K stack/frame follow-ups: the stack-based memory model for locals and parameters is implemented, including frame slots for locals, parameters in the caller-pushed incoming area (word-packed argument slots are now the default convention), per-activation virtual registers for re-entrant subroutines, prologue zero-store coalescing, and diagnostics/polish items. Remaining low-pri optimization work: reserved outgoing-argument area (allocate the max call-site arg size once in the caller's ``link`` frame; benchmark-driven, likely marginal) and loop-aware liveness for frame slot reuse (needs real backward live-in/live-out dataflow over the CFG; frame-size-driven). Register argument passing lives in ``ideas/m68k-register-allocation.md`` §9; recursion-sound defers is an IR/VM item above. See the user-facing description in ``technical.rst``.
Dead Code Elimination bug in 64tass, for nested subroutines
-76
View File
@@ -1,76 +0,0 @@
# M68K Frames Follow-ups
**Status: v1 implemented** on `m68k-stack` (uniform all-stack convention, caller cleanup).
User-facing description: `docs/source/technical.rst` ("M68K memory model and calling convention").
Implementation: `StackFrameLayout`, `AddressBase.FrameSlot` / `CallLocation.FrameSlot`,
`IRSubroutine.frameSize/incomingSize/frameVregSlots`. Decision: the VM does not get
activation records (see `docs/source/todo.rst`, "Won't do: VM stack frames"); it keeps
rejecting frame-based IR with an explicit error.
This file preserves the convention-level rationale from the deleted
`ideas/m68k-stack-memory-model.md` (§6.1/§6.4) so future optimizations stay variants
of v1, never second conventions.
## Why v1 is uniform 4-byte slots
- v1 follows the strict System V m68k scheme: every argument gets one padded longword slot,
narrow values right-justified (`move.b x,3(sp)` / `move.w x,2(sp)` / `move.l x,(sp)`),
param `i` of `N` at `8+4*(N-i)(a5)`, caller pops `N*4` after `jsr`, callee epilogue is
always `unlk a5` / `rts` regardless of arity.
- Rejected for the baseline: all-register (struct-by-value params have no register form),
hybrid first-scalar-in-D0/first-pointer-in-A0 (needs the register allocator to pay off;
without it, it just swaps `move.l x,-(sp)` for `move.l x,d0` plus clobber-ordering hazards
and mandatory copy-ins, doubling marshalling paths and tests).
- `asmsub`/`extsub` keep their explicit register-annotation ABI as a separate lane; normal
subs calling them (or vice versa) follow §6.3 push rules. A Linux/GCC-style word-packed
scheme (natural sizes, 16-bit alignment) is legal on 68000; **it has since been implemented
as the default convention** (IR format 8), so the sections about the uniform 4-byte layout
in this and older documents no longer describe the current behavior.
## Follow-ups (each needs its own design review + test pass)
### 2. Register argument passing
Paired caller/callee specialization over the fixed stack convention (e.g. first scalar
in D0, first pointer in A0, rest on stack), applied only where it measurably wins (small
scalar signatures). Requires the register allocator (`ideas/m68k-register-allocation.md`)
first.
### 3. Recursion-sound defers via framebase-as-parameter
Push (id, framebase) pairs on the defer handler stack, pass the parent's bottom-of-frame
address as a hidden trailing parameter, lower parent-local accesses to indirect
framebase-relative accesses. Lifts the current rejection of recursive deferring subs.
Also needs VM activation records in linear memory so saved frame bases are addresses.
### 4. Loop-aware liveness for frame slot reuse
**Priority: low, frame-size-driven.**
Today vreg frame slots are shared only when live ranges provably don't overlap, and any
back edge (loop, backward branch, cross-sub jump) falls back to one slot per register.
Lifting needs real backward live-in/live-out dataflow over the CFG; interval extension
alone is unsound.
### 5. Reserved outgoing-argument area
**Priority: low, benchmark-driven.** Allocate the max call-site arg size once in the
caller's `link` frame, write args via negative A5 offsets, and drop per-call `addq/lea`
cleanup. This may help tight loops with repeated multi-argument calls, but gains are
likely marginal: direct frame stores can be larger, frameless callers would gain a
frame, and every caller's frame would grow. Frame-size limits must count the area.
### 6. Prologue zero-store coalescing (done)
**Status: implemented.** Clean frame locals are grouped and merged into widest aligned
stores; long runs lower to a `dbra` loop in the m68k backend. Floats and
dirty/initialized locals remain boundaries. Details were in
`ideas/m68k-prologue-zero-coalescing.md` (removed now that the work is done).
### 7. Word-packed argument slots (done)
**Status: implemented.**: implemented as the default convention (IR format 8, documented in
`docs/source/technical.rst`). Linux/GCC scheme: byte/bool = 2 bytes incl. pad,
word = 2, long/float/pointer = 4, still even-aligned. Saves stack space and bus traffic
per narrow arg. Cost: frame layout sums per-arg rounded sizes instead of constant
stride; documented `asmsub` push rules change; hand-written asm (e.g. the stdlib
`die()` subs) and `.p8ir` consumers were updated accordingly.
+28 -8
View File
@@ -112,13 +112,15 @@ on the convention decisions in §2, not on the allocator existing.
Per the sequencing decision, the stack-frame implementation
lands *before* this allocator, so the convention this allocator is built
against is already the final one - the uniform all-stack convention documented
for M68K targets:
against is already the final one - the word-packed all-stack convention documented
for M68K targets (IR format 8):
- **Normal subs:** the caller pushes every argument to a longword slot
left-to-right (`-(sp)`), `jsr`s, and pops the area after the return
- **Normal subs:** the caller pushes every argument left-to-right into a
word-packed slot (byte/bool/word = 2 bytes, long/float/pointer = 4),
`jsr`s, and pops the whole area after the return
(caller cleanup). The callee reads arguments from positive A5 offsets as
incoming frame slots.
incoming frame slots, accumulating from the rightmost argument at `8(a5)`
by per-argument slot width.
- **asmsub/extsub:** arguments continue to go into fixed hardware slots via
`CallingConventionSlot`: slots 10..17 → D0..D7, 18..24 → A0..A6,
25..32 → FP0..FP7. In practice asmsub args/returns use D0–D2 and FP0–FP1
@@ -131,9 +133,16 @@ Consequences for the allocator:
callee-side parameters need no special allocator treatment while they stay
memory-resident. Once the callee loads a parameter into a vreg to compute
with it, that vreg is allocated like any other.
- Word-packed slot widths constrain callee-side reads: a byte/bool parameter
occupies a 2-byte slot whose second byte is garbage, so it must be read
byte-size-only (`move.b`), never as a word load of the slot. Word params
fill their 2-byte slot and wide params their 4-byte slot, so those read at
full width. This feeds the §7.1 #1 width semantics: loading an incoming
parameter at the wrong width reads garbage.
- Caller-side pushes are convention traffic, not allocation: the argument
value (in whatever register its vreg is allocated) is stored to `-(sp)`
like any memory move; the `addq` cleanup between pushes and the call
value (in whatever register its vreg is allocated) is stored to its slot
(`move.b` / `move.w` / `move.l x,-(sp)`) like any memory move; the
caller-side cleanup after the call
disturbs nothing held in registers (SP itself is never allocated).
- Register argument *passing* is a separate later optimization, layered on top
of this convention once the allocator works.
@@ -531,7 +540,8 @@ points. Three possible spill targets, with different soundness properties:
spills become frame-resident: per-invocation, recursion-safe, and cheaper
addressing (`move.l -8(a5),d0`). The frame layout pass does not yet budget
generic "compiler spill slots" for the allocator — it currently lays out
locals, the incoming argument area, and `frameVregSlots` for re-entrant
locals, the incoming argument area (sized by summed rounded slot widths,
always even), and `frameVregSlots` for re-entrant
subroutines only — so the slot-allocation policy is Stage-2 work (§7.1 #3).
- **SP-reserved spill area (proposed fallback for frameless subroutines):** for
subroutines the frame-layout pass leaves frameless (`hasFrame == false`,
@@ -893,3 +903,13 @@ structure) covering:
- Caller and callee both spilling in a call chain: stack/frame spills do not
collide; BSS spill slots are program-wide unique.
- Interrupt handlers save/restore every register they touch.
---
## 9. Future: register argument passing
Paired caller/callee specialization over the fixed stack convention (e.g. first scalar
in D0, first pointer in A0, rest on stack), applied only where it measurably wins (small
scalar signatures). Builds on the allocator in this document (§2.1, §2.6); when it lands,
fold immediate-forwarding into the call-boundary marshalling on allocator liveness and
delete `ImmediateCallOptimization` (Stage 2 step 9).