From 4975b84a078b2785041107b6339596e778545226 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Tue, 11 Mar 2025 06:28:32 -0700 Subject: [PATCH 1/5] Initial implementation of lazy JLLs for LinearAlgebra This alters CompilerSupportLibraries_jll, OpenBLAS_jll and libblastrampoline_jll to use `LazyLibrary` objects and thereby be loaded only upon first `dlopen()` or `ccall()` to the individual library objects. Note that this is one of the more complicated cases, as `libblastrampoline` must have OpenBLAS_jll added as a dynamic dependency (as it does not actually have it listed in its shared object headers) and also has some on-load callbacks that must be invoked. Long-term, I would like to replace the bespoke JLLs here with actual JLL source code, and vendor a version of `LazyJLLWrappers` in-tree to do the actual code generation even for Base. That is left as future work. This must be paired with the appropriate `LinearAlgebra.jl` changes [0]. [0] https://github.com/JuliaLang/LinearAlgebra.jl/pull/1235 --- .../src/CompilerSupportLibraries_jll.jl | 122 +++++++++++------- stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl | 54 +++----- stdlib/OpenBLAS_jll/test/runtests.jl | 2 +- stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl | 2 + .../src/libblastrampoline_jll.jl | 55 +++++--- stdlib/libblastrampoline_jll/test/runtests.jl | 2 +- 6 files changed, 130 insertions(+), 107 deletions(-) diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index b4df77c5167da..58fd3c4d0c880 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -5,80 +5,108 @@ baremodule CompilerSupportLibraries_jll using Base, Libdl, Base.BinaryPlatforms -const PATH_list = String[] -const LIBPATH_list = String[] - -export libgfortran, libstdcxx, libgomp +export libgfortran, libstdcxx, libgomp, libatomic, libgcc_s # These get calculated in __init__() -const PATH = Ref("") const LIBPATH = Ref("") +const LIBPATH_list = String[] artifact_dir::String = "" -libgcc_s_handle::Ptr{Cvoid} = C_NULL libgcc_s_path::String = "" -libgfortran_handle::Ptr{Cvoid} = C_NULL libgfortran_path::String = "" -libstdcxx_handle::Ptr{Cvoid} = C_NULL libstdcxx_path::String = "" -libgomp_handle::Ptr{Cvoid} = C_NULL libgomp_path::String = "" if Sys.iswindows() + const _libatomic_path = BundledLazyLibraryPath("bin\\libatomic-1.dll") + const _libquadmath_path = BundledLazyLibraryPath("bin\\libquadmath-0.dll") if arch(HostPlatform()) == "x86_64" - const libgcc_s = "libgcc_s_seh-1.dll" + const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_seh-1.dll") else - const libgcc_s = "libgcc_s_sjlj-1.dll" + const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_sjlj-1.dll") end - const libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll") - const libstdcxx = "libstdc++-6.dll" - const libgomp = "libgomp-1.dll" - const libssp = "libssp-0.dll" + const _libgfortran_path = BundledLazyLibraryPath(string("bin\\libgfortran-", libgfortran_version(HostPlatform()).major, ".dll")) + const _libstdcxx_path = BundledLazyLibraryPath("bin\\libstdc++-6.dll") + const _libgomp_path = BundledLazyLibraryPath("bin\\libgomp-1.dll") + const _libssp_path = BundledLazyLibraryPath("bin\\libssp-0.dll") elseif Sys.isapple() + const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.1.dylib") + const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.0.dylib") if arch(HostPlatform()) == "aarch64" || libgfortran_version(HostPlatform()) == v"5" - const libgcc_s = "@rpath/libgcc_s.1.1.dylib" + const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.1.1.dylib") else - const libgcc_s = "@rpath/libgcc_s.1.dylib" + const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.1.dylib") end - const libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib") - const libstdcxx = "@rpath/libstdc++.6.dylib" - const libgomp = "@rpath/libgomp.1.dylib" - const libssp = "@rpath/libssp.0.dylib" + const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib")) + const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.6.dylib") + const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.1.dylib") + const _libssp_path = BundledLazyLibraryPath("lib/libssp.0.dylib") else - const libgcc_s = "libgcc_s.so.1" - const libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major) - const libstdcxx = "libstdc++.so.6" - const libgomp = "libgomp.so.1" + if !Sys.isfreebsd() + const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.so.1") + end + const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.so.1") + const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.so.", libgfortran_version(HostPlatform()).major)) + const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.so.6") + const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.so.1") if libc(HostPlatform()) != "musl" - const libssp = "libssp.so.0" + const _libssp_path = BundledLazyLibraryPath("lib/libssp.so.0") + end + if arch(HostPlatform()) ∈ ("x86_64", "i686") + const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.so.0") end end +if @isdefined(_libatomic_path) + const libatomic = LazyLibrary(_libatomic_path) +end +const libgcc_s = LazyLibrary(_libgcc_s_path) +libgfortran_deps = [libgcc_s] +if @isdefined _libquadmath_path + const libquadmath = LazyLibrary(_libquadmath_path) + push!(libgfortran_deps, libquadmath) +end +const libgfortran = LazyLibrary(_libgfortran_path, dependencies=libgfortran_deps) +const libstdcxx = LazyLibrary(_libstdcxx_path, dependencies=[libgcc_s]) +const libgomp = LazyLibrary(_libgomp_path) +if @isdefined _libssp_path + const libssp = LazyLibrary(_libssp_path) +end + +# Conform to LazyJLLWrappers API +function eager_mode() + if @isdefined(libatomic) + dlopen(libatomic) + end + dlopen(libgcc_s) + dlopen(libgomp) + if @isdefined libquadmath + dlopen(libquadmath) + end + if @isdefined libssp + dlopen(libssp) + end + dlopen(libgfortran) + dlopen(libstdcxx) +end +is_available() = true + function __init__() - global libgcc_s_handle = dlopen(libgcc_s) - global libgcc_s_path = dlpath(libgcc_s_handle) - global libgfortran_handle = dlopen(libgfortran) - global libgfortran_path = dlpath(libgfortran_handle) - global libstdcxx_handle = dlopen(libstdcxx) - global libstdcxx_path = dlpath(libstdcxx_handle) - global libgomp_handle = dlopen(libgomp) - global libgomp_path = dlpath(libgomp_handle) - @static if libc(HostPlatform()) != "musl" - dlopen(libssp; throw_error = false) + if @isdefined _libatomic_path + global libatomic_path = string(_libatomic_path) end + global libgcc_s_path = string(_libgcc_s_path) + global libgomp_path = string(_libgomp_path) + if @isdefined _libquadmath_path + global libquadmath_path = string(_libquadmath_path) + end + if @isdefined _libssp_path + global libssp_path = string(_libssp_path) + end + global libgfortran_path = string(_libgfortran_path) + global libstdcxx_path = string(_libstdcxx_path) global artifact_dir = dirname(Sys.BINDIR) LIBPATH[] = dirname(libgcc_s_path) push!(LIBPATH_list, LIBPATH[]) end -# JLLWrappers API compatibility shims. Note that not all of these will really make sense. -# For instance, `find_artifact_dir()` won't actually be the artifact directory, because -# there isn't one. It instead returns the overall Julia prefix. -is_available() = true -find_artifact_dir() = artifact_dir -dev_jll() = error("stdlib JLLs cannot be dev'ed") -best_wrapper = nothing -get_libgfortran_path() = libgfortran_path -get_libstdcxx_path() = libstdcxx_path -get_libgomp_path() = libgomp_path - end # module CompilerSupportLibraries_jll diff --git a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl index 2f151f63f4413..c289537aee02b 100644 --- a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl +++ b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl @@ -3,16 +3,7 @@ ## dummy stub for https://github.com/JuliaBinaryWrappers/OpenBLAS_jll.jl baremodule OpenBLAS_jll using Base, Libdl, Base.BinaryPlatforms - -# We are explicitly NOT loading this at runtime, as it contains `libgomp` -# which conflicts with `libiomp5`, breaking things like MKL. In the future, -# we hope to transition to a JLL interface that provides a more granular -# interface than eagerly dlopen'ing all libraries provided in the JLL -# which will eliminate issues like this, where we avoid loading a JLL -# because we don't want to load a library that we don't even use yet. -# using CompilerSupportLibraries_jll -# Because of this however, we have to manually load the libraries we -# _do_ care about, namely libgfortran +using CompilerSupportLibraries_jll const PATH_list = String[] const LIBPATH_list = String[] @@ -20,10 +11,9 @@ const LIBPATH_list = String[] export libopenblas # These get calculated in __init__() -const PATH = Ref("") const LIBPATH = Ref("") +const LIBPATH_list = String[] artifact_dir::String = "" -libopenblas_handle::Ptr{Cvoid} = C_NULL libopenblas_path::String = "" if Base.USE_BLAS64 @@ -33,19 +23,25 @@ else end if Sys.iswindows() - const libopenblas = "libopenblas$(libsuffix).dll" - const _libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll") + const _libopenblas_path = BundledLazyLibraryPath(string("bin\\libopenblas", libsuffix, ".dll")) elseif Sys.isapple() - const libopenblas = "@rpath/libopenblas$(libsuffix).dylib" - const _libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib") + const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".dylib")) else - const libopenblas = "libopenblas$(libsuffix).so" - const _libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major) + const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".so")) +end +const libopenblas = LazyLibrary(_libopenblas_path, dependencies=[libgfortran]) + +# Conform to LazyJLLWrappers API +function eager_mode() + CompilerSupportLibraries_jll.eager_mode() + dlopen(libopenblas_path) end +is_available() = true function __init__() + global libopenblas_path = string(_libopenblas_path) # make sure OpenBLAS does not set CPU affinity (#1070, #9639) - if !haskey(ENV, "OPENBLAS_MAIN_FREE") + if !(haskey(ENV, "OPENBLAS_MAIN_FREE")) ENV["OPENBLAS_MAIN_FREE"] = "1" end @@ -54,32 +50,16 @@ function __init__() # threads it thinks it needs to use. # X-ref: https://github.com/xianyi/OpenBLAS/blob/c43ec53bdd00d9423fc609d7b7ecb35e7bf41b85/README.md#setting-the-number-of-threads-using-environment-variables # X-ref: https://github.com/JuliaLang/julia/issues/45434 - if !haskey(ENV, "OPENBLAS_NUM_THREADS") && - !haskey(ENV, "GOTO_NUM_THREADS") && - !haskey(ENV, "OMP_NUM_THREADS") + if !(haskey(ENV, "OPENBLAS_NUM_THREADS")) && (!(haskey(ENV, "GOTO_NUM_THREADS")) && !(haskey(ENV, "OMP_NUM_THREADS"))) # We set this to `1` here, and then LinearAlgebra will update # to the true value in its `__init__()` function. ENV["OPENBLAS_DEFAULT_NUM_THREADS"] = "1" end - # As mentioned above, we are sneaking this in here so that we don't have to - # depend on CSL_jll and load _all_ of its libraries. - dlopen(_libgfortran) - - global libopenblas_handle = dlopen(libopenblas) - global libopenblas_path = dlpath(libopenblas_handle) + global libopenblas_path = string(_libopenblas_path) global artifact_dir = dirname(Sys.BINDIR) LIBPATH[] = dirname(libopenblas_path) push!(LIBPATH_list, LIBPATH[]) end -# JLLWrappers API compatibility shims. Note that not all of these will really make sense. -# For instance, `find_artifact_dir()` won't actually be the artifact directory, because -# there isn't one. It instead returns the overall Julia prefix. -is_available() = true -find_artifact_dir() = artifact_dir -dev_jll() = error("stdlib JLLs cannot be dev'ed") -best_wrapper = nothing -get_libopenblas_path() = libopenblas_path - end # module OpenBLAS_jll diff --git a/stdlib/OpenBLAS_jll/test/runtests.jl b/stdlib/OpenBLAS_jll/test/runtests.jl index 76242b2e4080e..9f101d4c498c9 100644 --- a/stdlib/OpenBLAS_jll/test/runtests.jl +++ b/stdlib/OpenBLAS_jll/test/runtests.jl @@ -13,5 +13,5 @@ else end @testset "OpenBLAS_jll" begin - @test dlsym(OpenBLAS_jll.libopenblas_handle, @blasfunc(openblas_set_num_threads); throw_error=false) !== nothing + @test dlsym(OpenBLAS_jll.libopenblas, @blasfunc(openblas_set_num_threads); throw_error=false) !== nothing end diff --git a/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl b/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl index 9e03033c4e3fa..b0699f132eb7f 100644 --- a/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl +++ b/stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl @@ -81,6 +81,8 @@ else end function __init__() + libblastrampoline_jll.eager_mode() + # BSD-3-Clause global libamd_handle = dlopen(libamd) global libamd_path = dlpath(libamd_handle) diff --git a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl index bbdad252be14a..3670fe0305049 100644 --- a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl +++ b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl @@ -5,42 +5,55 @@ baremodule libblastrampoline_jll using Base, Libdl -const PATH_list = String[] -const LIBPATH_list = String[] - export libblastrampoline # These get calculated in __init__() -const PATH = Ref("") const LIBPATH = Ref("") +const LIBPATH_list = String[] artifact_dir::String = "" -libblastrampoline_handle::Ptr{Cvoid} = C_NULL libblastrampoline_path::String = "" + +# Because LBT needs to have a weak-dependence on OpenBLAS (or any other BLAS) +# we must manually construct a list of which modules and libraries we're going +# to be using with it, as well as the on load callbacks they may or may not need. +const on_load_callbacks::Vector{Function} = Function[] +const eager_mode_modules::Vector{Module} = Module[] +function libblastrampoline_on_load_callback() + for callback = on_load_callbacks + callback() + end +end + +function add_dependency!(mod::Module, lib::LazyLibrary, on_load_callback::Function = () -> nothing) + Libdl.add_dependency!(libblastrampoline, lib) + push!(eager_mode_modules, mod) + push!(on_load_callbacks, on_load_callback) +end + # NOTE: keep in sync with `Base.libblas_name` and `Base.liblapack_name`. -const libblastrampoline = if Sys.iswindows() - "libblastrampoline-5.dll" +const _libblastrampoline_path = if Sys.iswindows() + BundledLazyLibraryPath("bin\\libblastrampoline-5.dll") elseif Sys.isapple() - "@rpath/libblastrampoline.5.dylib" + BundledLazyLibraryPath("lib/libblastrampoline.5.dylib") else - "libblastrampoline.so.5" + BundledLazyLibraryPath("lib/libblastrampoline.so.5") +end +const libblastrampoline = LazyLibrary(_libblastrampoline_path, dependencies=[], + on_load_callback=libblastrampoline_on_load_callback) + +function eager_mode() + for mod in eager_mode_modules + mod.eager_mode() + end + dlopen(libblastrampoline) end +is_available() = true function __init__() - global libblastrampoline_handle = dlopen(libblastrampoline) - global libblastrampoline_path = dlpath(libblastrampoline_handle) + global libblastrampoline_path = string(_libblastrampoline_path) global artifact_dir = dirname(Sys.BINDIR) LIBPATH[] = dirname(libblastrampoline_path) push!(LIBPATH_list, LIBPATH[]) end - -# JLLWrappers API compatibility shims. Note that not all of these will really make sense. -# For instance, `find_artifact_dir()` won't actually be the artifact directory, because -# there isn't one. It instead returns the overall Julia prefix. -is_available() = true -find_artifact_dir() = artifact_dir -dev_jll() = error("stdlib JLLs cannot be dev'ed") -best_wrapper = nothing -get_libblastrampoline_path() = libblastrampoline_path - end # module libblastrampoline_jll diff --git a/stdlib/libblastrampoline_jll/test/runtests.jl b/stdlib/libblastrampoline_jll/test/runtests.jl index e64fc328771be..9e14d2ccfe77b 100644 --- a/stdlib/libblastrampoline_jll/test/runtests.jl +++ b/stdlib/libblastrampoline_jll/test/runtests.jl @@ -3,5 +3,5 @@ using Test, Libdl, libblastrampoline_jll @testset "libblastrampoline_jll" begin - @test isa(Libdl.dlsym(libblastrampoline_jll.libblastrampoline_handle, :dgemm_64_), Ptr{Nothing}) + @test isa(Libdl.dlsym(libblastrampoline_jll.libblastrampoline, :dgemm_64_), Ptr{Nothing}) end From f5278d80ef2291b59e14baadc702e13d8f9f1eaf Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Tue, 11 Mar 2025 21:54:39 -0700 Subject: [PATCH 2/5] Change `BundledLazyLibraryPath()` to be relative to `private_shlibdir` This makes more sense, reduces work, and avoids that perennial thorn in my side, that in-tree builds store libraries in `${prefix}/lib`, whereas installed builds store libraries in `${prefix}/lib/julia`. Co-authored-by: Cody Tapscott <84105208+topolarity@users.noreply.github.com> --- base/libdl.jl | 15 +++--- .../src/CompilerSupportLibraries_jll.jl | 48 +++++++++---------- stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl | 6 +-- .../src/libblastrampoline_jll.jl | 6 +-- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/base/libdl.jl b/base/libdl.jl index de8c6a7a597c5..0b65ae8b381b8 100644 --- a/base/libdl.jl +++ b/base/libdl.jl @@ -333,14 +333,17 @@ struct LazyLibraryPath pieces::Tuple{Vararg{Any}} LazyLibraryPath(pieces...) = new(pieces) end -Base.string(llp::LazyLibraryPath) = joinpath(String[string(p) for p in llp.pieces]) +@inline Base.string(llp::LazyLibraryPath) = joinpath(String[string(p) for p in llp.pieces]) Base.cconvert(::Type{Cstring}, llp::LazyLibraryPath) = Base.cconvert(Cstring, string(llp)) # Define `print` so that we can wrap this in a `LazyString` Base.print(io::IO, llp::LazyLibraryPath) = print(io, string(llp)) -# Helper to get `Sys.BINDIR` at runtime -struct SysBindirGetter; end -Base.string(::SysBindirGetter) = dirname(Sys.BINDIR) +# Helper to get `$(private_shlibdir)` at runtime +struct PrivateShlibdirGetter; end +const private_shlibdir = Base.OncePerProcess{String}() do + dirname(dlpath("libjulia-internal")) +end +Base.string(::PrivateShlibdirGetter) = private_shlibdir() """ BundledLazyLibraryPath @@ -349,10 +352,10 @@ Helper type for lazily constructed library paths that are stored within the bundled Julia distribution, primarily for use by Base modules. ``` -libfoo = LazyLibrary(BundledLazyLibraryPath("lib/libfoo.so.1.2.3")) +libfoo = LazyLibrary(BundledLazyLibraryPath("libfoo.so.1.2.3")) ``` """ -BundledLazyLibraryPath(subpath) = LazyLibraryPath(SysBindirGetter(), subpath) +BundledLazyLibraryPath(subpath) = LazyLibraryPath(PrivateShlibdirGetter(), subpath) """ diff --git a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl index 58fd3c4d0c880..e386a6e19dcca 100644 --- a/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl +++ b/stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl @@ -17,42 +17,42 @@ libstdcxx_path::String = "" libgomp_path::String = "" if Sys.iswindows() - const _libatomic_path = BundledLazyLibraryPath("bin\\libatomic-1.dll") - const _libquadmath_path = BundledLazyLibraryPath("bin\\libquadmath-0.dll") + const _libatomic_path = BundledLazyLibraryPath("libatomic-1.dll") + const _libquadmath_path = BundledLazyLibraryPath("libquadmath-0.dll") if arch(HostPlatform()) == "x86_64" - const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_seh-1.dll") + const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s_seh-1.dll") else - const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_sjlj-1.dll") + const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s_sjlj-1.dll") end - const _libgfortran_path = BundledLazyLibraryPath(string("bin\\libgfortran-", libgfortran_version(HostPlatform()).major, ".dll")) - const _libstdcxx_path = BundledLazyLibraryPath("bin\\libstdc++-6.dll") - const _libgomp_path = BundledLazyLibraryPath("bin\\libgomp-1.dll") - const _libssp_path = BundledLazyLibraryPath("bin\\libssp-0.dll") + const _libgfortran_path = BundledLazyLibraryPath(string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll")) + const _libstdcxx_path = BundledLazyLibraryPath("libstdc++-6.dll") + const _libgomp_path = BundledLazyLibraryPath("libgomp-1.dll") + const _libssp_path = BundledLazyLibraryPath("libssp-0.dll") elseif Sys.isapple() - const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.1.dylib") - const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.0.dylib") + const _libatomic_path = BundledLazyLibraryPath("libatomic.1.dylib") + const _libquadmath_path = BundledLazyLibraryPath("libquadmath.0.dylib") if arch(HostPlatform()) == "aarch64" || libgfortran_version(HostPlatform()) == v"5" - const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.1.1.dylib") + const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s.1.1.dylib") else - const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.1.dylib") + const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s.1.dylib") end - const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib")) - const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.6.dylib") - const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.1.dylib") - const _libssp_path = BundledLazyLibraryPath("lib/libssp.0.dylib") + const _libgfortran_path = BundledLazyLibraryPath(string("libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib")) + const _libstdcxx_path = BundledLazyLibraryPath("libstdc++.6.dylib") + const _libgomp_path = BundledLazyLibraryPath("libgomp.1.dylib") + const _libssp_path = BundledLazyLibraryPath("libssp.0.dylib") else - if !Sys.isfreebsd() - const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.so.1") + if !Sys.isfreebsd() + const _libatomic_path = BundledLazyLibraryPath("libatomic.so.1") end - const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.so.1") - const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.so.", libgfortran_version(HostPlatform()).major)) - const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.so.6") - const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.so.1") + const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s.so.1") + const _libgfortran_path = BundledLazyLibraryPath(string("libgfortran.so.", libgfortran_version(HostPlatform()).major)) + const _libstdcxx_path = BundledLazyLibraryPath("libstdc++.so.6") + const _libgomp_path = BundledLazyLibraryPath("libgomp.so.1") if libc(HostPlatform()) != "musl" - const _libssp_path = BundledLazyLibraryPath("lib/libssp.so.0") + const _libssp_path = BundledLazyLibraryPath("libssp.so.0") end if arch(HostPlatform()) ∈ ("x86_64", "i686") - const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.so.0") + const _libquadmath_path = BundledLazyLibraryPath("libquadmath.so.0") end end diff --git a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl index c289537aee02b..300996fb6796c 100644 --- a/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl +++ b/stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl @@ -23,11 +23,11 @@ else end if Sys.iswindows() - const _libopenblas_path = BundledLazyLibraryPath(string("bin\\libopenblas", libsuffix, ".dll")) + const _libopenblas_path = BundledLazyLibraryPath(string("libopenblas", libsuffix, ".dll")) elseif Sys.isapple() - const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".dylib")) + const _libopenblas_path = BundledLazyLibraryPath(string("libopenblas", libsuffix, ".dylib")) else - const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".so")) + const _libopenblas_path = BundledLazyLibraryPath(string("libopenblas", libsuffix, ".so")) end const libopenblas = LazyLibrary(_libopenblas_path, dependencies=[libgfortran]) diff --git a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl index 3670fe0305049..25949a50fede9 100644 --- a/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl +++ b/stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl @@ -33,11 +33,11 @@ end # NOTE: keep in sync with `Base.libblas_name` and `Base.liblapack_name`. const _libblastrampoline_path = if Sys.iswindows() - BundledLazyLibraryPath("bin\\libblastrampoline-5.dll") + BundledLazyLibraryPath("libblastrampoline-5.dll") elseif Sys.isapple() - BundledLazyLibraryPath("lib/libblastrampoline.5.dylib") + BundledLazyLibraryPath("libblastrampoline.5.dylib") else - BundledLazyLibraryPath("lib/libblastrampoline.so.5") + BundledLazyLibraryPath("libblastrampoline.so.5") end const libblastrampoline = LazyLibrary(_libblastrampoline_path, dependencies=[], on_load_callback=libblastrampoline_on_load_callback) From f1c55533f288e0c0cfab644721a2c80ecd1c45be Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Fri, 14 Mar 2025 09:24:13 -0700 Subject: [PATCH 3/5] Teach `detect_{libgfortran,libstdcxx}_version` to deal with lazy CSL We can no longer depend on the fact that these libraries will be eagerly loaded (huzzah!) so we must teach these helper functions to either inspect the lazy CSL JLL for the necessary information, or just directly load the library. --- base/Base.jl | 6 ++-- base/binaryplatforms.jl | 73 ++++++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 7b1672bf9602c..a2f8d8d71897b 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -213,9 +213,6 @@ using .PermutedDimsArrays include("sort.jl") using .Sort -# BinaryPlatforms, used by Artifacts. Needs `Sort`. -include("binaryplatforms.jl") - # Fast math include("fastmath.jl") using .FastMath @@ -269,6 +266,9 @@ include("linking.jl") include("staticdata.jl") include("loading.jl") +# BinaryPlatforms, used by Artifacts. Needs `Sort`. +include("binaryplatforms.jl") + # misc useful functions & macros include("timing.jl") include("client.jl") diff --git a/base/binaryplatforms.jl b/base/binaryplatforms.jl index 0a68ce56e9a6b..d6c7cce7ba704 100644 --- a/base/binaryplatforms.jl +++ b/base/binaryplatforms.jl @@ -841,20 +841,48 @@ function parse_dl_name_version(path::AbstractString, os::AbstractString) return parse_dl_name_version(string(path)::String, string(os)::String) end +function get_csl_member(member::Symbol) + # If CompilerSupportLibraries_jll is an stdlib, we can just grab things from it + csl_pkgids = filter(pkgid -> pkgid.name == "CompilerSupportLibraries_jll", keys(Base.loaded_modules)) + if !isempty(csl_pkgids) + CSL_mod = Base.loaded_modules[first(csl_pkgids)] + + # This can fail during bootstrap, so we skip in that case. + if isdefined(CSL_mod, member) + return getproperty(CSL_mod, member) + end + end + + return nothing +end + """ detect_libgfortran_version() Inspects the current Julia process to determine the libgfortran version this Julia is -linked against (if any). +linked against (if any). Returns `nothing` if no libgfortran version dependence is +detected. """ function detect_libgfortran_version() - libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist()) - if isempty(libgfortran_paths) + function get_libgfortran_path() + # If CompilerSupportLibraries_jll is an stdlib, we can just directly ask for + # the path here, without checking `dllist()`: + libgfortran_path = get_csl_member(:libgfortran_path) + if libgfortran_path !== nothing + return libgfortran_path::String + end + + # Otherwise, look for it having already been loaded by something + libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist()) + if !isempty(libgfortran_paths) + return first(libgfortran_paths)::String + end + # One day, I hope to not be linking against libgfortran in base Julia return nothing end - libgfortran_path = first(libgfortran_paths) + libgfortran_path = get_libgfortran_path() name, version = parse_dl_name_version(libgfortran_path, os()) if version === nothing # Even though we complain about this, we allow it to continue in the hopes that @@ -878,24 +906,37 @@ it is linked against (if any). `max_minor_version` is the latest version in the 3.4 series of GLIBCXX where the search is performed. """ function detect_libstdcxx_version(max_minor_version::Int=30) - libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist()) - if isempty(libstdcxx_paths) - # This can happen if we were built by clang, so we don't link against - # libstdc++ at all. + function get_libstdcxx_handle() + # If CompilerSupportLibraries_jll is an stdlib, we can just directly open it + libstdcxx = get_csl_member(:libstdcxx) + if libstdcxx !== nothing + return nothing + end + + # Otherwise, look for it having already been loaded by something + libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist()) + if !isempty(libstdcxx_paths) + return Libdl.dlopen(first(libstdcxx_paths), Libdl.RTLD_NOLOAD)::Ptr{Cvoid} + end + + # One day, I hope to not be linking against libgfortran in base Julia return nothing end # Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against - hdl = Libdl.dlopen(first(libstdcxx_paths))::Ptr{Cvoid} - # Try all GLIBCXX versions down to GCC v4.8: - # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html - for minor_version in max_minor_version:-1:18 - if Libdl.dlsym(hdl, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing - Libdl.dlclose(hdl) - return VersionNumber("3.4.$(minor_version)") + libstdcxx = get_libstdcxx_handle() + + if libstdcxx !== nothing + # Try all GLIBCXX versions down to GCC v4.8: + # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html + for minor_version in max_minor_version:-1:18 + if Libdl.dlsym(libstdcxx, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing + Libdl.dlclose(libstdcxx) + return VersionNumber("3.4.$(minor_version)") + end end end - Libdl.dlclose(hdl) + Libdl.dlclose(libstdcxx) return nothing end From 3204987f8a19f9833678d7936a02e53d904c4d4b Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Sat, 15 Mar 2025 21:32:47 -0700 Subject: [PATCH 4/5] test/trimming: Use `SHLIB_EXT` in Makefile --- test/trimming/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index bb2b64c2d0dd5..02e9fb5bdb257 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -33,14 +33,14 @@ LDFLAGS_ADD = -lm $(shell $(JULIA_CONFIG) --ldflags --ldlibs) -ljulia-internal release: hello$(EXE) basic_jll$(EXE) hello.o: $(SRCDIR)/hello.jl $(BUILDSCRIPT) - $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.so --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $(SRCDIR)/hello.jl --output-exe true + $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $(SRCDIR)/hello.jl --output-exe true init.o: $(SRCDIR)/init.c $(CC) -c -o $@ $< $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) basic_jll.o: $(SRCDIR)/basic_jll.jl $(BUILDSCRIPT) - $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.so --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" - $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.so --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $(SRCDIR)/basic_jll.jl --output-exe true + $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" + $(JULIA) -t 1 -J $(BIN)/../lib/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(BUILDSCRIPT) $(SRCDIR)/basic_jll.jl --output-exe true hello$(EXE): hello.o init.o $(CC) -o $@ $(WHOLE_ARCHIVE) hello.o $(NO_WHOLE_ARCHIVE) init.o $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) From f38a3b98a27ed0e33adc5bc1a434200ad04b5660 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Sun, 16 Mar 2025 14:07:59 -0700 Subject: [PATCH 5/5] Bump LinearAlgebra version to latest This includes the changes needed for lazy LinearAlgebra JLLs. --- .../md5 | 1 + .../sha512 | 1 + .../md5 | 1 - .../sha512 | 1 - stdlib/LinearAlgebra.version | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/md5 create mode 100644 deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/sha512 delete mode 100644 deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/md5 delete mode 100644 deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/sha512 diff --git a/deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/md5 b/deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/md5 new file mode 100644 index 0000000000000..cf11a21c45995 --- /dev/null +++ b/deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/md5 @@ -0,0 +1 @@ +33933c31906af2086702d38b9d8eb90b diff --git a/deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/sha512 b/deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/sha512 new file mode 100644 index 0000000000000..e11b0c3957407 --- /dev/null +++ b/deps/checksums/LinearAlgebra-1ce842652c07b33289046236b09bc59bad43dbeb.tar.gz/sha512 @@ -0,0 +1 @@ +3783b8c3a46c4db5cf750a99c6753e1bf377b2d979a85df8e26c81f41cb3c75a41c28a487fce332e19ba8287c2531d440b248a6f5aedc93e8fa6673d60456c33 diff --git a/deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/md5 b/deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/md5 deleted file mode 100644 index eea4830e2d129..0000000000000 --- a/deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -f82c7834ae58e2a9c4182f614b54b9f2 diff --git a/deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/sha512 b/deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/sha512 deleted file mode 100644 index 69fb475ea495f..0000000000000 --- a/deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -fc953c21c16f0ff9ec50ab4d65cc8ea5ffdabed1ce28b615e85b0ebe782d67af83a3480d6a0c0a9c6c38ea9a041416111ed328d6e00da334132c1eaaa0cf9fc3 diff --git a/stdlib/LinearAlgebra.version b/stdlib/LinearAlgebra.version index 2deb143ade413..57683cd38f060 100644 --- a/stdlib/LinearAlgebra.version +++ b/stdlib/LinearAlgebra.version @@ -1,4 +1,4 @@ LINEARALGEBRA_BRANCH = master -LINEARALGEBRA_SHA1 = f781708fffbd7e74ff17686c334ddd4f22d75a2c +LINEARALGEBRA_SHA1 = 1ce842652c07b33289046236b09bc59bad43dbeb LINEARALGEBRA_GIT_URL := https://github.com/JuliaLang/LinearAlgebra.jl.git LINEARALGEBRA_TAR_URL = https://api.github.com/repos/JuliaLang/LinearAlgebra.jl/tarball/$1