diff --git a/base/Base.jl b/base/Base.jl index a1df3ee0a3fba..762404f3349ea 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -550,6 +550,9 @@ for m in methods(include) delete_method(m) end +# Arm binding invalidation mechanism +const invalidate_code_for_globalref! = Core.Compiler.invalidate_code_for_globalref! + # This method is here only to be overwritten during the test suite to test # various sysimg related invalidation scenarios. a_method_to_overwrite_in_test() = inferencebarrier(1) diff --git a/base/boot.jl b/base/boot.jl index 5d2527efd44c0..31d114dc62b0f 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -541,8 +541,6 @@ GenericMemoryRef(mem::GenericMemory) = memoryref(mem) GenericMemoryRef(mem::GenericMemory, i::Integer) = memoryref(mem, i) GenericMemoryRef(mem::GenericMemoryRef, i::Integer) = memoryref(mem, i) -const Memory{T} = GenericMemory{:not_atomic, T, CPU} -const MemoryRef{T} = GenericMemoryRef{:not_atomic, T, CPU} const AtomicMemory{T} = GenericMemory{:atomic, T, CPU} const AtomicMemoryRef{T} = GenericMemoryRef{:atomic, T, CPU} diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 6d22083cbbe8c..f62a61f34b2ff 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2826,6 +2826,7 @@ end isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g)) isdefinedconst_globalref(g::GlobalRef) = isconst(g) && isdefined_globalref(g) +# TODO: This should verify that there is only one binding for this globalref function abstract_eval_globalref_type(g::GlobalRef) if isdefinedconst_globalref(g) return Const(ccall(:jl_get_globalref_value, Any, (Any,), g)) @@ -2834,10 +2835,37 @@ function abstract_eval_globalref_type(g::GlobalRef) ty === nothing && return Any return ty end -abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref_type(GlobalRef(M, s)) + +function abstract_eval_binding_type(b::Core.Binding) + if isdefined(b, :owner) + b = b.owner + end + if isconst(b) && isdefined(b, :value) + return Const(b.value) + end + isdefined(b, :ty) || return Any + ty = b.ty + ty === nothing && return Any + return ty +end +function abstract_eval_global(M::Module, s::Symbol) + # TODO: This needs to add a new globalref to globalref edges list + return abstract_eval_globalref_type(GlobalRef(M, s)) +end + +function lookup_binding(world::UInt, g::GlobalRef) + ccall(:jl_lookup_module_binding, Any, (Any, Any, UInt), g.mod, g.name, world)::Union{Core.Binding, Nothing} +end function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState) - rt = abstract_eval_globalref_type(g) + binding = lookup_binding(get_inference_world(interp), g) + if binding === nothing + # TODO: We could allocate a guard entry here, but that would require + # going through a binding replacement if the binding ends up being used. + return RTEffects(Any, UndefVarError, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, nothrow=false, inaccessiblememonly=ALWAYS_FALSE)) + end + update_valid_age!(sv, WorldRange(binding.min_world, binding.max_world)) + rt = abstract_eval_binding_type(binding) consistent = inaccessiblememonly = ALWAYS_FALSE nothrow = false if isa(rt, Const) @@ -2848,12 +2876,12 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv:: end elseif InferenceParams(interp).assume_bindings_static consistent = inaccessiblememonly = ALWAYS_TRUE - if isdefined_globalref(g) + if isdefined(binding, :value) nothrow = true else rt = Union{} end - elseif isdefinedconst_globalref(g) + elseif isdefined(binding, :value) && isconst(binding) nothrow = true end return RTEffects(rt, nothrow ? Union{} : UndefVarError, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 12d6d5eb38764..390540272c28e 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -222,5 +222,7 @@ ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel) include("compiler/parsing.jl") Core._setparser!(fl_parse) +include("compiler/invalidation.jl") + end # baremodule Compiler )) diff --git a/base/compiler/invalidation.jl b/base/compiler/invalidation.jl new file mode 100644 index 0000000000000..b32572dab8ee9 --- /dev/null +++ b/base/compiler/invalidation.jl @@ -0,0 +1,161 @@ +# GlobalRef/binding reflection +# TODO: This should potentially go in reflection.jl, but `@atomic` is not available +# there. +struct GlobalRefIterator + mod::Module +end +globalrefs(mod::Module) = GlobalRefIterator(mod) + +function iterate(gri::GlobalRefIterator, i = 1) + m = gri.mod + table = ccall(:jl_module_get_bindings, Ref{SimpleVector}, (Any,), m) + i == length(table) && return nothing + b = table[i] + b === nothing && return iterate(gri, i+1) + return ((b::Core.Binding).globalref, i+1) +end + +const TYPE_TYPE_MT = Type.body.name.mt +const NONFUNCTION_MT = MethodTable.name.mt +function foreach_module_mtable(visit, m::Module) + for gb in globalrefs(m) + binding = gb.binding + if isconst(binding) + isdefined(binding, :value) || continue + v = @atomic binding.value + uw = unwrap_unionall(v) + name = gb.name + if isa(uw, DataType) + tn = uw.name + if tn.module === m && tn.name === name && tn.wrapper === v && isdefined(tn, :mt) + # this is the original/primary binding for the type (name/wrapper) + mt = tn.mt + if mt !== nothing && mt !== TYPE_TYPE_MT && mt !== NONFUNCTION_MT + @assert mt.module === m + visit(mt) || return false + end + end + elseif isa(v, Module) && v !== m && parentmodule(v) === m && _nameof(v) === name + # this is the original/primary binding for the submodule + foreach_module_mtable(visit, v) || return false + elseif isa(v, MethodTable) && v.module === m && v.name === name + # this is probably an external method table here, so let's + # assume so as there is no way to precisely distinguish them + visit(v) || return false + end + end + end + return true +end + +function foreach_reachable_mtable(visit) + visit(TYPE_TYPE_MT) || return + visit(NONFUNCTION_MT) || return + if isdefined(Core.Main, :Base) + for mod in Core.Main.Base.loaded_modules_array() + foreach_module_mtable(visit, mod) + end + else + foreach_module_mtable(visit, Core) + foreach_module_mtable(visit, Core.Main) + end +end + +function invalidate_code_for_globalref!(gr::GlobalRef, src::CodeInfo) + found_any = false + labelchangemap = nothing + stmts = src.code + function get_labelchangemap() + if labelchangemap === nothing + labelchangemap = fill(0, length(stmts)) + end + labelchangemap + end + isgr(g::GlobalRef) = gr.mod == g.mod && gr.name === g.name + isgr(g) = false + for i = 1:length(stmts) + stmt = stmts[i] + if isgr(stmt) + found_any = true + continue + end + found_arg = false + ngrs = 0 + for ur in userefs(stmt) + arg = ur[] + # If any of the GlobalRefs in this stmt match the one that + # we are about, we need to move out all GlobalRefs to preseve + # effect order, in case we later invalidate a different GR + if isa(arg, GlobalRef) + ngrs += 1 + if isgr(arg) + @assert !isa(stmt, PhiNode) + found_arg = found_any = true + break + end + end + end + if found_arg + get_labelchangemap()[i] += ngrs + end + end + next_empty_idx = 1 + if labelchangemap !== nothing + cumsum_ssamap!(labelchangemap) + new_stmts = Vector(undef, length(stmts)+labelchangemap[end]) + new_ssaflags = Vector{UInt32}(undef, length(new_stmts)) + new_debuginfo = DebugInfoStream(nothing, src.debuginfo, length(new_stmts)) + new_debuginfo.def = src.debuginfo.def + for i = 1:length(stmts) + stmt = stmts[i] + urs = userefs(stmt) + new_stmt_idx = i+labelchangemap[i] + for ur in urs + arg = ur[] + if isa(arg, SSAValue) + ur[] = SSAValue(arg.id + labelchangemap[arg.id]) + elseif next_empty_idx != new_stmt_idx && isa(arg, GlobalRef) + new_debuginfo.codelocs[3next_empty_idx - 2] = i + new_stmts[next_empty_idx] = arg + new_ssaflags[next_empty_idx] = UInt32(0) + ur[] = SSAValue(next_empty_idx) + next_empty_idx += 1 + end + end + @assert new_stmt_idx == next_empty_idx + new_stmts[new_stmt_idx] = urs[] + new_debuginfo.codelocs[3new_stmt_idx - 2] = i + new_ssaflags[new_stmt_idx] = src.ssaflags[i] + next_empty_idx = new_stmt_idx+1 + end + src.code = new_stmts + src.ssavaluetypes = length(new_stmts) + src.ssaflags = new_ssaflags + src.debuginfo = Core.DebugInfo(new_debuginfo, length(new_stmts)) + end + return found_any +end + +function invalidate_code_for_globalref!(gr::GlobalRef, new_max_world::UInt) + valid_in_valuepos = false + foreach_reachable_mtable() do mt::MethodTable + for method in MethodList(mt) + if isdefined(method, :source) + src = _uncompressed_ir(method) + old_stmts = src.code + if invalidate_code_for_globalref!(gr, src) + if src.code !== old_stmts + method.debuginfo = src.debuginfo + method.source = src + method.source = ccall(:jl_compress_ir, Ref{String}, (Any, Ptr{Cvoid}), method, C_NULL) + end + + for mi in specializations(method) + ccall(:jl_invalidate_method_instance, Cvoid, (Any, UInt), mi, new_max_world) + end + end + end + end + return true + end +end diff --git a/base/essentials.jl b/base/essentials.jl index c907d95c47265..ac4a4199e9485 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -1069,6 +1069,50 @@ function invoke_in_world(world::UInt, @nospecialize(f), @nospecialize args...; k return Core._call_in_world(world, Core.kwcall, kwargs, f, args...) end +""" + @world(sym, world) + +Resolve the binding `sym` in world `world`. See [`invoke_in_world`](@ref) for running +arbitrary code in fixed worlds. `world` may be `UnitRange`, in which case the macro +will error unless the binding is valid and has the same value across the entire world +range. + +The `@world` macro is primarily used in the priniting of bindings that are no longer available +in the current world. + +## Example +``` +julia> struct Foo; a::Int; end +Foo + +julia> fold = Foo(1) + +julia> Int(Base.get_world_counter()) +26866 + +julia> struct Foo; a::Int; b::Int end +Foo + +julia> fold +@world(Foo, 26866)(1) +``` + +!!! compat "Julia 1.12" + This functionality requires at least Julia 1.12. +""" +macro world(sym, world) + if isa(sym, Symbol) + return :($(_resolve_in_world)($world, $(QuoteNode(GlobalRef(__module__, sym))))) + elseif isa(sym, GlobalRef) + return :($(_resolve_in_world)($world, $(QuoteNode(sym)))) + else + error("`@world` requires a symbol or GlobalRef") + end +end + +_resolve_in_world(world::Integer, gr::GlobalRef) = + invoke_in_world(UInt(world), Core.getglobal, gr.mod, gr.name) + inferencebarrier(@nospecialize(x)) = compilerbarrier(:type, x) """ diff --git a/base/exports.jl b/base/exports.jl index 5564cdbe9bff2..18789c6618ca8 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -810,6 +810,7 @@ export @invoke, invokelatest, @invokelatest, + @world, # loading source files __precompile__, diff --git a/base/range.jl b/base/range.jl index 8b30222382c9a..8733e585bff49 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1680,3 +1680,16 @@ function show(io::IO, r::LogRange{T}) where {T} show(io, length(r)) print(io, ')') end + +# Implementation detail of @world +# The rest of this is defined in essentials.jl, but UnitRange is not available +function _resolve_in_world(world::UnitRange, gr::GlobalRef) + # Validate that this binding's reference covers the entire world range + bnd = ccall(:jl_lookup_module_binding, Any, (Any, Any, UInt), gr.mod, gr.name, first(world))::Union{Core.Binding, Nothing} + if bnd !== nothing + if bnd.max_world < last(world) + error("Binding does not cover the full world range") + end + end + _resolve_in_world(last(world), gr) +end diff --git a/base/reflection.jl b/base/reflection.jl index 3fead12f2eb8e..4d4ffafea8e56 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -344,6 +344,9 @@ function isconst(g::GlobalRef) return ccall(:jl_globalref_is_const, Cint, (Any,), g) != 0 end +isconst(b::Core.Binding) = + ccall(:jl_binding_is_const, Cint, (Any,), b) != 0 + """ isconst(t::DataType, s::Union{Int,Symbol}) -> Bool @@ -2595,6 +2598,18 @@ function delete_method(m::Method) ccall(:jl_method_table_disable, Cvoid, (Any, Any), get_methodtable(m), m) end +""" + delete_binding(mod::Module, sym::Symbol) + +Force the binding `mod.sym` to be undefined again, allowing it be redefined. +Note that this operation is very expensive, requirinig a full scan of all code in the system, +as well as potential recompilation of any methods that (may) have used binding +information. +""" +function delete_binding(mod::Module, sym::Symbol) + ccall(:jl_disable_binding, Cvoid, (Any,), GlobalRef(mod, sym)) +end + function get_methodtable(m::Method) mt = ccall(:jl_method_get_table, Any, (Any,), m) if mt === nothing diff --git a/base/show.jl b/base/show.jl index b901eab5906ed..0f1cbd076988c 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1040,6 +1040,24 @@ function is_global_function(tn::Core.TypeName, globname::Union{Symbol,Nothing}) return false end +function check_world_bounded(tn) + bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint, UInt), tn.module, tn.name, false, 1)::Core.Binding + if bnd !== nothing + while true + if isdefined(bnd, :owner) && isdefined(bnd, :value) + if bnd.value <: tn.wrapper + max_world = @atomic bnd.max_world + max_world == typemax(UInt) && return nothing + return Int(bnd.min_world):Int(max_world) + end + end + isdefined(bnd, :next) || break + bnd = @atomic bnd.next + end + end + return nothing +end + function show_type_name(io::IO, tn::Core.TypeName) if tn === UnionAll.name # by coincidence, `typeof(Type)` is a valid representation of the UnionAll type. @@ -1068,7 +1086,10 @@ function show_type_name(io::IO, tn::Core.TypeName) end end end + world = check_world_bounded(tn) + world !== nothing && print(io, "@world(") show_sym(io, sym) + world !== nothing && print(io, ", ", world, ")") quo && print(io, ")") globfunc && print(io, ")") nothing diff --git a/src/ast.c b/src/ast.c index d8926ea8cd51f..e2428e90d0356 100644 --- a/src/ast.c +++ b/src/ast.c @@ -173,7 +173,8 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint (void)tosymbol(fl_ctx, args[0], "defined-julia-global"); jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx); jl_sym_t *var = scmsym_to_julia(fl_ctx, args[0]); - jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0); + // TODO - this lookup isn't really valid anymore + jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0, jl_atomic_load_acquire(&jl_world_counter)); return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F; } @@ -203,7 +204,8 @@ static value_t fl_nothrow_julia_global(fl_context_t *fl_ctx, value_t *args, uint (void)tosymbol(fl_ctx, args[1], "nothrow-julia-global"); var = scmsym_to_julia(fl_ctx, args[1]); } - jl_binding_t *b = jl_get_module_binding(mod, var, 0); + // TODO - this lookup isn't really valid anymore + jl_binding_t *b = jl_get_module_binding(mod, var, 0, jl_atomic_load_acquire(&jl_world_counter)); b = b ? jl_atomic_load_relaxed(&b->owner) : NULL; return b != NULL && jl_atomic_load_relaxed(&b->value) != NULL ? fl_ctx->T : fl_ctx->F; } diff --git a/src/builtins.c b/src/builtins.c index 1ac51da1ce2df..0104c12b04849 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1371,7 +1371,7 @@ JL_CALLABLE(jl_f_get_binding_type) JL_TYPECHK(get_binding_type, symbol, (jl_value_t*)var); jl_value_t *ty = jl_get_binding_type(mod, var); if (ty == (jl_value_t*)jl_nothing) { - jl_binding_t *b = jl_get_module_binding(mod, var, 0); + jl_binding_t *b = jl_get_module_binding(mod, var, 0, jl_current_task->world_age); if (b == NULL) return (jl_value_t*)jl_any_type; jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); diff --git a/src/codegen.cpp b/src/codegen.cpp index 51d383e9a61e7..cc135aee1bbf1 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2100,7 +2100,7 @@ static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value *fval, StringRef name, jl_value_t *sig, jl_value_t *jlrettype, bool is_opaque_closure, bool gcstack_arg, BitVector *used_arguments=nullptr, size_t *args_begin=nullptr); static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1); static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, - jl_binding_t **pbnd, bool assign, bool alloc); + jl_binding_t **pbnd, bool assign, size_t world, bool alloc); static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, MDNode *tbaa); static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i); static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const Twine &msg); @@ -3190,7 +3190,7 @@ static jl_value_t *jl_ensure_rooted(jl_codectx_t &ctx, jl_value_t *val) static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *name, AtomicOrdering order) { jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false, false); + Value *bp = global_binding_pointer(ctx, mod, name, &bnd, false, ctx.min_world, false); if (bp == NULL) return jl_cgval_t(); bp = julia_binding_pvalue(ctx, bp); @@ -3212,7 +3212,7 @@ static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *s const jl_cgval_t *modifyop, bool alloc) { jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, alloc); + Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, ctx.min_world, alloc); if (bp == NULL) return jl_cgval_t(); if (bnd && !bnd->constp) { @@ -5449,9 +5449,9 @@ static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t * // if the reference currently bound or assign == true, // pbnd will also be assigned with the binding address static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, - jl_binding_t **pbnd, bool assign, bool alloc) + jl_binding_t **pbnd, bool assign, size_t world, bool alloc) { - jl_binding_t *b = jl_get_module_binding(m, s, 1); + jl_binding_t *b = jl_get_module_binding(m, s, 1, world); if (assign) { if (jl_atomic_load_relaxed(&b->owner) == NULL) // not yet declared @@ -6469,7 +6469,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ } if (jl_is_symbol(sym)) { jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, true); + Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, ctx.min_world, true); if (bp) ctx.builder.CreateCall(prepare_call(jldeclareconst_func), { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) }); diff --git a/src/dlload.c b/src/dlload.c index 484c36a228886..726d763a404b5 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -308,7 +308,7 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, such as Windows, so we emulate them here. */ if (!abspath && !is_atpath && jl_base_module != NULL) { - jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH"), 0); + jl_binding_t *b = jl_get_module_binding(jl_base_module, jl_symbol("DL_LOAD_PATH"), 0, jl_atomic_load_acquire(&jl_world_counter)); jl_array_t *DL_LOAD_PATH = (jl_array_t*)(b ? jl_atomic_load_relaxed(&b->value) : NULL); if (DL_LOAD_PATH != NULL) { size_t j; diff --git a/src/gf.c b/src/gf.c index e5a33ecf68c5d..f240ad9f84bbc 100644 --- a/src/gf.c +++ b/src/gf.c @@ -1716,6 +1716,11 @@ static void invalidate_method_instance(jl_method_instance_t *replaced, size_t ma JL_UNLOCK(&replaced->def.method->writelock); } +JL_DLLEXPORT void jl_invalidate_method_instance(jl_method_instance_t *replaced, size_t max_world) +{ + invalidate_method_instance(replaced, max_world, 1); +} + static void _invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, int depth) { jl_array_t *backedges = replaced_mi->backedges; if (backedges) { diff --git a/src/interpreter.c b/src/interpreter.c index 76bf585b72c39..5bdc8989d2453 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -585,8 +585,12 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, sym = (jl_sym_t*)lhs; } JL_GC_PUSH1(&rhs); - jl_binding_t *b = jl_get_binding_wr(modu, sym, alloc); - jl_checked_assignment(b, modu, sym, rhs); + if (toplevel) { + jl_toplevel_checked_assignment(modu, sym, rhs, alloc); + } else { + jl_binding_t *b = jl_get_binding_wr(modu, sym, 0); + jl_checked_assignment(b, modu, sym, rhs); + } JL_GC_POP(); } } diff --git a/src/jltypes.c b/src/jltypes.c index 59807226fb4a9..2d69abecb1f9b 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3100,12 +3100,12 @@ void jl_init_types(void) JL_GC_DISABLED jl_binding_type = jl_new_datatype(jl_symbol("Binding"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(5, "value", "globalref", "owner", "ty", "flags"), - jl_svec(5, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_any_type/*jl_binding_type*/, jl_type_type, jl_uint8_type), + jl_perm_symsvec(8, "value", "globalref", "owner", "ty", "min_world", "max_world", "next", "flags"), + jl_svec(8, jl_any_type, jl_any_type/*jl_globalref_type*/, jl_any_type/*jl_binding_type*/, jl_type_type, jl_ulong_type, jl_ulong_type, jl_any_type/*jl_binding_type*/, jl_uint8_type), jl_emptysvec, 0, 1, 0); - const static uint32_t binding_atomicfields[] = { 0x0015 }; // Set fields 1, 3, 4 as atomic + const static uint32_t binding_atomicfields[] = { 0x006d }; // Set fields 1, 3, 4, 6, 7 as atomic jl_binding_type->name->atomicfields = binding_atomicfields; - const static uint32_t binding_constfields[] = { 0x0002 }; // Set fields 2 as constant + const static uint32_t binding_constfields[] = { 0x0022 }; // Set fields 2, 5 as constant jl_binding_type->name->constfields = binding_constfields; jl_globalref_type = @@ -3655,6 +3655,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_code_instance_type->types, 17, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 1, jl_globalref_type); jl_svecset(jl_binding_type->types, 2, jl_binding_type); + jl_svecset(jl_binding_type->types, 6, jl_binding_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); diff --git a/src/julia.h b/src/julia.h index 6910167dae46d..c647dba22e783 100644 --- a/src/julia.h +++ b/src/julia.h @@ -633,6 +633,9 @@ typedef struct _jl_binding_t { jl_globalref_t *globalref; // cached GlobalRef for this binding _Atomic(struct _jl_binding_t*) owner; // for individual imported bindings (NULL until 'resolved') _Atomic(jl_value_t*) ty; // binding type + size_t min_world; + _Atomic(size_t) max_world; + _Atomic(struct _jl_binding_t*) next; uint8_t constp: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. @@ -1960,6 +1963,7 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT); JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); +JL_DLLEXPORT void jl_toplevel_checked_assignment(jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED, int alloc); JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *expected, jl_value_t *rhs); JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs); diff --git a/src/julia_internal.h b/src/julia_internal.h index 6f71b6018606f..648aff761301b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -340,6 +340,7 @@ extern arraylist_t eytzinger_idxs; extern JL_DLLEXPORT size_t jl_page_size; extern jl_function_t *jl_typeinf_func JL_GLOBALLY_ROOTED; +extern jl_function_t *jl_invalidation_scanner JL_GLOBALLY_ROOTED; extern JL_DLLEXPORT size_t jl_typeinf_world; extern _Atomic(jl_typemap_entry_t*) call_cache[N_CALL_CACHE] JL_GLOBALLY_ROOTED; @@ -834,7 +835,7 @@ JL_DLLEXPORT int jl_pointer_egal(jl_value_t *t); JL_DLLEXPORT jl_value_t *jl_nth_slot_type(jl_value_t *sig JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; void jl_compute_field_offsets(jl_datatype_t *st); void jl_module_run_initializer(jl_module_t *m); -JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc); +JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc, size_t world); JL_DLLEXPORT void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *sym, jl_binding_t *b); extern jl_array_t *jl_module_init_order JL_GLOBALLY_ROOTED; extern htable_t jl_current_modules JL_GLOBALLY_ROOTED; diff --git a/src/module.c b/src/module.c index 52dc6df089215..3bf71ea4ef5d5 100644 --- a/src/module.c +++ b/src/module.c @@ -175,6 +175,9 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) jl_atomic_store_relaxed(&b->value, NULL); jl_atomic_store_relaxed(&b->owner, NULL); jl_atomic_store_relaxed(&b->ty, NULL); + jl_atomic_store_relaxed(&b->next, NULL); + b->min_world = 0; + jl_atomic_store_relaxed(&b->max_world, (size_t)-1); b->globalref = NULL; b->constp = 0; b->exportp = 0; @@ -221,7 +224,7 @@ static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym // get binding for assignment JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, int alloc) { - jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_binding_t *b = jl_get_module_binding(m, var, 1, jl_current_task->world_age); jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); if (b2 != b) { if (b2 == NULL) { @@ -255,7 +258,7 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var // like jl_get_binding_wr, but has different error paths and messages JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_binding_t *b = jl_get_module_binding(m, var, 1, jl_current_task->world_age); jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); if (b2 != b) { if (b2 == NULL) @@ -319,11 +322,12 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl JL_LOCK(&m->lock); int i = (int)m->usings.len - 1; JL_UNLOCK(&m->lock); + size_t world = jl_atomic_load_acquire(&jl_world_counter); for (; i >= 0; --i) { JL_LOCK(&m->lock); jl_module_t *imp = module_usings_getidx(m, i); JL_UNLOCK(&m->lock); - jl_binding_t *tempb = jl_get_module_binding(imp, var, 0); + jl_binding_t *tempb = jl_get_module_binding(imp, var, 0, world); if (tempb != NULL && tempb->exportp) { tempb = jl_resolve_owner(NULL, imp, var, st); // find the owner for tempb if (tempb == NULL) @@ -334,7 +338,7 @@ static jl_binding_t *using_resolve_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl if (warn) { // set usingfailed=1 to avoid repeating this warning // the owner will still be NULL, so it can be later imported or defined - tempb = jl_get_module_binding(m, var, 1); + tempb = jl_get_module_binding(m, var, 1, world); tempb->usingfailed = 1; jl_printf(JL_STDERR, "WARNING: both %s and %s export \"%s\"; uses of it in module %s must be qualified\n", @@ -378,7 +382,7 @@ static void jl_binding_dep_message(jl_module_t *m, jl_sym_t *name, jl_binding_t static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t *m, jl_sym_t *var, modstack_t *st) { if (b == NULL) - b = jl_get_module_binding(m, var, 1); + b = jl_get_module_binding(m, var, 1, jl_current_task->world_age); jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); if (b2 == NULL) { if (b->usingfailed) @@ -432,7 +436,7 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * // get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); jl_module_t *from = m; if (b == NULL || (!b->usingfailed && jl_atomic_load_relaxed(&b->owner) == NULL)) b = using_resolve_binding(m, var, &from, NULL, 0); @@ -444,7 +448,7 @@ JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) // get type of binding m.var, without resolving the binding JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); if (b == NULL) return jl_nothing; b = jl_atomic_load_relaxed(&b->owner); @@ -472,7 +476,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_binding_t *b = jl_get_module_binding(m, var, 1, jl_current_task->world_age); jl_globalref_t *globalref = b->globalref; assert(globalref != NULL); return (jl_value_t*)globalref; @@ -481,7 +485,7 @@ JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var) // does module m explicitly import s? JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); return b && b->imported; } @@ -570,7 +574,7 @@ static void module_import_(jl_module_t *to, jl_module_t *from, jl_sym_t *asname, } } - jl_binding_t *bto = jl_get_module_binding(to, asname, 1); + jl_binding_t *bto = jl_get_module_binding(to, asname, 1, jl_atomic_load_acquire(&jl_world_counter)); if (bto == b) { // importing a binding on top of itself. harmless. return; @@ -649,7 +653,7 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) break; if (b->exportp && (jl_atomic_load_relaxed(&b->owner) == b || b->imported)) { jl_sym_t *var = b->globalref->name; - jl_binding_t *tob = jl_get_module_binding(to, var, 0); + jl_binding_t *tob = jl_get_module_binding(to, var, 0, jl_atomic_load_acquire(&jl_world_counter)); if (tob && jl_atomic_load_relaxed(&tob->owner) != NULL && // don't warn for conflicts with the module name itself. // see issue #4715 @@ -667,7 +671,7 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) { - jl_binding_t *b = jl_get_module_binding(from, s, 1); + jl_binding_t *b = jl_get_module_binding(from, s, 1, jl_current_task->world_age); if (b->publicp) { // check for conflicting declarations if (b->exportp && !exported) @@ -689,25 +693,25 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) // unlike most queries JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); return b && (b->exportp || jl_atomic_load_relaxed(&b->owner) == b); } JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); return b && b->exportp; } JL_DLLEXPORT int jl_module_public_p(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); return b && b->publicp; } JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) { - jl_binding_t *b = jl_get_module_binding(m, var, 0); + jl_binding_t *b = jl_get_module_binding(m, var, 0, jl_current_task->world_age); return b && jl_atomic_load_relaxed(&b->owner) != NULL; } @@ -727,7 +731,8 @@ static int bindingkey_eq(size_t idx, const void *var, jl_value_t *data, uint_t h return var == name; } -JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, int alloc) +extern jl_mutex_t world_counter_lock; +JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, int alloc, size_t world) { uint_t hv = var->hash; for (int locked = 0; ; locked++) { @@ -738,7 +743,19 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, jl_binding_t *b = (jl_binding_t*)jl_svecref(bindings, idx); // relaxed if (locked) JL_UNLOCK(&m->lock); - return b; + while (1) { + if (world <= jl_atomic_load_acquire(&b->max_world)) + break; + jl_binding_t *nextb = jl_atomic_load_acquire(&b->next); + if (!nextb) + break; + b = nextb; + } + if (b && b->min_world <= world && world <= jl_atomic_load_acquire(&b->max_world)) + return b; + if (!alloc) + return NULL; + jl_error("Attempted to re-allocate existing binding"); } if (!alloc) { return NULL; @@ -774,6 +791,14 @@ JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, } } +JL_DLLEXPORT jl_value_t *jl_lookup_module_binding(jl_module_t *m, jl_sym_t *var, size_t world) +{ + jl_binding_t *b = jl_get_module_binding(m, var, 0, world); + if (!b) + return jl_nothing; + return (jl_value_t*)b; +} + JL_DLLEXPORT jl_value_t *jl_get_globalref_value(jl_globalref_t *gr) { @@ -803,10 +828,10 @@ JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *va JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT) { // this function is mostly only used during initialization, so the data races here are not too important to us - jl_binding_t *bp = jl_get_module_binding(m, var, 1); + jl_binding_t *bp = jl_get_module_binding(m, var, 1, jl_atomic_load_acquire(&jl_world_counter)); jl_binding_t *b2 = NULL; if (!jl_atomic_cmpswap(&bp->owner, &b2, bp) && b2 != bp) - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(var)); + jl_errorf("B: invalid redefinition of constant %s", jl_symbol_name(var)); if (jl_atomic_load_relaxed(&bp->value) == NULL) { jl_value_t *old_ty = NULL; jl_atomic_cmpswap_relaxed(&bp->ty, &old_ty, (jl_value_t*)jl_any_type); @@ -820,7 +845,75 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var } } } - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(var)); + jl_errorf("A: invalid redefinition of constant %s", jl_symbol_name(var)); +} + +void jl_invalidate_binding_refs(jl_globalref_t *ref, size_t new_world) +{ + static jl_value_t *invalidate_code_for_globalref = NULL; + if (invalidate_code_for_globalref == NULL && jl_base_module != NULL) + invalidate_code_for_globalref = jl_get_global(jl_base_module, jl_symbol("invalidate_code_for_globalref!")); + if (!invalidate_code_for_globalref) + jl_error("Binding invalidation is not permitted during bootstrap."); + jl_value_t *boxed_world = jl_box_ulong(new_world); + JL_GC_PUSH1(&boxed_world); + jl_call2((jl_function_t*)invalidate_code_for_globalref, (jl_value_t*)ref, boxed_world); + JL_GC_POP(); +} + +JL_DLLEXPORT void jl_replace_binding(jl_binding_t *b, jl_globalref_t *ref, jl_value_t *new_val) +{ + JL_LOCK(&world_counter_lock); + jl_task_t *ct = jl_current_task; + size_t new_max_world = jl_atomic_load_acquire(&jl_world_counter); + if (ct->world_age != new_max_world || b->max_world != (size_t)-1) { + JL_UNLOCK(&world_counter_lock); + jl_error("Bindings may only be replaced by tasks running in max world age"); + } + size_t last_age = ct->world_age; + JL_TRY { + ct->world_age = jl_typeinf_world; + jl_invalidate_binding_refs(ref, new_max_world); + } JL_CATCH { + JL_UNLOCK(&world_counter_lock); + jl_rethrow(); + } + ct->world_age = last_age; + jl_atomic_store_release(&b->max_world, new_max_world); + jl_atomic_store_release(&jl_world_counter, new_max_world + 1); + + jl_binding_t *newb = new_binding(ref->mod, ref->name); + newb->min_world = new_max_world + 1; + newb->constp = b->constp; + jl_atomic_store_release(&newb->value, new_val); + + // publish the binding + jl_atomic_store_release(&b->next, newb); + jl_gc_wb(b, newb); + + JL_UNLOCK(&world_counter_lock); +} + +JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *ref) +{ + jl_binding_t *bp = jl_get_module_binding(ref->mod, ref->name, 0, jl_atomic_load_acquire(&jl_world_counter)); + if (!bp) + return; // Binding never existed in the first place + JL_LOCK(&world_counter_lock); + jl_task_t *ct = jl_current_task; + size_t new_max_world = jl_atomic_load_acquire(&jl_world_counter); + size_t last_age = ct->world_age; + JL_TRY { + ct->world_age = jl_typeinf_world; + jl_invalidate_binding_refs(ref, new_max_world); + } JL_CATCH { + JL_UNLOCK(&world_counter_lock); + jl_rethrow(); + } + ct->world_age = last_age; + jl_atomic_store_release(&bp->max_world, new_max_world); + jl_atomic_store_release(&jl_world_counter, new_max_world + 1); + JL_UNLOCK(&world_counter_lock); } JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) @@ -830,6 +923,11 @@ JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr) return b && b->constp; } +JL_DLLEXPORT int jl_binding_is_const(jl_binding_t *b) +{ + return b->constp; +} + JL_DLLEXPORT int jl_globalref_boundp(jl_globalref_t *gr) { jl_binding_t *b = gr->binding; @@ -906,6 +1004,7 @@ jl_value_t *jl_check_binding_wr(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var } if (b->constp) { if (reassign) { + // TODO: Just disallow this entirely? jl_value_t *old = NULL; if (jl_atomic_cmpswap(&b->value, &old, rhs)) { jl_gc_wb(b, rhs); @@ -913,15 +1012,12 @@ jl_value_t *jl_check_binding_wr(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var } if (jl_egal(rhs, old)) return NULL; - if (jl_typeof(rhs) != jl_typeof(old) || jl_is_type(rhs) || jl_is_module(rhs)) - reassign = 0; - else - jl_safe_printf("WARNING: redefinition of constant %s.%s. This may fail, cause incorrect answers, or produce other errors.\n", - jl_symbol_name(mod->name), jl_symbol_name(var)); + reassign = 0; + } + if (!reassign) { + jl_errorf("Invalid redefinition of constant %s.%s. Redefinition of constants is permitted at toplevel only.", + jl_symbol_name(mod->name), jl_symbol_name(var)); } - if (!reassign) - jl_errorf("invalid redefinition of constant %s.%s", - jl_symbol_name(mod->name), jl_symbol_name(var)); } return old_ty; } @@ -934,6 +1030,49 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sy } } +JL_DLLEXPORT void jl_toplevel_checked_assignment(jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs, int alloc) +{ + jl_binding_t *b = jl_get_module_binding(mod, var, 1, jl_current_task->world_age); + jl_binding_t *b2 = jl_atomic_load_relaxed(&b->owner); + if (b2 != b) { + if (b2 == NULL) { + check_safe_newbinding(mod, var); + if (!alloc) + jl_errorf("Global %s.%s does not exist and cannot be assigned. Declare it using `global` before attempting assignment.", jl_symbol_name(mod->name), jl_symbol_name(var)); + } + if (b2 != NULL || (!jl_atomic_cmpswap(&b->owner, &b2, b) && b2 != b)) { + jl_module_t *from = jl_binding_dbgmodule(b, mod, var); + // TODO: Permit this? + if (from == mod) + jl_errorf("cannot assign a value to imported variable %s.%s", + jl_symbol_name(from->name), jl_symbol_name(var)); + else + jl_errorf("cannot assign a value to imported variable %s.%s from module %s", + jl_symbol_name(from->name), jl_symbol_name(var), jl_symbol_name(mod->name)); + } + } + + if (b->constp) { + jl_value_t *old = NULL; + if (jl_atomic_cmpswap(&b->value, &old, rhs)) { + // TODO: Do we want to force one-step here? + jl_gc_wb(b, rhs); + return; + } + if (jl_egal(rhs, old)) { + // Tried to re-assign with the same value. No need to partition the + // binding. + return; + } + jl_replace_binding(b, b->globalref, rhs); + } else { + if (jl_check_binding_wr(b, mod, var, rhs, 1) != NULL) { + jl_atomic_store_release(&b->value, rhs); + jl_gc_wb(b, rhs); + } + } +} + JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs) { jl_check_binding_wr(b, mod, var, rhs, 0); @@ -956,7 +1095,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl if (jl_atomic_cmpswap_relaxed(&b->ty, &ty, (jl_value_t*)jl_any_type)) ty = (jl_value_t*)jl_any_type; if (b->constp) - jl_errorf("invalid redefinition of constant %s.%s", + jl_errorf("D: invalid redefinition of constant %s.%s", jl_symbol_name(mod->name), jl_symbol_name(var)); return modify_value(ty, &b->value, (jl_value_t*)b, op, rhs, 1, mod, var); } @@ -1101,6 +1240,11 @@ JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) JL_UNLOCK(&m->lock); } +JL_DLLEXPORT jl_svec_t *jl_module_get_bindings(jl_module_t *m) +{ + return jl_atomic_load_relaxed(&m->bindings); +} + JL_DLLEXPORT void jl_init_restored_module(jl_value_t *mod) { if (!jl_generating_output() || jl_options.incremental) { diff --git a/src/rtutils.c b/src/rtutils.c index 7df3230755e63..6cb231c01b22e 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -552,7 +552,7 @@ JL_DLLEXPORT jl_value_t *jl_stderr_obj(void) JL_NOTSAFEPOINT { if (jl_base_module == NULL) return NULL; - jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr"), 0); + jl_binding_t *stderr_obj = jl_get_module_binding(jl_base_module, jl_symbol("stderr"), 0, jl_atomic_load_acquire(&jl_world_counter)); return stderr_obj ? jl_atomic_load_relaxed(&stderr_obj->value) : NULL; } @@ -647,7 +647,7 @@ static int is_globname_binding(jl_value_t *v, jl_datatype_t *dv) JL_NOTSAFEPOINT { jl_sym_t *globname = dv->name->mt != NULL ? dv->name->mt->name : NULL; if (globname && dv->name->module) { - jl_binding_t *b = jl_get_module_binding(dv->name->module, globname, 0); + jl_binding_t *b = jl_get_module_binding(dv->name->module, globname, 0, jl_atomic_load_acquire(&jl_world_counter)); if (b && jl_atomic_load_relaxed(&b->owner) && b->constp) { jl_value_t *bv = jl_atomic_load_relaxed(&b->value); // The `||` makes this function work for both function instances and function types. diff --git a/src/toplevel.c b/src/toplevel.c index c2fa9b72a9709..a5c5c6195686c 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -160,7 +160,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex jl_value_t *old = NULL; if (!jl_atomic_cmpswap(&b->value, &old, (jl_value_t*)newm)) { if (!jl_is_module(old)) { - jl_errorf("invalid redefinition of constant %s", jl_symbol_name(name)); + jl_errorf("E: invalid redefinition of constant %s", jl_symbol_name(name)); } if (jl_generating_output()) jl_errorf("cannot replace module %s during compilation", jl_symbol_name(name)); @@ -628,7 +628,7 @@ static void import_module(jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym assert(m); jl_sym_t *name = asname ? asname : import->name; // TODO: this is a bit race-y with what error message we might print - jl_binding_t *b = jl_get_module_binding(m, name, 0); + jl_binding_t *b = jl_get_module_binding(m, name, 0, jl_atomic_load_acquire(&jl_world_counter)); jl_binding_t *b2; if (b != NULL && (b2 = jl_atomic_load_relaxed(&b->owner)) != NULL) { if (b2->constp && jl_atomic_load_relaxed(&b2->value) == (jl_value_t*)import) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 34af995c9b162..f0e2751cd696a 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -33,7 +33,7 @@ function UndefVarError_hint(io::IO, ex::UndefVarError) if isdefined(ex, :scope) scope = ex.scope if scope isa Module - bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding + bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint, UInt), scope, var, true, Base.get_world_counter())::Core.Binding if isdefined(bnd, :owner) owner = bnd.owner if owner === bnd diff --git a/test/choosetests.jl b/test/choosetests.jl index 96d230d185c71..affdee412bd86 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -29,7 +29,7 @@ const TESTNAMES = [ "channels", "iostream", "secretbuffer", "specificity", "reinterpretarray", "syntax", "corelogging", "missing", "asyncmap", "smallarrayshrink", "opaque_closure", "filesystem", "download", - "scopedvalues", "compileall" + "scopedvalues", "compileall", "rebinding" ] const INTERNET_REQUIRED_LIST = [ diff --git a/test/rebinding.jl b/test/rebinding.jl new file mode 100644 index 0000000000000..2812a0ba470af --- /dev/null +++ b/test/rebinding.jl @@ -0,0 +1,21 @@ +struct ToRedefine1 + a::Int +end + +previously_defined_method() = ToRedefine1(1) +const old_reference = previously_defined_method() +const old_world_age = Base.get_world_counter() + +struct ToRedefine1 + a::Int + b::Int +end +@test_throws previously_defined_method() + +# Test that the binding rename worked +@test !isa(old_reference, ToRedefine1) +@test isa(old_reference, @world(ToRedefine1, old_world_age)) + +# Test that the lowering of the inner constructor references the type by identity, +# not binding +@test isa(@world(ToRedefine1, old_world_age)(1), @world(ToRedefine1, old_world_age))