Skip to content

[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

Merged
merged 4 commits into from
Apr 20, 2018

Conversation

xedin
Copy link
Contributor

@xedin xedin commented Apr 19, 2018

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

@xedin xedin requested a review from DougGregor April 19, 2018 01:59
@xedin xedin changed the title [Serialization] Don't serialize requirement substitutions records wit… [Serialization] Don't serialize requirement substitution records wit… Apr 19, 2018
@xedin
Copy link
Contributor Author

xedin commented Apr 19, 2018

@swift-ci please test

@xedin
Copy link
Contributor Author

xedin commented Apr 19, 2018

@swift-ci please test source compatibility

Copy link
Contributor

@slavapestov slavapestov left a 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
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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.

@@ -168,6 +168,11 @@ class Witness {
return storage.get<StoredWitness *>()->syntheticEnvironment;
}

/// Determine if the witness may have requirement substitutions.
bool mayHaveRequirementSubstitutions() const {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

@xedin xedin Apr 19, 2018

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).

Copy link
Member

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:

  1. 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 eliminate requiresSubstitution() outright. It's probably best to remove it---it's used incorrectly/inconsistently basically everywhere, mainly to dodge the assertions in getSyntheticEnvironment() and getRequirementToSyntheticSubs(). Let's make those functions do a dyn_cast to StoredWitness * 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.

  2. 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...

  3. When this happens, serialization (both before and after your PR) will drop the contents of reqToSyntheticSubs, so the deserialized Witness will be different from the originally-constructed form. This could potentially break SILGen's protocol witness emission from a deserialized Witness, which looks at and uses reqToSyntheticSubs always (without checking for a null requirement environment). To fix this, I recommend we always emit the size of reqToSyntheticSubs (e.g., take out this if on the serialization side and the corresponding one in deserialization).

Copy link
Contributor Author

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!

@@ -168,6 +168,11 @@ class Witness {
return storage.get<StoredWitness *>()->syntheticEnvironment;
}

/// Determine if the witness may have requirement substitutions.
bool mayHaveRequirementSubstitutions() const {
Copy link
Member

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.

xedin added 4 commits April 19, 2018 17:58
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.
@xedin xedin changed the title [Serialization] Don't serialize requirement substitution records wit… [Serialization] Refactor normal conformance serialization Apr 20, 2018
@xedin
Copy link
Contributor Author

xedin commented Apr 20, 2018

@swift-ci please test

@xedin
Copy link
Contributor Author

xedin commented Apr 20, 2018

@swift-ci please test source compatibility

Copy link
Member

@DougGregor DougGregor left a 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 xedin merged commit 2690560 into swiftlang:master Apr 20, 2018
@slavapestov
Copy link
Contributor

@xedin Thank you for taking the time to get to the bottom of the problem and come up with this great fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants