Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace @pure annotations in Base with effect settings #44776

Merged
merged 1 commit into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
4 changes: 2 additions & 2 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion base/c.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
53 changes: 27 additions & 26 deletions base/compiler/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -887,17 +890,15 @@ 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())
end
# 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 ####
Expand Down
4 changes: 2 additions & 2 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
30 changes: 29 additions & 1 deletion base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we delete _pure_meta now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and @pure?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's technically internal so we should be able to do so, but I suspect there's a decent amount of package code that uses it, so we should probably at least go through some kind of deprecation process. Or maybe just make PRs to affected packages to not use it if VERSION ≥ v"1.9".

Copy link
Member

@simeonschaub simeonschaub Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also be sneaky and make it a no-op and then see if people actually notice...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is anyone using it correctly? I don't think @vtjnash has added uses to packages, and as far as I understand, he is the only person allowed to use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now all of these internal macros are no-op when used outside of Base ;)

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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe name this _effect_total? The current name seems fairly confusing.

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)
Expand Down
8 changes: 3 additions & 5 deletions base/intfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions base/irrationals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

horribly, the return value also includes novel memory allocation and depends on the global runtime state of the DEFAULT_PRECISION

@assume_effects :total function Rational{T}(x::AbstractIrrational) where T<:Integer
o = precision(BigFloat)
p = 256
while true
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) ]...}
Expand Down Expand Up @@ -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
Expand Down
21 changes: 4 additions & 17 deletions base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 ##

Expand Down Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions base/promotion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading