[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.
This commit is contained in:
Nate Chandler
2023-10-24 15:52:21 -07:00
parent 8381f01e9e
commit b8990b51e8
4 changed files with 512 additions and 6 deletions

View File

@@ -673,7 +673,8 @@ public:
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.

View File

@@ -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<ConcreteCacheEntry>(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<ConcreteCacheEntry>(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<CanType, 4> 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<ClassType>(ty)) {
if (auto rawSuperTy = ct->getSuperclass()) {
auto superTy = rawSuperTy->getCanonicalType();
worklist.insert(superTy);
}
} else if (auto bgt = dyn_cast<BoundGenericType>(ty)) {
if (auto ct = dyn_cast<BoundGenericClassType>(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<TupleType>(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();

View File

@@ -271,6 +271,22 @@ public:
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) {

View File

@@ -0,0 +1,306 @@
// RUN: %target-swift-frontend -emit-ir %s | %FileCheck %s
sil_stage canonical
import Builtin
protocol P {
associatedtype Assoc
}
struct Box<T> {
var t: T
}
sil @getBox : $<T> (@thick T.Type) -> (@out Box<T>)
sil @getBoxBox : $<T> (@thick T.Type) -> (@out Box<Box<T>>)
enum Affirm<T> {
case yes(T)
}
sil @getAffirm : $<T> (@thick T.Type) -> (@out Affirm<T>)
sil @getAffirmAffirm : $<T> (@thick T.Type) -> (@out Affirm<Affirm<T>>)
class K<T> {
var t: T
}
sil @getK : $<T> (@thick T.Type) -> (@out K<T>)
sil @getKK : $<T> (@thick T.Type) -> (@out K<K<T>>)
sil @getTuple : $<T> (@thick T.Type) -> (@out (T, Builtin.Int1))
sil @getTupleTuple : $<T> (@thick T.Type) -> (@out ((T, Builtin.Int1), Builtin.Int1))
struct Vari<each T> {
var ts : (repeat each T)
}
sil @getVari : $<each T> () -> (@out Vari<each T>)
sil @takeTy : $<T> (@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<T.Assoc> in state ::Complete
// %2 = call swiftcc %swift.metadata_response @"$s4main3BoxVMa"(i64 0, ptr %T.Assoc)
// // Now that we have the complete metadata for Box<T.Assoc>, 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) <T : P> () -> () {
entry:
%box = alloc_stack $Box<T.Assoc>
cond_br undef, left, right
left:
%mt = metatype $@thick T.Assoc.Type
%getBox = function_ref @getBox : $@convention(thin) <T> (@thick T.Type) -> (@out Box<T>)
apply %getBox<T.Assoc>(%box, %mt) : $@convention(thin) <T> (@thick T.Type) -> (@out Box<T>)
destroy_addr %box : $*Box<T.Assoc>
br exit
right:
br exit
exit:
dealloc_stack %box : $*Box<T.Assoc>
%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) <T : P> () -> () {
entry:
%box = alloc_stack $Box<Box<T.Assoc>>
cond_br undef, left, right
left:
%mtb = metatype $@thick Box<T.Assoc>.Type
%getBox = function_ref @getBox : $@convention(thin) <T> (@thick T.Type) -> (@out Box<T>)
apply %getBox<Box<T.Assoc>>(%box, %mtb) : $@convention(thin) <T> (@thick T.Type) -> (@out Box<T>)
destroy_addr %box : $*Box<Box<T.Assoc>>
%mt = metatype $@thick T.Assoc.Type
%getBoxBox = function_ref @getBoxBox : $@convention(thin) <T> (@thick T.Type) -> (@out Box<Box<T>>)
apply %getBoxBox<T.Assoc>(%box, %mt) : $@convention(thin) <T> (@thick T.Type) -> (@out Box<Box<T>>)
destroy_addr %box : $*Box<Box<T.Assoc>>
br exit
right:
br exit
exit:
dealloc_stack %box : $*Box<Box<T.Assoc>>
%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) <T : P> () -> () {
entry:
%affirm = alloc_stack $Affirm<T.Assoc>
cond_br undef, left, right
left:
%mt = metatype $@thick T.Assoc.Type
%getAffirm = function_ref @getAffirm : $@convention(thin) <T> (@thick T.Type) -> (@out Affirm<T>)
apply %getAffirm<T.Assoc>(%affirm, %mt) : $@convention(thin) <T> (@thick T.Type) -> (@out Affirm<T>)
destroy_addr %affirm : $*Affirm<T.Assoc>
br exit
right:
br exit
exit:
dealloc_stack %affirm : $*Affirm<T.Assoc>
%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) <T : P> () -> () {
entry:
%affirm = alloc_stack $Affirm<Affirm<T.Assoc>>
cond_br undef, left, right
left:
%mtb = metatype $@thick Affirm<T.Assoc>.Type
%getAffirm = function_ref @getAffirm : $@convention(thin) <T> (@thick T.Type) -> (@out Affirm<T>)
apply %getAffirm<Affirm<T.Assoc>>(%affirm, %mtb) : $@convention(thin) <T> (@thick T.Type) -> (@out Affirm<T>)
destroy_addr %affirm : $*Affirm<Affirm<T.Assoc>>
%mt = metatype $@thick T.Assoc.Type
%getAffirmAffirm = function_ref @getAffirmAffirm : $@convention(thin) <T> (@thick T.Type) -> (@out Affirm<Affirm<T>>)
apply %getAffirmAffirm<T.Assoc>(%affirm, %mt) : $@convention(thin) <T> (@thick T.Type) -> (@out Affirm<Affirm<T>>)
destroy_addr %affirm : $*Affirm<Affirm<T.Assoc>>
br exit
right:
br exit
exit:
dealloc_stack %affirm : $*Affirm<Affirm<T.Assoc>>
%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) <T : P> () -> () {
entry:
%k = alloc_stack $K<T.Assoc>
cond_br undef, left, right
left:
%mt = metatype $@thick T.Assoc.Type
%getK = function_ref @getK : $@convention(thin) <T> (@thick T.Type) -> (@out K<T>)
apply %getK<T.Assoc>(%k, %mt) : $@convention(thin) <T> (@thick T.Type) -> (@out K<T>)
destroy_addr %k : $*K<T.Assoc>
br exit
right:
br exit
exit:
dealloc_stack %k : $*K<T.Assoc>
%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) <T : P> () -> () {
entry:
%k = alloc_stack $K<K<T.Assoc>>
cond_br undef, left, right
left:
%mtb = metatype $@thick K<T.Assoc>.Type
%getK = function_ref @getK : $@convention(thin) <T> (@thick T.Type) -> (@out K<T>)
apply %getK<K<T.Assoc>>(%k, %mtb) : $@convention(thin) <T> (@thick T.Type) -> (@out K<T>)
destroy_addr %k : $*K<K<T.Assoc>>
%mt = metatype $@thick T.Assoc.Type
%getKK = function_ref @getKK : $@convention(thin) <T> (@thick T.Type) -> (@out K<K<T>>)
apply %getKK<T.Assoc>(%k, %mt) : $@convention(thin) <T> (@thick T.Type) -> (@out K<K<T>>)
destroy_addr %k : $*K<K<T.Assoc>>
br exit
right:
br exit
exit:
dealloc_stack %k : $*K<K<T.Assoc>>
%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) <T : P> () -> () {
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) <T> (@thick T.Type) -> (@out (T, Builtin.Int1))
apply %get<T.Assoc>(%tuple, %mt) : $@convention(thin) <T> (@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) <T : P> () -> () {
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) <T> (@thick T.Type) -> (@out (T, Builtin.Int1))
apply %get<(T.Assoc, Builtin.Int1)>(%tuple, %mtb) : $@convention(thin) <T> (@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) <T> (@thick T.Type) -> (@out ((T, Builtin.Int1), Builtin.Int1))
apply %getTupleTuple<T.Assoc>(%tuple, %mt) : $@convention(thin) <T> (@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) <T : P, U : P> () -> () {
entry:
%vari = alloc_stack $Vari<T.Assoc, U.Assoc>
cond_br undef, left, right
left:
%mt = metatype $@thick T.Assoc.Type
%takeTy = function_ref @takeTy : $@convention(thin) <T> (@thick T.Type) -> ()
apply %takeTy<T.Assoc>(%mt) : $@convention(thin) <T> (@thick T.Type) -> ()
%mu = metatype $@thick U.Assoc.Type
apply %takeTy<U.Assoc>(%mu) : $@convention(thin) <T> (@thick T.Type) -> ()
br exit
right:
br exit
exit:
dealloc_stack %vari : $*Vari<T.Assoc, U.Assoc>
%retval = tuple ()
return %retval : $()
}