SampleProf.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. //=-- SampleProf.cpp - Sample profiling format support --------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // This file contains common definitions used in the reading and writing of
  10. // sample profile data.
  11. //
  12. //===----------------------------------------------------------------------===//
  13. #include "llvm/ProfileData/SampleProf.h"
  14. #include "llvm/Config/llvm-config.h"
  15. #include "llvm/IR/DebugInfoMetadata.h"
  16. #include "llvm/IR/PseudoProbe.h"
  17. #include "llvm/ProfileData/SampleProfReader.h"
  18. #include "llvm/Support/CommandLine.h"
  19. #include "llvm/Support/Compiler.h"
  20. #include "llvm/Support/Debug.h"
  21. #include "llvm/Support/Error.h"
  22. #include "llvm/Support/ErrorHandling.h"
  23. #include "llvm/Support/LEB128.h"
  24. #include "llvm/Support/ManagedStatic.h"
  25. #include "llvm/Support/raw_ostream.h"
  26. #include <string>
  27. #include <system_error>
  28. using namespace llvm;
  29. using namespace sampleprof;
  30. static cl::opt<uint64_t> ProfileSymbolListCutOff(
  31. "profile-symbol-list-cutoff", cl::Hidden, cl::init(-1), cl::ZeroOrMore,
  32. cl::desc("Cutoff value about how many symbols in profile symbol list "
  33. "will be used. This is very useful for performance debugging"));
  34. cl::opt<bool> GenerateMergedBaseProfiles(
  35. "generate-merged-base-profiles", cl::init(true), cl::ZeroOrMore,
  36. cl::desc("When generating nested context-sensitive profiles, always "
  37. "generate extra base profile for function with all its context "
  38. "profiles merged into it."));
  39. namespace llvm {
  40. namespace sampleprof {
  41. SampleProfileFormat FunctionSamples::Format;
  42. bool FunctionSamples::ProfileIsProbeBased = false;
  43. bool FunctionSamples::ProfileIsCSFlat = false;
  44. bool FunctionSamples::ProfileIsCSNested = false;
  45. bool FunctionSamples::UseMD5 = false;
  46. bool FunctionSamples::HasUniqSuffix = true;
  47. bool FunctionSamples::ProfileIsFS = false;
  48. } // namespace sampleprof
  49. } // namespace llvm
  50. namespace {
  51. // FIXME: This class is only here to support the transition to llvm::Error. It
  52. // will be removed once this transition is complete. Clients should prefer to
  53. // deal with the Error value directly, rather than converting to error_code.
  54. class SampleProfErrorCategoryType : public std::error_category {
  55. const char *name() const noexcept override { return "llvm.sampleprof"; }
  56. std::string message(int IE) const override {
  57. sampleprof_error E = static_cast<sampleprof_error>(IE);
  58. switch (E) {
  59. case sampleprof_error::success:
  60. return "Success";
  61. case sampleprof_error::bad_magic:
  62. return "Invalid sample profile data (bad magic)";
  63. case sampleprof_error::unsupported_version:
  64. return "Unsupported sample profile format version";
  65. case sampleprof_error::too_large:
  66. return "Too much profile data";
  67. case sampleprof_error::truncated:
  68. return "Truncated profile data";
  69. case sampleprof_error::malformed:
  70. return "Malformed sample profile data";
  71. case sampleprof_error::unrecognized_format:
  72. return "Unrecognized sample profile encoding format";
  73. case sampleprof_error::unsupported_writing_format:
  74. return "Profile encoding format unsupported for writing operations";
  75. case sampleprof_error::truncated_name_table:
  76. return "Truncated function name table";
  77. case sampleprof_error::not_implemented:
  78. return "Unimplemented feature";
  79. case sampleprof_error::counter_overflow:
  80. return "Counter overflow";
  81. case sampleprof_error::ostream_seek_unsupported:
  82. return "Ostream does not support seek";
  83. case sampleprof_error::compress_failed:
  84. return "Compress failure";
  85. case sampleprof_error::uncompress_failed:
  86. return "Uncompress failure";
  87. case sampleprof_error::zlib_unavailable:
  88. return "Zlib is unavailable";
  89. case sampleprof_error::hash_mismatch:
  90. return "Function hash mismatch";
  91. }
  92. llvm_unreachable("A value of sampleprof_error has no message.");
  93. }
  94. };
  95. } // end anonymous namespace
  96. static ManagedStatic<SampleProfErrorCategoryType> ErrorCategory;
  97. const std::error_category &llvm::sampleprof_category() {
  98. return *ErrorCategory;
  99. }
  100. void LineLocation::print(raw_ostream &OS) const {
  101. OS << LineOffset;
  102. if (Discriminator > 0)
  103. OS << "." << Discriminator;
  104. }
  105. raw_ostream &llvm::sampleprof::operator<<(raw_ostream &OS,
  106. const LineLocation &Loc) {
  107. Loc.print(OS);
  108. return OS;
  109. }
  110. /// Merge the samples in \p Other into this record.
  111. /// Optionally scale sample counts by \p Weight.
  112. sampleprof_error SampleRecord::merge(const SampleRecord &Other,
  113. uint64_t Weight) {
  114. sampleprof_error Result;
  115. Result = addSamples(Other.getSamples(), Weight);
  116. for (const auto &I : Other.getCallTargets()) {
  117. MergeResult(Result, addCalledTarget(I.first(), I.second, Weight));
  118. }
  119. return Result;
  120. }
  121. #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  122. LLVM_DUMP_METHOD void LineLocation::dump() const { print(dbgs()); }
  123. #endif
  124. /// Print the sample record to the stream \p OS indented by \p Indent.
  125. void SampleRecord::print(raw_ostream &OS, unsigned Indent) const {
  126. OS << NumSamples;
  127. if (hasCalls()) {
  128. OS << ", calls:";
  129. for (const auto &I : getSortedCallTargets())
  130. OS << " " << I.first << ":" << I.second;
  131. }
  132. OS << "\n";
  133. }
  134. #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  135. LLVM_DUMP_METHOD void SampleRecord::dump() const { print(dbgs(), 0); }
  136. #endif
  137. raw_ostream &llvm::sampleprof::operator<<(raw_ostream &OS,
  138. const SampleRecord &Sample) {
  139. Sample.print(OS, 0);
  140. return OS;
  141. }
  142. /// Print the samples collected for a function on stream \p OS.
  143. void FunctionSamples::print(raw_ostream &OS, unsigned Indent) const {
  144. if (getFunctionHash())
  145. OS << "CFG checksum " << getFunctionHash() << "\n";
  146. OS << TotalSamples << ", " << TotalHeadSamples << ", " << BodySamples.size()
  147. << " sampled lines\n";
  148. OS.indent(Indent);
  149. if (!BodySamples.empty()) {
  150. OS << "Samples collected in the function's body {\n";
  151. SampleSorter<LineLocation, SampleRecord> SortedBodySamples(BodySamples);
  152. for (const auto &SI : SortedBodySamples.get()) {
  153. OS.indent(Indent + 2);
  154. OS << SI->first << ": " << SI->second;
  155. }
  156. OS.indent(Indent);
  157. OS << "}\n";
  158. } else {
  159. OS << "No samples collected in the function's body\n";
  160. }
  161. OS.indent(Indent);
  162. if (!CallsiteSamples.empty()) {
  163. OS << "Samples collected in inlined callsites {\n";
  164. SampleSorter<LineLocation, FunctionSamplesMap> SortedCallsiteSamples(
  165. CallsiteSamples);
  166. for (const auto &CS : SortedCallsiteSamples.get()) {
  167. for (const auto &FS : CS->second) {
  168. OS.indent(Indent + 2);
  169. OS << CS->first << ": inlined callee: " << FS.second.getName() << ": ";
  170. FS.second.print(OS, Indent + 4);
  171. }
  172. }
  173. OS.indent(Indent);
  174. OS << "}\n";
  175. } else {
  176. OS << "No inlined callsites in this function\n";
  177. }
  178. }
  179. raw_ostream &llvm::sampleprof::operator<<(raw_ostream &OS,
  180. const FunctionSamples &FS) {
  181. FS.print(OS);
  182. return OS;
  183. }
  184. void sampleprof::sortFuncProfiles(
  185. const SampleProfileMap &ProfileMap,
  186. std::vector<NameFunctionSamples> &SortedProfiles) {
  187. for (const auto &I : ProfileMap) {
  188. assert(I.first == I.second.getContext() && "Inconsistent profile map");
  189. SortedProfiles.push_back(std::make_pair(I.second.getContext(), &I.second));
  190. }
  191. llvm::stable_sort(SortedProfiles, [](const NameFunctionSamples &A,
  192. const NameFunctionSamples &B) {
  193. if (A.second->getTotalSamples() == B.second->getTotalSamples())
  194. return A.first < B.first;
  195. return A.second->getTotalSamples() > B.second->getTotalSamples();
  196. });
  197. }
  198. unsigned FunctionSamples::getOffset(const DILocation *DIL) {
  199. return (DIL->getLine() - DIL->getScope()->getSubprogram()->getLine()) &
  200. 0xffff;
  201. }
  202. LineLocation FunctionSamples::getCallSiteIdentifier(const DILocation *DIL,
  203. bool ProfileIsFS) {
  204. if (FunctionSamples::ProfileIsProbeBased) {
  205. // In a pseudo-probe based profile, a callsite is simply represented by the
  206. // ID of the probe associated with the call instruction. The probe ID is
  207. // encoded in the Discriminator field of the call instruction's debug
  208. // metadata.
  209. return LineLocation(PseudoProbeDwarfDiscriminator::extractProbeIndex(
  210. DIL->getDiscriminator()),
  211. 0);
  212. } else {
  213. unsigned Discriminator =
  214. ProfileIsFS ? DIL->getDiscriminator() : DIL->getBaseDiscriminator();
  215. return LineLocation(FunctionSamples::getOffset(DIL), Discriminator);
  216. }
  217. }
  218. uint64_t FunctionSamples::getCallSiteHash(StringRef CalleeName,
  219. const LineLocation &Callsite) {
  220. uint64_t NameHash = std::hash<std::string>{}(CalleeName.str());
  221. uint64_t LocId =
  222. (((uint64_t)Callsite.LineOffset) << 32) | Callsite.Discriminator;
  223. return NameHash + (LocId << 5) + LocId;
  224. }
  225. const FunctionSamples *FunctionSamples::findFunctionSamples(
  226. const DILocation *DIL, SampleProfileReaderItaniumRemapper *Remapper) const {
  227. assert(DIL);
  228. SmallVector<std::pair<LineLocation, StringRef>, 10> S;
  229. const DILocation *PrevDIL = DIL;
  230. for (DIL = DIL->getInlinedAt(); DIL; DIL = DIL->getInlinedAt()) {
  231. // Use C++ linkage name if possible.
  232. StringRef Name = PrevDIL->getScope()->getSubprogram()->getLinkageName();
  233. if (Name.empty())
  234. Name = PrevDIL->getScope()->getSubprogram()->getName();
  235. S.emplace_back(FunctionSamples::getCallSiteIdentifier(
  236. DIL, FunctionSamples::ProfileIsFS),
  237. Name);
  238. PrevDIL = DIL;
  239. }
  240. if (S.size() == 0)
  241. return this;
  242. const FunctionSamples *FS = this;
  243. for (int i = S.size() - 1; i >= 0 && FS != nullptr; i--) {
  244. FS = FS->findFunctionSamplesAt(S[i].first, S[i].second, Remapper);
  245. }
  246. return FS;
  247. }
  248. void FunctionSamples::findAllNames(DenseSet<StringRef> &NameSet) const {
  249. NameSet.insert(getName());
  250. for (const auto &BS : BodySamples)
  251. for (const auto &TS : BS.second.getCallTargets())
  252. NameSet.insert(TS.getKey());
  253. for (const auto &CS : CallsiteSamples) {
  254. for (const auto &NameFS : CS.second) {
  255. NameSet.insert(NameFS.first);
  256. NameFS.second.findAllNames(NameSet);
  257. }
  258. }
  259. }
  260. const FunctionSamples *FunctionSamples::findFunctionSamplesAt(
  261. const LineLocation &Loc, StringRef CalleeName,
  262. SampleProfileReaderItaniumRemapper *Remapper) const {
  263. CalleeName = getCanonicalFnName(CalleeName);
  264. std::string CalleeGUID;
  265. CalleeName = getRepInFormat(CalleeName, UseMD5, CalleeGUID);
  266. auto iter = CallsiteSamples.find(Loc);
  267. if (iter == CallsiteSamples.end())
  268. return nullptr;
  269. auto FS = iter->second.find(CalleeName);
  270. if (FS != iter->second.end())
  271. return &FS->second;
  272. if (Remapper) {
  273. if (auto NameInProfile = Remapper->lookUpNameInProfile(CalleeName)) {
  274. auto FS = iter->second.find(*NameInProfile);
  275. if (FS != iter->second.end())
  276. return &FS->second;
  277. }
  278. }
  279. // If we cannot find exact match of the callee name, return the FS with
  280. // the max total count. Only do this when CalleeName is not provided,
  281. // i.e., only for indirect calls.
  282. if (!CalleeName.empty())
  283. return nullptr;
  284. uint64_t MaxTotalSamples = 0;
  285. const FunctionSamples *R = nullptr;
  286. for (const auto &NameFS : iter->second)
  287. if (NameFS.second.getTotalSamples() >= MaxTotalSamples) {
  288. MaxTotalSamples = NameFS.second.getTotalSamples();
  289. R = &NameFS.second;
  290. }
  291. return R;
  292. }
  293. #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  294. LLVM_DUMP_METHOD void FunctionSamples::dump() const { print(dbgs(), 0); }
  295. #endif
  296. std::error_code ProfileSymbolList::read(const uint8_t *Data,
  297. uint64_t ListSize) {
  298. const char *ListStart = reinterpret_cast<const char *>(Data);
  299. uint64_t Size = 0;
  300. uint64_t StrNum = 0;
  301. while (Size < ListSize && StrNum < ProfileSymbolListCutOff) {
  302. StringRef Str(ListStart + Size);
  303. add(Str);
  304. Size += Str.size() + 1;
  305. StrNum++;
  306. }
  307. if (Size != ListSize && StrNum != ProfileSymbolListCutOff)
  308. return sampleprof_error::malformed;
  309. return sampleprof_error::success;
  310. }
  311. void SampleContextTrimmer::trimAndMergeColdContextProfiles(
  312. uint64_t ColdCountThreshold, bool TrimColdContext, bool MergeColdContext,
  313. uint32_t ColdContextFrameLength, bool TrimBaseProfileOnly) {
  314. if (!TrimColdContext && !MergeColdContext)
  315. return;
  316. // Nothing to merge if sample threshold is zero
  317. if (ColdCountThreshold == 0)
  318. return;
  319. // Trimming base profiles only is mainly to honor the preinliner decsion. When
  320. // MergeColdContext is true preinliner decsion is not honored anyway so turn
  321. // off TrimBaseProfileOnly.
  322. if (MergeColdContext)
  323. TrimBaseProfileOnly = false;
  324. // Filter the cold profiles from ProfileMap and move them into a tmp
  325. // container
  326. std::vector<std::pair<SampleContext, const FunctionSamples *>> ColdProfiles;
  327. for (const auto &I : ProfileMap) {
  328. const SampleContext &Context = I.first;
  329. const FunctionSamples &FunctionProfile = I.second;
  330. if (FunctionProfile.getTotalSamples() < ColdCountThreshold &&
  331. (!TrimBaseProfileOnly || Context.isBaseContext()))
  332. ColdProfiles.emplace_back(Context, &I.second);
  333. }
  334. // Remove the cold profile from ProfileMap and merge them into
  335. // MergedProfileMap by the last K frames of context
  336. SampleProfileMap MergedProfileMap;
  337. for (const auto &I : ColdProfiles) {
  338. if (MergeColdContext) {
  339. auto MergedContext = I.second->getContext().getContextFrames();
  340. if (ColdContextFrameLength < MergedContext.size())
  341. MergedContext = MergedContext.take_back(ColdContextFrameLength);
  342. auto Ret = MergedProfileMap.emplace(MergedContext, FunctionSamples());
  343. FunctionSamples &MergedProfile = Ret.first->second;
  344. MergedProfile.merge(*I.second);
  345. }
  346. ProfileMap.erase(I.first);
  347. }
  348. // Move the merged profiles into ProfileMap;
  349. for (const auto &I : MergedProfileMap) {
  350. // Filter the cold merged profile
  351. if (TrimColdContext && I.second.getTotalSamples() < ColdCountThreshold &&
  352. ProfileMap.find(I.first) == ProfileMap.end())
  353. continue;
  354. // Merge the profile if the original profile exists, otherwise just insert
  355. // as a new profile
  356. auto Ret = ProfileMap.emplace(I.first, FunctionSamples());
  357. if (Ret.second) {
  358. SampleContext FContext(Ret.first->first, RawContext);
  359. FunctionSamples &FProfile = Ret.first->second;
  360. FProfile.setContext(FContext);
  361. }
  362. FunctionSamples &OrigProfile = Ret.first->second;
  363. OrigProfile.merge(I.second);
  364. }
  365. }
  366. void SampleContextTrimmer::canonicalizeContextProfiles() {
  367. std::vector<SampleContext> ProfilesToBeRemoved;
  368. SampleProfileMap ProfilesToBeAdded;
  369. for (auto &I : ProfileMap) {
  370. FunctionSamples &FProfile = I.second;
  371. SampleContext &Context = FProfile.getContext();
  372. if (I.first == Context)
  373. continue;
  374. // Use the context string from FunctionSamples to update the keys of
  375. // ProfileMap. They can get out of sync after context profile promotion
  376. // through pre-inliner.
  377. // Duplicate the function profile for later insertion to avoid a conflict
  378. // caused by a context both to be add and to be removed. This could happen
  379. // when a context is promoted to another context which is also promoted to
  380. // the third context. For example, given an original context A @ B @ C that
  381. // is promoted to B @ C and the original context B @ C which is promoted to
  382. // just C, adding B @ C to the profile map while removing same context (but
  383. // with different profiles) from the map can cause a conflict if they are
  384. // not handled in a right order. This can be solved by just caching the
  385. // profiles to be added.
  386. auto Ret = ProfilesToBeAdded.emplace(Context, FProfile);
  387. (void)Ret;
  388. assert(Ret.second && "Context conflict during canonicalization");
  389. ProfilesToBeRemoved.push_back(I.first);
  390. }
  391. for (auto &I : ProfilesToBeRemoved) {
  392. ProfileMap.erase(I);
  393. }
  394. for (auto &I : ProfilesToBeAdded) {
  395. ProfileMap.emplace(I.first, I.second);
  396. }
  397. }
  398. std::error_code ProfileSymbolList::write(raw_ostream &OS) {
  399. // Sort the symbols before output. If doing compression.
  400. // It will make the compression much more effective.
  401. std::vector<StringRef> SortedList(Syms.begin(), Syms.end());
  402. llvm::sort(SortedList);
  403. std::string OutputString;
  404. for (auto &Sym : SortedList) {
  405. OutputString.append(Sym.str());
  406. OutputString.append(1, '\0');
  407. }
  408. OS << OutputString;
  409. return sampleprof_error::success;
  410. }
  411. void ProfileSymbolList::dump(raw_ostream &OS) const {
  412. OS << "======== Dump profile symbol list ========\n";
  413. std::vector<StringRef> SortedList(Syms.begin(), Syms.end());
  414. llvm::sort(SortedList);
  415. for (auto &Sym : SortedList)
  416. OS << Sym << "\n";
  417. }
  418. CSProfileConverter::FrameNode *
  419. CSProfileConverter::FrameNode::getOrCreateChildFrame(
  420. const LineLocation &CallSite, StringRef CalleeName) {
  421. uint64_t Hash = FunctionSamples::getCallSiteHash(CalleeName, CallSite);
  422. auto It = AllChildFrames.find(Hash);
  423. if (It != AllChildFrames.end()) {
  424. assert(It->second.FuncName == CalleeName &&
  425. "Hash collision for child context node");
  426. return &It->second;
  427. }
  428. AllChildFrames[Hash] = FrameNode(CalleeName, nullptr, CallSite);
  429. return &AllChildFrames[Hash];
  430. }
  431. CSProfileConverter::CSProfileConverter(SampleProfileMap &Profiles)
  432. : ProfileMap(Profiles) {
  433. for (auto &FuncSample : Profiles) {
  434. FunctionSamples *FSamples = &FuncSample.second;
  435. auto *NewNode = getOrCreateContextPath(FSamples->getContext());
  436. assert(!NewNode->FuncSamples && "New node cannot have sample profile");
  437. NewNode->FuncSamples = FSamples;
  438. }
  439. }
  440. CSProfileConverter::FrameNode *
  441. CSProfileConverter::getOrCreateContextPath(const SampleContext &Context) {
  442. auto Node = &RootFrame;
  443. LineLocation CallSiteLoc(0, 0);
  444. for (auto &Callsite : Context.getContextFrames()) {
  445. Node = Node->getOrCreateChildFrame(CallSiteLoc, Callsite.FuncName);
  446. CallSiteLoc = Callsite.Location;
  447. }
  448. return Node;
  449. }
  450. void CSProfileConverter::convertProfiles(CSProfileConverter::FrameNode &Node) {
  451. // Process each child profile. Add each child profile to callsite profile map
  452. // of the current node `Node` if `Node` comes with a profile. Otherwise
  453. // promote the child profile to a standalone profile.
  454. auto *NodeProfile = Node.FuncSamples;
  455. for (auto &It : Node.AllChildFrames) {
  456. auto &ChildNode = It.second;
  457. convertProfiles(ChildNode);
  458. auto *ChildProfile = ChildNode.FuncSamples;
  459. if (!ChildProfile)
  460. continue;
  461. SampleContext OrigChildContext = ChildProfile->getContext();
  462. // Reset the child context to be contextless.
  463. ChildProfile->getContext().setName(OrigChildContext.getName());
  464. if (NodeProfile) {
  465. // Add child profile to the callsite profile map.
  466. auto &SamplesMap = NodeProfile->functionSamplesAt(ChildNode.CallSiteLoc);
  467. SamplesMap.emplace(OrigChildContext.getName().str(), *ChildProfile);
  468. NodeProfile->addTotalSamples(ChildProfile->getTotalSamples());
  469. }
  470. // Separate child profile to be a standalone profile, if the current parent
  471. // profile doesn't exist. This is a duplicating operation when the child
  472. // profile is already incorporated into the parent which is still useful and
  473. // thus done optionally. It is seen that duplicating context profiles into
  474. // base profiles improves the code quality for thinlto build by allowing a
  475. // profile in the prelink phase for to-be-fully-inlined functions.
  476. if (!NodeProfile || GenerateMergedBaseProfiles)
  477. ProfileMap[ChildProfile->getContext()].merge(*ChildProfile);
  478. // Contexts coming with a `ContextShouldBeInlined` attribute indicate this
  479. // is a preinliner-computed profile.
  480. if (OrigChildContext.hasAttribute(ContextShouldBeInlined))
  481. FunctionSamples::ProfileIsCSNested = true;
  482. // Remove the original child profile.
  483. ProfileMap.erase(OrigChildContext);
  484. }
  485. }
  486. void CSProfileConverter::convertProfiles() { convertProfiles(RootFrame); }