Skip to content

Commit

Permalink
[ThinLTO] Efficiency fix for writing type id records in per-module in…
Browse files Browse the repository at this point in the history
…dexes

Summary:
In D49565/r337503, the type id record writing was fixed so that only
referenced type ids were emitted into each per-module index for ThinLTO
distributed builds. However, this still left an efficiency issue: each
per-module index checked all type ids for membership in the referenced
set, yielding O(M*N) performance (M indexes and N type ids).

Change the TypeIdMap in the summary to be indexed by GUID, to facilitate
correlating with type identifier GUIDs referenced in the function
summary TypeIdInfo structures. This allowed simplifying other
places where a map from type id GUID to type id map entry was previously
being used to aid this correlation.

Also fix AsmWriter code to handle the rare case of type id GUID
collision.

For a large internal application, this reduced the thin link time by
almost 15%.

Reviewers: pcc, vitalybuka

Subscribers: mehdi_amini, inglorion, steven_wu, dexonsmith, llvm-commits

Differential Revision: https://reviews.llvm.org/D51330

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@343021 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
teresajohnson committed Sep 25, 2018
1 parent a7f65b7 commit 0d4dfd2
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 84 deletions.
38 changes: 25 additions & 13 deletions include/llvm/IR/ModuleSummaryIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Allocator.h"
Expand Down Expand Up @@ -753,6 +754,11 @@ using ModulePathStringTableTy = StringMap<std::pair<uint64_t, ModuleHash>>;
/// a particular module, and provide efficient access to their summary.
using GVSummaryMapTy = DenseMap<GlobalValue::GUID, GlobalValueSummary *>;

/// Map of a type GUID to type id string and summary (multimap used
/// in case of GUID conflicts).
using TypeIdSummaryMapTy =
std::multimap<GlobalValue::GUID, std::pair<std::string, TypeIdSummary>>;

/// Class to hold module path string table and global value map,
/// and encapsulate methods for operating on them.
class ModuleSummaryIndex {
Expand All @@ -764,9 +770,9 @@ class ModuleSummaryIndex {
/// Holds strings for combined index, mapping to the corresponding module ID.
ModulePathStringTableTy ModulePathStringTable;

/// Mapping from type identifiers to summary information for that type
/// identifier.
std::map<std::string, TypeIdSummary> TypeIdMap;
/// Mapping from type identifier GUIDs to type identifier and its summary
/// information.
TypeIdSummaryMapTy TypeIdMap;

/// Mapping from original ID to GUID. If original ID can map to multiple
/// GUIDs, it will be mapped to 0.
Expand Down Expand Up @@ -1079,23 +1085,29 @@ class ModuleSummaryIndex {
return ModulePathStringTable.count(M.getModuleIdentifier());
}

const std::map<std::string, TypeIdSummary> &typeIds() const {
return TypeIdMap;
}
const TypeIdSummaryMapTy &typeIds() const { return TypeIdMap; }

/// This accessor should only be used when exporting because it can mutate the
/// map.
/// Return an existing or new TypeIdSummary entry for \p TypeId.
/// This accessor can mutate the map and therefore should not be used in
/// the ThinLTO backends.
TypeIdSummary &getOrInsertTypeIdSummary(StringRef TypeId) {
return TypeIdMap[TypeId];
auto TidIter = TypeIdMap.equal_range(GlobalValue::getGUID(TypeId));
for (auto It = TidIter.first; It != TidIter.second; ++It)
if (It->second.first == TypeId)
return It->second.second;
auto It = TypeIdMap.insert(
{GlobalValue::getGUID(TypeId), {TypeId, TypeIdSummary()}});
return It->second.second;
}

/// This returns either a pointer to the type id summary (if present in the
/// summary map) or null (if not present). This may be used when importing.
const TypeIdSummary *getTypeIdSummary(StringRef TypeId) const {
auto I = TypeIdMap.find(TypeId);
if (I == TypeIdMap.end())
return nullptr;
return &I->second;
auto TidIter = TypeIdMap.equal_range(GlobalValue::getGUID(TypeId));
for (auto It = TidIter.first; It != TidIter.second; ++It)
if (It->second.first == TypeId)
return &It->second.second;
return nullptr;
}

/// Collect for the given module the list of functions it defines
Expand Down
13 changes: 12 additions & 1 deletion include/llvm/IR/ModuleSummaryIndexYAML.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ template <> struct MappingTraits<FunctionSummaryYaml> {
} // End yaml namespace
} // End llvm namespace

LLVM_YAML_IS_STRING_MAP(TypeIdSummary)
LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionSummaryYaml)

namespace llvm {
Expand Down Expand Up @@ -258,6 +257,18 @@ template <> struct CustomMappingTraits<GlobalValueSummaryMapTy> {
}
};

template <> struct CustomMappingTraits<TypeIdSummaryMapTy> {
static void inputOne(IO &io, StringRef Key, TypeIdSummaryMapTy &V) {
TypeIdSummary TId;
io.mapRequired(Key.str().c_str(), TId);
V.insert({GlobalValue::getGUID(Key), {Key, TId}});
}
static void output(IO &io, TypeIdSummaryMapTy &V) {
for (auto TidIter = V.begin(); TidIter != V.end(); TidIter++)
io.mapRequired(TidIter->second.first.c_str(), TidIter->second.second);
}
};

template <> struct MappingTraits<ModuleSummaryIndex> {
static void mapping(IO &io, ModuleSummaryIndex& index) {
io.mapOptional("GlobalValueMap", index.GlobalValueMap);
Expand Down
13 changes: 7 additions & 6 deletions lib/Bitcode/Writer/BitcodeWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3903,12 +3903,13 @@ void IndexBitcodeWriter::writeCombinedGlobalValueSummary() {
NameVals.clear();
}

if (!Index.typeIds().empty()) {
for (auto &S : Index.typeIds()) {
// Skip if not referenced in any GV summary within this index file.
if (!ReferencedTypeIds.count(GlobalValue::getGUID(S.first)))
continue;
writeTypeIdSummaryRecord(NameVals, StrtabBuilder, S.first, S.second);
// Walk the GUIDs that were referenced, and write the
// corresponding type id records.
for (auto &T : ReferencedTypeIds) {
auto TidIter = Index.typeIds().equal_range(T);
for (auto It = TidIter.first; It != TidIter.second; ++It) {
writeTypeIdSummaryRecord(NameVals, StrtabBuilder, It->second.first,
It->second.second);
Stream.EmitRecord(bitc::FS_TYPE_ID, NameVals);
NameVals.clear();
}
Expand Down
82 changes: 62 additions & 20 deletions lib/IR/AsmWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,10 @@ class SlotTracker {
DenseMap<GlobalValue::GUID, unsigned> GUIDMap;
unsigned GUIDNext = 0;

/// TypeIdMap - The slot map for type ids used in the summary index.
StringMap<unsigned> TypeIdMap;
unsigned TypeIdNext = 0;

public:
/// Construct from a module.
///
Expand Down Expand Up @@ -736,6 +740,7 @@ class SlotTracker {
int getAttributeGroupSlot(AttributeSet AS);
int getModulePathSlot(StringRef Path);
int getGUIDSlot(GlobalValue::GUID GUID);
int getTypeIdSlot(StringRef Id);

/// If you'd like to deal with a function instead of just a module, use
/// this method to get its data into the SlotTracker.
Expand Down Expand Up @@ -790,6 +795,7 @@ class SlotTracker {

inline void CreateModulePathSlot(StringRef Path);
void CreateGUIDSlot(GlobalValue::GUID GUID);
void CreateTypeIdSlot(StringRef Id);

/// Add all of the module level global variables (and their initializers)
/// and function declarations, but not the contents of those functions.
Expand Down Expand Up @@ -1026,8 +1032,12 @@ void SlotTracker::processIndex() {
for (auto &GlobalList : *TheIndex)
CreateGUIDSlot(GlobalList.first);

for (auto &TId : TheIndex->typeIds())
CreateGUIDSlot(GlobalValue::getGUID(TId.first));
// Start numbering the TypeIds after the GUIDs.
TypeIdNext = GUIDNext;

for (auto TidIter = TheIndex->typeIds().begin();
TidIter != TheIndex->typeIds().end(); TidIter++)
CreateTypeIdSlot(TidIter->second.first);

ST_DEBUG("end processIndex!\n");
}
Expand Down Expand Up @@ -1133,6 +1143,15 @@ int SlotTracker::getGUIDSlot(GlobalValue::GUID GUID) {
return I == GUIDMap.end() ? -1 : (int)I->second;
}

int SlotTracker::getTypeIdSlot(StringRef Id) {
// Check for uninitialized state and do lazy initialization.
initializeIndexIfNeeded();

// Find the TypeId string in the map
auto I = TypeIdMap.find(Id);
return I == TypeIdMap.end() ? -1 : (int)I->second;
}

/// CreateModuleSlot - Insert the specified GlobalValue* into the slot table.
void SlotTracker::CreateModuleSlot(const GlobalValue *V) {
assert(V && "Can't insert a null Value into SlotTracker!");
Expand Down Expand Up @@ -1203,6 +1222,11 @@ void SlotTracker::CreateGUIDSlot(GlobalValue::GUID GUID) {
GUIDMap[GUID] = GUIDNext++;
}

/// Create a new slot for the specified Id
void SlotTracker::CreateTypeIdSlot(StringRef Id) {
TypeIdMap[Id] = TypeIdNext++;
}

//===----------------------------------------------------------------------===//
// AsmWriter Implementation
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -2656,12 +2680,12 @@ void AssemblyWriter::printModuleSummaryIndex() {
}

// Print the TypeIdMap entries.
for (auto &TId : TheIndex->typeIds()) {
auto GUID = GlobalValue::getGUID(TId.first);
Out << "^" << Machine.getGUIDSlot(GUID) << " = typeid: (name: \""
<< TId.first << "\"";
printTypeIdSummary(TId.second);
Out << ") ; guid = " << GUID << "\n";
for (auto TidIter = TheIndex->typeIds().begin();
TidIter != TheIndex->typeIds().end(); TidIter++) {
Out << "^" << Machine.getTypeIdSlot(TidIter->second.first)
<< " = typeid: (name: \"" << TidIter->second.first << "\"";
printTypeIdSummary(TidIter->second.second);
Out << ") ; guid = " << TidIter->first << "\n";
}
}

Expand Down Expand Up @@ -2894,12 +2918,19 @@ void AssemblyWriter::printTypeIdInfo(
Out << "typeTests: (";
FieldSeparator FS;
for (auto &GUID : TIDInfo.TypeTests) {
Out << FS;
auto Slot = Machine.getGUIDSlot(GUID);
if (Slot != -1)
Out << "^" << Slot;
else
auto TidIter = TheIndex->typeIds().equal_range(GUID);
if (TidIter.first == TidIter.second) {
Out << FS;
Out << GUID;
continue;
}
// Print all type id that correspond to this GUID.
for (auto It = TidIter.first; It != TidIter.second; ++It) {
Out << FS;
auto Slot = Machine.getTypeIdSlot(It->second.first);
assert(Slot != -1);
Out << "^" << Slot;
}
}
Out << ")";
}
Expand All @@ -2925,14 +2956,25 @@ void AssemblyWriter::printTypeIdInfo(
}

void AssemblyWriter::printVFuncId(const FunctionSummary::VFuncId VFId) {
Out << "vFuncId: (";
auto Slot = Machine.getGUIDSlot(VFId.GUID);
if (Slot != -1)
Out << "^" << Slot;
else
auto TidIter = TheIndex->typeIds().equal_range(VFId.GUID);
if (TidIter.first == TidIter.second) {
Out << "vFuncId: (";
Out << "guid: " << VFId.GUID;
Out << ", offset: " << VFId.Offset;
Out << ")";
Out << ", offset: " << VFId.Offset;
Out << ")";
return;
}
// Print all type id that correspond to this GUID.
FieldSeparator FS;
for (auto It = TidIter.first; It != TidIter.second; ++It) {
Out << FS;
Out << "vFuncId: (";
auto Slot = Machine.getTypeIdSlot(It->second.first);
assert(Slot != -1);
Out << "^" << Slot;
Out << ", offset: " << VFId.Offset;
Out << ")";
}
}

void AssemblyWriter::printNonConstVCalls(
Expand Down
36 changes: 9 additions & 27 deletions lib/LTO/LTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ static cl::opt<bool>
DumpThinCGSCCs("dump-thin-cg-sccs", cl::init(false), cl::Hidden,
cl::desc("Dump the SCCs in the ThinLTO index's callgraph"));

// The values are (type identifier, summary) pairs.
typedef DenseMap<
GlobalValue::GUID,
TinyPtrVector<const std::pair<const std::string, TypeIdSummary> *>>
TypeIdSummariesByGuidTy;

// Returns a unique hash for the Module considering the current list of
// export/import and other global analysis results.
// The hash is produced in \p Key.
Expand All @@ -71,7 +65,6 @@ static void computeCacheKey(
const FunctionImporter::ExportSetTy &ExportList,
const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes> &ResolvedODR,
const GVSummaryMapTy &DefinedGlobals,
const TypeIdSummariesByGuidTy &TypeIdSummariesByGuid,
const std::set<GlobalValue::GUID> &CfiFunctionDefs,
const std::set<GlobalValue::GUID> &CfiFunctionDecls) {
// Compute the unique hash for this entry.
Expand Down Expand Up @@ -255,10 +248,9 @@ static void computeCacheKey(

// Include the hash for all type identifiers used by this module.
for (GlobalValue::GUID TId : UsedTypeIds) {
auto SummariesI = TypeIdSummariesByGuid.find(TId);
if (SummariesI != TypeIdSummariesByGuid.end())
for (auto *Summary : SummariesI->second)
AddTypeIdSummary(Summary->first, Summary->second);
auto TidIter = Index.typeIds().equal_range(TId);
for (auto It = TidIter.first; It != TidIter.second; ++It)
AddTypeIdSummary(It->second.first, It->second.second);
}

AddUnsigned(UsedCfiDefs.size());
Expand Down Expand Up @@ -917,7 +909,6 @@ class InProcessThinBackend : public ThinBackendProc {
ThreadPool BackendThreadPool;
AddStreamFn AddStream;
NativeObjectCache Cache;
TypeIdSummariesByGuidTy TypeIdSummariesByGuid;
std::set<GlobalValue::GUID> CfiFunctionDefs;
std::set<GlobalValue::GUID> CfiFunctionDecls;

Expand All @@ -933,12 +924,6 @@ class InProcessThinBackend : public ThinBackendProc {
: ThinBackendProc(Conf, CombinedIndex, ModuleToDefinedGVSummaries),
BackendThreadPool(ThinLTOParallelismLevel),
AddStream(std::move(AddStream)), Cache(std::move(Cache)) {
// Create a mapping from type identifier GUIDs to type identifier summaries.
// This allows backends to use the type identifier GUIDs stored in the
// function summaries to determine which type identifier summaries affect
// each function without needing to compute GUIDs in each backend.
for (auto &TId : CombinedIndex.typeIds())
TypeIdSummariesByGuid[GlobalValue::getGUID(TId.first)].push_back(&TId);
for (auto &Name : CombinedIndex.cfiFunctionDefs())
CfiFunctionDefs.insert(
GlobalValue::getGUID(GlobalValue::dropLLVMManglingEscape(Name)));
Expand All @@ -954,8 +939,7 @@ class InProcessThinBackend : public ThinBackendProc {
const FunctionImporter::ExportSetTy &ExportList,
const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes> &ResolvedODR,
const GVSummaryMapTy &DefinedGlobals,
MapVector<StringRef, BitcodeModule> &ModuleMap,
const TypeIdSummariesByGuidTy &TypeIdSummariesByGuid) {
MapVector<StringRef, BitcodeModule> &ModuleMap) {
auto RunThinBackend = [&](AddStreamFn AddStream) {
LTOLLVMContext BackendContext(Conf);
Expected<std::unique_ptr<Module>> MOrErr = BM.parseModule(BackendContext);
Expand All @@ -978,8 +962,8 @@ class InProcessThinBackend : public ThinBackendProc {
SmallString<40> Key;
// The module may be cached, this helps handling it.
computeCacheKey(Key, Conf, CombinedIndex, ModuleID, ImportList, ExportList,
ResolvedODR, DefinedGlobals, TypeIdSummariesByGuid,
CfiFunctionDefs, CfiFunctionDecls);
ResolvedODR, DefinedGlobals, CfiFunctionDefs,
CfiFunctionDecls);
if (AddStreamFn CacheAddStream = Cache(Task, Key))
return RunThinBackend(CacheAddStream);

Expand All @@ -1003,11 +987,10 @@ class InProcessThinBackend : public ThinBackendProc {
const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes>
&ResolvedODR,
const GVSummaryMapTy &DefinedGlobals,
MapVector<StringRef, BitcodeModule> &ModuleMap,
const TypeIdSummariesByGuidTy &TypeIdSummariesByGuid) {
MapVector<StringRef, BitcodeModule> &ModuleMap) {
Error E = runThinLTOBackendThread(
AddStream, Cache, Task, BM, CombinedIndex, ImportList, ExportList,
ResolvedODR, DefinedGlobals, ModuleMap, TypeIdSummariesByGuid);
ResolvedODR, DefinedGlobals, ModuleMap);
if (E) {
std::unique_lock<std::mutex> L(ErrMu);
if (Err)
Expand All @@ -1017,8 +1000,7 @@ class InProcessThinBackend : public ThinBackendProc {
}
},
BM, std::ref(CombinedIndex), std::ref(ImportList), std::ref(ExportList),
std::ref(ResolvedODR), std::ref(DefinedGlobals), std::ref(ModuleMap),
std::ref(TypeIdSummariesByGuid));
std::ref(ResolvedODR), std::ref(DefinedGlobals), std::ref(ModuleMap));
return Error::success();
}

Expand Down
12 changes: 6 additions & 6 deletions test/Assembler/thinlto-summary.ll
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@

; Test TypeId summaries:

; Test the AllOnes resolution, and all kinds of WholeProgramDevirtResolution
; types, including all optional resolution by argument kinds.
^24 = typeid: (name: "_ZTS1A", summary: (typeTestRes: (kind: allOnes, sizeM1BitWidth: 7), wpdResolutions: ((offset: 0, wpdRes: (kind: branchFunnel)), (offset: 8, wpdRes: (kind: singleImpl, singleImplName: "_ZN1A1nEi")), (offset: 16, wpdRes: (kind: indir, resByArg: (args: (1, 2), byArg: (kind: indir, byte: 2, bit: 3), args: (3), byArg: (kind: uniformRetVal, info: 1), args: (4), byArg: (kind: uniqueRetVal, info: 1), args: (5), byArg: (kind: virtualConstProp)))))))
^24 = typeid: (name: "_ZTS1C", summary: (typeTestRes: (kind: single, sizeM1BitWidth: 0)))
; Test TypeId with other optional fields (alignLog2/sizeM1/bitMask/inlineBits)
^25 = typeid: (name: "_ZTS1B", summary: (typeTestRes: (kind: inline, sizeM1BitWidth: 0, alignLog2: 1, sizeM1: 2, bitMask: 3, inlineBits: 4)))
; Test the AllOnes resolution, and all kinds of WholeProgramDevirtResolution
; types, including all optional resolution by argument kinds.
^26 = typeid: (name: "_ZTS1A", summary: (typeTestRes: (kind: allOnes, sizeM1BitWidth: 7), wpdResolutions: ((offset: 0, wpdRes: (kind: branchFunnel)), (offset: 8, wpdRes: (kind: singleImpl, singleImplName: "_ZN1A1nEi")), (offset: 16, wpdRes: (kind: indir, resByArg: (args: (1, 2), byArg: (kind: indir, byte: 2, bit: 3), args: (3), byArg: (kind: uniformRetVal, info: 1), args: (4), byArg: (kind: uniqueRetVal, info: 1), args: (5), byArg: (kind: virtualConstProp)))))))
; Test the other kinds of type test resoultions
^26 = typeid: (name: "_ZTS1C", summary: (typeTestRes: (kind: single, sizeM1BitWidth: 0)))
^27 = typeid: (name: "_ZTS1D", summary: (typeTestRes: (kind: byteArray, sizeM1BitWidth: 0)))
^28 = typeid: (name: "_ZTS1E", summary: (typeTestRes: (kind: unsat, sizeM1BitWidth: 0)))

Expand Down Expand Up @@ -89,8 +89,8 @@
; CHECK: ^21 = gv: (guid: 20, summaries: (function: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0), insts: 5, typeIdInfo: (typeCheckedLoadVCalls: (vFuncId: (^25, offset: 16))))))
; CHECK: ^22 = gv: (guid: 21, summaries: (function: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0), insts: 15, typeIdInfo: (typeTestAssumeConstVCalls: ((vFuncId: (^27, offset: 16), args: (42)), (vFuncId: (^27, offset: 24)))))))
; CHECK: ^23 = gv: (guid: 22, summaries: (function: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0), insts: 5, typeIdInfo: (typeCheckedLoadConstVCalls: ((vFuncId: (^28, offset: 16), args: (42)))))))
; CHECK: ^24 = typeid: (name: "_ZTS1A", summary: (typeTestRes: (kind: allOnes, sizeM1BitWidth: 7), wpdResolutions: ((offset: 0, wpdRes: (kind: branchFunnel)), (offset: 8, wpdRes: (kind: singleImpl, singleImplName: "_ZN1A1nEi")), (offset: 16, wpdRes: (kind: indir, resByArg: (args: (1, 2), byArg: (kind: indir, byte: 2, bit: 3), args: (3), byArg: (kind: uniformRetVal, info: 1), args: (4), byArg: (kind: uniqueRetVal, info: 1), args: (5), byArg: (kind: virtualConstProp))))))) ; guid = 7004155349499253778
; CHECK: ^24 = typeid: (name: "_ZTS1C", summary: (typeTestRes: (kind: single, sizeM1BitWidth: 0))) ; guid = 1884921850105019584
; CHECK: ^25 = typeid: (name: "_ZTS1B", summary: (typeTestRes: (kind: inline, sizeM1BitWidth: 0, alignLog2: 1, sizeM1: 2, bitMask: 3, inlineBits: 4))) ; guid = 6203814149063363976
; CHECK: ^26 = typeid: (name: "_ZTS1C", summary: (typeTestRes: (kind: single, sizeM1BitWidth: 0))) ; guid = 1884921850105019584
; CHECK: ^26 = typeid: (name: "_ZTS1A", summary: (typeTestRes: (kind: allOnes, sizeM1BitWidth: 7), wpdResolutions: ((offset: 0, wpdRes: (kind: branchFunnel)), (offset: 8, wpdRes: (kind: singleImpl, singleImplName: "_ZN1A1nEi")), (offset: 16, wpdRes: (kind: indir, resByArg: (args: (1, 2), byArg: (kind: indir, byte: 2, bit: 3), args: (3), byArg: (kind: uniformRetVal, info: 1), args: (4), byArg: (kind: uniqueRetVal, info: 1), args: (5), byArg: (kind: virtualConstProp))))))) ; guid = 7004155349499253778
; CHECK: ^27 = typeid: (name: "_ZTS1D", summary: (typeTestRes: (kind: byteArray, sizeM1BitWidth: 0))) ; guid = 9614786172484273522
; CHECK: ^28 = typeid: (name: "_ZTS1E", summary: (typeTestRes: (kind: unsat, sizeM1BitWidth: 0))) ; guid = 17437243864166745132
Loading

0 comments on commit 0d4dfd2

Please sign in to comment.