diff --git a/base/boot.jl b/base/boot.jl index 604817cbf2117..e5e39d32f6fc4 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -475,13 +475,13 @@ eval(Core, quote end) function CodeInstance( - mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), + mi::MethodInstance, @nospecialize(rettype), @nospecialize(exctype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), relocatability::UInt8) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, + (Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), + mi, rettype, exctype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects, argescapes, relocatability) end diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 96b63c91d9dd5..951cf67d64dc1 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -17,7 +17,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # which is all that's required for :consistent-cy. Of course, we don't # know anything else about this statement. effects = Effects(; consistent=ALWAYS_TRUE) - return CallMeta(Any, effects, NoCallInfo()) + return CallMeta(Any, Any, effects, NoCallInfo()) end argtypes = arginfo.argtypes @@ -25,13 +25,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), InferenceParams(interp).max_union_splitting, max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end (; valid_worlds, applicable, info) = matches update_valid_age!(sv, valid_worlds) napplicable = length(applicable) - rettype = Bottom + rettype = excttype = Bottom edges = MethodInstance[] conditionals = nothing # keeps refinement information of call argument types when the return type is boolean seen = 0 # number of signatures actually inferred @@ -51,6 +51,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), break end this_rt = Bottom + this_exct = Bottom splitunions = false # TODO: this used to trigger a bug in inference recursion detection, and is unmaintained now # sigtuple = unwrap_unionall(sig)::DataType @@ -59,15 +60,22 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), splitsigs = switchtupleunion(sig) for sig_n in splitsigs result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, si, sv) - (; rt, edge, effects, volatile_inf_result) = result + (; rt, exct, edge, effects, volatile_inf_result) = result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, si, match, sv) const_result = volatile_inf_result if const_call_result !== nothing - if const_call_result.rt ⊑ₚ rt + if !(rt ⊑ₚ const_call_result.rt) rt = const_call_result.rt + exct = const_call_result.exct + (; effects, const_result, edge) = const_call_result + elseif better_effects(const_call_result.effects, effects) + exct = const_call_result.exct + (; effects, const_result, edge) = const_call_result + elseif !(exct ⊑ₚ const_call_result.exct) + exct = const_call_result.exct (; effects, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") @@ -82,6 +90,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end edge === nothing || push!(edges, edge) this_rt = tmerge(this_rt, rt) + this_exct = tmerge(this_exct, exct) if bail_out_call(interp, this_rt, sv) break end @@ -90,9 +99,10 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_rt = widenwrappedconditional(this_rt) else result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, si, sv) - (; rt, edge, effects, volatile_inf_result) = result + (; rt, exct, edge, effects, volatile_inf_result) = result this_conditional = ignorelimited(rt) this_rt = widenwrappedconditional(rt) + this_exct = exct # try constant propagation with argtypes for this match # this is in preparation for inlining, or improving the return result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] @@ -105,9 +115,16 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_const_rt = widenwrappedconditional(const_call_result.rt) # return type of const-prop' inference can be wider than that of non const-prop' inference # e.g. in cases when there are cycles but cached result is still accurate - if this_const_rt ⊑ₚ this_rt + if !(this_rt ⊑ₚ this_const_rt) this_conditional = this_const_conditional this_rt = this_const_rt + this_exct = const_call_result.exct + (; effects, const_result, edge) = const_call_result + elseif better_effects(const_call_result.effects, effects) + this_exct = const_call_result.exct + (; effects, const_result, edge) = const_call_result + elseif !(this_exct ⊑ₚ const_call_result.exct) + this_exct = const_call_result.exct (; effects, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") @@ -125,6 +142,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), @assert !(this_conditional isa Conditional || this_rt isa MustAlias) "invalid lattice element returned from inter-procedural context" seen += 1 rettype = tmerge(𝕃ₚ, rettype, this_rt) + excttype = tmerge(𝕃ₚ, excttype, this_exct) if has_conditional(𝕃ₚ, sv) && this_conditional !== Bottom && is_lattice_bool(𝕃ₚ, rettype) && fargs !== nothing if conditionals === nothing conditionals = Any[Bottom for _ in 1:length(argtypes)], @@ -149,12 +167,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if seen ≠ napplicable # there is unanalyzed candidate, widen type and effects to the top - rettype = Any + rettype = excttype = Any all_effects = Effects() elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) : (!all(matches.fullmatches) || any_ambig(matches)) # Account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. all_effects = Effects(all_effects; nothrow=false) + excttype = tmerge(𝕃ₚ, excttype, MethodError) end rettype = from_interprocedural!(interp, rettype, sv, arginfo, conditionals) @@ -199,7 +218,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end end end - return CallMeta(rettype, all_effects, info) + + return CallMeta(rettype, excttype, all_effects, info) end struct FailedMethodMatch @@ -489,13 +509,13 @@ function abstract_call_method(interp::AbstractInterpreter, hardlimit::Bool, si::StmtInfo, sv::AbsIntState) if method.name === :depwarn && isdefined(Main, :Base) && method.module === Main.Base add_remark!(interp, sv, "Refusing to infer into `depwarn`") - return MethodCallResult(Any, false, false, nothing, Effects()) + return MethodCallResult(Any, Any, false, false, nothing, Effects()) end sigtuple = unwrap_unionall(sig) sigtuple isa DataType || - return MethodCallResult(Any, false, false, nothing, Effects()) + return MethodCallResult(Any, Any, false, false, nothing, Effects()) all(@nospecialize(x) -> valid_as_lattice(unwrapva(x), true), sigtuple.parameters) || - return MethodCallResult(Union{}, false, false, nothing, EFFECTS_THROWS) # catch bad type intersections early + return MethodCallResult(Union{}, Any, false, false, nothing, EFFECTS_THROWS) # catch bad type intersections early if is_nospecializeinfer(method) sig = get_nospecializeinfer_sig(method, sig, sparams) @@ -520,7 +540,7 @@ function abstract_call_method(interp::AbstractInterpreter, # we have a self-cycle in the call-graph, but not in the inference graph (typically): # break this edge now (before we record it) by returning early # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) - return MethodCallResult(Any, true, true, nothing, Effects()) + return MethodCallResult(Any, Any, true, true, nothing, Effects()) end topmost = nothing edgecycle = true @@ -575,7 +595,7 @@ function abstract_call_method(interp::AbstractInterpreter, # since it's very unlikely that we'll try to inline this, # or want make an invoke edge to its calling convention return type. # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) - return MethodCallResult(Any, true, true, nothing, Effects()) + return MethodCallResult(Any, Any, true, true, nothing, Effects()) end add_remark!(interp, sv, washardlimit ? RECURSION_MSG_HARDLIMIT : RECURSION_MSG) # TODO (#48913) implement a proper recursion handling for irinterp: @@ -621,7 +641,7 @@ function abstract_call_method(interp::AbstractInterpreter, sparams = recomputed[2]::SimpleVector end - (; rt, edge, effects, volatile_inf_result) = typeinf_edge(interp, method, sig, sparams, sv) + (; rt, exct, edge, effects, volatile_inf_result) = typeinf_edge(interp, method, sig, sparams, sv) if edge === nothing edgecycle = edgelimited = true @@ -645,7 +665,7 @@ function abstract_call_method(interp::AbstractInterpreter, end end - return MethodCallResult(rt, edgecycle, edgelimited, edge, effects, volatile_inf_result) + return MethodCallResult(rt, exct, edgecycle, edgelimited, edge, effects, volatile_inf_result) end function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, @@ -744,18 +764,19 @@ end # backedge computation, and concrete evaluation or constant-propagation struct MethodCallResult rt + exct edgecycle::Bool edgelimited::Bool edge::Union{Nothing,MethodInstance} effects::Effects volatile_inf_result::Union{Nothing,VolatileInferenceResult} - function MethodCallResult(@nospecialize(rt), + function MethodCallResult(@nospecialize(rt), @nospecialize(exct), edgecycle::Bool, edgelimited::Bool, edge::Union{Nothing,MethodInstance}, effects::Effects, volatile_inf_result::Union{Nothing,VolatileInferenceResult}=nothing) - return new(rt, edgecycle, edgelimited, edge, effects, volatile_inf_result) + return new(rt, exct, edgecycle, edgelimited, edge, effects, volatile_inf_result) end end @@ -767,15 +788,16 @@ end struct ConstCallResults rt::Any + exct::Any const_result::ConstResult effects::Effects edge::MethodInstance function ConstCallResults( - @nospecialize(rt), + @nospecialize(rt), @nospecialize(exct), const_result::ConstResult, effects::Effects, edge::MethodInstance) - return new(rt, const_result, effects, edge) + return new(rt, exct, const_result, effects, edge) end end @@ -902,11 +924,12 @@ function concrete_eval_call(interp::AbstractInterpreter, edge = result.edge::MethodInstance value = try Core._call_in_world_total(world, f, args...) - catch - # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Bottom, ConcreteResult(edge, result.effects), result.effects, edge) + 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) end - return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) + return ConstCallResults(Const(value), Union{}, ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) end # check if there is a cycle and duplicated inference of `mi` @@ -1169,7 +1192,7 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, if noub effects = Effects(effects; noub = ALWAYS_TRUE) end - return ConstCallResults(rt, SemiConcreteResult(mi, ir, effects), effects, mi) + return ConstCallResults(rt, result.exct, SemiConcreteResult(mi, ir, effects), effects, mi) end end end @@ -1178,7 +1201,7 @@ end function const_prop_call(interp::AbstractInterpreter, mi::MethodInstance, result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState, - concrete_eval_result::Union{Nothing,ConstCallResults}=nothing) + concrete_eval_result::Union{Nothing, ConstCallResults}=nothing) inf_cache = get_inference_cache(interp) 𝕃ᵢ = typeinf_lattice(interp) inf_result = cache_lookup(𝕃ᵢ, mi, arginfo.argtypes, inf_cache) @@ -1213,7 +1236,8 @@ function const_prop_call(interp::AbstractInterpreter, return nothing end end - return ConstCallResults(inf_result.result, ConstPropResult(inf_result), inf_result.ipo_effects, mi) + return ConstCallResults(inf_result.result, inf_result.exc_result, + ConstPropResult(inf_result), inf_result.ipo_effects, mi) end # TODO implement MustAlias forwarding @@ -1456,7 +1480,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # WARNING: Changes to the iteration protocol must be reflected here, # this is not just an optimization. # TODO: this doesn't realize that Array, SimpleVector, Tuple, and NamedTuple do not use the iterate protocol - stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, call.effects, info)], true)) + stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, Any, call.effects, info)], true)) valtype = statetype = Bottom ret = Any[] calls = CallMeta[call] @@ -1534,7 +1558,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: sv::AbsIntState, max_methods::Int=get_max_methods(interp, sv)) itft = argtype_by_index(argtypes, 2) aft = argtype_by_index(argtypes, 3) - (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) aargtypes = argtype_tail(argtypes, 4) aftw = widenconst(aft) if !isa(aft, Const) && !isa(aft, PartialOpaque) && (!isType(aftw) || has_free_typevars(aftw)) @@ -1542,7 +1566,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: add_remark!(interp, sv, "Core._apply_iterate called on a function of a non-concrete type") # bail now, since it seems unlikely that abstract_call will be able to do any better after splitting # this also ensures we don't call abstract_call_gf_by_type below on an IntrinsicFunction or Builtin - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end end res = Union{} @@ -1601,6 +1625,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: retinfo = UnionSplitApplyCallInfo(retinfos) napplicable = length(ctypes) seen = 0 + exct = effects.nothrow ? Union{} : Any for i = 1:napplicable ct = ctypes[i] arginfo = infos[i] @@ -1618,6 +1643,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: seen += 1 push!(retinfos, ApplyCallInfo(call.info, arginfo)) res = tmerge(typeinf_lattice(interp), res, call.rt) + exct = tmerge(typeinf_lattice(interp), exct, call.exct) effects = merge_effects(effects, call.effects) if bail_out_apply(interp, InferenceLoopState(ct, res, effects), sv) add_remark!(interp, sv, "_apply_iterate inference reached maximally imprecise information. Bailing on.") @@ -1627,12 +1653,13 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: if seen ≠ napplicable # there is unanalyzed candidate, widen type and effects to the top res = Any + exct = Any effects = Effects() retinfo = NoCallInfo() # NOTE this is necessary to prevent the inlining processing end # TODO: Add a special info type to capture all the iteration info. # For now, only propagate info if we don't also union-split the iteration - return CallMeta(res, effects, retinfo) + return CallMeta(res, exct, effects, retinfo) end function argtype_by_index(argtypes::Vector{Any}, i::Int) @@ -1881,9 +1908,9 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An na = length(argtypes) if isvarargtype(argtypes[end]) if na ≤ 2 - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) elseif na > 4 - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end a2 = argtypes[2] a3 = unwrapva(argtypes[3]) @@ -1894,7 +1921,7 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An ⊑ᵢ = ⊑(typeinf_lattice(interp)) nothrow = a2 ⊑ᵢ TypeVar && (a3 ⊑ᵢ Type || a3 ⊑ᵢ TypeVar) else - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end canconst = true if isa(a3, Const) @@ -1903,10 +1930,10 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An body = a3.parameters[1] canconst = false else - return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), call.info) + return CallMeta(Any, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end if !(isa(body, Type) || isa(body, TypeVar)) - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) end if has_free_typevars(body) if isa(a2, Const) @@ -1915,36 +1942,36 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An tv = a2.tv canconst = false else - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) end - isa(tv, TypeVar) || return CallMeta(Any, EFFECTS_THROWS, call.info) + isa(tv, TypeVar) || return CallMeta(Any, Any, EFFECTS_THROWS, call.info) body = UnionAll(tv, body) end ret = canconst ? Const(body) : Type{body} - return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow), call.info) + return CallMeta(ret, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, si::StmtInfo, sv::AbsIntState) ft′ = argtype_by_index(argtypes, 2) ft = widenconst(ft′) - ft === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + ft === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false) - isexact || return CallMeta(Any, Effects(), NoCallInfo()) + isexact || return CallMeta(Any, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end argtype = argtypes_to_type(argtype_tail(argtypes, 4)) nargtype = typeintersect(types, argtype) - nargtype === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - nargtype isa DataType || return CallMeta(Any, Effects(), NoCallInfo()) # other cases are not implemented below - isdispatchelem(ft) || return CallMeta(Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + nargtype === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargtype isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) # other cases are not implemented below + isdispatchelem(ft) || return CallMeta(Any, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below ft = ft::DataType lookupsig = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type nargtype = Tuple{ft, nargtype.parameters...} argtype = Tuple{ft, argtype.parameters...} match, valid_worlds = findsup(lookupsig, method_table(interp)) - match === nothing && return CallMeta(Any, Effects(), NoCallInfo()) + match === nothing && return CallMeta(Any, Any, Effects(), NoCallInfo()) update_valid_age!(sv, valid_worlds) method = match.method tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector @@ -1976,7 +2003,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) - return CallMeta(rt, effects, info) + return CallMeta(rt, Any, effects, info) end function invoke_rewrite(xs::Vector{Any}) @@ -1990,9 +2017,9 @@ function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, if length(argtypes) == 3 finalizer_argvec = Any[argtypes[2], argtypes[3]] call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false), sv, #=max_methods=#1) - return CallMeta(Nothing, Effects(), FinalizerInfo(call.info, call.effects)) + return CallMeta(Nothing, Any, Effects(), FinalizerInfo(call.info, call.effects)) end - return CallMeta(Nothing, Effects(), NoCallInfo()) + return CallMeta(Nothing, Any, Effects(), NoCallInfo()) end # call where the function is known exactly @@ -2014,17 +2041,19 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_finalizer(interp, argtypes, sv) elseif f === applicable return abstract_applicable(interp, argtypes, sv, max_methods) + elseif f === throw + return CallMeta(Union{}, la == 2 ? argtypes[2] : ArgumentError, EFFECTS_THROWS, NoCallInfo()) end rt = abstract_call_builtin(interp, f, arginfo, sv) effects = builtin_effects(𝕃ᵢ, f, arginfo, rt) - return CallMeta(rt, effects, NoCallInfo()) + return CallMeta(rt, effects.nothrow ? Union{} : Any, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information - return CallMeta(typeof(f).parameters[2], Effects(), NoCallInfo()) + return CallMeta(typeof(f).parameters[2], Any, Effects(), NoCallInfo()) elseif f === TypeVar # Manually look through the definition of TypeVar to # make sure to be able to get `PartialTypeVar`s out. - (la < 2 || la > 4) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + (la < 2 || la > 4) && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) n = argtypes[2] ub_var = Const(Any) lb_var = Const(Union{}) @@ -2044,7 +2073,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), pT = typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var) effects = builtin_effects(𝕃ᵢ, Core._typevar, ArgInfo(nothing, Any[Const(Core._typevar), n, lb_var, ub_var]), pT) - return CallMeta(pT, effects, call.info) + return CallMeta(pT, Any, effects, call.info) elseif f === UnionAll call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, sv, max_methods) return abstract_call_unionall(interp, argtypes, call) @@ -2052,7 +2081,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) if !isconcretetype(ty) - return CallMeta(Tuple, EFFECTS_UNKNOWN, NoCallInfo()) + return CallMeta(Tuple, Any, EFFECTS_UNKNOWN, NoCallInfo()) end elseif is_return_type(f) return return_type_tfunc(interp, argtypes, si, sv) @@ -2061,16 +2090,16 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), aty = argtypes[2] if isa(aty, Conditional) call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Bool]), si, Tuple{typeof(f), Bool}, sv, max_methods) # make sure we've inferred `!(::Bool)` - return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), call.effects, call.info) + return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), Any, call.effects, call.info) end elseif la == 3 && istopfunction(f, :!==) # mark !== as exactly a negated call to === call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, sv, max_methods) rty = abstract_call_known(interp, (===), arginfo, si, sv, max_methods).rt if isa(rty, Conditional) - return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), EFFECTS_TOTAL, NoCallInfo()) # swap if-else + return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else elseif isa(rty, Const) - return CallMeta(Const(rty.val === false), EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(Const(rty.val === false), Bottom, EFFECTS_TOTAL, MethodResultPure()) end return call elseif la == 3 && istopfunction(f, :(>:)) @@ -2084,7 +2113,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) elseif la == 2 && istopfunction(f, :typename) - return CallMeta(typename_static(argtypes[2]), EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(typename_static(argtypes[2]), Any, EFFECTS_TOTAL, MethodResultPure()) elseif f === Core._hasmethod return _hasmethod_tfunc(interp, argtypes, sv) end @@ -2123,7 +2152,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) edge !== nothing && add_backedge!(sv, edge) - return CallMeta(rt, effects, info) + return CallMeta(rt, Any, effects, info) end function most_general_argtypes(closure::PartialOpaque) @@ -2148,13 +2177,13 @@ function abstract_call_unknown(interp::AbstractInterpreter, @nospecialize(ft), wft = widenconst(ft) if hasintersect(wft, Builtin) add_remark!(interp, sv, "Could not identify method table for call") - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) elseif hasintersect(wft, Core.OpaqueClosure) uft = unwrap_unionall(wft) if isa(uft, DataType) - return CallMeta(rewrap_unionall(uft.parameters[2], wft), Effects(), NoCallInfo()) + return CallMeta(rewrap_unionall(uft.parameters[2], wft), Any, Effects(), NoCallInfo()) end - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end # non-constant function, but the number of arguments is known and the `f` is not a builtin or intrinsic atype = argtypes_to_type(arginfo.argtypes) @@ -2235,31 +2264,30 @@ end function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState) if isa(e, QuoteNode) - return RTEffects(Const(e.value), EFFECTS_TOTAL) + return RTEffects(Const(e.value), Union{}, EFFECTS_TOTAL) elseif isa(e, SSAValue) - return RTEffects(abstract_eval_ssavalue(e, sv), EFFECTS_TOTAL) + return RTEffects(abstract_eval_ssavalue(e, sv), Union{}, EFFECTS_TOTAL) elseif isa(e, SlotNumber) - effects = EFFECTS_THROWS if vtypes !== nothing vtyp = vtypes[slot_id(e)] if !vtyp.undef - effects = EFFECTS_TOTAL + return RTEffects(vtyp.typ, Union{}, EFFECTS_TOTAL) end - return RTEffects(vtyp.typ, effects) + return RTEffects(vtyp.typ, UndefVarError, EFFECTS_THROWS) end - return RTEffects(Any, effects) + return RTEffects(Any, UndefVarError, EFFECTS_THROWS) elseif isa(e, Argument) if vtypes !== nothing - return RTEffects(vtypes[slot_id(e)].typ, EFFECTS_TOTAL) + return RTEffects(vtypes[slot_id(e)].typ, Union{}, EFFECTS_TOTAL) else @assert isa(sv, IRInterpretationState) - return RTEffects(sv.ir.argtypes[e.n], EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion + return RTEffects(sv.ir.argtypes[e.n], Union{}, EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion end elseif isa(e, GlobalRef) return abstract_eval_globalref(interp, e, sv) end - return RTEffects(Const(e), EFFECTS_TOTAL) + return RTEffects(Const(e), Union{}, EFFECTS_TOTAL) end function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) @@ -2314,17 +2342,18 @@ end struct RTEffects rt + exct effects::Effects - RTEffects(@nospecialize(rt), effects::Effects) = new(rt, effects) + RTEffects(@nospecialize(rt), @nospecialize(exct), effects::Effects) = new(rt, exct, effects) end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) si = StmtInfo(!call_result_unused(sv, sv.currpc)) - (; rt, effects, info) = abstract_call(interp, arginfo, si, sv) + (; rt, exct, effects, info) = abstract_call(interp, arginfo, si, sv) sv.stmt_info[sv.currpc] = info # mark this call statement as DCE-eligible # TODO better to do this in a single pass based on the `info` object at the end of abstractinterpret? - return RTEffects(rt, effects) + return RTEffects(rt, exct, effects) end function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, @@ -2332,26 +2361,33 @@ function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{ ea = e.args argtypes = collect_argtypes(interp, ea, vtypes, sv) if argtypes === nothing - return RTEffects(Bottom, Effects()) + return RTEffects(Bottom, Any, Effects()) end arginfo = ArgInfo(ea, argtypes) return abstract_call(interp, arginfo, sv) end +function abstract_eval_the_exception(interp::AbstractInterpreter, sv::InferenceState) + return sv.handlers[sv.handler_at[sv.currpc][2]].exct +end +abstract_eval_the_exception(interp, sv) = Any + function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) effects = Effects() ehead = e.head 𝕃ᵢ = typeinf_lattice(interp) ⊑ᵢ = ⊑(𝕃ᵢ) + exct = Any if ehead === :call - (; rt, effects) = abstract_eval_call(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_call(interp, e, vtypes, sv) t = rt elseif ehead === :new t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) ut = unwrap_unionall(t) consistent = noub = ALWAYS_FALSE nothrow = false + exct = Union{ErrorException, TypeError} if isa(ut, DataType) && !isabstracttype(ut) ismutable = ismutabletype(ut) fcount = datatype_fieldcount(ut) @@ -2411,6 +2447,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp t = refine_partial_type(t) end end + nothrow && (exct = Union{}) effects = Effects(EFFECTS_TOTAL; consistent, nothrow, noub) elseif ehead === :splatnew t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) @@ -2461,7 +2498,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end end elseif ehead === :foreigncall - (; rt, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv) t = rt elseif ehead === :cfunction effects = EFFECTS_UNKNOWN @@ -2484,6 +2521,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp sym = e.args[1] t = Bool effects = EFFECTS_TOTAL + exct = Union{} if isa(sym, SlotNumber) && vtypes !== nothing vtyp = vtypes[slot_id(sym)] if vtyp.typ === Bottom @@ -2524,10 +2562,12 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp condt = argextype(stmt.args[2], ir) condval = maybe_extract_const_bool(condt) t = Nothing + exct = UndefVarError effects = EFFECTS_THROWS if condval isa Bool if condval effects = EFFECTS_TOTAL + exct = Union{} else t = Union{} end @@ -2536,13 +2576,16 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end elseif ehead === :boundscheck t = Bool + exct = Union{} effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) elseif ehead === :the_exception - t = Any + t = abstract_eval_the_exception(interp, sv) + exct = Union{} effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) elseif ehead === :static_parameter n = e.args[1]::Int nothrow = false + exct = UndefVarError if 1 <= n <= length(sv.sptypes) sp = sv.sptypes[n] t = sp.typ @@ -2550,15 +2593,21 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp else t = Any end + if nothrow + exct = Union{} + end effects = Effects(EFFECTS_TOTAL; nothrow) elseif ehead === :gc_preserve_begin || ehead === :aliasscope t = Any + exct = Union{} effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, effect_free=EFFECT_FREE_GLOBALLY) elseif ehead === :gc_preserve_end || ehead === :leave || ehead === :pop_exception || ehead === :global || ehead === :popaliasscope t = Nothing + exct = Union{} effects = Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY) elseif ehead === :method t = Method + exct = Union{} effects = Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY) elseif ehead === :thunk t = Any @@ -2574,7 +2623,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp # and recompute the effects. effects = EFFECTS_TOTAL end - return RTEffects(t, effects) + return RTEffects(t, exct, effects) end # refine the result of instantiation of partially-known type `t` if some invariant can be assumed @@ -2595,7 +2644,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: t = sp_type_rewrap(e.args[2], mi, true) for i = 3:length(e.args) if abstract_eval_value(interp, e.args[i], vtypes, sv) === Bottom - return RTEffects(Bottom, EFFECTS_THROWS) + return RTEffects(Bottom, Any, EFFECTS_THROWS) end end effects = foreigncall_effects(e) do @nospecialize x @@ -2613,7 +2662,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, noub = override.noub ? ALWAYS_TRUE : effects.noub) end - return RTEffects(t, effects) + return RTEffects(t, Any, effects) end function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) @@ -2637,11 +2686,11 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), if !isa(e, Expr) if isa(e, PhiNode) add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW) - return abstract_eval_phi(interp, e, vtypes, sv) + return RTEffects(abstract_eval_phi(interp, e, vtypes, sv), Union{}, EFFECTS_TOTAL) end - (; rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_special_value(interp, e, vtypes, sv) else - (; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv) if effects.noub === NOUB_IF_NOINBOUNDS if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) effects = Effects(effects; noub=ALWAYS_FALSE) @@ -2669,7 +2718,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), set_curr_ssaflag!(sv, flags_for_effects(effects), IR_FLAGS_EFFECTS) merge_effects!(interp, sv, effects) - return rt + return RTEffects(rt, exct, effects) end function isdefined_globalref(g::GlobalRef) @@ -2702,7 +2751,7 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv:: consistent = inaccessiblememonly = ALWAYS_TRUE rt = Union{} end - return RTEffects(rt, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) + return RTEffects(rt, nothrow ? Union{} : UndefVarError, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) end function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty)) @@ -2896,50 +2945,51 @@ end struct BasicStmtChange changes::Union{Nothing,StateUpdate} - type::Any # ::Union{Type, Nothing} - `nothing` if this statement may not be used as an SSA Value + rt::Any # extended lattice element or `nothing` - `nothing` if this statement may not be used as an SSA Value + exct::Any # TODO effects::Effects - BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize type) = new(changes, type) + BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize(rt), @nospecialize(exct)) = new(changes, rt, exct) end @inline function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState) if isa(stmt, NewvarNode) changes = StateUpdate(stmt.slot, VarState(Bottom, true), pc_vartable, false) - return BasicStmtChange(changes, nothing) + return BasicStmtChange(changes, nothing, Union{}) elseif !isa(stmt, Expr) - t = abstract_eval_statement(interp, stmt, pc_vartable, frame) - return BasicStmtChange(nothing, t) + (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, rt, exct) end changes = nothing stmt = stmt::Expr hd = stmt.head if hd === :(=) - t = abstract_eval_statement(interp, stmt.args[2], pc_vartable, frame) - if t === Bottom - return BasicStmtChange(nothing, Bottom) + (; rt, exct) = abstract_eval_statement(interp, stmt.args[2], pc_vartable, frame) + if rt === Bottom + return BasicStmtChange(nothing, Bottom, exct) end lhs = stmt.args[1] if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(t, false), pc_vartable, false) + changes = StateUpdate(lhs, VarState(rt, false), pc_vartable, false) elseif isa(lhs, GlobalRef) - handle_global_assignment!(interp, frame, lhs, t) + handle_global_assignment!(interp, frame, lhs, rt) elseif !isa(lhs, SSAValue) merge_effects!(interp, frame, EFFECTS_UNKNOWN) end - return BasicStmtChange(changes, t) + return BasicStmtChange(changes, rt, exct) elseif hd === :method fname = stmt.args[1] if isa(fname, SlotNumber) changes = StateUpdate(fname, VarState(Any, false), pc_vartable, false) end - return BasicStmtChange(changes, nothing) + return BasicStmtChange(changes, nothing, Union{}) elseif (hd === :code_coverage_effect || ( hd !== :boundscheck && # :boundscheck can be narrowed to Bool is_meta_expr(stmt))) - return BasicStmtChange(nothing, Nothing) + return BasicStmtChange(nothing, Nothing, Bottom) else - t = abstract_eval_statement(interp, stmt, pc_vartable, frame) - return BasicStmtChange(nothing, t) + (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, rt, exct) end end @@ -3000,9 +3050,9 @@ end function propagate_to_error_handler!(frame::InferenceState, currpc::Int, W::BitSet, 𝕃ᵢ::AbstractLattice, currstate::VarTable) # If this statement potentially threw, propagate the currstate to the # exception handler, BEFORE applying any state changes. - cur_hand = frame.handler_at[currpc] + cur_hand = frame.handler_at[currpc][1] if cur_hand != 0 - enter = frame.src.code[cur_hand]::Expr + enter = frame.src.code[frame.handlers[cur_hand].enter_idx]::Expr l = enter.args[1]::Int exceptbb = block_for_inst(frame.cfg, l) if update_bbstate!(𝕃ᵢ, frame, exceptbb, currstate) @@ -3148,23 +3198,47 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) # Fall through terminator - treat as regular stmt end # Process non control-flow statements - (; changes, type) = abstract_eval_basic_statement(interp, + (; changes, rt, exct) = abstract_eval_basic_statement(interp, stmt, currstate, frame) + if exct !== Union{} + 𝕃ₚ = ipo_lattice(interp) + cur_hand = frame.handler_at[currpc][1] + if cur_hand == 0 + if !⊑(𝕃ₚ, exct, frame.exc_bestguess) + frame.exc_bestguess = tmerge(𝕃ₚ, frame.exc_bestguess, exct) + for (caller, caller_pc) in frame.cycle_backedges + handler = caller.handler_at[caller_pc][1] + if (handler == 0 ? caller.exc_bestguess : caller.handlers[handler].exct) !== Any + push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) + end + end + end + else + handler_frame = frame.handlers[cur_hand] + if !⊑(𝕃ₚ, exct, handler_frame.exct) + handler_frame.exct = tmerge(𝕃ₚ, handler_frame.exct, exct) + enter = frame.src.code[handler_frame.enter_idx]::Expr + l = enter.args[1]::Int + exceptbb = block_for_inst(frame.cfg, l) + push!(W, exceptbb) + end + end + end if (get_curr_ssaflag(frame) & IR_FLAG_NOTHROW) != IR_FLAG_NOTHROW propagate_to_error_handler!(frame, currpc, W, 𝕃ᵢ, currstate) end - if type === Bottom + if rt === Bottom ssavaluetypes[currpc] = Bottom @goto find_next_bb end if changes !== nothing stoverwrite1!(currstate, changes) end - if type === nothing + if rt === nothing ssavaluetypes[currpc] = Any continue end - record_ssa_assign!(𝕃ᵢ, currpc, type, frame) + record_ssa_assign!(𝕃ᵢ, currpc, rt, frame) end # for currpc in bbstart:bbend # Case 1: Fallthrough termination diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 485ba5e416665..4b1ae9dd155b3 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -171,6 +171,60 @@ function Effects(effects::Effects = _EFFECTS_UNKNOWN; nonoverlayed) end +function better_effects(new::Effects, old::Effects) + any_improved = false + if new.consistent == ALWAYS_TRUE + any_improved |= old.consistent != ALWAYS_TRUE + elseif new.consistent != old.consistent + return false + end + if new.effect_free == ALWAYS_TRUE + any_improved |= old.consistent != ALWAYS_TRUE + elseif new.effect_free == EFFECT_FREE_IF_INACCESSIBLEMEMONLY + old.effect_free == ALWAYS_TRUE && return false + any_improved |= old.effect_free != EFFECT_FREE_IF_INACCESSIBLEMEMONLY + elseif new.effect_free != old.effect_free + return false + end + if new.nothrow + any_improved |= !old.nothrow + elseif new.nothrow != old.nothrow + return false + end + if new.terminates + any_improved |= !old.terminates + elseif new.terminates != old.terminates + return false + end + if new.notaskstate + any_improved |= !old.notaskstate + elseif new.notaskstate != old.notaskstate + return false + end + if new.inaccessiblememonly == ALWAYS_TRUE + any_improved |= old.inaccessiblememonly != ALWAYS_TRUE + elseif new.inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY + old.inaccessiblememonly == ALWAYS_TRUE && return false + any_improved |= old.inaccessiblememonly != INACCESSIBLEMEM_OR_ARGMEMONLY + elseif new.inaccessiblememonly != old.inaccessiblememonly + return false + end + if new.noub == ALWAYS_TRUE + any_improved |= old.noub != ALWAYS_TRUE + elseif new.noub == NOUB_IF_NOINBOUNDS + old.noub == ALWAYS_TRUE && return false + any_improved |= old.noub != NOUB_IF_NOINBOUNDS + elseif new.noub != old.noub + return false + end + if new.nonoverlayed + any_improved |= !old.nonoverlayed + elseif new.nonoverlayed != old.nonoverlayed + return false + end + return any_improved +end + function merge_effects(old::Effects, new::Effects) return Effects( merge_effectbits(old.consistent, new.consistent), diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 4024273944200..5f9855372ff08 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -203,6 +203,11 @@ const CACHE_MODE_GLOBAL = 0x01 << 0 # cached globally, optimization allowed const CACHE_MODE_LOCAL = 0x01 << 1 # cached locally, optimization allowed const CACHE_MODE_VOLATILE = 0x01 << 2 # not cached, optimization allowed +mutable struct TryCatchFrame + exct + const enter_idx +end + mutable struct InferenceState #= information about this method instance =# linfo::MethodInstance @@ -218,7 +223,8 @@ mutable struct InferenceState currbb::Int currpc::Int ip::BitSet#=TODO BoundedMinPrioritySet=# # current active instruction pointers - handler_at::Vector{Int} # current exception handler info + handlers::Vector{TryCatchFrame} + handler_at::Vector{Tuple{Int, Int}} # tuple of current (handler, exception stack) value at the pc ssavalue_uses::Vector{BitSet} # ssavalue sparsity and restart info # TODO: Could keep this sparsely by doing structural liveness analysis ahead of time. bb_vartables::Vector{Union{Nothing,VarTable}} # nothing if not analyzed yet @@ -239,6 +245,7 @@ mutable struct InferenceState unreachable::BitSet # statements that were found to be statically unreachable valid_worlds::WorldRange bestguess #::Type + exc_bestguess ipo_effects::Effects #= flags =# @@ -266,7 +273,7 @@ mutable struct InferenceState currbb = currpc = 1 ip = BitSet(1) # TODO BitSetBoundedMinPrioritySet(1) - handler_at = compute_trycatch(code, BitSet()) + handler_at, handlers = compute_trycatch(code, BitSet()) nssavalues = src.ssavaluetypes::Int ssavalue_uses = find_ssavalue_uses(code, nssavalues) nstmts = length(code) @@ -296,6 +303,7 @@ mutable struct InferenceState valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) bestguess = Bottom + exc_bestguess = Bottom ipo_effects = EFFECTS_TOTAL insert_coverage = should_insert_coverage(mod, src) @@ -315,9 +323,9 @@ mutable struct InferenceState return new( linfo, world, mod, sptypes, slottypes, src, cfg, method_info, - currbb, currpc, ip, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, + currbb, currpc, ip, handlers, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, - result, unreachable, valid_worlds, bestguess, ipo_effects, + result, unreachable, valid_worlds, bestguess, exc_bestguess, ipo_effects, restrict_abstract_call_sites, cache_mode, insert_coverage, interp) end @@ -347,16 +355,19 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) empty!(ip) ip.offset = 0 # for _bits_findnext push!(ip, n + 1) - handler_at = fill(0, n) + handler_at = fill((0, 0), n) + handlers = TryCatchFrame[] # start from all :enter statements and record the location of the try for pc = 1:n stmt = code[pc] if isexpr(stmt, :enter) l = stmt.args[1]::Int - handler_at[pc + 1] = pc + push!(handlers, TryCatchFrame(Bottom, pc)) + handler_id = length(handlers) + handler_at[pc + 1] = (handler_id, 0) push!(ip, pc + 1) - handler_at[l] = pc + handler_at[l] = (handler_id, handler_id) push!(ip, l) end end @@ -369,25 +380,26 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 pc´ = pc + 1 # next program-counter (after executing instruction) delete!(ip, pc) - cur_hand = handler_at[pc] - @assert cur_hand != 0 "unbalanced try/catch" + cur_stacks = handler_at[pc] + @assert cur_stacks != (0, 0) "unbalanced try/catch" stmt = code[pc] if isa(stmt, GotoNode) pc´ = stmt.label elseif isa(stmt, GotoIfNot) l = stmt.dest::Int - if handler_at[l] != cur_hand - @assert handler_at[l] == 0 "unbalanced try/catch" - handler_at[l] = cur_hand + if handler_at[l] != cur_stacks + @assert handler_at[l][1] == 0 || handler_at[l][1] == cur_stacks[1] "unbalanced try/catch" + handler_at[l] = cur_stacks push!(ip, l) end elseif isa(stmt, ReturnNode) - @assert !isdefined(stmt, :val) "unbalanced try/catch" + @assert !isdefined(stmt, :val) || cur_stacks[1] == 0 "unbalanced try/catch" break elseif isa(stmt, Expr) head = stmt.head if head === :enter - cur_hand = pc + # Already set above + cur_stacks = (handler_at[pc´][1], cur_stacks[2]) elseif head === :leave l = 0 for j = 1:length(stmt.args) @@ -403,19 +415,21 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) end l += 1 end + cur_hand = cur_stacks[1] for i = 1:l - cur_hand = handler_at[cur_hand] + cur_hand = handler_at[handlers[cur_hand].enter_idx][1] end - cur_hand == 0 && break + cur_stacks = (cur_hand, cur_stacks[2]) + cur_stacks == (0, 0) && break + elseif head === :pop_exception + cur_stacks = (cur_stacks[1], handler_at[(stmt.args[1]::SSAValue).id][2]) + cur_stacks == (0, 0) && break end end pc´ > n && break # can't proceed with the fast-path fall-through - if handler_at[pc´] != cur_hand - if handler_at[pc´] != 0 - @assert false "unbalanced try/catch" - end - handler_at[pc´] = cur_hand + if handler_at[pc´] != cur_stacks + handler_at[pc´] = cur_stacks elseif !in(pc´, ip) break # already visited end @@ -424,7 +438,7 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) end @assert first(ip) == n + 1 - return handler_at + return handler_at, handlers end # check if coverage mode is enabled diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 0e34cf9ce70bc..ff9f0044917f7 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -46,9 +46,9 @@ end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRInterpretationState) si = StmtInfo(true) # TODO better job here? - (; rt, effects, info) = abstract_call(interp, arginfo, si, irsv) + (; rt, exct, effects, info) = abstract_call(interp, arginfo, si, irsv) irsv.ir.stmts[irsv.curridx][:info] = info - return RTEffects(rt, effects) + return RTEffects(rt, exct, effects) end function update_phi!(irsv::IRInterpretationState, from::Int, to::Int) diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index f3253526d25e9..cbe167926014c 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -584,7 +584,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, end # Record the correct exception handler for all critical sections - handler_at = compute_trycatch(code, BitSet()) + handler_at, handlers = compute_trycatch(code, BitSet()) phi_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] live_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] @@ -627,10 +627,12 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, # The slot is live-in into this block. We need to # Create a PhiC node in the catch entry block and # an upsilon node in the corresponding enter block + varstate = sv.bb_vartables[li] + if varstate === nothing + continue + end node = PhiCNode(Any[]) insertpoint = first_insert_for_bb(code, cfg, li) - varstate = sv.bb_vartables[li] - @assert varstate !== nothing vt = varstate[idx] phic_ssa = NewSSAValue( insert_node!(ir, insertpoint, @@ -690,6 +692,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, new_nodes = ir.new_nodes @timeit "SSA Rename" while !isempty(worklist) (item::Int, pred, incoming_vals) = pop!(worklist) + if sv.bb_vartables[item] === nothing + continue + end # Rename existing phi nodes first, because their uses occur on the edge # TODO: This isn't necessary if inlining stops replacing arguments by slots. for idx in cfg.blocks[item].stmts @@ -810,8 +815,8 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, incoming_vals[id] = Pair{Any, Any}(thisval, thisdef) has_pinode[id] = false enter_idx = idx - while handler_at[enter_idx] != 0 - enter_idx = handler_at[enter_idx] + while handler_at[enter_idx][1] != 0 + (; enter_idx) = handlers[handler_at[enter_idx][1]] leave_block = block_for_inst(cfg, code[enter_idx].args[1]::Int) cidx = findfirst((; slot)::NewPhiCNode2->slot_id(slot)==id, new_phic_nodes[leave_block]) if cidx !== nothing diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index d634c88cdf8e8..e28858eea60aa 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -10,6 +10,7 @@ and any additional information (`call.info`) for a given generic call. """ struct CallMeta rt::Any + exct::Any effects::Effects info::CallInfo end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 588ecabe429ec..a115a719e4d21 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1379,10 +1379,10 @@ end function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) nargs = length(argtypes) if !isempty(argtypes) && isvarargtype(argtypes[nargs]) - nargs - 1 <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - nargs > 3 || return CallMeta(Any, Effects(), NoCallInfo()) + nargs - 1 <= 6 || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargs > 3 || return CallMeta(Any, Any, Effects(), NoCallInfo()) else - 5 <= nargs <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + 5 <= nargs <= 6 || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end 𝕃ᵢ = typeinf_lattice(interp) o = unwrapva(argtypes[2]) @@ -1404,7 +1404,7 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any end info = ModifyFieldInfo(callinfo.info) end - return CallMeta(RT, Effects(), info) + return CallMeta(RT, Any, Effects(), info) end @nospecs function replacefield!_tfunc(𝕃::AbstractLattice, o, f, x, v, success_order, failure_order) return replacefield!_tfunc(𝕃, o, f, x, v) @@ -2704,7 +2704,7 @@ end # since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type, # while this assumes that it is an absolutely precise and accurate and exact model of both function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) - UNKNOWN = CallMeta(Type, EFFECTS_THROWS, NoCallInfo()) + UNKNOWN = CallMeta(Type, Any, EFFECTS_THROWS, NoCallInfo()) if !(2 <= length(argtypes) <= 3) return UNKNOWN end @@ -2733,7 +2733,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s end if contains_is(argtypes_vec, Union{}) - return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(Const(Union{}), Any, EFFECTS_TOTAL, NoCallInfo()) end # Run the abstract_call without restricting abstract call @@ -2751,33 +2751,33 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s rt = widenslotwrapper(call.rt) if isa(rt, Const) # output was computed to be constant - return CallMeta(Const(typeof(rt.val)), EFFECTS_TOTAL, info) + return CallMeta(Const(typeof(rt.val)), Any, EFFECTS_TOTAL, info) end rt = widenconst(rt) if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt)) # output cannot be improved so it is known for certain - return CallMeta(Const(rt), EFFECTS_TOTAL, info) + return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info) elseif isa(sv, InferenceState) && !isempty(sv.pclimitations) # conservatively express uncertainty of this result # in two ways: both as being a subtype of this, and # because of LimitedAccuracy causes - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info) elseif isa(tt, Const) || isconstType(tt) # input arguments were known for certain # XXX: this doesn't imply we know anything about rt - return CallMeta(Const(rt), EFFECTS_TOTAL, info) + return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info) elseif isType(rt) - return CallMeta(Type{rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{rt}, Union{}, EFFECTS_TOTAL, info) else - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info) end 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 CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - isvarargtype(argtypes[2]) && return CallMeta(Bool, EFFECTS_UNKNOWN, NoCallInfo()) + length(argtypes) < 2 && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + isvarargtype(argtypes[2]) && return CallMeta(Bool, Any, EFFECTS_UNKNOWN, NoCallInfo()) argtypes = argtypes[2:end] atype = argtypes_to_type(argtypes) matches = find_matching_methods(typeinf_lattice(interp), argtypes, atype, method_table(interp), @@ -2816,7 +2816,7 @@ function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, end end end - return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(rt, Union{}, EFFECTS_TOTAL, NoCallInfo()) end add_tfunc(applicable, 1, INT_INF, @nospecs((𝕃::AbstractLattice, f, args...)->Bool), 40) @@ -2825,26 +2825,26 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv if length(argtypes) == 3 && !isvarargtype(argtypes[3]) ft′ = argtype_by_index(argtypes, 2) ft = widenconst(ft′) - ft === Bottom && return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + ft === Bottom && return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) typeidx = 3 elseif length(argtypes) == 2 && !isvarargtype(argtypes[2]) typeidx = 2 else - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, typeidx), false) - isexact || return CallMeta(Bool, Effects(), NoCallInfo()) + isexact || return CallMeta(Bool, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) end if typeidx == 3 - isdispatchelem(ft) || return CallMeta(Bool, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + isdispatchelem(ft) || return CallMeta(Bool, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below types = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type end mt = ccall(:jl_method_table_for, Any, (Any,), types) if !isa(mt, MethodTable) - return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) end match, valid_worlds = findsup(types, method_table(interp)) update_valid_age!(sv, valid_worlds) @@ -2856,7 +2856,7 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv edge = specialize_method(match)::MethodInstance add_invoke_backedge!(sv, types, edge) end - return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(rt, Any, EFFECTS_TOTAL, NoCallInfo()) end # N.B.: typename maps type equivalence classes to a single value diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 29d3c01d151be..b2f515a8fc95e 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -328,7 +328,7 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, end # relocatability = isa(inferred_result, String) ? inferred_result[end] : UInt8(0) return CodeInstance(result.linfo, - widenconst(result_type), rettype_const, inferred_result, + widenconst(result_type), widenconst(result.exc_result), rettype_const, inferred_result, const_flags, first(valid_worlds), last(valid_worlds), # TODO: Actually do something with non-IPO effects encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, @@ -523,7 +523,8 @@ function finish(me::InferenceState, interp::AbstractInterpreter) # 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) - limited_ret = bestguess isa LimitedAccuracy + exc_bestguess = me.exc_bestguess = cycle_fix_limited(me.exc_bestguess, me) + limited_ret = bestguess isa LimitedAccuracy || exc_bestguess isa LimitedAccuracy limited_src = false if !limited_ret gt = me.ssavaluetypes @@ -538,6 +539,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) me.result.valid_worlds = me.valid_worlds me.result.result = bestguess me.result.ipo_effects = me.ipo_effects = adjust_effects(me) + me.result.exc_result = exc_bestguess if limited_ret # a parent may be cached still, but not this intermediate work: @@ -567,6 +569,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) me.result.src = me.src # for reflection etc. end end + validate_code_in_debug_mode(me.linfo, me.src, "inferred") nothing end @@ -794,15 +797,16 @@ end ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) struct EdgeCallResult - rt #::Type + rt + exct edge::Union{Nothing,MethodInstance} effects::Effects volatile_inf_result::Union{Nothing,VolatileInferenceResult} - function EdgeCallResult(@nospecialize(rt), + function EdgeCallResult(@nospecialize(rt), @nospecialize(exct), edge::Union{Nothing,MethodInstance}, effects::Effects, volatile_inf_result::Union{Nothing,VolatileInferenceResult} = nothing) - return new(rt, edge, effects, volatile_inf_result) + return new(rt, exct, edge, effects, volatile_inf_result) end end @@ -822,14 +826,14 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize rt = cached_return_type(code) effects = ipo_effects(code) update_valid_age!(caller, WorldRange(min_world(code), max_world(code))) - return EdgeCallResult(rt, mi, effects) + return EdgeCallResult(rt, code.exctype, mi, effects) end else cache_mode = CACHE_MODE_GLOBAL # cache edge targets globally by default end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_output(#=incremental=#false) add_remark!(interp, caller, "Inference is disabled for the target module") - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end if !is_cached(caller) && frame_parent(caller) === nothing # this caller exists to return to the user @@ -847,7 +851,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize add_remark!(interp, caller, "Failed to retrieve source") # can't get the source for this, so we know nothing unlock_mi_inference(interp, mi) - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end if is_cached(caller) || frame_parent(caller) !== nothing # don't involve uncached functions in cycle resolution frame.parent = caller @@ -862,15 +866,15 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize volatile_inf_result = isinferred && let inferred_src = result.src isa(inferred_src, CodeInfo) && (is_inlineable(inferred_src) || force_inline) end ? VolatileInferenceResult(result) : nothing - return EdgeCallResult(frame.bestguess, edge, effects, volatile_inf_result) + return EdgeCallResult(frame.bestguess, frame.exc_bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(caller, frame.valid_worlds) - return EdgeCallResult(frame.bestguess, nothing, adjust_effects(Effects(), method)) + return EdgeCallResult(frame.bestguess, frame.exc_bestguess, nothing, adjust_effects(Effects(), method)) end function cached_return_type(code::CodeInstance) diff --git a/base/compiler/types.jl b/base/compiler/types.jl index db1d85cdddc40..45dc67f24a03a 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -71,6 +71,7 @@ mutable struct InferenceResult const argtypes::Vector{Any} const overridden_by_const::BitVector result # extended lattice element if inferred, nothing otherwise + exc_result # like `result`, but for the thrown value src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise valid_worlds::WorldRange # if inference and optimization is finished ipo_effects::Effects # if inference is finished @@ -81,7 +82,7 @@ mutable struct InferenceResult # def = linfo.def # nargs = def isa Method ? Int(def.nargs) : 0 # @assert length(cache_argtypes) == nargs - return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, + return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, nothing, WorldRange(), Effects(), Effects(), nothing, false) end end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 3d1fe35453060..d8ca4d9551656 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -462,14 +462,14 @@ function is_throw_call(e::Expr, code::Vector{Any}) return false end -function mark_throw_blocks!(src::CodeInfo, handler_at::Vector{Int}) +function mark_throw_blocks!(src::CodeInfo, handler_at::Vector{Tuple{Int, Int}}) for stmt in find_throw_blocks(src.code, handler_at) src.ssaflags[stmt] |= IR_FLAG_THROW_BLOCK end return nothing end -function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) +function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Tuple{Int, Int}}) stmts = BitSet() n = length(code) for i in n:-1:1 @@ -482,7 +482,7 @@ function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) elseif s.head === :return # see `ReturnNode` handling elseif is_throw_call(s, code) - if handler_at[i] == 0 + if handler_at[i][1] == 0 push!(stmts, i) end elseif i+1 in stmts diff --git a/src/gf.c b/src/gf.c index 0ad875b167506..a630b6d663654 100644 --- a/src/gf.c +++ b/src/gf.c @@ -284,13 +284,6 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ // ----- MethodInstance specialization instantiation ----- // -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { jl_sym_t *sname = jl_symbol(name); @@ -323,7 +316,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_gc_wb(m, mi); jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); @@ -466,7 +459,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( codeinst = jl_atomic_load_relaxed(&codeinst->next); } codeinst = jl_new_codeinst( - mi, rettype, NULL, NULL, + mi, rettype, (jl_value_t*)jl_any_type, NULL, NULL, 0, min_world, max_world, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; @@ -483,7 +476,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_codeinst_for_src( } JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, + jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *exctype, jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, @@ -498,6 +491,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( codeinst->min_world = min_world; codeinst->max_world = max_world; codeinst->rettype = rettype; + codeinst->exctype = exctype; jl_atomic_store_release(&codeinst->inferred, inferred); //codeinst->edges = NULL; if ((const_flags & 2) == 0) @@ -2455,7 +2449,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_callptr_t unspec_invoke = NULL; if (unspec && (unspec_invoke = jl_atomic_load_acquire(&unspec->invoke))) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, NULL, NULL, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); if (unspec_fptr) { @@ -2482,7 +2476,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_info_t *src = jl_code_for_interpreter(mi, world); if (!jl_code_requires_compiler(src, 0)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, NULL, NULL, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); @@ -2519,7 +2513,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t // only these care about the exact specTypes, otherwise we can use it directly return ucache; } - codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, + codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); if (unspec_fptr) { diff --git a/src/jltypes.c b/src/jltypes.c index c55c6f47f064c..ed51c26449358 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3244,12 +3244,13 @@ 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(15, + jl_perm_symsvec(16, "def", "next", "min_world", "max_world", "rettype", + "exctype", "rettype_const", "inferred", //"edges", @@ -3258,7 +3259,7 @@ void jl_init_types(void) JL_GC_DISABLED "argescapes", "isspecsig", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(15, + jl_svec(16, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -3266,6 +3267,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type, jl_any_type, + jl_any_type, //jl_any_type, //jl_bool_type, jl_uint32_type, jl_uint32_type, @@ -3277,8 +3279,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const - const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic + const static uint32_t code_instance_constfields[1] = { 0b0000010101110001 }; // Set fields 1, 5-7, 9, 11 as const + const static uint32_t code_instance_atomicfields[1] = { 0b1101001010000010 }; // Set fields 2, 8, 10, 13, 15-16 as atomic //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe jl_code_instance_type->name->constfields = code_instance_constfields; @@ -3420,8 +3422,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); jl_svecset(jl_method_type->types, 12, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); - jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 1, jl_globalref_type); jl_svecset(jl_binding_type->types, 2, jl_binding_type); diff --git a/src/julia.h b/src/julia.h index b2f6ab8b7a62f..b745a89a93a5d 100644 --- a/src/julia.h +++ b/src/julia.h @@ -410,6 +410,7 @@ typedef struct _jl_code_instance_t { // inference state cache jl_value_t *rettype; // return type for fptr + jl_value_t *exctype; // thrown type for fptr jl_value_t *rettype_const; // inferred constant return value, or null _Atomic(jl_value_t *) inferred; // inferred jl_code_info_t (may be compressed), or jl_nothing, or null //TODO: jl_array_t *edges; // stored information about edges from this object diff --git a/src/julia_internal.h b/src/julia_internal.h index 2c0c13d0bb343..b35c44b98c2ef 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -623,6 +623,13 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_codeinst_for_src( jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT); jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); +JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *exctype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint8_t relocatability); + JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world); JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types); jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *lam JL_PROPAGATES_ROOT, size_t world); diff --git a/src/opaque_closure.c b/src/opaque_closure.c index d73beff0f8587..8d8698c67ed9a 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -134,13 +134,6 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); -JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, - jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, - uint8_t relocatability); - JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile) { @@ -157,7 +150,7 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet sigtype = jl_argtype_with_function(env, (jl_value_t*)argt); jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); - inst = jl_new_codeinst(mi, rt_ub, NULL, (jl_value_t*)ci, + inst = jl_new_codeinst(mi, rt_ub, NULL, NULL, (jl_value_t*)ci, 0, meth->primary_world, -1, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, inst); diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index f7d248464f3c9..f60b101b33842 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -533,9 +533,9 @@ 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) - return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), CC.EFFECTS_TOTAL) + return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL) end - return CC.RTEffects(Union{}, CC.EFFECTS_THROWS) + return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS) end return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, sv::CC.InferenceState) @@ -577,7 +577,7 @@ function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f), sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE) - result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited, + result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited, result.edge, neweffects) end ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any, diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 2980e456e9f9c..3b4bb2c5da0db 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -330,7 +330,7 @@ function CC.abstract_call(interp::NoinlineInterpreter, ret = @invoke CC.abstract_call(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) if sv.mod in noinline_modules(interp) - return CC.CallMeta(ret.rt, ret.effects, NoinlineCallInfo(ret.info)) + return CC.CallMeta(ret.rt, ret.exct, ret.effects, NoinlineCallInfo(ret.info)) end return ret end diff --git a/test/compiler/EscapeAnalysis/EscapeAnalysis.jl b/test/compiler/EscapeAnalysis/EscapeAnalysis.jl index d8ea8be21fe07..7366024329ee6 100644 --- a/test/compiler/EscapeAnalysis/EscapeAnalysis.jl +++ b/test/compiler/EscapeAnalysis/EscapeAnalysis.jl @@ -70,7 +70,7 @@ function is_load_forwardable(x::EscapeInfo) end @testset "EAUtils" begin - @test_throws "everything has been constant folded" code_escapes() do; sin(42); end + @test code_escapes() do; println("prevent ConstABI"); sin(42); end isa EAUtils.EscapeResult @test code_escapes(sin, (Int,)) isa EAUtils.EscapeResult @test code_escapes(sin, (Int,)) isa EAUtils.EscapeResult end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 6872ad0bcc0bc..3bee8e6163814 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4410,8 +4410,8 @@ let x = Tuple{Int,Any}[ #=21=# (0, Expr(:pop_exception, Core.SSAValue(2))) #=22=# (0, Core.ReturnNode(Core.SlotNumber(3))) ] - handler_at = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) - @test handler_at == first.(x) + handler_at, handlers = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) + @test map(x->x[1] == 0 ? 0 : handlers[x[1]].enter_idx, handler_at) == first.(x) end @test only(Base.return_types((Bool,)) do y @@ -5504,3 +5504,28 @@ function test_exit_bottom(s) n end @test only(Base.return_types(test_exit_bottom, Tuple{String})) == Int + +function foo_typed_throw_error() + try + error() + catch e + if isa(e, ErrorException) + return 1.0 + end + end + return 1 +end +@test Base.return_types(foo_typed_throw_error) |> only === Float64 + +will_throw_no_method(x::Int) = 1 +function foo_typed_throw_metherr() + try + will_throw_no_method(1.0) + catch e + if isa(e, MethodError) + return 1.0 + end + end + return 1 +end +@test Base.return_types(foo_typed_throw_metherr) |> only === Float64 diff --git a/test/core.jl b/test/core.jl index 505c5d03a6484..f9aa21aadb1e1 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,7 +14,7 @@ include("testenv.jl") # sanity tests that our built-in types are marked correctly for const fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), + (Core.CodeInstance, [:def, :rettype, :exctype, :rettype_const, :ipo_purity_bits, :argescapes]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :is_for_opaque_closure, :constprop=#]), (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), diff --git a/test/precompile.jl b/test/precompile.jl index 8fd8e0a110cbb..2f4873b17c706 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1677,7 +1677,7 @@ precompile_test_harness("issue #46296") do load_path module CodeInstancePrecompile mi = first(Base.specializations(first(methods(identity)))) - ci = Core.CodeInstance(mi, Any, nothing, nothing, zero(Int32), typemin(UInt), + ci = Core.CodeInstance(mi, Any, Any, nothing, nothing, zero(Int32), typemin(UInt), typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00) __init__() = @assert ci isa Core.CodeInstance