Skip to content

[Task 111 defer] Cross-fn discharge propagation requires shared state — defer to Task 117#51

Merged
boldfield merged 1 commit into
mainfrom
plan-d-task-111-defer
Apr 30, 2026
Merged

[Task 111 defer] Cross-fn discharge propagation requires shared state — defer to Task 117#51
boldfield merged 1 commit into
mainfrom
plan-d-task-111-defer

Conversation

@boldfield
Copy link
Copy Markdown
Owner

Defers Plan D Task 111 (TLS → packed multi-return for sigil_run_loop). Plan B' carryover #1 stays open with revised closure scope.

Why deferred

Three implementation attempts on PR #50 all failed identically. The bug is architectural, not implementation:

Attempt Mechanism Result
4dfdbc7 Cranelift [I64, I64] register-pair multi-return + Rust #[repr(C)] struct { u64, u64 } 10 e2e tests fail (discharge-class shapes)
670f7a1 Out-pointer ABI + Cranelift Variable per-fn last-terminal vars Same 10 tests fail
5e2686e Out-pointer ABI + per-fn StackSlot (explicit memory ops) Same 10 tests fail

Diagnostic eprintln commit 4086307 confirmed the cause. For catch_example_recovers_with_42:

  • DISCHARGED bypass: writing (42, tag=2) to out=0x7ffc6c12aac8 (in risky's frame)
  • top-level terminal: writing (0, tag=0) to out=0x7ffc6c12ab00 (in user-main's frame)

The OLD TLS achieved cross-fn shared state implicitly because TLS is thread-global. Per-fn / per-call mechanisms scope the (value, tag) to the immediate caller; the discharge is invisible to the enclosing handle expression in a different stack frame. Specifically: risky has UserFnAbi::Sync (body shape mismatch with all three Cps body classifiers), so its discharge writes its own slot, then risky's body continues sequentially (result = 42; result + input = 49) and returns 49 via Sync ABI; user-main's handle exit reads its own untouched slot.

Closure path

  1. Recommended: defer to Task 117 first-class-k follow-up. Task 117 modifies the same surface; co-shipping the lift uses whichever ABI Task 117 settles on, informed by Task 117's actual requirements rather than guessed in advance.
  2. Alternative: thread *mut TerminalResult through every fn ABI as an extra parameter — own multi-PR architectural slice, comparable scope to Plan B' B.3 TypeExpr::Fn lift. Out of Plan D scope unless explicitly authorized.

Files changed

  • PLAN_D_DEVIATIONS.md — adds [DEVIATION Task 111] (53 LOC)
  • PLAN_D_PROGRESS.md — Task 111 status flips to deferred with closure-path summary

No code changes. PR #50 (the failed implementation attempts) closes without merge; its four commits are preserved on the plan-d-task-111 branch for the diagnostic record.

Stage 11 next

Task 112 (wrapper-fn-frame composition fix). Stage 11 review checkpoint will close on Task 112 alone since Task 111 deferred.

Test plan

  • CI green on ubuntu-24.04 (build + test, cold-checkout) — no code change so should pass cleanly
  • CI green on macos-14 (build + test, cold-checkout)

… — defer to Task 117 follow-up

Three implementation attempts on PR #50 (4dfdbc7 register-pair
multi-return, 670f7a1 out-pointer + Variable, 5e2686e out-pointer +
StackSlot) all failed identically with discharge-class shapes. A
diagnostic eprintln commit (4086307) confirmed the bug is
architectural, not implementation: the OLD LAST_TERMINAL_TAG/_VALUE
TLS achieved cross-FN shared state implicitly because TLS is
thread-global; per-fn / per-call mechanisms can't reproduce this.

Diagnostic case (catch_example_recovers_with_42):
  DISCHARGED bypass: writing (42, tag=2) to risky's slot 0x...aac8
  top-level terminal: writing (0, tag=0) to user-main's slot 0x...ab00

risky has Sync ABI (its body shape — let result = raise(...);
result + input — doesn't match any Cps body classifier). risky's
discharge writes its OWN slot. user-main's handle exit reads
user-main's slot, never touched by risky. Result: handle takes
DONE path with body_val = risky's normal-completion 49 instead of
discharge value 42.

Closure path (per [DEVIATION Task 111]):

1. Recommended: defer to Task 117 first-class-k follow-up. Task 117
   modifies the same surface; co-shipping the lift uses whichever
   ABI Task 117 settles on, informed by Task 117's actual
   requirements rather than guessed in advance.

2. Alternative: thread  through every fn ABI
   as an extra parameter — own multi-PR slice, comparable scope to
   Plan B' B.3 TypeExpr::Fn lift. Out of scope for Plan D.

Plan B' carryover #1 status updates to deferred-with-revised-
closure. The OLD TLS approach continues to work for all e2e tests;
no user-visible surface is affected by this deferral.

PR #50 closes without merge; the four Task 111 commits are
preserved on the plan-d-task-111 branch for the diagnostic record.

Stage 11 next: Task 112 (wrapper-fn-frame composition fix).
EOF
@boldfield boldfield merged commit d654b10 into main Apr 30, 2026
4 checks passed
@boldfield boldfield deleted the plan-d-task-111-defer branch April 30, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant