From dfa20f4520a715f047165c1929af0af9b3fd6b99 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 24 Jan 2025 03:17:29 +0000 Subject: [PATCH] bpart: Start enforcing min_world for global variable definitions This is the analog of #57102 for global variables. Unlike for consants, there is no automatic global backdate mechanism. The reasoning for this is that global variables can be declared at any time, unlike constants which can only be decalared once their value is available. As a result code patterns using `Core.eval` to declare globals are rarer and likely incorrect. --- Compiler/test/abioverride.jl | 2 +- base/docs/Docs.jl | 17 ++++-- base/exports.jl | 1 + base/loading.jl | 2 +- src/builtins.c | 10 ++-- src/codegen.cpp | 103 ++++------------------------------- src/jl_exported_funcs.inc | 1 + src/julia-syntax.scm | 11 +++- src/julia.h | 3 +- src/module.c | 44 ++++++++------- src/toplevel.c | 90 +++++++++++++++--------------- stdlib/REPL/src/REPL.jl | 28 +++++++--- stdlib/REPL/test/repl.jl | 14 ++--- sysimage.mk | 1 + test/core.jl | 1 + test/precompile.jl | 2 +- test/worlds.jl | 29 ++++++---- 17 files changed, 159 insertions(+), 200 deletions(-) diff --git a/Compiler/test/abioverride.jl b/Compiler/test/abioverride.jl index 49907ea8e4c63..feb992b27ee43 100644 --- a/Compiler/test/abioverride.jl +++ b/Compiler/test/abioverride.jl @@ -46,7 +46,7 @@ let world = Base.tls_world_age() global new_ci = Core.CodeInstance(Core.ABIOverride(Tuple{typeof(myplus), Int}, mi), #=owner=#SecondArgConstOverride(1), new_source.rettype, Any#=new_source.exctype is missing=#, #=inferred_const=#nothing, #=code=#nothing, #=const_flags=#Int32(0), - new_source.min_world, new_source.max_world, #=new_source.ipo_purity_bits is missing=#UInt32(0), + new_source.min_world, typemax(UInt), #=new_source.ipo_purity_bits is missing=#UInt32(0), #=analysis_results=#nothing, new_source.debuginfo, new_source.edges) # Poke the CI into the global cache diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 61c0cf71e70c2..061a94bffd9cf 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -75,18 +75,23 @@ const META = gensym(:meta) const METAType = IdDict{Any,Any} function meta(m::Module; autoinit::Bool=true) - if !isdefined(m, META) || getfield(m, META) === nothing - autoinit ? initmeta(m) : return nothing + if !isdefinedglobal(m, META) + return autoinit ? invokelatest(initmeta, m) : nothing end - return getfield(m, META)::METAType + # TODO: This `invokelatest` is not technically required, but because + # of the automatic constant backdating is currently required to avoid + # a warning. + return invokelatest(getglobal, m, META)::METAType end function initmeta(m::Module) - if !isdefined(m, META) || getfield(m, META) === nothing - Core.eval(m, :($META = $(METAType()))) + if !isdefinedglobal(m, META) + val = METAType() + Core.eval(m, :(const $META = $val)) push!(modules, m) + return val end - nothing + return getglobal(m, META) end function signature!(tv::Vector{Any}, expr::Expr) diff --git a/base/exports.jl b/base/exports.jl index 56cd58ce269e7..d81067478dd55 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -818,6 +818,7 @@ export @invoke, invokelatest, @invokelatest, + @world, # loading source files __precompile__, diff --git a/base/loading.jl b/base/loading.jl index 240406292246b..57d69f49483c9 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1389,7 +1389,7 @@ function register_restored_modules(sv::SimpleVector, pkg::PkgId, path::String) restored = sv[1]::Vector{Any} for M in restored M = M::Module - if isdefined(M, Base.Docs.META) && getfield(M, Base.Docs.META) !== nothing + if isdefinedglobal(M, Base.Docs.META) push!(Base.Docs.modules, M) end if is_root_module(M) diff --git a/src/builtins.c b/src/builtins.c index 90d8a0d453e20..f67ef65d35356 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1395,7 +1395,7 @@ JL_CALLABLE(jl_f_setglobal) jl_atomic_error("setglobal!: module binding cannot be written non-atomically"); else if (order >= jl_memory_order_seq_cst) jl_fence(); - jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + jl_binding_t *b = jl_get_binding_wr(mod, var); jl_checked_assignment(b, mod, var, args[2]); // release store if (order >= jl_memory_order_seq_cst) jl_fence(); @@ -1430,7 +1430,7 @@ JL_CALLABLE(jl_f_swapglobal) if (order == jl_memory_order_notatomic) jl_atomic_error("swapglobal!: module binding cannot be written non-atomically"); // is seq_cst already, no fence needed - jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + jl_binding_t *b = jl_get_binding_wr(mod, var); return jl_checked_swap(b, mod, var, args[2]); } @@ -1448,7 +1448,7 @@ JL_CALLABLE(jl_f_modifyglobal) JL_TYPECHK(modifyglobal!, symbol, (jl_value_t*)var); if (order == jl_memory_order_notatomic) jl_atomic_error("modifyglobal!: module binding cannot be written non-atomically"); - jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + jl_binding_t *b = jl_get_binding_wr(mod, var); // is seq_cst already, no fence needed return jl_checked_modify(b, mod, var, args[2], args[3]); } @@ -1477,7 +1477,7 @@ JL_CALLABLE(jl_f_replaceglobal) jl_atomic_error("replaceglobal!: module binding cannot be written non-atomically"); if (failure_order == jl_memory_order_notatomic) jl_atomic_error("replaceglobal!: module binding cannot be accessed non-atomically"); - jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + jl_binding_t *b = jl_get_binding_wr(mod, var); // is seq_cst already, no fence needed return jl_checked_replace(b, mod, var, args[2], args[3]); } @@ -1506,7 +1506,7 @@ JL_CALLABLE(jl_f_setglobalonce) jl_atomic_error("setglobalonce!: module binding cannot be written non-atomically"); if (failure_order == jl_memory_order_notatomic) jl_atomic_error("setglobalonce!: module binding cannot be accessed non-atomically"); - jl_binding_t *b = jl_get_binding_wr(mod, var, 0); + jl_binding_t *b = jl_get_binding_wr(mod, var); // is seq_cst already, no fence needed jl_value_t *old = jl_checked_assignonce(b, mod, var, args[2]); return old == NULL ? jl_true : jl_false; diff --git a/src/codegen.cpp b/src/codegen.cpp index 19ee1e9161152..e9e4275672c7e 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -930,12 +930,12 @@ static const auto jlgetbindingorerror_func = new JuliaFunction<>{ }, nullptr, }; -static const auto jlgetbindingwrorerror_func = new JuliaFunction<>{ - XSTR(jl_get_binding_wr), +static const auto jlcheckbpwritable_func = new JuliaFunction<>{ + XSTR(jl_check_binding_currently_writable), [](LLVMContext &C) { auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); - return FunctionType::get(T_pjlvalue, - {T_pjlvalue, T_pjlvalue, getInt32Ty(C)}, false); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false); }, nullptr, }; @@ -2098,8 +2098,6 @@ 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, ArrayRef ArgNames=None, unsigned nreq=0); 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); 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); @@ -3498,19 +3496,17 @@ static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *s bool issetglobal, bool isreplaceglobal, bool isswapglobal, bool ismodifyglobal, bool issetglobalonce, const jl_cgval_t *modifyop, bool alloc) { - jl_binding_t *bnd = NULL; - Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, alloc); + jl_binding_t *bnd = jl_get_module_binding(mod, sym, 1); jl_binding_partition_t *bpart = jl_get_binding_partition_all(bnd, ctx.min_world, ctx.max_world); - if (bp == NULL) - return jl_cgval_t(); + Value *bp = julia_binding_gv(ctx, bnd); if (bpart) { jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (!jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + if (decode_restriction_kind(pku) == BINDING_KIND_GLOBAL) { jl_value_t *ty = decode_restriction_value(pku); if (ty != nullptr) { const std::string fname = issetglobal ? "setglobal!" : isreplaceglobal ? "replaceglobal!" : isswapglobal ? "swapglobal!" : ismodifyglobal ? "modifyglobal!" : "setglobalonce!"; if (!ismodifyglobal) { - // TODO: use typeassert in jl_check_binding_wr too + // TODO: use typeassert in jl_check_binding_assign_value too emit_typecheck(ctx, rval, ty, "typeassert"); rval = update_julia_type(ctx, rval, ty); if (rval.typ == jl_bottom_type) @@ -3545,6 +3541,8 @@ static jl_cgval_t emit_globalop(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *s } Value *m = literal_pointer_val(ctx, (jl_value_t*)mod); Value *s = literal_pointer_val(ctx, (jl_value_t*)sym); + ctx.builder.CreateCall(prepare_call(jlcheckbpwritable_func), + { bp, m, s }); if (issetglobal) { ctx.builder.CreateCall(prepare_call(jlcheckassign_func), { bp, m, s, mark_callee_rooted(ctx, boxed(ctx, rval)) }); @@ -5991,85 +5989,6 @@ static void emit_hasnofield_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_datatyp ctx.builder.SetInsertPoint(ifok); } -// returns a jl_ppvalue_t location for the global variable m.s -// 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 *b = jl_get_module_binding(m, s, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition_all(b, ctx.min_world, ctx.max_world); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - if (assign) { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) - // not yet declared - b = NULL; - } - else { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - // try to look this up now - b = jl_get_binding(m, s); - bpart = jl_get_binding_partition_all(b, ctx.min_world, ctx.max_world); - } - pku = jl_walk_binding_inplace_all(&b, &bpart, ctx.min_world, ctx.max_world); - } - if (!b || !bpart) { - // var not found. switch to delayed lookup. - Constant *initnul = Constant::getNullValue(ctx.types().T_pjlvalue); - GlobalVariable *bindinggv = new GlobalVariable(*ctx.f->getParent(), ctx.types().T_pjlvalue, - false, GlobalVariable::PrivateLinkage, initnul, "jl_binding_ptr"); // LLVM has bugs with nameless globals - LoadInst *cachedval = ctx.builder.CreateAlignedLoad(ctx.types().T_pjlvalue, bindinggv, Align(sizeof(void*))); - setName(ctx.emission_context, cachedval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".cached"); - cachedval->setOrdering(AtomicOrdering::Unordered); - BasicBlock *have_val = BasicBlock::Create(ctx.builder.getContext(), "found"); - BasicBlock *not_found = BasicBlock::Create(ctx.builder.getContext(), "notfound"); - BasicBlock *currentbb = ctx.builder.GetInsertBlock(); - auto iscached = ctx.builder.CreateICmpNE(cachedval, initnul); - setName(ctx.emission_context, iscached, "iscached"); - ctx.builder.CreateCondBr(iscached, have_val, not_found); - not_found->insertInto(ctx.f); - ctx.builder.SetInsertPoint(not_found); - Value *bval = nullptr; - if (assign) { - bval = ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func), - { literal_pointer_val(ctx, (jl_value_t*)m), - literal_pointer_val(ctx, (jl_value_t*)s), - ConstantInt::get(getInt32Ty(ctx.builder.getContext()), alloc)}); - } else { - bval = ctx.builder.CreateCall(prepare_call(jlgetbindingorerror_func), - { literal_pointer_val(ctx, (jl_value_t*)m), - literal_pointer_val(ctx, (jl_value_t*)s)}); - } - setName(ctx.emission_context, bval, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s) + ".found"); - ctx.builder.CreateAlignedStore(bval, bindinggv, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release); - ctx.builder.CreateBr(have_val); - have_val->insertInto(ctx.f); - ctx.builder.SetInsertPoint(have_val); - PHINode *p = ctx.builder.CreatePHI(ctx.types().T_pjlvalue, 2); - p->addIncoming(cachedval, currentbb); - p->addIncoming(bval, not_found); - setName(ctx.emission_context, p, jl_symbol_name(m->name) + StringRef(".") + jl_symbol_name(s)); - return p; - } - if (assign) { - if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - // this will fail at runtime, so defer to the runtime to create the error - ctx.builder.CreateCall(prepare_call(jlgetbindingwrorerror_func), - { literal_pointer_val(ctx, (jl_value_t*)m), - literal_pointer_val(ctx, (jl_value_t*)s), - ConstantInt::get(getInt32Ty(ctx.builder.getContext()), alloc) }); - CreateTrap(ctx.builder); - return NULL; - } - } - else { - if (b->deprecated) - cg_bdw(ctx, s, b); - } - *pbnd = b; - return julia_binding_gv(ctx, b); -} - 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) { LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); @@ -10184,7 +10103,7 @@ static void init_jit_functions(void) add_named_global(jltypeerror_func, &jl_type_error); add_named_global(jlcheckassign_func, &jl_checked_assignment); add_named_global(jlgetbindingorerror_func, &jl_get_binding_or_error); - add_named_global(jlgetbindingwrorerror_func, &jl_get_binding_wr); + add_named_global(jlcheckbpwritable_func, &jl_check_binding_currently_writable); add_named_global(jlboundp_func, &jl_boundp); for (auto it : builtin_func_map()) add_named_global(it.second, it.first); diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index c1b29a091511b..9e221420aa9f4 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -196,6 +196,7 @@ XX(jl_get_binding_for_method_def) \ XX(jl_get_binding_or_error) \ XX(jl_get_binding_wr) \ + XX(jl_check_binding_currently_writable) \ XX(jl_get_cpu_name) \ XX(jl_get_cpu_features) \ XX(jl_cpu_has_fma) \ diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 57f67755df692..97d76e7762a9e 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -4626,6 +4626,8 @@ f(x) = yt(x) (if (globalref? lhs) (begin (emit `(global ,lhs)) + (if (null? (cadr lam)) + (emit `(latestworld))) (emit `(call (top setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs))) (emit `(= ,lhs ,rhs)))) (define (emit-assignment lhs rhs) @@ -4944,13 +4946,16 @@ f(x) = yt(x) #f)) ((global) ; keep global declarations as statements (if value (error "misplaced \"global\" declaration")) - (emit e)) + (emit e) + (if (null? (cadr lam)) + (emit `(latestworld)))) ((globaldecl) (if value (error "misplaced \"global\" declaration")) - (if (atom? (caddr e)) (emit e) + (if (atom? (caddr e)) (begin (emit e) (emit `(latestworld))) (let ((rr (make-ssavalue))) (emit `(= ,rr ,(caddr e))) - (emit `(globaldecl ,(cadr e) ,rr))))) + (emit `(globaldecl ,(cadr e) ,rr)) + (emit `(latestworld))))) ((local-def) #f) ((local) #f) ((moved-local) diff --git a/src/julia.h b/src/julia.h index f36133088119f..a5f56ad580335 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2001,7 +2001,8 @@ 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_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var); // 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_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module_t *m, jl_sym_t *s); +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import); JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var); diff --git a/src/module.c b/src/module.c index f4c56e19efa61..2630db2dbfd94 100644 --- a/src/module.c +++ b/src/module.c @@ -279,38 +279,42 @@ extern void check_safe_newbinding(jl_module_t *m, jl_sym_t *var) static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b, jl_module_t *m, jl_sym_t *var) JL_GLOBALLY_ROOTED; -// 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) +// Checks that the binding in general is currently writable, but does not perform any checks on the +// value to be written into the binding. +JL_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module_t *m, jl_sym_t *s) { - jl_binding_t *b = jl_get_module_binding(m, var, 1); jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); retry: if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL && !jl_bkind_is_some_constant(decode_restriction_kind(pku))) { if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { if (decode_restriction_kind(pku) != BINDING_KIND_DECLARED) { - check_safe_newbinding(m, var); - if (!alloc) - jl_errorf("Global %s.%s does not exist and cannot be assigned.\n" - "Note: Julia 1.9 and 1.10 inadvertently omitted this error check (#56933).\n" - "Hint: Declare it using `global %s` inside `%s` before attempting assignment.", - jl_symbol_name(m->name), jl_symbol_name(var), - jl_symbol_name(var), jl_symbol_name(m->name)); + jl_errorf("Global %s.%s does not exist and cannot be assigned.\n" + "Note: Julia 1.9 and 1.10 inadvertently omitted this error check (#56933).\n" + "Hint: Declare it using `global %s` inside `%s` before attempting assignment.", + jl_symbol_name(m->name), jl_symbol_name(s), + jl_symbol_name(s), jl_symbol_name(m->name)); } jl_ptr_kind_union_t new_pku = encode_restriction((jl_value_t*)jl_any_type, BINDING_KIND_GLOBAL); if (!jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) goto retry; jl_gc_wb_knownold(bpart, jl_any_type); } else { - jl_module_t *from = jl_binding_dbgmodule(b, m, var); + jl_module_t *from = jl_binding_dbgmodule(b, m, s); if (from == m) jl_errorf("cannot assign a value to imported variable %s.%s", - jl_symbol_name(from->name), jl_symbol_name(var)); + jl_symbol_name(from->name), jl_symbol_name(s)); 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(m->name)); + jl_symbol_name(from->name), jl_symbol_name(s), jl_symbol_name(m->name)); } } +} + +JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var) +{ + jl_binding_t *b = jl_get_module_binding(m, var, 1); + jl_check_binding_currently_writable(b, m, var); return b; } @@ -1066,7 +1070,7 @@ JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var) 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_binding_t *bp = jl_get_binding_wr(m, var, 0); + jl_binding_t *bp = jl_get_binding_wr(m, var); jl_checked_assignment(bp, m, var, val); } @@ -1186,7 +1190,9 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_sym_t *s, jl_binding_t *b } } -jl_value_t *jl_check_binding_wr(jl_binding_t *b JL_PROPAGATES_ROOT, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED, int reassign) +// For a generally writable binding (checked using jl_check_binding_currently_writable in this world age), check whether +// we can actually write the value `rhs` to it. +jl_value_t *jl_check_binding_assign_value(jl_binding_t *b JL_PROPAGATES_ROOT, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED) { JL_GC_PUSH1(&rhs); // callee-rooted jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); @@ -1219,7 +1225,7 @@ jl_value_t *jl_check_binding_wr(jl_binding_t *b JL_PROPAGATES_ROOT, jl_module_t JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs) { - if (jl_check_binding_wr(b, mod, var, rhs, 1) != NULL) { + if (jl_check_binding_assign_value(b, mod, var, rhs) != NULL) { jl_atomic_store_release(&b->value, rhs); jl_gc_wb(b, rhs); } @@ -1227,7 +1233,7 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sy 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); + jl_check_binding_assign_value(b, mod, var, rhs); jl_value_t *old = jl_atomic_exchange(&b->value, rhs); jl_gc_wb(b, rhs); if (__unlikely(old == NULL)) @@ -1237,7 +1243,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_s 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_value_t *ty = jl_check_binding_wr(b, mod, var, rhs, 0); + jl_value_t *ty = jl_check_binding_assign_value(b, mod, var, rhs); return replace_value(ty, &b->value, (jl_value_t*)b, expected, rhs, 1, mod, var); } @@ -1256,7 +1262,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl JL_DLLEXPORT jl_value_t *jl_checked_assignonce(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); + jl_check_binding_assign_value(b, mod, var, rhs); jl_value_t *old = NULL; if (jl_atomic_cmpswap(&b->value, &old, rhs)) jl_gc_wb(b, rhs); diff --git a/src/toplevel.c b/src/toplevel.c index 30e78bf813fc8..e05783ac61a0c 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -302,38 +302,6 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f return args[0]; } -void jl_binding_set_type(jl_binding_t *b, jl_module_t *mod, jl_sym_t *sym, jl_value_t *ty) -{ - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - jl_ptr_kind_union_t new_pku = encode_restriction(ty, BINDING_KIND_GLOBAL); - while (1) { - if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL) { - if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { - if (jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) - break; - continue; - } else { - jl_errorf("cannot set type for imported global %s.%s.", - jl_symbol_name(mod->name), jl_symbol_name(sym)); - } - } - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { - jl_errorf("cannot set type for imported constant %s.%s.", - jl_symbol_name(mod->name), jl_symbol_name(sym)); - } - jl_value_t *old_ty = decode_restriction_value(pku); - JL_GC_PROMISE_ROOTED(old_ty); - if (!jl_types_equal(ty, old_ty)) { - jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", - jl_symbol_name(mod->name), jl_symbol_name(sym)); - } - if (jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) - break; - } - jl_gc_wb(bpart, ty); -} - extern void check_safe_newbinding(jl_module_t *m, jl_sym_t *var); void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { // create uninitialized mutable binding for "global x" decl sometimes or probably @@ -349,17 +317,49 @@ void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { gm = m; gs = (jl_sym_t*)arg; } + JL_LOCK(&world_counter_lock); + size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1; jl_binding_t *b = jl_get_module_binding(gm, gs, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age); - jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); - while (decode_restriction_kind(pku) == BINDING_KIND_GUARD || decode_restriction_kind(pku) == BINDING_KIND_FAILED) { - check_safe_newbinding(gm, gs); - if (jl_atomic_cmpswap(&bpart->restriction, &pku, encode_restriction(NULL, BINDING_KIND_DECLARED))) - break; - } - if (set_type) { - jl_binding_set_type(b, gm, gs, set_type); + jl_binding_partition_t *bpart = NULL; + jl_ptr_kind_union_t new_pku = encode_restriction(set_type, set_type == NULL ? BINDING_KIND_DECLARED : BINDING_KIND_GLOBAL); + while (1) { + bpart = jl_get_binding_partition(b, new_world); + jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); + if (decode_restriction_kind(pku) != BINDING_KIND_GLOBAL) { + if (jl_bkind_is_some_guard(decode_restriction_kind(pku))) { + if (decode_restriction_kind(pku) == BINDING_KIND_DECLARED && !set_type) + goto done; + check_safe_newbinding(gm, gs); + if (jl_atomic_cmpswap(&bpart->restriction, &pku, new_pku)) { + break; + } + continue; + } else if (set_type) { + if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + jl_errorf("cannot set type for imported constant %s.%s.", + jl_symbol_name(gm->name), jl_symbol_name(gs)); + } else { + jl_errorf("cannot set type for imported global %s.%s.", + jl_symbol_name(gm->name), jl_symbol_name(gs)); + } + } + } + if (!set_type) + goto done; + jl_value_t *old_ty = decode_restriction_value(pku); + JL_GC_PROMISE_ROOTED(old_ty); + if (!jl_types_equal(set_type, old_ty)) { + jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", + jl_symbol_name(gm->name), jl_symbol_name(gs)); + } + goto done; } + if (set_type) + jl_gc_wb(bpart, set_type); + bpart->min_world = new_world; + jl_atomic_store_release(&jl_world_counter, new_world); +done: + JL_UNLOCK(&world_counter_lock); } void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) @@ -751,7 +751,8 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction); int did_warn = 0; while (1) { - if (jl_bkind_is_some_constant(decode_restriction_kind(pku))) { + enum jl_partition_kind kind = decode_restriction_kind(pku); + if (jl_bkind_is_some_constant(kind)) { if (!val) { break; } @@ -789,7 +790,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3( continue; } jl_gc_wb(bpart, val); - int needs_backdate = bpart->min_world == 0 && new_world && val; + int needs_backdate = (bpart->min_world == 0 || kind == BINDING_KIND_DECLARED) && new_world && val; bpart->min_world = new_world; if (needs_backdate) { jl_declare_constant_val3(b, mod, var, val, BINDING_KIND_BACKDATED_CONST, 0); @@ -1094,9 +1095,8 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val else { // use interpreter assert(thk); - if (has_opaque) { + if (has_opaque) jl_resolve_definition_effects_in_ir((jl_array_t*)thk->code, m, NULL, 0); - } size_t world = jl_atomic_load_acquire(&jl_world_counter); ct->world_age = world; result = jl_interpret_toplevel_thunk(m, thk); diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index f83fa867748af..699024c1723a4 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -44,8 +44,8 @@ function UndefVarError_hint(io::IO, ex::UndefVarError) "with the module it should come from.") elseif kind === Base.BINDING_KIND_GUARD print(io, "\nSuggestion: check for spelling errors or missing imports.") - else - print(io, "\nSuggestion: this global was defined as `$(bpart.restriction.globalref)` but not assigned a value.") + elseif Base.is_some_imported(kind) + print(io, "\nSuggestion: this global was defined as `$(Base.partition_restriction(bpart).globalref)` but not assigned a value.") end elseif scope === :static_parameter print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.") @@ -1884,16 +1884,26 @@ function get_usings!(usings, ex) return usings end +function create_global_out!(mod) + if !isdefinedglobal(mod, :Out) + out = Dict{Int, Any}() + @eval mod begin + const Out = $(out) + export Out + end + return out + end + return getglobal(mod, Out) +end + function capture_result(n::Ref{Int}, @nospecialize(x)) n = n[] mod = Base.MainInclude - if !isdefined(mod, :Out) - @eval mod global Out - @eval mod export Out - setglobal!(mod, :Out, Dict{Int, Any}()) - end - if x !== getglobal(mod, :Out) && x !== nothing # remove this? - getglobal(mod, :Out)[n] = x + # TODO: This invokelatest is only required due to backdated constants + # and should be removed after + out = isdefinedglobal(mod, :Out) ? invokelatest(getglobal, mod, :Out) : invokelatest(create_global_out!, mod) + if x !== out && x !== nothing # remove this? + out[n] = x end nothing end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 8944fd76f31de..018cf9c36430e 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -752,11 +752,11 @@ fake_repl() do stdin_write, stdout_read, repl # Test removal of prefix in single statement paste sendrepl2("\e[200~julia> A = 2\e[201~\n") - @test Main.A == 2 + @test @world(Main.A, ∞) == 2 # Test removal of prefix in single statement paste sendrepl2("\e[200~In [12]: A = 2.2\e[201~\n") - @test Main.A == 2.2 + @test @world(Main.A, ∞) == 2.2 # Test removal of prefix in multiple statement paste sendrepl2("""\e[200~ @@ -768,7 +768,7 @@ fake_repl() do stdin_write, stdout_read, repl julia> A = 3\e[201~ """) - @test Main.A == 3 + @test @world(Main.A, ∞) == 3 @test @invokelatest(Main.foo(4)) @test @invokelatest(Main.T17599(3)).a == 3 @test !@invokelatest(Main.foo(2)) @@ -780,12 +780,12 @@ fake_repl() do stdin_write, stdout_read, repl julia> A = 4 4\e[201~ """) - @test Main.A == 4 + @test @world(Main.A, ∞) == 4 @test @invokelatest(Main.goo(4)) == 5 # Test prefix removal only active in bracket paste mode sendrepl2("julia = 4\n julia> 3 && (A = 1)\n") - @test Main.A == 1 + @test @world(Main.A, ∞) == 1 # Test that indentation corresponding to the prompt is removed s = sendrepl2("""\e[200~julia> begin\n α=1\n β=2\n end\n\e[201~""") @@ -820,8 +820,8 @@ fake_repl() do stdin_write, stdout_read, repl julia> B = 2 2\e[201~ """) - @test Main.A == 1 - @test Main.B == 2 + @test @world(Main.A, ∞) == 1 + @test @world(Main.B, ∞) == 2 end # redirect_stdout # Close repl diff --git a/sysimage.mk b/sysimage.mk index 571e2da003346..ae6ce8699f417 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -39,6 +39,7 @@ COMPILER_SRCS := $(addprefix $(JULIAHOME)/, \ base/error.jl \ base/essentials.jl \ base/expr.jl \ + base/exports.jl \ base/generator.jl \ base/int.jl \ base/indices.jl \ diff --git a/test/core.jl b/test/core.jl index 3886e6728df10..5e2677f0f075f 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8233,6 +8233,7 @@ end let M = @__MODULE__ Core.eval(M, :(global a_typed_global)) @test Core.eval(M, :(global a_typed_global::$(Tuple{Union{Integer,Nothing}}))) === nothing + @Core.latestworld @test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}} @test Core.eval(M, :(global a_typed_global::$(Tuple{Union{Integer,Nothing}}))) === nothing @test Core.eval(M, :(global a_typed_global::$(Union{Tuple{Integer},Tuple{Nothing}}))) === nothing diff --git a/test/precompile.jl b/test/precompile.jl index f5a412b416ddc..a9516231ff8d7 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1969,7 +1969,7 @@ precompile_test_harness("Issue #50538") do load_path """ module I50538 const newglobal = try - Base.newglobal = false + eval(Expr(:global, GlobalRef(Base, :newglobal))) catch ex ex isa ErrorException || rethrow() ex diff --git a/test/worlds.jl b/test/worlds.jl index 025aaba6cea4f..48fb6593d3a37 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -107,7 +107,7 @@ end g265() = [f265(x) for x in 1:3.] wc265 = get_world_counter() wc265_41332a = Task(tls_world_age) -@test tls_world_age() == wc265 +@test tls_world_age() == wc265 + 2 (function () global wc265_41332b = Task(tls_world_age) @eval f265(::Any) = 1.0 @@ -115,24 +115,24 @@ wc265_41332a = Task(tls_world_age) global wc265_41332d = Task(tls_world_age) nothing end)() -@test wc265 + 4 == get_world_counter() == tls_world_age() +@test wc265 + 10 == get_world_counter() == tls_world_age() schedule(wc265_41332a) schedule(wc265_41332b) schedule(wc265_41332c) schedule(wc265_41332d) -@test wc265 == fetch(wc265_41332a) -@test wc265 + 2 == fetch(wc265_41332b) -@test wc265 + 4 == fetch(wc265_41332c) -@test wc265 + 2 == fetch(wc265_41332d) +@test wc265 + 1 == fetch(wc265_41332a) +@test wc265 + 8 == fetch(wc265_41332b) +@test wc265 + 10 == fetch(wc265_41332c) +@test wc265 + 8 == fetch(wc265_41332d) chnls, tasks = Base.channeled_tasks(2, wfunc) t265 = tasks[1] wc265 = get_world_counter() @test put_n_take!(get_world_counter, ()) == wc265 -@test put_n_take!(tls_world_age, ()) == wc265 +@test put_n_take!(tls_world_age, ()) + 3 == wc265 f265(::Int) = 1 @test put_n_take!(get_world_counter, ()) == wc265 + 1 == get_world_counter() == tls_world_age() -@test put_n_take!(tls_world_age, ()) == wc265 +@test put_n_take!(tls_world_age, ()) + 3 == wc265 @test g265() == Int[1, 1, 1] @test Core.Compiler.return_type(f265, Tuple{Any,}) == Union{Float64, Int} @@ -162,12 +162,12 @@ let ex = t265.exception @test ex isa MethodError @test ex.f == h265 @test ex.args == () - @test ex.world == wc265 + @test ex.world == wc265-3 str = sprint(showerror, ex) wc = get_world_counter() cmps = """ MethodError: no method matching h265() - The applicable method may be too new: running in world age $wc265, while current world is $wc.""" + The applicable method may be too new: running in world age $(wc265-3), while current world is $wc.""" @test startswith(str, cmps) cmps = "\n h265() (method too new to be called from this world context.)\n $loc_h265" @test occursin(cmps, str) @@ -500,3 +500,12 @@ end end @test_throws ErrorException("Generated function result with `edges == nothing` and `max_world == typemax(UInt)` must have `min_world == 1`") generated_no_edges() + +# Test that backdating of constants is working for structs +before_backdate_age = Base.tls_world_age() +struct FooBackdated + x::Vector{FooBackdated} + + FooBackdated() = new(FooBackdated[]) +end +@test Base.invoke_in_world(before_backdate_age, isdefined, @__MODULE__, :FooBackdated)