Skip to content

Commit ed23a99

Browse files
KenoKristofferC
authored andcommitted
bpart: Redesign representation of implicit imports (#57755)
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. 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, fixes #57699, fixes #57641, fixes #57700, fixes #57343 (cherry picked from commit 60a9f69)
1 parent e4e489a commit ed23a99

20 files changed

+533
-308
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,
@@ -75,6 +71,10 @@ import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, ge
7571
getindex, haskey, in, isempty, isready, iterate, iterate, last, length, max_world,
7672
min_world, popfirst!, push!, resize!, setindex!, size, intersect
7773

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

132132
baremodule BuildSettings
133-
using Core: ARGS, include
133+
using Core: ARGS, include, Int, ===
134134
using ..Compiler: >, getindex, length
135135

136136
global MAX_METHODS::Int = 3
@@ -190,7 +190,7 @@ macro __SOURCE_FILE__()
190190
end
191191

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

195195
if isdefined(Base, :end_base_include)
196196
# 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
@@ -3242,7 +3242,7 @@ function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, mod::Module,
32423242
if allow_import !== true
32433243
gr = GlobalRef(mod, sym)
32443244
partition = lookup_binding_partition!(interp, gr, sv)
3245-
if allow_import !== true && is_some_imported(binding_kind(partition))
3245+
if allow_import !== true && is_some_binding_imported(binding_kind(partition))
32463246
if allow_import === false
32473247
rt = Const(false)
32483248
else
@@ -3495,7 +3495,7 @@ end
34953495

34963496
function walk_binding_partition(imported_binding::Core.Binding, partition::Core.BindingPartition, world::UInt)
34973497
valid_worlds = WorldRange(partition.min_world, partition.max_world)
3498-
while is_some_imported(binding_kind(partition))
3498+
while is_some_binding_imported(binding_kind(partition))
34993499
imported_binding = partition_restriction(partition)::Core.Binding
35003500
partition = lookup_binding_partition(world, imported_binding)
35013501
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
@@ -1157,7 +1157,7 @@ function UndefVarError_hint(io::IO, ex::UndefVarError)
11571157
"with the module it should come from.")
11581158
elseif kind === Base.PARTITION_KIND_GUARD
11591159
print(io, "\nSuggestion: check for spelling errors or missing imports.")
1160-
elseif Base.is_some_imported(kind)
1160+
elseif Base.is_some_explicit_imported(kind)
11611161
print(io, "\nSuggestion: this global was defined as `$(Base.partition_restriction(bpart).globalref)` but not assigned a value.")
11621162
end
11631163
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
@@ -1023,14 +1023,20 @@ end
10231023
function isvisible(sym::Symbol, parent::Module, from::Module)
10241024
isdeprecated(parent, sym) && return false
10251025
isdefinedglobal(from, sym) || return false
1026+
isdefinedglobal(parent, sym) || return false
10261027
parent_binding = convert(Core.Binding, GlobalRef(parent, sym))
10271028
from_binding = convert(Core.Binding, GlobalRef(from, sym))
10281029
while true
10291030
from_binding === parent_binding && return true
10301031
partition = lookup_binding_partition(tls_world_age(), from_binding)
1031-
is_some_imported(binding_kind(partition)) || break
1032+
is_some_explicit_imported(binding_kind(partition)) || break
10321033
from_binding = partition_restriction(partition)::Core.Binding
10331034
end
1035+
parent_partition = lookup_binding_partition(tls_world_age(), parent_binding)
1036+
from_partition = lookup_binding_partition(tls_world_age(), from_binding)
1037+
if is_defined_const_binding(binding_kind(parent_partition)) && is_defined_const_binding(binding_kind(from_partition))
1038+
return parent_partition.restriction === from_partition.restriction
1039+
end
10341040
return false
10351041
end
10361042

@@ -3391,7 +3397,7 @@ function print_partition(io::IO, partition::Core.BindingPartition)
33913397
if kind == PARTITION_KIND_BACKDATED_CONST
33923398
print(io, "backdated constant binding to ")
33933399
print(io, partition_restriction(partition))
3394-
elseif is_defined_const_binding(kind)
3400+
elseif kind == PARTITION_KIND_CONST
33953401
print(io, "constant binding to ")
33963402
print(io, partition_restriction(partition))
33973403
elseif kind == PARTITION_KIND_UNDEF_CONST
@@ -3402,9 +3408,12 @@ function print_partition(io::IO, partition::Core.BindingPartition)
34023408
print(io, "ambiguous binding - guard entry")
34033409
elseif kind == PARTITION_KIND_DECLARED
34043410
print(io, "weak global binding declared using `global` (implicit type Any)")
3405-
elseif kind == PARTITION_KIND_IMPLICIT
3406-
print(io, "implicit `using` from ")
3411+
elseif kind == PARTITION_KIND_IMPLICIT_GLOBAL
3412+
print(io, "implicit `using` resolved to global ")
34073413
print(io, partition_restriction(partition).globalref)
3414+
elseif kind == PARTITION_KIND_IMPLICIT_CONST
3415+
print(io, "implicit `using` resolved to constant ")
3416+
print(io, partition_restriction(partition))
34083417
elseif kind == PARTITION_KIND_EXPLICIT
34093418
print(io, "explicit `using` from ")
34103419
print(io, partition_restriction(partition).globalref)
@@ -3427,7 +3436,7 @@ function show(io::IO, ::MIME"text/plain", bnd::Core.Binding)
34273436
print(io, "Binding ")
34283437
print(io, bnd.globalref)
34293438
if !isdefined(bnd, :partitions)
3430-
print(io, "No partitions")
3439+
print(io, " - No partitions")
34313440
else
34323441
partition = @atomic bnd.partitions
34333442
while true

0 commit comments

Comments
 (0)