Skip to content

More runtime support for subclass existentials #8958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions include/swift/ABI/MetadataValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,8 @@ class ExistentialTypeFlags {
enum : int_type {
NumWitnessTablesMask = 0x00FFFFFFU,
ClassConstraintMask = 0x80000000U,
SpecialProtocolMask = 0x7F000000U,
HasSuperclassMask = 0x40000000U,
SpecialProtocolMask = 0x3F000000U,
SpecialProtocolShift = 24U,
};
int_type Data;
Expand All @@ -421,6 +422,11 @@ class ExistentialTypeFlags {
| (bool(c) ? ClassConstraintMask : 0));
}
constexpr ExistentialTypeFlags
withHasSuperclass(bool hasSuperclass) const {
return ExistentialTypeFlags((Data & ~HasSuperclassMask)
| (hasSuperclass ? HasSuperclassMask : 0));
}
constexpr ExistentialTypeFlags
withSpecialProtocol(SpecialProtocol sp) const {
return ExistentialTypeFlags((Data & ~SpecialProtocolMask)
| (int_type(sp) << SpecialProtocolShift));
Expand All @@ -433,7 +439,11 @@ class ExistentialTypeFlags {
ProtocolClassConstraint getClassConstraint() const {
return ProtocolClassConstraint(bool(Data & ClassConstraintMask));
}


bool hasSuperclassConstraint() const {
return bool(Data & HasSuperclassMask);
}

/// Return whether this existential type represents an uncomposed special
/// protocol.
SpecialProtocol getSpecialProtocol() const {
Expand Down
13 changes: 10 additions & 3 deletions include/swift/Runtime/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -2445,9 +2445,16 @@ struct TargetExistentialTypeMetadata : public TargetMetadata<Runtime> {
return Flags.getClassConstraint() == ProtocolClassConstraint::Class;
}

const Metadata *getSuperclassConstraint() const {
// FIXME
return nullptr;
const TargetMetadata<Runtime> *getSuperclassConstraint() const {
if (!Flags.hasSuperclassConstraint())
return nullptr;

// Get a pointer to tail-allocated storage for this metadata record.
auto Pointer = reinterpret_cast<
ConstTargetMetadataPointer<Runtime, TargetMetadata> const *>(this + 1);

// The superclass immediately follows the list of protocol descriptors.
return Pointer[Protocols.NumProtocols];
}

static bool classof(const TargetMetadata<Runtime> *metadata) {
Expand Down
107 changes: 88 additions & 19 deletions lib/IRGen/GenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,21 +291,34 @@ llvm::Value *irgen::emitReferenceToObjCProtocol(IRGenFunction &IGF,
/// Emit a helper function to look up \c numProtocols witness tables given
/// a value and a type metadata reference.
///
/// The function's input type is (value, metadataValue, protocol...)
/// If \p checkClassConstraint is true, we must emit an explicit check that the
/// instance is a class.
///
/// If \p checkSuperclassConstraint is true, we are given an additional parameter
/// with a superclass type in it, and must emit a check that the instance is a
/// subclass of the given class.
///
/// The function's input type is (value, metadataValue, superclass?, protocol...)
/// The function's output type is (value, witnessTable...)
///
/// The value is NULL if the cast failed.
static llvm::Function *emitExistentialScalarCastFn(IRGenModule &IGM,
unsigned numProtocols,
CheckedCastMode mode,
bool checkClassConstraint) {
static llvm::Function *
emitExistentialScalarCastFn(IRGenModule &IGM,
unsigned numProtocols,
CheckedCastMode mode,
bool checkClassConstraint,
bool checkSuperclassConstraint) {
assert(!checkSuperclassConstraint || checkClassConstraint);

// Build the function name.
llvm::SmallString<32> name;
{
llvm::raw_svector_ostream os(name);
os << "dynamic_cast_existential_";
os << numProtocols;
if (checkClassConstraint)
if (checkSuperclassConstraint)
os << "_superclass";
else if (checkClassConstraint)
os << "_class";
switch (mode) {
case CheckedCastMode::Unconditional:
Expand All @@ -329,6 +342,8 @@ static llvm::Function *emitExistentialScalarCastFn(IRGenModule &IGM,
argTys.push_back(IGM.Int8PtrTy);
argTys.push_back(IGM.TypeMetadataPtrTy);
returnTys.push_back(IGM.Int8PtrTy);
if (checkSuperclassConstraint)
argTys.push_back(IGM.TypeMetadataPtrTy);
for (unsigned i = 0; i < numProtocols; ++i) {
argTys.push_back(IGM.ProtocolDescriptorPtrTy);
returnTys.push_back(IGM.WitnessTablePtrTy);
Expand All @@ -355,7 +370,26 @@ static llvm::Function *emitExistentialScalarCastFn(IRGenModule &IGM,
rets.add(value);

// Check the class constraint if necessary.
if (checkClassConstraint) {
if (checkSuperclassConstraint) {
auto superclassMetadata = args.claimNext();
auto castFn = IGF.IGM.getDynamicCastMetatypeFn();
auto castResult = IGF.Builder.CreateCall(castFn, {ref,
superclassMetadata});

auto cc = cast<llvm::Function>(castFn)->getCallingConv();

// FIXME: Eventually, we may want to throw.
castResult->setCallingConv(cc);
castResult->setDoesNotThrow();

auto isClass = IGF.Builder.CreateICmpNE(
castResult,
llvm::ConstantPointerNull::get(IGF.IGM.TypeMetadataPtrTy));

auto contBB = IGF.createBasicBlock("cont");
IGF.Builder.CreateCondBr(isClass, contBB, failBB);
IGF.Builder.emitBlock(contBB);
} else if (checkClassConstraint) {
auto isClass = IGF.Builder.CreateCall(IGM.getIsClassTypeFn(), ref);
auto contBB = IGF.createBasicBlock("cont");
IGF.Builder.CreateCondBr(isClass, contBB, failBB);
Expand All @@ -374,7 +408,7 @@ static llvm::Function *emitExistentialScalarCastFn(IRGenModule &IGM,
IGF.Builder.emitBlock(contBB);
rets.add(witness);
}

// If we succeeded, return the witnesses.
IGF.emitScalarReturn(returnTy, rets);

Expand Down Expand Up @@ -472,11 +506,15 @@ void irgen::emitScalarExistentialDowncast(IRGenFunction &IGF,
CheckedCastMode mode,
Optional<MetatypeRepresentation> metatypeKind,
Explosion &ex) {
auto instanceType = destType.getSwiftRValueType();
while (auto metatypeType = dyn_cast<ExistentialMetatypeType>(instanceType))
instanceType = metatypeType.getInstanceType();
auto srcInstanceType = srcType.getSwiftRValueType();
auto destInstanceType = destType.getSwiftRValueType();
while (auto metatypeType = dyn_cast<ExistentialMetatypeType>(
destInstanceType)) {
destInstanceType = metatypeType.getInstanceType();
srcInstanceType = cast<AnyMetatypeType>(srcInstanceType).getInstanceType();
}

auto layout = instanceType.getExistentialLayout();
auto layout = destInstanceType.getExistentialLayout();

// Look up witness tables for the protocols that need them and get
// references to the ObjC Protocol* values for the objc protocols.
Expand All @@ -486,7 +524,7 @@ void irgen::emitScalarExistentialDowncast(IRGenFunction &IGF,
bool hasClassConstraint = layout.requiresClass;
bool hasClassConstraintByProtocol = false;

assert(!layout.superclass && "Subclass existentials not supported yet");
bool hasSuperclassConstraint = bool(layout.superclass);

for (auto protoTy : layout.getProtocols()) {
auto *protoDecl = protoTy->getDecl();
Expand Down Expand Up @@ -532,10 +570,34 @@ void irgen::emitScalarExistentialDowncast(IRGenFunction &IGF,
auto schema = IGF.getTypeInfo(destType).getSchema();
resultType = schema[0].getScalarType();
}
// We only need to check the class constraint for metatype casts where
// no protocol conformance indirectly requires the constraint for us.
bool checkClassConstraint =
(bool)metatypeKind && hasClassConstraint && !hasClassConstraintByProtocol;

// The source of a scalar cast is statically known to be a class or a
// metatype, so we only have to check the class constraint in two cases:
//
// 1) The destination type has an explicit superclass constraint that is
// more derived than what the source type is known to be.
//
// 2) We are casting between metatypes, in which case the source might
// be a non-class metatype.
bool checkClassConstraint = false;
if ((bool)metatypeKind &&
hasClassConstraint &&
!hasClassConstraintByProtocol &&
!srcInstanceType->mayHaveSuperclass())
checkClassConstraint = true;

// If the source has an equal or more derived superclass constraint than
// the destination, we can elide the superclass check.
//
// Note that destInstanceType is always an existential type, so calling
// getSuperclass() returns the superclass constraint of the existential,
// not the superclass of some concrete class.
bool checkSuperclassConstraint =
hasSuperclassConstraint &&
!destInstanceType->getSuperclass()->isExactSuperclassOf(srcInstanceType);

if (checkSuperclassConstraint)
checkClassConstraint = true;

llvm::Value *resultValue = value;

Expand Down Expand Up @@ -671,8 +733,11 @@ void irgen::emitScalarExistentialDowncast(IRGenFunction &IGF,
}

// Look up witness tables for the protocols that need them.
auto fn = emitExistentialScalarCastFn(IGF.IGM, witnessTableProtos.size(),
mode, checkClassConstraint);
auto fn = emitExistentialScalarCastFn(IGF.IGM,
witnessTableProtos.size(),
mode,
checkClassConstraint,
checkSuperclassConstraint);

llvm::SmallVector<llvm::Value *, 4> args;

Expand All @@ -681,6 +746,10 @@ void irgen::emitScalarExistentialDowncast(IRGenFunction &IGF,
args.push_back(resultValue);

args.push_back(metadataValue);

if (checkSuperclassConstraint)
args.push_back(IGF.emitTypeMetadataRef(CanType(layout.superclass)));

for (auto proto : witnessTableProtos)
args.push_back(proto);

Expand Down
46 changes: 39 additions & 7 deletions lib/IRGen/GenReflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ class CaptureDescriptorBuilder : public ReflectionMetadataBuilder {
CanSILFunctionType SubstCalleeType;
SubstitutionList Subs;
const HeapLayout &Layout;

public:
CaptureDescriptorBuilder(IRGenModule &IGM,
SILFunction &Caller,
Expand Down Expand Up @@ -630,6 +631,35 @@ class CaptureDescriptorBuilder : public ReflectionMetadataBuilder {
}
}

/// Give up if we captured an opened existential type. Eventually we
/// should figure out how to represent this.
static bool hasOpenedExistential(CanSILFunctionType OrigCalleeType,
const HeapLayout &Layout) {
if (!OrigCalleeType->isPolymorphic() ||
OrigCalleeType->isPseudogeneric())
return false;

auto &Bindings = Layout.getBindings();
for (unsigned i = 0; i < Bindings.size(); ++i) {
// Skip protocol requirements (FIXME: for now?)
if (Bindings[i].Protocol != nullptr)
continue;

if (Bindings[i].TypeParameter->hasOpenedExistential())
return true;
}

auto ElementTypes = Layout.getElementTypes().slice(
Layout.hasBindings() ? 1 : 0);
for (auto ElementType : ElementTypes) {
auto SwiftType = ElementType.getSwiftRValueType();
if (SwiftType->hasOpenedExistential())
return true;
}

return false;
}

/// Slice off the NecessaryBindings struct at the beginning, if it's there.
/// We'll keep track of how many things are in the bindings struct with its
/// own count in the capture descriptor.
Expand Down Expand Up @@ -660,8 +690,9 @@ class CaptureDescriptorBuilder : public ReflectionMetadataBuilder {
continue;

auto Source = SourceBuilder.createClosureBinding(i);
auto BindingType = Caller.mapTypeOutOfContext(Bindings[i].TypeParameter);
SourceMap.push_back({BindingType->getCanonicalType(), Source});
auto BindingType = Bindings[i].TypeParameter;
auto InterfaceType = Caller.mapTypeOutOfContext(BindingType);
SourceMap.push_back({InterfaceType->getCanonicalType(), Source});
}

// Check if any requirements were fulfilled by metadata stored inside a
Expand Down Expand Up @@ -701,10 +732,9 @@ class CaptureDescriptorBuilder : public ReflectionMetadataBuilder {
// parameters.
auto Src = Path.getMetadataSource(SourceBuilder, Root);

auto SubstType =
Caller.mapTypeOutOfContext(
GenericParam.subst(SubstMap, None));
SourceMap.push_back({SubstType->getCanonicalType(), Src});
auto SubstType = GenericParam.subst(SubstMap);
auto InterfaceType = Caller.mapTypeOutOfContext(SubstType);
SourceMap.push_back({InterfaceType->getCanonicalType(), Src});
});

return SourceMap;
Expand Down Expand Up @@ -872,11 +902,13 @@ IRGenModule::getAddrOfCaptureDescriptor(SILFunction &Caller,
if (!IRGen.Opts.EnableReflectionMetadata)
return llvm::Constant::getNullValue(CaptureDescriptorPtrTy);

if (CaptureDescriptorBuilder::hasOpenedExistential(OrigCalleeType, Layout))
return llvm::Constant::getNullValue(CaptureDescriptorPtrTy);

CaptureDescriptorBuilder builder(*this, Caller,
OrigCalleeType, SubstCalleeType, Subs,
Layout);
auto var = builder.emit();

return llvm::ConstantExpr::getBitCast(var, CaptureDescriptorPtrTy);
}

Expand Down
5 changes: 3 additions & 2 deletions lib/IRGen/GenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1531,8 +1531,9 @@ llvm::StructType *IRGenModule::createNominalType(CanType type) {
assert(type.getNominalOrBoundGenericNominal());

// We share type infos for different instantiations of a generic type
// when the archetypes have the same exemplars. Mangling the archetypes
// in this case can be very misleading, so we just mangle the base name.
// when the archetypes have the same exemplars. We cannot mangle
// archetypes, and the mangling does not have to be unique, so we just
// mangle the unbound generic form of the type.
if (type->hasArchetype())
type = type.getNominalOrBoundGenericNominal()->getDeclaredType()
->getCanonicalType();
Expand Down
13 changes: 12 additions & 1 deletion lib/IRGen/IRGenMangler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,18 @@ mangleProtocolForLLVMTypeName(ProtocolCompositionType *type) {
appendOperator("_");
}
if (layout.superclass) {
appendType(layout.superclass);
auto superclassTy = layout.superclass;

// We share type infos for different instantiations of a generic type
// when the archetypes have the same exemplars. We cannot mangle
// archetypes, and the mangling does not have to be unique, so we just
// mangle the unbound generic form of the type.
if (superclassTy->hasArchetype()) {
superclassTy = superclassTy->getClassOrBoundGenericClass()
->getDeclaredType();
}

appendType(CanType(superclassTy));
appendOperator("Xc");
} else if (layout.getLayoutConstraint()) {
appendOperator("Xl");
Expand Down
Loading