Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: FFI Tuples #4157

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .release-notes/4150.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Enable passing and returning of tuples in FFI calls

This adds the ability to use tuples in FFI calls. Specifically, tuples are now valid call and return parameters in FFI calls. Note that the ability to use tuples in FFI function calls was previously removed due to concerns about ABI compatibility issues regarding their primary intended use: to allow struct values in FFI. While these issues may still be present with various OS/compiler combinations, this feature was added again for the benefit of those for who this is working/useful.

```pony
use @foo[None](x: (U8, U8))
use @bar[(U8, U8)]()

actor Main
new create(env: Env) =>
@foo((0, 0))
(let i: U8, let j: U8) = @bar()
```
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,11 @@ endif

test-full-programs-release: all
@mkdir -p $(outDir)/runner-tests/release
$(SILENT)cd '$(outDir)' && $(buildDir)/test/libponyc-run/runner/runner --timeout_s=60 --max_parallel=$(num_cores) --exclude=runner --ponyc=$(outDir)/ponyc --output=$(outDir)/runner-tests/release --test_lib=$(outDir)/test_lib $(srcDir)/test/libponyc-run
$(SILENT)cd '$(outDir)' && $(buildDir)/test/libponyc-run/runner/runner --timeout_s=60 --max_parallel=$(num_cores) --exclude=runner --ponyc=$(outDir)/ponyc --output=$(outDir)/runner-tests/release --test_lib=$(outDir)/test_lib $(srcDir)/test/libponyc-run $(extra_args)

test-full-programs-debug: all
@mkdir -p $(outDir)/runner-tests/debug
$(SILENT)cd '$(outDir)' && $(buildDir)/test/libponyc-run/runner/runner --timeout_s=60 --max_parallel=$(num_cores) --exclude=runner --ponyc=$(outDir)/ponyc --debug --output=$(outDir)/runner-tests/debug --test_lib=$(outDir)/test_lib $(srcDir)/test/libponyc-run
$(SILENT)cd '$(outDir)' && $(buildDir)/test/libponyc-run/runner/runner --timeout_s=60 --max_parallel=$(num_cores) --exclude=runner --ponyc=$(outDir)/ponyc --debug --output=$(outDir)/runner-tests/debug --test_lib=$(outDir)/test_lib $(srcDir)/test/libponyc-run $(extra_args)

test-stdlib-release: all
$(SILENT)cd '$(outDir)' && PONYPATH=.:$(PONYPATH) ./ponyc -b stdlib-release --pic --checktree --verify $(cross_args) ../../packages/stdlib && echo Built `pwd`/stdlib-release && $(cross_runner) ./stdlib-release --sequential
Expand Down
2 changes: 1 addition & 1 deletion packages/collections/_test.pony
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ class \nodoc\ iso _TestRange is UnitTest
let range: Range[N] = Range[N](min, max, step)
let range_str = "Range(" + min.string() + ", " + max.string() + ", " + step.string() + ")"

h.assert_true(range.is_infinite(), range_str + " should be infinite")
h.assert_true(range.is_infinite(), range_str + " should be infinite")
try
let nextval = range.next()?
h.assert_eq[N](nextval, min)
Expand Down
1 change: 1 addition & 0 deletions src/libponyc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ add_library(libponyc STATIC
codegen/gendesc.c
codegen/genexe.c
codegen/genexpr.c
codegen/genffi.c
codegen/genfun.c
codegen/genheader.c
codegen/genident.c
Expand Down
333 changes: 0 additions & 333 deletions src/libponyc/codegen/gencall.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,33 +58,6 @@ static size_t tuple_indices_pop(call_tuple_indices_t* ti)
return ti->data[--ti->count];
}

struct ffi_decl_t
{
LLVMValueRef func;
// First declaration encountered
ast_t* decl;
// First call encountered
ast_t* call;
};

static size_t ffi_decl_hash(ffi_decl_t* d)
{
return ponyint_hash_ptr(d->func);
}

static bool ffi_decl_cmp(ffi_decl_t* a, ffi_decl_t* b)
{
return a->func == b->func;
}

static void ffi_decl_free(ffi_decl_t* d)
{
POOL_FREE(ffi_decl_t, d);
}

DEFINE_HASHMAP(ffi_decls, ffi_decls_t, ffi_decl_t, ffi_decl_hash, ffi_decl_cmp,
ffi_decl_free);

static LLVMValueRef invoke_fun(compile_t* c, LLVMValueRef fun,
LLVMValueRef* args, int count, const char* ret, bool setcc)
{
Expand Down Expand Up @@ -1012,312 +985,6 @@ LLVMValueRef gen_pattern_eq(compile_t* c, ast_t* pattern, LLVMValueRef r_value)
return result;
}

static LLVMTypeRef ffi_return_type(compile_t* c, reach_type_t* t,
bool intrinsic)
{
compile_type_t* c_t = (compile_type_t*)t->c_type;

if(t->underlying == TK_TUPLETYPE)
{
(void)intrinsic;
pony_assert(intrinsic);

// Can't use the named type. Build an unnamed type with the same elements.
unsigned int count = LLVMCountStructElementTypes(c_t->use_type);
size_t buf_size = count * sizeof(LLVMTypeRef);
LLVMTypeRef* e_types = (LLVMTypeRef*)ponyint_pool_alloc_size(buf_size);
LLVMGetStructElementTypes(c_t->use_type, e_types);

ast_t* child = ast_child(t->ast);
size_t i = 0;

while(child != NULL)
{
// A Bool in an intrinsic tuple return type is an i1.
if(is_bool(child))
e_types[i] = c->i1;

child = ast_sibling(child);
i++;
}

LLVMTypeRef r_type = LLVMStructTypeInContext(c->context, e_types, count,
false);
ponyint_pool_free_size(buf_size, e_types);
return r_type;
} else if(is_none(t->ast_cap)) {
return c->void_type;
} else {
return c_t->use_type;
}
}

static LLVMValueRef declare_ffi(compile_t* c, const char* f_name,
reach_type_t* t, ast_t* args, bool intrinsic)
{
bool is_varargs = false;
ast_t* last_arg = ast_childlast(args);
int param_count = (int)ast_childcount(args);

if((last_arg != NULL) && (ast_id(last_arg) == TK_ELLIPSIS))
{
is_varargs = true;
param_count--;
}

size_t buf_size = 0;
LLVMTypeRef* f_params = NULL;
// Don't allocate argument list if all arguments are optional
if(param_count != 0)
{
buf_size = param_count * sizeof(LLVMTypeRef);
f_params = (LLVMTypeRef*)ponyint_pool_alloc_size(buf_size);
param_count = 0;

ast_t* arg = ast_child(args);

deferred_reification_t* reify = c->frame->reify;

while((arg != NULL) && (ast_id(arg) != TK_ELLIPSIS))
{
ast_t* p_type = ast_type(arg);

if(p_type == NULL)
p_type = ast_childidx(arg, 1);

p_type = deferred_reify(reify, p_type, c->opt);
reach_type_t* pt = reach_type(c->reach, p_type);
pony_assert(pt != NULL);
f_params[param_count++] = ((compile_type_t*)pt->c_type)->use_type;
ast_free_unattached(p_type);
arg = ast_sibling(arg);
}
}

LLVMTypeRef r_type = ffi_return_type(c, t, intrinsic);
LLVMTypeRef f_type = LLVMFunctionType(r_type, f_params, param_count, is_varargs);
LLVMValueRef func = LLVMAddFunction(c->module, f_name, f_type);

if(f_params != NULL)
ponyint_pool_free_size(buf_size, f_params);

return func;
}

static void report_ffi_type_err(compile_t* c, ffi_decl_t* decl, ast_t* ast,
const char* name)
{
ast_error(c->opt->check.errors, ast,
"conflicting calls for FFI function: %s have incompatible types",
name);

if(decl != NULL)
{
ast_error_continue(c->opt->check.errors, decl->decl, "first declaration is "
"here");
ast_error_continue(c->opt->check.errors, decl->call, "first call is here");
}
}

static LLVMValueRef cast_ffi_arg(compile_t* c, ffi_decl_t* decl, ast_t* ast,
LLVMValueRef arg, LLVMTypeRef param, const char* name)
{
if(arg == NULL)
return NULL;

LLVMTypeRef arg_type = LLVMTypeOf(arg);

if(param == arg_type)
return arg;

if((LLVMABISizeOfType(c->target_data, param) !=
LLVMABISizeOfType(c->target_data, arg_type)))
{
report_ffi_type_err(c, decl, ast, name);
return NULL;
}

switch(LLVMGetTypeKind(param))
{
case LLVMPointerTypeKind:
if(LLVMGetTypeKind(arg_type) == LLVMIntegerTypeKind)
return LLVMBuildIntToPtr(c->builder, arg, param, "");
else
return LLVMBuildBitCast(c->builder, arg, param, "");

case LLVMIntegerTypeKind:
if(LLVMGetTypeKind(arg_type) == LLVMPointerTypeKind)
return LLVMBuildPtrToInt(c->builder, arg, param, "");

break;

case LLVMStructTypeKind:
pony_assert(LLVMGetTypeKind(arg_type) == LLVMStructTypeKind);
return arg;

default: {}
}

pony_assert(false);
return NULL;
}

LLVMValueRef gen_ffi(compile_t* c, ast_t* ast)
{
AST_GET_CHILDREN(ast, id, typeargs, args, named_args, can_err);
bool err = (ast_id(can_err) == TK_QUESTION);

// Get the function name, +1 to skip leading @
const char* f_name = ast_name(id) + 1;

deferred_reification_t* reify = c->frame->reify;

// Get the return type.
ast_t* type = deferred_reify(reify, ast_type(ast), c->opt);
reach_type_t* t = reach_type(c->reach, type);
pony_assert(t != NULL);
ast_free_unattached(type);

// Get the function. First check if the name is in use by a global and error
// if it's the case.
ffi_decl_t* ffi_decl;
bool is_func = false;
LLVMValueRef func = LLVMGetNamedGlobal(c->module, f_name);

if(func == NULL)
{
func = LLVMGetNamedFunction(c->module, f_name);
is_func = true;
}

if(func == NULL)
{
// Prototypes are mandatory, the declaration is already stored.
ast_t* decl = (ast_t*)ast_data(ast);
pony_assert(decl != NULL);

bool is_intrinsic = (!strncmp(f_name, "llvm.", 5) || !strncmp(f_name, "internal.", 9));
AST_GET_CHILDREN(decl, decl_id, decl_ret, decl_params, decl_named_params, decl_err);
err = (ast_id(decl_err) == TK_QUESTION);
func = declare_ffi(c, f_name, t, decl_params, is_intrinsic);

size_t index = HASHMAP_UNKNOWN;

#ifndef PONY_NDEBUG
ffi_decl_t k;
k.func = func;

ffi_decl = ffi_decls_get(&c->ffi_decls, &k, &index);
pony_assert(ffi_decl == NULL);
#endif

ffi_decl = POOL_ALLOC(ffi_decl_t);
ffi_decl->func = func;
ffi_decl->decl = decl;
ffi_decl->call = ast;

ffi_decls_putindex(&c->ffi_decls, ffi_decl, index);
} else {
ffi_decl_t k;
k.func = func;
size_t index = HASHMAP_UNKNOWN;

ffi_decl = ffi_decls_get(&c->ffi_decls, &k, &index);

if((ffi_decl == NULL) && (!is_func || LLVMHasMetadataStr(func, "pony.abi")))
{
ast_error(c->opt->check.errors, ast, "cannot use '%s' as an FFI name: "
"name is already in use by the internal ABI", f_name);
return NULL;
}

pony_assert(is_func);
}

// Generate the arguments.
int count = (int)ast_childcount(args);
size_t buf_size = count * sizeof(LLVMValueRef);
LLVMValueRef* f_args = (LLVMValueRef*)ponyint_pool_alloc_size(buf_size);

LLVMTypeRef f_type = LLVMGetElementType(LLVMTypeOf(func));
LLVMTypeRef* f_params = NULL;
bool vararg = (LLVMIsFunctionVarArg(f_type) != 0);

if(!vararg)
{
if(count != (int)LLVMCountParamTypes(f_type))
{
ast_error(c->opt->check.errors, ast,
"conflicting declarations for FFI function: declarations have an "
"incompatible number of parameters");

if(ffi_decl != NULL)
ast_error_continue(c->opt->check.errors, ffi_decl->decl, "first "
"declaration is here");

return NULL;
}

f_params = (LLVMTypeRef*)ponyint_pool_alloc_size(buf_size);
LLVMGetParamTypes(f_type, f_params);
}

ast_t* arg = ast_child(args);

for(int i = 0; i < count; i++)
{
f_args[i] = gen_expr(c, arg);

if(!vararg)
f_args[i] = cast_ffi_arg(c, ffi_decl, ast, f_args[i], f_params[i],
"parameters");

if(f_args[i] == NULL)
{
ponyint_pool_free_size(buf_size, f_args);
return NULL;
}

arg = ast_sibling(arg);
}

// If we can error out and we have an invoke target, generate an invoke
// instead of a call.
LLVMValueRef result;
codegen_debugloc(c, ast);

if(err && (c->frame->invoke_target != NULL))
result = invoke_fun(c, func, f_args, count, "", false);
else
result = LLVMBuildCall_P(c->builder, func, f_args, count, "");

codegen_debugloc(c, NULL);
ponyint_pool_free_size(buf_size, f_args);

if(!vararg)
ponyint_pool_free_size(buf_size, f_params);

compile_type_t* c_t = (compile_type_t*)t->c_type;

// Special case a None return value, which is used for void functions.
bool isnone = is_none(t->ast);
bool isvoid = LLVMGetReturnType(f_type) == c->void_type;

if(isnone && isvoid)
{
result = c_t->instance;
} else if(isnone != isvoid) {
report_ffi_type_err(c, ffi_decl, ast, "return values");
return NULL;
}

result = cast_ffi_arg(c, ffi_decl, ast, result, c_t->use_type,
"return values");
result = gen_assign_cast(c, c_t->use_type, result, t->ast_cap);

return result;
}

LLVMValueRef gencall_runtime(compile_t* c, const char *name,
LLVMValueRef* args, int count, const char* ret)
{
Expand Down
Loading