Skip to content

Commit 7934810

Browse files
committed
cfunction: redesign cache support
Provides a simpler fast path (no allocations and rebuilding type vectors required to search the cache).
1 parent caa4824 commit 7934810

File tree

5 files changed

+114
-70
lines changed

5 files changed

+114
-70
lines changed

src/ccall.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,13 +1774,13 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs)
17741774
else if (is_libjulia_func(jl_function_ptr)) {
17751775
assert(!isVa && !llvmcall && nargt == 3);
17761776
assert(lrt == T_size);
1777-
jl_value_t *f = argv[0].constant;
1777+
jl_value_t *ft = argv[0].typ;
17781778
jl_value_t *frt = argv[1].constant;
17791779
if (!frt) {
17801780
if (jl_is_type_type(argv[1].typ) && !jl_has_free_typevars(argv[1].typ))
17811781
frt = jl_tparam0(argv[1].typ);
17821782
}
1783-
if (f && frt) {
1783+
if (ft != jl_bottom_type && frt) {
17841784
jl_value_t *fargt = argv[2].constant;;
17851785
JL_GC_PUSH1(&fargt);
17861786
if (!fargt && jl_is_type_type(argv[2].typ)) {
@@ -1794,7 +1794,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs)
17941794
if (fargt && jl_is_tuple_type(fargt)) {
17951795
Value *llvmf = NULL;
17961796
JL_TRY {
1797-
llvmf = jl_cfunction_object((jl_function_t*)f, frt, (jl_tupletype_t*)fargt);
1797+
llvmf = jl_cfunction_cache(ft, frt, (jl_tupletype_t*)fargt);
17981798
}
17991799
JL_CATCH {
18001800
llvmf = NULL;

src/codegen.cpp

Lines changed: 108 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,7 +1414,20 @@ jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, const char *F, size
14141414
return fptr;
14151415
}
14161416

1417-
static Function *jl_cfunction_object(jl_function_t *f, jl_value_t *rt, jl_tupletype_t *argt);
1417+
static Function *jl_cfunction_cache(jl_value_t *ft, jl_value_t *declrt, jl_tupletype_t *argt);
1418+
1419+
// Get a pointer to the cache for the C-callable entry point for the given argument types.
1420+
// here argt does not include the leading function type argument
1421+
static Function *jl_cfunction_object(jl_value_t *f, jl_value_t *declrt, jl_tupletype_t *argt)
1422+
{
1423+
jl_value_t *ft;
1424+
if (jl_is_type(f))
1425+
ft = (jl_value_t*)jl_wrap_Type(f);
1426+
else
1427+
ft = jl_typeof(f);
1428+
return jl_cfunction_cache(ft, declrt, argt);
1429+
}
1430+
14181431
// get the address of a C-callable entry point for a function
14191432
extern "C" JL_DLLEXPORT
14201433
void *jl_function_ptr(jl_function_t *f, jl_value_t *rt, jl_value_t *argt)
@@ -1443,7 +1456,6 @@ void *jl_function_ptr_by_llvm_name(char *name) {
14431456
extern "C" JL_DLLEXPORT
14441457
void jl_extern_c(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name)
14451458
{
1446-
assert(jl_is_tuple_type(argt));
14471459
JL_LOCK(&codegen_lock);
14481460
Function *llvmf = jl_cfunction_object(f, rt, (jl_tupletype_t*)argt);
14491461
// force eager emission of the function (llvm 3.3 gets confused otherwise and tries to do recursive compilation)
@@ -4168,9 +4180,9 @@ static void emit_cfunc_invalidate(
41684180
}
41694181
}
41704182

4171-
static Function *gen_cfun_wrapper(const function_sig_t &sig, jl_function_t *ff,
4172-
jl_value_t *jlrettype, jl_tupletype_t *argt,
4173-
jl_typemap_entry_t *sf, jl_value_t *declrt, jl_tupletype_t *sigt)
4183+
static Function*
4184+
gen_cfun_wrapper(const function_sig_t &sig, jl_value_t *ff,
4185+
jl_typemap_entry_t *sf, jl_value_t *declrt, jl_tupletype_t *sigt)
41744186
{
41754187
// Generate a c-callable wrapper
41764188
size_t nargs = sig.nargs;
@@ -4221,8 +4233,19 @@ static Function *gen_cfun_wrapper(const function_sig_t &sig, jl_function_t *ff,
42214233
#endif
42224234
Function *cw_proto = function_proto(cw);
42234235
// Save the Function object reference
4224-
sf->func.value = jl_box_voidpointer((void*)cw_proto);
4225-
jl_gc_wb(sf, sf->func.value);
4236+
{
4237+
jl_value_t *oldsf = sf->func.value;
4238+
size_t i, oldlen = jl_svec_len(oldsf);
4239+
jl_value_t *newsf = (jl_value_t*)jl_alloc_svec(oldlen + 2);
4240+
JL_GC_PUSH1(&newsf);
4241+
jl_svecset(newsf, 0, sig.rt);
4242+
jl_svecset(newsf, 1, jl_box_voidpointer((void*)cw_proto));
4243+
for (i = 0; i < oldlen; i++)
4244+
jl_svecset(newsf, i + 2, jl_svecref(oldsf, i));
4245+
sf->func.value = newsf;
4246+
jl_gc_wb(sf, sf->func.value);
4247+
JL_GC_POP();
4248+
}
42264249

42274250
jl_codectx_t ctx(jl_LLVMContext);
42284251
ctx.f = cw;
@@ -4266,10 +4289,11 @@ static Function *gen_cfun_wrapper(const function_sig_t &sig, jl_function_t *ff,
42664289
Function::arg_iterator AI = cw->arg_begin();
42674290
Value *sretPtr = sig.sret ? &*AI++ : NULL;
42684291
jl_cgval_t *inputargs = (jl_cgval_t*)alloca(sizeof(jl_cgval_t) * (nargs + 1));
4269-
inputargs[0] = mark_julia_const(ff); // we need to pass the function object even if (even though) it is a ghost
4292+
// we need to pass the function object even if (even though) it is a singleton
4293+
inputargs[0] = mark_julia_const(ff);
42704294
for (size_t i = 0; i < nargs; ++i, ++AI) {
42714295
Value *val = &*AI;
4272-
jl_value_t *jargty = jl_nth_slot_type((jl_value_t*)argt, i);
4296+
jl_value_t *jargty = jl_svecref(sig.at, i);
42734297
// figure out how to unpack this type
42744298
jl_cgval_t &inputarg = inputargs[i + 1];
42754299
if (jl_is_abstract_ref_type(jargty)) {
@@ -4521,90 +4545,112 @@ static Function *gen_cfun_wrapper(const function_sig_t &sig, jl_function_t *ff,
45214545
}
45224546

45234547
const struct jl_typemap_info cfunction_cache = {
4524-
1, &jl_voidpointer_type
4548+
1, (jl_datatype_t**)&jl_array_any_type
45254549
};
45264550

4527-
// Get the LLVM Function* for the C-callable entry point for a certain function
4528-
// and argument types.
4529-
// here argt does not include the leading function type argument
4530-
static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_tupletype_t *argt)
4551+
jl_array_t *jl_cfunction_list;
4552+
4553+
static Function *jl_cfunction_cache(jl_value_t *ft, jl_value_t *declrt, jl_tupletype_t *argt)
45314554
{
45324555
// Assumes the codegen lock is acquired. The caller is responsible for that.
4556+
jl_value_t *sigt = NULL; // dispatch sig: type signature (argt) with Ref{} annotations removed and ft added
4557+
JL_GC_PUSH2(&sigt, &ft);
45334558

45344559
// validate and unpack the arguments
4560+
JL_TYPECHK(cfunction, type, (jl_value_t*)ft);
45354561
JL_TYPECHK(cfunction, type, declrt);
4536-
JL_TYPECHK(cfunction, type, (jl_value_t*)argt);
4537-
if (!jl_is_datatype_singleton((jl_datatype_t*)jl_typeof(ff)))
4538-
jl_error("closures are not yet c-callable");
4562+
if (!jl_is_tuple_type(argt))
4563+
jl_type_error("cfunction", (jl_value_t*)jl_anytuple_type_type, (jl_value_t*)argt);
4564+
4565+
// check the cache structure
4566+
// this has three levels (for the 3 parameters above)
4567+
// first split on `ft` using a simple eqtable
4568+
// then use the typemap to split on argt
4569+
// and finally, pick declrt from the pair-list
4570+
union jl_typemap_t cache_l2 = { NULL };
4571+
// cache_l2.unknown = NULL;
4572+
jl_typemap_entry_t *cache_l3 = NULL;
4573+
if (!jl_cfunction_list) {
4574+
jl_cfunction_list = jl_alloc_vec_any(16);
4575+
}
4576+
else {
4577+
cache_l2.unknown = jl_eqtable_get(jl_cfunction_list, ft, NULL);
4578+
if (cache_l2.unknown) {
4579+
cache_l3 = jl_typemap_assoc_by_type(cache_l2, (jl_value_t*)argt, NULL,
4580+
/*subtype*/0, /*offs*/0, /*world*/1, /*max_world_mask*/0);
4581+
if (cache_l3) {
4582+
jl_svec_t *sf = (jl_svec_t*)cache_l3->func.value;
4583+
size_t i, l = jl_svec_len(sf);
4584+
for (i = 0; i < l; i += 2) {
4585+
jl_value_t *ti = jl_svecref(sf, i);
4586+
if (jl_egal(ti, declrt)) {
4587+
JL_GC_POP();
4588+
return (Function*)jl_unbox_voidpointer(jl_svecref(sf, i + 1));
4589+
}
4590+
}
4591+
}
4592+
}
4593+
}
45394594

4540-
size_t i, nargs = jl_nparams(argt);
4541-
jl_value_t *sigt = NULL; // type signature with Ref{} annotations removed
4542-
jl_value_t *cfunc_sig = NULL; // type signature of the call to cfunction (for caching)
4543-
JL_GC_PUSH2(&sigt, &cfunc_sig);
4544-
sigt = (jl_value_t*)jl_alloc_svec(nargs + 1);
4545-
cfunc_sig = (jl_value_t*)jl_alloc_svec(nargs + 2);
4595+
if (cache_l3 == NULL) {
4596+
union jl_typemap_t insert = cache_l2;
4597+
if (!insert.unknown)
4598+
insert.unknown = jl_nothing;
4599+
cache_l3 = jl_typemap_insert(&insert, (jl_value_t*)insert.unknown, (jl_tupletype_t*)argt,
4600+
NULL, jl_emptysvec, (jl_value_t*)jl_emptysvec, /*offs*/0, &cfunction_cache, 1, ~(size_t)0, NULL);
4601+
if (insert.unknown != cache_l2.unknown)
4602+
jl_cfunction_list = jl_eqtable_put(jl_cfunction_list, ft, insert.unknown, NULL);
4603+
}
4604+
4605+
// try to avoid needing a trampoline,
4606+
// if we know that `ft` is some sort of
4607+
// guaranteed singleton type
4608+
jl_value_t *ff = NULL;
4609+
if (jl_is_datatype(ft))
4610+
ff = ((jl_datatype_t*)ft)->instance;
4611+
if (jl_is_type_type(ft)) {
4612+
jl_value_t *tp0 = jl_tparam0(ft);
4613+
if (jl_is_concrete_type(tp0))
4614+
ff = tp0;
4615+
}
4616+
if (!ff)
4617+
jl_error("cfunction: function closures not yet supported");
45464618

4619+
// compute / validate return type
45474620
jl_value_t *crt = declrt;
4548-
jl_svecset(cfunc_sig, nargs + 1, declrt);
45494621
if (jl_is_abstract_ref_type(declrt)) {
45504622
declrt = jl_tparam0(declrt);
45514623
if (jl_is_typevar(declrt))
45524624
jl_error("cfunction: return type Ref should have an element type, not Ref{<:T}");
45534625
if (declrt == (jl_value_t*)jl_any_type)
45544626
jl_error("cfunction: return type Ref{Any} is invalid. Use Any or Ptr{Any} instead.");
4555-
if (!jl_is_concrete_type(declrt))
4556-
jl_svecset(cfunc_sig, nargs + 1, declrt); // Ref{Abstract} is the same calling convention as Abstract
45574627
crt = (jl_value_t*)jl_any_type;
45584628
}
4629+
bool toboxed;
4630+
Type *lcrt = julia_struct_to_llvm(crt, NULL, &toboxed);
4631+
if (lcrt == NULL)
4632+
jl_error("cfunction: return type doesn't correspond to a C type");
4633+
else if (toboxed)
4634+
lcrt = T_prjlvalue;
45594635

4560-
if (jl_is_type(ff))
4561-
jl_svecset(sigt, 0, jl_wrap_Type(ff));
4562-
else
4563-
jl_svecset(sigt, 0, jl_typeof(ff));
4564-
jl_svecset(cfunc_sig, 0, jl_svecref(sigt, 0));
4636+
// compute / validate method signature
4637+
size_t i, nargs = jl_nparams(argt);
4638+
sigt = (jl_value_t*)jl_alloc_svec(nargs + 1);
4639+
jl_svecset(sigt, 0, ft);
45654640
for (i = 0; i < nargs; i++) {
45664641
jl_value_t *ati = jl_tparam(argt, i);
4567-
jl_svecset(cfunc_sig, i + 1, ati);
45684642
if (jl_is_abstract_ref_type(ati)) {
45694643
ati = jl_tparam0(ati);
45704644
if (jl_is_typevar(ati))
45714645
jl_error("cfunction: argument type Ref should have an element type, not Ref{<:T}");
4572-
if (ati != (jl_value_t*)jl_any_type && !jl_is_concrete_type(ati))
4573-
jl_svecset(cfunc_sig, i + 1, ati); // Ref{Abstract} is the same calling convention as Abstract
45744646
}
45754647
if (jl_is_pointer(ati) && jl_is_typevar(jl_tparam0(ati)))
45764648
jl_error("cfunction: argument type Ptr should have an element type, Ptr{<:T}");
45774649
jl_svecset(sigt, i + 1, ati);
45784650
}
45794651
sigt = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)sigt);
4580-
cfunc_sig = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)cfunc_sig);
4581-
4582-
// check the cache
4583-
jl_typemap_entry_t *sf = NULL;
4584-
if (jl_cfunction_list.unknown != jl_nothing) {
4585-
sf = jl_typemap_assoc_by_type(jl_cfunction_list, cfunc_sig, NULL, /*subtype*/0, /*offs*/0, /*world*/1, /*max_world_mask*/0);
4586-
if (sf) {
4587-
jl_value_t *v = sf->func.value;
4588-
if (v) {
4589-
if (jl_is_svec(v))
4590-
v = jl_svecref(v, 0);
4591-
Function *f = (Function*)jl_unbox_voidpointer(v);
4592-
JL_GC_POP();
4593-
return f;
4594-
}
4595-
}
4596-
}
4597-
if (sf == NULL) {
4598-
sf = jl_typemap_insert(&jl_cfunction_list, (jl_value_t*)jl_cfunction_list.unknown, (jl_tupletype_t*)cfunc_sig,
4599-
NULL, jl_emptysvec, NULL, /*offs*/0, &cfunction_cache, 1, ~(size_t)0, NULL);
4600-
}
46014652

4602-
bool toboxed;
4603-
Type *lcrt = julia_struct_to_llvm(crt, NULL, &toboxed);
4604-
if (lcrt == NULL)
4605-
jl_error("cfunction: return type doesn't correspond to a C type");
4606-
else if (toboxed)
4607-
lcrt = T_prjlvalue;
4653+
// emit cfunction (trampoline)
46084654
jl_value_t *err;
46094655
{ // scope block for sig
46104656
function_sig_t sig("cfunction", lcrt, crt, toboxed,
@@ -4616,7 +4662,7 @@ static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_t
46164662
err = NULL;
46174663
}
46184664
else {
4619-
Function *f = gen_cfun_wrapper(sig, ff, crt, (jl_tupletype_t*)argt, sf, declrt, (jl_tupletype_t*)sigt);
4665+
auto f = gen_cfun_wrapper(sig, ff, cache_l3, declrt, (jl_tupletype_t*)sigt);
46204666
JL_GC_POP();
46214667
return f;
46224668
}

src/gc.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2266,7 +2266,8 @@ static void mark_roots(jl_gc_mark_cache_t *gc_cache, gc_mark_sp_t *sp)
22662266
gc_mark_queue_obj(gc_cache, sp, jl_an_empty_vec_any);
22672267
if (jl_module_init_order != NULL)
22682268
gc_mark_queue_obj(gc_cache, sp, jl_module_init_order);
2269-
gc_mark_queue_obj(gc_cache, sp, jl_cfunction_list.unknown);
2269+
if (jl_cfunction_list != NULL)
2270+
gc_mark_queue_obj(gc_cache, sp, jl_cfunction_list);
22702271
gc_mark_queue_obj(gc_cache, sp, jl_anytuple_type_type);
22712272
gc_mark_queue_obj(gc_cache, sp, jl_ANY_flag);
22722273
for (size_t i = 0; i < N_CALL_CACHE; i++)

src/jltypes.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ jl_value_t *jl_interrupt_exception;
129129
jl_datatype_t *jl_boundserror_type;
130130
jl_value_t *jl_memory_exception;
131131
jl_value_t *jl_readonlymemory_exception;
132-
union jl_typemap_t jl_cfunction_list;
133132

134133
jl_cgparams_t jl_default_cgparams = {1, 1, 1, 1, 0, NULL, NULL, NULL};
135134

@@ -2217,8 +2216,6 @@ void jl_init_types(void)
22172216
jl_compute_field_offsets(jl_unionall_type);
22182217
jl_compute_field_offsets(jl_simplevector_type);
22192218
jl_compute_field_offsets(jl_sym_type);
2220-
2221-
jl_cfunction_list.unknown = jl_nothing;
22222219
}
22232220

22242221
#ifdef __cplusplus

src/julia_internal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ jl_array_t *jl_new_array_for_deserialization(jl_value_t *atype, uint32_t ndims,
531531
int isunboxed, int elsz);
532532
void jl_module_run_initializer(jl_module_t *m);
533533
extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED;
534-
extern union jl_typemap_t jl_cfunction_list;
534+
extern jl_array_t *jl_cfunction_list JL_GLOBALLY_ROOTED;
535535

536536
#ifdef JL_USE_INTEL_JITEVENTS
537537
extern char jl_using_intel_jitevents;

0 commit comments

Comments
 (0)