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..9b386f90d4abe 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::UInt 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 60e53f75638ed..19ee1e9161152 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3469,7 +3469,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 1088f669ad773..f36133088119f 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); @@ -2590,9 +2589,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..30e78bf813fc8 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 && val; 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/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 79fdb89399d42..88fd055f7bd58 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -783,6 +783,19 @@ end const MACROEXPAND_LIKE = Symbol.(("@macroexpand", "@macroexpand1", "macroexpand")) +function isequalexception(@nospecialize(a), @nospecialize(b)) + for fld in 1:nfields(b) + if !isequal(getfield(a, fld), getfield(b, fld)) + return false + end + end + return true +end +function isequalexception(a::UndefVarError, b::UndefVarError) + # Ignore different world ages + return isequal(a.var, b.var) && isequal(a.scope, b.scope) +end + # An internal function, called by the code generated by @test_throws # to evaluate and catch the thrown exception - if it exists function do_test_throws(result::ExecutionResult, orig_expr, extype) @@ -817,13 +830,7 @@ function do_test_throws(result::ExecutionResult, orig_expr, extype) if isa(extype, UndefVarError) && !isdefined(extype, :scope) success = exc isa UndefVarError && exc.var == extype.var else isa(exc, typeof(extype)) - success = true - for fld in 1:nfields(extype) - if !isequal(getfield(extype, fld), getfield(exc, fld)) - success = false - break - end - end + success = isequalexception(exc, extype) end else message_only = true 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]