Repository navigation
parse cost forensics - #6367
parse cost forensics#6367
Conversation
…+ empty-list semantics pin (fix 2) Manager-mandated merge-bar demands on #6362, addressed with durable, re-runnable witnesses rather than a one-off manual diff: - parse_tree_content_hash_witness.dag: content_hash(ParseTree) over the tiny/small corpus sample, computed once against the true PR base (0f98a26, built as its own release binary in an isolated worktree) and once against HEAD. Both hashes are byte-identical (witness.dag=9afb740ee43f9e61, execution_mode.dag=ed071cb2baeaeb61), proving fix 2 (list_at_optional -> list_head) and fix 3 (fold_list -> parse_match_arm_stmt_step) preserve parse semantics exactly, not just "no test regressions." - parse_token_first_empty_semantics_test.dag: two `test fn` claims pinning parse_token_first (post-fix, list_head-based) equal to list_at_optional(xs, index: 0) (pre-fix arm) on both the empty-list boundary case that motivated the rewrite and the ordinary present case. Both PASS via claim_batch. Note: the local `main` ref was stale (21fb5d1); the true PR base is origin/main's 0f98a26 (merge-base origin/main HEAD) — using the stale ref for the pre-fix binary produced a false "grammar_invalid" rejection unrelated to this PR (fixed upstream by #6337, landed after 21fb5d1 but before 0f98a26).
…us + token-stream witness lex_walk_step accumulated tokens via list_snoc_item every token (O(n^2) before parsing even starts) -- 4th instance of the same shape, this time in a different pipeline stage than the three already-landed 02_parse.dag fixes. Rewritten as reverse-cons + single .reverse() at tokenize()'s exit. Verified byte-equal via a new order-sensitive token-stream content hash (class+lexeme, in stream order) run against independently-built pre-fix and post-fix binaries on tiny/small corpus samples -- identical both sides, confirming a pure algorithmic rewrite. Full read-based census of 02_parse.dag's 9 list_snoc_item call sites, each dispositioned (dead/test-only, bounded grammar-analysis cost, or already-fixed) -- none explain the file-size-scaling growth still seen deeper in live parsing on datetime.dag. That residual is not resolved by this PR; grammar_coverage_p8_wall_clock_blocker records the honest state (tokenize confirmed non-bottleneck, DNF persists past grammar-validation, driver not yet pinned) so the marker does not claim a false completion. Added parse_table_record_hit/miss/lookup_call and parse_choice_residue_backtrack to the interpreter's call-frequency watchlist for the continuing in-session hunt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The auto-committer snapshotted a mid-edit state (b16a357) that deleted the ProductionFirstRow type but left its consumers referencing it, breaking typecheck. This completes that edit -- not a new change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… removal" This reverts commit dae6602.
…FirstRow removal"" This reverts commit e2a24c3.
…istinct-key discriminator parse_match_arm_stmt_step and parse_expr_repeat_step both accumulated per-iteration diagnostics via list_append(left: diags.reverse(), right: acc.acc_diags) -- the acc_nodes field in the same accumulators was already reverse-cons fixed, but acc_diags was missed by the prior list_snoc_item-scoped census (this is a different primitive). Converted to List<List<Diagnostic>> batches (Cons-prepended, O(1) per step) plus a single flatten pass at the end (parse_flatten_diag_batches). Verified byte-equal via parse_tree_content_hash_witness.dag on tiny/small (9afb740ee43f9e61 / ed071cb2baeaeb61, unchanged). Honest caveat: list_append(left: small, right: large) is O(len(left)) per the native fold_list_right implementation (it walks `left`, not `right`), so this may not be the O(n^2) site the growing list_append call count suggested -- kept as a correctness-neutral cleanup bringing acc_diags in line with the already-safe acc_nodes pattern, not claimed as a proven complexity fix. Removed the temporary witness_forensics_scale_datetime_half_report (scratch-path fixture, not for the repo). Added a cross-thread parse-memo effectiveness discriminator (PARSE_MEMO_LOOKUPS/HITS/DISTINCT_KEYS globals + record_parse_memo_lookup, wired into try_parse_table_memo_dispatch's parse_table_lookup arm) per the manager's decisive-discriminator request: lookups >> distinct with hits == 0 would prove the native memo cache never serves a re-attempted span. Result on the datetime.dag half-truncation: lookups=0 hits=0 distinct_keys=0 -- the native fast-path memo cache never engages at all on this run, a separate real finding (not proven to be the DNF driver, since the .dag-level table's own stats are never reached either, given the run still doesn't finish). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…_SITE_DUMP_SECS, drop debug eprintln Addresses cursor/composer-2.5's REQUEST_CHANGES on #6365: record_call_frequency, record_flatten_site, record_list_cons_tail_split, and record_parse_memo_lookup ran unconditionally on hot paths (eval_call, every free_monoid_to_vec materialization, every native List Cons match, every packrat memo lookup) -- only the stderr dump was env-gated, not the recording itself. Added a residual_hunt_forensics_enabled() OnceLock check (same env var the dump already uses) and an early return at the top of each recorder, so the default/production path pays one relaxed load per call site instead of a mutex lock + HashMap/HashSet write. Added a SCAFFOLD/dissolve-on marker per DESIGN Sec6 (dissolve when adhoc-c328b166-bca closes). Also removed the ADHOC-DEBUG eprintln arms in parse_table_memo_scope_and_key -- a local one-off diagnostic (auto-committer captured a mid-investigation snapshot before I could revert it) that already served its purpose: it proved the extraction logic is sound (zero branches fire on a working file, lookups/hits/distinct_keys populate correctly), so it's not needed going forward and was correctly flagged as ambient unconditional stderr noise on a production code path. Re-verified byte-equal (9afb740ee43f9e61) and forensics gating with/without GUNBC_FLATTEN_SITE_DUMP_SECS set. cargo fmt --all applied (pre-existing formatting drift on this file, unrelated to this change, caught by the --check gate). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…+ measured-zero on interpreter fold-site cut Continuation of adhoc-c328b166-bca residual-driver hunt (interpreter-side free_monoid_to_vec fold chokepoint; .dag-side fixes stay in #6365). Root cause PINNED via a new caller-attribution probe: the datetime interpreted- parse DNF is lex_repeat_loop (src/v2/compiler/01_tokenize.dag:158) folding the whole remaining source char-list (max_len=19021=file char count, elem=Int) once per repeat-token => O(n²) in source length. A .dag tokenizer dataflow bug, routed to the tokenize lane; this receipt is its acceptance baseline. Interpreter fold-site cut = MEASURED CLEAN ZERO, dropped: a streaming left-fold (no intermediate Vec) was built, proven byte-identical (parse-tree hash equal, tiny+small, two independently-built binaries), and moved neither wall-clock (~20s both) nor peak RSS (~168 MiB both) on small — elem=Int makes the removed alloc churn negligible and the Vec is transient (no peak-RSS contribution). Kept free_monoid_to_vec + a permanent note (DESIGN §6: a no-op displaces nothing). Lands: caller-attribution probe (DagFnGuard + record_fold_caller, gated behind GUNBC_FLATTEN_SITE_DUMP_SECS, dissolve-on the hunt) extending neat-stag's flatten-site instrument with the .dag-caller axis; + the measured-zero note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconciles the caller-attribution probe + measured-zero note onto the squash-merged #6365 tokenizer/parse fixes. Conflicts were purely additive (my probe blocks sit adjacent to neat-stag's flatten-site instrument); kept both sides. Diff vs main is now only v1_interpreter.rs + cli_run.rs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed — the conflict markers were transient, never in a pushed commit. Timeline: #6365 squash-merged into main at 02:28:04, which put my then-head Verified on the current head
— sent from nimble-otter-476 |
…6369/#6370) Resolution choices: - 02_parse diag accumulation: main's O(1) batch-cons + single flatten wins over per-step list_append (kills the 252k-appends cost the self-time profile measured); my O(1) memo position key preserved - 01_tokenize: my superset kept (repeat short-circuit == main's #6368 semantics, plus delimited short-circuit + CompiledLexRule precompile) - v1 interpreter: both instrument sets kept (main's fold-caller/memo discriminator + my big-fold site/builtin-time/self-time profiler); main's Map-grounding fast path moved into call_function_inner so both profiled and unprofiled paths hit it; env gate consolidated on main's residual_hunt_forensics_enabled (same var, one authority) - grammar_coverage ledger: main's fixes (1)-(4) text + my RESIDUAL CLOSED continuation renumbered (5)-(9) Gates: build green, fmt/clippy green, regen divergence 0, tiny/small packrat stats byte-identical (0/43/43, 0/200/200). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZSaWfysRfg28bNVS7ueX
… as a RewriteRule) Corpus fix (the #6367-probe datetime driver): v2.std.provenance span_index_record snoc-copied the whole entries list per minted occurrence (one near-corpus-sized copy per token from the 02_parse mint sites) and span_index_merge_entries snoc-copied per merged entry - both converted to O(1) Cons-prepend (entries newest-first). Order- insensitivity is a consumer-census fact carried on the carrier (span_index_entries_prepend_note): every consumer resolves by unique occurrence id (lookup first-match, merge presence-scan keeping base, resolve-textual-locus via lookup), ids minted unique by the monotone allocator. New witnesses (span_index_prepend_test): multi-record round trip with wrong-extent red discriminator, merge-dedupe-keeps-base, duplicate-id -> most-recent pinned (unreachable under unique minting), merge lookups insensitive to incoming record order. No reverse added: no order-sensitive consumer exists, pinned by execution. Catalog row (first corpus-derived R1 row): v2.lens.rewrite. accumulator_copy_to_prepend recognizes a measured Loop whose iteration body feeds the loop-carried accumulator port through the copied port of a registered copying append (lowered list_snoc_item xs=acc / list_append left=acc; WHICH port copies read from the single authority copied_port_fact_registry, realization pair in the row-local grounded registry). apply -> prepend + reverse-once Bind form; claimed Polynomial{2} -> Linear with the CLRS-style aggregate/arithmetic-series accumulator-reversal citation; registered as row 3 of rewrite_rule_registry. Witnesses (accumulator_copy_to_prepend_test, 12): green - snoc and append subjects rewrite to independently hand-built expected forms (content-hash + changed-hash discriminator), fixpoint, strict drop via rewrite_rule_claims_strict_drop with rewritten-form Linear oracle-measured and before-form Linear pinned as the honest copy-blindness residue (executable dissolution trigger), equivalence by execution on order-discriminating inputs with unreversed-prepend pinned UNEQUAL; red - prepend output form not recognized, non-accumulator append not recognized, effectful lookalike / acc-re-read / binder-capture refuse typed, non-Loop silent; floor names the rule on the snoc subject over the real registry. Verified by execution: 12 new rewrite witnesses PASS; 4 new provenance witnesses PASS; regressions all PASS - occurrence_id_model, parse_stamp_span_index, edit_locus_resolver (2), cost_shape_lowering recovery pins (2), rewrite_pass admission (8), registry integration (5), nested_membership floor verdict, floor_coverage census incl. dag/std/logic.dag counts pin (5), cost_coverage (3), R1 lens (3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
Parse-cost forensics: pin the interpreted-parse DNF root cause + a measured land-or-zero on the interpreter fold-site cut
Continuation of the
adhoc-c328b166-bcaresidual-driver hunt (neat-stag-780's #6365 named it "next-session work"). This lane took the interpreter-sidefree_monoid_to_vecfold chokepoint; the.dag-side accumulator fixes stay in #6365. Verdict up front: the datetime DNF root cause is pinned to one function, and the interpreter cut is a measured clean zero (dropped), leaving a reusable caller-attribution probe and a permanent no-op note.Root cause — PINNED:
lex_repeat_loop(src/v2/compiler/01_tokenize.dag:158)A new caller-attribution probe (below) attributes each large
fold_listto the.dagfunction that contains it. On datetime.dag (the DNF file), one caller dominates:19021is datetime.dag's exact character count.lex_repeat_loopdoesfold_list(xs: init.remaining)— it folds the entire remaining source char-list purely to bound an iteration, and afterdone=trueit still visits every remaining char (thetrue => statepass-through). So each call is O(remaining source length) regardless of the actual token length; once per repeat-token ⇒ O(n²) in source length. This is a second, deeper tokenizer O(n²), distinct from neat-stag'slist_snocreverse-cons fix — it confirms the "dies upstream of parse-proper" reframe and names the culprit. The root-cause dataflow fix (bounded recursion/Loop, bound = lexeme length not source length) is a.dagtokenizer change routed to the tokenize lane (neat-stag right-of-first-refusal); this probe receipt is the acceptance baseline.The interpreter fold-site cut — MEASURED CLEAN ZERO (dropped)
Candidate: stream
eval_fold_list_native(the left fold) over the Cons-chain instead of materializing an intermediateVec(DESIGN §2 redundancy removal). It was built, proven byte-identical, and measured against two independently-built release binaries (base = neat-stag head; fix = base + streaming):9afb740ee43f9e619afb740ee43f9e61✓ed071cb2baeaeb61ed071cb2baeaeb61✓Byte-identical, but neither wall-clock nor peak RSS moves (both within run-to-run noise). Why it must be zero: the datetime driver folds
elem=Intcodepoint lists (a trivial copy, not a deep clone — so the removed materialization is pure alloc churn of the cheapest element), and the intermediateVecis transient (freed each fold, so it never contributes to peak RSS). The real O(n²) is the caller re-folding the whole source (lex_repeat_loop), not this per-call materialization — so no interpreter change tofold_listcan reprice the DNF.Per DESIGN §6 (denominate in displaced cost; a no-op displaces nothing) and the operator's "root-cause dataflow only" steer, the streaming rewrite is dropped. A permanent in-place note records that it was tried and measured, so the next hunter does not re-derive it.
Two-axis site data (why tiny/small looked different from datetime)
free_monoid_to_vecper-site (by items), tiny → small → datetime:value_hash(284) 427k→455k (1.06×, ~flat), right-fold (2580) call-heavy on ~2-element lists,contains(5434), left-fold (2556) small. The parsed file grew 4.65× (43→200 memo positions) while these grew ≤1.6× — they are not the file-size driver.lex_repeat_loop.This de-lists all four
free_monoid_to_vecheavy sites (value_hash, left/right fold,contains) for future hunts: none is the datetime driver; the driver is a.dagcaller.What lands
v1_interpreter.rs):DagFnGuardpushes the innermost.dagfn name on eachcall_function;record_fold_callerattributes large folds (≥100 items) to that caller with elementtype_label. Gated behindGUNBC_FLATTEN_SITE_DUMP_SECS(a no-op otherwise — the disabled overhead is within the ~20s noise, confirmed since the fix binary carrying it matched base). Extends neat-stag's flatten-site instrument with the.dag-caller axis that pinnedlex_repeat_loop. dissolve-on: the residual-hunt work item closes.eval_fold_list_native— records the streaming attempt + result so it is not re-investigated.Test plan
parse_tree_content_hash_witnesstiny+small, base vs fix binaries — identical.cargo fmt --all --check,cargo clippy --all-targets -- -D warnings(local).🤖 Generated with Claude Code