From 7120727068360430c13b3af3066e931aab51923d Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 21 Oct 2024 14:09:49 +0000 Subject: [PATCH 1/6] Teach compiler about partitioned bindings This commit teaches to compiler to update its world bounds whenever it looks at a binding partition, making the compiler sound in the presence of a partitioned binding. The key adjustment is that the compiler is no longer allowed to directly query the binding table without recording the world bounds, so all the various abstract evaluations that look at bindings need to be adjusted and are no longer pure tfuncs. We used to look at bindings a lot more, but thanks to earlier prep work to remove unnecessary binding-dependent code (#55288, #55289 and #55271), these changes become relatively straightforward. Note that as before, we do not create any binding partitions by default, so this commit is mostly preperatory. --- base/compiler/abstractinterpretation.jl | 330 +++++++++++++++++++++--- base/compiler/cicache.jl | 2 + base/compiler/optimize.jl | 10 +- base/compiler/ssair/inlining.jl | 5 - base/compiler/ssair/ir.jl | 9 +- base/compiler/ssair/legacy.jl | 2 +- base/compiler/ssair/passes.jl | 6 +- base/compiler/ssair/slot2ssa.jl | 2 +- base/compiler/tfuncs.jl | 180 +++---------- base/runtime_internals.jl | 2 + src/rtutils.c | 1 + stdlib/REPL/src/REPLCompletions.jl | 6 +- test/compiler/inference.jl | 3 - test/rebinding.jl | 12 + 14 files changed, 364 insertions(+), 206 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 777240adf581b..4474af081623a 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2316,6 +2316,173 @@ function abstract_throw_methoderror(interp::AbstractInterpreter, argtypes::Vecto return Future(CallMeta(Union{}, exct, EFFECTS_THROWS, NoCallInfo())) end +const generic_getglobal_effects = Effects(EFFECTS_THROWS, consistent=ALWAYS_FALSE, inaccessiblememonly=ALWAYS_FALSE) +function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s)) + ⊑ = partialorder(typeinf_lattice(interp)) + if M isa Const && s isa Const + M, s = M.val, s.val + if M isa Module && s isa Symbol + return CallMeta(abstract_eval_globalref(interp, GlobalRef(M, s), sv), NoCallInfo()) + end + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol) + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + elseif M ⊑ Module && s ⊑ Symbol + return CallMeta(Any, UndefVarError, generic_getglobal_effects, NoCallInfo()) + end + return CallMeta(Any, Union{UndefVarError, TypeError}, generic_getglobal_effects, NoCallInfo()) +end + +function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(order)) + goe = global_order_exct(order, #=loading=#true, #=storing=#false) + cm = abstract_eval_getglobal(interp, sv, M, s) + if goe !== Bottom + cm = CallMeta(cm.rt, Union{cm.exct, goe}, Effects(cm.effects; nothrow=false), cm.info) + end + return cm +end + +function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) + if length(argtypes) == 3 + return abstract_eval_getglobal(interp, sv, argtypes[2], argtypes[3]) + elseif length(argtypes) == 4 + return abstract_eval_getglobal(interp, sv, argtypes[2], argtypes[3], argtypes[4]) + elseif !isvarargtype(argtypes[end]) || length(argtypes) > 5 + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + else + return CallMeta(Any, Union{ArgumentError, UndefVarError, TypeError, ConcurrencyViolationError}, + generic_getglobal_effects, NoCallInfo()) + end +end + +@nospecs function abstract_eval_get_binding_type(interp::AbstractInterpreter, sv::AbsIntState, M, s) + ⊑ = partialorder(typeinf_lattice(interp)) + if isa(M, Const) && isa(s, Const) + (M, s) = (M.val, s.val) + if !isa(M, Module) || !isa(s, Symbol) + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + end + partition = abstract_eval_binding_partition!(interp, GlobalRef(M, s), sv) + + if is_some_guard(binding_kind(partition)) + # We do not currently assume an invalidation for guard -> defined transitions + # rt = Const(nothing) + rt = Type + elseif is_some_const_binding(binding_kind(partition)) + rt = Const(Any) + else + rt = Const(partition_restriction(partition)) + end + return CallMeta(rt, Union{}, EFFECTS_TOTAL, NoCallInfo()) + elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol) + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + elseif M ⊑ Module && s ⊑ Symbol + return CallMeta(Type, Union{}, EFFECTS_TOTAL, NoCallInfo()) + end + return CallMeta(Type, TypeError, EFFECTS_THROWS, NoCallInfo()) +end + +function abstract_eval_get_binding_type(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) + if length(argtypes) == 3 + return abstract_eval_get_binding_type(interp, sv, argtypes[2], argtypes[3]) + elseif !isvarargtype(argtypes[end]) && length(argtypes) > 4 + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + end + return CallMeta(Type, Union{TypeError, ArgumentError}, EFFECTS_THROWS, NoCallInfo()) +end + +const setglobal!_effects = Effects(EFFECTS_TOTAL; effect_free=ALWAYS_FALSE, nothrow=false, inaccessiblememonly=ALWAYS_FALSE) + +function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(v)) + if isa(M, Const) && isa(s, Const) + M, s = M.val, s.val + if M isa Module && s isa Symbol + exct = global_assignment_exct(interp, sv, GlobalRef(M, s), v) + return CallMeta(v, exct, Effects(setglobal!_effects, nothrow=exct===Bottom), NoCallInfo()) + end + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + end + ⊑ = partialorder(typeinf_lattice(interp)) + if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + elseif M ⊑ Module && s ⊑ Symbol + return CallMeta(v, ErrorException, setglobal!_effects, NoCallInfo()) + end + return CallMeta(v, Union{TypeError, ErrorException}, setglobal!_effects, NoCallInfo()) +end + +function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(v), @nospecialize(order)) + goe = global_order_exct(order, #=loading=#false, #=storing=#true) + cm = abstract_eval_setglobal!(interp, sv, M, s, v) + if goe !== Bottom + cm = CallMeta(cm.rt, Union{cm.exct, goe}, Effects(cm.effects; nothrow=false), cm.info) + end + return cm +end + +function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) + if length(argtypes) == 4 + return abstract_eval_setglobal!(interp, sv, argtypes[2], argtypes[3], argtypes[4]) + elseif length(argtypes) == 5 + return abstract_eval_setglobal!(interp, sv, argtypes[2], argtypes[3], argtypes[4], argtypes[5]) + elseif !isvarargtype(argtypes[end]) || length(argtypes) > 6 + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + else + return CallMeta(Any, Union{ArgumentError, TypeError, ErrorException, ConcurrencyViolationError}, setglobal!_effects, NoCallInfo()) + end +end + +function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) + if length(argtypes) in (5, 6, 7) + (M, s, x, v) = argtypes[2], argtypes[3], argtypes[4], argtypes[5] + + T = nothing + if isa(M, Const) && isa(s, Const) + M, s = M.val, s.val + if !(M isa Module && s isa Symbol) + return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) + end + partition = abstract_eval_binding_partition!(interp, g, sv) + if binding_kind(partition) == BINDING_KIND_GLOBAL + T = partition_restriction(partition) + end + exct = global_assignment_binding_exct(partition, v) + sg = CallMeta(v, exct, Effects(setglobal!_effects, nothrow=exct===Bottom), NoCallInfo()) + else + sg = abstract_eval_setglobal!(interp, sv, M, s, v) + end + if length(argtypes) >= 6 + goe = global_order_exct(argtypes[6], #=loading=#true, #=storing=#true) + if goe !== Bottom + sg = CallMeta(sg.rt, Union{sg.exct, goe}, Effects(sg.effects; nothrow=false), sg.info) + end + end + if length(argtypes) == 7 + goe = global_order_exct(argtypes[7], #=loading=#true, #=storing=#false) + if goe !== Bottom + sg = CallMeta(sg.rt, Union{sg.exct, goe}, Effects(sg.effects; nothrow=false), sg.info) + end + end + rt = T === nothing ? + ccall(:jl_apply_cmpswap_type, Any, (Any,), S) where S : + ccall(:jl_apply_cmpswap_type, Any, (Any,), T) + return CallMeta(rt, sg.exct, sg.effects, sg.info) + elseif !isvarargtype(argtypes[end]) || length(argtypes) > 8 + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + else + return CallMeta(Any, Union{ArgumentError, TypeError, ErrorException, ConcurrencyViolationError}, setglobal!_effects, NoCallInfo()) + end +end + +function args_are_actually_getglobal(argtypes) + length(argtypes) in (3, 4) || return false + M = argtypes[2] + s = argtypes[3] + isa(M, Const) || return false + isa(s, Const) || return false + return isa(M.val, Module) && isa(s.val, Symbol) +end + # call where the function is known exactly function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, @@ -2338,6 +2505,27 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_throw(interp, argtypes, sv) elseif f === Core.throw_methoderror return abstract_throw_methoderror(interp, argtypes, sv) + elseif f === Core.getglobal + return Future(abstract_eval_getglobal(interp, sv, argtypes)) + elseif f === Core.setglobal! + return Future(abstract_eval_setglobal!(interp, sv, argtypes)) + elseif f === Core.setglobalonce! + sg = abstract_eval_setglobal!(interp, sv, argtypes) + return Future(CallMeta(sg.rt === Bottom ? Bottom : Bool, sg.exct, sg.effects, sg.info)) + elseif f === Core.replaceglobal! + return Future(abstract_eval_replaceglobal!(interp, sv, argtypes)) + elseif f === Core.getfield && args_are_actually_getglobal(argtypes) + return Future(abstract_eval_getglobal(interp, sv, argtypes)) + elseif f === Core.isdefined && args_are_actually_getglobal(argtypes) + return Future(CallMeta( + abstract_eval_isdefined( + interp, + GlobalRef((argtypes[2]::Const).val, + (argtypes[3]::Const).val), + sv), + NoCallInfo())) + elseif f === Core.get_binding_type + return Future(abstract_eval_get_binding_type(interp, sv, argtypes)) end rt = abstract_call_builtin(interp, f, arginfo, sv) ft = popfirst!(argtypes) @@ -2674,6 +2862,9 @@ struct RTEffects end end +CallMeta(rte::RTEffects, info::CallInfo) = + CallMeta(rte.rt, rte.exct, rte.effects, info, rte.refinements) + function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) unused = call_result_unused(sv, sv.currpc) if unused @@ -2854,13 +3045,9 @@ function abstract_eval_copyast(interp::AbstractInterpreter, e::Expr, vtypes::Uni return RTEffects(rt, Any, effects) end -function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, +function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) sym = e.args[1] - rt = Bool - effects = EFFECTS_TOTAL - exct = Union{} - isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym)) if isa(sym, SlotNumber) && vtypes !== nothing vtyp = vtypes[slot_id(sym)] if vtyp.typ === Bottom @@ -2870,11 +3057,22 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::U else # form `Conditional` to refine `vtyp.undef` in the then branch rt = Conditional(sym, vtyp.typ, vtyp.typ; isdefined=true) end - elseif isa(sym, GlobalRef) - if InferenceParams(interp).assume_bindings_static - rt = Const(isdefined_globalref(sym)) - elseif isdefinedconst_globalref(sym) + return RTEffects(rt, Union{}, EFFECTS_TOTAL) + end + return abstract_eval_isdefined(interp, sym, sv) +end + +function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), sv::AbsIntState) + rt = Bool + effects = EFFECTS_TOTAL + exct = Union{} + isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym)) + if isa(sym, GlobalRef) + rte = abstract_eval_globalref(interp, sym, sv) + if rte.exct == Union{} rt = Const(true) + elseif rte.rt === Union{} && rte.exct === UndefVarError + rt = Const(false) else effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) end @@ -2958,7 +3156,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif ehead === :invoke || ehead === :invoke_modify error("type inference data-flow error: tried to double infer a function") elseif ehead === :isdefined - return abstract_eval_isdefined(interp, e, vtypes, sv) + return abstract_eval_isdefined_expr(interp, e, vtypes, sv) elseif ehead === :throw_undef_if_not return abstract_eval_throw_undef_if_not(interp, e, vtypes, sv) elseif ehead === :boundscheck @@ -3063,45 +3261,107 @@ function override_effects(effects::Effects, override::EffectsOverride) nortcall = override.nortcall ? true : effects.nortcall) end -isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g)) -isdefinedconst_globalref(g::GlobalRef) = isconst(g) && isdefined_globalref(g) +world_range(ir::IRCode) = ir.valid_worlds +world_range(ci::CodeInfo) = WorldRange(ci.min_world, ci.max_world) +world_range(compact::IncrementalCompact) = world_range(compact.ir) + +function force_binding_resolution!(g::GlobalRef) + # Force resolution of the binding + # TODO: This will go away once we switch over to fully partitioned semantics + ccall(:jl_globalref_boundp, Cint, (Any,), g) + return nothing +end -function abstract_eval_globalref_type(g::GlobalRef) - if isdefinedconst_globalref(g) - return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) +function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact}) + force_binding_resolution!(g) + worlds = world_range(src) + partition = lookup_binding_partition(min_world(worlds), g) + partition.max_world < max_world(worlds) && return Any + while is_some_imported(binding_kind(partition)) + imported_binding = partition_restriction(partition)::Core.Binding + partition = lookup_binding_partition(min_world(worlds), imported_binding) + partition.max_world < max_world(worlds) && return Any + end + if is_some_guard(binding_kind(partition)) + # return Union{} + return Any end - ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name) - ty === nothing && return Any - return ty + if is_some_const_binding(binding_kind(partition)) + return Const(partition_restriction(partition)) + end + return partition_restriction(partition) +end + +function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) + force_binding_resolution!(g) + partition = lookup_binding_partition(get_inference_world(interp), g) + update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world)) + + while is_some_imported(binding_kind(partition)) + imported_binding = partition_restriction(partition)::Core.Binding + partition = lookup_binding_partition(get_inference_world(interp), imported_binding) + update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world)) + end + + return partition end -abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref_type(GlobalRef(M, s)) function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) - rt = abstract_eval_globalref_type(g) + partition = abstract_eval_binding_partition!(interp, g, sv) + consistent = inaccessiblememonly = ALWAYS_FALSE nothrow = false - if isa(rt, Const) - consistent = ALWAYS_TRUE - nothrow = true - if is_mutation_free_argtype(rt) - inaccessiblememonly = ALWAYS_TRUE - end - elseif InferenceParams(interp).assume_bindings_static - consistent = inaccessiblememonly = ALWAYS_TRUE - if isdefined_globalref(g) - nothrow = true + generic_effects = Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) + if is_some_guard(binding_kind(partition)) + if InferenceParams(interp).assume_bindings_static + return RTEffects(Union{}, UndefVarError, EFFECTS_THROWS) else - rt = Union{} + # We do not currently assume an invalidation for guard -> defined transitions + # return RTEffects(Union{}, UndefVarError, EFFECTS_THROWS) + return RTEffects(Any, UndefVarError, generic_effects) end - elseif isdefinedconst_globalref(g) - nothrow = true end - return RTEffects(rt, nothrow ? Union{} : UndefVarError, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) + + if is_some_const_binding(binding_kind(partition)) + rt = Const(partition_restriction(partition)) + return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE)) + end + + rt = partition_restriction(partition) + + if InferenceParams(interp).assume_bindings_static + if isdefined(g, :binding) && isdefined(g.binding, :value) + return RTEffects(rt, Union{}, Effecst(generic_effects, nothrow=true)) + end + # We do not assume in general that assigned global bindings remain assigned. + # The existence of pkgimages allows them to revert in practice. + end + + return RTEffects(rt, UndefVarError, generic_effects) +end + +function global_assignment_exct(interp::AbstractInterpreter, sv::AbsIntState, g::GlobalRef, @nospecialize(newty)) + partition = abstract_eval_binding_partition!(interp, g, sv) + return global_assignment_binding_exct(partition, newty) +end + +function global_assignment_binding_exct(partition::Core.BindingPartition, @nospecialize(newty)) + kind = binding_kind(partition) + if is_some_guard(kind) || is_some_const_binding(kind) + return ErrorException + end + + ty = partition_restriction(partition) + if !(widenconst(newty) <: ty) + return TypeError + end + + return Union{} end function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty)) effect_free = ALWAYS_FALSE - nothrow = global_assignment_nothrow(lhs.mod, lhs.name, ignorelimited(newty)) + nothrow = global_assignment_exct(interp, frame, lhs, ignorelimited(newty)) === Union{} inaccessiblememonly = ALWAYS_FALSE if !nothrow sub_curr_ssaflag!(frame, IR_FLAG_NOTHROW) diff --git a/base/compiler/cicache.jl b/base/compiler/cicache.jl index bf32e8f12f085..a66d7f9f09650 100644 --- a/base/compiler/cicache.jl +++ b/base/compiler/cicache.jl @@ -31,6 +31,8 @@ WorldRange(r::UnitRange) = WorldRange(first(r), last(r)) first(wr::WorldRange) = wr.min_world last(wr::WorldRange) = wr.max_world in(world::UInt, wr::WorldRange) = wr.min_world <= world <= wr.max_world +min_world(wr::WorldRange) = first(wr) +max_world(wr::WorldRange) = last(wr) function intersect(a::WorldRange, b::WorldRange) ret = WorldRange(max(a.min_world, b.min_world), min(a.max_world, b.max_world)) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index c5606f80468c0..3e2901c12736c 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -307,8 +307,10 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe isa(stmt, GotoNode) && return (true, false, true) isa(stmt, GotoIfNot) && return (true, false, ⊑(𝕃ₒ, argextype(stmt.cond, src), Bool)) if isa(stmt, GlobalRef) - nothrow = consistent = isdefinedconst_globalref(stmt) - return (consistent, nothrow, nothrow) + # Modeled more precisely in abstract_eval_globalref. In general, if a + # GlobalRef was moved to statement position, it is probably not `const`, + # so we can't say much about it anyway. + return (false, false, false) elseif isa(stmt, Expr) (; head, args) = stmt if head === :static_parameter @@ -444,7 +446,7 @@ function argextype( elseif isa(x, QuoteNode) return Const(x.value) elseif isa(x, GlobalRef) - return abstract_eval_globalref_type(x) + return abstract_eval_globalref_type(x, src) elseif isa(x, PhiNode) || isa(x, PhiCNode) || isa(x, UpsilonNode) return Any elseif isa(x, PiNode) @@ -1277,7 +1279,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState) # types of call arguments only once `slot2reg` converts this `IRCode` to the SSA form # and eliminates slots (see below) argtypes = sv.slottypes - return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes) + return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes, WorldRange(ci.min_world, ci.max_world)) end function process_meta!(meta::Vector{Expr}, @nospecialize stmt) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index dfdd317f74d87..5a7571126098c 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1696,11 +1696,6 @@ function early_inline_special_case(ir::IRCode, stmt::Expr, flag::UInt32, if has_flag(flag, IR_FLAG_NOTHROW) return SomeCase(quoted(val)) end - elseif f === Core.get_binding_type - length(argtypes) == 3 || return nothing - if get_binding_type_effect_free(argtypes[2], argtypes[3]) - return SomeCase(quoted(val)) - end end end if f === compilerbarrier diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 90eab43a3f25b..41423a03cc276 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -430,14 +430,17 @@ struct IRCode cfg::CFG new_nodes::NewNodeStream meta::Vector{Expr} + valid_worlds::WorldRange - function IRCode(stmts::InstructionStream, cfg::CFG, debuginfo::DebugInfoStream, argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}) + function IRCode(stmts::InstructionStream, cfg::CFG, debuginfo::DebugInfoStream, + argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState}, + valid_worlds=WorldRange(typemin(UInt), typemax(UInt))) return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta) end function IRCode(ir::IRCode, stmts::InstructionStream, cfg::CFG, new_nodes::NewNodeStream) di = ir.debuginfo @assert di.codelocs === stmts.line - return new(stmts, ir.argtypes, ir.sptypes, di, cfg, new_nodes, ir.meta) + return new(stmts, ir.argtypes, ir.sptypes, di, cfg, new_nodes, ir.meta, ir.valid_worlds) end global function copy(ir::IRCode) di = ir.debuginfo @@ -445,7 +448,7 @@ struct IRCode di = copy(di) di.edges = copy(di.edges) di.codelocs = stmts.line - return new(stmts, copy(ir.argtypes), copy(ir.sptypes), di, copy(ir.cfg), copy(ir.new_nodes), copy(ir.meta)) + return new(stmts, copy(ir.argtypes), copy(ir.sptypes), di, copy(ir.cfg), copy(ir.new_nodes), copy(ir.meta), ir.valid_worlds) end end diff --git a/base/compiler/ssair/legacy.jl b/base/compiler/ssair/legacy.jl index 2b0721b8d2408..675ca2dea9b32 100644 --- a/base/compiler/ssair/legacy.jl +++ b/base/compiler/ssair/legacy.jl @@ -44,7 +44,7 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A di = DebugInfoStream(nothing, ci.debuginfo, nstmts) stmts = InstructionStream(code, ssavaluetypes, info, di.codelocs, ci.ssaflags) meta = Expr[] - return IRCode(stmts, cfg, di, argtypes, meta, sptypes) + return IRCode(stmts, cfg, di, argtypes, meta, sptypes, WorldRange(ci.min_world, ci.max_world)) end """ diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index e3f294c4e91fe..5bd24ac68d5ae 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -474,9 +474,9 @@ function lift_leaves(compact::IncrementalCompact, field::Int, elseif isa(leaf, QuoteNode) leaf = leaf.value elseif isa(leaf, GlobalRef) - mod, name = leaf.mod, leaf.name - if isdefined(mod, name) && isconst(mod, name) - leaf = getglobal(mod, name) + typ = argextype(leaf, compact) + if isa(typ, Const) + leaf = typ.val else return nothing end diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index 2eacdf0f56cfe..6fc87934d3bc5 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -176,7 +176,7 @@ function typ_for_val(@nospecialize(x), ci::CodeInfo, ir::IRCode, idx::Int, slott end return (ci.ssavaluetypes::Vector{Any})[idx] end - isa(x, GlobalRef) && return abstract_eval_globalref_type(x) + isa(x, GlobalRef) && return abstract_eval_globalref_type(x, ci) isa(x, SSAValue) && return (ci.ssavaluetypes::Vector{Any})[x.id] isa(x, Argument) && return slottypes[x.n] isa(x, NewSSAValue) && return types(ir)[new_to_regular(x, length(ir.stmts))] diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 2f78348b79844..ee494f807393d 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -407,10 +407,7 @@ end if isa(a1, DataType) && !isabstracttype(a1) if a1 === Module hasintersect(widenconst(sym), Symbol) || return Bottom - if isa(sym, Const) && isa(sym.val, Symbol) && isa(arg1, Const) && - isdefinedconst_globalref(GlobalRef(arg1.val::Module, sym.val::Symbol)) - return Const(true) - end + # isa(sym, Const) case intercepted in abstract interpretation elseif isa(sym, Const) val = sym.val if isa(val, Symbol) @@ -1160,7 +1157,9 @@ end if isa(sv, Module) setfield && return Bottom if isa(nv, Symbol) - return abstract_eval_global(sv, nv) + # In ordinary inference, this case is intercepted early and + # re-routed to `getglobal`. + return Any end return Bottom end @@ -1407,8 +1406,9 @@ end elseif ff === Core.modifyglobal! o = unwrapva(argtypes[2]) f = unwrapva(argtypes[3]) - RT = modifyglobal!_tfunc(𝕃ᵢ, o, f, Any, Any, Symbol) - TF = getglobal_tfunc(𝕃ᵢ, o, f, Symbol) + GT = abstract_eval_get_binding_type(interp, sv, o, f).rt + RT = isa(GT, Const) ? Pair{GT.val, GT.val} : Pair + TF = isa(GT, Const) ? GT.val : Any elseif ff === Core.memoryrefmodify! o = unwrapva(argtypes[2]) RT = memoryrefmodify!_tfunc(𝕃ᵢ, o, Any, Any, Symbol, Bool) @@ -2277,20 +2277,6 @@ function _builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f::Builtin), argt elseif f === typeassert na == 2 || return false return typeassert_nothrow(𝕃, argtypes[1], argtypes[2]) - elseif f === getglobal - if na == 2 - return getglobal_nothrow(argtypes[1], argtypes[2]) - elseif na == 3 - return getglobal_nothrow(argtypes[1], argtypes[2], argtypes[3]) - end - return false - elseif f === setglobal! - if na == 3 - return setglobal!_nothrow(argtypes[1], argtypes[2], argtypes[3]) - elseif na == 4 - return setglobal!_nothrow(argtypes[1], argtypes[2], argtypes[3], argtypes[4]) - end - return false elseif f === Core.get_binding_type na == 2 || return false return get_binding_type_nothrow(𝕃, argtypes[1], argtypes[2]) @@ -2473,7 +2459,8 @@ function getfield_effects(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospeci end end if hasintersect(widenconst(obj), Module) - inaccessiblememonly = getglobal_effects(argtypes, rt).inaccessiblememonly + # Modeled more precisely in abstract_eval_getglobal + inaccessiblememonly = ALWAYS_FALSE elseif is_mutation_free_argtype(obj) inaccessiblememonly = ALWAYS_TRUE else @@ -2482,24 +2469,7 @@ function getfield_effects(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospeci return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly, noub) end -function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) - 2 ≤ length(argtypes) ≤ 3 || return EFFECTS_THROWS - consistent = inaccessiblememonly = ALWAYS_FALSE - nothrow = false - M, s = argtypes[1], argtypes[2] - if (length(argtypes) == 3 ? getglobal_nothrow(M, s, argtypes[3]) : getglobal_nothrow(M, s)) - nothrow = true - # typeasserts below are already checked in `getglobal_nothrow` - Mval, sval = (M::Const).val::Module, (s::Const).val::Symbol - if isconst(Mval, sval) - consistent = ALWAYS_TRUE - if is_mutation_free_argtype(rt) - inaccessiblememonly = ALWAYS_TRUE - end - end - end - return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) -end + """ builtin_effects(𝕃::AbstractLattice, f::Builtin, argtypes::Vector{Any}, rt) -> Effects @@ -2525,11 +2495,13 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argty if f === isdefined return isdefined_effects(𝕃, argtypes) elseif f === getglobal - return getglobal_effects(argtypes, rt) + 2 ≤ length(argtypes) ≤ 3 || return EFFECTS_THROWS + # Modeled more precisely in abstract_eval_getglobal + return Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, nothrow=false, inaccessiblememonly=ALWAYS_FALSE) elseif f === Core.get_binding_type length(argtypes) == 2 || return EFFECTS_THROWS - effect_free = get_binding_type_effect_free(argtypes[1], argtypes[2]) ? ALWAYS_TRUE : ALWAYS_FALSE - return Effects(EFFECTS_TOTAL; effect_free) + # Modeled more precisely in abstract_eval_get_binding_type + return Effects(EFFECTS_TOTAL; effect_free=ALWAYS_FALSE) elseif f === compilerbarrier length(argtypes) == 2 || return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE) setting = argtypes[1] @@ -3070,118 +3042,28 @@ function typename_static(@nospecialize(t)) return isType(t) ? _typename(t.parameters[1]) : Core.TypeName end -function global_order_nothrow(@nospecialize(o), loading::Bool, storing::Bool) - o isa Const || return false +function global_order_exct(@nospecialize(o), loading::Bool, storing::Bool) + if !(o isa Const) + if o === Symbol + return ConcurrencyViolationError + elseif !hasintersect(o, Symbol) + return TypeError + else + return Union{ConcurrencyViolationError, TypeError} + end + end sym = o.val if sym isa Symbol order = get_atomic_order(sym, loading, storing) - return order !== MEMORY_ORDER_INVALID && order !== MEMORY_ORDER_NOTATOMIC - end - return false -end -@nospecs function getglobal_nothrow(M, s, o) - global_order_nothrow(o, #=loading=#true, #=storing=#false) || return false - return getglobal_nothrow(M, s) -end -@nospecs function getglobal_nothrow(M, s) - if M isa Const && s isa Const - M, s = M.val, s.val - if M isa Module && s isa Symbol - return isdefinedconst_globalref(GlobalRef(M, s)) - end - end - return false -end -@nospecs function getglobal_tfunc(𝕃::AbstractLattice, M, s, order=Symbol) - if M isa Const && s isa Const - M, s = M.val, s.val - if M isa Module && s isa Symbol - return abstract_eval_global(M, s) - end - return Bottom - elseif !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) - return Bottom - end - T = get_binding_type_tfunc(𝕃, M, s) - T isa Const && return T.val - return Any -end -@nospecs function setglobal!_tfunc(𝕃::AbstractLattice, M, s, v, order=Symbol) - if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol)) - return Bottom - end - return v -end -@nospecs function swapglobal!_tfunc(𝕃::AbstractLattice, M, s, v, order=Symbol) - setglobal!_tfunc(𝕃, M, s, v) === Bottom && return Bottom - return getglobal_tfunc(𝕃, M, s) -end -@nospecs function modifyglobal!_tfunc(𝕃::AbstractLattice, M, s, op, v, order=Symbol) - T = get_binding_type_tfunc(𝕃, M, s) - T === Bottom && return Bottom - T isa Const || return Pair - T = T.val - return Pair{T, T} -end -@nospecs function replaceglobal!_tfunc(𝕃::AbstractLattice, M, s, x, v, success_order=Symbol, failure_order=Symbol) - v = setglobal!_tfunc(𝕃, M, s, v) - v === Bottom && return Bottom - T = get_binding_type_tfunc(𝕃, M, s) - T === Bottom && return Bottom - T isa Const || return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) where T - T = T.val - return ccall(:jl_apply_cmpswap_type, Any, (Any,), T) -end -@nospecs function setglobalonce!_tfunc(𝕃::AbstractLattice, M, s, v, success_order=Symbol, failure_order=Symbol) - setglobal!_tfunc(𝕃, M, s, v) === Bottom && return Bottom - return Bool -end - -add_tfunc(Core.getglobal, 2, 3, getglobal_tfunc, 1) -add_tfunc(Core.setglobal!, 3, 4, setglobal!_tfunc, 3) -add_tfunc(Core.swapglobal!, 3, 4, swapglobal!_tfunc, 3) -add_tfunc(Core.modifyglobal!, 4, 5, modifyglobal!_tfunc, 3) -add_tfunc(Core.replaceglobal!, 4, 6, replaceglobal!_tfunc, 3) -add_tfunc(Core.setglobalonce!, 3, 5, setglobalonce!_tfunc, 3) - -@nospecs function setglobal!_nothrow(M, s, newty, o) - global_order_nothrow(o, #=loading=#false, #=storing=#true) || return false - return setglobal!_nothrow(M, s, newty) -end -@nospecs function setglobal!_nothrow(M, s, newty) - if M isa Const && s isa Const - M, s = M.val, s.val - if isa(M, Module) && isa(s, Symbol) - return global_assignment_nothrow(M, s, newty) - end - end - return false -end - -function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty)) - if !isconst(M, s) - ty = ccall(:jl_get_binding_type, Any, (Any, Any), M, s) - return ty isa Type && widenconst(newty) <: ty - end - return false -end - -@nospecs function get_binding_type_effect_free(M, s) - if M isa Const && s isa Const - M, s = M.val, s.val - if M isa Module && s isa Symbol - return ccall(:jl_get_binding_type, Any, (Any, Any), M, s) !== nothing + if order !== MEMORY_ORDER_INVALID && order !== MEMORY_ORDER_NOTATOMIC + return Union{} + else + return ConcurrencyViolationError end + else + return TypeError end - return false -end -@nospecs function get_binding_type_tfunc(𝕃::AbstractLattice, M, s) - if get_binding_type_effect_free(M, s) - return Const(Core.get_binding_type((M::Const).val::Module, (s::Const).val::Symbol)) - end - return Type end -add_tfunc(Core.get_binding_type, 2, 2, get_binding_type_tfunc, 0) @nospecs function get_binding_type_nothrow(𝕃::AbstractLattice, M, s) ⊑ = partialorder(𝕃) diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index dd526a24d6494..9e2413b536174 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -230,6 +230,8 @@ const BINDING_KIND_DECLARED = 0x7 const BINDING_KIND_GUARD = 0x8 is_some_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT) +is_some_imported(kind::UInt8) = (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED) +is_some_guard(kind::UInt8) = (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED) function lookup_binding_partition(world::UInt, b::Core.Binding) ccall(:jl_get_binding_partition, Ref{Core.BindingPartition}, (Any, UInt), b, world) diff --git a/src/rtutils.c b/src/rtutils.c index faa087dcb077d..7b04fbca5d032 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -288,6 +288,7 @@ JL_DLLEXPORT void jl_eh_restore_state(jl_task_t *ct, jl_handler_t *eh) if (!old_gc_state || !eh->gc_state) // it was or is unsafe now jl_gc_safepoint_(ptls); jl_value_t *exception = ptls->sig_exception; + JL_GC_PROMISE_ROOTED(exception); if (exception) { int8_t oldstate = jl_gc_unsafe_enter(ptls); /* The temporary ptls->bt_data is rooted by special purpose code in the diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index d59a18e6d4f16..86f74440b20de 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -624,11 +624,13 @@ function is_call_graph_uncached(sv::CC.InferenceState) return is_call_graph_uncached(parent::CC.InferenceState) end +isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g)) + # aggressive global binding resolution within `repl_frame` function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) - if CC.isdefined_globalref(g) + if isdefined_globalref(g) return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL) end return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS) @@ -655,7 +657,7 @@ function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f), a1val, a2val = a1.val, a2.val if isa(a1val, Module) && isa(a2val, Symbol) g = GlobalRef(a1val, a2val) - if CC.isdefined_globalref(g) + if isdefined_globalref(g) return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) end return Union{} diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index dab8e57aa2309..d0a5aa89afa85 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1184,9 +1184,6 @@ let isdefined_tfunc(@nospecialize xs...) = @test isdefined_tfunc(ComplexF32, Const(0)) === Const(false) @test isdefined_tfunc(SometimesDefined, Const(:x)) == Bool @test isdefined_tfunc(SometimesDefined, Const(:y)) === Const(false) - @test isdefined_tfunc(Const(Base), Const(:length)) === Const(true) - @test isdefined_tfunc(Const(Base), Symbol) == Bool - @test isdefined_tfunc(Const(Base), Const(:NotCurrentlyDefinedButWhoKnows)) == Bool @test isdefined_tfunc(Core.SimpleVector, Const(1)) === Const(false) @test Const(false) ⊑ isdefined_tfunc(Const(:x), Symbol) @test Const(false) ⊑ isdefined_tfunc(Const(:x), Const(:y)) diff --git a/test/rebinding.jl b/test/rebinding.jl index 564be70e44913..c93c34be7a75c 100644 --- a/test/rebinding.jl +++ b/test/rebinding.jl @@ -3,6 +3,8 @@ module Rebinding using Test + make_foo() = Foo(1) + @test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_GUARD struct Foo x::Int @@ -17,6 +19,16 @@ module Rebinding @test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_GUARD @test contains(repr(x), "@world") + struct Foo + x::Int + end + @test Foo != typeof(x) + + # This tests that the compiler uses the correct world, but does not test + # invalidation. + @test typeof(Base.invoke_in_world(defined_world_age, make_foo)) == typeof(x) + @test typeof(make_foo()) == Foo + # Tests for @world syntax @test Base.@world(Foo, defined_world_age) == typeof(x) @test Base.@world(Rebinding.Foo, defined_world_age) == typeof(x) From d7c93f99fc3bc342e5fd2ea54bb2aeda16140476 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 23 Oct 2024 11:20:26 -0400 Subject: [PATCH 2/6] Update base/compiler/abstractinterpretation.jl Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- base/compiler/abstractinterpretation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4474af081623a..460d8345aa362 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2385,7 +2385,7 @@ end function abstract_eval_get_binding_type(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) if length(argtypes) == 3 return abstract_eval_get_binding_type(interp, sv, argtypes[2], argtypes[3]) - elseif !isvarargtype(argtypes[end]) && length(argtypes) > 4 + elseif !isvarargtype(argtypes[end]) || length(argtypes) > 4 return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) end return CallMeta(Type, Union{TypeError, ArgumentError}, EFFECTS_THROWS, NoCallInfo()) From 967ab56859570c785241b71a1176ccf1071a4ad6 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 31 Oct 2024 01:54:57 +0000 Subject: [PATCH 3/6] Performance hack abstract_eval_globalref_type --- base/compiler/abstractinterpretation.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 460d8345aa362..21b2a7303e29b 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -3272,8 +3272,7 @@ function force_binding_resolution!(g::GlobalRef) return nothing end -function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact}) - force_binding_resolution!(g) +function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, IncrementalCompact}, retry_after_resolve::Bool=true) worlds = world_range(src) partition = lookup_binding_partition(min_world(worlds), g) partition.max_world < max_world(worlds) && return Any @@ -3283,6 +3282,13 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode, partition.max_world < max_world(worlds) && return Any end if is_some_guard(binding_kind(partition)) + if retry_after_resolve + # This method is surprisingly hot. For performance, don't ask the runtime to resolve + # the binding unless necessary - doing so triggers an additional lookup, which though + # not super expensive is hot enough to show up in benchmarks. + force_binding_resolution!(g) + return abstract_eval_globalref_type(g, src, false) + end # return Union{} return Any end From 4efebe7b4e7d57f381cf64ebc3b1d7939a7f72ae Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 31 Oct 2024 04:05:54 +0000 Subject: [PATCH 4/6] Fix some atomics effect models --- base/compiler/abstractinterpretation.jl | 77 +++++++++++++++++-------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 21b2a7303e29b..c704a2a9b4303 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2333,13 +2333,17 @@ function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, @ return CallMeta(Any, Union{UndefVarError, TypeError}, generic_getglobal_effects, NoCallInfo()) end +function merge_exct(cm::CallMeta, @nospecialize(exct)) + if exct !== Bottom + cm = CallMeta(cm.rt, Union{cm.exct, exct}, Effects(cm.effects; nothrow=false), cm.info) + end + return cm +end + function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(order)) goe = global_order_exct(order, #=loading=#true, #=storing=#false) cm = abstract_eval_getglobal(interp, sv, M, s) - if goe !== Bottom - cm = CallMeta(cm.rt, Union{cm.exct, goe}, Effects(cm.effects; nothrow=false), cm.info) - end - return cm + return merge_exct(cm, goe) end function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) @@ -2414,10 +2418,7 @@ end function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, @nospecialize(M), @nospecialize(s), @nospecialize(v), @nospecialize(order)) goe = global_order_exct(order, #=loading=#false, #=storing=#true) cm = abstract_eval_setglobal!(interp, sv, M, s, v) - if goe !== Bottom - cm = CallMeta(cm.rt, Union{cm.exct, goe}, Effects(cm.effects; nothrow=false), cm.info) - end - return cm + return merge_exct(cm, goe) end function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) @@ -2432,6 +2433,25 @@ function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState, end end +function abstract_eval_setglobalonce!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) + if length(argtypes) in (4, 5, 6) + cm = abstract_eval_setglobal!(interp, sv, argtypes[2], argtypes[3], argtypes[4]) + if length(argtypes) >= 5 + goe = global_order_exct(argtypes[5], #=loading=#true, #=storing=#true) + cm = merge_exct(cm, goe) + end + if length(argtypes) == 6 + goe = global_order_exct(argtypes[6], #=loading=#true, #=storing=#false) + cm = merge_exct(cm, goe) + end + return CallMeta(Bool, cm.exct, cm.effects, cm.info) + elseif !isvarargtype(argtypes[end]) || length(argtypes) > 6 + return CallMeta(Union{}, ArgumentError, EFFECTS_THROWS, NoCallInfo()) + else + return CallMeta(Bool, Union{ArgumentError, TypeError, ErrorException, ConcurrencyViolationError}, setglobal!_effects, NoCallInfo()) + end +end + function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntState, argtypes::Vector{Any}) if length(argtypes) in (5, 6, 7) (M, s, x, v) = argtypes[2], argtypes[3], argtypes[4], argtypes[5] @@ -2442,26 +2462,24 @@ function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntSta if !(M isa Module && s isa Symbol) return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo()) end - partition = abstract_eval_binding_partition!(interp, g, sv) + partition = abstract_eval_binding_partition!(interp, GlobalRef(M, s), sv) + rte = abstract_eval_partition_load(interp, partition) if binding_kind(partition) == BINDING_KIND_GLOBAL T = partition_restriction(partition) end - exct = global_assignment_binding_exct(partition, v) - sg = CallMeta(v, exct, Effects(setglobal!_effects, nothrow=exct===Bottom), NoCallInfo()) + exct = Union{rte.exct, global_assignment_binding_exct(partition, v)} + effects = merge_effects(rte.effects, Effects(setglobal!_effects, nothrow=exct===Bottom)) + sg = CallMeta(Any, exct, effects, NoCallInfo()) else sg = abstract_eval_setglobal!(interp, sv, M, s, v) end if length(argtypes) >= 6 goe = global_order_exct(argtypes[6], #=loading=#true, #=storing=#true) - if goe !== Bottom - sg = CallMeta(sg.rt, Union{sg.exct, goe}, Effects(sg.effects; nothrow=false), sg.info) - end + sg = merge_exct(sg, goe) end if length(argtypes) == 7 goe = global_order_exct(argtypes[7], #=loading=#true, #=storing=#false) - if goe !== Bottom - sg = CallMeta(sg.rt, Union{sg.exct, goe}, Effects(sg.effects; nothrow=false), sg.info) - end + sg = merge_exct(sg, goe) end rt = T === nothing ? ccall(:jl_apply_cmpswap_type, Any, (Any,), S) where S : @@ -2510,20 +2528,26 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif f === Core.setglobal! return Future(abstract_eval_setglobal!(interp, sv, argtypes)) elseif f === Core.setglobalonce! - sg = abstract_eval_setglobal!(interp, sv, argtypes) - return Future(CallMeta(sg.rt === Bottom ? Bottom : Bool, sg.exct, sg.effects, sg.info)) + return Future(abstract_eval_setglobalonce!(interp, sv, argtypes)) elseif f === Core.replaceglobal! return Future(abstract_eval_replaceglobal!(interp, sv, argtypes)) elseif f === Core.getfield && args_are_actually_getglobal(argtypes) return Future(abstract_eval_getglobal(interp, sv, argtypes)) elseif f === Core.isdefined && args_are_actually_getglobal(argtypes) - return Future(CallMeta( - abstract_eval_isdefined( + exct = Bottom + if length(argtypes) == 4 + order = argtypes[4] + exct = global_order_exct(order, true, false) + if !(isa(order, Const) && get_atomic_order(order.val, true, false).x >= MEMORY_ORDER_UNORDERED.x) + exct = Union{exct, ConcurrencyViolationError} + end + end + return Future(merge_exct(CallMeta(abstract_eval_isdefined( interp, GlobalRef((argtypes[2]::Const).val, (argtypes[3]::Const).val), sv), - NoCallInfo())) + NoCallInfo()), exct)) elseif f === Core.get_binding_type return Future(abstract_eval_get_binding_type(interp, sv, argtypes)) end @@ -3312,9 +3336,7 @@ function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::Global return partition end -function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) - partition = abstract_eval_binding_partition!(interp, g, sv) - +function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Core.BindingPartition) consistent = inaccessiblememonly = ALWAYS_FALSE nothrow = false generic_effects = Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) @@ -3346,6 +3368,11 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv:: return RTEffects(rt, UndefVarError, generic_effects) end +function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) + partition = abstract_eval_binding_partition!(interp, g, sv) + return abstract_eval_partition_load(interp, partition) +end + function global_assignment_exct(interp::AbstractInterpreter, sv::AbsIntState, g::GlobalRef, @nospecialize(newty)) partition = abstract_eval_binding_partition!(interp, g, sv) return global_assignment_binding_exct(partition, newty) From d63f2a222c9f4732dc249005d793b6f67a29148f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Thu, 31 Oct 2024 23:16:02 +0000 Subject: [PATCH 5/6] Add a missing GC root in constant declaration As pointed out in https://github.com/JuliaLang/julia/pull/56224#discussion_r1816974147. --- src/toplevel.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/toplevel.c b/src/toplevel.c index 6dcab3095e320..e93bad267cdc2 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -742,13 +742,16 @@ static void jl_eval_errorf(jl_module_t *m, const char *filename, int lineno, con JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val, enum jl_partition_kind constant_kind) { + JL_GC_PUSH1(&val); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); int did_warn = 0; while (1) { if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { - if (!val) + if (!val) { + JL_GC_POP(); return bpart; + } jl_value_t *old = decode_restriction_value(pku); JL_GC_PROMISE_ROOTED(old); if (jl_egal(val, old)) @@ -778,6 +781,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b, j break; } } + JL_GC_POP(); return bpart; } From 70a43784a0fe1eb81bf746cb40f627db5d72d096 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 1 Nov 2024 00:39:11 +0000 Subject: [PATCH 6/6] Move jl_get_globalref_partition to Julia for performance --- base/runtime_internals.jl | 7 ++++++- src/julia_internal.h | 1 - src/module.c | 12 ------------ 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 9e2413b536174..1da58af38d545 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -238,7 +238,12 @@ function lookup_binding_partition(world::UInt, b::Core.Binding) end function lookup_binding_partition(world::UInt, gr::Core.GlobalRef) - ccall(:jl_get_globalref_partition, Ref{Core.BindingPartition}, (Any, UInt), gr, world) + if isdefined(gr, :binding) + b = gr.binding + else + b = ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), gr.mod, gr.name, true) + end + return lookup_binding_partition(world, b) end partition_restriction(bpart::Core.BindingPartition) = ccall(:jl_bpart_get_restriction_value, Any, (Any,), bpart) diff --git a/src/julia_internal.h b/src/julia_internal.h index ade5940f30687..3fd7aabf52862 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -955,7 +955,6 @@ STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFE } JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world); -JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr JL_PROPAGATES_ROOT, size_t world); EXTERN_INLINE_DECLARE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT { return decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)); diff --git a/src/module.c b/src/module.c index bdacd487e978d..1655c781111b0 100644 --- a/src/module.c +++ b/src/module.c @@ -60,18 +60,6 @@ jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) } } -JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr, size_t world) -{ - if (!gr) - return NULL; - jl_binding_t *b = NULL; - if (gr) - b = gr->binding; - if (!b) - b = jl_get_module_binding(gr->mod, gr->name, 0); - return jl_get_binding_partition(b, world); -} - JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_names) { jl_task_t *ct = jl_current_task;