Skip to content

Commit 031f112

Browse files
authored
Merge pull request #69609 from artemcm/AlwaysEmitConformanceMetadataPreservation
Add mandatory SIL pass implementing '@_alwaysEmitConformanceMetadata' protocol attribute
2 parents 199dcc2 + feb5d45 commit 031f112

File tree

7 files changed

+148
-0
lines changed

7 files changed

+148
-0
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ PASS(AccessEnforcementSelection, "access-enforcement-selection",
9898
"Access Enforcement Selection")
9999
PASS(AccessEnforcementWMO, "access-enforcement-wmo",
100100
"Access Enforcement Whole Module Optimization")
101+
PASS(AlwaysEmitConformanceMetadataPreservation, "preserve-always-emit-conformance-metadata",
102+
"Mark conformances to @_alwaysEmitConformanceMetadata protocols as externally visible")
101103
PASS(CrossModuleOptimization, "cmo",
102104
"Perform cross-module optimization")
103105
PASS(AccessSummaryDumper, "access-summary-dump",

lib/IRGen/IRGenModule.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,11 @@ bool IRGenerator::canEmitWitnessTableLazily(SILWitnessTable *wt) {
13301330
if (wt->getLinkage() == SILLinkage::Shared)
13311331
return true;
13321332

1333+
// Check if this type is set to be explicitly externally visible
1334+
NominalTypeDecl *ConformingTy = wt->getConformingNominal();
1335+
if (PrimaryIGM->getSILModule().isExternallyVisibleDecl(ConformingTy))
1336+
return false;
1337+
13331338
switch (wt->getConformingNominal()->getEffectiveAccess()) {
13341339
case AccessLevel::Private:
13351340
case AccessLevel::FilePrivate:
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//===--- AlwaysEmitConformanceMetadataPreservation.cpp -------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// Some frameworks may rely on conformances to protocols they provide
14+
/// to be present in binary product they are compiled into even if
15+
/// such conformances are not otherwise referenced in user code.
16+
/// Such conformances may then, for example, be queried and used by the
17+
/// runtime.
18+
///
19+
/// The developer may not ever explicitly reference or instantiate this type in
20+
/// their code, as it is effectively defining an XPC endpoint. However, when
21+
/// optimizations are enabled, the type may be stripped from the binary as it is
22+
/// never referenced. `@_alwaysEmitConformanceMetadata` can be used to mark
23+
/// a protocol to ensure that its conformances are always marked as externally
24+
/// visible even if not `public` to ensure they do not get optimized away.
25+
/// This mandatory pass makes it so.
26+
///
27+
//===----------------------------------------------------------------------===//
28+
29+
#include "swift/AST/Attr.h"
30+
#define DEBUG_TYPE "always-emit-conformance-metadata-preservation"
31+
#include "swift/AST/ASTWalker.h"
32+
#include "swift/AST/NameLookup.h"
33+
#include "swift/AST/ProtocolConformance.h"
34+
#include "swift/AST/SourceFile.h"
35+
#include "swift/SIL/SILModule.h"
36+
#include "swift/SILOptimizer/PassManager/Passes.h"
37+
#include "swift/SILOptimizer/PassManager/Transforms.h"
38+
39+
using namespace swift;
40+
41+
namespace {
42+
43+
/// A helper class to collect all nominal type declarations that conform to
44+
/// `@_alwaysEmitConformanceMetadata` protocols.
45+
class AlwaysEmitMetadataConformanceCollector : public ASTWalker {
46+
std::vector<NominalTypeDecl *> &AlwaysEmitMetadataConformanceDecls;
47+
48+
public:
49+
AlwaysEmitMetadataConformanceCollector(
50+
std::vector<NominalTypeDecl *> &AlwaysEmitMetadataConformanceDecls)
51+
: AlwaysEmitMetadataConformanceDecls(AlwaysEmitMetadataConformanceDecls) {
52+
}
53+
54+
PreWalkAction walkToDeclPre(Decl *D) override {
55+
auto hasAlwaysEmitMetadataConformance =
56+
[&](llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> Decl) {
57+
bool anyObject = false;
58+
for (const auto &found :
59+
getDirectlyInheritedNominalTypeDecls(Decl, anyObject))
60+
if (auto Protocol = dyn_cast<ProtocolDecl>(found.Item))
61+
if (Protocol->getAttrs()
62+
.hasAttribute<AlwaysEmitConformanceMetadataAttr>())
63+
return true;
64+
return false;
65+
};
66+
67+
if (auto *NTD = dyn_cast<NominalTypeDecl>(D)) {
68+
if (hasAlwaysEmitMetadataConformance(NTD))
69+
AlwaysEmitMetadataConformanceDecls.push_back(NTD);
70+
} else if (auto *ETD = dyn_cast<ExtensionDecl>(D)) {
71+
if (hasAlwaysEmitMetadataConformance(ETD))
72+
AlwaysEmitMetadataConformanceDecls.push_back(ETD->getExtendedNominal());
73+
}
74+
75+
return Action::Continue();
76+
}
77+
};
78+
79+
class AlwaysEmitConformanceMetadataPreservation : public SILModuleTransform {
80+
void run() override {
81+
auto &M = *getModule();
82+
std::vector<NominalTypeDecl *> AlwaysEmitMetadataConformanceDecls;
83+
AlwaysEmitMetadataConformanceCollector Walker(
84+
AlwaysEmitMetadataConformanceDecls);
85+
86+
SmallVector<Decl *> TopLevelDecls;
87+
if (M.getSwiftModule()->isMainModule()) {
88+
if (M.isWholeModule()) {
89+
for (const auto File : M.getSwiftModule()->getFiles())
90+
File->getTopLevelDecls(TopLevelDecls);
91+
} else {
92+
for (const auto Primary : M.getSwiftModule()->getPrimarySourceFiles())
93+
Primary->getTopLevelDecls(TopLevelDecls);
94+
}
95+
}
96+
for (auto *TLD : TopLevelDecls)
97+
TLD->walk(Walker);
98+
99+
for (auto &NTD : AlwaysEmitMetadataConformanceDecls)
100+
M.addExternallyVisibleDecl(NTD);
101+
}
102+
};
103+
} // end anonymous namespace
104+
105+
SILTransform *swift::createAlwaysEmitConformanceMetadataPreservation() {
106+
return new AlwaysEmitConformanceMetadataPreservation();
107+
}

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
target_sources(swiftSILOptimizer PRIVATE
22
AccessEnforcementSelection.cpp
33
AccessMarkerElimination.cpp
4+
AlwaysEmitConformanceMetadataPreservation.cpp
45
AddressLowering.cpp
56
CapturePromotion.cpp
67
ClosureLifetimeFixup.cpp

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ SILPassPipelinePlan::getLoweringPassPipeline(const SILOptions &Options) {
875875
P.startPipeline("Lowering");
876876
P.addLowerHopToActor(); // FIXME: earlier for more opportunities?
877877
P.addOwnershipModelEliminator();
878+
P.addAlwaysEmitConformanceMetadataPreservation();
878879
P.addIRGenPrepare();
879880

880881
return P;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@_alwaysEmitConformanceMetadata
2+
public protocol TestEntity {}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/includes)
3+
4+
// Build support Protocols module
5+
// RUN: %target-build-swift %S/Inputs/PreservedConformanceProtocols.swift -parse-as-library -emit-module -emit-library -module-name PreservedConformanceProtocols -o %t/includes/PreservedConformanceProtocols.o
6+
7+
// Build the test into a binary
8+
// RUN: %target-build-swift %s -parse-as-library -emit-module -emit-library -module-name PreservedConformances -O -whole-module-optimization -I %t/includes -o %t/PreservedConformances -Xlinker %t/includes/PreservedConformanceProtocols.o
9+
10+
// RUN: %target-swift-reflection-dump %t/PreservedConformances | %FileCheck %s
11+
12+
import PreservedConformanceProtocols
13+
14+
struct internalTestEntity : TestEntity {
15+
struct internalNestedTestEntity : TestEntity {}
16+
}
17+
private struct privateTestEntity : TestEntity {
18+
private struct privateNestedTestEntity : TestEntity {}
19+
}
20+
fileprivate struct filePrivateTestEntity : TestEntity {}
21+
public struct publicTestEntity : TestEntity {}
22+
23+
// CHECK: CONFORMANCES:
24+
// CHECK: =============
25+
// CHECK-DAG: 21PreservedConformances16publicTestEntityV (PreservedConformances.publicTestEntity) : PreservedConformanceProtocols.TestEntity
26+
// CHECK-DAG: 21PreservedConformances21filePrivateTestEntity5${{[0-9a-f]+}}LLV (PreservedConformances.(filePrivateTestEntity in ${{[0-9a-f]+}})) : PreservedConformanceProtocols.TestEntity
27+
// CHECK-DAG: 21PreservedConformances17privateTestEntity5${{[0-9a-f]+}}LLV (PreservedConformances.(privateTestEntity in ${{[0-9a-f]+}})) : PreservedConformanceProtocols.TestEntity
28+
// CHECK-DAG: 21PreservedConformances17privateTestEntity5${{[0-9a-f]+}}LLV0c6NesteddE0V (PreservedConformances.(privateTestEntity in ${{[0-9a-f]+}}).privateNestedTestEntity) : PreservedConformanceProtocols.TestEntity
29+
// CHECK-DAG: 21PreservedConformances18internalTestEntityV (PreservedConformances.internalTestEntity) : PreservedConformanceProtocols.TestEntity
30+
// CHECK-DAG: 21PreservedConformances18internalTestEntityV0c6NesteddE0V (PreservedConformances.internalTestEntity.internalNestedTestEntity) : PreservedConformanceProtocols.TestEntity

0 commit comments

Comments
 (0)