Skip to content

Commit 8df83fe

Browse files
authored
Merge pull request #41614 from slavapestov/rqm-left-canonical-form
RequirementMachine: Reduce replacement paths for redundant rules to left-canonical normal form
2 parents b71dc9c + cfdb937 commit 8df83fe

17 files changed

+484
-68
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,10 @@ namespace swift {
545545
/// enabled. It can be disabled for debugging and testing.
546546
bool EnableRequirementMachineConcreteContraction = true;
547547

548+
/// Enable the stronger minimization algorithm. This is just for debugging;
549+
/// if you have a testcase which requires this, please submit a bug report.
550+
bool EnableRequirementMachineLoopNormalization = false;
551+
548552
/// Enables dumping type witness systems from associated type inference.
549553
bool DumpTypeWitnessSystems = false;
550554

include/swift/Option/FrontendOptions.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,9 @@ def disable_requirement_machine_concrete_contraction : Flag<["-"], "disable-requ
359359
HelpText<"Disable preprocessing pass to eliminate conformance requirements "
360360
"on generic parameters which are made concrete">;
361361

362+
def enable_requirement_machine_loop_normalization : Flag<["-"], "enable-requirement-machine-loop-normalization">,
363+
HelpText<"Enable stronger minimization algorithm, for debugging only">;
364+
362365
def dump_type_witness_systems : Flag<["-"], "dump-type-witness-systems">,
363366
HelpText<"Enables dumping type witness systems from associated type inference">;
364367

lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ add_swift_host_library(swiftAST STATIC
8383
RequirementMachine/InterfaceType.cpp
8484
RequirementMachine/KnuthBendix.cpp
8585
RequirementMachine/MinimalConformances.cpp
86+
RequirementMachine/NormalizeRewritePath.cpp
8687
RequirementMachine/PropertyMap.cpp
8788
RequirementMachine/PropertyRelations.cpp
8889
RequirementMachine/PropertyUnification.cpp

lib/AST/GenericSignature.cpp

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,9 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
827827
// We collect conformance requirements to check that they're minimal.
828828
llvm::SmallDenseMap<CanType, SmallVector<ProtocolDecl *, 2>, 2> conformances;
829829

830+
// We collect same-type requirements to check that they're minimal.
831+
llvm::SmallDenseMap<CanType, SmallVector<Type, 2>, 2> sameTypeComponents;
832+
830833
// Check that the requirements satisfy certain invariants.
831834
for (unsigned idx : indices(reqts)) {
832835
const auto &reqt = reqts[idx].getCanonical();
@@ -873,6 +876,10 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
873876

874877
auto firstType = reqt.getFirstType();
875878
auto secondType = reqt.getSecondType();
879+
880+
auto canType = canSig->getCanonicalTypeInContext(firstType);
881+
auto &component = sameTypeComponents[canType];
882+
876883
if (!hasCanonicalOrConcreteParent(firstType)) {
877884
llvm::errs() << "Left hand side does not have a canonical parent: ";
878885
reqt.dump(llvm::errs());
@@ -893,13 +900,34 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
893900
llvm::errs() << "\n";
894901
abort();
895902
}
903+
904+
if (component.empty()) {
905+
component.push_back(firstType);
906+
} else if (!component.back()->isEqual(firstType)) {
907+
llvm::errs() << "Same-type requirement within an equiv. class "
908+
<< "is out-of-order: ";
909+
reqt.dump(llvm::errs());
910+
llvm::errs() << "\n";
911+
abort();
912+
}
913+
914+
component.push_back(secondType);
896915
} else {
897916
if (!canSig->isCanonicalTypeInContext(secondType)) {
898917
llvm::errs() << "Right hand side is not canonical: ";
899918
reqt.dump(llvm::errs());
900919
llvm::errs() << "\n";
901920
abort();
902921
}
922+
923+
if (component.empty()) {
924+
component.push_back(secondType);
925+
} else if (!component.back()->isEqual(secondType)) {
926+
llvm::errs() << "Inconsistent concrete requirement in equiv. class: ";
927+
reqt.dump(llvm::errs());
928+
llvm::errs() << "\n";
929+
abort();
930+
}
903931
}
904932
break;
905933
}
@@ -925,33 +953,6 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
925953
abort();
926954
}
927955

928-
// If we have two same-type requirements where the left-hand sides differ
929-
// but fall into the same equivalence class, we can check the form.
930-
if (compareLHS < 0 && reqt.getKind() == RequirementKind::SameType &&
931-
prevReqt.getKind() == RequirementKind::SameType &&
932-
canSig->areSameTypeParameterInContext(prevReqt.getFirstType(),
933-
reqt.getFirstType())) {
934-
// If it's a it's a type parameter, make sure the equivalence class is
935-
// wired together sanely.
936-
if (prevReqt.getSecondType()->isTypeParameter()) {
937-
if (!prevReqt.getSecondType()->isEqual(reqt.getFirstType())) {
938-
llvm::errs() << "Same-type requirement within an equiv. class "
939-
<< "is out-of-order: ";
940-
reqt.dump(llvm::errs());
941-
llvm::errs() << "\n";
942-
abort();
943-
}
944-
} else {
945-
// Otherwise, the concrete types must match up.
946-
if (!prevReqt.getSecondType()->isEqual(reqt.getSecondType())) {
947-
llvm::errs() << "Inconsistent concrete requirement in equiv. class: ";
948-
reqt.dump(llvm::errs());
949-
llvm::errs() << "\n";
950-
abort();
951-
}
952-
}
953-
}
954-
955956
// If we have a concrete same-type requirement, we shouldn't have any
956957
// other requirements on the same type.
957958
if (reqt.getKind() == RequirementKind::SameType &&
@@ -974,7 +975,7 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
974975
}
975976

976977
// Make sure we don't have redundant protocol conformance requirements.
977-
for (auto pair : conformances) {
978+
for (const auto &pair : conformances) {
978979
const auto &protos = pair.second;
979980
auto canonicalProtos = protos;
980981

@@ -992,6 +993,18 @@ void GenericSignature::verify(ArrayRef<Requirement> reqts) const {
992993
abort();
993994
}
994995
}
996+
997+
// Check same-type components for consistency.
998+
for (const auto &pair : sameTypeComponents) {
999+
if (pair.second.front()->isTypeParameter() &&
1000+
!canSig->isCanonicalTypeInContext(pair.second.front())) {
1001+
llvm::errs() << "Abstract same-type requirement involving concrete types\n";
1002+
llvm::errs() << "Canonical type: " << pair.first << "\n";
1003+
llvm::errs() << "Left hand side of first requirement: "
1004+
<< pair.second.front() << "\n";
1005+
abort();
1006+
}
1007+
}
9951008
}
9961009

9971010
static Type stripBoundDependentMemberTypes(Type t) {

lib/AST/RequirementMachine/ConcreteTypeWitness.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,14 +483,29 @@ void PropertyMap::recordConcreteConformanceRule(
483483

484484
auto protocolSymbol = *conformanceRule.isPropertyRule();
485485

486-
// Now, transform T''.[concrete: C].[P] into T''.[concrete: C : P].
486+
// Now, transform T''.[concrete: C].[P] into T''.[concrete: C].[concrete: C : P].
487487
unsigned relationID = System.recordConcreteConformanceRelation(
488488
concreteSymbol, protocolSymbol, concreteConformanceSymbol);
489489

490490
path.add(RewriteStep::forRelation(
491491
/*startOffset=*/rhs.size(), relationID,
492492
/*inverse=*/false));
493493

494+
// If T' is a suffix of T, prepend the prefix to the concrete type's
495+
// substitutions.
496+
if (prefixLength > 0 &&
497+
!concreteConformanceSymbol.getSubstitutions().empty()) {
498+
path.add(RewriteStep::forPrefixSubstitutions(prefixLength, /*endOffset=*/1,
499+
/*inverse=*/true));
500+
}
501+
502+
// Finally, apply the concrete type rule to obtain T''.[concrete: C : P].
503+
path.add(RewriteStep::forRewriteRule(
504+
/*startOffset=*/rhs.size() - concreteRule.getRHS().size(),
505+
/*endOffset=*/1,
506+
/*ruleID=*/concreteRuleID,
507+
/*inverse=*/false));
508+
494509
MutableTerm lhs(rhs);
495510
lhs.add(concreteConformanceSymbol);
496511

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,18 @@
5555
#include "swift/Basic/Range.h"
5656
#include "llvm/ADT/DenseMap.h"
5757
#include "llvm/ADT/DenseSet.h"
58+
#include "llvm/ADT/SmallVector.h"
5859
#include "llvm/Support/Debug.h"
5960
#include "llvm/Support/raw_ostream.h"
6061
#include <algorithm>
62+
#include "RewriteContext.h"
6163
#include "RewriteSystem.h"
6264

6365
using namespace swift;
6466
using namespace rewriting;
6567

66-
/// Recompute RulesInEmptyContext and DecomposeCount if needed.
68+
/// Recompute Useful, RulesInEmptyContext, ProjectionCount and DecomposeCount
69+
/// if needed.
6770
void RewriteLoop::recompute(const RewriteSystem &system) {
6871
if (!Dirty)
6972
return;
@@ -109,6 +112,8 @@ void RewriteLoop::recompute(const RewriteSystem &system) {
109112
evaluator.apply(step, system);
110113
}
111114

115+
Useful = !rulesInEmptyContext.empty();
116+
112117
RulesInEmptyContext.clear();
113118

114119
// Collect all rules that we saw exactly once in empty context.
@@ -146,6 +151,14 @@ unsigned RewriteLoop::getDecomposeCount(
146151
return DecomposeCount;
147152
}
148153

154+
/// The number of Decompose steps, used by the elimination order to prioritize
155+
/// loops that are not concrete simplifications.
156+
bool RewriteLoop::isUseful(
157+
const RewriteSystem &system) const {
158+
const_cast<RewriteLoop *>(this)->recompute(system);
159+
return Useful;
160+
}
161+
149162
/// If a rewrite loop contains an explicit rule in empty context, propagate the
150163
/// explicit bit to all other rules appearing in empty context within the same
151164
/// loop.
@@ -418,16 +431,22 @@ findRuleToDelete(llvm::function_ref<bool(unsigned)> isRedundantRuleFn) {
418431
if (loop.isDeleted())
419432
continue;
420433

421-
bool foundAny = false;
434+
// Delete loops that don't contain any rewrite rules in empty context,
435+
// since such loops do not yield any elimination candidates.
436+
if (!loop.isUseful(*this)) {
437+
if (Debug.contains(DebugFlags::HomotopyReduction)) {
438+
llvm::dbgs() << "** Deleting useless loop #" << loopID << ": ";
439+
loop.dump(llvm::dbgs(), *this);
440+
llvm::dbgs() << "\n";
441+
}
442+
443+
loop.markDeleted();
444+
continue;
445+
}
446+
422447
for (unsigned ruleID : loop.findRulesAppearingOnceInEmptyContext(*this)) {
423448
redundancyCandidates.emplace_back(loopID, ruleID);
424-
foundAny = true;
425449
}
426-
427-
// Delete loops that don't contain any rewrite rules in empty context,
428-
// since such loops do not give us useful information.
429-
if (!foundAny)
430-
loop.markDeleted();
431450
}
432451

433452
Optional<std::pair<unsigned, unsigned>> found;
@@ -609,6 +628,10 @@ void RewriteSystem::deleteRule(unsigned ruleID,
609628
if (!changed)
610629
continue;
611630

631+
if (Context.getASTContext().LangOpts.EnableRequirementMachineLoopNormalization) {
632+
loop.computeNormalForm(*this);
633+
}
634+
612635
// The loop's path has changed, so we must invalidate the cached
613636
// result of findRulesAppearingOnceInEmptyContext().
614637
loop.markDirty();
@@ -658,6 +681,34 @@ void RewriteSystem::performHomotopyReduction(
658681
}
659682
}
660683

684+
void RewriteSystem::normalizeRedundantRules() {
685+
for (auto &pair : RedundantRules) {
686+
pair.second.computeNormalForm(*this);
687+
}
688+
689+
if (Debug.contains(DebugFlags::RedundantRules)) {
690+
llvm::dbgs() << "\nRedundant rules:\n";
691+
for (const auto &pair : RedundantRules) {
692+
const auto &rule = getRule(pair.first);
693+
llvm::dbgs() << "- ("
694+
<< rule.getLHS() << " => "
695+
<< rule.getRHS() << ") ::== ";
696+
697+
MutableTerm lhs(rule.getLHS());
698+
pair.second.dump(llvm::dbgs(), lhs, *this);
699+
700+
llvm::dbgs() << "\n";
701+
702+
if (Debug.contains(DebugFlags::RedundantRulesDetail)) {
703+
llvm::dbgs() << "\n";
704+
pair.second.dumpLong(llvm::dbgs(), lhs, *this);
705+
706+
llvm::dbgs() << "\n\n";
707+
}
708+
}
709+
}
710+
}
711+
661712
/// Use the loops to delete redundant rewrite rules via a series of Tietze
662713
/// transformations, updating and simplifying existing loops as each rule
663714
/// is deleted.
@@ -677,6 +728,12 @@ void RewriteSystem::minimizeRewriteSystem() {
677728
propagateExplicitBits();
678729
processConflicts();
679730

731+
if (Context.getASTContext().LangOpts.EnableRequirementMachineLoopNormalization) {
732+
for (auto &loop : Loops) {
733+
loop.computeNormalForm(*this);
734+
}
735+
}
736+
680737
// First pass:
681738
// - Eliminate all LHS-simplified non-conformance rules.
682739
// - Eliminate all RHS-simplified and substitution-simplified rules.
@@ -753,25 +810,7 @@ void RewriteSystem::minimizeRewriteSystem() {
753810
verifyRedundantConformances(redundantConformances);
754811
verifyMinimizedRules(redundantConformances);
755812

756-
if (Debug.contains(DebugFlags::RedundantRules)) {
757-
llvm::dbgs() << "\nRedundant rules:\n";
758-
for (const auto &pair : RedundantRules) {
759-
const auto &rule = getRule(pair.first);
760-
llvm::dbgs() << "- " << rule << " ::== ";
761-
762-
MutableTerm lhs(rule.getLHS());
763-
pair.second.dump(llvm::dbgs(), lhs, *this);
764-
765-
llvm::dbgs() << "\n";
766-
767-
if (Debug.contains(DebugFlags::RedundantRulesDetail)) {
768-
llvm::dbgs() << "\n";
769-
pair.second.dumpLong(llvm::dbgs(), lhs, *this);
770-
771-
llvm::dbgs() << "\n\n";
772-
}
773-
}
774-
}
813+
normalizeRedundantRules();
775814
}
776815

777816
/// In a conformance-valid rewrite system, any rule with unresolved symbols on

0 commit comments

Comments
 (0)