From ee6a96fba46964a4067c3bba2e9c42820d8f987f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Wed, 22 Jan 2025 18:10:24 +0000 Subject: [PATCH] bpart: Give a warning when accessing a backdated const binding This implements the strategy proposed in https://github.com/JuliaLang/julia/pull/57102#issuecomment-2605511266. 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. --- Compiler/src/Compiler.jl | 3 +- Compiler/src/abstractinterpretation.jl | 5 ++ Compiler/src/ssair/slot2ssa.jl | 4 -- Compiler/src/ssair/verify.jl | 11 +++- Compiler/test/invalidation.jl | 2 +- base/Base_compiler.jl | 4 ++ base/boot.jl | 8 ++- base/docs/bindings.jl | 4 +- base/runtime_internals.jl | 3 +- base/show.jl | 17 +++--- doc/src/manual/methods.md | 5 +- src/codegen.cpp | 3 +- src/julia.h | 22 +++++--- src/julia_internal.h | 12 +++- src/module.c | 77 +++++++++++++++++++++----- src/rtutils.c | 6 +- src/toplevel.c | 23 +++++--- stdlib/REPL/src/REPL.jl | 4 +- test/worlds.jl | 8 +-- 19 files changed, 157 insertions(+), 64 deletions(-) diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index ea939f86422c5..b83dc0b5970e1 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -49,7 +49,8 @@ using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstanc using Base using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospecializeinfer, - BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, Base, BitVector, Bottom, Callable, DataTypeFieldDesc, + BINDING_KIND_GLOBAL, BINDING_KIND_UNDEF_CONST, BINDING_KIND_BACKDATED_CONST, + Base, BitVector, Bottom, Callable, DataTypeFieldDesc, EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES, OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME, _array_for, _bits_findnext, _methods_by_ftype, _uniontypes, all, allocatedinline, any, diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index 2b1a7fb2dd448..dbd7e76615116 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -3524,6 +3524,11 @@ function abstract_eval_partition_load(interp::AbstractInterpreter, partition::Co end if is_defined_const_binding(kind) + if kind == BINDING_KIND_BACKDATED_CONST + # Infer this as guard. We do not want a later const definition to retroactively improve + # inference results in an earlier world. + return RTEffects(Any, UndefVarError, generic_getglobal_effects) + end rt = Const(partition_restriction(partition)) return RTEffects(rt, Union{}, Effects(EFFECTS_TOTAL, inaccessiblememonly=is_mutation_free_argtype(rt) ? ALWAYS_TRUE : ALWAYS_FALSE)) end diff --git a/Compiler/src/ssair/slot2ssa.jl b/Compiler/src/ssair/slot2ssa.jl index 80dffdab23243..e0f3e207789a3 100644 --- a/Compiler/src/ssair/slot2ssa.jl +++ b/Compiler/src/ssair/slot2ssa.jl @@ -137,10 +137,6 @@ function fixemup!(@specialize(slot_filter), @specialize(rename_slot), ir::IRCode return nothing end op[] = x - elseif isa(val, GlobalRef) && !(isdefined(val.mod, val.name) && isconst(val.mod, val.name)) - typ = typ_for_val(val, ci, ir, idx, Any[]) - new_inst = NewInstruction(val, typ) - op[] = NewSSAValue(insert_node!(ir, idx, new_inst).id - length(ir.stmts)) elseif isexpr(val, :static_parameter) ty = typ_for_val(val, ci, ir, idx, Any[]) if isa(ty, Const) diff --git a/Compiler/src/ssair/verify.jl b/Compiler/src/ssair/verify.jl index fa16bdcc7ab19..779a28afe9f56 100644 --- a/Compiler/src/ssair/verify.jl +++ b/Compiler/src/ssair/verify.jl @@ -121,7 +121,7 @@ function verify_ir(ir::IRCode, print::Bool=true, if mi !== nothing push!(error_args, "\n", " Method instance: ", mi) end - error(error_args...) + invokelatest(error, error_args...) end # Verify CFG graph. Must be well formed to construct domtree if !(length(ir.cfg.blocks) - 1 <= length(ir.cfg.index) <= length(ir.cfg.blocks)) @@ -380,6 +380,15 @@ function verify_ir(ir::IRCode, print::Bool=true, # undefined GlobalRef is OK in isdefined continue end + elseif stmt.head === :throw_undef_if_not + if length(stmt.args) > 3 + @verify_error "malformed throw_undef_if_not" + raise_error() + end + if stmt.args[1] isa GlobalRef + # undefined GlobalRef is OK in throw_undef_if_not + continue + end elseif stmt.head === :gc_preserve_end # We allow gc_preserve_end tokens to span across try/catch # blocks, which isn't allowed for regular SSA values, so diff --git a/Compiler/test/invalidation.jl b/Compiler/test/invalidation.jl index 2642c1647a682..b77c7677e6987 100644 --- a/Compiler/test/invalidation.jl +++ b/Compiler/test/invalidation.jl @@ -55,7 +55,7 @@ let mi = Base.method_instance(basic_caller, (Float64,)) end # this redefinition below should invalidate the cache -const BASIC_CALLER_WORLD = Base.get_world_counter() +const BASIC_CALLER_WORLD = Base.get_world_counter()+1 basic_callee(x) = x, x @test !isdefined(Base.method_instance(basic_callee, (Float64,)), :cache) let mi = Base.method_instance(basic_caller, (Float64,)) diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index db3ebb0232e38..abeec81f0c028 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -257,6 +257,10 @@ using .Order include("coreir.jl") include("invalidation.jl") +# Because lowering inserts direct references, it is mandatory for this binding +# to exist before we start inferring code. +function string end + # For OS specific stuff # We need to strcat things here, before strings are really defined function strcat(x::String, y::String) diff --git a/base/boot.jl b/base/boot.jl index 53e439d83ebe2..62042686b9a23 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -383,9 +383,10 @@ struct StackOverflowError <: Exception end struct UndefRefError <: Exception end struct UndefVarError <: Exception var::Symbol + world::UInt64 scope # a Module or Symbol or other object describing the context where this variable was looked for (e.g. Main or :local or :static_parameter) - UndefVarError(var::Symbol) = new(var) - UndefVarError(var::Symbol, @nospecialize scope) = new(var, scope) + UndefVarError(var::Symbol) = new(var, ccall(:jl_get_tls_world_age, UInt, ())) + UndefVarError(var::Symbol, @nospecialize scope) = new(var, ccall(:jl_get_tls_world_age, UInt, ()), scope) end struct ConcurrencyViolationError <: Exception msg::AbstractString @@ -717,7 +718,8 @@ macro __doc__(x) end isbasicdoc(@nospecialize x) = (isa(x, Expr) && x.head === :.) || isa(x, Union{QuoteNode, Symbol}) -iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(ex.args[1]) : (isa(ex, Expr) && ex.head === :call) +firstarg(arg1, args...) = arg1 +iscallexpr(ex::Expr) = (isa(ex, Expr) && ex.head === :where) ? iscallexpr(firstarg(ex.args...)) : (isa(ex, Expr) && ex.head === :call) iscallexpr(ex) = false function ignoredoc(source, mod, str, expr) (isbasicdoc(expr) || iscallexpr(expr)) && return Expr(:escape, nothing) diff --git a/base/docs/bindings.jl b/base/docs/bindings.jl index 6095d52a28e5a..5c65a35659f81 100644 --- a/base/docs/bindings.jl +++ b/base/docs/bindings.jl @@ -16,8 +16,8 @@ end bindingexpr(x) = Expr(:call, Binding, splitexpr(x)...) -defined(b::Binding) = isdefined(b.mod, b.var) -resolve(b::Binding) = getfield(b.mod, b.var) +defined(b::Binding) = invokelatest(isdefined, b.mod, b.var) +resolve(b::Binding) = invokelatest(getfield, b.mod, b.var) function splitexpr(x::Expr) isexpr(x, :macrocall) ? splitexpr(x.args[1]) : diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index 964e8063dd5af..b61e24c11f3f9 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -229,8 +229,9 @@ const BINDING_KIND_FAILED = 0x6 const BINDING_KIND_DECLARED = 0x7 const BINDING_KIND_GUARD = 0x8 const BINDING_KIND_UNDEF_CONST = 0x9 +const BINDING_KIND_BACKDATED_CONST = 0xa -is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT) +is_defined_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_BACKDATED_CONST) is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == BINDING_KIND_UNDEF_CONST) is_some_imported(kind::UInt8) = (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT || kind == BINDING_KIND_IMPORTED) is_some_guard(kind::UInt8) = (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST) diff --git a/base/show.jl b/base/show.jl index de45ca07e3131..42788f05eceb5 100644 --- a/base/show.jl +++ b/base/show.jl @@ -35,7 +35,7 @@ function _isself(ft::DataType) isdefined(ftname, :mt) || return false name = ftname.mt.name mod = parentmodule(ft) # NOTE: not necessarily the same as ft.name.mt.module - return isdefined(mod, name) && ft == typeof(getfield(mod, name)) + return invokelatest(isdefinedglobal, mod, name) && ft == typeof(invokelatest(getglobal, mod, name)) end function show(io::IO, ::MIME"text/plain", f::Function) @@ -542,8 +542,8 @@ function show_function(io::IO, f::Function, compact::Bool, fallback::Function) fallback(io, f) elseif compact print(io, mt.name) - elseif isdefined(mt, :module) && isdefined(mt.module, mt.name) && - getfield(mt.module, mt.name) === f + elseif isdefined(mt, :module) && isdefinedglobal(mt.module, mt.name) && + getglobal(mt.module, mt.name) === f # this used to call the removed internal function `is_exported_from_stdlib`, which effectively # just checked for exports from Core and Base. mod = get(io, :module, UsesCoreAndBaseOnly) @@ -1025,15 +1025,15 @@ function isvisible(sym::Symbol, parent::Module, from::Module) from_owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), from, sym) return owner !== C_NULL && from_owner === owner && !isdeprecated(parent, sym) && - isdefined(from, sym) # if we're going to return true, force binding resolution + isdefinedglobal(from, sym) # if we're going to return true, force binding resolution end function is_global_function(tn::Core.TypeName, globname::Union{Symbol,Nothing}) if globname !== nothing globname_str = string(globname::Symbol) if ('#' ∉ globname_str && '@' ∉ globname_str && isdefined(tn, :module) && - isbindingresolved(tn.module, globname) && isdefined(tn.module, globname) && - isconcretetype(tn.wrapper) && isa(getfield(tn.module, globname), tn.wrapper)) + isbindingresolved(tn.module, globname) && isdefinedglobal(tn.module, globname) && + isconcretetype(tn.wrapper) && isa(getglobal(tn.module, globname), tn.wrapper)) return true end end @@ -3364,7 +3364,10 @@ function print_partition(io::IO, partition::Core.BindingPartition) end print(io, " - ") kind = binding_kind(partition) - if is_defined_const_binding(kind) + if kind == BINDING_KIND_BACKDATED_CONST + print(io, "backdated constant binding to ") + print(io, partition_restriction(partition)) + elseif is_defined_const_binding(kind) print(io, "constant binding to ") print(io, partition_restriction(partition)) elseif kind == BINDING_KIND_UNDEF_CONST diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 3c234b17f10d8..e448c62465b0d 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -587,12 +587,13 @@ In the example above, we see that the "current world" (in which the method `newf is one greater than the task-local "runtime world" that was fixed when the execution of `tryeval` started. Sometimes it is necessary to get around this (for example, if you are implementing the above REPL). -Fortunately, there is an easy solution: call the function using [`Base.invokelatest`](@ref): +Fortunately, there is an easy solution: call the function using [`Base.invokelatest`](@ref) or +the macro version [`Base.@invokelatest`](@ref): ```jldoctest julia> function tryeval2() @eval newfun2() = 2 - Base.invokelatest(newfun2) + @invokelatest newfun2() end tryeval2 (generic function with 1 method) diff --git a/src/codegen.cpp b/src/codegen.cpp index e047632923f68..e2b1fb7c1ae33 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3467,7 +3467,8 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * break; pku = jl_atomic_load_acquire(&bpart->restriction); } - if (bpart && jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (bpart && (jl_bkind_is_some_constant(kind) && kind != BINDING_KIND_BACKDATED_CONST)) { jl_value_t *constval = decode_restriction_value(pku); if (!constval) { undef_var_error_ifnot(ctx, ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0), name, (jl_value_t*)mod); diff --git a/src/julia.h b/src/julia.h index 4c699ba059c65..1448488dbd84f 100644 --- a/src/julia.h +++ b/src/julia.h @@ -647,7 +647,10 @@ enum jl_partition_kind { // Undef Constant: This binding partition is a constant declared using `const`, but // without a value. // ->restriction is NULL - BINDING_KIND_UNDEF_CONST = 0x9 + BINDING_KIND_UNDEF_CONST = 0x9, + // Backated constant. A constant that was backdated for compatibility. In all other + // ways equivalent to BINDING_KIND_CONST, but prints a warning on access + BINDING_KIND_BACKDATED_CONST = 0xa, }; #ifdef _P64 @@ -693,7 +696,7 @@ typedef struct _jl_binding_t { jl_globalref_t *globalref; // cached GlobalRef for this binding _Atomic(jl_value_t*) value; _Atomic(jl_binding_partition_t*) partitions; - uint8_t declared:1; + uint8_t did_print_backdate_admonition:1; uint8_t exportp:1; // `public foo` sets `publicp`, `export foo` sets both `publicp` and `exportp` uint8_t publicp:1; // exportp without publicp is not allowed. uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package @@ -2025,10 +2028,6 @@ JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s); JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT void jl_add_standard_imports(jl_module_t *m); -STATIC_INLINE jl_function_t *jl_get_function(jl_module_t *m, const char *name) -{ - return (jl_function_t*)jl_get_global(m, jl_symbol(name)); -} // eq hash tables JL_DLLEXPORT jl_genericmemory_t *jl_eqtable_put(jl_genericmemory_t *h JL_ROOTING_ARGUMENT, jl_value_t *key, jl_value_t *val JL_ROOTED_ARGUMENT, int *inserted); @@ -2587,9 +2586,18 @@ typedef struct { } jl_nullable_float32_t; #define jl_root_task (jl_current_task->ptls->root_task) - JL_DLLEXPORT jl_task_t *jl_get_current_task(void) JL_GLOBALLY_ROOTED JL_NOTSAFEPOINT; +STATIC_INLINE jl_function_t *jl_get_function(jl_module_t *m, const char *name) +{ + jl_task_t *ct = jl_get_current_task(); + size_t last_world = ct->world_age; + ct->world_age = jl_get_world_counter(); + jl_value_t *r = jl_get_global(m, jl_symbol(name)); + ct->world_age = last_world; + return (jl_function_t*)r; +} + // TODO: we need to pin the task while using this (set pure bit) JL_DLLEXPORT jl_jmp_buf *jl_get_safe_restore(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_set_safe_restore(jl_jmp_buf *) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index 0da6d412c8a49..a838b75e506a2 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -933,6 +933,10 @@ EXTERN_INLINE_DECLARE enum jl_partition_kind decode_restriction_kind(jl_ptr_kind if (bits == BINDING_KIND_CONST) { return BINDING_KIND_UNDEF_CONST; } + } else { + if (bits == BINDING_KIND_DECLARED) { + return BINDING_KIND_BACKDATED_CONST; + } } return (enum jl_partition_kind)bits; @@ -956,12 +960,14 @@ STATIC_INLINE jl_ptr_kind_union_t encode_restriction(jl_value_t *val, enum jl_pa #ifdef _P64 if (kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED || kind == BINDING_KIND_FAILED || kind == BINDING_KIND_UNDEF_CONST) assert(val == NULL); - else if (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_CONST) + else if (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_CONST || kind == BINDING_KIND_BACKDATED_CONST) assert(val != NULL); if (kind == BINDING_KIND_GUARD) kind = BINDING_KIND_IMPLICIT; else if (kind == BINDING_KIND_UNDEF_CONST) kind = BINDING_KIND_CONST; + else if (kind == BINDING_KIND_BACKDATED_CONST) + kind = BINDING_KIND_DECLARED; assert((((uintptr_t)val) & 0x7) == 0); return ((jl_ptr_kind_union_t)val) | kind; #else @@ -975,11 +981,11 @@ STATIC_INLINE int jl_bkind_is_some_import(enum jl_partition_kind kind) JL_NOTSAF } STATIC_INLINE int jl_bkind_is_some_constant(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_UNDEF_CONST; + return kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_UNDEF_CONST || kind == BINDING_KIND_BACKDATED_CONST; } STATIC_INLINE int jl_bkind_is_defined_constant(enum jl_partition_kind kind) JL_NOTSAFEPOINT { - return kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT; + return kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT || kind == BINDING_KIND_BACKDATED_CONST; } STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFEPOINT { diff --git a/src/module.c b/src/module.c index be6779727bfdc..f4c56e19efa61 100644 --- a/src/module.c +++ b/src/module.c @@ -241,6 +241,7 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) b->exportp = 0; b->publicp = 0; b->deprecated = 0; + b->did_print_backdate_admonition = 0; JL_GC_PUSH1(&b); b->globalref = jl_new_globalref(mod, name, b); jl_gc_wb(b, b->globalref); @@ -322,14 +323,37 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var return b->globalref->mod; // TODO: deprecate this? } +static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT +{ + jl_safe_printf( + "WARNING: Detected access to binding `%s.%s` in a world prior to its definition world.\n" + " Julia 1.12 has introduced more strict world age semantics for global bindings.\n" + " !!! This code may malfunction under Revise.\n" + " !!! This code will error in future versions of Julia.\n" + "Hint: Add an appropriate `invokelatest` around the access to this binding.\n", + jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name)); + b->did_print_backdate_admonition = 1; +} + +static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT +{ + if (__unlikely(kind == BINDING_KIND_BACKDATED_CONST) && + !b->did_print_backdate_admonition) { + print_backdate_admonition(b); + } +} + JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_guard(kind)) return NULL; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + if (jl_bkind_is_some_constant(kind)) { + check_backdated_binding(b, kind); return decode_restriction_value(pku); + } return jl_atomic_load_relaxed(&b->value); } @@ -337,10 +361,13 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_guard(kind)) return NULL; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + if (jl_bkind_is_some_constant(kind)) { + check_backdated_binding(b, kind); return decode_restriction_value(pku); + } return jl_atomic_load(&b->value); } @@ -348,10 +375,12 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b) { jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_guard(kind)) return NULL; - if (!jl_bkind_is_some_constant(decode_restriction_kind(pku))) + if (!jl_bkind_is_some_constant(kind)) return NULL; + check_backdated_binding(b, kind); return decode_restriction_value(pku); } @@ -368,10 +397,12 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved_and_const(jl_binding_t if (bpart->min_world > jl_current_task->world_age || jl_current_task->world_age > max_world) return NULL; jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_guard(kind)) return NULL; - if (!jl_bkind_is_some_constant(decode_restriction_kind(pku))) + if (!jl_bkind_is_some_constant(kind)) return NULL; + check_backdated_binding(b, kind); return decode_restriction_value(pku); } @@ -388,12 +419,15 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved(jl_binding_t *b) if (bpart->min_world > jl_current_task->world_age || jl_current_task->world_age > max_world) return NULL; jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_guard(kind)) return NULL; - if (jl_bkind_is_some_import(decode_restriction_kind(pku))) + if (jl_bkind_is_some_import(kind)) return NULL; - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) + if (jl_bkind_is_some_constant(kind)) { + check_backdated_binding(b, kind); return decode_restriction_value(pku); + } return jl_atomic_load_relaxed(&b->value); } @@ -895,13 +929,26 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u jl_binding_t *b = jl_get_module_binding(m, var, allow_import); if (!b) return 0; + jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); + if (!bpart) + return 0; + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); if (!allow_import) { - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - if (!bpart || jl_bkind_is_some_import(decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)))) + if (!bpart || jl_bkind_is_some_import(decode_restriction_kind(pku))) return 0; - return jl_get_binding_value(b) != NULL; + } else { + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + jl_resolve_owner(b, b->globalref->mod, b->globalref->name, NULL, jl_current_task->world_age); + } + pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); + } + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) + return 0; + if (jl_bkind_is_defined_constant(decode_restriction_kind(pku))) { + // N.B.: No backdated check for isdefined + return 1; } - return jl_reresolve_binding_value_seqcst(b) != NULL; + return jl_atomic_load(&b->value) != NULL; } JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) diff --git a/src/rtutils.c b/src/rtutils.c index 00a5b639d8683..6515b80c5d2b5 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -148,8 +148,10 @@ JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var, jl_value_t * } jl_errorf("UndefVarError(%s%s%s)", jl_symbol_name(var), s1, s2); } - JL_GC_PUSH1(&scope); - jl_throw(jl_new_struct(jl_undefvarerror_type, var, scope)); + jl_value_t *active_age = NULL; + JL_GC_PUSH2(&scope, &active_age); + active_age = jl_box_long(jl_current_task->world_age); + jl_throw(jl_new_struct(jl_undefvarerror_type, var, active_age, scope)); } JL_DLLEXPORT void JL_NORETURN jl_has_no_field_error(jl_datatype_t *t, jl_sym_t *var) diff --git a/src/toplevel.c b/src/toplevel.c index dee9029e2feb7..ec53185a6a81e 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -669,7 +669,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym if (decode_restriction_kind(pku) != BINDING_KIND_GUARD && decode_restriction_kind(pku) != BINDING_KIND_FAILED) { // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. pku = jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age); - if (decode_restriction_kind(pku) == BINDING_KIND_CONST || decode_restriction_kind(pku) == BINDING_KIND_CONST_IMPORT) { + if (decode_restriction_kind(pku) == BINDING_KIND_CONST || decode_restriction_kind(pku) == BINDING_KIND_BACKDATED_CONST || decode_restriction_kind(pku) == BINDING_KIND_CONST_IMPORT) { // Already declared (e.g. on another thread) or imported. if (decode_restriction_value(pku) == (jl_value_t*)import) return; @@ -770,6 +770,12 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( jl_symbol_name(var)); did_warn = 1; } + if (new_world > bpart->min_world) { + // TODO: Invoke invalidation logic here + jl_atomic_store_relaxed(&bpart->max_world, new_world - 1); + bpart = jl_get_binding_partition(b, new_world); + pku = jl_atomic_load_relaxed(&bpart->restriction); + } } else if (!jl_bkind_is_some_guard(decode_restriction_kind(pku))) { if (jl_bkind_is_some_import(decode_restriction_kind(pku))) { jl_errorf("cannot declare %s.%s constant; it was already declared as an import", @@ -779,15 +785,16 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( jl_symbol_name(mod->name), jl_symbol_name(var)); } } - if (jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { - jl_gc_wb(bpart, val); - break; + if (!jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(val, constant_kind))) { + continue; } - } - // N.B.: This backdates the first definition of the constant to world age 0 for backwards compatibility - // TODO: Mark this specially with a separate partition. - if (bpart->min_world != 0) + jl_gc_wb(bpart, val); + int needs_backdate = bpart->min_world == 0 && new_world; bpart->min_world = new_world; + if (needs_backdate) { + jl_declare_constant_val3(b, mod, var, val, BINDING_KIND_BACKDATED_CONST, 0); + } + } JL_GC_POP(); return bpart; } diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 6c3f4bd4ba73a..f83fa867748af 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -33,9 +33,9 @@ function UndefVarError_hint(io::IO, ex::UndefVarError) if isdefined(ex, :scope) scope = ex.scope if scope isa Module - bpart = Base.lookup_binding_partition(Base.get_world_counter(), GlobalRef(scope, var)) + bpart = Base.lookup_binding_partition(ex.world, GlobalRef(scope, var)) kind = Base.binding_kind(bpart) - if kind === Base.BINDING_KIND_GLOBAL || kind === Base.BINDING_KIND_CONST || kind == Base.BINDING_KIND_DECLARED + if kind === Base.BINDING_KIND_GLOBAL || kind === Base.BINDING_KIND_UNDEF_CONST || kind == Base.BINDING_KIND_DECLARED print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.") elseif kind === Base.BINDING_KIND_FAILED print(io, "\nHint: It looks like two or more modules export different ", diff --git a/test/worlds.jl b/test/worlds.jl index 8bc96f8303aef..025aaba6cea4f 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -115,15 +115,15 @@ wc265_41332a = Task(tls_world_age) global wc265_41332d = Task(tls_world_age) nothing end)() -@test wc265 + 3 == get_world_counter() == tls_world_age() +@test wc265 + 4 == get_world_counter() == tls_world_age() schedule(wc265_41332a) schedule(wc265_41332b) schedule(wc265_41332c) schedule(wc265_41332d) @test wc265 == fetch(wc265_41332a) -@test wc265 + 1 == fetch(wc265_41332b) -@test wc265 + 3 == fetch(wc265_41332c) -@test wc265 + 1 == fetch(wc265_41332d) +@test wc265 + 2 == fetch(wc265_41332b) +@test wc265 + 4 == fetch(wc265_41332c) +@test wc265 + 2 == fetch(wc265_41332d) chnls, tasks = Base.channeled_tasks(2, wfunc) t265 = tasks[1]