From b1e3910799bca9ac9111cb7aa25db8d443accda8 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Fri, 28 Mar 2025 15:09:38 -0700 Subject: [PATCH 01/11] Add builtin functions _module_import, _module_using 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)) --- src/ast.c | 4 - src/builtin_proto.h | 2 + src/builtins.c | 230 ++++++++++++++++++++++++++++++++ src/jl_exported_funcs.inc | 1 - src/julia-syntax.scm | 52 +++++++- src/julia.h | 5 +- src/julia_internal.h | 2 - src/module.c | 22 +--- src/staticdata.c | 1 + src/toplevel.c | 268 +------------------------------------- 10 files changed, 284 insertions(+), 303 deletions(-) diff --git a/src/ast.c b/src/ast.c index ab6b04efa526a..cfad2e8963a07 100644 --- a/src/ast.c +++ b/src/ast.c @@ -30,7 +30,6 @@ JL_DLLEXPORT jl_sym_t *jl_module_sym; JL_DLLEXPORT jl_sym_t *jl_slot_sym; JL_DLLEXPORT jl_sym_t *jl_export_sym; JL_DLLEXPORT jl_sym_t *jl_public_sym; -JL_DLLEXPORT jl_sym_t *jl_import_sym; JL_DLLEXPORT jl_sym_t *jl_toplevel_sym; JL_DLLEXPORT jl_sym_t *jl_quote_sym; JL_DLLEXPORT jl_sym_t *jl_line_sym; @@ -51,7 +50,6 @@ JL_DLLEXPORT jl_sym_t *jl_pop_exception_sym; JL_DLLEXPORT jl_sym_t *jl_exc_sym; JL_DLLEXPORT jl_sym_t *jl_error_sym; JL_DLLEXPORT jl_sym_t *jl_new_sym; -JL_DLLEXPORT jl_sym_t *jl_using_sym; JL_DLLEXPORT jl_sym_t *jl_splatnew_sym; JL_DLLEXPORT jl_sym_t *jl_block_sym; JL_DLLEXPORT jl_sym_t *jl_new_opaque_closure_sym; @@ -349,8 +347,6 @@ void jl_init_common_symbols(void) jl_module_sym = jl_symbol("module"); jl_export_sym = jl_symbol("export"); jl_public_sym = jl_symbol("public"); - jl_import_sym = jl_symbol("import"); - jl_using_sym = jl_symbol("using"); jl_assign_sym = jl_symbol("="); jl_method_sym = jl_symbol("method"); jl_exc_sym = jl_symbol("the_exception"); diff --git a/src/builtin_proto.h b/src/builtin_proto.h index c82ec77414129..a30f77210dda1 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -84,6 +84,8 @@ JL_CALLABLE(jl_f__equiv_typedef); JL_CALLABLE(jl_f_get_binding_type); JL_CALLABLE(jl_f__compute_sparams); JL_CALLABLE(jl_f__svec_ref); +JL_CALLABLE(jl_f__module_import); +JL_CALLABLE(jl_f__module_using); #ifdef __cplusplus } #endif diff --git a/src/builtins.c b/src/builtins.c index 243d14f34d3bd..db5290bd4c1b4 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1482,7 +1482,234 @@ JL_CALLABLE(jl_f_setglobalonce) return old == NULL ? jl_true : jl_false; } +// import, using -------------------------------------------------------------- +extern size_t jl_require_world; +static jl_module_t *call_require(jl_task_t *ct, jl_module_t *mod, jl_sym_t *var) JL_GLOBALLY_ROOTED +{ + JL_TIMING(LOAD_IMAGE, LOAD_Require); + jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, "%s", jl_symbol_name(var)); + + int build_mode = jl_options.incremental && jl_generating_output(); + jl_module_t *m = NULL; + static jl_value_t *require_func = NULL; + if (require_func == NULL && jl_base_module != NULL) { + require_func = jl_get_global(jl_base_module, jl_symbol("require")); + } + if (require_func != NULL) { + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + if (build_mode && jl_require_world < ct->world_age) + ct->world_age = jl_require_world; + jl_value_t *reqargs[3]; + reqargs[0] = require_func; + reqargs[1] = (jl_value_t*)mod; + reqargs[2] = (jl_value_t*)var; + m = (jl_module_t*)jl_apply(reqargs, 3); + } + if (m == NULL || !jl_is_module(m)) { + jl_errorf("failed to load module %s", jl_symbol_name(var)); + } + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + return m; +} + +// either: +// - sets *name and returns the module to import *name from +// - sets *name to NULL and returns a module to import +// also updates world_age +static jl_module_t *eval_import_path(jl_task_t *ct, jl_module_t *where, jl_module_t *from JL_PROPAGATES_ROOT, + jl_array_t *args, jl_sym_t **name, const char *keyword) JL_GLOBALLY_ROOTED +{ + if (jl_array_nrows(args) == 0) + jl_errorf("malformed \"%s\" statement", keyword); + jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0); + size_t i = 1; + jl_module_t *m = NULL; + *name = NULL; + if (!jl_is_symbol(var)) + jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); + + if (from != NULL) { + m = from; + i = 0; + } + else if (var != jl_dot_sym) { + // `A.B`: call the loader to obtain the root A in the current environment. + if (jl_core_module && var == jl_core_module->name) { + m = jl_core_module; + } + else if (jl_base_module && var == jl_base_module->name) { + m = jl_base_module; + } + else { + m = call_require(ct, where, var); + } + if (i == jl_array_nrows(args)) + return m; + } + else { + // `.A.B.C`: strip off leading dots by following parent links + m = where; + while (1) { + if (i >= jl_array_nrows(args)) + jl_error("invalid module path"); + var = (jl_sym_t*)jl_array_ptr_ref(args, i); + if (var != jl_dot_sym) + break; + i++; + assert(m); + m = m->parent; + } + } + + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + + while (1) { + var = (jl_sym_t*)jl_array_ptr_ref(args, i); + if (!jl_is_symbol(var)) + jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); + if (var == jl_dot_sym) + jl_errorf("invalid %s path: \".\" in identifier path", keyword); + if (i == jl_array_nrows(args)-1) + break; + m = (jl_module_t*)jl_eval_global_var(m, var); + JL_GC_PROMISE_ROOTED(m); + if (!jl_is_module(m)) + jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(var)); + i++; + } + *name = var; + return m; +} + +static jl_module_t *eval_import_path_all(jl_task_t *ct, jl_module_t *where, jl_array_t *args, const char *keyword) +{ + jl_sym_t *name = NULL; + jl_module_t *from = eval_import_path(ct, where, NULL, args, &name, keyword); + if (name) { + from = (jl_module_t *)jl_eval_global_var(from, name); + if (!jl_is_module(from)) + jl_errorf("invalid using path: \"%s\" does not name a module", jl_symbol_name(name)); + } + return from; +} + +static void import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym_t *asname) +{ + assert(m); + jl_sym_t *name = asname ? asname : import->name; + // TODO: this is a bit race-y with what error message we might print + jl_binding_t *b = jl_get_module_binding(m, name, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); + enum jl_partition_kind kind = jl_binding_kind(bpart); + if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) { + // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. + jl_walk_binding_inplace(&b, &bpart, ct->world_age); + if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { + // Already declared (e.g. on another thread) or imported. + if (bpart->restriction == (jl_value_t*)import) + return; + } + jl_errorf("importing %s into %s conflicts with an existing global", + jl_symbol_name(name), jl_symbol_name(m->name)); + } + jl_declare_constant_val2(b, m, name, (jl_value_t*)import, PARTITION_KIND_CONST_IMPORT); +} + +static void check_macro_rename(jl_sym_t *from, jl_sym_t *to, const char *keyword) +{ + char *n1 = jl_symbol_name(from), *n2 = jl_symbol_name(to); + if (n1[0] == '@' && n2[0] != '@') + jl_errorf("cannot rename macro \"%s\" to non-macro \"%s\" in \"%s\"", n1, n2, keyword); + if (n1[0] != '@' && n2[0] == '@') + jl_errorf("cannot rename non-macro \"%s\" to macro \"%s\" in \"%s\"", n1, n2, keyword); +} + +// _module_import(explicit::Bool, to::Module, ctx::Union{Expr, Nothing}, names::Expr...) +// 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)) +JL_CALLABLE(jl_f__module_import) +{ + JL_NARGSV(_module_import, 4); + JL_TYPECHK(_module_import, bool, args[0]); + JL_TYPECHK(_module_import, module, args[1]); + if (!(jl_is_expr(args[2]) || args[2] == jl_nothing)) + jl_type_error("_module_import", (jl_value_t *)jl_expr_type, args[2]); + for (int i = 3; i < nargs; i++) + JL_TYPECHK(_module_import, expr, args[i]); + + jl_value_t **roots; + JL_GC_PUSHARGS(roots, nargs - 2); + memcpy(roots, args + 2, (nargs - 2) * sizeof *roots); + + int explici = args[0] == jl_true; + jl_module_t *to = (jl_module_t *)args[1]; + const char *keyword = explici ? "import" : "using"; + jl_task_t *ct = jl_current_task; + jl_module_t *ctx = NULL; + if (args[2] != jl_nothing) + ctx = eval_import_path_all(ct, to, ((jl_expr_t *)args[2])->args, keyword); + + for (int i = 3; i < nargs; i++) { + jl_expr_t *ex = (jl_expr_t *)args[i]; + jl_sym_t *name, *asname = NULL; + + if (ex->head == jl_as_sym && jl_expr_nargs(ex) == 2) { + asname = (jl_sym_t *)jl_exprarg(ex, 1); + ex = (jl_expr_t *)jl_exprarg(ex, 0); + if (!jl_is_expr(ex) || ex->head != jl_dot_sym) + goto malformed_err; + } + else if (ex->head == jl_dot_sym) { + } + else { + goto malformed_err; + } + jl_module_t *from = eval_import_path(ct, to, ctx, ex->args, &name, keyword); + if (!asname) + asname = name; + + if (name) { + check_macro_rename(name, asname, keyword); + jl_module_import(ct, to, from, asname, name, explici); + } + else { + import_module(ct, to, from, asname); + } + } + + JL_GC_POP(); + return jl_nothing; + +malformed_err: + jl_errorf("malformed \"%s\" statement", keyword); +} + +// _module_using(to::Module, from::Expr{Symbol}) +// using A.B => _module_using(Main, Expr(:., :A, :B)) +JL_CALLABLE(jl_f__module_using) +{ + JL_NARGS(_module_using, 2, 2); + jl_module_t *to = (jl_module_t *)args[0]; + jl_expr_t *ex = (jl_expr_t *)args[1]; + + jl_task_t *ct = jl_current_task; + jl_module_t *from = eval_import_path_all(ct, to, ex->args, "using"); + + jl_module_using(to, from); + int is_package = jl_expr_nargs(ex) == 2 && (jl_sym_t *)jl_exprarg(ex, 1) != jl_dot_sym; + if (to == jl_main_module && is_package) { + // TODO: for now, `using A` in Main also creates an explicit binding for `A` + // This will possibly be extended to all modules. + import_module(ct, to, from, NULL); + } + + return jl_nothing; +} // apply_type ----------------------------------------------------------------- @@ -2488,6 +2715,9 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_modifyglobal = add_builtin_func("modifyglobal!", jl_f_modifyglobal); jl_builtin_setglobalonce = add_builtin_func("setglobalonce!", jl_f_setglobalonce); + add_builtin_func("_module_import", jl_f__module_import); + add_builtin_func("_module_using", jl_f__module_using); + // memory primitives jl_builtin_memorynew = add_builtin_func("memorynew", jl_f_memorynew); jl_builtin_memoryref = add_builtin_func("memoryrefnew", jl_f_memoryref); diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index e4fd664478123..3463588866850 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -317,7 +317,6 @@ XX(jl_module_getloc) \ XX(jl_module_public) \ XX(jl_module_public_p) \ - XX(jl_module_use) \ XX(jl_module_using) \ XX(jl_module_usings) \ XX(jl_module_uuid) \ diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 0b6f56ac838a6..d6b6eeb904efe 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2519,7 +2519,7 @@ `(= ,lhs ,rhs))) (define (expand-forms e) - (if (or (atom? e) (memq (car e) '(quote inert top core globalref module toplevel ssavalue null true false meta using import export public thismodule toplevel-only))) + (if (or (atom? e) (memq (car e) '(quote inert top core globalref module toplevel ssavalue null true false meta export public thismodule toplevel-only))) e (let ((ex (get expand-table (car e) #f))) (if ex @@ -2539,6 +2539,20 @@ (define (something e) (find (lambda (x) (not (equal? x '(null)))) e)) +(define (check-import-paths what e) + (define (check-dot-path e) + (and (list? e) (eq? (car e) '|.|) (every symbol? (cdr e)))) + (define (check-path e) + (and (pair? e) + (or (check-dot-path e) + (and (eq? (car e) 'as) + (check-dot-path (cadr e)) (symbol? (caddr e)))))) + (unless (and (list? e) + (or (every check-path e) + (and (list? (car e)) (eq? (caar e) ':) + (every check-path (cdar e))))) + (error (string "malformed \"" what "\" statement")))) + ;; table mapping expression head to a function expanding that form (define expand-table (table @@ -2937,6 +2951,32 @@ (lambda (e) (set! *current-desugar-loc* e) e) + + 'import + (lambda (e) + (check-import-paths "import" (cdr e)) + `(block + (toplevel-only import) + ,.(if (eq? (caadr e) ':) + `((call (core _module_import) (true) (thismodule) + ,.(map (lambda (x) `(inert ,x)) (cdadr e)))) + (map (lambda (x) + `(call (core _module_import) (true) (thismodule) (null) (inert ,x))) + (cdr e))) + (latestworld))) + + 'using + (lambda (e) + (check-import-paths "using" (cdr e)) + `(block + (toplevel-only using) + ,.(if (eq? (caadr e) ':) + `((call (core _module_import) (false) (thismodule) + ,.(map (lambda (x) `(inert ,x)) (cdadr e)))) + (map (lambda (x) + `(call (core _module_using) (thismodule) (inert ,x))) + (cdr e))) + (latestworld))) )) (define (has-return? e) @@ -3181,7 +3221,7 @@ (check-valid-name (cadr e)) ;; remove local decls '(null)) - ((memq (car e) '(using import export public)) + ((memq (car e) '(export public)) ;; no scope resolution - identifiers remain raw symbols e) ((eq? (car e) 'require-existing-local) @@ -3813,7 +3853,7 @@ f(x) = yt(x) thunk with-static-parameters toplevel-only global globalref global-if-global assign-const-if-global isglobal thismodule const atomic null true false ssavalue isdefined toplevel module lambda - error gc_preserve_begin gc_preserve_end import using export public inline noinline purity))) + error gc_preserve_begin gc_preserve_end export public inline noinline purity))) (define (local-in? s lam (tab #f)) (or (and tab (has? tab s)) @@ -4045,7 +4085,7 @@ f(x) = yt(x) ((atom? e) e) (else (case (car e) - ((quote top core global globalref thismodule lineinfo line break inert module toplevel null true false meta import using) e) + ((quote top core global globalref thismodule lineinfo line break inert module toplevel null true false meta) e) ((toplevel-only) ;; hack to avoid generating a (method x) expr for struct types (if (eq? (cadr e) 'struct) @@ -5038,7 +5078,7 @@ f(x) = yt(x) '(null)) ;; other top level expressions - ((import using export public latestworld) + ((export public latestworld) (check-top-level e) (if (not (eq? (car e) 'latestworld)) (emit e)) @@ -5281,7 +5321,7 @@ f(x) = yt(x) ((nospecialize-meta? e) ;; convert nospecialize vars to slot numbers `(meta ,(cadr e) ,@(map renumber-stuff (cddr e)))) - ((or (atom? e) (quoted? e) (memq (car e) '(using import export public global toplevel))) + ((or (atom? e) (quoted? e) (memq (car e) '(export public global toplevel))) e) ((ssavalue? e) (let ((idx (get ssavalue-table (cadr e) #f))) diff --git a/src/julia.h b/src/julia.h index 66458d997b666..b6a26970b6642 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2107,11 +2107,8 @@ JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind); +JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); -JL_DLLEXPORT void jl_module_use(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s); -JL_DLLEXPORT void jl_module_use_as(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); -JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s); -JL_DLLEXPORT void jl_module_import_as(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported); JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s); JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var); diff --git a/src/julia_internal.h b/src/julia_internal.h index cf9ed6f08f4af..12edd3798c168 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1849,7 +1849,6 @@ extern JL_DLLEXPORT jl_sym_t *jl_module_sym; extern JL_DLLEXPORT jl_sym_t *jl_slot_sym; extern JL_DLLEXPORT jl_sym_t *jl_export_sym; extern JL_DLLEXPORT jl_sym_t *jl_public_sym; -extern JL_DLLEXPORT jl_sym_t *jl_import_sym; extern JL_DLLEXPORT jl_sym_t *jl_toplevel_sym; extern JL_DLLEXPORT jl_sym_t *jl_quote_sym; extern JL_DLLEXPORT jl_sym_t *jl_line_sym; @@ -1871,7 +1870,6 @@ extern JL_DLLEXPORT jl_sym_t *jl_pop_exception_sym; extern JL_DLLEXPORT jl_sym_t *jl_exc_sym; extern JL_DLLEXPORT jl_sym_t *jl_error_sym; extern JL_DLLEXPORT jl_sym_t *jl_new_sym; -extern JL_DLLEXPORT jl_sym_t *jl_using_sym; extern JL_DLLEXPORT jl_sym_t *jl_splatnew_sym; extern JL_DLLEXPORT jl_sym_t *jl_block_sym; extern JL_DLLEXPORT jl_sym_t *jl_new_opaque_closure_sym; diff --git a/src/module.c b/src/module.c index 07b79b707f024..b6ec6345801d3 100644 --- a/src/module.c +++ b/src/module.c @@ -1123,7 +1123,7 @@ static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_ } // NOTE: we use explici since explicit is a C++ keyword -static void module_import_(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici) +JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici) { check_safe_import_from(from); jl_binding_t *b = jl_get_binding(from, s); @@ -1200,26 +1200,6 @@ static void module_import_(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl JL_UNLOCK(&world_counter_lock); } -JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s) -{ - module_import_(ct, to, from, s, s, 1); -} - -JL_DLLEXPORT void jl_module_import_as(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) -{ - module_import_(ct, to, from, asname, s, 1); -} - -JL_DLLEXPORT void jl_module_use(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s) -{ - module_import_(ct, to, from, s, s, 0); -} - -JL_DLLEXPORT void jl_module_use_as(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname) -{ - module_import_(ct, to, from, asname, s, 0); -} - void jl_add_usings_backedge(jl_module_t *from, jl_module_t *to) { JL_LOCK(&from->lock); diff --git a/src/staticdata.c b/src/staticdata.c index b007fc04eeb4b..5ad9b5bf5363b 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -534,6 +534,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f__defaultctors, &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, &jl_f_get_binding_type, &jl_f_getglobal, &jl_f_setglobal, &jl_f_swapglobal, &jl_f_modifyglobal, &jl_f_replaceglobal, &jl_f_setglobalonce, + &jl_f__module_import, &jl_f__module_using, &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, &jl_f_current_scope, NULL }; diff --git a/src/toplevel.c b/src/toplevel.c index 174bb0ef4dd22..eca8b7822c8ef 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -487,110 +487,10 @@ static void body_attributes(jl_array_t *body, int *has_ccall, int *has_defs, int *forced_compile = jl_has_meta(body, jl_force_compile_sym); } -extern size_t jl_require_world; -static jl_module_t *call_require(jl_task_t *ct, jl_module_t *mod, jl_sym_t *var) JL_GLOBALLY_ROOTED -{ - JL_TIMING(LOAD_IMAGE, LOAD_Require); - jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, "%s", jl_symbol_name(var)); - - int build_mode = jl_options.incremental && jl_generating_output(); - jl_module_t *m = NULL; - static jl_value_t *require_func = NULL; - if (require_func == NULL && jl_base_module != NULL) { - require_func = jl_get_global(jl_base_module, jl_symbol("require")); - } - if (require_func != NULL) { - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - if (build_mode && jl_require_world < ct->world_age) - ct->world_age = jl_require_world; - jl_value_t *reqargs[3]; - reqargs[0] = require_func; - reqargs[1] = (jl_value_t*)mod; - reqargs[2] = (jl_value_t*)var; - m = (jl_module_t*)jl_apply(reqargs, 3); - } - if (m == NULL || !jl_is_module(m)) { - jl_errorf("failed to load module %s", jl_symbol_name(var)); - } - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - return m; -} - -// either: -// - sets *name and returns the module to import *name from -// - sets *name to NULL and returns a module to import -// also updates world_age -static jl_module_t *eval_import_path(jl_task_t *ct, jl_module_t *where, jl_module_t *from JL_PROPAGATES_ROOT, - jl_array_t *args, jl_sym_t **name, const char *keyword) JL_GLOBALLY_ROOTED -{ - if (jl_array_nrows(args) == 0) - jl_errorf("malformed \"%s\" statement", keyword); - jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0); - size_t i = 1; - jl_module_t *m = NULL; - *name = NULL; - if (!jl_is_symbol(var)) - jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); - - if (from != NULL) { - m = from; - i = 0; - } - else if (var != jl_dot_sym) { - // `A.B`: call the loader to obtain the root A in the current environment. - if (jl_core_module && var == jl_core_module->name) { - m = jl_core_module; - } - else if (jl_base_module && var == jl_base_module->name) { - m = jl_base_module; - } - else { - m = call_require(ct, where, var); - } - if (i == jl_array_nrows(args)) - return m; - } - else { - // `.A.B.C`: strip off leading dots by following parent links - m = where; - while (1) { - if (i >= jl_array_nrows(args)) - jl_error("invalid module path"); - var = (jl_sym_t*)jl_array_ptr_ref(args, i); - if (var != jl_dot_sym) - break; - i++; - assert(m); - m = m->parent; - } - } - - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - - while (1) { - var = (jl_sym_t*)jl_array_ptr_ref(args, i); - if (!jl_is_symbol(var)) - jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); - if (var == jl_dot_sym) - jl_errorf("invalid %s path: \".\" in identifier path", keyword); - if (i == jl_array_nrows(args)-1) - break; - m = (jl_module_t*)jl_eval_global_var(m, var); - JL_GC_PROMISE_ROOTED(m); - if (!jl_is_module(m)) - jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(var)); - i++; - } - *name = var; - return m; -} - int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT { return jl_is_expr(e) && (((jl_expr_t*)e)->head == jl_module_sym || - ((jl_expr_t*)e)->head == jl_import_sym || - ((jl_expr_t*)e)->head == jl_using_sym || ((jl_expr_t*)e)->head == jl_export_sym || ((jl_expr_t*)e)->head == jl_public_sym || ((jl_expr_t*)e)->head == jl_thunk_sym || @@ -608,10 +508,9 @@ int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT return 0; jl_expr_t *ex = (jl_expr_t*)e; jl_sym_t *head = ex->head; - if (head == jl_module_sym || head == jl_import_sym || head == jl_using_sym || - head == jl_export_sym || head == jl_public_sym || head == jl_thunk_sym || - head == jl_toplevel_sym || head == jl_error_sym || head == jl_incomplete_sym || - head == jl_method_sym) { + if (head == jl_module_sym || head == jl_export_sym || head == jl_public_sym || + head == jl_thunk_sym || head == jl_toplevel_sym || head == jl_error_sym || + head == jl_incomplete_sym || head == jl_method_sym) { return 0; } if (head == jl_global_sym || head == jl_const_sym) { @@ -651,62 +550,6 @@ JL_DLLEXPORT jl_method_instance_t *jl_method_instance_for_thunk(jl_code_info_t * return mi; } -static void import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym_t *asname) -{ - assert(m); - jl_sym_t *name = asname ? asname : import->name; - // TODO: this is a bit race-y with what error message we might print - jl_binding_t *b = jl_get_module_binding(m, name, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); - enum jl_partition_kind kind = jl_binding_kind(bpart); - if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) { - // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. - jl_walk_binding_inplace(&b, &bpart, ct->world_age); - if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { - // Already declared (e.g. on another thread) or imported. - if (bpart->restriction == (jl_value_t*)import) - return; - } - jl_errorf("importing %s into %s conflicts with an existing global", - jl_symbol_name(name), jl_symbol_name(m->name)); - } - jl_declare_constant_val2(b, m, name, (jl_value_t*)import, PARTITION_KIND_CONST_IMPORT); -} - -// in `import A.B: x, y, ...`, evaluate the `A.B` part if it exists -static jl_module_t *eval_import_from(jl_task_t *ct, jl_module_t *m JL_PROPAGATES_ROOT, jl_expr_t *ex, const char *keyword) -{ - if (jl_expr_nargs(ex) == 1 && jl_is_expr(jl_exprarg(ex, 0))) { - jl_expr_t *fr = (jl_expr_t*)jl_exprarg(ex, 0); - if (fr->head == jl_colon_sym) { - if (jl_expr_nargs(fr) > 0 && jl_is_expr(jl_exprarg(fr, 0))) { - jl_expr_t *path = (jl_expr_t*)jl_exprarg(fr, 0); - if (((jl_expr_t*)path)->head == jl_dot_sym) { - jl_sym_t *name = NULL; - jl_module_t *from = eval_import_path(ct, m, NULL, path->args, &name, keyword); - if (name != NULL) { - from = (jl_module_t*)jl_eval_global_var(from, name); - if (!from || !jl_is_module(from)) - jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(name)); - } - return from; - } - } - jl_errorf("malformed \"%s:\" statement", keyword); - } - } - return NULL; -} - -static void check_macro_rename(jl_sym_t *from, jl_sym_t *to, const char *keyword) -{ - char *n1 = jl_symbol_name(from), *n2 = jl_symbol_name(to); - if (n1[0] == '@' && n2[0] != '@') - jl_errorf("cannot rename macro \"%s\" to non-macro \"%s\" in \"%s\"", n1, n2, keyword); - if (n1[0] != '@' && n2[0] == '@') - jl_errorf("cannot rename non-macro \"%s\" to macro \"%s\" in \"%s\"", n1, n2, keyword); -} - // Eval `throw(ErrorException(msg)))` in module `m`. // Used in `jl_toplevel_eval_flex` instead of `jl_throw` so that the error // location in julia code gets into the backtrace. @@ -827,111 +670,6 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val JL_GC_POP(); return val; } - else if (head == jl_using_sym) { - jl_sym_t *name = NULL; - jl_module_t *from = eval_import_from(ct, m, ex, "using"); - size_t i = 0; - if (from) { - i = 1; - ex = (jl_expr_t*)jl_exprarg(ex, 0); - } - for (; i < jl_expr_nargs(ex); i++) { - jl_value_t *a = jl_exprarg(ex, i); - if (jl_is_expr(a) && ((jl_expr_t*)a)->head == jl_dot_sym) { - name = NULL; - jl_module_t *import = eval_import_path(ct, m, from, ((jl_expr_t*)a)->args, &name, "using"); - if (from) { - // `using A: B` and `using A: B.c` syntax - jl_module_use(ct, m, import, name); - } - else { - jl_module_t *u = import; - if (name != NULL) - u = (jl_module_t*)jl_eval_global_var(import, name); - if (!jl_is_module(u)) - jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, - "invalid using path: \"%s\" does not name a module", - jl_symbol_name(name)); - // `using A` and `using A.B` syntax - jl_module_using(m, u); - if (m == jl_main_module && name == NULL) { - // TODO: for now, `using A` in Main also creates an explicit binding for `A` - // This will possibly be extended to all modules. - import_module(ct, m, u, NULL); - } - } - continue; - } - else if (from && jl_is_expr(a) && ((jl_expr_t*)a)->head == jl_as_sym && jl_expr_nargs(a) == 2 && - jl_is_expr(jl_exprarg(a, 0)) && ((jl_expr_t*)jl_exprarg(a, 0))->head == jl_dot_sym) { - jl_sym_t *asname = (jl_sym_t*)jl_exprarg(a, 1); - if (jl_is_symbol(asname)) { - jl_expr_t *path = (jl_expr_t*)jl_exprarg(a, 0); - name = NULL; - jl_module_t *import = eval_import_path(ct, m, from, ((jl_expr_t*)path)->args, &name, "using"); - assert(name); - check_macro_rename(name, asname, "using"); - // `using A: B as C` syntax - jl_module_use_as(ct, m, import, name, asname); - continue; - } - } - jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, - "syntax: malformed \"using\" statement"); - } - JL_GC_POP(); - ct->world_age = last_age; - return jl_nothing; - } - else if (head == jl_import_sym) { - jl_sym_t *name = NULL; - jl_module_t *from = eval_import_from(ct, m, ex, "import"); - size_t i = 0; - if (from) { - i = 1; - ex = (jl_expr_t*)jl_exprarg(ex, 0); - } - for (; i < jl_expr_nargs(ex); i++) { - jl_value_t *a = jl_exprarg(ex, i); - if (jl_is_expr(a) && ((jl_expr_t*)a)->head == jl_dot_sym) { - name = NULL; - jl_module_t *import = eval_import_path(ct, m, from, ((jl_expr_t*)a)->args, &name, "import"); - if (name == NULL) { - // `import A` syntax - import_module(ct, m, import, NULL); - } - else { - // `import A.B` or `import A: B` syntax - jl_module_import(ct, m, import, name); - } - continue; - } - else if (jl_is_expr(a) && ((jl_expr_t*)a)->head == jl_as_sym && jl_expr_nargs(a) == 2 && - jl_is_expr(jl_exprarg(a, 0)) && ((jl_expr_t*)jl_exprarg(a, 0))->head == jl_dot_sym) { - jl_sym_t *asname = (jl_sym_t*)jl_exprarg(a, 1); - if (jl_is_symbol(asname)) { - jl_expr_t *path = (jl_expr_t*)jl_exprarg(a, 0); - name = NULL; - jl_module_t *import = eval_import_path(ct, m, from, ((jl_expr_t*)path)->args, &name, "import"); - if (name == NULL) { - // `import A as B` syntax - import_module(ct, m, import, asname); - } - else { - check_macro_rename(name, asname, "import"); - // `import A.B as C` syntax - jl_module_import_as(ct, m, import, name, asname); - } - continue; - } - } - jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, - "syntax: malformed \"import\" statement"); - } - JL_GC_POP(); - ct->world_age = last_age; - return jl_nothing; - } else if (head == jl_export_sym || head == jl_public_sym) { int exp = (head == jl_export_sym); for (size_t i = 0; i < jl_array_nrows(ex->args); i++) { From 1d3f5c402ea1fd2a8b94e31e9275ea6f10ad5825 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Thu, 3 Apr 2025 16:21:20 -0700 Subject: [PATCH 02/11] Fix creating const bindings for `using A` in Main --- src/builtins.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins.c b/src/builtins.c index db5290bd4c1b4..49766bd4bc8a1 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1701,7 +1701,7 @@ JL_CALLABLE(jl_f__module_using) jl_module_t *from = eval_import_path_all(ct, to, ex->args, "using"); jl_module_using(to, from); - int is_package = jl_expr_nargs(ex) == 2 && (jl_sym_t *)jl_exprarg(ex, 1) != jl_dot_sym; + int is_package = jl_expr_nargs(ex) == 1 && (jl_sym_t *)jl_exprarg(ex, 0) != jl_dot_sym; if (to == jl_main_module && is_package) { // TODO: for now, `using A` in Main also creates an explicit binding for `A` // This will possibly be extended to all modules. From 8e7a8478fa0e400e6eaa43bb249fa805c3196c54 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Mon, 7 Apr 2025 16:00:46 -0400 Subject: [PATCH 03/11] Fix REPL modules_to_be_loaded to detect import/using builtins --- stdlib/REPL/src/REPL.jl | 33 +++++++++++++----------- stdlib/REPL/test/repl.jl | 54 +++++++++++++++++++++------------------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index ae2fec4ee10e0..4966e221eca4e 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -358,26 +358,31 @@ function check_for_missing_packages_and_run_hooks(ast) end function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) + function add!(ctx) + if ctx.head == :as + ctx = ctx.args[1] + end + if ctx.args[1] != :. # don't include local import `import .Foo` + push!(mods, ctx.args[1]) + end + end ast.head === :quote && return mods # don't search if it's not going to be run during this eval - if ast.head === :using || ast.head === :import - for arg in ast.args - arg = arg::Expr - arg1 = first(arg.args) - if arg1 isa Symbol # i.e. `Foo` - if arg1 != :. # don't include local import `import .Foo` - push!(mods, arg1) - end - else # i.e. `Foo: bar` - sym = first((arg1::Expr).args)::Symbol - if sym != :. # don't include local import `import .Foo: a` - push!(mods, sym) - end + if ast.head == :call + if ast.args[1] == GlobalRef(Core, :_module_import) + ctx = ast.args[4] + if ctx isa QuoteNode # i.e. `Foo: bar` + ctx = ctx.value + else + ctx = ast.args[5].value end + add!(ctx) + elseif ast.args[1] == GlobalRef(Core, :_module_using) + add!(ast.args[3].value) end end if ast.head !== :thunk for arg in ast.args - if isexpr(arg, (:block, :if, :using, :import)) + if isexpr(arg, (:block, :if)) _modules_to_be_loaded!(arg, mods) end end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index ee787e55d78c8..b2172096a2e6b 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1550,59 +1550,61 @@ end @testset "Install missing packages via hooks" begin @testset "Parse AST for packages" begin - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Foo")) + test_find_packages(e) = + REPL.modules_to_be_loaded(Meta.lower(@__MODULE__, e)) + test_find_packages(s::String) = + REPL.modules_to_be_loaded(Meta.lower(@__MODULE__, Meta.parse(s))) + + mods = test_find_packages("using Foo") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("import Foo")) + mods = test_find_packages("import Foo") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Foo, Bar")) + mods = test_find_packages("using Foo, Bar") @test mods == [:Foo, :Bar] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("import Foo, Bar")) + mods = test_find_packages("import Foo, Bar") @test mods == [:Foo, :Bar] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Foo.bar, Foo.baz")) + mods = test_find_packages("using Foo.bar, Foo.baz") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("if false using Foo end")) + mods = test_find_packages("if false using Foo end") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("if false if false using Foo end end")) + mods = test_find_packages("if false if false using Foo end end") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("if false using Foo, Bar end")) + mods = test_find_packages("if false using Foo, Bar end") @test mods == [:Foo, :Bar] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("if false using Foo: bar end")) + mods = test_find_packages("if false using Foo: bar end") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("import Foo.bar as baz")) + mods = test_find_packages("import Foo.bar as baz") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using .Foo")) + mods = test_find_packages("using .Foo") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Base")) + mods = test_find_packages("using Base") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Base: nope")) + mods = test_find_packages("using Base: nope") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Main")) + mods = test_find_packages("using Main") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("using Core")) + mods = test_find_packages("using Core") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line(":(using Foo)")) - @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("ex = :(using Foo)")) + mods = test_find_packages(":(using Foo)") @test isempty(mods) - - mods = REPL.modules_to_be_loaded(Base.parse_input_line("Foo")) + mods = test_find_packages("ex = :(using Foo)") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("@eval using Foo")) + mods = test_find_packages("@eval using Foo") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("begin using Foo; @eval using Bar end")) + mods = test_find_packages("begin using Foo; @eval using Bar end") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(Base.parse_input_line("Core.eval(Main,\"using Foo\")")) + mods = test_find_packages("Core.eval(Main,\"using Foo\")") @test isempty(mods) - mods = REPL.modules_to_be_loaded(Base.parse_input_line("begin using Foo; Core.eval(Main,\"using Foo\") end")) + mods = test_find_packages("begin using Foo; Core.eval(Main,\"using Foo\") end") @test mods == [:Foo] - mods = REPL.modules_to_be_loaded(:(import .Foo: a)) + mods = test_find_packages(:(import .Foo: a)) @test isempty(mods) - mods = REPL.modules_to_be_loaded(:(using .Foo: a)) + mods = test_find_packages(:(using .Foo: a)) @test isempty(mods) end end From c63839802270841cf8b96f0fcfe7c18f33ecf352 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Wed, 16 Apr 2025 12:11:38 -0700 Subject: [PATCH 04/11] Move import/using logic to Julia; add low-level Core._import, _using 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. --- base/Base_compiler.jl | 1 + base/boot.jl | 48 ++++++++ base/c.jl | 2 +- base/checked.jl | 5 +- base/docs/basedocs.jl | 19 +++ base/iterators.jl | 12 +- base/module.jl | 156 +++++++++++++++++++++++++ base/ordering.jl | 2 +- src/builtin_proto.h | 4 +- src/builtins.c | 250 +++++----------------------------------- src/julia-syntax.scm | 26 +++-- src/julia.h | 1 + src/module.c | 22 ++++ src/staticdata.c | 2 +- stdlib/REPL/src/REPL.jl | 4 +- 15 files changed, 310 insertions(+), 244 deletions(-) create mode 100644 base/module.jl diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index e22a7d980f06c..2bbd32565246d 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -340,6 +340,7 @@ include("ordering.jl") using .Order include("coreir.jl") +include("module.jl") include("invalidation.jl") BUILDROOT::String = "" diff --git a/base/boot.jl b/base/boot.jl index f9208c3874229..566839d430e93 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -695,6 +695,54 @@ function Symbol(a::Array{UInt8, 1}) end Symbol(s::Symbol) = s +# Minimal implementations of using/import for bootstrapping (supports only +# `import .M: a, b, c, ...`, little error checking) +let + fail() = throw(ArgumentError("unsupported import/using while bootstrapping")) + length(a::Array{T, 1}) where {T} = getfield(getfield(a, :size), 1) + function getindex(A::Array, i::Int) + Intrinsics.ult_int(Intrinsics.bitcast(UInt, Intrinsics.sub_int(i, 1)), Intrinsics.bitcast(UInt, length(A))) || fail() + memoryrefget(memoryrefnew(getfield(A, :ref), i, false), :not_atomic, false) + end + x == y = Intrinsics.eq_int(x, y) + x + y = Intrinsics.add_int(x, y) + x <= y = Intrinsics.sle_int(x, y) + + global function _eval_import(explicit::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...) + from isa Expr || fail() + if length(from.args) == 2 && getindex(from.args, 1) === :. + from = getglobal(to, getindex(from.args, 2)) + elseif length(from.args) == 1 && getindex(from.args, 1) === :Core + from = Core + elseif length(from.args) == 1 && getindex(from.args, 1) === :Base + from = Main.Base + else + fail() + end + from isa Module || fail() + i = 1 + while i <= nfields(paths) + a = getfield(paths, i).args + length(a) == 1 || fail() + s = getindex(a, 1) + Core._import(to, from, s, s, explicit) + i += 1 + end + end + + global function _eval_using(to::Module, path::Expr) + getindex(path.args, 1) === :. || fail() + from = getglobal(to, getindex(path.args, 2)) + i = 3 + while i <= length(path.args) + from = getfield(from, getindex(path.args, i)) + i += 1 + end + from isa Module || fail() + Core._using(to, from) + end +end + # module providing the IR object model module IR diff --git a/base/c.jl b/base/c.jl index 78c48f267ca71..69ea3adf24404 100644 --- a/base/c.jl +++ b/base/c.jl @@ -2,7 +2,7 @@ # definitions related to C interface -import Core.Intrinsics: cglobal +import .Intrinsics: cglobal """ cglobal((symbol, library) [, type=Cvoid]) diff --git a/base/checked.jl b/base/checked.jl index b374d34830280..39d487cba6e37 100644 --- a/base/checked.jl +++ b/base/checked.jl @@ -16,12 +16,13 @@ export checked_neg, checked_abs, checked_add, checked_sub, checked_mul, checked_div, checked_rem, checked_fld, checked_mod, checked_cld, checked_pow, checked_length, add_with_overflow, sub_with_overflow, mul_with_overflow -import Core.Intrinsics: +import Core: Intrinsics +import .Intrinsics: checked_sadd_int, checked_ssub_int, checked_smul_int, checked_sdiv_int, checked_srem_int, checked_uadd_int, checked_usub_int, checked_umul_int, checked_udiv_int, checked_urem_int -import ..no_op_err, ..@inline, ..@noinline, ..checked_length +import Base: no_op_err, @inline, @noinline, checked_length # define promotion behavior for checked operations checked_add(x::Integer, y::Integer) = checked_add(promote(x,y)...) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index cf8a087b76489..60770c42bae7d 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -2753,6 +2753,25 @@ See also [`setpropertyonce!`](@ref Base.setpropertyonce!) and [`setglobal!`](@re """ setglobalonce! +""" + _import(to::Module, from::Module, asname::Symbol, [sym::Symbol, imported::Bool]) + +With all five arguments, imports `sym` from module `from` into `to` with name +`asname`. `imported` is true for bindings created with `import` (set it to +false for `using A: ...`). + +With only the first three arguments, creates a binding for the module `from` +with name `asname` in `to`. +""" +Core._import + +""" + _using(to::Module, from::Module) + +Add `from` to the usings list of `to`. +""" +Core._using + """ typeof(x) diff --git a/base/iterators.jl b/base/iterators.jl index c7450781c4928..4b956073f0c04 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -6,7 +6,7 @@ Methods for working with Iterators. baremodule Iterators # small dance to make this work from Base or Intrinsics -import ..@__MODULE__, ..parentmodule +import Base: @__MODULE__, parentmodule const Base = parentmodule(@__MODULE__) using .Base: @inline, Pair, Pairs, AbstractDict, IndexLinear, IndexStyle, AbstractVector, Vector, @@ -17,14 +17,14 @@ using .Base: any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex, tail, fieldtypes, min, max, minimum, zero, oneunit, promote, promote_shape, LazyString, afoldl -using Core +using .Core using Core: @doc -using .Base: - cld, fld, resize!, IndexCartesian -using .Base.Checked: checked_mul +using Base: + cld, fld, resize!, IndexCartesian, Checked +using .Checked: checked_mul -import .Base: +import Base: first, last, isempty, length, size, axes, ndims, eltype, IteratorSize, IteratorEltype, promote_typejoin, diff --git a/base/module.jl b/base/module.jl new file mode 100644 index 0000000000000..67d8006a58f2b --- /dev/null +++ b/base/module.jl @@ -0,0 +1,156 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# Full-featured versions of _eval_import and _eval_using + +for m in methods(Core._eval_import) + delete_method(m) +end +for m in methods(Core._eval_using) + delete_method(m) +end + +function call_require(into::Module, name::Symbol) + # TODO: JL_TIMING(LOAD_IMAGE, LOAD_Require) + build_mode = Bool(Base.JLOptions().incremental) && Bool(ccall(:jl_generating_output, Cint, ())) + m = nothing + req_w = unsafe_load(cglobal(:jl_require_world, UInt)) + cur_w, tls_w = get_world_counter(), tls_world_age() + if isdefined(Base, :require) + w = build_mode && req_w < tls_w ? req_w : cur_w + m = invoke_in_world(w, getglobal(Base, :require), into, name) + end + m isa Module || error("failed to load module $v") + m +end + +function eval_import_path(at::Module, from::Union{Module, Nothing}, path::Expr, keyword::String) + isempty(path.args) && error("malformed import statement") + + i = 1 + function next!() + i <= length(path.args) || error("invalid module path") + v = path.args[i] + i += 1 + v isa Symbol || throw(TypeError(Symbol(keyword), "", Symbol, v)) + v + end + v = next!() + m = nothing + + if from !== nothing + m = from + elseif v !== :. + # `A.B`: call the loader to obtain the root A in the current environment. + if v === :Core + m = Core + elseif v === :Base + m = Base + else + m = call_require(at, v) + end + i > lastindex(path.args) && return m, nothing + v = next!() + else + # `.A.B.C`: strip off leading dots by following parent links + m = at + while (v = next!()) === :. + m = parentmodule(m) + end + end + + while true + v === :. && error("invalid $keyword path: \".\" in identifier path") + i > lastindex(path.args) && break + m = getglobal(m, v) + m isa Module || error("invalid $keyword path: \"$v\" does not name a module") + v = next!() + end + m, v +end + +function eval_import_path_all(at::Module, path::Expr, keyword::String) + m, v = eval_import_path(at, nothing, path, keyword) + if v !== nothing + m = getglobal(m, v) + m isa Module || error("invalid $keyword path: \"$v\" does not name a module") + end + m +end + +function check_macro_rename(from::Symbol, to::Symbol, keyword::String) + c1(sym) = bitcast(Char, UInt32(unsafe_load(unsafe_convert(Ptr{UInt8}, sym))) << 24) + from_c, to_c = c1(from), c1(to) + if from_c == '@' && to_c != '@' + error("cannot rename macro \"$from\" to non-macro \"$to\" in \"$keyword\"") + end + if from_c != '@' && to_c == '@' + error("cannot rename non-macro \"$from\" to macro \"$to\" in \"$keyword\"") + end +end + +""" + _eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...) + +Evaluate the import paths, calling `Core._import` for each name to be imported. +`imported` imports are created with `import`, `using A: x` sets this to false. +The `from` is the part of the import path before the `:`. This is the lowered +form of `import`, `import ...:`, and `using ...:`. + +``` +import A => _eval_import(true, Main, nothing, Expr(:., :A)) +import A.b => _eval_import(true, Main, nothing, Expr(:., :A, :b)) +import A.b as c => _eval_import(true, Main, nothing, Expr(:as, Expr(:., :A, :b), :c)) +import A.B: C.d, e => _eval_import(true, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) +import A.B: C.d as e => _eval_import(true, Main, Expr(:., :A, :B), Expr(:as, Expr(:., :C, :d), :e)) +using A.B: C.d, e => _eval_import(false, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) + +See also [`_import`](@ref Core._import). +``` +""" +function Core._eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...) + keyword = imported ? "import" : "using" + fail() = error("malformed \"$keyword\" statement") + from = from !== nothing ? eval_import_path_all(to, from, keyword) : nothing + + for path in paths + path isa Expr || fail() + asname = nothing + if path.head === :as && length(path.args) == 2 + path, asname = path.args + elseif path.head !== :. + fail() + end + old_from = from + from, name = eval_import_path(to, from, path, keyword) + asname = asname === nothing ? name : asname + + if name !== nothing + check_macro_rename(name, asname, keyword) + Core._import(to, from, asname, name, imported) + else + Core._import(to, from, nameof(from)) + end + end +end + +""" + _eval_using(to::Module, path::Expr) + +Evaluate the import path to a module and call [`Core._using`](@ref) on it, +making its exports available to the `to` module; this is the lowered form of +`using A`. + +``` +using A.B => _module_using(Main, Expr(:., :A, :B)) +``` + +See also [`_using`](@ref Core._using). +""" +function Core._eval_using(to::Module, path::Expr) + from = eval_import_path_all(to, path, "using") + Core._using(to, from) + is_package = length(path.args) == 1 && path.args[1] !== :. + if to == Main && is_package + Core._import(to, from, nameof(from)) + end +end diff --git a/base/ordering.jl b/base/ordering.jl index 19e8a1cf18109..f2ddd20ab09f0 100644 --- a/base/ordering.jl +++ b/base/ordering.jl @@ -3,7 +3,7 @@ module Order -import ..@__MODULE__, ..parentmodule +import Base: @__MODULE__, parentmodule const Base = parentmodule(@__MODULE__) import .Base: AbstractVector, @propagate_inbounds, isless, identity, getindex, reverse, diff --git a/src/builtin_proto.h b/src/builtin_proto.h index a30f77210dda1..e5a8dbb5ef86b 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -84,8 +84,8 @@ JL_CALLABLE(jl_f__equiv_typedef); JL_CALLABLE(jl_f_get_binding_type); JL_CALLABLE(jl_f__compute_sparams); JL_CALLABLE(jl_f__svec_ref); -JL_CALLABLE(jl_f__module_import); -JL_CALLABLE(jl_f__module_using); +JL_CALLABLE(jl_f__import); +JL_CALLABLE(jl_f__using); #ifdef __cplusplus } #endif diff --git a/src/builtins.c b/src/builtins.c index 723782d1d88e2..d822da17d3531 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -3,6 +3,8 @@ /* implementations of built-in functions */ +#include "dtypes.h" +#include "julia_atomics.h" #include "platform.h" #include @@ -1484,230 +1486,40 @@ JL_CALLABLE(jl_f_setglobalonce) // import, using -------------------------------------------------------------- -extern size_t jl_require_world; -static jl_module_t *call_require(jl_task_t *ct, jl_module_t *mod, jl_sym_t *var) JL_GLOBALLY_ROOTED -{ - JL_TIMING(LOAD_IMAGE, LOAD_Require); - jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, "%s", jl_symbol_name(var)); - - int build_mode = jl_options.incremental && jl_generating_output(); - jl_module_t *m = NULL; - static jl_value_t *require_func = NULL; - if (require_func == NULL && jl_base_module != NULL) { - require_func = jl_get_global(jl_base_module, jl_symbol("require")); - } - if (require_func != NULL) { - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - if (build_mode && jl_require_world < ct->world_age) - ct->world_age = jl_require_world; - jl_value_t *reqargs[3]; - reqargs[0] = require_func; - reqargs[1] = (jl_value_t*)mod; - reqargs[2] = (jl_value_t*)var; - m = (jl_module_t*)jl_apply(reqargs, 3); - } - if (m == NULL || !jl_is_module(m)) { - jl_errorf("failed to load module %s", jl_symbol_name(var)); - } - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - return m; -} - -// either: -// - sets *name and returns the module to import *name from -// - sets *name to NULL and returns a module to import -// also updates world_age -static jl_module_t *eval_import_path(jl_task_t *ct, jl_module_t *where, jl_module_t *from JL_PROPAGATES_ROOT, - jl_array_t *args, jl_sym_t **name, const char *keyword) JL_GLOBALLY_ROOTED -{ - if (jl_array_nrows(args) == 0) - jl_errorf("malformed \"%s\" statement", keyword); - jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0); - size_t i = 1; - jl_module_t *m = NULL; - *name = NULL; - if (!jl_is_symbol(var)) - jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); - - if (from != NULL) { - m = from; - i = 0; - } - else if (var != jl_dot_sym) { - // `A.B`: call the loader to obtain the root A in the current environment. - if (jl_core_module && var == jl_core_module->name) { - m = jl_core_module; - } - else if (jl_base_module && var == jl_base_module->name) { - m = jl_base_module; - } - else { - m = call_require(ct, where, var); - } - if (i == jl_array_nrows(args)) - return m; - } - else { - // `.A.B.C`: strip off leading dots by following parent links - m = where; - while (1) { - if (i >= jl_array_nrows(args)) - jl_error("invalid module path"); - var = (jl_sym_t*)jl_array_ptr_ref(args, i); - if (var != jl_dot_sym) - break; - i++; - assert(m); - m = m->parent; - } +// Import binding `from.sym` as `asname` into `to`: +// _import(to::Module, from::Module, asname::Symbol, sym::Symbol, imported::Bool) +// +// Create const binding to `mod` in `to` with name `asname`: +// _import(to::Module, mod::Module, asname::Symbol) +JL_CALLABLE(jl_f__import) +{ + JL_NARGS(_import, 3, 5); + JL_TYPECHK(_import, module, args[0]); + JL_TYPECHK(_import, module, args[1]); + JL_TYPECHK(_import, symbol, args[2]); + if (nargs == 3) { + jl_import_module(jl_current_task, (jl_module_t *)args[0], (jl_module_t *)args[1], + (jl_sym_t *)args[2]); } - - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); - - while (1) { - var = (jl_sym_t*)jl_array_ptr_ref(args, i); - if (!jl_is_symbol(var)) - jl_type_error(keyword, (jl_value_t*)jl_symbol_type, (jl_value_t*)var); - if (var == jl_dot_sym) - jl_errorf("invalid %s path: \".\" in identifier path", keyword); - if (i == jl_array_nrows(args)-1) - break; - m = (jl_module_t*)jl_eval_global_var(m, var); - JL_GC_PROMISE_ROOTED(m); - if (!jl_is_module(m)) - jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(var)); - i++; - } - *name = var; - return m; -} - -static jl_module_t *eval_import_path_all(jl_task_t *ct, jl_module_t *where, jl_array_t *args, const char *keyword) -{ - jl_sym_t *name = NULL; - jl_module_t *from = eval_import_path(ct, where, NULL, args, &name, keyword); - if (name) { - from = (jl_module_t *)jl_eval_global_var(from, name); - if (!jl_is_module(from)) - jl_errorf("invalid using path: \"%s\" does not name a module", jl_symbol_name(name)); - } - return from; -} - -static void import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym_t *asname) -{ - assert(m); - jl_sym_t *name = asname ? asname : import->name; - // TODO: this is a bit race-y with what error message we might print - jl_binding_t *b = jl_get_module_binding(m, name, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); - enum jl_partition_kind kind = jl_binding_kind(bpart); - if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) { - // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. - jl_walk_binding_inplace(&b, &bpart, ct->world_age); - if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { - // Already declared (e.g. on another thread) or imported. - if (bpart->restriction == (jl_value_t*)import) - return; - } - jl_errorf("importing %s into %s conflicts with an existing global", - jl_symbol_name(name), jl_symbol_name(m->name)); + else if (nargs == 4) { + jl_too_few_args("_import", 5); } - jl_declare_constant_val2(b, m, name, (jl_value_t*)import, PARTITION_KIND_CONST_IMPORT); -} - -static void check_macro_rename(jl_sym_t *from, jl_sym_t *to, const char *keyword) -{ - char *n1 = jl_symbol_name(from), *n2 = jl_symbol_name(to); - if (n1[0] == '@' && n2[0] != '@') - jl_errorf("cannot rename macro \"%s\" to non-macro \"%s\" in \"%s\"", n1, n2, keyword); - if (n1[0] != '@' && n2[0] == '@') - jl_errorf("cannot rename non-macro \"%s\" to macro \"%s\" in \"%s\"", n1, n2, keyword); -} - -// _module_import(explicit::Bool, to::Module, ctx::Union{Expr, Nothing}, names::Expr...) -// 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)) -JL_CALLABLE(jl_f__module_import) -{ - JL_NARGSV(_module_import, 4); - JL_TYPECHK(_module_import, bool, args[0]); - JL_TYPECHK(_module_import, module, args[1]); - if (!(jl_is_expr(args[2]) || args[2] == jl_nothing)) - jl_type_error("_module_import", (jl_value_t *)jl_expr_type, args[2]); - for (int i = 3; i < nargs; i++) - JL_TYPECHK(_module_import, expr, args[i]); - - jl_value_t **roots; - JL_GC_PUSHARGS(roots, nargs - 2); - memcpy(roots, args + 2, (nargs - 2) * sizeof *roots); - - int explici = args[0] == jl_true; - jl_module_t *to = (jl_module_t *)args[1]; - const char *keyword = explici ? "import" : "using"; - jl_task_t *ct = jl_current_task; - jl_module_t *ctx = NULL; - if (args[2] != jl_nothing) - ctx = eval_import_path_all(ct, to, ((jl_expr_t *)args[2])->args, keyword); - - for (int i = 3; i < nargs; i++) { - jl_expr_t *ex = (jl_expr_t *)args[i]; - jl_sym_t *name, *asname = NULL; - - if (ex->head == jl_as_sym && jl_expr_nargs(ex) == 2) { - asname = (jl_sym_t *)jl_exprarg(ex, 1); - ex = (jl_expr_t *)jl_exprarg(ex, 0); - if (!jl_is_expr(ex) || ex->head != jl_dot_sym) - goto malformed_err; - } - else if (ex->head == jl_dot_sym) { - } - else { - goto malformed_err; - } - jl_module_t *from = eval_import_path(ct, to, ctx, ex->args, &name, keyword); - if (!asname) - asname = name; - - if (name) { - check_macro_rename(name, asname, keyword); - jl_module_import(ct, to, from, asname, name, explici); - } - else { - import_module(ct, to, from, asname); - } + else if (nargs == 5) { + JL_TYPECHK(_import, symbol, args[3]); + JL_TYPECHK(_import, bool, args[4]); + jl_module_import(jl_current_task, (jl_module_t *)args[0], (jl_module_t *)args[1], + (jl_sym_t *)args[2], (jl_sym_t *)args[3], args[4] == jl_true); } - - JL_GC_POP(); return jl_nothing; - -malformed_err: - jl_errorf("malformed \"%s\" statement", keyword); } -// _module_using(to::Module, from::Expr{Symbol}) -// using A.B => _module_using(Main, Expr(:., :A, :B)) -JL_CALLABLE(jl_f__module_using) +// _using(to::Module, from::Module) +JL_CALLABLE(jl_f__using) { - JL_NARGS(_module_using, 2, 2); - jl_module_t *to = (jl_module_t *)args[0]; - jl_expr_t *ex = (jl_expr_t *)args[1]; - - jl_task_t *ct = jl_current_task; - jl_module_t *from = eval_import_path_all(ct, to, ex->args, "using"); - - jl_module_using(to, from); - int is_package = jl_expr_nargs(ex) == 1 && (jl_sym_t *)jl_exprarg(ex, 0) != jl_dot_sym; - if (to == jl_main_module && is_package) { - // TODO: for now, `using A` in Main also creates an explicit binding for `A` - // This will possibly be extended to all modules. - import_module(ct, to, from, NULL); - } - + JL_NARGS(_using, 2, 2); + JL_TYPECHK(_using, module, args[0]); + JL_TYPECHK(_using, module, args[1]); + jl_module_using((jl_module_t *)args[0], (jl_module_t *)args[1]); return jl_nothing; } @@ -2713,8 +2525,8 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_modifyglobal = add_builtin_func("modifyglobal!", jl_f_modifyglobal); jl_builtin_setglobalonce = add_builtin_func("setglobalonce!", jl_f_setglobalonce); - add_builtin_func("_module_import", jl_f__module_import); - add_builtin_func("_module_using", jl_f__module_using); + add_builtin_func("_import", jl_f__import); + add_builtin_func("_using", jl_f__using); // memory primitives jl_builtin_memorynew = add_builtin_func("memorynew", jl_f_memorynew); diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index d6b6eeb904efe..1fe3cb60b63a7 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2952,18 +2952,22 @@ (set! *current-desugar-loc* e) e) + ;; We insert (latestworld) after every call to _eval_import or _eval_using + ;; to avoid having to do it in eval_import_path (#57316) 'import (lambda (e) (check-import-paths "import" (cdr e)) `(block (toplevel-only import) ,.(if (eq? (caadr e) ':) - `((call (core _module_import) (true) (thismodule) - ,.(map (lambda (x) `(inert ,x)) (cdadr e)))) + `((call (core _eval_import) (true) (thismodule) + ,.(map (lambda (x) `(inert ,x)) (cdadr e))) + (latestworld)) (map (lambda (x) - `(call (core _module_import) (true) (thismodule) (null) (inert ,x))) - (cdr e))) - (latestworld))) + `(block + (call (core _eval_import) (true) (thismodule) (null) (inert ,x)) + (latestworld))) + (cdr e))))) 'using (lambda (e) @@ -2971,12 +2975,14 @@ `(block (toplevel-only using) ,.(if (eq? (caadr e) ':) - `((call (core _module_import) (false) (thismodule) - ,.(map (lambda (x) `(inert ,x)) (cdadr e)))) + `((call (core _eval_import) (false) (thismodule) + ,.(map (lambda (x) `(inert ,x)) (cdadr e))) + (latestworld)) (map (lambda (x) - `(call (core _module_using) (thismodule) (inert ,x))) - (cdr e))) - (latestworld))) + `(block + (call (core _eval_using) (thismodule) (inert ,x)) + (latestworld))) + (cdr e))))) )) (define (has-return? e) diff --git a/src/julia.h b/src/julia.h index 60db302d01083..f2354bde65e07 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2111,6 +2111,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind); JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici); +JL_DLLEXPORT void jl_import_module(jl_task_t *ct, jl_module_t *m, jl_module_t *import, jl_sym_t *asname); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); int jl_module_public_(jl_module_t *from, jl_sym_t *s, int exported, size_t new_world); JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s); diff --git a/src/module.c b/src/module.c index 519ed2f861a40..c4e35b2a5946d 100644 --- a/src/module.c +++ b/src/module.c @@ -1269,6 +1269,28 @@ JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t * JL_UNLOCK(&world_counter_lock); } +JL_DLLEXPORT void jl_import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym_t *asname) +{ + assert(m); + jl_sym_t *name = asname ? asname : import->name; + // TODO: this is a bit race-y with what error message we might print + jl_binding_t *b = jl_get_module_binding(m, name, 1); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); + enum jl_partition_kind kind = jl_binding_kind(bpart); + if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) { + // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. + jl_walk_binding_inplace(&b, &bpart, ct->world_age); + if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { + // Already declared (e.g. on another thread) or imported. + if (bpart->restriction == (jl_value_t*)import) + return; + } + jl_errorf("importing %s into %s conflicts with an existing global", + jl_symbol_name(name), jl_symbol_name(m->name)); + } + jl_declare_constant_val2(b, m, name, (jl_value_t*)import, PARTITION_KIND_CONST_IMPORT); +} + void jl_add_usings_backedge(jl_module_t *from, jl_module_t *to) { JL_LOCK(&from->lock); diff --git a/src/staticdata.c b/src/staticdata.c index 559129c5d8e41..806d66d3e40d3 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -534,7 +534,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f__defaultctors, &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, &jl_f_get_binding_type, &jl_f_getglobal, &jl_f_setglobal, &jl_f_swapglobal, &jl_f_modifyglobal, &jl_f_replaceglobal, &jl_f_setglobalonce, - &jl_f__module_import, &jl_f__module_using, + &jl_f__import, &jl_f__using, &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, &jl_f_current_scope, NULL }; diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 4966e221eca4e..cac32492098c4 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -368,7 +368,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) end ast.head === :quote && return mods # don't search if it's not going to be run during this eval if ast.head == :call - if ast.args[1] == GlobalRef(Core, :_module_import) + if ast.args[1] == GlobalRef(Core, :_eval_import) ctx = ast.args[4] if ctx isa QuoteNode # i.e. `Foo: bar` ctx = ctx.value @@ -376,7 +376,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) ctx = ast.args[5].value end add!(ctx) - elseif ast.args[1] == GlobalRef(Core, :_module_using) + elseif ast.args[1] == GlobalRef(Core, :_eval_using) add!(ast.args[3].value) end end From 74af06bf3d16923a72d9f96be20721ce82c90c62 Mon Sep 17 00:00:00 2001 From: Sam Schweigel <33556084+xal-0@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:24:32 -0700 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: Jameson Nash --- base/module.jl | 2 +- stdlib/REPL/src/REPL.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/module.jl b/base/module.jl index 67d8006a58f2b..f689c8e53ebc6 100644 --- a/base/module.jl +++ b/base/module.jl @@ -26,7 +26,7 @@ end function eval_import_path(at::Module, from::Union{Module, Nothing}, path::Expr, keyword::String) isempty(path.args) && error("malformed import statement") - i = 1 + i::Int = 1 function next!() i <= length(path.args) || error("invalid module path") v = path.args[i] diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index cac32492098c4..d2fef0952c12b 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -368,7 +368,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) end ast.head === :quote && return mods # don't search if it's not going to be run during this eval if ast.head == :call - if ast.args[1] == GlobalRef(Core, :_eval_import) + if ast.args[1] === GlobalRef(Core, :_eval_import) ctx = ast.args[4] if ctx isa QuoteNode # i.e. `Foo: bar` ctx = ctx.value From 9443f5cc254c2eadbc59eedd7d62179d560f9694 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Tue, 22 Apr 2025 12:31:30 -0700 Subject: [PATCH 06/11] Remove call_require now that Base.require uses invoke_in_world Co-authored-by: Jameson Nash --- base/module.jl | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/base/module.jl b/base/module.jl index f689c8e53ebc6..5fcdb4fb6bd06 100644 --- a/base/module.jl +++ b/base/module.jl @@ -9,20 +9,6 @@ for m in methods(Core._eval_using) delete_method(m) end -function call_require(into::Module, name::Symbol) - # TODO: JL_TIMING(LOAD_IMAGE, LOAD_Require) - build_mode = Bool(Base.JLOptions().incremental) && Bool(ccall(:jl_generating_output, Cint, ())) - m = nothing - req_w = unsafe_load(cglobal(:jl_require_world, UInt)) - cur_w, tls_w = get_world_counter(), tls_world_age() - if isdefined(Base, :require) - w = build_mode && req_w < tls_w ? req_w : cur_w - m = invoke_in_world(w, getglobal(Base, :require), into, name) - end - m isa Module || error("failed to load module $v") - m -end - function eval_import_path(at::Module, from::Union{Module, Nothing}, path::Expr, keyword::String) isempty(path.args) && error("malformed import statement") @@ -46,7 +32,9 @@ function eval_import_path(at::Module, from::Union{Module, Nothing}, path::Expr, elseif v === :Base m = Base else - m = call_require(at, v) + # TODO: JL_TIMING(LOAD_IMAGE, LOAD_Require) + m = require(at, v) + m isa Module || error("failed to load module $v") end i > lastindex(path.args) && return m, nothing v = next!() From 190f1235503612bacc62d95df880e4caa3446095 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Tue, 22 Apr 2025 13:23:48 -0700 Subject: [PATCH 07/11] Check Expr.args length in REPL import hook Co-authored-by: Jameson Nash --- stdlib/REPL/src/REPL.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index d2fef0952c12b..dc287dd2a3e47 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -368,7 +368,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) end ast.head === :quote && return mods # don't search if it's not going to be run during this eval if ast.head == :call - if ast.args[1] === GlobalRef(Core, :_eval_import) + if length(ast.args) == 5 && ast.args[1] === GlobalRef(Core, :_eval_import) ctx = ast.args[4] if ctx isa QuoteNode # i.e. `Foo: bar` ctx = ctx.value @@ -376,7 +376,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) ctx = ast.args[5].value end add!(ctx) - elseif ast.args[1] == GlobalRef(Core, :_eval_using) + elseif length(ast.args) == 3 && ast.args[1] == GlobalRef(Core, :_eval_using) add!(ast.args[3].value) end end From 3d532fed1f5d7d050761a637b41d05a68ca02563 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Tue, 22 Apr 2025 13:55:47 -0700 Subject: [PATCH 08/11] Fix `import Package as M` --- base/module.jl | 4 ++-- test/loading.jl | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/base/module.jl b/base/module.jl index 5fcdb4fb6bd06..0b7895f8996a7 100644 --- a/base/module.jl +++ b/base/module.jl @@ -110,13 +110,13 @@ function Core._eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing end old_from = from from, name = eval_import_path(to, from, path, keyword) - asname = asname === nothing ? name : asname if name !== nothing + asname = asname === nothing ? name : asname check_macro_rename(name, asname, keyword) Core._import(to, from, asname, name, imported) else - Core._import(to, from, nameof(from)) + Core._import(to, from, asname === nothing ? nameof(from) : asname) end end end diff --git a/test/loading.jl b/test/loading.jl index d12cd2769ef1d..2dcf129a741a9 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1731,3 +1731,9 @@ end rm(depot_path, force=true, recursive=true) end end + +# Test `import Package as M` +module M57965 + import Random as R +end +@test M57965.R === Base.require(M57965, :Random) From fa2715ff72a86343a2d55b21501445a5b3b8d271 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Tue, 22 Apr 2025 14:24:12 -0700 Subject: [PATCH 09/11] Fix jl_import_module world age --- src/module.c | 5 +++-- test/worlds.jl | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/module.c b/src/module.c index 4a55bb63ed2b1..108f0f3c17275 100644 --- a/src/module.c +++ b/src/module.c @@ -1275,11 +1275,12 @@ JL_DLLEXPORT void jl_import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_ jl_sym_t *name = asname ? asname : import->name; // TODO: this is a bit race-y with what error message we might print jl_binding_t *b = jl_get_module_binding(m, name, 1); - jl_binding_partition_t *bpart = jl_get_binding_partition(b, ct->world_age); + size_t world = jl_atomic_load_acquire(&jl_world_counter); + jl_binding_partition_t *bpart = jl_get_binding_partition(b, world); enum jl_partition_kind kind = jl_binding_kind(bpart); if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) { // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant. - jl_walk_binding_inplace(&b, &bpart, ct->world_age); + jl_walk_binding_inplace(&b, &bpart, world); if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) { // Already declared (e.g. on another thread) or imported. if (bpart->restriction == (jl_value_t*)import) diff --git a/test/worlds.jl b/test/worlds.jl index dd98170721b1a..42cc74a010670 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -558,3 +558,12 @@ module C57316; import ..X57316.Y57316 as Z, .Z.Y57316 as W; end @test !isdefined(B57316, :X57316) @test !isdefined(C57316, :X57316) @test !isdefined(C57316, :Y57316) + +# jl_module_import should always manipulate the latest world +module M57965 +function f() + @eval Random = 1 + Core._eval_import(true, @__MODULE__, nothing, Expr(:., :Random)) +end +end +@test_throws ErrorException("importing Random into M57965 conflicts with an existing global") M57965.f()s From 9e855c69fbbfd23141e911878348c7951e07afce Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Tue, 22 Apr 2025 15:02:09 -0700 Subject: [PATCH 10/11] Refer to _eval_using and _eval_import through top module --- Compiler/src/Compiler.jl | 2 +- base/Base_compiler.jl | 3 +++ base/module.jl | 8 ++++---- src/julia-syntax.scm | 8 ++++---- stdlib/REPL/src/REPL.jl | 4 ++-- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index a046c71beff8f..fc705a4b50536 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -65,7 +65,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali structdiff, tls_world_age, unconstrain_vararg_length, unionlen, uniontype_layout, uniontypes, unsafe_convert, unwrap_unionall, unwrapva, vect, widen_diagonal, _uncompressed_ir, maybe_add_binding_backedge!, datatype_min_ninitialized, - partialstruct_init_undefs, fieldcount_noerror + partialstruct_init_undefs, fieldcount_noerror, _eval_import, _eval_using using Base.Order import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, get!, diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index 2bbd32565246d..28c07379c54d9 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -2,6 +2,9 @@ module Base +Core._import(Base, Core, :_eval_import, :_eval_import, true) +Core._import(Base, Core, :_eval_using, :_eval_using, true) + using .Core.Intrinsics, .Core.IR # to start, we're going to use a very simple definition of `include` diff --git a/base/module.jl b/base/module.jl index 0b7895f8996a7..373dd34c292ef 100644 --- a/base/module.jl +++ b/base/module.jl @@ -2,10 +2,10 @@ # Full-featured versions of _eval_import and _eval_using -for m in methods(Core._eval_import) +for m in methods(_eval_import) delete_method(m) end -for m in methods(Core._eval_using) +for m in methods(_eval_using) delete_method(m) end @@ -95,7 +95,7 @@ using A.B: C.d, e => _eval_import(false, Main, Expr(:., :A, :B), Expr(:., :C, See also [`_import`](@ref Core._import). ``` """ -function Core._eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...) +function _eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...) keyword = imported ? "import" : "using" fail() = error("malformed \"$keyword\" statement") from = from !== nothing ? eval_import_path_all(to, from, keyword) : nothing @@ -134,7 +134,7 @@ using A.B => _module_using(Main, Expr(:., :A, :B)) See also [`_using`](@ref Core._using). """ -function Core._eval_using(to::Module, path::Expr) +function _eval_using(to::Module, path::Expr) from = eval_import_path_all(to, path, "using") Core._using(to, from) is_package = length(path.args) == 1 && path.args[1] !== :. diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 3929046afdbaa..9db250c7bffa7 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2962,12 +2962,12 @@ `(block (toplevel-only import) ,.(if (eq? (caadr e) ':) - `((call (core _eval_import) (true) (thismodule) + `((call (top _eval_import) (true) (thismodule) ,.(map (lambda (x) `(inert ,x)) (cdadr e))) (latestworld)) (map (lambda (x) `(block - (call (core _eval_import) (true) (thismodule) (null) (inert ,x)) + (call (top _eval_import) (true) (thismodule) (null) (inert ,x)) (latestworld))) (cdr e))))) @@ -2977,12 +2977,12 @@ `(block (toplevel-only using) ,.(if (eq? (caadr e) ':) - `((call (core _eval_import) (false) (thismodule) + `((call (top _eval_import) (false) (thismodule) ,.(map (lambda (x) `(inert ,x)) (cdadr e))) (latestworld)) (map (lambda (x) `(block - (call (core _eval_using) (thismodule) (inert ,x)) + (call (top _eval_using) (thismodule) (inert ,x)) (latestworld))) (cdr e))))) )) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index dc287dd2a3e47..16b3f2b1c25fb 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -368,7 +368,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) end ast.head === :quote && return mods # don't search if it's not going to be run during this eval if ast.head == :call - if length(ast.args) == 5 && ast.args[1] === GlobalRef(Core, :_eval_import) + if length(ast.args) == 5 && ast.args[1] === GlobalRef(Base, :_eval_import) ctx = ast.args[4] if ctx isa QuoteNode # i.e. `Foo: bar` ctx = ctx.value @@ -376,7 +376,7 @@ function _modules_to_be_loaded!(ast::Expr, mods::Vector{Symbol}) ctx = ast.args[5].value end add!(ctx) - elseif length(ast.args) == 3 && ast.args[1] == GlobalRef(Core, :_eval_using) + elseif length(ast.args) == 3 && ast.args[1] == GlobalRef(Base, :_eval_using) add!(ast.args[3].value) end end From 013c75d7d0db02e053920d8a5ef9296aad0164b1 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Fri, 25 Apr 2025 11:06:39 -0700 Subject: [PATCH 11/11] Re-add timing zone for LOAD_Require --- base/loading.jl | 2 +- base/module.jl | 1 - sysimage.mk | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index b26a063247169..eed486098b35d 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2331,7 +2331,7 @@ function require(into::Module, mod::Symbol) if world == typemax(UInt) world = get_world_counter() end - return invoke_in_world(world, __require, into, mod) + return Compiler.@zone "LOAD_Require" invoke_in_world(world, __require, into, mod) end function check_for_hint(into, mod) diff --git a/base/module.jl b/base/module.jl index 373dd34c292ef..3e95d9882339c 100644 --- a/base/module.jl +++ b/base/module.jl @@ -32,7 +32,6 @@ function eval_import_path(at::Module, from::Union{Module, Nothing}, path::Expr, elseif v === :Base m = Base else - # TODO: JL_TIMING(LOAD_IMAGE, LOAD_Require) m = require(at, v) m isa Module || error("failed to load module $v") end diff --git a/sysimage.mk b/sysimage.mk index 550373bfba588..987ca4035870e 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -45,6 +45,7 @@ COMPILER_SRCS := $(addprefix $(JULIAHOME)/, \ base/indices.jl \ base/iterators.jl \ base/invalidation.jl \ + base/module.jl \ base/namedtuple.jl \ base/number.jl \ base/operators.jl \