diff --git a/.clangd b/.clangd index ce76e8276..b8e698ade 100644 --- a/.clangd +++ b/.clangd @@ -1,4 +1,4 @@ CompileFlags: - Add: [-DUMKA_BUILD -DUMKA_EXT_LIBS] + Add: [-DUMKA_BUILD -DUMKA_EXT_LIBS -DUMKA_FFI -I../libffi/build/include] InlayHints: Enabled: No \ No newline at end of file diff --git a/.github/workflows/main-ffi.yml b/.github/workflows/main-ffi.yml new file mode 100644 index 000000000..419d10cd8 --- /dev/null +++ b/.github/workflows/main-ffi.yml @@ -0,0 +1,42 @@ +# This is a basic workflow to help you get started with Actions + +name: CI (FFI) + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ master ] + pull_request: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ macos-latest, ubuntu-latest ] + + # The operating system of the runner the job will run on + runs-on: ${{ matrix.os }} + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + with: + submodules: true + + # prepare system for libffi build + - name: install dependencies + run: libffi/.ci/install.sh + + # Build it + - name: build + run: make all + env: + UMKA_LIBFFI: 1 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..51b2466ec --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libffi"] + path = libffi + url = https://github.com/libffi/libffi diff --git a/Makefile b/Makefile index 5d0c411b1..7488a24bd 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ BINDIR ?= $(PREFIX)/bin # platform specific settings: ifeq ($(PLATFORM), Linux) - LDFLAGS = -lm -ldl + LDFLAGS = -lm -ldl $(LIBFFI_LD) RANLIB = ar -crs LIBEXT = so DYNAMIC_CFLAGS_EXTRA = -shared -fvisibility=hidden @@ -29,12 +29,28 @@ else ifneq ($(findstring MINGW64_NT,$(PLATFORM)),) DYNAMIC_CFLAGS_EXTRA = -shared -fvisibility=hidden endif +ifeq ($(UMKA_LIBFFI),) + LIBFFI_LIBS = + LIBFFI_LIB = + LIBFFI_LD = + LIBFFI_STATIC = + LIBFFI_INCLUDE = +else + LIBFFI_LIBS = ./libffi/build/.libs + LIBFFI_LIB = libffi.a + LIBFFI_LD = -L$(LIBFFI_LIBS) -l:$(LIBFFI_LIB) + LIBFFI_STATIC = $(LIBFFI_LIBS)/$(LIBFFI_LIB) + LIBFFI_INCLUDE = ./libffi/build/include + LIBFFI_CFLAGS = -I$(LIBFFI_INCLUDE) -DUMKA_FFI +endif + # identical for all platforms: UMKA_LIB_STATIC = $(BUILD_PATH)/libumka.a UMKA_LIB_DYNAMIC = $(BUILD_PATH)/libumka.$(LIBEXT) UMKA_EXE = $(BUILD_PATH)/umka -CFLAGS = -s -fPIC -O3 -Wall -Wno-format-security -malign-double -fno-strict-aliasing -DUMKA_EXT_LIBS +#CFLAGS = -s -fPIC -O3 -Wall -Wno-format-security -malign-double -fno-strict-aliasing -DUMKA_EXT_LIBS +CFLAGS = -fPIC -ggdb -Wall -Wno-format-security -malign-double -fno-strict-aliasing -DUMKA_EXT_LIBS $(LIBFFI_CFLAGS) STATIC_CFLAGS = $(CFLAGS) -DUMKA_STATIC DYNAMIC_CFLAGS = $(CFLAGS) -DUMKA_BUILD $(DYNAMIC_CFLAGS_EXTRA) @@ -80,29 +96,48 @@ uninstall: @rm -f -- $(DESTDIR)$(INCLUDEDIR)/umka_api.h @echo "Uninstallation complete!" -$(UMKA_LIB_STATIC): $(OBJS_STATIC) +$(UMKA_LIB_STATIC): $(OBJS_STATIC) $(LIBFFI_STATIC) @echo AR $@ @mkdir -p -- $(BUILD_PATH)/include/ @$(RANLIB) $(UMKA_LIB_STATIC) $^ @cp $(APIS) $(BUILD_PATH)/include/ -$(UMKA_LIB_DYNAMIC): $(OBJS_DYNAMIC) +$(UMKA_LIB_DYNAMIC): $(OBJS_DYNAMIC) $(LIBFFI_STATIC) @echo LD $@ @mkdir -p -- $(BUILD_PATH)/include/ @$(CC) $(DYNAMIC_CFLAGS) -o $(UMKA_LIB_DYNAMIC) $^ $(LDFLAGS) @cp $(APIS) $(BUILD_PATH)/include/ -$(UMKA_EXE): $(OBJS_EXE) $(UMKA_LIB_STATIC) +$(UMKA_EXE): $(OBJS_EXE) $(UMKA_LIB_STATIC) $(LIBFFI_STATIC) @echo LD $@ @mkdir -p -- $(dir $@) @$(CC) $(STATIC_CFLAGS) -o $(UMKA_EXE) $^ $(LDFLAGS) -$(OBJ_PATH)/%_static.o: src/%.c +$(OBJ_PATH)/%_static.o: src/%.c $(LIBFFI_INCLUDE) @echo CC $@ @mkdir -p -- $(dir $@) @$(CC) $(STATIC_CFLAGS) -o $@ -c $^ -$(OBJ_PATH)/%_dynamic.o: src/%.c +$(OBJ_PATH)/%_dynamic.o: src/%.c $(LIBFFI_INCLUDE) @echo CC $@ @mkdir -p -- $(dir $@) @$(CC) $(DYNAMIC_CFLAGS) -o $@ -c $^ + +.PHONY: libffi/clean +libffi/clean: + rm -rf libffi/build + +libffi/build/.libs/libffi.a: libffi/build/Makefile + @cd libffi/build && make + +libffi/build/include: libffi/build/Makefile +libffi/build/Makefile: libffi/configure + @mkdir -p -- libffi/build/ + @cd libffi/build && CC=-fPIC ../configure --enable-pic + +libffi/configure: libffi/autogen.sh + @cd libffi && ./autogen.sh + +libffi/autogen.sh: + @git submodule init + @git submodule update diff --git a/libffi b/libffi new file mode 160000 index 000000000..e2eda0cf7 --- /dev/null +++ b/libffi @@ -0,0 +1 @@ +Subproject commit e2eda0cf72a0598b44278cc91860ea402273fa29 diff --git a/src/umka_common.c b/src/umka_common.c index 321330f99..60eec4eff 100644 --- a/src/umka_common.c +++ b/src/umka_common.c @@ -376,7 +376,6 @@ void moduleAddSource(Modules *modules, const char *path, const char *source, boo modules->moduleSource[modules->numModuleSources++] = moduleSource; } - void *moduleGetImplLibFunc(const Module *module, const char *name) { if (module->implLib) diff --git a/src/umka_decl.c b/src/umka_decl.c index cd694a495..087ea0f32 100644 --- a/src/umka_decl.c +++ b/src/umka_decl.c @@ -9,6 +9,9 @@ #include "umka_stmt.h" #include "umka_decl.h" +#ifdef UMKA_FFI +#include "umka_ffi.h" +#endif // UMKA_FFI static int parseModule(Umka *umka); @@ -742,6 +745,40 @@ void parseShortVarDecl(Umka *umka) } +#ifdef UMKA_FFI +// fnDecl = "extern fn" [rcvSignature] ident exportMark signature. +static void parseExternFnDecl(Umka *umka) +{ + if (umka->blocks.top != 0) + umka->error.handler(umka->error.context, "Extern functions can only be declared at top-level"); + + lexEat(&umka->lex, TOK_FFI); + lexEat(&umka->lex, TOK_FN); + Type *fnType = typeAdd(&umka->types, &umka->blocks, TYPE_FN); + + if (umka->lex.tok.kind == TOK_LPAR) + parseRcvSignature(umka, &fnType->sig); + + lexCheck(&umka->lex, TOK_IDENT); + IdentName name; + strcpy(name, umka->lex.tok.name); + + lexNext(&umka->lex); + bool exported = parseExportMark(umka); + + parseSignature(umka, &fnType->sig); + + Const constant = {.intVal = umka->gen.ip}; + Ident *fn = identAddExternFunc(&umka->idents, &umka->modules, &umka->blocks, name, fnType, exported, constant); + + if (umka->lex.tok.kind == TOK_LBRACE) + umka->error.handler(umka->error.context, "Extern functions can only be declared as prototype"); + else + parseFnPrototype(umka, fn); +} +#endif // UMKA_FFI + + // fnDecl = "fn" [rcvSignature] ident exportMark signature [block]. static void parseFnDecl(Umka *umka) { @@ -791,167 +828,170 @@ void parseDecl(Umka *umka) case TOK_CONST: parseConstDecl(umka); break; case TOK_VAR: parseFullVarDecl(umka); break; case TOK_IDENT: parseShortVarDecl(umka); break; +#ifdef UMKA_FFI + case TOK_FFI: parseExternFnDecl(umka); break; +#endif // UMKA_FFI case TOK_FN: parseFnDecl(umka); break; case TOK_EOF: if (umka->blocks.top == 0) - break; + break; - default: umka->error.handler(umka->error.context, "Declaration expected but %s found", lexSpelling(umka->lex.tok.kind)); break; - } + default: umka->error.handler(umka->error.context, "Declaration expected but %s found", lexSpelling(umka->lex.tok.kind)); break; +} } // decls = decl {";" decl}. static void parseDecls(Umka *umka) { - while (1) - { - parseDecl(umka); - if (umka->lex.tok.kind == TOK_EOF) - break; - lexEat(&umka->lex, TOK_SEMICOLON); - } +while (1) +{ + parseDecl(umka); + if (umka->lex.tok.kind == TOK_EOF) + break; + lexEat(&umka->lex, TOK_SEMICOLON); +} } // importItem = [ident "="] stringLiteral. static void parseImportItem(Umka *umka) { - char *alias = NULL; - if (umka->lex.tok.kind == TOK_IDENT) - { - alias = storageAdd(&umka->storage, DEFAULT_STR_LEN + 1); - strcpy(alias, umka->lex.tok.name); - lexNext(&umka->lex); - lexEat(&umka->lex, TOK_EQ); - } +char *alias = NULL; +if (umka->lex.tok.kind == TOK_IDENT) +{ + alias = storageAdd(&umka->storage, DEFAULT_STR_LEN + 1); + strcpy(alias, umka->lex.tok.name); + lexNext(&umka->lex); + lexEat(&umka->lex, TOK_EQ); +} - lexCheck(&umka->lex, TOK_STRLITERAL); +lexCheck(&umka->lex, TOK_STRLITERAL); - char path[DEFAULT_STR_LEN + 1] = ""; +char path[DEFAULT_STR_LEN + 1] = ""; - // Module source strings, if any, have precedence over files - const char *sourceString = NULL; - bool sourceTrusted = false; +// Module source strings, if any, have precedence over files +const char *sourceString = NULL; +bool sourceTrusted = false; - if (moduleRegularizePath(&umka->modules, umka->lex.tok.strVal, umka->modules.curFolder, path, DEFAULT_STR_LEN + 1)) +if (moduleRegularizePath(&umka->modules, umka->lex.tok.strVal, umka->modules.curFolder, path, DEFAULT_STR_LEN + 1)) +{ + const ModuleSource *sourceDesc = moduleFindSource(&umka->modules, path); + if (sourceDesc) { - const ModuleSource *sourceDesc = moduleFindSource(&umka->modules, path); - if (sourceDesc) - { - sourceString = sourceDesc->source; - sourceTrusted = sourceDesc->trusted; - } + sourceString = sourceDesc->source; + sourceTrusted = sourceDesc->trusted; } +} - if (!sourceString) - moduleAssertRegularizePath(&umka->modules, umka->lex.tok.strVal, umka->modules.module[umka->blocks.module]->folder, path, DEFAULT_STR_LEN + 1); +if (!sourceString) + moduleAssertRegularizePath(&umka->modules, umka->lex.tok.strVal, umka->modules.module[umka->blocks.module]->folder, path, DEFAULT_STR_LEN + 1); - char folder[DEFAULT_STR_LEN + 1] = ""; - char name [DEFAULT_STR_LEN + 1] = ""; +char folder[DEFAULT_STR_LEN + 1] = ""; +char name [DEFAULT_STR_LEN + 1] = ""; - moduleNameFromPath(&umka->modules, path, folder, name, DEFAULT_STR_LEN + 1); +moduleNameFromPath(&umka->modules, path, folder, name, DEFAULT_STR_LEN + 1); - if (!alias) - { - alias = storageAdd(&umka->storage, DEFAULT_STR_LEN + 1); - strcpy(alias, name); - } +if (!alias) +{ + alias = storageAdd(&umka->storage, DEFAULT_STR_LEN + 1); + strcpy(alias, name); +} - if (moduleFindImported(&umka->modules, &umka->blocks, alias) >= 0) - umka->modules.error->handler(umka->modules.error->context, "Duplicate imported module %s", alias); +if (moduleFindImported(&umka->modules, &umka->blocks, alias) >= 0) + umka->modules.error->handler(umka->modules.error->context, "Duplicate imported module %s", alias); - int importedModule = moduleFind(&umka->modules, path); - if (importedModule < 0) - { - // Save context - int currentModule = umka->blocks.module; - DebugInfo currentDebug = umka->debug; - Lexer currentLex = umka->lex; - lexInit(&umka->lex, &umka->storage, &umka->debug, path, sourceString, sourceTrusted, &umka->error); +int importedModule = moduleFind(&umka->modules, path); +if (importedModule < 0) +{ + // Save context + int currentModule = umka->blocks.module; + DebugInfo currentDebug = umka->debug; + Lexer currentLex = umka->lex; + lexInit(&umka->lex, &umka->storage, &umka->debug, path, sourceString, sourceTrusted, &umka->error); - lexNext(&umka->lex); - importedModule = parseModule(umka); + lexNext(&umka->lex); + importedModule = parseModule(umka); - // Restore context - lexFree(&umka->lex); - umka->lex = currentLex; - umka->debug = currentDebug; - umka->blocks.module = currentModule; - } + // Restore context + lexFree(&umka->lex); + umka->lex = currentLex; + umka->debug = currentDebug; + umka->blocks.module = currentModule; +} - // Imported module is registered but its body has not been compiled yet - this is only possible if it's imported in a cycle - if (!umka->modules.module[importedModule]->isCompiled) - umka->modules.error->handler(umka->modules.error->context, "Cyclic import of module %s", alias); +// Imported module is registered but its body has not been compiled yet - this is only possible if it's imported in a cycle +if (!umka->modules.module[importedModule]->isCompiled) + umka->modules.error->handler(umka->modules.error->context, "Cyclic import of module %s", alias); - // Module is imported iff it has an import alias (which may coincide with the module name if not specified explicitly) - char **importAlias = &umka->modules.module[umka->blocks.module]->importAlias[importedModule]; - if (*importAlias) - umka->modules.error->handler(umka->modules.error->context, "Duplicate imported module %s", path); - *importAlias = alias; +// Module is imported iff it has an import alias (which may coincide with the module name if not specified explicitly) +char **importAlias = &umka->modules.module[umka->blocks.module]->importAlias[importedModule]; +if (*importAlias) + umka->modules.error->handler(umka->modules.error->context, "Duplicate imported module %s", path); +*importAlias = alias; - identAddModule(&umka->idents, &umka->modules, &umka->blocks, alias, umka->voidType, importedModule); +identAddModule(&umka->idents, &umka->modules, &umka->blocks, alias, umka->voidType, importedModule); - lexNext(&umka->lex); +lexNext(&umka->lex); } // import = "import" (importItem | "(" {importItem ";"} ")"). static void parseImport(Umka *umka) { - lexEat(&umka->lex, TOK_IMPORT); +lexEat(&umka->lex, TOK_IMPORT); - if (umka->lex.tok.kind == TOK_LPAR) +if (umka->lex.tok.kind == TOK_LPAR) +{ + lexNext(&umka->lex); + while (umka->lex.tok.kind == TOK_STRLITERAL || umka->lex.tok.kind == TOK_IDENT) { - lexNext(&umka->lex); - while (umka->lex.tok.kind == TOK_STRLITERAL || umka->lex.tok.kind == TOK_IDENT) - { - parseImportItem(umka); - lexEat(&umka->lex, TOK_SEMICOLON); - } - lexEat(&umka->lex, TOK_RPAR); - } - else parseImportItem(umka); + lexEat(&umka->lex, TOK_SEMICOLON); + } + lexEat(&umka->lex, TOK_RPAR); +} +else + parseImportItem(umka); } // module = [import ";"] decls. static int parseModule(Umka *umka) { - umka->blocks.module = moduleAdd(&umka->modules, umka->lex.fileName); +umka->blocks.module = moduleAdd(&umka->modules, umka->lex.fileName); - if (umka->lex.tok.kind == TOK_IMPORT) - { - parseImport(umka); - lexEat(&umka->lex, TOK_SEMICOLON); - } - parseDecls(umka); - doResolveExtern(umka); +if (umka->lex.tok.kind == TOK_IMPORT) +{ + parseImport(umka); + lexEat(&umka->lex, TOK_SEMICOLON); +} +parseDecls(umka); +doResolveExtern(umka); - umka->modules.module[umka->blocks.module]->isCompiled = true; - return umka->blocks.module; +umka->modules.module[umka->blocks.module]->isCompiled = true; +return umka->blocks.module; } // program = module. void parseProgram(Umka *umka) { - genNop(&umka->gen); // Cleanup code jump stub +genNop(&umka->gen); // Cleanup code jump stub - lexNext(&umka->lex); - const int mainModule = parseModule(umka); +lexNext(&umka->lex); +const int mainModule = parseModule(umka); - const Ident *mainIdent = identFind(&umka->idents, &umka->modules, &umka->blocks, mainModule, "main", NULL, false); - if (mainIdent) - { - if (!identIsMain(mainIdent)) - umka->error.handler(umka->error.context, "Identifier main must be fn main()"); +const Ident *mainIdent = identFind(&umka->idents, &umka->modules, &umka->blocks, mainModule, "main", NULL, false); +if (mainIdent) +{ + if (!identIsMain(mainIdent)) + umka->error.handler(umka->error.context, "Identifier main must be fn main()"); - compilerMakeFuncContext(umka, mainIdent->type, mainIdent->offset, &umka->mainFn); - } + compilerMakeFuncContext(umka, mainIdent->type, mainIdent->offset, &umka->mainFn); +} - genEntryPoint(&umka->gen, JUMP_TO_CLEANUP); // Cleanup code jump - doGarbageCollection(umka); - genHalt(&umka->gen); +genEntryPoint(&umka->gen, JUMP_TO_CLEANUP); // Cleanup code jump +doGarbageCollection(umka); +genHalt(&umka->gen); } diff --git a/src/umka_expr.c b/src/umka_expr.c index d95626c01..62c5907d1 100644 --- a/src/umka_expr.c +++ b/src/umka_expr.c @@ -1814,6 +1814,9 @@ static void parsePrimary(Umka *umka, const Ident *ident, const Type **type, Cons switch (ident->kind) { case IDENT_CONST: +#ifdef UMKA_FFI + case IDENT_FFI_FN: +#endif { if (constant) *constant = ident->constant; @@ -2548,7 +2551,7 @@ static void parseCallSelector(Umka *umka, const Type **type, bool *isVar, bool * // Implicit dereferencing: f^(x) == f(x) doTryImplicitDeref(umka, type); - if ((*type)->kind == TYPE_PTR && ((*type)->base->kind == TYPE_FN || (*type)->base->kind == TYPE_CLOSURE)) + if ((*type)->kind == TYPE_PTR && ((*type)->base->kind == TYPE_FN ||(*type)->base->kind == TYPE_CLOSURE)) { genDeref(&umka->gen, (*type)->base->kind); *type = (*type)->base; diff --git a/src/umka_ffi.c b/src/umka_ffi.c new file mode 100644 index 000000000..722bdd025 --- /dev/null +++ b/src/umka_ffi.c @@ -0,0 +1,153 @@ +#ifdef UMKA_FFI + +#include "umka_ffi.h" +#include "umka_common.h" +#include "umka_compiler.h" +#include "umka_types.h" + + +static FfiStructs ffiStructs = {0}; + +ffi_type* appendFfiStructs(Umka *umka, const Type *type) { + if (ffiStructs.size >= ffiStructs.capacity) { + if (ffiStructs.capacity == 0) { + ffiStructs.capacity = sizeof(FfiStructs) * 16; + ffiStructs.items = storageAdd(&umka->storage, ffiStructs.capacity); + } else { + storageRealloc(&umka->storage, ffiStructs.items, ffiStructs.capacity*2); + } + } + + ffi_type *struct_type = storageAdd(&umka->storage, sizeof(ffi_type)); + struct_type->type = FFI_TYPE_STRUCT; + struct_type->alignment = type->alignment; + struct_type->size = type->size; + + ffiStructs.items[ffiStructs.size++] = (FfiStruct){ .hash = type->typeIdent->hash, .type = struct_type }; + return struct_type; +} + +ffi_type* findFfiStruct(int hash) { + for (int i = 0; i < ffiStructs.size; i++) + if (ffiStructs.items[i].hash == hash) return ffiStructs.items[i].type; + return NULL; +} + +ffi_type *mapToFfiStruct(Umka *umka, const Type *type) { + ffi_type *struct_type = findFfiStruct(type->typeIdent->hash); + if (struct_type != NULL) + return struct_type; + + struct_type = appendFfiStructs(umka, type); + if (type->numItems > MAX_STRUCT_FIELDS) { + // todo make this a flag? + umka->error.handler(umka->error.context, + "Structs passed to dynamic fn cannot have more than " + "%d direct members.\n" + "You can increase this by setting `MAX_STRUCT_FIELDS` compiler definition." + , MAX_STRUCT_FIELDS); + } + + size_t fieldsSize = sizeof(ffi_type)*(type->numItems+1); // +1 for null termination + ffi_type **structFields = storageAdd(&umka->storage, fieldsSize); + memset(structFields, 0, fieldsSize); + struct_type->elements = structFields; + + for (int i = 0; i < type->numItems && i < MAX_STRUCT_FIELDS; i++) { + structFields[i] = mapToFfiType(umka, type->field[i]->type); + } + + return struct_type; +} + +ffi_type *mapToFfiType(Umka *umka,const struct tagType *type) { + switch (type->kind) { + // int types + case TYPE_INT8: + return &ffi_type_sint8; + case TYPE_INT16: + return &ffi_type_sint16; + case TYPE_INT32: + return &ffi_type_sint32; + case TYPE_INT: + return &ffi_type_sint64; + case TYPE_UINT8: + return &ffi_type_uint8; + case TYPE_UINT16: + return &ffi_type_uint16; + case TYPE_UINT32: + return &ffi_type_uint32; + case TYPE_UINT: + return &ffi_type_uint64; + case TYPE_CHAR: + return &ffi_type_uchar; + case TYPE_BOOL: + switch (sizeof(bool)) { + case 1: + return &ffi_type_uint8; + case 2: + return &ffi_type_uint16; + case 4: + return &ffi_type_uint32; + case 8: + return &ffi_type_uint64; + } + + // ptr types + case TYPE_STR: + case TYPE_NULL: + case TYPE_ARRAY: + case TYPE_PTR: + return &ffi_type_pointer; + + case TYPE_STRUCT: + return mapToFfiStruct(umka, type->typeIdent->type); + + // float types + case TYPE_REAL32: + return &ffi_type_float; + break; + case TYPE_REAL: + return &ffi_type_double; + break; + + // skip + case TYPE_INTERFACE: + return NULL; + + case TYPE_VOID: + return &ffi_type_void; + + // not supported + case TYPE_WEAKPTR: + case TYPE_DYNARRAY: + case TYPE_MAP: + case TYPE_NONE: + case TYPE_FORWARD: + case TYPE_CLOSURE: + case TYPE_FIBER: + case TYPE_FN: + umka->error.handler( + umka->error.context, + "Type `%s` is unsupported in ffi function declarations", + typeKindSpelling(type->kind)); + } + return NULL; +} + +int assignFfiTypes(Umka *umka, ffi_type **types, const Signature *sig) +{ + int numArgs = 0; + for(int i = 0; i < sig->numParams && i < 16; i++) { + const Param *param = sig->param[i]; + ffi_type *type = mapToFfiType(umka, param->type); + + if (strcmp(param->name, "#upvalues") == 0) continue; + if (strcmp(param->name, "#result") == 0) continue; + + types[numArgs++] = type; + } + return numArgs; +} + +#endif // UMKA_FFI diff --git a/src/umka_ffi.h b/src/umka_ffi.h new file mode 100644 index 000000000..106eea552 --- /dev/null +++ b/src/umka_ffi.h @@ -0,0 +1,49 @@ +#ifndef UMKA_FFI_H_ +#define UMKA_FFI_H_ + +#include "ffi.h" + +#include "umka_common.h" +#include "umka_types.h" + +#ifndef MAX_STRUCT_FIELDS +#define MAX_STRUCT_FIELDS (64) +#endif + +#ifdef UMKA_VM_DEBUG + #define FORCE_INLINE + #define UNLIKELY(x) (x) +#else + #ifdef _MSC_VER // MSVC++ only + #define FORCE_INLINE __forceinline + #define UNLIKELY(x) (x) + #else + #define FORCE_INLINE __attribute__((always_inline)) inline + #define UNLIKELY(x) __builtin_expect(!!(x), 0) + #endif +#endif + + +typedef struct { + int hash; + ffi_type *type; +} FfiStruct; + + +typedef struct { + FfiStruct* items; + size_t size; + size_t capacity; +} FfiStructs; + +typedef struct +{ + void *entry; + ffi_cif cif; +} DynamicCall; + + +ffi_type* mapToFfiType (Umka *umka,const struct tagType *type); +int assignFfiTypes (Umka *umka, ffi_type **types, const Signature *sig); + +#endif // UMKA_FFI_H_ diff --git a/src/umka_gen.c b/src/umka_gen.c index 4ccc7c5f7..99fc54cb6 100644 --- a/src/umka_gen.c +++ b/src/umka_gen.c @@ -6,6 +6,10 @@ #include "umka_gen.h" #include "umka_const.h" +#ifdef UMKA_FFI +#include "umka_ffi.h" +#endif // UMKA_FFI + // Common functions @@ -800,6 +804,15 @@ void genCallIndirect(CodeGen *gen, int paramSlots) } +#ifdef UMKA_FFI +void genCallExternFfi(CodeGen *gen, DynamicCall *dynamicCall) +{ + const Instruction instr = {.opcode = OP_CALL_EXTERN_FFI, .tokKind = TOK_NONE, .typeKind = TYPE_NONE, .operand.ptrVal = dynamicCall}; + genAddInstr(gen, &instr); +} +#endif // UMKA_FFI + + void genCallExtern(CodeGen *gen, void *entry) { const Instruction instr = {.opcode = OP_CALL_EXTERN, .tokKind = TOK_NONE, .typeKind = TYPE_NONE, .operand.ptrVal = entry}; diff --git a/src/umka_gen.h b/src/umka_gen.h index fc98387b9..e21403687 100644 --- a/src/umka_gen.h +++ b/src/umka_gen.h @@ -4,6 +4,9 @@ #include "umka_common.h" #include "umka_vm.h" +#ifdef UMKA_FFI +#include "umka_ffi.h" +#endif typedef struct { @@ -99,6 +102,9 @@ void genGotoIfNot (CodeGen *gen, int dest); void genCall (CodeGen *gen, int entry); void genCallIndirect (CodeGen *gen, int paramSlots); +#ifdef UMKA_FFI +void genCallExternFfi (CodeGen *gen, DynamicCall *dynamicCall); +#endif void genCallExtern (CodeGen *gen, void *entry); void genCallBuiltin (CodeGen *gen, TypeKind typeKind, BuiltinFunc builtin); void genCallTypedBuiltin (CodeGen *gen, const Type *type, BuiltinFunc builtin); diff --git a/src/umka_ident.c b/src/umka_ident.c index b96898887..34ab0f8e7 100644 --- a/src/umka_ident.c +++ b/src/umka_ident.c @@ -261,6 +261,18 @@ Ident *identAddBuiltinFunc(Idents *idents, const Modules *modules, const Blocks } +#ifdef UMKA_FFI +Ident *identAddExternFunc(Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, bool exported, Const constant) +{ + Ident *ident = identAdd(idents, modules, blocks, IDENT_FFI_FN, name, type, false); + ident->exported = exported; + ident->ffi = true; + ident->constant = constant; + return ident; +} +#endif // UMKA_FFI + + Ident *identAddModule(Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, int moduleVal) { Ident *ident = identAdd(idents, modules, blocks, IDENT_MODULE, name, type, false); diff --git a/src/umka_ident.h b/src/umka_ident.h index ddcf94c86..8e34647a2 100644 --- a/src/umka_ident.h +++ b/src/umka_ident.h @@ -12,6 +12,9 @@ typedef enum IDENT_VAR, IDENT_TYPE, IDENT_BUILTIN_FN, +#ifdef UMKA_FFI + IDENT_FFI_FN, +#endif IDENT_MODULE } IdentKind; @@ -24,6 +27,9 @@ typedef struct tagIdent const Type *type; int module, block; // Place of definition (global identifiers are in block 0) bool exported, globallyAllocated, used, temporary; +#ifdef UMKA_FFI + bool ffi; +#endif int prototypeOffset; // For function prototypes union { @@ -64,6 +70,9 @@ Ident *identAddTempConst (Idents *idents, const Modules *modules, const Blocks Ident *identAddGlobalVar (Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, bool exported, void *ptr); Ident *identAddLocalVar (Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, bool exported, int offset); Ident *identAddType (Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, bool exported); +#ifdef UMKA_FFI +Ident *identAddExternFunc(Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, bool exported, Const constant); +#endif Ident *identAddBuiltinFunc(Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, BuiltinFunc builtin); Ident *identAddModule (Idents *idents, const Modules *modules, const Blocks *blocks, const char *name, const Type *type, int moduleVal); diff --git a/src/umka_lexer.c b/src/umka_lexer.c index 0c7e71273..f7658f9da 100644 --- a/src/umka_lexer.c +++ b/src/umka_lexer.c @@ -36,6 +36,9 @@ static const char *spelling [] = "switch", "type", "var", +#ifdef UMKA_FFI + "ffi", +#endif "weak", // Operators diff --git a/src/umka_lexer.h b/src/umka_lexer.h index 373b60c9a..6bda2c186 100644 --- a/src/umka_lexer.h +++ b/src/umka_lexer.h @@ -29,6 +29,9 @@ typedef enum TOK_SWITCH, TOK_TYPE, TOK_VAR, +#ifdef UMKA_FFI + TOK_FFI, +#endif TOK_WEAK, // Operators diff --git a/src/umka_stmt.c b/src/umka_stmt.c index b603dab4b..b108ddab3 100644 --- a/src/umka_stmt.c +++ b/src/umka_stmt.c @@ -1,12 +1,15 @@ #define __USE_MINGW_ANSI_STDIO 1 -#include #include #include "umka_stmt.h" #include "umka_expr.h" #include "umka_decl.h" +#ifdef UMKA_FFI +#include "umka_ffi.h" +#endif + static void parseStmtList(Umka *umka); static void parseBlock(Umka *umka); @@ -87,9 +90,9 @@ void doResolveExtern(Umka *umka) fn = external->entry; external->resolved = true; - } - else + } else { fn = moduleGetImplLibFunc(umka->modules.module[umka->blocks.module], ident->name); + } if (!fn) umka->error.handler(umka->error.context, "Unresolved prototype of %s", ident->name); @@ -102,7 +105,29 @@ void doResolveExtern(Umka *umka) for (int i = 0; i < ident->type->sig.numParams; i++) identAllocParam(&umka->idents, &umka->types, &umka->modules, &umka->blocks, &ident->type->sig, i); +#ifdef UMKA_FFI + if (ident->ffi) { + DynamicCall *dynamicCall = storageAdd(&umka->storage, sizeof(DynamicCall)); + dynamicCall->entry = fn; + + ffi_type **types = storageAdd(&umka->storage, sizeof(ffi_type)*16); + ffi_type *retType = mapToFfiType(umka, ident->type->sig.resultType); + int numArgs = assignFfiTypes(umka, types, &ident->type->sig); + + if (retType == NULL) + umka->error.handler(umka->error.context, "Unsupported return type %s for ffi function %s", typeKindSpelling(ident->type->kind), ident->name); + + ffi_status status = ffi_prep_cif(&dynamicCall->cif, FFI_DEFAULT_ABI, numArgs, retType, types); + if (status != FFI_OK) + umka->error.handler(umka->error.context, "Error creating ffi_cif for function %s: %d", ident->name, status); + + genCallExternFfi(&umka->gen, dynamicCall); + } else { + genCallExtern(&umka->gen, fn); + } +#else genCallExtern(&umka->gen, fn); +#endif // UMKA_FFI doGarbageCollection(umka); identWarnIfUnusedAll(&umka->idents, blocksCurrent(&umka->blocks)); @@ -120,7 +145,6 @@ void doResolveExtern(Umka *umka) } } - static bool doShortVarDeclLookahead(Umka *umka) { // ident {"," ident} ":=" diff --git a/src/umka_types.h b/src/umka_types.h index 8ca22979c..989259a75 100644 --- a/src/umka_types.h +++ b/src/umka_types.h @@ -37,7 +37,7 @@ typedef enum TYPE_INTERFACE, TYPE_CLOSURE, TYPE_FIBER, // Pointer of a special kind - TYPE_FN + TYPE_FN, } TypeKind; diff --git a/src/umka_vm.c b/src/umka_vm.c index 9ad988d1e..56d0b838d 100644 --- a/src/umka_vm.c +++ b/src/umka_vm.c @@ -30,6 +30,10 @@ #include "umka_vm.h" +#ifdef UMKA_FFI +#include "umka_ffi.h" +#endif + /* Virtual machine stack layout (64-bit slots): @@ -3545,6 +3549,42 @@ static FORCE_INLINE void doCallIndirect(Fiber *fiber, Error *error) } +#ifdef UMKA_FFI +static FORCE_INLINE void ffiEntry(UmkaStackSlot *params, UmkaStackSlot *result, const DynamicCall *dynamicCall) +{ + void* args[16] = {0}; + for (unsigned int i = 0; i < dynamicCall->cif.nargs; i++) { + args[i] = &umkaGetParam(params, i)->ptrVal; + } + + UmkaStackSlot *resultSlot = umkaGetResult(params, result); + void* retPtr = resultSlot; + if (dynamicCall->cif.rtype->type == FFI_TYPE_STRUCT) { + retPtr = resultSlot->ptrVal; + } + + ffi_call((ffi_cif *)&dynamicCall->cif, dynamicCall->entry, retPtr, args); + // workaround for real32 -> cast float to double + if (dynamicCall->cif.rtype->type == FFI_TYPE_FLOAT) { + resultSlot->realVal = resultSlot->real32Val; + } +} + +static FORCE_INLINE void doCallExternFfi(Fiber *fiber, Error *error) +{ + const DynamicCall *dynamicCall = fiber->code[fiber->ip].operand.ptrVal; + + fiber->reg[REG_RESULT].ptrVal = error->context; // Upon entry, the result slot stores the Umka instance + + const int ip = fiber->ip; + ffiEntry(&fiber->base[2].apiSlot, &fiber->reg[REG_RESULT].apiSlot, dynamicCall); + fiber->ip = ip; + + fiber->ip++; +} +#endif // UMKA_FFI + + static FORCE_INLINE void doCallExtern(Fiber *fiber, Error *error) { const UmkaExternFunc fn = (UmkaExternFunc)fiber->code[fiber->ip].operand.ptrVal; @@ -3796,6 +3836,9 @@ static void vmLoop(VM *vm) case OP_CALL: doCall(fiber, error); break; case OP_CALL_INDIRECT: doCallIndirect(fiber, error); break; case OP_CALL_EXTERN: doCallExtern(fiber, error); break; +#ifdef UMKA_FFI + case OP_CALL_EXTERN_FFI: doCallExternFfi(fiber, error); break; +#endif case OP_CALL_BUILTIN: { Fiber *newFiber = NULL; diff --git a/src/umka_vm.h b/src/umka_vm.h index 94999c9e5..c577b9c4c 100644 --- a/src/umka_vm.h +++ b/src/umka_vm.h @@ -85,6 +85,9 @@ typedef enum OP_CALL, OP_CALL_INDIRECT, OP_CALL_EXTERN, +#ifdef UMKA_FFI + OP_CALL_EXTERN_FFI, +#endif OP_CALL_BUILTIN, OP_RETURN, OP_ENTER_FRAME,