Skip to content

Commit c1f5037

Browse files
committed
bpart: Give a warning when accessing a backdated const binding
This implements the strategy proposed in #57102 (comment). Example: ``` julia> function foo(i) eval(:(const x = $i)) x end foo (generic function with 1 method) julia> foo(1) WARNING: Detected access to binding Main.x in a world prior to its definition world. Julia 1.12 has introduced more strict world age semantics for global bindings. !!! This code may malfunction under Revise. !!! This code will error in future versions of Julia. Hint: Add an appropriate `invokelatest` around the access to this binding. 1 ``` The warning is triggered once per binding to avoid spamming for repeated access.
1 parent 61e8f1d commit c1f5037

File tree

15 files changed

+114
-45
lines changed

15 files changed

+114
-45
lines changed

Compiler/src/Compiler.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ 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, BINDING_KIND_UNDEF_CONST, Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
52+
BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_CONST,
53+
Base, BitVector, Bottom, Callable, DataTypeFieldDesc,
5354
EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES,
5455
OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME,
5556
_array_for, _bits_findnext, _methods_by_ftype, _uniontypes, all, allocatedinline, any,

Compiler/src/abstractinterpretation.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3524,6 +3524,11 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co
35243524
end
35253525

35263526
if is_defined_const_binding(kind)
3527+
if kind == BINDING_KIND_BACKDATED_CONST
3528+
# Infer this as guard. We do not want a later const definition to retroactively improve
3529+
# inference results in an earlier world.
3530+
return RTEffects(Any, UndefVarError, generic_getglobal_effects)
3531+
end
35273532
rt = Const(partition_restriction(partition))
35283533
return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE))
35293534
end

Compiler/src/ssair/slot2ssa.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,6 @@ function fixemup!(@specialize(slot_filter), @specialize(rename_slot), ir::IRCode
137137
return nothing
138138
end
139139
op[] = x
140-
elseif isa(val, GlobalRef) && !(isdefined(val.mod, val.name) && isconst(val.mod, val.name))
141-
typ = typ_for_val(val, ci, ir, idx, Any[])
142-
new_inst = NewInstruction(val, typ)
143-
op[] = NewSSAValue(insert_node!(ir, idx, new_inst).id - length(ir.stmts))
144140
elseif isexpr(val, :static_parameter)
145141
ty = typ_for_val(val, ci, ir, idx, Any[])
146142
if isa(ty, Const)

Compiler/test/invalidation.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ let mi = Base.method_instance(basic_caller, (Float64,))
5555
end
5656

5757
# this redefinition below should invalidate the cache
58-
const BASIC_CALLER_WORLD = Base.get_world_counter()
58+
const BASIC_CALLER_WORLD = Base.get_world_counter()+1
5959
basic_callee(x) = x, x
6060
@test !isdefined(Base.method_instance(basic_callee, (Float64,)), :cache)
6161
let mi = Base.method_instance(basic_caller, (Float64,))

base/Base_compiler.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ using .Order
257257
include("coreir.jl")
258258
include("invalidation.jl")
259259

260+
# Because lowering inserts direct references, it is mandatory for this binding
261+
# to exist before we start inferring code.
262+
function string end
263+
260264
# For OS specific stuff
261265
# We need to strcat things here, before strings are really defined
262266
function strcat(x::String, y::String)

base/boot.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,8 @@ macro __doc__(x)
717717
end
718718

719719
isbasicdoc(@nospecialize x) = (isa(x, Expr) && x.head === :.) || isa(x, Union{QuoteNode, Symbol})
720-
iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(ex.args[1]) : (isa(ex, Expr) && ex.head === :call)
720+
firstarg(arg1, args...) = arg1
721+
iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(firstarg(ex.args...)) : (isa(ex, Expr) && ex.head === :call)
721722
iscallexpr(ex) = false
722723
function ignoredoc(source, mod, str, expr)
723724
(isbasicdoc(expr) || iscallexpr(expr)) && return Expr(:escape, nothing)

base/docs/bindings.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ end
1616

1717
bindingexpr(x) = Expr(:call, Binding, splitexpr(x)...)
1818

19-
defined(b::Binding) = isdefined(b.mod, b.var)
20-
resolve(b::Binding) = getfield(b.mod, b.var)
19+
defined(b::Binding) = invokelatest(isdefined, b.mod, b.var)
20+
resolve(b::Binding) = invokelatest(getfield, b.mod, b.var)
2121

2222
function splitexpr(x::Expr)
2323
isexpr(x, :macrocall) ? splitexpr(x.args[1]) :

base/runtime_internals.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,9 @@ const BINDING_KIND_FAILED = 0x6
229229
const BINDING_KIND_DECLARED = 0x7
230230
const BINDING_KIND_GUARD = 0x8
231231
const BINDING_KIND_UNDEF_CONST = 0x9
232+
const BINDING_KIND_BACKDATED_CONST = 0xa
232233

233-
is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT)
234+
is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_BACKDATED_CONST)
234235
is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == BINDING_KIND_UNDEF_CONST)
235236
is_some_imported(kind::UInt8) = (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED)
236237
is_some_guard(kind::UInt8) = (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST)

base/show.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,8 @@ function show_function(io::IO, f::Function, compact::Bool, fallback::Function)
542542
fallback(io, f)
543543
elseif compact
544544
print(io, mt.name)
545-
elseif isdefined(mt, :module) && isdefined(mt.module, mt.name) &&
546-
getfield(mt.module, mt.name) === f
545+
elseif isdefined(mt, :module) && isdefinedglobal(mt.module, mt.name) &&
546+
getglobal(mt.module, mt.name) === f
547547
# this used to call the removed internal function `is_exported_from_stdlib`, which effectively
548548
# just checked for exports from Core and Base.
549549
mod = get(io, :module, UsesCoreAndBaseOnly)
@@ -1025,15 +1025,15 @@ function isvisible(sym::Symbol, parent::Module, from::Module)
10251025
from_owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), from, sym)
10261026
return owner !== C_NULL && from_owner === owner &&
10271027
!isdeprecated(parent, sym) &&
1028-
isdefined(from, sym) # if we're going to return true, force binding resolution
1028+
isdefinedglobal(from, sym) # if we're going to return true, force binding resolution
10291029
end
10301030

10311031
function is_global_function(tn::Core.TypeName, globname::Union{Symbol,Nothing})
10321032
if globname !== nothing
10331033
globname_str = string(globname::Symbol)
10341034
if ('#' globname_str && '@' globname_str && isdefined(tn, :module) &&
1035-
isbindingresolved(tn.module, globname) && isdefined(tn.module, globname) &&
1036-
isconcretetype(tn.wrapper) && isa(getfield(tn.module, globname), tn.wrapper))
1035+
isbindingresolved(tn.module, globname) && isdefinedglobal(tn.module, globname) &&
1036+
isconcretetype(tn.wrapper) && isa(getglobal(tn.module, globname), tn.wrapper))
10371037
return true
10381038
end
10391039
end
@@ -3364,7 +3364,10 @@ function print_partition(io::IO, partition::Core.BindingPartition)
33643364
end
33653365
print(io, " - ")
33663366
kind = binding_kind(partition)
3367-
if is_defined_const_binding(kind)
3367+
if kind == BINDING_KIND_BACKDATED_CONST
3368+
print(io, "backdated constant binding to ")
3369+
print(io, partition_restriction(partition))
3370+
elseif is_defined_const_binding(kind)
33683371
print(io, "constant binding to ")
33693372
print(io, partition_restriction(partition))
33703373
elseif kind == BINDING_KIND_UNDEF_CONST

src/codegen.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3467,7 +3467,8 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *
34673467
break;
34683468
pku = jl_atomic_load_acquire(&bpart->restriction);
34693469
}
3470-
if (bpart && jl_bkind_is_some_constant(decode_restriction_kind(pku))) {
3470+
enum jl_partition_kind kind = decode_restriction_kind(pku);
3471+
if (bpart && (jl_bkind_is_some_constant(kind) && kind != BINDING_KIND_BACKDATED_CONST)) {
34713472
jl_value_t *constval = decode_restriction_value(pku);
34723473
if (!constval) {
34733474
undef_var_error_ifnot(ctx, ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), name, (jl_value_t*)mod);

0 commit comments

Comments
 (0)