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