Repository navigation
state_exec: free, print, compare, copy and snapshot deep values without native recursion - #657
Merged
Merged
Conversation
…ut native recursion The parser's nesting limit (parser.max_depth, default 256) exists because the reader recursed; it can rise only once nothing else recurses once per level of a value. Freeing, printing, comparing, copying, macro-expanding and snapshotting a value all did. - Release: value_tag and environment destructors drop the last owner of a list, dict, closure, generator or enclosing scope through detail::release_nested. The first release on a thread runs a loop over a local queue (8 owners inline, the rest on the heap); owners dropped while it frees something are queued, not freed inline. Dropping an owner that is not the last is still a plain decrement. - environment::lookup and set_existing are loops. - to_string, values_equal, deep_copy (state-data-get) and the stackless evaluator's macro substitute keep their work on heap stacks. The 1000-level caps of to_string, values_equal and deep_copy are gone: they did not protect small stacks (to_string overflowed a 256 KiB stack at 800 levels) and would have become functional limits once max_depth rises. to_string's 64 KiB length cap stays. - Snapshot codec: encode_value and decode_value are iterative. cvc::state itself recurses once per tree level on every write (fullName() and the childChanged climb to the root), so a list or dict more than 8 levels below where its stretch of nesting began goes into a __spill__ table under the encode root, referenced by __type__ "spill_ref" and the entry's index. Values at most 8 levels deep encode exactly as before. A value that contains itself throws instead of overflowing; decode rejects bad or reused references. __len__ is written once per container, not once per element. New state_exec_deep_data_test builds values 100000 levels deep at runtime and drops, prints, compares, macro-expands and snapshots them (10000 levels for the codec) on a 256 KiB thread. state_exec_bench_test gains ValueDropThroughput.
This was referenced Oct 17, 2026
Merged
transfix
added a commit
that referenced
this pull request
Oct 18, 2026
…xec-data-steps #653's branch merged master (bc20f8e), which brings #657 as 3003734. That is the content this branch already carried as the cherry-pick 4d3bc6b, so in every file where #658 builds on #657 (types.h/.cpp, intrinsics.cpp, stackless_evaluator.cpp, state_exec_bench_test.cpp) #653's side was byte-identical to 4d3bc6b, and #658's version stands. In src/cvc/tests/CMakeLists.txt, master's new test targets join #658's state_exec_data_steps_test lines.
This was referenced Oct 18, 2026
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.
Why
parser.max_depth(default 256, on the state tree) exists because the reader recursed. It can only rise once nothing else recurses once per level of a value. Freeing, printing, comparing, copying, macro-expanding and snapshotting a value all did. This PR makes them iterative.What
value_tagandenvironmentdestructors drop the last owner of a list, dict, closure, generator or enclosing scope throughdetail::release_nested.value_tagkeeps its moves.environment::lookupandset_existingare loops.to_string,values_equal,deep_copy(behindstate-data-get) andstackless_evaluator::substitute(macros) keep their work on heap stacks.to_string,values_equalanddeep_copyare gone. They didn't protect small stacks:to_stringoverflowed a 256 KiB stack at 800 levels. They would also have become functional limits oncemax_depthrises.to_string's 64 KiB output cap stays.values_equalanddeep_copystill poll the evaluation deadline.encode_valueanddecode_valueare iterative.cvc::stateitself recurses once per tree level on every write (fullName(), andchildChangedclimbing to the root), so the codec also keeps the tree it writes shallow.__spill__table under the encode root. Its place holds__type__spill_refwith the entry's index.__len__is written once per container, not once per element.cvc::state's signal climb. With 8 it stays under ~30.Tests
state_exec_deep_data_testbuilds values 100000 levels deep at runtime (not through the parser). It runs each body on a 256 KiB thread, so per-level recursion fails on every platform. It covers:list_ptr, shared tails, closure chains, environment chains;to_string,values_equaland macro expansion;state_exec_intrinsics_test:StateDataGetOfTooDeepValueThrowsbecomesStateDataGetOfDeepValueCopies, which copies 100000 levels on a 256 KiB thread.CVC_DEEP_DATA_DEPTHandCVC_DEEP_DATA_STACK_KIBoverride the depth and stack, for probing.check_test_targets.pyis OK;Measurements
Largest depth each operation survives, MSVC Release on this box. Before was measured with a scratch probe that leaks every other value, so each crash belongs to one operation. 1 MiB is the main thread; 256 and 64 KiB are threads with that stack (64 KiB is the wasm default).
to_stringvalues_equalenvironment::lookupalready survived 100000 (MSVC turned its tail recursion into a loop). It's a loop now on every compiler.Benchmark:
state_exec_bench_testwithCVC_STATE_EXEC_BENCH=1, addingValueDropThroughput. Old and new binaries were interleaved over 10 rounds; values are medians on a busy machine.An earlier 8-round run of the remaining benches gave eval_fib15 0.96, flood_1000_procs 0.92 and deep_recursion_100 0.98.
Still recursive (not in this PR)
value_to_py/py_to_value(bindings/pycvc/pycvc_exec.cpp), no cap.build_audio_source(src/cvcGL/ariadne/media_verbs.cpp), no cap.collect_channel_refs(src/cvc/ariadne/loader.cpp) stops at depth 256, so it has to followparser.max_depthif that rises.cvc::state::fullName()/notifyParent, once per tree level, with O(depth²) path strings.evaluatorandasync_evaluator, and theirsubstitute. Only tests construct them.Next: run these walkers on the executor a slice per step, like the sliced reader (#653), so deep-data work counts against
max_stepsandmax_time, and report steps and time by category (read / eval / data).