Skip to content

[5.9] Introduce -unavailable-decl-optimization=stub #65269

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
2 changes: 2 additions & 0 deletions docs/ABI/Mangling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ are always non-polymorphic ``<impl-function-type>`` types.
VALUE-WITNESS-KIND ::= 'ug' // getEnumTag
VALUE-WITNESS-KIND ::= 'up' // destructiveProjectEnumData
VALUE-WITNESS-KIND ::= 'ui' // destructiveInjectEnumTag
VALUE-WITNESS-KIND ::= 'et' // getEnumTagSinglePayload
VALUE-WITNESS-KIND ::= 'st' // storeEnumTagSinglePayload

``<VALUE-WITNESS-KIND>`` differentiates the kinds of value
witness functions for a type.
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownDecls.def
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ FUNC_DECL(DiagnoseUnexpectedError, "_unexpectedError")
FUNC_DECL(DiagnoseUnexpectedNilOptional, "_diagnoseUnexpectedNilOptional")
FUNC_DECL(DiagnoseUnexpectedEnumCase, "_diagnoseUnexpectedEnumCase")
FUNC_DECL(DiagnoseUnexpectedEnumCaseValue, "_diagnoseUnexpectedEnumCaseValue")
FUNC_DECL(DiagnoseUnavailableCodeReached, "_diagnoseUnavailableCodeReached")

FUNC_DECL(GetErrorEmbeddedNSError, "_getErrorEmbeddedNSError")

Expand Down
6 changes: 6 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ namespace swift {
/// resulting binary by default in this mode.
None,

/// Stub out code associated with unavailable declarations.
///
/// For example, the bodies of unavailable functions should be compiled as
/// if they just contained a call to fatalError().
Stub,

/// Avoid generating any code for unavailable declarations.
///
/// NOTE: This optimization can be ABI breaking for a library evolution
Expand Down
6 changes: 6 additions & 0 deletions include/swift/SIL/ApplySite.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ class ApplySite {
FOREACH_IMPL_RETURN(isCalleeKnownProgramTerminationPoint());
}

/// Returns true if the callee function is annotated with
/// @_semantics("unavailable_code_reached")
bool isCalleeUnavailableCodeReached() const {
FOREACH_IMPL_RETURN(isCalleeUnavailableCodeReached());
}

/// Check if this is a call of a never-returning function.
bool isCalleeNoReturn() const { FOREACH_IMPL_RETURN(isCalleeNoReturn()); }

Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -2777,6 +2777,14 @@ class ApplyInstBase<Impl, Base, false> : public Base {
return calleeFn->hasSemanticsAttr(SEMANTICS_PROGRAMTERMINATION_POINT);
}

/// Returns true if the callee function is annotated with
/// @_semantics("unavailable_code_reached")
bool isCalleeUnavailableCodeReached() const {
auto calleeFn = getCalleeFunction();
if (!calleeFn) return false;
return calleeFn->hasSemanticsAttr(SEMANTICS_UNAVAILABLE_CODE_REACHED);
}

/// True if this application has generic substitutions.
bool hasSubstitutions() const {
return Substitutions.hasAnySubstitutableParams();
Expand Down
4 changes: 4 additions & 0 deletions include/swift/SIL/SILModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,10 @@ LLVM_LIBRARY_VISIBILITY bool usesObjCAllocator(ClassDecl *theClass);
/// A declaration may not require lowering if, for example, it is annotated as
/// unavailable and optimization settings allow it to be omitted.
LLVM_LIBRARY_VISIBILITY bool shouldSkipLowering(Decl *D);

/// Returns true if SIL/IR lowering for the given declaration should produce
/// a stub that traps at runtime because the code ought to be unreachable.
LLVM_LIBRARY_VISIBILITY bool shouldLowerToUnavailableCodeStub(Decl *D);
} // namespace Lowering

/// Apply the given function to each ABI member of \c D skipping the members
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ constexpr static const StringLiteral SEMANTICS_PROGRAMTERMINATION_POINT =
constexpr static const StringLiteral SEMANTICS_DEFAULT_ACTOR =
"defaultActor";

constexpr static const StringLiteral SEMANTICS_UNAVAILABLE_CODE_REACHED =
"unavailable_code_reached";

constexpr static const StringLiteral DEFAULT_ACTOR_STORAGE_FIELD_NAME =
"$defaultActor";

Expand Down
1 change: 0 additions & 1 deletion lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6566,7 +6566,6 @@ CxxRecordSemanticsKind
CxxRecordSemantics::evaluate(Evaluator &evaluator,
CxxRecordSemanticsDescriptor desc) const {
const auto *decl = desc.decl;
auto &clangSema = desc.ctx.getClangModuleLoader()->getClangSema();

if (hasImportAsRefAttr(decl)) {
return CxxRecordSemanticsKind::Reference;
Expand Down
1 change: 1 addition & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
auto value =
llvm::StringSwitch<Optional<UnavailableDeclOptimization>>(A->getValue())
.Case("none", UnavailableDeclOptimization::None)
.Case("stub", UnavailableDeclOptimization::Stub)
.Case("complete", UnavailableDeclOptimization::Complete)
.Default(None);

Expand Down
10 changes: 10 additions & 0 deletions lib/SIL/IR/SILModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -965,3 +965,13 @@ bool Lowering::shouldSkipLowering(Decl *D) {
// -unavailable-decl-optimization=complete is specified.
return D->getSemanticUnavailableAttr() != None;
}

bool Lowering::shouldLowerToUnavailableCodeStub(Decl *D) {
if (D->getASTContext().LangOpts.UnavailableDeclOptimizationMode !=
UnavailableDeclOptimization::Stub)
return false;

// Unavailable declarations should trap at runtime if
// -unavailable-decl-optimization=stub is specified.
return D->getSemanticUnavailableAttr() != None;
}
33 changes: 32 additions & 1 deletion lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5829,7 +5829,15 @@ SILGenFunction::emitApplyOfLibraryIntrinsic(SILLocation loc,
SubstitutionMap subMap,
ArrayRef<ManagedValue> args,
SGFContext ctx) {
auto callee = Callee::forDirect(*this, SILDeclRef(fn), subMap, loc);
return emitApplyOfLibraryIntrinsic(loc, SILDeclRef(fn), subMap, args, ctx);
}

RValue SILGenFunction::emitApplyOfLibraryIntrinsic(SILLocation loc,
SILDeclRef declRef,
SubstitutionMap subMap,
ArrayRef<ManagedValue> args,
SGFContext ctx) {
auto callee = Callee::forDirect(*this, declRef, subMap, loc);

auto origFormalType = callee.getOrigFormalType();
auto substFormalType = callee.getSubstFormalType();
Expand Down Expand Up @@ -5863,6 +5871,29 @@ SILGenFunction::emitApplyOfLibraryIntrinsic(SILLocation loc,
finalArgs, calleeTypeInfo, ApplyOptions(), ctx, None);
}

void SILGenFunction::emitApplyOfUnavailableCodeReached() {
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
FuncDecl *fd = getASTContext().getDiagnoseUnavailableCodeReached();

if (!fd) {
// Broken stdlib?
B.createUnconditionalFail(loc, "unavailable code reached");
return;
}

auto declRef = SILDeclRef(fd);
if (fd->isBackDeployed(getASTContext())) {
// The standard library entry point for the diagnostic function was
// introduced in Swift 5.9 so we call the back deployment thunk in case this
// code will execute on an older runtime.
declRef =
declRef.asBackDeploymentKind(SILDeclRef::BackDeploymentKind::Thunk);
}

emitApplyOfLibraryIntrinsic(loc, declRef, SubstitutionMap(), {},
SGFContext());
}

StringRef SILGenFunction::getMagicFunctionString() {
assert(MagicFunctionName
&& "asking for #function but we don't have a function name?!");
Expand Down
8 changes: 8 additions & 0 deletions lib/SILGen/SILGenBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,11 @@ void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) {
assert(thunk.isForeign);
SILDeclRef native = thunk.asForeign(false);

if (thunk.hasDecl()) {
if (shouldLowerToUnavailableCodeStub(thunk.getDecl()))
emitApplyOfUnavailableCodeReached();
}

// If we're calling a native non-designated class initializer, we have to
// discard the `self` object we were given, since
// Swift convenience initializers only have allocating entry points that
Expand Down Expand Up @@ -2067,6 +2072,9 @@ void SILGenFunction::emitForeignToNativeThunk(SILDeclRef thunk) {
auto nativeFnTy = F.getLoweredFunctionType();
assert(nativeFnTy == nativeCI.SILFnType);

if (shouldLowerToUnavailableCodeStub(fd))
emitApplyOfUnavailableCodeReached();

// Use the same generic environment as the native entry point.
F.setGenericEnvironment(SGM.Types.getConstantGenericEnvironment(thunk));

Expand Down
15 changes: 15 additions & 0 deletions lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF,
RegularLocation Loc(ctor);
Loc.markAutoGenerated();

if (shouldLowerToUnavailableCodeStub(ctor))
SGF.emitApplyOfUnavailableCodeReached();

AssertingManualScope functionLevelScope(SGF.Cleanups,
CleanupLocation(Loc));

Expand Down Expand Up @@ -532,6 +535,9 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
bool isDelegating = ctor->getDelegatingOrChainedInitKind().initKind ==
BodyInitKind::Delegating;

if (shouldLowerToUnavailableCodeStub(ctor))
emitApplyOfUnavailableCodeReached();

// Get the 'self' decl and type.
VarDecl *selfDecl = ctor->getImplicitSelfDecl();
auto &lowering = getTypeLowering(selfDecl->getType());
Expand Down Expand Up @@ -744,6 +750,9 @@ void SILGenFunction::emitEnumConstructor(EnumElementDecl *element) {
auto &enumTI =
SGM.Types.getTypeLowering(enumTy, TypeExpansionContext::minimal());

if (shouldLowerToUnavailableCodeStub(element))
emitApplyOfUnavailableCodeReached();

RegularLocation Loc(element);
CleanupLocation CleanupLoc(element);
Loc.markAutoGenerated();
Expand Down Expand Up @@ -820,6 +829,9 @@ void SILGenFunction::emitClassConstructorAllocator(ConstructorDecl *ctor) {
SmallVector<SILValue, 8> args;
bindParametersForForwarding(ctor->getParameters(), args);

if (shouldLowerToUnavailableCodeStub(ctor))
emitApplyOfUnavailableCodeReached();

SILValue selfMetaValue = emitConstructorMetatypeArg(*this, ctor);

// Allocate the "self" value.
Expand Down Expand Up @@ -936,6 +948,9 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {

assert(ctor->getTypecheckedBody() && "Class constructor without a body?");

if (shouldLowerToUnavailableCodeStub(ctor))
emitApplyOfUnavailableCodeReached();

// True if this constructor delegates to a peer constructor with self.init().
bool isDelegating = false;
if (!ctor->hasStubImplementation()) {
Expand Down
12 changes: 12 additions & 0 deletions lib/SILGen/SILGenDestructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ void SILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
if (dd->isImplicit())
Loc.markAutoGenerated();

if (shouldLowerToUnavailableCodeStub(dd))
emitApplyOfUnavailableCodeReached();

auto cd = cast<ClassDecl>(dd->getDeclContext()->getSelfNominalTypeDecl());
auto &C = cd->getASTContext();
SILValue selfValue = emitSelfDeclForDestructor(dd->getImplicitSelfDecl());
Expand Down Expand Up @@ -178,6 +181,9 @@ void SILGenFunction::emitDeallocatingClassDestructor(DestructorDecl *dd) {
RegularLocation loc(dd);
loc.markAutoGenerated();

if (shouldLowerToUnavailableCodeStub(dd))
emitApplyOfUnavailableCodeReached();

// Emit the prolog.
SILValue initialSelfValue =
emitSelfDeclForDestructor(dd->getImplicitSelfDecl());
Expand Down Expand Up @@ -233,6 +239,9 @@ void SILGenFunction::emitDeallocatingMoveOnlyDestructor(DestructorDecl *dd) {
if (dd->isImplicit())
loc.markAutoGenerated();

if (shouldLowerToUnavailableCodeStub(dd))
emitApplyOfUnavailableCodeReached();

// Emit the prolog.
auto selfValue = emitSelfDeclForDestructor(dd->getImplicitSelfDecl());

Expand Down Expand Up @@ -603,6 +612,9 @@ void SILGenFunction::emitObjCDestructor(SILDeclRef dtor) {
if (dd->isImplicit())
loc.markAutoGenerated();

if (shouldLowerToUnavailableCodeStub(dd))
emitApplyOfUnavailableCodeReached();

SILValue selfValue = emitSelfDeclForDestructor(dd->getImplicitSelfDecl());

// Create a basic block to jump to for the implicit destruction behavior
Expand Down
3 changes: 3 additions & 0 deletions lib/SILGen/SILGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,9 @@ void SILGenFunction::emitFunction(FuncDecl *fd) {
prepareEpilog(fd->getResultInterfaceType(),
fd->hasThrows(), CleanupLocation(fd));

if (shouldLowerToUnavailableCodeStub(fd))
emitApplyOfUnavailableCodeReached();

emitProfilerIncrement(fd->getTypecheckedBody());

// Emit the actual function body as usual
Expand Down
9 changes: 9 additions & 0 deletions lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,15 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
ArrayRef<ManagedValue> args,
SGFContext ctx);

RValue emitApplyOfLibraryIntrinsic(SILLocation loc, SILDeclRef declRef,
SubstitutionMap subMap,
ArrayRef<ManagedValue> args,
SGFContext ctx);

/// Emits a call to the `_diagnoseUnavailableCodeReached()` function in the
/// standard library.
void emitApplyOfUnavailableCodeReached();

RValue emitApplyAllocatingInitializer(SILLocation loc, ConcreteDeclRef init,
PreparedArguments &&args, Type overriddenSelfType,
SGFContext ctx);
Expand Down
7 changes: 7 additions & 0 deletions lib/SILGen/SILGenGlobalVariable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ void SILGenFunction::emitLazyGlobalInitializer(PatternBindingDecl *binding,

// Add unused context pointer argument required to pass to `Builtin.once`
SILBasicBlock &entry = *F.begin();

if (shouldLowerToUnavailableCodeStub(binding))
emitApplyOfUnavailableCodeReached();

SILType rawPointerSILTy =
getLoweredLoadableType(getASTContext().TheRawPointerType);
entry.createFunctionArgument(rawPointerSILTy);
Expand Down Expand Up @@ -279,6 +283,9 @@ static void emitOnceCall(SILGenFunction &SGF, VarDecl *global,
void SILGenFunction::emitGlobalAccessor(VarDecl *global,
SILGlobalVariable *onceToken,
SILFunction *onceFunc) {
if (shouldLowerToUnavailableCodeStub(global))
emitApplyOfUnavailableCodeReached();

emitOnceCall(*this, global, onceToken, onceFunc);

// Return the address of the global variable.
Expand Down
3 changes: 3 additions & 0 deletions lib/SILGen/SILGenPoly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4966,6 +4966,9 @@ void SILGenFunction::emitProtocolWitness(
SmallVector<ManagedValue, 8> origParams;
collectThunkParams(loc, origParams);

if (shouldLowerToUnavailableCodeStub(witness.getDecl()))
emitApplyOfUnavailableCodeReached();

if (enterIsolation) {
// If we are supposed to enter the actor, do so now by hopping to the
// actor.
Expand Down
15 changes: 15 additions & 0 deletions lib/SILOptimizer/Mandatory/DiagnoseUnreachable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,14 @@ static SILInstruction *getPrecedingCallToNoReturn(SILBasicBlock &BB) {
return first;
}

static bool isUnavailableCodeReachedCall(SILInstruction *I) {
if (auto *AI = dyn_cast<ApplyInst>(I))
if (AI->hasSemantics(SEMANTICS_UNAVAILABLE_CODE_REACHED))
return true;

return false;
}

static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
UnreachableUserCodeReportingState *State) {
auto I = BB.begin(), E = BB.end();
Expand Down Expand Up @@ -766,6 +774,13 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
noReturnCall->getLoc().isASTNode<ExplicitCastExpr>())
return false;

// If the no-return instruction is a call to the unavailable code reached
// diagnostic function then we assume that the call was inserted by the
// compiler because the function is semantically unavailable. Diagnosing the
// user written body of the function as unreachable would be redundant.
if (isUnavailableCodeReachedCall(noReturnCall))
return false;

diagnose(BB.getModule().getASTContext(), currInst->getLoc().getSourceLoc(),
diag::unreachable_code);
diagnose(BB.getModule().getASTContext(),
Expand Down
8 changes: 2 additions & 6 deletions lib/SILOptimizer/Mandatory/LowerHopToActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ namespace {
class LowerHopToActor {
SILFunction *F;
DominanceInfo *Dominance;
SILOptFunctionBuilder &functionBuilder;

/// A map from an actor value to the executor we've derived for it.
llvm::ScopedHashTable<SILValue, SILValue> ExecutorForActor;
Expand All @@ -64,11 +63,9 @@ class LowerHopToActor {

public:
LowerHopToActor(SILFunction *f,
SILOptFunctionBuilder &FunctionBuilder,
DominanceInfo *dominance)
: F(f),
Dominance(dominance),
functionBuilder(FunctionBuilder)
Dominance(dominance)
{ }

/// The entry point to the transformation.
Expand Down Expand Up @@ -250,8 +247,7 @@ class LowerHopToActorPass : public SILFunctionTransform {
void run() override {
auto fn = getFunction();
auto domTree = getAnalysis<DominanceAnalysis>()->get(fn);
auto functionBuilder = SILOptFunctionBuilder(*this);
LowerHopToActor pass(getFunction(), functionBuilder, domTree);
LowerHopToActor pass(getFunction(), domTree);
if (pass.run())
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
Expand Down
Loading