← board

ESP32: Compiler-Directed ISR and IRAM Support

Decision 2026-06-21 — windowed interrupt; is out of scope BY DESIGN (not a gap)

interrupt; is a raw hardware-vector directive: the proc is entered directly by the CPU as a vector, saves its own context, and returns via the exception-return insn (riscv mret, xtensa rfe). Two independent axes decide where that is valid:

Call0 Windowed
Bare-metal (you install the vector) interrupt;DONE (rfe/mret) does not boot — no vecbase / window-overflow handlers (compiler.pas already errors --esp-profile=bare + windowed)
IDF + FreeRTOS (OS owns the vector + dispatcher) use iram;+esp_intr_alloc use iram;+esp_intr_alloc; FreeRTOS _xt_context_save spills windows once, then calls your fn normally

The windowed + raw-vector cell has no valid configuration: bare-metal xtensa can't boot windowed, and under IDF you never install a raw vector — you register a normal callback via esp_intr_alloc, and FreeRTOS does the window spill for you. A windowed raw interrupt; would have to emit a Tensilica-style window-spill loop inside the ISR — duplicating the FreeRTOS dispatcher, untestable here (bare windowed doesn't boot; IDF wouldn't use it), and a loop-in-ISR hazard. Rejected.

Resolution:

Status 2026-06-18 — iram; DONE (both ISAs)

procedure foo; iram; places the routine's machine code in a new ELF .iram1.text section (the IDF linker script routes it to internal IRAM). Validated: readelf -S shows .iram1.text PROGBITS / AX (ALLOC|EXECINSTR) on esp32s3 (xtensa) + esp32c3 (riscv32); test_esp_iram runs under qemu == x86-64 oracle (S/ABC/ABCDE/E). make test + cross-bootstrap byte-identical.

Design (single Code[] buffer, low-risk): .text = the full Code[] verbatim (each iram proc keeps a dead twin there so flash PC-relative call offsets computed at emit time stay valid); .iram1.text = a live duplicate of each iram proc's contiguous byte-range (internal relative branches + xtensa l32r literal pools survive because the whole proc moves as a unit). Cross-section calls (flash↔iram) can't stay PC-relative, so EmitCallProc lowers them to an indirect literal-slot call (same shape as an external call) recorded as an IramCallFix and relocated R_*_32 against the callee proc's own local symbol. The extended writer (writeELF32RelIram, gated on any iram proc so the proven non-iram path is byte-identical) partitions relocations by which text section their CodePos lands in and emits proc symbols with st_shndx = .iram1.text for iram procs. On non-ESP targets iram; is an accepted no-op (same source builds as the x86-64 oracle).

Status 2026-06-21 — interrupt; DONE on riscv32 (esp32c3); structurally verified

procedure foo; interrupt; now compiles a raw hardware trap handler on riscv32 (9ab4304). Prologue saves the interrupted caller-saved context (t0-t6, a0-a7; ra/s0 via the normal frame, 64-byte save area above the frame), the body runs, the epilogue restores that context and returns via mret (added rv32_mret). interrupt implies iram, so the handler lands in .iram1.text (existing placement + cross-section call lowering). Gated on ProcIsInterrupt → non-interrupt riscv codegen byte-identical, self-host unchanged.

Verified structurally (test/test_esp_interrupt.pas + --emit-obj, riscv32-esp-elf-{readelf,objdump}): MyIsr sits in .iram1.text; the disasm shows the full t0-t6/a0-a7 save, the normal ra/s0 frame, a working cross-section indirect call into flash (esp_rom_printf), the mirrored restore, and mret.

Remaining:

@proc on bare riscv32 — DONE 2026-06-21 (38242eb)

@Routine now yields the absolute code address on the bare ET_EXEC image: IR_PROCADDR emits a PC-relative inline-literal load (auipc/jal/literal/lw) recorded as a ProcAddrFix, patched to entry+BodyAddr by writeELF32 (the generic patch loop already existed; the esp32 guard was relaxed for riscv32). test_esp_procaddr matches the x86-64 oracle on esp32c3. This is the first half of the @isr path; the .o relocatable writers still need it (above), as does a self-contained live trap test (still also blocked on CSR setup: no rv32 inline asm / CSR ops to write mtvec).

Motivation

To support safe execution of Interrupt Service Routines (ISRs) under both bare-metal (esp32-bare) and SDK-hosted (esp32-idf) profiles. Code executing in an interrupt context must reside in internal Instruction RAM (IRAM) to prevent cache fetch exceptions when the flash cache is disabled. The compiler needs language-level keywords to route specific procedure code to IRAM and wrap raw bare-metal hardware interrupt registers.

Scope

Non-goals

Acceptance