Skip to content

Commit 65aeaf6

Browse files
authored
inference: fix too conservative effects for recursive cycles (#54323)
The `:terminates` effect bit must be conservatively tainted unless recursion cycle has been fully resolved. As for other effects, there's no need to taint them at this moment because they will be tainted as we try to resolve the cycle. - fixes #52938 - xref #51092
1 parent 583981f commit 65aeaf6

File tree

4 files changed

+46
-18
lines changed

4 files changed

+46
-18
lines changed

base/compiler/typeinfer.jl

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,15 +236,21 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState)
236236
# with no active ip's, frame is done
237237
frames = frame.callers_in_cycle
238238
isempty(frames) && push!(frames, frame)
239-
valid_worlds = WorldRange()
239+
cycle_valid_worlds = WorldRange()
240+
cycle_effects = EFFECTS_TOTAL
240241
for caller in frames
241242
@assert !(caller.dont_work_on_me)
242243
caller.dont_work_on_me = true
243-
# might might not fully intersect these earlier, so do that now
244-
valid_worlds = intersect(caller.valid_worlds, valid_worlds)
244+
# converge the world age range and effects for this cycle here:
245+
# all frames in the cycle should have the same bits of `valid_worlds` and `effects`
246+
# that are simply the intersection of each partial computation, without having
247+
# dependencies on each other (unlike rt and exct)
248+
cycle_valid_worlds = intersect(cycle_valid_worlds, caller.valid_worlds)
249+
cycle_effects = merge_effects(cycle_effects, caller.ipo_effects)
245250
end
246251
for caller in frames
247-
caller.valid_worlds = valid_worlds
252+
caller.valid_worlds = cycle_valid_worlds
253+
caller.ipo_effects = cycle_effects
248254
finish(caller, caller.interp)
249255
end
250256
for caller in frames
@@ -864,7 +870,8 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize
864870
update_valid_age!(caller, frame.valid_worlds)
865871
isinferred = is_inferred(frame)
866872
edge = isinferred ? mi : nothing
867-
effects = isinferred ? frame.result.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects
873+
effects = isinferred ? frame.result.ipo_effects : # effects are adjusted already within `finish` for ipo_effects
874+
adjust_effects(effects_for_cycle(frame.ipo_effects), method)
868875
exc_bestguess = refine_exception_type(frame.exc_bestguess, effects)
869876
# propagate newly inferred source to the inliner, allowing efficient inlining w/o deserialization:
870877
# note that this result is cached globally exclusively, so we can use this local result destructively
@@ -877,11 +884,16 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize
877884
# return the current knowledge about this cycle
878885
frame = frame::InferenceState
879886
update_valid_age!(caller, frame.valid_worlds)
880-
effects = adjust_effects(Effects(), method)
887+
effects = adjust_effects(effects_for_cycle(frame.ipo_effects), method)
881888
exc_bestguess = refine_exception_type(frame.exc_bestguess, effects)
882889
return EdgeCallResult(frame.bestguess, exc_bestguess, nothing, effects)
883890
end
884891

892+
# The `:terminates` effect bit must be conservatively tainted unless recursion cycle has
893+
# been fully resolved. As for other effects, there's no need to taint them at this moment
894+
# because they will be tainted as we try to resolve the cycle.
895+
effects_for_cycle(effects::Effects) = Effects(effects; terminates=false)
896+
885897
function cached_return_type(code::CodeInstance)
886898
rettype = code.rettype
887899
isdefined(code, :rettype_const) || return rettype

test/compiler/AbstractInterpreter.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,18 @@ function CC.concrete_eval_eligible(interp::Issue48097Interp,
152152
end
153153
@overlay Issue48097MT @noinline Core.throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} = return
154154
issue48097(; kwargs...) = return 42
155-
@test_broken fully_eliminated(; interp=Issue48097Interp(), retval=42) do
155+
@test fully_eliminated(; interp=Issue48097Interp(), retval=42) do
156156
issue48097(; a=1f0, b=1.0)
157157
end
158158

159+
# https://github.com/JuliaLang/julia/issues/52938
160+
@newinterp Issue52938Interp
161+
@MethodTable ISSUE_52938_MT
162+
CC.method_table(interp::Issue52938Interp) = CC.OverlayMethodTable(CC.get_inference_world(interp), ISSUE_52938_MT)
163+
inner52938(x, types::Type, args...; kwargs...) = x
164+
outer52938(x) = @inline inner52938(x, Tuple{}; foo=Ref(42), bar=1)
165+
@test fully_eliminated(outer52938, (Any,); interp=Issue52938Interp(), retval=Argument(2))
166+
159167
# Should not concrete-eval overlayed methods in semi-concrete interpretation
160168
@newinterp OverlaySinInterp
161169
@MethodTable OverlaySinMT

test/compiler/effects.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ Base.@assume_effects :terminates_globally function recur_termination1(x)
8989
0 x < 20 || error("bad fact")
9090
return x * recur_termination1(x-1)
9191
end
92-
@test_broken Core.Compiler.is_foldable(Base.infer_effects(recur_termination1, (Int,)))
92+
@test Core.Compiler.is_foldable(Base.infer_effects(recur_termination1, (Int,)))
9393
@test Core.Compiler.is_terminates(Base.infer_effects(recur_termination1, (Int,)))
9494
function recur_termination2()
9595
Base.@assume_effects :total !:terminates_globally
9696
recur_termination1(12)
9797
end
98-
@test_broken fully_eliminated(recur_termination2)
98+
@test fully_eliminated(recur_termination2)
9999
@test fully_eliminated() do; recur_termination2(); end
100100

101101
Base.@assume_effects :terminates_globally function recur_termination21(x)
@@ -104,15 +104,15 @@ Base.@assume_effects :terminates_globally function recur_termination21(x)
104104
return recur_termination22(x)
105105
end
106106
recur_termination22(x) = x * recur_termination21(x-1)
107-
@test_broken Core.Compiler.is_foldable(Base.infer_effects(recur_termination21, (Int,)))
108-
@test_broken Core.Compiler.is_foldable(Base.infer_effects(recur_termination22, (Int,)))
107+
@test Core.Compiler.is_foldable(Base.infer_effects(recur_termination21, (Int,)))
108+
@test Core.Compiler.is_foldable(Base.infer_effects(recur_termination22, (Int,)))
109109
@test Core.Compiler.is_terminates(Base.infer_effects(recur_termination21, (Int,)))
110110
@test Core.Compiler.is_terminates(Base.infer_effects(recur_termination22, (Int,)))
111111
function recur_termination2x()
112112
Base.@assume_effects :total !:terminates_globally
113113
recur_termination21(12) + recur_termination22(12)
114114
end
115-
@test_broken fully_eliminated(recur_termination2x)
115+
@test fully_eliminated(recur_termination2x)
116116
@test fully_eliminated() do; recur_termination2x(); end
117117

118118
# anonymous function support for `@assume_effects`
@@ -921,7 +921,7 @@ unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = (T; nothing)
921921
abstractly_recursive1() = abstractly_recursive2()
922922
abstractly_recursive2() = (Core.Compiler._return_type(abstractly_recursive1, Tuple{}); 1)
923923
abstractly_recursive3() = abstractly_recursive2()
924-
@test Core.Compiler.is_terminates(Base.infer_effects(abstractly_recursive3, ()))
924+
@test_broken Core.Compiler.is_terminates(Base.infer_effects(abstractly_recursive3, ()))
925925
actually_recursive1(x) = actually_recursive2(x)
926926
actually_recursive2(x) = (x <= 0) ? 1 : actually_recursive1(x - 1)
927927
actually_recursive3(x) = actually_recursive2(x)

test/math.jl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,13 +1595,21 @@ end
15951595
@testset let T = T
15961596
for f = Any[sin, cos, tan, log, log2, log10, log1p, exponent, sqrt, cbrt, fourthroot,
15971597
asin, atan, acos, sinh, cosh, tanh, asinh, acosh, atanh, exp, exp2, exp10, expm1]
1598-
@testset let f = f
1599-
@test Base.infer_return_type(f, (T,)) != Union{}
1600-
@test Core.Compiler.is_foldable(Base.infer_effects(f, (T,)))
1598+
@testset let f = f,
1599+
rt = Base.infer_return_type(f, (T,)),
1600+
effects = Base.infer_effects(f, (T,))
1601+
@test rt != Union{}
1602+
@test Core.Compiler.is_foldable(effects)
16011603
end
16021604
end
1603-
@test Core.Compiler.is_foldable(Base.infer_effects(^, (T,Int)))
1604-
@test Core.Compiler.is_foldable(Base.infer_effects(^, (T,T)))
1605+
@static if !(Sys.iswindows()&&Int==Int32) # COMBAK debug this
1606+
@testset let effects = Base.infer_effects(^, (T,Int))
1607+
@test Core.Compiler.is_foldable(effects)
1608+
end
1609+
end # @static
1610+
@testset let effects = Base.infer_effects(^, (T,T))
1611+
@test Core.Compiler.is_foldable(effects)
1612+
end
16051613
end
16061614
end
16071615
end;

0 commit comments

Comments
 (0)