diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9bdf2d00..f2231647 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,6 +30,10 @@ jobs: - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP) run: | + cd examples/ffi-example + cmake -DCMAKE_BUILD_TYPE=Debug -B ./build + cmake --build ./build + cd ../.. cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build cmake --build ./build ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' @@ -54,6 +58,10 @@ jobs: - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP) run: | + cd examples/ffi-example + cmake -DCMAKE_BUILD_TYPE=Debug -B ./build + cmake --build ./build + cd ../.. cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build cmake --build ./build ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' @@ -76,6 +84,10 @@ jobs: - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP) run: | + cd examples\ffi-example + cmake -DCMAKE_BUILD_TYPE=Debug -B .\build + cmake --build ./build --config Debug + cd ..\.. cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_SYSTEM_VERSION="10.0.18362.0" -DCICD=1 -DDISABLE_HTTP=1 -B build cmake --build build Debug\dictu.exe tests/runTests.du ci diff --git a/.gitignore b/.gitignore index 67cd9090..9c22668e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ \.idea/ +.cache build/ \.DS_Store dictu diff --git a/examples/ffi-example/src/lib.c b/examples/ffi-example/src/lib.c index 7144927a..17cac396 100644 --- a/examples/ffi-example/src/lib.c +++ b/examples/ffi-example/src/lib.c @@ -12,9 +12,17 @@ Value dictu_ffi_test_str(DictuVM *vm, int argCount, Value *args) { return OBJ_VAL(copyString(vm, "Hello From Dictu FFI module!", 28)); } +Value dictu_ffi_test_callback(DictuVM *vm, int argCount, Value *args) { + if(argCount < 1) + return NIL_VAL; + Value* funcs = args+1; + return callFunction(vm, args[0], argCount-1, funcs); +} + int dictu_ffi_init(DictuVM *vm, Table *method_table) { defineNative(vm, method_table, "dictuFFITestAdd", dictu_ffi_test); defineNative(vm, method_table, "dictuFFITestStr", dictu_ffi_test_str); + defineNative(vm, method_table, "dictuFFITestCallback", dictu_ffi_test_callback); defineNativeProperty( vm, method_table, "test", OBJ_VAL(copyString(vm, "Dictu!", 6))); diff --git a/src/include/dictu_ffi_include.h b/src/include/dictu_ffi_include.h index abefa144..7763834b 100644 --- a/src/include/dictu_ffi_include.h +++ b/src/include/dictu_ffi_include.h @@ -11,7 +11,7 @@ extern "C" { // This is used ti determine if we can safely load the function pointers without // UB. -#define FFI_MOD_API_VERSION 2 +#define FFI_MOD_API_VERSION 3 #define UNUSED(__x__) (void)__x__ @@ -343,7 +343,7 @@ struct _vm { }; #define DICTU_MAJOR_VERSION "0" -#define DICTU_MINOR_VERSION "29" +#define DICTU_MINOR_VERSION "30" #define DICTU_PATCH_VERSION "0" #define DICTU_STRING_VERSION \ @@ -364,6 +364,7 @@ struct sObjString { int length; char *chars; uint32_t hash; + int character_len; }; struct sObjList { @@ -550,6 +551,9 @@ typedef void defineNative_t(DictuVM *vm, Table *table, const char *name, typedef void defineNativeProperty_t(DictuVM *vm, Table *table, const char *name, Value value); + +typedef Value callFunction_t(DictuVM* vm, Value function, int argCount, Value* args); + reallocate_t * reallocate = NULL; copyString_t *copyString = NULL; @@ -616,6 +620,8 @@ defineNative_t *defineNative = NULL; defineNativeProperty_t *defineNativeProperty = NULL; +callFunction_t *callFunction = NULL; + // This needs to be implemented by the user and register all functions int dictu_ffi_init(DictuVM *vm, Table *method_table); @@ -665,6 +671,7 @@ int dictu_internal_ffi_init(void **function_ptrs, DictuVM *vm, defineNative = (defineNative_t *)function_ptrs[count++]; defineNativeProperty = (defineNativeProperty_t *)function_ptrs[count++]; reallocate = (reallocate_t *)function_ptrs[count++]; + callFunction = (callFunction_t *)function_ptrs[count++]; int initResult = dictu_ffi_init(vm, methodTable); if (initResult > 0) return 3 + initResult; diff --git a/src/optionals/ffi/ffi.c b/src/optionals/ffi/ffi.c index 90adec19..d9d00113 100644 --- a/src/optionals/ffi/ffi.c +++ b/src/optionals/ffi/ffi.c @@ -53,7 +53,8 @@ void *ffi_function_pointers[] = {©String, &compareStringGreater, &defineNative, &defineNativeProperty, - &reallocate}; + &reallocate, + &callFunction}; void freeFFI(DictuVM *vm, ObjAbstract *abstract) { FFIInstance *instance = (FFIInstance *)abstract->data; diff --git a/src/optionals/ffi/ffi.h b/src/optionals/ffi/ffi.h index 245ebcb6..7b1de964 100644 --- a/src/optionals/ffi/ffi.h +++ b/src/optionals/ffi/ffi.h @@ -19,7 +19,7 @@ // This is used to determine if we can safely load the function pointers without UB, // if this is greater then the version from the mod we error in the internal mod load function. -#define DICTU_FFI_API_VERSION 2 +#define DICTU_FFI_API_VERSION 3 Value createFFIModule(DictuVM *vm); diff --git a/src/vm/vm.c b/src/vm/vm.c index 3caef3d8..d11ae39c 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -27,6 +27,7 @@ #include "datatypes/enums.h" #include "natives.h" #include "../optionals/optionals.h" +#include "value.h" static void resetStack(DictuVM *vm) { vm->stackTop = vm->stack; @@ -58,6 +59,10 @@ void runtimeError(DictuVM *vm, const char *format, ...) { for (int i = vm->frameCount - 1; i >= 0; i--) { CallFrame *frame = &vm->frames[i]; + if(frame->closure == NULL) { + // synthetic frame created by callFunction + continue; + } ObjFunction *function = frame->closure->function; // -1 because the IP is sitting on the next instruction to be @@ -866,7 +871,9 @@ static void copyAnnotations(DictuVM *vm, ObjDict *superAnnotations, ObjDict *kla } } -static DictuInterpretResult run(DictuVM *vm) { + + +static DictuInterpretResult runWithBreakFrame(DictuVM *vm, int breakFrame) { CallFrame *frame = &vm->frames[vm->frameCount - 1]; register uint8_t* ip = frame->ip; @@ -2270,6 +2277,10 @@ static DictuInterpretResult run(DictuVM *vm) { frame = &vm->frames[vm->frameCount - 1]; ip = frame->ip; + if (breakFrame != -1 && vm->frameCount == breakFrame) { + return INTERPRET_OK; + } + DISPATCH(); } @@ -2435,6 +2446,9 @@ static DictuInterpretResult run(DictuVM *vm) { return INTERPRET_RUNTIME_ERROR; } +static DictuInterpretResult run(DictuVM *vm) { + return runWithBreakFrame(vm, -1); +} DictuInterpretResult dictuInterpret(DictuVM *vm, char *moduleName, char *source) { ObjString *name = copyString(vm, moduleName, strlen(moduleName)); @@ -2457,3 +2471,37 @@ DictuInterpretResult dictuInterpret(DictuVM *vm, char *moduleName, char *source) return result; } +Value callFunction(DictuVM* vm, Value function, int argCount, Value* args) { + if(!IS_FUNCTION(function) && !IS_CLOSURE(function)){ + if(IS_NATIVE(function)) { + NativeFn native = AS_NATIVE(function); + return native(vm, argCount, args); + } + runtimeError(vm, "Value passed to callFunction is not callable"); + return EMPTY_VAL; + } + int currentFrameCount = vm->frameCount; + Value* currentStack = vm->stackTop; + if (vm->frameCount == vm->frameCapacity) { + int oldCapacity = vm->frameCapacity; + vm->frameCapacity = GROW_CAPACITY(vm->frameCapacity); + vm->frames = GROW_ARRAY(vm, vm->frames, CallFrame, + oldCapacity, vm->frameCapacity); + } + CallFrame *frame = &vm->frames[vm->frameCount++]; + uint8_t code[4] = {OP_CALL, argCount, 0, OP_RETURN}; + frame->ip = code; + frame->closure = NULL; + push(vm, function); + for(int i = 0; i < argCount; i++) { + push(vm, args[i]); + } + DictuInterpretResult result = runWithBreakFrame(vm, currentFrameCount+1); + if(result != INTERPRET_OK) { + exit(70); + } + Value v = pop(vm); + vm->stackTop = currentStack; + vm->frameCount--; + return v; +} diff --git a/src/vm/vm.h b/src/vm/vm.h index 9bc63e2f..74e7cb36 100644 --- a/src/vm/vm.h +++ b/src/vm/vm.h @@ -69,4 +69,6 @@ bool isFalsey(Value value); ObjClosure *compileModuleToClosure(DictuVM *vm, char *name, char *source); +Value callFunction(DictuVM* vm, Value function, int argCount, Value* args); + #endif diff --git a/tests/ffi/ffi.du b/tests/ffi/ffi.du index a072f0d4..a59de0db 100644 --- a/tests/ffi/ffi.du +++ b/tests/ffi/ffi.du @@ -38,18 +38,49 @@ import FFI; import Path; class TestFFIModule < UnitTest { + var mod = nil; + setUp() { + if(this.mod != nil) + return; + const parts = ["examples", "ffi-example", "build"]; + if(System.platform == "windows"){ + parts.push("Debug"); + parts.push("ffi-test.dll"); + } else { + parts.push("libffi-test{}".format(FFI.suffix)); + } + + const path = Path.join(parts); + this.mod = FFI.load(path); + } testFFIModule() { - const path = Path.join(Path.dirname(__file__), "libs", "test-lib{}{}".format( - System.platform == "darwin" ? System.arch == "x86_64" ? "_64" : "_arm" : "", - FFI.suffix - )); - const mod = FFI.load(path); + const mod = this.mod; this.assertEquals(mod.test, "Dictu!"); for(var i = 0; i < 40; i+=2) { this.assertEquals(mod.dictuFFITestAdd(i, i), i+i); } this.assertEquals(mod.dictuFFITestStr(), "Hello From Dictu FFI module!"); } + testFFIModuleCallback() { + const mod = this.mod; + var count = 0; + const list = []; + def callback(value){ + count += value; + list.push(value); + return value * value; + } + def greet(name) { + return "Hello, {}!".format(name); + } + for(var i = 0; i < 5; i+=1){ + this.assertEquals(mod.dictuFFITestCallback(callback, i * i), i * i * i * i); + } + // 1, 4, 9, 16 + this.assertEquals(list, [0,1,4,9,16]); + this.assertEquals(count, 1 + 4 + 9 + 16); + this.assertEquals(mod.dictuFFITestCallback(greet, "World"), "Hello, World!"); + } } TestFFIModule().run(); diff --git a/tests/ffi/libs/test-lib.dll b/tests/ffi/libs/test-lib.dll deleted file mode 100644 index e1f14b94..00000000 Binary files a/tests/ffi/libs/test-lib.dll and /dev/null differ diff --git a/tests/ffi/libs/test-lib.so b/tests/ffi/libs/test-lib.so deleted file mode 100755 index cce02b91..00000000 Binary files a/tests/ffi/libs/test-lib.so and /dev/null differ diff --git a/tests/ffi/libs/test-lib_64.dylib b/tests/ffi/libs/test-lib_64.dylib deleted file mode 100755 index 84887d6b..00000000 Binary files a/tests/ffi/libs/test-lib_64.dylib and /dev/null differ diff --git a/tests/ffi/libs/test-lib_arm.dylib b/tests/ffi/libs/test-lib_arm.dylib deleted file mode 100755 index 082eceff..00000000 Binary files a/tests/ffi/libs/test-lib_arm.dylib and /dev/null differ