Now that we can detect protocol typealias rules, collect and keep
track of them so that they can be recorded in protocol requirement
signatures.
For now, this is all NFC since nothing introduces such rules into
the rewrite system, except for invalid requirements which are
diagnosed anyway.
We want to prefer to eliminate rules that are not concrete type rules,
unless non-concrete type rule in question is a projection. For example,
suppose that this rule comes from a protocol:
T.T == G<T.U, T.V>
And these three rules are in our minimization domain:
a) T.T == G<Int, W>
b) T.U == Int
c) T.V == W
Then a) implies both b) and c), and vice versa.
In this case, we want to keep T.U == Int and T.V == W, and eliminate
T.T == G<Int, W>.
T.T == G<Int, W> is concrete and T.V == W is not, but because T.V == W
is defined by a projection, we still prefer to eliminate T.T == G<Int, W>
over T.V == W.
We have three simplification passes, give each one its own predicate:
- Left hand side simplification
- Right hand side simplification
- Substitution simplification
This is for debugging output and will also allow me to tighten up
some invariants.
We can end up with two redundant concrete type rules on the same
term, but we would crash in homotopy reduction because the rules
were incomparable due to the concrete type symbol at the end.
If one of them is conflicting though, we don't really care about
the homotopy reduction order, so skip the check if the other
rule was already marked conflicting.
When minimizing a generic signature, we only care about loops
where the basepoint is a generic parameter symbol.
When minimizing protocol requirement signatures in a connected
component, we only care about loops where the basepoint is a
protocol symbol or associated type symbol whose protocol is
part of the connected component.
All other loops can be discarded since they do not encode
redundancies that are relevant to us.
A superclass requirement 'T : C' implies a layout requirement
'T : AnyObject' (if the class is @objc) or 'T : _NativeObject'
(if the class is not @objc).
In the latter case, there might already be a 'T : AnyObject'
requirement, in which case the type parameter 'T' is subject
to two layout requirements:
T : AnyObject
T : _NativeObject
The second requirement implies the first however. To encode this
in the world of rewrite loops, we the notion of a 'relation'
between property symbols, and a 'Relation' rewrite step.
Here, the relation is that _NativeObject < AnyObject. Once this
relation is recorded, the Relation rewrite step will transform
T.[layout: _NativeObject].[layout: AnyObject]
into
T.[layout: _NativeObject]
and vice versa.
This rewrite step allows us to construct a rewrite loop which
makes the first rewrite rule redundant via the second.
First pass:
- Eliminate rules with unresolved name symbols in LHS
- Eliminate simplified non-conformance rules
Second pass:
- Eliminate non-minimal conformance rules
Third pass:
- Eliminate all other non-conformance rules
If you have a concrete requirement with a non-canonical substitution, like
A : P, A == G<B.T>
We add an induced rule for the abstract type witness,
A.T == B.T
This rule has a rewrite path in terms of the previous two rules, but we
want to try to eliminate the other two rules first.
This new ordering ensures we eliminate the simplified rule A == G<B.T> and
compute minimal conformances before we get around to considering A.T == B.T,
which can no longer be eliminated by that point.
If we eliminate A.T == B.T first, we end up with simplified concrete
type and concrete conformance rules which cannot be eliminated:
A.[concrete: G<B.T>] => A
A.[concrete: G<B.T> : P] => A
This changes the minimized signature in an incompatible way in two test cases.
In Generics/rdar83308672.swift, the signature becomes equivalent to what the
GSB used to output.
In Generics/sr10532.swift, the GSB would output an invalid signature anyway,
so it doesn't matter.
When a rule is replaced by a rewrite path, if the rule is in context
then every step of the replacement step is necessarily always in context.
The only way new rules in empty context can be introduced by a
replacement is if the original rewrite step being replaced had no
context.
Therefore, loops that do not contain rules in empty context will
never provide any additional information during homotopy reduction.
This is a source-level error, not an invariant violation. Instead, plumb
a new hadError() flag, which in the future will assert if no diagnostic
was produced.
This cleans up 90 instances of this warning and reduces the build spew
when building on Linux. This helps identify actual issues when
building which can get lost in the stream of warning messages. It also
helps restore the ability to build the compiler with gcc.
If a rewrite loop contains two rules in empty context and one of them
is explicit, propagate the explicit bit to the other rule if the original
rule was found to be redundant.
This means that if we begin with an unresolved rule like
[P].X.[Q] => [P].X [explicit]
And then completion added the resolved rule
[P:X].[Q] => [P:X]
Then the new rule will become explicit once the original rule becomes
redundant.
This was a hack from before the change to minimize away the
least canonical rules first. Now, there's no reason to perform
a separate pass over rules containing unresolved name symbols.
When re-contextualizing a path containing RewriteStep::Decompose,
don't update StartOffset/EndOffset for steps that execute with
more than one term on the stack.
Instead of finding the best redundancy candidate from the first
homotopy generator that has one, find the best redundancy
candidate from *all* homotopy generators that have one.
This correctly minimizes the "Joe Groff monoid" without hitting
the assertion guarding against simplified rules being non-redundant.
It also brings us closer to being able to correctly minimize
the protocol from https://bugs.swift.org/browse/SR-7353.
I need to simplify concrete substitutions when adding a rewrite rule, so
for example if X.Y => Z, we want to simplify
A.[concrete: G<τ_0_0, τ_0_1> with <X.Y, X>] => A
to
A.[concrete: G<τ_0_0, τ_0_1> with <Z, X>] => A
The requirement machine used to do this, but I took it out, because it
didn't fit with the rewrite path representation. However, I found a case
where this is needed so I need to bring it back.
Until now, a rewrite path was a composition of rewrite steps where each
step would transform a source term into a destination term.
The question then becomes, how do we represent concrete substitution
simplification with such a scheme.
One approach is to rework rewrite paths to a 'nested' representation,
where a new kind of rewrite step applies a sequence of rewrite paths to
the concrete substitution terms. Unfortunately this would complicate
memory management and require recursion when visiting the steps of a
rewrite path.
Another, simpler approach that I'm going with here is to generalize a
rewrite path to a stack machine program instead.
I'm adding two new kinds of rewrite steps which manipulate a pair of
stacks, called 'A' and 'B':
- Decompose, which takes a term ending in a superclass or concrete type
symbol, and pushes each concrete substitution on the 'A' stack.
- A>B, which pops the top of the 'A' stack and pushes it onto the 'B'
stack.
Since all rewrite steps are invertible, the inverse of the two new
step kinds are as follows:
- Compose, which pops a series of terms from the 'A' stack, and replaces
the concrete substitutions in the term ending in a superclass or
concrete type symbol underneath.
- B>A, which pops the top of the 'B' stack and pushes it onto the
'B' stack.
Both Decompose and Compose take an operand, which is the number of
concrete substitutions to expect. This is encoded in the RuleID field
of RewriteStep.
The two existing rewrite steps ApplyRewriteRule and AdjustConcreteType
simply pop and push the term at the top of the 'A' stack.
Now, if addRule() wishes to transform
A.[concrete: G<τ_0_0, τ_0_1> with <X.Y, X>] => A
into
A.[concrete: G<τ_0_0, τ_0_1> with <Z, X>] => A
it can construct the rewrite path
Decompose(2) ⊗ A>B ⊗ <<rewrite path from X.Y to Z>> ⊗ B>A ⊗ Compose(2)
This commit lays down the plumbing for these new rewrite steps, and
replaces the existing 'evaluation' walks over rewrite paths that
mutate a single MutableTerm with a new RewritePathEvaluator type, that
stores the two stacks.
The changes to addRule() are coming in a subsequent commit.