diff --git a/base/boot.jl b/base/boot.jl index 98eceb1414ecc..4bf196a4c8265 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -471,13 +471,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 82aca46f3e7ed..e3922d3cc20f3 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) = result + (; rt, exct, edge, effects) = 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 = nothing 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) = result + (; rt, exct, edge, effects) = 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) = typeinf_edge(interp, method, sig, sparams, sv) + (; rt, exct, edge, effects) = 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) + return MethodCallResult(rt, exct, edgecycle, edgelimited, edge, effects) end function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, @@ -744,16 +764,17 @@ end # backedge computation, and concrete evaluation or constant-propagation struct MethodCallResult rt + exct edgecycle::Bool edgelimited::Bool edge::Union{Nothing,MethodInstance} effects::Effects - function MethodCallResult(@nospecialize(rt), + function MethodCallResult(@nospecialize(rt), @nospecialize(exct), edgecycle::Bool, edgelimited::Bool, edge::Union{Nothing,MethodInstance}, effects::Effects) - return new(rt, edgecycle, edgelimited, edge, effects) + return new(rt, exct, edgecycle, edgelimited, edge, effects) end end @@ -765,15 +786,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 @@ -900,11 +922,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` @@ -1167,7 +1190,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 @@ -1176,7 +1199,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) @@ -1211,7 +1234,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 @@ -1451,7 +1475,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] @@ -1529,7 +1553,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)) @@ -1537,7 +1561,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{} @@ -1596,6 +1620,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] @@ -1613,6 +1638,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.") @@ -1622,12 +1648,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) @@ -1876,9 +1903,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]) @@ -1889,7 +1916,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) @@ -1898,10 +1925,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) @@ -1910,36 +1937,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 @@ -1971,7 +1998,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}) @@ -1985,9 +2012,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 @@ -2009,17 +2036,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{}) @@ -2039,7 +2068,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) @@ -2047,7 +2076,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) @@ -2056,16 +2085,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, :(>:)) @@ -2079,7 +2108,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 @@ -2118,7 +2147,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) @@ -2143,13 +2172,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) @@ -2308,17 +2337,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}, @@ -2326,26 +2356,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) @@ -2405,6 +2442,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) @@ -2455,7 +2493,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 @@ -2478,6 +2516,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 @@ -2518,10 +2557,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 @@ -2530,13 +2571,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 @@ -2544,15 +2588,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 @@ -2568,7 +2618,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 @@ -2589,7 +2639,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 @@ -2607,7 +2657,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) @@ -2629,11 +2679,12 @@ 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 - return abstract_eval_special_value(interp, e, vtypes, sv) + return RTEffects(abstract_eval_special_value(interp, e, vtypes, sv), + Union{}, EFFECTS_TOTAL) end - (; 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) @@ -2659,7 +2710,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), sv.pclimitations = IdSet{InferenceState}() end end - return rt + return RTEffects(rt, exct, effects) end function isdefined_globalref(g::GlobalRef) @@ -2887,50 +2938,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 @@ -2991,9 +3043,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) @@ -3138,23 +3190,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 96543308e8bd7..625037f671427 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -198,6 +198,11 @@ to enable flow-sensitive analysis. """ const VarTable = Vector{VarState} +mutable struct TryCatchFrame + exct + const enter_idx +end + mutable struct InferenceState #= information about this method instance =# linfo::MethodInstance @@ -213,7 +218,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, excecption 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 @@ -234,6 +240,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 =# @@ -261,7 +268,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) @@ -291,6 +298,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) @@ -311,9 +319,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 @@ -343,16 +351,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 @@ -365,25 +376,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 aboves + cur_stacks = (handler_at[pc´][1], cur_stacks[2]) elseif head === :leave l = 0 for j = 1:length(stmt.args) @@ -399,19 +411,20 @@ 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]) 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 @@ -420,7 +433,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 1f682d9168413..5f07a2eb3f876 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)] @@ -810,8 +810,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 9f55d56181838..d2f47ab26a152 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 cc555e5bde5b6..d1f0cd28edd01 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1377,10 +1377,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]) @@ -1402,7 +1402,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) @@ -2702,7 +2702,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 @@ -2731,7 +2731,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 @@ -2749,33 +2749,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), @@ -2814,7 +2814,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) @@ -2823,26 +2823,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) @@ -2854,7 +2854,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 cab02a39c87c1..6ec49d76aba9f 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -333,7 +333,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, @@ -526,7 +526,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,9 +539,6 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end end end - me.result.valid_worlds = me.valid_worlds - me.result.result = bestguess - me.ipo_effects = me.result.ipo_effects = adjust_effects(me) if limited_ret # a parent may be cached still, but not this intermediate work: @@ -570,6 +568,10 @@ function finish(me::InferenceState, interp::AbstractInterpreter) me.result.src = me.src # for reflection etc. end end + me.result.valid_worlds = me.valid_worlds + me.result.result = bestguess + me.ipo_effects = me.result.ipo_effects = adjust_effects(me) + me.result.exc_result = exc_bestguess validate_code_in_debug_mode(me.linfo, me.src, "inferred") nothing end @@ -797,13 +799,14 @@ end ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) struct EdgeCallResult - rt #::Type + rt + exct edge::Union{Nothing,MethodInstance} effects::Effects - function EdgeCallResult(@nospecialize(rt), + function EdgeCallResult(@nospecialize(rt), @nospecialize(exct), edge::Union{Nothing,MethodInstance}, effects::Effects) - return new(rt, edge, effects) + return new(rt, exct, edge, effects) end end @@ -822,14 +825,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 = :global # cache edge targets 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 +850,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 @@ -857,15 +860,15 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize isinferred = is_inferred(frame) edge = isinferred ? mi : nothing effects = isinferred ? frame.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects - return EdgeCallResult(frame.bestguess, edge, effects) + return EdgeCallResult(frame.bestguess, frame.exc_bestguess, edge, effects) 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 c53256c61ace9..7a8222bd93674 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, true) end end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 8f980bb681142..7aaaa46095dfd 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -459,14 +459,14 @@ function is_throw_call(e::Expr) 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 @@ -479,7 +479,7 @@ function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) elseif s.head === :return # see `ReturnNode` handling elseif is_throw_call(s) - 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 154fa49953f34..b36ec677978a3 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3245,12 +3245,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", @@ -3259,7 +3260,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, @@ -3267,6 +3268,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, @@ -3278,8 +3280,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] = { 0b0000010101100001 }; // Set fields 1, 6-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; @@ -3421,8 +3423,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 1a8e62f9dcf9d..e6ca348c35c53 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 09a963258d566..c24e20786aa74 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 2c2646636b53a..bf64a85dd2f8e 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -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 083071b566da6..d9651ff3e57a9 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 fc2d6f774cd9d..1eb0201c8a3a6 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; sin(42); end isa EAUtils.EscapeResult @test code_escapes(sin, (Int,)) isa EAUtils.EscapeResult @test code_escapes(sin, (Int,)) isa EAUtils.EscapeResult end @@ -201,6 +201,7 @@ end let # try/catch result = code_escapes((Any,)) do a try + Base.inferencebarrier(false) && error() nothing catch err return a # return escape diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 861bbbbcec36d..a91edadeb8f2d 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4406,8 +4406,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 @@ -5379,3 +5379,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/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