From dfe0e34407b7cb5eac0e9efb8e9b90c4db95ef4a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 11 Apr 2022 23:34:37 +0900 Subject: [PATCH] replace `@pure` annotations in Base with effect settings (#44776) This commit replaces `@pure`/`@_pure_meta` annotations used in `Base` with corresponding effect settings (`:total` or `:total_or_throw`). The concrete evaluation mechanism based on the effect system (#43852) has the following benefits over the `@pure`-based optimization: - it can fold cases when consistent exception is thrown - it can handle constant union-split situation as well - effects can be propagated inter-procedurally While revisiting the existing annotations, I removed some unnecessary ones and also added some more hopefully new annotations (mostly for reflection utilities). In theory this should give us some performance benefits, e.g. now we can concrete-evaluate union-spit `typeintersect`: ```julia @test Base.return_types((Union{Int,Nothing},)) do x typeintersect(String, typeof(x)) end |> only === Type{Union{}} ``` Though this commit ends up bigger than I expected -- we should carefully check a benchmark result so that it doesn't come with any regressions. --- base/Base.jl | 2 +- base/array.jl | 2 +- base/broadcast.jl | 4 +- base/c.jl | 2 +- base/compiler/abstractinterpretation.jl | 2 +- base/compiler/typeinfer.jl | 53 +++++++++++----------- base/deprecated.jl | 4 +- base/essentials.jl | 30 ++++++++++++- base/intfuncs.jl | 8 ++-- base/irrationals.jl | 11 ++--- base/namedtuple.jl | 6 +-- base/operators.jl | 21 ++------- base/promotion.jl | 8 ++-- base/reflection.jl | 54 +++++++++++------------ base/reinterpretarray.jl | 2 +- base/strings/string.jl | 2 +- base/tuple.jl | 16 +++---- stdlib/LinearAlgebra/src/LinearAlgebra.jl | 2 +- test/broadcast.jl | 5 +++ 19 files changed, 125 insertions(+), 109 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 129dca296a845..e3fec462215ef 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -183,7 +183,7 @@ include("abstractarraymath.jl") include("arraymath.jl") # SIMD loops -@pure sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop +sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop include("simdloop.jl") using .SimdLoop diff --git a/base/array.jl b/base/array.jl index 613a771e9d9f8..5dd9a5660f54a 100644 --- a/base/array.jl +++ b/base/array.jl @@ -154,7 +154,7 @@ size(a::Array{<:Any,N}) where {N} = (@inline; ntuple(M -> size(a, M), Val(N))::D asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...) -allocatedinline(T::Type) = (@_pure_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0)) +allocatedinline(T::Type) = (@_total_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0)) """ Base.isbitsunion(::Type{T}) diff --git a/base/broadcast.jl b/base/broadcast.jl index c9694d6645099..0b9d1ac8297ee 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -8,7 +8,7 @@ Module containing the broadcasting implementation. module Broadcast using .Base.Cartesian -using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, promote_typejoin_union, @pure, +using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, promote_typejoin_union, _msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, unalias, negate import .Base: copy, copyto!, axes export broadcast, broadcast!, BroadcastStyle, broadcast_axes, broadcastable, dotview, @__dot__, BroadcastFunction @@ -137,7 +137,7 @@ BroadcastStyle(a::AbstractArrayStyle, ::Style{Tuple}) = a BroadcastStyle(::A, ::A) where A<:ArrayStyle = A() BroadcastStyle(::ArrayStyle, ::ArrayStyle) = Unknown() BroadcastStyle(::A, ::A) where A<:AbstractArrayStyle = A() -Base.@pure function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N} +function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N} if Base.typename(A) === Base.typename(B) return A(Val(max(M, N))) end diff --git a/base/c.jl b/base/c.jl index 3606d0fa0a9bc..7d168f2293c9c 100644 --- a/base/c.jl +++ b/base/c.jl @@ -734,6 +734,6 @@ macro ccall(expr) return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) end -macro ccall_effects(effects, expr) +macro ccall_effects(effects::UInt8, 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 5699514c53ebf..0cca9c38d2470 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1950,7 +1950,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end cconv = e.args[5] if isa(cconv, QuoteNode) && isa(cconv.value, Tuple{Symbol, UInt8}) - effects = cconv.value[2] + effects = cconv.value[2]::UInt8 effects = decode_effects_override(effects) tristate_merge!(sv, Effects( effects.consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN, diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 4efdd629208b6..94387b643b0b6 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -427,13 +427,34 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState) return typ end -function rt_adjust_effects(@nospecialize(rt), ipo_effects::Effects) +function adjust_effects(sv::InferenceState) + ipo_effects = Effects(sv) + # 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) + if !ipo_effects.inbounds_taints_consistency && sv.bestguess === Union{} + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) + end + + # override the analyzed effects using manually annotated effect settings + def = sv.linfo.def + if isa(def, Method) + override = decode_effects_override(def.purity) + if is_effect_overridden(override, :consistent) + ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) + end + if is_effect_overridden(override, :effect_free) + ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE) + end + if is_effect_overridden(override, :nothrow) + ipo_effects = Effects(ipo_effects; nothrow=ALWAYS_TRUE) + end + if is_effect_overridden(override, :terminates_globally) + ipo_effects = Effects(ipo_effects; terminates=ALWAYS_TRUE) + end end + return ipo_effects end @@ -495,25 +516,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end me.result.valid_worlds = me.valid_worlds me.result.result = me.bestguess - ipo_effects = rt_adjust_effects(me.bestguess, me.ipo_effects) - # override the analyzed effects using manually annotated effect settings - def = me.linfo.def - if isa(def, Method) - override = decode_effects_override(def.purity) - if is_effect_overridden(override, :consistent) - ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) - end - if is_effect_overridden(override, :effect_free) - ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE) - end - if is_effect_overridden(override, :nothrow) - ipo_effects = Effects(ipo_effects; nothrow=ALWAYS_TRUE) - end - if is_effect_overridden(override, :terminates_globally) - ipo_effects = Effects(ipo_effects; terminates=ALWAYS_TRUE) - end - end - me.ipo_effects = me.result.ipo_effects = ipo_effects + me.ipo_effects = me.result.ipo_effects = adjust_effects(me) validate_code_in_debug_mode(me.linfo, me.src, "inferred") nothing end @@ -887,8 +890,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize typeinf(interp, frame) update_valid_age!(frame, caller) edge = frame.inferred ? mi : nothing - edge_effects = rt_adjust_effects(frame.bestguess, Effects(frame)) - return EdgeCallResult(frame.bestguess, edge, edge_effects) + return EdgeCallResult(frame.bestguess, edge, Effects(frame)) # effects are adjusted already within `finish` elseif frame === true # unresolvable cycle return EdgeCallResult(Any, nothing, Effects()) @@ -896,8 +898,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(frame, caller) - edge_effects = rt_adjust_effects(frame.bestguess, Effects(frame)) - return EdgeCallResult(frame.bestguess, nothing, edge_effects) + return EdgeCallResult(frame.bestguess, nothing, adjust_effects(frame)) end #### entry points for inferring a MethodInstance given a type signature #### diff --git a/base/deprecated.jl b/base/deprecated.jl index c4f9699af30ea..28a35e23635f4 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -251,11 +251,11 @@ getindex(match::Core.MethodMatch, field::Int) = tuple_type_head(T::Type) = fieldtype(T, 1) tuple_type_cons(::Type, ::Type{Union{}}) = Union{} function tuple_type_cons(::Type{S}, ::Type{T}) where T<:Tuple where S - @_pure_meta + @_total_may_throw_meta Tuple{S, T.parameters...} end function parameter_upper_bound(t::UnionAll, idx) - @_pure_meta + @_total_may_throw_meta return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t) end diff --git a/base/essentials.jl b/base/essentials.jl index b837b556ed910..4bded3723711f 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -151,9 +151,37 @@ macro isdefined(s::Symbol) return Expr(:escape, Expr(:isdefined, s)) end +function _is_internal(__module__) + if ccall(:jl_base_relative_to, Any, (Any,), __module__)::Module === Core.Compiler || + nameof(__module__) === :Base + return true + end + return false +end + +# can be used in place of `@pure` (supposed to be used for bootstrapping) macro _pure_meta() - return Expr(:meta, :pure) + return _is_internal(__module__) && Expr(:meta, :pure) +end +# can be used in place of `@assume_effects :total` (supposed to be used for bootstrapping) +macro _total_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#true, + #=:effect_free=#true, + #=:nothrow=#true, + #=:terminates_globally=#true, + #=:terminates_locally=#false)) +end +# can be used in place of `@assume_effects :total_may_throw` (supposed to be used for bootstrapping) +macro _total_may_throw_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#true, + #=:effect_free=#true, + #=:nothrow=#false, + #=:terminates_globally=#true, + #=:terminates_locally=#false)) end + # another version of inlining that propagates an inbounds context macro _propagate_inbounds_meta() return Expr(:meta, :inline, :propagate_inbounds) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 44c7be0626126..523167e13f8ab 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -58,11 +58,9 @@ end # binary GCD (aka Stein's) algorithm # about 1.7x (2.1x) faster for random Int64s (Int128s) -# Unfortunately, we need to manually annotate this as `@pure` to work around #41694. Since -# this is used in the Rational constructor, constant prop is something we do care about here. -# This does call generic functions, so it might not be completely sound, but since `_gcd` is -# restricted to BitIntegers, it is probably fine in practice. -@pure function _gcd(a::T, b::T) where T<:BitInteger +# Unfortunately, we need to manually annotate this as `@assume_effects :terminates_locally` to work around #41694. +# Since this is used in the Rational constructor, constant folding is something we do care about here. +@assume_effects :terminates_locally function _gcd(a::T, b::T) where T<:BitInteger za = trailing_zeros(a) zb = trailing_zeros(b) k = min(za, zb) diff --git a/base/irrationals.jl b/base/irrationals.jl index d5fae08ffe276..ecc3aff6138c1 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -48,7 +48,8 @@ AbstractFloat(x::AbstractIrrational) = Float64(x)::Float64 Float16(x::AbstractIrrational) = Float16(Float32(x)::Float32) Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x)) -@pure function Rational{T}(x::AbstractIrrational) where T<:Integer +# XXX this may change `DEFAULT_PRECISION`, thus not effect free +@assume_effects :total function Rational{T}(x::AbstractIrrational) where T<:Integer o = precision(BigFloat) p = 256 while true @@ -64,7 +65,7 @@ Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x)) end Rational{BigInt}(x::AbstractIrrational) = throw(ArgumentError("Cannot convert an AbstractIrrational to a Rational{BigInt}: use rationalize(BigInt, x) instead")) -@pure function (t::Type{T})(x::AbstractIrrational, r::RoundingMode) where T<:Union{Float32,Float64} +@assume_effects :total function (t::Type{T})(x::AbstractIrrational, r::RoundingMode) where T<:Union{Float32,Float64} setprecision(BigFloat, 256) do T(BigFloat(x)::BigFloat, r) end @@ -106,11 +107,11 @@ end <=(x::AbstractFloat, y::AbstractIrrational) = x < y # Irrational vs Rational -@pure function rationalize(::Type{T}, x::AbstractIrrational; tol::Real=0) where T +@assume_effects :total function rationalize(::Type{T}, x::AbstractIrrational; tol::Real=0) where T return rationalize(T, big(x), tol=tol) end -@pure function lessrational(rx::Rational{<:Integer}, x::AbstractIrrational) - # an @pure version of `<` for determining if the rationalization of +@assume_effects :total function lessrational(rx::Rational{<:Integer}, x::AbstractIrrational) + # an @assume_effects :total version of `<` for determining if the rationalization of # an irrational number required rounding up or down return rx < big(x) end diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 9282bdcc91e73..b2ebb3f9d0d7e 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -218,7 +218,7 @@ function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names NamedTuple{names}(map(f, map(Tuple, (nt, nts...))...)) end -@pure function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) +@assume_effects :total function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) @nospecialize an bn names = Symbol[an...] for n in bn @@ -229,7 +229,7 @@ end (names...,) end -@pure function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) +@assume_effects :total function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple}) @nospecialize names a b bn = _nt_names(b) return Tuple{Any[ fieldtype(sym_in(names[n], bn) ? b : a, names[n]) for n in 1:length(names) ]...} @@ -321,7 +321,7 @@ get(f::Callable, nt::NamedTuple, key::Union{Integer, Symbol}) = isdefined(nt, ke tail(t::NamedTuple{names}) where names = NamedTuple{tail(names)}(t) front(t::NamedTuple{names}) where names = NamedTuple{front(names)}(t) -@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) +@assume_effects :total function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}}) @nospecialize an bn names = Symbol[] for n in an diff --git a/base/operators.jl b/base/operators.jl index c24a82d4fa4a5..e07911db86cc7 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -40,15 +40,8 @@ julia> supertype(Int32) Signed ``` """ -function supertype(T::DataType) - @_pure_meta - T.super -end - -function supertype(T::UnionAll) - @_pure_meta - UnionAll(T.var, supertype(T.body)) -end +supertype(T::DataType) = (@_total_meta; T.super) +supertype(T::UnionAll) = (@_total_meta; UnionAll(T.var, supertype(T.body))) ## generic comparison ## @@ -247,14 +240,8 @@ isunordered(x) = false isunordered(x::AbstractFloat) = isnan(x) isunordered(x::Missing) = true -function ==(T::Type, S::Type) - @_pure_meta - return ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0 -end -function !=(T::Type, S::Type) - @_pure_meta - return !(T == S) -end +==(T::Type, S::Type) = (@_total_meta; ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0) +!=(T::Type, S::Type) = (@_total_meta; !(T == S)) ==(T::TypeVar, S::Type) = false ==(T::Type, S::TypeVar) = false diff --git a/base/promotion.jl b/base/promotion.jl index 0ceb641e2f255..26205530dcd9a 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -10,9 +10,9 @@ they both inherit. """ typejoin() = Bottom typejoin(@nospecialize(t)) = t -typejoin(@nospecialize(t), ts...) = (@_pure_meta; typejoin(t, typejoin(ts...))) +typejoin(@nospecialize(t), ts...) = (@_total_meta; typejoin(t, typejoin(ts...))) function typejoin(@nospecialize(a), @nospecialize(b)) - @_pure_meta + @_total_meta if isa(a, TypeVar) return typejoin(a.ub, b) elseif isa(b, TypeVar) @@ -128,7 +128,7 @@ end # WARNING: this is wrong for some objects for which subtyping is broken # (Core.Compiler.isnotbrokensubtype), use only simple types for `b` function typesplit(@nospecialize(a), @nospecialize(b)) - @_pure_meta + @_total_may_throw_meta if a <: b return Bottom end @@ -180,7 +180,7 @@ function promote_typejoin_union(::Type{T}) where T end function typejoin_union_tuple(T::DataType) - @_pure_meta + @_total_may_throw_meta u = Base.unwrap_unionall(T) p = (u::DataType).parameters lr = length(p)::Int diff --git a/base/reflection.jl b/base/reflection.jl index 2713d2ea00514..a1eb5a9cfd74e 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -212,10 +212,7 @@ julia> hasfield(Foo, :x) false ``` """ -function hasfield(T::Type, name::Symbol) - @_pure_meta - return fieldindex(T, name, false) > 0 -end +hasfield(T::Type, name::Symbol) = fieldindex(T, name, false) > 0 """ nameof(t::DataType) -> Symbol @@ -360,7 +357,7 @@ Memory allocation minimum alignment for instances of this type. Can be called on any `isconcretetype`. """ function datatype_alignment(dt::DataType) - @_pure_meta + @_total_may_throw_meta dt.layout == C_NULL && throw(UndefRefError()) alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment return Int(alignment) @@ -377,7 +374,7 @@ LLT_ALIGN(x, sz) = (x + sz - 1) & -sz # amount of total space taken by T when stored in a container function aligned_sizeof(@nospecialize T::Type) - @_pure_meta + @_total_may_throw_meta if isbitsunion(T) _, sz, al = uniontype_layout(T) return LLT_ALIGN(sz, al) @@ -400,7 +397,7 @@ with no intervening padding bytes. Can be called on any `isconcretetype`. """ function datatype_haspadding(dt::DataType) - @_pure_meta + @_total_may_throw_meta dt.layout == C_NULL && throw(UndefRefError()) flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags return flags & 1 == 1 @@ -413,7 +410,7 @@ Return the number of fields known to this datatype's layout. Can be called on any `isconcretetype`. """ function datatype_nfields(dt::DataType) - @_pure_meta + @_total_may_throw_meta dt.layout == C_NULL && throw(UndefRefError()) return unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).nfields end @@ -425,7 +422,7 @@ Return whether instances of this type can contain references to gc-managed memor Can be called on any `isconcretetype`. """ function datatype_pointerfree(dt::DataType) - @_pure_meta + @_total_may_throw_meta dt.layout == C_NULL && throw(UndefRefError()) npointers = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).npointers return npointers == 0 @@ -441,7 +438,7 @@ Can be called on any `isconcretetype`. See also [`fieldoffset`](@ref). """ function datatype_fielddesc_type(dt::DataType) - @_pure_meta + @_total_may_throw_meta dt.layout == C_NULL && throw(UndefRefError()) flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags return (flags >> 1) & 3 @@ -511,8 +508,7 @@ true !!! compat "Julia 1.5" This function requires at least Julia 1.5. """ -ismutable(@nospecialize(x)) = (@_pure_meta; typeof(x).name.flags & 0x2 == 0x2) - +ismutable(@nospecialize(x)) = (@_total_meta; typeof(x).name.flags & 0x2 == 0x2) """ ismutabletype(T) -> Bool @@ -524,12 +520,12 @@ Determine whether type `T` was declared as a mutable type This function requires at least Julia 1.7. """ function ismutabletype(@nospecialize t) + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? return isa(t, DataType) && t.name.flags & 0x2 == 0x2 end - """ isstructtype(T) -> Bool @@ -537,7 +533,7 @@ Determine whether type `T` was declared as a struct type (i.e. using the `struct` or `mutable struct` keyword). """ function isstructtype(@nospecialize t) - @_pure_meta + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? isa(t, DataType) || return false @@ -552,7 +548,7 @@ Determine whether type `T` was declared as a primitive type (i.e. using the `primitive` keyword). """ function isprimitivetype(@nospecialize t) - @_pure_meta + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? isa(t, DataType) || return false @@ -583,14 +579,14 @@ julia> isbitstype(Complex) false ``` """ -isbitstype(@nospecialize t) = (@_pure_meta; isa(t, DataType) && (t.flags & 0x8) == 0x8) +isbitstype(@nospecialize t) = (@_total_meta; isa(t, DataType) && (t.flags & 0x8) == 0x8) """ isbits(x) Return `true` if `x` is an instance of an [`isbitstype`](@ref) type. """ -isbits(@nospecialize x) = (@_pure_meta; typeof(x).flags & 0x8 == 0x8) +isbits(@nospecialize x) = (@_total_meta; typeof(x).flags & 0x8 == 0x8) """ isdispatchtuple(T) @@ -599,7 +595,7 @@ Determine whether type `T` is a tuple "leaf type", meaning it could appear as a type signature in dispatch and has no subtypes (or supertypes) which could appear in a call. """ -isdispatchtuple(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && (t.flags & 0x4) == 0x4) +isdispatchtuple(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x4) == 0x4) iskindtype(@nospecialize t) = (t === DataType || t === UnionAll || t === Union || t === typeof(Bottom)) isconcretedispatch(@nospecialize t) = isconcretetype(t) && !iskindtype(t) @@ -642,7 +638,7 @@ julia> isconcretetype(Union{Int,String}) false ``` """ -isconcretetype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && (t.flags & 0x2) == 0x2) +isconcretetype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && (t.flags & 0x2) == 0x2) """ isabstracttype(T) @@ -660,7 +656,7 @@ false ``` """ function isabstracttype(@nospecialize(t)) - @_pure_meta + @_total_meta t = unwrap_unionall(t) # TODO: what to do for `Union`? return isa(t, DataType) && (t.name.flags & 0x1) == 0x1 @@ -672,7 +668,7 @@ end Determine whether type `T` has exactly one possible instance; for example, a struct type with no fields. """ -issingletontype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && isdefined(t, :instance)) +issingletontype(@nospecialize(t)) = (@_total_meta; isa(t, DataType) && isdefined(t, :instance)) """ typeintersect(T::Type, S::Type) @@ -680,9 +676,9 @@ issingletontype(@nospecialize(t)) = (@_pure_meta; isa(t, DataType) && isdefined( Compute a type that contains the intersection of `T` and `S`. Usually this will be the smallest such type or one close to it. """ -typeintersect(@nospecialize(a), @nospecialize(b)) = (@_pure_meta; ccall(:jl_type_intersection, Any, (Any, Any), a::Type, b::Type)) +typeintersect(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_intersection, Any, (Any, Any), a::Type, b::Type)) -morespecific(@nospecialize(a), @nospecialize(b)) = ccall(:jl_type_morespecific, Cint, (Any, Any), a, b) != 0 +morespecific(@nospecialize(a), @nospecialize(b)) = (@_total_meta; ccall(:jl_type_morespecific, Cint, (Any, Any), a::Type, b::Type) != 0) """ fieldoffset(type, i) @@ -710,7 +706,7 @@ julia> structinfo(Base.Filesystem.StatStruct) (0x0000000000000060, :ctime, Float64) ``` """ -fieldoffset(x::DataType, idx::Integer) = (@_pure_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx)) +fieldoffset(x::DataType, idx::Integer) = (@_total_may_throw_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx)) """ fieldtype(T, name::Symbol | index::Int) @@ -756,6 +752,7 @@ julia> Base.fieldindex(Foo, :z, false) ``` """ function fieldindex(T::DataType, name::Symbol, err::Bool=true) + @_total_may_throw_meta return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1) end @@ -767,7 +764,10 @@ function fieldindex(t::UnionAll, name::Symbol, err::Bool=true) return fieldindex(t, name, err) end -argument_datatype(@nospecialize t) = ccall(:jl_argument_datatype, Any, (Any,), t) +function argument_datatype(@nospecialize t) + @_total_meta + return ccall(:jl_argument_datatype, Any, (Any,), t)::Union{Nothing,DataType} +end """ fieldcount(t::Type) @@ -776,12 +776,12 @@ Get the number of fields that an instance of the given type would have. An error is thrown if the type is too abstract to determine this. """ function fieldcount(@nospecialize t) + @_total_may_throw_meta if t isa UnionAll || t isa Union t = argument_datatype(t) if t === nothing throw(ArgumentError("type does not have a definite number of fields")) end - t = t::DataType elseif t == Union{} throw(ArgumentError("The empty type does not have a well-defined number of fields since it does not have instances.")) end @@ -828,7 +828,7 @@ julia> fieldtypes(Foo) (Int64, String) ``` """ -fieldtypes(T::Type) = ntupleany(i -> fieldtype(T, i), fieldcount(T)) +fieldtypes(T::Type) = (@_total_may_throw_meta; ntupleany(i -> fieldtype(T, i), fieldcount(T))) # return all instances, for types that can be enumerated diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 3b54ed04089cd..7dc6607285fd0 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -707,7 +707,7 @@ function CyclePadding(T::DataType) end using .Iterators: Stateful -@pure function array_subpadding(S, T) +@assume_effects :total function array_subpadding(S, T) checked_size = 0 lcm_size = lcm(sizeof(S), sizeof(T)) s, t = Stateful{<:Any, Any}(CyclePadding(S)), diff --git a/base/strings/string.jl b/base/strings/string.jl index 3053c82ad2da1..e44746f9834d9 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -93,7 +93,7 @@ end Create a new `String` from an existing `AbstractString`. """ String(s::AbstractString) = print_to_string(s) -@pure String(s::Symbol) = unsafe_string(unsafe_convert(Ptr{UInt8}, s)) +@assume_effects :total String(s::Symbol) = unsafe_string(unsafe_convert(Ptr{UInt8}, s)) unsafe_wrap(::Type{Vector{UInt8}}, s::String) = ccall(:jl_string_to_array, Ref{Vector{UInt8}}, (Any,), s) diff --git a/base/tuple.jl b/base/tuple.jl index e2b4d9ee745e6..484a5d24e67df 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -206,7 +206,7 @@ function eltype(t::Type{<:Tuple{Vararg{E}}}) where {E} end eltype(t::Type{<:Tuple}) = _compute_eltype(t) function _tuple_unique_fieldtypes(@nospecialize t) - @_pure_meta + @_total_meta types = IdSet() t´ = unwrap_unionall(t) # Given t = Tuple{Vararg{S}} where S<:Real, the various @@ -223,7 +223,7 @@ function _tuple_unique_fieldtypes(@nospecialize t) return Core.svec(types...) end function _compute_eltype(@nospecialize t) - @_pure_meta # TODO: the compiler shouldn't need this + @_total_meta # TODO: the compiler shouldn't need this types = _tuple_unique_fieldtypes(t) return afoldl(types...) do a, b # if we've already reached Any, it can't widen any more @@ -345,7 +345,7 @@ fill_to_length(t::Tuple{}, val, ::Val{2}) = (val, val) if nameof(@__MODULE__) === :Base function tuple_type_tail(T::Type) - @_pure_meta # TODO: this method is wrong (and not @pure) + @_total_may_throw_meta # TODO: this method is wrong (and not :total_may_throw) if isa(T, UnionAll) return UnionAll(T.var, tuple_type_tail(T.body)) elseif isa(T, Union) @@ -583,18 +583,14 @@ _tuple_any(f::Function, tf::Bool) = tf # a version of `in` esp. for NamedTuple, to make it pure, and not compiled for each tuple length -function sym_in(x::Symbol, itr::Tuple{Vararg{Symbol}}) - @nospecialize itr - @_pure_meta +function sym_in(x::Symbol, @nospecialize itr::Tuple{Vararg{Symbol}}) + @_total_meta for y in itr y === x && return true end return false end -function in(x::Symbol, itr::Tuple{Vararg{Symbol}}) - @nospecialize itr - return sym_in(x, itr) -end +in(x::Symbol, @nospecialize itr::Tuple{Vararg{Symbol}}) = sym_in(x, itr) """ diff --git a/stdlib/LinearAlgebra/src/LinearAlgebra.jl b/stdlib/LinearAlgebra/src/LinearAlgebra.jl index f4460a274d325..ec93556988485 100644 --- a/stdlib/LinearAlgebra/src/LinearAlgebra.jl +++ b/stdlib/LinearAlgebra/src/LinearAlgebra.jl @@ -17,7 +17,7 @@ import Base: USE_BLAS64, abs, acos, acosh, acot, acoth, acsc, acsch, adjoint, as sincos, sinh, size, sqrt, strides, stride, tan, tanh, transpose, trunc, typed_hcat, vec, zero using Base: IndexLinear, promote_eltype, promote_op, promote_typeof, - @propagate_inbounds, @pure, reduce, typed_hvcat, typed_vcat, require_one_based_indexing, + @propagate_inbounds, reduce, typed_hvcat, typed_vcat, require_one_based_indexing, splat using Base.Broadcast: Broadcasted, broadcasted using OpenBLAS_jll diff --git a/test/broadcast.jl b/test/broadcast.jl index 4b8217ea618ec..113614505ba74 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -1082,3 +1082,8 @@ end y = randn(2) @inferred(test(x, y)) == [0, 0] end + +# test that `Broadcast` definition is defined as total and eligible for concrete evaluation +import Base.Broadcast: BroadcastStyle, DefaultArrayStyle +@test Base.infer_effects(BroadcastStyle, (DefaultArrayStyle{1},DefaultArrayStyle{2},)) |> + Core.Compiler.is_concrete_eval_eligible