kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit f500bfdc04a79ef0ef3c4b10958be34fa3a3c327
parent afb4cccb5119f51e1af693d59e161745019087ce
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 15 May 2026 10:24:37 -0700

docs: describe LocalId storage policy

Diffstat:
Adoc/LOCALS.md | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+), 0 deletions(-)

diff --git a/doc/LOCALS.md b/doc/LOCALS.md @@ -0,0 +1,127 @@ +# LocalId Design + +## Goal + +Make source locals independent from concrete frame slots. + +Today many frontend paths treat a local as a `FrameSlot`. That makes every +local memory-backed even when CG is recording to `opt_cgtarget`, where +`CGTarget.virtual_regs` already means CG can mint unbounded virtual registers +and leave physical allocation to opt. + +The new model introduces `LocalId` as the source-local identity. A `LocalId` is +the mutable lvalue for a local variable. Its storage policy is chosen once at +allocation time from the active `CGTarget`; it does not transition later. + +## Storage Policy + +Storage policy is a function of `CGTarget.virtual_regs`: + +- `virtual_regs != 0`: eligible locals are virtual-register locals. +- `virtual_regs == 0`: locals are frame-slot locals. + +This keeps `-O0` single-pass. Direct machine CG never needs source-local +liveness, local register pressure handling, or control-flow join repair. It +continues to use frame slots for locals and the existing expression vstack +spill path for temporary register pressure. + +The opt path records mutable local values in virtual registers. Register +pressure and frame placement are handled later by opt/lowering/regalloc, where +CFG and liveness information exist. + +## LocalId Shape + +Internally, a local needs roughly: + +```c +typedef enum LocalStorageKind { + LOCAL_STORAGE_FRAME, + LOCAL_STORAGE_VREG, +} LocalStorageKind; + +typedef struct Local { + CfreeCgTypeId type; + CfreeSym name; + uint32_t flags; + LocalStorageKind storage; + FrameSlot slot; /* valid for LOCAL_STORAGE_FRAME */ + Reg vreg; /* valid for LOCAL_STORAGE_VREG */ +} Local; +``` + +`LocalId` indexes this table. It is not a `FrameSlot`, and `OPK_LOCAL` should +remain the concrete frame-memory operand kind. If a local is frame-backed, +pushing it produces the same lvalue shape that `cfree_cg_push_local` produces +today. If a local is virtual-register-backed, pushing it produces a mutable +register local lvalue. + +## Lvalue Semantics + +`LocalId` is the source lvalue. The backing store decides how load, store, and +address operations lower: + +- frame local load/store: operate on the frame slot. +- virtual-register local load: read the current virtual register value. +- virtual-register local store: copy/define the local's virtual register value. +- address of a frame local: address the frame slot. +- address of a virtual-register local: unsupported unless the frontend has + already selected frame storage for that local. + +Virtual-register locals are mutable pseudo-locals, not SSA values at the public +CG layer. If opt later wants SSA, it must build SSA from this mutable local +stream. `LocalId` should not force direct CG to implement phi insertion. + +## Addressable Locals + +A local that can require an address must be frame-backed. Since `-O0` must stay +single-pass and this design has no storage transition, the frontend/API needs a +declaration-time way to request addressable storage. + +Use the existing slot-style attributes as the semantic source: + +- `CFREE_CG_SLOT_ADDRESS_TAKEN`: force frame storage. +- aggregate, VLA, `alloca`-like, volatile, ABI-required memory objects, and + compiler temporaries that need an address: force frame storage. +- scalar locals without addressable requirements may use virtual-register + storage when `CGTarget.virtual_regs` is set. + +This may be conservative. Correctness is more important than promoting every +possible scalar. Later analysis can mark more locals register-eligible before +creating them, but CG itself should not need to discover that mid-stream. + +## Public/API Direction + +The API should grow local handles distinct from slot handles: + +```c +CfreeCgLocal cfree_cg_local(CfreeCg*, CfreeCgTypeId type, + CfreeCgSlotAttrs attrs); +void cfree_cg_push_local_id(CfreeCg*, CfreeCgLocal local); +``` + +Compatibility can keep `cfree_cg_local_slot` and `cfree_cg_push_local` as the +explicit frame-slot path. C frontend migration should move source automatic +variables to `LocalId`; explicit stack objects and address-required temporaries +can keep using slots. + +## Non-Goals + +- No `REG -> FRAME` local transition in direct CG. +- No O0 local spilling for register-backed source locals. +- No phi or join repair in direct CG. +- No change to `OPK_LOCAL` meaning; it remains concrete frame memory. +- No guarantee that every scalar local becomes a virtual register. Addressable + or otherwise memory-required locals stay frame-backed. + +## Implementation Order + +1. Add the internal `LocalId` table and public/internal handle plumbing. +2. Route local allocation through the storage policy above. +3. Teach push/load/store/address paths to handle frame-backed and vreg-backed + locals. +4. Update the C parser adapter so normal automatic variables use `LocalId` + while explicit frame objects remain slots. +5. Keep O0 behavior equivalent by verifying `virtual_regs == 0` still allocates + frame-backed locals only. +6. Add opt-path tests that scalar locals record as virtual-register locals and + address-required locals still record frame slots.