-
Notifications
You must be signed in to change notification settings - Fork 420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open dynamic library and call to it using dlopen() from Chapel? #26024
Comments
On a toy (and by this time should be very stale, if I can find it) branch I was able to to dynamic function loading in the runtime. If something is doable in the runtime, it should be relatively easy to do in Chapel. So, I am hopeful that it should be doable without things getting too ugly. Doing this (in the runtime, that is) is still of interest to me to support a better cpu-as-device mode, which can allow us to find a function in the same binary using its name. |
Here is a complete example. I am aware of the following issues in this area:
test.sh
clib.h void clibfn(void); clib.c #include "clib.h"
#include <stdio.h>
void clibfn(void) {
printf("in clibfn\n");
} c-dlopen.c #include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
void* lib = NULL;
void (*fn)(void) = NULL;
lib = dlopen("clib.so", RTLD_LAZY);
if (!lib) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
exit(-1);
}
fn = dlsym(lib, "clibfn");
if (!fn) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
exit(-1);
}
fn();
dlclose(lib);
return 0;
} chapel-dlopen.chpl extern {
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
static int callit(void) {
void* lib = NULL;
void (*fn)(void) = NULL;
lib = dlopen("clib.so", RTLD_LAZY);
if (!lib) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
exit(-1);
}
fn = dlsym(lib, "clibfn");
if (!fn) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
exit(-1);
}
fn();
dlclose(lib);
return 0;
}
}
proc main() {
callit();
} expected behavior:
|
I was curious whether the dlopen/dlclose calls could be pushed outside of the C code easily, and it appears that's the case: extern {
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
static void callit(void* lib) {
void (*fn)(void) = NULL;
fn = dlsym(lib, "clibfn");
if (!fn) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
exit(-1);
}
fn();
}
}
proc main() {
const lib = dlopen("clib.so", RTLD_LAZY);
if (!lib) then halt("dlopen failed: " + dlerror():string);
callit(lib);
dlclose(lib);
} I also wrote a variant on the above that accepts an integer, prints it, and returns a variant of it, and that worked fine as well. |
I think you can go a step further and move the extern {
typedef void (*void_func_t)(void);
void callit(void* f);
void callit(void* f) { ((void_func_t)f)(); }
} So all that is missing the ability to represent c function pointers in Chapel (beyond just |
Great point—I considered doing that, but wasn't sure I liked the idea of casting the Chapel |
Here's a Chapel code that uses Jade's proposed cleanup to call a routine taking and returning a (C) integer: extern {
#include <dlfcn.h>
static int callit(void *fnptr) {
typedef int (*my_func_t)(int);
return ((my_func_t)fnptr)(42);
}
}
proc main() {
const lib = dlopen("clib.so", RTLD_LAZY);
if !lib then halt("dlopen failed: " + string.createBorrowingBuffer(dlerror()));
const fn = dlsym(lib, "clibfn2");
if !fn then halt("dlsym failed: " + string.createBorrowingBuffer(dlerror()));
writeln(callit(fn));
dlclose(lib);
} where I added this routine to Michael's clib.c/h: int clibfn2(int x) {
printf("in clibfn(%d)\n", x);
return x + 3;
} I was also able to call into a simple Chapel routine using the following: chpllib.chpl: export proc chpllibfn(x: int): int {
extern proc printf(x...); // note that we may also be able to call into writeln() if we called into `chpl__init_chpllib()`…
printf("x is: %lld\n", x);
return x+3;
} mytest.chpl: extern {
#include <dlfcn.h>
#include <stdint.h>
static int callit(void *fnptr) {
typedef int64_t (*my_func_t)(int64_t);
return ((my_func_t)fnptr)(42);
}
}
proc main() {
const lib = dlopen("libchpllib.so", RTLD_LAZY);
if !lib then halt("dlopen failed: " + string.createBorrowingBuffer(dlerror())\
);
const fn = dlsym(lib, "chpllibfn");
if !fn then halt("dlsym failed: " + string.createBorrowingBuffer(dlerror()));
writeln(callit(fn));
dlclose(lib);
} Using these commands: $ chpl --library --dynamic chpllib.chpl
$ ln -s lib/libchpllib.so . // alternatively, I could add `lib/` to my dynamic library path
$ chpl mytest.chpl
$ ./mytest [edit: Note that I've only tried all of this in single-locale settings so far; I expect that, for multi-locale settings, we'd need to make the dl*() calls on each locale that wanted to make calls] |
I've opened: #26099 This allows you to write: // Just includes the header containing 'dlopen', nothing more.
extern {
#include <dlfcn.h>
}
proc main() {
// Open the library.
const lib = dlopen("SomeCLibrary.so", RTLD_LAZY);
if !lib then halt("dlopen failed: " + string.createBorrowingBuffer(dlerror()));
// Get a 'c_ptr(void)' to the function.
const vp1 = dlsym(lib, "clibfn1");
if !vp1 then halt("dlsym failed: " + string.createBorrowingBuffer(dlerror()));
// Cast to the appropriate function type and call.
const f1 = vp1 : (proc(): void);
f1();
// Get a 'c_ptr(void)' to the function.
const vp2 = dlsym(lib, "clibfn2");
if !vp2 then halt("dlsym failed: " + string.createBorrowingBuffer(dlerror()));
// Cast to the appropriate function type and call.
const f2 = vp2 : (proc(_: int): int);
var x = f2(42);
writeln(x);
// Close the library.
dlclose(lib);
} You have to turn on An important caveat here is that these function pointers are usable only on the locale that loaded the library - the moment you try to call them on another locale the whole program explodes. This is a problem that the entire pointer implementation of FCFs is facing at the moment - due to ASLR a pointer can live at a different address on each locale. For symbols that are known at compile-time the fix will be to add them to the global procedure table of the program. That can't really happen for symbols produced by There are a couple of approaches we could investigate as ways to prevent the program from exploding:
|
I think we need to introduce a more generalized table mapping integers to function pointers on all locales. This is similar to the existing ftable / dispatch table, but the main difference is that it's not entirely known before execution. It can be modified at runtime. Our future separate compilation / dynamic loading efforts could even use this same mechanism. Note that we already have something like this in Presumably, at the same time, we would need to have our |
This issue asks whether it is currently possible to create a Chapel program that would open a dynamic library using
dlopen()
and make calls to it.If so, I'd be interested in an example program demonstrating the capability; if not, I'd like to understand what the limiting factors to doing so are today, and what would be required to resolve them (either as a heroic programmer or through changes to the language or implementation).
The text was updated successfully, but these errors were encountered: