//===--- Fulfillment.cpp - Static metadata search ------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 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 // //===----------------------------------------------------------------------===// // // This file implements routines for searching for ways to find metadata // from other metadata. // //===----------------------------------------------------------------------===// #include "Fulfillment.h" #include "IRGenModule.h" #include "GenericRequirement.h" #include "MetadataRequest.h" #include "ProtocolInfo.h" #include "swift/AST/Decl.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/SubstitutionMap.h" #include "swift/Basic/Assertions.h" #include "swift/SIL/SILWitnessTable.h" #include "swift/SIL/TypeLowering.h" using namespace swift; using namespace irgen; /// Is metadata for the given type kind a "leaf", or does it possibly /// store any other type metadata that we can statically extract? /// /// It's okay to conservatively answer "no". It's more important for this /// to be quick than for it to be accurate; don't recurse. static bool isLeafTypeMetadata(CanType type) { switch (type->getKind()) { #define SUGARED_TYPE(ID, SUPER) \ case TypeKind::ID: #define UNCHECKED_TYPE(ID, SUPER) \ case TypeKind::ID: #define TYPE(ID, SUPER) #include "swift/AST/TypeNodes.def" case TypeKind::Error: llvm_unreachable("kind is invalid for a canonical type"); #define ARTIFICIAL_TYPE(ID, SUPER) \ case TypeKind::ID: #define TYPE(ID, SUPER) #include "swift/AST/TypeNodes.def" case TypeKind::LValue: case TypeKind::InOut: case TypeKind::DynamicSelf: case TypeKind::PackExpansion: case TypeKind::PackElement: case TypeKind::BuiltinTuple: llvm_unreachable("these types do not have metadata"); // All the builtin types are leaves. #define BUILTIN_TYPE(ID, SUPER) \ case TypeKind::ID: #define TYPE(ID, SUPER) #include "swift/AST/TypeNodes.def" case TypeKind::Module: return true; // Type parameters are statically opaque. case TypeKind::PrimaryArchetype: case TypeKind::ExistentialArchetype: case TypeKind::OpaqueTypeArchetype: case TypeKind::PackArchetype: case TypeKind::ElementArchetype: case TypeKind::GenericTypeParam: case TypeKind::DependentMember: return true; // Only the empty tuple is a leaf. case TypeKind::Tuple: return cast(type)->getNumElements() == 0; case TypeKind::Pack: return cast(type)->getNumElements() == 0; // Nominal types might have generic parents. case TypeKind::Class: case TypeKind::Enum: case TypeKind::Protocol: case TypeKind::Struct: return !cast(type)->getDecl()->isGenericContext(); // Bound generic types have type arguments. case TypeKind::BoundGenericClass: case TypeKind::BoundGenericEnum: case TypeKind::BoundGenericStruct: return false; // Functions have component types. case TypeKind::Function: case TypeKind::GenericFunction: // included for future-proofing return false; // Protocol compositions have component types. case TypeKind::ProtocolComposition: return false; // Parametrized protocols have component types. case TypeKind::ParameterizedProtocol: return false; // Existential types have constraint types. case TypeKind::Existential: return false; // Metatypes have instance types. case TypeKind::Metatype: case TypeKind::ExistentialMetatype: return false; // Integer types are leaves. case TypeKind::Integer: return true; } llvm_unreachable("bad type kind"); } /// Given that we have a source for metadata of the given type, check /// to see if it fulfills anything. /// /// \param isExact - true if the metadata is known to be exactly the /// metadata for the given type, false if it might be a subtype bool FulfillmentMap::searchTypeMetadata(IRGenModule &IGM, CanType type, IsExact_t isExact, MetadataState metadataState, unsigned source, MetadataPath &&path, const InterestingKeysCallback &keys) { // If this is an exact source, and it's an interesting type, add this // as a fulfillment. if (isExact && keys.isInterestingType(type)) { // If the type isn't a leaf type, also check it as an inexact match. bool hadFulfillment = false; if (!isLeafTypeMetadata(type)) { hadFulfillment |= searchTypeMetadata(IGM, type, IsInexact, metadataState, source, MetadataPath(path), keys); } // Consider its super class bound. if (metadataState == MetadataState::Complete) { if (auto superclassTy = keys.getSuperclassBound(type)) { hadFulfillment |= searchNominalTypeMetadata( IGM, superclassTy, metadataState, source, std::move(path), keys); } } // Add the fulfillment. hadFulfillment |= addFulfillment(GenericRequirement::forMetadata(type), source, std::move(path), metadataState); return hadFulfillment; } // Search the superclass fields. We can only do this if the metadata // is complete. if (metadataState == MetadataState::Complete && keys.isInterestingType(type)) { if (auto superclassTy = keys.getSuperclassBound(type)) { return searchNominalTypeMetadata(IGM, superclassTy, metadataState, source, std::move(path), keys); } } // Inexact metadata will be a problem if we ever try to use this // to remember that we already have the metadata for something. if (isa(type) || isa(type)) { return searchNominalTypeMetadata(IGM, type, metadataState, source, std::move(path), keys); } // TODO: functions // TODO: metatypes return false; } /// Metadata fulfillment in a tuple conformance witness thunks. /// /// \return true if any fulfillments were added by this search. bool FulfillmentMap::searchTupleTypeMetadata(IRGenModule &IGM, CanTupleType tupleType, IsExact_t isExact, MetadataState metadataState, unsigned source, MetadataPath &&path, const InterestingKeysCallback &keys) { if (tupleType->getNumElements() == 1 && isa(tupleType.getElementType(0))) { bool hadFulfillment = false; auto packType = tupleType.getInducedPackType(); { auto argPath = path; argPath.addTuplePackComponent(); hadFulfillment |= searchTypeMetadataPack(IGM, packType, isExact, metadataState, source, std::move(argPath), keys); } { auto argPath = path; argPath.addTupleShapeComponent(); hadFulfillment |= searchShapeRequirement(IGM, packType, source, std::move(argPath)); } return hadFulfillment; } return false; } static CanType getSingletonPackExpansionParameter( CanPackType packType, const FulfillmentMap::InterestingKeysCallback &keys, std::optional &packExpansionComponent) { if (auto expansion = packType.unwrapSingletonPackExpansion()) { if (keys.isInterestingPackExpansion(expansion)) { packExpansionComponent = 0; return expansion.getPatternType(); } } return CanType(); } bool FulfillmentMap::searchTypeMetadataPack(IRGenModule &IGM, CanPackType packType, IsExact_t isExact, MetadataState metadataState, unsigned source, MetadataPath &&path, const InterestingKeysCallback &keys) { // We can fulfill pack parameters if the pack is a singleton pack // expansion over one. // TODO: we can also fulfill pack expansions if we can slice away // constant-sized prefixes and suffixes. std::optional packExpansionComponent; if (auto parameter = getSingletonPackExpansionParameter(packType, keys, packExpansionComponent)) { MetadataPath singletonPath = path; singletonPath.addPackExpansionPatternComponent(*packExpansionComponent); return addFulfillment(GenericRequirement::forMetadata(parameter), source, std::move(singletonPath), metadataState); } // TODO: fulfill non-expansion metadata out of the pack // TODO: fulfill the pack type itself return false; } bool FulfillmentMap::searchConformance( IRGenModule &IGM, const ProtocolConformance *conformance, unsigned sourceIndex, MetadataPath &&path, const InterestingKeysCallback &interestingKeys) { bool hadFulfillment = false; SILWitnessTable::enumerateWitnessTableConditionalConformances( conformance, [&](unsigned index, CanType type, ProtocolDecl *protocol) { std::optional packExpansionComponent; if (auto packType = dyn_cast(type)) { auto param = getSingletonPackExpansionParameter(packType, interestingKeys, packExpansionComponent); if (!param) return /*finished?*/ false; type = param; } MetadataPath conditionalPath = path; conditionalPath.addConditionalConformanceComponent(index); if (packExpansionComponent) conditionalPath.addPackExpansionPatternComponent(*packExpansionComponent); hadFulfillment |= searchWitnessTable(IGM, type, protocol, sourceIndex, std::move(conditionalPath), interestingKeys); return /*finished?*/ false; }); return hadFulfillment; } bool FulfillmentMap::searchWitnessTable(IRGenModule &IGM, CanType type, ProtocolDecl *protocol, unsigned source, MetadataPath &&path, const InterestingKeysCallback &keys) { assert(Lowering::TypeConverter::protocolRequiresWitnessTable(protocol)); llvm::SmallPtrSet interestingConformancesBuffer; llvm::SmallPtrSetImpl *interestingConformances = nullptr; // If the interesting-keys set is limiting the set of interesting // conformances, collect that filter. if (keys.hasInterestingType(type) && keys.hasLimitedInterestingConformances(type)) { // Bail out immediately if the set is empty. // This only makes sense because we're not trying to fulfill // associated types this way. auto requiredConformances = keys.getInterestingConformances(type); if (requiredConformances.empty()) return false; interestingConformancesBuffer.insert(requiredConformances.begin(), requiredConformances.end()); interestingConformances = &interestingConformancesBuffer; } return searchWitnessTable(IGM, type, protocol, source, std::move(path), keys, interestingConformances); } bool FulfillmentMap::searchWitnessTable( IRGenModule &IGM, CanType type, ProtocolDecl *protocol, unsigned source, MetadataPath &&path, const InterestingKeysCallback &keys, llvm::SmallPtrSetImpl *interestingConformances) { bool hadFulfillment = false; auto &pi = IGM.getProtocolInfo(protocol, ProtocolInfoKind::RequirementSignature); for (auto &entry : pi.getWitnessEntries()) { if (!entry.isBase()) continue; ProtocolDecl *inherited = entry.getBase(); MetadataPath inheritedPath = path; inheritedPath.addInheritedProtocolComponent(pi.getBaseWitnessIndex(&entry)); hadFulfillment |= searchWitnessTable(IGM, type, inherited, source, std::move(inheritedPath), keys, interestingConformances); } // If we're not limiting the set of interesting conformances, or if // this is an interesting conformance, record it. if (!interestingConformances || interestingConformances->count(protocol)) { hadFulfillment |= addFulfillment( GenericRequirement::forWitnessTable(type, protocol), source, std::move(path), MetadataState::Complete); } return hadFulfillment; } bool FulfillmentMap::searchNominalTypeMetadata(IRGenModule &IGM, CanType type, MetadataState metadataState, unsigned source, MetadataPath &&path, const InterestingKeysCallback &keys) { // Objective-C generics don't preserve their generic parameters at runtime, // so they aren't able to fulfill type metadata requirements. if (type.getAnyNominal()->hasClangNode()) { return false; } auto *nominal = type.getAnyNominal(); if (!nominal->isGenericContext() || isa(nominal)) { return false; } bool hadFulfillment = false; auto subs = type->getContextSubstitutionMap(); GenericTypeRequirements requirements(IGM, nominal); for (unsigned reqtIndex : indices(requirements.getRequirements())) { auto requirement = requirements.getRequirements()[reqtIndex]; // FIXME: The correct fix is to pass down the substitution map's // output generic signature and reduce the result of subst() with // this signature before forming the lookup key. // // Once that's fixed, change the below back to this: // auto arg = requirement.getTypeParameter().subst(subs)->getCanonicalType(); auto arg = requirement.getTypeParameter().subst( QuerySubstitutionMap{subs}, [&](InFlightSubstitution &IFS, Type origType, ProtocolDecl *proto) -> ProtocolConformanceRef { auto substType = origType.subst(IFS); if (substType->isTypeParameter()) return ProtocolConformanceRef::forAbstract(substType, proto); return subs.lookupConformance(origType->getCanonicalType(), proto); })->getCanonicalType(); // Skip uninteresting type arguments. if (!keys.hasInterestingType(arg)) continue; switch (requirement.getKind()) { case GenericRequirement::Kind::Shape: { // If the fulfilled value is a shape class, refine the path. MetadataPath argPath = path; argPath.addNominalTypeArgumentShapeComponent(reqtIndex); hadFulfillment |= searchShapeRequirement(IGM, arg, source, std::move(argPath)); break; } case GenericRequirement::Kind::Metadata: case GenericRequirement::Kind::MetadataPack: { // If the fulfilled value is type metadata, refine the path. auto argState = getPresumedMetadataStateForTypeArgument(metadataState); MetadataPath argPath = path; argPath.addNominalTypeArgumentComponent(reqtIndex); if (requirement.getKind() == GenericRequirement::Kind::Metadata) hadFulfillment |= searchTypeMetadata(IGM, arg, IsExact, argState, source, std::move(argPath), keys); else hadFulfillment |= searchTypeMetadataPack(IGM, cast(arg), IsExact, argState, source, std::move(argPath), keys); break; } case GenericRequirement::Kind::WitnessTablePack: case GenericRequirement::Kind::WitnessTable: { std::optional packExpansionComponent; if (requirement.getKind() == GenericRequirement::Kind::WitnessTable) { // Ignore it unless the type itself is interesting. if (!keys.isInterestingType(arg)) continue; } else { // Ignore it unless the pack is a singleton pack expansion of a // type parameter, in which case use that type below. auto param = getSingletonPackExpansionParameter(cast(arg), keys, packExpansionComponent); if (!param) continue; arg = param; } // Refine the path. MetadataPath argPath = path; argPath.addNominalTypeArgumentConformanceComponent(reqtIndex); if (packExpansionComponent) argPath.addPackExpansionPatternComponent(*packExpansionComponent); // This code just handles packs directly. hadFulfillment |= searchWitnessTable(IGM, arg, requirement.getProtocol(), source, std::move(argPath), keys); break; } case GenericRequirement::Kind::Value: { // Refine the path. MetadataPath argPath = path; argPath.addNominalValueArgumentComponent(reqtIndex); hadFulfillment |= addFulfillment(GenericRequirement::forValue(arg), source, std::move(argPath), MetadataState::Complete); break; } } } return hadFulfillment; } bool FulfillmentMap::searchShapeRequirement(IRGenModule &IGM, CanType argType, unsigned source, MetadataPath &&path) { // argType is the substitution for a pack parameter, so it should always // be a pack. auto packType = cast(argType); // For now, don't try to fulfill shapes if this isn't a singleton // pack containing a pack expansion. In theory, though, as long as // there aren't expansions over pack parameters with different shapes, // we should always be able to turn this into the equation // `ax + b = ` and then solve that. auto expansion = packType.unwrapSingletonPackExpansion(); if (!expansion) return false; path.addPackExpansionCountComponent(0); auto parameter = expansion.getCountType(); // Add the fulfillment. return addFulfillment(GenericRequirement::forShape(parameter), source, std::move(path), MetadataState::Complete); } /// Testify that there's a fulfillment at the given path. bool FulfillmentMap::addFulfillment(GenericRequirement key, unsigned source, MetadataPath &&path, MetadataState metadataState) { // Only add a fulfillment if we don't have any previous // fulfillment for that value or if it 's cheaper than the existing // fulfillment. auto it = Fulfillments.find(key); if (it != Fulfillments.end()) { // If the new fulfillment is worse than the existing one, ignore it. auto existingState = it->second.getState(); if (!isAtLeast(metadataState, existingState)) { return false; } // Consider cost only if the fulfillments are equivalent in state. // TODO: this is potentially suboptimal, but it generally won't matter. if (metadataState == existingState && path.cost() >= it->second.Path.cost()) { return false; } it->second.SourceIndex = source; it->second.Path = std::move(path); return true; } else { Fulfillments.insert({ key, Fulfillment(source, std::move(path), metadataState) }); return true; } } static StringRef getStateName(MetadataState state) { switch (state) { case MetadataState::Complete: return "complete"; case MetadataState::NonTransitiveComplete: return "non-transitive-complete"; case MetadataState::LayoutComplete: return "layout-complete"; case MetadataState::Abstract: return "abstract"; } llvm_unreachable("unhandled state"); } void FulfillmentMap::dump() const { auto &out = llvm::errs(); for (auto &entry : Fulfillments) { out << "("; entry.first.dump(out); out << ") => " << getStateName(entry.second.getState()) << " at sources[" << entry.second.SourceIndex << "]." << entry.second.Path << "\n"; } }