Skip to content

Commit 2655bfb

Browse files
Kenoxal-0
authored andcommitted
bpart: Start enforcing minimum world age for const bparts (JuliaLang#57102)
Currently, even though the binding partition system is implemented, it is largely enabled. New `const` definitions get magically "backdated" to the first world age in which the binding was undefined. Additionally, they do not get their own world age and there is currently no `latestworld` marker after `const` definitions. This PR changes this situation to give const markers their own world age with appropriate `latestworld` increments. Both of these are mandatory for `const` replacement to work. The other thing this PR does is prepare to remove the automatic "backdating". To see the difference, consider: ``` function foo($i) Core.eval(:(const x = $i)) x end ``` Without an intervening world age increment, this will throw an UndefVarError on this PR. I believe this is the best option for two reasons: 1. It will allow us infer these to `Union{}` in the future (thus letting inference prune dead code faster). 2. I think it is less confusing in terms of the world age semantics for `const` definitions to become active only after they are defined. To illustrate the second point, suppose we did keep the automatic backdating. Then we would have: ``` foo(1) => 1 foo(2) => 1 foo(3) => 2 ``` as opposed to on this PR: ``` foo(1) => UndefVarError foo(2) => 1 foo(3) => 2 ``` The semantics are consistent, of course, but I am concerned that an automatic backdating will give users the wrong mental model about world age, since it "fixes itself" the first time, but if you Revise it, it will give an unexpected answer. I think this would encourage accidentally bad code patterns that only break under Revise (where they are hard to debug). The counterpoint of course is that that not backdating is a more breaking choice. As with the rest of the 1.12-era world age semantics changes, I think taking a look at PkgEval will be helpful.
1 parent ac52ee0 commit 2655bfb

36 files changed

+318
-164
lines changed

Compiler/src/abstractinterpretation.jl

+6-5
Original file line numberDiff line numberDiff line change
@@ -3456,10 +3456,10 @@ world_range(ir::IRCode) = ir.valid_worlds
34563456
world_range(ci::CodeInfo) = WorldRange(ci.min_world, ci.max_world)
34573457
world_range(compact::IncrementalCompact) = world_range(compact.ir)
34583458

3459-
function force_binding_resolution!(g::GlobalRef)
3459+
function force_binding_resolution!(g::GlobalRef, world::UInt)
34603460
# Force resolution of the binding
34613461
# TODO: This will go away once we switch over to fully partitioned semantics
3462-
ccall(:jl_globalref_boundp, Cint, (Any,), g)
3462+
ccall(:jl_force_binding_resolution, Cvoid, (Any, Csize_t), g, world)
34633463
return nothing
34643464
end
34653465

@@ -3477,7 +3477,7 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode,
34773477
# This method is surprisingly hot. For performance, don't ask the runtime to resolve
34783478
# the binding unless necessary - doing so triggers an additional lookup, which though
34793479
# not super expensive is hot enough to show up in benchmarks.
3480-
force_binding_resolution!(g)
3480+
force_binding_resolution!(g, min_world(worlds))
34813481
return abstract_eval_globalref_type(g, src, false)
34823482
end
34833483
# return Union{}
@@ -3490,7 +3490,7 @@ function abstract_eval_globalref_type(g::GlobalRef, src::Union{CodeInfo, IRCode,
34903490
end
34913491

34923492
function lookup_binding_partition!(interp::AbstractInterpreter, g::GlobalRef, sv::AbsIntState)
3493-
force_binding_resolution!(g)
3493+
force_binding_resolution!(g, get_inference_world(interp))
34943494
partition = lookup_binding_partition(get_inference_world(interp), g)
34953495
update_valid_age!(sv, WorldRange(partition.min_world, partition.max_world))
34963496
partition
@@ -3539,7 +3539,8 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, saw_
35393539
partition = abstract_eval_binding_partition!(interp, g, sv)
35403540
ret = abstract_eval_partition_load(interp, partition)
35413541
if ret.rt !== Union{} && ret.exct === UndefVarError && InferenceParams(interp).assume_bindings_static
3542-
if isdefined(g, :binding) && isdefined(g.binding, :value)
3542+
b = convert(Core.Binding, g)
3543+
if isdefined(b, :value)
35433544
ret = RTEffects(ret.rt, Union{}, Effects(generic_getglobal_effects, nothrow=true))
35443545
end
35453546
# We do not assume in general that assigned global bindings remain assigned.

Compiler/src/optimize.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1286,7 +1286,7 @@ function convert_to_ircode(ci::CodeInfo, sv::OptimizationState)
12861286
# types of call arguments only once `slot2reg` converts this `IRCode` to the SSA form
12871287
# and eliminates slots (see below)
12881288
argtypes = sv.slottypes
1289-
return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes, WorldRange(ci.min_world, ci.max_world))
1289+
return IRCode(stmts, sv.cfg, di, argtypes, meta, sv.sptypes, world_range(ci))
12901290
end
12911291

12921292
function process_meta!(meta::Vector{Expr}, @nospecialize stmt)

Compiler/src/ssair/ir.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ struct IRCode
434434
function IRCode(stmts::InstructionStream, cfg::CFG, debuginfo::DebugInfoStream,
435435
argtypes::Vector{Any}, meta::Vector{Expr}, sptypes::Vector{VarState},
436436
valid_worlds=WorldRange(typemin(UInt), typemax(UInt)))
437-
return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta)
437+
return new(stmts, argtypes, sptypes, debuginfo, cfg, NewNodeStream(), meta, valid_worlds)
438438
end
439439
function IRCode(ir::IRCode, stmts::InstructionStream, cfg::CFG, new_nodes::NewNodeStream)
440440
di = ir.debuginfo
@@ -1462,7 +1462,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr
14621462
result[result_idx][:stmt] = GotoNode(label)
14631463
result_idx += 1
14641464
elseif isa(stmt, GlobalRef)
1465-
total_flags = IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE
1465+
total_flags = IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
14661466
flag = result[result_idx][:flag]
14671467
if has_flag(flag, total_flags)
14681468
ssa_rename[idx] = stmt

Compiler/src/ssair/legacy.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function inflate_ir!(ci::CodeInfo, sptypes::Vector{VarState}, argtypes::Vector{A
4444
di = DebugInfoStream(nothing, ci.debuginfo, nstmts)
4545
stmts = InstructionStream(code, ssavaluetypes, info, di.codelocs, ci.ssaflags)
4646
meta = Expr[]
47-
return IRCode(stmts, cfg, di, argtypes, meta, sptypes, WorldRange(ci.min_world, ci.max_world))
47+
return IRCode(stmts, cfg, di, argtypes, meta, sptypes, world_range(ci))
4848
end
4949

5050
"""

Compiler/src/ssair/verify.jl

+9-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ end
1212

1313
if !isdefined(@__MODULE__, Symbol("@verify_error"))
1414
macro verify_error(arg)
15-
arg isa String && return esc(:(print && println(stderr, $arg)))
15+
arg isa String && return esc(:(print && println($(GlobalRef(Core, :stderr)), $arg)))
1616
isexpr(arg, :string) || error("verify_error macro expected a string expression")
1717
pushfirst!(arg.args, GlobalRef(Core, :stderr))
1818
pushfirst!(arg.args, :println)
@@ -61,8 +61,14 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int,
6161
raise_error()
6262
end
6363
elseif isa(op, GlobalRef)
64-
if !isdefined(op.mod, op.name) || !isconst(op.mod, op.name)
65-
@verify_error "Unbound GlobalRef not allowed in value position"
64+
force_binding_resolution!(op, min_world(ir.valid_worlds))
65+
bpart = lookup_binding_partition(min_world(ir.valid_worlds), op)
66+
while is_some_imported(binding_kind(bpart)) && max_world(ir.valid_worlds) <= bpart.max_world
67+
imported_binding = partition_restriction(bpart)::Core.Binding
68+
bpart = lookup_binding_partition(min_world(ir.valid_worlds), imported_binding)
69+
end
70+
if !is_defined_const_binding(binding_kind(bpart)) || (bpart.max_world < max_world(ir.valid_worlds))
71+
@verify_error "Unbound or partitioned GlobalRef not allowed in value position"
6672
raise_error()
6773
end
6874
elseif isa(op, Expr)

Compiler/src/tfuncs.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ function _getfield_tfunc_const(@nospecialize(sv), name::Const)
11091109
if isa(sv, DataType) && nv == DATATYPE_TYPES_FIELDINDEX && isdefined(sv, nv)
11101110
return Const(getfield(sv, nv))
11111111
end
1112-
if isconst(typeof(sv), nv)
1112+
if !isa(sv, Module) && isconst(typeof(sv), nv)
11131113
if isdefined(sv, nv)
11141114
return Const(getfield(sv, nv))
11151115
end

Compiler/test/inline.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ end
149149

150150
(src, _) = only(code_typed(sum27403, Tuple{Vector{Int}}))
151151
@test !any(src.code) do x
152-
x isa Expr && x.head === :invoke && x.args[2] !== Core.GlobalRef(Base, :throw_boundserror)
152+
x isa Expr && x.head === :invoke && !(x.args[2] in (Core.GlobalRef(Base, :throw_boundserror), Base.throw_boundserror))
153153
end
154154
end
155155

@@ -313,7 +313,7 @@ end
313313
const _a_global_array = [1]
314314
f_inline_global_getindex() = _a_global_array[1]
315315
let ci = code_typed(f_inline_global_getindex, Tuple{})[1].first
316-
@test any(x->(isexpr(x, :call) && x.args[1] === GlobalRef(Base, :memoryrefget)), ci.code)
316+
@test any(x->(isexpr(x, :call) && x.args[1] in (GlobalRef(Base, :memoryrefget), Base.memoryrefget)), ci.code)
317317
end
318318

319319
# Issue #29114 & #36087 - Inlining of non-tuple splats

base/Base.jl

+1
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ end
401401
# we know whether the .ji can just give the Base copy or not.
402402
# TODO: We may want to do this earlier to avoid TOCTOU issues.
403403
const _compiler_require_dependencies = Any[]
404+
@Core.latestworld
404405
for i = 1:length(_included_files)
405406
isassigned(_included_files, i) || continue
406407
(mod, file) = _included_files[i]

base/client.jl

+6-6
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ function exec_options(opts)
299299
elseif cmd == 'm'
300300
entrypoint = push!(split(arg, "."), "main")
301301
Base.eval(Main, Expr(:import, Expr(:., Symbol.(entrypoint)...)))
302-
if !should_use_main_entrypoint()
302+
if !invokelatest(should_use_main_entrypoint)
303303
error("`main` in `$arg` not declared as entry point (use `@main` to do so)")
304304
end
305305
return false
@@ -408,8 +408,7 @@ function load_InteractiveUtils(mod::Module=Main)
408408
return nothing
409409
end
410410
end
411-
Core.eval(mod, :(using Base.MainInclude.InteractiveUtils))
412-
return MainInclude.InteractiveUtils
411+
return Core.eval(mod, :(using Base.MainInclude.InteractiveUtils; Base.MainInclude.InteractiveUtils))
413412
end
414413

415414
function load_REPL()
@@ -556,11 +555,12 @@ function _start()
556555
local ret = 0
557556
try
558557
repl_was_requested = exec_options(JLOptions())
559-
if should_use_main_entrypoint() && !is_interactive
558+
if invokelatest(should_use_main_entrypoint) && !is_interactive
559+
main = invokelatest(getglobal, Main, :main)
560560
if Base.generating_output()
561-
precompile(Main.main, (typeof(ARGS),))
561+
precompile(main, (typeof(ARGS),))
562562
else
563-
ret = invokelatest(Main.main, ARGS)
563+
ret = invokelatest(main, ARGS)
564564
end
565565
elseif (repl_was_requested || is_interactive)
566566
# Run the Base `main`, which will either load the REPL stdlib

base/essentials.jl

+6
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,9 @@ arbitrary code in fixed worlds. `world` may be `UnitRange`, in which case the ma
12661266
will error unless the binding is valid and has the same value across the entire world
12671267
range.
12681268
1269+
As a special case, the world `∞` always refers to the latest world, even if that world
1270+
is newer than the world currently running.
1271+
12691272
The `@world` macro is primarily used in the printing of bindings that are no longer
12701273
available in the current world.
12711274
@@ -1290,6 +1293,9 @@ julia> fold
12901293
This functionality requires at least Julia 1.12.
12911294
"""
12921295
macro world(sym, world)
1296+
if world == :∞
1297+
world = Expr(:call, get_world_counter)
1298+
end
12931299
if isa(sym, Symbol)
12941300
return :($(_resolve_in_world)($(esc(world)), $(QuoteNode(GlobalRef(__module__, sym)))))
12951301
elseif isa(sym, GlobalRef)

base/loading.jl

+13-9
Original file line numberDiff line numberDiff line change
@@ -2364,6 +2364,18 @@ function require(into::Module, mod::Symbol)
23642364
return invoke_in_world(world, __require, into, mod)
23652365
end
23662366

2367+
function check_for_hint(into, mod)
2368+
return begin
2369+
if isdefined(into, mod) && getfield(into, mod) isa Module
2370+
true, "."
2371+
elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
2372+
true, ".."
2373+
else
2374+
false, ""
2375+
end
2376+
end
2377+
end
2378+
23672379
function __require(into::Module, mod::Symbol)
23682380
if into === __toplevel__ && generating_output(#=incremental=#true)
23692381
error("`using/import $mod` outside of a Module detected. Importing a package outside of a module \
@@ -2377,15 +2389,7 @@ function __require(into::Module, mod::Symbol)
23772389
if uuidkey_env === nothing
23782390
where = PkgId(into)
23792391
if where.uuid === nothing
2380-
hint, dots = begin
2381-
if isdefined(into, mod) && getfield(into, mod) isa Module
2382-
true, "."
2383-
elseif isdefined(parentmodule(into), mod) && getfield(parentmodule(into), mod) isa Module
2384-
true, ".."
2385-
else
2386-
false, ""
2387-
end
2388-
end
2392+
hint, dots = invokelatest(check_for_hint, into, mod)
23892393
hint_message = hint ? ", maybe you meant `import/using $(dots)$(mod)`" : ""
23902394
install_message = if mod != :Pkg
23912395
start_sentence = hint ? "Otherwise, run" : "Run"

base/logging/logging.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ function logmsg_code(_module, file, line, level, message, exs...)
372372
kwargs = (;$(log_data.kwargs...))
373373
true
374374
else
375-
@invokelatest logging_error(logger, level, _module, group, id, file, line, err, false)
375+
@invokelatest $(logging_error)(logger, level, _module, group, id, file, line, err, false)
376376
false
377377
end
378378
end
@@ -384,7 +384,7 @@ function logmsg_code(_module, file, line, level, message, exs...)
384384
kwargs = (;$(log_data.kwargs...))
385385
true
386386
catch err
387-
@invokelatest logging_error(logger, level, _module, group, id, file, line, err, true)
387+
@invokelatest $(logging_error)(logger, level, _module, group, id, file, line, err, true)
388388
false
389389
end
390390
end

base/reflection.jl

+56-21
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,18 @@ macro invoke(ex)
13621362
return esc(out)
13631363
end
13641364

1365+
apply_gr(gr::GlobalRef, @nospecialize args...) = getglobal(gr.mod, gr.name)(args...)
1366+
apply_gr_kw(@nospecialize(kwargs::NamedTuple), gr::GlobalRef, @nospecialize args...) = Core.kwcall(kwargs, getglobal(gr.mod, gr.name), args...)
1367+
1368+
function invokelatest_gr(gr::GlobalRef, @nospecialize args...; kwargs...)
1369+
@inline
1370+
kwargs = merge(NamedTuple(), kwargs)
1371+
if isempty(kwargs)
1372+
return Core._call_latest(apply_gr, gr, args...)
1373+
end
1374+
return Core._call_latest(apply_gr_kw, kwargs, gr, args...)
1375+
end
1376+
13651377
"""
13661378
@invokelatest f(args...; kwargs...)
13671379
@@ -1375,22 +1387,11 @@ It also supports the following syntax:
13751387
- `@invokelatest xs[i]` expands to `Base.invokelatest(getindex, xs, i)`
13761388
- `@invokelatest xs[i] = v` expands to `Base.invokelatest(setindex!, xs, v, i)`
13771389
1378-
```jldoctest
1379-
julia> @macroexpand @invokelatest f(x; kw=kwv)
1380-
:(Base.invokelatest(f, x; kw = kwv))
1381-
1382-
julia> @macroexpand @invokelatest x.f
1383-
:(Base.invokelatest(Base.getproperty, x, :f))
1384-
1385-
julia> @macroexpand @invokelatest x.f = v
1386-
:(Base.invokelatest(Base.setproperty!, x, :f, v))
1387-
1388-
julia> @macroexpand @invokelatest xs[i]
1389-
:(Base.invokelatest(Base.getindex, xs, i))
1390-
1391-
julia> @macroexpand @invokelatest xs[i] = v
1392-
:(Base.invokelatest(Base.setindex!, xs, v, i))
1393-
```
1390+
!!! note
1391+
If `f` is a global, it will be resolved consistently
1392+
in the (latest) world as the call target. However, all other arguments
1393+
(as well as `f` itself if it is not a literal global) will be evaluated
1394+
in the current world age.
13941395
13951396
!!! compat "Julia 1.7"
13961397
This macro requires Julia 1.7 or later.
@@ -1404,11 +1405,45 @@ julia> @macroexpand @invokelatest xs[i] = v
14041405
macro invokelatest(ex)
14051406
topmod = _topmod(__module__)
14061407
f, args, kwargs = destructure_callex(topmod, ex)
1407-
out = Expr(:call, GlobalRef(Base, :invokelatest))
1408-
isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...))
1409-
push!(out.args, f)
1410-
append!(out.args, args)
1411-
return esc(out)
1408+
1409+
if !isa(f, GlobalRef)
1410+
out_f = Expr(:call, GlobalRef(Base, :invokelatest))
1411+
isempty(kwargs) || push!(out_f.args, Expr(:parameters, kwargs...))
1412+
1413+
if isexpr(f, :(.))
1414+
s = gensym()
1415+
check = quote
1416+
$s = $(f.args[1])
1417+
isa($s, Module)
1418+
end
1419+
push!(out_f.args, Expr(:(.), s, f.args[2]))
1420+
else
1421+
push!(out_f.args, f)
1422+
end
1423+
append!(out_f.args, args)
1424+
1425+
if @isdefined(s)
1426+
f = :(GlobalRef($s, $(f.args[2])))
1427+
elseif !isa(f, Symbol)
1428+
return esc(out_f)
1429+
else
1430+
check = :($(Expr(:isglobal, f)))
1431+
end
1432+
end
1433+
1434+
out_gr = Expr(:call, GlobalRef(Base, :invokelatest_gr))
1435+
isempty(kwargs) || push!(out_gr.args, Expr(:parameters, kwargs...))
1436+
push!(out_gr.args, isa(f, GlobalRef) ? QuoteNode(f) :
1437+
isa(f, Symbol) ? QuoteNode(GlobalRef(__module__, f)) :
1438+
f)
1439+
append!(out_gr.args, args)
1440+
1441+
if isa(f, GlobalRef)
1442+
return esc(out_gr)
1443+
end
1444+
1445+
# f::Symbol
1446+
return esc(:($check ? $out_gr : $out_f))
14121447
end
14131448

14141449
function destructure_callex(topmod::Module, @nospecialize(ex))

base/runtime_internals.jl

+12-4
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,16 @@ function lookup_binding_partition(world::UInt, b::Core.Binding)
239239
ccall(:jl_get_binding_partition, Ref{Core.BindingPartition}, (Any, UInt), b, world)
240240
end
241241

242-
function lookup_binding_partition(world::UInt, gr::Core.GlobalRef)
242+
function convert(::Type{Core.Binding}, gr::Core.GlobalRef)
243243
if isdefined(gr, :binding)
244-
b = gr.binding
244+
return gr.binding
245245
else
246-
b = ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), gr.mod, gr.name, true)
246+
return ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), gr.mod, gr.name, true)
247247
end
248+
end
249+
250+
function lookup_binding_partition(world::UInt, gr::Core.GlobalRef)
251+
b = convert(Core.Binding, gr)
248252
return lookup_binding_partition(world, b)
249253
end
250254

@@ -420,7 +424,11 @@ end
420424
"""
421425
isconst(t::DataType, s::Union{Int,Symbol}) -> Bool
422426
423-
Determine whether a field `s` is declared `const` in a given type `t`.
427+
Determine whether a field `s` is const in a given type `t`
428+
in the sense that a read from said field is consistent
429+
for egal objects. Note in particular that out-of-bounds
430+
fields are considered const under this definition (because
431+
they always throw).
424432
"""
425433
function isconst(@nospecialize(t::Type), s::Symbol)
426434
@_foldable_meta

0 commit comments

Comments
 (0)