Punch list §25 item 0.5 complete.
irq_spx now branches (one instruction, well inside the 128-byte vector slot)
to irq_spx_trampoline, a 672-byte-frame save/restore sequence that calls a
C handler and returns via ERET. The other fifteen vectors are untouched,
still routing to the existing fatal handler.
EL selection (B3) happens once, in aarch64_install_vectors(), not per
interrupt: aarch64_current_el() (item 0.4) picks VBAR_EL1 or VBAR_EL2, and
the same answer is cached in a byte flag (el2_mode_flag) that the trampoline
reads to choose ELR_EL1/SPSR_EL1 vs ELR_EL2/SPSR_EL2 -- the two forms are
genuinely different MRS/MSR encodings, not runtime-selectable operands, so
this is the cheapest correct design: decide once at install time, branch
twice (save, restore) per interrupt afterward. VBAR_EL1 was previously
written unconditionally; this closes that half of item 0.4's known gap.
EL2 is coded from the architecture reference and cannot be boot-tested in
this environment (QEMU's aarch64 virt/EDK2 combination here yields EL1) --
reported as unverified rather than asserted as tested.
FP/SIMD save is not optional (B2, carried from item 0.4's finding that the
build has no -mgeneral-regs-only): the AAPCS64 caller-saved set -- v0-v7,
v16-v31, full 128 bits each -- plus FPSR/FPCR is saved and restored around
the C handler call. v8-v15 are callee-saved by the ABI and deliberately
excluded: the handler, being ordinary compiled C, preserves those itself.
aarch64_irq_handler() (interrupts.c) is deliberately empty. Distinguishing
which interrupt fired needs the GIC's IAR, which does not exist until item
0.6; nothing unmasks or routes any source to this vector yet, so the
function is not reachable during a normal boot. Per the item's own text,
no attempt was made to manufacture an interrupt to exercise this path early
-- 0.6 (GIC) and 0.7 (timer) are what prove it took and returned one.
Verified: every hand-computed frame offset (0, 16, 32 ... 640, frame size
0x2a0=672) checked against the actual disassembly of the built kernel, not
just visually reviewed -- save and restore sequences mirror exactly, and
aarch64_install_vectors' branch on the detected EL, the flag write, and the
trampoline's read of the same flag address all confirmed consistent. Boots
clean on real QEMU output, no regression: dict_hash 0x3d4e1daf289da94f
unchanged from the item 0.1-0.4 baseline, and the item 0.4 EL banner
("AArch64: running at EL1") still prints correctly ahead of "IDT installed.".
Only aarch64-scoped files touched (isr.S, interrupts.c) -- no shared loader
or header changed, so amd64 and riscv64 are provably unaffected; not
rebuilt for this item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
capsules/
FORTH personality files loaded by the VM at boot. A capsule is an immutable, content-addressed payload; its XXHash64 hash is its identity. Any mutation changes the hash and the birth protocol rejects the image.
Key files
| File | Type | Purpose |
|---|---|---|
init.4th |
(m) MAMA_INIT |
Default Mama VM personality — loaded at LBN 2048 |
ACL.4th |
user | Word-level ACL system; self-activating at boot |
zuse.4th |
user | Bootstrap superuser; loaded by ACL.4th |
doe.4th |
user | DoE workload words (EXEC-DOE) — opt-in |
init-0.4th … init-9.4th |
(p) |
Numbered personality variants |
init-l8-*.4th |
(p) |
L8 Jacquard mode variants (stable/volatile/diverse/temporal/transition/omni) |
hermes/init.4th |
(p) |
Hermes baby VM personality |
artemis/init.4th |
(p) |
Artemis baby VM personality |
Block namespace
Block ranges are shared across all loaded capsules — collisions cause silent word-definition overwrites.
| Range | Owner |
|---|---|
| 2048–2099 | init.4th |
| 2100–2199 | doe.4th |
| 3000–3999 | workload capsules |
| 4000+ | user-defined (ACL.4th, zuse.4th, …) |
Each block is limited to 1024 bytes. Verify with wc -c before committing.
See also
experiments/bare_metal/README.md— DoE protocols and block namespace rulesdocs/03-architecture/word-acl/DESIGN.md— ACL system designtools/mkcapsule.c— assembles capsules intocapsule_generated.c- Project root