Skip to content

Commit e98f2ce

Browse files
committed
inference: Remove special casing for !
We have a handful of cases in inference where we look up functions by name (using `istopfunction`) and give them special behavior. I'd like to remove these. They're not only aesthetically ugly, but because they depend on binding lookups, rather than values, they have unclear semantics as those bindings change. They are also unsound should a user use the same name for something different in their own top modules (of course, it's unlikely that a user would do such a thing, but it's bad that they can't). This particular PR removes the special case for `!`, which was there to strengthen the inference result for `!` on Conditional. However, with a little bit of strengthening of the rest of the system, this can be equally well evaluated through the existing InterConditional mechanism.
1 parent 125bac4 commit e98f2ce

File tree

4 files changed

+56
-9
lines changed

4 files changed

+56
-9
lines changed

base/compiler/abstractinterpretation.jl

+38-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ call_result_unused(sv::InferenceState, currpc::Int) =
1313
isexpr(sv.src.code[currpc], :call) && isempty(sv.ssavalue_uses[currpc])
1414
call_result_unused(si::StmtInfo) = !si.used
1515

16+
is_const_bool_or_bottom(@nospecialize(b)) = (isa(b, Const) && isa(b.val, Bool)) || b == Bottom
17+
function can_propagate_conditional(@nospecialize(rt), argtypes::Vector{Any})
18+
isa(rt, InterConditional) || return false
19+
if rt.slot > length(argtypes)
20+
# In the vararg tail - can't be conditional
21+
@assert isvarargtype(argtypes[end])
22+
return false
23+
end
24+
return isa(argtypes[rt.slot], Conditional) &&
25+
is_const_bool_or_bottom(rt.thentype) && is_const_bool_or_bottom(rt.thentype)
26+
end
27+
28+
function propagate_conditional(rt::InterConditional, cond::Conditional)
29+
new_thentype = rt.thentype === Const(false) ? cond.elsetype : cond.thentype
30+
new_elsetype = rt.elsetype === Const(true) ? cond.thentype : cond.elsetype
31+
if rt.thentype == Bottom
32+
@assert rt.elsetype != Bottom
33+
return Conditional(cond.slot, Bottom, new_elsetype)
34+
elseif rt.elsetype == Bottom
35+
@assert rt.thentype != Bottom
36+
return Conditional(cond.slot, new_thentype, Bottom)
37+
end
38+
return Conditional(cond.slot, new_thentype, new_elsetype)
39+
end
40+
1641
function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
1742
arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype),
1843
sv::AbsIntState, max_methods::Int)
@@ -156,6 +181,15 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
156181
end
157182
@assert !(this_conditional isa Conditional || this_rt isa MustAlias) "invalid lattice element returned from inter-procedural context"
158183
seen += 1
184+
185+
if can_propagate_conditional(this_conditional, argtypes)
186+
# The only case where we need to keep this in rt is where
187+
# we can directly propagate the conditional to a slot argument
188+
# that is not one of our arguments, otherwise we keep all the
189+
# relevant information in `conditionals` below.
190+
this_rt = this_conditional
191+
end
192+
159193
rettype = rettype ₚ this_rt
160194
exctype = exctype ₚ this_exct
161195
if has_conditional(𝕃ₚ, sv) && this_conditional !== Bottom && is_lattice_bool(𝕃ₚ, rettype) && fargs !== nothing
@@ -409,6 +443,9 @@ function from_interconditional(𝕃ᵢ::AbstractLattice, @nospecialize(rt), sv::
409443
has_conditional(𝕃ᵢ, sv) || return widenconditional(rt)
410444
(; fargs, argtypes) = arginfo
411445
fargs === nothing && return widenconditional(rt)
446+
if can_propagate_conditional(rt, argtypes)
447+
return propagate_conditional(rt, argtypes[rt.slot]::Conditional)
448+
end
412449
slot = 0
413450
alias = nothing
414451
thentype = elsetype = Any
@@ -2217,13 +2254,6 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
22172254
end
22182255
elseif is_return_type(f)
22192256
return return_type_tfunc(interp, argtypes, si, sv)
2220-
elseif la == 2 && istopfunction(f, :!)
2221-
# handle Conditional propagation through !Bool
2222-
aty = argtypes[2]
2223-
if isa(aty, Conditional)
2224-
call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Bool]), si, Tuple{typeof(f), Bool}, sv, max_methods) # make sure we've inferred `!(::Bool)`
2225-
return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), Any, call.effects, call.info)
2226-
end
22272257
elseif la == 3 && istopfunction(f, :!==)
22282258
# mark !== as exactly a negated call to ===
22292259
call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, sv, max_methods)
@@ -3194,7 +3224,7 @@ function update_bestguess!(interp::AbstractInterpreter, frame::InferenceState,
31943224
# narrow representation of bestguess slightly to prepare for tmerge with rt
31953225
if rt isa InterConditional && bestguess isa Const
31963226
slot_id = rt.slot
3197-
old_id_type = slottypes[slot_id]
3227+
old_id_type = widenconditional(slottypes[slot_id])
31983228
if bestguess.val === true && rt.elsetype !== Bottom
31993229
bestguess = InterConditional(slot_id, old_id_type, Bottom)
32003230
elseif bestguess.val === false && rt.thentype !== Bottom

base/compiler/inferencestate.jl

+3
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ mutable struct InferenceState
312312
nargtypes = length(argtypes)
313313
for i = 1:nslots
314314
argtyp = (i > nargtypes) ? Bottom : argtypes[i]
315+
if argtyp === Bool && has_conditional(typeinf_lattice(interp))
316+
argtyp = Conditional(i, Const(true), Const(false))
317+
end
315318
slottypes[i] = argtyp
316319
bb_vartable1[i] = VarState(argtyp, i > nargtypes)
317320
end

base/compiler/tfuncs.jl

+10-1
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,19 @@ end
227227
@nospecs shift_tfunc(𝕃::AbstractLattice, x, y) = shift_tfunc(widenlattice(𝕃), x, y)
228228
@nospecs shift_tfunc(::JLTypeLattice, x, y) = widenconst(x)
229229

230+
function not_tfunc(𝕃::AbstractLattice, @nospecialize(b))
231+
if isa(b, Conditional)
232+
return Conditional(b.slot, b.elsetype, b.thentype)
233+
elseif isa(b, Const)
234+
return Const(not_int(b.val))
235+
end
236+
return math_tfunc(𝕃, b)
237+
end
238+
230239
add_tfunc(and_int, 2, 2, and_int_tfunc, 1)
231240
add_tfunc(or_int, 2, 2, or_int_tfunc, 1)
232241
add_tfunc(xor_int, 2, 2, math_tfunc, 1)
233-
add_tfunc(not_int, 1, 1, math_tfunc, 0) # usually used as not_int(::Bool) to negate a condition
242+
add_tfunc(not_int, 1, 1, not_tfunc, 0) # usually used as not_int(::Bool) to negate a condition
234243
add_tfunc(shl_int, 2, 2, shift_tfunc, 1)
235244
add_tfunc(lshr_int, 2, 2, shift_tfunc, 1)
236245
add_tfunc(ashr_int, 2, 2, shift_tfunc, 1)

test/compiler/inference.jl

+5
Original file line numberDiff line numberDiff line change
@@ -5866,3 +5866,8 @@ end
58665866
bar54341(args...) = foo54341(4, args...)
58675867

58685868
@test Core.Compiler.return_type(bar54341, Tuple{Vararg{Int}}) === Int
5869+
5870+
# InterConditional rt with Vararg argtypes
5871+
fcondvarargs(a, b, c, d) = isa(d, Int64)
5872+
gcondvarargs(a, x...) = return fcondvarargs(a, x...) ? isa(a, Int64) : !isa(a, Int64)
5873+
@test Core.Compiler.return_type(gcondvarargs, Tuple{Vararg{Any}}) === Bool

0 commit comments

Comments
 (0)