fix(codegen): context-sensitive cloning so nested stateful helpers don't share TA/var state#32
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A stateful Pine helper reached through multiple distinct call paths shared one TA member (and one
var). Two compounding defects:_ta_site_mappoisoning (codegen/base.py): function-local TA sites are shared across clones (clones copynode=orig.node). The_u{n}collision-renamed clone sites overwrote the canonicalcs0entry, so the active-remap lookup missed its base name and every clone collapsed onto one member.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. Forleg(reached viaf_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-sizedta::Highest/Lowestmembers 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: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 freshvarmembers (so two paths never share scalar state)._instance_dispatchthen authoritatively routes each nested call.Validation
legreached via 9 paths)PINEFORGE_ENGINE_INCLUDE=... pytestNew
test_nested_helper_multi_path_distinct_ta_members/..._distinct_var_statehave teeth (fail on the unpatched tree: only 2 clones + dead members).🤖 Generated with Claude Code