Skip to content

Commit

Permalink
[IRGen] Pass witness tables for conditional conformances to witness t…
Browse files Browse the repository at this point in the history
…able accessor.

When calling an accessor, one has to pull the witness tables for each
conditional conformance requirement into a(n appropriately ordered) buffer that
is passed to the accessor. This is simple enough, if the appropriate
specialization of the relevant conformances are known, which the compiler didn't
track deep enough until now.
  • Loading branch information
huonw committed Nov 9, 2017
1 parent ae54ac0 commit 901bd35
Show file tree
Hide file tree
Showing 6 changed files with 641 additions and 37 deletions.
2 changes: 0 additions & 2 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ ProtocolConformanceRef::subst(Type origType,
if (auto result = conformances(origType->getCanonicalType(),
substType,
proto->getDeclaredType())) {
assert(result->getConditionalRequirements().empty() &&
"unhandled conditional requirements");
return *result;
}

Expand Down
121 changes: 88 additions & 33 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,25 +1015,69 @@ class irgen::ConformanceInfo {
CanType conformingType) const = 0;
};

static llvm::Value *
emitWitnessTableAccessorCall(IRGenFunction &IGF,
const NormalProtocolConformance *conformance,
CanType conformingType,
llvm::Value **srcMetadataCache) {
auto accessor =
IGF.IGM.getAddrOfWitnessTableAccessFunction(conformance, NotForDefinition);
static std::pair<llvm::Value *, llvm::Value *>
emitConditionalConformancesBuffer(IRGenFunction &IGF,
const ProtocolConformance *conformance) {
// Pointers to the witness tables, in the right order, which will be included
// in the buffer that gets passed to the witness table accessor.
llvm::SmallVector<llvm::Value *, 4> tables;

auto subMap = conformance->getSubstitutions(IGF.IGM.getSwiftModule());
auto rootConformance = conformance->getRootNormalConformance();

SILWitnessTable::enumerateWitnessTableConditionalConformances(
rootConformance, [&](unsigned, CanType type, ProtocolDecl *proto) {
auto substType = type.subst(subMap)->getCanonicalType();
auto reqConformance = subMap.lookupConformance(type, proto);
assert(reqConformance && "conditional conformance must exist");

tables.push_back(emitWitnessTableRef(IGF, substType, *reqConformance));
return /*finished?*/ false;
});

// No conditional requirements means no need for a buffer, and size == 0 means
// no reading of the pointer.
// FIXME(cond. conf. assert): once the dynamic assertion is removed from the
// instantiation function, we can have size as undef too.
if (tables.empty()) {
return {llvm::UndefValue::get(IGF.IGM.WitnessTablePtrPtrTy),
llvm::ConstantInt::get(IGF.IGM.SizeTy, 0)};
}

auto buffer = IGF.createAlloca(
llvm::ArrayType::get(IGF.IGM.WitnessTablePtrTy, tables.size()),
IGF.IGM.getPointerAlignment(), "conditional.requirement.buffer");
buffer = IGF.Builder.CreateStructGEP(buffer, 0, Size(0));

// Write each of the conditional witness tables into the buffer.
for (auto idx : indices(tables)) {
auto slot =
IGF.Builder.CreateConstArrayGEP(buffer, idx, IGF.IGM.getPointerSize());
IGF.Builder.CreateStore(tables[idx], slot);
}

auto count = llvm::ConstantInt::get(IGF.IGM.SizeTy, tables.size());
return {buffer.getAddress(), count};
}

static llvm::Value *emitWitnessTableAccessorCall(
IRGenFunction &IGF, const ProtocolConformance *conformance,
CanType conformingType, llvm::Value **srcMetadataCache) {
auto accessor = IGF.IGM.getAddrOfWitnessTableAccessFunction(
conformance->getRootNormalConformance(), NotForDefinition);

// If the conformance is generic, the accessor takes the metatype
// as an argument.
// If the conformance is generic, the accessor takes the metatype plus
// possible conditional conformances arguments.
llvm::CallInst *call;
if (conformance->getDeclContext()->isGenericContext()) {
// Emit the source metadata if we haven't yet.
if (!*srcMetadataCache) {
*srcMetadataCache = IGF.emitTypeMetadataRef(conformingType);
}

auto conditionalTables = llvm::ConstantPointerNull::get(IGF.IGM.WitnessTablePtrPtrTy);
auto numConditionalTables = llvm::ConstantInt::get(IGF.IGM.SizeTy, 0);
llvm::Value *conditionalTables, *numConditionalTables;
std::tie(conditionalTables, numConditionalTables) =
emitConditionalConformancesBuffer(IGF, conformance);

call = IGF.Builder.CreateCall(
accessor, {*srcMetadataCache, conditionalTables, numConditionalTables});
Expand All @@ -1053,12 +1097,12 @@ emitWitnessTableAccessorCall(IRGenFunction &IGF,
/// given type.
static llvm::Function *
getWitnessTableLazyAccessFunction(IRGenModule &IGM,
const NormalProtocolConformance *conformance,
const ProtocolConformance *conformance,
CanType conformingType) {
assert(!conformingType->hasArchetype());
llvm::Function *accessor =
IGM.getAddrOfWitnessTableLazyAccessFunction(conformance, conformingType,
ForDefinition);
auto rootConformance = conformance->getRootNormalConformance();
llvm::Function *accessor = IGM.getAddrOfWitnessTableLazyAccessFunction(
rootConformance, conformingType, ForDefinition);

// If we're not supposed to define the accessor, or if we already
// have defined it, just return the pointer.
Expand All @@ -1069,9 +1113,9 @@ getWitnessTableLazyAccessFunction(IRGenModule &IGM,
accessor->addFnAttr(llvm::Attribute::NoInline);

// Okay, define the accessor.
auto cacheVariable = cast<llvm::GlobalVariable>(
IGM.getAddrOfWitnessTableLazyCacheVariable(conformance, conformingType,
ForDefinition));
auto cacheVariable =
cast<llvm::GlobalVariable>(IGM.getAddrOfWitnessTableLazyCacheVariable(
rootConformance, conformingType, ForDefinition));
emitLazyCacheAccessFunction(IGM, accessor, cacheVariable,
[&](IRGenFunction &IGF) -> llvm::Value* {
llvm::Value *conformingMetadataCache = nullptr;
Expand All @@ -1090,8 +1134,8 @@ class DirectConformanceInfo : public ConformanceInfo {

const NormalProtocolConformance *RootConformance;
public:
DirectConformanceInfo(const NormalProtocolConformance *C)
: RootConformance(C) {}
DirectConformanceInfo(const ProtocolConformance *C)
: RootConformance(C->getRootNormalConformance()) {}

llvm::Value *getTable(IRGenFunction &IGF, CanType conformingType,
llvm::Value **conformingMetadataCache) const override {
Expand All @@ -1108,11 +1152,10 @@ class DirectConformanceInfo : public ConformanceInfo {
class AccessorConformanceInfo : public ConformanceInfo {
friend ProtocolInfo;

const NormalProtocolConformance *Conformance;
const ProtocolConformance *Conformance;

public:
AccessorConformanceInfo(const NormalProtocolConformance *C)
: Conformance(C) {}
AccessorConformanceInfo(const ProtocolConformance *C) : Conformance(C) {}

llvm::Value *getTable(IRGenFunction &IGF, CanType type,
llvm::Value **typeMetadataCache) const override {
Expand Down Expand Up @@ -1932,28 +1975,40 @@ ProtocolInfo::getConformance(IRGenModule &IGM, ProtocolDecl *protocol,
assert(conformance->getProtocol() == protocol &&
"conformance is for wrong protocol");

// Drill down to the root normal conformance.
auto normalConformance = conformance->getRootNormalConformance();
auto checkCache =
[&](const ProtocolConformance *conf) -> Optional<ConformanceInfo *> {
// Check whether we've already cached this.
auto it = Conformances.find(conformance);
if (it != Conformances.end())
return it->second;

return None;
};

// Check whether we've already cached this.
auto it = Conformances.find(normalConformance);
if (it != Conformances.end()) return *it->second;
if (auto found = checkCache(conformance))
return **found;

ConformanceInfo *info;
// Drill down to the root normal
auto normalConformance = conformance->getRootNormalConformance();

ConformanceInfo *info;
// If the conformance is dependent in any way, we need to unique it.
// TODO: maybe this should apply whenever it's out of the module?
// TODO: actually enable this
if (isDependentConformance(IGM, normalConformance,
ResilienceExpansion::Maximal)) {
info = new AccessorConformanceInfo(normalConformance);

// Otherwise, we can use a direct-referencing conformance.
info = new AccessorConformanceInfo(conformance);
Conformances.insert({conformance, info});
} else {
// Otherwise, we can use a direct-referencing conformance, which can get
// away with the non-specialized conformance.
if (auto found = checkCache(normalConformance))
return **found;

info = new DirectConformanceInfo(normalConformance);
Conformances.insert({normalConformance, info});
}

Conformances.insert({normalConformance, info});
return *info;
}

Expand Down
2 changes: 1 addition & 1 deletion test/IRGen/associated_type_witness.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ struct GenericComputed<T: P> : DerivedFromSimpleAssoc {
// CHECK-NEXT: [[COUNT:%.*]] = getelementptr inbounds %swift.witness_table_slice, %swift.witness_table_slice* %conditional.tables, i32 0, i32 1
// CHECK-NEXT: store i64 %2, i64* [[COUNT]], align 8
// CHECK-NEXT: [[INSTANTIATION_ARGS:%.*]] = bitcast %swift.witness_table_slice* %conditional.tables to i8**
// CHECK-NEXT: [[WTABLE:%.*]] = call i8** @swift_rt_swift_getGenericWitnessTable(%swift.generic_witness_table_cache* @_T023associated_type_witness15GenericComputedVyxGAA14HasSimpleAssocAAWG, %swift.type* %0, [[INSTANTIATION_ARGS]])
// CHECK-NEXT: [[WTABLE:%.*]] = call i8** @swift_rt_swift_getGenericWitnessTable(%swift.generic_witness_table_cache* @_T023associated_type_witness15GenericComputedVyxGAA14HasSimpleAssocAAWG, %swift.type* %0, i8** [[INSTANTIATION_ARGS]])
// CHECK-NEXT: ret i8** [[WTABLE]]


Expand Down
Loading

0 comments on commit 901bd35

Please sign in to comment.