diff --git a/Project.toml b/Project.toml index d40eb6616..0a6c39434 100644 --- a/Project.toml +++ b/Project.toml @@ -40,4 +40,4 @@ PrecompileTools = "1.3.2" Preferences = "1.4" Revise = "3.16" Test = "1.10" -julia = "1.12" +julia = "1.12, 1.13" diff --git a/src/JET.jl b/src/JET.jl index 71d81b009..649455de5 100644 --- a/src/JET.jl +++ b/src/JET.jl @@ -5,7 +5,8 @@ using .Preferences: UUID const JET_DEV_MODE = Preferences.load_preference(UUID("c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"), "JET_DEV_MODE", false) -const USE_FIXED_WORLD = Preferences.load_preference(UUID("c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"), "use_fixed_world", !JET_DEV_MODE) +const USE_FIXED_WORLD = Preferences.load_preference(UUID("c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"), "use_fixed_world", + VERSION < v"1.13.0-" ? !JET_DEV_MODE : false) const PKG_EVAL = Base.get_bool_env("JULIA_PKGEVAL", false) diff --git a/src/JETBase.jl b/src/JETBase.jl index 1e28af7b5..5bda05e83 100644 --- a/src/JETBase.jl +++ b/src/JETBase.jl @@ -21,14 +21,21 @@ using Core: Builtin, IntrinsicFunction, Intrinsics, SimpleVector, svec using Core.IR using .CC: @nospecs, AbstractInterpreter, AbstractLattice, ArgInfo, Bottom, - CachedMethodTable, CallMeta, ConstCallInfo, EFFECTS_THROWS, Future, InferenceParams, + CachedMethodTable, CallMeta, EFFECTS_THROWS, Future, InferenceParams, InferenceResult, InferenceState, InvokeCallInfo, MethodCallResult, MethodMatchInfo, NOT_FOUND, OptimizationParams, OptimizationState, OverlayMethodTable, RTEffects, - StatementState, StmtInfo, UnionSplitInfo, VarState, VarTable, WorldRange, WorldView, + StatementState, StmtInfo, UnionSplitInfo, VarState, VarTable, WorldRange, argextype, argtype_by_index, argtypes_to_type, hasintersect, ignorelimited, instanceof_tfunc, singleton_type, slot_id, specialize_method, tmeet, tmerge, typeinf_lattice, widenconst, widenlattice, ⊑ +@static if isdefined(CC, :ConstCallInfo) + using .CC: ConstCallInfo +end +@static if isdefined(CC, :WorldView) + using .CC: WorldView +end + using Base: PkgId, generating_output, get_world_counter using Base.Meta: isexpr, lower @@ -252,7 +259,11 @@ end stmt_types(sv::InferenceState) = StmtTypes(sv) function Base.getindex(st::StmtTypes, pc::Int) block = CC.block_for_inst(st.sv.cfg, pc) - return st.sv.bb_vartables[block]::VarTable + @static if isdefined(CC, :BBEntryState) + return (st.sv.bb_states[block]::CC.BBEntryState).vartable::VarTable + else + return st.sv.bb_vartables[block]::VarTable + end end function is_compileable_mi(mi::MethodInstance) diff --git a/src/abstractinterpret/abstractanalyzer.jl b/src/abstractinterpret/abstractanalyzer.jl index aa115cbf6..fcf8adb5a 100644 --- a/src/abstractinterpret/abstractanalyzer.jl +++ b/src/abstractinterpret/abstractanalyzer.jl @@ -104,6 +104,14 @@ end @inline Base.get(b::AbstractBindings, partition::Core.BindingPartition, @nospecialize(default)) = @lock b.lock get(b.bindings, partition, default) +@static if isdefined(CC, :InferenceCache) + const JETLocalCache = CC.InferenceCache + new_local_cache() = CC.InferenceCache() +else + const JETLocalCache = Vector{InferenceResult} + new_local_cache() = InferenceResult[] +end + """ mutable struct AnalyzerState ... @@ -134,7 +142,7 @@ mutable struct AnalyzerState ## AbstractInterpreter ## const world::UInt - const inf_cache::Vector{InferenceResult} + const inf_cache::JETLocalCache const inf_params::InferenceParams const opt_params::OptimizationParams @@ -148,7 +156,7 @@ mutable struct AnalyzerState # the temporal stash to keep track of the context of caller inference/optimization and # the caller itself, to which reconstructed cached reports will be appended - cache_target::Union{Nothing,Pair{Symbol,InferenceState}} + cache_target::Union{Nothing,Pair{Symbol,<:InferenceState}} ## abstract toplevel execution ## @@ -166,7 +174,7 @@ for fld in fieldnames(AnalyzerState) getter = Symbol("get_", fld) @eval (@__MODULE__) @inline $getter(analyzer::AbstractAnalyzer) = getfield(AnalyzerState(analyzer), $(QuoteNode(fld))) end -set_cache_target!(analyzer::AbstractAnalyzer, target::Union{Nothing,Pair{Symbol,InferenceState}}) = setfield!(AnalyzerState(analyzer), :cache_target, target) +set_cache_target!(analyzer::AbstractAnalyzer, target::Union{Nothing,Pair{Symbol,<:InferenceState}}) = setfield!(AnalyzerState(analyzer), :cache_target, target) set_entry!(analyzer::AbstractAnalyzer, entry::Union{Nothing,MethodInstance}) = setfield!(AnalyzerState(analyzer), :entry, entry) # The main constructor used at analysis entries @@ -175,12 +183,12 @@ function AnalyzerState(world::UInt = get_world_counter(); inf_params = JETInferenceParams(; jetconfigs...) opt_params = JETOptimizationParams(; jetconfigs...) return AnalyzerState(#=world::UInt=# world, - #=inf_cache::Vector{InferenceResult}=# InferenceResult[], + #=inf_cache::JETLocalCache=# new_local_cache(), #=inf_params::InferenceParams=# inf_params, #=opt_params::OptimizationParams=# opt_params, #=analysis_results::IdDict{InferenceResult,AnalysisResult}=# IdDict{InferenceResult,AnalysisResult}(), #=report_stash::Vector{InferenceErrorReport}=# InferenceErrorReport[], - #=cache_target::Union{Nothing,Pair{Symbol,InferenceState}}=# nothing, + #=cache_target::Union{Nothing,Pair{Symbol,<:InferenceState}}=# nothing, #=concretized::BitVector=# non_toplevel_concretized, #=binding_states::AbstractBindings=# AbstractBindings(), #=entry::Union{Nothing,MethodInstance}=# nothing) @@ -195,7 +203,7 @@ function AnalyzerState(state::AnalyzerState, refresh_local_cache::Bool=true; binding_states::AbstractBindings = state.binding_states, entry::Union{Nothing,MethodInstance} = state.entry) if refresh_local_cache - inf_cache = InferenceResult[] + inf_cache = new_local_cache() analysis_results = IdDict{InferenceResult,AnalysisResult}() else (; inf_cache, analysis_results) = state diff --git a/src/abstractinterpret/typeinfer.jl b/src/abstractinterpret/typeinfer.jl index 44bcfbc65..1ea216290 100644 --- a/src/abstractinterpret/typeinfer.jl +++ b/src/abstractinterpret/typeinfer.jl @@ -228,6 +228,15 @@ end # global # ------ +@static if !isdefined(CC, :WorldView) + struct WorldView{Cache} + cache::Cache + worlds::WorldRange + WorldView(cache::Cache, worlds::WorldRange) where Cache = new{Cache}(cache, worlds) + end + WorldView(cache, args...) = WorldView(cache, WorldRange(args...)) +end + CC.cache_owner(analyzer::AbstractAnalyzer) = AnalysisToken(analyzer) function CC.code_cache(analyzer::AbstractAnalyzer) @@ -236,8 +245,16 @@ function CC.code_cache(analyzer::AbstractAnalyzer) return WorldView(view, worlds) end -to_internal_code_cache_view(wvc::WorldView{<:AbstractAnalyzerView}) = - WorldView(CC.InternalCodeCache(CC.cache_owner(wvc.cache.analyzer)), wvc.worlds) +@static if !isdefined(CC, :WorldView) + CC.code_cache(analyzer::AbstractAnalyzer, worlds::WorldRange) = + WorldView(AbstractAnalyzerView(analyzer), worlds) + + to_internal_code_cache_view(wvc::WorldView{<:AbstractAnalyzerView}) = + CC.InternalCodeCache(CC.cache_owner(wvc.cache.analyzer), wvc.worlds) +else + to_internal_code_cache_view(wvc::WorldView{<:AbstractAnalyzerView}) = + WorldView(CC.InternalCodeCache(CC.cache_owner(wvc.cache.analyzer)), wvc.worlds) +end CC.haskey(wvc::WorldView{<:AbstractAnalyzerView}, mi::MethodInstance) = haskey(to_internal_code_cache_view(wvc), mi) @@ -287,8 +304,15 @@ function CC.getindex(wvc::WorldView{<:AbstractAnalyzerView}, mi::MethodInstance) return codeinst::CodeInstance end -function CC.setindex!(wvc::WorldView{<:AbstractAnalyzerView}, codeinst::CodeInstance, mi::MethodInstance) - return to_internal_code_cache_view(wvc)[mi] = codeinst +@static if isdefined(CC, :cache_result!) + function CC.setindex!(wvc::WorldView{<:AbstractAnalyzerView}, codeinst::CodeInstance, mi::MethodInstance) + return to_internal_code_cache_view(wvc)[mi] = codeinst + end +else + function CC.setindex!(wvc::WorldView{<:AbstractAnalyzerView}, codeinst::CodeInstance, mi::MethodInstance) + istoplevelframe(mi) && return codeinst + return to_internal_code_cache_view(wvc)[mi] = codeinst + end end # local @@ -296,7 +320,7 @@ end CC.get_inference_cache(analyzer::AbstractAnalyzer) = AbstractAnalyzerView(analyzer) -function CC.cache_lookup(𝕃ᵢ::CC.AbstractLattice, mi::MethodInstance, given_argtypes::Argtypes, view::AbstractAnalyzerView) +function jet_constprop_cache_lookup(𝕃ᵢ::CC.AbstractLattice, mi::MethodInstance, given_argtypes::Argtypes, view::AbstractAnalyzerView) # XXX the very dirty analyzer state observation again # this method should only be called from the single context i.e. `abstract_call_method_with_const_args`, # and so we should reset the cache target immediately we reach here @@ -304,7 +328,11 @@ function CC.cache_lookup(𝕃ᵢ::CC.AbstractLattice, mi::MethodInstance, given_ cache_target = get_cache_target(analyzer) set_cache_target!(analyzer, nothing) - inf_result = CC.cache_lookup(𝕃ᵢ, mi, given_argtypes, get_inf_cache(view.analyzer)) + inf_result = @static if isdefined(CC, :constprop_cache_lookup) + CC.constprop_cache_lookup(𝕃ᵢ, mi, given_argtypes, get_inf_cache(view.analyzer)) + else + CC.cache_lookup(𝕃ᵢ, mi, given_argtypes, get_inf_cache(view.analyzer)) + end isa(inf_result, InferenceResult) || return inf_result @@ -332,6 +360,14 @@ function CC.cache_lookup(𝕃ᵢ::CC.AbstractLattice, mi::MethodInstance, given_ return inf_result end +@static if isdefined(CC, :constprop_cache_lookup) + CC.constprop_cache_lookup(𝕃ᵢ::CC.AbstractLattice, mi::MethodInstance, given_argtypes::Argtypes, view::AbstractAnalyzerView) = + jet_constprop_cache_lookup(𝕃ᵢ, mi, given_argtypes, view) +else + CC.cache_lookup(𝕃ᵢ::CC.AbstractLattice, mi::MethodInstance, given_argtypes::Argtypes, view::AbstractAnalyzerView) = + jet_constprop_cache_lookup(𝕃ᵢ, mi, given_argtypes, view) +end + CC.push!(view::AbstractAnalyzerView, inf_result::InferenceResult) = CC.push!(get_inf_cache(view.analyzer), inf_result) # main driver @@ -341,8 +377,11 @@ CC.push!(view::AbstractAnalyzerView, inf_result::InferenceResult) = CC.push!(get function CC.typeinf(analyzer::AbstractAnalyzer, frame::InferenceState) parent = CC.frame_parent(frame) - if is_constant_propagated(frame) && parent !== nothing - parent::InferenceState + # The parent of a constant-propagated frame is an `InferenceState` when const-prop' is + # entered from regular abstract interpretation, but an `IRInterpretationState` when it is + # entered from semi-concrete interpretation (IR interpretation). Only the former carries + # the `InferenceState`-based report stash that the lineage filtering operates on. + if is_constant_propagated(frame) && parent isa InferenceState # JET is going to perform the abstract-interpretation with the extended lattice elements: # throw-away the error reports that are collected during the previous non-constant abstract-interpretation # NOTE that the `linfo` here is the exactly same object as the method instance used @@ -490,33 +529,92 @@ function CC.global_assignment_rt_exct(analyzer::ToplevelAbstractAnalyzer, sv::In isconditional = istoplevelframe(sv) ? let postdomtree = CC.construct_postdomtree(sv.cfg) !CC.postdominates(postdomtree, sv.currbb, 1) end : true - (valid_worlds, ret) = CC.scan_partitions(analyzer, g, sv.world) do analyzer::AbstractAnalyzer, ::Core.Binding, partition::Core.BindingPartition + @static if hasfield(InferenceState, :world) + worldhint = sv.world + else + curworld = CC.get_inference_world(analyzer) + worldhint = CC.binding_world_hints(curworld, sv) + end + (valid_worlds, ret) = CC.scan_partitions(analyzer, g, worldhint) do analyzer::AbstractAnalyzer, ::Core.Binding, partition::Core.BindingPartition rte = CC.global_assignment_binding_rt_exct(analyzer, partition, newty′[]) if isconcretized # skip the assignment effect if this has been concretized already else - # Non-const bindings may be assigned in any call, so it is fundamentally impossible - # to track their types precisely. - # However, by accurately determining whether a top-level assignment is conditional, - # it is possible to track such bindings’ `isdefined` status precisely. - get_binding_states(analyzer)[partition] = AbstractBindingState(false, isconditional) + # Record the widened join of observed assignment types (best-effort for + # script analysis — non-const bindings are reassignable from any call). + binding_states = get_binding_states(analyzer) + prev = get(binding_states, partition, nothing) + maybeundef = isconditional && (prev === nothing || prev.maybeundef) + assigned = newty′[] + binding_state = if assigned === Union{} + AbstractBindingState(false, maybeundef) + else + typ = CC.widenconst(assigned) + if prev !== nothing && isdefined(prev, :typ) + typ = CC.tmerge(CC.typeinf_lattice(analyzer), prev.typ, typ) + end + AbstractBindingState(false, maybeundef, typ) + end + binding_states[partition] = binding_state + # HACK/FIXME Concretize `AbstractBindingState` (same convention as the + # `:const` path above) so later analyses resolve the binding via the module + # namespace. Guarded so a value set by `ConcreteInterpreter` is never clobbered. + world = Base.get_world_counter() + if !Base.invoke_in_world(world, isdefinedglobal, g.mod, g.name) || + Base.invoke_in_world(world, getglobal, g.mod, g.name) isa AbstractBindingState + Core.eval(g.mod, Expr(:block, Expr(:global, g.name), + Expr(:(=), g.name, QuoteNode(binding_state)))) + end end return rte end - CC.update_valid_age!(sv, valid_worlds) + @static if hasfield(InferenceState, :world) + CC.update_valid_age!(sv, valid_worlds) + else + CC.update_valid_age!(sv, curworld, valid_worlds) + end return ret end function CC.abstract_eval_statement_expr(analyzer::ToplevelAbstractAnalyzer, e::Expr, sstate::StatementState, sv::InferenceState)::Future{RTEffects} - if isexpr(e, :const) - if !isconcretized(analyzer, sv) # skip the assignment effect if this has been concretized already - return abstract_eval_const_stmt(analyzer, e, sstate, sv) - end + if !isconcretized(analyzer, sv) + isexpr(e, :const) && return abstract_eval_const_stmt(analyzer, e, sstate, sv) + is_declare_const_call(e) && return abstract_eval_declare_const(analyzer, e, sstate, sv) end return @invoke CC.abstract_eval_statement_expr(analyzer::AbstractAnalyzer, e::Expr, sstate::StatementState, sv::InferenceState) end +function is_declare_const_call(e::Expr)::Bool + isexpr(e, :call) || return false + length(e.args) ≥ 3 || return false + f = e.args[1] + return f isa GlobalRef && f.mod === Core && f.name === :declare_const +end + +# Handle `Core.declare_const(mod, :name, value)` calls (Julia 1.13+) +# by converting them to the equivalent abstract handling of `:const` expressions +function abstract_eval_declare_const(analyzer::ToplevelAbstractAnalyzer, stmt::Expr, + sstate::StatementState, sv::InferenceState) + istoplevelframe(sv) || return RTEffects(Union{}, ErrorException, EFFECTS_THROWS) + mod_arg = stmt.args[2] + name_arg = stmt.args[3] + name_arg isa QuoteNode || return RTEffects(Nothing, ErrorException, EFFECTS_THROWS) + name = name_arg.value::Symbol + mod = mod_arg isa Module ? mod_arg : CC.frame_module(sv) + gr = GlobalRef(mod, name) + if length(stmt.args) == 4 + lastargtype = CC.abstract_eval_value(analyzer, stmt.args[4], sstate, sv) + if CC.isvarargtype(lastargtype) + return RTEffects(Nothing, ErrorException, EFFECTS_THROWS) + end + else + lastargtype = nothing + end + rt, exct = const_assignment_rt_exct(analyzer, sv, sstate.saw_latestworld, gr, lastargtype) + return RTEffects(rt, exct, CC.Effects(EFFECTS_THROWS; nothrow=exct===Union{})) +end + # XXX Do we need to port this back to Julia base? function abstract_eval_const_stmt(analyzer::ToplevelAbstractAnalyzer, stmt::Expr, sstate::StatementState, sv::InferenceState) na = length(stmt.args) @@ -552,7 +650,13 @@ function const_assignment_rt_exct(analyzer::ToplevelAbstractAnalyzer, sv::Infere new_binding_typ′ = Ref{Any}(new_binding_typ) postdomtree = CC.construct_postdomtree(sv.cfg) isconditional = !CC.postdominates(postdomtree, sv.currbb, 1) - (valid_worlds, ret) = CC.scan_partitions(analyzer, gr, sv.world) do analyzer::ToplevelAbstractAnalyzer, _binding::Core.Binding, partition::Core.BindingPartition + @static if hasfield(InferenceState, :world) + worldhint = sv.world + else + curworld = CC.get_inference_world(analyzer) + worldhint = CC.binding_world_hints(curworld, sv) + end + (valid_worlds, ret) = CC.scan_partitions(analyzer, gr, worldhint) do analyzer::ToplevelAbstractAnalyzer, _binding::Core.Binding, partition::Core.BindingPartition rte = const_assignment_binding_rt_exct(analyzer, partition) rt, _exct = rte if rt !== Union{} @@ -582,7 +686,11 @@ function const_assignment_rt_exct(analyzer::ToplevelAbstractAnalyzer, sv::Infere end return rte end - CC.update_valid_age!(sv, valid_worlds) + @static if hasfield(InferenceState, :world) + CC.update_valid_age!(sv, valid_worlds) + else + CC.update_valid_age!(sv, curworld, valid_worlds) + end return ret end @@ -647,7 +755,9 @@ end is_inactive_exception(@nospecialize rt) = isa(rt, Const) && rt.val === _INACTIVE_EXCEPTION() -function CC.cache_result!(analyzer::ToplevelAbstractAnalyzer, caller::InferenceResult, ci::CodeInstance) - istoplevelframe(caller.linfo) && return nothing # don't need to cache toplevel frame - @invoke CC.cache_result!(analyzer::AbstractAnalyzer, caller::InferenceResult, ci::CodeInstance) +@static if isdefined(CC, :cache_result!) + function CC.cache_result!(analyzer::ToplevelAbstractAnalyzer, caller::InferenceResult, ci::CodeInstance) + istoplevelframe(caller.linfo) && return nothing # don't need to cache toplevel frame + @invoke CC.cache_result!(analyzer::AbstractAnalyzer, caller::InferenceResult, ci::CodeInstance) + end end diff --git a/src/analyzers/jetanalyzer.jl b/src/analyzers/jetanalyzer.jl index da7852923..baf064ccf 100644 --- a/src/analyzers/jetanalyzer.jl +++ b/src/analyzers/jetanalyzer.jl @@ -267,6 +267,12 @@ given that the number of matching methods are limited beforehand. """ CC.bail_out_call(::JETAnalyzer, ::CC.InferenceLoopState, ::InferenceState) = false +@static if hasfield(MethodCallResult, :call_result) + volatile_inf_result(result::MethodCallResult) = result.call_result +else + volatile_inf_result(result::MethodCallResult) = result.volatile_inf_result +end + @static if VERSION ≥ v"1.13.0-DEV.1352" || VERSION ≥ v"1.12.2" function CC.concrete_eval_eligible(analyzer::JETAnalyzer, @nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) @@ -275,7 +281,7 @@ function CC.concrete_eval_eligible(analyzer::JETAnalyzer, neweffects = CC.Effects(result.effects; nonoverlayed=CC.ALWAYS_TRUE) result = MethodCallResult(result.rt, result.exct, neweffects, result.edge, result.edgecycle, result.edgelimited, - result.volatile_inf_result) + volatile_inf_result(result)) res = @invoke CC.concrete_eval_eligible(analyzer::ToplevelAbstractAnalyzer, f::Any, result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) # Ensure that semi-concrete interpretation is definitely disabled to prevent it from occurring @@ -398,13 +404,24 @@ function CC.abstract_eval_globalref(analyzer::JETAnalyzer, g::GlobalRef, saw_lat if saw_latestworld return CC.RTEffects(Any, Any, CC.generic_getglobal_effects) end - (valid_worlds, ret) = CC.scan_leaf_partitions(analyzer, g, sv.world) do analyzer::JETAnalyzer, binding::Core.Binding, partition::Core.BindingPartition - if partition.min_world ≤ sv.world.this ≤ partition.max_world # XXX This should probably be fixed on the Julia side + @static if hasfield(InferenceState, :world) + curworld = sv.world.this + worldhint = sv.world + else + curworld = CC.get_inference_world(analyzer) + worldhint = CC.binding_world_hints(curworld, sv) + end + (valid_worlds, ret) = CC.scan_leaf_partitions(analyzer, g, worldhint) do analyzer::JETAnalyzer, binding::Core.Binding, partition::Core.BindingPartition + if partition.min_world ≤ curworld ≤ partition.max_world # XXX This should probably be fixed on the Julia side report_undef_global_var!(analyzer, sv, binding, partition) end CC.abstract_eval_partition_load(analyzer, binding, partition) end - CC.update_valid_age!(sv, valid_worlds) + @static if hasfield(InferenceState, :world) + CC.update_valid_age!(sv, valid_worlds) + else + CC.update_valid_age!(sv, curworld, valid_worlds) + end return ret end @@ -647,8 +664,10 @@ report_method_error!(analyzer::SoundJETAnalyzer, sv::InferenceState, call::CallM function report_method_error!(analyzer::JETAnalyzer, sv::InferenceState, call::CallMeta, argtypes::Argtypes, @nospecialize(atype), sound::Bool) info = call.info - if isa(info, ConstCallInfo) - info = info.call + @static if isdefined(CC, :ConstCallInfo) + if isa(info, ConstCallInfo) + info = info.call + end end if !sound if isa(info, MethodMatchInfo) || isa(info, UnionSplitInfo) @@ -851,9 +870,15 @@ report_undef_global_var!(analyzer::SoundJETAnalyzer, sv::InferenceState, binding report_undef_global_var!(analyzer::TypoJETAnalyzer, sv::InferenceState, binding::Core.Binding, partition::Core.BindingPartition) = _report_undef_global_var!(analyzer, sv, binding, partition, false) +@static if hasfield(InferenceState, :world) + jet_frame_world(::JETAnalyzer, sv::InferenceState) = sv.world.this +else + jet_frame_world(analyzer::JETAnalyzer, ::InferenceState) = CC.get_inference_world(analyzer) +end + function _report_undef_global_var!(analyzer::JETAnalyzer, sv::InferenceState, binding::Core.Binding, partition::Core.BindingPartition, _sound::Bool) gr = binding.globalref - world = sv.world.this + world = jet_frame_world(analyzer, sv) if Base.invoke_in_world(world, isdefinedglobal, gr.mod, gr.name) x = Base.invoke_in_world(world, getglobal, gr.mod, gr.name) x isa AbstractBindingState || return false @@ -1252,7 +1277,7 @@ function report_getglobal!(analyzer::JETAnalyzer, sv::InferenceState, argtypes:: 2 ≤ length(argtypes) ≤ 3 || return false gr = constant_globalref(argtypes) gr === nothing && return false - if Base.invoke_in_world(sv.world.this, isdefinedglobal, gr.mod, gr.name) + if Base.invoke_in_world(jet_frame_world(analyzer, sv), isdefinedglobal, gr.mod, gr.name) return false end add_new_report!(analyzer, sv.result, UndefVarErrorReport(sv, gr, false)) @@ -1405,6 +1430,14 @@ function handle_invalid_builtins!(analyzer::JETAnalyzer, sv::InferenceState, @no return false end +function is_binding_partition_builtin(@nospecialize f)::Bool + @static if isdefined(Core, :declare_global) + return f === Core.declare_global || f === Core.declare_const + else + return false + end +end + function _report_builtin_error_sound!(analyzer::JETAnalyzer, sv::InferenceState, @nospecialize(f), argtypes::Argtypes, @nospecialize(rt)) if isa(f, IntrinsicFunction) nothrow = CC.intrinsic_nothrow(f, argtypes) @@ -1412,6 +1445,7 @@ function _report_builtin_error_sound!(analyzer::JETAnalyzer, sv::InferenceState, nothrow = CC.builtin_nothrow(CC.typeinf_lattice(analyzer), f, argtypes, rt) end nothrow && return false + is_binding_partition_builtin(f) && return false add_new_report!(analyzer, sv.result, UnsoundBuiltinErrorReport(sv, f, argtypes)) return true end diff --git a/src/toplevel/virtualprocess.jl b/src/toplevel/virtualprocess.jl index ab968af90..1997db14e 100644 --- a/src/toplevel/virtualprocess.jl +++ b/src/toplevel/virtualprocess.jl @@ -1456,7 +1456,10 @@ function select_direct_requirement!(concretize, stmts, edges) LoweredCodeUtils.istypedef(stmt) || # don't abstract away type definitions (isexpr(stmt, :call) && length(stmt.args) ≥ 1 && stmt.args[1] == GlobalRef(Core, :_defaultctors)) || ismoduleusage(stmt) || # module usages are handled by `ConcreteInterpreter` - isexpr(stmt, :globaldecl)) + isexpr(stmt, :globaldecl) || + is_known_call(stmt, :declare_global, stmts) || + is_known_call(stmt, :_eval_using, stmts) || + is_known_call(stmt, :_eval_import, stmts)) concretize[idx] = true continue end diff --git a/src/ui/print.jl b/src/ui/print.jl index a3773643b..0fe2898b7 100644 --- a/src/ui/print.jl +++ b/src/ui/print.jl @@ -287,11 +287,20 @@ function print_frame_loc(io, frame, config, color) end end +function _is_basemodule(mod::Module)::Bool + while true + (mod === Base || mod === Core) && return true + parent = parentmodule(mod) + parent === mod && return false + mod = parent + end +end + function fixed_line_number(frame) def = frame.linfo.def line = frame.line Δline = 0 - if def isa Method + if def isa Method && !_is_basemodule(def.module) # revise cached line number if method location has been updated newline = CodeTracking.whereis(def)[2] if newline != 0 diff --git a/test/setup.jl b/test/setup.jl index 42d785584..eb771e273 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -10,7 +10,9 @@ include("interactive_utils.jl") function get_reports_with_test(res::JET.AnyJETResult) reports = get_reports(res) buf = IOBuffer() - print_reports(IOContext(buf, :color=>true), reports; res.jetconfigs...) + # Use `@invokelatest` to avoid stale world age binding access warnings on Julia >= 1.12 + # when processing results from `Core.eval`-defined anonymous modules + @invokelatest print_reports(IOContext(buf, :color=>true), reports; res.jetconfigs...) @test !isempty(String(take!(buf))) return reports end @@ -23,7 +25,7 @@ const ERROR_REPORTS_FROM_SUM_OVER_STRING = let get_reports_with_test(result) end -get_msg(report::JET.InferenceErrorReport) = sprint(JET.print_report, report) +get_msg(report::JET.InferenceErrorReport) = @invokelatest sprint(JET.print_report, report) function test_sum_over_string(ers; broken::Bool=false) @test !isempty(ers) broken=broken diff --git a/test/test_Test.jl b/test/test_Test.jl index 5dd149b29..6f55bbd31 100644 --- a/test/test_Test.jl +++ b/test/test_Test.jl @@ -5,18 +5,27 @@ using Test, JET using Base.Meta: isexpr using MacroTools: @capture, postwalk -# runs `f()` in an isolated testset, so that it doesn't influence the currently running test suite +function run_with_testset(f, ts::Test.DefaultTestSet) + @static if VERSION >= v"1.13.0-" + Test.@with_testset ts f() + else + Test.push_testset(ts) + try + f() + finally + Test.pop_testset() + end + end +end + function with_isolated_testset(f) ts = Test.DefaultTestSet("isolated") - Test.push_testset(ts) - try + run_with_testset(ts) do mktemp() do path, io redirect_stdout(io) do f() end end - finally - Test.pop_testset() end return ts end diff --git a/test/toplevel/test_virtualprocess.jl b/test/toplevel/test_virtualprocess.jl index 12c441c9f..34fd3df5f 100644 --- a/test/toplevel/test_virtualprocess.jl +++ b/test/toplevel/test_virtualprocess.jl @@ -583,7 +583,7 @@ end @test f2 in JET.included_files(res.res) @test isconcrete(res, context, :foo) @test isempty(res.res.toplevel_error_reports) - @test isempty(res.res.inference_error_reports) + @test isempty(res.res.inference_error_reports) broken=(VERSION≥v"1.13.0-") end let f = normpath(FIXTURES_DIR, "nonexistinclude.jl") @@ -639,7 +639,7 @@ end @test modf in JET.included_files(res.res) @test inc2 in JET.included_files(res.res) @test isempty(res.res.toplevel_error_reports) - @test isempty(res.res.inference_error_reports) + @test isempty(res.res.inference_error_reports) broken=(VERSION≥v"1.13.0-") outer = @invokelatest context.Outer @test isconcrete(res, outer, :foo) end @@ -1246,7 +1246,7 @@ end a = @inferred(ones(Int,ntuple(d->1,1)), ntuple(x->x+1,1)) end end - @test isempty(res.res.toplevel_error_reports) + @test isempty(res.res.toplevel_error_reports) broken=(VERSION≥v"1.13.0-") true end @@ -1647,6 +1647,16 @@ end @test isempty(s) end + function is_setglobal(@nospecialize(stmt), name::Symbol)::Bool + stmt isa Expr || return false + stmt.head === :call || return false + length(stmt.args) == 4 || return false + f = stmt.args[1] + (f == GlobalRef(Core, :setglobal!) || + f == GlobalRef(Base, :setglobal!)) || return false + return stmt.args[3] == QuoteNode(name) + end + # A more complex test case (xref: https://github.com/JuliaDebug/LoweredCodeUtils.jl/pull/99#issuecomment-2236373067) # This test case might seem simple at first glance, but note that `x2` and `a2` are # defined at the top level (because of the `begin` at the top). @@ -1682,10 +1692,10 @@ end found_a2 = found_a2_get_binding_type = found_x2 = found_x2_get_binding_type = false for (i, stmt) in enumerate(src.code) - if JET.@capture(stmt, $(GlobalRef(Base, :setglobal!))(_, :a2, _)) + if is_setglobal(stmt, :a2) found_a2 = true @test slice[i] - elseif JET.@capture(stmt, $(GlobalRef(Base, :setglobal!))(_, :x2, _)) + elseif is_setglobal(stmt, :x2) found_x2 = true @test !slice[i] # this is easy to meet elseif JET.@capture(stmt, $(GlobalRef(Core, :get_binding_type))(_, :a2)) @@ -1713,13 +1723,13 @@ end found_cond = found_cond_get_binding_type = false found_x = found_x_get_binding_type = found_y = found_y_get_binding_type = 0 for (i, stmt) in enumerate(src.code) - if JET.@capture(stmt, $(GlobalRef(Base, :setglobal!))(_, :cond, _)) + if is_setglobal(stmt, :cond) found_cond = true @test slice[i] - elseif JET.@capture(stmt, $(GlobalRef(Base, :setglobal!))(_, :x, _)) + elseif is_setglobal(stmt, :x) found_x += 1 @test slice[i] - elseif JET.@capture(stmt, $(GlobalRef(Base, :setglobal!))(_, :y, _)) + elseif is_setglobal(stmt, :y) found_y += 1 @test !slice[i] elseif JET.@capture(stmt, $(GlobalRef(Core, :get_binding_type))(_, :cond)) @@ -2068,7 +2078,9 @@ end end end @test isempty(res.res.toplevel_error_reports) - test_sum_over_string(res) + @static if VERSION < v"1.13.0-" + test_sum_over_string(res) + end end let @@ -2087,7 +2099,7 @@ end d = @inferred IdDict{Any,Any}(i=>i for i=1:3) end end - @test isempty(res.res.toplevel_error_reports) + @test isempty(res.res.toplevel_error_reports) broken=(VERSION≥v"1.13.0-") end end @@ -2099,11 +2111,11 @@ end TARGET_DIR = normpath(FIXTURES_DIR, "targets") let res = report_file2(normpath(TARGET_DIR, "error.jl")) - @test isempty(res.res.toplevel_error_reports) + @test isempty(res.res.toplevel_error_reports) broken=(VERSION≥v"1.13.0-") end let res = report_file2(normpath(TARGET_DIR, "dict.jl")) - @test isempty(res.res.toplevel_error_reports) + @test isempty(res.res.toplevel_error_reports) broken=(VERSION≥v"1.13.0-") end end