Skip to content

fix(codegen): context-sensitive cloning so nested stateful helpers don't share TA/var state#32

Merged
luisleo526 merged 1 commit into
mainfrom
fix/nested-helper-context-sensitive-cloning
Jun 30, 2026
Merged

fix(codegen): context-sensitive cloning so nested stateful helpers don't share TA/var state#32
luisleo526 merged 1 commit into
mainfrom
fix/nested-helper-context-sensitive-cloning

Conversation

@luisleo526

Copy link
Copy Markdown
Contributor

Problem

A stateful Pine helper reached through multiple distinct call paths shared one TA member (and one var). Two compounding defects:

  1. _ta_site_map poisoning (codegen/base.py): function-local TA sites are shared across clones (clones copy node=orig.node). The _u{n} collision-renamed clone sites overwrote the canonical cs0 entry, so the active-remap lookup missed its base name and every clone collapsed onto one member.
  2. Broken nested dispatch (codegen/visit_call.py): a nested helper call inside a clone emitted {G}_cs{enclosing_active_cs}, conflating the callee's own textual call sites with the enclosing function's. For leg (reached via f_get×3 / track / wyckoff×2 / getcur×2 / direct = 9 paths) this produced 5 clones all bound to one shared length-30 member; the other 8 correctly-sized ta::Highest/Lowest members were declared but dead.

Fix

Add a context-sensitive (call-path) instance pre-pass _build_func_instances: walk the call graph from each natural clone and, per nested stateful call, compose the enclosing clone's active remap with the callee's per-call-site remap:

composed[m] = R_enclosing.get(R_callee_cs[m], R_callee_cs[m])

When the composition equals the callee's natural cs{j} remap it reuses the existing {G}_cs{j} clone — so output is byte-identical for the common single-caller case. Otherwise it mints a fresh instance bound to the path-specific TA members and fresh var members (so two paths never share scalar state). _instance_dispatch then authoritatively routes each nested call.

Validation

result
jevondijefferson-big-breakout (leg reached via 9 paths) 0.9% → 36.1% match vs TV (count 143→172, price-exact 94.9% of matched); deterministic ×3
full gate PINEFORGE_ENGINE_INCLUDE=... pytest 1386 passed, 1 skipped, 0 failed (1384 baseline + 2 new tests; 254-strategy corpus compile all green)
87-strategy behavioral sweep byte-identical transpile on 86/87, only jevondijefferson moved (improved) — zero regressions

New test_nested_helper_multi_path_distinct_ta_members / ..._distinct_var_state have teeth (fail on the unpatched tree: only 2 clones + dead members).

Note: the residual to 36% on jevondijefferson is a window-start warmup transient (path-dependent state needs TV's full pre-range history), not a fill-model or accounting gap — out of scope for this codegen fix.

🤖 Generated with Claude Code

…n't share TA/var state

A stateful helper reached through multiple distinct call paths shared one TA
member (and one var). Two compounding defects:

1. _ta_site_map poisoning (codegen/base.py): function-local TA sites are shared
   across clones (clones copy node=orig.node). The `_u{n}` collision-renamed
   clone sites overwrote the canonical cs0 entry, so the active-remap lookup
   missed its base name and every clone collapsed onto one member.
2. Broken nested dispatch (codegen/visit_call.py): a nested helper call inside a
   clone emitted `{G}_cs{enclosing_active_cs}`, conflating the callee's own
   textual call sites with the enclosing function's. For `leg` (reached via
   f_get x3 / track / wyckoff x2 / getcur x2 / direct = 9 paths) this produced 5
   clones all bound to one shared length-30 member; the other 8 correctly-sized
   ta::Highest/Lowest members were declared but DEAD.

Add a context-sensitive (call-path) instance pre-pass `_build_func_instances`:
walk the call graph from each natural clone and, per nested stateful call,
compose the enclosing clone's active remap with the callee's per-call-site
remap (composed[m] = R_enclosing.get(R_callee_cs[m], R_callee_cs[m])). When the
composition equals the callee's natural cs{j} remap it reuses the existing
{G}_cs{j} clone — so output is BYTE-IDENTICAL for the common single-caller case;
otherwise it mints a fresh instance bound to the path-specific TA members and
FRESH var members (so two paths never share scalar state). _instance_dispatch
then authoritatively routes each nested call.

jevondijefferson-big-breakout (leg reached via 9 paths): 0.9%->36.1% match vs
TradingView (count 143->172 toward TV 214, price-exact 94.9% of matched);
deterministic across runs. Full gate PINEFORGE_ENGINE_INCLUDE=... pytest: 1386
passed, 1 skipped, 0 failed (1384 baseline + 2 new tests; 254-strategy corpus
compile all green, 0 new failures, nothing added to KNOWN_*_FAILURES). 87-strategy
behavioral sweep: byte-identical transpile on 86/87, only jevondijefferson moved
(improved). New test_nested_helper_multi_path_distinct_ta_members /
..._distinct_var_state have teeth (fail on the unpatched tree).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@luisleo526 luisleo526 merged commit ceaf1a2 into main Jun 30, 2026
9 checks passed
@luisleo526 luisleo526 deleted the fix/nested-helper-context-sensitive-cloning branch June 30, 2026 15:05
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