diff options
Diffstat (limited to 'foreign_avcall.c')
-rw-r--r-- | foreign_avcall.c | 340 |
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; +} + |