julia
7a8cd6e2 - Remove :globaldecl and :global lowered forms; add Core.declare_global (#58279)

Commit
142 days ago
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>
Author
Parents
Loading