Skip to content

Commit 6d957c3

Browse files
committed
Consolidate various isdefined functionality into a new builtin
In #54999 I extended `:isdefined` with the ability to specify whether or not to consider imported bindings defined. As a result, we now have two mechanisms for querying `isdefined` on globals (the other being `Core.isdefined`) with incompatible feature sets (`Core.isdefined` supports an atomic ordering argument, but not `allow_import`). Additionally, only one of them had proper codegen support. I also don't like to have IR forms for things that could be perfectly well handled by builtin calls (along the lines of #56713). So this tries to clean that all up by: 1. Adding a new builtin `isdefinedglobal` that has the full feature set 2. Dropping `:isdefined` on globals as an IR form (the frontend form gets lowered to the intrinsic if called on globals) 3. Wiring up codegen and correcting inference for that new builtin An additional motivation is that `isdefined` on globals needs support for partition edges (like other builtins), and having to have a special case for :isdefined was marginally annoying. Just using an intrinsic for this is much cleaner. Lastly, the reason for a new intrinsic over extending the existing `isdefined`, is that over time we've moved away from conflating fields and globals for Module (e.g. introducing `getglobal`/`setglobal!`), so this is a natural extension of that. Of course, the existing behavior is retained for ordinary `isdefined`.
1 parent ec2b509 commit 6d957c3

File tree

13 files changed

+244
-95
lines changed

13 files changed

+244
-95
lines changed

Compiler/src/Compiler.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc
4949

5050
using Base
5151
using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer,
52-
BINDING_KIND_GLOBAL, Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
52+
BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
5353
EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES,
5454
OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME,
5555
_array_for, _bits_findnext, _methods_by_ftype, _uniontypes, all, allocatedinline, any,
@@ -58,7 +58,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali
5858
datatype_pointerfree, decode_effects_override, diff_names, fieldindex,
5959
generating_output, get_nospecializeinfer_sig, get_world_counter, has_free_typevars,
6060
hasgenerator, hasintersect, indexed_iterate, isType, is_file_tracked, is_function_def,
61-
is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer,
61+
is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_defined_const_binding,
6262
is_some_const_binding, is_some_guard, is_some_imported, is_valid_intrinsic_elptr,
6363
isbitsunion, isconcretedispatch, isdispatchelem, isexpr, isfieldatomic, isidentityfree,
6464
iskindtype, ismutabletypename, ismutationfree, issingletontype, isvarargtype, isvatuple,

Compiler/src/abstractinterpretation.jl

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,21 +2637,14 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f),
26372637
elseif f === Core.getfield && argtypes_are_actually_getglobal(argtypes)
26382638
return Future(abstract_eval_getglobal(interp, sv, si.saw_latestworld, argtypes))
26392639
elseif f === Core.isdefined && argtypes_are_actually_getglobal(argtypes)
2640-
exct = Bottom
2641-
if length(argtypes) == 4
2642-
order = argtypes[4]
2643-
exct = global_order_exct(order, #=loading=#true, #=storing=#false)
2644-
if !(isa(order, Const) && get_atomic_order(order.val, #=loading=#true, #=storing=#false).x >= MEMORY_ORDER_UNORDERED.x)
2645-
exct = Union{exct, ConcurrencyViolationError}
2646-
end
2647-
end
2648-
return Future(merge_exct(CallMeta(abstract_eval_isdefined(
2649-
interp,
2650-
GlobalRef((argtypes[2]::Const).val::Module,
2651-
(argtypes[3]::Const).val::Symbol),
2652-
si.saw_latestworld,
2653-
sv),
2654-
NoCallInfo()), exct))
2640+
return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3], Const(true),
2641+
length(argtypes) == 4 ? argtypes[4] : Const(:unordered),
2642+
si.saw_latestworld, sv))
2643+
elseif f === Core.isdefinedglobal && 3 <= length(argtypes) <= 5
2644+
return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3],
2645+
length(argtypes) >= 4 ? argtypes[4] : Const(true),
2646+
length(argtypes) >= 5 ? argtypes[5] : Const(:unordered),
2647+
si.saw_latestworld, sv))
26552648
elseif f === Core.get_binding_type
26562649
return Future(abstract_eval_get_binding_type(interp, sv, argtypes))
26572650
end
@@ -3203,20 +3196,76 @@ function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, ssta
32033196
return abstract_eval_isdefined(interp, sym, sstate.saw_latestworld, sv)
32043197
end
32053198

3206-
function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), saw_latestworld::Bool, sv::AbsIntState)
3199+
const generic_isdefinedglobal_effects = Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE, nothrow=false)
3200+
function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, mod::Module, sym::Symbol, allow_import::Union{Bool, Nothing}, saw_latestworld::Bool, sv::AbsIntState)
32073201
rt = Bool
3202+
if saw_latestworld
3203+
return RTEffects(rt, Union{}, Effects(generic_isdefinedglobal_effects, nothrow=true))
3204+
end
3205+
32083206
effects = EFFECTS_TOTAL
3209-
exct = Union{}
3210-
isa(sym, Symbol) && (sym = GlobalRef(frame_module(sv), sym))
3211-
if isa(sym, GlobalRef)
3212-
rte = abstract_eval_globalref(interp, sym, saw_latestworld, sv)
3207+
partition = lookup_binding_partition!(interp, GlobalRef(mod, sym), sv)
3208+
if allow_import !== true && is_some_imported(binding_kind(partition))
3209+
if allow_import === false
3210+
rt = Const(false)
3211+
else
3212+
effects = Effects(generic_isdefinedglobal_effects, nothrow=true)
3213+
end
3214+
else
3215+
partition = walk_binding_partition!(interp, partition, sv)
3216+
rte = abstract_eval_partition_load(interp, partition)
32133217
if rte.exct == Union{}
32143218
rt = Const(true)
32153219
elseif rte.rt === Union{} && rte.exct === UndefVarError
32163220
rt = Const(false)
32173221
else
3218-
effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)
3222+
effects = Effects(generic_isdefinedglobal_effects, nothrow=true)
3223+
end
3224+
end
3225+
return RTEffects(rt, Union{}, effects)
3226+
end
3227+
3228+
function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, @nospecialize(M), @nospecialize(s), @nospecialize(allow_import_arg), @nospecialize(order_arg), saw_latestworld::Bool, sv::AbsIntState)
3229+
exct = Bottom
3230+
allow_import = true
3231+
if allow_import_arg !== nothing
3232+
if !isa(allow_import_arg, Const)
3233+
allow_import = nothing
3234+
if widenconst(allow_import_arg) != Bool
3235+
exct = Union{exct, TypeError}
3236+
end
3237+
else
3238+
allow_import = allow_import_arg.val
3239+
end
3240+
end
3241+
if order_arg !== nothing
3242+
exct = global_order_exct(order_arg, #=loading=#true, #=storing=#false)
3243+
if !(isa(order_arg, Const) && get_atomic_order(order_arg.val, #=loading=#true, #=storing=#false).x >= MEMORY_ORDER_UNORDERED.x)
3244+
exct = Union{exct, ConcurrencyViolationError}
3245+
end
3246+
end
3247+
if M isa Const && s isa Const
3248+
M, s = M.val, s.val
3249+
if M isa Module && s isa Symbol
3250+
return merge_exct(CallMeta(abstract_eval_isdefinedglobal(interp, M, s, allow_import, saw_latestworld, sv), NoCallInfo()), exct)
32193251
end
3252+
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
3253+
elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol)
3254+
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
3255+
elseif M Module && s Symbol
3256+
return CallMeta(Bool, Union{exct, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo())
3257+
end
3258+
return CallMeta(Bool, Union{exct, TypeError, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo())
3259+
end
3260+
3261+
function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), saw_latestworld::Bool, sv::AbsIntState)
3262+
rt = Bool
3263+
effects = EFFECTS_TOTAL
3264+
exct = Union{}
3265+
if isa(sym, Symbol)
3266+
return abstract_eval_isdefinedglobal(interp, frame_module(sv), sym, true, saw_latestworld, sv)
3267+
elseif isa(sym, GlobalRef)
3268+
return abstract_eval_isdefinedglobal(interp, sym.mod, sym.name, true, saw_latestworld, sv)
32203269
elseif isexpr(sym, :static_parameter)
32213270
n = sym.args[1]::Int
32223271
if 1 <= n <= length(sv.sptypes)
@@ -3443,22 +3492,31 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode,
34433492
return partition_restriction(partition)
34443493
end
34453494

3446-
function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
3495+
function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
34473496
force_binding_resolution!(g)
34483497
partition = lookup_binding_partition(get_inference_world(interp), g)
34493498
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
3499+
partition
3500+
end
34503501

3502+
function walk_binding_partition!(interp::AbstractInterpreter, partition::Core.BindingPartition, sv::AbsIntState)
34513503
while is_some_imported(binding_kind(partition))
34523504
imported_binding = partition_restriction(partition)::Core.Binding
34533505
partition = lookup_binding_partition(get_inference_world(interp), imported_binding)
34543506
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
34553507
end
3508+
return partition
3509+
end
34563510

3511+
function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
3512+
partition = lookup_binding_partition!(interp, g, sv)
3513+
partition = walk_binding_partition!(interp, partition, sv)
34573514
return partition
34583515
end
34593516

34603517
function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Core.BindingPartition)
3461-
if is_some_guard(binding_kind(partition))
3518+
kind = binding_kind(partition)
3519+
if is_some_guard(kind) || kind == BINDING_KIND_UNDEF_CONST
34623520
if InferenceParams(interp).assume_bindings_static
34633521
return RTEffects(Union{}, UndefVarError, EFFECTS_THROWS)
34643522
else
@@ -3468,13 +3526,12 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co
34683526
end
34693527
end
34703528

3471-
if is_some_const_binding(binding_kind(partition))
3529+
if is_defined_const_binding(kind)
34723530
rt = Const(partition_restriction(partition))
34733531
return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE))
34743532
end
34753533

34763534
rt = partition_restriction(partition)
3477-
34783535
return RTEffects(rt, UndefVarError, generic_getglobal_effects)
34793536
end
34803537

base/boot.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export
231231
fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!, setfieldonce!,
232232
nfields, throw, tuple, ===, isdefined, eval,
233233
# access to globals
234-
getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!,
234+
getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!, isdefinedglobal,
235235
# ifelse, sizeof # not exported, to avoid conflicting with Base
236236
# type reflection
237237
<:, typeof, isa, typeassert,

base/docs/basedocs.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,9 @@ compatible with the stores to that location. Otherwise, if not declared as
27542754
27552755
To test whether an array element is defined, use [`isassigned`](@ref) instead.
27562756
2757+
The global variable variant is supported for compatibility with older julia
2758+
releases. For new code, prefer [`isdefinedglobal`](@ref).
2759+
27572760
See also [`@isdefined`](@ref).
27582761
27592762
# Examples
@@ -2781,6 +2784,37 @@ false
27812784
"""
27822785
isdefined
27832786

2787+
2788+
"""
2789+
isdefinedglobal(m::Module, s::Symbol, [allow_import::Bool=true, [order::Symbol=:unordered]])
2790+
2791+
Tests whether a global variable `s` is defined in module `m` (in the current world age).
2792+
A variable is considered defined if and only if a value may be read from this global variable
2793+
and an access will not throw. This includes both constants and global variables that have
2794+
a value set.
2795+
2796+
If `allow_import` is `false`, the global variable must be defined inside `m`
2797+
and may not be imported from another module.
2798+
2799+
See also [`@isdefined`](@ref).
2800+
2801+
# Examples
2802+
```jldoctest
2803+
julia> isdefinedglobal(Base, :sum)
2804+
true
2805+
2806+
julia> isdefinedglobal(Base, :NonExistentMethod)
2807+
false
2808+
2809+
julia> isdefinedglobal(Base, :sum, false)
2810+
true
2811+
2812+
julia> isdefinedglobal(Main, :sum, false)
2813+
false
2814+
```
2815+
"""
2816+
isdefinedglobal
2817+
27842818
"""
27852819
Memory{T}(undef, n)
27862820

doc/src/base/base.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ Core.replacefield!
166166
Core.swapfield!
167167
Core.setfieldonce!
168168
Core.isdefined
169+
Core.isdefinedglobal
169170
Base.@isdefined
170171
Base.convert
171172
Base.promote

src/builtin_proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ DECLARE_BUILTIN(invoke);
4545
DECLARE_BUILTIN(is);
4646
DECLARE_BUILTIN(isa);
4747
DECLARE_BUILTIN(isdefined);
48+
DECLARE_BUILTIN(isdefinedglobal);
4849
DECLARE_BUILTIN(issubtype);
4950
DECLARE_BUILTIN(memorynew);
5051
DECLARE_BUILTIN(memoryref);

src/builtins.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,33 @@ JL_CALLABLE(jl_f_getglobal)
13521352
return v;
13531353
}
13541354

1355+
JL_CALLABLE(jl_f_isdefinedglobal)
1356+
{
1357+
jl_module_t *m = NULL;
1358+
jl_sym_t *s = NULL;
1359+
JL_NARGS(isdefined, 2, 3);
1360+
int allow_import = 1;
1361+
enum jl_memory_order order = jl_memory_order_unspecified;
1362+
JL_TYPECHK(isdefined, module, args[0]);
1363+
JL_TYPECHK(isdefined, symbol, args[1]);
1364+
if (nargs == 3) {
1365+
JL_TYPECHK(isdefined, bool, args[2]);
1366+
allow_import = jl_unbox_bool(args[2]);
1367+
}
1368+
if (nargs == 4) {
1369+
JL_TYPECHK(isdefined, symbol, args[3]);
1370+
order = jl_get_atomic_order_checked((jl_sym_t*)args[2], 1, 0);
1371+
}
1372+
m = (jl_module_t*)args[0];
1373+
s = (jl_sym_t*)args[1];
1374+
if (order == jl_memory_order_unspecified)
1375+
order = jl_memory_order_unordered;
1376+
if (order < jl_memory_order_unordered)
1377+
jl_atomic_error("isdefined: module binding cannot be accessed non-atomically");
1378+
int bound = jl_boundp(m, s, allow_import); // seq_cst always
1379+
return bound ? jl_true : jl_false;
1380+
}
1381+
13551382
JL_CALLABLE(jl_f_setglobal)
13561383
{
13571384
enum jl_memory_order order = jl_memory_order_release;
@@ -2451,6 +2478,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
24512478
// module bindings
24522479
jl_builtin_getglobal = add_builtin_func("getglobal", jl_f_getglobal);
24532480
jl_builtin_setglobal = add_builtin_func("setglobal!", jl_f_setglobal);
2481+
jl_builtin_isdefinedglobal = add_builtin_func("isdefinedglobal", jl_f_isdefinedglobal);
24542482
add_builtin_func("get_binding_type", jl_f_get_binding_type);
24552483
jl_builtin_swapglobal = add_builtin_func("swapglobal!", jl_f_swapglobal);
24562484
jl_builtin_replaceglobal = add_builtin_func("replaceglobal!", jl_f_replaceglobal);

0 commit comments

Comments
 (0)