-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpart: Redesign representation of implicit imports #57755
Conversation
d7b361b
to
b7b70c5
Compare
Probably a good idea to run a full PkgEval here to not jump around between local minima on the backport branch too much. |
a5af611
to
9f0919f
Compare
9f0919f
to
6f0a4a4
Compare
@nanosoldier |
The package evaluation job you requested has completed - possible new issues were detected. Report summary❗ Packages that crashed3 packages crashed on the previous version too. ✖ Packages that failed32 packages failed only on the current version.
1410 packages failed on the previous version too. ✔ Packages that passed tests83 packages passed tests only on the current version.
5072 packages passed tests on the previous version too. ~ Packages that at least loaded71 packages successfully loaded only on the current version.
2813 packages successfully loaded on the previous version too. ➖ Packages that were skipped altogether1 packages were skipped only on the current version.
909 packages were skipped on the previous version too. |
there is at least one remaining bug here
|
Reproducer for that failure:
|
6f0a4a4
to
20dcb25
Compare
@nanosoldier |
The package evaluation job you requested has completed - possible new issues were detected. Report summary✖ Packages that failed7 packages failed only on the current version.
2 packages failed on the previous version too. ✔ Packages that passed tests18 packages passed tests on the previous version too. ~ Packages that at least loaded5 packages successfully loaded on the previous version too. |
There is only one USD definition in https://s3.amazonaws.com/julialang-reports/nanosoldier/pkgeval/by_hash/20dcb25_vs_bbb0596/Miletus.primary.log (at https://github.com/JuliaComputing/Miletus.jl/blob/4e0e63a3728e7de842ad4a5288141815cac1922c/src/currency.jl#L82) I think? So why is there an ambiguity error? (The same with https://s3.amazonaws.com/julialang-reports/nanosoldier/pkgeval/by_hash/20dcb25_vs_bbb0596/AMLPipelineBase.primary.log where it is defined at https://github.com/IBM/AMLPipelineBase.jl/blob/2ae5ea4055b44d0d7732cf1323fd93edf2cf9450/src/utils.jl#L243) |
A handful of packages were blacklisted due to the old bugs, so attempt a few of those explicitly too @nanosoldier |
The Miletus MWE is: module M
export S
module C
export S
struct A end
S = A() # making S const makes the error go away
end
using .C
end
using .M
using .M.C
S
#=
ERROR: UndefVarError: `S` not defined in `Main`
Hint: It looks like two or more modules export different bindings with this name, resulting in ambiguity. Try explicitly importing it from a particular module, or qualifying the name with the module it should come from.
=# Since this only happens for non-const bindings I think it is the same issue for https://github.com/IBM/AMLPipelineBase.jl/blob/2ae5ea4055b44d0d7732cf1323fd93edf2cf9450/src/utils.jl#L243 since that one is also non-const. |
# 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
20dcb25
to
a46c5e0
Compare
@nanosoldier |
The package evaluation job you requested has completed - no new issues were detected. Report summary✖ Packages that failed1 packages failed on the previous version too. ~ Packages that at least loaded2 packages successfully loaded on the previous version too. |
Oh wait, that was the wrong pkgeval. Oh well, I'm pretty sure I fixed it. If there's a remaining issue, I guess I'll do a follow up PR. |
The package evaluation job you requested has completed - possible new issues were detected. Report summary✖ Packages that failed2 packages failed only on the current version.
✔ Packages that passed tests4 packages passed tests on the previous version too. ~ Packages that at least loaded1 packages successfully loaded on the previous version too. |
The error message in https://s3.amazonaws.com/julialang-reports/nanosoldier/pkgeval/by_hash/a46c5e0_vs_bbb0596/ExplicitImports.primary.log
feels quite unclear. What binding? What modules? Why is it an issue to import a constant binding from multiple modules? |
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)
This is the following case:
In this particular example:
which is from this hack here: https://github.com/JuliaLang/julia/blob/master/stdlib/Logging/src/Logging.jl#L11-L30. I'm not entirely sure what to do about this case. It used to return an arbitrary answer between the two of them. We could try to return all the modules and have |
It possibly shouldn't do that either? I do think we may want to also tweak the behavior of |
Fixes a regression introduced in #57755 seen on PkgEval in #57755 (comment)
Fixes a regression introduced in #57755 seen on PkgEval in #57755 (comment)
Fixes a regression introduced in #57755 seen on PkgEval in #57755 (comment) (cherry picked from commit 1d2e165)
Current Design
When we explicitly import a binding (via either
using M: x
orimport
), 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, fixes #57699, fixes #57641, fixes #57700, fixes #57343