[Protocol conformance] Refactor protocol conformance representation.

Factor the ProtocolConformance class into a small hierarchy of
protocol conformances: 
  - "normal" conformance, which provides a complete mapping for the
  explicit conformance of a nominal type (which may be generic) to a
  protocol;
  -  "specialized" conformance, which specializes a generic
  conformance by applying a set of substitutions; and
  - "inherited" conformance, which projects the conformance from a
  superclass to a conformance for a subclass.

In this scheme "normal" conformances are fairly heavyweight, because
they provide a complete mapping. Normal conformances are unique,
because they're associated with explicit conformance declarations
(which cannot be repeated within a module; checking is TBD). Thus, IR
generation will eventually emit them as strong symbols.

"Specialized" and "inherited" conformances occur when we're dealing
with generic specializations or subclasses. They project most of their
members through to some underlying conformance, eventually landing at
a "normal" conformance. ASTContext is responsible for uniquing these
conformances when it sees them. The IR generation model for
specialized conformances will involve runtime specialization of the
underlying witness table; inherited conformances are probably no-ops
from the IR generation perspective.

Aside from being the right thing to do, having small, uniqued
conformances for the specialization and inheritance cases is good for
compile-time performance and memory usage. We're not really taking
advantage of this everywhere we could, yet.

This change uncovered a few existing issues (one known, one not
known), particularly because we're projecting inherited conformances
rather than building new conformances:
  - <rdar://problem/14620454>: protocol witnesses to methods of
  classes need to perform dynamic dispatch. See the
  test/Interpreter/typeof.swift test for an example.
  - <rdar://problem/14637688>: comparing NSString and String with ==
  fails, because they are inter-convertible. I suspect we were missing
  some protocol conformances previously, and therefore accepting this
  obviously-invalid code.



Swift SVN r6865
This commit is contained in:
Doug Gregor
2013-08-02 22:59:54 +00:00
parent aa0468a939
commit 666213348d
9 changed files with 986 additions and 241 deletions

View File

@@ -209,24 +209,132 @@ Optional<ConformancePair> ModuleFile::maybeReadConformance(Type conformingType){
return Nothing;
unsigned kind = DeclTypeCursor.readRecord(next.ID, scratch);
if (kind != PROTOCOL_CONFORMANCE && kind != NO_CONFORMANCE)
return Nothing;
lastRecordOffset.reset();
if (kind == NO_CONFORMANCE) {
switch (kind) {
case NO_CONFORMANCE: {
lastRecordOffset.reset();
DeclID protoID;
NoConformanceLayout::readRecord(scratch, protoID);
return std::make_pair(cast<ProtocolDecl>(getDecl(protoID)), nullptr);
}
case NORMAL_PROTOCOL_CONFORMANCE:
// Handled below.
break;
case SPECIALIZED_PROTOCOL_CONFORMANCE: {
lastRecordOffset.reset();
DeclID protoID;
DeclID nominalID;
IdentifierID moduleOrTypeID;
unsigned numTypeWitnesses;
unsigned numSubstitutions;
ArrayRef<uint64_t> rawIDs;
SpecializedProtocolConformanceLayout::readRecord(scratch, protoID,
nominalID,
moduleOrTypeID,
numTypeWitnesses,
numSubstitutions,
rawIDs);
ASTContext &ctx = ModuleContext->Ctx;
auto proto = cast<ProtocolDecl>(getDecl(protoID));
// Read the substitutions.
SmallVector<Substitution, 4> substitutions;
while (numSubstitutions--) {
auto sub = maybeReadSubstitution();
assert(sub.hasValue() && "Missing substitution?");
substitutions.push_back(*sub);
}
// Read the type witnesses.
ArrayRef<uint64_t>::iterator rawIDIter = rawIDs.begin();
TypeWitnessMap typeWitnesses;
while (numTypeWitnesses--) {
// FIXME: We don't actually want to allocate an archetype here; we just
// want to get an access path within the protocol.
auto first = cast<TypeAliasDecl>(getDecl(*rawIDIter++));
auto second = maybeReadSubstitution();
assert(second.hasValue());
typeWitnesses[first] = *second;
}
assert(rawIDIter <= rawIDs.end() && "read too much");
// Reset the offset RAII to the end of the trailing records.
lastRecordOffset.reset();
ProtocolConformance *genericConformance = nullptr;
if (nominalID) {
// Dig out the protocol conformance within the nominal declaration.
auto nominal = cast<NominalTypeDecl>(getDecl(nominalID));
Module *owningModule;
if (moduleOrTypeID == 0)
owningModule = ModuleContext;
else
owningModule = getModule(getIdentifier(moduleOrTypeID-1));
(void)owningModule; // FIXME: Currently only used for checking.
for (unsigned i = 0, n = nominal->getProtocols().size(); i != n; ++i) {
if (nominal->getProtocols()[i] == proto) {
genericConformance = nominal->getConformances()[i];
// FIXME: Eventually, filter by owning module.
assert(nominal->getModuleContext() == owningModule);
break;
}
}
if (!genericConformance) {
for (auto ext : nominal->getExtensions()) {
for (unsigned i = 0, n = ext->getProtocols().size(); i != n; ++i) {
if (ext->getProtocols()[i] == proto) {
genericConformance = ext->getConformances()[i];
// FIXME: Eventually, filter by owning module.
assert(ext->getModuleContext() == owningModule);
break;
}
}
if (genericConformance)
break;
}
}
} else {
// The generic conformance is in the following record.
genericConformance
= maybeReadConformance(getType(moduleOrTypeID))->second;
}
assert(genericConformance && "Missing generic conformance?");
return { proto,
ctx.getSpecializedConformance(conformingType,
genericConformance,
substitutions,
std::move(typeWitnesses)) };
}
case INHERITED_PROTOCOL_CONFORMANCE: {
// FIXME: Does this even make sense?
llvm_unreachable("Not implemented");
}
// Not a protocol conformance.
default:
return Nothing;
}
lastRecordOffset.reset();
DeclID protoID;
unsigned valueCount, typeCount, inheritedCount, defaultedCount;
ArrayRef<uint64_t> rawIDs;
ProtocolConformanceLayout::readRecord(scratch, protoID,
valueCount, typeCount,
inheritedCount, defaultedCount,
rawIDs);
NormalProtocolConformanceLayout::readRecord(scratch, protoID,
valueCount, typeCount,
inheritedCount, defaultedCount,
rawIDs);
InheritedConformanceMap inheritedConformances;
@@ -281,72 +389,29 @@ Optional<ConformancePair> ModuleFile::maybeReadConformance(Type conformingType){
// Reset the offset RAII to the end of the trailing records.
lastRecordOffset.reset();
std::unique_ptr<ProtocolConformance> conformance(
new ProtocolConformance(conformingType,
proto,
ModuleContext,
std::move(witnesses),
std::move(typeWitnesses),
std::move(inheritedConformances),
defaultedDefinitions));
return std::make_pair(proto, conformance.release());
return { proto,
ctx.getConformance(conformingType, proto, ModuleContext,
std::move(witnesses),
std::move(typeWitnesses),
std::move(inheritedConformances),
defaultedDefinitions) };
}
/// Uniques a single protocol conformance and any inherited conformances, and
/// records the conformances in the ASTContext.
///
/// If a conformance has already been recorded, the given record \p conformance
/// will be deleted.
///
/// \returns The canonical conformance for this (\p decl, \p canTy) pair.
///
/// \sa processConformances
/// Applies protocol conformances to a decl.
template <typename T>
ProtocolConformance *processConformance(ASTContext &ctx, T decl, CanType canTy,
ProtocolDecl *proto,
ProtocolConformance *conformance) {
if (!conformance)
return nullptr;
for (auto &inherited : conformance->getInheritedConformances()) {
inherited.second = processConformance(ctx, decl, canTy, inherited.first,
inherited.second);
}
auto &conformanceRecord = ctx.ConformsTo[{canTy, proto}];
if (conformanceRecord.getPointer() &&
conformanceRecord.getPointer() != conformance) {
delete conformance;
conformance = conformanceRecord.getPointer();
// Only explicit conformances ever get serialized.
conformanceRecord.setInt(true);
} else {
// Only explicit conformances ever get serialized.
conformanceRecord.setPointer(conformance);
conformanceRecord.setInt(true);
}
if (decl)
ctx.recordConformance(proto, decl);
return conformance;
}
/// Uniques and applies protocol conformances to a decl, as well as recording
/// the conformances in the ASTContext.
template <typename T>
void processConformances(ASTContext &ctx, T *decl, CanType canTy,
void processConformances(ASTContext &ctx, T *decl,
ArrayRef<ConformancePair> conformances) {
SmallVector<ProtocolDecl *, 16> protoBuf;
SmallVector<ProtocolConformance *, 16> conformanceBuf;
for (auto conformancePair : conformances) {
auto proto = conformancePair.first;
auto conformance = conformancePair.second;
conformance = processConformance(ctx, decl, canTy, proto, conformance);
protoBuf.push_back(proto);
conformanceBuf.push_back(conformance);
if (conformance && decl)
ctx.recordConformance(proto, decl);
}
decl->setProtocols(ctx.AllocateCopy(protoBuf));
@@ -379,17 +444,13 @@ Optional<Substitution> ModuleFile::maybeReadSubstitution() {
auto archetypeTy = getType(archetypeID)->castTo<ArchetypeType>();
auto replacementTy = getType(replacementID);
CanType canReplTy = replacementTy->getCanonicalType();
ASTContext &ctx = ModuleContext->Ctx;
SmallVector<ProtocolConformance *, 16> conformanceBuf;
while (numConformances--) {
auto conformancePair = maybeReadConformance(replacementTy);
assert(conformancePair.hasValue() && "Missing conformance");
auto conformance = processConformance(ctx, nullptr, canReplTy,
conformancePair->first,
conformancePair->second);
conformanceBuf.push_back(conformance);
conformanceBuf.push_back(conformancePair->second);
}
lastRecordOffset.reset();
@@ -672,12 +733,10 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional<DeclContext *> ForcedContext,
alias->setSuperclass(getType(superclassTypeID));
}
CanType canBaseTy = underlyingType.getType()->getCanonicalType();
SmallVector<ConformancePair, 16> conformances;
while (auto conformance = maybeReadConformance(underlyingType.getType()))
conformances.push_back(*conformance);
processConformances(ctx, alias, canBaseTy, conformances);
processConformances(ctx, alias, conformances);
break;
}
@@ -715,7 +774,7 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional<DeclContext *> ForcedContext,
SmallVector<ConformancePair, 16> conformances;
while (auto conformance = maybeReadConformance(canTy))
conformances.push_back(*conformance);
processConformances(ctx, theStruct, canTy, conformances);
processConformances(ctx, theStruct, conformances);
auto members = readMembers();
assert(members.hasValue() && "could not read struct members");
@@ -1046,7 +1105,7 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional<DeclContext *> ForcedContext,
SmallVector<ConformancePair, 16> conformances;
while (auto conformance = maybeReadConformance(canTy))
conformances.push_back(*conformance);
processConformances(ctx, theClass, canTy, conformances);
processConformances(ctx, theClass, conformances);
auto members = readMembers();
assert(members.hasValue() && "could not read class members");
@@ -1092,7 +1151,7 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional<DeclContext *> ForcedContext,
SmallVector<ConformancePair, 16> conformances;
while (auto conformance = maybeReadConformance(canTy))
conformances.push_back(*conformance);
processConformances(ctx, theUnion, canTy, conformances);
processConformances(ctx, theUnion, conformances);
auto members = readMembers();
assert(members.hasValue() && "could not read union members");
@@ -1203,7 +1262,7 @@ Decl *ModuleFile::getDecl(DeclID DID, Optional<DeclContext *> ForcedContext,
SmallVector<ConformancePair, 16> conformances;
while (auto conformance = maybeReadConformance(canBaseTy))
conformances.push_back(*conformance);
processConformances(ctx, extension, canBaseTy, conformances);
processConformances(ctx, extension, conformances);
auto members = readMembers();
assert(members.hasValue() && "could not read extension members");
@@ -1716,9 +1775,11 @@ Type ModuleFile::getType(TypeID TID) {
case decls_block::BOUND_GENERIC_TYPE: {
DeclID declID;
TypeID parentID;
unsigned numSubstitutions;
ArrayRef<uint64_t> rawArgumentIDs;
decls_block::BoundGenericTypeLayout::readRecord(scratch, declID, parentID,
numSubstitutions,
rawArgumentIDs);
SmallVector<Type, 8> genericArgs;
for (TypeID type : rawArgumentIDs)
@@ -1736,9 +1797,9 @@ Type ModuleFile::getType(TypeID TID) {
break;
SmallVector<Substitution, 8> substitutions;
while (auto sub = maybeReadSubstitution())
substitutions.push_back(sub.getValue());
while (numSubstitutions--) {
substitutions.push_back(*maybeReadSubstitution());
}
boundTy->setSubstitutions(ctx.AllocateCopy(substitutions));
break;
}