Skip to content

Commit 8483130

Browse files
committed
inference: refine slot undef info within then branch of @isdefined
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 86cba99 commit 8483130

File tree

3 files changed

+31
-10
lines changed

3 files changed

+31
-10
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2723,6 +2723,8 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::U
27232723
rt = Const(false) # never assigned previously
27242724
elseif !vtyp.undef
27252725
rt = Const(true) # definitely assigned previously
2726+
else # form `Conditional` to refine `vtyp.undef` in the then branch
2727+
rt = Conditional(sym, vtyp.typ, vtyp.typ, #=isdefined=#true)
27262728
end
27272729
elseif isa(sym, GlobalRef)
27282730
if InferenceParams(interp).assume_bindings_static
@@ -3195,7 +3197,7 @@ end
31953197
@inline function abstract_eval_basic_statement(interp::AbstractInterpreter,
31963198
@nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState)
31973199
if isa(stmt, NewvarNode)
3198-
changes = StateUpdate(stmt.slot, VarState(Bottom, true), false)
3200+
changes = StateUpdate(stmt.slot, VarState(Bottom, true))
31993201
return BasicStmtChange(changes, nothing, Union{})
32003202
elseif !isa(stmt, Expr)
32013203
(; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame)
@@ -3210,7 +3212,7 @@ end
32103212
end
32113213
lhs = stmt.args[1]
32123214
if isa(lhs, SlotNumber)
3213-
changes = StateUpdate(lhs, VarState(rt, false), false)
3215+
changes = StateUpdate(lhs, VarState(rt, false))
32143216
elseif isa(lhs, GlobalRef)
32153217
handle_global_assignment!(interp, frame, lhs, rt)
32163218
elseif !isa(lhs, SSAValue)
@@ -3220,7 +3222,7 @@ end
32203222
elseif hd === :method
32213223
fname = stmt.args[1]
32223224
if isa(fname, SlotNumber)
3223-
changes = StateUpdate(fname, VarState(Any, false), false)
3225+
changes = StateUpdate(fname, VarState(Any, false))
32243226
end
32253227
return BasicStmtChange(changes, nothing, Union{})
32263228
elseif (hd === :code_coverage_effect || (
@@ -3566,7 +3568,7 @@ function apply_refinement!(𝕃ᵢ::AbstractLattice, slot::SlotNumber, @nospecia
35663568
oldtyp = vtype.typ
35673569
= strictpartialorder(𝕃ᵢ)
35683570
if newtyp oldtyp
3569-
stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef), false)
3571+
stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef))
35703572
stoverwrite1!(currstate, stmtupdate)
35713573
end
35723574
end
@@ -3590,7 +3592,9 @@ function conditional_change(𝕃ᵢ::AbstractLattice, currstate::VarTable, condt
35903592
# "causes" since we ignored those in the comparison
35913593
newtyp = tmerge(𝕃ᵢ, newtyp, LimitedAccuracy(Bottom, oldtyp.causes))
35923594
end
3593-
return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, vtype.undef), true)
3595+
# if this `Conditional` is from from `@isdefined condt.slot`, refine its `undef` information
3596+
newundef = condt.isdefined ? !then_or_else : vtype.undef
3597+
return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, newundef), #=conditional=#true)
35943598
end
35953599

35963600
function condition_object_change(currstate::VarTable, condt::Conditional,
@@ -3599,7 +3603,7 @@ function condition_object_change(currstate::VarTable, condt::Conditional,
35993603
newcondt = Conditional(condt.slot,
36003604
then_or_else ? condt.thentype : Union{},
36013605
then_or_else ? Union{} : condt.elsetype)
3602-
return StateUpdate(condslot, VarState(newcondt, vtype.undef), false)
3606+
return StateUpdate(condslot, VarState(newcondt, vtype.undef))
36033607
end
36043608

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

base/compiler/typelattice.jl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,18 @@ 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), isdefined::Bool=false)
7781
assert_nested_slotwrapper(thentype)
7882
assert_nested_slotwrapper(elsetype)
79-
return new(slot, thentype, elsetype)
83+
return new(slot, thentype, elsetype, isdefined)
8084
end
8185
end
82-
Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) =
83-
Conditional(slot_id(var), thentype, elsetype)
86+
Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype), isdefined::Bool=false) =
87+
Conditional(slot_id(var), thentype, elsetype, isdefined)
8488

8589
import Core: InterConditional
8690
"""
@@ -182,6 +186,7 @@ struct StateUpdate
182186
var::SlotNumber
183187
vtype::VarState
184188
conditional::Bool
189+
StateUpdate(var::SlotNumber, vtype::VarState, conditional::Bool=false) = new(var, vtype, conditional)
185190
end
186191

187192
"""

test/compiler/inference.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6034,6 +6034,18 @@ end |> Core.Compiler.is_nothrow
60346034
end
60356035
end |> !Core.Compiler.is_nothrow
60366036

6037+
# refine `undef` information from `@isdefined` check
6038+
@test Base.infer_effects((Bool,Int)) do c, x
6039+
local val
6040+
if c
6041+
val = x
6042+
end
6043+
if @isdefined val
6044+
return val
6045+
end
6046+
return zero(Int)
6047+
end |> Core.Compiler.is_nothrow
6048+
60376049
# End to end test case for the partially initialized struct with `PartialStruct`
60386050
@noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing)
60396051
@test fully_eliminated() do

0 commit comments

Comments
 (0)