@@ -9,7 +9,7 @@ import Base.DL_LOAD_PATH
99
1010export  DL_LOAD_PATH, RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL,
1111    RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW, dlclose, dlopen, dlopen_e, dlsym, dlsym_e,
12-     dlpath, find_library, dlext, dllist
12+     dlpath, find_library, dlext, dllist, LazyLibrary, LazyLibraryPath, BundledLazyLibraryPath 
1313
1414""" 
1515    DL_LOAD_PATH 
@@ -45,6 +45,9 @@ applicable.
4545""" 
4646(RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW)
4747
48+ #  The default flags for `dlopen()`
49+ const  default_rtld_flags =  RTLD_LAZY |  RTLD_DEEPBIND
50+ 
4851""" 
4952    dlsym(handle, sym; throw_error::Bool = true) 
5053
7275Look up a symbol from a shared library handle, silently return `C_NULL` on lookup failure. 
7376This method is now deprecated in favor of `dlsym(handle, sym; throw_error=false)`. 
7477""" 
75- function  dlsym_e (hnd :: Ptr , s :: Union{Symbol,AbstractString} )
76-     return  something (dlsym (hnd, s ; throw_error= false ), C_NULL )
78+ function  dlsym_e (args ... )
79+     return  something (dlsym (args ... ; throw_error= false ), C_NULL )
7780end 
7881
7982""" 
@@ -110,10 +113,10 @@ If the library cannot be found, this method throws an error, unless the keyword
110113""" 
111114function  dlopen end 
112115
113- dlopen (s:: Symbol , flags:: Integer  =  RTLD_LAZY  |  RTLD_DEEPBIND ; kwargs... ) = 
116+ dlopen (s:: Symbol , flags:: Integer  =  default_rtld_flags ; kwargs... ) = 
114117    dlopen (string (s), flags; kwargs... )
115118
116- function  dlopen (s:: AbstractString , flags:: Integer  =  RTLD_LAZY  |  RTLD_DEEPBIND ; throw_error:: Bool  =  true )
119+ function  dlopen (s:: AbstractString , flags:: Integer  =  default_rtld_flags ; throw_error:: Bool  =  true )
117120    ret =  ccall (:jl_load_dynamic_library , Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint (throw_error))
118121    if  ret ==  C_NULL 
119122        return  nothing 
@@ -314,4 +317,133 @@ function dllist()
314317    return  dynamic_libraries
315318end 
316319
320+ 
321+ """ 
322+     LazyLibraryPath 
323+ 
324+ Helper type for lazily constructed library paths for use with `LazyLibrary`. 
325+ Arguments are passed to `joinpath()`.  Arguments must be able to have 
326+ `string()` called on them. 
327+ 
328+ ``` 
329+ libfoo = LazyLibrary(LazyLibraryPath(prefix, "lib/libfoo.so.1.2.3")) 
330+ ``` 
331+ """ 
332+ struct  LazyLibraryPath
333+     pieces:: Vector 
334+     LazyLibraryPath (pieces:: Vector ) =  new (pieces)
335+ end 
336+ LazyLibraryPath (args... ) =  LazyLibraryPath (collect (args))
337+ Base. string (llp:: LazyLibraryPath ) =  joinpath (string .(llp. pieces)... )
338+ Base. cconvert (:: Type{Cstring} , llp:: LazyLibraryPath ) =  Base. cconvert (Cstring, string (llp))
339+ #  Define `print` so that we can wrap this in a `LazyString`
340+ Base. print (io:: IO , llp:: LazyLibraryPath ) =  print (io, string (llp))
341+ 
342+ #  Helper to get `Sys.BINDIR` at runtime
343+ struct  SysBindirGetter; end 
344+ Base. string (:: SysBindirGetter ) =  dirname (Sys. BINDIR)
345+ 
346+ """ 
347+     BundledLazyLibraryPath 
348+ 
349+ Helper type for lazily constructed library paths that are stored within the 
350+ bundled Julia distribution, primarily for use by Base modules. 
351+ 
352+ ``` 
353+ libfoo = LazyLibrary(BundledLazyLibraryPath("lib/libfoo.so.1.2.3")) 
354+ ``` 
355+ """ 
356+ BundledLazyLibraryPath (subpath) =  LazyLibraryPath (SysBindirGetter (), subpath)
357+ 
358+ 
359+ """ 
360+     LazyLibrary(name, flags = <default dlopen flags>, 
361+                 dependencies = LazyLibrary[], on_load_callback = nothing) 
362+ 
363+ Represents a lazily-loaded library that opens itself and its dependencies on first usage 
364+ in a `dlopen()`, `dlsym()`, or `ccall()` usage.  While this structure contains the 
365+ ability to run arbitrary code on first load via `on_load_callback`, we caution that this 
366+ should be used sparingly, as it is not expected that `ccall()` should result in large 
367+ amounts of Julia code being run.  You may call `ccall()` from within the 
368+ `on_load_callback` but only for the current library and its dependencies, and user should 
369+ not call `wait()` on any tasks within the on load callback. 
370+ """ 
371+ mutable struct  LazyLibrary
372+     #  Name and flags to open with
373+     const  path
374+     const  flags:: UInt32 
375+ 
376+     #  Dependencies that must be loaded before we can load
377+     dependencies:: Vector{LazyLibrary} 
378+ 
379+     #  Function that get called once upon initial load
380+     on_load_callback
381+     const  lock:: Base.ReentrantLock 
382+ 
383+     #  Pointer that we eventually fill out upon first `dlopen()`
384+     @atomic  handle:: Ptr{Cvoid} 
385+     function  LazyLibrary (path; flags =  default_rtld_flags, dependencies =  LazyLibrary[],
386+                          on_load_callback =  nothing )
387+         return  new (
388+             path,
389+             UInt32 (flags),
390+             collect (dependencies),
391+             on_load_callback,
392+             Base. ReentrantLock (),
393+             C_NULL ,
394+         )
395+     end 
396+ end 
397+ 
398+ #  We support adding dependencies only because of very special situations
399+ #  such as LBT needing to have OpenBLAS_jll added as a dependency dynamically.
400+ function  add_dependency! (ll:: LazyLibrary , dep:: LazyLibrary )
401+     @lock  ll. lock begin 
402+         push! (ll. dependencies, dep)
403+     end 
404+ end 
405+ 
406+ #  Register `jl_libdl_dlopen_func` so that `ccall()` lowering knows
407+ #  how to call `dlopen()`;  This hack brought to you by Valentin. \[0_o]/
408+ Base. unsafe_store! (cglobal (:jl_libdl_dlopen_func , Any), dlopen)
409+ 
410+ function  dlopen (ll:: LazyLibrary , flags:: Integer  =  ll. flags; kwargs... )
411+     handle =  @atomic  :acquire  ll. handle
412+     if  handle ==  C_NULL 
413+         @lock  ll. lock begin 
414+             #  Check to see if another thread has already run this
415+             if  ll. handle ==  C_NULL 
416+                 #  Ensure that all dependencies are loaded
417+                 for  dep in  ll. dependencies
418+                     dlopen (dep; kwargs... )
419+                 end 
420+ 
421+                 #  Load our library
422+                 handle =  dlopen (string (ll. path), flags; kwargs... )
423+                 @atomic  :release  ll. handle =  handle
424+ 
425+                 #  Only the thread that loaded the library calls the `on_load_callback()`.
426+                 if  ll. on_load_callback != =  nothing 
427+                     ll. on_load_callback ()
428+                 end 
429+             end 
430+         end 
431+     else 
432+         #  Invoke our on load callback, if it exists
433+         if  ll. on_load_callback != =  nothing 
434+             #  This empty lock protects against the case where we have updated
435+             #  `ll.handle` in the branch above, but not exited the lock.  We want
436+             #  a second thread that comes in at just the wrong time to have to wait
437+             #  for that lock to be released (and thus for the on_load_callback to
438+             #  have finished), hence the empty lock here. But we want the
439+             #  on_load_callback thread to bypass this, which will be happen thanks
440+             #  to the fact that we're using a reentrant lock here.
441+             @lock  ll. lock begin  end 
442+         end 
443+     end 
444+ 
445+     return  handle
446+ end 
447+ dlsym (ll:: LazyLibrary , args... ; kwargs... ) =  dlsym (dlopen (ll), args... ; kwargs... )
448+ dlpath (ll:: LazyLibrary ) =  dlpath (dlopen (ll))
317449end  #  module Libdl
0 commit comments