mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Resolve metadata cycles through non-generic value types with resilient layout.
The central thrust of this patch is to get these metadata initializations off of `swift_once` and onto the metadata-request system where we can properly detect and resolve dependencies. We do this by first introducing runtime support for resolving metadata requests for "in-place" initializations (committed previously) and then teaching IRGen to actually generate code to use them (this patch). A non-trivial amount of this patch is just renaming and refactoring some of existing infrastructure that was being used for in-place initializations to try to avoid unnecessary confusion. The remaining cases that are still using `swift_once` resolution of metadata initialization are: - non-generic classes that can't statically fill their superclass or have resilient internal layout - foreign type metadata Classes require more work because I'd like to switch at least the resilient-superclass case over to using a pattern much more like what we do with generic class instantiation. That is, I'd like in-place initialization to be reserved for classes that actually don't need relocation. Foreign metadata should also be updated to the request/dependency scheme before we declare ABI stability. I'm not sure why foreign metadata would ever require a type to be resolved, but let's assume it's possible. Fixes part of SR-7876.
This commit is contained in:
@@ -1284,16 +1284,18 @@ static bool isLoadFrom(llvm::Value *value, Address address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Emit the body of a lazy cache accessor.
|
||||
/// Emit the body of a cache accessor.
|
||||
///
|
||||
/// If cacheVariable is null, we perform the direct access every time.
|
||||
/// This is used for metadata accessors that come about due to resilience,
|
||||
/// where the direct access is completely trivial.
|
||||
void irgen::emitLazyCacheAccessFunction(IRGenModule &IGM,
|
||||
llvm::Function *accessor,
|
||||
llvm::GlobalVariable *cacheVariable,
|
||||
LazyCacheEmitter getValue,
|
||||
bool isReadNone) {
|
||||
void irgen::emitCacheAccessFunction(IRGenModule &IGM,
|
||||
llvm::Function *accessor,
|
||||
llvm::Constant *cacheVariable,
|
||||
CacheStrategy cacheStrategy,
|
||||
CacheEmitter getValue,
|
||||
bool isReadNone) {
|
||||
assert((cacheStrategy == CacheStrategy::None) == (cacheVariable == nullptr));
|
||||
accessor->setDoesNotThrow();
|
||||
|
||||
// This function is logically 'readnone': the caller does not need
|
||||
@@ -1310,8 +1312,10 @@ void irgen::emitLazyCacheAccessFunction(IRGenModule &IGM,
|
||||
bool returnsResponse =
|
||||
(accessor->getReturnType() == IGM.TypeMetadataResponseTy);
|
||||
|
||||
switch (cacheStrategy) {
|
||||
|
||||
// If there's no cache variable, just perform the direct access.
|
||||
if (cacheVariable == nullptr) {
|
||||
case CacheStrategy::None: {
|
||||
auto response = getValue(IGF, parameters);
|
||||
llvm::Value *ret;
|
||||
if (returnsResponse) {
|
||||
@@ -1325,13 +1329,22 @@ void irgen::emitLazyCacheAccessFunction(IRGenModule &IGM,
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the cache variable.
|
||||
// For in-place initialization, drill to the first element of the cache.
|
||||
case CacheStrategy::InPlaceInitialization:
|
||||
cacheVariable =
|
||||
llvm::ConstantExpr::getBitCast(cacheVariable,
|
||||
IGM.TypeMetadataPtrTy->getPointerTo());
|
||||
break;
|
||||
|
||||
case CacheStrategy::Lazy:
|
||||
break;
|
||||
}
|
||||
|
||||
llvm::Constant *null =
|
||||
llvm::ConstantPointerNull::get(
|
||||
cast<llvm::PointerType>(cacheVariable->getValueType()));
|
||||
cast<llvm::PointerType>(
|
||||
cacheVariable->getType()->getPointerElementType()));
|
||||
|
||||
cacheVariable->setInitializer(null);
|
||||
cacheVariable->setAlignment(IGM.getPointerAlignment().getValue());
|
||||
Address cache(cacheVariable, IGM.getPointerAlignment());
|
||||
|
||||
// Okay, first thing, check the cache variable.
|
||||
@@ -1381,33 +1394,41 @@ void irgen::emitLazyCacheAccessFunction(IRGenModule &IGM,
|
||||
|
||||
// Emit a branch around the caching code if we're working with responses
|
||||
// and the fetched result is not complete. We can avoid doing this if
|
||||
// the response is statically known to be complete.
|
||||
// the response is statically known to be complete, and we don't need to
|
||||
// do it if this is an in-place initiazation cache because the store
|
||||
// is done within the runtime.
|
||||
llvm::BasicBlock *completionCheckBB = nullptr;
|
||||
llvm::Value *directState = nullptr;
|
||||
if (returnsResponse && !response.isStaticallyKnownComplete()) {
|
||||
completionCheckBB = IGF.Builder.GetInsertBlock();
|
||||
if (cacheStrategy == CacheStrategy::InPlaceInitialization) {
|
||||
directState = response.getDynamicState();
|
||||
completionCheckBB = IGF.Builder.GetInsertBlock();
|
||||
} else {
|
||||
if (returnsResponse &&
|
||||
!response.isStaticallyKnownComplete()) {
|
||||
completionCheckBB = IGF.Builder.GetInsertBlock();
|
||||
directState = response.getDynamicState();
|
||||
|
||||
auto isCompleteBB = IGF.createBasicBlock("is_complete");
|
||||
auto isComplete =
|
||||
IGF.Builder.CreateICmpEQ(directState, completedState);
|
||||
auto isCompleteBB = IGF.createBasicBlock("is_complete");
|
||||
auto isComplete =
|
||||
IGF.Builder.CreateICmpEQ(directState, completedState);
|
||||
|
||||
IGF.Builder.CreateCondBr(isComplete, isCompleteBB, contBB);
|
||||
IGF.Builder.emitBlock(isCompleteBB);
|
||||
}
|
||||
IGF.Builder.CreateCondBr(isComplete, isCompleteBB, contBB);
|
||||
IGF.Builder.emitBlock(isCompleteBB);
|
||||
}
|
||||
|
||||
// Store it back to the cache variable. This needs to be a store-release
|
||||
// because it needs to propagate memory visibility to the other threads
|
||||
// that can access the cache: the initializing stores might be visible
|
||||
// to this thread, but they aren't transitively guaranteed to be visible
|
||||
// to other threads unless this is a store-release.
|
||||
//
|
||||
// However, we can skip this if the value was actually loaded from the
|
||||
// cache. This is a simple, if hacky, peephole that's useful for the
|
||||
// code in emitInPlaceTypeMetadataAccessFunctionBody.
|
||||
if (!isLoadFrom(directResult, cache)) {
|
||||
IGF.Builder.CreateStore(directResult, cache)
|
||||
->setAtomic(llvm::AtomicOrdering::Release);
|
||||
// Store it back to the cache variable. This needs to be a store-release
|
||||
// because it needs to propagate memory visibility to the other threads
|
||||
// that can access the cache: the initializing stores might be visible
|
||||
// to this thread, but they aren't transitively guaranteed to be visible
|
||||
// to other threads unless this is a store-release.
|
||||
//
|
||||
// However, we can skip this if the value was actually loaded from the
|
||||
// cache. This is a simple, if hacky, peephole that's useful for the
|
||||
// code in emitOnceTypeMetadataAccessFunctionBody.
|
||||
if (!isLoadFrom(directResult, cache)) {
|
||||
IGF.Builder.CreateStore(directResult, cache)
|
||||
->setAtomic(llvm::AtomicOrdering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
IGF.Builder.CreateBr(contBB);
|
||||
@@ -1424,12 +1445,14 @@ void irgen::emitLazyCacheAccessFunction(IRGenModule &IGM,
|
||||
// Add a phi for the metadata state if we're returning a response.
|
||||
llvm::Value *stateToReturn = nullptr;
|
||||
if (directState) {
|
||||
phi->addIncoming(directResult, completionCheckBB);
|
||||
if (storeBB != completionCheckBB)
|
||||
phi->addIncoming(directResult, completionCheckBB);
|
||||
|
||||
auto completionStatePHI = IGF.Builder.CreatePHI(IGM.SizeTy, 3);
|
||||
completionStatePHI->addIncoming(completedState, loadBB);
|
||||
completionStatePHI->addIncoming(directState, completionCheckBB);
|
||||
completionStatePHI->addIncoming(completedState, storeBB);
|
||||
if (storeBB != completionCheckBB)
|
||||
completionStatePHI->addIncoming(completedState, storeBB);
|
||||
stateToReturn = completionStatePHI;
|
||||
} else if (returnsResponse) {
|
||||
stateToReturn = completedState;
|
||||
@@ -1565,11 +1588,11 @@ emitGenericTypeMetadataAccessFunction(IRGenFunction &IGF,
|
||||
/// Emit a helper function for swift_once that performs in-place
|
||||
/// initialization of the given nominal type.
|
||||
static llvm::Constant *
|
||||
createInPlaceMetadataInitializationFunction(IRGenModule &IGM,
|
||||
CanNominalType type,
|
||||
llvm::Constant *metadata,
|
||||
llvm::Constant *cacheVariable,
|
||||
InPlaceMetadataInitializer &&initialize) {
|
||||
createOnceMetadataInitializationFunction(IRGenModule &IGM,
|
||||
CanNominalType type,
|
||||
llvm::Constant *metadata,
|
||||
llvm::Constant *cacheVariable,
|
||||
OnceMetadataInitializer initialize) {
|
||||
// There's an ignored i8* parameter.
|
||||
auto fnTy = llvm::FunctionType::get(IGM.VoidTy, {IGM.Int8PtrTy},
|
||||
/*variadic*/ false);
|
||||
@@ -1603,12 +1626,12 @@ createInPlaceMetadataInitializationFunction(IRGenModule &IGM,
|
||||
}
|
||||
|
||||
/// Emit the function body for the type metadata accessor of a nominal type
|
||||
/// that might require in-place initialization.
|
||||
/// that uses swift_once to control in-place initialization.
|
||||
MetadataResponse
|
||||
irgen::emitInPlaceTypeMetadataAccessFunctionBody(IRGenFunction &IGF,
|
||||
CanNominalType type,
|
||||
llvm::Constant *cacheVariable,
|
||||
InPlaceMetadataInitializer &&initializer) {
|
||||
irgen::emitOnceTypeMetadataAccessFunctionBody(IRGenFunction &IGF,
|
||||
CanNominalType type,
|
||||
llvm::Constant *cacheVariable,
|
||||
OnceMetadataInitializer initializer) {
|
||||
llvm::Constant *metadata =
|
||||
IGF.IGM.requiresForeignTypeMetadata(type)
|
||||
? IGF.IGM.getAddrOfForeignTypeMetadataCandidate(type)
|
||||
@@ -1635,9 +1658,9 @@ irgen::emitInPlaceTypeMetadataAccessFunctionBody(IRGenFunction &IGF,
|
||||
|
||||
// Create the protected function. swift_once wants this as an i8*.
|
||||
llvm::Value *onceFn =
|
||||
createInPlaceMetadataInitializationFunction(IGF.IGM, type, metadata,
|
||||
cacheVariable,
|
||||
std::move(initializer));
|
||||
createOnceMetadataInitializationFunction(IGF.IGM, type, metadata,
|
||||
cacheVariable,
|
||||
std::move(initializer));
|
||||
onceFn = IGF.Builder.CreateBitCast(onceFn, IGF.IGM.Int8PtrTy);
|
||||
auto context = llvm::UndefValue::get(IGF.IGM.Int8PtrTy);
|
||||
|
||||
@@ -1653,7 +1676,7 @@ irgen::emitInPlaceTypeMetadataAccessFunctionBody(IRGenFunction &IGF,
|
||||
if (IGF.IGM.IRGen.Opts.Sanitizers & SanitizerKind::Thread)
|
||||
relocatedMetadata->setOrdering(llvm::AtomicOrdering::Acquire);
|
||||
|
||||
// emitLazyCacheAccessFunction will see that the value was loaded from
|
||||
// emitCacheAccessFunction will see that the value was loaded from
|
||||
// the guard variable and skip the redundant store back.
|
||||
return MetadataResponse::forComplete(relocatedMetadata);
|
||||
}
|
||||
@@ -1728,6 +1751,7 @@ llvm::Function *
|
||||
irgen::getTypeMetadataAccessFunction(IRGenModule &IGM,
|
||||
CanType type,
|
||||
ForDefinition_t shouldDefine,
|
||||
CacheStrategy cacheStrategy,
|
||||
MetadataAccessGenerator generator) {
|
||||
assert(!type->hasArchetype());
|
||||
// Type should be bound unless it's type erased.
|
||||
@@ -1744,20 +1768,37 @@ irgen::getTypeMetadataAccessFunction(IRGenModule &IGM,
|
||||
return accessor;
|
||||
|
||||
// Okay, define the accessor.
|
||||
llvm::GlobalVariable *cacheVariable = nullptr;
|
||||
llvm::Constant *cacheVariable = nullptr;
|
||||
|
||||
// If our preferred access method is to go via an accessor, it means
|
||||
// there is some non-trivial computation that needs to be cached.
|
||||
if (!isTypeMetadataAccessTrivial(IGM, type)) {
|
||||
cacheVariable = cast<llvm::GlobalVariable>(
|
||||
IGM.getAddrOfTypeMetadataLazyCacheVariable(type, ForDefinition));
|
||||
if (isTypeMetadataAccessTrivial(IGM, type)) {
|
||||
cacheStrategy = CacheStrategy::None;
|
||||
} else {
|
||||
switch (cacheStrategy) {
|
||||
// Nothing to do.
|
||||
case CacheStrategy::None:
|
||||
break;
|
||||
|
||||
// For lazy initialization, the cache variable is just a pointer.
|
||||
case CacheStrategy::Lazy:
|
||||
cacheVariable =
|
||||
IGM.getAddrOfTypeMetadataLazyCacheVariable(type, ForDefinition);
|
||||
break;
|
||||
|
||||
// For in-place initialization, drill down to the first element.
|
||||
case CacheStrategy::InPlaceInitialization:
|
||||
cacheVariable = IGM.getAddrOfTypeMetadataInPlaceInitializationCache(
|
||||
type->getAnyNominal(), ForDefinition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (IGM.getOptions().optimizeForSize())
|
||||
accessor->addFnAttr(llvm::Attribute::NoInline);
|
||||
}
|
||||
|
||||
emitLazyCacheAccessFunction(IGM, accessor, cacheVariable,
|
||||
[&](IRGenFunction &IGF, Explosion ¶ms) {
|
||||
emitCacheAccessFunction(IGM, accessor, cacheVariable, cacheStrategy,
|
||||
[&](IRGenFunction &IGF, Explosion ¶ms) {
|
||||
auto request = DynamicMetadataRequest(params.claimNext());
|
||||
return generator(IGF, request, cacheVariable);
|
||||
});
|
||||
@@ -1770,6 +1811,7 @@ llvm::Function *irgen::getTypeMetadataAccessFunction(IRGenModule &IGM,
|
||||
CanType type,
|
||||
ForDefinition_t shouldDefine) {
|
||||
return getTypeMetadataAccessFunction(IGM, type, shouldDefine,
|
||||
CacheStrategy::Lazy,
|
||||
[&](IRGenFunction &IGF,
|
||||
DynamicMetadataRequest request,
|
||||
llvm::Constant *cacheVariable) {
|
||||
@@ -1805,12 +1847,12 @@ irgen::getGenericTypeMetadataAccessFunction(IRGenModule &IGM,
|
||||
bool isReadNone =
|
||||
(genericArgs.Types.size() <= NumDirectGenericTypeMetadataAccessFunctionArgs);
|
||||
|
||||
emitLazyCacheAccessFunction(IGM, accessor, /*cacheVariable=*/nullptr,
|
||||
[&](IRGenFunction &IGF, Explosion ¶ms) {
|
||||
return emitGenericTypeMetadataAccessFunction(
|
||||
emitCacheAccessFunction(IGM, accessor, /*cache*/nullptr, CacheStrategy::None,
|
||||
[&](IRGenFunction &IGF, Explosion ¶ms) {
|
||||
return emitGenericTypeMetadataAccessFunction(
|
||||
IGF, params, nominal, genericArgs);
|
||||
},
|
||||
isReadNone);
|
||||
},
|
||||
isReadNone);
|
||||
|
||||
return accessor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user