Skip to content

Commit 0d363a8

Browse files
authored
Initial implementation of lazy JLLs for LinearAlgebra (#57719)
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. [0] JuliaLang/LinearAlgebra.jl#1235
2 parents 159424f + f38a3b9 commit 0d363a8

File tree

15 files changed

+205
-138
lines changed

15 files changed

+205
-138
lines changed

base/Base.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,6 @@ using .PermutedDimsArrays
213213
include("sort.jl")
214214
using .Sort
215215

216-
# BinaryPlatforms, used by Artifacts. Needs `Sort`.
217-
include("binaryplatforms.jl")
218-
219216
# Fast math
220217
include("fastmath.jl")
221218
using .FastMath
@@ -269,6 +266,9 @@ include("linking.jl")
269266
include("staticdata.jl")
270267
include("loading.jl")
271268

269+
# BinaryPlatforms, used by Artifacts. Needs `Sort`.
270+
include("binaryplatforms.jl")
271+
272272
# misc useful functions & macros
273273
include("timing.jl")
274274
include("client.jl")

base/binaryplatforms.jl

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -841,20 +841,48 @@ function parse_dl_name_version(path::AbstractString, os::AbstractString)
841841
return parse_dl_name_version(string(path)::String, string(os)::String)
842842
end
843843

844+
function get_csl_member(member::Symbol)
845+
# If CompilerSupportLibraries_jll is an stdlib, we can just grab things from it
846+
csl_pkgids = filter(pkgid -> pkgid.name == "CompilerSupportLibraries_jll", keys(Base.loaded_modules))
847+
if !isempty(csl_pkgids)
848+
CSL_mod = Base.loaded_modules[first(csl_pkgids)]
849+
850+
# This can fail during bootstrap, so we skip in that case.
851+
if isdefined(CSL_mod, member)
852+
return getproperty(CSL_mod, member)
853+
end
854+
end
855+
856+
return nothing
857+
end
858+
844859
"""
845860
detect_libgfortran_version()
846861
847862
Inspects the current Julia process to determine the libgfortran version this Julia is
848-
linked against (if any).
863+
linked against (if any). Returns `nothing` if no libgfortran version dependence is
864+
detected.
849865
"""
850866
function detect_libgfortran_version()
851-
libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist())
852-
if isempty(libgfortran_paths)
867+
function get_libgfortran_path()
868+
# If CompilerSupportLibraries_jll is an stdlib, we can just directly ask for
869+
# the path here, without checking `dllist()`:
870+
libgfortran_path = get_csl_member(:libgfortran_path)
871+
if libgfortran_path !== nothing
872+
return libgfortran_path::String
873+
end
874+
875+
# Otherwise, look for it having already been loaded by something
876+
libgfortran_paths = filter!(x -> occursin("libgfortran", x), Libdl.dllist())
877+
if !isempty(libgfortran_paths)
878+
return first(libgfortran_paths)::String
879+
end
880+
853881
# One day, I hope to not be linking against libgfortran in base Julia
854882
return nothing
855883
end
856-
libgfortran_path = first(libgfortran_paths)
857884

885+
libgfortran_path = get_libgfortran_path()
858886
name, version = parse_dl_name_version(libgfortran_path, os())
859887
if version === nothing
860888
# 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
878906
3.4 series of GLIBCXX where the search is performed.
879907
"""
880908
function detect_libstdcxx_version(max_minor_version::Int=30)
881-
libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
882-
if isempty(libstdcxx_paths)
883-
# This can happen if we were built by clang, so we don't link against
884-
# libstdc++ at all.
909+
function get_libstdcxx_handle()
910+
# If CompilerSupportLibraries_jll is an stdlib, we can just directly open it
911+
libstdcxx = get_csl_member(:libstdcxx)
912+
if libstdcxx !== nothing
913+
return nothing
914+
end
915+
916+
# Otherwise, look for it having already been loaded by something
917+
libstdcxx_paths = filter!(x -> occursin("libstdc++", x), Libdl.dllist())
918+
if !isempty(libstdcxx_paths)
919+
return Libdl.dlopen(first(libstdcxx_paths), Libdl.RTLD_NOLOAD)::Ptr{Cvoid}
920+
end
921+
922+
# One day, I hope to not be linking against libgfortran in base Julia
885923
return nothing
886924
end
887925

888926
# Brute-force our way through GLIBCXX_* symbols to discover which version we're linked against
889-
hdl = Libdl.dlopen(first(libstdcxx_paths))::Ptr{Cvoid}
890-
# Try all GLIBCXX versions down to GCC v4.8:
891-
# https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
892-
for minor_version in max_minor_version:-1:18
893-
if Libdl.dlsym(hdl, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing
894-
Libdl.dlclose(hdl)
895-
return VersionNumber("3.4.$(minor_version)")
927+
libstdcxx = get_libstdcxx_handle()
928+
929+
if libstdcxx !== nothing
930+
# Try all GLIBCXX versions down to GCC v4.8:
931+
# https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
932+
for minor_version in max_minor_version:-1:18
933+
if Libdl.dlsym(libstdcxx, "GLIBCXX_3.4.$(minor_version)"; throw_error=false) !== nothing
934+
Libdl.dlclose(libstdcxx)
935+
return VersionNumber("3.4.$(minor_version)")
936+
end
896937
end
897938
end
898-
Libdl.dlclose(hdl)
939+
Libdl.dlclose(libstdcxx)
899940
return nothing
900941
end
901942

base/libdl.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,14 +333,17 @@ struct LazyLibraryPath
333333
pieces::Tuple{Vararg{Any}}
334334
LazyLibraryPath(pieces...) = new(pieces)
335335
end
336-
Base.string(llp::LazyLibraryPath) = joinpath(String[string(p) for p in llp.pieces])
336+
@inline Base.string(llp::LazyLibraryPath) = joinpath(String[string(p) for p in llp.pieces])
337337
Base.cconvert(::Type{Cstring}, llp::LazyLibraryPath) = Base.cconvert(Cstring, string(llp))
338338
# Define `print` so that we can wrap this in a `LazyString`
339339
Base.print(io::IO, llp::LazyLibraryPath) = print(io, string(llp))
340340

341-
# Helper to get `Sys.BINDIR` at runtime
342-
struct SysBindirGetter; end
343-
Base.string(::SysBindirGetter) = dirname(Sys.BINDIR)
341+
# Helper to get `$(private_shlibdir)` at runtime
342+
struct PrivateShlibdirGetter; end
343+
const private_shlibdir = Base.OncePerProcess{String}() do
344+
dirname(dlpath("libjulia-internal"))
345+
end
346+
Base.string(::PrivateShlibdirGetter) = private_shlibdir()
344347

345348
"""
346349
BundledLazyLibraryPath
@@ -349,10 +352,10 @@ Helper type for lazily constructed library paths that are stored within the
349352
bundled Julia distribution, primarily for use by Base modules.
350353
351354
```
352-
libfoo = LazyLibrary(BundledLazyLibraryPath("lib/libfoo.so.1.2.3"))
355+
libfoo = LazyLibrary(BundledLazyLibraryPath("libfoo.so.1.2.3"))
353356
```
354357
"""
355-
BundledLazyLibraryPath(subpath) = LazyLibraryPath(SysBindirGetter(), subpath)
358+
BundledLazyLibraryPath(subpath) = LazyLibraryPath(PrivateShlibdirGetter(), subpath)
356359

357360

358361
"""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
33933c31906af2086702d38b9d8eb90b
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3783b8c3a46c4db5cf750a99c6753e1bf377b2d979a85df8e26c81f41cb3c75a41c28a487fce332e19ba8287c2531d440b248a6f5aedc93e8fa6673d60456c33

deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/md5

Lines changed: 0 additions & 1 deletion
This file was deleted.

deps/checksums/LinearAlgebra-f781708fffbd7e74ff17686c334ddd4f22d75a2c.tar.gz/sha512

Lines changed: 0 additions & 1 deletion
This file was deleted.

stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,80 +5,108 @@
55
baremodule CompilerSupportLibraries_jll
66
using Base, Libdl, Base.BinaryPlatforms
77

8-
const PATH_list = String[]
9-
const LIBPATH_list = String[]
10-
11-
export libgfortran, libstdcxx, libgomp
8+
export libgfortran, libstdcxx, libgomp, libatomic, libgcc_s
129

1310
# These get calculated in __init__()
14-
const PATH = Ref("")
1511
const LIBPATH = Ref("")
12+
const LIBPATH_list = String[]
1613
artifact_dir::String = ""
17-
libgcc_s_handle::Ptr{Cvoid} = C_NULL
1814
libgcc_s_path::String = ""
19-
libgfortran_handle::Ptr{Cvoid} = C_NULL
2015
libgfortran_path::String = ""
21-
libstdcxx_handle::Ptr{Cvoid} = C_NULL
2216
libstdcxx_path::String = ""
23-
libgomp_handle::Ptr{Cvoid} = C_NULL
2417
libgomp_path::String = ""
2518

2619
if Sys.iswindows()
20+
const _libatomic_path = BundledLazyLibraryPath("libatomic-1.dll")
21+
const _libquadmath_path = BundledLazyLibraryPath("libquadmath-0.dll")
2722
if arch(HostPlatform()) == "x86_64"
28-
const libgcc_s = "libgcc_s_seh-1.dll"
23+
const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s_seh-1.dll")
2924
else
30-
const libgcc_s = "libgcc_s_sjlj-1.dll"
25+
const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s_sjlj-1.dll")
3126
end
32-
const libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll")
33-
const libstdcxx = "libstdc++-6.dll"
34-
const libgomp = "libgomp-1.dll"
35-
const libssp = "libssp-0.dll"
27+
const _libgfortran_path = BundledLazyLibraryPath(string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll"))
28+
const _libstdcxx_path = BundledLazyLibraryPath("libstdc++-6.dll")
29+
const _libgomp_path = BundledLazyLibraryPath("libgomp-1.dll")
30+
const _libssp_path = BundledLazyLibraryPath("libssp-0.dll")
3631
elseif Sys.isapple()
32+
const _libatomic_path = BundledLazyLibraryPath("libatomic.1.dylib")
33+
const _libquadmath_path = BundledLazyLibraryPath("libquadmath.0.dylib")
3734
if arch(HostPlatform()) == "aarch64" || libgfortran_version(HostPlatform()) == v"5"
38-
const libgcc_s = "@rpath/libgcc_s.1.1.dylib"
35+
const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s.1.1.dylib")
3936
else
40-
const libgcc_s = "@rpath/libgcc_s.1.dylib"
37+
const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s.1.dylib")
4138
end
42-
const libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib")
43-
const libstdcxx = "@rpath/libstdc++.6.dylib"
44-
const libgomp = "@rpath/libgomp.1.dylib"
45-
const libssp = "@rpath/libssp.0.dylib"
39+
const _libgfortran_path = BundledLazyLibraryPath(string("libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib"))
40+
const _libstdcxx_path = BundledLazyLibraryPath("libstdc++.6.dylib")
41+
const _libgomp_path = BundledLazyLibraryPath("libgomp.1.dylib")
42+
const _libssp_path = BundledLazyLibraryPath("libssp.0.dylib")
4643
else
47-
const libgcc_s = "libgcc_s.so.1"
48-
const libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major)
49-
const libstdcxx = "libstdc++.so.6"
50-
const libgomp = "libgomp.so.1"
44+
if !Sys.isfreebsd()
45+
const _libatomic_path = BundledLazyLibraryPath("libatomic.so.1")
46+
end
47+
const _libgcc_s_path = BundledLazyLibraryPath("libgcc_s.so.1")
48+
const _libgfortran_path = BundledLazyLibraryPath(string("libgfortran.so.", libgfortran_version(HostPlatform()).major))
49+
const _libstdcxx_path = BundledLazyLibraryPath("libstdc++.so.6")
50+
const _libgomp_path = BundledLazyLibraryPath("libgomp.so.1")
5151
if libc(HostPlatform()) != "musl"
52-
const libssp = "libssp.so.0"
52+
const _libssp_path = BundledLazyLibraryPath("libssp.so.0")
53+
end
54+
if arch(HostPlatform()) ("x86_64", "i686")
55+
const _libquadmath_path = BundledLazyLibraryPath("libquadmath.so.0")
5356
end
5457
end
5558

59+
if @isdefined(_libatomic_path)
60+
const libatomic = LazyLibrary(_libatomic_path)
61+
end
62+
const libgcc_s = LazyLibrary(_libgcc_s_path)
63+
libgfortran_deps = [libgcc_s]
64+
if @isdefined _libquadmath_path
65+
const libquadmath = LazyLibrary(_libquadmath_path)
66+
push!(libgfortran_deps, libquadmath)
67+
end
68+
const libgfortran = LazyLibrary(_libgfortran_path, dependencies=libgfortran_deps)
69+
const libstdcxx = LazyLibrary(_libstdcxx_path, dependencies=[libgcc_s])
70+
const libgomp = LazyLibrary(_libgomp_path)
71+
if @isdefined _libssp_path
72+
const libssp = LazyLibrary(_libssp_path)
73+
end
74+
75+
# Conform to LazyJLLWrappers API
76+
function eager_mode()
77+
if @isdefined(libatomic)
78+
dlopen(libatomic)
79+
end
80+
dlopen(libgcc_s)
81+
dlopen(libgomp)
82+
if @isdefined libquadmath
83+
dlopen(libquadmath)
84+
end
85+
if @isdefined libssp
86+
dlopen(libssp)
87+
end
88+
dlopen(libgfortran)
89+
dlopen(libstdcxx)
90+
end
91+
is_available() = true
92+
5693
function __init__()
57-
global libgcc_s_handle = dlopen(libgcc_s)
58-
global libgcc_s_path = dlpath(libgcc_s_handle)
59-
global libgfortran_handle = dlopen(libgfortran)
60-
global libgfortran_path = dlpath(libgfortran_handle)
61-
global libstdcxx_handle = dlopen(libstdcxx)
62-
global libstdcxx_path = dlpath(libstdcxx_handle)
63-
global libgomp_handle = dlopen(libgomp)
64-
global libgomp_path = dlpath(libgomp_handle)
65-
@static if libc(HostPlatform()) != "musl"
66-
dlopen(libssp; throw_error = false)
94+
if @isdefined _libatomic_path
95+
global libatomic_path = string(_libatomic_path)
6796
end
97+
global libgcc_s_path = string(_libgcc_s_path)
98+
global libgomp_path = string(_libgomp_path)
99+
if @isdefined _libquadmath_path
100+
global libquadmath_path = string(_libquadmath_path)
101+
end
102+
if @isdefined _libssp_path
103+
global libssp_path = string(_libssp_path)
104+
end
105+
global libgfortran_path = string(_libgfortran_path)
106+
global libstdcxx_path = string(_libstdcxx_path)
68107
global artifact_dir = dirname(Sys.BINDIR)
69108
LIBPATH[] = dirname(libgcc_s_path)
70109
push!(LIBPATH_list, LIBPATH[])
71110
end
72111

73-
# JLLWrappers API compatibility shims. Note that not all of these will really make sense.
74-
# For instance, `find_artifact_dir()` won't actually be the artifact directory, because
75-
# there isn't one. It instead returns the overall Julia prefix.
76-
is_available() = true
77-
find_artifact_dir() = artifact_dir
78-
dev_jll() = error("stdlib JLLs cannot be dev'ed")
79-
best_wrapper = nothing
80-
get_libgfortran_path() = libgfortran_path
81-
get_libstdcxx_path() = libstdcxx_path
82-
get_libgomp_path() = libgomp_path
83-
84112
end # module CompilerSupportLibraries_jll

stdlib/LinearAlgebra.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
LINEARALGEBRA_BRANCH = master
2-
LINEARALGEBRA_SHA1 = f781708fffbd7e74ff17686c334ddd4f22d75a2c
2+
LINEARALGEBRA_SHA1 = 1ce842652c07b33289046236b09bc59bad43dbeb
33
LINEARALGEBRA_GIT_URL := https://github.com/JuliaLang/LinearAlgebra.jl.git
44
LINEARALGEBRA_TAR_URL = https://api.github.com/repos/JuliaLang/LinearAlgebra.jl/tarball/$1

0 commit comments

Comments
 (0)