Files
swift-mirror/lib/AST/RequirementMachine/RewriteContext.cpp
Hamish Knight edca7c85ad Adopt ABORT throughout the compiler
Convert a bunch of places where we're dumping to stderr and calling
`abort` over to using `ABORT` such that the message gets printed to
the pretty stack trace. This ensures it gets picked up by
CrashReporter.
2025-05-19 20:55:01 +01:00

621 lines
21 KiB
C++

//===--- RewriteContext.cpp - Term rewriting allocation arena -------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// The RewriteContext is a global singleton object with three primary
// responsibilities:
//
// - Arena allocation of uniqued immutable Symbols and Terms.
// - Caching requirement machine instances corresponding to generic signatures,
// used for generic signature queries.
// - Building the graph of protocol connected components, in support of the
// above.
//
// # Requirement machines for generic signatures
//
// The RewriteContext caches requirement machine instances built from generic
// signatures. When a generic signature is performed for the first time,
// a requirement machine is built for the generic signature by calling the
// RewriteContext::getRequirementMachine() method.
//
// An optimization is performed if this signature was written in source. When
// a new minimal generic signature is built from generic requirements, the
// AbstractGenericSignatureRequest and InferredGenericSignatureRequest requests
// transfer ownership of the requirement machine used for minimization to the
// RewriteContext by calling the installRequirementMachine() method, which
// associates this requirement machine with the newly-built generic signature.
//
// This saves the effort of rebuilding a new requirement machine from this
// signature the first time a query is performed, which typically happens when
// type checking the body of the generic declaration.
//
// A requirement machine for a generic signature must include rewrite rules
// for all requirements in protocols referenced from this signature as well.
// Instead of rebuilding all of these rules every time a requirement machine
// is created, the rewrite rules for protocols themselves are also cached in
// the RewriteContext.
//
// # Protocol dependency graph
//
// The central concept behind this caching is the protocol dependency graph.
// This graph records which protocols mention other protocols via conformance
// requirements.
//
// Formally, the protocol dependency graph is a (directed) graph where the
// vertices are protocols, and there is an edge from protocol P to a protocol Q
// if P has a conformance requirement with Q on the right hand side.
//
// Consider these definitions:
//
// protocol P1 : P2 {}
// protocol P2 { associatedtype T : P3; associatedtype V : P4 }
// protocol P3 { associatedtype U : P2; associatedtype V : P5 }
// protocol P4 {}
//
// P1 has a dependency on P2; P2 and P3 depend on each other; P2 depends on P4,
// and finally, P3 depends on P5. The protocol dependency graph looks like this:
//
// +----+
// | P1 |
// +----+
// / \
// / \
// / \
// / \
// / \
// / \
// v v
// +----+ -----> +----+
// | P2 | | P3 |
// +----+ <----- +----+
// | |
// v v
// +----+ +----+
// | P4 | | P5 |
// +----+ +----+
//
// When building a rewrite system for a generic signature that includes a
// conformance to protocol P2, we must include rewrite rules for P2, as well as
// all protocols reachable from P2 via the protocol dependency graph: P3, P4,
// and P5. Note that the set of all protocols reachable from P2 includes P3,
// and the set of all protocols reachable from P3 includes P2; so if a generic
// signature depends on one, it necessarily depends on the other.
//
// In general, this graph can contain cycles, as with P2 and P3 above. If we
// compute the strongly connected components of the protocol dependency graph,
// we get an acyclic graph:
//
// +----+
// | P1 |
// +----+
// |
// v
// +-------+
// | P2 P3 |
// +-------+
// / \
// / \
// v v
// +----+ +----+
// | P4 | | P5 |
// +----+ +----+
//
// The vertices of this graph are the strongly connected components of the
// original protocol dependency graph. Each connected component is a set of
// protocols that are interdependent and must be considered together as a
// single unit when building a rewrite system.
//
// # Requirement machines for protocol connected components
//
// The RewriteContext computes the protocol dependency graph and the associated
// graph of connected components. When building a rewrite system for a generic
// signature, the RuleBuilder queries the RewriteContext for the set of all
// connected components reachable from all conformance requirements in the
// generic signature.
//
// The RewriteContext associates a requirement machine to each connected
// component. This requirement machine is created when needed by the
// RewriteContext::getRequirementMachine(ProtocolDecl *) method.
//
// The rewrite rules from the requirement machine of each connected component
// are then imported into the newly-built requirement machine for the generic
// signature.
//
// If the protocol definitions in the connected component were parsed from
// source, this requirement machine is constructed when evaluating
// RequirementSignatureRequest, which computes a requirement signature for
// each protocol in the component from user-written requirements, and then
// saves the requirement machine in the RewriteContext by calling the
// installRequirementMachine() method.
//
// If the protocol definitions came from a deserialized module, we build a
// requirement machine from the previously-computed requirement signatures
// of those protocols.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/Decl.h"
#include "swift/AST/Types.h"
#include "swift/Basic/Assertions.h"
#include "RequirementMachine.h"
#include "RewriteSystem.h"
#include "RewriteContext.h"
using namespace swift;
using namespace rewriting;
/// Parse a comma-separated list from the -debug-requirement-machine= frontend
/// flag.
static DebugOptions parseDebugFlags(StringRef debugFlags) {
DebugOptions result;
SmallVector<StringRef, 2> debug;
debugFlags.split(debug, ',');
for (auto flagStr : debug) {
auto flag =
llvm::StringSwitch<std::optional<DebugFlags>>(flagStr)
.Case("simplify", DebugFlags::Simplify)
.Case("add", DebugFlags::Add)
.Case("completion", DebugFlags::Completion)
.Case("property-map", DebugFlags::PropertyMap)
.Case("concrete-unification", DebugFlags::ConcreteUnification)
.Case("concretize-nested-types", DebugFlags::ConcretizeNestedTypes)
.Case("conditional-requirements",
DebugFlags::ConditionalRequirements)
.Case("homotopy-reduction", DebugFlags::HomotopyReduction)
.Case("homotopy-reduction-detail",
DebugFlags::HomotopyReductionDetail)
.Case("minimal-conformances", DebugFlags::MinimalConformances)
.Case("minimal-conformances-detail",
DebugFlags::MinimalConformancesDetail)
.Case("protocol-dependencies", DebugFlags::ProtocolDependencies)
.Case("minimization", DebugFlags::Minimization)
.Case("redundant-rules", DebugFlags::RedundantRules)
.Case("redundant-rules-detail", DebugFlags::RedundantRulesDetail)
.Case("concrete-contraction", DebugFlags::ConcreteContraction)
.Case("timers", DebugFlags::Timers)
.Case("conflicting-rules", DebugFlags::ConflictingRules)
.Case("split-concrete-equiv-class",
DebugFlags::SplitConcreteEquivalenceClass)
.Default(std::nullopt);
if (!flag) {
llvm::errs() << "Unknown debug flag in -debug-requirement-machine "
<< flagStr << "\n";
abort();
}
result |= *flag;
}
return result;
}
RewriteContext::RewriteContext(ASTContext &ctx)
: TheShapeSymbol(nullptr),
ThePackElementSymbol(nullptr),
Context(ctx),
Stats(ctx.Stats),
SymbolHistogram(Symbol::NumKinds),
TermHistogram(4, /*Start=*/1),
RuleTrieHistogram(16, /*Start=*/1),
RuleTrieRootHistogram(16),
PropertyTrieHistogram(16, /*Start=*/1),
PropertyTrieRootHistogram(16),
ConformanceRulesHistogram(16),
MinimalConformancesHistogram(8, /*Start=*/2) {
auto debugFlags = StringRef(ctx.LangOpts.DebugRequirementMachine);
if (!debugFlags.empty())
Debug = parseDebugFlags(debugFlags);
}
void RewriteContext::beginTimer(StringRef name) {
auto now = std::chrono::system_clock::now();
auto dur = now.time_since_epoch();
for (unsigned i = 0; i < Timers.size(); ++i)
llvm::dbgs() << "| ";
llvm::dbgs() << "+ started " << name << " ";
Timers.push_back(std::chrono::duration_cast<std::chrono::microseconds>(dur).count());
}
void RewriteContext::endTimer(StringRef name) {
auto now = std::chrono::system_clock::now();
auto dur = now.time_since_epoch();
auto time = (std::chrono::duration_cast<std::chrono::microseconds>(dur).count()
- Timers.back());
Timers.pop_back();
// If we're nested inside of another timer, don't charge our time to the parent.
if (!Timers.empty()) {
Timers.back() += time;
}
for (unsigned i = 0; i < Timers.size(); ++i)
llvm::dbgs() << "| ";
llvm::dbgs() << "+ ";
if (time > 100000)
llvm::dbgs() << "**** SLOW **** ";
llvm::dbgs() << "finished " << name << " in " << time << "us: ";
}
int RewriteContext::compareProtocols(const ProtocolDecl *lhs,
const ProtocolDecl *rhs) {
unsigned lhsSupport = lhs->getAllInheritedProtocols().size();
unsigned rhsSupport = rhs->getAllInheritedProtocols().size();
if (lhsSupport != rhsSupport)
return rhsSupport - lhsSupport;
return TypeDecl::compare(lhs, rhs);
}
RequirementMachine *RewriteContext::getRequirementMachine(
CanGenericSignature sig) {
auto &machine = Machines[sig];
if (machine) {
if (!machine->isComplete()) {
ABORT([&](auto &out) {
out << "Re-entrant construction of requirement machine for " << sig;
});
}
return machine;
}
if (Debug.contains(DebugFlags::Timers)) {
beginTimer("getRequirementMachine()");
llvm::dbgs() << sig << "\n";
}
// Store this requirement machine before adding the signature,
// to catch re-entrant construction via initWithGenericSignature()
// below.
auto *newMachine = new rewriting::RequirementMachine(*this);
machine = newMachine;
// This might re-entrantly invalidate 'machine'.
auto status = newMachine->initWithGenericSignature(sig);
newMachine->checkCompletionResult(status.first);
if (Debug.contains(DebugFlags::Timers)) {
endTimer("getRequirementMachine()");
llvm::dbgs() << sig << "\n";
}
return newMachine;
}
bool RewriteContext::isRecursivelyConstructingRequirementMachine(
CanGenericSignature sig) {
auto found = Machines.find(sig);
if (found == Machines.end())
return false;
return !found->second->isComplete();
}
/// Given a requirement machine that built a minimized signature, attempt to
/// re-use it for subsequent queries against the minimized signature, instead
/// of building a new one later.
void RewriteContext::installRequirementMachine(
CanGenericSignature sig,
std::unique_ptr<RequirementMachine> machine) {
if (!Context.LangOpts.EnableRequirementMachineReuse)
return;
auto &entry = Machines[sig];
if (entry != nullptr)
return;
machine->freeze();
entry = machine.release();
}
/// Implement Tarjan's algorithm to compute strongly-connected components in
/// the protocol dependency graph.
void RewriteContext::getProtocolComponentRec(
const ProtocolDecl *proto,
SmallVectorImpl<const ProtocolDecl *> &stack) {
ASSERT(Protos.count(proto) == 0);
// Initialize the next component index and push the entry
// on the stack
{
auto &entry = Protos[proto];
entry.Index = NextComponentIndex;
entry.LowLink = NextComponentIndex;
entry.OnStack = 1;
}
NextComponentIndex++;
stack.push_back(proto);
// Look at each successor.
auto found = Dependencies.find(proto);
ASSERT(found != Dependencies.end());
for (auto *depProto : found->second) {
auto found = Protos.find(depProto);
if (found == Protos.end()) {
// Successor has not yet been visited. Recurse.
getProtocolComponentRec(depProto, stack);
auto &entry = Protos[proto];
ASSERT(Protos.count(depProto) != 0);
entry.LowLink = std::min(entry.LowLink, Protos[depProto].LowLink);
} else if (found->second.OnStack) {
// Successor is on the stack and hence in the current SCC.
auto &entry = Protos[proto];
entry.LowLink = std::min(entry.LowLink, found->second.Index);
}
}
auto &entry = Protos[proto];
// If this a root node, pop the stack and generate an SCC.
if (entry.LowLink == entry.Index) {
unsigned id = Components.size();
SmallVector<const ProtocolDecl *, 3> protos;
const ProtocolDecl *depProto = nullptr;
do {
depProto = stack.back();
stack.pop_back();
ASSERT(Protos.count(depProto) != 0);
Protos[depProto].OnStack = false;
Protos[depProto].ComponentID = id;
protos.push_back(depProto);
} while (depProto != proto);
if (Debug.contains(DebugFlags::ProtocolDependencies)) {
llvm::dbgs() << "Connected component: [";
bool first = true;
for (auto *depProto : protos) {
if (!first) {
llvm::dbgs() << ", ";
} else {
first = false;
}
llvm::dbgs() << depProto->getName();
}
llvm::dbgs() << "]\n";
}
Components[id].Protos = Context.AllocateCopy(protos);
}
}
/// Get the strongly connected component (SCC) of the protocol dependency
/// graph containing the given protocol.
///
/// You must not hold on to this reference across calls to any other
/// Requirement Machine operations, since they might insert new entries
/// into the underlying DenseMap, invalidating the reference.
RewriteContext::ProtocolComponent &
RewriteContext::getProtocolComponentImpl(const ProtocolDecl *proto) {
{
// We pre-load protocol dependencies into the Dependencies map
// because getProtocolDependencies() can trigger recursive calls into
// the requirement machine in highly-invalid code, which violates
// invariants in getProtocolComponentRec().
SmallVector<const ProtocolDecl *, 3> worklist;
worklist.push_back(proto);
while (!worklist.empty()) {
const auto *otherProto = worklist.back();
worklist.pop_back();
auto found = Dependencies.find(otherProto);
if (found != Dependencies.end())
continue;
auto protoDeps = otherProto->getProtocolDependencies();
Dependencies.insert(std::make_pair(otherProto, protoDeps));
for (auto *nextProto : protoDeps)
worklist.push_back(nextProto);
}
}
auto found = Protos.find(proto);
if (found == Protos.end()) {
if (ProtectProtocolComponentRec)
ABORT("Too much recursion is bad");
ProtectProtocolComponentRec = true;
SmallVector<const ProtocolDecl *, 3> stack;
getProtocolComponentRec(proto, stack);
ASSERT(stack.empty());
found = Protos.find(proto);
ASSERT(found != Protos.end());
ProtectProtocolComponentRec = false;
}
CONDITIONAL_ASSERT(Components.count(found->second.ComponentID) != 0);
auto &component = Components[found->second.ComponentID];
CONDITIONAL_ASSERT(std::find(component.Protos.begin(), component.Protos.end(), proto)
!= component.Protos.end() && "Protocol is in the wrong SCC");
return component;
}
/// Get the list of protocols in the strongly connected component (SCC)
/// of the protocol dependency graph containing the given protocol.
///
/// This can only be called once, to prevent multiple requirement machines
/// for being built with the same component.
ArrayRef<const ProtocolDecl *>
RewriteContext::startComputingRequirementSignatures(
const ProtocolDecl *proto) {
auto &component = getProtocolComponentImpl(proto);
if (component.ComputingRequirementSignatures) {
ABORT([&](auto &out) {
out << "Re-entrant minimization of requirement signatures for: ";
for (auto *proto : component.Protos)
out << " " << proto->getName();
});
}
component.ComputingRequirementSignatures = true;
return component.Protos;
}
/// Mark the component as having completed, which will ensure that
/// isRecursivelyComputingRequirementMachine() returns false.
void RewriteContext::finishComputingRequirementSignatures(
const ProtocolDecl *proto) {
auto &component = getProtocolComponentImpl(proto);
ASSERT(component.ComputingRequirementSignatures &&
"Didn't call startComputingRequirementSignatures()");
component.ComputedRequirementSignatures = true;
}
/// Get the list of protocols in the strongly connected component (SCC)
/// of the protocol dependency graph containing the given protocol.
///
/// This can only be called once, to prevent multiple requirement machines
/// for being built with the same component.
RequirementMachine *RewriteContext::getRequirementMachine(
const ProtocolDecl *proto) {
// First, get the requirement signature. If this protocol was written in
// source, we'll minimize it and install the machine below, saving us the
// effort of recomputing it.
(void) proto->getRequirementSignature();
auto &component = getProtocolComponentImpl(proto);
if (component.Machine) {
if (!component.Machine->isComplete()) {
ABORT([&](auto &out) {
out << "Re-entrant construction of requirement machine for: ";
for (auto *proto : component.Protos)
out << " " << proto->getName();
});
}
return component.Machine;
}
auto protos = component.Protos;
if (Debug.contains(DebugFlags::Timers)) {
beginTimer("getRequirementMachine()");
llvm::dbgs() << "[";
for (auto *proto : protos)
llvm::dbgs() << " " << proto->getName();
llvm::dbgs() << " ]\n";
}
// Store this requirement machine before adding the protocols, to catch
// re-entrant construction via initWithProtocolSignatureRequirements()
// below.
auto *newMachine = new rewriting::RequirementMachine(*this);
component.Machine = newMachine;
// This might re-entrantly invalidate 'component.Machine'.
auto status = newMachine->initWithProtocolSignatureRequirements(protos);
newMachine->checkCompletionResult(status.first);
if (Debug.contains(DebugFlags::Timers)) {
endTimer("getRequirementMachine()");
llvm::dbgs() << "[";
for (auto *proto : protos)
llvm::dbgs() << " " << proto->getName();
llvm::dbgs() << " ]\n";
}
return newMachine;
}
/// Note: This doesn't use Evaluator::hasActiveRequest(), because in reality
/// the active request could be for any protocol in the connected component.
///
/// Instead, just check a flag set in the component itself.
bool RewriteContext::isRecursivelyConstructingRequirementMachine(
const ProtocolDecl *proto) {
auto found = Protos.find(proto);
if (found == Protos.end())
return false;
auto component = Components.find(found->second.ComponentID);
if (component == Components.end())
return false;
// If we've started but not finished, we're in the middle of computing
// requirement signatures.
return (component->second.ComputingRequirementSignatures &&
!component->second.ComputedRequirementSignatures);
}
/// Given a requirement machine that built the requirement signatures for a
/// protocol connected component, attempt to re-use it for subsequent
/// queries against the connected component, instead of building a new one
/// later.
void RewriteContext::installRequirementMachine(
const ProtocolDecl *proto,
std::unique_ptr<RequirementMachine> machine) {
if (!Context.LangOpts.EnableRequirementMachineReuse)
return;
auto &component = getProtocolComponentImpl(proto);
if (component.Machine != nullptr)
return;
machine->freeze();
component.Machine = machine.release();
}
/// We print stats in the destructor, which should get executed at the end of
/// a compilation job.
RewriteContext::~RewriteContext() {
for (const auto &pair : Components)
delete pair.second.Machine;
Components.clear();
for (const auto &pair : Machines)
delete pair.second;
Machines.clear();
if (Context.LangOpts.AnalyzeRequirementMachine) {
llvm::dbgs() << "--- Requirement Machine Statistics ---\n";
llvm::dbgs() << "\n* Symbol kind:\n";
SymbolHistogram.dump(llvm::dbgs(), Symbol::Kinds);
llvm::dbgs() << "\n* Term length:\n";
TermHistogram.dump(llvm::dbgs());
llvm::dbgs() << "\n* Rule trie fanout:\n";
RuleTrieHistogram.dump(llvm::dbgs());
llvm::dbgs() << "\n* Rule trie root fanout:\n";
RuleTrieRootHistogram.dump(llvm::dbgs());
llvm::dbgs() << "\n* Property trie fanout:\n";
PropertyTrieHistogram.dump(llvm::dbgs());
llvm::dbgs() << "\n* Property trie root fanout:\n";
PropertyTrieRootHistogram.dump(llvm::dbgs());
llvm::dbgs() << "\n* Conformance rules:\n";
ConformanceRulesHistogram.dump(llvm::dbgs());
llvm::dbgs() << "\n* Minimal conformance equations:\n";
MinimalConformancesHistogram.dump(llvm::dbgs());
}
}