From 437ebe15df68c69d9bc64374080f6c6b7b47d52a Mon Sep 17 00:00:00 2001 From: apaz Date: Wed, 14 Dec 2022 11:25:33 -0600 Subject: [PATCH] Make jl_binding_t into a first-class object (#47592) * Make jl_binding_t into a first-class object (called Binding) * remove special handling for bindings in GC This removes a feature where all bindings were promoted to old if the module containing them was old and cleans up some odd dead code for scanparent. It could be restored explicitly by calling jl_gc_force_mark_old during construction and sweeping. Co-authored-by: Jameson Nash --- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/tfuncs.jl | 4 +- src/builtins.c | 3 +- src/cgutils.cpp | 8 -- src/codegen.cpp | 2 +- src/common_symbols1.inc | 1 - src/common_symbols2.inc | 2 +- src/gc-debug.c | 4 +- src/gc.c | 103 +++--------------------- src/gc.h | 1 - src/jl_exported_data.inc | 1 + src/jltypes.c | 11 +++ src/julia.h | 6 +- src/julia_internal.h | 6 +- src/julia_threads.h | 1 - src/llvm-alloc-helpers.cpp | 3 +- src/llvm-alloc-opt.cpp | 9 +-- src/llvm-final-gc-lowering.cpp | 24 +----- src/llvm-julia-licm.cpp | 5 +- src/llvm-late-gc-lowering.cpp | 8 +- src/llvm-pass-helpers.cpp | 34 +------- src/llvm-pass-helpers.h | 7 -- src/module.c | 12 +-- src/serialize.h | 3 +- src/staticdata.c | 26 +++--- test/core.jl | 8 +- 26 files changed, 72 insertions(+), 222 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 766c27f271b8c..50ccf9ac25e6b 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2490,7 +2490,7 @@ function abstract_eval_globalref(g::GlobalRef) g.binding != C_NULL && return Const(ccall(:jl_binding_value, Any, (Ptr{Cvoid},), g.binding)) return Const(getglobal(g.mod, g.name)) end - ty = ccall(:jl_binding_type, Any, (Any, Any), g.mod, g.name) + ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name) ty === nothing && return Any return ty end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 5dac285de6933..21b08fd8c872c 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2526,7 +2526,7 @@ end function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty)) if isdefined(M, s) && !isconst(M, s) - ty = ccall(:jl_binding_type, Any, (Any, Any), M, s) + ty = ccall(:jl_get_binding_type, Any, (Any, Any), M, s) return ty === nothing || newty ⊑ ty end return false @@ -2536,7 +2536,7 @@ end if M isa Const && s isa Const M, s = M.val, s.val if M isa Module && s isa Symbol - return ccall(:jl_binding_type, Any, (Any, Any), M, s) !== nothing + return ccall(:jl_get_binding_type, Any, (Any, Any), M, s) !== nothing end end return false diff --git a/src/builtins.c b/src/builtins.c index 824f0112d6acb..6ebad43629c8c 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1228,7 +1228,7 @@ JL_CALLABLE(jl_f_get_binding_type) JL_TYPECHK(get_binding_type, symbol, args[1]); jl_module_t *mod = (jl_module_t*)args[0]; jl_sym_t *sym = (jl_sym_t*)args[1]; - jl_value_t *ty = jl_binding_type(mod, sym); + jl_value_t *ty = jl_get_binding_type(mod, sym); if (ty == (jl_value_t*)jl_nothing) { jl_binding_t *b = jl_get_binding_wr(mod, sym, 0); if (b && b->owner == mod) { @@ -2057,6 +2057,7 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin("UpsilonNode", (jl_value_t*)jl_upsilonnode_type); add_builtin("QuoteNode", (jl_value_t*)jl_quotenode_type); add_builtin("NewvarNode", (jl_value_t*)jl_newvarnode_type); + add_builtin("Binding", (jl_value_t*)jl_binding_type); add_builtin("GlobalRef", (jl_value_t*)jl_globalref_type); add_builtin("NamedTuple", (jl_value_t*)jl_namedtuple_type); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index ba13e1cbe86c9..6a70171aea5f8 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -3488,14 +3488,6 @@ static void emit_write_barrier(jl_codectx_t &ctx, Value *parent, ArrayRef decay_ptrs; - decay_ptrs.push_back(maybe_decay_untracked(ctx, emit_bitcast(ctx, parent, ctx.types().T_prjlvalue))); - decay_ptrs.push_back(maybe_decay_untracked(ctx, emit_bitcast(ctx, ptr, ctx.types().T_prjlvalue))); - ctx.builder.CreateCall(prepare_call(jl_write_barrier_binding_func), decay_ptrs); -} - static void find_perm_offsets(jl_datatype_t *typ, SmallVector &res, unsigned offset) { // This is a inlined field at `offset`. diff --git a/src/codegen.cpp b/src/codegen.cpp index 024f30ad576e9..b37364ef80a27 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2604,7 +2604,7 @@ static void emit_globalset(jl_codectx_t &ctx, jl_binding_t *bnd, Value *bp, cons StoreInst *v = ctx.builder.CreateAlignedStore(rval, julia_binding_pvalue(ctx, bp), Align(sizeof(void*))); v->setOrdering(Order); tbaa_decorate(ctx.tbaa().tbaa_binding, v); - emit_write_barrier_binding(ctx, bp, rval); + emit_write_barrier(ctx, bp, rval); return; } } diff --git a/src/common_symbols1.inc b/src/common_symbols1.inc index 867961bc9a1d2..547d5d0eabede 100644 --- a/src/common_symbols1.inc +++ b/src/common_symbols1.inc @@ -96,4 +96,3 @@ jl_symbol("structdiff"), jl_symbol("undef"), jl_symbol("sizeof"), jl_symbol("String"), -jl_symbol("namedtuple.jl"), diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index c9f4e41b83e33..b5a334172dd76 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -1,3 +1,4 @@ +jl_symbol("namedtuple.jl"), jl_symbol("pop"), jl_symbol("inbounds"), jl_symbol("strings/string.jl"), @@ -251,4 +252,3 @@ jl_symbol("view"), jl_symbol("GitError"), jl_symbol("zeros"), jl_symbol("InexactError"), -jl_symbol("LogLevel"), diff --git a/src/gc-debug.c b/src/gc-debug.c index 3f60ca17e0dc4..011788fbc7b2e 100644 --- a/src/gc-debug.c +++ b/src/gc-debug.c @@ -1385,8 +1385,8 @@ NOINLINE void gc_mark_loop_unwind(jl_ptls_t ptls, jl_gc_mark_sp_t sp, int pc_off jl_safe_printf("Mark stack unwind overflow -- ABORTING !!!\n"); break; } - jl_safe_printf("%p: %s Module (bindings) %p (bits %d) -- [%p, %p)\n", - (void*)data, prefix, (void*)data->parent, (int)data->bits, + jl_safe_printf("%p: %s Module (bindings) %p -- [%p, %p)\n", + (void*)data, prefix, (void*)data->parent, (void*)data->begin, (void*)data->end); } else { diff --git a/src/gc.c b/src/gc.c index aebf3300a71a9..92e7e00a6c281 100644 --- a/src/gc.c +++ b/src/gc.c @@ -1467,7 +1467,9 @@ static jl_taggedvalue_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, jl_t else { // marked young or old if (*ages & msk || bits == GC_OLD_MARKED) { // old enough // `!age && bits == GC_OLD_MARKED` is possible for - // non-first-class objects like `jl_binding_t` + // non-first-class objects like array buffers + // (they may get promoted by jl_gc_wb_buf for example, + // or explicitly by jl_gc_force_mark_old) if (sweep_full || bits == GC_MARKED) { bits = v->bits.gc = GC_OLD; // promote } @@ -1751,14 +1753,6 @@ void jl_gc_queue_multiroot(const jl_value_t *parent, const jl_value_t *ptr) JL_N } } -JL_DLLEXPORT void jl_gc_queue_binding(jl_binding_t *bnd) -{ - jl_ptls_t ptls = jl_current_task->ptls; - jl_taggedvalue_t *buf = jl_astaggedvalue(bnd); - buf->bits.gc = GC_MARKED; - arraylist_push(&ptls->heap.rem_bindings, bnd); -} - #ifdef JL_DEBUG_BUILD static void *volatile gc_findval; // for usage from gdb, for finding the gc-root for a value @@ -2543,61 +2537,18 @@ module_binding: { gc_mark_binding_t *binding = gc_pop_markdata(&sp, gc_mark_binding_t); jl_binding_t **begin = binding->begin; jl_binding_t **end = binding->end; - uint8_t mbits = binding->bits; for (; begin < end; begin += 2) { jl_binding_t *b = *begin; if (b == (jl_binding_t*)HT_NOTFOUND) continue; - if (jl_object_in_image((jl_value_t*)b)) { - jl_taggedvalue_t *buf = jl_astaggedvalue(b); - uintptr_t tag = buf->header; - uint8_t bits; - if (!gc_marked(tag)) - gc_setmark_tag(buf, GC_OLD_MARKED, tag, &bits); - } - else { - gc_setmark_buf_(ptls, b, mbits, sizeof(jl_binding_t)); - } - void *vb = jl_astaggedvalue(b); - verify_parent1("module", binding->parent, &vb, "binding_buff"); + verify_parent1("module", binding->parent, begin, "binding_buff"); // Record the size used for the box for non-const bindings gc_heap_snapshot_record_module_to_binding(binding->parent, b); - (void)vb; - jl_value_t *ty = jl_atomic_load_relaxed(&b->ty); - if (ty && ty != (jl_value_t*)jl_any_type) { - verify_parent2("module", binding->parent, - &b->ty, "binding(%s)", jl_symbol_name(b->name)); - if (gc_try_setmark(ty, &binding->nptr, &tag, &bits)) { - new_obj = ty; - gc_repush_markdata(&sp, gc_mark_binding_t); - goto mark; - } - } - jl_value_t *value = jl_atomic_load_relaxed(&b->value); - jl_value_t *globalref = jl_atomic_load_relaxed(&b->globalref); - if (value) { - verify_parent2("module", binding->parent, - &b->value, "binding(%s)", jl_symbol_name(b->name)); - if (gc_try_setmark(value, &binding->nptr, &tag, &bits)) { - new_obj = value; - begin += 2; - binding->begin = begin; - gc_repush_markdata(&sp, gc_mark_binding_t); - uintptr_t gr_tag; - uint8_t gr_bits; - if (gc_try_setmark(globalref, &binding->nptr, &gr_tag, &gr_bits)) { - gc_mark_marked_obj_t data = {globalref, gr_tag, gr_bits}; - gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(marked_obj), - &data, sizeof(data), 1); - } - goto mark; - } - } - if (gc_try_setmark(globalref, &binding->nptr, &tag, &bits)) { + if (gc_try_setmark((jl_value_t*)b, &binding->nptr, &tag, &bits)) { begin += 2; binding->begin = begin; gc_repush_markdata(&sp, gc_mark_binding_t); - new_obj = globalref; + new_obj = (jl_value_t*)b; goto mark; } } @@ -2614,6 +2565,7 @@ module_binding: { gc_mark_objarray_t data = {(jl_value_t*)m, objary_begin, objary_end, 1, binding->nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(objarray), &data, sizeof(data), 0); + // gc_mark_scan_objarray will eventually handle the remset for m if (!scanparent) { objary = (gc_mark_objarray_t*)sp.data; goto objarray_loaded; @@ -2622,6 +2574,7 @@ module_binding: { sp.pc++; } else { + // done with m gc_mark_push_remset(ptls, (jl_value_t*)m, binding->nptr); } if (scanparent) { @@ -2810,7 +2763,7 @@ mark: { jl_binding_t **table = (jl_binding_t**)m->bindings.table; size_t bsize = m->bindings.size; uintptr_t nptr = ((bsize + m->usings.len + 1) << 2) | (bits & GC_OLD); - gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr, bits}; + gc_mark_binding_t markdata = {m, table + 1, table + bsize, nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(module_binding), &markdata, sizeof(markdata), 0); sp.data = (jl_gc_mark_data_t *)(((char*)sp.data) + sizeof(markdata)); @@ -3181,12 +3134,6 @@ static void jl_gc_premark(jl_ptls_t ptls2) objprofile_count(jl_typeof(item), 2, 0); jl_astaggedvalue(item)->bits.gc = GC_OLD_MARKED; } - len = ptls2->heap.rem_bindings.len; - items = ptls2->heap.rem_bindings.items; - for (size_t i = 0; i < len; i++) { - void *ptr = items[i]; - jl_astaggedvalue(ptr)->bits.gc = GC_OLD_MARKED; - } } static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) @@ -3195,29 +3142,6 @@ static void jl_gc_queue_remset(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp void **items = ptls2->heap.last_remset->items; for (size_t i = 0; i < len; i++) gc_mark_queue_scan_obj(gc_cache, sp, (jl_value_t*)items[i]); - int n_bnd_refyoung = 0; - len = ptls2->heap.rem_bindings.len; - items = ptls2->heap.rem_bindings.items; - for (size_t i = 0; i < len; i++) { - jl_binding_t *ptr = (jl_binding_t*)items[i]; - // A null pointer can happen here when the binding is cleaned up - // as an exception is thrown after it was already queued (#10221) - int bnd_refyoung = 0; - jl_value_t *v = jl_atomic_load_relaxed(&ptr->value); - if (v != NULL && gc_mark_queue_obj(gc_cache, sp, v)) - bnd_refyoung = 1; - jl_value_t *ty = jl_atomic_load_relaxed(&ptr->ty); - if (ty != NULL && gc_mark_queue_obj(gc_cache, sp, ty)) - bnd_refyoung = 1; - jl_value_t *globalref = jl_atomic_load_relaxed(&ptr->globalref); - if (globalref != NULL && gc_mark_queue_obj(gc_cache, sp, globalref)) - bnd_refyoung = 1; - if (bnd_refyoung) { - items[n_bnd_refyoung] = ptr; - n_bnd_refyoung++; - } - } - ptls2->heap.rem_bindings.len = n_bnd_refyoung; } static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp, jl_ptls_t ptls2) @@ -3263,7 +3187,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) jl_ptls_t ptls2 = gc_all_tls_states[t_i]; if (ptls2 == NULL) continue; - // 2.1. mark every object in the `last_remsets` and `rem_binding` + // 2.1. mark every object in the `last_remsets` jl_gc_queue_remset(gc_cache, &sp, ptls2); // 2.2. mark every thread local root jl_gc_queue_thread_local(gc_cache, &sp, ptls2); @@ -3438,16 +3362,12 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) continue; if (!sweep_full) { for (int i = 0; i < ptls2->heap.remset->len; i++) { - jl_astaggedvalue(ptls2->heap.remset->items[i])->bits.gc = GC_MARKED; - } - for (int i = 0; i < ptls2->heap.rem_bindings.len; i++) { - void *ptr = ptls2->heap.rem_bindings.items[i]; + void *ptr = ptls2->heap.remset->items[i]; jl_astaggedvalue(ptr)->bits.gc = GC_MARKED; } } else { ptls2->heap.remset->len = 0; - ptls2->heap.rem_bindings.len = 0; } } @@ -3636,7 +3556,6 @@ void jl_init_thread_heap(jl_ptls_t ptls) heap->mallocarrays = NULL; heap->mafreelist = NULL; heap->big_objects = NULL; - arraylist_new(&heap->rem_bindings, 0); heap->remset = &heap->_remset[0]; heap->last_remset = &heap->_remset[1]; arraylist_new(heap->remset, 0); diff --git a/src/gc.h b/src/gc.h index 7b02df69abbc1..dfd4213089880 100644 --- a/src/gc.h +++ b/src/gc.h @@ -191,7 +191,6 @@ typedef struct { jl_binding_t **begin; // The first slot to be scanned. jl_binding_t **end; // The end address (after the last slot to be scanned) uintptr_t nptr; // See notes about `nptr` above. - uint8_t bits; // GC bits of the module (the bits to mark the binding buffer with) } gc_mark_binding_t; // Finalizer (or object) list diff --git a/src/jl_exported_data.inc b/src/jl_exported_data.inc index 6f0671ef0d6f7..a254fba5e2b28 100644 --- a/src/jl_exported_data.inc +++ b/src/jl_exported_data.inc @@ -44,6 +44,7 @@ XX(jl_float64_type) \ XX(jl_floatingpoint_type) \ XX(jl_function_type) \ + XX(jl_binding_type) \ XX(jl_globalref_type) \ XX(jl_gotoifnot_type) \ XX(jl_gotonode_type) \ diff --git a/src/jltypes.c b/src/jltypes.c index b73d5ecab82aa..d77d711f71a3a 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2727,6 +2727,16 @@ void jl_init_types(void) JL_GC_DISABLED jl_value_t *pointer_void = jl_apply_type1((jl_value_t*)jl_pointer_type, (jl_value_t*)jl_nothing_type); + jl_binding_type = + jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, + jl_perm_symsvec(6, "name", "value", "globalref", "owner", "ty", "flags"), + jl_svec(6, jl_symbol_type, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_module_type, jl_any_type, jl_uint8_type), + jl_emptysvec, 0, 1, 1); + const static uint32_t binding_constfields[1] = { 0x0001 }; // Set fields 1 as const + const static uint32_t binding_atomicfields[1] = { 0x0016 }; // Set fields 2, 3, 5 as atomic + jl_binding_type->name->constfields = binding_constfields; + jl_binding_type->name->atomicfields = binding_atomicfields; + jl_globalref_type = jl_new_datatype(jl_symbol("GlobalRef"), core, jl_any_type, jl_emptysvec, jl_perm_symsvec(3, "mod", "name", "binding"), @@ -2772,6 +2782,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_binding_type->types, 2, jl_globalref_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); diff --git a/src/julia.h b/src/julia.h index 2c9be8ae1aa2a..a27b66241793a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -560,7 +560,7 @@ typedef struct { } jl_weakref_t; typedef struct { - // not first-class + JL_DATA_TYPE jl_sym_t *name; _Atomic(jl_value_t*) value; _Atomic(jl_value_t*) globalref; // cached GlobalRef for this binding @@ -779,6 +779,7 @@ extern JL_DLLIMPORT jl_value_t *jl_array_symbol_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_int32_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_value_t *jl_array_uint64_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_expr_type JL_GLOBALLY_ROOTED; +extern JL_DLLIMPORT jl_datatype_t *jl_binding_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_globalref_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_linenumbernode_type JL_GLOBALLY_ROOTED; extern JL_DLLIMPORT jl_datatype_t *jl_gotonode_type JL_GLOBALLY_ROOTED; @@ -1235,6 +1236,7 @@ static inline int jl_is_layout_opaque(const jl_datatype_layout_t *l) JL_NOTSAFEP #define jl_is_ssavalue(v) jl_typeis(v,jl_ssavalue_type) #define jl_is_slot(v) (jl_typeis(v,jl_slotnumber_type) || jl_typeis(v,jl_typedslot_type)) #define jl_is_expr(v) jl_typeis(v,jl_expr_type) +#define jl_is_binding(v) jl_typeis(v,jl_binding_type) #define jl_is_globalref(v) jl_typeis(v,jl_globalref_type) #define jl_is_gotonode(v) jl_typeis(v,jl_gotonode_type) #define jl_is_gotoifnot(v) jl_typeis(v,jl_gotoifnot_type) @@ -1623,7 +1625,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); -JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var); +JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); JL_DLLEXPORT jl_binding_t *jl_get_binding_wr_or_error(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); diff --git a/src/julia_internal.h b/src/julia_internal.h index 1e59cf6f18b5a..f310770bdf9b1 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -560,14 +560,11 @@ void jl_gc_run_all_finalizers(jl_task_t *ct); void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task); void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT; -JL_DLLEXPORT void jl_gc_queue_binding(jl_binding_t *bnd) JL_NOTSAFEPOINT; void gc_setmark_buf(jl_ptls_t ptls, void *buf, uint8_t, size_t) JL_NOTSAFEPOINT; STATIC_INLINE void jl_gc_wb_binding(jl_binding_t *bnd, void *val) JL_NOTSAFEPOINT // val isa jl_value_t* { - if (__unlikely(jl_astaggedvalue(bnd)->bits.gc == 3 && - (jl_astaggedvalue(val)->bits.gc & 1) == 0)) - jl_gc_queue_binding(bnd); + jl_gc_wb(bnd, val); } STATIC_INLINE void jl_gc_wb_buf(void *parent, void *bufptr, size_t minsz) JL_NOTSAFEPOINT // parent isa jl_value_t* @@ -1543,6 +1540,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_return_sym; extern JL_DLLEXPORT jl_sym_t *jl_lineinfo_sym; extern JL_DLLEXPORT jl_sym_t *jl_lambda_sym; extern JL_DLLEXPORT jl_sym_t *jl_assign_sym; +extern JL_DLLEXPORT jl_sym_t *jl_binding_sym; extern JL_DLLEXPORT jl_sym_t *jl_globalref_sym; extern JL_DLLEXPORT jl_sym_t *jl_do_sym; extern JL_DLLEXPORT jl_sym_t *jl_method_sym; diff --git a/src/julia_threads.h b/src/julia_threads.h index 847465b363a2e..ff28765346fbc 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -150,7 +150,6 @@ typedef struct { struct _bigval_t *big_objects; // variables for tracking "remembered set" - arraylist_t rem_bindings; arraylist_t _remset[2]; // contains jl_value_t* // lower bound of the number of pointers inside remembered values int remset_nptr; diff --git a/src/llvm-alloc-helpers.cpp b/src/llvm-alloc-helpers.cpp index 7a80985cf0219..79aa270094d2b 100644 --- a/src/llvm-alloc-helpers.cpp +++ b/src/llvm-alloc-helpers.cpp @@ -213,8 +213,7 @@ void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArg assert(use->get() == I); return true; } - if (required.pass.write_barrier_func == callee || - required.pass.write_barrier_binding_func == callee) + if (required.pass.write_barrier_func == callee) return true; auto opno = use->getOperandNo(); // Uses in `jl_roots` operand bundle are not counted as escaping, everything else is. diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index c04a5cd3af625..a611f71d2bc11 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -662,8 +662,7 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) } return; } - if (pass.write_barrier_func == callee || - pass.write_barrier_binding_func == callee) { + if (pass.write_barrier_func == callee) { ++RemovedWriteBarriers; call->eraseFromParent(); return; @@ -771,8 +770,7 @@ void Optimizer::removeAlloc(CallInst *orig_inst) call->eraseFromParent(); return; } - if (pass.write_barrier_func == callee || - pass.write_barrier_binding_func == callee) { + if (pass.write_barrier_func == callee) { ++RemovedWriteBarriers; call->eraseFromParent(); return; @@ -1070,8 +1068,7 @@ void Optimizer::splitOnStack(CallInst *orig_inst) call->eraseFromParent(); return; } - if (pass.write_barrier_func == callee || - pass.write_barrier_binding_func == callee) { + if (pass.write_barrier_func == callee) { ++RemovedWriteBarriers; call->eraseFromParent(); return; diff --git a/src/llvm-final-gc-lowering.cpp b/src/llvm-final-gc-lowering.cpp index 2eb89a15692d9..8c71c0bae5841 100644 --- a/src/llvm-final-gc-lowering.cpp +++ b/src/llvm-final-gc-lowering.cpp @@ -26,7 +26,6 @@ STATISTIC(PopGCFrameCount, "Number of lowered popGCFrameFunc intrinsics"); STATISTIC(GetGCFrameSlotCount, "Number of lowered getGCFrameSlotFunc intrinsics"); STATISTIC(GCAllocBytesCount, "Number of lowered GCAllocBytesFunc intrinsics"); STATISTIC(QueueGCRootCount, "Number of lowered queueGCRootFunc intrinsics"); -STATISTIC(QueueGCBindingCount, "Number of lowered queueGCBindingFunc intrinsics"); using namespace llvm; @@ -46,7 +45,6 @@ struct FinalLowerGC: private JuliaPassContext { private: Function *queueRootFunc; - Function *queueBindingFunc; Function *poolAllocFunc; Function *bigAllocFunc; Instruction *pgcstack; @@ -68,9 +66,6 @@ struct FinalLowerGC: private JuliaPassContext { // Lowers a `julia.queue_gc_root` intrinsic. Value *lowerQueueGCRoot(CallInst *target, Function &F); - - // Lowers a `julia.queue_gc_binding` intrinsic. - Value *lowerQueueGCBinding(CallInst *target, Function &F); }; Value *FinalLowerGC::lowerNewGCFrame(CallInst *target, Function &F) @@ -193,14 +188,6 @@ Value *FinalLowerGC::lowerQueueGCRoot(CallInst *target, Function &F) return target; } -Value *FinalLowerGC::lowerQueueGCBinding(CallInst *target, Function &F) -{ - ++QueueGCBindingCount; - assert(target->arg_size() == 1); - target->setCalledFunction(queueBindingFunc); - return target; -} - Value *FinalLowerGC::lowerGCAllocBytes(CallInst *target, Function &F) { ++GCAllocBytesCount; @@ -234,11 +221,10 @@ bool FinalLowerGC::doInitialization(Module &M) { // Initialize platform-specific references. queueRootFunc = getOrDeclare(jl_well_known::GCQueueRoot); - queueBindingFunc = getOrDeclare(jl_well_known::GCQueueBinding); poolAllocFunc = getOrDeclare(jl_well_known::GCPoolAlloc); bigAllocFunc = getOrDeclare(jl_well_known::GCBigAlloc); - GlobalValue *functionList[] = {queueRootFunc, queueBindingFunc, poolAllocFunc, bigAllocFunc}; + GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc}; unsigned j = 0; for (unsigned i = 0; i < sizeof(functionList) / sizeof(void*); i++) { if (!functionList[i]) @@ -254,8 +240,8 @@ bool FinalLowerGC::doInitialization(Module &M) { bool FinalLowerGC::doFinalization(Module &M) { - GlobalValue *functionList[] = {queueRootFunc, queueBindingFunc, poolAllocFunc, bigAllocFunc}; - queueRootFunc = queueBindingFunc = poolAllocFunc = bigAllocFunc = nullptr; + GlobalValue *functionList[] = {queueRootFunc, poolAllocFunc, bigAllocFunc}; + queueRootFunc = poolAllocFunc = bigAllocFunc = nullptr; auto used = M.getGlobalVariable("llvm.compiler.used"); if (!used) return false; @@ -320,7 +306,6 @@ bool FinalLowerGC::runOnFunction(Function &F) auto getGCFrameSlotFunc = getOrNull(jl_intrinsics::getGCFrameSlot); auto GCAllocBytesFunc = getOrNull(jl_intrinsics::GCAllocBytes); auto queueGCRootFunc = getOrNull(jl_intrinsics::queueGCRoot); - auto queueGCBindingFunc = getOrNull(jl_intrinsics::queueGCBinding); // Lower all calls to supported intrinsics. for (BasicBlock &BB : F) { @@ -353,9 +338,6 @@ bool FinalLowerGC::runOnFunction(Function &F) else if (callee == queueGCRootFunc) { replaceInstruction(CI, lowerQueueGCRoot(CI, F), it); } - else if (callee == queueGCBindingFunc) { - replaceInstruction(CI, lowerQueueGCBinding(CI, F), it); - } else { ++it; } diff --git a/src/llvm-julia-licm.cpp b/src/llvm-julia-licm.cpp index d641d61ca126b..28dddf18c5394 100644 --- a/src/llvm-julia-licm.cpp +++ b/src/llvm-julia-licm.cpp @@ -156,7 +156,7 @@ struct JuliaLICM : public JuliaPassContext { // `gc_preserve_end_func` is optional since the input to // `gc_preserve_end_func` must be from `gc_preserve_begin_func`. // We also hoist write barriers here, so we don't exit if write_barrier_func exists - if (!gc_preserve_begin_func && !write_barrier_func && !write_barrier_binding_func && + if (!gc_preserve_begin_func && !write_barrier_func && !alloc_obj_func) return false; auto LI = &GetLI(); @@ -235,8 +235,7 @@ struct JuliaLICM : public JuliaPassContext { createNewInstruction(CI, call, MSSAU); } } - else if (callee == write_barrier_func || - callee == write_barrier_binding_func) { + else if (callee == write_barrier_func) { bool valid = true; for (std::size_t i = 0; i < call->arg_size(); i++) { if (!makeLoopInvariant(L, call->getArgOperand(i), diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index 08376426b855d..20fc78b4d742f 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -1607,7 +1607,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { callee == gc_preserve_end_func || callee == typeof_func || callee == pgcstack_getter || callee->getName() == XSTR(jl_egal__unboxed) || callee->getName() == XSTR(jl_lock_value) || callee->getName() == XSTR(jl_unlock_value) || - callee == write_barrier_func || callee == write_barrier_binding_func || + callee == write_barrier_func || callee->getName() == "memcmp") { continue; } @@ -2420,8 +2420,7 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { typ->takeName(CI); CI->replaceAllUsesWith(typ); UpdatePtrNumbering(CI, typ, S); - } else if ((write_barrier_func && callee == write_barrier_func) || - (write_barrier_binding_func && callee == write_barrier_binding_func)) { + } else if (write_barrier_func && callee == write_barrier_func) { // The replacement for this requires creating new BasicBlocks // which messes up the loop. Queue all of them to be replaced later. assert(CI->arg_size() >= 1); @@ -2533,9 +2532,6 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) { if (CI->getCalledOperand() == write_barrier_func) { builder.CreateCall(getOrDeclare(jl_intrinsics::queueGCRoot), parent); } - else if (CI->getCalledOperand() == write_barrier_binding_func) { - builder.CreateCall(getOrDeclare(jl_intrinsics::queueGCBinding), parent); - } else { assert(false); } diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index 3b55339984516..8e4045d14b80d 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -27,8 +27,7 @@ JuliaPassContext::JuliaPassContext() gc_preserve_begin_func(nullptr), gc_preserve_end_func(nullptr), pointer_from_objref_func(nullptr), alloc_obj_func(nullptr), typeof_func(nullptr), write_barrier_func(nullptr), - write_barrier_binding_func(nullptr), call_func(nullptr), - call2_func(nullptr), module(nullptr) + call_func(nullptr), call2_func(nullptr), module(nullptr) { } @@ -51,7 +50,6 @@ void JuliaPassContext::initFunctions(Module &M) pointer_from_objref_func = M.getFunction("julia.pointer_from_objref"); typeof_func = M.getFunction("julia.typeof"); write_barrier_func = M.getFunction("julia.write_barrier"); - write_barrier_binding_func = M.getFunction("julia.write_barrier_binding"); alloc_obj_func = M.getFunction("julia.gc_alloc_obj"); call_func = M.getFunction("julia.call"); call2_func = M.getFunction("julia.call2"); @@ -118,7 +116,6 @@ namespace jl_intrinsics { static const char *PUSH_GC_FRAME_NAME = "julia.push_gc_frame"; static const char *POP_GC_FRAME_NAME = "julia.pop_gc_frame"; static const char *QUEUE_GC_ROOT_NAME = "julia.queue_gc_root"; - static const char *QUEUE_GC_BINDING_NAME = "julia.queue_gc_binding"; // Annotates a function with attributes suitable for GC allocation // functions. Specifically, the return value is marked noalias and nonnull. @@ -210,27 +207,12 @@ namespace jl_intrinsics { intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); return intrinsic; }); - - const IntrinsicDescription queueGCBinding( - QUEUE_GC_BINDING_NAME, - [](const JuliaPassContext &context) { - auto intrinsic = Function::Create( - FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - { context.T_prjlvalue }, - false), - Function::ExternalLinkage, - QUEUE_GC_BINDING_NAME); - intrinsic->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); - return intrinsic; - }); } namespace jl_well_known { static const char *GC_BIG_ALLOC_NAME = XSTR(jl_gc_big_alloc); static const char *GC_POOL_ALLOC_NAME = XSTR(jl_gc_pool_alloc); static const char *GC_QUEUE_ROOT_NAME = XSTR(jl_gc_queue_root); - static const char *GC_QUEUE_BINDING_NAME = XSTR(jl_gc_queue_binding); using jl_intrinsics::addGCAllocAttributes; @@ -265,20 +247,6 @@ namespace jl_well_known { return addGCAllocAttributes(poolAllocFunc, context.getLLVMContext()); }); - const WellKnownFunctionDescription GCQueueBinding( - GC_QUEUE_BINDING_NAME, - [](const JuliaPassContext &context) { - auto func = Function::Create( - FunctionType::get( - Type::getVoidTy(context.getLLVMContext()), - { context.T_prjlvalue }, - false), - Function::ExternalLinkage, - GC_QUEUE_BINDING_NAME); - func->addFnAttr(Attribute::InaccessibleMemOrArgMemOnly); - return func; - }); - const WellKnownFunctionDescription GCQueueRoot( GC_QUEUE_ROOT_NAME, [](const JuliaPassContext &context) { diff --git a/src/llvm-pass-helpers.h b/src/llvm-pass-helpers.h index 68f6efe42be6d..4774f87612871 100644 --- a/src/llvm-pass-helpers.h +++ b/src/llvm-pass-helpers.h @@ -58,7 +58,6 @@ struct JuliaPassContext { llvm::Function *alloc_obj_func; llvm::Function *typeof_func; llvm::Function *write_barrier_func; - llvm::Function *write_barrier_binding_func; llvm::Function *call_func; llvm::Function *call2_func; @@ -126,9 +125,6 @@ namespace jl_intrinsics { // `julia.queue_gc_root`: an intrinsic that queues a GC root. extern const IntrinsicDescription queueGCRoot; - - // `julia.queue_gc_binding`: an intrinsic that queues a binding for GC. - extern const IntrinsicDescription queueGCBinding; } // A namespace for well-known Julia runtime function descriptions. @@ -149,9 +145,6 @@ namespace jl_well_known { // `jl_gc_queue_root`: queues a GC root. extern const WellKnownFunctionDescription GCQueueRoot; - - // `jl_gc_queue_binding`: queues a binding for GC. - extern const WellKnownFunctionDescription GCQueueBinding; } #endif diff --git a/src/module.c b/src/module.c index ec62e6d83f2aa..f4187d23ad462 100644 --- a/src/module.c +++ b/src/module.c @@ -160,7 +160,7 @@ static jl_binding_t *new_binding(jl_sym_t *name) { jl_task_t *ct = jl_current_task; assert(jl_is_symbol(name)); - jl_binding_t *b = (jl_binding_t*)jl_gc_alloc_buf(ct->ptls, sizeof(jl_binding_t)); + jl_binding_t *b = (jl_binding_t*)jl_gc_alloc(ct->ptls, sizeof(jl_binding_t), jl_binding_type); b->name = name; jl_atomic_store_relaxed(&b->value, NULL); b->owner = NULL; @@ -197,7 +197,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, b->owner = m; *bp = b; JL_GC_PROMISE_ROOTED(b); - jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); + jl_gc_wb(m, b); } else { b = NULL; @@ -263,7 +263,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_ b->owner = m; *bp = b; JL_GC_PROMISE_ROOTED(b); - jl_gc_wb_buf(m, b, sizeof(jl_binding_t)); + jl_gc_wb(m, b); } JL_UNLOCK(&m->lock); @@ -393,7 +393,7 @@ JL_DLLEXPORT jl_value_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) } // get type of binding m.var, without resolving the binding -JL_DLLEXPORT jl_value_t *jl_binding_type(jl_module_t *m, jl_sym_t *var) +JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { JL_LOCK(&m->lock); jl_binding_t *b = _jl_get_module_binding(m, var); @@ -568,7 +568,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_s nb->imported = (explici!=0); nb->deprecated = b->deprecated; *bp = nb; - jl_gc_wb_buf(to, nb, sizeof(jl_binding_t)); + jl_gc_wb(to, nb); } JL_UNLOCK(&to->lock); } @@ -647,7 +647,7 @@ JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) // don't yet know who the owner is b->owner = NULL; *bp = b; - jl_gc_wb_buf(from, b, sizeof(jl_binding_t)); + jl_gc_wb(from, b); } assert(*bp != HT_NOTFOUND); (*bp)->exportp = 1; diff --git a/src/serialize.h b/src/serialize.h index 020cafc74c962..afcdcc31d66c4 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -63,8 +63,9 @@ extern "C" { #define TAG_RETURNNODE 55 #define TAG_ARGUMENT 56 #define TAG_RELOC_METHODROOT 57 +#define TAG_BINDING 58 -#define LAST_TAG 57 +#define LAST_TAG 58 #define write_uint8(s, n) ios_putc((n), (s)) #define read_uint8(s) ((uint8_t)ios_getc((s))) diff --git a/src/staticdata.c b/src/staticdata.c index 2098596b9b612..786d0c966693b 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -98,7 +98,7 @@ extern "C" { // TODO: put WeakRefs on the weak_refs list during deserialization // TODO: handle finalizers -#define NUM_TAGS 157 +#define NUM_TAGS 158 // An array of references that need to be restored from the sysimg // This is a manually constructed dual of the gvars array, which would be produced by codegen for Julia code, for C. @@ -120,6 +120,7 @@ jl_value_t **const*const get_tags(void) { INSERT_TAG(jl_array_type); INSERT_TAG(jl_typedslot_type); INSERT_TAG(jl_expr_type); + INSERT_TAG(jl_binding_type); INSERT_TAG(jl_globalref_type); INSERT_TAG(jl_string_type); INSERT_TAG(jl_module_type); @@ -383,7 +384,6 @@ enum RefTags { ConstDataRef, // constant data (e.g., layouts) TagRef, // items serialized via their tags SymbolRef, // symbols - BindingRef, // module bindings FunctionRef, // generic functions BuiltinFunctionRef, // builtin functions ExternalLinkage // items defined externally (used when serializing packages) @@ -425,11 +425,6 @@ static void write_reloc_t(ios_t *s, uintptr_t reloc_id) JL_NOTSAFEPOINT } } -static int jl_is_binding(uintptr_t v) JL_NOTSAFEPOINT -{ - return jl_typeis(v, (jl_datatype_t*)jl_buff_tag); -} - // Reporting to PkgCacheInspector typedef struct { size_t sysdata; @@ -971,11 +966,11 @@ static void write_pointerfield(jl_serializer_state *s, jl_value_t *fld) JL_NOTSA // Save blank space in stream `s` for a pointer `fld`, storing both location and target // in `gctags_list`. -static void write_gctaggedfield(jl_serializer_state *s, uintptr_t ref) JL_NOTSAFEPOINT +static void write_gctaggedfield(jl_serializer_state *s, jl_datatype_t *ref) JL_NOTSAFEPOINT { // jl_printf(JL_STDOUT, "gctaggedfield: position %p, value 0x%lx\n", (void*)(uintptr_t)ios_pos(s->s), ref); arraylist_push(&s->gctags_list, (void*)(uintptr_t)ios_pos(s->s)); - arraylist_push(&s->gctags_list, (void*)ref); + arraylist_push(&s->gctags_list, (void*)backref_id(s, ref, s->link_ids_gctags)); write_pointer(s->s); } @@ -1009,7 +1004,7 @@ static void jl_write_module(jl_serializer_state *s, uintptr_t item, jl_module_t jl_binding_t *b = (jl_binding_t*)table[i+1]; write_pointerfield(s, (jl_value_t*)table[i]); tot += sizeof(void*); - write_gctaggedfield(s, (uintptr_t)BindingRef << RELOC_TAG_OFFSET); + write_gctaggedfield(s, jl_binding_type); tot += sizeof(void*); size_t binding_reloc_offset = ios_pos(s->s); ptrhash_put(&bindings, b, (void*)(((uintptr_t)DataRef << RELOC_TAG_OFFSET) + binding_reloc_offset)); @@ -1108,7 +1103,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED // write header if (s->incremental && jl_needs_serialization(s, (jl_value_t*)t) && needs_uniquing((jl_value_t*)t)) arraylist_push(&s->uniquing_types, (void*)(uintptr_t)(ios_pos(s->s)|1)); - write_gctaggedfield(s, backref_id(s, t, s->link_ids_gctags)); + write_gctaggedfield(s, t); size_t reloc_offset = ios_pos(s->s); assert(item < layout_table.len && layout_table.items[item] == NULL); layout_table.items[item] = (void*)reloc_offset; // store the inverse mapping of `serialization_order` (`id` => object-as-streampos) @@ -1238,6 +1233,9 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (jl_typeis(v, jl_task_type)) { jl_error("Task cannot be serialized"); } + else if (jl_typeis(v, jl_binding_type)) { + jl_error("Binding cannot be serialized"); // no way (currently) to recover its identity + } else if (jl_is_svec(v)) { ios_write(s->s, (char*)v, sizeof(void*)); size_t ii, l = jl_svec_len(v); @@ -1430,6 +1428,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED else if (jl_is_globalref(v)) { jl_globalref_t *newg = (jl_globalref_t*)&s->s->buf[reloc_offset]; // Don't save the cached binding reference in staticdata + // (it does not happen automatically since we declare the struct immutable) // TODO: this should be a relocation pointing to the binding in the new image newg->bnd_cache = NULL; if (s->incremental) @@ -1549,9 +1548,6 @@ static uintptr_t get_reloc_for_item(uintptr_t reloc_item, size_t reloc_offset) case TagRef: assert(offset < 2 * NBOX_C + 258 && "corrupt relocation item id"); break; - case BindingRef: - assert(offset == 0 && "corrupt relocation offset"); - break; case BuiltinFunctionRef: assert(offset < sizeof(id_to_fptrs) / sizeof(*id_to_fptrs) && "unknown function pointer id"); break; @@ -1584,8 +1580,6 @@ static inline uintptr_t get_item_for_reloc(jl_serializer_state *s, uintptr_t bas case SymbolRef: assert(offset < deser_sym.len && deser_sym.items[offset] && "corrupt relocation item id"); return (uintptr_t)deser_sym.items[offset]; - case BindingRef: - return jl_buff_tag | GC_OLD; case TagRef: if (offset == 0) return (uintptr_t)s->ptls->root_task; diff --git a/test/core.jl b/test/core.jl index bab6be0de5644..96ec765235adb 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7870,15 +7870,15 @@ end @test methods(SpecializeModuleTest.f)[1].nospecialize & 0b11 == 0b10 let # https://github.com/JuliaLang/julia/issues/46918 - # jl_binding_type shouldn't be unstable + # jl_get_binding_type shouldn't be unstable code = quote - res1 = ccall(:jl_binding_type, Any, (Any, Any), Main, :stderr) + res1 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) stderr - res2 = ccall(:jl_binding_type, Any, (Any, Any), Main, :stderr) + res2 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) - res3 = ccall(:jl_binding_type, Any, (Any, Any), Main, :stderr) + res3 = ccall(:jl_get_binding_type, Any, (Any, Any), Main, :stderr) print(stdout, res1, " ", res2, " ", res3) end |> x->join(x.args, ';')