diff --git a/NEWS.md b/NEWS.md index 053ed9e711ecb..2e78ee5154779 100644 --- a/NEWS.md +++ b/NEWS.md @@ -57,7 +57,6 @@ Compiler/Runtime improvements * Abstract callsite can now be inlined or statically resolved as far as the callsite has a single matching method ([#43113]). * Builtin function are now a bit more like generic functions, and can be enumerated with `methods` ([#43865]). -* Inference now tracks various effects such as sideeffectful-ness and nothrow-ness on a per-specialization basis. Code heavily dependent on constant propagation should see significant compile-time performance improvements and certain cases (e.g. calls to uninlinable functions that are nevertheless effect free) should see runtime performance improvements. Effects may be overwritten manually with the `@Base.assume_effects` macro. (#43852). Command-line option changes --------------------------- diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 5c4188b0f1471..534055fba23f4 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -1238,16 +1238,10 @@ function unsafe_getindex(A::AbstractArray, I...) r end -struct CanonicalIndexError - func::String - type::Any - CanonicalIndexError(func::String, @nospecialize(type)) = new(func, type) -end - error_if_canonical_getindex(::IndexLinear, A::AbstractArray, ::Int) = - throw(CanonicalIndexError("getindex", typeof(A))) + error("getindex not defined for ", typeof(A)) error_if_canonical_getindex(::IndexCartesian, A::AbstractArray{T,N}, ::Vararg{Int,N}) where {T,N} = - throw(CanonicalIndexError("getindex", typeof(A))) + error("getindex not defined for ", typeof(A)) error_if_canonical_getindex(::IndexStyle, ::AbstractArray, ::Any...) = nothing ## Internal definitions @@ -1339,9 +1333,9 @@ function unsafe_setindex!(A::AbstractArray, v, I...) end error_if_canonical_setindex(::IndexLinear, A::AbstractArray, ::Int) = - throw(CanonicalIndexError("setindex!", typeof(A))) + error("setindex! not defined for ", typeof(A)) error_if_canonical_setindex(::IndexCartesian, A::AbstractArray{T,N}, ::Vararg{Int,N}) where {T,N} = - throw(CanonicalIndexError("setindex!", typeof(A))) + error("setindex! not defined for ", typeof(A)) error_if_canonical_setindex(::IndexStyle, ::AbstractArray, ::Any...) = nothing ## Internal definitions diff --git a/base/array.jl b/base/array.jl index 5b9b5b25dcf15..807f99342e25f 100644 --- a/base/array.jl +++ b/base/array.jl @@ -213,7 +213,7 @@ function bitsunionsize(u::Union) end length(a::Array) = arraylen(a) -elsize(@nospecialize _::Type{A}) where {T,A<:Array{T}} = aligned_sizeof(T) +elsize(::Type{<:Array{T}}) where {T} = aligned_sizeof(T) sizeof(a::Array) = Core.sizeof(a) function isassigned(a::Array, i::Int...) diff --git a/base/boot.jl b/base/boot.jl index ecc037407685e..abdd7987ce901 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -418,10 +418,9 @@ eval(Core, :(LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)))) eval(Core, :(CodeInstance(mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, - min_world::UInt, max_world::UInt, ipo_effects::UInt8, effects::UInt8, - relocatability::UInt8) = - ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt, UInt8, UInt8, UInt8), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects, relocatability))) + min_world::UInt, max_world::UInt, relocatability::UInt8) = + ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt, UInt8), + mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, relocatability))) eval(Core, :(Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)))) eval(Core, :(PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields)))) eval(Core, :(PartialOpaque(@nospecialize(typ), @nospecialize(env), isva::Bool, parent::MethodInstance, source::Method) = $(Expr(:new, :PartialOpaque, :typ, :env, :isva, :parent, :source)))) diff --git a/base/c.jl b/base/c.jl index 3606d0fa0a9bc..fb0d4e7dc0583 100644 --- a/base/c.jl +++ b/base/c.jl @@ -733,7 +733,3 @@ name, if desired `"libglib-2.0".g_uri_escape_string(...` macro ccall(expr) return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) end - -macro ccall_effects(effects, expr) - return ccall_macro_lower((:ccall, effects), ccall_macro_parse(expr)...) -end diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index fe94a7f0600aa..9a01f5aa80d59 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -23,26 +23,11 @@ end const empty_bitset = BitSet() -function should_infer_for_effects(sv::InferenceState) - sv.ipo_effects.terminates === ALWAYS_TRUE && - sv.ipo_effects.effect_free === ALWAYS_TRUE -end - function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), arginfo::ArgInfo, @nospecialize(atype), sv::InferenceState, max_methods::Int = get_max_methods(sv.mod, interp)) - if !should_infer_for_effects(sv) && - sv.params.unoptimize_throw_blocks && - is_stmt_throw_block(get_curr_ssaflag(sv)) - # Disable inference of calls in throw blocks, since we're unlikely to - # need their types. There is one exception however: If up until now, the - # function has not seen any side effects, we would like to make sure there - # aren't any in the throw block either to enable other optimizations. + if sv.params.unoptimize_throw_blocks && is_stmt_throw_block(get_curr_ssaflag(sv)) add_remark!(interp, sv, "Skipped call in throw block") - # At this point we are guaranteed to end up throwing on this path, - # which is all that's required for :consistent-cy. Of course, we don't - # know anything else about this statement. - tristate_merge!(sv, Effects(Effects(), consistent=ALWAYS_TRUE)) return CallMeta(Any, false) end @@ -50,7 +35,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), matches = find_matching_methods(argtypes, atype, method_table(interp, sv), InferenceParams(interp).MAX_UNION_SPLITTING, max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) - tristate_merge!(sv, Effects()) return CallMeta(Any, false) end @@ -62,7 +46,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), conditionals = nothing # keeps refinement information of call argument types when the return type is boolean seen = 0 # number of signatures actually inferred any_const_result = false - const_results = Union{InferenceResult,Nothing,ConstResult}[] + const_results = Union{InferenceResult,Nothing}[] multiple_matches = napplicable > 1 if f !== nothing && napplicable == 1 && is_method_pure(applicable[1]::MethodMatch) @@ -100,11 +84,9 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv, false) - effects = result.edge_effects if const_result !== nothing - (;rt, effects, const_result) = const_result + rt, const_result = const_result end - tristate_merge!(sv, effects) push!(const_results, const_result) if const_result !== nothing any_const_result = true @@ -139,12 +121,9 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, match, sv, false) - effects = result.edge_effects if const_result !== nothing - this_rt = const_result.rt - (; effects, const_result) = const_result + this_rt, const_result = const_result end - tristate_merge!(sv, effects) push!(const_results, const_result) if const_result !== nothing any_const_result = true @@ -176,14 +155,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), info = ConstCallInfo(info, const_results) end - if seen != napplicable - tristate_merge!(sv, Effects()) - elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) : - (!_all(b->b, matches.fullmatches) || any_ambig(matches)) - # Account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. - tristate_merge!(sv, Effects(EFFECTS_TOTAL, nothrow=TRISTATE_UNKNOWN)) - end - rettype = from_interprocedural!(rettype, sv, arginfo, conditionals) if call_result_unused(sv) && !(rettype === Bottom) @@ -217,8 +188,6 @@ struct MethodMatches mt::Core.MethodTable fullmatch::Bool end -any_ambig(info::MethodMatchInfo) = info.results.ambig -any_ambig(m::MethodMatches) = any_ambig(m.info) struct UnionSplitMethodMatches applicable::Vector{Any} @@ -228,7 +197,6 @@ struct UnionSplitMethodMatches mts::Vector{Core.MethodTable} fullmatches::Vector{Bool} end -any_ambig(m::UnionSplitMethodMatches) = _any(any_ambig, m.info.matches) function find_matching_methods(argtypes::Vector{Any}, @nospecialize(atype), method_table::MethodTableView, union_split::Int, max_methods::Int) @@ -434,7 +402,7 @@ const RECURSION_MSG = "Bounded recursion detected. Call was widened to force con function abstract_call_method(interp::AbstractInterpreter, method::Method, @nospecialize(sig), sparams::SimpleVector, hardlimit::Bool, sv::InferenceState) 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, false, false, nothing) end topmost = nothing # Limit argument type tuple growth of functions: @@ -503,7 +471,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp # 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, true, true, nothing) end topmost = nothing edgecycle = true @@ -552,7 +520,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp # 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, true, true, nothing) end add_remark!(interp, sv, RECURSION_MSG) topmost = topmost::InferenceState @@ -590,17 +558,11 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp sparams = recomputed[2]::SimpleVector end - rt, edge, edge_effects = typeinf_edge(interp, method, sig, sparams, sv) + rt, edge = typeinf_edge(interp, method, sig, sparams, sv) if edge === nothing edgecycle = edgelimited = true end - if edgecycle - # Some sort of recursion was detected. Even if we did not limit types, - # we cannot guarantee that the call will terminate. - edge_effects = tristate_merge(edge_effects, - Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) - end - return MethodCallResult(rt, edgecycle, edgelimited, edge, edge_effects) + return MethodCallResult(rt, edgecycle, edgelimited, edge) end # keeps result and context information of abstract method call, will be used by succeeding constant-propagation @@ -609,82 +571,17 @@ struct MethodCallResult edgecycle::Bool edgelimited::Bool edge::Union{Nothing,MethodInstance} - edge_effects::Effects function MethodCallResult(@nospecialize(rt), edgecycle::Bool, edgelimited::Bool, - edge::Union{Nothing,MethodInstance}, - edge_effects::Effects) - return new(rt, edgecycle, edgelimited, edge, edge_effects) - end -end - -function is_all_const_arg((; argtypes)::ArgInfo) - for a in argtypes - if !isa(a, Const) && !isconstType(a) && !issingletontype(a) - return false - end - end - return true -end - -function concrete_eval_const_proven_total_or_error(interp::AbstractInterpreter, - @nospecialize(f), (; argtypes)::ArgInfo, _::InferenceState) - args = Any[ (a = widenconditional(argtypes[i]); - isa(a, Const) ? a.val : - isconstType(a) ? (a::DataType).parameters[1] : - (a::DataType).instance) for i in 2:length(argtypes) ] - try - value = Core._call_in_world_total(get_world_counter(interp), f, args...) - return Const(value) - catch e - return nothing - end -end - -function const_prop_enabled(interp::AbstractInterpreter, sv::InferenceState, match::MethodMatch) - if !InferenceParams(interp).ipo_constant_propagation - add_remark!(interp, sv, "[constprop] Disabled by parameter") - return false - end - method = match.method - if method.constprop == 0x02 - add_remark!(interp, sv, "[constprop] Disabled by method parameter") - return false + edge::Union{Nothing,MethodInstance}) + return new(rt, edgecycle, edgelimited, edge) end - return true -end - -struct ConstCallResults - rt::Any - const_result::Union{InferenceResult, ConstResult} - effects::Effects - ConstCallResults(@nospecialize(rt), - const_result::Union{InferenceResult, ConstResult}, - effects::Effects) = - new(rt, const_result, effects) end function abstract_call_method_with_const_args(interp::AbstractInterpreter, result::MethodCallResult, @nospecialize(f), arginfo::ArgInfo, match::MethodMatch, sv::InferenceState, va_override::Bool) - if !const_prop_enabled(interp, sv, match) - return nothing - end - if f !== nothing && result.edge !== nothing && is_total_or_error(result.edge_effects) && is_all_const_arg(arginfo) - rt = concrete_eval_const_proven_total_or_error(interp, f, arginfo, sv) - add_backedge!(result.edge, sv) - if rt === nothing - # The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Union{}, ConstResult(result.edge), result.edge_effects) - end - if is_inlineable_constant(rt.val) || call_result_unused(sv) - # If the constant is not inlineable, still do the const-prop, since the - # code that led to the creation of the Const may be inlineable in the same - # circumstance and may be optimizable. - return ConstCallResults(rt, ConstResult(result.edge, rt.val), EFFECTS_TOTAL) - end - end mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv) mi === nothing && return nothing # try constant prop' @@ -719,7 +616,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul # if constant inference hits a cycle, just bail out isa(result, InferenceState) && return nothing add_backedge!(mi, sv) - return ConstCallResults(result, inf_result, inf_result.ipo_effects) + return Pair{Any,InferenceResult}(result, inf_result) end # if there's a possibility we could get a better result (hopefully without doing too much work) @@ -727,7 +624,15 @@ end function maybe_get_const_prop_profitable(interp::AbstractInterpreter, result::MethodCallResult, @nospecialize(f), arginfo::ArgInfo, match::MethodMatch, sv::InferenceState) + if !InferenceParams(interp).ipo_constant_propagation + add_remark!(interp, sv, "[constprop] Disabled by parameter") + return nothing + end method = match.method + if method.constprop == 0x02 + add_remark!(interp, sv, "[constprop] Disabled by method parameter") + return nothing + end force = force_const_prop(interp, f, method) force || const_prop_entry_heuristic(interp, result, sv) || return nothing nargs::Int = method.nargs @@ -738,8 +643,7 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter, result::Me return nothing end all_overridden = is_all_overridden(arginfo, sv) - if !force && !const_prop_function_heuristic(interp, f, arginfo, nargs, all_overridden, - sv.ipo_effects.nothrow === ALWAYS_TRUE, sv) + if !force && !const_prop_function_heuristic(interp, f, arginfo, nargs, all_overridden, sv) add_remark!(interp, sv, "[constprop] Disabled by function heuristic") return nothing end @@ -862,17 +766,13 @@ end function const_prop_function_heuristic( _::AbstractInterpreter, @nospecialize(f), (; argtypes)::ArgInfo, - nargs::Int, all_overridden::Bool, still_nothrow::Bool, _::InferenceState) + nargs::Int, all_overridden::Bool, _::InferenceState) if nargs > 1 if istopfunction(f, :getindex) || istopfunction(f, :setindex!) arrty = argtypes[2] # don't propagate constant index into indexing of non-constant array if arrty isa Type && arrty <: AbstractArray && !issingletontype(arrty) - # For static arrays, allow the constprop if we could possibly - # deduce nothrow as a result. - if !still_nothrow || ismutabletype(arrty) - return false - end + return false elseif arrty ⊑ Array return false end @@ -1132,7 +1032,6 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv:: if !isa(aft, Const) && !isa(aft, PartialOpaque) && (!isType(aftw) || has_free_typevars(aftw)) if !isconcretetype(aftw) || (aftw <: Builtin) add_remark!(interp, sv, "Core._apply_iterate called on a function of a non-concrete type") - tristate_merge!(sv, Effects()) # 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, false) @@ -1459,7 +1358,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn # end const_result = abstract_call_method_with_const_args(interp, result, singleton_type(ft′), arginfo, match, sv, false) if const_result !== nothing - (;rt, const_result) = const_result + rt, const_result = const_result end return CallMeta(from_interprocedural!(rt, sv, arginfo, sig), InvokeCallInfo(match, const_result)) end @@ -1486,12 +1385,9 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), elseif f === modifyfield! return abstract_modifyfield!(interp, argtypes, sv) end - rt = abstract_call_builtin(interp, f, arginfo, sv, max_methods) - tristate_merge!(sv, builtin_effects(f, argtypes, rt)) - return CallMeta(rt, false) + return CallMeta(abstract_call_builtin(interp, f, arginfo, sv, max_methods), false) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information - tristate_merge!(sv, Effects()) return CallMeta(Any, false) elseif f === Core.kwfunc if la == 2 @@ -1503,12 +1399,10 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), end end end - tristate_merge!(sv, Effects()) # TODO return CallMeta(Any, false) elseif f === TypeVar # Manually look through the definition of TypeVar to # make sure to be able to get `PartialTypeVar`s out. - tristate_merge!(sv, Effects()) # TODO (la < 2 || la > 4) && return CallMeta(Union{}, false) n = argtypes[2] ub_var = Const(Any) @@ -1521,17 +1415,14 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), end return CallMeta(typevar_tfunc(n, lb_var, ub_var), false) elseif f === UnionAll - tristate_merge!(sv, Effects()) # TODO return CallMeta(abstract_call_unionall(argtypes), false) elseif f === Tuple && la == 2 - tristate_merge!(sv, Effects()) # TODO aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) if !isconcretetype(ty) return CallMeta(Tuple, false) end elseif is_return_type(f) - tristate_merge!(sv, Effects()) # TODO return return_type_tfunc(interp, argtypes, sv) elseif la == 2 && istopfunction(f, :!) # handle Conditional propagation through !Bool @@ -1593,10 +1484,10 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::Part match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) const_result = nothing if !result.edgecycle - const_result = abstract_call_method_with_const_args(interp, result, nothing, + const_result = abstract_call_method_with_const_args(interp, result, closure, arginfo, match, sv, closure.isva) if const_result !== nothing - (;rt, const_result) = const_result + rt, const_result = const_result end end info = OpaqueClosureCallInfo(match, const_result) @@ -1702,13 +1593,13 @@ end function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) if isa(e, QuoteNode) - return Const(e.value) + return Const((e::QuoteNode).value) elseif isa(e, SSAValue) - return abstract_eval_ssavalue(e, sv) + return abstract_eval_ssavalue(e::SSAValue, sv.src) elseif isa(e, SlotNumber) || isa(e, Argument) return vtypes[slot_id(e)].typ elseif isa(e, GlobalRef) - return abstract_eval_global(e.mod, e.name, sv) + return abstract_eval_global(e.mod, e.name) end return Const(e) @@ -1760,25 +1651,18 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = callinfo.rt end elseif ehead === :new - t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv)) - is_nothrow = true - if isconcretedispatch(t) - fcount = fieldcount(t) + t = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))[1] + if isconcretetype(t) && !ismutabletype(t) nargs = length(e.args) - 1 - is_nothrow && (is_nothrow = fcount ≥ nargs) ats = Vector{Any}(undef, nargs) local anyrefine = false local allconst = true for i = 2:length(e.args) at = widenconditional(abstract_eval_value(interp, e.args[i], vtypes, sv)) ft = fieldtype(t, i-1) - is_nothrow && (is_nothrow = at ⊑ ft) at = tmeet(at, ft) if at === Bottom t = Bottom - tristate_merge!(sv, Effects( - ALWAYS_TRUE, # N.B depends on !ismutabletype(t) above - ALWAYS_TRUE, ALWAYS_FALSE, ALWAYS_TRUE)) @goto t_computed elseif !isa(at, Const) allconst = false @@ -1789,10 +1673,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end ats[i-1] = at end - # For now, don't allow: - # - Const/PartialStruct of mutables - # - partially initialized Const/PartialStruct - if !ismutabletype(t) && fcount == nargs + # For now, don't allow partially initialized Const/PartialStruct + if fieldcount(t) == nargs if allconst argvals = Vector{Any}(undef, nargs) for j in 1:nargs @@ -1803,33 +1685,21 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = PartialStruct(t, ats) end end - else - is_nothrow = false end - tristate_merge!(sv, Effects(EFFECTS_TOTAL, - consistent = !ismutabletype(t) ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)) elseif ehead === :splatnew - t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv)) - is_nothrow = false # TODO: More precision + t = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))[1] if length(e.args) == 2 && isconcretetype(t) && !ismutabletype(t) at = abstract_eval_value(interp, e.args[2], vtypes, sv) n = fieldcount(t) if isa(at, Const) && isa(at.val, Tuple) && n == length(at.val::Tuple) && let t = t, at = at; _all(i->getfield(at.val::Tuple, i) isa fieldtype(t, i), 1:n); end - is_nothrow = isexact && isconcretedispatch(t) t = Const(ccall(:jl_new_structt, Any, (Any, Any), t, at.val)) elseif isa(at, PartialStruct) && at ⊑ Tuple && n == length(at.fields::Vector{Any}) && let t = t, at = at; _all(i->(at.fields::Vector{Any})[i] ⊑ fieldtype(t, i), 1:n); end - is_nothrow = isexact && isconcretedispatch(t) t = PartialStruct(t, at.fields::Vector{Any}) end end - tristate_merge!(sv, Effects(EFFECTS_TOTAL, - consistent = ismutabletype(t) ? ALWAYS_FALSE : ALWAYS_TRUE, - nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)) elseif ehead === :new_opaque_closure - tristate_merge!(sv, Effects()) # TODO t = Union{} if length(e.args) >= 5 ea = e.args @@ -1858,29 +1728,13 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = Bottom end end - cconv = e.args[5] - if isa(cconv, QuoteNode) && isa(cconv.value, Tuple{Symbol, UInt8}) - effects = cconv.value[2] - effects = decode_effects_override(effects) - tristate_merge!(sv, Effects( - effects.consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - effects.effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - effects.nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - effects.terminates ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - )) - else - tristate_merge!(sv, Effects()) - end elseif ehead === :cfunction - tristate_merge!(sv, Effects()) t = e.args[1] isa(t, Type) || (t = Any) abstract_eval_cfunction(interp, e, vtypes, sv) elseif ehead === :method - tristate_merge!(sv, Effects()) t = (length(e.args) == 1) ? Any : Nothing elseif ehead === :copyast - tristate_merge!(sv, Effects()) t = abstract_eval_value(interp, e.args[1], vtypes, sv) if t isa Const && t.val isa Expr # `copyast` makes copies of Exprs @@ -1936,28 +1790,14 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end function abstract_eval_global(M::Module, s::Symbol) - if isdefined(M,s) - if isconst(M,s) - return Const(getfield(M,s)) - end + if isdefined(M,s) && isconst(M,s) + return Const(getfield(M,s)) end ty = ccall(:jl_binding_type, Any, (Any, Any), M, s) ty === nothing && return Any return ty end -function abstract_eval_global(M::Module, s::Symbol, frame::InferenceState) - ty = abstract_eval_global(M, s) - isa(ty, Const) && return ty - if isdefined(M,s) - tristate_merge!(frame, Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE)) - else - tristate_merge!(frame, Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE, nothrow=ALWAYS_FALSE)) - end - return ty -end - -abstract_eval_ssavalue(s::SSAValue, sv::InferenceState) = abstract_eval_ssavalue(s, sv.src) function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo) typ = (src.ssavaluetypes::Vector{Any})[s.id] if typ === NOT_FOUND @@ -2037,17 +1877,6 @@ function widenreturn(@nospecialize(rt), @nospecialize(bestguess), nslots::Int, s return widenconst(rt) end -function handle_control_backedge!(frame::InferenceState, from::Int, to::Int) - if from > to - def = frame.linfo.def - if isa(def, Method) && decode_effects_override(def.purity).terminates_locally - return nothing - end - tristate_merge!(frame, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) - end - return nothing -end - # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !frame.inferred @@ -2085,9 +1914,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) sn = slot_id(stmt.slot) changes[sn] = VarState(Bottom, true) elseif isa(stmt, GotoNode) - l = (stmt::GotoNode).label - handle_control_backedge!(frame, pc, l) - pc´ = l + pc´ = (stmt::GotoNode).label elseif isa(stmt, GotoIfNot) condx = stmt.cond condt = abstract_eval_value(interp, condx, changes, frame) @@ -2112,7 +1939,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) # constant conditions if condval === true elseif condval === false - handle_control_backedge!(frame, pc, l) pc´ = l else # general case @@ -2123,7 +1949,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) end newstate_else = stupdate!(states[l], changes_else) if newstate_else !== nothing - handle_control_backedge!(frame, pc, l) # add else branch to active IP list if l < frame.pc´´ frame.pc´´ = l @@ -2199,12 +2024,6 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) lhs = stmt.args[1] if isa(lhs, SlotNumber) changes = StateUpdate(lhs, VarState(t, false), changes, false) - elseif isa(lhs, GlobalRef) - tristate_merge!(frame, Effects(EFFECTS_TOTAL, - effect_free=ALWAYS_FALSE, - nothrow=TRISTATE_UNKNOWN)) - elseif !isa(lhs, SSAValue) - tristate_merge!(frame, Effects()) end elseif hd === :method stmt = stmt::Expr diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 17539f7621c74..ff9ffa5456458 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -59,9 +59,6 @@ mutable struct InferenceState inferred::Bool dont_work_on_me::Bool - # Inferred purity flags - ipo_effects::Effects - # The place to look up methods while working on this function. # In particular, we cache method lookup results for the same function to # fast path repeated queries. @@ -116,16 +113,6 @@ mutable struct InferenceState valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) - # TODO: Currently, any :inbounds declaration taints consistency, - # because we cannot be guaranteed whether or not boundschecks - # will be eliminated and if they are, we cannot be guaranteed - # that no undefined behavior will occur (the effects assumptions - # are stronger than the inbounds assumptions, since the latter - # requires dynamic reachability, while the former is global). - inbounds = inbounds_option() - inbounds_taints_consistency = !(inbounds === :on || (inbounds === :default && !any_inbounds(code))) - consistent = inbounds_taints_consistency ? TRISTATE_UNKNOWN : ALWAYS_TRUE - @assert cache === :no || cache === :local || cache === :global frame = new( params, result, linfo, @@ -139,8 +126,6 @@ mutable struct InferenceState Vector{InferenceState}(), # callers_in_cycle #=parent=#nothing, cache === :global, false, false, - Effects(consistent, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, - inbounds_taints_consistency), CachedMethodTable(method_table(interp)), interp) result.result = frame @@ -148,17 +133,6 @@ mutable struct InferenceState return frame end end -Effects(state::InferenceState) = state.ipo_effects - -function any_inbounds(code::Vector{Any}) - for i=1:length(code) - stmt = code[i] - if isa(stmt, Expr) && stmt.head === :inbounds - return true - end - end - return false -end function compute_trycatch(code::Vector{Any}, ip::BitSet) # The goal initially is to record the frame like this for the state at exit: diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 58f20b5ef2a0c..3c00b9faa6d0a 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -149,6 +149,16 @@ const IR_FLAG_THROW_BLOCK = 0x01 << 3 # thus be both pure and effect free. const IR_FLAG_EFFECT_FREE = 0x01 << 4 +# known to be always effect-free (in particular nothrow) +const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields] + +# known to be effect-free if the are nothrow +const _PURE_OR_ERROR_BUILTINS = [ + fieldtype, apply_type, isa, UnionAll, + getfield, arrayref, const_arrayref, arraysize, isdefined, Core.sizeof, + Core.kwfunc, Core.ifelse, Core._typevar, (<:), +] + const TOP_TUPLE = GlobalRef(Core, :tuple) ######### @@ -215,7 +225,7 @@ function stmt_effect_free(@nospecialize(stmt), @nospecialize(rt), src::Union{IRC M, s = argextype(args[2], src), argextype(args[3], src) return get_binding_type_effect_free(M, s) end - contains_is(_EFFECT_FREE_BUILTINS, f) || return false + contains_is(_PURE_OR_ERROR_BUILTINS, f) || return false rt === Bottom && return false return _builtin_nothrow(f, Any[argextype(args[i], src) for i = 2:length(args)], rt) elseif head === :new @@ -287,14 +297,12 @@ function alloc_array_ndims(name::Symbol) return nothing end -const FOREIGNCALL_ARG_START = 6 - function alloc_array_no_throw(args::Vector{Any}, ndims::Int, src::Union{IRCode,IncrementalCompact}) - length(args) ≥ ndims+FOREIGNCALL_ARG_START || return false - atype = instanceof_tfunc(argextype(args[FOREIGNCALL_ARG_START], src))[1] + length(args) ≥ ndims+6 || return false + atype = instanceof_tfunc(argextype(args[6], src))[1] dims = Csize_t[] for i in 1:ndims - dim = argextype(args[i+FOREIGNCALL_ARG_START], src) + dim = argextype(args[i+6], src) isa(dim, Const) || return false dimval = dim.val isa(dimval, Int) || return false @@ -304,9 +312,9 @@ function alloc_array_no_throw(args::Vector{Any}, ndims::Int, src::Union{IRCode,I end function new_array_no_throw(args::Vector{Any}, src::Union{IRCode,IncrementalCompact}) - length(args) ≥ FOREIGNCALL_ARG_START+1 || return false - atype = instanceof_tfunc(argextype(args[FOREIGNCALL_ARG_START], src))[1] - dims = argextype(args[FOREIGNCALL_ARG_START+1], src) + length(args) ≥ 7 || return false + atype = instanceof_tfunc(argextype(args[6], src))[1] + dims = argextype(args[7], src) isa(dims, Const) || return dims === Tuple{} dimsval = dims.val isa(dimsval, Tuple{Vararg{Int}}) || return false @@ -613,6 +621,21 @@ function slot2reg(ir::IRCode, ci::CodeInfo, sv::OptimizationState) return ir end +# whether `f` is pure for inference +function is_pure_intrinsic_infer(f::IntrinsicFunction) + return !(f === Intrinsics.pointerref || # this one is volatile + f === Intrinsics.pointerset || # this one is never effect-free + f === Intrinsics.llvmcall || # this one is never effect-free + f === Intrinsics.arraylen || # this one is volatile + f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps) + f === Intrinsics.have_fma || # this one depends on the runtime environment + f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime +end + +# whether `f` is effect free if nothrow +intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || + f === Intrinsics.have_fma || is_pure_intrinsic_infer(f) + ## Computing the cost of a function body # saturating sum (inputs are nonnegative), prevents overflow with typemax(Int) below diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index d3a322f7d44c2..a3faba0e76f5d 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -15,8 +15,6 @@ struct ResolvedInliningSpec # If the function being inlined is a single basic block we can use a # simpler inlining algorithm. This flag determines whether that's allowed linear_inline_eligible::Bool - # Effects of the call statement - effects::Effects end """ @@ -51,16 +49,11 @@ struct SomeCase SomeCase(val) = new(val) end -struct InvokeCase - invoke::MethodInstance - effects::Effects -end - struct InliningCase sig # ::Type item # Union{InliningTodo, MethodInstance, ConstantCase} function InliningCase(@nospecialize(sig), @nospecialize(item)) - @assert isa(item, Union{InliningTodo, InvokeCase, ConstantCase}) "invalid inlining item" + @assert isa(item, Union{InliningTodo, MethodInstance, ConstantCase}) "invalid inlining item" return new(sig, item) end end @@ -515,11 +508,9 @@ function ir_inline_unionsplit!(compact::IncrementalCompact, idx::Int, end if isa(case, InliningTodo) val = ir_inline_item!(compact, idx, argexprs′, linetable, case, boundscheck, todo_bbs) - elseif isa(case, InvokeCase) - effect_free = is_removable_if_unused(case.effects) + elseif isa(case, MethodInstance) val = insert_node_here!(compact, - NewInstruction(Expr(:invoke, case.invoke, argexprs′...), typ, nothing, - line, effect_free ? IR_FLAG_EFFECT_FREE : IR_FLAG_NULL, effect_free)) + NewInstruction(Expr(:invoke, case, argexprs′...), typ, line)) else case = case::ConstantCase val = case.val @@ -728,22 +719,16 @@ function rewrite_apply_exprargs!( return new_argtypes end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, match::MethodMatch, effects::Effects) +function compileable_specialization(et::Union{EdgeTracker, Nothing}, match::MethodMatch) mi = specialize_method(match; compilesig=true) mi !== nothing && et !== nothing && push!(et, mi::MethodInstance) - mi === nothing && return nothing - return InvokeCase(mi, effects) + return mi end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, linfo::MethodInstance, effects::Effects) +function compileable_specialization(et::Union{EdgeTracker, Nothing}, (; linfo)::InferenceResult) mi = specialize_method(linfo.def::Method, linfo.specTypes, linfo.sparam_vals; compilesig=true) mi !== nothing && et !== nothing && push!(et, mi::MethodInstance) - mi === nothing && return nothing - return InvokeCase(mi, effects) -end - -function compileable_specialization(et::Union{EdgeTracker, Nothing}, (; linfo)::InferenceResult, effects::Effects) - return compileable_specialization(et, linfo, effects) + return mi end function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) @@ -761,7 +746,6 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) else src = inferred_src end - effects = match.ipo_effects else code = get(state.mi_cache, mi, nothing) if code isa CodeInstance @@ -772,9 +756,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) else src = code.inferred end - effects = decode_effects(code.ipo_purity_bits) else - effects = Effects() src = code end end @@ -782,13 +764,13 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) # the duplicated check might have been done already within `analyze_method!`, but still # we need it here too since we may come here directly using a constant-prop' result if !state.params.inlining || is_stmt_noinline(flag) - return compileable_specialization(et, match, effects) + return compileable_specialization(et, match) end src = inlining_policy(state.interp, src, flag, mi, argtypes) if src === nothing - return compileable_specialization(et, match, effects) + return compileable_specialization(et, match) end if isa(src, IRCode) @@ -796,7 +778,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) end et !== nothing && push!(et, mi) - return InliningTodo(mi, src, effects) + return InliningTodo(mi, src) end function resolve_todo((; fully_covered, atype, cases, #=bbs=#)::UnionSplit, state::InliningState, flag::UInt8) @@ -838,9 +820,13 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, et = state.et + if !state.params.inlining || is_stmt_noinline(flag) + return compileable_specialization(et, match) + end + # See if there exists a specialization for this method signature mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance} - isa(mi, MethodInstance) || return compileable_specialization(et, match, Effects()) + isa(mi, MethodInstance) || return compileable_specialization(et, match) todo = InliningTodo(mi, match, argtypes) # If we don't have caches here, delay resolving this MethodInstance @@ -849,17 +835,17 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, return resolve_todo(todo, state, flag) end -function InliningTodo(mi::MethodInstance, ir::IRCode, effects::Effects) - return InliningTodo(mi, ResolvedInliningSpec(ir, linear_inline_eligible(ir), effects)) +function InliningTodo(mi::MethodInstance, ir::IRCode) + return InliningTodo(mi, ResolvedInliningSpec(ir, linear_inline_eligible(ir))) end -function InliningTodo(mi::MethodInstance, src::Union{CodeInfo, Array{UInt8, 1}}, effects::Effects) +function InliningTodo(mi::MethodInstance, src::Union{CodeInfo, Array{UInt8, 1}}) if !isa(src, CodeInfo) src = ccall(:jl_uncompress_ir, Any, (Any, Ptr{Cvoid}, Any), mi.def, C_NULL, src::Vector{UInt8})::CodeInfo end - @timeit "inline IR inflation" begin; - return InliningTodo(mi, inflate_ir(src, mi)::IRCode, effects) + @timeit "inline IR inflation" begin + return InliningTodo(mi, inflate_ir(src, mi)::IRCode) end end @@ -868,14 +854,10 @@ function handle_single_case!( @nospecialize(case), todo::Vector{Pair{Int, Any}}, params::OptimizationParams, isinvoke::Bool = false) if isa(case, ConstantCase) ir[SSAValue(idx)][:inst] = case.val - elseif isa(case, InvokeCase) - is_total(case.effects) && inline_const_if_inlineable!(ir[SSAValue(idx)]) && return nothing + elseif isa(case, MethodInstance) isinvoke && rewrite_invoke_exprargs!(stmt) stmt.head = :invoke - pushfirst!(stmt.args, case.invoke) - if is_removable_if_unused(case.effects) - ir[SSAValue(idx)][:flag] |= IR_FLAG_EFFECT_FREE - end + pushfirst!(stmt.args, case) elseif case === nothing # Do, well, nothing else @@ -1034,22 +1016,18 @@ function inline_invoke!( # TODO: We could union split out the signature check and continue on return nothing end + argtypes = invoke_rewrite(sig.argtypes) result = info.result - if isa(result, ConstResult) - item = const_result_item(result, state) - else - argtypes = invoke_rewrite(sig.argtypes) - if isa(result, InferenceResult) - (; mi) = item = InliningTodo(result, argtypes) - validate_sparams(mi.sparam_vals) || return nothing - if argtypes_to_type(argtypes) <: mi.def.sig - state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) - handle_single_case!(ir, idx, stmt, item, todo, state.params, true) - return nothing - end + if isa(result, InferenceResult) + (; mi) = item = InliningTodo(result, argtypes) + validate_sparams(mi.sparam_vals) || return nothing + if argtypes_to_type(argtypes) <: mi.def.sig + state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) + handle_single_case!(ir, idx, stmt, item, todo, state.params, true) + return nothing end - item = analyze_method!(match, argtypes, flag, state) end + item = analyze_method!(match, argtypes, flag, state) handle_single_case!(ir, idx, stmt, item, todo, state.params, true) return nothing end @@ -1126,10 +1104,10 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto length(info.results) == 1 || return nothing match = info.results[1]::MethodMatch match.fully_covers || return nothing - case = compileable_specialization(state.et, match, Effects()) + case = compileable_specialization(state.et, match) case === nothing && return nothing stmt.head = :invoke_modify - pushfirst!(stmt.args, case.invoke) + pushfirst!(stmt.args, case) ir.stmts[idx][:inst] = stmt end return nothing @@ -1245,18 +1223,12 @@ function handle_const_call!( for match in meth j += 1 result = results[j] - if isa(result, ConstResult) - case = const_result_item(result, state) - signature_union = Union{signature_union, result.mi.specTypes} - push!(cases, InliningCase(result.mi.specTypes, case)) - continue - elseif isa(result, InferenceResult) - signature_union = Union{signature_union, result.linfo.specTypes} - fully_covered &= handle_inf_result!(result, argtypes, flag, state, cases) - else - @assert result === nothing + if result === nothing signature_union = Union{signature_union, match.spec_types} fully_covered &= handle_match!(match, argtypes, flag, state, cases) + else + signature_union = Union{signature_union, result.linfo.specTypes} + fully_covered &= handle_const_result!(result, argtypes, flag, state, cases) end end end @@ -1264,7 +1236,7 @@ function handle_const_call!( # if the signature is fully covered and there is only one applicable method, # we can try to inline it even if the signature is not a dispatch tuple atype = argtypes_to_type(argtypes) - if length(cases) == 0 && length(results) == 1 && isa(results[1], InferenceResult) + if length(cases) == 0 && length(results) == 1 (; mi) = item = InliningTodo(results[1]::InferenceResult, argtypes) state.mi_cache !== nothing && (item = resolve_todo(item, state, flag)) validate_sparams(mi.sparam_vals) || return nothing @@ -1290,7 +1262,7 @@ function handle_match!( return true end -function handle_inf_result!( +function handle_const_result!( result::InferenceResult, argtypes::Vector{Any}, flag::UInt8, state::InliningState, cases::Vector{InliningCase}) (; mi) = item = InliningTodo(result, argtypes) @@ -1303,14 +1275,6 @@ function handle_inf_result!( return true end -function const_result_item(result::ConstResult, state::InliningState) - if !isdefined(result, :result) || !is_inlineable_constant(result.result) - return compileable_specialization(state.et, result.mi, EFFECTS_TOTAL) - else - return ConstantCase(quoted(result.result)) - end -end - function handle_cases!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(atype), cases::Vector{InliningCase}, fully_covered::Bool, todo::Vector{Pair{Int, Any}}, params::OptimizationParams) @@ -1350,7 +1314,6 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) # todo = (inline_idx, (isva, isinvoke, na), method, spvals, inline_linetable, inline_ir, lie) todo = Pair{Int, Any}[] et = state.et - for idx in 1:length(ir.stmts) simpleres = process_simple!(ir, idx, state, todo) simpleres === nothing && continue @@ -1377,11 +1340,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState) ir, idx, stmt, result, flag, sig, state, todo) else - if isa(result, ConstResult) - item = const_result_item(result, state) - else - item = analyze_method!(info.match, sig.argtypes, flag, state) - end + item = analyze_method!(info.match, sig.argtypes, flag, state) handle_single_case!(ir, idx, stmt, item, todo, state.params) end continue @@ -1439,7 +1398,6 @@ function early_inline_special_case( params::OptimizationParams) params.inlining || return nothing (; f, ft, argtypes) = sig - if isa(type, Const) # || isconstType(type) val = type.val is_inlineable_constant(val) || return nothing @@ -1449,7 +1407,7 @@ function early_inline_special_case( end elseif ispuretopfunction(f) || contains_is(_PURE_BUILTINS, f) return SomeCase(quoted(val)) - elseif contains_is(_EFFECT_FREE_BUILTINS, f) + elseif contains_is(_PURE_OR_ERROR_BUILTINS, f) if _builtin_nothrow(f, argtypes[2:end], type) return SomeCase(quoted(val)) end diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 1e98dda039040..9598d3e8cfa26 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -790,19 +790,4 @@ function show_ir(io::IO, code::Union{IRCode, CodeInfo}, config::IRShowConfig=def nothing end -tristate_letter(t::TriState) = t === ALWAYS_TRUE ? '+' : t === ALWAYS_FALSE ? '!' : '?' -tristate_color(t::TriState) = t === ALWAYS_TRUE ? :green : t === ALWAYS_FALSE ? :red : :orange - -function Base.show(io::IO, e::Core.Compiler.Effects) - print(io, "(") - printstyled(io, string(tristate_letter(e.consistent), 'c'); color=tristate_color(e.consistent)) - print(io, ',') - printstyled(io, string(tristate_letter(e.effect_free), 'e'); color=tristate_color(e.effect_free)) - print(io, ',') - printstyled(io, string(tristate_letter(e.nothrow), 'n'); color=tristate_color(e.nothrow)) - print(io, ',') - printstyled(io, string(tristate_letter(e.terminates), 't'); color=tristate_color(e.terminates)) - print(io, ')') -end - @specialize diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index e3f69b2c43e54..ca8c7d0d27d56 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -47,13 +47,6 @@ function nmatches(info::UnionSplitInfo) return n end -struct ConstResult - mi::MethodInstance - result - ConstResult(mi::MethodInstance) = new(mi) - ConstResult(mi::MethodInstance, @nospecialize val) = new(mi, val) -end - """ info::ConstCallInfo @@ -63,7 +56,7 @@ the inference results with constant information `info.results::Vector{Union{Noth """ struct ConstCallInfo call::Union{MethodMatchInfo,UnionSplitInfo} - results::Vector{Union{Nothing,InferenceResult,ConstResult}} + results::Vector{Union{Nothing,InferenceResult}} end """ @@ -129,7 +122,7 @@ Optionally keeps `info.result::InferenceResult` that keeps constant information. """ struct InvokeCallInfo match::MethodMatch - result::Union{Nothing,InferenceResult,ConstResult} + result::Union{Nothing,InferenceResult} end """ @@ -141,7 +134,7 @@ Optionally keeps `info.result::InferenceResult` that keeps constant information. """ struct OpaqueClosureCallInfo match::MethodMatch - result::Union{Nothing,InferenceResult,ConstResult} + result::Union{Nothing,InferenceResult} end """ diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 2238d43d65b27..d335995558d8f 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -701,7 +701,7 @@ function try_compute_fieldidx(typ::DataType, @nospecialize(field)) return field end -function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing, Type{Bool}} +function getfield_nothrow(argtypes::Vector{Any}) if length(argtypes) == 2 boundscheck = Bool elseif length(argtypes) == 3 @@ -712,21 +712,11 @@ function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing, Ty elseif length(argtypes) == 4 boundscheck = argtypes[4] else - return nothing - end - widenconst(boundscheck) !== Bool && return nothing - boundscheck = widenconditional(boundscheck) - if isa(boundscheck, Const) - return boundscheck.val - else - return Bool + return false end -end - -function getfield_nothrow(argtypes::Vector{Any}) - boundscheck = getfield_boundscheck(argtypes) - boundscheck === nothing && return false - return getfield_nothrow(argtypes[1], argtypes[2], !(boundscheck === false)) + widenconst(boundscheck) !== Bool && return false + bounds_check_disabled = isa(boundscheck, Const) && boundscheck.val === false + return getfield_nothrow(argtypes[1], argtypes[2], !bounds_check_disabled) end function getfield_nothrow(@nospecialize(s00), @nospecialize(name), boundscheck::Bool) # If we don't have boundscheck and don't know the field, don't even bother @@ -1714,85 +1704,6 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ return false end -# known to be always effect-free (in particular nothrow) -const _PURE_BUILTINS = Any[tuple, svec, ===, typeof, nfields] - -# known to be effect-free (but not necessarily nothrow) -const _EFFECT_FREE_BUILTINS = [ - fieldtype, apply_type, isa, UnionAll, - getfield, arrayref, const_arrayref, isdefined, Core.sizeof, - Core.kwfunc, Core.ifelse, Core._typevar, (<:), - typeassert, throw, arraysize -] - -const _CONSISTENT_BUILTINS = Any[ - tuple, # tuple is immutable, thus tuples of egal arguments are egal - ===, - typeof, - nfields, - fieldtype, - apply_type, - isa, - UnionAll, - Core.sizeof, - Core.kwfunc, - Core.ifelse, - (<:), - typeassert, - throw -] - -const _SPECIAL_BUILTINS = Any[ - Core._apply_iterate -] - -function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt) - if isa(f, IntrinsicFunction) - return intrinsic_effects(f, argtypes) - end - - @assert !contains_is(_SPECIAL_BUILTINS, f) - - nothrow = false - if (f === Core.getfield || f === Core.isdefined) && length(argtypes) >= 3 - # consistent if the argtype is immutable - if isvarargtype(argtypes[2]) - return Effects(Effects(), effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE) - end - s = widenconst(argtypes[2]) - if isType(s) || !isa(s, DataType) || isabstracttype(s) - return Effects(Effects(), effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE) - end - s = s::DataType - ipo_consistent = !ismutabletype(s) - nothrow = false - if f === Core.getfield && !isvarargtype(argtypes[end]) && - getfield_boundscheck(argtypes[2:end]) !== true - # If we cannot independently prove inboundsness, taint consistency. - # The inbounds-ness assertion requires dynamic reachability, while - # :consistent needs to be true for all input values. - # N.B. We do not taint for `--check-bounds=no` here -that happens in - # InferenceState. - nothrow = getfield_nothrow(argtypes[2], argtypes[3], true) - ipo_consistent &= nothrow - end - else - ipo_consistent = contains_is(_CONSISTENT_BUILTINS, f) - end - # If we computed nothrow above for getfield, no need to repeat the procedure here - if !nothrow - nothrow = isvarargtype(argtypes[end]) ? false : - builtin_nothrow(f, argtypes[2:end], rt) - end - effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) - - return Effects( - ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, - effect_free ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - ALWAYS_TRUE) -end - function builtin_nothrow(@nospecialize(f), argtypes::Array{Any, 1}, @nospecialize(rt)) rt === Bottom && return false contains_is(_PURE_BUILTINS, f) && return true @@ -1935,45 +1846,6 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Array{Any, 1}) return true end -# whether `f` is pure for inference -function is_pure_intrinsic_infer(f::IntrinsicFunction) - return !(f === Intrinsics.pointerref || # this one is volatile - f === Intrinsics.pointerset || # this one is never effect-free - f === Intrinsics.llvmcall || # this one is never effect-free - f === Intrinsics.arraylen || # this one is volatile - f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps) - f === Intrinsics.have_fma || # this one depends on the runtime environment - f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime -end - -# whether `f` is effect free if nothrow -intrinsic_effect_free_if_nothrow(f) = f === Intrinsics.pointerref || - f === Intrinsics.have_fma || is_pure_intrinsic_infer(f) - -function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) - if f === Intrinsics.llvmcall - # llvmcall can do arbitrary things - return Effects() - end - - ipo_consistent = !(f === Intrinsics.pointerref || # this one is volatile - f === Intrinsics.arraylen || # this one is volatile - f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps) - f === Intrinsics.have_fma || # this one depends on the runtime environment - f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime - - effect_free = !(f === Intrinsics.pointerset) - - nothrow = isvarargtype(argtypes[end]) ? false : - intrinsic_nothrow(f, argtypes[2:end]) - - return Effects( - ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, - effect_free ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN, - ALWAYS_TRUE) -end - # TODO: this function is a very buggy and poor model of the return_type function # 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 diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 03ba383de4f61..749462b25fa0b 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -277,8 +277,8 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) return true end -function CodeInstance( - result::InferenceResult, @nospecialize(inferred_result), valid_worlds::WorldRange) +function CodeInstance(result::InferenceResult, @nospecialize(inferred_result), + valid_worlds::WorldRange, relocatability::UInt8) local const_flags::Int32 result_type = result.result @assert !(result_type isa LimitedAccuracy) @@ -308,13 +308,9 @@ function CodeInstance( const_flags = 0x00 end end - relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0) return CodeInstance(result.linfo, widenconst(result_type), 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), - relocatability) + const_flags, first(valid_worlds), last(valid_worlds), relocatability) end # For the NativeInterpreter, we don't need to do an actual cache query to know @@ -388,7 +384,8 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) # TODO: also don't store inferred code if we've previously decided to interpret this function if !already_inferred inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src) - code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) + relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0) + code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds, relocatability) end unlock_mi_inference(interp, linfo) nothing @@ -416,16 +413,6 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) return typ end -function rt_adjust_effects(@nospecialize(rt), ipo_effects::Effects) - # Always throwing an error counts or never returning both count as consistent, - # but we don't currently model idempontency using dataflow, so we don't notice. - # Fix that up here to improve precision. - if !ipo_effects.inbounds_taints_consistency && rt === Union{} - return Effects(ipo_effects, consistent=ALWAYS_TRUE) - end - return ipo_effects -end - # inference completed on `me` # update the MethodInstance function finish(me::InferenceState, interp::AbstractInterpreter) @@ -484,7 +471,6 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end me.result.valid_worlds = me.valid_worlds me.result.result = me.bestguess - me.result.ipo_effects = rt_adjust_effects(me.bestguess, me.ipo_effects) validate_code_in_debug_mode(me.linfo, me.src, "inferred") nothing end @@ -725,13 +711,9 @@ function merge_call_chain!(parent::InferenceState, ancestor::InferenceState, chi # then add all backedges of parent <- parent.parent # and merge all of the callers into ancestor.callers_in_cycle # and ensure that walking the parent list will get the same result (DAG) from everywhere - # Also taint the termination effect, because we can no longer guarantee the absence - # of recursion. - tristate_merge!(parent, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) while true add_cycle_backedge!(child, parent, parent.currpc) union_caller_cycle!(ancestor, child) - tristate_merge!(child, Effects(EFFECTS_TOTAL, terminates=TRISTATE_UNKNOWN)) child = parent child === ancestor && break parent = child.parent::InferenceState @@ -787,16 +769,6 @@ end generating_sysimg() = ccall(:jl_generating_output, Cint, ()) != 0 && JLOptions().incremental == 0 -function tristate_merge!(caller::InferenceState, callee::Effects) - caller.ipo_effects = tristate_merge(caller.ipo_effects, callee) -end - -function tristate_merge!(caller::InferenceState, callee::InferenceState) - tristate_merge!(caller, Effects(callee)) -end - -ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) - # compute (and cache) an inferred AST and return the current best estimate of the result type function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, caller::InferenceState) mi = specialize_method(method, atype, sparams)::MethodInstance @@ -807,7 +779,6 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # but the inlinear will request to use it, we re-infer it here and keep it around in the local cache cache = :local else - effects = ipo_effects(code) update_valid_age!(caller, WorldRange(min_world(code), max_world(code))) rettype = code.rettype if isdefined(code, :rettype_const) @@ -815,23 +786,23 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # the second subtyping conditions are necessary to distinguish usual cases # from rare cases when `Const` wrapped those extended lattice type objects if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype) - return PartialStruct(rettype, rettype_const), mi, effects + return PartialStruct(rettype, rettype_const), mi elseif isa(rettype_const, PartialOpaque) && rettype <: Core.OpaqueClosure - return rettype_const, mi, effects + return rettype_const, mi elseif isa(rettype_const, InterConditional) && !(InterConditional <: rettype) - return rettype_const, mi, effects + return rettype_const, mi else - return Const(rettype_const), mi, effects + return Const(rettype_const), mi end else - return rettype, mi, effects + return rettype, mi end end else cache = :global # cache edge targets by default end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_sysimg() - return Any, nothing, Effects() + return Any, nothing end if !caller.cached && caller.parent === nothing # this caller exists to return to the user @@ -848,22 +819,22 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize if frame === nothing # can't get the source for this, so we know nothing unlock_mi_inference(interp, mi) - return Any, nothing, Effects() + return Any, nothing end if caller.cached || caller.parent !== nothing # don't involve uncached functions in cycle resolution frame.parent = caller end typeinf(interp, frame) update_valid_age!(frame, caller) - return frame.bestguess, frame.inferred ? mi : nothing, rt_adjust_effects(frame.bestguess, Effects(frame)) + return frame.bestguess, frame.inferred ? mi : nothing elseif frame === true # unresolvable cycle - return Any, nothing, Effects() + return Any, nothing end # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(frame, caller) - return frame.bestguess, nothing, rt_adjust_effects(frame.bestguess, Effects(frame)) + return frame.bestguess, nothing end #### entry points for inferring a MethodInstance given a type signature #### diff --git a/base/compiler/types.jl b/base/compiler/types.jl index cebb560a2010b..e5894ab3d3f89 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -22,95 +22,6 @@ struct ArgInfo argtypes::Vector{Any} end -struct TriState; state::UInt8; end -const ALWAYS_FALSE = TriState(0x00) -const ALWAYS_TRUE = TriState(0x01) -const TRISTATE_UNKNOWN = TriState(0x02) - -function tristate_merge(old::TriState, new::TriState) - (old === ALWAYS_FALSE || new === ALWAYS_FALSE) && return ALWAYS_FALSE - old === TRISTATE_UNKNOWN && return old - return new -end - -struct Effects - consistent::TriState - effect_free::TriState - nothrow::TriState - terminates::TriState - # This effect is currently only tracked in inference and modified - # :consistent before caching. We may want to track it in the future. - inbounds_taints_consistency::Bool -end -Effects(consistent::TriState, effect_free::TriState, nothrow::TriState, terminates::TriState) = - Effects(consistent, effect_free, nothrow, terminates, false) -Effects() = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN) - -Effects(e::Effects; consistent::TriState=e.consistent, - effect_free::TriState = e.effect_free, nothrow::TriState=e.nothrow, terminates::TriState=e.terminates, - inbounds_taints_consistency::Bool = e.inbounds_taints_consistency) = - Effects(consistent, effect_free, nothrow, terminates, inbounds_taints_consistency) - -is_total_or_error(effects::Effects) = - effects.consistent === ALWAYS_TRUE && effects.effect_free === ALWAYS_TRUE && - effects.terminates === ALWAYS_TRUE - -is_total(effects::Effects) = - is_total_or_error(effects) && effects.nothrow === ALWAYS_TRUE - -is_removable_if_unused(effects::Effects) = - effects.effect_free === ALWAYS_TRUE && - effects.terminates === ALWAYS_TRUE && - effects.nothrow === ALWAYS_TRUE - -const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE) - -encode_effects(e::Effects) = e.consistent.state | (e.effect_free.state << 2) | (e.nothrow.state << 4) | (e.terminates.state << 6) -decode_effects(e::UInt8) = - Effects(TriState(e & 0x3), - TriState((e >> 2) & 0x3), - TriState((e >> 4) & 0x3), - TriState((e >> 6) & 0x3), false) - -function tristate_merge(old::Effects, new::Effects) - Effects(tristate_merge( - old.consistent, new.consistent), - tristate_merge( - old.effect_free, new.effect_free), - tristate_merge( - old.nothrow, new.nothrow), - tristate_merge( - old.terminates, new.terminates), - old.inbounds_taints_consistency || - new.inbounds_taints_consistency) -end - -struct EffectsOverride - consistent::Bool - effect_free::Bool - nothrow::Bool - terminates::Bool - terminates_locally::Bool -end - -function encode_effects_override(eo::EffectsOverride) - e = 0x00 - eo.consistent && (e |= 0x01) - eo.effect_free && (e |= 0x02) - eo.nothrow && (e |= 0x04) - eo.terminates && (e |= 0x08) - eo.terminates_locally && (e |= 0x10) - e -end - -decode_effects_override(e::UInt8) = - EffectsOverride( - (e & 0x01) != 0x00, - (e & 0x02) != 0x00, - (e & 0x04) != 0x00, - (e & 0x08) != 0x00, - (e & 0x10) != 0x00) - """ InferenceResult @@ -123,13 +34,11 @@ mutable struct InferenceResult result # ::Type, or InferenceState if WIP src #::Union{CodeInfo, OptimizationState, Nothing} # if inferred copy is available valid_worlds::WorldRange # if inference and optimization is finished - ipo_effects::Effects # if inference is finished - effects::Effects # if optimization is finished function InferenceResult(linfo::MethodInstance, arginfo#=::Union{Nothing,Tuple{ArgInfo,InferenceState}}=# = nothing, va_override::Bool = false) argtypes, overridden_by_const = matching_cache_argtypes(linfo, arginfo, va_override) - return new(linfo, argtypes, overridden_by_const, Any, nothing, WorldRange(), Effects(), Effects()) + return new(linfo, argtypes, overridden_by_const, Any, nothing, WorldRange()) end end diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 77ee422b6ffcd..bcde5d894159c 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -23,7 +23,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}( :copyast => 1:1, :meta => 0:typemax(Int), :global => 1:1, - :foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects), args..., roots... + :foreigncall => 5:typemax(Int), # name, RT, AT, nreq, cconv, args..., roots... :cfunction => 5:5, :isdefined => 1:1, :code_coverage_effect => 0:0, diff --git a/base/errorshow.jl b/base/errorshow.jl index eaa294a8f7a46..8b8ec532355a8 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -170,10 +170,6 @@ function showerror(io::IO, ex::InexactError) Experimental.show_error_hints(io, ex) end -function showerror(io::IO, ex::CanonicalIndexError) - print(io, "CanonicalIndexError: ", ex.func, " not defined for ", ex.type) -end - typesof(@nospecialize args...) = Tuple{Any[ Core.Typeof(args[i]) for i in 1:length(args) ]...} function print_with_compare(io::IO, @nospecialize(a::DataType), @nospecialize(b::DataType), color::Symbol) diff --git a/base/expr.jl b/base/expr.jl index 38e89d284c989..7719eff3b334b 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -371,188 +371,6 @@ macro constprop(setting, ex) throw(ArgumentError("@constprop $setting not supported")) end -""" - @assume_effects setting... ex - @assume_effects(setting..., ex) - -`@assume_effects` overrides the compiler's effect modeling for the given method. -`ex` must be a method definition or `@ccall` expression. - -!!! warning - Improper use of this macro causes undefined behavior (including crashes, - incorrect answers, or other hard to track bugs). Use with care and only if - absolutely required. - -In general, each `setting` value makes an assertion about the behavior of the -function, without requiring the compiler to prove that this behavior is indeed -true. These assertions are made for all world ages. It is thus advisable to limit -the use of generic functions that may later be extended to invalidate the -assumption (which would cause undefined behavior). - -The following `setting`s are supported. -- `:consistent` -- `:effect_free` -- `:nothrow` -- `:terminates_globally` -- `:terminates_locally` -- `:total` - ---- -# `:consistent` - -The `:consistent` setting asserts that for egal inputs: -- The manner of termination (return value, exception, non-termination) will always be the same. -- If the method returns, the results will always be egal. - -!!! note - This in particular implies that the return value of the method must be - immutable. Multiple allocations of mutable objects (even with identical - contents) are not egal. - -!!! note - The `:consistent`-cy assertion is made world-age wise. More formally, write - ``fᵢ`` for the evaluation of ``f`` in world-age ``i``, then we require: - ```math - ∀ i, x, y: x ≡ y → fᵢ(x) ≡ fᵢ(y) - ``` - However, for two world ages ``i``, ``j`` s.t. ``i ≠ j``, we may have ``fᵢ(x) ≢ fⱼ(y)``. - - A further implication is that `:consistent` functions may not make their - return value dependent on the state of the heap or any other global state - that is not constant for a given world age. - -!!! note - The `:consistent`-cy includes all legal rewrites performed by the optimizer. - For example, floating-point fastmath operations are not considered `:consistent`, - because the optimizer may rewrite them causing the output to not be `:consistent`, - even for the same world age (e.g. because one ran in the interpreter, while - the other was optimized). - -!!! note - If `:consistent` functions terminate by throwing an exception, that exception - itself is not required to meet the egality requirement specified above. - ---- -# `:effect_free` - -The `:effect_free` setting asserts that the method is free of externally semantically -visible side effects. The following is an incomplete list of externally semantically -visible side effects: -- Changing the value of a global variable. -- Mutating the heap (e.g. an array or mutable value), except as noted below -- Changing the method table (e.g. through calls to eval) -- File/Network/etc. I/O -- Task switching - -However, the following are explicitly not semantically visible, even if they -may be observable: -- Memory allocations (both mutable and immutable) -- Elapsed time -- Garbage collection -- Heap mutations of objects whose lifetime does not exceed the method (i.e. - were allocated in the method and do not escape). -- The returned value (which is externally visible, but not a side effect) - -The rule of thumb here is that an externally visible side effect is anything -that would affect the execution of the remainder of the program if the function -were not executed. - -!!! note - The `:effect_free` assertion is made both for the method itself and any code - that is executed by the method. Keep in mind that the assertion must be - valid for all world ages and limit use of this assertion accordingly. - ---- -# `:nothrow` - -The `:nothrow` settings asserts that this method does not terminate abnormally -(i.e. will either always return a value or never return). - -!!! note - It is permissible for `:nothrow` annotated methods to make use of exception - handling internally as long as the exception is not rethrown out of the - method itself. - -!!! note - `MethodErrors` and similar exceptions count as abnormal termination. - ---- -# `:terminates_globally` - -The `:terminates_globally` settings asserts that this method will eventually terminate -(either normally or abnormally), i.e. does not loop indefinitely. - -!!! note - This `:terminates_globally` assertion covers any other methods called by the annotated method. - -!!! note - The compiler will consider this a strong indication that the method will - terminate relatively *quickly* and may (if otherwise legal), call this - method at compile time. I.e. it is a bad idea to annotate this setting - on a method that *technically*, but not *practically*, terminates. - ---- -# `:terminates_locally` - -The `:terminates_locally` setting is like `:terminates_globally`, except that it only -applies to syntactic control flow *within* the annotated method. It is thus -a much weaker (and thus safer) assertion that allows for the possibility of -non-termination if the method calls some other method that does not terminate. - -!!! note - `:terminates_globally` implies `:terminates_locally`. - ---- -# `:total` - -This `setting` combines the following other assertions: -- `:consistent` -- `:effect_free` -- `:nothrow` -- `:terminates_globally` -and is a convenient shortcut. - -!!! note - `@assume_effects :total` is similar to `@Base.pure` with the primary - distinction that the `:consistent`-cy requirement applies world-age wise rather - than globally as described above. However, in particular, a method annotated - `@Base.pure` is always `:total`. -""" -macro assume_effects(args...) - (consistent, effect_free, nothrow, terminates_globally, terminates_locally) = - (false, false, false, false, false, false) - for setting in args[1:end-1] - if isa(setting, QuoteNode) - setting = setting.value - end - if setting === :consistent - consistent = true - elseif setting === :effect_free - effect_free = true - elseif setting === :nothrow - nothrow = true - elseif setting === :terminates_globally - terminates_globally = true - elseif setting === :terminates_locally - terminates_locally = true - elseif setting === :total - consistent = effect_free = nothrow = terminates_globally = true - else - throw(ArgumentError("@assume_effects $setting not supported")) - end - end - ex = args[end] - isa(ex, Expr) || throw(ArgumentError("Bad expression `$ex` in @constprop [settings] ex")) - if ex.head === :macrocall && ex.args[1] == Symbol("@ccall") - ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) - insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride( - consistent, effect_free, nothrow, terminates_globally, terminates_locally - ))) - return esc(ex) - end - return esc(pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally)) -end - """ @propagate_inbounds diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index d1164005d3e44..3963927f7e717 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -419,8 +419,8 @@ fma_llvm(x::Float64, y::Float64, z::Float64) = fma_float(x, y, z) # Disable LLVM's fma if it is incorrect, e.g. because LLVM falls back # onto a broken system libm; if so, use a software emulated fma -@assume_effects :consistent fma(x::Float32, y::Float32, z::Float32) = Core.Intrinsics.have_fma(Float32) ? fma_llvm(x,y,z) : fma_emulated(x,y,z) -@assume_effects :consistent fma(x::Float64, y::Float64, z::Float64) = Core.Intrinsics.have_fma(Float64) ? fma_llvm(x,y,z) : fma_emulated(x,y,z) +fma(x::Float32, y::Float32, z::Float32) = Core.Intrinsics.have_fma(Float32) ? fma_llvm(x,y,z) : fma_emulated(x,y,z) +fma(x::Float64, y::Float64, z::Float64) = Core.Intrinsics.have_fma(Float64) ? fma_llvm(x,y,z) : fma_emulated(x,y,z) function fma(a::Float16, b::Float16, c::Float16) Float16(muladd(Float32(a), Float32(b), Float32(c))) #don't use fma if the hardware doesn't have it. diff --git a/base/math.jl b/base/math.jl index af86c11c01b26..319d96246c55b 100644 --- a/base/math.jl +++ b/base/math.jl @@ -18,7 +18,7 @@ export sin, cos, sincos, tan, sinh, cosh, tanh, asin, acos, atan, import .Base: log, exp, sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, asinh, acosh, atanh, sqrt, log2, log10, max, min, minmax, ^, exp2, muladd, rem, - exp10, expm1, log1p, @constprop, @assume_effects + exp10, expm1, log1p, @constprop using .Base: sign_mask, exponent_mask, exponent_one, exponent_half, uinttype, significand_mask, @@ -1033,7 +1033,7 @@ end return pow_body(x, n) end -@assume_effects :terminates_locally @noinline function pow_body(x::Float64, n::Integer) +@noinline function pow_body(x::Float64, n::Integer) y = 1.0 xnlo = ynlo = 0.0 if n < 0 diff --git a/base/meta.jl b/base/meta.jl index fcf66a7a787b2..2ba9baec9a443 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -393,7 +393,7 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any}, elseif i == 4 @assert isa(x.args[4], Int) elseif i == 5 - @assert isa((x.args[5]::QuoteNode).value, Union{Symbol, Tuple{Symbol, UInt8}}) + @assert isa((x.args[5]::QuoteNode).value, Symbol) else x.args[i] = _partially_inline!(x.args[i], slot_replacements, type_signature, static_param_values, diff --git a/base/show.jl b/base/show.jl index f52023912b786..5d8899ee7a4c9 100644 --- a/base/show.jl +++ b/base/show.jl @@ -2486,9 +2486,7 @@ module IRShow const Compiler = Core.Compiler using Core.IR import ..Base - import .Compiler: IRCode, ReturnNode, GotoIfNot, CFG, scan_ssa_use!, Argument, - isexpr, compute_basic_blocks, block_for_inst, - TriState, Effects, ALWAYS_TRUE, ALWAYS_FALSE + import .Compiler: IRCode, ReturnNode, GotoIfNot, CFG, scan_ssa_use!, Argument, isexpr, compute_basic_blocks, block_for_inst Base.getindex(r::Compiler.StmtRange, ind::Integer) = Compiler.getindex(r, ind) Base.size(r::Compiler.StmtRange) = Compiler.size(r) Base.first(r::Compiler.StmtRange) = Compiler.first(r) diff --git a/base/special/rem_pio2.jl b/base/special/rem_pio2.jl index 4ec9945885e7e..7242eb8f17b69 100644 --- a/base/special/rem_pio2.jl +++ b/base/special/rem_pio2.jl @@ -23,7 +23,7 @@ # @printf "0x%016x,\n" k # I -= k # end -const INV_2PI = ( +const INV_2PI = UInt64[ 0x28be_60db_9391_054a, 0x7f09_d5f4_7d4d_3770, 0x36d8_a566_4f10_e410, @@ -42,7 +42,7 @@ const INV_2PI = ( 0x5d49_eeb1_faf9_7c5e, 0xcf41_ce7d_e294_a4ba, 0x9afe_d7ec_47e3_5742, - 0x1580_cc11_bf1e_daea) + 0x1580_cc11_bf1e_daea] @inline function cody_waite_2c_pio2(x::Float64, fn, n) pio2_1 = 1.57079632673412561417e+00 diff --git a/base/strings/string.jl b/base/strings/string.jl index c37e36594119e..c818e2e1844fb 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -71,9 +71,7 @@ function unsafe_string(p::Union{Ptr{UInt8},Ptr{Int8}}) ccall(:jl_cstr_to_string, Ref{String}, (Ptr{UInt8},), p) end -# This is @assume_effects :effect_free :nothrow :terminates_globally @ccall jl_alloc_string(n::Csize_t)::Ref{String}, -# but the macro is not available at this time in bootstrap, so we write it manually. -@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0xe)), :(convert(Csize_t, n)))) +_string_n(n::Integer) = ccall(:jl_alloc_string, Ref{String}, (Csize_t,), n) """ String(s::AbstractString) @@ -97,7 +95,7 @@ String(s::CodeUnits{UInt8,String}) = s.s pointer(s::String) = unsafe_convert(Ptr{UInt8}, s) pointer(s::String, i::Integer) = pointer(s) + Int(i)::Int - 1 -ncodeunits(s::String) = Core.sizeof(s) +@pure ncodeunits(s::String) = Core.sizeof(s) codeunit(s::String) = UInt8 @inline function codeunit(s::String, i::Integer) diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 93d0547098706..80280997b30d1 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -285,7 +285,6 @@ Base.@simd Base.@polly Base.@generated Base.@pure -Base.@assume_effects Base.@deprecate ``` diff --git a/doc/src/manual/strings.md b/doc/src/manual/strings.md index ee2b3f9d71d54..5628c4fa96d81 100644 --- a/doc/src/manual/strings.md +++ b/doc/src/manual/strings.md @@ -1088,7 +1088,7 @@ julia> x[1] 0x31 julia> x[1] = 0x32 -ERROR: CanonicalIndexError: setindex! not defined for Base.CodeUnits{UInt8, String} +ERROR: setindex! not defined for Base.CodeUnits{UInt8, String} [...] julia> Vector{UInt8}(x) diff --git a/src/ast.c b/src/ast.c index 5dfd2107d6e3e..ef8fff7e39e7a 100644 --- a/src/ast.c +++ b/src/ast.c @@ -82,7 +82,6 @@ JL_DLLEXPORT jl_sym_t *jl_propagate_inbounds_sym; JL_DLLEXPORT jl_sym_t *jl_specialize_sym; JL_DLLEXPORT jl_sym_t *jl_aggressive_constprop_sym; JL_DLLEXPORT jl_sym_t *jl_no_constprop_sym; -JL_DLLEXPORT jl_sym_t *jl_purity_sym; JL_DLLEXPORT jl_sym_t *jl_nospecialize_sym; JL_DLLEXPORT jl_sym_t *jl_macrocall_sym; JL_DLLEXPORT jl_sym_t *jl_colon_sym; @@ -331,7 +330,6 @@ void jl_init_common_symbols(void) jl_propagate_inbounds_sym = jl_symbol("propagate_inbounds"); jl_aggressive_constprop_sym = jl_symbol("aggressive_constprop"); jl_no_constprop_sym = jl_symbol("no_constprop"); - jl_purity_sym = jl_symbol("purity"); jl_isdefined_sym = jl_symbol("isdefined"); jl_nospecialize_sym = jl_symbol("nospecialize"); jl_specialize_sym = jl_symbol("specialize"); diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 7b11813e7a58b..c7027f1b67f9e 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -29,7 +29,6 @@ DECLARE_BUILTIN(arrayref); DECLARE_BUILTIN(arrayset); DECLARE_BUILTIN(arraysize); DECLARE_BUILTIN(_call_in_world); -DECLARE_BUILTIN(_call_in_world_total); DECLARE_BUILTIN(_call_latest); DECLARE_BUILTIN(replacefield); DECLARE_BUILTIN(const_arrayref); diff --git a/src/builtins.c b/src/builtins.c index ca2f56adaf6d8..e78034446d19a 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -790,31 +790,6 @@ JL_CALLABLE(jl_f__call_in_world) return ret; } -JL_CALLABLE(jl_f__call_in_world_total) -{ - JL_NARGSV(_call_in_world_total, 2); - JL_TYPECHK(_apply_in_world, ulong, args[0]); - jl_task_t *ct = jl_current_task; - int last_in = ct->ptls->in_pure_callback; - jl_value_t *ret = NULL; - size_t last_age = ct->world_age; - JL_TRY { - ct->ptls->in_pure_callback = 1; - size_t world = jl_unbox_ulong(args[0]); - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - if (ct->world_age > world) - ct->world_age = world; - ret = jl_apply(&args[1], nargs - 1); - ct->world_age = last_age; - ct->ptls->in_pure_callback = last_in; - } - JL_CATCH { - ct->ptls->in_pure_callback = last_in; - jl_rethrow(); - } - return ret; -} - // tuples --------------------------------------------------------------------- JL_CALLABLE(jl_f_tuple) @@ -1907,7 +1882,6 @@ void jl_init_primitives(void) JL_GC_DISABLED add_builtin_func("_apply_pure", jl_f__apply_pure); add_builtin_func("_call_latest", jl_f__call_latest); add_builtin_func("_call_in_world", jl_f__call_in_world); - add_builtin_func("_call_in_world_total", jl_f__call_in_world_total); add_builtin_func("_typevar", jl_f__typevar); add_builtin_func("_structtype", jl_f__structtype); add_builtin_func("_abstracttype", jl_f__abstracttype); diff --git a/src/ccall.cpp b/src/ccall.cpp index 332c057afa5c4..915e976a03c0d 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -1247,9 +1247,7 @@ static const std::string verify_ccall_sig(jl_value_t *&rt, jl_value_t *at, return ""; } -const int fc_args_start = 6; - -// Expr(:foreigncall, pointer, rettype, (argtypes...), nreq, [cconv | (cconv, effects)], args..., roots...) +// Expr(:foreigncall, pointer, rettype, (argtypes...), nreq, cconv, args..., roots...) static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) { JL_NARGSV(ccall, 5); @@ -1259,14 +1257,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) size_t nccallargs = jl_svec_len(at); size_t nreqargs = jl_unbox_long(args[4]); // if vararg assert(jl_is_quotenode(args[5])); - jl_value_t *jlcc = jl_quotenode_value(args[5]); - jl_sym_t *cc_sym = NULL; - if (jl_is_symbol(jlcc)) { - cc_sym = (jl_sym_t*)jlcc; - } - else if (jl_is_tuple(jlcc)) { - cc_sym = (jl_sym_t*)jl_get_nth_field_noalloc(jlcc, 0); - } + jl_sym_t *cc_sym = *(jl_sym_t**)args[5]; assert(jl_is_symbol(cc_sym)); native_sym_arg_t symarg = {}; JL_GC_PUSH3(&rt, &at, &symarg.gcroot); @@ -1288,8 +1279,8 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) } auto ccallarg = [=] (size_t i) { - assert(i < nccallargs && i + fc_args_start <= nargs); - return args[fc_args_start + i]; + assert(i < nccallargs && i + 6 <= nargs); + return args[6 + i]; }; auto _is_libjulia_func = [&] (uintptr_t ptr, StringRef name) { @@ -1323,7 +1314,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) // emit roots SmallVector gc_uses; - for (size_t i = nccallargs + fc_args_start; i <= nargs; i++) { + for (size_t i = nccallargs + 6; i <= nargs; i++) { // Julia (expression) value of current parameter gcroot jl_value_t *argi_root = args[i]; if (jl_is_long(argi_root)) diff --git a/src/codegen.cpp b/src/codegen.cpp index 46f6899028f13..4d71a9802658b 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8155,7 +8155,6 @@ extern "C" void jl_init_llvm(void) { jl_f__apply_pure_addr, new JuliaFunction{XSTR(jl_f__apply_pure), get_func_sig, get_func_attrs} }, { jl_f__call_latest_addr, new JuliaFunction{XSTR(jl_f__call_latest), get_func_sig, get_func_attrs} }, { jl_f__call_in_world_addr, new JuliaFunction{XSTR(jl_f__call_in_world), get_func_sig, get_func_attrs} }, - { jl_f__call_in_world_total_addr, new JuliaFunction{XSTR(jl_f__call_in_world_total), get_func_sig, get_func_attrs} }, { jl_f_throw_addr, new JuliaFunction{XSTR(jl_f_throw), get_func_sig, get_func_attrs} }, { jl_f_tuple_addr, jltuple_func }, { jl_f_svec_addr, new JuliaFunction{XSTR(jl_f_svec), get_func_sig, get_func_attrs} }, diff --git a/src/dump.c b/src/dump.c index 168034d89236d..4a448bcb23376 100644 --- a/src/dump.c +++ b/src/dump.c @@ -517,8 +517,6 @@ static void jl_serialize_code_instance(jl_serializer_state *s, jl_code_instance_ write_uint8(s->s, TAG_CODE_INSTANCE); write_uint8(s->s, flags); - write_uint8(s->s, codeinst->ipo_purity_bits); - write_uint8(s->s, codeinst->purity_bits); jl_serialize_value(s, (jl_value_t*)codeinst->def); if (write_ret_type) { jl_serialize_value(s, codeinst->inferred); @@ -706,7 +704,6 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li write_int8(s->s, m->pure); write_int8(s->s, m->is_for_opaque_closure); write_int8(s->s, m->constprop); - write_uint8(s->s, m->purity.bits); jl_serialize_value(s, (jl_value_t*)m->slot_syms); jl_serialize_value(s, (jl_value_t*)m->roots); jl_serialize_value(s, (jl_value_t*)m->root_blocks); @@ -1575,7 +1572,6 @@ static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_ m->pure = read_int8(s->s); m->is_for_opaque_closure = read_int8(s->s); m->constprop = read_int8(s->s); - m->purity.bits = read_uint8(s->s); m->slot_syms = jl_deserialize_value(s, (jl_value_t**)&m->slot_syms); jl_gc_wb(m, m->slot_syms); m->roots = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->roots); @@ -1656,8 +1652,6 @@ static jl_value_t *jl_deserialize_value_code_instance(jl_serializer_state *s, jl int flags = read_uint8(s->s); int validate = (flags >> 0) & 3; int constret = (flags >> 2) & 1; - codeinst->ipo_purity_bits = read_uint8(s->s); - codeinst->purity_bits = read_uint8(s->s); codeinst->def = (jl_method_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->def); jl_gc_wb(codeinst, codeinst->def); codeinst->inferred = jl_deserialize_value(s, &codeinst->inferred); diff --git a/src/gf.c b/src/gf.c index 7c42a9b802df3..d4a5df81a8d1a 100644 --- a/src/gf.c +++ b/src/gf.c @@ -206,8 +206,7 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ 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, - uint8_t ipo_effects, uint8_t effects, uint8_t relocatability); + int32_t const_flags, size_t min_world, size_t max_world, uint8_t relocatability); JL_DLLEXPORT void jl_mi_cache_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMENT, jl_code_instance_t *ci JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED); @@ -244,7 +243,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0, 0, 0, 0); + 0, 1, ~(size_t)0, 0); jl_mi_cache_insert(mi, codeinst); codeinst->specptr.fptr1 = fptr; codeinst->invoke = jl_fptr_args; @@ -367,7 +366,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( } codeinst = jl_new_codeinst( mi, rettype, NULL, NULL, - 0, min_world, max_world, 0, 0, 0); + 0, min_world, max_world, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; } @@ -375,8 +374,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( 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, - uint8_t ipo_effects, uint8_t effects, uint8_t relocatability + int32_t const_flags, size_t min_world, size_t max_world, uint8_t relocatability /*, jl_array_t *edges, int absolute_max*/) { jl_task_t *ct = jl_current_task; @@ -402,8 +400,6 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( codeinst->precompile = 0; codeinst->next = NULL; codeinst->relocatability = relocatability; - codeinst->ipo_purity_bits = ipo_effects; - codeinst->purity_bits = effects; return codeinst; } @@ -2013,7 +2009,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (unspec && jl_atomic_load_relaxed(&unspec->invoke)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, 0); + 0, 1, ~(size_t)0, 0); codeinst->isspecsig = 0; codeinst->specptr = unspec->specptr; codeinst->rettype_const = unspec->rettype_const; @@ -2031,7 +2027,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (!jl_code_requires_compiler(src)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, 0); + 0, 1, ~(size_t)0, 0); codeinst->invoke = jl_fptr_interpret_call; jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); @@ -2066,7 +2062,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t return ucache; } codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0, 0, 0, 0); + 0, 1, ~(size_t)0, 0); codeinst->isspecsig = 0; codeinst->specptr = ucache->specptr; codeinst->rettype_const = ucache->rettype_const; diff --git a/src/ircode.c b/src/ircode.c index 73e99f2281491..5be83ed4caac3 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -731,7 +731,6 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) jl_code_info_flags_t flags = code_info_flags(code->pure, code->propagate_inbounds, code->inlineable, code->inferred, code->constprop); write_uint8(s.s, flags.packed); - write_uint8(s.s, code->purity.bits); size_t nslots = jl_array_len(code->slotflags); assert(nslots >= m->nargs && nslots < INT32_MAX); // required by generated functions @@ -821,7 +820,6 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t code->inlineable = flags.bits.inlineable; code->propagate_inbounds = flags.bits.propagate_inbounds; code->pure = flags.bits.pure; - code->purity.bits = read_uint8(s.s); size_t nslots = read_int32(&src); code->slotflags = jl_alloc_array_1d(jl_array_uint8_type, nslots); @@ -937,7 +935,7 @@ JL_DLLEXPORT ssize_t jl_ir_nslots(jl_array_t *data) } else { assert(jl_typeis(data, jl_array_uint8_type)); - int nslots = jl_load_unaligned_i32((char*)data->data + 2); + int nslots = jl_load_unaligned_i32((char*)data->data + 1); return nslots; } } @@ -948,7 +946,7 @@ JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_array_t *data, size_t i) if (jl_is_code_info(data)) return ((uint8_t*)((jl_code_info_t*)data)->slotflags->data)[i]; assert(jl_typeis(data, jl_array_uint8_type)); - return ((uint8_t*)data->data)[2 + sizeof(int32_t) + i]; + return ((uint8_t*)data->data)[1 + sizeof(int32_t) + i]; } JL_DLLEXPORT jl_array_t *jl_uncompress_argnames(jl_value_t *syms) diff --git a/src/jltypes.c b/src/jltypes.c index f6f9db0762810..c50ff27c59f5d 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2350,7 +2350,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(20, + jl_perm_symsvec(19, "code", "codelocs", "ssavaluetypes", @@ -2369,9 +2369,8 @@ void jl_init_types(void) JL_GC_DISABLED "inlineable", "propagate_inbounds", "pure", - "constprop", - "purity"), - jl_svec(20, + "constprop"), + jl_svec(19, jl_array_any_type, jl_array_int32_type, jl_any_type, @@ -2390,15 +2389,14 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type, - jl_uint8_type, jl_uint8_type), jl_emptysvec, - 0, 1, 20); + 0, 1, 19); jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(29, + jl_perm_symsvec(28, "name", "module", "file", @@ -2426,9 +2424,8 @@ void jl_init_types(void) JL_GC_DISABLED "isva", "pure", "is_for_opaque_closure", - "constprop", - "purity"), - jl_svec(29, + "constprop"), + jl_svec(28, jl_symbol_type, jl_module_type, jl_symbol_type, @@ -2456,7 +2453,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_bool_type, jl_bool_type, - jl_uint8_type, jl_uint8_type), jl_emptysvec, 0, 1, 10); @@ -2492,7 +2488,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(14, + jl_perm_symsvec(12, "def", "next", "min_world", @@ -2502,10 +2498,9 @@ void jl_init_types(void) JL_GC_DISABLED "inferred", //"edges", //"absolute_max", - "ipo_purity_bits", "purity_bits", "isspecsig", "precompile", "invoke", "specptr", // function object decls "relocatability"), - jl_svec(14, + jl_svec(12, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -2515,7 +2510,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, //jl_any_type, //jl_bool_type, - jl_uint8_type, jl_uint8_type, jl_bool_type, jl_bool_type, jl_any_type, jl_any_type, // fptrs @@ -2668,8 +2662,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 11, 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, 11, jl_voidpointer_type); - jl_svecset(jl_code_instance_type->types, 12, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 9, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 10, jl_voidpointer_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); diff --git a/src/julia.h b/src/julia.h index 20edd53ad39a7..ff0542307c1cf 100644 --- a/src/julia.h +++ b/src/julia.h @@ -232,21 +232,6 @@ typedef struct _jl_line_info_node_t { intptr_t inlined_at; } jl_line_info_node_t; -typedef union __jl_purity_overrides_t { - struct { - uint8_t ipo_consistent : 1; - uint8_t ipo_effect_free : 1; - uint8_t ipo_nothrow : 1; - uint8_t ipo_terminates : 1; - // Weaker form of `terminates` that asserts - // that any control flow syntactically in the method - // is guaranteed to terminate, but does not make - // assertions about any called functions. - uint8_t ipo_terminates_locally : 1; - } overrides; - uint8_t bits; -} _jl_purity_overrides_t; - // This type describes a single function body typedef struct _jl_code_info_t { // ssavalue-indexed arrays of properties: @@ -280,7 +265,6 @@ typedef struct _jl_code_info_t { uint8_t pure; // uint8 settings uint8_t constprop; // 0 = use heuristic; 1 = aggressive; 2 = none - _jl_purity_overrides_t purity; } jl_code_info_t; // This type describes a single method definition, and stores data @@ -335,10 +319,6 @@ typedef struct _jl_method_t { // uint8 settings uint8_t constprop; // 0x00 = use heuristic; 0x01 = aggressive; 0x02 = none - // Override the conclusions of inter-procedural effect analysis, - // forcing the conclusion to always true. - _jl_purity_overrides_t purity; - // hidden fields: // lock for modifications to the method jl_mutex_t writelock; @@ -391,26 +371,6 @@ typedef struct _jl_code_instance_t { //TODO: jl_array_t *edges; // stored information about edges from this object //TODO: uint8_t absolute_max; // whether true max world is unknown - // purity results - union { - uint8_t ipo_purity_bits; - struct { - uint8_t ipo_consistent:2; - uint8_t ipo_effect_free:2; - uint8_t ipo_nothrow:2; - uint8_t ipo_terminates:2; - } ipo_purity_flags; - }; - union { - uint8_t purity_bits; - struct { - uint8_t consistent:2; - uint8_t effect_free:2; - uint8_t nothrow:2; - uint8_t terminates:2; - } purity_flags; - }; - // compilation state cache uint8_t isspecsig; // if specptr is a specialized function signature for specTypes->rettype _Atomic(uint8_t) precompile; // if set, this will be added to the output system image diff --git a/src/julia_internal.h b/src/julia_internal.h index c7454e2d5a904..5b7b81e7a66c5 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1433,7 +1433,6 @@ extern JL_DLLEXPORT jl_sym_t *jl_propagate_inbounds_sym; extern JL_DLLEXPORT jl_sym_t *jl_specialize_sym; extern JL_DLLEXPORT jl_sym_t *jl_aggressive_constprop_sym; extern JL_DLLEXPORT jl_sym_t *jl_no_constprop_sym; -extern JL_DLLEXPORT jl_sym_t *jl_purity_sym; extern JL_DLLEXPORT jl_sym_t *jl_nospecialize_sym; extern JL_DLLEXPORT jl_sym_t *jl_macrocall_sym; extern JL_DLLEXPORT jl_sym_t *jl_colon_sym; diff --git a/src/method.c b/src/method.c index d68757114b2b4..0b615e7e46dd5 100644 --- a/src/method.c +++ b/src/method.c @@ -146,7 +146,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve return expr; } if (e->head == jl_foreigncall_sym) { - JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects)) + JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, cc, narg) jl_value_t *rt = jl_exprarg(e, 1); jl_value_t *at = jl_exprarg(e, 2); if (!jl_is_type(rt)) { @@ -176,15 +176,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve check_c_types("ccall method definition", rt, at); JL_TYPECHK(ccall method definition, long, jl_exprarg(e, 3)); JL_TYPECHK(ccall method definition, quotenode, jl_exprarg(e, 4)); - jl_value_t *cc = jl_quotenode_value(jl_exprarg(e, 4)); - if (!jl_is_symbol(cc)) { - JL_TYPECHK(ccall method definition, tuple, cc); - if (jl_nfields(cc) != 2) { - jl_error("In ccall calling convention, expected two argument tuple or symbol."); - } - JL_TYPECHK(ccall method definition, symbol, jl_get_nth_field(cc, 0)); - JL_TYPECHK(ccall method definition, uint8, jl_get_nth_field(cc, 1)); - } + JL_TYPECHK(ccall method definition, symbol, *(jl_value_t**)jl_exprarg(e, 4)); jl_exprargset(e, 0, resolve_globals(jl_exprarg(e, 0), module, sparam_vals, binding_effects, 1)); i++; } @@ -316,15 +308,6 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) li->constprop = 1; else if (ma == (jl_value_t*)jl_no_constprop_sym) li->constprop = 2; - else if (jl_is_expr(ma) && ((jl_expr_t*)ma)->head == jl_purity_sym) { - if (jl_expr_nargs(ma) == 5) { - li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0)); - li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1)); - li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2)); - li->purity.overrides.ipo_terminates = jl_unbox_bool(jl_exprarg(ma, 3)); - li->purity.overrides.ipo_terminates_locally = jl_unbox_bool(jl_exprarg(ma, 4)); - } - } else jl_array_ptr_set(meta, ins++, ma); } @@ -465,7 +448,6 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void) src->pure = 0; src->edges = jl_nothing; src->constprop = 0; - src->purity.bits = 0; return src; } @@ -653,7 +635,6 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) m->called = called; m->pure = src->pure; m->constprop = src->constprop; - m->purity.bits = src->purity.bits; jl_add_function_name_to_lineinfo(src, (jl_value_t*)m->name); jl_array_t *copy = NULL; diff --git a/src/staticdata.c b/src/staticdata.c index 2605aede78d2b..fb42d9cdf23f9 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -245,7 +245,7 @@ static htable_t field_replace; static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_throw, &jl_f_is, &jl_f_typeof, &jl_f_issubtype, &jl_f_isa, &jl_f_typeassert, &jl_f__apply_iterate, &jl_f__apply_pure, - &jl_f__call_latest, &jl_f__call_in_world, &jl_f__call_in_world_total, &jl_f_isdefined, + &jl_f__call_latest, &jl_f__call_in_world, &jl_f_isdefined, &jl_f_tuple, &jl_f_svec, &jl_f_intrinsic_call, &jl_f_invoke_kwsorter, &jl_f_getfield, &jl_f_setfield, &jl_f_swapfield, &jl_f_modifyfield, &jl_f_replacefield, &jl_f_fieldtype, &jl_f_nfields, diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index 05e3a744644e1..8372fb16d3a13 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -314,7 +314,7 @@ end # manually generate a broken function, which will break codegen # and make sure Julia doesn't crash -@eval @noinline @Base.constprop :none f_broken_code() = 0 +@eval @noinline f_broken_code() = 0 let m = which(f_broken_code, ()) let src = Base.uncompressed_ast(m) src.code = Any[ diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index f36999d63d311..7df53f216716f 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -79,7 +79,7 @@ const TAGS = Any[ @assert length(TAGS) == 255 -const ser_version = 17 # do not make changes without bumping the version #! +const ser_version = 16 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -419,7 +419,6 @@ function serialize(s::AbstractSerializer, meth::Method) serialize(s, meth.isva) serialize(s, meth.is_for_opaque_closure) serialize(s, meth.constprop) - serialize(s, meth.purity) if isdefined(meth, :source) serialize(s, Base._uncompressed_ast(meth, meth.source)) else @@ -1027,16 +1026,12 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) isva = deserialize(s)::Bool is_for_opaque_closure = false constprop = 0x00 - purity = 0x00 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque if format_version(s) >= 14 constprop = deserialize(s)::UInt8 end - if format_version(s) >= 17 - purity = deserialize(s)::UInt8 - end template = deserialize(s) else template = template_or_is_opaque @@ -1056,7 +1051,6 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) meth.isva = isva meth.is_for_opaque_closure = is_for_opaque_closure meth.constprop = constprop - meth.purity = purity if template !== nothing # TODO: compress template meth.source = template::CodeInfo @@ -1188,9 +1182,6 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 14 ci.constprop = deserialize(s)::UInt8 end - if format_version(s) >= 17 - ci.purity = deserialize(s)::UInt8 - end return ci end diff --git a/test/abstractarray.jl b/test/abstractarray.jl index a33cf53698d1c..95bd33424c3d0 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -529,7 +529,7 @@ mutable struct TestThrowNoGetindex{T} <: AbstractVector{T} end @testset "ErrorException if getindex is not defined" begin Base.length(::TestThrowNoGetindex) = 2 Base.size(::TestThrowNoGetindex) = (2,) - @test_throws Base.CanonicalIndexError isassigned(TestThrowNoGetindex{Float64}(), 1) + @test_throws ErrorException isassigned(TestThrowNoGetindex{Float64}(), 1) end function test_in_bounds(::Type{TestAbstractArray}) @@ -565,10 +565,10 @@ end function test_getindex_internals(::Type{TestAbstractArray}) U = UnimplementedFastArray{Int, 2}() V = UnimplementedSlowArray{Int, 2}() - @test_throws Base.CanonicalIndexError getindex(U, 1) - @test_throws Base.CanonicalIndexError Base.unsafe_getindex(U, 1) - @test_throws Base.CanonicalIndexError getindex(V, 1, 1) - @test_throws Base.CanonicalIndexError Base.unsafe_getindex(V, 1, 1) + @test_throws ErrorException getindex(U, 1) + @test_throws ErrorException Base.unsafe_getindex(U, 1) + @test_throws ErrorException getindex(V, 1, 1) + @test_throws ErrorException Base.unsafe_getindex(V, 1, 1) end function test_setindex!_internals(::Type{T}, shape, ::Type{TestAbstractArray}) where T @@ -583,10 +583,10 @@ end function test_setindex!_internals(::Type{TestAbstractArray}) U = UnimplementedFastArray{Int, 2}() V = UnimplementedSlowArray{Int, 2}() - @test_throws Base.CanonicalIndexError setindex!(U, 0, 1) - @test_throws Base.CanonicalIndexError Base.unsafe_setindex!(U, 0, 1) - @test_throws Base.CanonicalIndexError setindex!(V, 0, 1, 1) - @test_throws Base.CanonicalIndexError Base.unsafe_setindex!(V, 0, 1, 1) + @test_throws ErrorException setindex!(U, 0, 1) + @test_throws ErrorException Base.unsafe_setindex!(U, 0, 1) + @test_throws ErrorException setindex!(V, 0, 1, 1) + @test_throws ErrorException Base.unsafe_setindex!(V, 0, 1, 1) end function test_get(::Type{TestAbstractArray}) diff --git a/test/boundscheck_exec.jl b/test/boundscheck_exec.jl index 4040a2739730f..735cd88c13758 100644 --- a/test/boundscheck_exec.jl +++ b/test/boundscheck_exec.jl @@ -272,16 +272,4 @@ end end end - -# Test that --check-bounds=off doesn't permit const prop of indices into -# function that are not dynamically reachable (the same test for @inbounds -# is in the compiler tests). -function f_boundscheck_elim(n) - # Inbounds here assumes that this is only ever called with n==0, but of - # course the compiler has no way of knowing that, so it must not attempt - # to run the @inbounds `getfield(sin, 1)`` that ntuple generates. - ntuple(x->getfield(sin, x), n) -end -@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] - end diff --git a/test/broadcast.jl b/test/broadcast.jl index 5cddd0cb174f8..6d85ac8624ca8 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -695,12 +695,12 @@ end A = [[1,2,3],4:5,6] A[1] .= 0 @test A[1] == [0,0,0] - @test_throws Base.CanonicalIndexError A[2] .= 0 + @test_throws ErrorException A[2] .= 0 @test_throws MethodError A[3] .= 0 A = [[1,2,3],4:5] A[1] .= 0 @test A[1] == [0,0,0] - @test_throws Base.CanonicalIndexError A[2] .= 0 + @test_throws ErrorException A[2] .= 0 end # Issue #22180 diff --git a/test/ccall.jl b/test/ccall.jl index 3a1b6ff3db733..9837757f29f68 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1893,9 +1893,3 @@ end @test cglobal33413_literal() != C_NULL @test cglobal33413_literal_notype() != C_NULL end - -@testset "ccall_effects" begin - ctest_total(x) = @Base.assume_effects :total @ccall libccalltest.ctest(x::Complex{Int})::Complex{Int} - ctest_total_const() = Val{ctest_total(1 + 2im)}() - Core.Compiler.return_type(ctest_total_const, Tuple{}) == Val{2 + 0im} -end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 79030bd910990..fe49b2d6000af 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -3419,10 +3419,10 @@ end # Recursive function @eval module _Recursive f(n::Integer) = n == 0 ? 0 : f(n-1) + 1 end timing = time_inference() do - @eval _Recursive.f(Base.inferencebarrier(5)) + @eval _Recursive.f(5) end - @test 2 <= depth(timing) <= 3 # root -> f (-> +) - @test 2 <= length(flatten_times(timing)) <= 3 # root, f, + + @test depth(timing) == 3 # root -> f -> + + @test length(flatten_times(timing)) == 3 # root, f, + # Functions inferred with multiple constants @eval module C @@ -4009,22 +4009,3 @@ end end |> only === Union{} end end - -# Test that purity modeling doesn't accidentally introduce new world age issues -f_redefine_me(x) = x+1 -f_call_redefine() = f_redefine_me(0) -f_mk_opaque() = @Base.Experimental.opaque ()->Base.inferencebarrier(f_call_redefine)() -const op_capture_world = f_mk_opaque() -f_redefine_me(x) = x+2 -@test op_capture_world() == 1 -@test f_mk_opaque()() == 2 - -# Test that purity doesn't try to accidentally run unreachable code due to -# boundscheck elimination -function f_boundscheck_elim(n) - # Inbounds here assumes that this is only ever called with n==0, but of - # course the compiler has no way of knowing that, so it must not attempt - # to run the @inbounds `getfield(sin, 1)`` that ntuple generates. - ntuple(x->(@inbounds getfield(sin, x)), n) -end -@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 4261665edc80e..f6bd13a0ad154 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -4,8 +4,6 @@ using Test using Base.Meta using Core: ReturnNode -include(normpath(@__DIR__, "irutils.jl")) - """ Helper to walk the AST and call a function on every node. """ @@ -152,6 +150,19 @@ end @test !any(x -> x isa Expr && x.head === :invoke, src.code) end +function fully_eliminated(f, args) + @nospecialize f args + let code = code_typed(f, args)[1][1].code + return length(code) == 1 && isa(code[1], ReturnNode) + end +end +function fully_eliminated(f, args, retval) + @nospecialize f args + let code = code_typed(f, args)[1][1].code + return length(code) == 1 && isa(code[1], ReturnNode) && code[1].val == retval + end +end + # check that ismutabletype(type) can be fully eliminated f_mutable_nothrow(s::String) = Val{typeof(s).name.flags} @test fully_eliminated(f_mutable_nothrow, (String,)) @@ -235,7 +246,7 @@ function f_subtype() T = SomeArbitraryStruct T <: Bool end -@test fully_eliminated(f_subtype, Tuple{}; retval=false) +@test fully_eliminated(f_subtype, Tuple{}, false) # check that pointerref gets deleted if unused f_pointerref(T::Type{S}) where S = Val(length(T.parameters)) @@ -259,7 +270,7 @@ function foo_apply_apply_type_svec() B = Tuple{Float32, Float32} Core.apply_type(A..., B.types...) end -@test fully_eliminated(foo_apply_apply_type_svec, Tuple{}; retval=NTuple{3, Float32}) +@test fully_eliminated(foo_apply_apply_type_svec, Tuple{}, NTuple{3, Float32}) # The that inlining doesn't drop ambiguity errors (#30118) c30118(::Tuple{Ref{<:Type}, Vararg}) = nothing @@ -273,7 +284,7 @@ b30118(x...) = c30118(x) f34900(x::Int, y) = x f34900(x, y::Int) = y f34900(x::Int, y::Int) = invoke(f34900, Tuple{Int, Any}, x, y) -@test fully_eliminated(f34900, Tuple{Int, Int}; retval=Core.Argument(2)) +@test fully_eliminated(f34900, Tuple{Int, Int}, Core.Argument(2)) @testset "check jl_ir_flag_inlineable for inline macro" begin @test ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods(@inline x -> x)).source) @@ -313,7 +324,10 @@ struct NonIsBitsDims dims::NTuple{N, Int} where N end NonIsBitsDims() = NonIsBitsDims(()) -@test fully_eliminated(NonIsBitsDims, (); retval=QuoteNode(NonIsBitsDims())) +let ci = code_typed(NonIsBitsDims, Tuple{})[1].first + @test length(ci.code) == 1 && isa(ci.code[1], ReturnNode) && + ci.code[1].val.value == NonIsBitsDims() +end struct NonIsBitsDimsUndef dims::NTuple{N, Int} where N @@ -909,139 +923,8 @@ end g() = Core.get_binding_type($m, :y) end - @test fully_eliminated(m.f, Tuple{}; retval=Int) + @test fully_eliminated(m.f, Tuple{}, Int) src = code_typed(m.g, ())[][1] @test count(iscall((src, Core.get_binding_type)), src.code) == 1 @test m.g() === Any end - -# have_fma elimination inside ^ -f_pow() = ^(2.0, -1.0) -@test fully_eliminated(f_pow, Tuple{}) - -# unused total, noinline function -@noinline function f_total_noinline(x) - return x + 1.0 -end -@noinline function f_voltatile_escape(ptr) - unsafe_store!(ptr, 0) -end -function f_call_total_noinline_unused(x) - f_total_noinline(x) - return x -end -function f_call_volatile_escape(ptr) - f_voltatile_escape(ptr) - return ptr -end - -@test fully_eliminated(f_call_total_noinline_unused, Tuple{Float64}) -@test !fully_eliminated(f_call_volatile_escape, Tuple{Ptr{Int}}) - -let b = Expr(:block, (:(y += sin($x)) for x in randn(1000))...) - @eval function f_sin_perf() - y = 0.0 - $b - y - end -end -@test fully_eliminated(f_sin_perf, Tuple{}) - -# Test that we inline the constructor of something that is not const-inlineable -const THE_REF_NULL = Ref{Int}() -const THE_REF = Ref{Int}(0) -struct FooTheRef - x::Ref - FooTheRef(v) = new(v === nothing ? THE_REF_NULL : THE_REF) -end -let src = code_typed1() do - FooTheRef(nothing) - end - @test count(isnew, src.code) == 1 -end -let src = code_typed1() do - FooTheRef(0) - end - @test count(isnew, src.code) == 1 -end -let src = code_typed1() do - Base.@invoke FooTheRef(nothing::Any) - end - @test count(isnew, src.code) == 1 -end -let src = code_typed1() do - Base.@invoke FooTheRef(0::Any) - end - @test count(isnew, src.code) == 1 -end -@test fully_eliminated() do - FooTheRef(nothing) - nothing -end -@test fully_eliminated() do - FooTheRef(0) - nothing -end -@test fully_eliminated() do - Base.@invoke FooTheRef(nothing::Any) - nothing -end -@test fully_eliminated() do - Base.@invoke FooTheRef(0::Any) - nothing -end - -# Test that the Core._apply_iterate bail path taints effects -function f_apply_bail(f) - f(()...) - return nothing -end -f_call_apply_bail(f) = f_apply_bail(f) -@test !fully_eliminated(f_call_apply_bail, Tuple{Function}) - -# Test that arraysize has proper effect modeling -@test fully_eliminated(M->(size(M, 2); nothing), Tuple{Matrix{Float64}}) - -# DCE of non-inlined callees -@noinline noninlined_dce_simple(a) = identity(a) -@test fully_eliminated((String,)) do s - noninlined_dce_simple(s) - nothing -end -@noinline noninlined_dce_new(a::String) = Some(a) -@test fully_eliminated((String,)) do s - noninlined_dce_new(s) - nothing -end -mutable struct SafeRef{T} - x::T -end -Base.getindex(s::SafeRef) = getfield(s, 1) -Base.setindex!(s::SafeRef, x) = setfield!(s, 1, x) -@noinline noninlined_dce_new(a::Symbol) = SafeRef(a) -@test fully_eliminated((Symbol,)) do s - noninlined_dce_new(s) - nothing -end -# should be resolved once we merge https://github.com/JuliaLang/julia/pull/43923 -@test_broken fully_eliminated((Union{Symbol,String},)) do s - noninlined_dce_new(s) - nothing -end - -# Test that ambigous calls don't accidentally get nothrow effect -ambig_effect_test(a::Int, b) = 1 -ambig_effect_test(a, b::Int) = 1 -ambig_effect_test(a, b) = 1 -global ambig_unknown_type_global=1 -@noinline function conditionally_call_ambig(b::Bool, a) - if b - ambig_effect_test(a, ambig_unknown_type_global) - end - return 0 -end -function call_call_ambig(b::Bool) - conditionally_call_ambig(b, 1) - return 1 -end -@test !fully_eliminated(call_call_ambig, Tuple{Bool}) diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 128fd6cc84b7b..9eb77490cbdb4 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -4,7 +4,31 @@ using Test using Base.Meta using Core: PhiNode, SSAValue, GotoNode, PiNode, QuoteNode, ReturnNode, GotoIfNot -include(normpath(@__DIR__, "irutils.jl")) +# utilities +# ========= + +import Core.Compiler: argextype, singleton_type + +argextype(@nospecialize args...) = argextype(args..., Any[]) +code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::Core.CodeInfo +get_code(args...; kwargs...) = code_typed1(args...; kwargs...).code + +# check if `x` is a statement with a given `head` +isnew(@nospecialize x) = Meta.isexpr(x, :new) + +# check if `x` is a dynamic call of a given function +iscall(y) = @nospecialize(x) -> iscall(y, x) +function iscall((src, f)::Tuple{Core.CodeInfo,Base.Callable}, @nospecialize(x)) + return iscall(x) do @nospecialize x + singleton_type(argextype(x, src)) === f + end +end +iscall(pred::Base.Callable, @nospecialize(x)) = Meta.isexpr(x, :call) && pred(x.args[1]) + +# check if `x` is a statically-resolved call of a function whose name is `sym` +isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) +isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) +isinvoke(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :invoke) && pred(x.args[1]::Core.MethodInstance) # domsort # ======= @@ -449,7 +473,9 @@ struct FooPartial global f_partial f_partial(x) = new(x, 2).x end -@test fully_eliminated(f_partial, Tuple{Float64}) +let ci = code_typed(f_partial, Tuple{Float64})[1].first + @test length(ci.code) == 1 && isa(ci.code[1], ReturnNode) +end # A SSAValue after the compaction line let m = Meta.@lower 1 + 1 @@ -631,7 +657,11 @@ function no_op_refint(r) r[] return end -@test fully_eliminated(no_op_refint,Tuple{Base.RefValue{Int}}; retval=nothing) +let code = code_typed(no_op_refint,Tuple{Base.RefValue{Int}})[1].first.code + @test length(code) == 1 + @test isa(code[1], Core.ReturnNode) + @test code[1].val === nothing +end # check getfield elim handling of GlobalRef const _some_coeffs = (1,[2],3,4) @@ -743,6 +773,19 @@ end # test `stmt_effect_free` and DCE # =============================== +function fully_eliminated(f, args) + @nospecialize f args + let code = code_typed(f, args)[1][1].code + return length(code) == 1 && isa(code[1], ReturnNode) + end +end +function fully_eliminated(f, args, retval) + @nospecialize f args + let code = code_typed(f, args)[1][1].code + return length(code) == 1 && isa(code[1], ReturnNode) && code[1].val == retval + end +end + let # effect-freeness computation for array allocation # should eliminate dead allocations diff --git a/test/compiler/irutils.jl b/test/compiler/irutils.jl deleted file mode 100644 index 06d261720bdf8..0000000000000 --- a/test/compiler/irutils.jl +++ /dev/null @@ -1,34 +0,0 @@ -import Core: CodeInfo, ReturnNode, MethodInstance -import Core.Compiler: argextype, singleton_type -import Base.Meta: isexpr - -argextype(@nospecialize args...) = argextype(args..., Any[]) -code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::CodeInfo -get_code(args...; kwargs...) = code_typed1(args...; kwargs...).code - -# check if `x` is a statement with a given `head` -isnew(@nospecialize x) = isexpr(x, :new) -isreturn(@nospecialize x) = isa(x, ReturnNode) - -# check if `x` is a dynamic call of a given function -iscall(y) = @nospecialize(x) -> iscall(y, x) -function iscall((src, f)::Tuple{CodeInfo,Base.Callable}, @nospecialize(x)) - return iscall(x) do @nospecialize x - singleton_type(argextype(x, src)) === f - end -end -iscall(pred::Base.Callable, @nospecialize(x)) = isexpr(x, :call) && pred(x.args[1]) - -# check if `x` is a statically-resolved call of a function whose name is `sym` -isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) -isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) -isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1]::MethodInstance) - -function fully_eliminated(@nospecialize args...; retval=(@__FILE__), kwargs...) - code = code_typed1(args...; kwargs...).code - if retval !== (@__FILE__) - return length(code) == 1 && isreturn(code[1]) && code[1].val == retval - else - return length(code) == 1 && isreturn(code[1]) - end -end diff --git a/test/read.jl b/test/read.jl index 91b5043ae2a55..d26f2463dcbd1 100644 --- a/test/read.jl +++ b/test/read.jl @@ -604,7 +604,7 @@ end read!(io, @view y[4:7]) @test y[4:7] == v seekstart(io) - @test_throws Base.CanonicalIndexError read!(io, @view z[4:6]) + @test_throws ErrorException read!(io, @view z[4:6]) end # Bulk read from pipe diff --git a/test/strings/basic.jl b/test/strings/basic.jl index c1df87420d7da..1da897667a2ea 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -1039,7 +1039,7 @@ let s = "∀x∃y", u = codeunits(s) @test u[1] == 0xe2 @test u[2] == 0x88 @test u[8] == 0x79 - @test_throws Base.CanonicalIndexError (u[1] = 0x00) + @test_throws ErrorException (u[1] = 0x00) @test collect(u) == b"∀x∃y" @test Base.elsize(u) == Base.elsize(typeof(u)) == 1 end @@ -1100,4 +1100,4 @@ end let d = Dict(lazy"$(1+2) is 3" => 3) @test d["3 is 3"] == 3 end -end +end \ No newline at end of file diff --git a/test/strings/util.jl b/test/strings/util.jl index 8957513e37f25..b313a0fa1af4a 100644 --- a/test/strings/util.jl +++ b/test/strings/util.jl @@ -629,7 +629,7 @@ let testb() = b"0123" b = testb() @test eltype(b) === UInt8 @test b isa AbstractVector - @test_throws Base.CanonicalIndexError b[4] = '4' + @test_throws ErrorException b[4] = '4' @test testb() == UInt8['0','1','2','3'] end diff --git a/test/testenv.jl b/test/testenv.jl index 41706dd24e75e..2aeee0f6dfc80 100644 --- a/test/testenv.jl +++ b/test/testenv.jl @@ -43,7 +43,7 @@ if !@isdefined(testenv_defined) const curmod = @__MODULE__ const curmod_name = fullname(curmod) const curmod_str = curmod === Main ? "Main" : join(curmod_name, ".") - const curmod_prefix = curmod === Main ? "" : "$(["$m." for m in curmod_name]...)" + const curmod_prefix = "$(["$m." for m in curmod_name]...)" # platforms that support cfunction with closures # (requires LLVM back-end support for trampoline intrinsics)