Skip to content
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

RequirementMachine: A term rewriting system for reasoning about generic signatures #37675

Merged
merged 20 commits into from
Jun 2, 2021

Conversation

slavapestov
Copy link
Member

@slavapestov slavapestov commented May 27, 2021

Motivation

The GenericSignatureBuilder is used for four purposes:

  • Answering queries against already-built signatures, such as "compute canonical type" and "type conforms to protocol"
  • Building ArchetypeTypes from interface types inside a GenericEnvironment, which relies on the aforesaid queries
  • Building conformance access paths from already-built signatures
  • Computing minimized signatures from user-written requirements

The RequirementMachine is meant to provide a direct replacement for the first item above, as well as more solid underpinnings for the other three.

Implementation

A RequirementMachine consists of a term rewriting system built from a set of rewrite rules. The rewrite rules are the top-level requirements in the original generic signature, together with the union of all protocol requirement signatures of all protocols that are transitively referenced via conformance requirements.

The Knuth-Bendix algorithm together with an addition is used to compute the confluent completion. The basic completion algorithm looks for types T which can be reduced into two distinct minimal types T' and T'', and then adds a rewrite rule T' == T'', iterating the process if needed. The addition to the completion algorithm is meant to resolve certain difficult cases where associated types with the same name from different protocols must be merged together.

The -requirement-machine-depth-limit and -requirement-machine-step-limit frontend flags provide an upper bound on the amount of work done by the completion procedure. If the completion procedure terminates, we have a generic signature with a decidable word problem.

If the generic signature does not have a decidable word problem, completion will fail. It also necessarily fails in some decidable cases.

Current status

This PR introduces an implementation of the RequirementMachine that supports conformance, layout and same-type requirements. The main entry point is a method on ASTContext for lazily creating RequirementMachines from GenericSignatures, named getOrCreateRequirementMachine(). This new method is analogous to getOrCreateGenericSignatureBuilder().

For now, if the -enable-requirement-machine frontend flag is passed in, a RequirementMachine is built for each generic signature, but it is not actually used for anything. The flag is off by default.

Next steps

The next major milestone is adding support for concrete type and superclass requirements.

Once that's done, we can actually start using the RequirementMachine for generic signature queries. This can be staged in gradually, with parallel implementations and cross-checking between the new and old query results. At this point we will also need to optimize the rewriting engine, since it is intentionally not very good about avoiding unnecessary copying and memory allocation; furthermore, tries can be used to speed up lookups significantly.

Finally, the last step is to re-implement the GenericSignatureBuilder's algorithms for conformance access paths and requirement minimization on top of the RequirementMachine, at which point the GenericSignatureBuilder can be removed.`

References

@slavapestov slavapestov force-pushed the gsb-v2 branch 2 times, most recently from e09f6f8 to b693027 Compare May 29, 2021 05:39
@slavapestov
Copy link
Member Author

@swift-ci Please test

@slavapestov
Copy link
Member Author

@swift-ci Please test source compatibility

@swift-ci
Copy link
Collaborator

Build failed
Swift Test Linux Platform
Git Sha - b6930276899387cadc409842f63a2c21f95c7ff8

@swift-ci
Copy link
Collaborator

Build failed
Swift Test OS X Platform
Git Sha - b6930276899387cadc409842f63a2c21f95c7ff8

@slavapestov slavapestov changed the title RequirementMachine: Initial skeleton implementation RequirementMachine: A term rewriting system for reasoning about generic signatures Jun 2, 2021
@slavapestov slavapestov marked this pull request as ready for review June 2, 2021 05:58
@slavapestov
Copy link
Member Author

@swift-ci Please test source compatibility

Term() {}

explicit Term(llvm::SmallVector<Atom, 3> &&atoms)
: Atoms(atoms) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. There’s a copy here. Might wanna use std::move to preserve the rvalue-ness.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

: Atoms(atoms.begin(), atoms.end()) {}

void add(Atom atom) {
Atoms.push_back(atom);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: emplace_back and a move are probably slight wins.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atom will soon become uniqued/immutable so I'll leave this alone for now.


// Store this requirement machine before adding the signature,
// to catch re-entrant construction.
machines[sig] = std::unique_ptr<RequirementMachine>(machine);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could save a lookup by doing a lookup above then emplacing into it if there’s no existing match for the signature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -4026,7 +4026,7 @@ GenericSignatureBuilder::getConformanceAccessPath(Type type,

auto *Stats = Context.Stats;

FrontendStatsTracer(Stats, "get-conformance-access-path");
FrontendStatsTracer tracer(Stats, "get-conformance-access-path");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, good catch.

unsigned depth = 0;

for (auto *inherited : info.Inherited) {
unsigned inheritedDepth = computeProtocolDepth(inherited);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any risk of this recursion blowing out the stack? Seems unlikely, but might want to use a worklist.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactoring this to use a worklist is going to be a little bit tricky. We recurse over inherited protocols in other places too. Note that we should already be able to detect cycles here, because of the 'Mark' bit.

@slavapestov
Copy link
Member Author

@swift-ci Please smoke test

@slavapestov slavapestov merged commit 66c4ace into apple:main Jun 2, 2021
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.

None yet

3 participants