← board

A direct call to an interrupt; routine is the same fault by another spelling

Why it is filed separately rather than left in a comment

d305e1afa refused @<interrupt proc> at the AN_PROCADDR convergence point. That closes the arm anyone hits by accident. It does not close the class, and this repo's own normalise-dont-special-case rule says the sibling is usually a spelling rather than a shape — which is exactly what this is. The code comment in ir.inc names it, but a comment is read by whoever is already editing that function; the point of a ticket is to be found by someone who is not.

The mechanism, restated so it does not depend on the sibling ticket

interrupt; emits a raw trap routine whose epilogue is mret (riscv32) or rfe (xtensa). Those pop privilege state and resume through the machine exception PC. Entered by a normal jalr/callx, there is no trap frame to return through and the caller's return address is never consulted. Nothing diagnoses it at any stage.

The actual obstacle

test/test_esp_interrupt.pas is a structural probe: it must get MyIsr EMITTED so the prologue/epilogue and .iram1.text placement can be inspected, without ever executing it. Its only lever today is a call behind a runtime-false guard:

if counter < 0 then MyIsr;   { never true; forces emission }

Refuse direct calls and that fixture has no way to do its job — and it is the fixture that proves the ISR codegen exists at all.

So the ticket is the mechanism, not the refusal

Pick one, then the refusal is trivial:

Positive control when it lands, both arms: a direct call to an interrupt; routine must be REFUSED, and test_esp_interrupt.pas (rewritten onto whatever mechanism is chosen) must still compile AND still emit MyIsr — assert the symbol is present in .iram1.text, not merely that the file compiled, or the new mechanism can silently do nothing and the row still passes.