Skip to content

Commit 5f10eb9

Browse files
authored
inference: make Limited tracking part of the type lattice (#39116)
This helps refine our knowledge of the `[limited]` flag setting, which previously would always exclude a result from the cache when hitting a cycle. However, we really only need to exclude a result if the result might be dependent on that flag setting. That makes this formally part of the lattice, though can be annoying to work with yet another wrapper, so we try to add/remove it late/early to propagate it when necessary.
1 parent caeacef commit 5f10eb9

File tree

6 files changed

+235
-106
lines changed

6 files changed

+235
-106
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ const _REF_NAME = Ref.body.name
1212
# logic #
1313
#########
1414

15-
# see if the inference result might affect the final answer
16-
call_result_unused(frame::InferenceState, pc::LineNum=frame.currpc) =
17-
isexpr(frame.src.code[frame.currpc], :call) && isempty(frame.ssavalue_uses[pc])
15+
# See if the inference result of the current statement's result value might affect
16+
# the final answer for the method (aside from optimization potential and exceptions).
17+
# To do that, we need to check both for slot assignment and SSA usage.
18+
call_result_unused(frame::InferenceState) =
19+
isexpr(frame.src.code[frame.currpc], :call) && isempty(frame.ssavalue_uses[frame.currpc])
1820

1921
# check if this return type is improvable (i.e. whether it's possible that with
2022
# more information, we might get a more precise type)
@@ -192,6 +194,16 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
192194
end
193195
end
194196
#print("=> ", rettype, "\n")
197+
if rettype isa LimitedAccuracy
198+
union!(sv.pclimitations, rettype.causes)
199+
rettype = rettype.typ
200+
end
201+
if !isempty(sv.pclimitations) # remove self, if present
202+
delete!(sv.pclimitations, sv)
203+
for caller in sv.callers_in_cycle
204+
delete!(sv.pclimitations, caller)
205+
end
206+
end
195207
return CallMeta(rettype, info)
196208
end
197209

@@ -313,7 +325,6 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, @nosp
313325
inf_result = InferenceResult(mi, argtypes)
314326
frame = InferenceState(inf_result, #=cache=#false, interp)
315327
frame === nothing && return Any # this is probably a bad generated function (unsound), but just ignore it
316-
frame.limited = true
317328
frame.parent = sv
318329
push!(inf_cache, inf_result)
319330
typeinf(interp, frame) || return Any
@@ -394,7 +405,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
394405
parent = parent::InferenceState
395406
parent_method2 = parent.src.method_for_inference_limit_heuristics # limit only if user token match
396407
parent_method2 isa Method || (parent_method2 = nothing) # Union{Method, Nothing}
397-
if (parent.cached || parent.limited) && parent.linfo.def === sv.linfo.def && sv_method2 === parent_method2
408+
if (parent.cached || parent.parent !== nothing) && parent.linfo.def === sv.linfo.def && sv_method2 === parent_method2
398409
topmost = infstate
399410
edgecycle = true
400411
end
@@ -443,7 +454,8 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
443454
# (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases)
444455
return Any, true, nothing
445456
end
446-
poison_callstack(sv, topmost::InferenceState, true)
457+
topmost = topmost::InferenceState
458+
poison_callstack(sv, topmost.parent === nothing ? topmost : topmost.parent)
447459
sig = newsig
448460
sparams = svec()
449461
end
@@ -1124,7 +1136,12 @@ function abstract_eval_value(interp::AbstractInterpreter, @nospecialize(e), vtyp
11241136
if isa(e, Expr)
11251137
return abstract_eval_value_expr(interp, e, vtypes, sv)
11261138
else
1127-
return abstract_eval_special_value(interp, e, vtypes, sv)
1139+
typ = abstract_eval_special_value(interp, e, vtypes, sv)
1140+
if typ isa LimitedAccuracy
1141+
union!(sv.pclimitations, typ.causes)
1142+
typ = typ.typ
1143+
end
1144+
return typ
11281145
end
11291146
end
11301147

@@ -1247,13 +1264,21 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
12471264
end
12481265
end
12491266
else
1250-
return abstract_eval_value_expr(interp, e, vtypes, sv)
1267+
t = abstract_eval_value_expr(interp, e, vtypes, sv)
12511268
end
12521269
@assert !isa(t, TypeVar)
12531270
if isa(t, DataType) && isdefined(t, :instance)
12541271
# replace singleton types with their equivalent Const object
12551272
t = Const(t.instance)
12561273
end
1274+
if !isempty(sv.pclimitations)
1275+
if t isa Const || t === Union{}
1276+
empty!(sv.pclimitations)
1277+
else
1278+
t = LimitedAccuracy(t, sv.pclimitations)
1279+
sv.pclimitations = IdSet{InferenceState}()
1280+
end
1281+
end
12571282
return t
12581283
end
12591284

@@ -1308,10 +1333,18 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
13081333
elseif isa(stmt, GotoIfNot)
13091334
condt = abstract_eval_value(interp, stmt.cond, s[pc], frame)
13101335
if condt === Bottom
1336+
empty!(frame.pclimitations)
13111337
break
13121338
end
13131339
condval = maybe_extract_const_bool(condt)
13141340
l = stmt.dest::Int
1341+
if !isempty(frame.pclimitations)
1342+
# we can't model the possible effect of control
1343+
# dependencies on the return value, so we propagate it
1344+
# directly to all the return values (unless we error first)
1345+
condval isa Bool || union!(frame.limitations, frame.pclimitations)
1346+
empty!(frame.pclimitations)
1347+
end
13151348
# constant conditions
13161349
if condval === true
13171350
elseif condval === false
@@ -1346,6 +1379,14 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
13461379
# and is valid inter-procedurally
13471380
rt = widenconst(rt)
13481381
end
1382+
# copy limitations to return value
1383+
if !isempty(frame.pclimitations)
1384+
union!(frame.limitations, frame.pclimitations)
1385+
empty!(frame.pclimitations)
1386+
end
1387+
if !isempty(frame.limitations)
1388+
rt = LimitedAccuracy(rt, copy(frame.limitations))
1389+
end
13491390
if tchanged(rt, frame.bestguess)
13501391
# new (wider) return type for frame
13511392
frame.bestguess = tmerge(frame.bestguess, rt)
@@ -1420,6 +1461,8 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
14201461
end
14211462
end
14221463

1464+
@assert isempty(frame.pclimitations) "unhandled LimitedAccuracy"
1465+
14231466
if t === nothing
14241467
# mark other reached expressions as `Any` to indicate they don't throw
14251468
frame.src.ssavaluetypes[pc] = Any

base/compiler/inferencestate.jl

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ mutable struct InferenceState
1010
slottypes::Vector{Any}
1111
mod::Module
1212
currpc::LineNum
13+
pclimitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on currpc ssavalue
14+
limitations::IdSet{InferenceState} # causes of precision restrictions (LimitedAccuracy) on return
1315

1416
# info on the state of inference and the linfo
1517
src::CodeInfo
@@ -39,7 +41,6 @@ mutable struct InferenceState
3941

4042
# TODO: move these to InferenceResult / Params?
4143
cached::Bool
42-
limited::Bool
4344
inferred::Bool
4445
dont_work_on_me::Bool
4546

@@ -105,6 +106,7 @@ mutable struct InferenceState
105106
frame = new(
106107
InferenceParams(interp), result, linfo,
107108
sp, slottypes, inmodule, 0,
109+
IdSet{InferenceState}(), IdSet{InferenceState}(),
108110
src, get_world_counter(interp), valid_worlds,
109111
nargs, s_types, s_edges, stmt_info,
110112
Union{}, W, 1, n,
@@ -113,7 +115,7 @@ mutable struct InferenceState
113115
Vector{Tuple{InferenceState,LineNum}}(), # cycle_backedges
114116
Vector{InferenceState}(), # callers_in_cycle
115117
#=parent=#nothing,
116-
cached, false, false, false,
118+
cached, false, false,
117119
CachedMethodTable(method_table(interp)),
118120
interp)
119121
result.result = frame
@@ -265,37 +267,13 @@ function add_mt_backedge!(mt::Core.MethodTable, @nospecialize(typ), caller::Infe
265267
nothing
266268
end
267269

268-
function poison_callstack(infstate::InferenceState, topmost::InferenceState, poison_topmost::Bool)
269-
poison_topmost && (topmost = topmost.parent)
270-
while !(infstate === topmost)
271-
if call_result_unused(infstate)
272-
# If we won't propagate the result any further (since it's typically unused),
273-
# it's OK that we keep and cache the "limited" result in the parents
274-
# (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases)
275-
# TODO: we might be able to halt progress much more strongly here,
276-
# since now we know we won't be able to keep anything much that we learned.
277-
# We were mainly only here to compute the calling convention return type,
278-
# but in most situations now, we are unlikely to be able to use that information.
279-
break
280-
end
281-
infstate.limited = true
282-
for infstate_cycle in infstate.callers_in_cycle
283-
infstate_cycle.limited = true
284-
end
285-
infstate = infstate.parent
286-
infstate === nothing && return
287-
end
288-
end
289-
290270
function print_callstack(sv::InferenceState)
291271
while sv !== nothing
292272
print(sv.linfo)
293-
sv.limited && print(" [limited]")
294273
!sv.cached && print(" [uncached]")
295274
println()
296275
for cycle in sv.callers_in_cycle
297276
print(' ', cycle.linfo)
298-
cycle.limited && print(" [limited]")
299277
println()
300278
end
301279
sv = sv.parent

base/compiler/tfuncs.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,10 +1620,14 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
16201620
# output was computed to be constant
16211621
return Const(typeof(rt.val))
16221622
else
1623+
inaccurate = nothing
1624+
rt isa LimitedAccuracy && (inaccurate = rt.causes; rt = rt.typ)
16231625
rt = widenconst(rt)
16241626
if hasuniquerep(rt) || rt === Bottom
16251627
# output type was known for certain
16261628
return Const(rt)
1629+
elseif inaccurate !== nothing
1630+
return LimitedAccuracy(Type{<:rt}, inaccurate)
16271631
elseif (isa(tt, Const) || isconstType(tt)) &&
16281632
(isa(aft, Const) || isconstType(aft))
16291633
# input arguments were known for certain

0 commit comments

Comments
 (0)