Skip to content

Commit d085375

Browse files
committed
WIP: Make world-age increments explicit
This PR introduces a new, toplevel-only, syntax form `:worldinc` that semantically represents the effect of raising the current task's world age to the latest world for the remainder of the current toplevel evaluation (that context being an entry to `eval` or a module expression). For detailed motivation on why this is desirable, see #55145, which I won't repeat here, but the gist is that we never really defined when world-age increments and worse are inconsistent about it. This is something we need to figure out now, because the bindings partition work will make world age even more observable via bindings. Having created a mechanism for world age increments, the big question is one of policy, i.e. when should these world age increments be inserted. Several reasonable options exist: 1. After world-age affecting syntax constructs (as proprosed in #55145) 2. Option 1 + some reasonable additional cases that people rely on 3. Before any top level `call` expression 4. Before any expression at toplevel whatsover As in example, case, consider `a == a` at toplevel. Depending on the semantics that could either be the same as in local scope, or each of the four world age dependent lookups (three binding lookups, one method lookup could occur in a different world age). The general tradeoff here is between the risk of exposing the user to confusing world age errors and our ability to optimize top-level code (in general, any :worldinc statement will require us to fully pessimize or recompile all following code). This PR basically implements option 2 with the following semantics: 1. The interpreter explicit raises the world age only at `:worldinc` exprs or after `:module` exprs. 2. The frontend inserts `:worldinc` after all struct definitions, method definitions, `using` and `import. 3. The `@eval` macro inserts a worldinc following the call to `eval` if at toplevel 4. A literal (syntactic) call to `include` gains an implicit `worldinc`. Of these the fourth is probably the most questionable, but is necessary to make this non-breaking for most code patterns. Perhaps it would have been better to make `include` a macro from the beginning (esp because it already has semantics that look a little like reaching into the calling module), but that ship has sailed. Unfortunately, I don't see any good intermediate options between this PR and option #3 above. I think option #3 is closes to what we have right now, but if we were to choose it and actually fix the soundness issues, I expect that we would be destroying all performance of global-scope code. For this reason, I would like to try to make the version in this PR work, even if the semantics are a little ugly. The biggest pattern that this PR does not catch is: ``` eval(:(f() = 1)) f() ``` We could apply the same `include` special case to eval, but given the existence of `@eval` which allows addressing this at the macro level, I decided not to. We can decide which way we want to go on this based on what the package ecosystem looks like.
1 parent 8593792 commit d085375

24 files changed

+293
-171
lines changed

base/boot.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,18 +259,21 @@ else
259259
const UInt = UInt32
260260
end
261261

262-
function iterate end
263262
function Typeof end
264263
ccall(:jl_toplevel_eval_in, Any, (Any, Any),
265264
Core, quote
266265
(f::typeof(Typeof))(x) = ($(_expr(:meta,:nospecialize,:x)); isa(x,Type) ? Type{x} : typeof(x))
267266
end)
268267

268+
function iterate end
269+
269270
macro nospecialize(x)
270271
_expr(:meta, :nospecialize, x)
271272
end
272273
Expr(@nospecialize args...) = _expr(args...)
273274

275+
macro worldinc() Expr(:worldinc) end
276+
274277
_is_internal(__module__) = __module__ === Core
275278
# can be used in place of `@assume_effects :total` (supposed to be used for bootstrapping)
276279
macro _total_meta()
@@ -518,6 +521,7 @@ eval(Core, quote
518521
InterConditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :thentype, :elsetype))
519522
MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers))
520523
end)
524+
@worldinc
521525

522526
const NullDebugInfo = DebugInfo(:none)
523527

base/essentials.jl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,20 @@ Evaluate an expression with values interpolated into it using `eval`.
470470
If two arguments are provided, the first is the module to evaluate in.
471471
"""
472472
macro eval(ex)
473-
return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), __module__, Expr(:quote, ex)))
473+
g = ccall(:jl_gensym, Ref{Symbol}, ())
474+
return Expr(:let, Expr(:(=), g,
475+
Expr(:escape, Expr(:call, GlobalRef(Core, :eval), __module__, Expr(:quote, ex)))),
476+
Expr(:block,
477+
Expr(:var"worldinc-if-toplevel"),
478+
g))
474479
end
475480
macro eval(mod, ex)
476-
return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex)))
481+
g = ccall(:jl_gensym, Ref{Symbol}, ())
482+
return Expr(:let, Expr(:(=), g,
483+
Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex)))),
484+
Expr(:block,
485+
Expr(:var"worldinc-if-toplevel"),
486+
g))
477487
end
478488

479489
# use `@eval` here to directly form `:new` expressions avoid implicit `convert`s

base/sysimg.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ using .Base
66

77
# Set up Main module
88
using Base.MainInclude # ans, err, and sometimes Out
9+
Core.@worldinc
910

1011
# These definitions calls Base._include rather than Base.include to get
1112
# one-frame stacktraces for the common case of using include(fname) in Main.
@@ -29,6 +30,13 @@ actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`i
2930
3031
Use [`Base.include`](@ref) to evaluate a file into another module.
3132
33+
!!! note
34+
Julia's syntax lowering recognizes an explicit call to a literal `include`
35+
at top-level and inserts an implicit `@Core.worldinc` to make any include'd
36+
definitions visible to subsequent code. Note however that this recognition
37+
is *syntactic*. I.e. assigning `const myinclude = include` may require
38+
and explicit `@Core.worldinc` call after `myinclude`.
39+
3240
!!! compat "Julia 1.5"
3341
Julia 1.5 is required for passing the `mapexpr` argument.
3442
"""

base/tuple.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ end
6060

6161
function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N}
6262
@inline
63-
return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}())
63+
return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}())::NTuple{N, Any}
6464
end
6565

6666

src/ast.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ JL_DLLEXPORT jl_sym_t *jl_release_sym;
119119
JL_DLLEXPORT jl_sym_t *jl_acquire_release_sym;
120120
JL_DLLEXPORT jl_sym_t *jl_sequentially_consistent_sym;
121121
JL_DLLEXPORT jl_sym_t *jl_uninferred_sym;
122+
JL_DLLEXPORT jl_sym_t *jl_worldinc_sym;
122123

123124
static const uint8_t flisp_system_image[] = {
124125
#include <julia_flisp.boot.inc>
@@ -461,6 +462,7 @@ void jl_init_common_symbols(void)
461462
jl_acquire_release_sym = jl_symbol("acquire_release");
462463
jl_sequentially_consistent_sym = jl_symbol("sequentially_consistent");
463464
jl_uninferred_sym = jl_symbol("uninferred");
465+
jl_worldinc_sym = jl_symbol("worldinc");
464466
}
465467

466468
JL_DLLEXPORT void jl_lisp_prompt(void)

src/interpreter.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip,
463463
s->ip = ip;
464464
if (ip >= ns)
465465
jl_error("`body` expression must terminate in `return`. Use `block` instead.");
466-
if (toplevel)
467-
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
468466
jl_value_t *stmt = jl_array_ptr_ref(stmts, ip);
469467
assert(!jl_is_phinode(stmt));
470468
size_t next_ip = ip + 1;
@@ -643,6 +641,9 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip,
643641
jl_eval_const_decl(s->module, jl_exprarg(stmt, 0), val);
644642
s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing;
645643
}
644+
else if (head == jl_worldinc_sym) {
645+
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
646+
}
646647
else if (jl_is_toplevel_only_expr(stmt)) {
647648
jl_toplevel_eval(s->module, stmt);
648649
}
@@ -887,10 +888,7 @@ jl_value_t *NOINLINE jl_interpret_toplevel_thunk(jl_module_t *m, jl_code_info_t
887888
s->mi = NULL;
888889
s->ci = NULL;
889890
JL_GC_ENABLEFRAME(s);
890-
jl_task_t *ct = jl_current_task;
891-
size_t last_age = ct->world_age;
892891
jl_value_t *r = eval_body(stmts, s, 0, 1);
893-
ct->world_age = last_age;
894892
JL_GC_POP();
895893
return r;
896894
}

src/jlfrontend.scm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139

140140
(define (toplevel-only-expr? e)
141141
(and (pair? e)
142-
(or (memq (car e) '(toplevel line module import using export public
142+
(or (memq (car e) '(toplevel line module export public
143143
error incomplete))
144144
(and (memq (car e) '(global const)) (every symbol? (cdr e))))))
145145

src/julia-syntax.scm

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,7 @@
10261026
;; otherwise do an assignment to trigger an error
10271027
(const (globalref (thismodule) ,name) ,name)))
10281028
(const (globalref (thismodule) ,name) ,name))
1029+
(worldinc)
10291030
(call (core _typebody!) ,name (call (core svec) ,@field-types))
10301031
(null)))
10311032
;; "inner" constructors
@@ -1076,6 +1077,7 @@
10761077
(call (core _equiv_typedef) (globalref (thismodule) ,name) ,name))
10771078
(null)
10781079
(const (globalref (thismodule) ,name) ,name))
1080+
(worldinc)
10791081
(null))))))
10801082

10811083
(define (primitive-type-def-expr n name params super)
@@ -1096,6 +1098,7 @@
10961098
(call (core _equiv_typedef) (globalref (thismodule) ,name) ,name))
10971099
(null)
10981100
(const (globalref (thismodule) ,name) ,name))
1101+
(worldinc)
10991102
(null))))))
11001103

11011104
;; take apart a type signature, e.g. T{X} <: S{Y}
@@ -1210,7 +1213,7 @@
12101213
(cond ((and (length= e 2) (or (symbol? name) (globalref? name)))
12111214
(if (not (valid-name? name))
12121215
(error (string "invalid function name \"" name "\"")))
1213-
`(method ,name))
1216+
`(block (method ,name) (worldinc) (unnecessary ,name)))
12141217
((not (pair? name)) e)
12151218
((eq? (car name) 'call)
12161219
(let* ((raw-typevars (or where '()))
@@ -2733,6 +2736,9 @@
27332736
((and (eq? (identifier-name f) '^) (length= e 4) (integer? (cadddr e)))
27342737
(expand-forms
27352738
`(call (top literal_pow) ,f ,(caddr e) (call (call (core apply_type) (top Val) ,(cadddr e))))))
2739+
((eq? f 'include)
2740+
(let ((r (make-ssavalue)))
2741+
`(block (= ,r ,(map expand-forms e)) (worldinc-if-toplevel) ,r)))
27362742
(else
27372743
(map expand-forms e))))
27382744
(map expand-forms e)))
@@ -4114,15 +4120,17 @@ f(x) = yt(x)
41144120
`(lambda ,(cadr lam2)
41154121
(,(clear-capture-bits (car vis))
41164122
,@(cdr vis))
4117-
,body)))))
4123+
,body)))
4124+
(worldinc)))
41184125
(else
41194126
(let* ((exprs (lift-toplevel (convert-lambda lam2 '|#anon| #t '() #f parsed-method-stack)))
41204127
(top-stmts (cdr exprs))
41214128
(newlam (compact-and-renumber (linearize (car exprs)) 'none 0)))
41224129
`(toplevel-butfirst
41234130
(block ,@sp-inits
41244131
(method ,(cadr e) ,(cl-convert sig fname lam namemap defined toplevel interp opaq parsed-method-stack globals locals)
4125-
,(julia-bq-macro newlam)))
4132+
,(julia-bq-macro newlam))
4133+
(worldinc))
41264134
,@top-stmts))))
41274135

41284136
;; local case - lift to a new type at top level
@@ -4261,15 +4269,17 @@ f(x) = yt(x)
42614269
`(toplevel-butfirst
42624270
(null)
42634271
,@sp-inits
4264-
,@mk-method)
4272+
,@mk-method
4273+
(worldinc))
42654274
(begin
42664275
(put! defined name #t)
42674276
`(toplevel-butfirst
42684277
,(convert-assignment name mk-closure fname lam interp opaq parsed-method-stack globals locals)
42694278
,@typedef
42704279
,@(map (lambda (v) `(moved-local ,v)) moved-vars)
42714280
,@sp-inits
4272-
,@mk-method))))))))
4281+
,@mk-method
4282+
(worldinc)))))))))
42734283
((lambda) ;; happens inside (thunk ...) and generated function bodies
42744284
(for-each (lambda (vi) (vinfo:set-asgn! vi #t))
42754285
(list-tail (car (lam:vinfo e)) (length (lam:args e))))
@@ -4501,6 +4511,7 @@ f(x) = yt(x)
45014511
((struct_type) "\"struct\" expression")
45024512
((method) "method definition")
45034513
((set_binding_type!) (string "type declaration for global \"" (deparse (cadr e)) "\""))
4514+
((worldinc) "World age increment")
45044515
(else (string "\"" h "\" expression"))))
45054516
(if (not (null? (cadr lam)))
45064517
(error (string (head-to-text (car e)) " not at top level"))))
@@ -4952,7 +4963,12 @@ f(x) = yt(x)
49524963
(else (emit temp)))))
49534964

49544965
;; top level expressions
4955-
((thunk module)
4966+
((thunk)
4967+
(check-top-level e)
4968+
(emit e)
4969+
(if tail (emit-return tail '(null)))
4970+
'(null))
4971+
((module)
49564972
(check-top-level e)
49574973
(emit e)
49584974
(if tail (emit-return tail '(null)))
@@ -4968,8 +4984,22 @@ f(x) = yt(x)
49684984
(if tail (emit-return tail val))
49694985
val))
49704986

4987+
((worldinc-if-toplevel)
4988+
(if (null? (cadr lam))
4989+
(emit `(worldinc)))
4990+
'(null))
4991+
4992+
((import using)
4993+
(check-top-level e)
4994+
(emit e)
4995+
(emit `(worldinc))
4996+
(let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return))))
4997+
(if (and tail (not have-ret?))
4998+
(emit-return tail '(null))))
4999+
'(null))
5000+
49715001
;; other top level expressions
4972-
((import using export public)
5002+
((export public worldinc)
49735003
(check-top-level e)
49745004
(emit e)
49755005
(let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return))))

src/julia_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_release_sym;
18411841
extern JL_DLLEXPORT jl_sym_t *jl_acquire_release_sym;
18421842
extern JL_DLLEXPORT jl_sym_t *jl_sequentially_consistent_sym;
18431843
extern JL_DLLEXPORT jl_sym_t *jl_uninferred_sym;
1844+
extern JL_DLLEXPORT jl_sym_t *jl_worldinc_sym;
18441845

18451846
JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order(jl_sym_t *order, char loading, char storing);
18461847
JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, char loading, char storing);

src/toplevel.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex
225225

226226
for (int i = 0; i < jl_array_nrows(exprs); i++) {
227227
// process toplevel form
228-
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
229228
form = jl_expand_stmt_with_loc(jl_array_ptr_ref(exprs, i), newm, filename, lineno);
230-
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
231229
(void)jl_toplevel_eval_flex(newm, form, 1, 1, &filename, &lineno);
232230
}
233231
ct->world_age = last_age;
@@ -607,7 +605,8 @@ int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT
607605
((jl_expr_t*)e)->head == jl_const_sym ||
608606
((jl_expr_t*)e)->head == jl_toplevel_sym ||
609607
((jl_expr_t*)e)->head == jl_error_sym ||
610-
((jl_expr_t*)e)->head == jl_incomplete_sym);
608+
((jl_expr_t*)e)->head == jl_incomplete_sym ||
609+
((jl_expr_t*)e)->head == jl_worldinc_sym);
611610
}
612611

613612
int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT
@@ -864,6 +863,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val
864863

865864
if (head == jl_module_sym) {
866865
jl_value_t *val = jl_eval_module_expr(m, ex);
866+
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
867867
JL_GC_POP();
868868
return val;
869869
}
@@ -919,6 +919,9 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val
919919
jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno,
920920
"syntax: malformed \"using\" statement");
921921
}
922+
if (!expanded) {
923+
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
924+
}
922925
JL_GC_POP();
923926
return jl_nothing;
924927
}
@@ -967,6 +970,12 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val
967970
jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno,
968971
"syntax: malformed \"import\" statement");
969972
}
973+
if (!expanded) {
974+
// To avoid having to roundtrip every `using` expression through
975+
// lowering, just to add the world-age increment effect, do it
976+
// manually here.
977+
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
978+
}
970979
JL_GC_POP();
971980
return jl_nothing;
972981
}
@@ -1111,8 +1120,11 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex)
11111120
jl_value_t *v = NULL;
11121121
int last_lineno = jl_lineno;
11131122
const char *last_filename = jl_filename;
1123+
jl_task_t *ct = jl_current_task;
11141124
jl_lineno = 1;
11151125
jl_filename = "none";
1126+
size_t last_age = ct->world_age;
1127+
ct->world_age = jl_atomic_load_relaxed(&jl_world_counter);
11161128
JL_TRY {
11171129
v = jl_toplevel_eval(m, ex);
11181130
}
@@ -1123,6 +1135,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex)
11231135
}
11241136
jl_lineno = last_lineno;
11251137
jl_filename = last_filename;
1138+
ct->world_age = last_age;
11261139
assert(v);
11271140
return v;
11281141
}
@@ -1170,6 +1183,7 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text,
11701183
int last_lineno = jl_lineno;
11711184
const char *last_filename = jl_filename;
11721185
size_t last_age = ct->world_age;
1186+
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
11731187
int lineno = 0;
11741188
jl_lineno = 0;
11751189
const char *filename_str = jl_string_data(filename);
@@ -1187,7 +1201,6 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text,
11871201
}
11881202
expression = jl_expand_with_loc_warn(expression, module,
11891203
jl_string_data(filename), lineno);
1190-
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
11911204
result = jl_toplevel_eval_flex(module, expression, 1, 1, &filename_str, &lineno);
11921205
}
11931206
}

0 commit comments

Comments
 (0)