Fix Julia 1.12: replace Cassette with code_typed-based branch detection#55
Merged
ChrisRackauckas merged 2 commits intoJun 13, 2026
Conversation
Julia 1.12 requires generated functions to return `CodeInfo`, not `Expr` (see `Base.generated_body_to_codeinfo`). Cassette v0.3.14 does not yet implement this change, so all `overdub` calls fail at runtime on 1.12. Replace the entire Cassette machinery with a direct `code_typed` call: - `code_typed(f, argtypes; optimize=false)` gives type-inferred but unoptimized IR, which is exactly what the pass was inspecting before. - Scanning the resulting `CodeInfo` for `GotoIfNot` nodes reproduces the original semantics: value-dependent `if`/`?:` produce `GotoIfNot`, `ifelse()` does not, and stdlib iteration/boundscheck branches stay inside their callees and are invisible at the user-function level. - The `is_leaf(f, args...) = false` dispatch replaces the Cassette `overdub` extension point. Users who previously added an `overdub` specialization now add `FunctionProperties.is_leaf(::typeof(f)) = true`. Remove `Cassette` and `DiffRules` from deps entirely (no deps remain). Bump version to 0.1.4. All tests pass on Julia 1.10 and 1.12. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Indent the body of the `if GROUP in ("All", "Core")` block, which PR SciML#53
introduced without indentation. Runic v1.7 (run in CI via FormatCheck)
requires it.
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas
added a commit
that referenced
this pull request
Jun 13, 2026
PR #55 exported `is_leaf` with a docstring but did not reference it in the manual, so Documenter's `:missing_docs` check now fails on main. Add `is_leaf` to the API reference, and replace the outdated "uses Cassette internally" section with a description of the `code_typed`-based branch detection. Verified locally: `julia --project=docs docs/make.jl` builds cleanly. Co-authored-by: Chris Rackauckas (Claude) <accounts@chrisrackauckas.com>
|
@ChrisRackauckas FYI I took a peek since you mentioned this in the meeting today It looks like this PR only checks the immediate function and doesn't consider any callees - I assume that's a problem for correctness |
Member
|
Oh good point, since it needs to check all functions that aren't inlined. Okay, that's not too hard though, and can still avoid doing a full cassette approach. |
ChrisRackauckas
added a commit
that referenced
this pull request
Jun 27, 2026
Wire all six ExplicitImports.jl checks (no_implicit_imports, no_stale_explicit_imports, all_explicit_imports_via_owners, all_qualified_accesses_via_owners, all_qualified_accesses_are_public, all_explicit_imports_are_public) into the QA group through SciMLTesting's `run_qa(...; ExplicitImports = ExplicitImports, explicit_imports = true, ei_kwargs = ...)`. The four standard checks and `no_implicit_imports` pass with no configuration. The two public-API checks flag two internal `Core` accesses inherent to `hasbranching` (a compiler-introspection utility): `Core.GotoIfNot` (the IR node it scans for) and `Core.Typeof` (used to build the dispatch signature tuple for `code_typed`). Neither has a public equivalent — `typeof` differs from `Core.Typeof` on type-valued arguments and `Base.typesof` is itself non-public — so they are added to a minimal, documented per-check ignore-list rather than rewritten. Also fold Aqua and JET into the same `run_qa` call and drop the two stale `@test_broken false` placeholders (issues from #54): Aqua now passes with deps_compat enabled (root Project.toml carries compat for all test extras) and JET typo-mode passes (Cassette was replaced with `code_typed` in #55), so those placeholders would now error as unexpected passes. Test deps: add ExplicitImports (compat 1.15) to test/qa/Project.toml and bump SciMLTesting floor to 1.4 (run_qa ExplicitImports support). Verified locally on Julia 1.12 (`GROUP=QA Pkg.test`): QA group 18/18 pass, all six ExplicitImports checks green. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ChrisRackauckas
added a commit
that referenced
this pull request
Jun 27, 2026
PR #55 replaced the Cassette-based pass with a single `code_typed(...; optimize = false)` scan that only inspects the immediate IR of `f`. That made `hasbranching` miss value-dependent branches that live in a helper the entry function calls but that does not get inlined — a false negative. For SciMLSensitivity that is the dangerous direction: a missed branch lets a ReverseDiff tape be compiled on a branchy function, silently producing wrong gradients. Restore the recursive semantics of the old Cassette pass, but on type-inferred IR instead of a Cassette context: - Scan the entry function's IR for `GotoIfNot`, then recurse through statically resolved calls into the callees' IR. - Treat `Base`, `Core`, and stdlib methods as leaves (detected via `Base.moduleroot` / `Sys.STDLIB`). Their internal branches are structural / compile-time, not value-dependent user logic; recursing into matrix multiply, broadcasting, or `getindex` bounds checks would reintroduce the false positives the old code suppressed with a hand-curated leaf list. This module rule subsumes that list. - `is_leaf` overrides now also stop recursion into the marked callee. - Unexpanded generated functions return `Method` rather than `CodeInfo` and are treated as leaves. Tests cover a branch behind a `@noinline` helper, a branch-free nested helper, and an `is_leaf`-opted-out helper, alongside the existing broadcast / matmul / neural-ODE false-positive cases. Passes on Julia 1.10 and 1.12. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
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.
Problem
Julia 1.12 changed generated functions to require
CodeInforeturn values instead ofExpr(see changelog). Cassette v0.3.14 does not implement this change, so every call tohasbranchingfails on 1.12 with:This is an upstream Cassette limitation with no imminent fix.
Solution
Remove the Cassette dependency entirely and replace the IR-pass approach with a direct
code_typedcall:code_typed(...; optimize=false)gives type-inferred but unoptimized IR — exactly the same level Cassette's pass was inspecting. The semantics are preserved:x < 0 ? -x : x→GotoIfNotin IR →trueifelse(x < 0, -x, x)→ function call, noGotoIfNot→falsetrue ? 1 : 0→GotoIfNotin unoptimized IR →true(constant folding only happens atoptimize=true)x1, x2 = xdestructuring →GotoIfNotstays insideiterate, invisible at the caller level →falseBreaking change: extension API
The
Cassette.overdubspecialization pattern is replaced byis_leafdispatch:Changes
src/FunctionProperties.jl: 170 lines → 45 lines; no depsProject.toml: dropCassetteandDiffRulesdeps; bump to v0.1.4test/runtests.jl: update extension example to useis_leafAll tests pass on Julia 1.10 and 1.12.