Skip to content

add new :total_may_throw utility setting for @assume_effects #44775

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

Merged
merged 1 commit into from
Apr 1, 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/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter,
isoverlayed(method_table(interp)) && !is_nonoverlayed(result.edge_effects) && return false
return f !== nothing &&
result.edge !== nothing &&
is_total_or_error(result.edge_effects) &&
is_concrete_eval_eligible(result.edge_effects) &&
is_all_const_arg(arginfo)
end

Expand Down
4 changes: 2 additions & 2 deletions base/compiler/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ is_nothrow(effects::Effects) = effects.nothrow === ALWAYS_TRUE
is_terminates(effects::Effects) = effects.terminates === ALWAYS_TRUE
is_nonoverlayed(effects::Effects) = effects.nonoverlayed

is_total_or_error(effects::Effects) =
is_concrete_eval_eligible(effects::Effects) =
is_consistent(effects) &&
is_effect_free(effects) &&
is_terminates(effects)

is_total(effects::Effects) =
is_total_or_error(effects) &&
is_concrete_eval_eligible(effects) &&
is_nothrow(effects)

is_removable_if_unused(effects::Effects) =
Expand Down
61 changes: 53 additions & 8 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -337,20 +337,24 @@ end

"""
@pure ex
@pure(ex)

`@pure` gives the compiler a hint for the definition of a pure function,
helping for type inference.

This macro is intended for internal compiler use and may be subject to changes.
!!! warning
This macro is intended for internal compiler use and may be subject to changes.

!!! warning
In Julia 1.8 and higher, it is favorable to use [`@assume_effects`](@ref) instead of `@pure`.
This is because `@assume_effects` allows a finer grained control over Julia's purity
modeling and the effect system enables a wider range of optimizations.
"""
macro pure(ex)
esc(isa(ex, Expr) ? pushmeta!(ex, :pure) : ex)
end

"""
@constprop setting ex
@constprop(setting, ex)

`@constprop` controls the mode of interprocedural constant propagation for the
annotated function. Two `setting`s are supported:
Expand All @@ -373,11 +377,35 @@ 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.

```jldoctest
julia> Base.@assume_effects :terminates_locally function pow(x)
# this :terminates_locally allows `pow` to be constant-folded
res = 1
1 < x < 20 || error("bad pow")
while x > 1
res *= x
x -= 1
end
return res
end
pow (generic function with 1 method)

julia> code_typed() do
pow(12)
end
1-element Vector{Any}:
CodeInfo(
1 ─ return 479001600
) => Int64

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

!!! 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
Expand Down Expand Up @@ -512,11 +540,26 @@ This `setting` combines the following other assertions:
- `:terminates_globally`
and is a convenient shortcut.

---
# `:total_may_throw`

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

!!! note
`@assume_effects :total` is similar to `@Base.pure` with the primary
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
`@Base.pure` is always `:total`.
`@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.
"""
macro assume_effects(args...)
(consistent, effect_free, nothrow, terminates_globally, terminates_locally) =
Expand All @@ -537,12 +580,14 @@ macro assume_effects(args...)
terminates_locally = true
elseif setting === :total
consistent = effect_free = nothrow = terminates_globally = true
elseif setting === :total_may_throw
consistent = effect_free = 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"))
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(
Expand Down Expand Up @@ -725,7 +770,7 @@ end

"""
@generated f
@generated(f)

`@generated` is used to annotate a function which will be generated.
In the body of the generated function, only types of arguments can be read
(not the values). The function returns a quoted expression evaluated when the
Expand Down
6 changes: 3 additions & 3 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1185,11 +1185,11 @@ recur_termination22(x) = x * recur_termination21(x-1)
recur_termination21(12) + recur_termination22(12)
end

const ___CONST_DICT___ = Dict{Any,Any}(:a => 1, :b => 2)
Base.@assume_effects :consistent :effect_free :terminates_globally consteval(
const ___CONST_DICT___ = Dict{Any,Any}(Symbol(c) => i for (i, c) in enumerate('a':'z'))
Base.@assume_effects :total_may_throw concrete_eval(
f, args...; kwargs...) = f(args...; kwargs...)
@test fully_eliminated() do
consteval(getindex, ___CONST_DICT___, :a)
concrete_eval(getindex, ___CONST_DICT___, :a)
end

# https://github.com/JuliaLang/julia/issues/44732
Expand Down