Skip to content

Commit fb3ae01

Browse files
authored
inference correctness: fields and globals can revert to undef (#53750)
Particularly under precompilation, fields and globals can revert to being undef, which is heavily relied upon by many `__init__` methods in the ecosystem (because JLL emits this pattern, so we cannot simply disallow it). While here, also fix and improve several places where we compute or use the isdefined check poorly.
1 parent 13d4f0e commit fb3ae01

File tree

5 files changed

+86
-116
lines changed

5 files changed

+86
-116
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,26 +2603,19 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
26032603
t = Bool
26042604
effects = EFFECTS_TOTAL
26052605
exct = Union{}
2606+
isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym))
26062607
if isa(sym, SlotNumber) && vtypes !== nothing
26072608
vtyp = vtypes[slot_id(sym)]
26082609
if vtyp.typ === Bottom
26092610
t = Const(false) # never assigned previously
26102611
elseif !vtyp.undef
26112612
t = Const(true) # definitely assigned previously
26122613
end
2613-
elseif isa(sym, Symbol)
2614-
if isdefined(frame_module(sv), sym)
2615-
t = Const(true)
2616-
elseif InferenceParams(interp).assume_bindings_static
2617-
t = Const(false)
2618-
else
2619-
effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)
2620-
end
26212614
elseif isa(sym, GlobalRef)
2622-
if isdefined(sym.mod, sym.name)
2615+
if InferenceParams(interp).assume_bindings_static
2616+
t = Const(isdefined_globalref(sym))
2617+
elseif isdefinedconst_globalref(sym)
26232618
t = Const(true)
2624-
elseif InferenceParams(interp).assume_bindings_static
2625-
t = Const(false)
26262619
else
26272620
effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)
26282621
end
@@ -2819,9 +2812,10 @@ function override_effects(effects::Effects, override::EffectsOverride)
28192812
end
28202813

28212814
isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g))
2815+
isdefinedconst_globalref(g::GlobalRef) = isconst(g) && isdefined_globalref(g)
28222816

28232817
function abstract_eval_globalref_type(g::GlobalRef)
2824-
if isdefined_globalref(g) && isconst(g)
2818+
if isdefinedconst_globalref(g)
28252819
return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
28262820
end
28272821
ty = ccall(:jl_get_binding_type, Any, (Any, Any), g.mod, g.name)
@@ -2840,11 +2834,15 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::
28402834
if is_mutation_free_argtype(rt)
28412835
inaccessiblememonly = ALWAYS_TRUE
28422836
end
2843-
elseif isdefined_globalref(g)
2844-
nothrow = true
28452837
elseif InferenceParams(interp).assume_bindings_static
28462838
consistent = inaccessiblememonly = ALWAYS_TRUE
2847-
rt = Union{}
2839+
if isdefined_globalref(g)
2840+
nothrow = true
2841+
else
2842+
rt = Union{}
2843+
end
2844+
elseif isdefinedconst_globalref(g)
2845+
nothrow = true
28482846
end
28492847
return RTEffects(rt, nothrow ? Union{} : UndefVarError, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))
28502848
end

base/compiler/optimize.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,7 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe
301301
isa(stmt, GotoNode) && return (true, false, true)
302302
isa(stmt, GotoIfNot) && return (true, false, (𝕃ₒ, argextype(stmt.cond, src), Bool))
303303
if isa(stmt, GlobalRef)
304-
nothrow = isdefined(stmt.mod, stmt.name)
305-
consistent = nothrow && isconst(stmt.mod, stmt.name)
304+
nothrow = consistent = isdefinedconst_globalref(stmt)
306305
return (consistent, nothrow, nothrow)
307306
elseif isa(stmt, Expr)
308307
(; head, args) = stmt

base/compiler/tfuncs.jl

Lines changed: 32 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -392,20 +392,13 @@ end
392392
return isdefined_tfunc(𝕃, arg1, sym)
393393
end
394394
@nospecs function isdefined_tfunc(𝕃::AbstractLattice, arg1, sym)
395-
if isa(arg1, Const)
396-
arg1t = typeof(arg1.val)
397-
else
398-
arg1t = widenconst(arg1)
399-
end
400-
if isType(arg1t)
401-
return Bool
402-
end
395+
arg1t = arg1 isa Const ? typeof(arg1.val) : isconstType(arg1) ? typeof(arg1.parameters[1]) : widenconst(arg1)
403396
a1 = unwrap_unionall(arg1t)
404397
if isa(a1, DataType) && !isabstracttype(a1)
405398
if a1 === Module
406399
hasintersect(widenconst(sym), Symbol) || return Bottom
407400
if isa(sym, Const) && isa(sym.val, Symbol) && isa(arg1, Const) &&
408-
isdefined(arg1.val::Module, sym.val::Symbol)
401+
isdefinedconst_globalref(GlobalRef(arg1.val::Module, sym.val::Symbol))
409402
return Const(true)
410403
end
411404
elseif isa(sym, Const)
@@ -431,9 +424,8 @@ end
431424
elseif idx <= 0 || (!isvatuple(a1) && idx > fieldcount(a1))
432425
return Const(false)
433426
elseif isa(arg1, Const)
434-
arg1v = (arg1::Const).val
435-
if !ismutable(arg1v) || isdefined(arg1v, idx) || isconst(typeof(arg1v), idx)
436-
return Const(isdefined(arg1v, idx))
427+
if !ismutabletype(a1) || isconst(a1, idx)
428+
return Const(isdefined(arg1.val, idx))
437429
end
438430
elseif !isvatuple(a1)
439431
fieldT = fieldtype(a1, idx)
@@ -990,7 +982,7 @@ end
990982
# If we have s00 being a const, we can potentially refine our type-based analysis above
991983
if isa(s00, Const) || isconstType(s00)
992984
if !isa(s00, Const)
993-
sv = s00.parameters[1]
985+
sv = (s00::DataType).parameters[1]
994986
else
995987
sv = s00.val
996988
end
@@ -1000,15 +992,16 @@ end
1000992
isa(sv, Module) && return false
1001993
isa(nval, Int) || return false
1002994
end
1003-
return isdefined(sv, nval)
995+
return isdefined_tfunc(𝕃, s00, name) === Const(true)
1004996
end
1005997
boundscheck && return false
1006998
# If bounds checking is disabled and all fields are assigned,
1007999
# we may assume that we don't throw
10081000
isa(sv, Module) && return false
10091001
name Int || name Symbol || return false
1010-
for i = 1:fieldcount(typeof(sv))
1011-
isdefined(sv, i) || return false
1002+
typeof(sv).name.n_uninitialized == 0 && return true
1003+
for i = (datatype_min_ninitialized(typeof(sv)) + 1):nfields(sv)
1004+
isdefined_tfunc(𝕃, s00, Const(i)) === Const(true) || return false
10121005
end
10131006
return true
10141007
end
@@ -1247,27 +1240,22 @@ end
12471240
return rewrap_unionall(R, s00)
12481241
end
12491242

1250-
@nospecs function getfield_notundefined(typ0, name)
1251-
if isa(typ0, Const) && isa(name, Const)
1252-
typv = typ0.val
1253-
namev = name.val
1254-
isa(typv, Module) && return true
1255-
if isa(namev, Symbol) || isa(namev, Int)
1256-
# Fields are not allowed to transition from defined to undefined, so
1257-
# even if the field is not const, all we need to check here is that
1258-
# it is defined here.
1259-
return isdefined(typv, namev)
1260-
end
1243+
@nospecs function getfield_notuninit(typ0, name)
1244+
if isa(typ0, Const)
1245+
# If the object is Const, then we know exactly the bit patterns that
1246+
# must be returned by getfield if not an error
1247+
return true
12611248
end
12621249
typ0 = widenconst(typ0)
12631250
typ = unwrap_unionall(typ0)
12641251
if isa(typ, Union)
1265-
return getfield_notundefined(rewrap_unionall(typ.a, typ0), name) &&
1266-
getfield_notundefined(rewrap_unionall(typ.b, typ0), name)
1252+
return getfield_notuninit(rewrap_unionall(typ.a, typ0), name) &&
1253+
getfield_notuninit(rewrap_unionall(typ.b, typ0), name)
12671254
end
12681255
isa(typ, DataType) || return false
1269-
if typ.name === Tuple.name || typ.name === _NAMEDTUPLE_NAME
1270-
# tuples and named tuples can't be instantiated with undefined fields,
1256+
isabstracttype(typ) && !isconstType(typ) && return false # cannot say anything about abstract types
1257+
if typ.name.n_uninitialized == 0
1258+
# Types such as tuples and named tuples that can't be instantiated with undefined fields,
12711259
# so we don't need to be conservative here
12721260
return true
12731261
end
@@ -2439,25 +2427,16 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any})
24392427
# consistent if the first arg is immutable
24402428
na = length(argtypes)
24412429
2 na 3 || return EFFECTS_THROWS
2442-
obj, sym = argtypes
2443-
wobj = unwrapva(obj)
2430+
wobj, sym = argtypes
2431+
wobj = unwrapva(wobj)
2432+
sym = unwrapva(sym)
24442433
consistent = CONSISTENT_IF_INACCESSIBLEMEMONLY
24452434
if is_immutable_argtype(wobj)
24462435
consistent = ALWAYS_TRUE
2447-
else
2448-
# Bindings/fields are not allowed to transition from defined to undefined, so even
2449-
# if the object is not immutable, we can prove `:consistent`-cy if it is defined:
2450-
if isa(wobj, Const) && isa(sym, Const)
2451-
objval = wobj.val
2452-
symval = sym.val
2453-
if isa(objval, Module)
2454-
if isa(symval, Symbol) && isdefined(objval, symval)
2455-
consistent = ALWAYS_TRUE
2456-
end
2457-
elseif (isa(symval, Symbol) || isa(symval, Int)) && isdefined(objval, symval)
2458-
consistent = ALWAYS_TRUE
2459-
end
2460-
end
2436+
elseif isdefined_tfunc(𝕃, wobj, sym) isa Const
2437+
# Some bindings/fields are not allowed to transition from defined to undefined or the reverse, so even
2438+
# if the object is not immutable, we can prove `:consistent`-cy of this:
2439+
consistent = ALWAYS_TRUE
24612440
end
24622441
nothrow = isdefined_nothrow(𝕃, argtypes)
24632442
if hasintersect(widenconst(wobj), Module)
@@ -2486,11 +2465,11 @@ function getfield_effects(𝕃::AbstractLattice, argtypes::Vector{Any}, @nospeci
24862465
# taint `:consistent` if accessing `isbitstype`-type object field that may be initialized
24872466
# with undefined value: note that we don't need to taint `:consistent` if accessing
24882467
# uninitialized non-`isbitstype` field since it will simply throw `UndefRefError`
2489-
# NOTE `getfield_notundefined` conservatively checks if this field is never initialized
2468+
# NOTE `getfield_notuninit` conservatively checks if this field is never initialized
24902469
# with undefined value to avoid tainting `:consistent` too aggressively
24912470
# TODO this should probably taint `:noub`, however, it would hinder concrete eval for
24922471
# `REPLInterpreter` that can ignore `:consistent-cy`, causing worse completions
2493-
if !(length(argtypes) 2 && getfield_notundefined(obj, argtypes[2]))
2472+
if !(length(argtypes) 2 && getfield_notuninit(obj, argtypes[2]))
24942473
consistent = ALWAYS_FALSE
24952474
end
24962475
noub = ALWAYS_TRUE
@@ -3125,7 +3104,7 @@ end
31253104
if M isa Const && s isa Const
31263105
M, s = M.val, s.val
31273106
if M isa Module && s isa Symbol
3128-
return isdefined(M, s)
3107+
return isdefinedconst_globalref(GlobalRef(M, s))
31293108
end
31303109
end
31313110
return false
@@ -3198,9 +3177,9 @@ end
31983177
end
31993178

32003179
function global_assignment_nothrow(M::Module, s::Symbol, @nospecialize(newty))
3201-
if isdefined(M, s) && !isconst(M, s)
3180+
if !isconst(M, s)
32023181
ty = ccall(:jl_get_binding_type, Any, (Any, Any), M, s)
3203-
return ty === nothing || newty ty
3182+
return ty isa Type && widenconst(newty) <: ty
32043183
end
32053184
return false
32063185
end
@@ -3216,7 +3195,7 @@ end
32163195
end
32173196
@nospecs function get_binding_type_tfunc(𝕃::AbstractLattice, M, s)
32183197
if get_binding_type_effect_free(M, s)
3219-
return Const(Core.get_binding_type((M::Const).val, (s::Const).val))
3198+
return Const(Core.get_binding_type((M::Const).val::Module, (s::Const).val::Symbol))
32203199
end
32213200
return Type
32223201
end

src/codegen.cpp

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3188,22 +3188,16 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *
31883188
if (bp == NULL)
31893189
return jl_cgval_t();
31903190
bp = julia_binding_pvalue(ctx, bp);
3191+
jl_value_t *ty = nullptr;
31913192
if (bnd) {
31923193
jl_value_t *v = jl_atomic_load_acquire(&bnd->value); // acquire value for ty
3193-
if (v != NULL) {
3194-
if (bnd->constp)
3195-
return mark_julia_const(ctx, v);
3196-
LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*)));
3197-
setName(ctx.emission_context, v, jl_symbol_name(name));
3198-
v->setOrdering(order);
3199-
jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, ctx.tbaa().tbaa_binding);
3200-
ai.decorateInst(v);
3201-
jl_value_t *ty = jl_atomic_load_relaxed(&bnd->ty);
3202-
return mark_julia_type(ctx, v, true, ty);
3203-
}
3194+
if (v != NULL && bnd->constp)
3195+
return mark_julia_const(ctx, v);
3196+
ty = jl_atomic_load_relaxed(&bnd->ty);
32043197
}
3205-
// todo: use type info to avoid undef check
3206-
return emit_checked_var(ctx, bp, name, (jl_value_t*)mod, false, ctx.tbaa().tbaa_binding);
3198+
if (ty == nullptr)
3199+
ty = (jl_value_t*)jl_any_type;
3200+
return update_julia_type(ctx, emit_checked_var(ctx, bp, name, (jl_value_t*)mod, false, ctx.tbaa().tbaa_binding), ty);
32073201
}
32083202

32093203
static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, jl_cgval_t rval, const jl_cgval_t &cmp,
@@ -5461,7 +5455,7 @@ static jl_cgval_t emit_isdefined(jl_codectx_t &ctx, jl_value_t *sym)
54615455
}
54625456
jl_binding_t *bnd = jl_get_binding(modu, name);
54635457
if (bnd) {
5464-
if (jl_atomic_load_relaxed(&bnd->value) != NULL)
5458+
if (jl_atomic_load_acquire(&bnd->value) != NULL && bnd->constp)
54655459
return mark_julia_const(ctx, jl_true);
54665460
Value *bp = julia_binding_gv(ctx, bnd);
54675461
bp = julia_binding_pvalue(ctx, bp);

test/compiler/effects.jl

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -249,52 +249,52 @@ struct SyntacticallyDefined{T}
249249
x::T
250250
end
251251

252-
import Core.Compiler: Const, getfield_notundefined
252+
import Core.Compiler: Const, getfield_notuninit
253253
for T = (Base.RefValue, Maybe) # both mutable and immutable
254254
for name = (Const(1), Const(:x))
255-
@test getfield_notundefined(T{String}, name)
256-
@test getfield_notundefined(T{Integer}, name)
257-
@test getfield_notundefined(T{Union{String,Integer}}, name)
258-
@test getfield_notundefined(Union{T{String},T{Integer}}, name)
259-
@test !getfield_notundefined(T{Int}, name)
260-
@test !getfield_notundefined(T{<:Integer}, name)
261-
@test !getfield_notundefined(T{Union{Int32,Int64}}, name)
262-
@test !getfield_notundefined(T, name)
255+
@test getfield_notuninit(T{String}, name)
256+
@test getfield_notuninit(T{Integer}, name)
257+
@test getfield_notuninit(T{Union{String,Integer}}, name)
258+
@test getfield_notuninit(Union{T{String},T{Integer}}, name)
259+
@test !getfield_notuninit(T{Int}, name)
260+
@test !getfield_notuninit(T{<:Integer}, name)
261+
@test !getfield_notuninit(T{Union{Int32,Int64}}, name)
262+
@test !getfield_notuninit(T, name)
263263
end
264264
# throw doesn't account for undefined behavior
265265
for name = (Const(0), Const(2), Const(1.0), Const(:y), Const("x"),
266266
Float64, String, Nothing)
267-
@test getfield_notundefined(T{String}, name)
268-
@test getfield_notundefined(T{Int}, name)
269-
@test getfield_notundefined(T{Integer}, name)
270-
@test getfield_notundefined(T{<:Integer}, name)
271-
@test getfield_notundefined(T{Union{Int32,Int64}}, name)
272-
@test getfield_notundefined(T, name)
267+
@test getfield_notuninit(T{String}, name)
268+
@test getfield_notuninit(T{Int}, name)
269+
@test getfield_notuninit(T{Integer}, name)
270+
@test getfield_notuninit(T{<:Integer}, name)
271+
@test getfield_notuninit(T{Union{Int32,Int64}}, name)
272+
@test getfield_notuninit(T, name)
273273
end
274274
# should not be too conservative when field isn't known very well but object information is accurate
275-
@test getfield_notundefined(T{String}, Int)
276-
@test getfield_notundefined(T{String}, Symbol)
277-
@test getfield_notundefined(T{Integer}, Int)
278-
@test getfield_notundefined(T{Integer}, Symbol)
279-
@test !getfield_notundefined(T{Int}, Int)
280-
@test !getfield_notundefined(T{Int}, Symbol)
281-
@test !getfield_notundefined(T{<:Integer}, Int)
282-
@test !getfield_notundefined(T{<:Integer}, Symbol)
275+
@test getfield_notuninit(T{String}, Int)
276+
@test getfield_notuninit(T{String}, Symbol)
277+
@test getfield_notuninit(T{Integer}, Int)
278+
@test getfield_notuninit(T{Integer}, Symbol)
279+
@test !getfield_notuninit(T{Int}, Int)
280+
@test !getfield_notuninit(T{Int}, Symbol)
281+
@test !getfield_notuninit(T{<:Integer}, Int)
282+
@test !getfield_notuninit(T{<:Integer}, Symbol)
283283
end
284284
# should be conservative when object information isn't accurate
285-
@test !getfield_notundefined(Any, Const(1))
286-
@test !getfield_notundefined(Any, Const(:x))
285+
@test !getfield_notuninit(Any, Const(1))
286+
@test !getfield_notuninit(Any, Const(:x))
287287
# tuples and namedtuples should be okay if not given accurate information
288288
for TupleType = Any[Tuple{Int,Int,Int}, Tuple{Int,Vararg{Int}}, Tuple{Any}, Tuple,
289289
NamedTuple{(:a, :b), Tuple{Int,Int}}, NamedTuple{(:x,),Tuple{Any}}, NamedTuple],
290290
FieldType = Any[Int, Symbol, Any]
291-
@test getfield_notundefined(TupleType, FieldType)
291+
@test getfield_notuninit(TupleType, FieldType)
292292
end
293293
# skip analysis on fields that are known to be defined syntactically
294-
@test Core.Compiler.getfield_notundefined(SyntacticallyDefined{Float64}, Symbol)
295-
@test Core.Compiler.getfield_notundefined(Const(Main), Const(:var))
296-
@test Core.Compiler.getfield_notundefined(Const(Main), Const(42))
297-
# high-level tests for `getfield_notundefined`
294+
@test Core.Compiler.getfield_notuninit(SyntacticallyDefined{Float64}, Symbol)
295+
@test Core.Compiler.getfield_notuninit(Const(Main), Const(:var))
296+
@test Core.Compiler.getfield_notuninit(Const(Main), Const(42))
297+
# high-level tests for `getfield_notuninit`
298298
@test Base.infer_effects() do
299299
Maybe{Int}()
300300
end |> !Core.Compiler.is_consistent
@@ -904,7 +904,7 @@ end |> Core.Compiler.is_foldable_nothrow
904904
@test Base.infer_effects(Tuple{WrapperOneField{Float64}, Symbol}) do w, s
905905
getfield(w, s)
906906
end |> Core.Compiler.is_foldable
907-
@test Core.Compiler.getfield_notundefined(WrapperOneField{Float64}, Symbol)
907+
@test Core.Compiler.getfield_notuninit(WrapperOneField{Float64}, Symbol)
908908
@test Base.infer_effects(Tuple{WrapperOneField{Symbol}, Symbol}) do w, s
909909
getfield(w, s)
910910
end |> Core.Compiler.is_foldable
@@ -996,7 +996,7 @@ end
996996
let effects = Base.infer_effects() do
997997
isdefined(defined_ref, :x)
998998
end
999-
@test Core.Compiler.is_consistent(effects)
999+
@test !Core.Compiler.is_consistent(effects)
10001000
@test Core.Compiler.is_nothrow(effects)
10011001
end
10021002
let effects = Base.infer_effects() do

0 commit comments

Comments
 (0)