Skip to content

Commit 5963dc0

Browse files
aviateskudesou
authored andcommitted
inference: refine slot undef info within then branch of @isdefined (JuliaLang#55545)
By adding some information to `Conditional`, it is possible to improve the `undef` information of `slot` within the `then` branch of `@isdefined slot`. As a result, it's now possible to prove the `:nothrow`-ness in cases like: ```julia @test Base.infer_effects((Bool,Int)) do c, x local val if c val = x end if @isdefined val return val end return zero(Int) end |> Core.Compiler.is_nothrow ```
1 parent b48dad2 commit 5963dc0

File tree

3 files changed

+42
-14
lines changed

3 files changed

+42
-14
lines changed

base/compiler/abstractinterpretation.jl

+10-6
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,8 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::U
27332733
rt = Const(false) # never assigned previously
27342734
elseif !vtyp.undef
27352735
rt = Const(true) # definitely assigned previously
2736+
else # form `Conditional` to refine `vtyp.undef` in the then branch
2737+
rt = Conditional(sym, vtyp.typ, vtyp.typ; isdefined=true)
27362738
end
27372739
elseif isa(sym, GlobalRef)
27382740
if InferenceParams(interp).assume_bindings_static
@@ -3205,7 +3207,7 @@ end
32053207
@inline function abstract_eval_basic_statement(interp::AbstractInterpreter,
32063208
@nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState)
32073209
if isa(stmt, NewvarNode)
3208-
changes = StateUpdate(stmt.slot, VarState(Bottom, true), false)
3210+
changes = StateUpdate(stmt.slot, VarState(Bottom, true))
32093211
return BasicStmtChange(changes, nothing, Union{})
32103212
elseif !isa(stmt, Expr)
32113213
(; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame)
@@ -3220,7 +3222,7 @@ end
32203222
end
32213223
lhs = stmt.args[1]
32223224
if isa(lhs, SlotNumber)
3223-
changes = StateUpdate(lhs, VarState(rt, false), false)
3225+
changes = StateUpdate(lhs, VarState(rt, false))
32243226
elseif isa(lhs, GlobalRef)
32253227
handle_global_assignment!(interp, frame, lhs, rt)
32263228
elseif !isa(lhs, SSAValue)
@@ -3230,7 +3232,7 @@ end
32303232
elseif hd === :method
32313233
fname = stmt.args[1]
32323234
if isa(fname, SlotNumber)
3233-
changes = StateUpdate(fname, VarState(Any, false), false)
3235+
changes = StateUpdate(fname, VarState(Any, false))
32343236
end
32353237
return BasicStmtChange(changes, nothing, Union{})
32363238
elseif (hd === :code_coverage_effect || (
@@ -3576,7 +3578,7 @@ function apply_refinement!(𝕃ᵢ::AbstractLattice, slot::SlotNumber, @nospecia
35763578
oldtyp = vtype.typ
35773579
= strictpartialorder(𝕃ᵢ)
35783580
if newtyp oldtyp
3579-
stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef), false)
3581+
stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef))
35803582
stoverwrite1!(currstate, stmtupdate)
35813583
end
35823584
end
@@ -3600,7 +3602,9 @@ function conditional_change(𝕃ᵢ::AbstractLattice, currstate::VarTable, condt
36003602
# "causes" since we ignored those in the comparison
36013603
newtyp = tmerge(𝕃ᵢ, newtyp, LimitedAccuracy(Bottom, oldtyp.causes))
36023604
end
3603-
return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, vtype.undef), true)
3605+
# if this `Conditional` is from from `@isdefined condt.slot`, refine its `undef` information
3606+
newundef = condt.isdefined ? !then_or_else : vtype.undef
3607+
return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, newundef), #=conditional=#true)
36043608
end
36053609

36063610
function condition_object_change(currstate::VarTable, condt::Conditional,
@@ -3609,7 +3613,7 @@ function condition_object_change(currstate::VarTable, condt::Conditional,
36093613
newcondt = Conditional(condt.slot,
36103614
then_or_else ? condt.thentype : Union{},
36113615
then_or_else ? Union{} : condt.elsetype)
3612-
return StateUpdate(condslot, VarState(newcondt, vtype.undef), false)
3616+
return StateUpdate(condslot, VarState(newcondt, vtype.undef))
36133617
end
36143618

36153619
# make as much progress on `frame` as possible (by handling cycles)

base/compiler/typelattice.jl

+20-8
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,19 @@ struct Conditional
7373
slot::Int
7474
thentype
7575
elsetype
76-
function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype))
76+
# `isdefined` indicates this `Conditional` is from `@isdefined slot`, implying that
77+
# the `undef` information of `slot` can be improved in the then branch.
78+
# Since this is only beneficial for local inference, it is not translated into `InterConditional`.
79+
isdefined::Bool
80+
function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype);
81+
isdefined::Bool=false)
7782
assert_nested_slotwrapper(thentype)
7883
assert_nested_slotwrapper(elsetype)
79-
return new(slot, thentype, elsetype)
84+
return new(slot, thentype, elsetype, isdefined)
8085
end
8186
end
82-
Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) =
83-
Conditional(slot_id(var), thentype, elsetype)
87+
Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) =
88+
Conditional(slot_id(var), thentype, elsetype; isdefined)
8489

8590
import Core: InterConditional
8691
"""
@@ -180,6 +185,7 @@ struct StateUpdate
180185
var::SlotNumber
181186
vtype::VarState
182187
conditional::Bool
188+
StateUpdate(var::SlotNumber, vtype::VarState, conditional::Bool=false) = new(var, vtype, conditional)
183189
end
184190

185191
"""
@@ -305,11 +311,17 @@ end
305311

306312
# `Conditional` and `InterConditional` are valid in opposite contexts
307313
# (i.e. local inference and inter-procedural call), as such they will never be compared
308-
@nospecializeinfer function issubconditional(lattice::AbstractLattice, a::C, b::C) where {C<:AnyConditional}
314+
@nospecializeinfer issubconditional(𝕃::AbstractLattice, a::Conditional, b::Conditional) =
315+
_issubconditional(𝕃, a, b, #=check_isdefined=#true)
316+
@nospecializeinfer issubconditional(𝕃::AbstractLattice, a::InterConditional, b::InterConditional) =
317+
_issubconditional(𝕃, a, b, #=check_isdefined=#false)
318+
@nospecializeinfer function _issubconditional(𝕃::AbstractLattice, a::C, b::C, check_isdefined::Bool) where C<:AnyConditional
309319
if is_same_conditionals(a, b)
310-
if (lattice, a.thentype, b.thentype)
311-
if (lattice, a.elsetype, b.elsetype)
312-
return true
320+
if (𝕃, a.thentype, b.thentype)
321+
if (𝕃, a.elsetype, b.elsetype)
322+
if !check_isdefined || a.isdefined b.isdefined
323+
return true
324+
end
313325
end
314326
end
315327
end

test/compiler/inference.jl

+12
Original file line numberDiff line numberDiff line change
@@ -6050,6 +6050,18 @@ end |> Core.Compiler.is_nothrow
60506050
return nothing
60516051
end |> Core.Compiler.is_nothrow
60526052

6053+
# refine `undef` information from `@isdefined` check
6054+
@test Base.infer_effects((Bool,Int)) do c, x
6055+
local val
6056+
if c
6057+
val = x
6058+
end
6059+
if @isdefined val
6060+
return val
6061+
end
6062+
return zero(Int)
6063+
end |> Core.Compiler.is_nothrow
6064+
60536065
# End to end test case for the partially initialized struct with `PartialStruct`
60546066
@noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing)
60556067
@test fully_eliminated() do

0 commit comments

Comments
 (0)