Skip to content

Commit 78d9dd0

Browse files
authored
add new :total_may_throw utility setting for @assume_effects (#44775)
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, no matter if the call results in an error or not. This commit also adds some more explanations on the difference between `@pure` and `@assume_effects`.
1 parent 4422a1d commit 78d9dd0

File tree

4 files changed

+59
-14
lines changed

4 files changed

+59
-14
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter,
715715
isoverlayed(method_table(interp)) && !is_nonoverlayed(result.edge_effects) && return false
716716
return f !== nothing &&
717717
result.edge !== nothing &&
718-
is_total_or_error(result.edge_effects) &&
718+
is_concrete_eval_eligible(result.edge_effects) &&
719719
is_all_const_arg(arginfo)
720720
end
721721

base/compiler/types.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ is_nothrow(effects::Effects) = effects.nothrow === ALWAYS_TRUE
113113
is_terminates(effects::Effects) = effects.terminates === ALWAYS_TRUE
114114
is_nonoverlayed(effects::Effects) = effects.nonoverlayed
115115

116-
is_total_or_error(effects::Effects) =
116+
is_concrete_eval_eligible(effects::Effects) =
117117
is_consistent(effects) &&
118118
is_effect_free(effects) &&
119119
is_terminates(effects)
120120

121121
is_total(effects::Effects) =
122-
is_total_or_error(effects) &&
122+
is_concrete_eval_eligible(effects) &&
123123
is_nothrow(effects)
124124

125125
is_removable_if_unused(effects::Effects) =

base/expr.jl

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -337,20 +337,24 @@ end
337337

338338
"""
339339
@pure ex
340-
@pure(ex)
341340
342341
`@pure` gives the compiler a hint for the definition of a pure function,
343342
helping for type inference.
344343
345-
This macro is intended for internal compiler use and may be subject to changes.
344+
!!! warning
345+
This macro is intended for internal compiler use and may be subject to changes.
346+
347+
!!! warning
348+
In Julia 1.8 and higher, it is favorable to use [`@assume_effects`](@ref) instead of `@pure`.
349+
This is because `@assume_effects` allows a finer grained control over Julia's purity
350+
modeling and the effect system enables a wider range of optimizations.
346351
"""
347352
macro pure(ex)
348353
esc(isa(ex, Expr) ? pushmeta!(ex, :pure) : ex)
349354
end
350355

351356
"""
352357
@constprop setting ex
353-
@constprop(setting, ex)
354358
355359
`@constprop` controls the mode of interprocedural constant propagation for the
356360
annotated function. Two `setting`s are supported:
@@ -373,11 +377,35 @@ end
373377

374378
"""
375379
@assume_effects setting... ex
376-
@assume_effects(setting..., ex)
377380
378381
`@assume_effects` overrides the compiler's effect modeling for the given method.
379382
`ex` must be a method definition or `@ccall` expression.
380383
384+
```jldoctest
385+
julia> Base.@assume_effects :terminates_locally function pow(x)
386+
# this :terminates_locally allows `pow` to be constant-folded
387+
res = 1
388+
1 < x < 20 || error("bad pow")
389+
while x > 1
390+
res *= x
391+
x -= 1
392+
end
393+
return res
394+
end
395+
pow (generic function with 1 method)
396+
397+
julia> code_typed() do
398+
pow(12)
399+
end
400+
1-element Vector{Any}:
401+
CodeInfo(
402+
1 ─ return 479001600
403+
) => Int64
404+
405+
julia> Base.@assume_effects :total_may_throw @ccall jl_type_intersection(Vector{Int}::Any, Vector{<:Integer}::Any)::Any
406+
Vector{Int64} (alias for Array{Int64, 1})
407+
```
408+
381409
!!! warning
382410
Improper use of this macro causes undefined behavior (including crashes,
383411
incorrect answers, or other hard to track bugs). Use with care and only if
@@ -512,11 +540,26 @@ This `setting` combines the following other assertions:
512540
- `:terminates_globally`
513541
and is a convenient shortcut.
514542
543+
---
544+
# `:total_may_throw`
545+
546+
This `setting` combines the following other assertions:
547+
- `:consistent`
548+
- `:effect_free`
549+
- `:terminates_globally`
550+
and is a convenient shortcut.
551+
515552
!!! note
516-
`@assume_effects :total` is similar to `@Base.pure` with the primary
553+
This setting is particularly useful since it allows the compiler to evaluate a call of
554+
the applied method when all the call arguments are fully known to be constant, no matter
555+
if the call results in an error or not.
556+
557+
`@assume_effects :total_may_throw` is similar to [`@pure`](@ref) with the primary
517558
distinction that the `:consistent`-cy requirement applies world-age wise rather
518559
than globally as described above. However, in particular, a method annotated
519-
`@Base.pure` is always `:total`.
560+
`@pure` should always be `:total` or `:total_may_throw`.
561+
Another advantage is that effects introduced by `@assume_effects` are propagated to
562+
callers interprocedurally while a purity defined by `@pure` is not.
520563
"""
521564
macro assume_effects(args...)
522565
(consistent, effect_free, nothrow, terminates_globally, terminates_locally) =
@@ -537,12 +580,14 @@ macro assume_effects(args...)
537580
terminates_locally = true
538581
elseif setting === :total
539582
consistent = effect_free = nothrow = terminates_globally = true
583+
elseif setting === :total_may_throw
584+
consistent = effect_free = terminates_globally = true
540585
else
541586
throw(ArgumentError("@assume_effects $setting not supported"))
542587
end
543588
end
544589
ex = args[end]
545-
isa(ex, Expr) || throw(ArgumentError("Bad expression `$ex` in @constprop [settings] ex"))
590+
isa(ex, Expr) || throw(ArgumentError("Bad expression `$ex` in `@assume_effects [settings] ex`"))
546591
if ex.head === :macrocall && ex.args[1] == Symbol("@ccall")
547592
ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects"))
548593
insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride(
@@ -725,7 +770,7 @@ end
725770

726771
"""
727772
@generated f
728-
@generated(f)
773+
729774
`@generated` is used to annotate a function which will be generated.
730775
In the body of the generated function, only types of arguments can be read
731776
(not the values). The function returns a quoted expression evaluated when the

test/compiler/inline.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,11 +1185,11 @@ recur_termination22(x) = x * recur_termination21(x-1)
11851185
recur_termination21(12) + recur_termination22(12)
11861186
end
11871187

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

11951195
# https://github.com/JuliaLang/julia/issues/44732

0 commit comments

Comments
 (0)