-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Serialization] Refactor normal conformance serialization #16023
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
Conversation
@swift-ci please test |
@swift-ci please test source compatibility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commit message is not very clear. It should explain what the two conditions mean in terms of language features used and why the old one was insufficient. Right now you’re just paraphrasing the jargon in the diff.
@@ -0,0 +1,44 @@ | |||
// RUN: %target-build-swift -emit-module -o %t %s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to produce a smaller test case, and it should go in test/Serialization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check how serialization tests are set up but if there is not way to check layout of the binary the test i have is good enough becuase it exposes the problem through one of the substitutions being error type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should still be in test/Serialization since its testing serialization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've put it into crashers because it was a crash, but I can put it where ever you tell me to put it.
include/swift/AST/Witness.h
Outdated
@@ -168,6 +168,11 @@ class Witness { | |||
return storage.get<StoredWitness *>()->syntheticEnvironment; | |||
} | |||
|
|||
/// Determine if the witness may have requirement substitutions. | |||
bool mayHaveRequirementSubstitutions() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does getSyntheticEnvironment not imply requiresSubstitution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it does but not it would actually assert if requiresSubstitution
is not set, so not really suitable for checks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the specific case where requiresSubstitution() is true and getSyntheticEnvironment() is false? What about vice versa? I think it would help to understand what's actually going on here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand requiresSubstitution
covers both requirement and witness substitutions but the latter doesn't require generic env to be present, what is happening in the example I have - a
would not have generic environment but still requires substitutions because it's a static witness (I think). There is a single requirement substitution in the map from S to S (I think because it's static witness) but information about generic env and number of requirement substitutions is not going to be stored altough that single requirement substitution still would which offsets every other substitution by one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this predicate per se, because I think the issue is in the construction of Witness
not establishing invariants assumed by requiresSubstitution()
. Specifically, that the storage is a StoredWitness *
only when there are substitutions or a generic environment (or both), and a ValueDecl *
for the simple case. That the invariant that requiresSubstitution()
assumes, but the most-general Witness
constructor doesn't establish that invariant. Oops.
More importantly, the two things that create Witness
instances---the type checker and deserialization---do so differently: the type checker always goes through the most general Witness
constructor, meaning that it always gets the StoredWitness *
version (for which requiresSubstitution()
will always return true), whereas deserialization uses the simpler Witness(ValueDecl *)
constructor whenever it can---in which case it will have !requiresSubstitution()
when deserializing something that the type checker built as requiresSubstitution()
. Your change fixes the problem because it avoids relying on the invariant (so it checks the underlying condition), but it would be better to
(1) Teach the most-general Witness
constructor to use the simpler ValueDecl *
storage form when it can, and
(2) Eliminate the "optimization" from the deserialization code that tries to guess when to provide only the ValueDecl *
, so the two Witness
-constructing pieces use the same information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might not be understanding completely what is going on here but what you suggest doesn't actually fix the problem (I tried moving if
block from deserialization to Witness general constructor) ... Because if you consider declaration of a
in P3
it does return true
for requiresSubstitution
, doesn't have generic synthetic environment (which seems correct), does have a single synthetic requirement substitution S
, and one witness substitution S
when it's being originally written to partial module so checking just generic signature at one place and requiresSubstitution()
when writing records would produce different results in serialization that's why deserialization produces incorrect results.
It looks like https://github.com/apple/swift/blob/master/lib/Sema/TypeCheckProtocol.cpp#L93-L101 is getting synthetic substitutions independent from presence of synthetic environment, that's why there could be requirement substitutions without synthetic environment, maybe the bug hides in that fact?
More concretely for static method a
synthetic env ref would be 0
and # of requirement substitutions 0
(because synthetic env is absent), number of witness substitutions 1
, but we'd still write one record for both requirement and witness substitutions later one because requiresSubstitution
is true
and size of reqToSyntheticSubs
is 1
(since we got it from requirement signature in RequirementMatch::getWitness
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are several bugs here, one of which is tripped up by the test case you've reduced. Here are the issues I see:
-
requiresSubstitution()
is checking the kind of storage (StoredWitness *
) as a proxy for "has interesting an synthetic environment", but nothing establishes that as an invariant. We can either establish that as an invariant or eliminaterequiresSubstitution()
outright. It's probably best to remove it---it's used incorrectly/inconsistently basically everywhere, mainly to dodge the assertions ingetSyntheticEnvironment()
andgetRequirementToSyntheticSubs()
. Let's make those functions do adyn_cast
toStoredWitness *
and return its information when available, or nullptr/{} when not available, so we can clean up the callers. This is what my first big comment was about, but it's not the whole solution. -
You've found that one of the assumptions that serialization is making is not an invariant in the AST, i.e., that one can only have a non-empty
reqToSyntheticSubs
if the requirement environment is non-null. You pull request fixes this by using a consistent predicate in both places, which is good, but... -
When this happens, serialization (both before and after your PR) will drop the contents of
reqToSyntheticSubs
, so the deserializedWitness
will be different from the originally-constructed form. This could potentially break SILGen's protocol witness emission from a deserializedWitness
, which looks at and usesreqToSyntheticSubs
always (without checking for a null requirement environment). To fix this, I recommend we always emit the size ofreqToSyntheticSubs
(e.g., take out thisif
on the serialization side and the corresponding one in deserialization).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @DougGregor! What you are saying totally makes sense to me, I don't think number of requirement substitution should be dependent on presence of synthetic environment!
include/swift/AST/Witness.h
Outdated
@@ -168,6 +168,11 @@ class Witness { | |||
return storage.get<StoredWitness *>()->syntheticEnvironment; | |||
} | |||
|
|||
/// Determine if the witness may have requirement substitutions. | |||
bool mayHaveRequirementSubstitutions() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this predicate per se, because I think the issue is in the construction of Witness
not establishing invariants assumed by requiresSubstitution()
. Specifically, that the storage is a StoredWitness *
only when there are substitutions or a generic environment (or both), and a ValueDecl *
for the simple case. That the invariant that requiresSubstitution()
assumes, but the most-general Witness
constructor doesn't establish that invariant. Oops.
More importantly, the two things that create Witness
instances---the type checker and deserialization---do so differently: the type checker always goes through the most general Witness
constructor, meaning that it always gets the StoredWitness *
version (for which requiresSubstitution()
will always return true), whereas deserialization uses the simpler Witness(ValueDecl *)
constructor whenever it can---in which case it will have !requiresSubstitution()
when deserializing something that the type checker built as requiresSubstitution()
. Your change fixes the problem because it avoids relying on the invariant (so it checks the underlying condition), but it would be better to
(1) Teach the most-general Witness
constructor to use the simpler ValueDecl *
storage form when it can, and
(2) Eliminate the "optimization" from the deserialization code that tries to guess when to provide only the ValueDecl *
, so the two Witness
-constructing pieces use the same information.
Absence of synthetic generic environment should not affect serialization of the required substitutions because they can come from outer requirement context for static members. Resolves: rdar://problem/36497404
Avoid storing more information than necessary in witnesses and establishing the invariant that we only use out-of-line storage when we require substitutions.
@swift-ci please test |
@swift-ci please test source compatibility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks fantastic, thank you!
@xedin Thank you for taking the time to get to the bottom of the problem and come up with this great fix! |
Make it so requirement substitutions are serialized/deserialized independently from
synthetic context (because these two are not always related), also optimize storage
of the witness to avoid unnecessary allocation and remove
requiresSubstitution
invariant which is not always correctly established and not really needed.
Resolves SR-6754.
Resolves: rdar://problem/36497404