Repository navigation
state_exec: state-json and state-from-json, a slice per step - #681
Merged
Merged
Conversation
cvc::state::json()/ptree()/json(j)/restore() were iterative but synchronous: a script had no way to serialize or load a subtree, and a host call ran it whole. Now one implementation backs both: - cvc/state/state_json.h: state_subtree_cursor (the pre-order walk that snapshots each node under its own lock and holds the children it has yet to visit), state_json_writer and state_json_reader, each able to stop after any number of nodes or values. - The writer's text is what write_json(ptree()) wrote, byte for byte (boost's escapes and layout). ptree(), json() and save() use it. - The reader keeps read_json()'s grammar, values, BOM handling, error messages and lines, and applies keys as ptree(pt) did. It checks the whole document before setting anything, so a malformed one sets nothing, and its container stack is on the heap: a document nested 100000 deep loads on a 64 KiB stack (read_json overflowed past 349). json(j) and restore() use it. - (state-json path) and (state-from-json path doc) run the writer and reader as data jobs: data.step_nodes units per step (a node or value, and one per 256 bytes), counted against max_steps and max_time, with other processes running between slices; the text is held to the allocation limit. state-json reads through a transparent link and is nil for a missing node, like state-get; state-from-json writes through a writable transparent link, like state-set. state-json joins the Ariadne predicates' read-only allowlist. Tests: the writer against boost's writer over every byte, the reader against read_json + ptree(pt) on a document corpus and 21 malformed documents (same messages and lines, nothing set), sliced runs equal to whole ones, and the builtins on a scheduler (deep and wide subtrees, round trip, interleaving, max_steps, the allocation limit, a malformed document spanning steps, nodes removed mid-walk).
transfix
enabled auto-merge (squash)
October 18, 2026 05:20
transfix
added a commit
that referenced
this pull request
Oct 18, 2026
…p-finish Conflicts: STATE_EXEC_GRAMMAR.md's native-calls list (state-json/state-from-json from #681, state-set here: both kept), and the tests #681/#682 and this branch each added to state_exec_data_steps_test.cpp and state_exec_deep_data_test.cpp (all kept). #675's pycvc tests asserted the old parser.max_depth default of 256; they now compare with pycvc.parse_options().max_depth, as agreed with the parity session.
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
cvc::state'sjson(),ptree(),json(j)andrestore()became iterative in #668 and #674, but they are still synchronouscvc::statecalls. That leaves two gaps:state_exec already runs its deep-data work a slice per step: #658 added
data.step_nodes, and #665 the sliced stdlib. This adds JSON builtins that work the same way. They share their code withcvc::state, so the two can't drift.What
cvc/state/state_json.hholds one implementation, three classes. Each can stop after any number of nodes or values and go on later:state_subtree_cursoris the pre-order walk. It snapshots each node's value and children under that node's own lock, and holds the children it has yet to visit by owning pointer.state_json_writerwrites the document. Its text is byte for byte whatwrite_json(ptree())wrote: boost's escapes (create_escapes) and its pretty layout.ptree(),json()andsave()now use the cursor and writer.state_json_readerloads a document asread_json()+ptree(pt)did.json(j)andrestore()now use it. It keeps read_json's handling of:true/false/null;It checks the whole document before it sets anything, so a malformed document sets nothing. Its container stack lives on the heap rather than one call per level, so nesting costs no native stack.
(state-json path)and(state-from-json path doc)(intrinsics) run that writer and reader as data jobs:data.step_nodesunits, where a unit is a node or value, plus one per 256 bytes (kDataNodeBytes). A slice ends when its units are spent, so a long value counts for its bytes.max_stepsandmax_time, and other processes run between slices.state-json's text is held tolimits.max_alloc_bytesas it grows, and each slice's growth is reported to heap accounting once.state-jsonreads through a transparent link and returns nil for a missing node, likestate-get.state-from-jsonwrites through a writable transparent link, likestate-set, and returns nil.state-jsonjoins the Ariadne predicates' allowlist of side-effect-free state readers.STATE_EXEC_GRAMMAR.md(deep data on the executor) andSTATE_EXEC_DEVELOPER_GUIDE.md(state-tree operations, sliced work).Measurements (MSVC 14.38 Release, Windows)
read_json()+ptree(pt)path overflowed past 349 levels on 64 KiB, 1,897 on 256 KiB and 7,994 on 1 MiB. The new reader loads 100,000 levels on 64 KiB (StateDeepTree.ADeeplyNestedDocumentLoads).(state-json "wide")(20,000 children) and(state-json "deep")(a 1,000-level chain, about 1 MB of text) take 74 or more data steps, and theirdata_nodescover every node plus the text's bytes.(state-from-json "copy" (state-json "wide"))takes over 200.Tests
state_deep_tree_test(+4):JsonIsWhatWriteJsonWrites: keys and values with every byte except., compared with boost's writer overptree(); a slice at a time gives the same text.JsonLoadsWhatReadJsonLoads: 13 documents, compared withread_json+ptree(pt). They cover nested legacy documents, arrays, numbers, literals, escapes and surrogates, a BOM, an empty document, a top-level scalar, duplicate keys and UTF-8. Loading a value at a time gives the same tree.AMalformedDocumentFailsAsReadJsonDoesAndSetsNothing: 21 malformed documents, with the same messages and lines asread_json, and nothing set.ADeeplyNestedDocumentLoads: 100,000 levels of objects and of arrays, on a 64 KiB stack.state_exec_data_steps_test(+7, onasync_scheduler):json()for a deep and a wide subtree, with the data steps and nodes counted;state-from-jsoncopies a subtree and restores one into its own node;max_stepskills the job;state-from-json: line 1: expected value);state-json;test_pycvc_lod.pystill crashes at exit after its checks, as before.