Skip to content

Commit

Permalink
Add internal dlvsym support for ccall's
Browse files Browse the repository at this point in the history
This change lays the necessary groundwork to support performing versioned
symbol lookups using `dlvsym`.

Since there's no way to ask for a versioned symbol from `ccall` this
code is currently unused, but it is a pre-requisite to add symbol
versioning to Julia's internal libraries.
  • Loading branch information
topolarity committed Mar 14, 2023
1 parent 7ba7e32 commit 93580e2
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 23 deletions.
78 changes: 55 additions & 23 deletions src/ccall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ static Value *runtime_sym_lookup(
IRBuilder<> &irbuilder,
jl_codectx_t *ctx,
PointerType *funcptype, const char *f_lib, jl_value_t *lib_expr,
const char *f_name, Function *f,
const char *f_name, const char *f_version, Function *f,
GlobalVariable *libptrgv,
GlobalVariable *llvmgv, bool runtime_lib)
{
Expand Down Expand Up @@ -158,8 +158,14 @@ static Value *runtime_sym_lookup(
Value *nameval = stringConstPtr(emission_context, irbuilder, f_name);
if (lib_expr) {
jl_cgval_t libval = emit_expr(*ctx, lib_expr);
llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jllazydlsym_func),
{ boxed(*ctx, libval), nameval });
if (f_version != NULL) {
Value *versionval = stringConstPtr(emission_context, irbuilder, f_version);
llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jllazydlvsym_func),
{ boxed(*ctx, libval), nameval, versionval });
} else {
llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jllazydlsym_func),
{ boxed(*ctx, libval), nameval });
}
}
else {
Value *libname;
Expand All @@ -170,8 +176,14 @@ static Value *runtime_sym_lookup(
// f_lib is actually one of the special sentinel values
libname = ConstantExpr::getIntToPtr(ConstantInt::get(getSizeTy(irbuilder.getContext()), (uintptr_t)f_lib), getInt8PtrTy(irbuilder.getContext()));
}
llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jldlsym_func),
{ libname, nameval, libptrgv });
if (f_version != NULL) {
Value *versionval = stringConstPtr(emission_context, irbuilder, f_version);
llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jldlvsym_func),
{ libname, nameval, versionval, libptrgv });
} else {
llvmf = irbuilder.CreateCall(prepare_call_in(jl_builderModule(irbuilder), jldlsym_func),
{ libname, nameval, libptrgv });
}
}
StoreInst *store = irbuilder.CreateAlignedStore(llvmf, llvmgv, Align(sizeof(void*)));
store->setAtomic(AtomicOrdering::Release);
Expand All @@ -188,18 +200,18 @@ static Value *runtime_sym_lookup(
static Value *runtime_sym_lookup(
jl_codectx_t &ctx,
PointerType *funcptype, const char *f_lib, jl_value_t *lib_expr,
const char *f_name, Function *f,
const char *f_name, const char *f_version, Function *f,
GlobalVariable *libptrgv,
GlobalVariable *llvmgv, bool runtime_lib)
{
return runtime_sym_lookup(ctx.emission_context, ctx.builder, &ctx, funcptype, f_lib, lib_expr,
f_name, f, libptrgv, llvmgv, runtime_lib);
f_name, f_version, f, libptrgv, llvmgv, runtime_lib);
}

static Value *runtime_sym_lookup(
jl_codectx_t &ctx,
PointerType *funcptype, const char *f_lib, jl_value_t *lib_expr,
const char *f_name, Function *f)
const char *f_name, const char *f_version, Function *f)
{
auto T_pvoidfunc = JuliaType::get_pvoidfunc_ty(ctx.builder.getContext());
GlobalVariable *libptrgv;
Expand All @@ -223,15 +235,16 @@ static Value *runtime_sym_lookup(
libptrgv = prepare_global_in(jl_Module, libptrgv);
}
llvmgv = prepare_global_in(jl_Module, llvmgv);
return runtime_sym_lookup(ctx, funcptype, f_lib, lib_expr, f_name, f, libptrgv, llvmgv, runtime_lib);
return runtime_sym_lookup(ctx, funcptype, f_lib, lib_expr, f_name, f_version, f, libptrgv, llvmgv, runtime_lib);
}

// Emit a "PLT" entry that will be lazily initialized
// when being called the first time.
static GlobalVariable *emit_plt_thunk(
jl_codectx_t &ctx,
FunctionType *functype, const AttributeList &attrs,
CallingConv::ID cc, const char *f_lib, const char *f_name,
CallingConv::ID cc,
const char *f_lib, const char *f_name, const char *f_version,
GlobalVariable *libptrgv, GlobalVariable *llvmgv,
bool runtime_lib)
{
Expand All @@ -256,8 +269,8 @@ static GlobalVariable *emit_plt_thunk(
fname);
BasicBlock *b0 = BasicBlock::Create(M->getContext(), "top", plt);
IRBuilder<> irbuilder(b0);
Value *ptr = runtime_sym_lookup(ctx.emission_context, irbuilder, NULL, funcptype, f_lib, NULL, f_name, plt, libptrgv,
llvmgv, runtime_lib);
Value *ptr = runtime_sym_lookup(ctx.emission_context, irbuilder, NULL, funcptype,
f_lib, NULL, f_name, f_version, plt, libptrgv, llvmgv, runtime_lib);
StoreInst *store = irbuilder.CreateAlignedStore(irbuilder.CreateBitCast(ptr, T_pvoidfunc), got, Align(sizeof(void*)));
store->setAtomic(AtomicOrdering::Release);
SmallVector<Value*, 16> args;
Expand Down Expand Up @@ -303,7 +316,8 @@ static Value *emit_plt(
jl_codectx_t &ctx,
FunctionType *functype,
const AttributeList &attrs,
CallingConv::ID cc, const char *f_lib, const char *f_name)
CallingConv::ID cc,
const char *f_lib, const char *f_name, const char *f_version)
{
++PLT;
assert(ctx.emission_context.imaging);
Expand All @@ -320,7 +334,7 @@ static Value *emit_plt(
GlobalVariable *&sharedgot = pltMap[key];
if (!sharedgot) {
sharedgot = emit_plt_thunk(ctx,
functype, attrs, cc, f_lib, f_name, libptrgv, llvmgv, runtime_lib);
functype, attrs, cc, f_lib, f_name, f_version, libptrgv, llvmgv, runtime_lib);
}
GlobalVariable *got = prepare_global_in(jl_Module, sharedgot);
LoadInst *got_val = ctx.builder.CreateAlignedLoad(got->getValueType(), got, Align(sizeof(void*)));
Expand Down Expand Up @@ -565,6 +579,7 @@ typedef struct {
void (*fptr)(void); // if the argument is a constant pointer
const char *f_name; // if the symbol name is known
const char *f_lib; // if a library name is specified
const char *f_version;
jl_value_t *lib_expr; // expression to compute library path lazily
jl_value_t *gcroot;
} native_sym_arg_t;
Expand All @@ -576,6 +591,8 @@ static void interpret_symbol_arg(jl_codectx_t &ctx, native_sym_arg_t &out, jl_va
void (*&fptr)(void) = out.fptr;
const char *&f_name = out.f_name;
const char *&f_lib = out.f_lib;
const char *&f_version = out.f_version;
f_version = NULL;

jl_value_t *ptr = static_eval(ctx, arg);
if (ptr == NULL) {
Expand Down Expand Up @@ -709,20 +726,29 @@ static jl_cgval_t emit_cglobal(jl_codectx_t &ctx, jl_value_t **args, size_t narg
}
else {
if (sym.lib_expr) {
res = runtime_sym_lookup(ctx, cast<PointerType>(getInt8PtrTy(ctx.builder.getContext())), NULL, sym.lib_expr, sym.f_name, ctx.f);
res = runtime_sym_lookup(ctx, cast<PointerType>(getInt8PtrTy(ctx.builder.getContext())), NULL,
sym.lib_expr, sym.f_name, sym.f_version, ctx.f);
}
else if (ctx.emission_context.imaging) {
res = runtime_sym_lookup(ctx, cast<PointerType>(getInt8PtrTy(ctx.builder.getContext())), sym.f_lib, NULL, sym.f_name, ctx.f);
res = runtime_sym_lookup(ctx, cast<PointerType>(getInt8PtrTy(ctx.builder.getContext())),
sym.f_lib, NULL, sym.f_name, sym.f_version, ctx.f);
res = ctx.builder.CreatePtrToInt(res, lrt);
}
else {
void *symaddr;

void* libsym = jl_get_library_(sym.f_lib, 0);
if (!libsym || !jl_dlsym(libsym, sym.f_name, &symaddr, 0)) {
int symbol_found = 0;
if (sym.f_version != NULL) {
symbol_found = jl_dlvsym(libsym, sym.f_name, sym.f_version, &symaddr, 0);
} else {
symbol_found = jl_dlsym(libsym, sym.f_name, &symaddr, 0);
}
if (!libsym || !symbol_found) {
// Error mode, either the library or the symbol couldn't be find during compiletime.
// Fallback to a runtime symbol lookup.
res = runtime_sym_lookup(ctx, cast<PointerType>(getInt8PtrTy(ctx.builder.getContext())), sym.f_lib, NULL, sym.f_name, ctx.f);
res = runtime_sym_lookup(ctx, cast<PointerType>(getInt8PtrTy(ctx.builder.getContext())),
sym.f_lib, NULL, sym.f_name, sym.f_version, ctx.f);
res = ctx.builder.CreatePtrToInt(res, lrt);
} else {
// since we aren't saving this code, there's no sense in
Expand Down Expand Up @@ -2043,25 +2069,31 @@ jl_cgval_t function_sig_t::emit_a_ccall(
PointerType *funcptype = PointerType::get(functype, 0);
if (symarg.lib_expr) {
++DeferredCCallLookups;
llvmf = runtime_sym_lookup(ctx, funcptype, NULL, symarg.lib_expr, symarg.f_name, ctx.f);
llvmf = runtime_sym_lookup(ctx, funcptype, NULL, symarg.lib_expr, symarg.f_name, symarg.f_version, ctx.f);
}
else if (ctx.emission_context.imaging) {
++DeferredCCallLookups;
// vararg requires musttail,
// but musttail is incompatible with noreturn.
if (functype->isVarArg())
llvmf = runtime_sym_lookup(ctx, funcptype, symarg.f_lib, NULL, symarg.f_name, ctx.f);
llvmf = runtime_sym_lookup(ctx, funcptype, symarg.f_lib, NULL, symarg.f_name, symarg.f_version, ctx.f);
else
llvmf = emit_plt(ctx, functype, attributes, cc, symarg.f_lib, symarg.f_name);
llvmf = emit_plt(ctx, functype, attributes, cc, symarg.f_lib, symarg.f_name, symarg.f_version);
}
else {
void *symaddr;
void *libsym = jl_get_library_(symarg.f_lib, 0);
if (!libsym || !jl_dlsym(libsym, symarg.f_name, &symaddr, 0)) {
int symbol_found = 0;
if (symarg.f_version != NULL) {
symbol_found = jl_dlvsym(libsym, symarg.f_name, symarg.f_version, &symaddr, 0);
} else {
symbol_found = jl_dlsym(libsym, symarg.f_name, &symaddr, 0);
}
if (!libsym || !symbol_found) {
++DeferredCCallLookups;
// either the library or the symbol could not be found, place a runtime
// lookup here instead.
llvmf = runtime_sym_lookup(ctx, funcptype, symarg.f_lib, NULL, symarg.f_name, ctx.f);
llvmf = runtime_sym_lookup(ctx, funcptype, symarg.f_lib, NULL, symarg.f_name, symarg.f_version, ctx.f);
} else {
++LiteralCCalls;
// since we aren't saving this code, there's no sense in
Expand Down
13 changes: 13 additions & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1030,12 +1030,24 @@ static const auto jldlsym_func = new JuliaFunction{
{getInt8PtrTy(C), getInt8PtrTy(C), PointerType::get(getInt8PtrTy(C), 0)}, false); },
nullptr,
};
static const auto jldlvsym_func = new JuliaFunction{
XSTR(jl_load_and_lookup_with_ver),
[](LLVMContext &C) { return FunctionType::get(JuliaType::get_pvoidfunc_ty(C),
{getInt8PtrTy(C), getInt8PtrTy(C), getInt8PtrTy(C), PointerType::get(getInt8PtrTy(C), 0)}, false); },
nullptr,
};
static const auto jllazydlsym_func = new JuliaFunction{
XSTR(jl_lazy_load_and_lookup),
[](LLVMContext &C) { return FunctionType::get(JuliaType::get_pvoidfunc_ty(C),
{JuliaType::get_prjlvalue_ty(C), getInt8PtrTy(C)}, false); },
nullptr,
};
static const auto jllazydlvsym_func = new JuliaFunction{
XSTR(jl_lazy_load_and_lookup_with_ver),
[](LLVMContext &C) { return FunctionType::get(JuliaType::get_pvoidfunc_ty(C),
{JuliaType::get_prjlvalue_ty(C), getInt8PtrTy(C), getInt8PtrTy(C)}, false); },
nullptr,
};
static const auto jltypeassert_func = new JuliaFunction{
XSTR(jl_typeassert),
[](LLVMContext &C) {
Expand Down Expand Up @@ -8743,6 +8755,7 @@ static void init_jit_functions(void)
add_named_global(jl_write_barrier_func, (void*)NULL);
add_named_global(jl_write_barrier_binding_func, (void*)NULL);
add_named_global(jldlsym_func, &jl_load_and_lookup);
add_named_global(jldlvsym_func, &jl_load_and_lookup_with_ver);
add_named_global("jl_adopt_thread", &jl_adopt_thread);
add_named_global(jlgetcfunctiontrampoline_func, &jl_get_cfunction_trampoline);
add_named_global(jlgetnthfieldchecked_func, &jl_get_nth_field_checked);
Expand Down
37 changes: 37 additions & 0 deletions src/dlload.c
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,43 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags,
return handle;
}

JL_DLLEXPORT int jl_dlvsym(void *handle, const char *symbol, const char *version, void ** value, int throw_err) JL_NOTSAFEPOINT
{
#if defined(_OS_WINDOWS_) || defined(__APPLE__)
if (throw_err) {
jl_errorf("could not load symbol \"%s\" (version \"%s\"):\ndlvsym is not available on this platform", symbol, version);
}
return 0;
#else
int symbol_found = 0;

/* First, get the symbol value */
*value = dlvsym(handle, symbol, version);

/* Next, check for errors. On Windows, a NULL pointer means the symbol was
* not found. On everything else, we can have NULL symbols, so we check for
* non-NULL returns from dlerror(). Since POSIX doesn't require `dlerror`
* to be implemented safely, FreeBSD doesn't (unlike everyone else, who
* realized decades ago that threads are here to stay), so we avoid calling
* `dlerror` unless we need to get the error message.
* https://github.com/freebsd/freebsd-src/blob/12db51d20823a5e3b9e5f8a2ea73156fe1cbfc28/libexec/rtld-elf/rtld.c#L198
*/
symbol_found = *value != NULL;
const char *err = "";
if (!symbol_found) {
dlerror(); /* Reset error status. */
*value = dlvsym(handle, symbol, version);
err = dlerror();
symbol_found = *value != NULL || err == NULL;
}

if (!symbol_found && throw_err) {
jl_errorf("could not load symbol \"%s\" (version \"%s\"):\n%s", symbol, version, err);
}
return symbol_found;
#endif
}

JL_DLLEXPORT int jl_dlsym(void *handle, const char *symbol, void ** value, int throw_err) JL_NOTSAFEPOINT
{
int symbol_found = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/jl_exported_funcs.inc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
XX(jl_dlclose) \
XX(jl_dlopen) \
XX(jl_dlsym) \
XX(jl_dlvsym) \
XX(jl_dump_host_cpu) \
XX(jl_check_pkgimage_clones) \
XX(jl_egal) \
Expand Down Expand Up @@ -301,10 +302,12 @@
XX(jl_is_unary_and_binary_operator) \
XX(jl_is_unary_operator) \
XX(jl_lazy_load_and_lookup) \
XX(jl_lazy_load_and_lookup_with_ver) \
XX(jl_lisp_prompt) \
XX(jl_load) \
XX(jl_load_) \
XX(jl_load_and_lookup) \
XX(jl_load_and_lookup_with_ver) \
XX(jl_load_dynamic_library) \
XX(jl_load_file_string) \
XX(jl_lookup_code_address) \
Expand Down
1 change: 1 addition & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,7 @@ JL_DLLEXPORT jl_libhandle jl_load_dynamic_library(const char *fname, unsigned fl
JL_DLLEXPORT jl_libhandle jl_dlopen(const char *filename, unsigned flags) JL_NOTSAFEPOINT;
JL_DLLEXPORT int jl_dlclose(jl_libhandle handle) JL_NOTSAFEPOINT;
JL_DLLEXPORT int jl_dlsym(jl_libhandle handle, const char *symbol, void ** value, int throw_err) JL_NOTSAFEPOINT;
JL_DLLEXPORT int jl_dlvsym(jl_libhandle handle, const char *symbol, const char *version, void ** value, int throw_err) JL_NOTSAFEPOINT;

// evaluation
JL_DLLEXPORT jl_value_t *jl_toplevel_eval(jl_module_t *m, jl_value_t *v);
Expand Down
2 changes: 2 additions & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,9 @@ void win32_formatmessage(DWORD code, char *reason, int len) JL_NOTSAFEPOINT;
JL_DLLEXPORT void *jl_get_library_(const char *f_lib, int throw_err);
#define jl_get_library(f_lib) jl_get_library_(f_lib, 1)
JL_DLLEXPORT void *jl_load_and_lookup(const char *f_lib, const char *f_name, _Atomic(void*) *hnd);
JL_DLLEXPORT void *jl_load_and_lookup_with_ver(const char *f_lib, const char *f_name, const char *f_version, _Atomic(void*) *hnd);
JL_DLLEXPORT void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name);
JL_DLLEXPORT void *jl_lazy_load_and_lookup_with_ver(jl_value_t *lib_val, const char *f_name, const char *f_version);
JL_DLLEXPORT jl_value_t *jl_get_cfunction_trampoline(
jl_value_t *fobj, jl_datatype_t *result, htable_t *cache, jl_svec_t *fill,
void *(*init_trampoline)(void *tramp, void **nval),
Expand Down
28 changes: 28 additions & 0 deletions src/runtime_ccall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ void *jl_load_and_lookup(const char *f_lib, const char *f_name, _Atomic(void*) *
return ptr;
}

extern "C" JL_DLLEXPORT
void *jl_load_and_lookup_with_ver(const char *f_lib, const char *f_name, const char *f_version, _Atomic(void*) *hnd)
{
void *handle = jl_atomic_load_acquire(hnd);
if (!handle)
jl_atomic_store_release(hnd, (handle = jl_get_library(f_lib)));
void * ptr;
jl_dlvsym(handle, f_name, f_version, &ptr, 1);
return ptr;
}

// jl_load_and_lookup, but with library computed at run time on first call
extern "C" JL_DLLEXPORT
void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name)
Expand All @@ -81,6 +92,23 @@ void *jl_lazy_load_and_lookup(jl_value_t *lib_val, const char *f_name)
return ptr;
}

// jl_load_and_lookup_with_ver, but with library computed at run time on first call
extern "C" JL_DLLEXPORT
void *jl_lazy_load_and_lookup_with_ver(jl_value_t *lib_val, const char *f_name, const char *f_version)
{
char *f_lib;

if (jl_is_symbol(lib_val))
f_lib = jl_symbol_name((jl_sym_t*)lib_val);
else if (jl_is_string(lib_val))
f_lib = jl_string_data(lib_val);
else
jl_type_error("ccall", (jl_value_t*)jl_symbol_type, lib_val);
void *ptr;
jl_dlvsym(jl_get_library(f_lib), f_name, f_version, &ptr, 1);
return ptr;
}

// miscellany
std::string jl_get_cpu_name_llvm(void)
{
Expand Down

0 comments on commit 93580e2

Please sign in to comment.