From 06e693927789fc43c121256adaf9f958ef72e1b4 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 1 Jun 2024 21:18:28 -0400 Subject: [PATCH] WIP: World-age parition bindings This implements world-age partitioning of bindings as proposed in #40399. In effect, much like methods, the global view of bindings now depends on your currently executing world. This means that `const` bindings can now have different values in different worlds. In principle it also means that regular global variables could have different values in different worlds, but there is currently no case where the system does this. The reasons for this change are manifold: 1. The primary motivation is to permit Revise to redefine structs. This has been a feature request since the very begining of Revise (https://github.com/timholy/Revise.jl/issues/18) and there have been numerous attempts over the past 7 years to address this, as well as countless duplicate feature request. A past attempt to implement the necessary julia support in #22721 failed because the consequences and semantics of re-defining bindings were not sufficiently worked out. One way to think of this implementation (at least with respect to types) is that it provides a well-grounded implementation of #22721. 2. A secondary motivation is to make `const`-redefinition no longer UB (although `const` redefinition will still have a significant performance penalty, so it is not recommended). See e.g. the full discussion in #54099. 3. Not currently implemented, but this mechanism can be used to re-compile code where bindings are introduced after the first compile, which is a common performance trap for new users (#53958). 4. Not currently implemented, but this mechanism can be used to clarify the semantics of bindings import and resolution to address issues like #14055. In this PR: - `Binding` gets `min_world`/`max_world` fields like `CodeInstance` - Various lookup functions walk this linked list using the current task world_age as a key - Inference accumulates world bounds as it would for methods - Upon binding replacement, we walk all methods in the system, invalidating those whose uninferred IR references the replaced GlobalRef - One primary complication is that our IR definition permits `const` globals in value position, but if binding replacement is permitted, the validity of this may change after the fact. To address this, there is a helper in `Core.Compiler` that gets invoked in the type inference world and will rewrite the method source to be legal in all worlds. - A new `@world` macro can be used to access bindings from old world ages. This is used in printing for old objects. - The `const`-override behavior was changed to only be permitted at toplevel. The warnings about it being UB was removed. Of particular note, this PR does not include any mechanism for invalidating methods whose signatures were created using an old Binding (or types whose fields were the result of a binding evaluation). There was some discussion among the compiler team of whether such a mechanism should exist in base, but the consensus was that it should not. In particular, although uncommon, a pattern like: ``` f() = Any g(::f()) = 1 f() = Int ``` Does not redefine `g`. Thus to fully address the Revise issue, additional code will be required in Revise to track the dependency of various signatures and struct definitions on bindings. ``` julia> struct Foo a::Int end julia> g() = Foo(1) g (generic function with 1 method) julia> g() Foo(1) julia> f(::Foo) = 1 f (generic function with 1 method) julia> fold = Foo(1) Foo(1) julia> struct Foo a::Int b::Int end julia> g() ERROR: MethodError: no method matching Foo(::Int64) The type `Foo` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: Foo(::Int64, ::Int64) @ Main REPL[6]:2 Foo(::Any, ::Any) @ Main REPL[6]:2 Stacktrace: [1] g() @ Main ./REPL[2]:1 [2] top-level scope @ REPL[7]:1 julia> f(::Foo) = 2 f (generic function with 2 methods) julia> methods(f) [1] f(::Foo) @ REPL[8]:1 [2] f(::@world(Foo, 0:26898)) @ REPL[4]:1 julia> fold @world(Foo, 0:26898)(1) ``` On my machine, the validation required upon binding replacement for the full system image takes about 200ms. With CedarSim loaded (I tried OmniPackage, but it's not working on master), this increases about 5x. That's a fair bit of compute, but not the end of the world. Still, Revise may have to batch its validation. There may also be opportunities for performance improvement by operating on the compressed representation directly. - [ ] Do we want to change the resolution time of bindings to (semantically) resolve them immediately? - [ ] Do we want to introduce guard bindings when inference assumes the absence of a binding? - [ ] Precompile re-validation - [ ] Various cleanups in the accessors - [ ] Invert the order of the binding linked list to make the most recent one always the head of the list - [ ] CodeInstances need forward edges for GlobalRefs not part of the uninferred code - [ ] Generated function support --- base/Base.jl | 3 + base/boot.jl | 2 - base/compiler/abstractinterpretation.jl | 36 ++++- base/compiler/compiler.jl | 2 + base/compiler/invalidation.jl | 161 +++++++++++++++++++ base/essentials.jl | 44 +++++ base/exports.jl | 1 + base/range.jl | 13 ++ base/reflection.jl | 15 ++ base/show.jl | 21 +++ src/ast.c | 6 +- src/builtins.c | 2 +- src/codegen.cpp | 12 +- src/dlload.c | 2 +- src/gf.c | 5 + src/interpreter.c | 8 +- src/jltypes.c | 9 +- src/julia.h | 4 + src/julia_internal.h | 3 +- src/module.c | 204 ++++++++++++++++++++---- src/rtutils.c | 4 +- src/toplevel.c | 4 +- stdlib/REPL/src/REPL.jl | 2 +- test/choosetests.jl | 2 +- test/rebinding.jl | 21 +++ 25 files changed, 527 insertions(+), 59 deletions(-) create mode 100644 base/compiler/invalidation.jl create mode 100644 test/rebinding.jl 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))