Skip to content

Commit 0953fa9

Browse files
committed
replace @pure annotations in Base with effect settings
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.
1 parent 6d78404 commit 0953fa9

19 files changed

+125
-109
lines changed

base/Base.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ include("abstractarraymath.jl")
183183
include("arraymath.jl")
184184

185185
# SIMD loops
186-
@pure sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop
186+
sizeof(s::String) = Core.sizeof(s) # needed by gensym as called from simdloop
187187
include("simdloop.jl")
188188
using .SimdLoop
189189

base/array.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ size(a::Array{<:Any,N}) where {N} = (@inline; ntuple(M -> size(a, M), Val(N))::D
154154

155155
asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1)...)
156156

157-
allocatedinline(T::Type) = (@_pure_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0))
157+
allocatedinline(T::Type) = (@_total_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0))
158158

159159
"""
160160
Base.isbitsunion(::Type{T})

base/broadcast.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Module containing the broadcasting implementation.
88
module Broadcast
99

1010
using .Base.Cartesian
11-
using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, promote_typejoin_union, @pure,
11+
using .Base: Indices, OneTo, tail, to_shape, isoperator, promote_typejoin, promote_typejoin_union,
1212
_msk_end, unsafe_bitgetindex, bitcache_chunks, bitcache_size, dumpbitcache, unalias, negate
1313
import .Base: copy, copyto!, axes
1414
export broadcast, broadcast!, BroadcastStyle, broadcast_axes, broadcastable, dotview, @__dot__, BroadcastFunction
@@ -137,7 +137,7 @@ BroadcastStyle(a::AbstractArrayStyle, ::Style{Tuple}) = a
137137
BroadcastStyle(::A, ::A) where A<:ArrayStyle = A()
138138
BroadcastStyle(::ArrayStyle, ::ArrayStyle) = Unknown()
139139
BroadcastStyle(::A, ::A) where A<:AbstractArrayStyle = A()
140-
Base.@pure function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N}
140+
function BroadcastStyle(a::A, b::B) where {A<:AbstractArrayStyle{M},B<:AbstractArrayStyle{N}} where {M,N}
141141
if Base.typename(A) === Base.typename(B)
142142
return A(Val(max(M, N)))
143143
end

base/c.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,6 @@ macro ccall(expr)
734734
return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...)
735735
end
736736

737-
macro ccall_effects(effects, expr)
737+
macro ccall_effects(effects::UInt8, expr)
738738
return ccall_macro_lower((:ccall, effects), ccall_macro_parse(expr)...)
739739
end

base/compiler/abstractinterpretation.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1950,7 +1950,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
19501950
end
19511951
cconv = e.args[5]
19521952
if isa(cconv, QuoteNode) && isa(cconv.value, Tuple{Symbol, UInt8})
1953-
effects = cconv.value[2]
1953+
effects = cconv.value[2]::UInt8
19541954
effects = decode_effects_override(effects)
19551955
tristate_merge!(sv, Effects(
19561956
effects.consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN,

base/compiler/typeinfer.jl

+27-26
Original file line numberDiff line numberDiff line change
@@ -427,13 +427,34 @@ function cycle_fix_limited(@nospecialize(typ), sv::InferenceState)
427427
return typ
428428
end
429429

430-
function rt_adjust_effects(@nospecialize(rt), ipo_effects::Effects)
430+
function adjust_effects(sv::InferenceState)
431+
ipo_effects = Effects(sv)
432+
431433
# Always throwing an error counts or never returning both count as consistent,
432434
# but we don't currently model idempontency using dataflow, so we don't notice.
433435
# Fix that up here to improve precision.
434-
if !ipo_effects.inbounds_taints_consistency && rt === Union{}
435-
return Effects(ipo_effects; consistent=ALWAYS_TRUE)
436+
if !ipo_effects.inbounds_taints_consistency && sv.bestguess === Union{}
437+
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
438+
end
439+
440+
# override the analyzed effects using manually annotated effect settings
441+
def = sv.linfo.def
442+
if isa(def, Method)
443+
override = decode_effects_override(def.purity)
444+
if is_effect_overridden(override, :consistent)
445+
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
446+
end
447+
if is_effect_overridden(override, :effect_free)
448+
ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE)
449+
end
450+
if is_effect_overridden(override, :nothrow)
451+
ipo_effects = Effects(ipo_effects; nothrow=ALWAYS_TRUE)
452+
end
453+
if is_effect_overridden(override, :terminates_globally)
454+
ipo_effects = Effects(ipo_effects; terminates=ALWAYS_TRUE)
455+
end
436456
end
457+
437458
return ipo_effects
438459
end
439460

@@ -495,25 +516,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter)
495516
end
496517
me.result.valid_worlds = me.valid_worlds
497518
me.result.result = me.bestguess
498-
ipo_effects = rt_adjust_effects(me.bestguess, me.ipo_effects)
499-
# override the analyzed effects using manually annotated effect settings
500-
def = me.linfo.def
501-
if isa(def, Method)
502-
override = decode_effects_override(def.purity)
503-
if is_effect_overridden(override, :consistent)
504-
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
505-
end
506-
if is_effect_overridden(override, :effect_free)
507-
ipo_effects = Effects(ipo_effects; effect_free=ALWAYS_TRUE)
508-
end
509-
if is_effect_overridden(override, :nothrow)
510-
ipo_effects = Effects(ipo_effects; nothrow=ALWAYS_TRUE)
511-
end
512-
if is_effect_overridden(override, :terminates_globally)
513-
ipo_effects = Effects(ipo_effects; terminates=ALWAYS_TRUE)
514-
end
515-
end
516-
me.ipo_effects = me.result.ipo_effects = ipo_effects
519+
me.ipo_effects = me.result.ipo_effects = adjust_effects(me)
517520
validate_code_in_debug_mode(me.linfo, me.src, "inferred")
518521
nothing
519522
end
@@ -887,17 +890,15 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize
887890
typeinf(interp, frame)
888891
update_valid_age!(frame, caller)
889892
edge = frame.inferred ? mi : nothing
890-
edge_effects = rt_adjust_effects(frame.bestguess, Effects(frame))
891-
return EdgeCallResult(frame.bestguess, edge, edge_effects)
893+
return EdgeCallResult(frame.bestguess, edge, Effects(frame)) # effects are adjusted already within `finish`
892894
elseif frame === true
893895
# unresolvable cycle
894896
return EdgeCallResult(Any, nothing, Effects())
895897
end
896898
# return the current knowledge about this cycle
897899
frame = frame::InferenceState
898900
update_valid_age!(frame, caller)
899-
edge_effects = rt_adjust_effects(frame.bestguess, Effects(frame))
900-
return EdgeCallResult(frame.bestguess, nothing, edge_effects)
901+
return EdgeCallResult(frame.bestguess, nothing, adjust_effects(frame))
901902
end
902903

903904
#### entry points for inferring a MethodInstance given a type signature ####

base/deprecated.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ getindex(match::Core.MethodMatch, field::Int) =
251251
tuple_type_head(T::Type) = fieldtype(T, 1)
252252
tuple_type_cons(::Type, ::Type{Union{}}) = Union{}
253253
function tuple_type_cons(::Type{S}, ::Type{T}) where T<:Tuple where S
254-
@_pure_meta
254+
@_total_may_throw_meta
255255
Tuple{S, T.parameters...}
256256
end
257257
function parameter_upper_bound(t::UnionAll, idx)
258-
@_pure_meta
258+
@_total_may_throw_meta
259259
return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t)
260260
end
261261

base/essentials.jl

+29-1
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,37 @@ macro isdefined(s::Symbol)
151151
return Expr(:escape, Expr(:isdefined, s))
152152
end
153153

154+
function _is_internal(__module__)
155+
if ccall(:jl_base_relative_to, Any, (Any,), __module__)::Module === Core.Compiler ||
156+
nameof(__module__) === :Base
157+
return true
158+
end
159+
return false
160+
end
161+
162+
# can be used in place of `@pure` (supposed to be used for bootstrapping)
154163
macro _pure_meta()
155-
return Expr(:meta, :pure)
164+
return _is_internal(__module__) && Expr(:meta, :pure)
165+
end
166+
# can be used in place of `@assume_effects :total` (supposed to be used for bootstrapping)
167+
macro _total_meta()
168+
return _is_internal(__module__) && Expr(:meta, Expr(:purity,
169+
#=:consistent=#true,
170+
#=:effect_free=#true,
171+
#=:nothrow=#true,
172+
#=:terminates_globally=#true,
173+
#=:terminates_locally=#false))
174+
end
175+
# can be used in place of `@assume_effects :total_may_throw` (supposed to be used for bootstrapping)
176+
macro _total_may_throw_meta()
177+
return _is_internal(__module__) && Expr(:meta, Expr(:purity,
178+
#=:consistent=#true,
179+
#=:effect_free=#true,
180+
#=:nothrow=#false,
181+
#=:terminates_globally=#true,
182+
#=:terminates_locally=#false))
156183
end
184+
157185
# another version of inlining that propagates an inbounds context
158186
macro _propagate_inbounds_meta()
159187
return Expr(:meta, :inline, :propagate_inbounds)

base/intfuncs.jl

+3-5
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,9 @@ end
5858

5959
# binary GCD (aka Stein's) algorithm
6060
# about 1.7x (2.1x) faster for random Int64s (Int128s)
61-
# Unfortunately, we need to manually annotate this as `@pure` to work around #41694. Since
62-
# this is used in the Rational constructor, constant prop is something we do care about here.
63-
# This does call generic functions, so it might not be completely sound, but since `_gcd` is
64-
# restricted to BitIntegers, it is probably fine in practice.
65-
@pure function _gcd(a::T, b::T) where T<:BitInteger
61+
# Unfortunately, we need to manually annotate this as `@assume_effects :terminates_locally` to work around #41694.
62+
# Since this is used in the Rational constructor, constant folding is something we do care about here.
63+
@assume_effects :terminates_locally function _gcd(a::T, b::T) where T<:BitInteger
6664
za = trailing_zeros(a)
6765
zb = trailing_zeros(b)
6866
k = min(za, zb)

base/irrationals.jl

+6-5
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ AbstractFloat(x::AbstractIrrational) = Float64(x)::Float64
4848
Float16(x::AbstractIrrational) = Float16(Float32(x)::Float32)
4949
Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x))
5050

51-
@pure function Rational{T}(x::AbstractIrrational) where T<:Integer
51+
# XXX this may change `DEFAULT_PRECISION`, thus not effect free
52+
@assume_effects :total function Rational{T}(x::AbstractIrrational) where T<:Integer
5253
o = precision(BigFloat)
5354
p = 256
5455
while true
@@ -64,7 +65,7 @@ Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x))
6465
end
6566
Rational{BigInt}(x::AbstractIrrational) = throw(ArgumentError("Cannot convert an AbstractIrrational to a Rational{BigInt}: use rationalize(BigInt, x) instead"))
6667

67-
@pure function (t::Type{T})(x::AbstractIrrational, r::RoundingMode) where T<:Union{Float32,Float64}
68+
@assume_effects :total function (t::Type{T})(x::AbstractIrrational, r::RoundingMode) where T<:Union{Float32,Float64}
6869
setprecision(BigFloat, 256) do
6970
T(BigFloat(x)::BigFloat, r)
7071
end
@@ -106,11 +107,11 @@ end
106107
<=(x::AbstractFloat, y::AbstractIrrational) = x < y
107108

108109
# Irrational vs Rational
109-
@pure function rationalize(::Type{T}, x::AbstractIrrational; tol::Real=0) where T
110+
@assume_effects :total function rationalize(::Type{T}, x::AbstractIrrational; tol::Real=0) where T
110111
return rationalize(T, big(x), tol=tol)
111112
end
112-
@pure function lessrational(rx::Rational{<:Integer}, x::AbstractIrrational)
113-
# an @pure version of `<` for determining if the rationalization of
113+
@assume_effects :total function lessrational(rx::Rational{<:Integer}, x::AbstractIrrational)
114+
# an @assume_effects :total version of `<` for determining if the rationalization of
114115
# an irrational number required rounding up or down
115116
return rx < big(x)
116117
end

base/namedtuple.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ function map(f, nt::NamedTuple{names}, nts::NamedTuple...) where names
218218
NamedTuple{names}(map(f, map(Tuple, (nt, nts...))...))
219219
end
220220

221-
@pure function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
221+
@assume_effects :total function merge_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
222222
@nospecialize an bn
223223
names = Symbol[an...]
224224
for n in bn
@@ -229,7 +229,7 @@ end
229229
(names...,)
230230
end
231231

232-
@pure function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple})
232+
@assume_effects :total function merge_types(names::Tuple{Vararg{Symbol}}, a::Type{<:NamedTuple}, b::Type{<:NamedTuple})
233233
@nospecialize names a b
234234
bn = _nt_names(b)
235235
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
321321
tail(t::NamedTuple{names}) where names = NamedTuple{tail(names)}(t)
322322
front(t::NamedTuple{names}) where names = NamedTuple{front(names)}(t)
323323

324-
@pure function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
324+
@assume_effects :total function diff_names(an::Tuple{Vararg{Symbol}}, bn::Tuple{Vararg{Symbol}})
325325
@nospecialize an bn
326326
names = Symbol[]
327327
for n in an

base/operators.jl

+4-17
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,8 @@ julia> supertype(Int32)
4040
Signed
4141
```
4242
"""
43-
function supertype(T::DataType)
44-
@_pure_meta
45-
T.super
46-
end
47-
48-
function supertype(T::UnionAll)
49-
@_pure_meta
50-
UnionAll(T.var, supertype(T.body))
51-
end
43+
supertype(T::DataType) = (@_total_meta; T.super)
44+
supertype(T::UnionAll) = (@_total_meta; UnionAll(T.var, supertype(T.body)))
5245

5346
## generic comparison ##
5447

@@ -247,14 +240,8 @@ isunordered(x) = false
247240
isunordered(x::AbstractFloat) = isnan(x)
248241
isunordered(x::Missing) = true
249242

250-
function ==(T::Type, S::Type)
251-
@_pure_meta
252-
return ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0
253-
end
254-
function !=(T::Type, S::Type)
255-
@_pure_meta
256-
return !(T == S)
257-
end
243+
==(T::Type, S::Type) = (@_total_meta; ccall(:jl_types_equal, Cint, (Any, Any), T, S) != 0)
244+
!=(T::Type, S::Type) = (@_total_meta; !(T == S))
258245
==(T::TypeVar, S::Type) = false
259246
==(T::Type, S::TypeVar) = false
260247

base/promotion.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ they both inherit.
1010
"""
1111
typejoin() = Bottom
1212
typejoin(@nospecialize(t)) = t
13-
typejoin(@nospecialize(t), ts...) = (@_pure_meta; typejoin(t, typejoin(ts...)))
13+
typejoin(@nospecialize(t), ts...) = (@_total_meta; typejoin(t, typejoin(ts...)))
1414
function typejoin(@nospecialize(a), @nospecialize(b))
15-
@_pure_meta
15+
@_total_meta
1616
if isa(a, TypeVar)
1717
return typejoin(a.ub, b)
1818
elseif isa(b, TypeVar)
@@ -128,7 +128,7 @@ end
128128
# WARNING: this is wrong for some objects for which subtyping is broken
129129
# (Core.Compiler.isnotbrokensubtype), use only simple types for `b`
130130
function typesplit(@nospecialize(a), @nospecialize(b))
131-
@_pure_meta
131+
@_total_may_throw_meta
132132
if a <: b
133133
return Bottom
134134
end
@@ -180,7 +180,7 @@ function promote_typejoin_union(::Type{T}) where T
180180
end
181181

182182
function typejoin_union_tuple(T::DataType)
183-
@_pure_meta
183+
@_total_may_throw_meta
184184
u = Base.unwrap_unionall(T)
185185
p = (u::DataType).parameters
186186
lr = length(p)::Int

0 commit comments

Comments
 (0)