From aa8194c26cfb835816f83f9837bfb17f35b1acfd Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 21 Aug 2024 16:57:55 +0900 Subject: [PATCH] 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 ``` --- base/compiler/abstractinterpretation.jl | 16 ++++++++++------ base/compiler/typelattice.jl | 13 +++++++++---- test/compiler/inference.jl | 12 ++++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 83abfc952bf8e4..228c0819f788c9 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2723,6 +2723,8 @@ function abstract_eval_isdefined(interp::AbstractInterpreter, e::Expr, vtypes::U rt = Const(false) # never assigned previously elseif !vtyp.undef rt = Const(true) # definitely assigned previously + else # form `Conditional` to refine `vtyp.undef` in the then branch + rt = Conditional(sym, vtyp.typ, vtyp.typ, #=isdefined=#true) end elseif isa(sym, GlobalRef) if InferenceParams(interp).assume_bindings_static @@ -3195,7 +3197,7 @@ end @inline function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState) if isa(stmt, NewvarNode) - changes = StateUpdate(stmt.slot, VarState(Bottom, true), false) + changes = StateUpdate(stmt.slot, VarState(Bottom, true)) return BasicStmtChange(changes, nothing, Union{}) elseif !isa(stmt, Expr) (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) @@ -3210,7 +3212,7 @@ end end lhs = stmt.args[1] if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(rt, false), false) + changes = StateUpdate(lhs, VarState(rt, false)) elseif isa(lhs, GlobalRef) handle_global_assignment!(interp, frame, lhs, rt) elseif !isa(lhs, SSAValue) @@ -3220,7 +3222,7 @@ end elseif hd === :method fname = stmt.args[1] if isa(fname, SlotNumber) - changes = StateUpdate(fname, VarState(Any, false), false) + changes = StateUpdate(fname, VarState(Any, false)) end return BasicStmtChange(changes, nothing, Union{}) elseif (hd === :code_coverage_effect || ( @@ -3566,7 +3568,7 @@ function apply_refinement!(๐•ƒแตข::AbstractLattice, slot::SlotNumber, @nospecia oldtyp = vtype.typ โŠ = strictpartialorder(๐•ƒแตข) if newtyp โŠ oldtyp - stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef), false) + stmtupdate = StateUpdate(slot, VarState(newtyp, vtype.undef)) stoverwrite1!(currstate, stmtupdate) end end @@ -3590,7 +3592,9 @@ function conditional_change(๐•ƒแตข::AbstractLattice, currstate::VarTable, condt # "causes" since we ignored those in the comparison newtyp = tmerge(๐•ƒแตข, newtyp, LimitedAccuracy(Bottom, oldtyp.causes)) end - return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, vtype.undef), true) + # if this `Conditional` is from from `@isdefined condt.slot`, refine its `undef` inforamtion + newundef = condt.isdefined ? !then_or_else : vtype.undef + return StateUpdate(SlotNumber(condt.slot), VarState(newtyp, newundef), #=conditional=#true) end function condition_object_change(currstate::VarTable, condt::Conditional, @@ -3599,7 +3603,7 @@ function condition_object_change(currstate::VarTable, condt::Conditional, newcondt = Conditional(condt.slot, then_or_else ? condt.thentype : Union{}, then_or_else ? Union{} : condt.elsetype) - return StateUpdate(condslot, VarState(newcondt, vtype.undef), false) + return StateUpdate(condslot, VarState(newcondt, vtype.undef)) end # make as much progress on `frame` as possible (by handling cycles) diff --git a/base/compiler/typelattice.jl b/base/compiler/typelattice.jl index d375d61c0bdd81..2057d27c683e0f 100644 --- a/base/compiler/typelattice.jl +++ b/base/compiler/typelattice.jl @@ -73,14 +73,18 @@ struct Conditional slot::Int thentype elsetype - function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) + # `isdefined` indicates this `Conditional` is from `@isdefined slot`, implying that + # the `undef` information of `slot` can be improved in the then branch. + # Since this is only beneficial for local inference, it is not translated into `InterConditional`. + isdefined::Bool + function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype), isdefined::Bool=false) assert_nested_slotwrapper(thentype) assert_nested_slotwrapper(elsetype) - return new(slot, thentype, elsetype) + return new(slot, thentype, elsetype, isdefined) end end -Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype)) = - Conditional(slot_id(var), thentype, elsetype) +Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype), isdefined::Bool=false) = + Conditional(slot_id(var), thentype, elsetype, isdefined) import Core: InterConditional """ @@ -182,6 +186,7 @@ struct StateUpdate var::SlotNumber vtype::VarState conditional::Bool + StateUpdate(var::SlotNumber, vtype::VarState, conditional::Bool=false) = new(var, vtype, conditional) end """ diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 75f33a280e245c..7b9327463db310 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6034,6 +6034,18 @@ end |> Core.Compiler.is_nothrow end end |> !Core.Compiler.is_nothrow +# refine `undef` information from `@isdefined` check +@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 + # End to end test case for the partially initialized struct with `PartialStruct` @noinline broadcast_noescape1(a) = (broadcast(identity, a); nothing) @test fully_eliminated() do