From b8990b51e82ee700aea986ef47bb4f803334f81b Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Tue, 24 Oct 2023 15:52:21 -0700 Subject: [PATCH] [IRGen] Promote generic args to complete. When metadata record for a generic type is locally cached as ::Complete, the metadata is known to be complete in that scope. If it's for a generic type, being complete means that all of its recursive generic arguments are also complete. Previously, though, that fact was not exploited. So a metadata record for such an argument which was originally obtained via an incomplete request would remain cached in that incomplete state even when a generic type in which it appeared as a generic argument was cached as complete. At worst, the result was a runtime call (checkMetadataState) to promote the complete the metadata record for the recursive argument. Here, when a bound generic type's metadata is locally cached as complete, its recursive generic arguments are visited; for each such type for which a metadata record is already locally cached, the preexisting record is recached as complete. --- lib/IRGen/IRGenFunction.h | 3 +- lib/IRGen/LocalTypeData.cpp | 193 +++++++++++++++++++- lib/IRGen/LocalTypeData.h | 16 ++ test/IRGen/minimal_metadata.sil | 306 ++++++++++++++++++++++++++++++++ 4 files changed, 512 insertions(+), 6 deletions(-) create mode 100644 test/IRGen/minimal_metadata.sil diff --git a/lib/IRGen/IRGenFunction.h b/lib/IRGen/IRGenFunction.h index da0e73853e0a4..fffda2a04c5d3 100644 --- a/lib/IRGen/IRGenFunction.h +++ b/lib/IRGen/IRGenFunction.h @@ -673,7 +673,8 @@ class IRGenFunction { MetadataResponse tryGetConcreteLocalTypeData(LocalTypeDataKey key, DynamicMetadataRequest request); void setUnscopedLocalTypeData(LocalTypeDataKey key, MetadataResponse value); - void setScopedLocalTypeData(LocalTypeDataKey key, MetadataResponse value); + void setScopedLocalTypeData(LocalTypeDataKey key, MetadataResponse value, + bool mayEmitDebugInfo = true); /// Given a concrete type metadata node, add all the local type data /// that we can reach from it. diff --git a/lib/IRGen/LocalTypeData.cpp b/lib/IRGen/LocalTypeData.cpp index 4474d0f97fb48..b188a257595eb 100644 --- a/lib/IRGen/LocalTypeData.cpp +++ b/lib/IRGen/LocalTypeData.cpp @@ -28,6 +28,7 @@ #include "swift/AST/IRGenOptions.h" #include "swift/AST/PackConformance.h" #include "swift/AST/ProtocolConformance.h" +#include "swift/Basic/GraphNodeWorklist.h" #include "swift/SIL/SILModule.h" using namespace swift; @@ -304,6 +305,84 @@ LocalTypeDataCache::tryGet(IRGenFunction &IGF, LocalTypeDataKey key, llvm_unreachable("bad cache entry kind"); } +LocalTypeDataCache::StateAdvancement LocalTypeDataCache::advanceStateInScope( + IRGenFunction &IGF, LocalTypeDataKey key, MetadataState state) { + // Use the caching key. + key = key.getCachingKey(); + + auto iterator = Map.find(key); + // There's no chain of entries, no no entry which could possibly be used. + if (iterator == Map.end()) + return StateAdvancement::NoEntry; + auto &chain = iterator->second; + + // Scan the chain for entries with the appropriate relationship to the active + // dominance scope, and "promote their state". Any entry whose state is + // already at least as complete than `state` is unaffected, and results in + // exiting early. + // + // There are two cases of interest: + // + // (1) DominancePoint(entry) dominates ActiveDominancePoint . + // (2) DominancePoint(entry) is dominated by ActiveDominancePoint . + // + // For (1), a new cache entry is created at ActiveDominancePoint. + // For (2), the state of the existing entry would be updated. + // + // Because of the order in which IRGen lowers, however, (2) can't actually + // happen: metadata whose dominance point is dominated by + // ActiveDominancePoint would not have been emitted yet. + + // Find the best entry in the chain from which to produce a new entry. + CacheEntry *best = nullptr; + for (auto *link = chain.Root; link; link = link->getNext()) { + // In case (1)? + if (!IGF.isActiveDominancePointDominatedBy(link->DefinitionPoint)) + continue; + + switch (link->getKind()) { + case CacheEntry::Kind::Concrete: { + auto entry = cast(link); + // If the entry is already as complete as `state`, it doesn't need to be + // used to create a new entry. In fact, no new entry needs to be created + // at all: this entry will be seen to be best if locally cached metadata + // is requested later. Stop traversal and return. + if (isAtLeast(entry->Value.getStaticLowerBoundOnState(), state)) + return StateAdvancement::AlreadyAtLeast; + + // Any suitable concrete entry is equally ideal. + best = entry; + break; + } + case CacheEntry::Kind::Abstract: { + // TODO: Consider the cost to materialize the abstract entry in order to + // determine which is best. + break; + } + } + } + + if (!best) + return StateAdvancement::NoEntry; + + switch (best->getKind()) { + case CacheEntry::Kind::Concrete: { + auto *entry = cast(best); + // Create a new entry at the ActiveDominancePoint. + auto response = + MetadataResponse::forBounded(entry->Value.getMetadata(), state); + IGF.setScopedLocalTypeData(key, response, + /*mayEmitDebugInfo=*/false); + + return StateAdvancement::Advanced; + } + case CacheEntry::Kind::Abstract: + // TODO: Advance abstract entries. + return StateAdvancement::NoEntry; + } + llvm_unreachable("covered switch!?"); +} + MetadataResponse LocalTypeDataCache::AbstractCacheEntry::follow(IRGenFunction &IGF, AbstractSource &source, @@ -381,10 +460,112 @@ IRGenFunction::setScopedLocalTypeMetadataForLayout(SILType type, setScopedLocalTypeData(key, response); } -void IRGenFunction::setScopedLocalTypeMetadata(CanType type, - MetadataResponse response) { +namespace { + +void setScopedLocalTypeMetadataImpl(IRGenFunction &IGF, CanType type, + MetadataResponse response) { auto key = LocalTypeDataKey(type, LocalTypeDataKind::forFormalTypeMetadata()); - setScopedLocalTypeData(key, response); + IGF.setScopedLocalTypeData(key, response); +} + +/// Walks the types upon whose corresponding metadata records' completeness the +/// completeness of \p rootTy's metadata record depends. For each such type, +/// marks the corresponding locally cached metadata record, if any, complete. +class TransitiveMetadataCompletion { + IRGenFunction &IGF; + LocalTypeDataCache &cache; + CanType rootTy; + GraphNodeWorklist worklist; + +public: + TransitiveMetadataCompletion(IRGenFunction &IGF, LocalTypeDataCache &cache, + CanType rootTy) + : IGF(IGF), cache(cache), rootTy(rootTy) {} + + void complete(); + +private: + /// Marks the metadata record currently locally cached corresponding to \p ty + /// complete. + /// + /// Returns whether \p ty's transitive metadata should be marked complete. + bool visit(CanType ty) { + // If it's the root type, it's already been marked complete, but we want to + // mark its transitively dependent metadata as complete. + if (ty == rootTy) + return true; + auto key = LocalTypeDataKey(ty, LocalTypeDataKind::forFormalTypeMetadata()); + // The metadata record was already marked complete. When that was done, the + // records for types it has transitive completeness requirements on would + // have been marked complete, if they had already been materialized. + // + // Such records may have been materialized since then in an abstract state, + // but that is an unlikely case and scanning again would incur compile-time + // overhead. + if (cache.advanceStateInScope(IGF, key, MetadataState::Complete) == + LocalTypeDataCache::StateAdvancement::AlreadyAtLeast) + return false; + return true; + } +}; + +void TransitiveMetadataCompletion::complete() { + worklist.initialize(rootTy); + + while (auto ty = worklist.pop()) { + if (!visit(ty)) { + // The transitively dependent metadata of `ty` doesn't need to be marked + // complete. + continue; + } + + // Walk into every type that `ty` has transitive completeness requirements + // on and mark each one transitively complete. + // + // This should mirror findAnyTransitiveMetadata: every type whose metadata + // is visited (i.e. has predicate called on it) by that function should be + // pushed onto the worklist. + if (auto ct = dyn_cast(ty)) { + if (auto rawSuperTy = ct->getSuperclass()) { + auto superTy = rawSuperTy->getCanonicalType(); + worklist.insert(superTy); + } + } else if (auto bgt = dyn_cast(ty)) { + if (auto ct = dyn_cast(bgt)) { + if (auto rawSuperTy = ct->getSuperclass()) { + auto superTy = rawSuperTy->getCanonicalType(); + worklist.insert(superTy); + } + } + for (auto arg : bgt->getExpandedGenericArgs()) { + auto childTy = arg->getCanonicalType(); + worklist.insert(childTy); + } + } else if (auto tt = dyn_cast(ty)) { + for (auto elt : tt.getElementTypes()) { + worklist.insert(elt); + } + } + } +} + +} // end anonymous namespace + +void IRGenFunction::setScopedLocalTypeMetadata(CanType rootTy, + MetadataResponse response) { + setScopedLocalTypeMetadataImpl(*this, rootTy, response); + + if (response.getStaticLowerBoundOnState() != MetadataState::Complete) + return; + + // If the metadata record is complete, then it is _transitively_ complete. + // So every metadata record that it has transitive completeness requirements + // on must also be complete. + // + // Mark all such already materialized metadata that the given type has + // transitive completeness requirements on as complete. + TransitiveMetadataCompletion(*this, getOrCreateLocalTypeData(), rootTy) + .complete(); } void IRGenFunction::setScopedLocalTypeData(CanType type, @@ -404,8 +585,10 @@ void IRGenFunction::setScopedLocalTypeDataForLayout(SILType type, } void IRGenFunction::setScopedLocalTypeData(LocalTypeDataKey key, - MetadataResponse value) { - maybeEmitDebugInfoForLocalTypeData(*this, key, value); + MetadataResponse value, + bool mayEmitDebugInfo) { + if (mayEmitDebugInfo) + maybeEmitDebugInfoForLocalTypeData(*this, key, value); // Register with the active ConditionalDominanceScope if necessary. bool isConditional = isConditionalDominancePoint(); diff --git a/lib/IRGen/LocalTypeData.h b/lib/IRGen/LocalTypeData.h index e2fc44ee08b08..1cce1aa90e259 100644 --- a/lib/IRGen/LocalTypeData.h +++ b/lib/IRGen/LocalTypeData.h @@ -271,6 +271,22 @@ class LocalTypeDataCache { MetadataResponse tryGet(IRGenFunction &IGF, LocalTypeDataKey key, bool allowAbstract, DynamicMetadataRequest request); + /// Whether the cached state was advanced or otherwise why not. + enum class StateAdvancement { + /// No entry whose state could be advanced was found. + NoEntry, + /// An entry was found whose state was already advanced at least as far as + /// the indicated state. + AlreadyAtLeast, + /// The state was advanced. + Advanced, + }; + + /// Advances the state cached for \p key to \p state within the active + /// dominance scope. + StateAdvancement advanceStateInScope(IRGenFunction &IGF, LocalTypeDataKey key, + MetadataState state); + /// Add a new concrete entry to the cache at the given definition point. void addConcrete(DominancePoint point, bool isConditional, LocalTypeDataKey key, MetadataResponse value) { diff --git a/test/IRGen/minimal_metadata.sil b/test/IRGen/minimal_metadata.sil new file mode 100644 index 0000000000000..7888cd5372c31 --- /dev/null +++ b/test/IRGen/minimal_metadata.sil @@ -0,0 +1,306 @@ +// RUN: %target-swift-frontend -emit-ir %s | %FileCheck %s + +sil_stage canonical + +import Builtin + +protocol P { + associatedtype Assoc +} + +struct Box { + var t: T +} + +sil @getBox : $ (@thick T.Type) -> (@out Box) +sil @getBoxBox : $ (@thick T.Type) -> (@out Box>) + +enum Affirm { + case yes(T) +} + +sil @getAffirm : $ (@thick T.Type) -> (@out Affirm) +sil @getAffirmAffirm : $ (@thick T.Type) -> (@out Affirm>) + +class K { + var t: T +} + +sil @getK : $ (@thick T.Type) -> (@out K) +sil @getKK : $ (@thick T.Type) -> (@out K>) + +sil @getTuple : $ (@thick T.Type) -> (@out (T, Builtin.Int1)) +sil @getTupleTuple : $ (@thick T.Type) -> (@out ((T, Builtin.Int1), Builtin.Int1)) + +struct Vari { + var ts : (repeat each T) +} + +sil @getVari : $ () -> (@out Vari) + +sil @takeTy : $ (@thick T.Type) -> () + +// CHECK-LABEL: define {{.*}}@test_struct(ptr %T, ptr %T.P) +// // The metadata for T.Assoc is for obtained in state ::Abstract VVVVVVV +// %0 = call swiftcc %swift.metadata_response @swift_getAssociatedTypeWitness(i64 255, ptr %T.P, ptr %T, ... +// %T.Assoc = extractvalue %swift.metadata_response %0, 0 +// // It's used to obtain the metadata for Box in state ::Complete +// %2 = call swiftcc %swift.metadata_response @"$s4main3BoxVMa"(i64 0, ptr %T.Assoc) +// // Now that we have the complete metadata for Box, the metadata for T.Assoc is also complete. +// +// // Verify that the metadata for T.Assoc isn't spuriously completed again. +// CHECK-NOT: call swiftcc %swift.metadata_response @swift_checkMetadataState(i64 0, ptr %T.Assoc) +// // Verify that the metadata for T.Assoc from swift_getAssociatedTypeWitness is reused directly in the getBox call. +// CHECK: call swiftcc void @getBox(ptr noalias sret(%swift.opaque) %6, ptr %T.Assoc, ptr %T.Assoc) +// } +sil @test_struct : $@convention(thin) () -> () { +entry: + %box = alloc_stack $Box + cond_br undef, left, right + +left: + %mt = metatype $@thick T.Assoc.Type + %getBox = function_ref @getBox : $@convention(thin) (@thick T.Type) -> (@out Box) + apply %getBox(%box, %mt) : $@convention(thin) (@thick T.Type) -> (@out Box) + destroy_addr %box : $*Box + br exit + +right: + br exit + +exit: + dealloc_stack %box : $*Box + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_struct_nested(ptr %T, ptr %T.P) +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getBox +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getBoxBox +// } +sil @test_struct_nested : $@convention(thin) () -> () { +entry: + %box = alloc_stack $Box> + cond_br undef, left, right + +left: + %mtb = metatype $@thick Box.Type + %getBox = function_ref @getBox : $@convention(thin) (@thick T.Type) -> (@out Box) + apply %getBox>(%box, %mtb) : $@convention(thin) (@thick T.Type) -> (@out Box) + destroy_addr %box : $*Box> + + %mt = metatype $@thick T.Assoc.Type + %getBoxBox = function_ref @getBoxBox : $@convention(thin) (@thick T.Type) -> (@out Box>) + apply %getBoxBox(%box, %mt) : $@convention(thin) (@thick T.Type) -> (@out Box>) + destroy_addr %box : $*Box> + br exit + +right: + br exit + +exit: + dealloc_stack %box : $*Box> + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_enum(ptr %T, ptr %T.P) +// CHECK-NOT: call swiftcc %swift.metadata_response @swift_checkMetadataState(i64 0, ptr %T.Assoc) +// CHECK: call swiftcc void @getAffirm +// } +sil @test_enum : $@convention(thin) () -> () { +entry: + %affirm = alloc_stack $Affirm + cond_br undef, left, right + +left: + %mt = metatype $@thick T.Assoc.Type + %getAffirm = function_ref @getAffirm : $@convention(thin) (@thick T.Type) -> (@out Affirm) + apply %getAffirm(%affirm, %mt) : $@convention(thin) (@thick T.Type) -> (@out Affirm) + destroy_addr %affirm : $*Affirm + br exit + +right: + br exit + +exit: + dealloc_stack %affirm : $*Affirm + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_enum_nested(ptr %T, ptr %T.P) +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getAffirm +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getAffirmAffirm +// } +sil @test_enum_nested : $@convention(thin) () -> () { +entry: + %affirm = alloc_stack $Affirm> + cond_br undef, left, right + +left: + %mtb = metatype $@thick Affirm.Type + %getAffirm = function_ref @getAffirm : $@convention(thin) (@thick T.Type) -> (@out Affirm) + apply %getAffirm>(%affirm, %mtb) : $@convention(thin) (@thick T.Type) -> (@out Affirm) + destroy_addr %affirm : $*Affirm> + + %mt = metatype $@thick T.Assoc.Type + %getAffirmAffirm = function_ref @getAffirmAffirm : $@convention(thin) (@thick T.Type) -> (@out Affirm>) + apply %getAffirmAffirm(%affirm, %mt) : $@convention(thin) (@thick T.Type) -> (@out Affirm>) + destroy_addr %affirm : $*Affirm> + br exit + +right: + br exit + +exit: + dealloc_stack %affirm : $*Affirm> + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_class(ptr %T, ptr %T.P) +// CHECK-NOT: call swiftcc %swift.metadata_response @swift_checkMetadataState(i64 0, ptr %T.Assoc) +// CHECK: call swiftcc void @getK +// } +sil @test_class : $@convention(thin) () -> () { +entry: + %k = alloc_stack $K + cond_br undef, left, right + +left: + %mt = metatype $@thick T.Assoc.Type + %getK = function_ref @getK : $@convention(thin) (@thick T.Type) -> (@out K) + apply %getK(%k, %mt) : $@convention(thin) (@thick T.Type) -> (@out K) + destroy_addr %k : $*K + br exit + +right: + br exit + +exit: + dealloc_stack %k : $*K + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_class_nested(ptr %T, ptr %T.P) +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getK +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getKK +// } +sil @test_class_nested : $@convention(thin) () -> () { +entry: + %k = alloc_stack $K> + cond_br undef, left, right + +left: + %mtb = metatype $@thick K.Type + %getK = function_ref @getK : $@convention(thin) (@thick T.Type) -> (@out K) + apply %getK>(%k, %mtb) : $@convention(thin) (@thick T.Type) -> (@out K) + destroy_addr %k : $*K> + + %mt = metatype $@thick T.Assoc.Type + %getKK = function_ref @getKK : $@convention(thin) (@thick T.Type) -> (@out K>) + apply %getKK(%k, %mt) : $@convention(thin) (@thick T.Type) -> (@out K>) + destroy_addr %k : $*K> + br exit + +right: + br exit + +exit: + dealloc_stack %k : $*K> + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_tuple(ptr %T, ptr %T.P) +// CHECK-NOT: call swiftcc %swift.metadata_response @swift_checkMetadataState(i64 0, ptr %T.Assoc) +// CHECK: call swiftcc void @getTuple +// } +sil @test_tuple : $@convention(thin) () -> () { +entry: + %tuple = alloc_stack $(T.Assoc, Builtin.Int1) + cond_br undef, left, right + +left: + %mt = metatype $@thick T.Assoc.Type + %get = function_ref @getTuple : $@convention(thin) (@thick T.Type) -> (@out (T, Builtin.Int1)) + apply %get(%tuple, %mt) : $@convention(thin) (@thick T.Type) -> (@out (T, Builtin.Int1)) + destroy_addr %tuple : $*(T.Assoc, Builtin.Int1) + br exit + +right: + br exit + +exit: + dealloc_stack %tuple : $*(T.Assoc, Builtin.Int1) + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_tuple_nested(ptr %T, ptr %T.P) +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getTuple +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @getTupleTuple +// } +sil @test_tuple_nested : $@convention(thin) () -> () { +entry: + %tuple = alloc_stack $((T.Assoc, Builtin.Int1), Builtin.Int1) + cond_br undef, left, right + +left: + %mtb = metatype $@thick (T.Assoc, Builtin.Int1).Type + %get = function_ref @getTuple : $@convention(thin) (@thick T.Type) -> (@out (T, Builtin.Int1)) + apply %get<(T.Assoc, Builtin.Int1)>(%tuple, %mtb) : $@convention(thin) (@thick T.Type) -> (@out (T, Builtin.Int1)) + destroy_addr %tuple : $*((T.Assoc, Builtin.Int1), Builtin.Int1) + + %mt = metatype $@thick T.Assoc.Type + %getTupleTuple = function_ref @getTupleTuple : $@convention(thin) (@thick T.Type) -> (@out ((T, Builtin.Int1), Builtin.Int1)) + apply %getTupleTuple(%tuple, %mt) : $@convention(thin) (@thick T.Type) -> (@out ((T, Builtin.Int1), Builtin.Int1)) + destroy_addr %tuple : $*((T.Assoc, Builtin.Int1), Builtin.Int1) + br exit + +right: + br exit + +exit: + dealloc_stack %tuple : $*((T.Assoc, Builtin.Int1), Builtin.Int1) + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: define {{.*}}@test_variadic_struct(ptr %T, ptr %U, ptr %T.P, ptr %U.P) +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @takeTy +// CHECK-NOT: @swift_checkMetadataState +// CHECK: call swiftcc void @takeTy +// } +sil @test_variadic_struct : $@convention(thin) () -> () { +entry: + %vari = alloc_stack $Vari + cond_br undef, left, right + +left: + %mt = metatype $@thick T.Assoc.Type + %takeTy = function_ref @takeTy : $@convention(thin) (@thick T.Type) -> () + apply %takeTy(%mt) : $@convention(thin) (@thick T.Type) -> () + %mu = metatype $@thick U.Assoc.Type + apply %takeTy(%mu) : $@convention(thin) (@thick T.Type) -> () + br exit + +right: + br exit + +exit: + dealloc_stack %vari : $*Vari + %retval = tuple () + return %retval : $() +}