Skip to content

Detect value-independent branches: const-fold, splat-follow, is_leaf_sig#62

Merged
ChrisRackauckas merged 1 commit into
SciML:mainfrom
ChrisRackauckas-Claude:value-independent-branch-detection
Jun 30, 2026
Merged

Detect value-independent branches: const-fold, splat-follow, is_leaf_sig#62
ChrisRackauckas merged 1 commit into
SciML:mainfrom
ChrisRackauckas-Claude:value-independent-branch-detection

Conversation

@ChrisRackauckas-Claude

@ChrisRackauckas-Claude ChrisRackauckas-Claude commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Please ignore until reviewed by @ChrisRackauckas. Draft opened by an agent.

Summary

hasbranching reports false positives (and, behind splat forwarders, false negatives) on real SciML/MTK ODE right-hand sides — a purely linear ẋ = x − a·x comes back true, needlessly disabling ReverseDiff tape compilation in SciMLSensitivity. Three value-independent constructs were mis-scored; this PR teaches the scan to ignore them, with one general mechanism per case and no per-container overrides.

1. Compile-time-constant branches are skipped

A GotoIfNot whose condition inference proves Core.Const (an x isa T test on a concretely-typed field — the ODEFunction functor's if f.f isa AbstractSciMLOperator — or the device/type-introspection dispatch inside ML library layers, #46) can never be taken differently under a tracing AD. A literal true/false condition written directly in source is not skipped — that stays a genuine branch.

2. Branches behind splatted calls are followed

g(args...) lowers to Core._apply_iterate(iter, g, groups...), burying the real callee as an argument of a Core builtin, so the old scan dead-ended at the forwarder. That's the SciML/MTK RHS shape (ODEFunctionGeneratedFunctionWrapperRuntimeGeneratedFunctiongenerated_callfunc), so a registered relu behind it was missed. The scan now follows the apply.

3. Branches a constant argument decides are refuted

Ordinary recursion widens arguments to types, so a branch selecting a buffer by a literal index (split-mode MTK getindex(::MTKParameters, ::Int)) looks value-dependent even though every real call site folds it. The type recursion stays the source of truth. Only when it reports a branch inside a call carrying Core.Const arguments does the callee get re-inferred with those constants preserved (no optimizer, so no library/structural branches are inlined into view); if that folds the branch, the finding is refuted.

This is a strict refinement: it can only downgrade a reported branch to branch-free, never the reverse. So it never introduces a false positive on arbitrary code — verified against broadcast and ComponentArray-based neural-network RHS, which an earlier "run const-prop everywhere" approach broke (const inference propagates a constant into getproperty(::ComponentArray, ::Symbol)'s specialized path and surfaces an unfolded index branch). Refuting only found branches sidesteps that entirely.

Version robustness (lts / 1 / pre)

The constant refutation uses Base.Compiler/Core.Compiler internals whose API differs across versions (the InferenceState construction and inferred-source location already differ between 1.12 and 1.13). It is functionally gated: a probe runs a constant-decided-branch fixture through the const inference at first use and only activates the refutation if the fold actually works; otherwise the analysis is exactly the plain type recursion. Any inference failure is caught and leaves the branch reported (conservative).

Verified by running the suite on each channel:

channel version refutation tests
lts 1.10.11 gated off
1 1.12.6 active
pre 1.13.0-rc1 active

End to end

The MTK RHS table through the ODEFunction (linear/power branch-free, relu branchy, both split modes) is correct with no container-specific overrides — the constant refutation handles the split-mode MTKParameters case generically. (Requires the companion unwrap so hasbranching looks through the SciMLFunction wrapper: SciML/SciMLBase.jl#1413.)

Tests

Existing tests pass unchanged; added regression tests for each mechanism (const isa skipped while real inner branch kept; splat-forwarded branch detected; constant-index branch refuted where the compiler cooperates, genuine and dynamic-index branches always kept). Runic clean.

Semver

Additive / false-positive fix, non-breaking: 0.1.60.1.7.

`hasbranching` previously reported many false positives (and, behind splat
forwarders, false negatives) on real SciML/MTK ODE right-hand sides. Three
value-independent constructs were mis-scored:

1. Compile-time-constant branches. A `GotoIfNot` whose condition inference
   proves `Core.Const` (e.g. `x isa T` on a concretely-typed field, the
   `ODEFunction` functor's `if f.f isa AbstractSciMLOperator`, or ML-library
   device/type-introspection dispatch, SciML#46) can never be taken differently
   under a tracing AD. Such branches are now skipped. A literal `true`/`false`
   condition written directly in source is still a genuine branch and is kept.

2. Branches behind splatted calls. `g(args...)` lowers to
   `Core._apply_iterate(iter, g, groups...)`, burying the real callee as an
   argument of a `Core` builtin, so the scan dead-ended at the forwarder and
   missed every branch behind it. This is the SciML/MTK RHS shape
   (`ODEFunction` -> `GeneratedFunctionWrapper` -> `RuntimeGeneratedFunction`
   -> `generated_callfunc`, each an `f(args...)` forwarder). The scan now
   follows the apply through to the real callee, recovering its argument types
   from the (concrete) splatted tuple types.

3. Argument-type-dependent plumbing. Adds `is_leaf_sig(sig)`, a signature-level
   counterpart to `is_leaf` consulted during recursion, for exemptions that
   depend on the argument types rather than just the function value (e.g.
   selecting a buffer by integer index inside a parameter container, where the
   branch is on a constant-at-each-call-site index that the recursion widens).

All existing tests pass unchanged; regression tests added for each case.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas marked this pull request as ready for review June 30, 2026 10:03
@ChrisRackauckas ChrisRackauckas merged commit 1be96a2 into SciML:main Jun 30, 2026
8 of 11 checks passed
ChrisRackauckas added a commit to SciML/SciMLBase.jl that referenced this pull request Jun 30, 2026
…#1413)

`hasbranching` (FunctionProperties.jl) is used by SciMLSensitivity to decide
whether a ReverseDiff tape can be compiled for an ODE right-hand side. It is
called on `unwrapped_f(prob.f)`, i.e. an `AbstractSciMLFunction`, and analyzes
whatever callable it is handed.

Handed the SciMLFunction functor, it scans dispatch plumbing rather than the
user's RHS: the operator-selection branch (`if f.f isa AbstractSciMLOperator`)
and, for an MTK-compiled RHS, splat forwarding into a generated function. The
result is that even a branch-free linear RHS reports `true`, and (without the
companion FunctionProperties changes) genuine branches behind the forwarder are
missed.

This weakdep extension defines

    FunctionProperties.hasbranching(f::AbstractSciMLFunction, args...) =
        FunctionProperties.hasbranching(f.f, args...)

so the analysis targets the wrapped RHS `f.f`. Loaded only when both packages
are present; no new hard dependency.

Stacked on SciML/FunctionProperties.jl#62 (the value-independent-branch
detection that the unwrapped analysis relies on); compat set to that release.

Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude ChrisRackauckas-Claude changed the title Detect value-independent branches: const-fold, splat-follow, is_leaf_sig [WIP – ignore until reviewed by @ChrisRackauckas] Detect value-independent branches: const-fold, splat-follow, is_leaf_sig Jul 2, 2026
@ChrisRackauckas-Claude ChrisRackauckas-Claude changed the title [WIP – ignore until reviewed by @ChrisRackauckas] Detect value-independent branches: const-fold, splat-follow, is_leaf_sig [WIP – ignore until reviewed by @ChrisRackauckas] Report only value-dependent branches: const-fold, splat-follow, constant refutation Jul 3, 2026
@ChrisRackauckas-Claude ChrisRackauckas-Claude changed the title [WIP – ignore until reviewed by @ChrisRackauckas] Report only value-dependent branches: const-fold, splat-follow, constant refutation Detect value-independent branches: const-fold, splat-follow, is_leaf_sig Jul 3, 2026
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/FunctionProperties.jl that referenced this pull request Jul 3, 2026
Promotes FunctionProperties to a stable 1.0 public API surface -- `hasbranching`
and `is_leaf` -- and makes constant-propagation-aware analysis the single
mechanism for suppressing value-independent branches.

Analysis change (follow-up to SciML#62): the type recursion is the source of truth;
when it reports a branch inside a call carrying `Core.Const` arguments, the callee
is re-inferred with those constants preserved (no optimizer, so no
library/structural branches are inlined into view) and, only if that folds the
branch, the finding is refuted. This is a strict refinement -- it can only
downgrade a reported branch to branch-free -- so it adds no false positives on
arbitrary code (broadcast, ComponentArrays), unlike a "run const-prop everywhere"
approach. It replaces the `is_leaf_sig` hook, so no per-container override is
needed (e.g. split-mode MTK `getindex(::MTKParameters, ::Int)`).

The refutation uses `Base.Compiler`/`Core.Compiler` internals whose API differs
across versions, so it is functionally gated: a probe folds a constant-decided
branch fixture at first use and only activates if the fold works; otherwise, and
on any inference failure, the analysis is exactly the plain type recursion.
Verified: lts 1.10.11 (gated off), 1.12.6, 1.13.0-rc1 all green; MTK RHS table
correct through the `ODEFunction` with no container-specific overrides; docs build
clean.

Also fixes the docs config: `deploydocs` pointed at MultiScaleArrays.jl, and the
docs environment pinned `FunctionProperties = "0.1.2"`.

BREAKING (1.0.0): removes the exported `is_leaf_sig` (public in 0.1.7).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/FunctionProperties.jl that referenced this pull request Jul 3, 2026
Promotes FunctionProperties to a stable 1.0 public API surface -- `hasbranching`
and `is_leaf` -- and makes constant-propagation-aware analysis the single
mechanism for suppressing value-independent branches.

Analysis change (follow-up to SciML#62): the type recursion is the source of truth;
when it reports a branch inside a call carrying `Core.Const` arguments, the callee
is re-inferred with those constants preserved (no optimizer, so no
library/structural branches are inlined into view) and, only if that folds the
branch, the finding is refuted. This is a strict refinement -- it can only
downgrade a reported branch to branch-free -- so it adds no false positives on
arbitrary code (broadcast, ComponentArrays), unlike a "run const-prop everywhere"
approach. It replaces the `is_leaf_sig` hook, so no per-container override is
needed (e.g. split-mode MTK `getindex(::MTKParameters, ::Int)`).

The refutation uses `Base.Compiler`/`Core.Compiler` internals whose API differs
across versions, so it is functionally gated: a probe folds a constant-decided
branch fixture at first use and only activates if the fold works; otherwise, and
on any inference failure, the analysis is exactly the plain type recursion.
Verified: lts 1.10.11 (gated off), 1.12.6, 1.13.0-rc1 all green; MTK RHS table
correct through the `ODEFunction` with no container-specific overrides; docs build
clean.

Also fixes the docs config: `deploydocs` pointed at MultiScaleArrays.jl, and the
docs environment pinned `FunctionProperties = "0.1.2"`.

BREAKING (1.0.0): removes the exported `is_leaf_sig` (public in 0.1.7).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants