Skip to content
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

SILGen: Correctly emit vtables when an override is more visible than the base [5.1] #25190

5 changes: 5 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5728,6 +5728,11 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
return Bits.AbstractFunctionDecl.NeedsNewVTableEntry;
}

bool isEffectiveLinkageMoreVisibleThan(ValueDecl *other) const {
return (std::min(getEffectiveAccess(), AccessLevel::Public) >
std::min(other->getEffectiveAccess(), AccessLevel::Public));
}

bool isSynthesized() const {
return Bits.AbstractFunctionDecl.Synthesized;
}
Expand Down
30 changes: 24 additions & 6 deletions include/swift/SIL/SILVTableVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ template <class T> class SILVTableVisitor {
void maybeAddMethod(FuncDecl *fd) {
assert(!fd->hasClangNode());

SILDeclRef constant(fd, SILDeclRef::Kind::Func);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
maybeAddEntry(SILDeclRef(fd, SILDeclRef::Kind::Func));
}

void maybeAddConstructor(ConstructorDecl *cd) {
Expand All @@ -97,19 +96,38 @@ template <class T> class SILVTableVisitor {
// The initializing entry point for designated initializers is only
// necessary for super.init chaining, which is sufficiently constrained
// to never need dynamic dispatch.
SILDeclRef constant(cd, SILDeclRef::Kind::Allocator);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
maybeAddEntry(SILDeclRef(cd, SILDeclRef::Kind::Allocator));
}

void maybeAddEntry(SILDeclRef declRef, bool needsNewEntry) {
void maybeAddEntry(SILDeclRef declRef) {
// Introduce a new entry if required.
if (needsNewEntry)
if (declRef.requiresNewVTableEntry())
asDerived().addMethod(declRef);

// Update any existing entries that it overrides.
auto nextRef = declRef;
while ((nextRef = nextRef.getNextOverriddenVTableEntry())) {
auto baseRef = nextRef.getOverriddenVTableEntry();

// If A.f() is overridden by B.f() which is overridden by
// C.f(), it's possible that C.f() is not visible from C.
// In this case, we pretend that B.f() is the least derived
// method with a vtable entry in the override chain.
//
// This works because we detect the possibility of this
// happening when we emit B.f() and do two things:
// - B.f() always gets a new vtable entry, even if it is
// ABI compatible with A.f()
// - The vtable thunk for the override of A.f() in B does a
// vtable dispatch to the implementation of B.f() for the
// concrete subclass, so a subclass of B only needs to
// replace the vtable entry for B.f(); a call to A.f()
// will correctly dispatch to the implementation of B.f()
// in the subclass.
if (!baseRef.getDecl()->isAccessibleFrom(
declRef.getDecl()->getDeclContext()))
break;

asDerived().addMethodOverride(baseRef, declRef);
nextRef = baseRef;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6184,6 +6184,12 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) {
}
}

// If the base is less visible than the override, we might need a vtable
// entry since callers of the override might not be able to see the base
// at all.
if (decl->isEffectiveLinkageMoreVisibleThan(base))
return true;

// If the method overrides something, we only need a new entry if the
// override has a more general AST type. However an abstraction
// change is OK; we don't want to add a whole new vtable entry just
Expand Down
19 changes: 1 addition & 18 deletions lib/IRGen/Linking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,16 +426,7 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const {
switch (getKind()) {
case Kind::DispatchThunk:
case Kind::DispatchThunkInitializer:
case Kind::DispatchThunkAllocator: {
auto *decl = getDecl();

// Protocol requirements don't have their own access control
if (auto *proto = dyn_cast<ProtocolDecl>(decl->getDeclContext()))
decl = proto;

return getSILLinkage(getDeclLinkage(decl), forDefinition);
}

case Kind::DispatchThunkAllocator:
case Kind::MethodDescriptor:
case Kind::MethodDescriptorInitializer:
case Kind::MethodDescriptorAllocator: {
Expand All @@ -445,14 +436,6 @@ SILLinkage LinkEntity::getLinkage(ForDefinition_t forDefinition) const {
if (auto *proto = dyn_cast<ProtocolDecl>(decl->getDeclContext()))
decl = proto;

// Method descriptors for internal class initializers can be referenced
// from outside the module.
if (auto *ctor = dyn_cast<ConstructorDecl>(decl)) {
auto *classDecl = cast<ClassDecl>(ctor->getDeclContext());
if (classDecl->getEffectiveAccess() == AccessLevel::Open)
decl = classDecl;
}

return getSILLinkage(getDeclLinkage(decl), forDefinition);
}

Expand Down
2 changes: 2 additions & 0 deletions lib/SIL/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
if (overridden.kind == SILDeclRef::Kind::Initializer) {
return SILDeclRef();
}

// Overrides of @objc dynamic declarations are not in the vtable.
if (overridden.getDecl()->isObjCDynamic()) {
return SILDeclRef();
}
Expand Down
21 changes: 19 additions & 2 deletions lib/SIL/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2412,6 +2412,9 @@ TypeConverter::checkFunctionForABIDifferences(SILFunctionType *fnTy1,
if (fnTy1->getNumResults() != fnTy2->getNumResults())
return ABIDifference::NeedsThunk;

if (fnTy1->getNumYields() != fnTy2->getNumYields())
return ABIDifference::NeedsThunk;

// If we don't have a context but the other type does, we'll return
// ABIDifference::ThinToThick below.
if (fnTy1->getExtInfo().hasContext() &&
Expand All @@ -2426,8 +2429,22 @@ TypeConverter::checkFunctionForABIDifferences(SILFunctionType *fnTy1,
return ABIDifference::NeedsThunk;

if (checkForABIDifferences(result1.getSILStorageType(),
result2.getSILStorageType(),
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
result2.getSILStorageType(),
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
!= ABIDifference::Trivial)
return ABIDifference::NeedsThunk;
}

for (unsigned i : indices(fnTy1->getYields())) {
auto yield1 = fnTy1->getYields()[i];
auto yield2 = fnTy2->getYields()[i];

if (yield1.getConvention() != yield2.getConvention())
return ABIDifference::NeedsThunk;

if (checkForABIDifferences(yield1.getSILStorageType(),
yield2.getSILStorageType(),
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
!= ABIDifference::Trivial)
return ABIDifference::NeedsThunk;
}
Expand Down
10 changes: 8 additions & 2 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -682,11 +682,17 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
/// \param inputOrigType Abstraction pattern of base class method
/// \param inputSubstType Formal AST type of base class method
/// \param outputSubstType Formal AST type of derived class method
void emitVTableThunk(SILDeclRef derived,
/// \param baseLessVisibleThanDerived If true, the thunk does a
/// double dispatch to the derived method's vtable entry, so that if
/// the derived method has an override that cannot access the base,
/// calls to the base dispatch to the correct method.
void emitVTableThunk(SILDeclRef base,
SILDeclRef derived,
SILFunction *implFn,
AbstractionPattern inputOrigType,
CanAnyFunctionType inputSubstType,
CanAnyFunctionType outputSubstType);
CanAnyFunctionType outputSubstType,
bool baseLessVisibleThanDerived);

//===--------------------------------------------------------------------===//
// Control flow
Expand Down
106 changes: 83 additions & 23 deletions lib/SILGen/SILGenPoly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3541,11 +3541,13 @@ SILGenFunction::emitTransformedValue(SILLocation loc, RValue &&v,
//===----------------------------------------------------------------------===//

void
SILGenFunction::emitVTableThunk(SILDeclRef derived,
SILGenFunction::emitVTableThunk(SILDeclRef base,
SILDeclRef derived,
SILFunction *implFn,
AbstractionPattern inputOrigType,
CanAnyFunctionType inputSubstType,
CanAnyFunctionType outputSubstType) {
CanAnyFunctionType outputSubstType,
bool baseLessVisibleThanDerived) {
auto fd = cast<AbstractFunctionDecl>(derived.getDecl());

SILLocation loc(fd);
Expand All @@ -3554,13 +3556,21 @@ SILGenFunction::emitVTableThunk(SILDeclRef derived,
cleanupLoc.markAutoGenerated();
Scope scope(Cleanups, cleanupLoc);

auto fTy = implFn->getLoweredFunctionType();

SmallVector<ManagedValue, 8> thunkArgs;
collectThunkParams(loc, thunkArgs);

CanSILFunctionType derivedFTy;
if (baseLessVisibleThanDerived) {
derivedFTy = SGM.Types.getConstantOverrideType(derived);
} else {
derivedFTy = SGM.Types.getConstantInfo(derived).SILFnType;
}

SubstitutionMap subs;
if (auto *genericEnv = fd->getGenericEnvironment()) {
F.setGenericEnvironment(genericEnv);
subs = getForwardingSubstitutionMap();
fTy = fTy->substGenericArgs(SGM.M, subs);
derivedFTy = derivedFTy->substGenericArgs(SGM.M, subs);

inputSubstType = cast<FunctionType>(
cast<GenericFunctionType>(inputSubstType)
Expand All @@ -3573,42 +3583,92 @@ SILGenFunction::emitVTableThunk(SILDeclRef derived,
// Emit the indirect return and arguments.
auto thunkTy = F.getLoweredFunctionType();

SmallVector<ManagedValue, 8> thunkArgs;
collectThunkParams(loc, thunkArgs);

SmallVector<ManagedValue, 8> substArgs;

AbstractionPattern outputOrigType(outputSubstType);

// Reabstract the arguments.
TranslateArguments(*this, loc, thunkArgs, substArgs, fTy->getParameters())
TranslateArguments(*this, loc, thunkArgs, substArgs,
derivedFTy->getParameters())
.translate(inputOrigType,
inputSubstType.getParams(),
outputOrigType,
outputSubstType.getParams());

auto coroutineKind = F.getLoweredFunctionType()->getCoroutineKind();

// Collect the arguments to the implementation.
SmallVector<SILValue, 8> args;

// First, indirect results.
ResultPlanner resultPlanner(*this, loc);
resultPlanner.plan(outputOrigType.getFunctionResultType(),
outputSubstType.getResult(),
inputOrigType.getFunctionResultType(),
inputSubstType.getResult(),
fTy, thunkTy, args);
Optional<ResultPlanner> resultPlanner;

if (coroutineKind == SILCoroutineKind::None) {
// First, indirect results.
resultPlanner.emplace(*this, loc);
resultPlanner->plan(outputOrigType.getFunctionResultType(),
outputSubstType.getResult(),
inputOrigType.getFunctionResultType(),
inputSubstType.getResult(),
derivedFTy, thunkTy, args);
}

// Then, the arguments.
forwardFunctionArguments(*this, loc, fTy, substArgs, args);
forwardFunctionArguments(*this, loc, derivedFTy, substArgs, args);

// Create the call.
auto implRef = B.createFunctionRefFor(loc, implFn);
SILValue implResult = emitApplyWithRethrow(loc, implRef,
SILType::getPrimitiveObjectType(fTy),
subs, args);
SILValue derivedRef;
if (baseLessVisibleThanDerived) {
// See the comment in SILVTableVisitor.h under maybeAddMethod().
auto selfValue = thunkArgs.back().getValue();
auto derivedTy = SGM.Types.getConstantOverrideType(derived);
derivedRef = emitClassMethodRef(loc, selfValue, derived, derivedTy);
} else {
derivedRef = B.createFunctionRefFor(loc, implFn);
}

// Reabstract the return.
SILValue result = resultPlanner.execute(implResult);
SILValue result;

switch (coroutineKind) {
case SILCoroutineKind::None: {
auto implResult =
emitApplyWithRethrow(loc, derivedRef,
SILType::getPrimitiveObjectType(derivedFTy),
subs, args);

// Reabstract the return.
result = resultPlanner->execute(implResult);
break;
}

case SILCoroutineKind::YieldOnce: {
SmallVector<SILValue, 4> derivedYields;
auto tokenAndCleanup =
emitBeginApplyWithRethrow(loc, derivedRef,
SILType::getPrimitiveObjectType(derivedFTy),
subs, args, derivedYields);
auto overrideSubs = SubstitutionMap::getOverrideSubstitutions(
base.getDecl(), derived.getDecl(), /*derivedSubs=*/subs);

YieldInfo derivedYieldInfo(SGM, derived, derivedFTy, subs);
YieldInfo baseYieldInfo(SGM, base, thunkTy, overrideSubs);

translateYields(*this, loc, derivedYields, derivedYieldInfo, baseYieldInfo);

// Kill the abort cleanup without emitting it.
Cleanups.setCleanupState(tokenAndCleanup.second, CleanupState::Dead);

// End the inner coroutine normally.
emitEndApplyWithRethrow(loc, tokenAndCleanup.first);

result = B.createTuple(loc, {});
break;
}

case SILCoroutineKind::YieldMany:
SGM.diagnose(loc, diag::unimplemented_generator_witnesses);
result = B.createTuple(loc, {});
break;
}

scope.pop();
B.createReturn(loc, result);
Expand Down
27 changes: 20 additions & 7 deletions lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
SILDeclRef derived, SILDeclRef base) {
assert(base.kind == derived.kind);

auto *baseDecl = base.getDecl();
auto *derivedDecl = derived.getDecl();
auto *baseDecl = cast<AbstractFunctionDecl>(base.getDecl());
auto *derivedDecl = cast<AbstractFunctionDecl>(derived.getDecl());

// Note: We intentionally don't support extension members here.
//
Expand Down Expand Up @@ -80,8 +80,11 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
// If the member is dynamic, reference its dynamic dispatch thunk so that
// it will be redispatched, funneling the method call through the runtime
// hook point.
if (derivedDecl->isObjCDynamic()
&& derived.kind != SILDeclRef::Kind::Allocator) {
bool usesObjCDynamicDispatch =
(derivedDecl->isObjCDynamic() &&
derived.kind != SILDeclRef::Kind::Allocator);

if (usesObjCDynamicDispatch) {
implFn = getDynamicThunk(derived, Types.getConstantInfo(derived).SILFnType);
implLinkage = SILLinkage::Public;
} else {
Expand All @@ -93,6 +96,12 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
if (derived == base)
return SILVTable::Entry(base, implFn, implKind, implLinkage);

// If the base method is less visible than the derived method, we need
// a thunk.
bool baseLessVisibleThanDerived =
(derivedDecl->isEffectiveLinkageMoreVisibleThan(baseDecl) &&
!usesObjCDynamicDispatch);

// Determine the derived thunk type by lowering the derived type against the
// abstraction pattern of the base.
auto baseInfo = Types.getConstantInfo(base);
Expand All @@ -104,7 +113,8 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
// The override member type is semantically a subtype of the base
// member type. If the override is ABI compatible, we do not need
// a thunk.
if (M.Types.checkFunctionForABIDifferences(derivedInfo.SILFnType,
if (!baseLessVisibleThanDerived &&
M.Types.checkFunctionForABIDifferences(derivedInfo.SILFnType,
overrideInfo.SILFnType)
== TypeConverter::ABIDifference::Trivial)
return SILVTable::Entry(base, implFn, implKind, implLinkage);
Expand Down Expand Up @@ -138,10 +148,13 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
IsBare, IsNotTransparent, IsNotSerialized, IsNotDynamic);
thunk->setDebugScope(new (M) SILDebugScope(loc, thunk));

PrettyStackTraceSILFunction trace("generating vtable thunk", thunk);

SILGenFunction(*this, *thunk, theClass)
.emitVTableThunk(derived, implFn, basePattern,
.emitVTableThunk(base, derived, implFn, basePattern,
overrideInfo.LoweredType,
derivedInfo.LoweredType);
derivedInfo.LoweredType,
baseLessVisibleThanDerived);

return SILVTable::Entry(base, thunk, implKind, implLinkage);
}
Expand Down
Loading