-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Add builtin functions Core._import, Core._using; implement import/using logic in Julia #57965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Removes the lowered IR forms Expr(:using, ...) and Expr(:import, ...). `import` and `using` expressions are now lowered to calls to `_module_import` like so: import A, B.c (block (call (core _module_import) (true) (thismodule) (null) (inert (|.| A))) (call (core _module_import) (true) (thismodule) (null) (inert (|.| B c))) (latestworld)) import A.B: C.d, e as f (block (call (core _module_import) (true) (thismodule) (inert (|.| A B)) (inert (|.| C d)) (inert (as (|.| e) f)))) using A.B: C.d (block (call (core _module_import) (false) (thismodule) (inert (|.| A B)) (inert (|.| C d))) (latestworld)) Each comma-separated import is lowered to a call to the new _module_import builtin, which takes an "explicit" flag (`using X:` is lowered to _module_import with explicit=false), a "context" path (set to `nothing` for `import` without `:`), and any number of Exprs of the form `(|.| ...)` or `(as (|.| ...) x)` representing import paths under the context path. Without `:`, using is lowered to the other new builtin: using A.B (block (call (core _module_using) (thismodule) (inert (|.| A B))) (latestworld))
Generally positive on the direction. For this particular implementation, I'll need to think about it in a bit more depth, but my initial impression is that this feels too "syntax oriented" for a builtin. I mean this in two senses. The first is that these builtins are taking
From this, there could then be higher level functions or macros that implement the syntax parts. The primary downside I see is that this approach would prevent batching of the world age update. However, I think that is possibly better handled with a generic world agree compression optimization in the runtime rather than trying to design the syntax around it. |
Agreed as to our eventual plan here. I think there are few phases, one (here) is keeping most of the existing code structure, but introducing a function form for it. Secondly we can move a lot of this C code into julia using some simpler builtins for the primitive bits. I think the tricky part of deciding on that form is dealing with the fact this is very much just interpreting the syntax, so it is hard to see what that julia function's arguments should be other than exactly the AST. |
Renames _module_import -> _eval_import and _module_using -> _eval_using (we have jl_module_using and jl_module_using in C and it's confusing enough already), and moves the logic into Julia. Since we need _eval_import very early in bootstrapping, boot.jl provides a minimal version that understands only `import M: a, b, ...`. Some imports in code included by Base_compiler.jl has been updated to match this. The Julia _eval_* functions call two new low-level builtins: - Core._import calls jl_module_import with the normal 5 argument form, or jl_import_module for the special case of importing a package to get a PARTITION_KIND_CONST_IMPORT binding (3 argument form). - Core._using is a direct wrapper around jl_module_using.
c638398 is a more-or-less direct translation of
|
Co-authored-by: Jameson Nash <[email protected]>
Co-authored-by: Jameson Nash <[email protected]>
Co-authored-by: Jameson Nash <[email protected]>
base/module.jl
Outdated
elseif v === :Base | ||
m = Base | ||
else | ||
# TODO: JL_TIMING(LOAD_IMAGE, LOAD_Require) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use Core.Compiler.@zone
now perhaps.
…ng logic in Julia (JuliaLang#57965) Currently, `import` and `using` statements are compiled into calls to `jl_toplevel_eval` with the Expr as an argument. It can be useful in an interactive environment to do import/using programmatically, for which `eval(Expr(:import, ...))` or a macro is the only option at the moment (see for example `InteractiveUtils.@activate`). This PR adds two functions, `_module_import ` and `_module_using`, with these signatures, and removes `Expr(:import/:using, ...)` from the lowered IR: ``` _module_import(explicit::Bool, to::Module, ctx::Union{Expr, Nothing}, names::Expr...) _module_using(to::Module, from::Expr{Symbol}) ``` ## Lowering example `import` statements and `using X:` statements are lowered to `_module_import` like so: ``` import A => _module_import(true, Main, nothing, Expr(:., :A)) import A.b => _module_import(true, Main, nothing, Expr(:., :A, :b)) import A.b as c => _module_import(true, Main, nothing, Expr(:as, Expr(:., :A, :b), :c)) import A.B: C.d, e => _module_import(true, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) import A.B: C.d as e => _module_import(true, Main, Expr(:., :A, :B), Expr(:as, Expr(:., :C, :d), :e)) using A.B: C.d, e => _module_import(false, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) ``` Plain `using` statements become `_module_using`: ``` using A.B => _module_using(Main, Expr(:., :A, :B)) ``` Multiple comma-separated `using` or `import` paths are lowered to multiple calls to the appropriate builtin: ``` julia> Meta.@lower using A.B, C :($(Expr(:thunk, CodeInfo( 1 ─ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :A, :B)))))) │ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :C)))))) │ $(Expr(:latestworld)) └── return nothing )))) ```
…ng logic in Julia (JuliaLang#57965) Currently, `import` and `using` statements are compiled into calls to `jl_toplevel_eval` with the Expr as an argument. It can be useful in an interactive environment to do import/using programmatically, for which `eval(Expr(:import, ...))` or a macro is the only option at the moment (see for example `InteractiveUtils.@activate`). This PR adds two functions, `_module_import ` and `_module_using`, with these signatures, and removes `Expr(:import/:using, ...)` from the lowered IR: ``` _module_import(explicit::Bool, to::Module, ctx::Union{Expr, Nothing}, names::Expr...) _module_using(to::Module, from::Expr{Symbol}) ``` ## Lowering example `import` statements and `using X:` statements are lowered to `_module_import` like so: ``` import A => _module_import(true, Main, nothing, Expr(:., :A)) import A.b => _module_import(true, Main, nothing, Expr(:., :A, :b)) import A.b as c => _module_import(true, Main, nothing, Expr(:as, Expr(:., :A, :b), :c)) import A.B: C.d, e => _module_import(true, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) import A.B: C.d as e => _module_import(true, Main, Expr(:., :A, :B), Expr(:as, Expr(:., :C, :d), :e)) using A.B: C.d, e => _module_import(false, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) ``` Plain `using` statements become `_module_using`: ``` using A.B => _module_using(Main, Expr(:., :A, :B)) ``` Multiple comma-separated `using` or `import` paths are lowered to multiple calls to the appropriate builtin: ``` julia> Meta.@lower using A.B, C :($(Expr(:thunk, CodeInfo( 1 ─ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :A, :B)))))) │ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :C)))))) │ $(Expr(:latestworld)) └── return nothing )))) ```
…ation (#688) Adjusts for JuliaLang/julia#57965.
…ng logic in Julia (JuliaLang#57965) Currently, `import` and `using` statements are compiled into calls to `jl_toplevel_eval` with the Expr as an argument. It can be useful in an interactive environment to do import/using programmatically, for which `eval(Expr(:import, ...))` or a macro is the only option at the moment (see for example `InteractiveUtils.@activate`). This PR adds two functions, `_module_import ` and `_module_using`, with these signatures, and removes `Expr(:import/:using, ...)` from the lowered IR: ``` _module_import(explicit::Bool, to::Module, ctx::Union{Expr, Nothing}, names::Expr...) _module_using(to::Module, from::Expr{Symbol}) ``` ## Lowering example `import` statements and `using X:` statements are lowered to `_module_import` like so: ``` import A => _module_import(true, Main, nothing, Expr(:., :A)) import A.b => _module_import(true, Main, nothing, Expr(:., :A, :b)) import A.b as c => _module_import(true, Main, nothing, Expr(:as, Expr(:., :A, :b), :c)) import A.B: C.d, e => _module_import(true, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) import A.B: C.d as e => _module_import(true, Main, Expr(:., :A, :B), Expr(:as, Expr(:., :C, :d), :e)) using A.B: C.d, e => _module_import(false, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) ``` Plain `using` statements become `_module_using`: ``` using A.B => _module_using(Main, Expr(:., :A, :B)) ``` Multiple comma-separated `using` or `import` paths are lowered to multiple calls to the appropriate builtin: ``` julia> Meta.@lower using A.B, C :($(Expr(:thunk, CodeInfo( 1 ─ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :A, :B)))))) │ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :C)))))) │ $(Expr(:latestworld)) └── return nothing )))) ```
is |
I thought it was nicest to keep them separate, and this distinction already existing in |
Drive by comment: this is great, it's exactly what I needed to fix some janky use of (It also breaks JuliaLowering, but that's a price I'm very much willing to pay 😆) |
Calls to `import` and `using` are expanded by lowering as of the changes in JuliaLang/julia#57965 and no longer dealt with by the C function `jl_toplevel_eval_flex`. This implies we can't use `eval()` for these if we want to activate JuliaLowering in Core, or we'll hit a stack overflow. I've chosen to duplicate the flisp lowering here for consistency and import paths are thus lowered to a restricted kind of quoted `Expr`. (It's mildly annoying to rely on quoted `Expr` in the lowered paths than the previous use of `Core.svec` but deleting the svec representation allows us to use `Base._eval_import` and `Base._eval_using` directly so seems like a worthy simplification.)
Calls to `import` and `using` are expanded by lowering as of the changes in JuliaLang/julia#57965 and no longer dealt with by the C function `jl_toplevel_eval_flex`. This implies we can't use `eval()` for these if we want to activate JuliaLowering in Core, or we'll hit a stack overflow. I've chosen to duplicate the flisp lowering here for consistency and import paths are thus lowered to a restricted kind of quoted `Expr`. (It's mildly annoying to rely on quoted `Expr` in the lowered paths than the previous use of `Core.svec` but deleting the svec representation allows us to use `Base._eval_import` and `Base._eval_using` directly so seems like a worthy simplification.) Similarly, use a precomputed vector of names in public/export expansion - this list can be computed at expansion time rather than emitting each element into the lowered code individually. Includes minor test+CI fixes julia 1.12 in support of JETLS.
Calls to `import` and `using` are expanded by lowering as of the changes in JuliaLang/julia#57965 and no longer dealt with by the C function `jl_toplevel_eval_flex`. This implies we can't use `eval()` for these if we want to activate JuliaLowering in Core, or we'll hit a stack overflow. I've chosen to duplicate the flisp lowering here for consistency and import paths are thus lowered to a restricted kind of quoted `Expr`. (It's mildly annoying to rely on quoted `Expr` in the lowered paths than the previous use of `Core.svec` but deleting the svec representation allows us to use `Base._eval_import` and `Base._eval_using` directly so seems like a worthy simplification.) Similarly, use a precomputed vector of names in public/export expansion - this list can be computed at expansion time rather than emitting each element into the lowered code individually. Includes minor test+CI fixes julia 1.12 in support of JETLS.
Provide a way to create new modules without resorting to passing an `Expr(:module, ...)` to `eval()` by splitting up `jl_eval_module_expr()` into three pieces: Two module creation functions which are exported: * `jl_begin_new_module()` - Creates the module - Situates it within the module hierarchy (creating a binding within the parent module, setting the module parent and inheriting uuid, registering root modules) - Deals with global rooting during initialization and state for detecting whether the module is open for eval. - Creates standard imports and bindings for module-local eval and include * `jl_end_new_module()` cleans up some of this state and calls `__init__()` for the module and its children when necessary. The `Expr`-related machinery is left in the internal function `jl_eval_module_expr()`. Something along these lines is required to avoid calling `eval(Expr(:module, ...))` in the JuliaLowering implementation of module evaluation where we try never to construct `Expr`. There's clearly further cleanup which could be done here. It would be nice to use less global state (`jl_current_modules` and `jl_module_init_order`) and to not to have the `Base.__toplevel__` hack. However I wanted to keep this PR small so I've resisted the temptation to change anything more for now. Similar to #57965 in the sense that it splits more functionality out of `eval()`.
Provide a way to create new modules without resorting to passing an `Expr(:module, ...)` to `eval()` by splitting up `jl_eval_module_expr()` into three pieces: Two module creation functions which are exported: * `jl_begin_new_module()` - Creates the module - Situates it within the module hierarchy (creating a binding within the parent module, setting the module parent and inheriting uuid, registering root modules) - Deals with global rooting during initialization and state for detecting whether the module is open for eval. - Creates standard imports and bindings for module-local eval and include * `jl_end_new_module()` cleans up some of this state and calls `__init__()` for the module and its children when necessary. The `Expr`-related machinery is left in the internal function `jl_eval_module_expr()`. Something along these lines is required to avoid calling `eval(Expr(:module, ...))` in the JuliaLowering implementation of module evaluation where we try never to construct `Expr`. There's clearly further cleanup which could be done here. It would be nice to use less global state (`jl_current_modules` and `jl_module_init_order`) and to not to have the `Base.__toplevel__` hack. However I wanted to keep this PR small so I've resisted the temptation to change anything more for now. Similar to JuliaLang#57965 in the sense that it splits more functionality out of `eval()`.
…#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]>
Overview
Currently,
import
andusing
statements are compiled into calls tojl_toplevel_eval
with the Expr as an argument. It can be useful in aninteractive environment to do import/using programmatically, for which
eval(Expr(:import, ...))
or a macro is the only option at the moment (see forexample
InteractiveUtils.@activate
).This PR adds two builtins,
_module_import
and_module_using
, with thesesignatures, and removes
Expr(:import/:using, ...)
from the lowered IR:Lowering example
import
statements andusing X:
statements are lowered to_module_import
like so:
Plain
using
statements become_module_using
:Multiple comma-separated
using
orimport
paths are lowered to multiple calls to the appropriate builtin:Alternative designs
Some alternatives to these builtins are worth considering.
The lowering pass could do even less work by inserting a call to
_module_import
or_module_using
that takes a single Expr argument for theentire statement.
Another option is to do even more in lowering, introducing very low-level
builtins that wrap
jl_module_import
andjl_module_using
directly. Thelowering pass would expand the import path into direct calls to
Base.require
,Base.getproperty
, etc. For example,import A.B: C.d, e
could becomesomething like:
I like the idea of supporting calls to
_module_import
with a module forctx
instead of an Expr, but this could be added to the proposed design too.