Skip to content

Commit 8145cd0

Browse files
committed
[Serialization] Add a "nested types" lookup table for partial modules.
There's a class of errors in Serialization called "circularity issues", where declaration A in file A.swift depends on declaration B in file B.swift, and B also depends on A. In some cases we can manage to type-check each of these files individually due to the laziness of 'validateDecl', but then fail to merge the "partial modules" generated from A.swift and B.swift to form a single swiftmodule for the library (because deserialization is a little less lazy for some things). A common case of this is when at least one of the declarations is nested, in which case a lookup to find that declaration needs to load all the members of the parent type. This gets even worse when the nested type is defined in an extension. This commit sidesteps that issue specifically for nested types by creating a top-level, per-file table of nested types in the "partial modules". When a type is in the same module, we can then look it up /without/ importing all other members of the parent type. The long-term solution is to allow accessing any members of a type without having to load them all, something we should support not just for module-merging while building a single target but when reading from imported modules as well. This should improve both compile time and memory usage, though I'm not sure to what extent. (Unfortunately, too many things still depend on the whole members list being loaded.) Because this is a new code path, I put in a switch to turn it off: frontend flag -disable-serialization-nested-type-lookup-table https://bugs.swift.org/browse/SR-3707 (and possibly others)
1 parent 5d89d86 commit 8145cd0

17 files changed

+376
-14
lines changed

include/swift/Frontend/FrontendOptions.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ class FrontendOptions {
194194
/// until the end of all files.
195195
bool DelayedFunctionBodyParsing = false;
196196

197+
/// If true, serialization encodes an extra lookup table for use in module-
198+
/// merging when emitting partial modules (the per-file modules in a non-WMO
199+
/// build).
200+
bool EnableSerializationNestedTypeLookupTable = true;
201+
197202
/// Indicates whether or not an import statement can pick up a Swift source
198203
/// file (as opposed to a module file).
199204
bool EnableSourceImport = false;

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ def autolink_library : Separate<["-"], "autolink-library">,
119119
def disable_typo_correction : Flag<["-"], "disable-typo-correction">,
120120
HelpText<"Disable typo correction">;
121121

122+
def disable_serialization_nested_type_lookup_table :
123+
Flag<["-"], "disable-serialization-nested-type-lookup-table">,
124+
HelpText<"Force module merging to use regular lookups to find nested types">;
125+
122126
} // end let Flags = [FrontendOption, NoDriverOption]
123127

124128
def debug_crash_Group : OptionGroup<"<automatic crashing options>">;

include/swift/Serialization/ModuleFile.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,13 +316,18 @@ class ModuleFile : public LazyMemberLoader {
316316
using SerializedLocalDeclTable =
317317
llvm::OnDiskIterableChainedHashTable<LocalDeclTableInfo>;
318318

319+
class NestedTypeDeclsTableInfo;
320+
using SerializedNestedTypeDeclsTable =
321+
llvm::OnDiskIterableChainedHashTable<NestedTypeDeclsTableInfo>;
322+
319323
std::unique_ptr<SerializedDeclTable> TopLevelDecls;
320324
std::unique_ptr<SerializedDeclTable> OperatorDecls;
321325
std::unique_ptr<SerializedDeclTable> PrecedenceGroupDecls;
322326
std::unique_ptr<SerializedDeclTable> ExtensionDecls;
323327
std::unique_ptr<SerializedDeclTable> ClassMembersByName;
324328
std::unique_ptr<SerializedDeclTable> OperatorMethodDecls;
325329
std::unique_ptr<SerializedLocalDeclTable> LocalTypeDecls;
330+
std::unique_ptr<SerializedNestedTypeDeclsTable> NestedTypeDecls;
326331

327332
class ObjCMethodTableInfo;
328333
using SerializedObjCMethodTable =
@@ -441,6 +446,11 @@ class ModuleFile : public LazyMemberLoader {
441446
std::unique_ptr<ModuleFile::SerializedObjCMethodTable>
442447
readObjCMethodTable(ArrayRef<uint64_t> fields, StringRef blobData);
443448

449+
/// Read an on-disk local decl hash table stored in
450+
/// index_block::NestedTypeDeclsLayout format.
451+
std::unique_ptr<SerializedNestedTypeDeclsTable>
452+
readNestedTypeDeclsTable(ArrayRef<uint64_t> fields, StringRef blobData);
453+
444454
/// Reads the index block, which contains global tables.
445455
///
446456
/// Returns false if there was an error.
@@ -580,6 +590,10 @@ class ModuleFile : public LazyMemberLoader {
580590
/// Searches the module's local type decls for the given mangled name.
581591
TypeDecl *lookupLocalType(StringRef MangledName);
582592

593+
/// Searches the module's nested type decls table for the given member of
594+
/// the given type.
595+
TypeDecl *lookupNestedType(Identifier name, const ValueDecl *parent);
596+
583597
/// Searches the module's operators for one with the given name and fixity.
584598
///
585599
/// If none is found, returns null.

include/swift/Serialization/ModuleFormat.h

Lines changed: 9 additions & 2 deletions
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 = 307; // Last change: layout requirements
57+
const uint16_t VERSION_MINOR = 308; // Last change: nested type table
5858

5959
using DeclID = PointerEmbeddedInt<unsigned, 31>;
6060
using DeclIDField = BCFixed<31>;
@@ -1463,7 +1463,8 @@ namespace index_block {
14631463
SIL_LAYOUT_OFFSETS,
14641464

14651465
PRECEDENCE_GROUPS,
1466-
1466+
NESTED_TYPE_DECLS,
1467+
14671468
LastRecordKind = PRECEDENCE_GROUPS,
14681469
};
14691470

@@ -1494,6 +1495,12 @@ namespace index_block {
14941495
BCBlob // map from Objective-C selectors to methods with that selector
14951496
>;
14961497

1498+
using NestedTypeDeclsLayout = BCRecordLayout<
1499+
NESTED_TYPE_DECLS, // record ID
1500+
BCVBR<16>, // table offset within the blob (see below)
1501+
BCBlob // map from identifier strings to decl kinds / decl IDs
1502+
>;
1503+
14971504
using EntryPointLayout = BCRecordLayout<
14981505
ENTRY_POINT,
14991506
DeclIDField // the ID of the main class; 0 if there was a main source file

include/swift/Serialization/SerializationOptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace swift {
3636
ArrayRef<std::string> ExtraClangOptions;
3737

3838
bool AutolinkForceLoad = false;
39+
bool EnableNestedTypeLookupTable = false;
3940
bool SerializeAllSIL = false;
4041
bool SerializeOptionsForDebugging = false;
4142
bool IsSIB = false;

include/swift/Serialization/SerializedModuleLoader.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ class SerializedModuleLoader : public ModuleLoader {
104104
class SerializedASTFile final : public LoadedFile {
105105
friend class SerializedModuleLoader;
106106
friend class SerializedSILLoader;
107+
friend class ModuleFile;
107108

108109
ModuleFile &File;
109110
bool IsSIB;

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,8 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args,
767767
Opts.EnableSourceImport |= Args.hasArg(OPT_enable_source_import);
768768
Opts.ImportUnderlyingModule |= Args.hasArg(OPT_import_underlying_module);
769769
Opts.SILSerializeAll |= Args.hasArg(OPT_sil_serialize_all);
770+
Opts.EnableSerializationNestedTypeLookupTable &=
771+
!Args.hasArg(OPT_disable_serialization_nested_type_lookup_table);
770772

771773
if (const Arg *A = Args.getLastArgNoClaim(OPT_import_objc_header)) {
772774
Opts.ImplicitObjCHeaderPath = A->getValue();

lib/FrontendTool/FrontendTool.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,8 @@ static bool performCompile(std::unique_ptr<CompilerInstance> &Instance,
666666
serializationOpts.ModuleLinkName = opts.ModuleLinkName;
667667
serializationOpts.ExtraClangOptions =
668668
Invocation.getClangImporterOptions().ExtraArgs;
669+
serializationOpts.EnableNestedTypeLookupTable =
670+
opts.EnableSerializationNestedTypeLookupTable;
669671
if (!IRGenOpts.ForceLoadSymbolName.empty())
670672
serializationOpts.AutolinkForceLoad = true;
671673

lib/Serialization/Deserialization.cpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,17 @@
2323
#include "swift/ClangImporter/ClangImporter.h"
2424
#include "swift/Parse/Parser.h"
2525
#include "swift/Serialization/BCReadingExtras.h"
26+
#include "swift/Serialization/SerializedModuleLoader.h"
27+
#include "llvm/ADT/Statistic.h"
2628
#include "llvm/Support/raw_ostream.h"
2729

30+
#define DEBUG_TYPE "Serialization"
31+
32+
STATISTIC(NumMemberListsLoaded,
33+
"# of nominals/extensions whose members were loaded");
34+
STATISTIC(NumNestedTypeShortcuts,
35+
"# of same-module nested types resolved without lookup");
36+
2837
using namespace swift;
2938
using namespace swift::serialization;
3039

@@ -226,6 +235,10 @@ namespace {
226235
path.push_back({ PathPiece::Kind::Unknown, kind });
227236
}
228237

238+
void removeLast() {
239+
path.pop_back();
240+
}
241+
229242
void print(raw_ostream &os) const override {
230243
os << "Cross-reference to module '" << baseM.getName() << "'\n";
231244
for (auto &piece : path) {
@@ -1455,7 +1468,61 @@ Decl *ModuleFile::resolveCrossReference(ModuleDecl *M, uint32_t pathLen) {
14551468
unsigned recordID = DeclTypeCursor.readRecord(entry.ID, scratch,
14561469
&blobData);
14571470
switch (recordID) {
1458-
case XREF_TYPE_PATH_PIECE:
1471+
case XREF_TYPE_PATH_PIECE: {
1472+
if (values.size() == 1) {
1473+
ModuleDecl *module = values.front()->getModuleContext();
1474+
if (module == this->getAssociatedModule()) {
1475+
// Fast path for nested types in the same module.
1476+
IdentifierID IID;
1477+
bool onlyInNominal = false;
1478+
XRefTypePathPieceLayout::readRecord(scratch, IID, onlyInNominal);
1479+
Identifier memberName = getIdentifier(IID);
1480+
pathTrace.addValue(memberName);
1481+
1482+
llvm::PrettyStackTraceString message{
1483+
"If you're seeing a crash here, try passing "
1484+
"-Xfrontend -disable-serialization-nested-type-lookup-table"};
1485+
1486+
TypeDecl *nestedType = nullptr;
1487+
if (onlyInNominal) {
1488+
// Only look in the file containing the type itself.
1489+
const DeclContext *dc = values.front()->getDeclContext();
1490+
auto *serializedFile =
1491+
dyn_cast<SerializedASTFile>(dc->getModuleScopeContext());
1492+
if (serializedFile) {
1493+
nestedType =
1494+
serializedFile->File.lookupNestedType(memberName,
1495+
values.front());
1496+
}
1497+
} else {
1498+
// Fault in extensions, then ask every serialized AST in the module.
1499+
(void)cast<NominalTypeDecl>(values.front())->getExtensions();
1500+
for (FileUnit *file : module->getFiles()) {
1501+
if (file == getFile())
1502+
continue;
1503+
auto *serializedFile = dyn_cast<SerializedASTFile>(file);
1504+
if (!serializedFile)
1505+
continue;
1506+
nestedType =
1507+
serializedFile->File.lookupNestedType(memberName,
1508+
values.front());
1509+
if (nestedType)
1510+
break;
1511+
}
1512+
}
1513+
1514+
if (nestedType) {
1515+
values.clear();
1516+
values.push_back(nestedType);
1517+
++NumNestedTypeShortcuts;
1518+
break;
1519+
}
1520+
1521+
pathTrace.removeLast();
1522+
}
1523+
}
1524+
SWIFT_FALLTHROUGH;
1525+
}
14591526
case XREF_VALUE_PATH_PIECE:
14601527
case XREF_INITIALIZER_PATH_PIECE: {
14611528
TypeID TID = 0;
@@ -4254,6 +4321,7 @@ Type ModuleFile::getType(TypeID TID) {
42544321

42554322
void ModuleFile::loadAllMembers(Decl *D, uint64_t contextData) {
42564323
PrettyStackTraceDecl trace("loading members for", D);
4324+
++NumMemberListsLoaded;
42574325

42584326
BCOffsetRAII restoreOffset(DeclTypeCursor);
42594327
DeclTypeCursor.JumpToBit(contextData);

lib/Serialization/ModuleFile.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,50 @@ class ModuleFile::LocalDeclTableInfo {
377377
}
378378
};
379379

380+
class ModuleFile::NestedTypeDeclsTableInfo {
381+
public:
382+
using internal_key_type = StringRef;
383+
using external_key_type = Identifier;
384+
using data_type = SmallVector<std::pair<DeclID, DeclID>, 4>;
385+
using hash_value_type = uint32_t;
386+
using offset_type = unsigned;
387+
388+
internal_key_type GetInternalKey(external_key_type ID) {
389+
return ID.str();
390+
}
391+
392+
hash_value_type ComputeHash(internal_key_type key) {
393+
return llvm::HashString(key);
394+
}
395+
396+
static bool EqualKey(internal_key_type lhs, internal_key_type rhs) {
397+
return lhs == rhs;
398+
}
399+
400+
static std::pair<unsigned, unsigned> ReadKeyDataLength(const uint8_t *&data) {
401+
unsigned keyLength = endian::readNext<uint16_t, little, unaligned>(data);
402+
unsigned dataLength = endian::readNext<uint16_t, little, unaligned>(data);
403+
return { keyLength, dataLength };
404+
}
405+
406+
static internal_key_type ReadKey(const uint8_t *data, unsigned length) {
407+
return StringRef(reinterpret_cast<const char *>(data), length);
408+
}
409+
410+
static data_type ReadData(internal_key_type key, const uint8_t *data,
411+
unsigned length) {
412+
data_type result;
413+
while (length > 0) {
414+
DeclID parentID = endian::readNext<uint32_t, little, unaligned>(data);
415+
DeclID childID = endian::readNext<uint32_t, little, unaligned>(data);
416+
result.push_back({ parentID, childID });
417+
length -= sizeof(uint32_t) * 2;
418+
}
419+
420+
return result;
421+
}
422+
};
423+
380424
std::unique_ptr<ModuleFile::SerializedDeclTable>
381425
ModuleFile::readDeclTable(ArrayRef<uint64_t> fields, StringRef blobData) {
382426
uint32_t tableOffset;
@@ -399,6 +443,18 @@ ModuleFile::readLocalDeclTable(ArrayRef<uint64_t> fields, StringRef blobData) {
399443
base + sizeof(uint32_t), base));
400444
}
401445

446+
std::unique_ptr<ModuleFile::SerializedNestedTypeDeclsTable>
447+
ModuleFile::readNestedTypeDeclsTable(ArrayRef<uint64_t> fields,
448+
StringRef blobData) {
449+
uint32_t tableOffset;
450+
index_block::NestedTypeDeclsLayout::readRecord(fields, tableOffset);
451+
auto base = reinterpret_cast<const uint8_t *>(blobData.data());
452+
453+
using OwnedTable = std::unique_ptr<SerializedNestedTypeDeclsTable>;
454+
return OwnedTable(SerializedNestedTypeDeclsTable::Create(base + tableOffset,
455+
base + sizeof(uint32_t), base));
456+
}
457+
402458
/// Used to deserialize entries in the on-disk Objective-C method table.
403459
class ModuleFile::ObjCMethodTableInfo {
404460
public:
@@ -531,6 +587,9 @@ bool ModuleFile::readIndexBlock(llvm::BitstreamCursor &cursor) {
531587
case index_block::LOCAL_TYPE_DECLS:
532588
LocalTypeDecls = readLocalDeclTable(scratch, blobData);
533589
break;
590+
case index_block::NESTED_TYPE_DECLS:
591+
NestedTypeDecls = readNestedTypeDeclsTable(scratch, blobData);
592+
break;
534593
case index_block::LOCAL_DECL_CONTEXT_OFFSETS:
535594
assert(blobData.empty());
536595
LocalDeclContexts.assign(scratch.begin(), scratch.end());
@@ -1194,6 +1253,31 @@ TypeDecl *ModuleFile::lookupLocalType(StringRef MangledName) {
11941253
return cast<TypeDecl>(getDecl((*iter).first));
11951254
}
11961255

1256+
TypeDecl *ModuleFile::lookupNestedType(Identifier name,
1257+
const ValueDecl *parent) {
1258+
PrettyModuleFileDeserialization stackEntry(*this);
1259+
1260+
if (!NestedTypeDecls)
1261+
return nullptr;
1262+
1263+
auto iter = NestedTypeDecls->find(name);
1264+
if (iter == NestedTypeDecls->end())
1265+
return nullptr;
1266+
1267+
auto data = *iter;
1268+
for (std::pair<DeclID, DeclID> entry : data) {
1269+
assert(entry.first);
1270+
auto declOrOffset = Decls[entry.first - 1];
1271+
if (!declOrOffset.isComplete())
1272+
continue;
1273+
if (declOrOffset != parent)
1274+
continue;
1275+
return cast<TypeDecl>(getDecl(entry.second));
1276+
}
1277+
1278+
return nullptr;
1279+
}
1280+
11971281
OperatorDecl *ModuleFile::lookupOperator(Identifier name, DeclKind fixity) {
11981282
PrettyModuleFileDeserialization stackEntry(*this);
11991283

0 commit comments

Comments
 (0)