Skip to content

Commit 3d85309

Browse files
authored
Consolidate various isdefined functionality into a new builtin (#56985)
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 cfd3922 commit 3d85309

15 files changed

+245
-103
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: 79 additions & 26 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,21 +3196,73 @@ 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
3220-
elseif isexpr(sym, :static_parameter)
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 isexpr(sym, :static_parameter)
32213266
n = sym.args[1]::Int
32223267
if 1 <= n <= length(sv.sptypes)
32233268
sp = sv.sptypes[n]
@@ -3443,22 +3488,31 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode,
34433488
return partition_restriction(partition)
34443489
end
34453490

3446-
function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
3491+
function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
34473492
force_binding_resolution!(g)
34483493
partition = lookup_binding_partition(get_inference_world(interp), g)
34493494
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
3495+
partition
3496+
end
34503497

3498+
function walk_binding_partition!(interp::AbstractInterpreter, partition::Core.BindingPartition, sv::AbsIntState)
34513499
while is_some_imported(binding_kind(partition))
34523500
imported_binding = partition_restriction(partition)::Core.Binding
34533501
partition = lookup_binding_partition(get_inference_world(interp), imported_binding)
34543502
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
34553503
end
3504+
return partition
3505+
end
34563506

3507+
function abstract_eval_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
3508+
partition = lookup_binding_partition!(interp, g, sv)
3509+
partition = walk_binding_partition!(interp, partition, sv)
34573510
return partition
34583511
end
34593512

34603513
function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Core.BindingPartition)
3461-
if is_some_guard(binding_kind(partition))
3514+
kind = binding_kind(partition)
3515+
if is_some_guard(kind) || kind == BINDING_KIND_UNDEF_CONST
34623516
if InferenceParams(interp).assume_bindings_static
34633517
return RTEffects(Union{}, UndefVarError, EFFECTS_THROWS)
34643518
else
@@ -3468,13 +3522,12 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co
34683522
end
34693523
end
34703524

3471-
if is_some_const_binding(binding_kind(partition))
3525+
if is_defined_const_binding(kind)
34723526
rt = Const(partition_restriction(partition))
34733527
return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE))
34743528
end
34753529

34763530
rt = partition_restriction(partition)
3477-
34783531
return RTEffects(rt, UndefVarError, generic_getglobal_effects)
34793532
end
34803533

Compiler/src/ssair/verify.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
366366
@verify_error "Assignment should have been removed during SSA conversion"
367367
raise_error()
368368
elseif stmt.head === :isdefined
369-
if length(stmt.args) > 2 || (length(stmt.args) == 2 && !isa(stmt.args[2], Bool))
369+
if length(stmt.args) > 2
370370
@verify_error "malformed isdefined"
371371
raise_error()
372372
end
@@ -382,7 +382,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
382382
elseif stmt.head === :foreigncall
383383
isforeigncall = true
384384
elseif stmt.head === :isdefined && length(stmt.args) == 1 &&
385-
(stmt.args[1] isa GlobalRef || isexpr(stmt.args[1], :static_parameter))
385+
isexpr(stmt.args[1], :static_parameter)
386386
# a GlobalRef or static_parameter isdefined check does not evaluate its argument
387387
continue
388388
elseif stmt.head === :call

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

doc/src/devdocs/ast.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,8 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form.
426426

427427
* `isdefined`
428428

429-
`Expr(:isdefined, :x [, allow_import])` returns a Bool indicating whether `x` has
430-
already been defined in the current scope. The optional second argument is a boolean
431-
that specifies whether `x` should be considered defined by an import or if only constants
432-
or globals in the current module count as being defined. If `x` is not a global, the argument
433-
is ignored.
429+
`Expr(:isdefined, :x)` returns a Bool indicating whether `x` has
430+
already been defined in the current scope.
434431

435432
* `the_exception`
436433

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)