Skip to content

Commit

Permalink
bpart: Start enforcing min_world for global variable definitions (#57150
Browse files Browse the repository at this point in the history
)

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.
Keno authored Jan 29, 2025
1 parent 989b32b commit f209eba
Showing 17 changed files with 175 additions and 201 deletions.
2 changes: 1 addition & 1 deletion Compiler/test/abioverride.jl
Original file line number Diff line number Diff line change
@@ -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
17 changes: 11 additions & 6 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
@@ -818,6 +818,7 @@ export
@invoke,
invokelatest,
@invokelatest,
@world,

# loading source files
__precompile__,
2 changes: 1 addition & 1 deletion base/loading.jl
Original file line number Diff line number Diff line change
@@ -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)
10 changes: 5 additions & 5 deletions src/builtins.c
Original file line number Diff line number Diff line change
@@ -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;
103 changes: 11 additions & 92 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
@@ -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<const char*> 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);
1 change: 1 addition & 0 deletions src/jl_exported_funcs.inc
Original file line number Diff line number Diff line change
@@ -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) \
11 changes: 8 additions & 3 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 2 additions & 1 deletion src/julia.h
Original file line number Diff line number Diff line change
@@ -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);
Loading

0 comments on commit f209eba

Please sign in to comment.