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 <vtjnash@gmail.com>