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:
John McCall
2018-07-25 02:40:06 -04:00
parent dadb51e708
commit dc052e6364
18 changed files with 551 additions and 194 deletions

View File

@@ -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 &params) {
emitCacheAccessFunction(IGM, accessor, cacheVariable, cacheStrategy,
[&](IRGenFunction &IGF, Explosion &params) {
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 &params) {
return emitGenericTypeMetadataAccessFunction(
emitCacheAccessFunction(IGM, accessor, /*cache*/nullptr, CacheStrategy::None,
[&](IRGenFunction &IGF, Explosion &params) {
return emitGenericTypeMetadataAccessFunction(
IGF, params, nominal, genericArgs);
},
isReadNone);
},
isReadNone);
return accessor;
}