Skip to content

Commit 76e5a0f

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. 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] JuliaLang/LinearAlgebra.jl#1235
1 parent 7d84c53 commit 76e5a0f

File tree

3 files changed

+100
-106
lines changed

3 files changed

+100
-106
lines changed

stdlib/CompilerSupportLibraries_jll/src/CompilerSupportLibraries_jll.jl

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,80 +5,96 @@
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("bin\\libatomic-1.dll")
21+
const _libquadmath_path = BundledLazyLibraryPath("bin\\libquadmath-0.dll")
2722
if arch(HostPlatform()) == "x86_64"
28-
const libgcc_s = "libgcc_s_seh-1.dll"
23+
const _libgcc_s_path = BundledLazyLibraryPath("bin\\libgcc_s_seh-1.dll")
2924
else
30-
const libgcc_s = "libgcc_s_sjlj-1.dll"
25+
const _libgcc_s_path = BundledLazyLibraryPath("bin\\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("bin\\libgfortran-", libgfortran_version(HostPlatform()).major, ".dll"))
28+
const _libstdcxx_path = BundledLazyLibraryPath("bin\\libstdc++-6.dll")
29+
const _libgomp_path = BundledLazyLibraryPath("bin\\libgomp-1.dll")
30+
const _libssp_path = BundledLazyLibraryPath("bin\\libssp-0.dll")
3631
elseif Sys.isapple()
32+
const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.1.dylib")
33+
const _libquadmath_path = BundledLazyLibraryPath("lib/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("lib/libgcc_s.1.1.dylib")
3936
else
40-
const libgcc_s = "@rpath/libgcc_s.1.dylib"
37+
const _libgcc_s_path = BundledLazyLibraryPath("lib/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("lib/libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib"))
40+
const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.6.dylib")
41+
const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.1.dylib")
42+
const _libssp_path = BundledLazyLibraryPath("lib/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+
const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.so.1")
45+
const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.so.1")
46+
const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.so.", libgfortran_version(HostPlatform()).major))
47+
const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.so.6")
48+
const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.so.1")
5149
if libc(HostPlatform()) != "musl"
52-
const libssp = "libssp.so.0"
50+
const _libssp_path = BundledLazyLibraryPath("lib/libssp.so.0")
51+
end
52+
if arch(HostPlatform()) ("x86_64", "i686")
53+
const _libquadmath_path = BundledLazyLibraryPath("lib/libquadmath.so.0")
5354
end
5455
end
5556

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)
57+
const libatomic = LazyLibrary(_libatomic_path)
58+
const libgcc_s = LazyLibrary(_libgcc_s_path)
59+
libgfortran_deps = [libgcc_s]
60+
if @isdefined _libquadmath_path
61+
const libquadmath = LazyLibrary(_libquadmath_path)
62+
push!(libgfortran_deps, libquadmath)
63+
end
64+
const libgfortran = LazyLibrary(_libgfortran_path, dependencies=libgfortran_deps)
65+
const libstdcxx = LazyLibrary(_libstdcxx_path, dependencies=[libgcc_s])
66+
const libgomp = LazyLibrary(_libgomp_path)
67+
if @isdefined _libssp_path
68+
const libssp = LazyLibrary(_libssp_path)
69+
end
70+
71+
# Conform to LazyJLLWrappers API
72+
function eager_mode()
73+
dlopen(libatomic)
74+
dlopen(libgcc_s)
75+
dlopen(libgomp)
76+
if @isdefined libquadmath
77+
dlopen(libquadmath)
6778
end
79+
if @isdefined libssp
80+
dlopen(libssp)
81+
end
82+
dlopen(libgfortran)
83+
dlopen(libstdcxx)
84+
end
85+
is_available() = true
86+
87+
function __init__()
88+
global libatomic_path = string(_libatomic_path)
89+
global libgcc_s_path = string(_libgcc_s_path)
90+
global libgomp_path = string(_libgomp_path)
91+
global libquadmath_path = string(_libquadmath_path)
92+
global libssp_path = string(_libssp_path)
93+
global libgfortran_path = string(_libgfortran_path)
94+
global libstdcxx_path = string(_libstdcxx_path)
6895
global artifact_dir = dirname(Sys.BINDIR)
6996
LIBPATH[] = dirname(libgcc_s_path)
7097
push!(LIBPATH_list, LIBPATH[])
7198
end
7299

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

stdlib/OpenBLAS_jll/src/OpenBLAS_jll.jl

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,17 @@
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[]
1910

2011
export libopenblas
2112

2213
# These get calculated in __init__()
23-
const PATH = Ref("")
2414
const LIBPATH = Ref("")
15+
const LIBPATH_list = String[]
2516
artifact_dir::String = ""
26-
libopenblas_handle::Ptr{Cvoid} = C_NULL
2717
libopenblas_path::String = ""
2818

2919
if Base.USE_BLAS64
@@ -33,19 +23,24 @@ else
3323
end
3424

3525
if Sys.iswindows()
36-
const libopenblas = "libopenblas$(libsuffix).dll"
37-
const _libgfortran = string("libgfortran-", libgfortran_version(HostPlatform()).major, ".dll")
26+
const _libopenblas_path = BundledLazyLibraryPath(string("bin\\libopenblas", libsuffix, ".dll"))
3827
elseif Sys.isapple()
39-
const libopenblas = "@rpath/libopenblas$(libsuffix).dylib"
40-
const _libgfortran = string("@rpath/", "libgfortran.", libgfortran_version(HostPlatform()).major, ".dylib")
28+
const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".dylib"))
4129
else
42-
const libopenblas = "libopenblas$(libsuffix).so"
43-
const _libgfortran = string("libgfortran.so.", libgfortran_version(HostPlatform()).major)
30+
const _libopenblas_path = BundledLazyLibraryPath(string("lib/libopenblas", libsuffix, ".so"))
31+
end
32+
const libopenblas = LazyLibrary(_libopenblas_path, dependencies=[libgfortran])
33+
34+
# Conform to LazyJLLWrappers API
35+
function eager_mode()
36+
dlopen(libopenblas_path)
4437
end
38+
is_available() = true
4539

4640
function __init__()
41+
global libopenblas_path = string(_libopenblas_path)
4742
# make sure OpenBLAS does not set CPU affinity (#1070, #9639)
48-
if !haskey(ENV, "OPENBLAS_MAIN_FREE")
43+
if !(haskey(ENV, "OPENBLAS_MAIN_FREE"))
4944
ENV["OPENBLAS_MAIN_FREE"] = "1"
5045
end
5146

@@ -54,32 +49,16 @@ function __init__()
5449
# threads it thinks it needs to use.
5550
# X-ref: https://github.com/xianyi/OpenBLAS/blob/c43ec53bdd00d9423fc609d7b7ecb35e7bf41b85/README.md#setting-the-number-of-threads-using-environment-variables
5651
# 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")
52+
if !(haskey(ENV, "OPENBLAS_NUM_THREADS")) && (!(haskey(ENV, "GOTO_NUM_THREADS")) && !(haskey(ENV, "OMP_NUM_THREADS")))
6053
# We set this to `1` here, and then LinearAlgebra will update
6154
# to the true value in its `__init__()` function.
6255
ENV["OPENBLAS_DEFAULT_NUM_THREADS"] = "1"
6356
end
6457

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-
69-
global libopenblas_handle = dlopen(libopenblas)
70-
global libopenblas_path = dlpath(libopenblas_handle)
58+
global libopenblas_path = string(_libopenblas_path)
7159
global artifact_dir = dirname(Sys.BINDIR)
7260
LIBPATH[] = dirname(libopenblas_path)
7361
push!(LIBPATH_list, LIBPATH[])
7462
end
7563

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-
8564
end # module OpenBLAS_jll

stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl

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

8-
const PATH_list = String[]
9-
const LIBPATH_list = String[]
10-
118
export libblastrampoline
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-
libblastrampoline_handle::Ptr{Cvoid} = C_NULL
1814
libblastrampoline_path::String = ""
1915

16+
const on_load_callbacks::Vector{Function} = Function[]
17+
function libblastrampoline_on_load_callback()
18+
for callback = on_load_callbacks
19+
callback()
20+
end
21+
end
22+
2023
# NOTE: keep in sync with `Base.libblas_name` and `Base.liblapack_name`.
21-
const libblastrampoline = if Sys.iswindows()
22-
"libblastrampoline-5.dll"
24+
const _libblastrampoline_path = if Sys.iswindows()
25+
BundledLazyLibraryPath("bin\\libblastrampoline-5.dll")
2326
elseif Sys.isapple()
24-
"@rpath/libblastrampoline.5.dylib"
27+
BundledLazyLibraryPath("lib/libblastrampoline.5.dylib")
2528
else
26-
"libblastrampoline.so.5"
29+
BundledLazyLibraryPath("lib/libblastrampoline.so.5")
2730
end
31+
const libblastrampoline = LazyLibrary(_libblastrampoline_path, dependencies=[],
32+
on_load_callback=libblastrampoline_on_load_callback)
33+
34+
function eager_mode()
35+
dlopen(libblastrampoline)
36+
end
37+
is_available() = true
2838

2939
function __init__()
30-
global libblastrampoline_handle = dlopen(libblastrampoline)
31-
global libblastrampoline_path = dlpath(libblastrampoline_handle)
40+
global libblastrampoline_path = string(_libblastrampoline_path)
3241
global artifact_dir = dirname(Sys.BINDIR)
3342
LIBPATH[] = dirname(libblastrampoline_path)
3443
push!(LIBPATH_list, LIBPATH[])
3544
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-
4645
end # module libblastrampoline_jll

0 commit comments

Comments
 (0)