Skip to content

Commit 85eb73d

Browse files
committed
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. This must be paired with the appropriate `LinearAlgebra.jl` changes [0]. [0]
1 parent 7d84c53 commit 85eb73d

File tree

3 files changed

+95
-99
lines changed

3 files changed

+95
-99
lines changed

stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ using Base, Libdl, Base.BinaryPlatforms
88
const PATH_list = String[]
99
const LIBPATH_list = String[]
1010

11-
export libgfortran, libstdcxx, libgomp
11+
export libgfortran, libstdcxx, libgomp, libatomic, libgcc_s
1212

1313
# These get calculated in __init__()
1414
const PATH = Ref("")
@@ -24,61 +24,84 @@ libgomp_handle::Ptr{Cvoid} = C_NULL
2424
libgomp_path::String = ""
2525

2626
if Sys.iswindows()
27+
const _libatomic_path = BundledLazyLibraryPath("bin\\libatomic-1.dll")
28+
const _libquadmath_path = BundledLazyLibraryPath("bin\\libquadmath-0.dll")
2729
if arch(HostPlatform()) == "x86_64"
28-
const libgcc_s = "libgcc_s_seh-1.dll"
30+
const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_seh-1.dll")
2931
else
30-
const libgcc_s = "libgcc_s_sjlj-1.dll"
32+
const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_sjlj-1.dll")
3133
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"
34+
const _libgfortran_path = BundledLazyLibraryPath(string("bin\\libgfortran-", libgfortran_version(HostPlatform()).major, ".dll"))
35+
const _libstdcxx_path = BundledLazyLibraryPath("bin\\libstdc++-6.dll")
36+
const _libgomp_path = BundledLazyLibraryPath("bin\\libgomp-1.dll")
37+
const _libssp_path = BundledLazyLibraryPath("bin\\libssp-0.dll")
3638
elseif Sys.isapple()
39+
const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.1.dylib")
40+
const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.0.dylib")
3741
if arch(HostPlatform()) == "aarch64" || libgfortran_version(HostPlatform()) == v"5"
38-
const libgcc_s = "@rpath/libgcc_s.1.1.dylib"
42+
const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.1.1.dylib")
3943
else
40-
const libgcc_s = "@rpath/libgcc_s.1.dylib"
44+
const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.1.dylib")
4145
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"
46+
const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib"))
47+
const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.6.dylib")
48+
const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.1.dylib")
49+
const _libssp_path = BundledLazyLibraryPath("lib/libssp.0.dylib")
4650
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"
51+
const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.so.1")
52+
const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.so.1")
53+
const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.so.", libgfortran_version(HostPlatform()).major))
54+
const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.so.6")
55+
const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.so.1")
5156
if libc(HostPlatform()) != "musl"
52-
const libssp = "libssp.so.0"
57+
const _libssp_path = BundledLazyLibraryPath("lib/libssp.so.0")
58+
end
59+
if arch(HostPlatform()) ("x86_64", "i686")
60+
const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.so.0")
5361
end
5462
end
5563

56-
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)
64+
const libatomic = LazyLibrary(_libatomic_path)
65+
const libgcc_s = LazyLibrary(_libgcc_s_path)
66+
libgfortran_deps = [libgcc_s]
67+
if @isdefined _libquadmath_path
68+
const libquadmath = LazyLibrary(_libquadmath_path)
69+
push!(libgfortran_deps, libquadmath)
70+
end
71+
const libgfortran = LazyLibrary(_libgfortran_path, dependencies=libgfortran_deps)
72+
const libstdcxx = LazyLibrary(_libstdcxx_path, dependencies=[libgcc_s])
73+
const libgomp = LazyLibrary(_libgomp_path)
74+
if @isdefined _libssp_path
75+
const libssp = LazyLibrary(_libssp_path)
76+
end
77+
78+
# Conform to LazyJLLWrappers API
79+
function eager_mode()
80+
dlopen(libatomic)
81+
dlopen(libgcc_s)
82+
dlopen(libgomp)
83+
if @isdefined libquadmath
84+
dlopen(libquadmath)
85+
end
86+
if @isdefined libssp
87+
dlopen(libssp)
6788
end
89+
dlopen(libgfortran)
90+
dlopen(libstdcxx)
91+
end
92+
is_available() = true
93+
94+
function __init__()
95+
global libatomic_path = string(_libatomic_path)
96+
global libgcc_s_path = string(_libgcc_s_path)
97+
global libgomp_path = string(_libgomp_path)
98+
global libquadmath_path = string(_libquadmath_path)
99+
global libssp_path = string(_libssp_path)
100+
global libgfortran_path = string(_libgfortran_path)
101+
global libstdcxx_path = string(_libstdcxx_path)
68102
global artifact_dir = dirname(Sys.BINDIR)
69103
LIBPATH[] = dirname(libgcc_s_path)
70104
push!(LIBPATH_list, LIBPATH[])
71105
end
72106

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-
84107
end # module CompilerSupportLibraries_jll

stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,7 @@
33
## dummy stub for https://github.com/JuliaBinaryWrappers/OpenBLAS_jll.jl
44
baremodule OpenBLAS_jll
55
using Base, Libdl, Base.BinaryPlatforms
6-
7-
# We are explicitly NOT loading this at runtime, as it contains `libgomp`
8-
# which conflicts with `libiomp5`, breaking things like MKL. In the future,
9-
# we hope to transition to a JLL interface that provides a more granular
10-
# interface than eagerly dlopen'ing all libraries provided in the JLL
11-
# which will eliminate issues like this, where we avoid loading a JLL
12-
# because we don't want to load a library that we don't even use yet.
13-
# using CompilerSupportLibraries_jll
14-
# Because of this however, we have to manually load the libraries we
15-
# _do_ care about, namely libgfortran
6+
using CompilerSupportLibraries_jll: libgfortran
167

178
const PATH_list = String[]
189
const LIBPATH_list = String[]
@@ -33,19 +24,24 @@ else
3324
end
3425

3526
if Sys.iswindows()
36-
const libopenblas = "libopenblas$(libsuffix).dll"
37-
const _libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll")
27+
const _libopenblas_path = BundledLazyLibraryPath(string("bin\\libopenblas", libsuffix, ".dll"))
3828
elseif Sys.isapple()
39-
const libopenblas = "@rpath/libopenblas$(libsuffix).dylib"
40-
const _libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib")
29+
const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".dylib"))
4130
else
42-
const libopenblas = "libopenblas$(libsuffix).so"
43-
const _libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major)
31+
const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".so"))
32+
end
33+
const libopenblas = LazyLibrary(_libopenblas_path, dependencies=[libgfortran])
34+
35+
# Conform to LazyJLLWrappers API
36+
function eager_mode()
37+
dlopen(libopenblas_path)
4438
end
39+
is_available() = true
4540

4641
function __init__()
42+
global libopenblas_path = string(_libopenblas_path)
4743
# make sure OpenBLAS does not set CPU affinity (#1070, #9639)
48-
if !haskey(ENV, "OPENBLAS_MAIN_FREE")
44+
if !(haskey(ENV, "OPENBLAS_MAIN_FREE"))
4945
ENV["OPENBLAS_MAIN_FREE"] = "1"
5046
end
5147

@@ -54,32 +50,17 @@ function __init__()
5450
# threads it thinks it needs to use.
5551
# X-ref: https://github.com/xianyi/OpenBLAS/blob/c43ec53bdd00d9423fc609d7b7ecb35e7bf41b85/README.md#setting-the-number-of-threads-using-environment-variables
5652
# X-ref: https://github.com/JuliaLang/julia/issues/45434
57-
if !haskey(ENV, "OPENBLAS_NUM_THREADS") &&
58-
!haskey(ENV, "GOTO_NUM_THREADS") &&
59-
!haskey(ENV, "OMP_NUM_THREADS")
53+
if !(haskey(ENV, "OPENBLAS_NUM_THREADS")) && (!(haskey(ENV, "GOTO_NUM_THREADS")) && !(haskey(ENV, "OMP_NUM_THREADS")))
6054
# We set this to `1` here, and then LinearAlgebra will update
6155
# to the true value in its `__init__()` function.
6256
ENV["OPENBLAS_DEFAULT_NUM_THREADS"] = "1"
6357
end
6458

65-
# As mentioned above, we are sneaking this in here so that we don't have to
66-
# depend on CSL_jll and load _all_ of its libraries.
67-
dlopen(_libgfortran)
68-
6959
global libopenblas_handle = dlopen(libopenblas)
7060
global libopenblas_path = dlpath(libopenblas_handle)
7161
global artifact_dir = dirname(Sys.BINDIR)
7262
LIBPATH[] = dirname(libopenblas_path)
7363
push!(LIBPATH_list, LIBPATH[])
7464
end
7565

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

stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,34 @@
55
baremodule libblastrampoline_jll
66
using Base, Libdl
77

8-
const PATH_list = String[]
9-
const LIBPATH_list = String[]
10-
118
export libblastrampoline
129

13-
# These get calculated in __init__()
14-
const PATH = Ref("")
15-
const LIBPATH = Ref("")
16-
artifact_dir::String = ""
17-
libblastrampoline_handle::Ptr{Cvoid} = C_NULL
18-
libblastrampoline_path::String = ""
19-
2010
# NOTE: keep in sync with `Base.libblas_name` and `Base.liblapack_name`.
21-
const libblastrampoline = if Sys.iswindows()
22-
"libblastrampoline-5.dll"
11+
const _libblastrampoline_path = if Sys.iswindows()
12+
BundledLazyLibraryPath("bin\\libblastrampoline-5.dll")
2313
elseif Sys.isapple()
24-
"@rpath/libblastrampoline.5.dylib"
14+
BundledLazyLibraryPath("lib/libblastrampoline.5.dylib")
2515
else
26-
"libblastrampoline.so.5"
16+
BundledLazyLibraryPath("lib/libblastrampoline.so.5")
17+
end
18+
const libblastrampoline = LazyLibrary(_libblastrampoline_path, dependencies=[])
19+
20+
const on_load_callbacks::Vector{Function} = Function[]
21+
function libblastrampoline_on_load_callback()
22+
for callback = on_load_callbacks
23+
callback()
24+
end
25+
end
26+
27+
function eager_mode()
28+
dlopen(libblastrampoline)
2729
end
30+
is_available() = true
2831

2932
function __init__()
30-
global libblastrampoline_handle = dlopen(libblastrampoline)
31-
global libblastrampoline_path = dlpath(libblastrampoline_handle)
33+
global libblastrampoline_path = string(_libblastrampoline_path)
3234
global artifact_dir = dirname(Sys.BINDIR)
3335
LIBPATH[] = dirname(libblastrampoline_path)
3436
push!(LIBPATH_list, LIBPATH[])
3537
end
36-
37-
# JLLWrappers API compatibility shims. Note that not all of these will really make sense.
38-
# For instance, `find_artifact_dir()` won't actually be the artifact directory, because
39-
# there isn't one. It instead returns the overall Julia prefix.
40-
is_available() = true
41-
find_artifact_dir() = artifact_dir
42-
dev_jll() = error("stdlib JLLs cannot be dev'ed")
43-
best_wrapper = nothing
44-
get_libblastrampoline_path() = libblastrampoline_path
45-
4638
end # module libblastrampoline_jll

0 commit comments

Comments
 (0)