Skip to content

Commit af67204

Browse files
authored
[Serialization] Handle XREFs to private types (swiftlang#14352)
We can encounter these when the compiler modifies an inlinable function to break apart a struct and the struct uses a private type for one of its fields. It's questionable whether we /should/ handle this, but meanwhile this /is/ a non-intrusive fix that preserves the performance of non-resilient libraries. (That is, it appears this worked in Swift 4.0, though perhaps not all of the same optimizations kicked in.) https://bugs.swift.org/browse/SR-6874
1 parent 573ebc5 commit af67204

File tree

7 files changed

+156
-17
lines changed

7 files changed

+156
-17
lines changed

include/swift/Serialization/ModuleFormat.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const uint16_t VERSION_MAJOR = 0;
5454
/// in source control, you should also update the comment to briefly
5555
/// describe what change you made. The content of this comment isn't important;
5656
/// it just ensures a conflict if two people change the module format.
57-
const uint16_t VERSION_MINOR = 397; // No resilience expansion in SILDeclRef
57+
const uint16_t VERSION_MINOR = 398; // Private discriminators for type xrefs
5858

5959
using DeclIDField = BCFixed<31>;
6060

@@ -1288,6 +1288,7 @@ namespace decls_block {
12881288
using XRefTypePathPieceLayout = BCRecordLayout<
12891289
XREF_TYPE_PATH_PIECE,
12901290
IdentifierIDField, // name
1291+
IdentifierIDField, // private discriminator
12911292
BCFixed<1> // restrict to protocol extension
12921293
>;
12931294

lib/Serialization/Deserialization.cpp

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,18 +1264,22 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
12641264
case XREF_TYPE_PATH_PIECE:
12651265
case XREF_VALUE_PATH_PIECE: {
12661266
IdentifierID IID;
1267+
IdentifierID privateDiscriminator = 0;
12671268
TypeID TID = 0;
12681269
bool isType = (recordID == XREF_TYPE_PATH_PIECE);
12691270
bool inProtocolExt = false;
12701271
bool isStatic = false;
12711272
if (isType)
1272-
XRefTypePathPieceLayout::readRecord(scratch, IID, inProtocolExt);
1273+
XRefTypePathPieceLayout::readRecord(scratch, IID, privateDiscriminator,
1274+
inProtocolExt);
12731275
else
12741276
XRefValuePathPieceLayout::readRecord(scratch, TID, IID, inProtocolExt,
12751277
isStatic);
12761278

12771279
DeclBaseName name = getDeclBaseName(IID);
12781280
pathTrace.addValue(name);
1281+
if (privateDiscriminator)
1282+
pathTrace.addValue(getIdentifier(privateDiscriminator));
12791283

12801284
Type filterTy;
12811285
if (!isType) {
@@ -1290,9 +1294,14 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
12901294
pathTrace.addType(filterTy);
12911295
}
12921296

1293-
baseModule->lookupQualified(ModuleType::get(baseModule), name,
1294-
NL_QualifiedDefault | NL_KnownNoDependency,
1295-
/*typeResolver=*/nullptr, values);
1297+
if (privateDiscriminator) {
1298+
baseModule->lookupMember(values, baseModule, name,
1299+
getIdentifier(privateDiscriminator));
1300+
} else {
1301+
baseModule->lookupQualified(ModuleType::get(baseModule), name,
1302+
NL_QualifiedDefault | NL_KnownNoDependency,
1303+
/*typeResolver=*/nullptr, values);
1304+
}
12961305
filterValues(filterTy, nullptr, nullptr, isType, inProtocolExt, isStatic,
12971306
None, values);
12981307
break;
@@ -1348,7 +1357,7 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
13481357
switch (recordID) {
13491358
case XREF_TYPE_PATH_PIECE: {
13501359
IdentifierID IID;
1351-
XRefTypePathPieceLayout::readRecord(scratch, IID, None);
1360+
XRefTypePathPieceLayout::readRecord(scratch, IID, None, None);
13521361
result = getIdentifier(IID);
13531362
break;
13541363
}
@@ -1405,8 +1414,13 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
14051414
// Fast path for nested types that avoids deserializing all
14061415
// members of the parent type.
14071416
IdentifierID IID;
1417+
IdentifierID privateDiscriminator;
14081418
bool onlyInNominal = false;
1409-
XRefTypePathPieceLayout::readRecord(scratch, IID, onlyInNominal);
1419+
XRefTypePathPieceLayout::readRecord(scratch, IID, privateDiscriminator,
1420+
onlyInNominal);
1421+
if (privateDiscriminator)
1422+
goto giveUpFastPath;
1423+
14101424
Identifier memberName = getIdentifier(IID);
14111425
pathTrace.addValue(memberName);
14121426

@@ -1449,21 +1463,25 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
14491463

14501464
pathTrace.removeLast();
14511465
}
1466+
giveUpFastPath:
14521467
LLVM_FALLTHROUGH;
14531468
}
14541469
case XREF_VALUE_PATH_PIECE:
14551470
case XREF_INITIALIZER_PATH_PIECE: {
14561471
TypeID TID = 0;
14571472
DeclBaseName memberName;
1473+
Identifier privateDiscriminator;
14581474
Optional<swift::CtorInitializerKind> ctorInit;
14591475
bool isType = false;
14601476
bool inProtocolExt = false;
14611477
bool isStatic = false;
14621478
switch (recordID) {
14631479
case XREF_TYPE_PATH_PIECE: {
1464-
IdentifierID IID;
1465-
XRefTypePathPieceLayout::readRecord(scratch, IID, inProtocolExt);
1480+
IdentifierID IID, discriminatorID;
1481+
XRefTypePathPieceLayout::readRecord(scratch, IID, discriminatorID,
1482+
inProtocolExt);
14661483
memberName = getDeclBaseName(IID);
1484+
privateDiscriminator = getIdentifier(discriminatorID);
14671485
isType = true;
14681486
break;
14691487
}
@@ -1490,6 +1508,8 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
14901508
}
14911509

14921510
pathTrace.addValue(memberName);
1511+
if (!privateDiscriminator.empty())
1512+
pathTrace.addPrivateDiscriminator(privateDiscriminator);
14931513

14941514
Type filterTy;
14951515
if (!isType) {
@@ -1519,8 +1539,17 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
15191539
getXRefDeclNameForError());
15201540
}
15211541

1522-
auto members = nominal->lookupDirect(memberName);
1523-
values.append(members.begin(), members.end());
1542+
if (!privateDiscriminator.empty()) {
1543+
ModuleDecl *searchModule = M;
1544+
if (!searchModule)
1545+
searchModule = nominal->getModuleContext();
1546+
searchModule->lookupMember(values, nominal, memberName,
1547+
privateDiscriminator);
1548+
1549+
} else {
1550+
auto members = nominal->lookupDirect(memberName);
1551+
values.append(members.begin(), members.end());
1552+
}
15241553
filterValues(filterTy, M, genericSig, isType, inProtocolExt, isStatic,
15251554
ctorInit, values);
15261555
break;

lib/Serialization/DeserializationErrors.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class XRefTracePath {
3737
Accessor,
3838
Extension,
3939
GenericParam,
40+
PrivateDiscriminator,
4041
Unknown
4142
};
4243

@@ -55,11 +56,12 @@ class XRefTracePath {
5556
: kind(K),
5657
data(llvm::PointerLikeTypeTraits<T>::getAsVoidPointer(value)) {}
5758

58-
Identifier getAsIdentifier() const {
59+
DeclBaseName getAsBaseName() const {
5960
switch (kind) {
6061
case Kind::Value:
6162
case Kind::Operator:
62-
return getDataAs<Identifier>();
63+
case Kind::PrivateDiscriminator:
64+
return getDataAs<DeclBaseName>();
6365
case Kind::Type:
6466
case Kind::OperatorFilter:
6567
case Kind::Accessor:
@@ -73,7 +75,7 @@ class XRefTracePath {
7375
void print(raw_ostream &os) const {
7476
switch (kind) {
7577
case Kind::Value:
76-
os << getDataAs<Identifier>();
78+
os << getDataAs<DeclBaseName>();
7779
break;
7880
case Kind::Type:
7981
os << "with type " << getDataAs<Type>();
@@ -137,6 +139,9 @@ class XRefTracePath {
137139
case Kind::GenericParam:
138140
os << "generic param #" << getDataAs<uintptr_t>();
139141
break;
142+
case Kind::PrivateDiscriminator:
143+
os << "(in " << getDataAs<Identifier>() << ")";
144+
break;
140145
case Kind::Unknown:
141146
os << "unknown xref kind " << getDataAs<uintptr_t>();
142147
break;
@@ -180,17 +185,21 @@ class XRefTracePath {
180185
path.push_back({ PathPiece::Kind::GenericParam, index });
181186
}
182187

188+
void addPrivateDiscriminator(Identifier name) {
189+
path.push_back({ PathPiece::Kind::PrivateDiscriminator, name });
190+
}
191+
183192
void addUnknown(uintptr_t kind) {
184193
path.push_back({ PathPiece::Kind::Unknown, kind });
185194
}
186195

187-
Identifier getLastName() const {
196+
DeclBaseName getLastName() const {
188197
for (auto &piece : reversed(path)) {
189-
Identifier result = piece.getAsIdentifier();
198+
DeclBaseName result = piece.getAsBaseName();
190199
if (!result.empty())
191200
return result;
192201
}
193-
return Identifier();
202+
return DeclBaseName();
194203
}
195204

196205
void removeLast() {

lib/Serialization/Serialization.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,8 +1868,16 @@ void Serializer::writeCrossReference(const DeclContext *DC, uint32_t pathLen) {
18681868

18691869
auto generic = cast<GenericTypeDecl>(DC);
18701870
abbrCode = DeclTypeAbbrCodes[XRefTypePathPieceLayout::Code];
1871+
1872+
Identifier discriminator;
1873+
if (generic->isOutermostPrivateOrFilePrivateScope()) {
1874+
auto *containingFile = cast<FileUnit>(generic->getModuleScopeContext());
1875+
discriminator = containingFile->getDiscriminatorForPrivateValue(generic);
1876+
}
1877+
18711878
XRefTypePathPieceLayout::emitRecord(Out, ScratchRecord, abbrCode,
18721879
addDeclBaseNameRef(generic->getName()),
1880+
addDeclBaseNameRef(discriminator),
18731881
false);
18741882
break;
18751883
}
@@ -2019,8 +2027,17 @@ void Serializer::writeCrossReference(const Decl *D) {
20192027
bool isProtocolExt = D->getDeclContext()->getAsProtocolExtensionContext();
20202028
if (auto type = dyn_cast<TypeDecl>(D)) {
20212029
abbrCode = DeclTypeAbbrCodes[XRefTypePathPieceLayout::Code];
2030+
2031+
Identifier discriminator;
2032+
if (type->isOutermostPrivateOrFilePrivateScope()) {
2033+
auto *containingFile =
2034+
cast<FileUnit>(type->getDeclContext()->getModuleScopeContext());
2035+
discriminator = containingFile->getDiscriminatorForPrivateValue(type);
2036+
}
2037+
20222038
XRefTypePathPieceLayout::emitRecord(Out, ScratchRecord, abbrCode,
20232039
addDeclBaseNameRef(type->getName()),
2040+
addDeclBaseNameRef(discriminator),
20242041
isProtocolExt);
20252042
return;
20262043
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import LibCore
2+
3+
public let lazyFoo = Foo()
4+
public func testFoo(_: Foo = lazyFoo) {}
5+
public let lazyBar = Bar()
6+
public func testBar(_: Bar = lazyBar) {}
7+
public let lazyBaz = Baz()
8+
public func testBaz(_: Baz = lazyBaz) {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
private class TopLevelInternalClass {}
2+
public struct Foo {
3+
private var ref: TopLevelInternalClass
4+
5+
public init() { self.ref = .init() }
6+
}
7+
8+
public struct Bar {
9+
private class NestedInternalClass {}
10+
11+
private var ref: NestedInternalClass
12+
13+
public init() { self.ref = .init() }
14+
}
15+
16+
public struct Baz {
17+
fileprivate class NestedInternalClass {
18+
fileprivate class DoublyNestedInternalClass {}
19+
}
20+
21+
private var ref: NestedInternalClass.DoublyNestedInternalClass
22+
23+
public init() { self.ref = .init() }
24+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -swift-version 4 -O -force-single-frontend-invocation -emit-module-path %t/LibCore.swiftmodule %S/Inputs/xref-private-type/LibCore.swift
3+
// RUN: %target-build-swift -swift-version 4 -O -I %t -force-single-frontend-invocation -emit-module-path %t/Lib.swiftmodule %S/Inputs/xref-private-type/Lib.swift
4+
// RUN: %target-build-swift -swift-version 4 -O -I %t -emit-sil %s | %FileCheck %s
5+
6+
import Lib
7+
8+
// CHECK: sil{{.*}} @[[TESTSR6874:[^ ]+10testSR6874[^ ]+]] :
9+
func testSR6874() {
10+
// The important lines in this test are the strong_retains, which refer to
11+
// private types defined in LibCore. Normally we shouldn't have references to
12+
// non-public declarations in inlinable code, but because SIL passes can break
13+
// apart non-resilient structs and enums we can end up in that situation.
14+
// Worse, this can happen *across module boundaries.*
15+
//
16+
// In this test, the addressor for each global defined in Lib ends up
17+
// referencing private types defined in LibCore. Using those globals in
18+
// default argument position leads to the addressor getting inlined into
19+
// calling code in Swift 4 and later. This results in an attempt to not just
20+
// reference a private type, but to *resolve a cross-reference to a private
21+
// type.*
22+
//
23+
// This is the situation in SR-6874 (simplified). I'm not sure of a simpler
24+
// way to reliably trigger the issue. But if this test breaks, please try to
25+
// find one.
26+
//
27+
// (We may want to revisit this whole thing later, as it violates the model.
28+
// But it's also useful for performance in non-resilient code.)
29+
30+
// CHECK: [[ADDR:%.+]] = global_addr @{{[^ ]+}}3Lib7lazyFoo
31+
// CHECK: [[LOADED:%.+]] = load [[ADDR]] : $*Foo
32+
// CHECK: [[REF:%.+]] = struct_extract [[LOADED]] : $Foo, #Foo.ref
33+
// CHECK: strong_retain [[REF]] : $TopLevelInternalClass
34+
// CHECK: apply {{%.+}}([[LOADED]])
35+
testFoo()
36+
37+
// CHECK: [[ADDR:%.+]] = global_addr @{{[^ ]+}}3Lib7lazyBar
38+
// CHECK: [[LOADED:%.+]] = load [[ADDR]] : $*Bar
39+
// CHECK: [[REF:%.+]] = struct_extract [[LOADED]] : $Bar, #Bar.ref
40+
// CHECK: strong_retain [[REF]] : $Bar.NestedInternalClass
41+
// CHECK: apply {{%.+}}([[LOADED]])
42+
testBar()
43+
44+
// CHECK: [[ADDR:%.+]] = global_addr @{{[^ ]+}}3Lib7lazyBaz
45+
// CHECK: [[LOADED:%.+]] = load [[ADDR]] : $*Baz
46+
// CHECK: [[REF:%.+]] = struct_extract [[LOADED]] : $Baz, #Baz.ref
47+
// CHECK: strong_retain [[REF]] : $Baz.NestedInternalClass.DoublyNestedInternalClass
48+
// CHECK: apply {{%.+}}([[LOADED]])
49+
testBaz()
50+
} // CHECK: end sil function '[[TESTSR6874]]'
51+

0 commit comments

Comments
 (0)