added: callback functionality to `cbindgen`
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 23 Oct 2023 13:42:35 +0000 (08:42 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 23 Oct 2023 13:42:35 +0000 (08:42 -0500)
compiler/build.sh
compiler/src/wasm_runtime.c
core/onyx/cbindgen.onyx
interpreter/include/ovm_wasm.h
interpreter/src/wasm/extern.c
interpreter/src/wasm/instance.c
interpreter/src/wasm/ref.c
interpreter/src/wasm/table.c
shared/include/onyx_library.h

index 715a7baabf9b1fd2bd6207e4b88475067fde2f3c..eff0031d33c4a2d4e2fd75dbbb0c8101ef7e15b5 100755 (executable)
@@ -30,7 +30,7 @@ fi
 
 FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER"
 
-if [ "$USE_DYNCALL" = "1" ]; then
+if [ "$USE_DYNCALL" = "1" ] && [ "$RUNTIME_LIBRARY" = "ovmwasm" ]; then
     LIBS="$LIBS ../shared/lib/linux_$ARCH/lib/libdyncall_s.a ../shared/lib/linux_$ARCH/lib/libdyncallback_s.a"
     FLAGS="$FLAGS -DUSE_DYNCALL"
 fi
index 58c3cdeb614ad0425ff4ca3c3405ef4a0d19c3b1..9e4b221f1da90a99ecdf8e4765e0d893f69f986d 100644 (file)
@@ -202,6 +202,11 @@ static void lookup_and_load_custom_libraries(LinkLibraryContext *ctx, bh_arr(Was
 
 #ifdef USE_DYNCALL
 
+//
+// This code implements the ability to use `dyncall:some_library.so` as the
+// module import name to dynamically load a shared library at runtime.
+//
+
 typedef struct DynCallContext {
     void (*func)();
     char types[64];
@@ -290,6 +295,84 @@ static wasm_func_t *link_and_prepare_dyncall_function(
     return wasm_func;
 }
 
+
+//
+// This code implements the ability to wrap a wasm function in a callback
+// that can be handed to a library that expects a C function.
+//
+
+typedef struct DynCallbackContext {
+    wasm_func_t *wasm_func;
+    char *sig;
+} DynCallbackContext;
+
+static DCsigchar __wasm_dyncallback(DCCallback *cb, DCArgs *args, DCValue *result, void *userdata) {
+    DynCallbackContext *ctx = userdata;
+    int arg_count = bh_str_last_index_of(ctx->sig, ')');
+
+    wasm_val_vec_t wasm_args;
+    wasm_val_vec_new_uninitialized(&wasm_args, arg_count);
+
+    for (int i = 0; i < arg_count; i++) {
+        switch (ctx->sig[i]) {
+            case 'B': wasm_args.data[i] = WASM_I32_VAL(dcbArgBool(args)); break;
+            case 'c': wasm_args.data[i] = WASM_I32_VAL(dcbArgChar(args)); break;
+            case 'C': wasm_args.data[i] = WASM_I32_VAL(dcbArgUChar(args)); break;
+            case 's': wasm_args.data[i] = WASM_I32_VAL(dcbArgShort(args)); break;
+            case 'S': wasm_args.data[i] = WASM_I32_VAL(dcbArgUShort(args)); break;
+            case 'i': wasm_args.data[i] = WASM_I32_VAL(dcbArgInt(args)); break;
+            case 'I': wasm_args.data[i] = WASM_I32_VAL(dcbArgUInt(args)); break;
+            case 'j': wasm_args.data[i] = WASM_I64_VAL(dcbArgLong(args)); break;
+            case 'J': wasm_args.data[i] = WASM_I64_VAL(dcbArgULong(args)); break;
+            case 'l': wasm_args.data[i] = WASM_I64_VAL(dcbArgLongLong(args)); break;
+            case 'L': wasm_args.data[i] = WASM_I64_VAL(dcbArgULongLong(args)); break;
+            case 'f': wasm_args.data[i] = WASM_F32_VAL(dcbArgFloat(args)); break;
+            case 'd': wasm_args.data[i] = WASM_F64_VAL(dcbArgDouble(args)); break;
+            case 'p': wasm_args.data[i] = WASM_I64_VAL((uintptr_t) dcbArgPointer(args)); break;
+        }
+    }
+
+    wasm_val_vec_t wasm_results;
+    wasm_val_vec_new_uninitialized(&wasm_results, 1);
+
+    wasm_func_call(ctx->wasm_func, &wasm_args, &wasm_results);
+
+    switch (ctx->sig[arg_count + 1]) {
+        case 'B': result->B = wasm_results.data[0].of.i32; break;
+        case 'c': result->c = wasm_results.data[0].of.i32; break;
+        case 'C': result->C = wasm_results.data[0].of.i32; break;
+        case 's': result->s = wasm_results.data[0].of.i32; break;
+        case 'S': result->S = wasm_results.data[0].of.i32; break;
+        case 'i': result->i = wasm_results.data[0].of.i32; break;
+        case 'I': result->I = wasm_results.data[0].of.i32; break;
+        case 'j': result->j = wasm_results.data[0].of.i64; break;
+        case 'J': result->J = wasm_results.data[0].of.i64; break;
+        case 'l': result->l = wasm_results.data[0].of.i64; break;
+        case 'L': result->L = wasm_results.data[0].of.i64; break;
+        case 'f': result->f = wasm_results.data[0].of.f32; break;
+        case 'd': result->d = wasm_results.data[0].of.f64; break;
+        case 'p': result->p = (void *) wasm_results.data[0].of.i64; break;
+    }
+
+    wasm_val_vec_delete(&wasm_args);
+    wasm_val_vec_delete(&wasm_results);
+
+    return ctx->sig[arg_count + 1];
+}
+
+static void (* wasm_func_from_idx(wasm_table_t *func_table, unsigned int index, char *signature))(void) {
+    wasm_ref_t *func_ref = wasm_table_get(func_table, index);
+    assert(func_ref); // TODO: Switch to trap
+
+    wasm_func_t *func = wasm_ref_as_func(func_ref);
+
+    DynCallbackContext *dcc = bh_alloc_item(bh_heap_allocator(), DynCallbackContext);
+    dcc->wasm_func = func;
+    dcc->sig = signature;
+
+    return (void (*)()) dcbNewCallback(signature, &__wasm_dyncallback, dcc);
+}
+
 #endif // USE_DYNCALL
 
 static void onyx_print_trap(wasm_trap_t* trap) {
@@ -560,6 +643,13 @@ b32 onyx_run_wasm(bh_buffer wasm_bytes, int argc, char *argv[]) {
     wasm_runtime.wasm_imports = wasm_imports;
     wasm_runtime.wasm_memory = wasm_memory;
     wasm_runtime.wasm_instance = wasm_instance;
+    wasm_runtime.wasm_func_table = wasm_extern_as_table(
+        wasm_extern_lookup_by_name(wasm_module, wasm_instance, "__indirect_function_table")
+    );
+
+#ifdef USE_DYNCALL
+    wasm_runtime.wasm_func_from_idx = wasm_func_from_idx;
+#endif
     
     wasm_runtime.argc = argc;
     wasm_runtime.argv = argv;
index b2678a88d8dd804c3d3d92abc63685af1cb5e2bb..d381ecfeca405a28115097a60f19df1f53f67e61 100644 (file)
@@ -42,6 +42,10 @@ package cbindgen
 
 use runtime
 
+customize :: struct {
+    symbol_name: str;
+}
+
 #if #defined (runtime.Generated_Foreign_Info) {
 
 use core {package, *}
@@ -56,10 +60,6 @@ use simd {*}
 }
 
 
-customize :: struct {
-    symbol_name: str;
-}
-
 Cast_Mapping :: struct {
     type: type_expr;
     name: str;
@@ -246,12 +246,59 @@ compile_c_file :: (
 
         param_num := 0;
         for method_info.parameter_types {
-            if get_type_info(it).kind == .Slice {
+            it_info := it->info();
+
+            if it_info.kind == .Slice {
                 io.write_format(&callw, "ONYX_PTR(P({}, i32)), P({}, i32)", param_num, param_num + 1);
                 param_num += 1;
 
             } elseif is_pointer(it) {
                 io.write_format(&callw, "ONYX_PTR(P({}, i32))", param_num);
+            
+            } elseif it_info.kind == .Function {
+                call_signature := make(dyn_str);
+                defer delete(&call_signature);
+
+                func_info := it_info->as_function();
+                for p: func_info.parameter_types {
+                    map_to_dyncall(p, &call_signature);
+                }
+
+                string.append(&call_signature, ")");
+                map_to_dyncall(func_info.return_type, &call_signature);
+
+                io.write_format(&callw, "(void *) runtime->wasm_func_from_idx(runtime->wasm_func_table, P({}, i32), \"{}\")", param_num, cast(str) call_signature);
+
+                map_to_dyncall :: (t: type_expr, call_signature: &dyn_str) {
+                    p := t;
+                    switch i := t->info(); i.kind {
+                        case .Distinct {
+                            p = i->as_distinct().base_type;
+                        }
+                    }
+
+                    switch p {
+                        case void do string.append(call_signature, "v");
+                        case bool do string.append(call_signature, "B");
+                        case i8   do string.append(call_signature, "c");
+                        case u8   do string.append(call_signature, "C");
+                        case i16  do string.append(call_signature, "s");
+                        case u16  do string.append(call_signature, "S");
+                        case i32  do string.append(call_signature, "i");
+                        case u32  do string.append(call_signature, "I");
+                        case i64  do string.append(call_signature, "l");
+                        case u64  do string.append(call_signature, "L");
+                        case f32  do string.append(call_signature, "f");
+                        case f64  do string.append(call_signature, "d");
+                        case #default {
+                            if is_pointer(p) {
+                                string.append(call_signature, "p");
+                            } else {
+                                assert(false, tprintf("Unsupported type in function pointer: {}", p));
+                            }
+                        }
+                    }
+                }
 
             } else {
                 matched := false;
@@ -318,7 +365,7 @@ compile_c_file :: (
             case .Multi_Pointer do return "ptr"; 
             case .Array do return "ptr";
 
-            case .Function do assert(false, "Passing functions between wasm and c is not yet supported.");
+            case .Function do return "i32"; // assert(false, "Passing functions between wasm and c is not yet supported.");
             case .Slice do assert(false, "Passing a slice from c to wasm is not yet supported.");
             case .Enum do return type_to_wasm_type((cast(&Type_Info_Enum) param_info).backing_type);
             case .Distinct do return type_to_wasm_type((cast(&Type_Info_Distinct) param_info).base_type);
@@ -373,7 +420,7 @@ compile_c_file :: (
 
             case .Pointer do return "WASM_I32"; // This will also have to depend on the pointer size...
             case .Multi_Pointer do return "WASM_I32"; // This will also have to depend on the pointer size...
-            case .Function do assert(false, "Passing functions between wasm and c is not yet supported.");
+            case .Function do return "WASM_I32, WASM_I32, WASM_I32"; // assert(false, "Passing functions between wasm and c is not yet supported.");
             case .Array do return "WASM_I32";
             case .Slice do return "WASM_I32,WASM_I32";
             case .Enum do return type_encoding((cast(&Type_Info_Enum) param_info).backing_type);
index 0dac195e9c6acf120c5f444f99f20a66301bc52b..16ee7b371f3d21146c0e478031e363c5c4402cab 100644 (file)
@@ -94,7 +94,17 @@ struct wasm_exporttype_t {
 
 // Runtime Objects
 
+enum wasm_ref_stored_obj_t {
+    wasm_ref_stored_obj_unknown,
+    wasm_ref_stored_obj_func,
+};
+
 struct wasm_ref_t {
+    enum wasm_ref_stored_obj_t stored_obj;
+
+    union {
+        struct wasm_func_t *func;
+    };
 };
 
 struct wasm_frame_t {
@@ -180,6 +190,7 @@ struct wasm_global_inner_t {
 struct wasm_table_inner_t {
     ovm_program_t *program;
     ovm_engine_t  *engine;
+    struct wasm_instance_t *instance;
 
     int static_arr;
 
index 9cda46c80d45b7a07ff02f9d2f008bc3059b190a..c0dc3d7101efaef98db3f2e621b73e9b74b94b88 100644 (file)
@@ -18,10 +18,10 @@ wasm_extern_t* wasm_global_as_extern(wasm_global_t* ext) { return (wasm_extern_t
 wasm_extern_t* wasm_table_as_extern(wasm_table_t* ext)   { return (wasm_extern_t *) ext; }
 wasm_extern_t* wasm_memory_as_extern(wasm_memory_t* ext) { return (wasm_extern_t *) ext; }
 
-wasm_func_t* wasm_extern_as_func(wasm_extern_t* ext)     { return ext->type->kind == WASM_EXTERN_FUNC ? (wasm_func_t *) ext : NULL; }
-wasm_global_t* wasm_extern_as_global(wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_GLOBAL ? (wasm_global_t *) ext : NULL; }
-wasm_table_t* wasm_extern_as_table(wasm_extern_t* ext)   { return ext->type->kind == WASM_EXTERN_TABLE ? (wasm_table_t *) ext : NULL; }
-wasm_memory_t* wasm_extern_as_memory(wasm_extern_t* ext) { return ext->type->kind == WASM_EXTERN_MEMORY ? (wasm_memory_t *) ext : NULL; }
+wasm_func_t* wasm_extern_as_func(wasm_extern_t* ext)     { return (ext && ext->type->kind == WASM_EXTERN_FUNC) ? (wasm_func_t *) ext : NULL; }
+wasm_global_t* wasm_extern_as_global(wasm_extern_t* ext) { return (ext && ext->type->kind == WASM_EXTERN_GLOBAL) ? (wasm_global_t *) ext : NULL; }
+wasm_table_t* wasm_extern_as_table(wasm_extern_t* ext)   { return (ext && ext->type->kind == WASM_EXTERN_TABLE) ? (wasm_table_t *) ext : NULL; }
+wasm_memory_t* wasm_extern_as_memory(wasm_extern_t* ext) { return (ext && ext->type->kind == WASM_EXTERN_MEMORY) ? (wasm_memory_t *) ext : NULL; }
 
 const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t* ext)     { return (const wasm_extern_t *) ext; }
 const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t* ext) { return (const wasm_extern_t *) ext; }
index 2b04f91b7635d320b166704abdb67fdd1a9057e2..d2a9a3975f9ea4681568c3df08e2ab3e605c6b41 100644 (file)
@@ -242,6 +242,7 @@ static void prepare_instance(wasm_instance_t *instance, const wasm_extern_vec_t
         wasm_table_t *table = wasm_table_new(instance->store, instance->module->tabletypes.data[i], NULL);
         table->inner.table.engine     = ovm_engine;
         table->inner.table.program    = ovm_program;
+        table->inner.table.instance   = instance;
         table->inner.table.static_arr = instance->module->tabletypes.data[i]->type.table.static_arr;
 
         bh_arr_push(instance->tables, table);
index 0ade447ecfc2027a78dd2352e36079425e0fd4ec..1777d19772ab8fad531e65b87cb2c5e4d2398d65 100644 (file)
@@ -1,2 +1,20 @@
 
-// TODO
+#include "ovm_wasm.h"
+#include "vm.h"
+
+wasm_func_t *wasm_ref_as_func(wasm_ref_t *ref) {
+    if (ref->stored_obj == wasm_ref_stored_obj_func) {
+        return ref->func;
+    }
+
+    return NULL;
+}
+
+const wasm_func_t *wasm_ref_as_func_const(const wasm_ref_t *ref) {
+    if (ref->stored_obj == wasm_ref_stored_obj_func) {
+        return (const wasm_func_t *) ref->func;
+    }
+
+    return NULL;
+}
+
index b878eba83bb4420bebc19d9a0f2889cccb66c1af..0f66a4f75a50e9727c696ac2a2db3af5954a910c 100644 (file)
@@ -14,3 +14,19 @@ wasm_tabletype_t *wasm_table_type(const wasm_table_t *table) {
     return (wasm_tabletype_t *) table->inner.table.type;
 }
 
+wasm_ref_t *wasm_table_get(const wasm_table_t *table, unsigned int index) {
+    const struct wasm_table_inner_t *tab = &table->inner.table;
+    ovm_program_t *program = tab->program;
+    ovm_engine_t  *engine  = tab->engine;
+
+    // TODO bounds checking
+    wasm_func_t *func = tab->instance->funcs[
+        program->static_integers[program->static_data[tab->static_arr].start_idx + index]
+    ];
+
+    wasm_ref_t *new_ref = bh_alloc(engine->store->arena_allocator, sizeof(*new_ref));
+    new_ref->stored_obj = wasm_ref_stored_obj_func;
+    new_ref->func = func;
+
+    return new_ref;
+}
\ No newline at end of file
index ef4db7c89c49fbc3f51929de1ee960064487e669..8b0d26ad876093a979a3e3e71bed136886881549 100644 (file)
@@ -19,12 +19,12 @@ typedef struct OnyxRuntime {
     wasm_memory_t* wasm_memory;
     wasm_engine_t *wasm_engine;
     wasm_extern_vec_t wasm_imports;
+    wasm_table_t *wasm_func_table;
 
     int argc;
     char **argv;
 
-    // HACK HACK HACK
-    // There should need to be this much stuff in here, but because Wasmer doesn't ship a "wasmerdll.lib"
+    // There shouldn't need to be this much stuff in here, but because Wasmer doesn't ship a "wasmerdll.lib"
     // file for windows, it is impossible for it to link successfully against the function provided in onyx.exe.
     // Therefore, we must serve as the linker and do this manually. Hopefully that library file will be
     // shipped soon so this can go away...
@@ -36,6 +36,10 @@ typedef struct OnyxRuntime {
     void (*onyx_print_trap)(wasm_trap_t* trap);
     wasm_store_t *(*wasm_store_new)(wasm_engine_t *engine);
     void (*wasm_store_delete)(wasm_store_t *store);
+
+    // This is only set when using the OVMwasm runtime, as Wasmer's C-api does not allow
+    // for this function to exist, yet.
+    void (*(*wasm_func_from_idx)(wasm_table_t *table, unsigned int index, char *signature))(void);
 } OnyxRuntime;
 
 OnyxRuntime* runtime;