Skip to content

Commit 0c46852

Browse files
authored
effects: refine :nothrow when exct is known to be Bottom (#52270)
Implements effects refinement that goes in the reverse direction of what was implemented in 8dd0cf5. In the discussion at #52268, there's a debate on how to deal with exceptions like `StackOverflowError` or `InterruptException` that are caused by environment. The original purpose of #52268 was to fix issues where methods that could cause a `StackOverflowError` were mistakenly refined to `:nothrow`. But now it feels like it's a bit weird to model such environment-dependent exceptions as `:nothrow` in the first place. If we can exclude environment-dependent errors from the `:nothrow` modeling, those methods might be safely considered `:nothrow`. To prevent the concrete evaluation of methods that may cause `StackOverflowError`, it's enough to taint its `:teminates` after all. This commit excludes environment-depending errors from the `:nothrow` modeling, reverting the changes from #52268 while making it explicit in the `Effects` and `Base.@assume_effects` docstrings. Anyway, now the compile is able to prove `:nothrow`-ness of the following frame: ```julia function getindex_nothrow(xs::Vector{Int}, i::Int) try return xs[i] catch err err isa BoundsError && return nothing rethrow(err) end end ```
1 parent a624d44 commit 0c46852

File tree

5 files changed

+29
-8
lines changed

5 files changed

+29
-8
lines changed

base/compiler/effects.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ following meanings:
2424
* `EFFECT_FREE_IF_INACCESSIBLEMEMONLY`: the `:effect-free`-ness of this method can later be
2525
refined to `ALWAYS_TRUE` in a case when `:inaccessiblememonly` is proven.
2626
- `nothrow::Bool`: this method is guaranteed to not throw an exception.
27+
If the execution of this method may raise `MethodError`s and similar exceptions, then
28+
the method is not considered as `:nothrow`.
29+
However, note that environment-dependent errors like `StackOverflowError` or `InterruptException`
30+
are not modeled by this effect and thus a method that may result in `StackOverflowError`
31+
does not necessarily need to taint `:nothrow` (although it should usually taint `:terminates` too).
2732
- `terminates::Bool`: this method is guaranteed to terminate.
2833
- `notaskstate::Bool`: this method does not access any state bound to the current
2934
task and may thus be moved to a different task without changing observable

base/compiler/typeinfer.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,11 @@ function adjust_effects(sv::InferenceState)
463463
# always throwing an error counts or never returning both count as consistent
464464
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
465465
end
466+
if sv.exc_bestguess === Bottom
467+
# if the exception type of this frame is known to be `Bottom`,
468+
# this frame is known to be safe
469+
ipo_effects = Effects(ipo_effects; nothrow=true)
470+
end
466471
if is_inaccessiblemem_or_argmemonly(ipo_effects) && all(1:narguments(sv, #=include_va=#true)) do i::Int
467472
return is_mutation_free_argtype(sv.slottypes[i])
468473
end
@@ -883,9 +888,6 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize
883888
update_valid_age!(caller, frame.valid_worlds)
884889
effects = adjust_effects(Effects(), method)
885890
exc_bestguess = refine_exception_type(frame.exc_bestguess, effects)
886-
# this call can fail into an infinite cycle, so incorporate this fact into
887-
# `exc_bestguess` by merging `StackOverflowError` into it
888-
exc_bestguess = tmerge(typeinf_lattice(interp), Core.StackOverflowError, exc_bestguess)
889891
return EdgeCallResult(frame.bestguess, exc_bestguess, nothing, effects)
890892
end
891893

base/expr.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ were not executed.
552552
---
553553
## `:nothrow`
554554
555-
The `:nothrow` settings asserts that this method does not terminate abnormally
555+
The `:nothrow` settings asserts that this method does not throw an exception
556556
(i.e. will either always return a value or never return).
557557
558558
!!! note
@@ -561,7 +561,11 @@ The `:nothrow` settings asserts that this method does not terminate abnormally
561561
method itself.
562562
563563
!!! note
564-
`MethodErrors` and similar exceptions count as abnormal termination.
564+
If the execution of a method may raise `MethodError`s and similar exceptions, then
565+
the method is not considered as `:nothrow`.
566+
However, note that environment-dependent errors like `StackOverflowError` or `InterruptException`
567+
are not modeled by this effect and thus a method that may result in `StackOverflowError`
568+
does not necessarily need to be `!:nothrow` (although it should usually be `!:terminates` too).
565569
566570
---
567571
## `:terminates_globally`

test/compiler/effects.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,3 +1290,15 @@ end |> !Core.Compiler.is_noub
12901290
@test Base.infer_effects((Vector{Any},Int)) do xs, i
12911291
@inbounds getindex_dont_propagate(xs, i)
12921292
end |> Core.Compiler.is_noub
1293+
1294+
# refine `:nothrow` when `exct` is known to be `Bottom`
1295+
@test Base.infer_exception_type(getindex, (Vector{Int},Int)) == BoundsError
1296+
function getindex_nothrow(xs::Vector{Int}, i::Int)
1297+
try
1298+
return xs[i]
1299+
catch err
1300+
err isa BoundsError && return nothing
1301+
rethrow(err)
1302+
end
1303+
end
1304+
@test Core.Compiler.is_nothrow(Base.infer_effects(getindex_nothrow, (Vector{Int}, Int)))

test/compiler/inference.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,11 @@ A14009(a::T) where {T} = A14009{T}()
423423
f14009(a) = rand(Bool) ? f14009(A14009(a)) : a
424424
code_typed(f14009, (Int,))
425425
code_llvm(devnull, f14009, (Int,))
426-
@test Base.infer_exception_type(f14009, (Int,)) != Union{}
427426

428427
mutable struct B14009{T}; end
429428
g14009(a) = g14009(B14009{a})
430429
code_typed(g14009, (Type{Int},))
431430
code_llvm(devnull, g14009, (Type{Int},))
432-
@test Base.infer_exception_type(g14009, (Type{Int},)) == StackOverflowError
433431

434432
# issue #9232
435433
arithtype9232(::Type{T},::Type{T}) where {T<:Real} = arithtype9232(T)
@@ -5560,7 +5558,7 @@ function foo_typed_throw_metherr()
55605558
end
55615559
@test Base.return_types(foo_typed_throw_metherr) |> only === Float64
55625560

5563-
# using `exct` information if `:nothrow` is proven
5561+
# refine `exct` when `:nothrow` is proven
55645562
Base.@assume_effects :nothrow function sin_nothrow(x::Float64)
55655563
x == Inf && return zero(x)
55665564
return sin(x)

0 commit comments

Comments
 (0)