Skip to content

Commit ddd1511

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 3e57a8a commit ddd1511

File tree

6 files changed

+130
-107
lines changed

6 files changed

+130
-107
lines changed

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("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+
if !Sys.isfreebsd()
45+
const _libatomic_path = BundledLazyLibraryPath("lib/libatomic.so.1")
46+
end
47+
const _libgcc_s_path = BundledLazyLibraryPath("lib/libgcc_s.so.1")
48+
const _libgfortran_path = BundledLazyLibraryPath(string("lib/libgfortran.so.", libgfortran_version(HostPlatform()).major))
49+
const _libstdcxx_path = BundledLazyLibraryPath("lib/libstdc++.so.6")
50+
const _libgomp_path = BundledLazyLibraryPath("lib/libgomp.so.1")
5151
if libc(HostPlatform()) != "musl"
52-
const libssp = "libssp.so.0"
52+
const _libssp_path = BundledLazyLibraryPath("lib/libssp.so.0")
53+
end
54+
if arch(HostPlatform()) ("x86_64", "i686")
55+
const _libquadmath_path = BundledLazyLibraryPath("lib/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/OpenBLAS_jll/src/OpenBLAS_jll.jl

Lines changed: 17 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
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,25 @@ 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+
CompilerSupportLibraries_jll.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,16 @@ 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-
69-
global libopenblas_handle = dlopen(libopenblas)
70-
global libopenblas_path = dlpath(libopenblas_handle)
59+
global libopenblas_path = string(_libopenblas_path)
7160
global artifact_dir = dirname(Sys.BINDIR)
7261
LIBPATH[] = dirname(libopenblas_path)
7362
push!(LIBPATH_list, LIBPATH[])
7463
end
7564

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

stdlib/OpenBLAS_jll/test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ else
1313
end
1414

1515
@testset "OpenBLAS_jll" begin
16-
@test dlsym(OpenBLAS_jll.libopenblas_handle, @blasfunc(openblas_set_num_threads); throw_error=false) !== nothing
16+
@test dlsym(OpenBLAS_jll.libopenblas, @blasfunc(openblas_set_num_threads); throw_error=false) !== nothing
1717
end

stdlib/SuiteSparse_jll/src/SuiteSparse_jll.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ else
8181
end
8282

8383
function __init__()
84+
libblastrampoline_jll.eager_mode()
85+
8486
# BSD-3-Clause
8587
global libamd_handle = dlopen(libamd)
8688
global libamd_path = dlpath(libamd_handle)

stdlib/libblastrampoline_jll/src/libblastrampoline_jll.jl

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,55 @@
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+
17+
# Because LBT needs to have a weak-dependence on OpenBLAS (or any other BLAS)
18+
# we must manually construct a list of which modules and libraries we're going
19+
# to be using with it, as well as the on load callbacks they may or may not need.
20+
const on_load_callbacks::Vector{Function} = Function[]
21+
const eager_mode_modules::Vector{Module} = Module[]
22+
function libblastrampoline_on_load_callback()
23+
for callback = on_load_callbacks
24+
callback()
25+
end
26+
end
27+
28+
function add_dependency!(mod::Module, lib::LazyLibrary, on_load_callback::Function = () -> nothing)
29+
Libdl.add_dependency!(libblastrampoline, lib)
30+
push!(eager_mode_modules, mod)
31+
push!(on_load_callbacks, on_load_callback)
32+
end
33+
2034
# NOTE: keep in sync with `Base.libblas_name` and `Base.liblapack_name`.
21-
const libblastrampoline = if Sys.iswindows()
22-
"libblastrampoline-5.dll"
35+
const _libblastrampoline_path = if Sys.iswindows()
36+
BundledLazyLibraryPath("bin\\libblastrampoline-5.dll")
2337
elseif Sys.isapple()
24-
"@rpath/libblastrampoline.5.dylib"
38+
BundledLazyLibraryPath("lib/libblastrampoline.5.dylib")
2539
else
26-
"libblastrampoline.so.5"
40+
BundledLazyLibraryPath("lib/libblastrampoline.so.5")
41+
end
42+
const libblastrampoline = LazyLibrary(_libblastrampoline_path, dependencies=[],
43+
on_load_callback=libblastrampoline_on_load_callback)
44+
45+
function eager_mode()
46+
for mod in eager_mode_modules
47+
mod.eager_mode()
48+
end
49+
dlopen(libblastrampoline)
2750
end
51+
is_available() = true
2852

2953
function __init__()
30-
global libblastrampoline_handle = dlopen(libblastrampoline)
31-
global libblastrampoline_path = dlpath(libblastrampoline_handle)
54+
global libblastrampoline_path = string(_libblastrampoline_path)
3255
global artifact_dir = dirname(Sys.BINDIR)
3356
LIBPATH[] = dirname(libblastrampoline_path)
3457
push!(LIBPATH_list, LIBPATH[])
3558
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-
4659
end # module libblastrampoline_jll

stdlib/libblastrampoline_jll/test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
using Test, Libdl, libblastrampoline_jll
44

55
@testset "libblastrampoline_jll" begin
6-
@test isa(Libdl.dlsym(libblastrampoline_jll.libblastrampoline_handle, :dgemm_64_), Ptr{Nothing})
6+
@test isa(Libdl.dlsym(libblastrampoline_jll.libblastrampoline, :dgemm_64_), Ptr{Nothing})
77
end

0 commit comments

Comments
 (0)