[WIP – ignore until reviewed by @ChrisRackauckas] Experimental: constant-propagation-aware recursion (opt-in)#63
Closed
ChrisRackauckas-Claude wants to merge 2 commits into
Conversation
Ordinary recursion widens every argument to its type, so a branch decided by a
*constant* argument — e.g. selecting a buffer by a literal index inside a
parameter container — looks value-dependent even though every real call site
folds it. This is the false positive the `is_leaf_sig` overrides paper over.
When enabled, a call carrying `Core.Const` arguments is re-inferred with those
constants preserved (`Base.Compiler` `InferenceResult`/`InferenceState`/
`typeinf`), *without the optimizer*, so the constant-decided branch folds to a
`Core.Const` condition that `_is_const_gotoifnot` already skips — while Base's
structural branches are not inlined into view (which rules out an
`optimize=true` whole-function scan). It stays conservative: a genuinely
value-dependent branch, or a dynamic (non-constant) index, is still reported.
This generalizes `is_leaf_sig`: it removes the same false positives without any
per-container knowledge (e.g. it makes a split-mode MTK `MTKParameters` RHS
branch-free with no MTK-specific override).
Because it depends on `Base.Compiler` internals whose API churns across Julia
versions, it is:
- capability-gated (`_CONST_PROP_CAPABLE`, requires the 1.12 `InferenceResult`
shape) and
- OFF by default; enable with `enable_const_prop!()`.
On unsupported versions, or whenever the const inference fails, it falls back to
the existing type-signature recursion, so behavior is unchanged unless opted in.
Added regression tests (default-off unchanged; enabled: constant index folds,
genuine branch kept, dynamic index still reported).
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
…1/pre)
The first cut gated on `hasmethod(InferenceResult, ...)` and read the inferred
body from `frame.result.src` -- both 1.12-shaped. On 1.13 the `hasmethod` check
still passes but the `InferenceState`/source-extraction dance changed, so the
fold silently did not happen and the enabled test failed on the `pre` channel.
Two fixes:
- Cross-version inference: pass the uninferred source explicitly
(`retrieve_code_info`) into a 4-arg `InferenceState` with the non-caching
`:volatile` mode, and read the inferred `CodeInfo` from whichever of
`frame.src` / `result.src` is populated. This folds on both 1.12 and 1.13.
- Functional capability gate: instead of probing for a method signature, run a
real constant-decided-branch fixture through the const inference at first use
and require that the constant-index call comes back branch-free while the
widened-index call does not. If the compiler internals ever shift shape, the
probe returns false and the feature stays inert -- so behaviour is identical
to the plain type recursion and CI stays green on unsupported versions.
Verified: lts 1.10.11 capable=false 22/22 (feature inert); 1.12.6 capable=true
26/26; pre 1.13.0-rc1 capable=true 26/26.
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Contributor
Author
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.
What & why
The
is_leaf_sigmechanism (#62) papers over one false-positive class: a branch decided by a constant argument — e.g. selecting a parameter buffer by a literal index (getindex(::MTKParameters, ::Int)) — looks value-dependent because the recursion widens the index toInt, even though every real call site passes a literal that folds the branch.This PR fixes that generically. When enabled, a call carrying
Core.Constarguments is re-inferred with the constants preserved, so the constant-decided branch folds to aCore.Constcondition that #62's_is_const_gotoifnotalready skips.Key point (why this works where
optimize=truedoesn't): inference runs without the optimizer, so the constant-decided branch folds but Base's structural branches are not inlined into view. A whole-functionoptimize=truescan was measured to turn a branch-free linear RHS from 0 → 8GotoIfNotand a broadcast RHS 0 → 45; this avoids all of that.It generalizes
is_leaf_sig: verified end-to-end that a split-mode MTKMTKParametersRHS becomes branch-free with no MTK-specific override (retiring the need for SciML/ModelingToolkit.jl#4701), whilerelustays detected and a dynamic (non-constant) index is still conservatively reported.Mechanism
_arg_lattice— keep an argument asCore.Const(v)when it's a compile-time constant, instead of widening._recurse_sig— if const-prop is active and a call has anyCore.Constargument (and isn't library code), take the constant path; otherwise the existing type path, byte-for-byte unchanged._const_infer_src/_hasbranching_const— re-infer viaBase.Compiler(InferenceResult+ explicit-sourceInferenceState+typeinf), scanning the resulting unoptimizedCodeInfo.Safety / experimental framing
Depends on
Base.Compilerinternals whose API churns across Julia versions (theInferenceStateconstruction and inferred-source location already differ between 1.12 and 1.13). So it is:falseand the feature stays inert (behaviour identical to the plain type recursion). This is what keeps CI green on every channel, now and on future prereleases.enable_const_prop!().Cross-version status (verified locally by running the suite on each):
capable=false)Open questions for review (why it's opt-in, not default): reentrancy / inference-cache side-effects of running
typeinffrom withinhasbranching; the extra inference per constant-carrying call; and the ongoing maintenance burden of tracking compiler internals vs. the stableis_leaf_sig.Relationship to the stable stack
A follow-up spike, not a replacement. Recommended: land #62 + SciML/SciMLBase.jl#1413 + SciML/ModelingToolkit.jl#4701 (stable), then evaluate flipping this on (and dropping #4701) once the reentrancy/cost questions are settled.
Semver
Additive, opt-in:
0.1.7→0.1.8.