Skip to content

Commit a46c5e0

Browse files
committed
bpart: Redesign representation of implicit imports
# Current Design When we explicitly import a binding (via either `using M: x` or `import`), the corresponding bpart representation is a direct pointer to the imported binding. This is necessary, because that is the precise semantic representation of the import. However, for implicit imports (those created via `using M` without explicit symbol name), the situation is a bit different. Here, there is no semantic content to the particular binding being imported. Worse, the binding is not necessarily unique. Our current semantics are essentially that we walk the entire import graph (both explicit and implicit edges) and if all reachable terminal nodes are either (i) the same binding or (ii) constant bindings with the same value, the import is allowed. In this, we are supposed to ignore cycles, although the current implementation has some trouble with this (#57638, #57699). If the import succeeds, in the current implementation, we then record an arbitrary implicit import edge as in the BindingPartition. In essence, we are creating a spanning tree of the import graph (formed from both the implicit and explicit import edges). However, dynamic algorithms for spanning tree maintenance are complicated and we didn't implement any. As a result, it is possible for later edge additions to accidentally introduce cycles (causing #57699). An additional problem, even if we could keep a consistent spanning tree, is that the spanning tree is not unique. In practice, this is not supposed to be observable, but it is still odd to have a non-unique representation for a particular set of imports. That said, we don't really need the spanning tree. The only place that actually uses it is `which(::Module, ::Symbol)` which is not performance sensitive and arguably a bad API for the above reason that the answer is ill-defined. # This PR With all these problems, let's just get rid of the spanning tree all together - as mentioned we don't really need it. Instead, we split the PARTITION_KIND_IMPLICIT into two, one for each of the two cases (importing a global binding, or importing a particular constant value). This is actually a more efficient implementation for the common case of needing to follow a lookup - we no longer need to follow all the import edges. In exchange, more work needs to be done during binding replacement to re-scan the entire import graph. This is probably the right trade-off though, since binding replacement is already a slow path. Fixes #57638, #57699
1 parent bbb0596 commit a46c5e0

20 files changed

+532
-307
lines changed

Compiler/src/Compiler.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ else
3535

3636
@eval baremodule Compiler
3737

38-
# Needs to match UUID defined in Project.toml
39-
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Compiler,
40-
(0x807dbc54_b67e_4c79, 0x8afb_eafe4df6f2e1))
41-
4238
using Core.Intrinsics, Core.IR
4339

4440
using Core: ABIOverride, Builtin, CodeInstance, IntrinsicFunction, MethodInstance, MethodMatch,
@@ -61,7 +57,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali
6157
generating_output, get_nospecializeinfer_sig, get_world_counter, has_free_typevars,
6258
hasgenerator, hasintersect, indexed_iterate, isType, is_file_tracked, is_function_def,
6359
is_meta_expr, is_meta_expr_head, is_nospecialized, is_nospecializeinfer, is_defined_const_binding,
64-
is_some_const_binding, is_some_guard, is_some_imported, is_valid_intrinsic_elptr,
60+
is_some_const_binding, is_some_guard, is_some_imported, is_some_explicit_imported, is_some_binding_imported, is_valid_intrinsic_elptr,
6561
isbitsunion, isconcretedispatch, isdispatchelem, isexpr, isfieldatomic, isidentityfree,
6662
iskindtype, ismutabletypename, ismutationfree, issingletontype, isvarargtype, isvatuple,
6763
kwerr, lookup_binding_partition, may_invoke_generator, methods, midpoint, moduleroot,
@@ -76,6 +72,10 @@ import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, ge
7672
getindex, haskey, in, isempty, isready, iterate, iterate, last, length, max_world,
7773
min_world, popfirst!, push!, resize!, setindex!, size, intersect
7874

75+
# Needs to match UUID defined in Project.toml
76+
ccall(:jl_set_module_uuid, Cvoid, (Any, NTuple{2, UInt64}), Compiler,
77+
(0x807dbc54_b67e_4c79, 0x8afb_eafe4df6f2e1))
78+
7979
const getproperty = Core.getfield
8080
const setproperty! = Core.setfield!
8181
const swapproperty! = Core.swapfield!
@@ -131,7 +131,7 @@ something(x::Any, y...) = x
131131
############
132132

133133
baremodule BuildSettings
134-
using Core: ARGS, include
134+
using Core: ARGS, include, Int, ===
135135
using ..Compiler: >, getindex, length
136136

137137
global MAX_METHODS::Int = 3
@@ -191,7 +191,7 @@ macro __SOURCE_FILE__()
191191
end
192192

193193
module IRShow end # relies on string and IO operations defined in Base
194-
baremodule TrimVerifier end # relies on IRShow, so define this afterwards
194+
baremodule TrimVerifier using Core end # relies on IRShow, so define this afterwards
195195

196196
if isdefined(Base, :end_base_include)
197197
# When this module is loaded as the standard library, include these files as usual

Compiler/src/abstractinterpretation.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3287,7 +3287,7 @@ function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, mod::Module,
32873287
if allow_import !== true
32883288
gr = GlobalRef(mod, sym)
32893289
partition = lookup_binding_partition!(interp, gr, sv)
3290-
if allow_import !== true && is_some_imported(binding_kind(partition))
3290+
if allow_import !== true && is_some_binding_imported(binding_kind(partition))
32913291
if allow_import === false
32923292
rt = Const(false)
32933293
else
@@ -3540,7 +3540,7 @@ end
35403540

35413541
function walk_binding_partition(imported_binding::Core.Binding, partition::Core.BindingPartition, world::UInt)
35423542
valid_worlds = WorldRange(partition.min_world, partition.max_world)
3543-
while is_some_imported(binding_kind(partition))
3543+
while is_some_binding_imported(binding_kind(partition))
35443544
imported_binding = partition_restriction(partition)::Core.Binding
35453545
partition = lookup_binding_partition(world, imported_binding)
35463546
valid_worlds = intersect(valid_worlds, WorldRange(partition.min_world, partition.max_world))

Compiler/src/ssair/EscapeAnalysis.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ using Base: Base
1515
# imports
1616
import Base: ==, copy, getindex, setindex!
1717
# usings
18+
using Core
1819
using Core: Builtin, IntrinsicFunction, SimpleVector, ifelse, sizeof
1920
using Core.IR
2021
using Base: # Base definitions

Compiler/test/codegen.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,3 +1007,23 @@ let
10071007
end
10081008
nothing
10091009
end
1010+
1011+
# Test that turning an implicit import into an explicit one doesn't pessimize codegen
1012+
module TurnedIntoExplicit
1013+
using Test
1014+
import ..get_llvm
1015+
1016+
module ReExportBitCast
1017+
export bitcast
1018+
import Base: bitcast
1019+
end
1020+
using .ReExportBitCast
1021+
1022+
f(x::UInt) = bitcast(Float64, x)
1023+
1024+
@test !occursin("jl_apply_generic", get_llvm(f, Tuple{UInt}))
1025+
1026+
import Base: bitcast
1027+
1028+
@test !occursin("jl_apply_generic", get_llvm(f, Tuple{UInt}))
1029+
end

base/Base_compiler.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,18 +277,21 @@ include("operators.jl")
277277
include("pointer.jl")
278278
include("refvalue.jl")
279279
include("cmem.jl")
280+
281+
function nextfloat end
282+
function prevfloat end
280283
include("rounding.jl")
281284
include("float.jl")
282285

283-
include("checked.jl")
284-
using .Checked
285-
function cld end
286-
function fld end
287-
288286
# Lazy strings
289287
import Core: String
290288
include("strings/lazy.jl")
291289

290+
function cld end
291+
function fld end
292+
include("checked.jl")
293+
using .Checked
294+
292295
# array structures
293296
include("indices.jl")
294297
include("genericmemory.jl")
@@ -373,3 +376,4 @@ Core._setparser!(fl_parse)
373376
# Further definition of Base will happen in Base.jl if loaded.
374377

375378
end # baremodule Base
379+
using .Base

base/errorshow.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1161,7 +1161,7 @@ function UndefVarError_hint(io::IO, ex::UndefVarError)
11611161
"with the module it should come from.")
11621162
elseif kind === Base.PARTITION_KIND_GUARD
11631163
print(io, "\nSuggestion: check for spelling errors or missing imports.")
1164-
elseif Base.is_some_imported(kind)
1164+
elseif Base.is_some_explicit_imported(kind)
11651165
print(io, "\nSuggestion: this global was defined as `$(Base.partition_restriction(bpart).globalref)` but not assigned a value.")
11661166
end
11671167
elseif scope === :static_parameter

base/invalidation.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core
151151
latest_bpart = edge.partitions
152152
latest_bpart.max_world == typemax(UInt) || continue
153153
is_some_imported(binding_kind(latest_bpart)) || continue
154-
partition_restriction(latest_bpart) === b || continue
154+
if is_some_binding_imported(binding_kind(latest_bpart))
155+
partition_restriction(latest_bpart) === b || continue
156+
end
155157
invalidate_code_for_globalref!(edge, latest_bpart, latest_bpart, new_max_world)
156158
else
157159
invalidate_method_for_globalref!(gr, edge::Method, invalidated_bpart, new_max_world)
@@ -171,9 +173,9 @@ function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core
171173
isdefined(user_binding, :partitions) || continue
172174
latest_bpart = user_binding.partitions
173175
latest_bpart.max_world == typemax(UInt) || continue
174-
binding_kind(latest_bpart) in (PARTITION_KIND_IMPLICIT, PARTITION_KIND_FAILED, PARTITION_KIND_GUARD) || continue
176+
is_some_implicit(binding_kind(latest_bpart)) || continue
175177
new_bpart = need_to_invalidate_export ?
176-
ccall(:jl_maybe_reresolve_implicit, Any, (Any, Any, Csize_t), user_binding, latest_bpart, new_max_world) :
178+
ccall(:jl_maybe_reresolve_implicit, Any, (Any, Csize_t), user_binding, new_max_world) :
177179
latest_bpart
178180
if need_to_invalidate_code || new_bpart !== latest_bpart
179181
invalidate_code_for_globalref!(convert(Core.Binding, user_binding), latest_bpart, new_bpart, new_max_world)

base/iterators.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ using .Base:
1717
any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex,
1818
tail, fieldtypes, min, max, minimum, zero, oneunit, promote, promote_shape, LazyString,
1919
afoldl
20+
using Core
2021
using Core: @doc
2122

2223
using .Base:

base/runtime_internals.jl

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,18 @@ function _fieldnames(@nospecialize t)
197197
end
198198

199199
# N.B.: Needs to be synced with julia.h
200-
const PARTITION_KIND_CONST = 0x0
201-
const PARTITION_KIND_CONST_IMPORT = 0x1
202-
const PARTITION_KIND_GLOBAL = 0x2
203-
const PARTITION_KIND_IMPLICIT = 0x3
204-
const PARTITION_KIND_EXPLICIT = 0x4
205-
const PARTITION_KIND_IMPORTED = 0x5
206-
const PARTITION_KIND_FAILED = 0x6
207-
const PARTITION_KIND_DECLARED = 0x7
208-
const PARTITION_KIND_GUARD = 0x8
209-
const PARTITION_KIND_UNDEF_CONST = 0x9
210-
const PARTITION_KIND_BACKDATED_CONST = 0xa
200+
const PARTITION_KIND_CONST = 0x0
201+
const PARTITION_KIND_CONST_IMPORT = 0x1
202+
const PARTITION_KIND_GLOBAL = 0x2
203+
const PARTITION_KIND_IMPLICIT_GLOBAL = 0x3
204+
const PARTITION_KIND_IMPLICIT_CONST = 0x4
205+
const PARTITION_KIND_EXPLICIT = 0x5
206+
const PARTITION_KIND_IMPORTED = 0x6
207+
const PARTITION_KIND_FAILED = 0x7
208+
const PARTITION_KIND_DECLARED = 0x8
209+
const PARTITION_KIND_GUARD = 0x9
210+
const PARTITION_KIND_UNDEF_CONST = 0xa
211+
const PARTITION_KIND_BACKDATED_CONST = 0xb
211212

212213
const PARTITION_FLAG_EXPORTED = 0x10
213214
const PARTITION_FLAG_DEPRECATED = 0x20
@@ -218,9 +219,12 @@ const PARTITION_MASK_FLAG = 0xf0
218219

219220
const BINDING_FLAG_ANY_IMPLICIT_EDGES = 0x8
220221

221-
is_defined_const_binding(kind::UInt8) = (kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_BACKDATED_CONST)
222+
is_defined_const_binding(kind::UInt8) = (kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_BACKDATED_CONST)
222223
is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == PARTITION_KIND_UNDEF_CONST)
223-
is_some_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED)
224+
is_some_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED)
225+
is_some_implicit(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED)
226+
is_some_explicit_imported(kind::UInt8) = (kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED)
227+
is_some_binding_imported(kind::UInt8) = is_some_explicit_imported(kind) || kind == PARTITION_KIND_IMPLICIT_GLOBAL
224228
is_some_guard(kind::UInt8) = (kind == PARTITION_KIND_GUARD || kind == PARTITION_KIND_FAILED || kind == PARTITION_KIND_UNDEF_CONST)
225229

226230
function lookup_binding_partition(world::UInt, b::Core.Binding)

base/show.jl

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,14 +1030,20 @@ end
10301030
function isvisible(sym::Symbol, parent::Module, from::Module)
10311031
isdeprecated(parent, sym) && return false
10321032
isdefinedglobal(from, sym) || return false
1033+
isdefinedglobal(parent, sym) || return false
10331034
parent_binding = convert(Core.Binding, GlobalRef(parent, sym))
10341035
from_binding = convert(Core.Binding, GlobalRef(from, sym))
10351036
while true
10361037
from_binding === parent_binding && return true
10371038
partition = lookup_binding_partition(tls_world_age(), from_binding)
1038-
is_some_imported(binding_kind(partition)) || break
1039+
is_some_explicit_imported(binding_kind(partition)) || break
10391040
from_binding = partition_restriction(partition)::Core.Binding
10401041
end
1042+
parent_partition = lookup_binding_partition(tls_world_age(), parent_binding)
1043+
from_partition = lookup_binding_partition(tls_world_age(), from_binding)
1044+
if is_defined_const_binding(binding_kind(parent_partition)) && is_defined_const_binding(binding_kind(from_partition))
1045+
return parent_partition.restriction === from_partition.restriction
1046+
end
10411047
return false
10421048
end
10431049

@@ -3405,7 +3411,7 @@ function print_partition(io::IO, partition::Core.BindingPartition)
34053411
if kind == PARTITION_KIND_BACKDATED_CONST
34063412
print(io, "backdated constant binding to ")
34073413
print(io, partition_restriction(partition))
3408-
elseif is_defined_const_binding(kind)
3414+
elseif kind == PARTITION_KIND_CONST
34093415
print(io, "constant binding to ")
34103416
print(io, partition_restriction(partition))
34113417
elseif kind == PARTITION_KIND_UNDEF_CONST
@@ -3416,9 +3422,12 @@ function print_partition(io::IO, partition::Core.BindingPartition)
34163422
print(io, "ambiguous binding - guard entry")
34173423
elseif kind == PARTITION_KIND_DECLARED
34183424
print(io, "weak global binding declared using `global` (implicit type Any)")
3419-
elseif kind == PARTITION_KIND_IMPLICIT
3420-
print(io, "implicit `using` from ")
3425+
elseif kind == PARTITION_KIND_IMPLICIT_GLOBAL
3426+
print(io, "implicit `using` resolved to global ")
34213427
print(io, partition_restriction(partition).globalref)
3428+
elseif kind == PARTITION_KIND_IMPLICIT_CONST
3429+
print(io, "implicit `using` resolved to constant ")
3430+
print(io, partition_restriction(partition))
34223431
elseif kind == PARTITION_KIND_EXPLICIT
34233432
print(io, "explicit `using` from ")
34243433
print(io, partition_restriction(partition).globalref)
@@ -3441,7 +3450,7 @@ function show(io::IO, ::MIME"text/plain", bnd::Core.Binding)
34413450
print(io, "Binding ")
34423451
print(io, bnd.globalref)
34433452
if !isdefined(bnd, :partitions)
3444-
print(io, "No partitions")
3453+
print(io, " - No partitions")
34453454
else
34463455
partition = @atomic bnd.partitions
34473456
while true

0 commit comments

Comments
 (0)