summaryrefslogtreecommitdiff
path: root/foreign_avcall.c
diff options
context:
space:
mode:
Diffstat (limited to 'foreign_avcall.c')
-rw-r--r--foreign_avcall.c340
1 files changed, 340 insertions, 0 deletions
diff --git a/foreign_avcall.c b/foreign_avcall.c
new file mode 100644
index 0000000..69146bb
--- /dev/null
+++ b/foreign_avcall.c
@@ -0,0 +1,340 @@
+/* WARNING: In this file, you will find crazy macros and dubious usage of avcall. Beware! */
+
+typedef struct {
+ void *handle;
+} Library;
+
+#if CHAR_BIT != 8
+#error "Compile-time foreign functions can only be used on systems where CHAR_BIT is 8."
+#endif
+
+/* avcall has some sign conversion problems on BSD */
+/* (the macros it defines cause problems too, which is why this is ignored for so long) */
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#elif defined __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#endif
+#include <avcall.h>
+#include <dlfcn.h>
+
+
+#if SCHAR_MAX != 127
+#error "Foreign function support requires an 8-bit signed type."
+#else
+#define av_type_with_limit_127 schar
+#endif
+
+#if UCHAR_MAX != 255
+#error "Foreign function support requires an 8-bit unsigned type."
+#else
+#define av_type_with_limit_255 uchar
+#endif
+
+#if SHRT_MAX != 32767
+#error "Foreign function support requires a 16-bit signed type."
+#else
+#define av_type_with_limit_32767 short
+#endif
+
+#if USHRT_MAX != 65535
+#error "Foreign function support requires an 16-bit unsigned type."
+#else
+#define av_type_with_limit_65535 ushort
+#endif
+
+#if INT_MAX == 2147483647
+#define av_type_with_limit_2147483647 int
+#elif LONG_MAX == 2147483647
+#define av_type_with_limit_2147483647 long
+#else
+#error "Foreign function support requires an 32-bit signed type."
+#endif
+
+#if UINT_MAX == 4294967295
+#define av_type_with_limit_4294967295 uint
+#elif ULONG_MAX == 4294967295
+#define av_type_with_limit_4294967295 ulong
+#else
+#error "Foreign function support requires an 32-bit unsigned type."
+#endif
+
+#if LONG_MAX == 9223372036854775807
+#define av_type_with_limit_9223372036854775807 long
+#elif LLONG_MAX == 9223372036854775807
+#define av_type_with_limit_9223372036854775807 longlong
+#else
+#error "Foreign function support requires a 64-bit signed type."
+#endif
+
+#if ULONG_MAX == 18446744073709551615ULL
+#define av_type_with_limit_18446744073709551615 ulong
+#elif ULLONG_MAX == 18446744073709551615ULL
+#define av_type_with_limit_18446744073709551615 ulonglong
+#else
+#error "Foreign function support requires a 64-bit unsigned type."
+#endif
+
+
+
+#define av_type_with_limit(x) join(av_type_with_limit_, x)
+
+#define toc_av_start(limit) join(av_start_, av_type_with_limit(limit))
+#define toc_av_add(limit) join(av_, av_type_with_limit(limit))
+
+#define toc_av_start_f32 av_start_float
+#define toc_av_start_f64 av_start_double
+#define toc_av_f32 av_float
+#define toc_av_f64 av_double
+
+static Status arg_list_start(av_alist *arg_list, FnPtr fn, Value *return_val, Type *return_type, Location where) {
+ switch (return_type->kind) {
+ case TYPE_UNKNOWN:
+ err_print(where, "Cannot call foreign function with unknown return type.");
+ return false;
+ case TYPE_TUPLE:
+ err_print(where, "Cannot call foreign function with tuple return type.");
+ return false;
+ case TYPE_ARR:
+ err_print(where, "Foreign functions cannot return arrays.");
+ return false;
+ case TYPE_FN:
+ warn_print(where, "Foreign function returns function pointer. If it returns a C-style function pointer, it won't be called properly by toc.");
+ av_start_ptr(*arg_list, fn, FnExpr *, &return_val->fn);
+ break;
+ case TYPE_PTR:
+ av_start_ptr(*arg_list, fn, void *, &return_val->ptr);
+ break;
+ case TYPE_BUILTIN:
+ switch (return_type->builtin) {
+ case BUILTIN_I8:
+ toc_av_start(127)(*arg_list, fn, &return_val->i8);
+ break;
+ case BUILTIN_U8:
+ toc_av_start(255)(*arg_list, fn, &return_val->u8);
+ break;
+ case BUILTIN_I16:
+ toc_av_start(32767)(*arg_list, fn, &return_val->i16);
+ break;
+ case BUILTIN_U16:
+ toc_av_start(65535)(*arg_list, fn, &return_val->u16);
+ break;
+ case BUILTIN_I32:
+ toc_av_start(2147483647)(*arg_list, fn, &return_val->i32);
+ break;
+ case BUILTIN_U32:
+ toc_av_start(4294967295)(*arg_list, fn, &return_val->u32);
+ break;
+ case BUILTIN_I64:
+ toc_av_start(9223372036854775807)(*arg_list, fn, &return_val->i64);
+ break;
+ case BUILTIN_U64:
+ toc_av_start(18446744073709551615)(*arg_list, fn, &return_val->u64);
+ break;
+ case BUILTIN_BOOL:
+ /* bool is probably just unsigned char.... hopefully... */
+ av_start_uchar(*arg_list, fn, &return_val->u8);
+ break;
+ case BUILTIN_CHAR:
+ av_start_char(*arg_list, fn, &return_val->charv);
+ break;
+ case BUILTIN_F32:
+ toc_av_start_f32(*arg_list, fn, &return_val->f32);
+ break;
+ case BUILTIN_F64:
+ toc_av_start_f64(*arg_list, fn, &return_val->f64);
+ break;
+ case BUILTIN_TYPE:
+ av_start_ptr(*arg_list, fn, Type *, &return_val->type);
+ break;
+ case BUILTIN_NMS:
+ av_start_ptr(*arg_list, fn, Namespace *, &return_val->nms);
+ break;
+ case BUILTIN_VOID:
+ av_start_void(*arg_list, fn);
+ break;
+ case BUILTIN_VARARGS:
+ assert(0);
+ break;
+ }
+ break;
+ case TYPE_STRUCT: {
+ size_t struct_size = compiler_sizeof(return_type);
+ StructDef *struc = return_type->struc;
+ return_val->struc = err_calloc(1, struct_size);
+ bool splittable;
+ /* hopefully this is right! */
+ if (struct_size <= sizeof(long)) {
+ splittable = true;
+ } else if (struct_size > 2*sizeof(long)) {
+ splittable = false;
+ } else if (arr_len(struc->fields) > 4) {
+ splittable = false;
+ } else {
+ /* NOTE: this warning is not because splittable is being computed incorrectly! it doesn't handle it right with *either* splittable = 0 or splittable = 1 */
+ warn_print(where, "Dynamically calling function which returns a struct. avcall seems to not handle structs of size ~2*sizeof(long) correctly.");
+ splittable = true;
+ size_t word_size = sizeof(__avword);
+ arr_foreach(struc->fields, Field, f) {
+ if (f->offset / word_size != (f->offset + compiler_sizeof(f->type) - 1) / word_size) {
+ splittable = false;
+ break;
+ }
+ }
+ }
+ /* getting warning on Debian stretch about splittable being set but not used */
+ _av_start_struct(*arg_list, fn, struct_size, splittable, return_val->struc); (void)splittable;
+ } break;
+ case TYPE_SLICE:
+ av_start_struct(*arg_list, fn, Slice, av_word_splittable_2(I64, void *), &return_val->slice);
+ break;
+ case TYPE_EXPR:
+ assert(0);
+ return false;
+ }
+ return true;
+}
+
+static Status arg_list_add(av_alist *arg_list, Value val, Type *type, Location where) {
+ switch (type->kind) {
+ case TYPE_TUPLE:
+ case TYPE_UNKNOWN:
+ case TYPE_ARR: { /* @TODO: maybe just pass pointer for arr? */
+ char *s = type_to_str(type);
+ err_print(where, "Cannot pass type %s to foreign function.", s);
+ free(s);
+ return false;
+ }
+ case TYPE_PTR:
+ av_ptr(*arg_list, void *, val.ptr);
+ break;
+ case TYPE_FN:
+ warn_print(where, "Passing toc function pointer to foreign function. This will not work if the function expects a C-style function pointer.");
+ av_ptr(*arg_list, FnExpr *, val.fn);
+ break;
+ case TYPE_BUILTIN:
+ switch (type->builtin) {
+ case BUILTIN_I8:
+ toc_av_add(127)(*arg_list, val.i8);
+ break;
+ case BUILTIN_U8:
+ toc_av_add(255)(*arg_list, val.u8);
+ break;
+ case BUILTIN_I16:
+ toc_av_add(32767)(*arg_list, val.i16);
+ break;
+ case BUILTIN_U16:
+ toc_av_add(65535)(*arg_list, val.u16);
+ break;
+ case BUILTIN_I32:
+ toc_av_add(2147483647)(*arg_list, val.i32);
+ break;
+ case BUILTIN_U32:
+ toc_av_add(4294967295)(*arg_list, val.u32);
+ break;
+ case BUILTIN_I64:
+ toc_av_add(9223372036854775807)(*arg_list, val.i64);
+ break;
+ case BUILTIN_U64:
+ toc_av_add(18446744073709551615)(*arg_list, val.u64);
+ break;
+ case BUILTIN_CHAR:
+ av_char(*arg_list, val.charv);
+ break;
+ case BUILTIN_BOOL:
+ av_uchar(*arg_list, val.boolv);
+ break;
+ case BUILTIN_F32:
+ toc_av_f32(*arg_list, val.f32);
+ break;
+ case BUILTIN_F64:
+ toc_av_f64(*arg_list, val.f64);
+ break;
+ case BUILTIN_TYPE:
+ av_ptr(*arg_list, Type *, val.type);
+ break;
+ case BUILTIN_NMS:
+ av_ptr(*arg_list, Namespace *, val.nms);
+ break;
+ case BUILTIN_VARARGS:
+ arr_foreach(val.varargs, VarArg, arg) {
+ arg_list_add(arg_list, arg->val, arg->type, where);
+ }
+ break;
+ case BUILTIN_VOID:
+ err_print(where, "Cannot pass type void to foreign function.");
+ return false;
+ }
+ break;
+ case TYPE_SLICE:
+ av_struct(*arg_list, Slice, val.slice);
+ break;
+ case TYPE_STRUCT:
+ _av_struct(*arg_list, compiler_sizeof(type), compiler_alignof(type), val.struc);
+ break;
+ case TYPE_EXPR:
+ assert(0);
+ return false;
+ }
+ return true;
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#elif defined __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static Status foreign_call(ForeignFnManager *ffmgr, FnExpr *fn, Type *ret_type, Type *arg_types, size_t arg_types_stride, Value *args, size_t nargs, Location call_where, Value *ret) {
+ FnPtr fn_ptr = fn->foreign.fn_ptr;
+ if (!fn_ptr) {
+ assert(fn->flags & FN_EXPR_FOREIGN);
+ const char *libname = fn->foreign.lib;
+ if (!libname) {
+ err_print(call_where, "Attempt to call function at compile time which does not have an associated library.");
+ info_print(fn->where, "Function was declared here.");
+ return false;
+ }
+ Library *lib = str_hash_table_get(&ffmgr->libs_loaded, libname, strlen(libname));
+ if (!lib) {
+ void *handle = dlopen(libname, RTLD_LAZY);
+ if (!handle) {
+ err_print(call_where, "Could not open dynamic library: %s.", libname);
+ return false;
+ }
+ lib = str_hash_table_insert(&ffmgr->libs_loaded, libname, strlen(libname));
+ lib->handle = handle;
+ }
+ const char *name = fn->foreign.name;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+ fn_ptr = dlsym(lib->handle, name);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ if (!fn_ptr) {
+ err_print(call_where, "Could not get function from dynamic library: %s.", name);
+ return false;
+ }
+ fn->foreign.fn_ptr = fn_ptr;
+ }
+
+ av_alist arg_list;
+ if (!arg_list_start(&arg_list, fn_ptr, ret, ret_type, call_where))
+ return false;
+ char *type = (char *)arg_types;
+ for (size_t i = 0; i < nargs; ++i) {
+ if (!arg_list_add(&arg_list, args[i], (Type *)type, call_where))
+ return false;
+ type += arg_types_stride;
+ }
+ av_call(arg_list);
+ return true;
+}
+