[Serialization] Handle XREFs to private types (#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
This commit is contained in:
Jordan Rose
2018-02-07 16:42:16 -08:00
committed by GitHub
parent 573ebc5920
commit af67204b51
7 changed files with 156 additions and 17 deletions

View File

@@ -54,7 +54,7 @@ const uint16_t VERSION_MAJOR = 0;
/// in source control, you should also update the comment to briefly /// in source control, you should also update the comment to briefly
/// describe what change you made. The content of this comment isn't important; /// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format. /// it just ensures a conflict if two people change the module format.
const uint16_t VERSION_MINOR = 397; // No resilience expansion in SILDeclRef const uint16_t VERSION_MINOR = 398; // Private discriminators for type xrefs
using DeclIDField = BCFixed<31>; using DeclIDField = BCFixed<31>;
@@ -1288,6 +1288,7 @@ namespace decls_block {
using XRefTypePathPieceLayout = BCRecordLayout< using XRefTypePathPieceLayout = BCRecordLayout<
XREF_TYPE_PATH_PIECE, XREF_TYPE_PATH_PIECE,
IdentifierIDField, // name IdentifierIDField, // name
IdentifierIDField, // private discriminator
BCFixed<1> // restrict to protocol extension BCFixed<1> // restrict to protocol extension
>; >;

View File

@@ -1264,18 +1264,22 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
case XREF_TYPE_PATH_PIECE: case XREF_TYPE_PATH_PIECE:
case XREF_VALUE_PATH_PIECE: { case XREF_VALUE_PATH_PIECE: {
IdentifierID IID; IdentifierID IID;
IdentifierID privateDiscriminator = 0;
TypeID TID = 0; TypeID TID = 0;
bool isType = (recordID == XREF_TYPE_PATH_PIECE); bool isType = (recordID == XREF_TYPE_PATH_PIECE);
bool inProtocolExt = false; bool inProtocolExt = false;
bool isStatic = false; bool isStatic = false;
if (isType) if (isType)
XRefTypePathPieceLayout::readRecord(scratch, IID, inProtocolExt); XRefTypePathPieceLayout::readRecord(scratch, IID, privateDiscriminator,
inProtocolExt);
else else
XRefValuePathPieceLayout::readRecord(scratch, TID, IID, inProtocolExt, XRefValuePathPieceLayout::readRecord(scratch, TID, IID, inProtocolExt,
isStatic); isStatic);
DeclBaseName name = getDeclBaseName(IID); DeclBaseName name = getDeclBaseName(IID);
pathTrace.addValue(name); pathTrace.addValue(name);
if (privateDiscriminator)
pathTrace.addValue(getIdentifier(privateDiscriminator));
Type filterTy; Type filterTy;
if (!isType) { if (!isType) {
@@ -1290,9 +1294,14 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
pathTrace.addType(filterTy); pathTrace.addType(filterTy);
} }
baseModule->lookupQualified(ModuleType::get(baseModule), name, if (privateDiscriminator) {
NL_QualifiedDefault | NL_KnownNoDependency, baseModule->lookupMember(values, baseModule, name,
/*typeResolver=*/nullptr, values); getIdentifier(privateDiscriminator));
} else {
baseModule->lookupQualified(ModuleType::get(baseModule), name,
NL_QualifiedDefault | NL_KnownNoDependency,
/*typeResolver=*/nullptr, values);
}
filterValues(filterTy, nullptr, nullptr, isType, inProtocolExt, isStatic, filterValues(filterTy, nullptr, nullptr, isType, inProtocolExt, isStatic,
None, values); None, values);
break; break;
@@ -1348,7 +1357,7 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
switch (recordID) { switch (recordID) {
case XREF_TYPE_PATH_PIECE: { case XREF_TYPE_PATH_PIECE: {
IdentifierID IID; IdentifierID IID;
XRefTypePathPieceLayout::readRecord(scratch, IID, None); XRefTypePathPieceLayout::readRecord(scratch, IID, None, None);
result = getIdentifier(IID); result = getIdentifier(IID);
break; break;
} }
@@ -1405,8 +1414,13 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
// Fast path for nested types that avoids deserializing all // Fast path for nested types that avoids deserializing all
// members of the parent type. // members of the parent type.
IdentifierID IID; IdentifierID IID;
IdentifierID privateDiscriminator;
bool onlyInNominal = false; bool onlyInNominal = false;
XRefTypePathPieceLayout::readRecord(scratch, IID, onlyInNominal); XRefTypePathPieceLayout::readRecord(scratch, IID, privateDiscriminator,
onlyInNominal);
if (privateDiscriminator)
goto giveUpFastPath;
Identifier memberName = getIdentifier(IID); Identifier memberName = getIdentifier(IID);
pathTrace.addValue(memberName); pathTrace.addValue(memberName);
@@ -1449,21 +1463,25 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
pathTrace.removeLast(); pathTrace.removeLast();
} }
giveUpFastPath:
LLVM_FALLTHROUGH; LLVM_FALLTHROUGH;
} }
case XREF_VALUE_PATH_PIECE: case XREF_VALUE_PATH_PIECE:
case XREF_INITIALIZER_PATH_PIECE: { case XREF_INITIALIZER_PATH_PIECE: {
TypeID TID = 0; TypeID TID = 0;
DeclBaseName memberName; DeclBaseName memberName;
Identifier privateDiscriminator;
Optional<swift::CtorInitializerKind> ctorInit; Optional<swift::CtorInitializerKind> ctorInit;
bool isType = false; bool isType = false;
bool inProtocolExt = false; bool inProtocolExt = false;
bool isStatic = false; bool isStatic = false;
switch (recordID) { switch (recordID) {
case XREF_TYPE_PATH_PIECE: { case XREF_TYPE_PATH_PIECE: {
IdentifierID IID; IdentifierID IID, discriminatorID;
XRefTypePathPieceLayout::readRecord(scratch, IID, inProtocolExt); XRefTypePathPieceLayout::readRecord(scratch, IID, discriminatorID,
inProtocolExt);
memberName = getDeclBaseName(IID); memberName = getDeclBaseName(IID);
privateDiscriminator = getIdentifier(discriminatorID);
isType = true; isType = true;
break; break;
} }
@@ -1490,6 +1508,8 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
} }
pathTrace.addValue(memberName); pathTrace.addValue(memberName);
if (!privateDiscriminator.empty())
pathTrace.addPrivateDiscriminator(privateDiscriminator);
Type filterTy; Type filterTy;
if (!isType) { if (!isType) {
@@ -1519,8 +1539,17 @@ ModuleFile::resolveCrossReference(ModuleDecl *baseModule, uint32_t pathLen) {
getXRefDeclNameForError()); getXRefDeclNameForError());
} }
auto members = nominal->lookupDirect(memberName); if (!privateDiscriminator.empty()) {
values.append(members.begin(), members.end()); ModuleDecl *searchModule = M;
if (!searchModule)
searchModule = nominal->getModuleContext();
searchModule->lookupMember(values, nominal, memberName,
privateDiscriminator);
} else {
auto members = nominal->lookupDirect(memberName);
values.append(members.begin(), members.end());
}
filterValues(filterTy, M, genericSig, isType, inProtocolExt, isStatic, filterValues(filterTy, M, genericSig, isType, inProtocolExt, isStatic,
ctorInit, values); ctorInit, values);
break; break;

View File

@@ -37,6 +37,7 @@ class XRefTracePath {
Accessor, Accessor,
Extension, Extension,
GenericParam, GenericParam,
PrivateDiscriminator,
Unknown Unknown
}; };
@@ -55,11 +56,12 @@ class XRefTracePath {
: kind(K), : kind(K),
data(llvm::PointerLikeTypeTraits<T>::getAsVoidPointer(value)) {} data(llvm::PointerLikeTypeTraits<T>::getAsVoidPointer(value)) {}
Identifier getAsIdentifier() const { DeclBaseName getAsBaseName() const {
switch (kind) { switch (kind) {
case Kind::Value: case Kind::Value:
case Kind::Operator: case Kind::Operator:
return getDataAs<Identifier>(); case Kind::PrivateDiscriminator:
return getDataAs<DeclBaseName>();
case Kind::Type: case Kind::Type:
case Kind::OperatorFilter: case Kind::OperatorFilter:
case Kind::Accessor: case Kind::Accessor:
@@ -73,7 +75,7 @@ class XRefTracePath {
void print(raw_ostream &os) const { void print(raw_ostream &os) const {
switch (kind) { switch (kind) {
case Kind::Value: case Kind::Value:
os << getDataAs<Identifier>(); os << getDataAs<DeclBaseName>();
break; break;
case Kind::Type: case Kind::Type:
os << "with type " << getDataAs<Type>(); os << "with type " << getDataAs<Type>();
@@ -137,6 +139,9 @@ class XRefTracePath {
case Kind::GenericParam: case Kind::GenericParam:
os << "generic param #" << getDataAs<uintptr_t>(); os << "generic param #" << getDataAs<uintptr_t>();
break; break;
case Kind::PrivateDiscriminator:
os << "(in " << getDataAs<Identifier>() << ")";
break;
case Kind::Unknown: case Kind::Unknown:
os << "unknown xref kind " << getDataAs<uintptr_t>(); os << "unknown xref kind " << getDataAs<uintptr_t>();
break; break;
@@ -180,17 +185,21 @@ public:
path.push_back({ PathPiece::Kind::GenericParam, index }); path.push_back({ PathPiece::Kind::GenericParam, index });
} }
void addPrivateDiscriminator(Identifier name) {
path.push_back({ PathPiece::Kind::PrivateDiscriminator, name });
}
void addUnknown(uintptr_t kind) { void addUnknown(uintptr_t kind) {
path.push_back({ PathPiece::Kind::Unknown, kind }); path.push_back({ PathPiece::Kind::Unknown, kind });
} }
Identifier getLastName() const { DeclBaseName getLastName() const {
for (auto &piece : reversed(path)) { for (auto &piece : reversed(path)) {
Identifier result = piece.getAsIdentifier(); DeclBaseName result = piece.getAsBaseName();
if (!result.empty()) if (!result.empty())
return result; return result;
} }
return Identifier(); return DeclBaseName();
} }
void removeLast() { void removeLast() {

View File

@@ -1868,8 +1868,16 @@ void Serializer::writeCrossReference(const DeclContext *DC, uint32_t pathLen) {
auto generic = cast<GenericTypeDecl>(DC); auto generic = cast<GenericTypeDecl>(DC);
abbrCode = DeclTypeAbbrCodes[XRefTypePathPieceLayout::Code]; abbrCode = DeclTypeAbbrCodes[XRefTypePathPieceLayout::Code];
Identifier discriminator;
if (generic->isOutermostPrivateOrFilePrivateScope()) {
auto *containingFile = cast<FileUnit>(generic->getModuleScopeContext());
discriminator = containingFile->getDiscriminatorForPrivateValue(generic);
}
XRefTypePathPieceLayout::emitRecord(Out, ScratchRecord, abbrCode, XRefTypePathPieceLayout::emitRecord(Out, ScratchRecord, abbrCode,
addDeclBaseNameRef(generic->getName()), addDeclBaseNameRef(generic->getName()),
addDeclBaseNameRef(discriminator),
false); false);
break; break;
} }
@@ -2019,8 +2027,17 @@ void Serializer::writeCrossReference(const Decl *D) {
bool isProtocolExt = D->getDeclContext()->getAsProtocolExtensionContext(); bool isProtocolExt = D->getDeclContext()->getAsProtocolExtensionContext();
if (auto type = dyn_cast<TypeDecl>(D)) { if (auto type = dyn_cast<TypeDecl>(D)) {
abbrCode = DeclTypeAbbrCodes[XRefTypePathPieceLayout::Code]; abbrCode = DeclTypeAbbrCodes[XRefTypePathPieceLayout::Code];
Identifier discriminator;
if (type->isOutermostPrivateOrFilePrivateScope()) {
auto *containingFile =
cast<FileUnit>(type->getDeclContext()->getModuleScopeContext());
discriminator = containingFile->getDiscriminatorForPrivateValue(type);
}
XRefTypePathPieceLayout::emitRecord(Out, ScratchRecord, abbrCode, XRefTypePathPieceLayout::emitRecord(Out, ScratchRecord, abbrCode,
addDeclBaseNameRef(type->getName()), addDeclBaseNameRef(type->getName()),
addDeclBaseNameRef(discriminator),
isProtocolExt); isProtocolExt);
return; return;
} }

View File

@@ -0,0 +1,8 @@
import LibCore
public let lazyFoo = Foo()
public func testFoo(_: Foo = lazyFoo) {}
public let lazyBar = Bar()
public func testBar(_: Bar = lazyBar) {}
public let lazyBaz = Baz()
public func testBaz(_: Baz = lazyBaz) {}

View File

@@ -0,0 +1,24 @@
private class TopLevelInternalClass {}
public struct Foo {
private var ref: TopLevelInternalClass
public init() { self.ref = .init() }
}
public struct Bar {
private class NestedInternalClass {}
private var ref: NestedInternalClass
public init() { self.ref = .init() }
}
public struct Baz {
fileprivate class NestedInternalClass {
fileprivate class DoublyNestedInternalClass {}
}
private var ref: NestedInternalClass.DoublyNestedInternalClass
public init() { self.ref = .init() }
}

View File

@@ -0,0 +1,51 @@
// RUN: %empty-directory(%t)
// 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
// 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
// RUN: %target-build-swift -swift-version 4 -O -I %t -emit-sil %s | %FileCheck %s
import Lib
// CHECK: sil{{.*}} @[[TESTSR6874:[^ ]+10testSR6874[^ ]+]] :
func testSR6874() {
// The important lines in this test are the strong_retains, which refer to
// private types defined in LibCore. Normally we shouldn't have references to
// non-public declarations in inlinable code, but because SIL passes can break
// apart non-resilient structs and enums we can end up in that situation.
// Worse, this can happen *across module boundaries.*
//
// In this test, the addressor for each global defined in Lib ends up
// referencing private types defined in LibCore. Using those globals in
// default argument position leads to the addressor getting inlined into
// calling code in Swift 4 and later. This results in an attempt to not just
// reference a private type, but to *resolve a cross-reference to a private
// type.*
//
// This is the situation in SR-6874 (simplified). I'm not sure of a simpler
// way to reliably trigger the issue. But if this test breaks, please try to
// find one.
//
// (We may want to revisit this whole thing later, as it violates the model.
// But it's also useful for performance in non-resilient code.)
// CHECK: [[ADDR:%.+]] = global_addr @{{[^ ]+}}3Lib7lazyFoo
// CHECK: [[LOADED:%.+]] = load [[ADDR]] : $*Foo
// CHECK: [[REF:%.+]] = struct_extract [[LOADED]] : $Foo, #Foo.ref
// CHECK: strong_retain [[REF]] : $TopLevelInternalClass
// CHECK: apply {{%.+}}([[LOADED]])
testFoo()
// CHECK: [[ADDR:%.+]] = global_addr @{{[^ ]+}}3Lib7lazyBar
// CHECK: [[LOADED:%.+]] = load [[ADDR]] : $*Bar
// CHECK: [[REF:%.+]] = struct_extract [[LOADED]] : $Bar, #Bar.ref
// CHECK: strong_retain [[REF]] : $Bar.NestedInternalClass
// CHECK: apply {{%.+}}([[LOADED]])
testBar()
// CHECK: [[ADDR:%.+]] = global_addr @{{[^ ]+}}3Lib7lazyBaz
// CHECK: [[LOADED:%.+]] = load [[ADDR]] : $*Baz
// CHECK: [[REF:%.+]] = struct_extract [[LOADED]] : $Baz, #Baz.ref
// CHECK: strong_retain [[REF]] : $Baz.NestedInternalClass.DoublyNestedInternalClass
// CHECK: apply {{%.+}}([[LOADED]])
testBaz()
} // CHECK: end sil function '[[TESTSR6874]]'