Repository navigation
steins-syntax, steins-infer: a nested call's argument is lowered and resolved once, so nested calls cost linear rather than exponential time in depth (#1063) - #1064
Draft
zonuexe wants to merge 4 commits into
Conversation
…ment list without guard readings, so nested method calls lower in polynomial time, not 2^n (#1063) lower_arg_value of a method call, a static call and a new lowered its argument list through lower_argument_list, which also reads every positional argument as a guard. A call argument's guard reading is the call lowered again (named_call), so each level re-entered both the value road and the guard road: k = 2 per level, and $o->m($o->m(...)) at depth 22 lowered about 4M argument lists. ArgValue carries no guard readings, so the value road now lowers values only; the statement-position CallExpr keeps its arg_conds exactly as before.
…outermost resolution, so nested calls resolve in polynomial time, not k^n (#1063) Recursion structure, measured with `sample` on strlen nested 17 deep under --no-php. Cx::resolve_literal_under(c(a)) resolved the argument `a` along three roads, each from scratch: - the fold: try_fold_under -> fold_resolved_under -> resolve(a); - when the fold declines (no runtime, off the allowlist, a project function), the transfer rung shape_builtin_return_fact, whose subject arm resolves a call subject again: resolve_literal_strat(a); - the rung's string-predicate and argument-dispatch legs, which read a's fact through transfer_arg_known -> resolve(a). Each re-entry did the same for a's own argument, so the branching factor per level was k = 3 for a builtin (about 2.7-3x per level measured) and k = 2 for a project function (the fold gate refuses it, leaving the two rung roads). A thread-local memo now lives for one outermost resolution and stores an answer, with the findings its computation emitted, keyed by the value's address. It is reused only when every input is provably the root's: the value lies inside the root's own value tree (registered as the resolution reaches it, so no temporary's address is ever a key), the env, folder and context are the root's, and no live Descent is threaded (a descent's own memo would make a recomputation emit fewer findings than a replay). A hit replays the stored findings into the caller's sink, which is what the recomputation would have emitted. A guard closes the memo on return and on unwind. Findings are byte-identical to the base for strlen, abs, a project function, a self-recursive function, method, static and new calls at depths 1-14 (with and without dumpType and strict_types, with and without the PHP sidecar). strlen nested 18 deep went from over 60 s to 0.05 s.
…ed 200 deep walk inside a deadline (#1063) Each test runs the check on its own thread with the binary's stack and fails on a 120 s deadline. All four fail on the base; the method and static shapes also fail with only the literal-seam memo, since their cost was in lowering.
4 tasks
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.
Fixes #1063. Before this change, nested calls cost time exponential in nesting depth. Two independent roots were responsible.
Recursion structure
Walk (steins-infer).
Cx::resolve_literal_under(c(a))resolved the argumentafrom scratch up to three times:try_fold_under→fold_resolved_under);shape_builtin_return_fact's call-subject arm, when the fold declines;transfer_arg_known).That made the branching factor 3 per level for builtins and 2 for project functions.
Lowering (steins-syntax). For method, static and
newcalls,lower_arg_valuewent throughlower_argument_list. That function also lowers each argument as a guard, which lowers the inner call a second time, so these grew 2× per level.Fix
lower_argument_values, which lowers values only.ArgValuenever stored the guard readings. Statement-levelCallExpr.arg_condsare unchanged.literal_memo.rsis a thread-local memo that lives for one outermost resolution. Its key is the value's address. An entry is reused only when the value lies inside the root's own value tree, the env, folder andCxare the root's, and no liveDescentis threaded.crates/steins-infer/tests/it/nested_call_depth.rspins builtin, project-function, method and static calls at depth 200 under a time bound. All four fail on base, and both fixes are needed for them to pass.Timings (release,
--no-php)strlen(…)strlen(…)strlen(…)g(…)$o->m(…)new C(…)Identity
strlen,abs, a project function, a self-recursive function, method, static,newandstrlenof a variable. This holds for the plain,dumpTypeandstrict_typesvariants under--no-php, and for depths 1–12 of six shapes with the PHP sidecar on, where the fold runs.Not covered
Polynomial residue is filed separately: lowering is about cubic in depth, and a self-recursive nested call grows about n^2.7. The private half of the fp-gate is run by the orchestrator before merge.