From ca87651a06360449af6809aa25c2341de69726c1 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 16 Jul 2024 15:51:13 -0400 Subject: [PATCH 1/8] 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 608e273d4b514..96204af5832cf 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -511,11 +511,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 8b85f7c6f35f1..bfe0a24f516be 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -112,13 +112,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 @@ -133,14 +136,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 @@ -214,7 +217,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)) @@ -409,7 +411,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, @@ -494,11 +496,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 @@ -881,6 +884,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 @@ -898,7 +902,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) = @@ -1060,7 +1064,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 478ccd7d7cc20..6652147473298 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 56ebe6fe2fa84..c4fac6d0a3f07 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); @@ -616,7 +626,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; } @@ -2647,8 +2657,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); @@ -2705,7 +2717,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); codeinst->rettype_const = unspec->rettype_const; uint8_t specsigflags; jl_callptr_t invoke; @@ -2730,7 +2742,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, 0); @@ -2790,7 +2802,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) { @@ -2810,7 +2822,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); codeinst->rettype_const = ucache->rettype_const; uint8_t specsigflags; jl_callptr_t invoke; 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 11f1d11a14edc..71eaa003d7d4a 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3638,7 +3638,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", @@ -3648,13 +3648,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, @@ -3665,6 +3666,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, @@ -3675,8 +3677,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) @@ -3826,8 +3828,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, 0, jl_globalref_type); jl_svecset(jl_binding_partition_type->types, 3, jl_binding_partition_type); diff --git a/src/julia.h b/src/julia.h index ed3d9bf825658..adcdbe7dbdc0e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -452,6 +452,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 9a61c3d18356f..75b8d34477b87 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -663,7 +663,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_DLLEXPORT jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); JL_DLLEXPORT void jl_read_codeinst_invoke(jl_code_instance_t *ci, uint8_t *specsigflags, jl_callptr_t *invoke, void **specptr, int waitcompile) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t world, size_t min_valid, size_t max_valid, int mt_cache); @@ -675,7 +675,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; @@ -715,6 +715,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 6aba60e7fe12c..4f67abd613667 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; @@ -752,9 +752,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; @@ -790,6 +791,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 @@ -813,7 +815,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`"); } @@ -825,15 +827,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 8caa8b086ec00..0d4da4e34eb5a 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -633,7 +633,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 fc91a37c5bd9e..408f01b1aea6a 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -88,7 +88,7 @@ module MiniCassette src = retrieve_code_info(mi, world) @assert isa(src, CodeInfo) src = copy(src) - @assert src.edges === nothing + @assert src.edges === Core.svec() src.edges = MethodInstance[mi] transform!(mi, src, 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 b27832209a835..6173d41456052 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, :flags]), (Core.MethodTable, [:defs, :leafcache, :cache, :max_args]), diff --git a/test/precompile.jl b/test/precompile.jl index 7a6e41061f9b1..badc6f85618d1 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -802,8 +802,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"), @@ -1712,7 +1713,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 From 1465d55c9ba6cb09262ef5c308b34ecb64dff33f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 14 Jul 2024 20:27:21 +0200 Subject: [PATCH 2/8] inference: compute edges more precisely in post-inference Start computing edges from stmt_info later (after CodeInstance is able to have been allocated for recursion) instead of immediately. --- base/compiler/abstractinterpretation.jl | 6 +- base/compiler/stmtinfo.jl | 10 +-- base/compiler/tfuncs.jl | 2 +- base/compiler/typeinfer.jl | 92 +++++++++++++++++++++++++ base/compiler/types.jl | 1 - 5 files changed, 102 insertions(+), 9 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index c8a25be422637..d8f467c3bc740 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -339,7 +339,7 @@ function find_union_split_method_matches(interp::AbstractInterpreter, argtypes:: end valid_worlds = intersect(valid_worlds, thismatches.valid_worlds) thisfullmatch = any(match::MethodMatch->match.fully_covers, thismatches) - thisinfo = MethodMatchInfo(thismatches, mt, thisfullmatch) + thisinfo = MethodMatchInfo(thismatches, mt, sig_n, thisfullmatch) push!(infos, thisinfo) end info = UnionSplitInfo(infos) @@ -360,7 +360,7 @@ function find_simple_method_matches(interp::AbstractInterpreter, @nospecialize(a return FailedMethodMatch("Too many methods matched") end fullmatch = any(match::MethodMatch->match.fully_covers, matches) - info = MethodMatchInfo(matches, mt, fullmatch) + info = MethodMatchInfo(matches, mt, atype, fullmatch) return MethodMatches(matches.matches, info, matches.valid_worlds) end @@ -2257,7 +2257,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt end end rt = from_interprocedural!(interp, rt, sv, arginfo, sig) - info = InvokeCallInfo(match, const_result) + info = InvokeCallInfo(match, const_result, lookupsig) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) if !match.fully_covers effects = Effects(effects; nothrow=false) diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index 9dba7a4459f9e..473d5bbf2fe0b 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -26,14 +26,15 @@ struct NoCallInfo <: CallInfo end """ info::MethodMatchInfo <: CallInfo -Captures the result of a `:jl_matching_methods` lookup for the given call (`info.results`). -This info may then be used by the optimizer to inline the matches, without having -to re-consult the method table. This info is illegal on any statement that is -not a call to a generic function. +Captures the essential arguments and result of a `:jl_matching_methods` lookup +for the given call (`info.results`). This info may then be used by the +optimizer, without having to re-consult the method table. +This info is illegal on any statement that is not a call to a generic function. """ struct MethodMatchInfo <: CallInfo results::MethodLookupResult mt::MethodTable + atype fullmatch::Bool end nsplit_impl(info::MethodMatchInfo) = 1 @@ -186,6 +187,7 @@ Optionally keeps `info.result::InferenceResult` that keeps constant information. struct InvokeCallInfo <: CallInfo match::MethodMatch result::Union{Nothing,ConstResult} + atype # ::Type end """ diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index a6b7e53c6f320..847bdfd2df801 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2949,7 +2949,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s if isa(sv, InferenceState) sv.restrict_abstract_call_sites = old_restrict end - info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure() + info = MethodResultPure(ReturnTypeCallInfo(call.info)) rt = widenslotwrapper(call.rt) if isa(rt, Const) # output was computed to be constant diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index bfe0a24f516be..04b177044fd3b 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -411,6 +411,12 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) append!(s_edges, edges) empty!(edges) end + #s_edges_new = compute_edges(me) + #println(Any[z isa MethodTable ? z.name : z for z in s_edges_new]) + #if length(s_edges) != length(s_edges_new) || !all(i -> isassigned(s_edges, i) ? isassigned(s_edges_new, i) && s_edges_new[i] === s_edges[i] : !isassigned(s_edges_new, i), length(s_edges)) + # println(sizehint!(s_edges, length(s_edges))) + # println(sizehint!(s_edges_new, length(s_edges_new))) + #end if me.src.edges !== nothing && me.src.edges !== Core.svec() append!(s_edges, me.src.edges::Vector) end @@ -528,6 +534,92 @@ function store_backedges(caller::MethodInstance, edges::Vector{Any}) return nothing end +add_edges!(edges::Vector{Any}, info::MethodResultPure) = add_edges!(edges, info.info) +add_edges!(edges::Vector{Any}, info::ConstCallInfo) = add_edges!(edges, info.call) +add_edges!(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = nothing # TODO(jwn) +add_edges!(edges::Vector{Any}, info::ReturnTypeCallInfo) = add_edges!(edges, info.info) +function add_edges!(edges::Vector{Any}, info::ApplyCallInfo) + add_edges!(edges, info.call) + for arg in info.arginfo + arg === nothing && continue + for edge in arg.each + add_edges!(edges, edge.info) + end + end +end +add_edges!(edges::Vector{Any}, info::ModifyOpInfo) = add_edges!(edges, info.call) +add_edges!(edges::Vector{Any}, info::UnionSplitInfo) = for split in info.matches; add_edges!(edges, split); end +add_edges!(edges::Vector{Any}, info::UnionSplitApplyCallInfo) = for split in info.infos; add_edges!(edges, split); end +add_edges!(edges::Vector{Any}, info::FinalizerInfo) = nothing +add_edges!(edges::Vector{Any}, info::NoCallInfo) = nothing +function add_edges!(edges::Vector{Any}, info::MethodMatchInfo) + matches = info.results.matches + #if length(matches) == 1 && !info.results.ambig && (matches[end]::Core.MethodMatch).fully_covers + # push!(edges, specialize_method(matches[1])) + #elseif isempty(matches) || info.results.ambig || !(matches[end]::Core.MethodMatch).fully_covers + #else + # push!(edges, length(matches)) + # for m in matches + # push!(edges, specialize_method(m)) + # end + #end + if isempty(matches) || !(matches[end]::Core.MethodMatch).fully_covers + exists = false + for i in 1:length(edges) + if edges[i] === info.mt && edges[i + 1] == info.atype + exists = true + break + end + end + if !exists + push!(edges, info.mt) + push!(edges, info.atype) + end + end + for m in matches + mi = specialize_method(m) + exists = false + for i in 1:length(edges) + if edges[i] === mi && !(i > 1 && edges[i - 1] isa Type) + exists = true + break + end + end + exists || push!(edges, mi) + end +end +function add_edges!(edges::Vector{Any}, info::InvokeCallInfo) + #push!(edges, 1) + mi = specialize_method(info.match) + exists = false + for i in 2:length(edges) + if edges[i] === mi && edges[i - 1] isa Type && edges[i - 1] == info.atype + exists = true + break + end + end + if !exists + push!(edges, info.atype) + push!(edges, mi) + end + nothing +end + +function compute_edges(sv::InferenceState) + edges = [] + for i in 1:length(sv.stmt_info) + info = sv.stmt_info[i] + #rt = sv.ssavaluetypes[i] + #effects = EFFECTS_TOTAL # sv.stmt_effects[i] + #if rt === Any && effects === Effects() + # continue + #end + add_edges!(edges, info) + end + return edges +end + + function record_slot_assign!(sv::InferenceState) # look at all assignments to slots # and union the set of types stored there diff --git a/base/compiler/types.jl b/base/compiler/types.jl index ecf2417fd6199..a57aee4641e2d 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -414,7 +414,6 @@ function add_remark! end may_optimize(::AbstractInterpreter) = true may_compress(::AbstractInterpreter) = true may_discard_trees(::AbstractInterpreter) = true -verbose_stmt_info(::AbstractInterpreter) = false """ method_table(interp::AbstractInterpreter) -> MethodTableView From 319c529a44af83ac37e9a281c366141f48b1780e Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 16 Jul 2024 15:44:19 -0400 Subject: [PATCH 3/8] compute edges post-inference, from info available there --- base/compiler/abstractinterpretation.jl | 29 ------ base/compiler/inferencestate.jl | 53 ++--------- base/compiler/optimize.jl | 4 +- base/compiler/ssair/inlining.jl | 20 ++--- base/compiler/ssair/irinterp.jl | 6 -- base/compiler/ssair/passes.jl | 4 +- base/compiler/tfuncs.jl | 14 +-- base/compiler/typeinfer.jl | 112 +++++++++++------------- base/compiler/utilities.jl | 13 +-- src/ircode.c | 31 ------- test/compiler/AbstractInterpreter.jl | 2 +- test/compiler/invalidation.jl | 11 +-- 12 files changed, 89 insertions(+), 210 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index d8f467c3bc740..b962c92176617 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -231,7 +231,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), rettype = Any end any_slot_refined = slotrefinements !== nothing - add_call_backedges!(interp, rettype, all_effects, any_slot_refined, edges, matches, atype.contents, sv) if isa(sv, InferenceState) # TODO (#48913) implement a proper recursion handling for irinterp: # This works just because currently the `:terminate` condition guarantees that @@ -560,31 +559,6 @@ function collect_slot_refinements(𝕃ᡒ::AbstractLattice, applicable::Vector{A return slotrefinements end -function add_call_backedges!(interp::AbstractInterpreter, @nospecialize(rettype), - all_effects::Effects, any_slot_refined::Bool, edges::Vector{MethodInstance}, - matches::Union{MethodMatches,UnionSplitMethodMatches}, @nospecialize(atype), - sv::AbsIntState) - # don't bother to add backedges when both type and effects information are already - # maximized to the top since a new method couldn't refine or widen them anyway - if rettype === Any - # ignore the `:nonoverlayed` property if `interp` doesn't use overlayed method table - # since it will never be tainted anyway - if !isoverlayed(method_table(interp)) - all_effects = Effects(all_effects; nonoverlayed=ALWAYS_FALSE) - end - if all_effects === Effects() && !any_slot_refined - return nothing - end - end - for edge in edges - add_backedge!(sv, edge) - end - # also need an edge to the method table in case something gets - # added that did not intersect with any existing method - add_uncovered_edges!(sv, matches, atype) - return nothing -end - const RECURSION_UNUSED_MSG = "Bounded recursion detected with unused result. Annotated return type may be wider than true result." const RECURSION_MSG = "Bounded recursion detected. Call was widened to force convergence." const RECURSION_MSG_HARDLIMIT = "Bounded recursion detected under hardlimit. Call was widened to force convergence." @@ -2258,7 +2232,6 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt end rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result, lookupsig) - edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) if !match.fully_covers effects = Effects(effects; nothrow=false) exct = exct βŠ” TypeError @@ -2481,7 +2454,6 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, end rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) - edge !== nothing && add_backedge!(sv, edge) return CallMeta(rt, exct, effects, info) end end @@ -3430,7 +3402,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState, nextr while currpc < bbend currpc += 1 frame.currpc = currpc - empty_backedges!(frame, currpc) stmt = frame.src.code[currpc] # If we're at the end of the basic block ... if currpc == bbend diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 5f8fb82caaa34..6d0c8f44cd0e3 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -247,7 +247,7 @@ mutable struct InferenceState # TODO: Could keep this sparsely by doing structural liveness analysis ahead of time. bb_vartables::Vector{Union{Nothing,VarTable}} # nothing if not analyzed yet ssavaluetypes::Vector{Any} - stmt_edges::Vector{Vector{Any}} + edges::Vector{Any} stmt_info::Vector{CallInfo} #= intermediate states for interprocedural abstract interpretation =# @@ -302,7 +302,7 @@ mutable struct InferenceState nssavalues = src.ssavaluetypes::Int ssavalue_uses = find_ssavalue_uses(code, nssavalues) nstmts = length(code) - stmt_edges = Vector{Vector{Any}}(undef, nstmts) + edges = [] stmt_info = CallInfo[ NoCallInfo() for i = 1:nstmts ] nslots = length(src.slotflags) @@ -350,10 +350,12 @@ mutable struct InferenceState restrict_abstract_call_sites = isa(def, Module) + parentid = frameid = cycleid = 0 + this = new( mi, world, mod, sptypes, slottypes, src, cfg, spec_info, - currbb, currpc, ip, handler_info, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, - tasks, pclimitations, limitations, cycle_backedges, callstack, 0, 0, 0, + currbb, currpc, ip, handler_info, ssavalue_uses, bb_vartables, ssavaluetypes, edges, stmt_info, + tasks, pclimitations, limitations, cycle_backedges, callstack, parentid, frameid, cycleid, result, unreachable, valid_worlds, bestguess, exc_bestguess, ipo_effects, restrict_abstract_call_sites, cache_mode, insert_coverage, interp) @@ -758,26 +760,9 @@ function add_cycle_backedge!(caller::InferenceState, frame::InferenceState) update_valid_age!(caller, frame.valid_worlds) backedge = (caller, caller.currpc) contains_is(frame.cycle_backedges, backedge) || push!(frame.cycle_backedges, backedge) - add_backedge!(caller, frame.linfo) return frame end -function get_stmt_edges!(caller::InferenceState, currpc::Int=caller.currpc) - stmt_edges = caller.stmt_edges - if !isassigned(stmt_edges, currpc) - return stmt_edges[currpc] = Any[] - else - return stmt_edges[currpc] - end -end - -function empty_backedges!(frame::InferenceState, currpc::Int=frame.currpc) - if isassigned(frame.stmt_edges, currpc) - empty!(frame.stmt_edges[currpc]) - end - return nothing -end - function narguments(sv::InferenceState, include_va::Bool=true) nargs = Int(sv.src.nargs) if !include_va @@ -1008,32 +993,6 @@ function callers_in_cycle(sv::InferenceState) end callers_in_cycle(sv::IRInterpretationState) = AbsIntCycle(sv.callstack::Vector{AbsIntState}, 0, 0) -# temporarily accumulate our edges to later add as backedges in the callee -function add_backedge!(caller::InferenceState, mi::MethodInstance) - isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance - return push!(get_stmt_edges!(caller), mi) -end -function add_backedge!(irsv::IRInterpretationState, mi::MethodInstance) - return push!(irsv.edges, mi) -end - -function add_invoke_backedge!(caller::InferenceState, @nospecialize(invokesig::Type), mi::MethodInstance) - isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance - return push!(get_stmt_edges!(caller), invokesig, mi) -end -function add_invoke_backedge!(irsv::IRInterpretationState, @nospecialize(invokesig::Type), mi::MethodInstance) - return push!(irsv.edges, invokesig, mi) -end - -# used to temporarily accumulate our no method errors to later add as backedges in the callee method table -function add_mt_backedge!(caller::InferenceState, mt::MethodTable, @nospecialize(typ)) - isa(caller.linfo.def, Method) || return nothing # don't add backedges to toplevel method instance - return push!(get_stmt_edges!(caller), mt, typ) -end -function add_mt_backedge!(irsv::IRInterpretationState, mt::MethodTable, @nospecialize(typ)) - return push!(irsv.edges, mt, typ) -end - get_curr_ssaflag(sv::InferenceState) = sv.src.ssaflags[sv.currpc] get_curr_ssaflag(sv::IRInterpretationState) = sv.ir.stmts[sv.curridx][:flag] diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 02f6b46e2e73f..1cd620314712c 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -141,8 +141,7 @@ struct InliningState{Interp<:AbstractInterpreter} interp::Interp end function InliningState(sv::InferenceState, interp::AbstractInterpreter) - edges = sv.stmt_edges[1] - return InliningState(edges, sv.world, interp) + return InliningState(sv.edges, sv.world, interp) end function InliningState(interp::AbstractInterpreter) return InliningState(Any[], get_inference_world(interp), interp) @@ -225,6 +224,7 @@ include("compiler/ssair/irinterp.jl") function ir_to_codeinf!(opt::OptimizationState) (; linfo, src) = opt src = ir_to_codeinf!(src, opt.ir::IRCode) + src.edges = opt.inlining.edges opt.ir = nothing maybe_validate_code(linfo, src, "optimized") return src diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 5017b619469ff..2f1d7dc279d70 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -68,11 +68,11 @@ struct InliningEdgeTracker new(state.edges, invokesig) end -function add_inlining_backedge!((; edges, invokesig)::InliningEdgeTracker, mi::MethodInstance) +function add_inlining_edge!((; edges, invokesig)::InliningEdgeTracker, mi::MethodInstance) if invokesig === nothing - push!(edges, mi) + add_one_edge!(edges, mi) else # invoke backedge - push!(edges, invoke_signature(invokesig), mi) + add_invoke_edge!(edges, invoke_signature(invokesig), mi) end return nothing end @@ -805,8 +805,8 @@ function compileable_specialization(mi::MethodInstance, effects::Effects, return nothing end end - add_inlining_backedge!(et, mi) # to the dispatch lookup - mi_invoke !== mi && push!(et.edges, method.sig, mi_invoke) # add_inlining_backedge to the invoke call, if that is different + add_inlining_edge!(et, mi) # to the dispatch lookup + mi_invoke !== mi && add_invoke_edge!(et.edges, method.sig, mi_invoke) # add_inlining_edge to the invoke call, if that is different return InvokeCase(mi_invoke, effects, info) end @@ -861,7 +861,7 @@ function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult, inferred_result = get_cached_result(state, mi) end if inferred_result isa ConstantCase - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) return inferred_result elseif inferred_result isa InferredResult (; src, effects) = inferred_result @@ -884,7 +884,7 @@ function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult, return compileable_specialization(mi, effects, et, info; compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) if inferred_result isa CodeInstance ir, spec_info, debuginfo = retrieve_ir_for_inlining(inferred_result, src) else @@ -904,7 +904,7 @@ function resolve_todo(mi::MethodInstance, @nospecialize(info::CallInfo), flag::U cached_result = get_cached_result(state, mi) if cached_result isa ConstantCase - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) return cached_result elseif cached_result isa CodeInstance src = @atomic :monotonic cached_result.inferred @@ -915,7 +915,7 @@ function resolve_todo(mi::MethodInstance, @nospecialize(info::CallInfo), flag::U src_inlining_policy(state.interp, src, info, flag) || return nothing ir, spec_info, debuginfo = retrieve_ir_for_inlining(cached_result, src) - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) return InliningTodo(mi, ir, spec_info, debuginfo, effects) end @@ -1470,7 +1470,7 @@ function semiconcrete_result_item(result::SemiConcreteResult, return compileable_specialization(mi, result.effects, et, info; compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) preserve_local_sources = OptimizationParams(state.interp).preserve_local_sources ir, _, debuginfo = retrieve_ir_for_inlining(mi, result.ir, preserve_local_sources) return InliningTodo(mi, ir, result.spec_info, debuginfo, result.effects) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index ca8ca770df413..dd423640fd8d3 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -450,12 +450,6 @@ function ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRI (nothrow | noub) || break end - if last(irsv.valid_worlds) >= get_world_counter() - # if we aren't cached, we don't need this edge - # but our caller might, so let's just make it anyways - store_backedges(frame_instance(irsv), irsv.edges) - end - if irsv.frameid != 0 callstack = irsv.callstack::Vector{AbsIntState} @assert callstack[end] === irsv && length(callstack) == irsv.frameid diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index e227249b48598..07afbac7e2a78 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1523,7 +1523,7 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, if code isa CodeInstance if use_const_api(code) # No code in the function - Nothing to do - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) return true end src = @atomic :monotonic code.inferred @@ -1538,7 +1538,7 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, length(src.cfg.blocks) == 1 || return false # Ok, we're committed to inlining the finalizer - add_inlining_backedge!(et, mi) + add_inlining_edge!(et, mi) # TODO: Should there be a special line number node for inlined finalizers? inline_at = ir[SSAValue(idx)][:line] diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 847bdfd2df801..b1263b3ef161e 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2998,14 +2998,7 @@ function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, else rt = Const(true) # has applicable matches end - for i in 1:napplicable - match = applicable[i]::MethodMatch - edge = specialize_method(match)::MethodInstance - add_backedge!(sv, edge) - end - # also need an edge to the method table in case something gets - # added that did not intersect with any existing method - add_uncovered_edges!(sv, matches, atype) + add_edges!(sv.edges, matches.info) end return Future(CallMeta(rt, Union{}, EFFECTS_TOTAL, NoCallInfo())) end @@ -3041,11 +3034,10 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv update_valid_age!(sv, valid_worlds) if match === nothing rt = Const(false) - add_mt_backedge!(sv, mt, types) # this should actually be an invoke-type backedge + add_edges!(sv.edges, MethodMatchInfo(MethodLookupResult(Any[], valid_worlds, true), types, mt)) # XXX: this should actually be an invoke-type backedge else rt = Const(true) - edge = specialize_method(match)::MethodInstance - add_invoke_backedge!(sv, types, edge) + add_edges!(sv.edges, InvokeCallInfo(match, nothing, types)) end return CallMeta(rt, Any, EFFECTS_TOTAL, NoCallInfo()) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 04b177044fd3b..44c6dfd2e3539 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -99,28 +99,28 @@ const __measure_typeinf__ = RefValue{Bool}(false) function finish!(interp::AbstractInterpreter, caller::InferenceState; can_discard_trees::Bool=may_discard_trees(interp)) result = caller.result + opt = result.src + if opt isa OptimizationState + result.src = opt = ir_to_codeinf!(opt) + end valid_worlds = result.valid_worlds if last(valid_worlds) >= get_world_counter() # if we aren't cached, we don't need this edge # but our caller might, so let's just make it anyways - store_backedges(result, caller.stmt_edges[1]) - end - opt = result.src - if opt isa OptimizationState - result.src = opt = ir_to_codeinf!(opt) + store_backedges(result, caller.edges) end + isa(caller.linfo.def, Method) || empty!(caller.edges) # don't add backedges to toplevel method instance 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 + edges = Core.svec(caller.edges...) 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) + # TODO: do we want to augment edges here with any :invoke targets that we got from inlining (such that we didn't have a direct edge to it already)? relocatability = 0x0 if inferred_result isa CodeInfo - 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) @@ -404,22 +404,6 @@ end function finishinfer!(me::InferenceState, interp::AbstractInterpreter) # prepare to run optimization passes on fulltree @assert isempty(me.ip) - s_edges = get_stmt_edges!(me, 1) - for i = 2:length(me.stmt_edges) - isassigned(me.stmt_edges, i) || continue - edges = me.stmt_edges[i] - append!(s_edges, edges) - empty!(edges) - end - #s_edges_new = compute_edges(me) - #println(Any[z isa MethodTable ? z.name : z for z in s_edges_new]) - #if length(s_edges) != length(s_edges_new) || !all(i -> isassigned(s_edges, i) ? isassigned(s_edges_new, i) && s_edges_new[i] === s_edges[i] : !isassigned(s_edges_new, i), length(s_edges)) - # println(sizehint!(s_edges, length(s_edges))) - # println(sizehint!(s_edges_new, length(s_edges_new))) - #end - 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, # else it may be suitable to cache bestguess = me.bestguess = cycle_fix_limited(me.bestguess, me) @@ -444,6 +428,9 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) me.src.rettype = widenconst(ignorelimited(bestguess)) me.src.min_world = first(me.valid_worlds) me.src.max_world = last(me.valid_worlds) + if isa(me.linfo.def, Method) # don't add backedges to toplevel method instance + compute_edges!(me) + end if limited_ret # a parent may be cached still, but not this intermediate work: @@ -474,6 +461,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) end maybe_validate_code(me.linfo, me.src, "inferred") + isa(me.linfo.def, Method) || empty!(me.edges) # don't add backedges to toplevel method instance # finish populating inference results into the CodeInstance if possible, and maybe cache that globally for use elsewhere if isdefined(result, :ci) && !limited_ret @@ -503,7 +491,7 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) end relocatability = 0x0 di = nothing - edges = Core.svec() + edges = Core.svec(me.edges...) 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), @@ -536,7 +524,8 @@ end add_edges!(edges::Vector{Any}, info::MethodResultPure) = add_edges!(edges, info.info) add_edges!(edges::Vector{Any}, info::ConstCallInfo) = add_edges!(edges, info.call) -add_edges!(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = nothing # TODO(jwn) +add_edges!(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = nothing # merely creating the object does not imply edges +add_edges!(edges::Vector{Any}, info::OpaqueClosureCallInfo) = nothing # TODO: inference appears to have always mis-accounted for these backedges add_edges!(edges::Vector{Any}, info::ReturnTypeCallInfo) = add_edges!(edges, info.info) function add_edges!(edges::Vector{Any}, info::ApplyCallInfo) add_edges!(edges, info.call) @@ -547,23 +536,24 @@ function add_edges!(edges::Vector{Any}, info::ApplyCallInfo) end end end -add_edges!(edges::Vector{Any}, info::ModifyOpInfo) = add_edges!(edges, info.call) +add_edges!(edges::Vector{Any}, info::ModifyOpInfo) = add_edges!(edges, info.info) add_edges!(edges::Vector{Any}, info::UnionSplitInfo) = for split in info.matches; add_edges!(edges, split); end add_edges!(edges::Vector{Any}, info::UnionSplitApplyCallInfo) = for split in info.infos; add_edges!(edges, split); end -add_edges!(edges::Vector{Any}, info::FinalizerInfo) = nothing +add_edges!(edges::Vector{Any}, info::FinalizerInfo) = nothing # merely allocating a finalizer does not imply edges (unless it gets inlined later) add_edges!(edges::Vector{Any}, info::NoCallInfo) = nothing function add_edges!(edges::Vector{Any}, info::MethodMatchInfo) matches = info.results.matches - #if length(matches) == 1 && !info.results.ambig && (matches[end]::Core.MethodMatch).fully_covers - # push!(edges, specialize_method(matches[1])) - #elseif isempty(matches) || info.results.ambig || !(matches[end]::Core.MethodMatch).fully_covers - #else - # push!(edges, length(matches)) - # for m in matches - # push!(edges, specialize_method(m)) - # end - #end + if length(matches) != 1 + # TODO: add check for whether this info already exists in the edges + push!(edges, length(matches)) + push!(edges, info.atype) + end + for m in matches + mi = specialize_method(m) + length(matches) == 1 ? add_one_edge!(edges, mi) : push!(edges, mi) + end if isempty(matches) || !(matches[end]::Core.MethodMatch).fully_covers + # add legacy-style missing backedge info also exists = false for i in 1:length(edges) if edges[i] === info.mt && edges[i + 1] == info.atype @@ -576,37 +566,34 @@ function add_edges!(edges::Vector{Any}, info::MethodMatchInfo) push!(edges, info.atype) end end - for m in matches - mi = specialize_method(m) - exists = false - for i in 1:length(edges) - if edges[i] === mi && !(i > 1 && edges[i - 1] isa Type) - exists = true - break - end - end - exists || push!(edges, mi) - end + nothing end -function add_edges!(edges::Vector{Any}, info::InvokeCallInfo) - #push!(edges, 1) - mi = specialize_method(info.match) - exists = false +add_edges!(edges::Vector{Any}, info::InvokeCallInfo) = add_invoke_edge!(edges, info.atype, specialize_method(info.match)) + +function add_invoke_edge!(edges::Vector{Any}, @nospecialize(atype), mi::MethodInstance) for i in 2:length(edges) - if edges[i] === mi && edges[i - 1] isa Type && edges[i - 1] == info.atype - exists = true - break + if edges[i] === mi && edges[i - 1] isa Type && edges[i - 1] == atype + return end end - if !exists - push!(edges, info.atype) - push!(edges, mi) + push!(edges, atype) + push!(edges, mi) + nothing +end + +function add_one_edge!(edges::Vector{Any}, mi::MethodInstance) + for i in 1:length(edges) + if edges[i] === mi && !(i > 1 && edges[i - 1] isa Type) + return + end end + push!(edges, mi) nothing end -function compute_edges(sv::InferenceState) - edges = [] + +function compute_edges!(sv::InferenceState) + edges = sv.edges for i in 1:length(sv.stmt_info) info = sv.stmt_info[i] #rt = sv.ssavaluetypes[i] @@ -616,7 +603,10 @@ function compute_edges(sv::InferenceState) #end add_edges!(edges, info) end - return edges + if sv.src.edges !== nothing && sv.src.edges !== Core.svec() + append!(edges, sv.src.edges) + end + nothing end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index b3dfd73d53452..364e28a9232d5 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -346,11 +346,14 @@ end function iterate(iter::BackedgeIterator, i::Int=1) backedges = iter.backedges - i > length(backedges) && return nothing - item = backedges[i] - isa(item, MethodInstance) && return BackedgePair(nothing, item), i+1 # regular dispatch - isa(item, MethodTable) && return BackedgePair(backedges[i+1], item), i+2 # abstract dispatch - return BackedgePair(item, backedges[i+1]::MethodInstance), i+2 # `invoke` calls + while true + i > length(backedges) && return nothing + item = backedges[i] + item isa Int && (i += 2; continue) # ignore the query information if present + isa(item, MethodInstance) && return BackedgePair(nothing, item), i+1 # regular dispatch + isa(item, MethodTable) && return BackedgePair(backedges[i+1], item), i+2 # abstract dispatch + return BackedgePair(item, backedges[i+1]::MethodInstance), i+2 # `invoke` calls + end end ######### diff --git a/src/ircode.c b/src/ircode.c index df11d06e64a17..bec8d46513eef 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -868,37 +868,6 @@ 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); diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 009128b289ade..a544d8d3e9da9 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -409,7 +409,7 @@ end CC.nsplit_impl(info::NoinlineCallInfo) = CC.nsplit(info.info) CC.getsplit_impl(info::NoinlineCallInfo, idx::Int) = CC.getsplit(info.info, idx) CC.getresult_impl(info::NoinlineCallInfo, idx::Int) = CC.getresult(info.info, idx) -CC.add_uncovered_edges_impl(edges::Vector{Any}, info::NoinlineCallInfo, @nospecialize(atype)) = CC.add_uncovered_edges!(edges, info.info, atype) +CC.add_edges!(edges::Vector{Any}, info::NoinlineCallInfo) = CC.add_edges!(edges, info.info) function CC.abstract_call(interp::NoinlineInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) diff --git a/test/compiler/invalidation.jl b/test/compiler/invalidation.jl index 76cf3cbdc0796..55faa4287da24 100644 --- a/test/compiler/invalidation.jl +++ b/test/compiler/invalidation.jl @@ -95,7 +95,8 @@ end const GLOBAL_BUFFER = IOBuffer() # test backedge optimization when the callee's type and effects information are maximized -begin take!(GLOBAL_BUFFER) +begin + take!(GLOBAL_BUFFER) pr48932_callee(x) = (print(GLOBAL_BUFFER, x); Base.inferencebarrier(x)) pr48932_caller(x) = pr48932_callee(Base.inferencebarrier(x)) @@ -150,11 +151,11 @@ begin take!(GLOBAL_BUFFER) ci = mi.cache @test isdefined(ci, :next) @test ci.owner === nothing - @test ci.max_world == typemax(UInt) + @test_broken ci.max_world == typemax(UInt) ci = ci.next @test !isdefined(ci, :next) @test ci.owner === InvalidationTesterToken() - @test ci.max_world == typemax(UInt) + @test_broken ci.max_world == typemax(UInt) end @test isnothing(pr48932_caller(42)) @@ -213,11 +214,11 @@ begin take!(GLOBAL_BUFFER) ci = mi.cache @test isdefined(ci, :next) @test ci.owner === nothing - @test ci.max_world == typemax(UInt) + @test_broken ci.max_world == typemax(UInt) ci = ci.next @test !isdefined(ci, :next) @test ci.owner === InvalidationTesterToken() - @test ci.max_world == typemax(UInt) + @test_broken ci.max_world == typemax(UInt) end @test isnothing(pr48932_caller_unuse(42)) @test "foo" == String(take!(GLOBAL_BUFFER)) From 24c7aa12c9c5232725408e322d3947d0a51c3933 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 18 Jul 2024 09:33:42 -0400 Subject: [PATCH 4/8] Use original computed edges during serialization instead of trying to guess them --- base/compiler/abstractinterpretation.jl | 2 - base/compiler/typeinfer.jl | 42 +- src/gf.c | 5 +- src/staticdata.c | 68 ++- src/staticdata_utils.c | 602 +++++++++--------------- test/precompile.jl | 2 +- test/precompile_absint1.jl | 12 +- test/precompile_absint2.jl | 10 +- 8 files changed, 305 insertions(+), 438 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index b962c92176617..2b4e814394f8c 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -57,7 +57,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), gfresult = Future{CallMeta}() # intermediate work for computing gfresult rettype = exctype = Bottom - edges = MethodInstance[] conditionals = nothing # keeps refinement information of call argument types when the return type is boolean seenall = true const_results = nothing # or const_results::Vector{Union{Nothing,ConstResult}} if any const results are available @@ -142,7 +141,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end const_results[i] = const_result end - edge === nothing || push!(edges, edge) @assert !(this_conditional isa Conditional || this_rt isa MustAlias) "invalid lattice element returned from inter-procedural context" if can_propagate_conditional(this_conditional, argtypes) # The only case where we need to keep this in rt is where diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 44c6dfd2e3539..79e46cc0d7498 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -524,8 +524,8 @@ end add_edges!(edges::Vector{Any}, info::MethodResultPure) = add_edges!(edges, info.info) add_edges!(edges::Vector{Any}, info::ConstCallInfo) = add_edges!(edges, info.call) -add_edges!(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = nothing # merely creating the object does not imply edges -add_edges!(edges::Vector{Any}, info::OpaqueClosureCallInfo) = nothing # TODO: inference appears to have always mis-accounted for these backedges +add_edges!(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = add_edges!(edges, info.unspec.info) # merely creating the object implies edges for OC, unlike normal objects, since calling them doesn't normally have edges in contrast +add_edges!(edges::Vector{Any}, info::OpaqueClosureCallInfo) = add_one_edge!(edges, specialize_method(info.match)) add_edges!(edges::Vector{Any}, info::ReturnTypeCallInfo) = add_edges!(edges, info.info) function add_edges!(edges::Vector{Any}, info::ApplyCallInfo) add_edges!(edges, info.call) @@ -543,15 +543,6 @@ add_edges!(edges::Vector{Any}, info::FinalizerInfo) = nothing # merely allocatin add_edges!(edges::Vector{Any}, info::NoCallInfo) = nothing function add_edges!(edges::Vector{Any}, info::MethodMatchInfo) matches = info.results.matches - if length(matches) != 1 - # TODO: add check for whether this info already exists in the edges - push!(edges, length(matches)) - push!(edges, info.atype) - end - for m in matches - mi = specialize_method(m) - length(matches) == 1 ? add_one_edge!(edges, mi) : push!(edges, mi) - end if isempty(matches) || !(matches[end]::Core.MethodMatch).fully_covers # add legacy-style missing backedge info also exists = false @@ -566,6 +557,29 @@ function add_edges!(edges::Vector{Any}, info::MethodMatchInfo) push!(edges, info.atype) end end + if length(matches) == 1 + # try the optimized format for the representation, if possible and applicable + # if this doesn't succeed, the backedge will be less precise, + # but the forward edge will maintain the precision + m = matches[1]::Core.MethodMatch + mi = specialize_method(m) + if mi.specTypes === m.spec_types + add_one_edge!(edges, mi) + return + end + end + # add check for whether this lookup already existed in the edges list + for i in 1:length(edges) + if edges[i] === length(matches) && edges[i + 1] == info.atype + return + end + end + push!(edges, length(matches)) + push!(edges, info.atype) + for m in matches + mi = specialize_method(m::Core.MethodMatch) + push!(edges, mi) + end nothing end add_edges!(edges::Vector{Any}, info::InvokeCallInfo) = add_invoke_edge!(edges, info.atype, specialize_method(info.match)) @@ -591,14 +605,14 @@ function add_one_edge!(edges::Vector{Any}, mi::MethodInstance) nothing end - function compute_edges!(sv::InferenceState) edges = sv.edges for i in 1:length(sv.stmt_info) info = sv.stmt_info[i] #rt = sv.ssavaluetypes[i] - #effects = EFFECTS_TOTAL # sv.stmt_effects[i] - #if rt === Any && effects === Effects() + #et = sv.exectiontypes[i] + #effects = EFFECTS_TOTAL # TODO: sv.stmt_effects[i] + #if rt === Any && et === Any && effects === Effects() # continue #end add_edges!(edges, info) diff --git a/src/gf.c b/src/gf.c index c4fac6d0a3f07..49bc7d551ad7a 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1820,9 +1820,12 @@ static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_w // add a backedge from callee to caller JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_value_t *invokesig, jl_method_instance_t *caller) { - JL_LOCK(&callee->def.method->writelock); if (invokesig == jl_nothing) invokesig = NULL; // julia uses `nothing` but C uses NULL (#undef) + assert(jl_is_method_instance(callee)); + assert(jl_is_method_instance(caller)); + assert(invokesig == NULL || jl_is_type(invokesig)); + JL_LOCK(&callee->def.method->writelock); int found = 0; // TODO: use jl_cache_type_(invokesig) like cache_method does to save memory if (!callee->backedges) { diff --git a/src/staticdata.c b/src/staticdata.c index 0a8cbe6db7c67..aaa3cbc907736 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -2708,25 +2708,21 @@ JL_DLLEXPORT jl_value_t *jl_as_global_root(jl_value_t *val, int insert) } static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *newly_inferred, uint64_t worklist_key, - /* outputs */ jl_array_t **extext_methods, jl_array_t **new_ext_cis, - jl_array_t **method_roots_list, jl_array_t **ext_targets, jl_array_t **edges) + /* outputs */ jl_array_t **extext_methods JL_REQUIRE_ROOTED_SLOT, + jl_array_t **new_ext_cis JL_REQUIRE_ROOTED_SLOT, + jl_array_t **method_roots_list JL_REQUIRE_ROOTED_SLOT, + jl_array_t **edges JL_REQUIRE_ROOTED_SLOT) { // extext_methods: [method1, ...], worklist-owned "extending external" methods added to functions owned by modules outside the worklist - // ext_targets: [invokesig1, callee1, matches1, ...] non-worklist callees of worklist-owned methods - // ordinary dispatch: invokesig=NULL, callee is MethodInstance - // `invoke` dispatch: invokesig is signature, callee is MethodInstance - // abstract call: callee is signature - // edges: [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods - assert(edges_map == NULL); + // edges: [caller1, ext_targets, ...] for worklist-owned methods calling external methods // Save the inferred code from newly inferred, external methods *new_ext_cis = queue_external_cis(newly_inferred); // Collect method extensions and edges data - JL_GC_PUSH1(&edges_map); - if (edges) - edges_map = jl_alloc_memory_any(0); *extext_methods = jl_alloc_vec_any(0); + internal_methods = jl_alloc_vec_any(0); + JL_GC_PUSH1(&internal_methods); jl_collect_methtable_from_mod(jl_type_type_mt, *extext_methods); jl_collect_methtable_from_mod(jl_nonfunction_mt, *extext_methods); size_t i, len = jl_array_len(mod_array); @@ -2739,18 +2735,15 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new if (edges) { size_t world = jl_atomic_load_acquire(&jl_world_counter); - jl_collect_missing_backedges(jl_type_type_mt); - jl_collect_missing_backedges(jl_nonfunction_mt); - // jl_collect_extext_methods_from_mod and jl_collect_missing_backedges also accumulate data in callers_with_edges. - // Process this to extract `edges` and `ext_targets`. - *ext_targets = jl_alloc_vec_any(0); - *edges = jl_alloc_vec_any(0); + // jl_collect_extext_methods_from_mod accumulate data in callers_with_edges. + // Process this to extract `new_ext_cis` and `edges` *method_roots_list = jl_alloc_vec_any(0); // Collect the new method roots for external specializations jl_collect_new_roots(&relocatable_ext_cis, *method_roots_list, *new_ext_cis, worklist_key); - jl_collect_edges(*edges, *ext_targets, *new_ext_cis, world); + *edges = jl_alloc_vec_any(0); + jl_collect_edges(*edges, *new_ext_cis, world); } - assert(edges_map == NULL); // jl_collect_edges clears this when done + internal_methods = NULL; JL_GC_POP(); } @@ -2759,7 +2752,7 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_array_t *worklist, jl_array_t *extext_methods, jl_array_t *new_ext_cis, jl_array_t *method_roots_list, - jl_array_t *ext_targets, jl_array_t *edges) JL_GC_DISABLED + jl_array_t *edges) JL_GC_DISABLED { htable_new(&field_replace, 0); // strip metadata and IR when requested @@ -2892,7 +2885,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, } // step 1.1: as needed, serialize the data needed for insertion into the running system if (extext_methods) { - assert(ext_targets); assert(edges); // Queue method extensions jl_queue_for_serialization(&s, extext_methods); @@ -2901,7 +2893,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, // Queue the new roots jl_queue_for_serialization(&s, method_roots_list); // Queue the edges - jl_queue_for_serialization(&s, ext_targets); jl_queue_for_serialization(&s, edges); } jl_serialize_reachable(&s); @@ -3081,7 +3072,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_write_value(&s, extext_methods); jl_write_value(&s, new_ext_cis); jl_write_value(&s, method_roots_list); - jl_write_value(&s, ext_targets); jl_write_value(&s, edges); } write_uint32(f, jl_array_len(s.link_ids_gctags)); @@ -3171,18 +3161,18 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli } jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_ext_cis = NULL; - jl_array_t *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; + jl_array_t *method_roots_list = NULL, *edges = NULL; int64_t checksumpos = 0; int64_t checksumpos_ff = 0; int64_t datastartpos = 0; - JL_GC_PUSH6(&mod_array, &extext_methods, &new_ext_cis, &method_roots_list, &ext_targets, &edges); + JL_GC_PUSH5(&mod_array, &extext_methods, &new_ext_cis, &method_roots_list, &edges); if (worklist) { mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array) // Generate _native_data` if (_native_data != NULL) { jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), - &extext_methods, &new_ext_cis, NULL, NULL, NULL); + &extext_methods, &new_ext_cis, NULL, NULL); jl_precompile_toplevel_module = (jl_module_t*)jl_array_ptr_ref(worklist, jl_array_len(worklist)-1); *_native_data = jl_precompile_worklist(worklist, extext_methods, new_ext_cis); jl_precompile_toplevel_module = NULL; @@ -3215,7 +3205,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli if (worklist) { htable_new(&relocatable_ext_cis, 0); jl_prepare_serialization_data(mod_array, newly_inferred, jl_worklist_key(worklist), - &extext_methods, &new_ext_cis, &method_roots_list, &ext_targets, &edges); + &extext_methods, &new_ext_cis, &method_roots_list, &edges); if (!emit_split) { write_int32(f, 0); // No clone_targets write_padding(f, LLT_ALIGN(ios_pos(f), JL_CACHE_BYTE_ALIGNMENT) - ios_pos(f)); @@ -3227,7 +3217,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli } if (_native_data != NULL) native_functions = *_native_data; - jl_save_system_image_to_stream(ff, mod_array, worklist, extext_methods, new_ext_cis, method_roots_list, ext_targets, edges); + jl_save_system_image_to_stream(ff, mod_array, worklist, extext_methods, new_ext_cis, method_roots_list, edges); if (_native_data != NULL) native_functions = NULL; if (worklist) @@ -3317,7 +3307,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl /* outputs */ jl_array_t **restored, jl_array_t **init_order, jl_array_t **extext_methods, jl_array_t **internal_methods, jl_array_t **new_ext_cis, jl_array_t **method_roots_list, - jl_array_t **ext_targets, jl_array_t **edges, + jl_array_t **edges, char **base, arraylist_t *ccallable_list, pkgcachesizes *cachesizes) JL_GC_DISABLED { jl_task_t *ct = jl_current_task; @@ -3388,7 +3378,7 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl assert(!ios_eof(f)); s.s = f; uintptr_t offset_restored = 0, offset_init_order = 0, offset_extext_methods = 0, offset_new_ext_cis = 0, offset_method_roots_list = 0; - uintptr_t offset_ext_targets = 0, offset_edges = 0; + uintptr_t offset_edges = 0; if (!s.incremental) { size_t i; for (i = 0; tags[i] != NULL; i++) { @@ -3421,7 +3411,6 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl offset_extext_methods = jl_read_offset(&s); offset_new_ext_cis = jl_read_offset(&s); offset_method_roots_list = jl_read_offset(&s); - offset_ext_targets = jl_read_offset(&s); offset_edges = jl_read_offset(&s); } s.buildid_depmods_idxs = depmod_to_imageidx(depmods); @@ -3448,13 +3437,12 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl uint32_t external_fns_begin = read_uint32(f); jl_read_arraylist(s.s, ccallable_list ? ccallable_list : &s.ccallable_list); if (s.incremental) { - assert(restored && init_order && extext_methods && internal_methods && new_ext_cis && method_roots_list && ext_targets && edges); + assert(restored && init_order && extext_methods && internal_methods && new_ext_cis && method_roots_list && edges); *restored = (jl_array_t*)jl_delayed_reloc(&s, offset_restored); *init_order = (jl_array_t*)jl_delayed_reloc(&s, offset_init_order); *extext_methods = (jl_array_t*)jl_delayed_reloc(&s, offset_extext_methods); *new_ext_cis = (jl_array_t*)jl_delayed_reloc(&s, offset_new_ext_cis); *method_roots_list = (jl_array_t*)jl_delayed_reloc(&s, offset_method_roots_list); - *ext_targets = (jl_array_t*)jl_delayed_reloc(&s, offset_ext_targets); *edges = (jl_array_t*)jl_delayed_reloc(&s, offset_edges); *internal_methods = jl_alloc_vec_any(0); } @@ -3892,9 +3880,9 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i arraylist_t ccallable_list; jl_value_t *restored = NULL; - jl_array_t *init_order = NULL, *extext_methods = NULL, *internal_methods = NULL, *new_ext_cis = NULL, *method_roots_list = NULL, *ext_targets = NULL, *edges = NULL; + jl_array_t *init_order = NULL, *extext_methods = NULL, *internal_methods = NULL, *new_ext_cis = NULL, *method_roots_list = NULL, *edges = NULL; jl_svec_t *cachesizes_sv = NULL; - JL_GC_PUSH9(&restored, &init_order, &extext_methods, &internal_methods, &new_ext_cis, &method_roots_list, &ext_targets, &edges, &cachesizes_sv); + JL_GC_PUSH8(&restored, &init_order, &extext_methods, &internal_methods, &new_ext_cis, &method_roots_list, &edges, &cachesizes_sv); { // make a permanent in-memory copy of f (excluding the header) ios_bufmode(f, bm_none); @@ -3919,7 +3907,7 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i ios_static_buffer(f, sysimg, len); pkgcachesizes cachesizes; jl_restore_system_image_from_stream_(f, image, depmods, checksum, (jl_array_t**)&restored, &init_order, &extext_methods, &internal_methods, &new_ext_cis, &method_roots_list, - &ext_targets, &edges, &base, &ccallable_list, &cachesizes); + &edges, &base, &ccallable_list, &cachesizes); JL_SIGATOMIC_END(); // No special processing of `new_ext_cis` is required because recaching handled it @@ -3933,7 +3921,7 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i // allow users to start running in this updated world jl_atomic_store_release(&jl_world_counter, world); // but one of those immediate users is going to be our cache updates - jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)ext_targets, (jl_array_t*)new_ext_cis, world); // restore external backedges (needs to be last) + jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)new_ext_cis, world); // restore external backedges (needs to be last) // now permit more methods to be added again JL_UNLOCK(&world_counter_lock); // reinit ccallables @@ -3949,9 +3937,9 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i jl_svecset(cachesizes_sv, 4, jl_box_long(cachesizes.reloclist)); jl_svecset(cachesizes_sv, 5, jl_box_long(cachesizes.gvarlist)); jl_svecset(cachesizes_sv, 6, jl_box_long(cachesizes.fptrlist)); - restored = (jl_value_t*)jl_svec(8, restored, init_order, extext_methods, + restored = (jl_value_t*)jl_svec(7, restored, init_order, extext_methods, new_ext_cis ? (jl_value_t*)new_ext_cis : jl_nothing, - method_roots_list, ext_targets, edges, cachesizes_sv); + method_roots_list, edges, cachesizes_sv); } else { restored = (jl_value_t*)jl_svec(2, restored, init_order); @@ -3966,7 +3954,7 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image, uint32_t checksum) { JL_TIMING(LOAD_IMAGE, LOAD_Sysimg); - jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 8eb223d3cfbde..823b5e1174057 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -1,5 +1,5 @@ // inverse of backedges graph (caller=>callees hash) -jl_genericmemory_t *edges_map JL_GLOBALLY_ROOTED = NULL; // rooted for the duration of our uses of this +jl_array_t *internal_methods JL_GLOBALLY_ROOTED = NULL; // rooted for the duration of our uses of this static void write_float64(ios_t *s, double x) JL_NOTSAFEPOINT { @@ -246,7 +246,7 @@ static jl_array_t *queue_external_cis(jl_array_t *list) continue; jl_method_instance_t *mi = ci->def; jl_method_t *m = mi->def.method; - if (jl_atomic_load_relaxed(&ci->inferred) && jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) { + if (ci->owner == jl_nothing && jl_atomic_load_relaxed(&ci->inferred) && jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) { int found = has_backedge_to_worklist(mi, &visited, &stack); assert(found == 0 || found == 1 || found == 2); assert(stack.len == 0); @@ -314,82 +314,19 @@ static void jl_collect_new_roots(htable_t *relocatable_ext_cis, jl_array_t *root htable_free(&mset); } -// Create the forward-edge map (caller => callees) -// the intent of these functions is to invert the backedges tree -// for anything that points to a method not part of the worklist -// -// from MethodTables -static void jl_collect_missing_backedges(jl_methtable_t *mt) -{ - jl_array_t *backedges = mt->backedges; - if (backedges) { - size_t i, l = jl_array_nrows(backedges); - for (i = 1; i < l; i += 2) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); - jl_value_t *missing_callee = jl_array_ptr_ref(backedges, i - 1); // signature of abstract callee - jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); - if (edges == NULL) { - edges = jl_alloc_vec_any(0); - JL_GC_PUSH1(&edges); - edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); - JL_GC_POP(); - } - jl_array_ptr_1d_push(edges, NULL); - jl_array_ptr_1d_push(edges, missing_callee); - } - } -} - - -// from MethodInstances -static void collect_backedges(jl_method_instance_t *callee, int internal) -{ - jl_array_t *backedges = callee->backedges; - if (backedges) { - size_t i = 0, l = jl_array_nrows(backedges); - while (i < l) { - jl_value_t *invokeTypes; - jl_method_instance_t *caller; - i = get_next_edge(backedges, i, &invokeTypes, &caller); - jl_array_t *edges = (jl_array_t*)jl_eqtable_get(edges_map, (jl_value_t*)caller, NULL); - if (edges == NULL) { - edges = jl_alloc_vec_any(0); - JL_GC_PUSH1(&edges); - edges_map = jl_eqtable_put(edges_map, (jl_value_t*)caller, (jl_value_t*)edges, NULL); - JL_GC_POP(); - } - jl_array_ptr_1d_push(edges, invokeTypes); - jl_array_ptr_1d_push(edges, (jl_value_t*)callee); - } - } -} - -// For functions owned by modules not on the worklist, call this on each method. +// For every method: // - if the method is owned by a worklist module, add it to the list of things to be -// fully serialized -// - Collect all backedges (may be needed later when we invert this list). +// verified on reloading +// - if the method is extext, record that it needs to be reinserted later in the method table static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) { jl_array_t *s = (jl_array_t*)closure; jl_method_t *m = ml->func.method; - if (s && !jl_object_in_image((jl_value_t*)m->module)) { - jl_array_ptr_1d_push(s, (jl_value_t*)m); - } - if (edges_map == NULL) - return 1; - jl_value_t *specializations = jl_atomic_load_relaxed(&m->specializations); - if (!jl_is_svec(specializations)) { - jl_method_instance_t *callee = (jl_method_instance_t*)specializations; - collect_backedges(callee, !s); - } - else { - size_t i, l = jl_svec_len(specializations); - for (i = 0; i < l; i++) { - jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(specializations, i); - if ((jl_value_t*)callee != jl_nothing) - collect_backedges(callee, !s); - } + if (!jl_object_in_image((jl_value_t*)m->module)) { + jl_array_ptr_1d_push(internal_methods, (jl_value_t*)m); + if (s) + jl_array_ptr_1d_push(s, (jl_value_t*)m); // extext } return 1; } @@ -397,10 +334,8 @@ static int jl_collect_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) static int jl_collect_methtable_from_mod(jl_methtable_t *mt, void *env) { if (!jl_object_in_image((jl_value_t*)mt)) - env = NULL; // do not collect any methods from here + env = NULL; // mark internal, not extext jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), jl_collect_methcache_from_mod, env); - if (env && edges_map) - jl_collect_missing_backedges(mt); return 1; } @@ -412,169 +347,69 @@ static void jl_collect_extext_methods_from_mod(jl_array_t *s, jl_module_t *m) foreach_mtable_in_module(m, jl_collect_methtable_from_mod, s); } -static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_array_t *edges) +static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, htable_t *inserted, jl_array_t *edges) { - jl_array_t *callees = NULL; - JL_GC_PUSH2(&caller, &callees); - callees = (jl_array_t*)jl_eqtable_pop(edges_map, (jl_value_t*)caller, NULL, NULL); - if (callees != NULL) { - jl_array_ptr_1d_push(edges, (jl_value_t*)caller); - jl_array_ptr_1d_push(edges, (jl_value_t*)callees); - size_t i, l = jl_array_nrows(callees); - for (i = 1; i < l; i += 2) { - jl_method_instance_t *c = (jl_method_instance_t*)jl_array_ptr_ref(callees, i); - if (c && jl_is_method_instance(c)) { - arraylist_push(wq, c); + JL_GC_PROMISE_ROOTED(caller); + void **bp = ptrhash_bp(inserted, (void*)caller); + if (*bp != HT_NOTFOUND) + return; + *bp = (void*)caller; + jl_code_instance_t *ci = jl_atomic_load_relaxed(&caller->cache); + while (ci != NULL) { + jl_svec_t *targets = jl_atomic_load_relaxed(&ci->edges); + if (ci->owner == jl_nothing && targets && targets != jl_emptysvec && + jl_atomic_load_relaxed(&ci->max_world) == ~(size_t)0 && jl_atomic_load_relaxed(&ci->min_world) >= 1) { + jl_array_ptr_1d_push(edges, (jl_value_t*)caller); + jl_array_ptr_1d_push(edges, (jl_value_t*)targets); + size_t i, l = jl_svec_len(targets); + for (i = 0; i < l; i++) { + jl_value_t *c = jl_svecref(targets, i); + if (c && jl_is_method_instance(c)) { + arraylist_push(wq, c); + } } } + ci = jl_atomic_load_relaxed(&ci->next); } - JL_GC_POP(); } - // Extract `edges` and `ext_targets` from `edges_map` -// `edges` = [caller1, targets_indexes1, ...], the list of methods and their edges -// `ext_targets` is [invokesig1, callee1, matches1, ...], the edges for each target -static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *external_cis, size_t world) +// `edges` = [caller1, targets, ...], the list of methodinstances and their edges info +static void jl_collect_edges(jl_array_t *edges, jl_array_t *external_cis, size_t world) { - htable_t external_mis; - htable_new(&external_mis, 0); + htable_t inserted; + htable_new(&inserted, 0); + arraylist_t wq; + arraylist_new(&wq, 0); if (external_cis) { for (size_t i = 0; i < jl_array_nrows(external_cis); i++) { jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(external_cis, i); jl_method_instance_t *mi = ci->def; - ptrhash_put(&external_mis, (void*)mi, (void*)mi); + jl_record_edges(mi, &wq, &inserted, edges); } } - arraylist_t wq; - arraylist_new(&wq, 0); - void **table = (void**) edges_map->ptr; // edges_map is caller => callees - size_t table_size = edges_map->length; - for (size_t i = 0; i < table_size; i += 2) { - assert(table == edges_map->ptr && table_size == edges_map->length && - "edges_map changed during iteration"); - jl_method_instance_t *caller = (jl_method_instance_t*)table[i]; - jl_array_t *callees = (jl_array_t*)table[i + 1]; - if (callees == NULL) - continue; - assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - if (!jl_object_in_image((jl_value_t*)caller->def.method->module) || - ptrhash_get(&external_mis, caller) != HT_NOTFOUND) { - jl_record_edges(caller, &wq, edges); + for (size_t i = 0; i < jl_array_nrows(internal_methods); i++) { + jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(internal_methods, i); + jl_value_t *specializations = jl_atomic_load_relaxed(&m->specializations); + if (!jl_is_svec(specializations)) { + jl_method_instance_t *mi = (jl_method_instance_t*)specializations; + jl_record_edges(mi, &wq, &inserted, edges); + } + else { + size_t j, l = jl_svec_len(specializations); + for (j = 0; j < l; j++) { + jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, j); + if ((jl_value_t*)mi != jl_nothing) + jl_record_edges(mi, &wq, &inserted, edges); + } } } - htable_free(&external_mis); while (wq.len) { jl_method_instance_t *caller = (jl_method_instance_t*)arraylist_pop(&wq); - jl_record_edges(caller, &wq, edges); + jl_record_edges(caller, &wq, &inserted, edges); } + htable_free(&inserted); arraylist_free(&wq); - edges_map = NULL; - htable_t edges_map2; - htable_new(&edges_map2, 0); - htable_t edges_ids; - size_t l = edges ? jl_array_nrows(edges) : 0; - htable_new(&edges_ids, l); - for (size_t i = 0; i < l / 2; i++) { - jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, i * 2); - void *target = (void*)((char*)HT_NOTFOUND + i + 1); - ptrhash_put(&edges_ids, (void*)caller, target); - } - // process target list to turn it into a memoized validity table - // and compute the old methods list, ready for serialization - jl_value_t *matches = NULL; - jl_array_t *callee_ids = NULL; - jl_value_t *sig = NULL; - JL_GC_PUSH3(&matches, &callee_ids, &sig); - for (size_t i = 0; i < l; i += 2) { - jl_array_t *callees = (jl_array_t*)jl_array_ptr_ref(edges, i + 1); - size_t l = jl_array_nrows(callees); - callee_ids = jl_alloc_array_1d(jl_array_int32_type, l + 1); - int32_t *idxs = jl_array_data(callee_ids, int32_t); - idxs[0] = 0; - size_t nt = 0; - for (size_t j = 0; j < l; j += 2) { - jl_value_t *invokeTypes = jl_array_ptr_ref(callees, j); - jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); - assert(callee && "unsupported edge"); - - if (jl_is_method_instance(callee)) { - jl_methtable_t *mt = jl_method_get_table(((jl_method_instance_t*)callee)->def.method); - if (!jl_object_in_image((jl_value_t*)mt)) - continue; - } - - // (nullptr, c) => call - // (invokeTypes, c) => invoke - // (nullptr, invokeTypes) => missing call - // (invokeTypes, nullptr) => missing invoke (unused--inferred as Any) - void *target = ptrhash_get(&edges_map2, invokeTypes ? (void*)invokeTypes : (void*)callee); - if (target == HT_NOTFOUND) { - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - if (invokeTypes) { - assert(jl_is_method_instance(callee)); - jl_method_t *m = ((jl_method_instance_t*)callee)->def.method; - matches = (jl_value_t*)m; // valid because there is no method replacement permitted -#ifndef NDEBUG - jl_methtable_t *mt = jl_method_get_table(m); - if ((jl_value_t*)mt != jl_nothing) { - jl_value_t *matches = jl_gf_invoke_lookup_worlds(invokeTypes, (jl_value_t*)mt, world, &min_valid, &max_valid); - if (matches != jl_nothing) { - assert(m == ((jl_method_match_t*)matches)->method); - } - } -#endif - } - else { - if (jl_is_method_instance(callee)) { - jl_method_instance_t *mi = (jl_method_instance_t*)callee; - sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); - } - else { - sig = callee; - } - int ambig = 0; - matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, - INT32_MAX, 0, world, &min_valid, &max_valid, &ambig); - sig = NULL; - if (matches == jl_nothing) { - callee_ids = NULL; // invalid - break; - } - size_t k; - for (k = 0; k < jl_array_nrows(matches); k++) { - jl_method_match_t *match = (jl_method_match_t *)jl_array_ptr_ref(matches, k); - jl_array_ptr_set(matches, k, match->method); - } - } - jl_array_ptr_1d_push(ext_targets, invokeTypes); - jl_array_ptr_1d_push(ext_targets, callee); - jl_array_ptr_1d_push(ext_targets, matches); - target = (void*)((char*)HT_NOTFOUND + jl_array_nrows(ext_targets) / 3); - ptrhash_put(&edges_map2, (void*)callee, target); - } - idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; - } - jl_array_ptr_set(edges, i + 1, callee_ids); // swap callees for ids - if (!callee_ids) - continue; - idxs[0] = nt; - // record place of every method in edges - // add method edges to the callee_ids list - for (size_t j = 0; j < l; j += 2) { - jl_value_t *callee = jl_array_ptr_ref(callees, j + 1); - if (callee && jl_is_method_instance(callee)) { - void *target = ptrhash_get(&edges_ids, (void*)callee); - if (target != HT_NOTFOUND) { - idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1; - } - } - } - jl_array_del_end(callee_ids, l - nt); - } - JL_GC_POP(); - htable_free(&edges_map2); } // Headers @@ -943,155 +778,147 @@ static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) } } - -// verify that these edges intersect with the same methods as before -static jl_array_t *jl_verify_edges(jl_array_t *targets, size_t minworld) +static size_t verify_invokesig(jl_value_t *invokesig, jl_method_instance_t *expected, size_t minworld) { - JL_TIMING(VERIFY_IMAGE, VERIFY_Edges); - size_t i, l = jl_array_nrows(targets) / 3; - static jl_value_t *ulong_array JL_ALWAYS_LEAFTYPE = NULL; - if (ulong_array == NULL) - ulong_array = jl_apply_array_type((jl_value_t*)jl_ulong_type, 1); - jl_array_t *maxvalids = jl_alloc_array_1d(ulong_array, l); - memset(jl_array_data(maxvalids, size_t), 0, l * sizeof(size_t)); - jl_value_t *loctag = NULL; - jl_value_t *matches = NULL; - jl_value_t *sig = NULL; - JL_GC_PUSH4(&maxvalids, &matches, &sig, &loctag); - for (i = 0; i < l; i++) { - jl_value_t *invokesig = jl_array_ptr_ref(targets, i * 3); - jl_value_t *callee = jl_array_ptr_ref(targets, i * 3 + 1); - jl_value_t *expected = jl_array_ptr_ref(targets, i * 3 + 2); - size_t min_valid = 0; - size_t max_valid = ~(size_t)0; - if (invokesig) { - assert(callee && "unsupported edge"); - jl_method_t *m = ((jl_method_instance_t*)callee)->def.method; - if (jl_egal(invokesig, m->sig)) { - // the invoke match is `m` for `m->sig`, unless `m` is invalid - if (jl_atomic_load_relaxed(&m->deleted_world) < max_valid) - max_valid = 0; - } - else { - jl_methtable_t *mt = jl_method_get_table(m); - if ((jl_value_t*)mt == jl_nothing) { - max_valid = 0; - } - else { - matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, minworld, &min_valid, &max_valid); - if (matches == jl_nothing) { - max_valid = 0; - } - else { - matches = (jl_value_t*)((jl_method_match_t*)matches)->method; - if (matches != expected) { - max_valid = 0; - } - } - } - } + assert(jl_is_type(invokesig)); + assert(jl_is_method_instance(expected)); + jl_method_t *m = ((jl_method_instance_t*)expected)->def.method; + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + if (jl_egal(invokesig, m->sig)) { + // the invoke match is `m` for `m->sig`, unless `m` is invalid + if (jl_atomic_load_relaxed(&m->deleted_world) < max_valid) + max_valid = 0; + } + else { + jl_methtable_t *mt = jl_method_get_table(m); + if ((jl_value_t*)mt == jl_nothing) { + max_valid = 0; } else { - if (jl_is_method_instance(callee)) { - jl_method_instance_t *mi = (jl_method_instance_t*)callee; - sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); - } - else { - sig = callee; - } - assert(jl_is_array(expected)); - int ambig = 0; - // TODO: possibly need to included ambiguities too (for the optimizer correctness)? - // len + 1 is to allow us to log causes of invalidation (SnoopCompile's @snoopr) - matches = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, - _jl_debug_method_invalidation ? INT32_MAX : jl_array_nrows(expected), - 0, minworld, &min_valid, &max_valid, &ambig); - sig = NULL; + jl_value_t *matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, minworld, &min_valid, &max_valid); if (matches == jl_nothing) { max_valid = 0; } else { - // setdiff!(matches, expected) - size_t j, k, ins = 0; - if (jl_array_nrows(matches) != jl_array_nrows(expected)) { + if (((jl_method_match_t*)matches)->method != m) { max_valid = 0; } - for (k = 0; k < jl_array_nrows(matches); k++) { - jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(matches, k))->method; - size_t l = jl_array_nrows(expected); - for (j = 0; j < l; j++) - if (match == (jl_method_t*)jl_array_ptr_ref(expected, j)) - break; - if (j == l) { - // intersection has a new method or a method was - // deleted--this is now probably no good, just invalidate - // everything about it now - max_valid = 0; - if (!_jl_debug_method_invalidation) - break; - jl_array_ptr_set(matches, ins++, match); - } - } - if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) - jl_array_del_end((jl_array_t*)matches, jl_array_nrows(matches) - ins); } } - jl_array_data(maxvalids, size_t)[i] = max_valid; - if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, invokesig ? (jl_value_t*)invokesig : callee); - loctag = jl_cstr_to_string("insert_backedges_callee"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - loctag = jl_box_int32((int32_t)i); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); + } + return max_valid; +} + +static size_t verify_call(jl_value_t *sig, jl_svec_t *expecteds, size_t i, size_t n, size_t minworld, jl_value_t **matches JL_REQUIRE_ROOTED_SLOT) +{ + // verify that these edges intersect with the same methods as before + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + int ambig = 0; + // TODO: possibly need to included ambiguities too (for the optimizer correctness)? + jl_value_t *result = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, + _jl_debug_method_invalidation ? INT32_MAX : n, + 0, minworld, &min_valid, &max_valid, &ambig); + *matches = result; + if (result == jl_nothing) { + max_valid = 0; + } + else { + // setdiff!(result, expected) + size_t j, k, ins = 0; + if (jl_array_nrows(result) != n) { + max_valid = 0; } - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)invokesig); - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)callee); - //ios_puts(max_valid == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); + for (k = 0; k < jl_array_nrows(result); k++) { + jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(result, k))->method; + for (j = 0; j < n; j++) + if (match == ((jl_method_instance_t*)jl_svecref(expecteds, j + i))->def.method) + break; + if (j == n) { + // intersection has a new method or a method was + // deleted--this is now probably no good, just invalidate + // everything about it now + max_valid = 0; + if (!_jl_debug_method_invalidation) + break; + jl_array_ptr_set(result, ins++, match); + } + } + if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) + jl_array_del_end((jl_array_t*)result, jl_array_nrows(result) - ins); } - JL_GC_POP(); - return maxvalids; + return max_valid; } // Combine all edges relevant to a method to initialize the maxvalids list -static jl_array_t *jl_verify_methods(jl_array_t *edges, jl_array_t *maxvalids) +static jl_array_t *jl_verify_methods(jl_array_t *edges, size_t minworld) { JL_TIMING(VERIFY_IMAGE, VERIFY_Methods); jl_value_t *loctag = NULL; + jl_value_t *sig = NULL; + jl_value_t *matches = NULL; jl_array_t *maxvalids2 = NULL; - JL_GC_PUSH2(&loctag, &maxvalids2); + JL_GC_PUSH4(&loctag, &maxvalids2, &matches, &sig); size_t i, l = jl_array_nrows(edges) / 2; - maxvalids2 = jl_alloc_array_1d(jl_typeof(maxvalids), l); + jl_value_t *ulong_array = jl_apply_array_type((jl_value_t*)jl_ulong_type, 1); + JL_GC_PROMISE_ROOTED(ulong_array); // (JL_ALWAYS_LEAFTYPE) + maxvalids2 = jl_alloc_array_1d(ulong_array, l); size_t *maxvalids2_data = jl_array_data(maxvalids2, size_t); - memset(maxvalids2_data, 0, l * sizeof(size_t)); + memset(maxvalids2_data, -1, l * sizeof(size_t)); for (i = 0; i < l; i++) { jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method)); - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); - assert(jl_typetagis((jl_value_t*)callee_ids, jl_array_int32_type)); - if (callee_ids == NULL) { - // serializing the edges had failed - maxvalids2_data[i] = 0; - } - else { - int32_t *idxs = jl_array_data(callee_ids, int32_t); - size_t j; - maxvalids2_data[i] = ~(size_t)0; - for (j = 0; j < idxs[0]; j++) { - int32_t idx = idxs[j + 1]; - size_t max_valid = jl_array_data(maxvalids, size_t)[idx]; - if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); - loctag = jl_cstr_to_string("verify_methods"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - loctag = jl_box_int32((int32_t)idx); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - } - if (max_valid < maxvalids2_data[i]) - maxvalids2_data[i] = max_valid; - if (max_valid == 0) - break; + jl_svec_t *callees = (jl_svec_t*)jl_array_ptr_ref(edges, 2 * i + 1); + assert(jl_is_svec((jl_value_t*)callees)); + for (size_t j = 0; j < jl_svec_len(callees); ) { + jl_value_t *edge = jl_svecref(callees, j); + size_t max_valid; + if (jl_is_method_instance(edge)) { + jl_method_instance_t *mi = (jl_method_instance_t*)edge; + sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); + max_valid = verify_call(sig, callees, j, 1, minworld, &matches); + sig = NULL; + j += 1; } + else if (jl_is_long(edge)) { + jl_value_t *sig = jl_svecref(callees, j + 1); + size_t nedges = jl_unbox_long(edge); + max_valid = verify_call(sig, callees, j + 2, nedges, minworld, &matches); + j += 2 + nedges; + edge = sig; + } + else if (jl_is_mtable(edge)) { + // skip the legacy edge (missing backedge) + j += 2; + continue; + } + else { + jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(callees, j + 1); + max_valid = verify_invokesig(edge, mi, minworld); + j += 2; + } + if (max_valid < maxvalids2_data[i]) + maxvalids2_data[i] = max_valid; + if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, edge); + loctag = jl_cstr_to_string("insert_backedges_callee"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + loctag = jl_box_int32((int32_t)i); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); + } + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)edge); + //ios_puts(max_valid == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); + if (max_valid == 0 && !_jl_debug_method_invalidation) + break; + } + if (maxvalids2_data[i] != ~(size_t)0 && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)caller); + loctag = jl_cstr_to_string("verify_methods"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + loctag = jl_box_int32((int32_t)i); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); } //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)caller); //ios_puts(maxvalids2_data[i] == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); @@ -1104,8 +931,9 @@ static jl_array_t *jl_verify_methods(jl_array_t *edges, jl_array_t *maxvalids) // Visit the entire call graph, starting from edges[idx] to determine if that method is valid // Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable // and slightly modified with an early termination option once the computation reaches its minimum -static int jl_verify_graph_edge(size_t *maxvalids2_data, jl_array_t *edges, size_t idx, arraylist_t *visited, arraylist_t *stack) +static int jl_verify_graph_edge(size_t *maxvalids2_data, htable_t *idxs, jl_array_t *edges, size_t idx, arraylist_t *visited, arraylist_t *stack) { + assert(idx < visited->len); if (maxvalids2_data[idx] == 0) { visited->items[idx] = (void*)1; return 0; @@ -1117,14 +945,18 @@ static int jl_verify_graph_edge(size_t *maxvalids2_data, jl_array_t *edges, size arraylist_push(stack, (void*)idx); size_t depth = stack->len; visited->items[idx] = (void*)(1 + depth); - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, idx * 2 + 1); - assert(jl_typetagis((jl_value_t*)callee_ids, jl_array_int32_type)); - int32_t *idxs = jl_array_data(callee_ids, int32_t); - size_t i, n = jl_array_nrows(callee_ids); cycle = depth; - for (i = idxs[0] + 1; i < n; i++) { - int32_t childidx = idxs[i]; - int child_cycle = jl_verify_graph_edge(maxvalids2_data, edges, childidx, visited, stack); + jl_svec_t *callees = (jl_svec_t*)jl_array_ptr_ref(edges, 2 * idx + 1); + assert(jl_is_svec((jl_value_t*)callees)); + for (size_t i = 0; i < jl_svec_len(callees); i++) { + jl_value_t *edge = jl_svecref(callees, i); + if (!jl_is_method_instance(edge)) + continue; + void *verify_edge = ptrhash_get(idxs, edge); + if (verify_edge == HT_NOTFOUND) + continue; + size_t childidx = (char*)verify_edge - (char*)HT_NOTFOUND - 1; + int child_cycle = jl_verify_graph_edge(maxvalids2_data, idxs, edges, childidx, visited, stack); size_t child_max_valid = maxvalids2_data[childidx]; if (child_max_valid < maxvalids2_data[idx]) { maxvalids2_data[idx] = child_max_valid; @@ -1172,32 +1004,54 @@ static int jl_verify_graph_edge(size_t *maxvalids2_data, jl_array_t *edges, size static void jl_verify_graph(jl_array_t *edges, jl_array_t *maxvalids2) { JL_TIMING(VERIFY_IMAGE, VERIFY_Graph); + htable_t idxs; + htable_new(&idxs, 0); + size_t i, n = jl_array_nrows(edges) / 2; + // populate edge lookup hash + for (i = 0; i < n; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); + ptrhash_put(&idxs, caller, (char*)HT_NOTFOUND + i + 1); + } + // visit each edge recursively to propagate maxvalids2 data arraylist_t stack, visited; arraylist_new(&stack, 0); - size_t i, n = jl_array_nrows(edges) / 2; - arraylist_new(&visited, n); + arraylist_new(&visited, 0); + arraylist_grow(&visited, n); memset(visited.items, 0, n * sizeof(size_t)); size_t *maxvalids2_data = jl_array_data(maxvalids2, size_t); + assert(jl_array_nrows(maxvalids2) == n); for (i = 0; i < n; i++) { assert(visited.items[i] == (void*)0 || visited.items[i] == (void*)1); - int child_cycle = jl_verify_graph_edge(maxvalids2_data, edges, i, &visited, &stack); + int child_cycle = jl_verify_graph_edge(maxvalids2_data, &idxs, edges, i, &visited, &stack); assert(child_cycle == 0); (void)child_cycle; assert(stack.len == 0); assert(visited.items[i] == (void*)1); } arraylist_free(&stack); arraylist_free(&visited); + htable_free(&idxs); } // Restore backedges to external targets -// `edges` = [caller1, targets_indexes1, ...], the list of worklist-owned methods calling external methods. -// `ext_targets` is [invokesig1, callee1, matches1, ...], the global set of non-worklist callees of worklist-owned methods. -static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *ext_ci_list, size_t minworld) +// `edges` = [caller1, targets, ...], the list of worklist-owned methods calling external methods. +static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_ci_list, size_t minworld) { // determine which CodeInstance objects are still valid in our image - jl_array_t *valids = jl_verify_edges(ext_targets, minworld); + //ios_puts("===\n", ios_stderr); + //for (size_t i = 0; i < jl_array_nrows(edges) / 2; i++) { + // jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(edges, 2 * i); + // jl_svec_t *targets = (jl_svec_t*)jl_array_ptr_ref(edges, 2 * i + 1); + // ios_puts(" => ", ios_stderr); + // jl_(caller); + // for (size_t j = 0; j < jl_svec_len(targets); j++) { + // jl_value_t *edge = jl_svecref(targets, j); + // if (jl_is_mtable(edge)) { j++; continue; } + // jl_(edge); + // } + //} + //ios_puts("===\n", ios_stderr); + jl_array_t *valids = jl_verify_methods(edges, minworld); JL_GC_PUSH1(&valids); - valids = jl_verify_methods(edges, valids); // consumes edges valids, initializes methods valids jl_verify_graph(edges, valids); // propagates methods valids for each edge size_t n_ext_cis = ext_ci_list ? jl_array_nrows(ext_ci_list) : 0; @@ -1240,24 +1094,26 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a size_t maxvalid = jl_array_data(valids, size_t)[i]; if (maxvalid == ~(size_t)0) { // if this callee is still valid, add all the backedges - jl_array_t *callee_ids = (jl_array_t*)jl_array_ptr_ref(edges, 2 * i + 1); - int32_t *idxs = jl_array_data(callee_ids, int32_t); - for (size_t j = 0; j < idxs[0]; j++) { - int32_t idx = idxs[j + 1]; - jl_value_t *invokesig = jl_array_ptr_ref(ext_targets, idx * 3); - jl_value_t *callee = jl_array_ptr_ref(ext_targets, idx * 3 + 1); - if (callee && jl_is_method_instance(callee)) { - jl_method_instance_add_backedge((jl_method_instance_t*)callee, invokesig, caller); + jl_svec_t *callees = (jl_svec_t*)jl_array_ptr_ref(edges, 2 * i + 1); + for (size_t j = 0; j < jl_svec_len(callees); ) { + jl_value_t *edge = jl_svecref(callees, j); + if (jl_is_method_instance(edge)) { + jl_method_instance_add_backedge((jl_method_instance_t*)edge, NULL, caller); + j += 1; + } + else if (jl_is_long(edge)) { + j += 2; // skip over signature but not methods + continue; + } + else if (jl_is_mtable(edge)) { + jl_methtable_t *mt = (jl_methtable_t*)edge; + jl_value_t *sig = jl_svecref(callees, j + 1); + jl_method_table_add_backedge(mt, sig, (jl_value_t*)caller); + j += 2; } else { - jl_value_t *sig = callee == NULL ? invokesig : callee; - jl_methtable_t *mt = jl_method_table_for(sig); - // FIXME: rarely, `callee` has an unexpected `Union` signature, - // see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1030329344 - // Fix the issue and turn this back into an `assert((jl_value_t*)mt != jl_nothing)` - // This workaround exposes us to (rare) 265-violations. - if ((jl_value_t*)mt != jl_nothing) - jl_method_table_add_backedge(mt, sig, (jl_value_t*)caller); + jl_method_instance_add_backedge((jl_method_instance_t*)jl_svecref(callees, j + 1), edge, caller); + j += 2; } } } diff --git a/test/precompile.jl b/test/precompile.jl index badc6f85618d1..9509ed8db9287 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -804,7 +804,7 @@ precompile_test_harness("code caching") do dir mi = mispecs[2]::Core.MethodInstance mi.specTypes == Tuple{typeof(M.getelsize),Vector{M.X2}} ci = mi.cache - @test ci.relocatability == 0x01 + @test ci.relocatability == 0 # 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"), diff --git a/test/precompile_absint1.jl b/test/precompile_absint1.jl index 7bc0382ffda85..93e15e8a5bfea 100644 --- a/test/precompile_absint1.jl +++ b/test/precompile_absint1.jl @@ -41,29 +41,33 @@ precompile_test_harness() do load_path let m = only(methods(TestAbsIntPrecompile1.basic_callee)) mi = only(Base.specializations(m)) ci = mi.cache - @test isdefined(ci, :next) + @test_broken isdefined(ci, :next) @test ci.owner === nothing @test ci.max_world == typemax(UInt) @test Base.module_build_id(TestAbsIntPrecompile1) == Base.object_build_id(ci) + @test_skip begin ci = ci.next @test !isdefined(ci, :next) @test ci.owner === cache_owner @test ci.max_world == typemax(UInt) @test Base.module_build_id(TestAbsIntPrecompile1) == Base.object_build_id(ci) + end end let m = only(methods(sum, (Vector{Float64},))) found = false for mi in Base.specializations(m) if mi isa Core.MethodInstance && mi.specTypes == Tuple{typeof(sum),Vector{Float64}} ci = mi.cache - @test isdefined(ci, :next) - @test ci.owner === cache_owner + @test_broken isdefined(ci, :next) + @test_broken ci.owner === cache_owner + @test_skip begin @test ci.max_world == typemax(UInt) - @test Base.module_build_id(TestAbsIntPrecompile1) == + @tesst Base.module_build_id(TestAbsIntPrecompile1) == Base.object_build_id(ci) ci = ci.next + end @test !isdefined(ci, :next) @test ci.owner === nothing @test ci.max_world == typemax(UInt) diff --git a/test/precompile_absint2.jl b/test/precompile_absint2.jl index 066dcbaece4c4..6250075df7e21 100644 --- a/test/precompile_absint2.jl +++ b/test/precompile_absint2.jl @@ -64,29 +64,33 @@ precompile_test_harness() do load_path let m = only(methods(TestAbsIntPrecompile2.basic_callee)) mi = only(Base.specializations(m)) ci = mi.cache - @test isdefined(ci, :next) + @test_broken isdefined(ci, :next) @test ci.owner === nothing @test ci.max_world == typemax(UInt) @test Base.module_build_id(TestAbsIntPrecompile2) == Base.object_build_id(ci) + @test_skip begin ci = ci.next @test !isdefined(ci, :next) @test ci.owner === cache_owner @test ci.max_world == typemax(UInt) @test Base.module_build_id(TestAbsIntPrecompile2) == Base.object_build_id(ci) + end end let m = only(methods(sum, (Vector{Float64},))) found = false for mi = Base.specializations(m) if mi isa Core.MethodInstance && mi.specTypes == Tuple{typeof(sum),Vector{Float64}} ci = mi.cache - @test isdefined(ci, :next) - @test ci.owner === cache_owner + @test_broken isdefined(ci, :next) + @test_broken ci.owner === cache_owner + @test_skip begin @test ci.max_world == typemax(UInt) @test Base.module_build_id(TestAbsIntPrecompile2) == Base.object_build_id(ci) ci = ci.next + end @test !isdefined(ci, :next) @test ci.owner === nothing @test ci.max_world == typemax(UInt) From d76b0453c88d4592aa37fe75230e237a97073f7b Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Sat, 21 Sep 2024 13:52:33 +0900 Subject: [PATCH 5/8] make `add_edges!` a proper `CallInfo` interface --- base/compiler/abstractinterpretation.jl | 27 ++--- base/compiler/inferencestate.jl | 9 +- base/compiler/stmtinfo.jl | 115 ++++++++++++++++++-- base/compiler/tfuncs.jl | 5 +- base/compiler/typeinfer.jl | 134 +++++------------------- base/compiler/types.jl | 23 ++-- 6 files changed, 157 insertions(+), 156 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 2b4e814394f8c..6a51d7281ae30 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -264,12 +264,6 @@ any_ambig(info::MethodMatchInfo) = any_ambig(info.results) any_ambig(m::MethodMatches) = any_ambig(m.info) fully_covering(info::MethodMatchInfo) = info.fullmatch fully_covering(m::MethodMatches) = fully_covering(m.info) -function add_uncovered_edges!(sv::AbsIntState, info::MethodMatchInfo, @nospecialize(atype)) - fully_covering(info) || add_mt_backedge!(sv, info.mt, atype) - nothing -end -add_uncovered_edges!(sv::AbsIntState, matches::MethodMatches, @nospecialize(atype)) = - add_uncovered_edges!(sv, matches.info, atype) struct UnionSplitMethodMatches applicable::Vector{Any} @@ -281,23 +275,14 @@ any_ambig(info::UnionSplitInfo) = any(any_ambig, info.split) any_ambig(m::UnionSplitMethodMatches) = any_ambig(m.info) fully_covering(info::UnionSplitInfo) = all(fully_covering, info.split) fully_covering(m::UnionSplitMethodMatches) = fully_covering(m.info) -function add_uncovered_edges!(sv::AbsIntState, info::UnionSplitInfo, @nospecialize(atype)) - all(fully_covering, info.split) && return nothing - # add mt backedges with removing duplications - for mt in uncovered_method_tables(info) - add_mt_backedge!(sv, mt, atype) - end -end -add_uncovered_edges!(sv::AbsIntState, matches::UnionSplitMethodMatches, @nospecialize(atype)) = - add_uncovered_edges!(sv, matches.info, atype) -function uncovered_method_tables(info::UnionSplitInfo) - mts = MethodTable[] + +nmatches(info::MethodMatchInfo) = length(info.results) +function nmatches(info::UnionSplitInfo) + n = 0 for mminfo in info.split - fully_covering(mminfo) && continue - any(mtβ€²::MethodTable->mtβ€²===mminfo.mt, mts) && continue - push!(mts, mminfo.mt) + n += nmatches(mminfo) end - return mts + return n end function find_method_matches(interp::AbstractInterpreter, argtypes::Vector{Any}, @nospecialize(atype); diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 6d0c8f44cd0e3..1d1a64a9c83c0 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -327,7 +327,7 @@ mutable struct InferenceState unreachable = BitSet() pclimitations = IdSet{InferenceState}() limitations = IdSet{InferenceState}() - cycle_backedges = Vector{Tuple{InferenceState,Int}}() + cycle_backedges = Tuple{InferenceState,Int}[] callstack = AbsIntState[] tasks = WorkThunk[] @@ -756,13 +756,6 @@ function record_ssa_assign!(𝕃ᡒ::AbstractLattice, ssa_id::Int, @nospecialize return nothing end -function add_cycle_backedge!(caller::InferenceState, frame::InferenceState) - update_valid_age!(caller, frame.valid_worlds) - backedge = (caller, caller.currpc) - contains_is(frame.cycle_backedges, backedge) || push!(frame.cycle_backedges, backedge) - return frame -end - function narguments(sv::InferenceState, include_va::Bool=true) nargs = Int(sv.src.nargs) if !include_va diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index 473d5bbf2fe0b..1971b58c650df 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -22,6 +22,7 @@ struct CallMeta end struct NoCallInfo <: CallInfo end +add_edges_impl(::Vector{Any}, ::NoCallInfo) = nothing """ info::MethodMatchInfo <: CallInfo @@ -37,6 +38,56 @@ struct MethodMatchInfo <: CallInfo atype fullmatch::Bool end +function add_edges_impl(edges::Vector{Any}, info::MethodMatchInfo) + matches = info.results.matches + if !fully_covering(info) + # add legacy-style missing backedge info also + exists = false + for i in 1:length(edges) + if edges[i] === info.mt && edges[i + 1] == info.atype + exists = true + break + end + end + if !exists + push!(edges, info.mt) + push!(edges, info.atype) + end + end + if length(matches) == 1 + # try the optimized format for the representation, if possible and applicable + # if this doesn't succeed, the backedge will be less precise, + # but the forward edge will maintain the precision + m = matches[1]::Core.MethodMatch + mi = specialize_method(m) + if mi.specTypes === m.spec_types + add_one_edge!(edges, mi) + return nothing + end + end + # add check for whether this lookup already existed in the edges list + for i in 1:length(edges) + if edges[i] === length(matches) && edges[i + 1] == info.atype + return nothing + end + end + push!(edges, length(matches)) + push!(edges, info.atype) + for m in matches + mi = specialize_method(m::Core.MethodMatch) + push!(edges, mi) + end + nothing +end +function add_one_edge!(edges::Vector{Any}, mi::MethodInstance) + for i in 1:length(edges) + if edges[i] === mi && !(i > 1 && edges[i - 1] isa Type) + return + end + end + push!(edges, mi) + nothing +end nsplit_impl(info::MethodMatchInfo) = 1 getsplit_impl(info::MethodMatchInfo, idx::Int) = (@assert idx == 1; info.results) getresult_impl(::MethodMatchInfo, ::Int) = nothing @@ -57,15 +108,8 @@ This info is illegal on any statement that is not a call to a generic function. struct UnionSplitInfo <: CallInfo split::Vector{MethodMatchInfo} end - -nmatches(info::MethodMatchInfo) = length(info.results) -function nmatches(info::UnionSplitInfo) - n = 0 - for mminfo in info.split - n += nmatches(mminfo) - end - return n -end +add_edges_impl(edges::Vector{Any}, info::UnionSplitInfo) = + for split in info.split; add_edges!(edges, split); end nsplit_impl(info::UnionSplitInfo) = length(info.split) getsplit_impl(info::UnionSplitInfo, idx::Int) = getsplit(info.split[idx], 1) getresult_impl(::UnionSplitInfo, ::Int) = nothing @@ -76,6 +120,15 @@ function add_uncovered_edges_impl(edges::Vector{Any}, info::UnionSplitInfo, @nos push!(edges, mt, atype) end end +function uncovered_method_tables(info::UnionSplitInfo) + mts = MethodTable[] + for mminfo in info.split + fully_covering(mminfo) && continue + any(mtβ€²::MethodTable->mtβ€²===mminfo.mt, mts) && continue + push!(mts, mminfo.mt) + end + return mts +end abstract type ConstResult end @@ -117,6 +170,7 @@ struct ConstCallInfo <: CallInfo call::Union{MethodMatchInfo,UnionSplitInfo} results::Vector{Union{Nothing,ConstResult}} end +add_edges_impl(edges::Vector{Any}, info::ConstCallInfo) = add_edges!(edges, info.call) nsplit_impl(info::ConstCallInfo) = nsplit(info.call) getsplit_impl(info::ConstCallInfo, idx::Int) = getsplit(info.call, idx) getresult_impl(info::ConstCallInfo, idx::Int) = info.results[idx] @@ -136,6 +190,7 @@ let instance = MethodResultPure(NoCallInfo()) global MethodResultPure MethodResultPure() = instance end +add_edges_impl(edges::Vector{Any}, info::MethodResultPure) = add_edges!(edges, info.info) """ ainfo::AbstractIterationInfo @@ -162,10 +217,21 @@ not an `_apply_iterate` call. """ struct ApplyCallInfo <: CallInfo # The info for the call itself - call::Any + call::CallInfo # AbstractIterationInfo for each argument, if applicable arginfo::Vector{MaybeAbstractIterationInfo} end +function add_edges_impl(edges::Vector{Any}, info::ApplyCallInfo) + add_edges!(edges, info.call) + for arg in info.arginfo + arg === nothing && continue + for edge in arg.each + add_edges!(edges, edge.info) + end + end +end +# N.B. `ApplyCallInfo` doesn't need to implement the interfaces for the inlining, +# since `Core._apply_iterate` is handled by the special case """ info::UnionSplitApplyCallInfo <: CallInfo @@ -176,6 +242,10 @@ This info is illegal on any statement that is not an `_apply_iterate` call. struct UnionSplitApplyCallInfo <: CallInfo infos::Vector{ApplyCallInfo} end +add_edges_impl(edges::Vector{Any}, info::UnionSplitApplyCallInfo) = + for split in info.infos; add_edges!(edges, split); end +# N.B. `UnionSplitApplyCallInfo` doesn't need to implement the interfaces for the inlining, +# since `Core._apply_iterate` is handled by the special case """ info::InvokeCallInfo @@ -189,6 +259,20 @@ struct InvokeCallInfo <: CallInfo result::Union{Nothing,ConstResult} atype # ::Type end +add_edges_impl(edges::Vector{Any}, info::InvokeCallInfo) = + add_invoke_edge!(edges, info.atype, specialize_method(info.match)) +function add_invoke_edge!(edges::Vector{Any}, @nospecialize(atype), mi::MethodInstance) + for i in 2:length(edges) + if edges[i] === mi && edges[i - 1] isa Type && edges[i - 1] == atype + return + end + end + push!(edges, atype) + push!(edges, mi) + nothing +end +# N.B. `InvokeCallInfo` doesn't need to implement the interfaces for the inlining, +# since `Core.invoke` is handled by the special case """ info::OpaqueClosureCallInfo @@ -201,6 +285,10 @@ struct OpaqueClosureCallInfo <: CallInfo match::MethodMatch result::Union{Nothing,ConstResult} end +add_edges_impl(edges::Vector{Any}, info::OpaqueClosureCallInfo) = + add_one_edge!(edges, specialize_method(info.match)) +# N.B. `OpaqueClosureCallInfo` doesn't need to implement the interfaces for the inlining, +# since `Core.invoke` is handled by the special case """ info::OpaqueClosureCreateInfo <: CallInfo @@ -217,6 +305,9 @@ struct OpaqueClosureCreateInfo <: CallInfo return new(unspec) end end +# merely creating the object implies edges for OC, unlike normal objects, +# since calling them doesn't normally have edges in contrast +add_edges_impl(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = add_edges!(edges, info.unspec.info) # Stmt infos that are used by external consumers, but not by optimization. # These are not produced by default and must be explicitly opted into by @@ -232,6 +323,7 @@ was supposed to analyze. struct ReturnTypeCallInfo <: CallInfo info::CallInfo end +add_edges_impl(edges::Vector{Any}, info::ReturnTypeCallInfo) = add_edges!(edges, info.info) """ info::FinalizerInfo <: CallInfo @@ -243,6 +335,8 @@ struct FinalizerInfo <: CallInfo info::CallInfo # the callinfo for the finalizer call effects::Effects # the effects for the finalizer call end +# merely allocating a finalizer does not imply edges (unless it gets inlined later) +add_edges_impl(::Vector{Any}, ::FinalizerInfo) = nothing """ info::ModifyOpInfo <: CallInfo @@ -258,5 +352,6 @@ Represents a resolved call of one of: struct ModifyOpInfo <: CallInfo info::CallInfo # the callinfo for the `op(getval(), x)` call end +add_edges_impl(edges::Vector{Any}, info::ModifyOpInfo) = add_edges!(edges, info.info) @specialize diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index b1263b3ef161e..a5cf6df3e7571 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -3034,7 +3034,10 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv update_valid_age!(sv, valid_worlds) if match === nothing rt = Const(false) - add_edges!(sv.edges, MethodMatchInfo(MethodLookupResult(Any[], valid_worlds, true), types, mt)) # XXX: this should actually be an invoke-type backedge + let vresults = MethodLookupResult(Any[], valid_worlds, true) + vinfo = MethodMatchInfo(vresults, mt, types, false) + add_edges!(sv.edges, vinfo) # XXX: this should actually be an invoke-type backedge + end else rt = Const(true) add_edges!(sv.edges, InvokeCallInfo(match, nothing, types)) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 79e46cc0d7498..7de702a41f3f9 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -101,7 +101,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState; result = caller.result opt = result.src if opt isa OptimizationState - result.src = opt = ir_to_codeinf!(opt) + result.src = ir_to_codeinf!(opt) end valid_worlds = result.valid_worlds if last(valid_worlds) >= get_world_counter() @@ -167,8 +167,8 @@ end function finish_cycle(::AbstractInterpreter, frames::Vector{AbsIntState}, cycleid::Int) cycle_valid_worlds = WorldRange() cycle_valid_effects = EFFECTS_TOTAL - for caller in cycleid:length(frames) - caller = frames[caller]::InferenceState + for frameid = cycleid:length(frames) + caller = frames[frameid]::InferenceState @assert caller.cycleid == cycleid # converge the world age range and effects for this cycle here: # all frames in the cycle should have the same bits of `valid_worlds` and `effects` @@ -177,20 +177,20 @@ function finish_cycle(::AbstractInterpreter, frames::Vector{AbsIntState}, cyclei cycle_valid_worlds = intersect(cycle_valid_worlds, caller.valid_worlds) cycle_valid_effects = merge_effects(cycle_valid_effects, caller.ipo_effects) end - for caller in cycleid:length(frames) - caller = frames[caller]::InferenceState + for frameid = cycleid:length(frames) + caller = frames[frameid]::InferenceState adjust_cycle_frame!(caller, cycle_valid_worlds, cycle_valid_effects) finishinfer!(caller, caller.interp) end - for caller in cycleid:length(frames) - caller = frames[caller]::InferenceState + for frameid = cycleid:length(frames) + caller = frames[frameid]::InferenceState opt = caller.result.src if opt isa OptimizationState # implies `may_optimize(caller.interp) === true` optimize(caller.interp, opt, caller.result) end end - for caller in cycleid:length(frames) - caller = frames[caller]::InferenceState + for frameid = cycleid:length(frames) + caller = frames[frameid]::InferenceState finish!(caller.interp, caller) end resize!(frames, cycleid - 1) @@ -522,100 +522,10 @@ function store_backedges(caller::MethodInstance, edges::Vector{Any}) return nothing end -add_edges!(edges::Vector{Any}, info::MethodResultPure) = add_edges!(edges, info.info) -add_edges!(edges::Vector{Any}, info::ConstCallInfo) = add_edges!(edges, info.call) -add_edges!(edges::Vector{Any}, info::OpaqueClosureCreateInfo) = add_edges!(edges, info.unspec.info) # merely creating the object implies edges for OC, unlike normal objects, since calling them doesn't normally have edges in contrast -add_edges!(edges::Vector{Any}, info::OpaqueClosureCallInfo) = add_one_edge!(edges, specialize_method(info.match)) -add_edges!(edges::Vector{Any}, info::ReturnTypeCallInfo) = add_edges!(edges, info.info) -function add_edges!(edges::Vector{Any}, info::ApplyCallInfo) - add_edges!(edges, info.call) - for arg in info.arginfo - arg === nothing && continue - for edge in arg.each - add_edges!(edges, edge.info) - end - end -end -add_edges!(edges::Vector{Any}, info::ModifyOpInfo) = add_edges!(edges, info.info) -add_edges!(edges::Vector{Any}, info::UnionSplitInfo) = for split in info.matches; add_edges!(edges, split); end -add_edges!(edges::Vector{Any}, info::UnionSplitApplyCallInfo) = for split in info.infos; add_edges!(edges, split); end -add_edges!(edges::Vector{Any}, info::FinalizerInfo) = nothing # merely allocating a finalizer does not imply edges (unless it gets inlined later) -add_edges!(edges::Vector{Any}, info::NoCallInfo) = nothing -function add_edges!(edges::Vector{Any}, info::MethodMatchInfo) - matches = info.results.matches - if isempty(matches) || !(matches[end]::Core.MethodMatch).fully_covers - # add legacy-style missing backedge info also - exists = false - for i in 1:length(edges) - if edges[i] === info.mt && edges[i + 1] == info.atype - exists = true - break - end - end - if !exists - push!(edges, info.mt) - push!(edges, info.atype) - end - end - if length(matches) == 1 - # try the optimized format for the representation, if possible and applicable - # if this doesn't succeed, the backedge will be less precise, - # but the forward edge will maintain the precision - m = matches[1]::Core.MethodMatch - mi = specialize_method(m) - if mi.specTypes === m.spec_types - add_one_edge!(edges, mi) - return - end - end - # add check for whether this lookup already existed in the edges list - for i in 1:length(edges) - if edges[i] === length(matches) && edges[i + 1] == info.atype - return - end - end - push!(edges, length(matches)) - push!(edges, info.atype) - for m in matches - mi = specialize_method(m::Core.MethodMatch) - push!(edges, mi) - end - nothing -end -add_edges!(edges::Vector{Any}, info::InvokeCallInfo) = add_invoke_edge!(edges, info.atype, specialize_method(info.match)) - -function add_invoke_edge!(edges::Vector{Any}, @nospecialize(atype), mi::MethodInstance) - for i in 2:length(edges) - if edges[i] === mi && edges[i - 1] isa Type && edges[i - 1] == atype - return - end - end - push!(edges, atype) - push!(edges, mi) - nothing -end - -function add_one_edge!(edges::Vector{Any}, mi::MethodInstance) - for i in 1:length(edges) - if edges[i] === mi && !(i > 1 && edges[i - 1] isa Type) - return - end - end - push!(edges, mi) - nothing -end - function compute_edges!(sv::InferenceState) edges = sv.edges for i in 1:length(sv.stmt_info) - info = sv.stmt_info[i] - #rt = sv.ssavaluetypes[i] - #et = sv.exectiontypes[i] - #effects = EFFECTS_TOTAL # TODO: sv.stmt_effects[i] - #if rt === Any && et === Any && effects === Effects() - # continue - #end - add_edges!(edges, info) + add_edges!(edges, sv.stmt_info[i]) end if sv.src.edges !== nothing && sv.src.edges !== Core.svec() append!(edges, sv.src.edges) @@ -623,7 +533,6 @@ function compute_edges!(sv::InferenceState) nothing end - function record_slot_assign!(sv::InferenceState) # look at all assignments to slots # and union the set of types stored there @@ -726,7 +635,7 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState) return nothing end -function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, child::InferenceState) +function merge_call_chain!(::AbstractInterpreter, parent::InferenceState, child::InferenceState) # add backedge of parent <- child # then add all backedges of parent <- parent.parent frames = parent.callstack::Vector{AbsIntState} @@ -739,14 +648,21 @@ function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, parent = frame_parent(child)::InferenceState end # ensure that walking the callstack has the same cycleid (DAG) - for frame = reverse(ancestorid:length(frames)) - frame = frames[frame]::InferenceState + for frameid = reverse(ancestorid:length(frames)) + frame = frames[frameid]::InferenceState frame.cycleid == ancestorid && break @assert frame.cycleid > ancestorid frame.cycleid = ancestorid end end +function add_cycle_backedge!(caller::InferenceState, frame::InferenceState) + update_valid_age!(caller, frame.valid_worlds) + backedge = (caller, caller.currpc) + contains_is(frame.cycle_backedges, backedge) || push!(frame.cycle_backedges, backedge) + return frame +end + function is_same_frame(interp::AbstractInterpreter, mi::MethodInstance, frame::InferenceState) return mi === frame_instance(frame) && cache_owner(interp) === cache_owner(frame.interp) end @@ -770,8 +686,8 @@ function resolve_call_cycle!(interp::AbstractInterpreter, mi::MethodInstance, pa parent isa InferenceState || return false frames = parent.callstack::Vector{AbsIntState} uncached = false - for frame = reverse(1:length(frames)) - frame = frames[frame] + for frameid = reverse(1:length(frames)) + frame = frames[frameid] isa(frame, InferenceState) || break uncached |= !is_cached(frame) # ensure we never add an uncached frame to a cycle if is_same_frame(interp, mi, frame) @@ -1061,13 +977,13 @@ function typeinf_frame(interp::AbstractInterpreter, mi::MethodInstance, run_opti if run_optimizer if result_is_constabi(interp, frame.result) rt = frame.result.result::Const - opt = codeinfo_for_const(interp, frame.linfo, rt.val) + src = codeinfo_for_const(interp, frame.linfo, rt.val) else opt = OptimizationState(frame, interp) optimize(interp, opt, frame.result) - opt = ir_to_codeinf!(opt) + src = ir_to_codeinf!(opt) end - result.src = frame.src = opt + result.src = frame.src = src end return frame end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index a57aee4641e2d..45994ed5cc5e9 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -458,18 +458,27 @@ abstract type CallInfo end @nospecialize +function add_edges!(edges::Vector{Any}, info::CallInfo) + if info === NoCallInfo() + return nothing # just a minor optimization to avoid dynamic dispatch + end + add_edges_impl(edges, info) + nothing +end nsplit(info::CallInfo) = nsplit_impl(info)::Union{Nothing,Int} getsplit(info::CallInfo, idx::Int) = getsplit_impl(info, idx)::MethodLookupResult add_uncovered_edges!(edges::Vector{Any}, info::CallInfo, @nospecialize(atype)) = add_uncovered_edges_impl(edges, info, atype) +getresult(info::CallInfo, idx::Int) = getresult_impl(info, idx)#=::Union{Nothing,ConstResult}=# -getresult(info::CallInfo, idx::Int) = getresult_impl(info, idx) - -# must implement `nsplit`, `getsplit`, and `add_uncovered_edges!` to opt in to inlining +add_edges_impl(::Vector{Any}, ::CallInfo) = error(""" + All `CallInfo` is required to implement `add_edges_impl(::Vector{Any}, ::CallInfo)`""") nsplit_impl(::CallInfo) = nothing -getsplit_impl(::CallInfo, ::Int) = error("unexpected call into `getsplit`") -add_uncovered_edges_impl(::Vector{Any}, ::CallInfo, _) = error("unexpected call into `add_uncovered_edges!`") - -# must implement `getresult` to opt in to extended lattice return information +getsplit_impl(::CallInfo, ::Int) = error(""" + A `info::CallInfo` that implements `nsplit_impl(info::CallInfo) -> Int` must implement `getsplit_impl(info::CallInfo, idx::Int) -> MethodLookupResult` + in order to correctly opt in to inlining""") +add_uncovered_edges_impl(::Vector{Any}, ::CallInfo, _) = error(""" + A `info::CallInfo` that implements `nsplit_impl(info::CallInfo) -> Int` must implement `add_uncovered_edges_impl(edges::Vector{Any}, info::CallInfo, atype)` + in order to correctly opt in to inlining""") getresult_impl(::CallInfo, ::Int) = nothing @specialize From acfe6a1171459c0b9b68947ee87cbe28a700bf10 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 26 Sep 2024 18:34:52 +0900 Subject: [PATCH 6/8] fix `NoinlineCallInfo` implementation --- test/compiler/AbstractInterpreter.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index a544d8d3e9da9..02e9c16272087 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -406,10 +406,11 @@ import .CC: CallInfo struct NoinlineCallInfo <: CallInfo info::CallInfo # wrapped call end +CC.add_edges_impl(edges::Vector{Any}, info::NoinlineCallInfo) = CC.add_edges!(edges, info.info) CC.nsplit_impl(info::NoinlineCallInfo) = CC.nsplit(info.info) CC.getsplit_impl(info::NoinlineCallInfo, idx::Int) = CC.getsplit(info.info, idx) +CC.add_uncovered_edges_impl(edges::Vector{Any}, info::NoinlineCallInfo, @nospecialize(atype)) = CC.add_uncovered_edges!(edges, info.info, atype) CC.getresult_impl(info::NoinlineCallInfo, idx::Int) = CC.getresult(info.info, idx) -CC.add_edges!(edges::Vector{Any}, info::NoinlineCallInfo) = CC.add_edges!(edges, info.info) function CC.abstract_call(interp::NoinlineInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) From a674c1c42690a92d068ba29145ee3d2bb38b7d0b Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 26 Sep 2024 18:57:37 +0900 Subject: [PATCH 7/8] propagate results of `_hasmethod`/`applicable` using `MethodResultPure` To avoid calling `add_edges!` directly. In fact, it might be better to define something like `VirtualizedCallInfo` rather than using `MethodResultPure`. --- base/Base.jl | 1 - base/compiler/stmtinfo.jl | 4 +--- base/compiler/tfuncs.jl | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 23633f0b5138b..57bb26adc087e 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -198,7 +198,6 @@ function Core._hasmethod(@nospecialize(f), @nospecialize(t)) # this function has return Core._hasmethod(tt) end - # core operations & types include("promotion.jl") include("tuple.jl") diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index 1971b58c650df..95e6dc156b374 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -179,9 +179,7 @@ add_uncovered_edges_impl(edges::Vector{Any}, info::ConstCallInfo, @nospecialize( """ info::MethodResultPure <: CallInfo -This struct represents a method result constant was proven to be -effect-free, including being no-throw (typically because the value was computed -by calling an `@pure` function). +This struct represents a method result constant was proven to be effect-free. """ struct MethodResultPure <: CallInfo info::CallInfo diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index a5cf6df3e7571..940f297984f8d 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2979,13 +2979,14 @@ end # a simplified model of abstract_call_gf_by_type for applicable function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState, max_methods::Int) - length(argtypes) < 2 && return Future(CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())) - isvarargtype(argtypes[2]) && return Future(CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo())) + length(argtypes) < 2 && return Future(CallMeta(Bottom, ArgumentError, EFFECTS_THROWS, NoCallInfo())) + isvarargtype(argtypes[2]) && return Future(CallMeta(Bool, ArgumentError, EFFECTS_THROWS, NoCallInfo())) argtypes = argtypes[2:end] atype = argtypes_to_type(argtypes) matches = find_method_matches(interp, argtypes, atype; max_methods) if isa(matches, FailedMethodMatch) rt = Bool # too many matches to analyze + info = NoCallInfo() else (; valid_worlds, applicable) = matches update_valid_age!(sv, valid_worlds) @@ -2998,9 +2999,9 @@ function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, else rt = Const(true) # has applicable matches end - add_edges!(sv.edges, matches.info) + info = MethodResultPure(matches.info) # XXX this should probably be something like `VirtualizedCallInfo` end - return Future(CallMeta(rt, Union{}, EFFECTS_TOTAL, NoCallInfo())) + return Future(CallMeta(rt, Union{}, EFFECTS_TOTAL, info)) end add_tfunc(applicable, 1, INT_INF, @nospecs((𝕃::AbstractLattice, f, args...)->Bool), 40) @@ -3034,15 +3035,14 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv update_valid_age!(sv, valid_worlds) if match === nothing rt = Const(false) - let vresults = MethodLookupResult(Any[], valid_worlds, true) - vinfo = MethodMatchInfo(vresults, mt, types, false) - add_edges!(sv.edges, vinfo) # XXX: this should actually be an invoke-type backedge - end + vresults = MethodLookupResult(Any[], valid_worlds, true) + vinfo = MethodMatchInfo(vresults, mt, types, false) # XXX: this should actually be an info with invoke-type edge else rt = Const(true) - add_edges!(sv.edges, InvokeCallInfo(match, nothing, types)) + vinfo = InvokeCallInfo(match, nothing, types) end - return CallMeta(rt, Any, EFFECTS_TOTAL, NoCallInfo()) + info = MethodResultPure(vinfo) # XXX this should probably be something like `VirtualizedCallInfo` + return CallMeta(rt, Union{}, EFFECTS_TOTAL, info) end # N.B.: typename maps type equivalence classes to a single value From 6f64fd663f5e6b523ff9c602d88518c18c1e9d93 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Fri, 27 Sep 2024 18:44:39 +0900 Subject: [PATCH 8/8] remove no longer needed `edge` variables --- base/compiler/abstractinterpretation.jl | 37 ++++++++++++------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 6a51d7281ae30..9079e43363f02 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -94,7 +94,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), #end mresult = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, si, sv)::Future function handle1(interp, sv) - local (; rt, exct, edge, effects, volatile_inf_result) = mresult[] + local (; rt, exct, effects, volatile_inf_result) = mresult[] this_conditional = ignorelimited(rt) this_rt = widenwrappedconditional(rt) this_exct = exct @@ -118,9 +118,9 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # e.g. in cases when there are cycles but cached result is still accurate this_conditional = this_const_conditional this_rt = this_const_rt - (; effects, const_result, edge) = const_call_result + (; effects, const_result) = const_call_result elseif is_better_effects(const_call_result.effects, effects) - (; effects, const_result, edge) = const_call_result + (; effects, const_result) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end @@ -128,7 +128,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # because consistent-cy does not apply to exceptions. if const_call_result.exct β‹€ this_exct this_exct = const_call_result.exct - (; const_result, edge) = const_call_result + (; const_result) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") end @@ -228,7 +228,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # and avoid keeping track of a more complex result type. rettype = Any end - any_slot_refined = slotrefinements !== nothing if isa(sv, InferenceState) # TODO (#48913) implement a proper recursion handling for irinterp: # This works just because currently the `:terminate` condition guarantees that @@ -814,13 +813,11 @@ struct ConstCallResults exct::Any const_result::ConstResult effects::Effects - edge::MethodInstance function ConstCallResults( @nospecialize(rt), @nospecialize(exct), const_result::ConstResult, - effects::Effects, - edge::MethodInstance) - return new(rt, exct, const_result, effects, edge) + effects::Effects) + return new(rt, exct, const_result, effects) end end @@ -972,9 +969,9 @@ function concrete_eval_call(interp::AbstractInterpreter, catch e # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime. # Howevever, at present, :consistency does not mandate the type of the exception - return ConstCallResults(Bottom, Any, ConcreteResult(edge, result.effects), result.effects, edge) + return ConstCallResults(Bottom, Any, ConcreteResult(edge, result.effects), result.effects) end - return ConstCallResults(Const(value), Union{}, ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) + return ConstCallResults(Const(value), Union{}, ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL) end # check if there is a cycle and duplicated inference of `mi` @@ -1239,7 +1236,7 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, effects = Effects(effects; noub=ALWAYS_TRUE) end exct = refine_exception_type(result.exct, effects) - return ConstCallResults(rt, exct, SemiConcreteResult(mi, ir, effects, spec_info(irsv)), effects, mi) + return ConstCallResults(rt, exct, SemiConcreteResult(mi, ir, effects, spec_info(irsv)), effects) end end end @@ -1248,7 +1245,7 @@ end const_prop_result(inf_result::InferenceResult) = ConstCallResults(inf_result.result, inf_result.exc_result, ConstPropResult(inf_result), - inf_result.ipo_effects, inf_result.linfo) + inf_result.ipo_effects) # return cached result of constant analysis return_localcache_result(::AbstractInterpreter, inf_result::InferenceResult, ::AbsIntState) = @@ -2186,7 +2183,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt mresult = abstract_call_method(interp, method, ti, env, false, si, sv)::Future match = MethodMatch(ti, env, method, argtype <: method.sig) return Future{CallMeta}(mresult, interp, sv) do result, interp, sv - (; rt, exct, edge, effects, volatile_inf_result) = result + (; rt, exct, effects, volatile_inf_result) = result res = nothing sig = match.spec_types argtypesβ€² = invoke_rewrite(argtypes) @@ -2207,10 +2204,10 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt const_result = volatile_inf_result if const_call_result !== nothing if const_call_result.rt βŠ‘ rt - (; rt, effects, const_result, edge) = const_call_result + (; rt, effects, const_result) = const_call_result end if const_call_result.exct β‹€ exct - (; exct, const_result, edge) = const_call_result + (; exct, const_result) = const_call_result end end rt = from_interprocedural!(interp, rt, sv, arginfo, sig) @@ -2410,19 +2407,19 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, mresult = abstract_call_method(interp, ocmethod, sig, Core.svec(), false, si, sv) ocsig_box = Core.Box(ocsig) return Future{CallMeta}(mresult, interp, sv) do result, interp, sv - (; rt, exct, edge, effects, volatile_inf_result, edgecycle) = result + (; rt, exct, effects, volatile_inf_result, edgecycle) = result π•ƒβ‚š = ipo_lattice(interp) βŠ‘, β‹€, βŠ” = partialorder(π•ƒβ‚š), strictneqpartialorder(π•ƒβ‚š), join(π•ƒβ‚š) const_result = volatile_inf_result if !edgecycle const_call_result = abstract_call_method_with_const_args(interp, result, - nothing, arginfo, si, match, sv) + #=f=#nothing, arginfo, si, match, sv) if const_call_result !== nothing if const_call_result.rt βŠ‘ rt - (; rt, effects, const_result, edge) = const_call_result + (; rt, effects, const_result) = const_call_result end if const_call_result.exct β‹€ exct - (; exct, const_result, edge) = const_call_result + (; exct, const_result) = const_call_result end end end