← board

The exec() __body__ trampoline is boxed as an OBJECT

Closes Track T's open regression test-uforth#00 (plexus), bad 6590c700cbca, last good c5ed9a8311c2.

The bisect is right and the conclusion it invites is wrong

Both sides reproduced directly, building each commit:

commit uforth smoke
c5ed9a8311c2 prints 3 3
6590c700cbca (the callee guard) SIGSEGV, no output at all

So the guard commit is genuinely where it starts — but the guard is not the defect and must not be reverted. It exposed a mis-tag that had been sitting in pyeval.pas harmlessly:

l.store(MakeStr('__body__'), PyBoxObj(Pointer(@PyBodyTramp)));

PyBoxObj stamps VT_OBJECT (7) — the tag that asserts "my payload is a headered heap instance" — onto a code address. Nothing in the runtime ever inspected a tag-7 payload as an instance, so the claim was never tested and the lie stayed inert. The guard then added, for a tag-7 receiver:

if PyFindMethCI(GetInstanceRTTI(Pointer(...Payload)), '__call__') = nil then

GetInstanceRTTI reads the class pointer at [p-8]. For the trampoline that is whatever bytes precede its entry point, so PyFindMethCI walked a garbage ParentRTTI chain and the process died — with uforth's output still in the buffer, which is why the test log showed nothing rather than a partial run.

Fix

pyvar_of_callable instead of PyBoxObj. It stamps VT_CALLABLE (12) for a bare code address — the tag added by [[feature-nilpy-a-callable-value-needs-its-own-variant-tag]] for exactly this shape — and takes no phantom reference (PyBoxObj also called PXXObjRetain on a static address; magic-guarded, so a no-op, but it was asserting ownership of something it could not own).

Dispatch is unchanged: pyvar_callv<n> sees tag 12, the guard allows it, PyCallableObj finds neither closure nor bound-fn magic, and the payload is called as the code address it always was.

Sibling sweep

Every other PyBoxObj call site was checked and boxes a genuine object — an instance, a TPyBytes, a TPyList. This was the only one handing it code.

Two measurement traps this hit, recorded because both nearly won

Gate

make test-uforth (smoke + the differential corpora + the ANS suite) and tools/gate.sh quick + self-host byte-identical.

Verified

Log