Repository navigation
Put the extensions under QA's ExplicitImports checks (1/39 -> 26/39) - #1133
Merged
ChrisRackauckas merged 1 commit intoAug 1, 2026
Merged
Conversation
ChrisRackauckas-Claude
force-pushed
the
qa-check-extensions
branch
from
August 1, 2026 11:41
8384017 to
fa3b16a
Compare
ExplicitImports reads the [extensions] table out of Project.toml and analyzes each extension module, but only once that module exists -- which requires every one of its trigger packages to be loaded. The QA environment loaded none of them, so 38 of LinearSolve's 39 extensions were silently unchecked (only LinearSolveSparseArraysExt was covered, because SparseArrays is a hard dep). Add the resolvable weakdeps to test/qa/Project.toml and load them in test/qa/qa.jl, taking coverage from 1/39 to 26/39. That includes the GPU backends: ExplicitImports needs the extension module to exist, not a device, and AMDGPU, cuSOLVER, CUDSS and Metal all precompile and import fine on a CPU-only host (Metal logs "only supported on Apple Silicon" at init and still loads). The remaining 13 need real system installs, a newer Julia than the LTS that QA also runs on, or cannot co-resolve; qa.jl lists them and why. `Base.get_extension` returning `nothing` makes ExplicitImports skip an extension silently, so a testset asserts the 26 modules actually exist rather than trusting a green run_qa. CUSOLVERRF is the one GPU weakdep left out: it loads on its own, but its 0.2.6 pin on CUDA 5.7-5.11 is unsatisfiable alongside CUDSS 0.7, which needs CUDACore 6. CUDSS is kept. Fixes for what the new coverage found, in the extension sources: * `using Foo` + `Foo.bar` replaced with explicit imports in 21 extensions. * LinearSolveCliqueTreesExt imported cholesky!/ldiv! from CliqueTrees.Multifrontal rather than their owner LinearAlgebra. * LinearSolveArpackExt / ArnoldiMethodExt / KrylovKitExt / JacobiDavidsonExt reached build_eigenvalue_solution through LinearSolve instead of SciMLBase. * LinearSolveFastLapackInterfaceExt used LinearSolve.@set! and LinearSolve.ArrayInterface; LinearSolveRecursiveFactorizationExt used LinearSolve.ArrayInterface and RecursiveFactorization.ldiv! (owner TriangularSolve); LinearSolveForwardDiffExt used LinearSolve.@concrete. * LinearSolveBLISExt imported require_one_based_indexing via LinearAlgebra.LAPACK rather than Base. * Stale imports dropped from the Mooncake, ChainRulesCore, ForwardDiff, RecursiveFactorization, Enzyme, CUDA, CUDSS, AMDGPU and Metal extensions. * LinearSolveMooncakeExt referenced OperatorAssumptions and defaultalg without importing them, so its `_alg::CoDual{Nothing}` rrule!! would have thrown UndefVarError. Names with no public spelling at their owner are ignored with a comment naming the owner: Mooncake's rule interface, EnzymeCore.EnzymeRules, ForwardDiff's Dual/Partials, ArnoldiMethod's target singletons, Sparspak's sparspaklu, the CommonSolve names reached through SciMLBase, and LinearSolve's own internals (issue SciML#1058). The CUDA stack's CUDACore/cuSPARSE aliases are ignored rather than made extra triggers, since that would stop the extension loading for a plain `using cuSOLVER`. The @eval-generated init_cacheval imports in the banded extensions are ignored for staleness since EI cannot see macro-generated uses. qa.jl now runs after jet.jl and the allocation tests: package loading is process-global and several extensions change algorithm selection (LinearSolveBLISExt makes BLIS the default LU), which perturbed those assertions. That reordering also activates run_qa's own JET pass, which caught a real bug: the fallback branch of the `defaultalg_adjoint_eval` generator emitted `error("... $(alg) ...")` inside a `quote`, leaving a reference to a nonexistent runtime binding `alg` instead of interpolating the algorithm name. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude
force-pushed
the
qa-check-extensions
branch
from
August 1, 2026 12:10
fa3b16a to
af0e4d8
Compare
ChrisRackauckas
marked this pull request as ready for review
August 1, 2026 14:51
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.
The gap
SciMLTesting.run_qaruns ExplicitImports' checks on the package. ExplicitImportsdoes know about extensions — it reads the
[extensions]table out ofProject.tomland adds each extension module to the set it analyzes — but only if the module actually
exists:
An extension module only exists once every one of its trigger packages has been loaded,
and
test/qa/Project.tomlloaded none of them. Verified on the base branch:Only
LinearSolveSparseArraysExtwas covered, and only incidentally —SparseArraysisa hard dependency, so that extension always loads. The other 38 extensions were never
looked at.
What this does
Adds the resolvable weakdeps to
test/qa/Project.toml(with compat bounds mirroring theroot
Project.toml) andusings them intest/qa/qa.jl. That takes coverage from1/39 to 26/39:
Now scanned (26):
AMDGPU,AlgebraicMultigrid,ArnoldiMethod,Arpack,BLIS,BandedMatrices,BlockDiagonals,CUDA(cuSOLVER),CUDSS,ChainRulesCore,CliqueTrees,Enzyme,FastAlmostBandedMatrices,FastLapackInterface,ForwardDiff,IterativeSolvers,JacobiDavidson,KernelAbstractions,KrylovKit,Metal,Mooncake,PureUMFPACK,RecursiveFactorization,SparseArrays,Sparspak,SpecializingFactorizations.LinearSolveBLISExtcame for free —blis_jllandLAPACK_jllwere already deps ofthe QA environment, they just were not being loaded.
The GPU backends do not need a GPU
ExplicitImports needs the extension module to exist, not a device. Tested one at a
time on this CPU-only x86-64 Linux host; every one precompiled and imported, and
Base.get_extensionreturned non-nothing:get_extensionAMDGPUcuSOLVERCUDSSCUSOLVERRFMetalMetal.jl is only supported on Apple Siliconat init)Four of the five are in.
CUSOLVERRFis left out for a resolution conflict, not ahardware one:
CUSOLVERRF = "0.2.6"pins CUDA to5.7.3 - 5.11.3, whileCUDSS = "0.7"requiresCUDACore 6, so the root[compat]entries for the two cannotboth be satisfied in one environment:
I kept
CUDSS. AddingCUSOLVERRFback needs that upstream conflict resolved first.Resolving + precompiling the whole QA environment takes ~128 s here; the CUDA/ROCm
toolkit artifacts stay lazy with no driver present.
Still unscanned (13), listed with reasons in a comment in
qa.jl:Elemental,Ginkgo,HSL,HYPRE,MUMPS,Pardiso,PETSc,PETScMPI,PartitionedSolvers,STRUMPACK,SuperLUDISTParUCUDSS(above)CUSOLVERRFLoad guard
Base.get_extensionreturningnothingmakes ExplicitImports skip an extensionsilently — the checks still report a clean pass. A future change that broke an
extension's precompilation would therefore turn QA green while coverage dropped back to
zero.
qa.jlnow asserts the 22 modules exist:Test ordering
qa.jlnow runs afterjet.jland the allocation tests. Package loading isprocess-global, and several of the newly-loaded extensions change algorithm selection —
LinearSolveBLISExtdefinesLinearSolve.useblis(::Nothing) = true, making BLIS thedefault LU. With
qa.jlfirst,jet.jlstarted analyzing the BLIS path and failed onruntime dispatch inside
get_blas_operation_info. Runningqa.jllast keeps thepristine environment those files expect.
One consequence worth flagging: the QA group is already red on lts on
main(see"Pre-existing failures" below), and the group aborts at the first failing safetestset.
So while
jet.jlis broken on lts,qa.jl— and therefore ExplicitImports — will notrun there. It runs and passes on Julia 1.
Fixes made in extension source (not ignored)
using Foo+Foo.barreplaced with explicit imports across 21 extensions.LinearSolveCliqueTreesExtimportedcholesky!/ldiv!fromCliqueTrees.Multifrontal; owner isLinearAlgebra.LinearSolveArpackExt/ArnoldiMethodExt/KrylovKitExt/JacobiDavidsonExtreached
build_eigenvalue_solutionthroughLinearSolve; owner isSciMLBase.LinearSolveFastLapackInterfaceExtusedLinearSolve.@set!(ownerSetfield) andLinearSolve.ArrayInterface;LinearSolveRecursiveFactorizationExtusedLinearSolve.ArrayInterfaceandRecursiveFactorization.ldiv!(ownerTriangularSolve);LinearSolveForwardDiffExtusedLinearSolve.@concrete(ownerConcreteStructs).LinearSolveBLISExtimportedrequire_one_based_indexingviaLinearAlgebra.LAPACK; owner isBase.RecursiveFactorization, Enzyme, CUDA, CUDSS, AMDGPU and Metal extensions.
Two real bugs fell out of this:
LinearSolveMooncakeExtreferencedOperatorAssumptionsanddefaultalgwithoutimporting them. Its
rrule!!(..., _alg::CoDual{Nothing}, ...)method would havethrown
UndefVarErroron first call. Both are now imported.defaultalg_adjoint_eval's generator emitted a broken error message. The fallbackbranch was
Inside
quote, the$(alg)of a string literal is not interpolated at generatortime — it is left as a reference to a runtime binding
alg, which does not exist inthe generated method, so the branch would throw
UndefVarError: LinearSolve.alginstead of the intended message. The name is now interpolated when the method is
generated. This is what
run_qa's JET pass caught once the reordering above made JETavailable to it.
Ignore entries added, and why
All are in
test/qa/qa.jlwith a comment naming the owner. No check was disabled and no@test_broken/@test_skipwas added.no_stale_explicit_imports—eval_generated_imports: the 15 algorithm types theBandedMatrices / FastAlmostBandedMatrices extensions reference only from inside
for alg in (...) @eval ... endloops. They must be imported for the generatedinit_cachevalmethods to resolve (precompilation fails without them — that is how theywere found), but ExplicitImports analyzes source text rather than expanded code, so it
sees the imports as unused.
all_qualified_accesses_via_owners—CUDACore,cuSPARSE.LinearSolveCUDAExtand
LinearSolveCUDSSExtreach these throughcuSOLVER/CUDSS, their declaredtriggers. The usual fix is to add the owner as a second trigger, but that would stop the
extension loading for anyone who does a plain
using cuSOLVER, so the aliases stay.all_explicit_imports_via_owners—init,solve,solve!. Owner isCommonSolve,reached via
SciMLBase.CommonSolveis not a direct LinearSolve dependency, and onlythe extensions use these names, so adding it would just make it a stale dep of the main
module.
all_explicit_imports_are_public, two groups:backend_internal_imports— names with no public spelling at the backend theextension wraps: Mooncake's reverse-rule interface
(
@from_chainrules,@is_primitive,CoDual,MinimalCtx,NoRData,ReverseMode,fdata,primal,rdata,zero_fcodual),EnzymeCore.EnzymeRules, ForwardDiff'sDual/Partials, ArnoldiMethod'sLM/LR/SR/LI/SItarget singletons, andSparspak's
sparspaklu/sparspaklu!.linearsolve_internal_imports— LinearSolve's own non-public names imported fromLinearSolve by its extensions (
@get_cacheval,LinearCache,defaultalg,init_cacheval, …). Same class as the qualified accesses already tracked in QA: ExplicitImports tracked @test_broken markers (qualified-accesses-public, no-implicit-imports) #1058;promoting them is a separate public-API decision.
The pre-existing
ei_broken = (:all_qualified_accesses_are_public,)(also #1058) nowcovers a lot more ground, since the extensions reach LinearSolve internals heavily.
Local runs
GROUP=QA julia --project=. -e 'using Pkg; Pkg.test()'on Julia 1.12.4:The two
Brokenare the pre-existingaqua: ambiguitiesandall_qualified_accesses_are_publicplaceholders.Also smoke-tested every refactored code path directly in the QA environment, since
narrowing
using Footo explicit imports can only go wrong for names ExplicitImportscannot see (macro-generated code). All pass:
The
@eval-generatedinit_cachevalmethods and the KernelAbstractions@kernelarethe two macro-generated spots; both are exercised above (and the
@evalones failprecompilation if an import is missing, which is how they were caught).
The four GPU extensions cannot be exercised beyond loading on this host — no device.
What is verified for them is that they precompile, that
Base.get_extensionreturnsnon-
nothing, and that ExplicitImports reports zero implicit imports, i.e. every nametheir source refers to is explicitly available. Their runtime paths are untested here.
GROUP=Corealso passes end to end on this branch (rerun after rebasing ontob6d3f82, so no workaround of any kind is involved):
Pre-existing failures on
main(not caused by this PR)Both were confirmed by running against a pristine checkout of
main:GROUP=Corefailed in "Default Alg Tests" withUndefVarError: FunctionOperator not defined— pristinemain47 passed, 0 failed, 1 errored, identical on this branch. Root cause was not aLinearSolve commit: SciMLBase dropped its
@reexport using SciMLOperatorsinMake strict QA owner-contract clean SciMLBase.jl#1472 (shipped in SciMLBase v3.40.0), and
LinearSolve's compat is
SciMLBase = "3.33", so it broke purely on a dependencyupgrade. Fixed and already merged in
Import FunctionOperator from its owner in Core tests #1132; this branch is rebased on top of
it.
GROUP=QAfails on lts (Julia 1.10.11) injet.jl— pristinemainon 1.10:JET Tests | 19 pass, 2 fail, 21 broken, identical on this branch and unaffected byanything here (
jet.jlruns beforeqa.jlloads any of the new packages).Fix two JET runtime dispatches that only show up on Julia LTS #1134 fixes the
SupernodalLU.Int(refine::Symbol)half (a genuineMethodErrorfor anyrefineother than
:auto) and theKLU._extract!non-constant-keyword half, taking lts to19 pass, 2 fail→20 pass, 1 fail. The last one turns out to be a Julia 1.10 Basewart on any log emission (
current_logger_for_env→moduleroot→sprint(show, ::String; sizehint)reports runtime dispatch on 1.10 and not on 1.12),reached through
@SciMLMessagein the KLU failure branch; it was left unmarked ratherthan papered over with
broken =.Consequence for this PR while that is open: since the QA group aborts at the first
failing safetestset and
qa.jlnow runs last, ExplicitImports will not execute on ltsuntil that assertion is resolved. It runs and passes on Julia 1.
Notes for review
LinearSolveSTRUMPACKExtis a plausible 23rd:STRUMPACK_jllis already a base testdep and the Core suite probes that extension, so it may well resolve in the QA
environment too. Left out here to keep the environment to pure-Julia packages; easy
follow-up if wanted.
ext/LinearSolvePETScCSRExt.jlexists on disk but has no entry in[extensions], so itis dead code. Left alone here.
lib/LinearSolveAutotuneandlib/LinearSolvePyAMGhave no[weakdeps]/[extensions], so there is nothing analogous to do in the sublibraries.defaultalg_adjoint_eval's Krylov branch readscache.val.abstol/cache.val.reltol/
cache.val.verbose, which looks like it should becache.abstoletc. Not touchedhere since JET did not flag it and no test covers that path, but worth a look.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Yb5kCpT5SRzTrhppKSh1n7