← board

An interrupt; proc reached through a normal call returns via mret, silently

The mechanism

Measured 2026-09-21 at pin v414 by disassembling test/test_esp_interrupt.pas compiled --emit-obj on both ESP ISAs. An interrupt; body is a correct raw trap routine:

riscv32 xtensa Call0
prologue saves t0-t6, a0-a7 (64 B) + ra/s0 a2-a13 (48 B) + a0/a15
returns via mret (30200073) rfe (003000)

That is exactly right for a hardware vector entry and exactly wrong for anything reached by jalr/callx. mret/rfe pop privilege state and jump through the machine exception PC — in a normal call there is no trap frame to return through, and the return address the caller left is never consulted.

Why nothing catches it

So the failure surfaces only as a fault on the device, at a point unrelated to the declaration, which is the most expensive place this fleet can be wrong.

Why this is a bug rather than documentation

The two existing call sites are both correct, and that is the whole hazard: the rule is enforced by coincidence. CLAUDE.md's normalise-dont-special-case sibling clause is exactly this shape — "both spellings mean the same thing to the person who wrote the source, so neither the construct name nor the test corpus distinguishes them" — and its prescription is to reach for the other spelling's handler rather than the feature. Here the cheaper discharge is a refusal at the one site that can see both facts.

Suggested shape (not prescribed)

Refuse, or at minimum warn, when @<proc> of a routine with ProcIsInterrupt set is used as anything other than the operand of a raw vector install. Today that is trivially decidable because there is no raw vector install at all — see the CSR ticket below — so every @interrupt_proc is currently a mistake. That makes the positive control easy and it makes the refusal cheap now and refinable later.

Positive control, and it must be drawn from the right population: test_esp_isr_register.pas with its iram; changed to interrupt; must be REFUSED, and the file as it stands must still compile. Assert both arms — a refusal that also rejects the correct spelling is not a guard, it is a break.

Log