From 38d57cdce42115fac4eb48bb441ac31f0fd81a7a Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Wed, 6 May 2020 13:22:03 -0400 Subject: system v amd64 calling convention --- Makefile | 6 +- README.md | 10 +- build.sh | 22 +++-- foreign64.c | 172 +++++++++++++++++++++++++++++++++ foreign_avcall.c | 42 +------- foreign_msvc.c | 8 +- foreign_msvc32.c | 2 +- foreign_msvc64.c | 138 -------------------------- foreign_unix.c | 51 ++++++++++ main.c | 1 - systemv64call.asm | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++ systemv64call_test.c | 149 ++++++++++++++++++++++++++++ test.toc | 1 + toc.c | 6 +- 14 files changed, 675 insertions(+), 200 deletions(-) create mode 100644 foreign64.c delete mode 100644 foreign_msvc64.c create mode 100644 foreign_unix.c create mode 100644 systemv64call.asm create mode 100644 systemv64call_test.c diff --git a/Makefile b/Makefile index f568f2a..efc2fca 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -toc: *.[ch] +toc: *.[ch] build.sh ./build.sh -release: *.[ch] +release: *.[ch] build.sh ./build.sh release clean: - rm toc + rm toc *.o diff --git a/README.md b/README.md index b230bea..f6e3f57 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,14 @@ https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line . You can run `build32.bat release` or `build64.bat release` depending on whether you want a 32-bit or 64-bit version of toc. On other systems, you can just compile main.c with a C compiler. -By default, toc will use libdl and libffcall. If you don't have these installed, add the preprocessor define `COMPILE_TIME_FOREIGN_FN_SUPPORT=0`. -This is usually done with `-DCOMPILE_TIME_FOREIGN_FN_SUPPORT=0`. +By default, on Unix x86\_64 systems, you will need to link in an assembled version of `systemv64call.asm` (this is for foreign function support at compile time). +You can do this with: +``` +nasm -f elf64 systemv64call.asm +cc main.c systemv64call.o -o toc +``` +If you want to diable foreign function support at compile time, add your equivalent of `-DCOMPILE_TIME_FOREIGN_FN_SUPPORT=0` to the compile command. + `toc` uses several C99 and a couple of C11 features, so it might not work on all compilers. But it does compile on quite a few, including `clang`, `gcc`, `tcc`, and MSVC. It can also be compiled as if it were C++, so and `g++` can also compile it (it does rely on implicit casting of `void *` though). The *outputted* code should be C99-compliant. diff --git a/build.sh b/build.sh index 49b3728..06dc638 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,6 @@ #!/bin/sh +[ "$NASM" = "" ] && NASM=nasm + if [ "$CC" = "" ]; then if [ "$1" = "release" ]; then CC=clang @@ -27,12 +29,12 @@ else WARNINGS='' fi +[ "$ARCH" = "" ] && ARCH="$(uname -m)" + + if [ "$COMPILE_TIME_FOREIGN_FN_SUPPORT" != "no" ]; then - if uname | grep -qi bsd; then - LIBRARIES='-lavcall' - else - LIBRARIES='-ldl -lavcall' - fi + uname | grep -qi bsd || LIBRARIES="$LIBRARIES -ldl" + [ "$ARCH" = "x86_64" ] || LIBRARIES="$LIBRARIES -lavcall" ADDITIONAL_FLAGS="$ADDITIONAL_FLAGS $LIBRARIES" else ADDITIONAL_FLAGS="$ADDITIONAL_FLAGS -DCOMPILE_TIME_FOREIGN_FN_SUPPORT=0" @@ -53,6 +55,10 @@ else FLAGS="$DEBUG_FLAGS $ADDITIONAL_FLAGS" fi -COMMAND="$CC $FLAGS -o toc main.c" -echo $COMMAND -$COMMAND || exit 1 +c() { + echo "$1" && $1 || exit 1 +} + +c "$NASM -f elf64 systemv64call.asm" +c "$CC $FLAGS -o toc main.c systemv64call.o" + diff --git a/foreign64.c b/foreign64.c new file mode 100644 index 0000000..7b577d3 --- /dev/null +++ b/foreign64.c @@ -0,0 +1,172 @@ +/* + Copyright (C) 2019, 2020 Leo Tenenbaum. + This file is part of toc. toc is distributed under version 3 of the GNU General Public License, without any warranty whatsoever. + You should have received a copy of the GNU General Public License along with toc. If not, see . +*/ +#if SIZE_MAX != U64_MAX +#error "What's going on? The 64-bit #foreign file was included, but size_t isn't 64 bits!" +#endif + +#ifdef _WIN64 +extern U64 win64_call(FnPtr fn, U64 *args, I64 nargs); +extern float win64_callf(FnPtr fn, U64 *args, I64 nargs); +extern double win64_calld(FnPtr fn, U64 *args, I64 nargs); + +static inline U64 foreign_calli(FnPtr fn, U64 *args, I64 nargs, bool *is_float) { + (void)is_float; + return win64_call(fn, args, nargs); +} + +static inline float foreign_callf(FnPtr fn, U64 *args, I64 nargs, bool *is_float) { + (void)is_float; + return win64_callf(fn, args, nargs); +} + +static inline double foreign_calld(FnPtr fn, U64 *args, I64 nargs, bool *is_float) { + (void)is_float; + return win64_calld(fn, args, nargs); +} +#else +extern U64 systemv64_call(FnPtr fn, U64 *args, I64 nargs, bool *is_float); +extern float systemv64_callf(FnPtr fn, U64 *args, I64 nargs, bool *is_float); +extern double systemv64_calld(FnPtr fn, U64 *args, I64 nargs, bool *is_float); +static inline U64 foreign_calli(FnPtr fn, U64 *args, I64 nargs, bool *is_float) { + return systemv64_call(fn, args, nargs, is_float); +} +static inline float foreign_callf(FnPtr fn, U64 *args, I64 nargs, bool *is_float) { + return systemv64_callf(fn, args, nargs, is_float); +} +static inline double foreign_calld(FnPtr fn, U64 *args, I64 nargs, bool *is_float) { + return systemv64_calld(fn, args, nargs, is_float); +} +#endif + +static Status val_to_word(Value v, Type *t, Location where, U64 *w) { + switch (t->kind) { + case TYPE_BUILTIN: + switch (t->builtin) { + case BUILTIN_I8: *w = (U64)v.i8; break; + case BUILTIN_I16: *w = (U64)v.i16; break; + case BUILTIN_I32: *w = (U64)v.i32; break; + case BUILTIN_U8: *w = (U64)v.u8; break; + case BUILTIN_U16: *w = (U64)v.u16; break; + case BUILTIN_U32: *w = (U64)v.u32; break; + case BUILTIN_I64: *w = (U64)v.i64; break; + case BUILTIN_U64: *w = v.u64; break; + case BUILTIN_F32: *w = (U64)*(U32 *)&v.f32; break; + case BUILTIN_F64: *w = *(U64 *)&v.f64; break; + case BUILTIN_CHAR: *w = (U64)v.charv; break; + case BUILTIN_BOOL: *w = (U64)v.boolv; break; + case BUILTIN_TYPE: + case BUILTIN_VARARGS: + case BUILTIN_NMS: + case BUILTIN_VOID: + goto unsupported; + } + break; + case TYPE_PTR: + *w = (U64)v.ptr; break; + default: + unsupported: { + /* @TODO(eventually) */ + char *s = type_to_str(t); + err_print(where, "#foreign functions can't take arguments of type %s at compile time on Windows.", s); + free(s); + return false; + } + } + return true; +} + +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) { + possibly_static_assert(sizeof(double) == 8); /* if either of these assertions fails, you'll need to use libffcall */ + possibly_static_assert(sizeof(float) == 4); + FnPtr fn_ptr = foreign_get_fn_ptr(ffmgr, fn, call_where); + + /* @OPTIM: use alloca/_malloca if available */ + U64 *words = err_malloc(nargs * sizeof *words); + bool *is_float = err_malloc(nargs); + U64 *word = words; + char *type = (char *)arg_types; + for (size_t i = 0; i < nargs; ++i) { + if (!val_to_word(args[i], (Type *)type, call_where, word)) + return false; + is_float[i] = type_is_float((Type *)type); + type += arg_types_stride; + ++word; + } + int kind = 0; /* 0=>integer, 1=>f32, 2=>f64 */ + switch (ret_type->kind) { + case TYPE_BUILTIN: + switch (ret_type->builtin) { + case BUILTIN_I8: + case BUILTIN_I16: + case BUILTIN_I32: + case BUILTIN_I64: + case BUILTIN_U8: + case BUILTIN_U16: + case BUILTIN_U32: + case BUILTIN_U64: + case BUILTIN_BOOL: + case BUILTIN_CHAR: + case BUILTIN_VOID: + break; + case BUILTIN_F32: + kind = 1; + break; + case BUILTIN_F64: + kind = 2; + break; + default: + goto unsupported; + } + break; + case TYPE_PTR: + break; + default: + unsupported: { + char *s = type_to_str(ret_type); + /* @TODO(eventually) */ + err_print(call_where, "You can't call functions which return type %s at compile time on Windows.", s); + free(s); + return false; + } + } + + switch (kind) { + case 0: { + U64 r = foreign_calli(fn_ptr, words, (I64)nargs, is_float); + switch (ret_type->kind) { + case TYPE_BUILTIN: + switch (ret_type->builtin) { + case BUILTIN_I8: ret->i8 = (I8)r; break; + case BUILTIN_I16: ret->i16 = (I16)r; break; + case BUILTIN_I32: ret->i32 = (I32)r; break; + case BUILTIN_I64: ret->i64 = (I64)r; break; + case BUILTIN_U8: ret->u8 = (U8)r; break; + case BUILTIN_U16: ret->u16 = (U16)r; break; + case BUILTIN_U32: ret->u32 = (U32)r; break; + case BUILTIN_U64: ret->u64 = (U64)r; break; + case BUILTIN_BOOL: ret->boolv = (bool)r; break; + case BUILTIN_CHAR: ret->charv = (char)r; break; + case BUILTIN_VOID: (void)r; break; + default: assert(0); break; + } + break; + case TYPE_PTR: + ret->ptr = (void *)r; + break; + default: assert(0); break; + } + } break; + case 1: + ret->f32 = foreign_callf(fn_ptr, words, (I64)nargs, is_float); + break; + case 2: + ret->f64 = foreign_calld(fn_ptr, words, (I64)nargs, is_float); + break; + } + free(words); + free(is_float); + return true; +} diff --git a/foreign_avcall.c b/foreign_avcall.c index cce52a1..8c0fe40 100644 --- a/foreign_avcall.c +++ b/foreign_avcall.c @@ -5,10 +5,6 @@ */ /* 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 @@ -24,7 +20,6 @@ typedef struct { #pragma GCC diagnostic ignored "-Wsign-conversion" #endif #include -#include #if SCHAR_MAX != 127 @@ -295,42 +290,7 @@ static Status arg_list_add(av_alist *arg_list, Value val, Type *type, Location w #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; - } - + FnPtr fn_ptr = foreign_get_fn_ptr(ffmgr, fn, call_where); av_alist arg_list; if (!arg_list_start(&arg_list, fn_ptr, ret, ret_type, call_where)) return false; diff --git a/foreign_msvc.c b/foreign_msvc.c index 86f5519..6a8bb7f 100644 --- a/foreign_msvc.c +++ b/foreign_msvc.c @@ -9,7 +9,7 @@ typedef struct { HMODULE handle; } Library; -static FnPtr msvc_get_fn_ptr(ForeignFnManager *ffmgr, FnExpr *fn, Location call_where) { +static FnPtr foreign_get_fn_ptr(ForeignFnManager *ffmgr, FnExpr *fn, Location call_where) { FnPtr fn_ptr = fn->foreign.fn_ptr; if (!fn_ptr) { assert(fn->flags & FN_EXPR_FOREIGN); @@ -41,8 +41,10 @@ static FnPtr msvc_get_fn_ptr(ForeignFnManager *ffmgr, FnExpr *fn, Location call_ return fn_ptr; } -#ifdef _WIN64 -#include "foreign_msvc64.c" +#ifdef FOREIGN_USE_AVCALL +#include "foreign_avcall.c" +#elif defined _WIN64 +#include "foreign64.c" #else #include "foreign_msvc32.c" #endif diff --git a/foreign_msvc32.c b/foreign_msvc32.c index d2a116d..35b09db 100644 --- a/foreign_msvc32.c +++ b/foreign_msvc32.c @@ -169,7 +169,7 @@ static double (*const msvc_callf[11])(FnPtr fn, Word *w) = { 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) { possibly_static_assert(sizeof(double) == 8); possibly_static_assert(sizeof(float) == 4); - FnPtr fn_ptr = msvc_get_fn_ptr(ffmgr, fn, call_where); + FnPtr fn_ptr = foreign_get_fn_ptr(ffmgr, fn, call_where); Word words[10]; Word *word = words; diff --git a/foreign_msvc64.c b/foreign_msvc64.c deleted file mode 100644 index b797c86..0000000 --- a/foreign_msvc64.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - Copyright (C) 2019, 2020 Leo Tenenbaum. - This file is part of toc. toc is distributed under version 3 of the GNU General Public License, without any warranty whatsoever. - You should have received a copy of the GNU General Public License along with toc. If not, see . -*/ -#if SIZE_MAX != U64_MAX -#error "What's going on? The 64-bit Windows file was included, but size_t isn't 64 bits!" -#endif - -extern U64 win64_call(FnPtr fn, U64 *args, I64 nargs); -extern float win64_callf(FnPtr fn, U64 *args, I64 nargs); -extern double win64_calld(FnPtr fn, U64 *args, I64 nargs); - -static Status val_to_word(Value v, Type *t, Location where, U64 *w) { - switch (t->kind) { - case TYPE_BUILTIN: - switch (t->builtin) { - case BUILTIN_I8: *w = (U64)v.i8; break; - case BUILTIN_I16: *w = (U64)v.i16; break; - case BUILTIN_I32: *w = (U64)v.i32; break; - case BUILTIN_U8: *w = (U64)v.u8; break; - case BUILTIN_U16: *w = (U64)v.u16; break; - case BUILTIN_U32: *w = (U64)v.u32; break; - case BUILTIN_I64: *w = (U64)v.i64; break; - case BUILTIN_U64: *w = v.u64; break; - case BUILTIN_F32: *w = (U64)*(U32 *)&v.f32; break; - case BUILTIN_F64: *w = *(U64 *)&v.f64; break; - case BUILTIN_CHAR: *w = (U64)v.charv; break; - case BUILTIN_BOOL: *w = (U64)v.boolv; break; - case BUILTIN_TYPE: - case BUILTIN_VARARGS: - case BUILTIN_NMS: - case BUILTIN_VOID: - goto unsupported; - } - break; - case TYPE_PTR: - *w = (U64)v.ptr; break; - default: - unsupported: { - /* @TODO(eventually) */ - char *s = type_to_str(t); - err_print(where, "#foreign functions can't take arguments of type %s at compile time on Windows.", s); - free(s); - return false; - } - } - return true; -} - -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) { - possibly_static_assert(sizeof(double) == 8); - possibly_static_assert(sizeof(float) == 4); - FnPtr fn_ptr = msvc_get_fn_ptr(ffmgr, fn, call_where); - - U64 *words = err_malloc(nargs * sizeof *words); - U64 *word = words; - char *type = (char *)arg_types; - for (size_t i = 0; i < nargs; ++i) { - if (!val_to_word(args[i], (Type *)type, call_where, word)) - return false; - type += arg_types_stride; - ++word; - } - int kind = 0; /* 0=>integer, 1=>f32, 2=>f64 */ - switch (ret_type->kind) { - case TYPE_BUILTIN: - switch (ret_type->builtin) { - case BUILTIN_I8: - case BUILTIN_I16: - case BUILTIN_I32: - case BUILTIN_I64: - case BUILTIN_U8: - case BUILTIN_U16: - case BUILTIN_U32: - case BUILTIN_U64: - case BUILTIN_BOOL: - case BUILTIN_CHAR: - case BUILTIN_VOID: - break; - case BUILTIN_F32: - kind = 1; - break; - case BUILTIN_F64: - kind = 2; - break; - default: - goto unsupported; - } - break; - case TYPE_PTR: - break; - default: - unsupported: { - char *s = type_to_str(ret_type); - /* @TODO(eventually) */ - err_print(call_where, "You can't call functions which return type %s at compile time on Windows.", s); - free(s); - return false; - } - } - - switch (kind) { - case 0: { - U64 r = win64_call(fn_ptr, words, (I64)nargs); - switch (ret_type->kind) { - case TYPE_BUILTIN: - switch (ret_type->builtin) { - case BUILTIN_I8: ret->i8 = (I8)r; break; - case BUILTIN_I16: ret->i16 = (I16)r; break; - case BUILTIN_I32: ret->i32 = (I32)r; break; - case BUILTIN_I64: ret->i64 = (I64)r; break; - case BUILTIN_U8: ret->u8 = (U8)r; break; - case BUILTIN_U16: ret->u16 = (U16)r; break; - case BUILTIN_U32: ret->u32 = (U32)r; break; - case BUILTIN_U64: ret->u64 = (U64)r; break; - case BUILTIN_BOOL: ret->boolv = (bool)r; break; - case BUILTIN_CHAR: ret->charv = (char)r; break; - case BUILTIN_VOID: (void)r; break; - default: assert(0); break; - } - break; - case TYPE_PTR: - ret->ptr = (void *)r; - break; - default: assert(0); break; - } - } break; - case 1: - ret->f32 = win64_callf(fn_ptr, words, (I64)nargs); - break; - case 2: - ret->f64 = win64_calld(fn_ptr, words, (I64)nargs); - break; - } - free(words); - return true; -} diff --git a/foreign_unix.c b/foreign_unix.c new file mode 100644 index 0000000..3b4222a --- /dev/null +++ b/foreign_unix.c @@ -0,0 +1,51 @@ +#include +typedef struct { + void *handle; +} Library; + +static FnPtr foreign_get_fn_ptr(ForeignFnManager *ffmgr, FnExpr *fn, Location call_where) { + 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; + } + return fn_ptr; +} + +#ifdef FOREIGN_USE_AVCALL +#include "foreign_avcall.c" +#elif defined __x86_64__ +#include "foreign64.c" +#else +#include "foreign_avcall.c" +#endif diff --git a/main.c b/main.c index de2c818..1b2dbc0 100644 --- a/main.c +++ b/main.c @@ -8,7 +8,6 @@ /* @TODO: -win64 #foreign allow #include "foo.toc", foo; #include "foo.toc", foo; diff --git a/systemv64call.asm b/systemv64call.asm new file mode 100644 index 0000000..e12f304 --- /dev/null +++ b/systemv64call.asm @@ -0,0 +1,267 @@ +;;; Call SystemV x64 functions dynamically +;;; Written in NASM +;;; This implements the SystemV calling convention (which is used by Linux and OS X) so that +;;; you can call functions with a variable (i.e. not known at compile time) number of arguments. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This is free and unencumbered software released into the public domain. +;; +;; Anyone is free to copy, modify, publish, use, compile, sell, or +;; distribute this software, either in source code form or as a compiled +;; binary, for any purpose, commercial or non-commercial, and by any +;; means. +;; +;; In jurisdictions that recognize copyright laws, the author or authors +;; of this software dedicate any and all copyright interest in the +;; software to the public domain. We make this dedication for the benefit +;; of the public at large and to the detriment of our heirs and +;; successors. We intend this dedication to be an overt act of +;; relinquishment in perpetuity of all present and future rights to this +;; software under copyright law. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +;; IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +;; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +;; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +;; OTHER DEALINGS IN THE SOFTWARE. +;; +;; For more information, please refer to +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;To compile this: +;; first get nasm: https://nasm.us/ +;; add it to your path, then do: +;; nasm -f elf64 systemv64call.asm +;; You will get systemv64call.o +;;You can use it like this (in C/C++): +;; typedef void (*FnPtr)(); +;; extern unsigned long systemv64_call(FnPtr fn, void *args, long nargs, bool *is_float); +;; extern float systemv64_callf(FnPtr fn, void *args, long nargs, bool *is_float); +;; extern double systemv64_calld(FnPtr fn, void *args, long nargs, bool *is_float); +;; extern SomeType systemv64_call_other(FnPtr fn, void *args, long nargs, bool *is_float); +;; (all of these refer to the same actual function) +;; Let's say you want to call the function: +;; float foo(double a, float b, int c, unsigned long long d) { +;; return (float)a + b*(float)c - (float)d; +;; } +;; with the arguments a = 3.4, b = 3.5f, c = -73, d = 46. +;; First, you need to convert all the arguments to a single data type, so that +;; they can call be stored in one array. You should probably convert them all to +;; as unsigned long/uint64_ts (you can also do all doubles). For integers, you can just +;; cast them to unsigned long/uint64_t. For floating point numbers, you need to do something +;; a bit dubious: +;; double x = 3.4; +;; uint64_t num = *(uint64_t *)&x; +;; float y = 3.5f; +;; uint64_t num2 = (uint64_t) *(uint32_t *)&y; +;; (The cast to uint64_t on that last line is unnecessary, unless you have very strict warnings on.) +;; Now store them all in an array: +;; uint64_t args[4] = {num, num2, (uint64_t)-73, 46}; +;; Finally, you will need an is_float array. is_float[i] is true if the ith argument is floating-point, +;; and false if it's an integer/pointer. So in this case it would be: +;; bool is_float[4] = {true, true, false, false}; +;; (if you are using C89, you can use unsigned char instead of bool, 0 instead of false, and 1 instead of true) +;; Now use systemv64_callf (since the function returns a float). +;; float ret = systemv64_callf((FnPtr)foo, args, is_float, 4); +;; printf("foo returned: %f\n", ret); +;; Here's the full code: +;; extern float systemv64_callf(FnPtr fn, void *args, bool *is_float, long nargs); +;; float foo(double a, float b, int c, unsigned long long d) { +;; return (float)a + b*(float)c - (float)d; +;; } +;; int main(void) { +;; double x = 3.4; +;; uint64_t num = *(uint64_t *)&x; +;; float y = 3.5f; +;; uint64_t num2 = (uint64_t) *(uint32_t *)&y; +;; uint64_t args[4] = {num, num2, (uint64_t)-73, 46}; +;; bool is_float[4] = {true, true, false, false}; +;; float ret = systemv64_callf((FnPtr)foo, args, 4, is_float); +;; printf("foo returned: %f\n", ret); +;; return 0; +;; } +;;Why might you need this? +;; Sometimes you don't know how many arguments you're gonna call a function with at compile time. +;; For example, maybe you're making an interpreted programming language which calls out to C. +;; Usually it will be with a function pointer you got from dlsym. +;;If you find a bug, +;; Please email pommicket@pommicket.com +global systemv64_call +global systemv64_callf +global systemv64_calld +global systemv64_call_other + +; rdi - fn - function pointer +; rsi - args - arguments to the function +; rdx - nargs - number of arguments +; rcx - is_float - is each argument floating point? +systemv64_call: +systemv64_callf: +systemv64_calld: +systemv64_call_other: + sub rsp, 32 + mov [rsp+24], rbx ; save non-volatile register values + mov [rsp+16], rbp + mov [rsp+8], r12 + mov [rsp], r13 + + mov rbp, rsp ; save stack pointer + mov r13, rdi ; save function pointer + + ; this section here calculates the number of floating point and integer arguments + mov r10, 0 ; integer index + mov r11, 0 ; floating point index + mov rbx, 0 ; arg index +.fp_loop: + cmp byte [rcx], 0 + jne .fp_loop_float + ; integer argument + add r10, 1 + jmp .fp_continue + .fp_loop_float: + add r11, 1 + .fp_continue: + inc rbx ; ++arg_index + inc rcx ; ++is_float + cmp rbx, rdx ; if arg_index < nargs + jl .fp_loop + + ; r10 now holds the number of integer arguments, and r11 holds the number of floating point arguments + + ; we need to calculate the number of stack arguments so we can align the stack properly + mov rbx, 0 ; num_stack_args + lea r12, [r10-6] ; number of integer args + cmp r12, 0 + jle .skip_int + add rbx, r12 ; add int stack args + .skip_int: + lea r12, [r11-8] ; number of float args + cmp r12, 0 + jle .skip_flt + add rbx, r12 ; add float stack args + .skip_flt: + + ; align the stack + lea rbx, [rsp+8*rbx] ; where rsp will be after we push the arguments + and rbx, 0xf ; calculate future alignment + sub rsp, rbx ; align the stack + + mov rax, r11 ; save number of floating point arguments (needs to be in rax, at least for varargs. I'm not sure why) + ; we go right to left because that's the order stuff's put on the stack + lea r12, [rsi+8*rdx] ; arg = &args[nargs] + mov rbx, rcx ; is_arg_fp = &is_fp[nargs] +.loop: + ; if r10 (int index) and r11 (float index) are both 0, ... + cmp r10, 0 + jne .skip_fp_check + cmp r11, 0 + je .loop_end ; ... break out of the loop +.skip_fp_check: + sub r12, 8 ; --arg + dec rbx ; --is_arg_fp + + cmp byte [rbx], 0 ; is it float? + jne .float + + dec r10 ; --int_arg_index + + cmp r10, 0 + jg .after_1st + ; 1st integer argument + mov rdi, qword [r12] + jmp .loop +.after_1st: + cmp r10, 1 + jg .after_2nd + ; 2nd int argument + mov rsi, qword [r12] + jmp .loop +.after_2nd: + cmp r10, 2 + jg .after_3rd + ; 3rd int argument + mov rdx, qword [r12] + jmp .loop +.after_3rd: + cmp r10, 3 + jg .after_4th + ; 4th int argument + mov rcx, qword [r12] + jmp .loop +.after_4th: + cmp r10, 4 + jg .after_5th + ; 5th int argument + mov r8, qword [r12] + jmp .loop +.after_5th: + cmp r10, 5 + jg .stack_arg + ; 6th int argument + mov r9, qword [r12] + jmp .loop + +.float: + dec r11 ; --float_arg_index + cmp r11, 0 + jg .after_1stf + ; 1st float argument + movsd xmm0, qword [r12] + jmp .loop +.after_1stf: + cmp r11, 1 + jg .after_2ndf + ; 2nd float argument + movsd xmm1, qword [r12] + jmp .loop +.after_2ndf: + cmp r11, 2 + jg .after_3rdf + ; 3rd float argument + movsd xmm2, qword [r12] + jmp .loop +.after_3rdf: + cmp r11, 3 + jg .after_4thf + ; 4th float argument + movsd xmm3, qword [r12] + jmp .loop +.after_4thf: + cmp r11, 4 + jg .after_5thf + ; 5th float argument + movsd xmm4, qword [r12] + jmp .loop +.after_5thf: + cmp r11, 5 + jg .after_6thf + ; 6th float argument + movsd xmm5, qword [r12] + jmp .loop +.after_6thf: + cmp r11, 6 + jg .after_7thf + ; 7th float argument + movsd xmm6, qword [r12] + jmp .loop +.after_7thf: + cmp r11, 7 + jg .stack_arg + ; 8th float argument + movsd xmm7, qword [r12] + jmp .loop +.stack_arg: + ; argument pushed on the stack (>6th integer, >8th float argument) + push qword [r12] + jmp .loop +.loop_end: + + call r13 + + mov rsp, rbp ; restore stack pointer (stored here at top of function) + mov r13, [rsp]; restore non-volatile register values + mov r12, [rsp+8] + mov rbp, [rsp+16] + mov rbx, [rsp+24] + add rsp, 32 + ret diff --git a/systemv64call_test.c b/systemv64call_test.c new file mode 100644 index 0000000..3febbba --- /dev/null +++ b/systemv64call_test.c @@ -0,0 +1,149 @@ +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to + +should output: +123 456 789 101 112 -131 -415 -161 -718 19 999 888 2626183732628 +2626183734690 +1.414214 +Hello 3.000000 -2848239.700000 -123 456 789 43873243.234982 111.100000 222.200000 333.300000 444.400000 555.500000 666.600000 +126 +foo returned: -298.100006 +*/ + +#include +#include +#include +#include + + +typedef void (*FnPtr)(); + +extern uint64_t systemv64_call(FnPtr fn, void *args, int64_t nargs, bool *is_fp); +extern float systemv64_callf(FnPtr fn, void *args, int64_t nargs, bool *is_fp); +extern double systemv64_calld(FnPtr fn, void *args, int64_t nargs, bool *is_fp); +typedef struct { + double x,y,z; +} Point; +extern Point systemv64_call_other(FnPtr fn, void *args, bool *is_fp, int64_t nargs); + +#define arr_sz(x) (sizeof (x) / sizeof *(x)) + + +float foo(double a, float b, int c, unsigned long long d) { + return (float)a + b*(float)c - (float)d; +} +int main2(void) { + double x = 3.4; + uint64_t num = *(uint64_t *)&x; + float y = 3.5f; + uint64_t num2 = (uint64_t) *(uint32_t *)&y; + uint64_t args[4] = {num, num2, (uint64_t)-73, 46}; + bool is_float[4] = {true, true, false, false}; + float ret = systemv64_callf((FnPtr)foo, args, 4, is_float); + printf("foo returned: %f\n", ret); + return 0; +} + +long long bar(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, long long m) { + printf("%d %d %d %d %d %d %d %d %d %d %d %d %lld\n", a,b,c,d,e,f,g,h,i,j,k,l,m); + return a+b+c+d+e+f+g+h+i+j+k+l+m; +} + +Point mkpoint(int a, int b, int c) { + Point ret = {a,b,c}; + return ret; +} + +int main(void) { + uint64_t params[13] = { + 123, + 456, + 789, + 101, + 112, + -131, + -415, + -161, + -718, + 19, + 999, + 888, + 2626183732628LL + }; + bool is_fp[arr_sz(params)] = { + 0,0,0,0,0,0,0,0, + 0,0,0,0,0 + }; + long long ret = (long long)systemv64_call((FnPtr)bar, params, arr_sz(params), is_fp); + printf("%lld\n",ret); + + float two = 2.0f; + uint64_t params2[] = { + *(uint32_t *)&two + }; + bool is_fp2[arr_sz(params)] = {1}; + float ret2 = systemv64_callf((FnPtr)sqrtf, params2, arr_sz(params2), is_fp2); + printf("%f\n",ret2); + + + double nums[] = { + 3.0, + -2848239.7, + 43873243.234982, + 111.1, + 222.2, + 333.3, + 444.4, + 555.5, + 666.6, + 777.7, + 888.8, + 999.9 + }; + + uint64_t params3[] = { + (uint64_t)"Hello %f %f %d %d %d %f %f %f %f %f %f %f\n", + *(uint64_t *)&nums[0], + *(uint64_t *)&nums[1], + -123, + 456, + 789, + *(uint64_t *)&nums[2], + *(uint64_t *)&nums[3], + *(uint64_t *)&nums[4], + *(uint64_t *)&nums[5], + *(uint64_t *)&nums[6], + *(uint64_t *)&nums[7], + *(uint64_t *)&nums[8], + + }; + + bool is_fp3[arr_sz(params3)] = {0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1}; + int ret3 = systemv64_call((FnPtr)printf, params3, arr_sz(params3), is_fp3); + printf("%d\n",ret3); + main2(); + return 0; +} diff --git a/test.toc b/test.toc index 5117906..b63bb2f 100644 --- a/test.toc +++ b/test.toc @@ -21,6 +21,7 @@ main ::= fn() { io.puti(f.b.f.k); io.puti(f.b.f.b.f.k); } +main(); slice_to_ll ::= fn(t::=, slice: []t) use ll: LinkedList(t) { head = slice[0]; diff --git a/toc.c b/toc.c index 6a84170..b5660f9 100644 --- a/toc.c +++ b/toc.c @@ -153,10 +153,10 @@ static Location token_location(File *file, Token *t); #include "parse.c" #if COMPILE_TIME_FOREIGN_FN_SUPPORT -#if defined _MSC_VER && !defined COMPILE_TIME_FOREIGN_FN_AVCALL +#if defined _MSC_VER #include "foreign_msvc.c" -#else -#include "foreign_avcall.c" +#elif defined __unix__ || defined __OSX__ +#include "foreign_unix.c" #endif #else static bool 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) { -- cgit v1.2.3