Skip to content

Commit 7a8cd6e

Browse files
xal-0vtjnash
andauthored
Remove :globaldecl and :global lowered forms; add Core.declare_global (#58279)
# Overview In the spirit of #58187 and #57965, this PR lowers more surface syntax to calls, eliminating the lowered `:global` and `:globaldecl` operations in favour of a single `Core.declare_global` builtin. `Core.declare_global` has the signature: ``` declare_global(module::Module, name::Symbol, strong::Bool=false, [ty::Type]) ``` - When `strong = false`, it has the effect of `global name` at the top level (see the description for [`PARTITION_KIND_DECLARED`](https://github.com/JuliaLang/julia/blob/d46b665067bd9fc352c89c9d0abb591eaa4f7695/src/julia.h#L706-L710)). - With `strong = true`: - No `ty` provided: if no global exists, creates a strong global with type `Any`. Has no effect if one already exists. This form is generated by global assignments with no type declaration. - `ty` provided: always creates a new global with the given type, failing if one already exists with a different declared type. ## Definition effects One of the purposes of this change is to remove the definitions effects for `:global` and `:globaldecl`: https://github.com/JuliaLang/julia/blob/d46b665067bd9fc352c89c9d0abb591eaa4f7695/src/method.c#L95-L105 The eventual goal is to make all the definition effects for a method explicit after lowering, simplifying interpreters for lowered IR. ## Minor lowering changes ### `global` permitted in more places Adds a new ephemeral syntax head, `unused-only`, to wrap expressions whose result should not be used. It generates the `misplaced "global" declaration` error, and is slightly more forgiving than the old restriction. This was necessary to permit `global` to be lowered in all contexts. Old: ``` julia> global example julia> begin global example end ERROR: syntax: misplaced "global" declaration Stacktrace: [1] top-level scope @ REPL[2]:1 ``` New: ``` julia> global example julia> begin global example end ``` ### `global` always lowered This change maintains support for some expressions that cannot be produced by the parser (similar to `Expr(:const, :foo)`): https://github.com/JuliaLang/julia/blob/d46b665067bd9fc352c89c9d0abb591eaa4f7695/test/precompile.jl#L2036 This used to work by bypassing lowering but is now lowered to the appropriate `declare_global` call. ## Generated functions After lowering the body AST returned by a `@generated` function, the definition effects are still performed. Instead of relying on a check in `jl_declare_global` to fail during this process, `GeneratedFunctionStub` now wraps the AST in a new Expr head, `Expr(:toplevel_pure, ...)`, indicating lowering should not produce toplevel side effects. Currently, this is used only to omit calls to `declare_global` for generated functions, but it could also be used to improve the catch-all error message when lowering returns a thunk (telling the user if it failed because of a closure, generator, etc), or even to support some closures by making them opaque. The error message for declaring a global as a side effect of a `@generated` function AST has changed, because it now fails when the assignment to an undeclared global is performed. Old: ``` julia> @generated function foo(x) :(global bar = x) end foo (generic function with 1 method) julia> foo(1) ERROR: new strong globals cannot be created in a generated function. Declare them outside using `global x::Any`. Stacktrace: [1] top-level scope @ REPL[2]:1 ``` New: ``` julia> @generated function foo(x) :(global bar = x) end foo (generic function with 1 method) julia> foo(1) ERROR: Global Main.bar does not exist and cannot be assigned. Note: Julia 1.9 and 1.10 inadvertently omitted this error check (#56933). Hint: Declare it using `global bar` inside `Main` before attempting assignment. Stacktrace: [1] macro expansion @ ./REPL[1]:1 [inlined] [2] foo(x::Int64) @ Main ./REPL[1]:1 [3] top-level scope @ REPL[2]:1 ``` ## Examples of the new lowering Toplevel weak global: ``` julia> Meta.@lower global example :($(Expr(:thunk, CodeInfo( 1 ─ builtin Core.declare_global(Main, :example, false) │ $(Expr(:latestworld)) └── return nothing )))) ``` Toplevel strong global declaration with type: ``` julia> Meta.@lower example::Int :($(Expr(:thunk, CodeInfo( 1 ─ %1 = Main.example │ %2 = Main.Int │ %3 = builtin Core.typeassert(%1, %2) └── return %3 )))) ``` Toplevel strong global assignment: ``` julia> Meta.@lower example = 1 :($(Expr(:thunk, CodeInfo( 1 ─ builtin Core.declare_global(Main, :example, true) │ $(Expr(:latestworld)) │ %3 = builtin Core.get_binding_type(Main, :example) │ #s1 = 1 │ %5 = #s1 │ %6 = builtin %5 isa %3 └── goto #3 if not %6 2 ─ goto #4 3 ─ %9 = #s1 └── #s1 = Base.convert(%3, %9) 4 ┄ %11 = #s1 │ dynamic Base.setglobal!(Main, :example, %11) └── return 1 )))) ``` Toplevel strong global assignment with type: ``` julia> Meta.@lower example::Int = 1 :($(Expr(:thunk, CodeInfo( 1 ─ %1 = Main.Int │ builtin Core.declare_global(Main, :example, true, %1) │ $(Expr(:latestworld)) │ %4 = builtin Core.get_binding_type(Main, :example) │ #s1 = 1 │ %6 = #s1 │ %7 = builtin %6 isa %4 └── goto #3 if not %7 2 ─ goto #4 3 ─ %10 = #s1 └── #s1 = Base.convert(%4, %10) 4 ┄ %12 = #s1 │ dynamic Base.setglobal!(Main, :example, %12) └── return 1 )))) ``` Global assignment inside function (call to `declare_global` hoisted to top level): ``` julia> Meta.@lower function f1(x) global example = x end :($(Expr(:thunk, CodeInfo( 1 ─ $(Expr(:method, :(Main.f1))) │ $(Expr(:latestworld)) │ $(Expr(:latestworld)) │ builtin Core.declare_global(Main, :example, false) │ $(Expr(:latestworld)) │ builtin Core.declare_global(Main, :example, true) │ $(Expr(:latestworld)) │ %8 = Main.f1 │ %9 = dynamic Core.Typeof(%8) │ %10 = builtin Core.svec(%9, Core.Any) │ %11 = builtin Core.svec() │ %12 = builtin Core.svec(%10, %11, $(QuoteNode(:(#= REPL[7]:1 =#)))) │ $(Expr(:method, :(Main.f1), :(%12), CodeInfo( @ REPL[7]:2 within `unknown scope` 1 ─ %1 = x │ %2 = builtin Core.get_binding_type(Main, :example) │ @_3 = %1 │ %4 = @_3 │ %5 = builtin %4 isa %2 └── goto #3 if not %5 2 ─ goto #4 3 ─ %8 = @_3 └── @_3 = Base.convert(%2, %8) 4 ┄ %10 = @_3 │ dynamic Base.setglobal!(Main, :example, %10) └── return %1 ))) │ $(Expr(:latestworld)) │ %15 = Main.f1 └── return %15 )))) ``` --------- Co-authored-by: Jameson Nash <[email protected]>
1 parent a7b8f0c commit 7a8cd6e

18 files changed

+123
-147
lines changed

Compiler/src/abstractinterpretation.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3457,11 +3457,8 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, ssta
34573457
return abstract_eval_static_parameter(interp, e, sv)
34583458
elseif ehead === :gc_preserve_begin || ehead === :aliasscope
34593459
return RTEffects(Any, Union{}, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, effect_free=EFFECT_FREE_GLOBALLY))
3460-
elseif ehead === :gc_preserve_end || ehead === :leave || ehead === :pop_exception ||
3461-
ehead === :global || ehead === :popaliasscope
3460+
elseif ehead === :gc_preserve_end || ehead === :leave || ehead === :pop_exception || ehead === :popaliasscope
34623461
return RTEffects(Nothing, Union{}, Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY))
3463-
elseif ehead === :globaldecl
3464-
return RTEffects(Nothing, Any, EFFECTS_UNKNOWN)
34653462
elseif ehead === :thunk
34663463
return RTEffects(Any, Any, Effects())
34673464
end

Compiler/src/ssair/ir.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ function is_relevant_expr(e::Expr)
584584
:foreigncall, :isdefined, :copyast,
585585
:throw_undef_if_not,
586586
:cfunction, :method, :pop_exception,
587-
:leave, :globaldecl,
587+
:leave,
588588
:new_opaque_closure)
589589
end
590590

Compiler/src/ssair/verify.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if !isdefined(@__MODULE__, Symbol("@verify_error"))
2525
end
2626
end
2727

28-
is_toplevel_expr_head(head::Symbol) = head === :global || head === :method || head === :thunk
28+
is_toplevel_expr_head(head::Symbol) = head === :method || head === :thunk
2929
is_value_pos_expr_head(head::Symbol) = head === :static_parameter
3030
function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, use_idx::Int, printed_use_idx::Int, print::Bool, isforeigncall::Bool, arg_idx::Int,
3131
allow_frontend_forms::Bool, @nospecialize(raise_error))

Compiler/src/validation.jl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
2020
:boundscheck => 0:1,
2121
:copyast => 1:1,
2222
:meta => 0:typemax(Int),
23-
:global => 1:1,
24-
:globaldecl => 1:2,
2523
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects, gc_safe), args..., roots...
2624
:cfunction => 5:5,
2725
:isdefined => 1:2,
@@ -52,7 +50,6 @@ const SSAVALUETYPES_MISMATCH = "not all SSAValues in AST have a type in ssavalue
5250
const SSAVALUETYPES_MISMATCH_UNINFERRED = "uninferred CodeInfo ssavaluetypes field does not equal the number of present SSAValues"
5351
const SSAFLAGS_MISMATCH = "not all SSAValues have a corresponding `ssaflags`"
5452
const NON_TOP_LEVEL_METHOD = "encountered `Expr` head `:method` in non-top-level code (i.e. `nargs` > 0)"
55-
const NON_TOP_LEVEL_GLOBAL = "encountered `Expr` head `:global` in non-top-level code (i.e. `nargs` > 0)"
5653
const SIGNATURE_NARGS_MISMATCH = "method signature does not match number of method arguments"
5754
const SLOTNAMES_NARGS_MISMATCH = "CodeInfo for method contains fewer slotnames than the number of method arguments"
5855
const INVALID_SIGNATURE_OPAQUE_CLOSURE = "invalid signature of method for opaque closure - `sig` field must always be set to `Tuple`"
@@ -124,7 +121,6 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le
124121
head = x.head
125122
if !is_top_level
126123
head === :method && push!(errors, InvalidCodeError(NON_TOP_LEVEL_METHOD))
127-
head === :global && push!(errors, InvalidCodeError(NON_TOP_LEVEL_GLOBAL))
128124
end
129125
narg_bounds = get(VALID_EXPR_HEADS, head, -1:-1)
130126
nargs = length(x.args)
@@ -149,7 +145,7 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le
149145
head === :gc_preserve_end || head === :meta ||
150146
head === :inbounds || head === :foreigncall || head === :cfunction ||
151147
head === :leave || head === :pop_exception ||
152-
head === :method || head === :global || head === :static_parameter ||
148+
head === :method || head === :static_parameter ||
153149
head === :new || head === :splatnew || head === :thunk || head === :loopinfo ||
154150
head === :throw_undef_if_not || head === :code_coverage_effect || head === :inline || head === :noinline
155151
validate_val!(x)

base/docs/basedocs.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,17 @@ See also [`setpropertyonce!`](@ref Base.setpropertyonce!) and [`setglobal!`](@re
27582758
"""
27592759
setglobalonce!
27602760

2761+
"""
2762+
declare_global(module::Module, name::Symbol, strong::Bool=false, [ty::Type])
2763+
2764+
Declare the global `name` in module `module`. If `ty` is given, declares a
2765+
"strong" global, which cannot be replaced with a constant binding, otherwise
2766+
declares a weak global.
2767+
2768+
See also [`global`](@ref), [`setglobal!`](@ref), [`get_binding_type`](@ref Core.get_binding_type).
2769+
"""
2770+
Core.declare_global
2771+
27612772
"""
27622773
declare_const(module::Module, name::Symbol, [x])
27632774

base/expr.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1833,7 +1833,7 @@ function (g::Core.GeneratedFunctionStub)(world::UInt, source::Method, @nospecial
18331833
Expr(:block,
18341834
LineNumberNode(Int(source.line), source.file),
18351835
Expr(:meta, :push_loc, file, :var"@generated body"),
1836-
Expr(:return, body),
1836+
Expr(:return, Expr(:toplevel_pure, body)),
18371837
Expr(:meta, :pop_loc))))
18381838
spnames = g.spnames
18391839
return generated_body_to_codeinfo(spnames === Core.svec() ? lam : Expr(Symbol("with-static-parameters"), lam, spnames...),

src/ast.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ void jl_init_common_symbols(void)
264264
jl_opaque_closure_method_sym = jl_symbol("opaque_closure_method");
265265
jl_const_sym = jl_symbol("const");
266266
jl_global_sym = jl_symbol("global");
267-
jl_globaldecl_sym = jl_symbol("globaldecl");
268267
jl_local_sym = jl_symbol("local");
269268
jl_thunk_sym = jl_symbol("thunk");
270269
jl_toplevel_sym = jl_symbol("toplevel");

src/builtin_proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ extern "C" {
3535
XX(get_binding_type,"get_binding_type") \
3636
XX(getfield,"getfield") \
3737
XX(getglobal,"getglobal") \
38+
XX(declare_global,"declare_global") \
3839
XX(ifelse,"ifelse") \
3940
XX(intrinsic_call,"intrinsic_call") \
4041
XX(invoke,"invoke") \

src/builtins.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,23 @@ JL_CALLABLE(jl_f_setglobalonce)
15181518
return old == NULL ? jl_true : jl_false;
15191519
}
15201520

1521+
// declare_global(module::Module, name::Symbol, [strong::Bool=false, [ty::Type]])
1522+
JL_CALLABLE(jl_f_declare_global)
1523+
{
1524+
JL_NARGS(declare_global, 3, 4);
1525+
JL_TYPECHK(declare_global, module, args[0]);
1526+
JL_TYPECHK(declare_global, symbol, args[1]);
1527+
JL_TYPECHK(declare_global, bool, args[2]);
1528+
int strong = args[2] == jl_true;
1529+
jl_value_t *set_type = NULL;
1530+
if (nargs >= 4) {
1531+
JL_TYPECHK(declare_global, type, args[3]);
1532+
set_type = args[3];
1533+
}
1534+
jl_declare_global((jl_module_t *)args[0], args[1], set_type, strong);
1535+
return jl_nothing;
1536+
}
1537+
15211538
JL_CALLABLE(jl_f_declare_const)
15221539
{
15231540
JL_NARGS(declare_const, 2, 3);

src/codegen.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6460,23 +6460,6 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_
64606460
jl_method_type);
64616461
return meth;
64626462
}
6463-
else if (head == jl_globaldecl_sym) {
6464-
assert(nargs <= 2 && nargs >= 1);
6465-
jl_sym_t *sym = (jl_sym_t*)args[0];
6466-
jl_module_t *mod = ctx.module;
6467-
if (jl_is_globalref(sym)) {
6468-
mod = jl_globalref_mod(sym);
6469-
sym = jl_globalref_name(sym);
6470-
}
6471-
if (nargs == 2) {
6472-
jl_cgval_t typ = emit_expr(ctx, args[1]);
6473-
ctx.builder.CreateCall(prepare_call(jldeclareglobal_func),
6474-
{ literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, typ), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1) });
6475-
} else {
6476-
ctx.builder.CreateCall(prepare_call(jldeclareglobal_func),
6477-
{ literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), ConstantPointerNull::get(cast<PointerType>(ctx.types().T_prjlvalue)), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 1) });
6478-
}
6479-
}
64806463
else if (head == jl_new_sym) {
64816464
bool is_promotable = false;
64826465
if (ssaidx_0based >= 0) {

0 commit comments

Comments
 (0)