Repository navigation
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) <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 6, 2026
* docs: changelog and version scoping for 1.3.0 1.3.0 pairs only with engine v1.3.0. The changelog section takes in the Unreleased note and covers #173, #178, #179, #177, #174 and #175: the confirmed-bar receipt and what engine v1.3.0's live runner does with it, the capability receipt's request entries now read from the lowering, the order-shapes receipt, which no engine reads yet, the checked settings fix for string dropdowns with built-in constants (now supported=false where 1.2.0 wrote an integer code or published the input as supported), the PineForge Source License 1.2, and the pairing and migration steps. The Python and JSON contract is unchanged; the engine's report harness is the same file as in v1.2.0, so no report key changes. The README, AGENTS.md / CLAUDE.md, CONTRIBUTING.md, LEGAL.md, docs/PUBLIC_CONTRACT.md, docs/pine-cap-activation.md and npm/README.md name 1.3.0 where they named 1.2.0 as the current release or pair, the pairing table gets a 1.3.0 row, the "unreleased (after 1.2.0)" markers name 1.3.0, and the license badge reads 1.2. The release scoreboard sentence and the pf markers are left to the release lane. Measured for 1.3.0: the engine's 325 corpus sources and the 277 gate fixtures, each in a fresh process, transpile to 1.2.0's C++ byte for byte once the confirmed-bar and order-shapes functions are removed (13 refusals, same messages), with transpile_full results and transpile_json envelopes without cpp equal to 1.2.0's, each in under 0.1 s on CPython 3.14.6 on an Apple M4 Max. The tutorial run of the quick-start C++ against engine main 7a1f01c0 (Linux x86_64, GCC 13) prints 9 trades and +738.20, and 13 trades with the old sizing declared, as 1.2.0 with engine v1.2.0 does. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: release 1.3.0's grades in the README and the changelog, from the facts tokens The README's release sentence moves to the releases[1.3.0] tokens and its main-scoreboard lines re-render, and the 1.3.0 changelog says which registry baseline grades the release and why that baseline is the release's code, all rendered from pineforge-release facts/facts.json at ff21de0 (#32). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
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