From 91be20bff112d6e5f8a50230c656e64312e1abdf Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 28 May 2024 23:27:17 +0000 Subject: [PATCH] add edges metadata field to CodeInfo/CodeInstance, prepare for using This records all invoke targets as edges as a functionality test, before finishing the implementation of recording the edges accurately during inference (via backedges + inference). --- base/boot.jl | 6 +-- base/compiler/typeinfer.jl | 28 +++++++----- base/expr.jl | 2 +- src/common_symbols1.inc | 2 - src/common_symbols2.inc | 4 +- src/gf.c | 58 ++++++++++++++---------- src/ircode.c | 62 ++++++++++++++++++++++++- src/jltypes.c | 14 +++--- src/julia.h | 1 + src/julia_internal.h | 5 ++- src/method.c | 38 +++++++++++----- src/opaque_closure.c | 12 +++-- src/serialize.h | 90 +++++++++++++++++++------------------ src/toplevel.c | 2 +- test/compiler/contextual.jl | 2 +- test/core.jl | 2 +- test/precompile.jl | 5 ++- 17 files changed, 217 insertions(+), 116 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 3d19e46bf5d5d..432b6061cb358 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -510,11 +510,11 @@ function CodeInstance( mi::MethodInstance, owner, @nospecialize(rettype), @nospecialize(exctype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, effects::UInt32, @nospecialize(analysis_results), - relocatability::UInt8, edges::Union{DebugInfo,Nothing}) + relocatability::UInt8, di::Union{DebugInfo,Nothing}, edges::SimpleVector) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, Any, UInt8, Any), + (Any, Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, Any, UInt8, Any, Any), mi, owner, rettype, exctype, inferred_const, inferred, const_flags, min_world, max_world, - effects, analysis_results, relocatability, edges) + effects, analysis_results, relocatability, di, edges) end GlobalRef(m::Module, s::Symbol) = ccall(:jl_module_globalref, Ref{GlobalRef}, (Any, Any), m, s) Module(name::Symbol=:anonymous, std_imports::Bool=true, default_names::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool, Bool), name, std_imports, default_names) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index c49a49d2e4bf7..e67c78a7a8ea0 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -224,13 +224,16 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState; if isdefined(result, :ci) ci = result.ci inferred_result = nothing + edges = Core.svec() # This should be a computed input, for now it is approximated (badly) here relocatability = 0x1 const_flag = is_result_constabi_eligible(result) if !can_discard_trees || (is_cached(caller) && !const_flag) inferred_result = transform_result_for_cache(interp, result.linfo, result.valid_worlds, result, can_discard_trees) relocatability = 0x0 if inferred_result isa CodeInfo - edges = inferred_result.debuginfo + edges = ccall(:jl_ir_edges_legacy, Any, (Any,), inferred_result.code) + inferred_result.edges = edges + di = inferred_result.debuginfo uncompressed = inferred_result inferred_result = maybe_compress_codeinfo(interp, result.linfo, inferred_result, can_discard_trees) result.is_src_volatile |= uncompressed !== inferred_result @@ -245,14 +248,14 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState; end end # n.b. relocatability = isa(inferred_result, String) && inferred_result[end] - if !@isdefined edges - edges = DebugInfo(result.linfo) + if !@isdefined di + di = DebugInfo(result.linfo) end - ccall(:jl_update_codeinst, Cvoid, (Any, Any, Int32, UInt, UInt, UInt32, Any, UInt8, Any), + ccall(:jl_update_codeinst, Cvoid, (Any, Any, Int32, UInt, UInt, UInt32, Any, UInt8, Any, Any), ci, inferred_result, const_flag, first(result.valid_worlds), last(result.valid_worlds), encode_effects(result.ipo_effects), result.analysis_results, - relocatability, edges) + relocatability, di, edges) engine_reject(interp, ci) end return nothing @@ -334,7 +337,6 @@ function is_result_constabi_eligible(result::InferenceResult) return isa(result_type, Const) && is_foldable_nothrow(result.ipo_effects) && is_inlineable_constant(result_type.val) end - function transform_result_for_cache(interp::AbstractInterpreter, ::MethodInstance, valid_worlds::WorldRange, result::InferenceResult, can_discard_trees::Bool=may_discard_trees(interp)) @@ -528,7 +530,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) append!(s_edges, edges) empty!(edges) end - if me.src.edges !== nothing + if me.src.edges !== nothing && me.src.edges !== Core.svec() append!(s_edges, me.src.edges::Vector) end # inspect whether our inference had a limited result accuracy, @@ -613,11 +615,12 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) const_flags = 0x00 end relocatability = 0x0 - edges = nothing - ccall(:jl_fill_codeinst, Cvoid, (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, Any, Any), + di = nothing + edges = Core.svec() + ccall(:jl_fill_codeinst, Cvoid, (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, Any, Any, Any), result.ci, widenconst(result_type), widenconst(result.exc_result), rettype_const, const_flags, first(result.valid_worlds), last(result.valid_worlds), - encode_effects(result.ipo_effects), result.analysis_results, edges) + encode_effects(result.ipo_effects), result.analysis_results, di, edges) if is_cached(me) cached_results = cache_result!(me.interp, me.result) if !cached_results @@ -991,6 +994,7 @@ function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, @no tree.debuginfo = DebugInfo(mi) tree.ssaflags = UInt32[0] tree.rettype = Core.Typeof(val) + tree.edges = Core.svec() set_inlineable!(tree, true) tree.parent = mi return tree @@ -1008,7 +1012,7 @@ function codeinstance_for_const_with_code(interp::AbstractInterpreter, code::Cod return CodeInstance(code.def, cache_owner(interp), code.rettype, code.exctype, code.rettype_const, src, Int32(0x3), code.min_world, code.max_world, code.ipo_purity_bits, code.analysis_results, - code.relocatability, src.debuginfo) + code.relocatability, src.debuginfo, src.edges) end result_is_constabi(interp::AbstractInterpreter, result::InferenceResult, @@ -1170,7 +1174,7 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod src isa CodeInfo || return nothing return CodeInstance(mi, cache_owner(interp), Any, Any, nothing, src, Int32(0), get_inference_world(interp), get_inference_world(interp), - UInt32(0), nothing, UInt8(0), src.debuginfo) + UInt32(0), nothing, UInt8(0), src.debuginfo, src.edges) end end ci = engine_reserve(interp, mi) diff --git a/base/expr.jl b/base/expr.jl index b92332c7bbfd8..e19aba275569f 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -79,7 +79,7 @@ function copy(c::CodeInfo) cnew.slottypes = copy(cnew.slottypes::Vector{Any}) end cnew.ssaflags = copy(cnew.ssaflags) - cnew.edges = cnew.edges === nothing ? nothing : copy(cnew.edges::Vector) + cnew.edges = cnew.edges === nothing || cnew.edges isa Core.SimpleVector ? cnew.edges : copy(cnew.edges::Vector) ssavaluetypes = cnew.ssavaluetypes ssavaluetypes isa Vector{Any} && (cnew.ssavaluetypes = copy(ssavaluetypes)) return cnew diff --git a/src/common_symbols1.inc b/src/common_symbols1.inc index f54be52729a4f..3dfcf17a07b5c 100644 --- a/src/common_symbols1.inc +++ b/src/common_symbols1.inc @@ -88,5 +88,3 @@ jl_symbol("ifelse"), jl_symbol("Array"), jl_symbol("eq_int"), jl_symbol("throw_inexacterror"), -jl_symbol("|"), -jl_symbol("setproperty!"), diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index ee2a0e2edd9fe..2a6990bac52ff 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -1,3 +1,5 @@ +jl_symbol("|"), +jl_symbol("setproperty!"), jl_symbol("sext_int"), jl_symbol("String"), jl_symbol("Int"), @@ -244,5 +246,3 @@ jl_symbol("invokelatest"), jl_symbol("jl_array_del_end"), jl_symbol("_mod64"), jl_symbol("parameters"), -jl_symbol("monotonic"), -jl_symbol("regex.jl"), diff --git a/src/gf.c b/src/gf.c index 5c7d4b77026aa..960f897bcf00c 100644 --- a/src/gf.c +++ b/src/gf.c @@ -322,7 +322,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL); + 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); jl_atomic_store_relaxed(&codeinst->invoke, jl_fptr_args); @@ -480,7 +480,7 @@ JL_DLLEXPORT jl_value_t *jl_call_in_typeinf_world(jl_value_t **args, int nargs) JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, - size_t min_world, size_t max_world, jl_debuginfo_t *edges) + size_t min_world, size_t max_world, jl_debuginfo_t *di, jl_svec_t *edges) { jl_value_t *owner = jl_nothing; // TODO: owner should be arg jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); @@ -489,27 +489,30 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_atomic_load_relaxed(&codeinst->max_world) == max_world && jl_egal(codeinst->owner, owner) && jl_egal(codeinst->rettype, rettype)) { - if (edges == NULL) + if (di == NULL) return codeinst; jl_debuginfo_t *debuginfo = jl_atomic_load_relaxed(&codeinst->debuginfo); - if (edges == debuginfo) - return codeinst; - if (debuginfo == NULL && jl_atomic_cmpswap_relaxed(&codeinst->debuginfo, &debuginfo, edges)) - return codeinst; - if (debuginfo && jl_egal((jl_value_t*)debuginfo, (jl_value_t*)edges)) + if (di != debuginfo) { + if (!(debuginfo == NULL && jl_atomic_cmpswap_relaxed(&codeinst->debuginfo, &debuginfo, di))) + if (!(debuginfo && jl_egal((jl_value_t*)debuginfo, (jl_value_t*)di))) + continue; + } + // TODO: this is implied by the matching worlds, since it is intrinsic, so do we really need to verify it? + jl_svec_t *e = jl_atomic_load_relaxed(&codeinst->edges); + if (e && jl_egal((jl_value_t*)e, (jl_value_t*)edges)) return codeinst; } codeinst = jl_atomic_load_relaxed(&codeinst->next); } codeinst = jl_new_codeinst( mi, owner, rettype, (jl_value_t*)jl_any_type, NULL, NULL, - 0, min_world, max_world, 0, jl_nothing, 0, edges); + 0, min_world, max_world, 0, jl_nothing, 0, di, edges); jl_mi_cache_insert(mi, codeinst); return codeinst; } JL_DLLEXPORT int jl_mi_cache_has_ci(jl_method_instance_t *mi, - jl_code_instance_t *ci) + jl_code_instance_t *ci) { jl_code_instance_t *codeinst = jl_atomic_load_relaxed(&mi->cache); while (codeinst) { @@ -527,7 +530,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( int32_t const_flags, size_t min_world, size_t max_world, uint32_t effects, jl_value_t *analysis_results, uint8_t relocatability, - jl_debuginfo_t *edges /* , int absolute_max*/) + jl_debuginfo_t *di, jl_svec_t *edges /*, int absolute_max*/) { jl_task_t *ct = jl_current_task; assert(min_world <= max_world && "attempting to set invalid world constraints"); @@ -535,6 +538,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_code_instance_type); codeinst->def = mi; codeinst->owner = owner; + jl_atomic_store_relaxed(&codeinst->edges, edges); jl_atomic_store_relaxed(&codeinst->min_world, min_world); jl_atomic_store_relaxed(&codeinst->max_world, max_world); codeinst->rettype = rettype; @@ -543,7 +547,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( if ((const_flags & 2) == 0) inferred_const = NULL; codeinst->rettype_const = inferred_const; - jl_atomic_store_relaxed(&codeinst->debuginfo, (jl_value_t*)edges == jl_nothing ? NULL : edges); + jl_atomic_store_relaxed(&codeinst->debuginfo, (jl_value_t*)di == jl_nothing ? NULL : di); jl_atomic_store_relaxed(&codeinst->specptr.fptr, NULL); jl_atomic_store_relaxed(&codeinst->invoke, NULL); if ((const_flags & 1) != 0) { @@ -563,13 +567,15 @@ JL_DLLEXPORT void jl_update_codeinst( jl_code_instance_t *codeinst, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, uint32_t effects, jl_value_t *analysis_results, - uint8_t relocatability, jl_debuginfo_t *edges /* , int absolute_max*/) + uint8_t relocatability, jl_debuginfo_t *di, jl_svec_t *edges /* , int absolute_max*/) { codeinst->relocatability = relocatability; codeinst->analysis_results = analysis_results; jl_gc_wb(codeinst, analysis_results); jl_atomic_store_relaxed(&codeinst->ipo_purity_bits, effects); - jl_atomic_store_relaxed(&codeinst->debuginfo, edges); + jl_atomic_store_relaxed(&codeinst->debuginfo, di); + jl_gc_wb(codeinst, di); + jl_atomic_store_relaxed(&codeinst->edges, edges); jl_gc_wb(codeinst, edges); if ((const_flags & 1) != 0) { assert(codeinst->rettype_const); @@ -587,7 +593,7 @@ JL_DLLEXPORT void jl_fill_codeinst( jl_value_t *inferred_const, int32_t const_flags, size_t min_world, size_t max_world, uint32_t effects, jl_value_t *analysis_results, - jl_debuginfo_t *edges /* , int absolute_max*/) + jl_debuginfo_t *di, jl_svec_t *edges /* , int absolute_max*/) { assert(min_world <= max_world && "attempting to set invalid world constraints"); codeinst->rettype = rettype; @@ -598,8 +604,12 @@ JL_DLLEXPORT void jl_fill_codeinst( codeinst->rettype_const = inferred_const; jl_gc_wb(codeinst, inferred_const); } - jl_atomic_store_relaxed(&codeinst->debuginfo, (jl_value_t*)edges == jl_nothing ? NULL : edges); + jl_atomic_store_relaxed(&codeinst->edges, edges); jl_gc_wb(codeinst, edges); + if ((jl_value_t*)di != jl_nothing) { + jl_atomic_store_relaxed(&codeinst->debuginfo, di); + jl_gc_wb(codeinst, di); + } if ((const_flags & 1) != 0) { // TODO: may want to follow ordering restrictions here (see jitlayers.cpp) assert(const_flags & 2); @@ -615,7 +625,7 @@ JL_DLLEXPORT void jl_fill_codeinst( JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst_uninit(jl_method_instance_t *mi, jl_value_t *owner) { - jl_code_instance_t *codeinst = jl_new_codeinst(mi, owner, NULL, NULL, NULL, NULL, 0, 0, 0, 0, NULL, 0, NULL); + jl_code_instance_t *codeinst = jl_new_codeinst(mi, owner, NULL, NULL, NULL, NULL, 0, 0, 0, 0, NULL, 0, NULL, NULL); jl_atomic_store_relaxed(&codeinst->min_world, 1); // make temporarily invalid before returning, so that jl_fill_codeinst is valid later return codeinst; } @@ -2563,8 +2573,10 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_instance_t *codeinst2 = jl_compile_method_internal(mi2, world); jl_code_instance_t *codeinst = jl_get_method_inferred( mi, codeinst2->rettype, - jl_atomic_load_relaxed(&codeinst2->min_world), jl_atomic_load_relaxed(&codeinst2->max_world), - jl_atomic_load_relaxed(&codeinst2->debuginfo)); + jl_atomic_load_relaxed(&codeinst2->min_world), + jl_atomic_load_relaxed(&codeinst2->max_world), + jl_atomic_load_relaxed(&codeinst2->debuginfo), + jl_atomic_load_relaxed(&codeinst2->edges)); if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { codeinst->rettype_const = codeinst2->rettype_const; jl_gc_wb(codeinst, codeinst->rettype_const); @@ -2623,7 +2635,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (unspec && (unspec_invoke = jl_atomic_load_acquire(&unspec->invoke))) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL); + 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL); void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set @@ -2650,7 +2662,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (!jl_code_requires_compiler(src, 0)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, jl_nothing, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL); + 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL); jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi, 0); @@ -2710,7 +2722,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_method_instance_t *unspec = jl_get_unspecialized(def); if (unspec == NULL) unspec = mi; - jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0, NULL); + jl_code_instance_t *ucache = jl_get_method_inferred(unspec, (jl_value_t*)jl_any_type, 1, ~(size_t)0, NULL, NULL); // ask codegen to make the fptr for unspec jl_callptr_t ucache_invoke = jl_atomic_load_acquire(&ucache->invoke); if (ucache_invoke == NULL) { @@ -2730,7 +2742,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t } codeinst = jl_new_codeinst(mi, jl_nothing, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL); + 0, 1, ~(size_t)0, 0, jl_nothing, 0, NULL, NULL); void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); if (unspec_fptr) { // wait until invoke and specsigflags are properly set diff --git a/src/ircode.c b/src/ircode.c index 873d33d2d7523..df11d06e64a17 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -25,6 +25,7 @@ typedef struct { ios_t *s; // method we're compressing for jl_method_t *method; + jl_svec_t *edges; jl_ptls_t ptls; uint8_t relocatability; } jl_ircode_state; @@ -72,7 +73,7 @@ static void literal_val_id(rle_reference *rr, jl_ircode_state *s, jl_value_t *v) { jl_array_t *rs = s->method->roots; int i, l = jl_array_nrows(rs); - if (jl_is_symbol(v) || jl_is_concrete_type(v)) { + if (jl_is_symbol(v) || jl_is_concrete_type(v)) { // TODO: or more generally, any ptr-egal value for (i = 0; i < l; i++) { if (jl_array_ptr_ref(rs, i) == v) return tagged_root(rr, s, i); @@ -84,6 +85,12 @@ static void literal_val_id(rle_reference *rr, jl_ircode_state *s, jl_value_t *v) return tagged_root(rr, s, i); } } + for (size_t i = 0; i < jl_svec_len(s->edges); i++) { + if (jl_svecref(s->edges, i) == v) { + rr->index = i; + return; + } + } jl_add_method_root(s->method, jl_precompile_toplevel_module, v); return tagged_root(rr, s, jl_array_nrows(rs) - 1); } @@ -102,13 +109,24 @@ static void jl_encode_int32(jl_ircode_state *s, int32_t x) static void jl_encode_as_indexed_root(jl_ircode_state *s, jl_value_t *v) { - rle_reference rr; + rle_reference rr = {.key = -1, .index = -1}; if (jl_is_string(v)) v = jl_as_global_root(v, 1); literal_val_id(&rr, s, v); int id = rr.index; assert(id >= 0); + if (rr.key == -1) { + if (id <= UINT8_MAX) { + write_uint8(s->s, TAG_EDGE); + write_uint8(s->s, id); + } + else { + write_uint8(s->s, TAG_LONG_EDGE); + write_uint32(s->s, id); + } + return; + } if (rr.key) { write_uint8(s->s, TAG_RELOC_METHODROOT); write_uint64(s->s, rr.key); @@ -689,6 +707,10 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED return lookup_root(s->method, 0, read_uint8(s->s)); case TAG_LONG_METHODROOT: return lookup_root(s->method, 0, read_uint32(s->s)); + case TAG_EDGE: + return jl_svecref(s->edges, read_uint8(s->s)); + case TAG_LONG_EDGE: + return jl_svecref(s->edges, read_uint32(s->s)); case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC: return jl_decode_value_svec(s, tag); case TAG_COMMONSYM: @@ -846,6 +868,37 @@ typedef enum { #define checked_size(data, macro_size) \ (declaration_context(static_assert(sizeof(data) == macro_size, #macro_size " does not match written size")), data) +// n.b. this does not compute edges correctly, but is just a temporary legacy helper while porting +JL_DLLEXPORT jl_value_t *jl_ir_edges_legacy(jl_array_t *src) +{ + arraylist_t edges; + arraylist_new(&edges, 0); + for (size_t i = 0; i < jl_array_dim0(src); i++) { + jl_value_t *v = jl_array_ptr_ref(src, i); + if (jl_is_expr(v)) { + jl_expr_t *e = (jl_expr_t*)v; + if (e->head == jl_assign_sym && jl_expr_nargs(e) == 2 && jl_is_expr(jl_exprarg(e, 1))) { + e = (jl_expr_t*)jl_exprarg(e, 1); + } + if (e->head == jl_invoke_sym) { + jl_value_t *target = jl_array_ptr_ref(e->args, 0); + if (jl_is_code_instance(target) || jl_is_method_instance(target)) { + size_t j; + for (j = 0; j < edges.len; j++) + if (edges.items[j] == (void*)target) + break; + if (j == edges.len) + arraylist_push(&edges, target); + } + } + } + } + jl_value_t *e = jl_f_svec(NULL, (jl_value_t**)edges.items, edges.len); + arraylist_free(&edges); + return e; +} + + JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) { JL_TIMING(AST_COMPRESS, AST_COMPRESS); @@ -865,9 +918,11 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) m->roots = jl_alloc_vec_any(0); jl_gc_wb(m, m->roots); } + jl_value_t *edges = code->edges; jl_ircode_state s = { &dest, m, + (!isdef && jl_is_svec(edges)) ? (jl_svec_t*)edges : jl_emptysvec, jl_current_task->ptls, 1 }; @@ -950,6 +1005,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t jl_ircode_state s = { &src, m, + metadata == NULL ? NULL : jl_atomic_load_relaxed(&metadata->edges), jl_current_task->ptls, 1 }; @@ -1015,6 +1071,8 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t jl_gc_wb(code, code->rettype); code->min_world = jl_atomic_load_relaxed(&metadata->min_world); code->max_world = jl_atomic_load_relaxed(&metadata->max_world); + code->edges = (jl_value_t*)s.edges; + jl_gc_wb(code, s.edges); } return code; diff --git a/src/jltypes.c b/src/jltypes.c index 78ecb9231df7e..aa868179c2346 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3467,7 +3467,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(17, + jl_perm_symsvec(18, "def", "owner", "next", @@ -3477,13 +3477,14 @@ void jl_init_types(void) JL_GC_DISABLED "exctype", "rettype_const", "inferred", - "debuginfo", // TODO: rename to edges? + "debuginfo", + "edges", //"absolute_max", "ipo_purity_bits", "analysis_results", "specsigflags", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(17, + jl_svec(18, jl_method_instance_type, jl_any_type, jl_any_type, @@ -3494,6 +3495,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type, jl_debuginfo_type, + jl_simplevector_type, //jl_bool_type, jl_uint32_type, jl_any_type, @@ -3504,8 +3506,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 2, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b00000100011100011 }; // Set fields 1, 2, 6-8, 12 as const - const static uint32_t code_instance_atomicfields[1] = { 0b11011011100011100 }; // Set fields 3-5, 9, 10, 11, 13-14, 16-17 as atomic + const static uint32_t code_instance_constfields[1] = { 0b000001000011100011 }; // Set fields 1, 2, 6-8, 13 as const + const static uint32_t code_instance_atomicfields[1] = { 0b110110111100011100 }; // Set fields 3-5, 9-12, 14-15, 17-18 as atomic // Fields 4-5 are only operated on by construction and deserialization, so are effectively const at runtime // Fields ipo_purity_bits and analysis_results are not currently threadsafe or reliable, as they get mutated after optimization, but are not declared atomic // and there is no way to tell (during inference) if their value is finalized yet (to wait for them to be narrowed if applicable) @@ -3649,8 +3651,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_method_type->types, 13, jl_method_instance_type); //jl_svecset(jl_debuginfo_type->types, 0, jl_method_instance_type); // union(jl_method_instance_type, jl_method_type, jl_symbol_type) jl_svecset(jl_method_instance_type->types, 4, jl_code_instance_type); - jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 16, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 17, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 1, jl_globalref_type); jl_svecset(jl_binding_type->types, 2, jl_binding_type); diff --git a/src/julia.h b/src/julia.h index 3100f674fc28b..81e9933be79ed 100644 --- a/src/julia.h +++ b/src/julia.h @@ -453,6 +453,7 @@ typedef struct _jl_code_instance_t { // - null, indicating that inference was not yet completed or did not succeed _Atomic(jl_value_t *) inferred; _Atomic(jl_debuginfo_t *) debuginfo; // stored information about edges from this object (set once, with a happens-before both source and invoke) + _Atomic(jl_svec_t *) edges; // forward edge info //TODO: uint8_t absolute_max; // whether true max world is unknown // purity results diff --git a/src/julia_internal.h b/src/julia_internal.h index a4f57e3fb079f..1a8e1e70efc90 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -664,7 +664,7 @@ JL_DLLEXPORT jl_code_info_t *jl_gdbcodetyped1(jl_method_instance_t *mi, size_t w JL_DLLEXPORT jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *meth JL_PROPAGATES_ROOT, size_t world); JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( jl_method_instance_t *mi JL_PROPAGATES_ROOT, jl_value_t *rettype, - size_t min_world, size_t max_world, jl_debuginfo_t *edges); + size_t min_world, size_t max_world, jl_debuginfo_t *di, jl_svec_t *edges); jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst_uninit(jl_method_instance_t *mi, jl_value_t *owner); @@ -674,7 +674,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, uint32_t effects, jl_value_t *analysis_results, - uint8_t relocatability, jl_debuginfo_t *edges /* , int absolute_max*/); + uint8_t relocatability, jl_debuginfo_t *di, jl_svec_t *edges /* , int absolute_max*/); JL_DLLEXPORT const char *jl_debuginfo_file(jl_debuginfo_t *debuginfo) JL_NOTSAFEPOINT; JL_DLLEXPORT const char *jl_debuginfo_file1(jl_debuginfo_t *debuginfo) JL_NOTSAFEPOINT; @@ -713,6 +713,7 @@ JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t); #define JL_CALLABLE(name) \ JL_DLLEXPORT jl_value_t *name(jl_value_t *F, jl_value_t **args, uint32_t nargs) +JL_CALLABLE(jl_f_svec); JL_CALLABLE(jl_f_tuple); JL_CALLABLE(jl_f_intrinsic_call); JL_CALLABLE(jl_f_opaque_closure_call); diff --git a/src/method.c b/src/method.c index 095f18c692535..c320af217173e 100644 --- a/src/method.c +++ b/src/method.c @@ -648,10 +648,10 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->slotnames = NULL; src->slottypes = jl_nothing; src->rettype = (jl_value_t*)jl_any_type; + src->edges = (jl_value_t*)jl_emptysvec; src->parent = (jl_method_instance_t*)jl_nothing; src->min_world = 1; src->max_world = ~(size_t)0; - src->edges = jl_nothing; src->propagate_inbounds = 0; src->has_fcall = 0; src->nospecializeinfer = 0; @@ -754,9 +754,10 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *mi, size_t assert(jl_is_method(def)); jl_code_info_t *func = NULL; jl_value_t *ex = NULL; + jl_value_t *kind = NULL; jl_code_info_t *uninferred = NULL; jl_code_instance_t *ci = NULL; - JL_GC_PUSH4(&ex, &func, &uninferred, &ci); + JL_GC_PUSH5(&ex, &func, &uninferred, &ci, &kind); jl_task_t *ct = jl_current_task; int last_lineno = jl_lineno; int last_in = ct->ptls->in_pure_callback; @@ -792,6 +793,7 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *mi, size_t // but currently our isva determination is non-syntactic func->isva = def->isva; } + ex = NULL; // If this generated function has an opaque closure, cache it for // correctness of method identity. In particular, other methods that call @@ -815,7 +817,7 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *mi, size_t } } - if (func->edges == jl_nothing && func->max_world == ~(size_t)0) { + if ((func->edges == jl_nothing || func->edges == (jl_value_t*)jl_emptysvec) && func->max_world == ~(size_t)0) { if (func->min_world != 1) { jl_error("Generated function result with `edges == nothing` and `max_world == typemax(UInt)` must have `min_world == 1`"); } @@ -827,15 +829,31 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *mi, size_t if (uninferred->edges != jl_nothing) { // N.B.: This needs to match `store_backedges` on the julia side - jl_array_t *edges = (jl_array_t*)uninferred->edges; - for (size_t i = 0; i < jl_array_len(edges); ++i) { - jl_value_t *kind = jl_array_ptr_ref(edges, i); + jl_value_t *edges = uninferred->edges; + size_t l; + jl_value_t **data; + if (jl_is_svec(edges)) { + l = jl_svec_len(edges); + data = jl_svec_data(edges); + } + else { + l = jl_array_dim0(edges); + data = jl_array_data(edges, jl_value_t*); + } + for (size_t i = 0; i < l; ) { + kind = data[i++]; if (jl_is_method_instance(kind)) { jl_method_instance_add_backedge((jl_method_instance_t*)kind, jl_nothing, mi); - } else if (jl_is_mtable(kind)) { - jl_method_table_add_backedge((jl_methtable_t*)kind, jl_array_ptr_ref(edges, ++i), (jl_value_t*)mi); - } else { - jl_method_instance_add_backedge((jl_method_instance_t*)jl_array_ptr_ref(edges, ++i), kind, mi); + } + else if (jl_is_mtable(kind)) { + assert(i < l); + ex = data[i++]; + jl_method_table_add_backedge((jl_methtable_t*)kind, ex, (jl_value_t*)mi); + } + else { + assert(i < l); + ex = data[i++]; + jl_method_instance_add_backedge((jl_method_instance_t*)ex, kind, mi); } } } diff --git a/src/opaque_closure.c b/src/opaque_closure.c index 0bf3a729cbcb1..cf0f32458d254 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -112,8 +112,8 @@ static jl_opaque_closure_t *new_opaque_closure(jl_tupletype_t *argt, jl_value_t sigtype = jl_argtype_with_function_type((jl_value_t*)oc_type, (jl_value_t*)argt); jl_method_instance_t *mi_generic = jl_specializations_get_linfo(jl_opaque_closure_method, sigtype, jl_emptysvec); - // OC wrapper methods are not world dependent - ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0, NULL); + // OC wrapper methods are not world dependent and have no edges or other info + ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0, NULL, NULL); if (!jl_atomic_load_acquire(&ci->invoke)) jl_compile_codeinst(ci); specptr = jl_atomic_load_relaxed(&ci->specptr.fptr); @@ -144,7 +144,8 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet { jl_value_t *root = NULL, *sigtype = NULL; jl_code_instance_t *inst = NULL; - JL_GC_PUSH3(&root, &sigtype, &inst); + jl_svec_t *edges = NULL; + JL_GC_PUSH4(&root, &sigtype, &inst, &edges); root = jl_box_long(lineno); root = jl_new_struct(jl_linenumbernode_type, root, file); jl_method_t *meth = jl_make_opaque_closure_method(mod, jl_nothing, nargs, root, ci, isva, isinferred); @@ -158,8 +159,11 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet jl_value_t *argslotty = jl_array_ptr_ref(ci->slottypes, 0); sigtype = jl_argtype_with_function_type(argslotty, (jl_value_t*)argt); jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); + edges = (jl_svec_t*)ci->edges; + if (!jl_is_svec(edges)) + edges = jl_emptysvec; // OC doesn't really have edges, so just drop them for now inst = jl_new_codeinst(mi, jl_nothing, rt_ub, (jl_value_t*)jl_any_type, NULL, (jl_value_t*)ci, - 0, world, world, 0, jl_nothing, 0, ci->debuginfo); + 0, world, world, 0, jl_nothing, 0, ci->debuginfo, edges); jl_mi_cache_insert(mi, inst); } diff --git a/src/serialize.h b/src/serialize.h index 3d3eb4df5e862..3aa82a1d09a9b 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -23,50 +23,52 @@ extern "C" { #define TAG_LONG_PHINODE 15 #define TAG_LONG_PHICNODE 16 #define TAG_METHODROOT 17 -#define TAG_STRING 18 -#define TAG_SHORT_INT64 19 -#define TAG_SHORT_GENERAL 20 -#define TAG_CNULL 21 -#define TAG_ARRAY1D 22 -#define TAG_SINGLETON 23 -#define TAG_MODULE 24 -#define TAG_TVAR 25 -#define TAG_METHOD_INSTANCE 26 -#define TAG_METHOD 27 -#define TAG_CODE_INSTANCE 28 -#define TAG_COMMONSYM 29 -#define TAG_NEARBYGLOBAL 30 -#define TAG_GLOBALREF 31 -#define TAG_CORE 32 -#define TAG_BASE 33 -#define TAG_BITYPENAME 34 -#define TAG_NEARBYMODULE 35 -#define TAG_INT32 36 -#define TAG_INT64 37 -#define TAG_UINT8 38 -#define TAG_VECTORTY 39 -#define TAG_PTRTY 40 -#define TAG_LONG_SSAVALUE 41 -#define TAG_LONG_METHODROOT 42 -#define TAG_SHORTER_INT64 43 -#define TAG_SHORT_INT32 44 -#define TAG_CALL1 45 -#define TAG_CALL2 46 -#define TAG_SHORT_BACKREF 47 -#define TAG_BACKREF 48 -#define TAG_UNIONALL 49 -#define TAG_GOTONODE 50 -#define TAG_QUOTENODE 51 -#define TAG_GENERAL 52 -#define TAG_GOTOIFNOT 53 -#define TAG_RETURNNODE 54 -#define TAG_ARGUMENT 55 -#define TAG_RELOC_METHODROOT 56 -#define TAG_BINDING 57 -#define TAG_MEMORYT 58 -#define TAG_ENTERNODE 59 - -#define LAST_TAG 59 +#define TAG_EDGE 18 +#define TAG_STRING 19 +#define TAG_SHORT_INT64 20 +#define TAG_SHORT_GENERAL 21 +#define TAG_CNULL 22 +#define TAG_ARRAY1D 23 +#define TAG_SINGLETON 24 +#define TAG_MODULE 25 +#define TAG_TVAR 26 +#define TAG_METHOD_INSTANCE 27 +#define TAG_METHOD 28 +#define TAG_CODE_INSTANCE 29 +#define TAG_COMMONSYM 30 +#define TAG_NEARBYGLOBAL 31 +#define TAG_GLOBALREF 32 +#define TAG_CORE 33 +#define TAG_BASE 34 +#define TAG_BITYPENAME 35 +#define TAG_NEARBYMODULE 36 +#define TAG_INT32 37 +#define TAG_INT64 38 +#define TAG_UINT8 39 +#define TAG_VECTORTY 40 +#define TAG_PTRTY 41 +#define TAG_LONG_SSAVALUE 42 +#define TAG_LONG_METHODROOT 43 +#define TAG_LONG_EDGE 44 +#define TAG_SHORTER_INT64 45 +#define TAG_SHORT_INT32 46 +#define TAG_CALL1 47 +#define TAG_CALL2 48 +#define TAG_SHORT_BACKREF 49 +#define TAG_BACKREF 50 +#define TAG_UNIONALL 51 +#define TAG_GOTONODE 52 +#define TAG_QUOTENODE 53 +#define TAG_GENERAL 54 +#define TAG_GOTOIFNOT 55 +#define TAG_RETURNNODE 56 +#define TAG_ARGUMENT 57 +#define TAG_RELOC_METHODROOT 58 +#define TAG_BINDING 59 +#define TAG_MEMORYT 60 +#define TAG_ENTERNODE 61 + +#define LAST_TAG 61 #define write_uint8(s, n) ios_putc((n), (s)) #define read_uint8(s) ((uint8_t)ios_getc((s))) diff --git a/src/toplevel.c b/src/toplevel.c index 3f9c6ba5e71ca..1675e8186897a 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -621,7 +621,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst_for_uninferred(jl_method_instan jl_code_instance_t *ci = jl_new_codeinst(mi, (jl_value_t*)jl_uninferred_sym, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, jl_nothing, (jl_value_t*)src, 0, src->min_world, src->max_world, - 0, NULL, 1, NULL); + 0, NULL, 1, NULL, NULL); return ci; } diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index f86453c451e71..f5c15ceeb1771 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -91,7 +91,7 @@ module MiniCassette code_info = retrieve_code_info(mi, world) @assert isa(code_info, CodeInfo) code_info = copy(code_info) - @assert code_info.edges === nothing + @assert code_info.edges === Core.svec() code_info.edges = MethodInstance[mi] transform!(mi, code_info, length(args), match.sparams) # TODO: this is mandatory: code_info.min_world = max(code_info.min_world, min_world[]) diff --git a/test/core.jl b/test/core.jl index 462c24fa04ab5..83d8974b8921a 100644 --- a/test/core.jl +++ b/test/core.jl @@ -32,7 +32,7 @@ end # sanity tests that our built-in types are marked correctly for atomic fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :debuginfo, :ipo_purity_bits, :invoke, :specptr, :specsigflags, :precompile]), + (Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :edges, :debuginfo, :ipo_purity_bits, :invoke, :specptr, :specsigflags, :precompile]), (Core.Method, [:primary_world, :deleted_world]), (Core.MethodInstance, [:cache, :precompiled]), (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), diff --git a/test/precompile.jl b/test/precompile.jl index 53efe963c101c..b424b7f0da67c 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -798,8 +798,9 @@ precompile_test_harness("code caching") do dir mispecs = minternal.specializations::Core.SimpleVector @test mispecs[1] === mi mi = mispecs[2]::Core.MethodInstance + mi.specTypes == Tuple{typeof(M.getelsize),Vector{M.X2}} ci = mi.cache - @test ci.relocatability == 0 + @test ci.relocatability == 0x01 # PkgA loads PkgB, and both add roots to the same `push!` method (both before and after loading B) Cache_module2 = :Cachea1544c83560f0c99 write(joinpath(dir, "$Cache_module2.jl"), @@ -1708,7 +1709,7 @@ precompile_test_harness("issue #46296") do load_path mi = first(Base.specializations(first(methods(identity)))) ci = Core.CodeInstance(mi, nothing, Any, Any, nothing, nothing, zero(Int32), typemin(UInt), typemax(UInt), zero(UInt32), nothing, 0x00, - Core.DebugInfo(mi)) + Core.DebugInfo(mi), Core.svec()) __init__() = @assert ci isa Core.CodeInstance