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

effect overrides: Add notaskstate & refactor slightly #45448

Merged
merged 2 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1999,7 +1999,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
effects.nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
effects.terminates_globally ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
#=nonoverlayed=#true,
#=notaskstate=#TRISTATE_UNKNOWN
effects.notaskstate ? ALWAYS_TRUE : TRISTATE_UNKNOWN
))
else
tristate_merge!(sv, EFFECTS_UNKNOWN)
Expand Down
3 changes: 3 additions & 0 deletions base/compiler/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ function adjust_effects(sv::InferenceState)
if is_effect_overridden(override, :terminates_globally)
ipo_effects = Effects(ipo_effects; terminates=ALWAYS_TRUE)
end
if is_effect_overridden(override, :notaskstate)
ipo_effects = Effects(ipo_effects; notaskstate=ALWAYS_TRUE)
end
end

return ipo_effects
Expand Down
6 changes: 5 additions & 1 deletion base/compiler/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ is_terminates(effects::Effects) = effects.terminates === ALWAYS_TRUE
is_notaskstate(effects::Effects) = effects.notaskstate === ALWAYS_TRUE
is_nonoverlayed(effects::Effects) = effects.nonoverlayed

# implies :notaskstate, but not explicitly checked here
is_concrete_eval_eligible(effects::Effects) =
is_consistent(effects) &&
is_effect_free(effects) &&
Expand Down Expand Up @@ -179,6 +180,7 @@ struct EffectsOverride
nothrow::Bool
terminates_globally::Bool
terminates_locally::Bool
notaskstate::Bool
end

function encode_effects_override(eo::EffectsOverride)
Expand All @@ -188,6 +190,7 @@ function encode_effects_override(eo::EffectsOverride)
eo.nothrow && (e |= 0x04)
eo.terminates_globally && (e |= 0x08)
eo.terminates_locally && (e |= 0x10)
eo.notaskstate && (e |= 0x20)
return e
end

Expand All @@ -197,7 +200,8 @@ function decode_effects_override(e::UInt8)
(e & 0x02) != 0x00,
(e & 0x04) != 0x00,
(e & 0x08) != 0x00,
(e & 0x10) != 0x00)
(e & 0x10) != 0x00,
(e & 0x20) != 0x00)
end

"""
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
@_total_may_throw_meta
@_foldable_meta
Tuple{S, T.parameters...}
end
function parameter_upper_bound(t::UnionAll, idx)
@_total_may_throw_meta
@_foldable_meta
return rewrap_unionall((unwrap_unionall(t)::DataType).parameters[idx], t)
end

Expand Down
10 changes: 6 additions & 4 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,18 @@ macro _total_meta()
#=:effect_free=#true,
#=:nothrow=#true,
#=:terminates_globally=#true,
#=:terminates_locally=#false))
#=:terminates_locally=#false,
#=:notaskstate=#true))
end
# can be used in place of `@assume_effects :total_may_throw` (supposed to be used for bootstrapping)
macro _total_may_throw_meta()
# can be used in place of `@assume_effects :foldable` (supposed to be used for bootstrapping)
macro _foldable_meta()
return _is_internal(__module__) && Expr(:meta, Expr(:purity,
#=:consistent=#true,
#=:effect_free=#true,
#=:nothrow=#false,
#=:terminates_globally=#true,
#=:terminates_locally=#false))
#=:terminates_locally=#false,
#=:notaskstate=#false))
end

# another version of inlining that propagates an inbounds context
Expand Down
129 changes: 93 additions & 36 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ julia> code_typed() do
1 ─ return 479001600
) => Int64

julia> Base.@assume_effects :total_may_throw @ccall jl_type_intersection(Vector{Int}::Any, Vector{<:Integer}::Any)::Any
julia> Base.@assume_effects :total !:nothrow @ccall jl_type_intersection(Vector{Int}::Any, Vector{<:Integer}::Any)::Any
Vector{Int64} (alias for Array{Int64, 1})
```

Expand Down Expand Up @@ -535,71 +535,128 @@ non-termination if the method calls some other method that does not terminate.
`:terminates_globally` implies `:terminates_locally`.

---
# `:total`
# `:notaskstate`

The `:notaskstate` setting asserts that the method does not use or modify the
local task state (task local storage, RNG state, etc.) and may thus be safely
moved between tasks without observable results.

!!! note
The implementation of exception handling makes use of state stored in the
task object. However, this state is currently not considered to be within
the scope of `:notaskstate` and is tracked separately using the `:nothrow`
effect.

!!! note
The `:notaskstate` assertion concerns the state of the *currently running task*.
If a reference to a `Task` object is obtained by some other means that
does not consider which task is *currently* running, the `:notaskstate`
effect need not be tainted. This is true, even if said task object happens
to be `===` to the currently running task.

!!! note
Access to task state usually also results in the tainting of other effects,
such as `:effect_free` (if task state is modified) or `:consistent` (if
task state is used in the computation of the result). In particular,
code that is not `:notaskstate`, but is `:effect_free` and `:consistent`
may still be dead-code-eliminated and thus promoted to `:total`.

---
# `:foldable`

This setting is a convenient shortcut for the set of effects that the compiler
requires to be guaranteed to constant fold a call at compile time. It is
currently equivalent to the following `setting`s:

This `setting` combines the following other assertions:
- `:consistent`
- `:effect_free`
- `:nothrow`
- `:terminates_globally`
and is a convenient shortcut.

!!! note
This list in particular does not include `:nothrow`. The compiler will still
attempt constant propagation and note any thrown error at compile time. Note
however, that by the `:consistent`-cy requirements, any such annotated call
must consistently throw given the same argument values.

---
# `:total_may_throw`
# `:total`

This `setting` combines the following other assertions:
This `setting` is the maximum possible set of effects. It currently implies
the following other `setting`s:
- `:consistent`
- `:effect_free`
- `:nothrow`
- `:terminates_globally`
and is a convenient shortcut.
- `:notaskstate`

!!! note
This setting is particularly useful since it allows the compiler to evaluate a call of
the applied method when all the call arguments are fully known to be constant, no matter
if the call results in an error or not.

`@assume_effects :total_may_throw` is similar to [`@pure`](@ref) 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
`@pure` should always be `:total` or `:total_may_throw`.
Another advantage is that effects introduced by `@assume_effects` are propagated to
callers interprocedurally while a purity defined by `@pure` is not.
!!! warning
`:total` is a very strong assertion and will likely gain additional semantics
in future versions of julia (e.g. if additional effects are added and included
in the definition of `:total`). As a result, it should be used with care.
Whenever possible, prefer to use the minimum possible set of specific effect
assertions required for a particular application. In cases where a large
number of effect overrides apply to a set of functions, a custom macro is
recommended over the use of `:total`.

## Negated effects

Effect names may be prefixed by `!` to indicate that the effect should be removed
from an earlier meta effect. For example, `:total !:nothrow` indicates that while
the call is generally total, it may however throw.

## Comparison to @pure
`@assume_effects :foldable` is similar to [`@pure`](@ref) 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
`@pure` should always be at least `:foldable`.
Another advantage is that effects introduced by `@assume_effects` are propagated to
callers interprocedurally while a purity defined by `@pure` is not.
"""
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
(consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate) =
(false, false, false, false, false, false, false)
for org_setting in args[1:end-1]
(setting, val) = compute_setting(org_setting)
if setting === :consistent
consistent = true
consistent = val
elseif setting === :effect_free
effect_free = true
effect_free = val
elseif setting === :nothrow
nothrow = true
nothrow = val
elseif setting === :terminates_globally
terminates_globally = true
terminates_globally = val
elseif setting === :terminates_locally
terminates_locally = true
terminates_locally = val
elseif setting === :notaskstate
notaskstate = val
elseif setting === :foldable
consistent = effect_free = terminates_globally = val
elseif setting === :total
consistent = effect_free = nothrow = terminates_globally = true
elseif setting === :total_may_throw
consistent = effect_free = terminates_globally = true
consistent = effect_free = nothrow = terminates_globally = notaskstate = val
else
throw(ArgumentError("@assume_effects $setting not supported"))
throw(ArgumentError("@assume_effects $org_setting not supported"))
end
end
ex = args[end]
isa(ex, Expr) || throw(ArgumentError("Bad expression `$ex` in `@assume_effects [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
consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate
)))
return esc(ex)
end
return esc(pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally))
return esc(pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate))
end

function compute_setting(@nospecialize(setting), val::Bool=true)
if isexpr(setting, :call) && setting.args[1] === :(!)
return compute_setting(setting.args[2], !val)
elseif isa(setting, QuoteNode)
return compute_setting(setting.value, val)
else
return (setting, val)
end
end

"""
Expand Down
4 changes: 2 additions & 2 deletions base/promotion.jl
Original file line number Diff line number Diff line change
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))
@_total_may_throw_meta
@_foldable_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)
@_total_may_throw_meta
@_foldable_meta
u = Base.unwrap_unionall(T)
p = (u::DataType).parameters
lr = length(p)::Int
Expand Down
20 changes: 10 additions & 10 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ Memory allocation minimum alignment for instances of this type.
Can be called on any `isconcretetype`.
"""
function datatype_alignment(dt::DataType)
@_total_may_throw_meta
@_foldable_meta
dt.layout == C_NULL && throw(UndefRefError())
alignment = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).alignment
return Int(alignment)
Expand All @@ -374,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)
@_total_may_throw_meta
@_foldable_meta
if isbitsunion(T)
_, sz, al = uniontype_layout(T)
return LLT_ALIGN(sz, al)
Expand All @@ -397,7 +397,7 @@ with no intervening padding bytes.
Can be called on any `isconcretetype`.
"""
function datatype_haspadding(dt::DataType)
@_total_may_throw_meta
@_foldable_meta
dt.layout == C_NULL && throw(UndefRefError())
flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags
return flags & 1 == 1
Expand All @@ -410,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)
@_total_may_throw_meta
@_foldable_meta
dt.layout == C_NULL && throw(UndefRefError())
return unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).nfields
end
Expand All @@ -422,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)
@_total_may_throw_meta
@_foldable_meta
dt.layout == C_NULL && throw(UndefRefError())
npointers = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).npointers
return npointers == 0
Expand All @@ -438,7 +438,7 @@ Can be called on any `isconcretetype`.
See also [`fieldoffset`](@ref).
"""
function datatype_fielddesc_type(dt::DataType)
@_total_may_throw_meta
@_foldable_meta
dt.layout == C_NULL && throw(UndefRefError())
flags = unsafe_load(convert(Ptr{DataTypeLayout}, dt.layout)).flags
return (flags >> 1) & 3
Expand Down Expand Up @@ -706,7 +706,7 @@ julia> structinfo(Base.Filesystem.StatStruct)
(0x0000000000000060, :ctime, Float64)
```
"""
fieldoffset(x::DataType, idx::Integer) = (@_total_may_throw_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx))
fieldoffset(x::DataType, idx::Integer) = (@_foldable_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx))

"""
fieldtype(T, name::Symbol | index::Int)
Expand Down Expand Up @@ -752,7 +752,7 @@ julia> Base.fieldindex(Foo, :z, false)
```
"""
function fieldindex(T::DataType, name::Symbol, err::Bool=true)
@_total_may_throw_meta
@_foldable_meta
return Int(ccall(:jl_field_index, Cint, (Any, Any, Cint), T, name, err)+1)
end

Expand All @@ -776,7 +776,7 @@ 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
@_foldable_meta
if t isa UnionAll || t isa Union
t = argument_datatype(t)
if t === nothing
Expand Down Expand Up @@ -828,7 +828,7 @@ julia> fieldtypes(Foo)
(Int64, String)
```
"""
fieldtypes(T::Type) = (@_total_may_throw_meta; ntupleany(i -> fieldtype(T, i), fieldcount(T)))
fieldtypes(T::Type) = (@_foldable_meta; ntupleany(i -> fieldtype(T, i), fieldcount(T)))

# return all instances, for types that can be enumerated

Expand Down
2 changes: 1 addition & 1 deletion base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ fill_to_length(t::Tuple{}, val, ::Val{2}) = (val, val)
if nameof(@__MODULE__) === :Base

function tuple_type_tail(T::Type)
@_total_may_throw_meta # TODO: this method is wrong (and not :total_may_throw)
@_foldable_meta # TODO: this method is wrong (and not :foldable)
if isa(T, UnionAll)
return UnionAll(T.var, tuple_type_tail(T.body))
elseif isa(T, Union)
Expand Down
1 change: 1 addition & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ typedef union __jl_purity_overrides_t {
// is guaranteed to terminate, but does not make
// assertions about any called functions.
uint8_t ipo_terminates_locally : 1;
uint8_t ipo_notaskstate : 1;
} overrides;
uint8_t bits;
} _jl_purity_overrides_t;
Expand Down
Loading