Repository navigation
Interpreter: a threaded String argument is content-hashed once per lineage, not once per call - #12065
Conversation
…neage, not once per call parse_json_document was reported as not returning on a large document. The json_parse_ladder instrument (tools/json_parse_ladder) discriminates the three readings: the parser is linear with GUNBC_EVAL_MEMO=0 on the same binary and inputs (not a parser defect), plain deep and flat recursion is cheap (not a general interpreter property), and a walk that merely passes a large string through a recursion pays the whole cost (a shared primitive). The link is the default-on eval-call memo's key derivation: composites reach the content key through an identity memo, strings did not, so every pure call rehashed its String arguments in full. RcStr now carries its content hash, filled on first demand, beside the ASCII flag; v1_rt::str_content_hash is the one hash function; the two Value::Str key sites read the carried hash. Key values are unchanged; re-serialized documents are byte-identical between the fixed and unfixed binaries. Residue, not fixed here: a freshly built accumulator argument misses the identity memo on every step and is rehashed in full, so the parse is still super-linear with the memo on. That is a provider-admission question for the eval-call memo, not a parser repair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a declared rung with a capability trigger The route by which the eval-call memo's key sites read RcStr's carried hash is established by measurement only: reverting a Value::Str key site leaves every committed test green. That gap now lives on a gunbc.recurring_failure_mode row, not in a PR body, with its restoration trigger named as the capability that would make a reverted key site go red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ag, every rung checks its own value Review 70155: the committed ladder read gitignored fixtures that nothing in the tree produced, and its Python generator was never committed (the repo ignores *.py by policy). tools.json_parse_ladder now lives with the other instruments in dag/gunbc/instruments, builds its documents in memory with a bounded-depth builder, and returns ProcessExit per rung -- 0 only when the rung returns the value it owes, a located failure otherwise. The failure-mode row and the seed admission now cite it by symbol. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Review 70155 was right, and the defect was worse than it read: the generator did exist locally, but
Stated plainly so the provenance is not overstated: the curves in the PR body were measured with the earlier file-based harness. The rebuilt module measures the same shapes over near-identical documents, but I re-ran only I did not add a — sent from crisp-seal-609 |
Node:
adhoc-cec2976b-a10. The brief asked for the (a)/(b)/(c) discrimination first. This PR reports it, repairs the one link that is proven and local, and hands the remaining link off as a decision. It does not makeparse_json_documentlinear on large documents. Do not treat it as firing gunbc#12012's dissolution trigger.The discrimination
Instrument:
tools.json_parse_ladder(dag/gunbc/instruments/json_parse_ladder.dag). It builds safetensors-index-shaped documents in memory, so nothing sits beside it. Run onegunbc run --entry dag/gunbc/instruments/json_parse_ladder.dag --function <rung>per rung, with the process timed by the caller. Each rung returnsProcessExit: 0 only when it returned the value it owes (member count, step count), a located failure otherwise. Every invocation pays one corpus-resolve baseline and each document rung pays its own construction, so a rung is read against itsbuild_only_<n>rung, and only curves are compared, never absolute seconds. No figure from the brief was reused; everything below was re-derived with this instrument.parse_4000…parse_96000, memo on vsGUNBC_EVAL_MEMO=0(the memo's declared diagnostic realization switch, identical values), same binarydepth_20000_times_4,depth_80000,flat_80000,flat_320000pass_large_textvspass_held_in_scopeandpass_small_textThe shared primitive is
v1_interpretereval_pure_named_call→eval_recompute_key. The eval-call memo is on by default and keys every pure call by argument content:eval_recompute_hash_memo, which is identity-keyed andWeak-guarded.Value::Strwas fed toeval_recompute_str_hashdirectly, on every call.So a recursion that threads one document through its steps (every JSON span scanner does) rehashed the entire document per step: O(|text|) per call, O(|text|²) in total. The parser is a victim. The link that fails its own contract is the key derivation: it states one relation (content hash) and realizes it at per-call O(size) cost for exactly one value kind.
The repair (this PR)
v1_rt::RcStrnow carries a second fact beside the ASCII flag: its content hash, filled on first demand.v1_rt::str_content_hashis the single hash function; the interpreter'seval_recompute_str_hashdelegates to it.Value::Strkey sites readRcStr::content_hash.Edited in the authority
v1.compiler.runtime_rust(src/v1/runtime_rust.dag). The mirrors were regenerated throughclaim_executor --required-regen, two passes, becausev1_rt.rsis emitted from the template string compiled into the running seed.Not done, and why:
extdeps.languages.jsonis wrong.GUNBC_EVAL_MEMO=0was the discriminator, not the fix.Weak<str>keeps the string's bytes allocated, so it would retain every hashed argument for the context's lifetime.Seed admission. Two
pubitems join the seed surface. That is argued ingunbc.v1_maintenance_standingon the same ground as gunbc#9256'sRcStradmission, and inherits that argument's stated weakness. A reviewer may reject it on that ground.Evidence
reserialize_1000,reserialize_16000(--arg path=<file>) re-serialize the parsed document throughextdeps.languages.json.emitserialize_json. The fixed and unfixed binaries (both built from this tree) produce byte-identical output: same length, same sha256. Member counts are equal at every rung.gunbc run, with the memo in its default-on state, i.e. the route the symptom was reported on. The 16k rung drops by a large multiple; the passed-string walk returns to baseline.v1-stage0-runtimerc_str_content_hash_tests, all green:eval_recompute_str_hash(s)would leave every committed test green. This is filed asgunbc.recurring_failure_modememo_key_cost_proportional_to_argument_size, at the rung found (mitigatable, by measurement only). Its restoration trigger names the capability: an observation of content-hash computations readable by the interpreter's own tests (a work-counter cargo feature or an exported accessor), sufficient for av1-compilertest that goes red when a key site is reverted. The same row carries the open accumulator instance and names its work item.Residue: NOT fixed, and it is a decision rather than a patch
With this fix and the memo on, the top of the ladder is still super-linear. That falsifies my pre-registered prediction that the fixed curve would match the memo-off curve.
Discriminator:
accumulate_10000…accumulate_40000, memo on vs off. A recursion threading a growinglist_pushaccumulator is super-linear with the memo on and at baseline with it off.parse_json_object_membersthreads exactly such an accumulator. A persistent list is a newRcevery step, so the identity hash memo misses and the whole accumulator is rehashed each call.Same class, different value kind, and it cannot be closed at the carrier. The real question is whether the eval-call memo should key calls that can never recur (each step differs in its index), i.e. whether its per-call serving cost exceeds recomputation under
std.materialization_ladder. That is a provider-admission ruling for a shared interpreter provider, and it is escalated to the parent lane rather than decided here.Unrelated drift seen in passing:
--required-regenalso reportsstd_measure.rsdrifting on this base. It is untouched here.🤖 Generated with Claude Code