Skip to content

Commit

Permalink
make ccallable work with system images and precompilation
Browse files Browse the repository at this point in the history
fixes #35014
  • Loading branch information
JeffBezanson committed May 3, 2020
1 parent ba9a003 commit c075053
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 172 deletions.
15 changes: 10 additions & 5 deletions base/c.jl
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,8 @@ function exit_on_sigint(on::Bool)
ccall(:jl_exit_on_sigint, Cvoid, (Cint,), on)
end

function ccallable(f::Function, rt::Type, argt::Type, name::Union{AbstractString,Symbol}=string(f))
ccall(:jl_extern_c, Cvoid, (Any, Any, Any, Cstring), f, rt, argt, name)
function _ccallable(rt::Type, sigt::Type)
ccall(:jl_extern_c, Cvoid, (Any, Any), rt, sigt)
end

function expand_ccallable(rt, def)
Expand All @@ -499,17 +499,22 @@ function expand_ccallable(rt, def)
error("@ccallable requires a return type")
end
if sig.head === :call
name = sig.args[1]
f = sig.args[1]
if isa(f,Expr) && f.head === :(::)
f = f.args[end]
else
f = :(typeof($f))
end
at = map(sig.args[2:end]) do a
if isa(a,Expr) && a.head === :(::)
a.args[2]
a.args[end]
else
:Any
end
end
return quote
$(esc(def))
ccallable($(esc(name)), $(esc(rt)), $(Expr(:curly, :Tuple, map(esc, at)...)), $(string(name)))
_ccallable($(esc(rt)), $(Expr(:curly, :Tuple, esc(f), map(esc, at)...)))
end
end
end
Expand Down
2 changes: 0 additions & 2 deletions src/anticodegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,3 @@ JL_DLLEXPORT uint32_t jl_get_LLVM_VERSION(void)
{
return 0;
}

jl_array_t *jl_cfunction_list;
9 changes: 8 additions & 1 deletion src/aotcompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,14 @@ void *jl_create_native(jl_array_t *methods, const jl_cgparams_t cgparams)
continue;
size_t i, l;
for (i = 0, l = jl_array_len(methods); i < l; i++) {
mi = (jl_method_instance_t*)jl_array_ptr_ref(methods, i);
// each item in this list is either a MethodInstance indicating something
// to compile, or an svec(rettype, sig) describing a C-callable alias to create.
jl_value_t *item = jl_array_ptr_ref(methods, i);
if (jl_is_simplevector(item)) {
jl_compile_extern_c(shadow_output, &params, NULL, jl_svecref(item, 0), jl_svecref(item, 1));
continue;
}
mi = (jl_method_instance_t*)item;
src = NULL;
// if this method is generally visible to the current compilation world,
// and this is either the primary world, or not applicable in the primary world
Expand Down
7 changes: 7 additions & 0 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,13 @@ static Type *julia_struct_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, jl_unionall
return _julia_struct_to_llvm(&ctx.emission_context, jt, ua, isboxed);
}

bool jl_type_mappable_to_c(jl_value_t *ty)
{
jl_codegen_params_t params;
bool toboxed;
return _julia_struct_to_llvm(&params, ty, NULL, &toboxed) != NULL;
}

static bool is_datatype_all_pointers(jl_datatype_t *dt)
{
size_t i, l = jl_datatype_nfields(dt);
Expand Down
159 changes: 43 additions & 116 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4014,8 +4014,8 @@ static void emit_cfunc_invalidate(

static Function* gen_cfun_wrapper(
Module *into, jl_codegen_params_t &params,
const function_sig_t &sig, jl_value_t *ff,
jl_typemap_entry_t *sf, jl_value_t *declrt, jl_method_instance_t *lam,
const function_sig_t &sig, jl_value_t *ff, const char *aliasname,
jl_value_t *declrt, jl_method_instance_t *lam,
jl_unionall_t *unionall_env, jl_svec_t *sparam_vals, jl_array_t **closure_types)
{
// Generate a c-callable wrapper
Expand All @@ -4027,12 +4027,11 @@ static Function* gen_cfun_wrapper(
jl_value_t *astrt = (jl_value_t*)jl_any_type;
void *callptr = NULL;
int calltype = 0;
// infer it first, if necessary
// FIXME! pretend this is OK
if (lam)
if (aliasname)
name = aliasname;
else if (lam)
name = jl_symbol_name(lam->def.method->name);
if (lam && params.cache) {
// if (!into)
// TODO: this isn't ideal to be unconditionally calling type inference (and compile) from here
codeinst = jl_compile_method_internal(lam, world);
assert(codeinst->invoke);
Expand Down Expand Up @@ -4086,20 +4085,6 @@ static Function* gen_cfun_wrapper(
cw->setAttributes(attributes);
jl_init_function(cw);
Function *cw_proto = into ? cw : function_proto(cw);
// Save the Function object reference
if (sf) {
jl_value_t *oldsf = sf->func.value;
size_t i, oldlen = jl_svec_len(oldsf);
jl_value_t *newsf = (jl_value_t*)jl_alloc_svec(oldlen + 2);
JL_GC_PUSH1(&newsf);
jl_svecset(newsf, 0, sig.rt);
jl_svecset(newsf, 1, jl_box_voidpointer((void*)cw_proto));
for (i = 0; i < oldlen; i++)
jl_svecset(newsf, i + 2, jl_svecref(oldsf, i));
sf->func.value = newsf;
jl_gc_wb(sf, sf->func.value);
JL_GC_POP();
}

jl_codectx_t ctx(jl_LLVMContext, params);
ctx.f = cw;
Expand Down Expand Up @@ -4515,6 +4500,10 @@ static Function* gen_cfun_wrapper(
cw_proto = into ? cw_make : function_proto(cw_make);
}

if (aliasname) {
GlobalAlias::create(cw->getType()->getElementType(), cw->getType()->getAddressSpace(),
GlobalValue::ExternalLinkage, aliasname, cw, M);
}
if (!into)
jl_finalize_module(std::unique_ptr<Module>(M));

Expand Down Expand Up @@ -4628,8 +4617,8 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con
jl_method_instance_t *lam = sigt ? jl_get_specialization1((jl_tupletype_t*)sigt, world, &min_valid, &max_valid, 0) : NULL;
Value *F = gen_cfun_wrapper(
jl_Module, ctx.emission_context,
sig, fexpr_rt.constant,
NULL, declrt, lam,
sig, fexpr_rt.constant, NULL,
declrt, lam,
unionall_env, sparam_vals, &closure_types);
bool outboxed;
if (nest) {
Expand Down Expand Up @@ -4689,116 +4678,54 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con
return mark_julia_type(ctx, F, outboxed, output_type);
}

const struct jl_typemap_info cfunction_cache = {
1, (jl_datatype_t**)&jl_array_any_type
};

jl_array_t *jl_cfunction_list;

Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_tupletype_t *argt,
jl_codegen_params_t &params)
// do codegen to create a C-callable alias/wrapper, or if sysimg_handle is set,
// restore one from a loaded system image.
Function *jl_generate_ccallable(void *llvmmod, void *sysimg_handle, jl_value_t *declrt, jl_value_t *sigt, jl_codegen_params_t &params)
{
// Assumes the codegen lock is acquired. The caller is responsible for that.
jl_ptls_t ptls = jl_get_ptls_states();
if (ptls->in_pure_callback)
jl_error("cfunction cannot be used in a generated function");

// validate and unpack the arguments
JL_TYPECHK(cfunction, type, declrt);
if (!jl_is_tuple_type(argt)) // the C API requires that argt Tuple type actually be an svec
jl_type_error("cfunction", (jl_value_t*)jl_anytuple_type_type, (jl_value_t*)argt);
// trampolines are not supported here:
// check that f is a guaranteed singleton type
jl_value_t *ft = jl_typeof(ff);
if (((jl_datatype_t*)ft)->instance != ff)
jl_error("cfunction: use `@cfunction` to make closures");

// check the cache structure
// this has three levels (for the 3 parameters above)
// first split on `ft` using a simple eqtable
// then use the typemap to split on argt
// and finally, pick declrt from the pair-list
jl_typemap_t *cache_l2 = NULL;
jl_typemap_entry_t *cache_l3 = NULL;
if (!jl_cfunction_list) {
jl_cfunction_list = jl_alloc_vec_any(16);
}
else {
cache_l2 = jl_eqtable_get(jl_cfunction_list, ft, NULL);
if (cache_l2) {
struct jl_typemap_assoc search = {(jl_value_t*)argt, 1, NULL, 0, ~(size_t)0};
cache_l3 = jl_typemap_assoc_by_type(cache_l2, &search, /*offs*/0, /*subtype*/0);
if (cache_l3) {
jl_svec_t *sf = (jl_svec_t*)cache_l3->func.value;
size_t i, l = jl_svec_len(sf);
for (i = 0; i < l; i += 2) {
jl_value_t *ti = jl_svecref(sf, i);
if (jl_egal(ti, declrt)) {
return (Function*)jl_unbox_voidpointer(jl_svecref(sf, i + 1));
}
}
}
}
}

if (cache_l3 == NULL) {
jl_typemap_t *insert = cache_l2;
if (!insert)
insert = jl_nothing;
cache_l3 = jl_typemap_insert(&insert, (jl_value_t*)insert, (jl_tupletype_t*)argt,
NULL, jl_emptysvec, (jl_value_t*)jl_emptysvec, /*offs*/0, &cfunction_cache, 1, ~(size_t)0);
if (insert != cache_l2)
jl_cfunction_list = jl_eqtable_put(jl_cfunction_list, ft, insert, NULL);
}

// compute / validate return type
jl_datatype_t *ft = (jl_datatype_t*)jl_tparam0(sigt);
jl_value_t *ff = ft->instance;
assert(ff);
const char *name = jl_symbol_name(ft->name->mt->name);
jl_value_t *crt = declrt;
if (jl_is_abstract_ref_type(declrt)) {
declrt = jl_tparam0(declrt);
if (jl_is_typevar(declrt))
jl_error("cfunction: return type Ref should have an element type, not Ref{<:T}");
if (declrt == (jl_value_t*)jl_any_type)
jl_error("cfunction: return type Ref{Any} is invalid. Use Any or Ptr{Any} instead.");
crt = (jl_value_t*)jl_any_type;
}
bool toboxed;
Type *lcrt = _julia_struct_to_llvm(&params, crt, NULL, &toboxed);
if (lcrt == NULL)
jl_error("cfunction: return type doesn't correspond to a C type");
else if (toboxed)
if (toboxed)
lcrt = T_prjlvalue;

// compute / validate method signature
jl_value_t *sigt = NULL; // dispatch sig: type signature (argt) with Ref{} annotations removed and ft added
JL_GC_PUSH1(&sigt);
size_t i, nargs = jl_nparams(argt);
sigt = (jl_value_t*)jl_alloc_svec(nargs + 1);
jl_svecset(sigt, 0, ft);
for (i = 0; i < nargs; i++) {
jl_value_t *ati = jl_tparam(argt, i);
if (jl_is_abstract_ref_type(ati)) {
ati = jl_tparam0(ati);
if (jl_is_typevar(ati))
jl_error("cfunction: argument type Ref should have an element type, not Ref{<:T}");
}
if (jl_is_pointer(ati) && jl_is_typevar(jl_tparam0(ati)))
jl_error("cfunction: argument type Ptr should have an element type, Ptr{<:T}");
jl_svecset(sigt, i + 1, ati);
}
sigt = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)sigt);

// emit cfunction (trampoline)
size_t nargs = jl_nparams(sigt)-1;
jl_svec_t *argtypes = NULL;
JL_GC_PUSH1(&argtypes);
argtypes = jl_alloc_svec(nargs);
for (size_t i = 0; i < nargs; i++) {
jl_svecset(argtypes, i, jl_tparam(sigt, i+1));
}
jl_value_t *err;
{ // scope block for sig
function_sig_t sig("cfunction", lcrt, crt, toboxed,
argt->parameters, NULL, false, CallingConv::C, false, &params);
argtypes, NULL, false, CallingConv::C, false, &params);
if (sig.err_msg.empty()) {
size_t world = jl_world_counter;
size_t min_valid = 0;
size_t max_valid = ~(size_t)0;
// try to look up this function for direct invoking
jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world, &min_valid, &max_valid, 0);
Function *F = gen_cfun_wrapper(NULL, params, sig, ff, cache_l3, declrt, lam, NULL, NULL, NULL);
Function *F = NULL;
if (sysimg_handle) {
// restore a ccallable from the system image
void *addr;
int found = jl_dlsym(sysimg_handle, name, &addr, 0);
if (found) {
FunctionType *ftype = sig.functype();
F = Function::Create(ftype, GlobalVariable::ExternalLinkage,
name, shadow_output);
add_named_global(F, addr);
}
}
else {
jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world, &min_valid, &max_valid, 0);
F = gen_cfun_wrapper((Module*)llvmmod, params, sig, ff, name, declrt, lam, NULL, NULL, NULL);
}
JL_GC_POP();
return F;
}
Expand Down
16 changes: 16 additions & 0 deletions src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ static jl_array_t *serializer_worklist JL_GLOBALLY_ROOTED;
// (only used by the incremental serializer in MODE_MODULE)
htable_t edges_map;

// list of requested ccallable signatures
static arraylist_t ccallable_list;

#define TAG_SYMBOL 2
#define TAG_SSAVALUE 3
#define TAG_DATATYPE 4
Expand Down Expand Up @@ -873,6 +876,7 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li
write_int8(s->s, m->pure);
jl_serialize_value(s, (jl_value_t*)m->slot_syms);
jl_serialize_value(s, (jl_value_t*)m->roots);
jl_serialize_value(s, (jl_value_t*)m->ccallable);
jl_serialize_value(s, (jl_value_t*)m->source);
jl_serialize_value(s, (jl_value_t*)m->unspecialized);
jl_serialize_value(s, (jl_value_t*)m->generator);
Expand Down Expand Up @@ -1778,6 +1782,11 @@ static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_
m->roots = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->roots);
if (m->roots)
jl_gc_wb(m, m->roots);
m->ccallable = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->ccallable);
if (m->ccallable) {
jl_gc_wb(m, m->ccallable);
arraylist_push(&ccallable_list, m->ccallable);
}
m->source = jl_deserialize_value(s, &m->source);
if (m->source)
jl_gc_wb(m, m->source);
Expand Down Expand Up @@ -3217,6 +3226,7 @@ static jl_value_t *_jl_restore_incremental(ios_t *f, jl_array_t *mod_array)
arraylist_new(&backref_list, 4000);
arraylist_push(&backref_list, jl_main_module);
arraylist_new(&flagref_list, 0);
arraylist_new(&ccallable_list, 0);
htable_new(&uniquing_table, 0);

jl_serializer_state s = {
Expand Down Expand Up @@ -3267,6 +3277,12 @@ static jl_value_t *_jl_restore_incremental(ios_t *f, jl_array_t *mod_array)
arraylist_free(tracee_list);
free(tracee_list);
}
for (int i = 0; i < ccallable_list.len; i++) {
jl_svec_t *item = (jl_svec_t*)ccallable_list.items[i];
JL_GC_PROMISE_ROOTED(item);
jl_compile_extern_c(NULL, NULL, NULL, jl_svecref(item, 0), jl_svecref(item, 1));
}
arraylist_free(&ccallable_list);
jl_value_t *ret = (jl_value_t*)jl_svec(2, restored, init_order);
JL_GC_POP();

Expand Down
2 changes: 0 additions & 2 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2672,8 +2672,6 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp)
gc_mark_queue_obj(gc_cache, sp, jl_current_modules.table[i]);
}
}
if (jl_cfunction_list != NULL)
gc_mark_queue_obj(gc_cache, sp, jl_cfunction_list);
gc_mark_queue_obj(gc_cache, sp, jl_anytuple_type_type);
for (size_t i = 0; i < N_CALL_CACHE; i++)
if (call_cache[i])
Expand Down
Loading

0 comments on commit c075053

Please sign in to comment.