-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[ctxprof] Profile section for flat profiles #129932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ctxprof] Profile section for flat profiles #129932
Conversation
kazutakahirata
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
|
@llvm/pr-subscribers-pgo Author: Mircea Trofin (mtrofin) ChangesA section for flat profiles (i.e. non-contextual). This is useful for debugging or for intentional cases where a root isn't identified. This patch adds the reader/writer support. Full diff: https://github.com/llvm/llvm-project/pull/129932.diff 11 Files Affected:
diff --git a/llvm/include/llvm/ProfileData/PGOCtxProfReader.h b/llvm/include/llvm/ProfileData/PGOCtxProfReader.h
index dbd8288caaff5..4026e6b45b938 100644
--- a/llvm/include/llvm/ProfileData/PGOCtxProfReader.h
+++ b/llvm/include/llvm/ProfileData/PGOCtxProfReader.h
@@ -174,6 +174,7 @@ using CtxProfContextualProfiles =
std::map<GlobalValue::GUID, PGOCtxProfContext>;
struct PGOCtxProfile {
CtxProfContextualProfiles Contexts;
+ CtxProfFlatProfile FlatProfiles;
PGOCtxProfile() = default;
PGOCtxProfile(const PGOCtxProfile &) = delete;
@@ -192,10 +193,12 @@ class PGOCtxProfileReader final {
Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
readProfile(PGOCtxProfileBlockIDs Kind);
+ bool tryGetNextKnownBlockID(PGOCtxProfileBlockIDs &ID);
bool canEnterBlockWithID(PGOCtxProfileBlockIDs ID);
Error enterBlockWithID(PGOCtxProfileBlockIDs ID);
Error loadContexts(CtxProfContextualProfiles &);
+ Error loadFlatProfiles(CtxProfFlatProfile &);
public:
PGOCtxProfileReader(StringRef Buffer)
diff --git a/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h b/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h
index 8923fe57c180c..99da9ac9df68b 100644
--- a/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h
+++ b/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h
@@ -22,10 +22,14 @@ namespace llvm {
enum PGOCtxProfileRecords { Invalid = 0, Version, Guid, CalleeIndex, Counters };
enum PGOCtxProfileBlockIDs {
- ProfileMetadataBlockID = bitc::FIRST_APPLICATION_BLOCKID,
+ FIRST_VALID = bitc::FIRST_APPLICATION_BLOCKID,
+ ProfileMetadataBlockID = FIRST_VALID,
ContextsSectionBlockID = ProfileMetadataBlockID + 1,
ContextRootBlockID = ContextsSectionBlockID + 1,
ContextNodeBlockID = ContextRootBlockID + 1,
+ FlatProfilesSectionBlockID = ContextNodeBlockID + 1,
+ FlatProfileBlockID = FlatProfilesSectionBlockID + 1,
+ LAST_VALID = FlatProfileBlockID
};
/// Write one or more ContextNodes to the provided raw_fd_stream.
@@ -83,6 +87,11 @@ class PGOCtxProfileWriter : public ctx_profile::ProfileWriter {
void writeContextual(const ctx_profile::ContextNode &RootNode) override;
void endContextSection() override;
+ void startFlatSection();
+ void writeFlatSection(ctx_profile::GUID Guid, const uint64_t *Buffer,
+ size_t BufferSize);
+ void endFlatSection();
+
// constants used in writing which a reader may find useful.
static constexpr unsigned CodeLen = 2;
static constexpr uint32_t CurrentVersion = 2;
diff --git a/llvm/lib/ProfileData/PGOCtxProfReader.cpp b/llvm/lib/ProfileData/PGOCtxProfReader.cpp
index bb912635879d2..8409fe3659f46 100644
--- a/llvm/lib/ProfileData/PGOCtxProfReader.cpp
+++ b/llvm/lib/ProfileData/PGOCtxProfReader.cpp
@@ -57,13 +57,24 @@ Error PGOCtxProfileReader::unsupported(const Twine &Msg) {
return make_error<InstrProfError>(instrprof_error::unsupported_version, Msg);
}
-bool PGOCtxProfileReader::canEnterBlockWithID(PGOCtxProfileBlockIDs ID) {
+bool PGOCtxProfileReader::tryGetNextKnownBlockID(PGOCtxProfileBlockIDs &ID) {
auto Blk = advance();
if (!Blk) {
consumeError(Blk.takeError());
return false;
}
- return Blk->Kind == BitstreamEntry::SubBlock && Blk->ID == ID;
+ if (Blk->Kind != BitstreamEntry::SubBlock)
+ return false;
+ if (PGOCtxProfileBlockIDs::FIRST_VALID > Blk->ID ||
+ PGOCtxProfileBlockIDs::LAST_VALID < Blk->ID)
+ return false;
+ ID = static_cast<PGOCtxProfileBlockIDs>(Blk->ID);
+ return true;
+}
+
+bool PGOCtxProfileReader::canEnterBlockWithID(PGOCtxProfileBlockIDs ID) {
+ PGOCtxProfileBlockIDs Test = {};
+ return tryGetNextKnownBlockID(Test) && Test == ID;
}
Error PGOCtxProfileReader::enterBlockWithID(PGOCtxProfileBlockIDs ID) {
@@ -71,10 +82,14 @@ Error PGOCtxProfileReader::enterBlockWithID(PGOCtxProfileBlockIDs ID) {
return Error::success();
}
+// Note: we use PGOCtxProfContext for flat profiles also, as the latter are
+// structurally similar. Alternative modeling here seems a bit overkill at the
+// moment.
Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) {
assert((Kind == PGOCtxProfileBlockIDs::ContextRootBlockID ||
- Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID) &&
+ Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID ||
+ Kind == PGOCtxProfileBlockIDs::FlatProfileBlockID) &&
"Unexpected profile kind");
RET_ON_ERR(enterBlockWithID(Kind));
@@ -176,14 +191,24 @@ Error PGOCtxProfileReader::readMetadata() {
}
Error PGOCtxProfileReader::loadContexts(CtxProfContextualProfiles &P) {
- if (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextsSectionBlockID)) {
- RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::ContextsSectionBlockID));
- while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextRootBlockID)) {
- EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::ContextRootBlockID));
- auto Key = E->second.guid();
- if (!P.insert({Key, std::move(E->second)}).second)
- return wrongValue("Duplicate roots");
- }
+ RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::ContextsSectionBlockID));
+ while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextRootBlockID)) {
+ EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::ContextRootBlockID));
+ auto Key = E->second.guid();
+ if (!P.insert({Key, std::move(E->second)}).second)
+ return wrongValue("Duplicate roots");
+ }
+ return Error::success();
+}
+
+Error PGOCtxProfileReader::loadFlatProfiles(CtxProfFlatProfile &P) {
+ RET_ON_ERR(
+ enterBlockWithID(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID));
+ while (canEnterBlockWithID(PGOCtxProfileBlockIDs::FlatProfileBlockID)) {
+ EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::FlatProfileBlockID));
+ auto Guid = E->second.guid();
+ if (!P.insert({Guid, std::move(E->second.counters())}).second)
+ return wrongValue("Duplicate flat profile entries");
}
return Error::success();
}
@@ -191,7 +216,19 @@ Error PGOCtxProfileReader::loadContexts(CtxProfContextualProfiles &P) {
Expected<PGOCtxProfile> PGOCtxProfileReader::loadProfiles() {
RET_ON_ERR(readMetadata());
PGOCtxProfile Ret;
- RET_ON_ERR(loadContexts(Ret.Contexts));
+ PGOCtxProfileBlockIDs Test = {};
+ for (auto I = 0; I < 2; ++I) {
+ if (!tryGetNextKnownBlockID(Test))
+ break;
+ if (Test == PGOCtxProfileBlockIDs::ContextsSectionBlockID) {
+ RET_ON_ERR(loadContexts(Ret.Contexts));
+ } else if (Test == PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID) {
+ RET_ON_ERR(loadFlatProfiles(Ret.FlatProfiles));
+ } else {
+ return wrongValue("Unexpected section");
+ }
+ }
+
return std::move(Ret);
}
@@ -288,5 +325,17 @@ void llvm::convertCtxProfToYaml(raw_ostream &OS,
toYaml(Out, Profiles.Contexts);
Out.postflightKey(nullptr);
}
+ if (!Profiles.FlatProfiles.empty()) {
+ Out.preflightKey("FlatProfiles", false, false, UseDefault, SaveInfo);
+ Out.beginSequence();
+ size_t ElemID = 0;
+ for (const auto &[Guid, Counters] : Profiles.FlatProfiles) {
+ Out.preflightElement(ElemID++, SaveInfo);
+ toYaml(Out, Guid, Counters, {});
+ Out.postflightElement(nullptr);
+ }
+ Out.endSequence();
+ Out.postflightKey(nullptr);
+ }
Out.endMapping();
}
diff --git a/llvm/lib/ProfileData/PGOCtxProfWriter.cpp b/llvm/lib/ProfileData/PGOCtxProfWriter.cpp
index d4184da1c2509..d099572fc152a 100644
--- a/llvm/lib/ProfileData/PGOCtxProfWriter.cpp
+++ b/llvm/lib/ProfileData/PGOCtxProfWriter.cpp
@@ -13,6 +13,7 @@
#include "llvm/ProfileData/PGOCtxProfWriter.h"
#include "llvm/Bitstream/BitCodeEnums.h"
#include "llvm/ProfileData/CtxInstrContextNode.h"
+#include "llvm/ProfileData/PGOCtxProfReader.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/YAMLTraits.h"
@@ -59,6 +60,11 @@ PGOCtxProfileWriter::PGOCtxProfileWriter(
DescribeRecord(PGOCtxProfileRecords::Guid, "GUID");
DescribeRecord(PGOCtxProfileRecords::CalleeIndex, "CalleeIndex");
DescribeRecord(PGOCtxProfileRecords::Counters, "Counters");
+ DescribeBlock(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID,
+ "FlatProfiles");
+ DescribeBlock(PGOCtxProfileBlockIDs::FlatProfileBlockID, "Flat");
+ DescribeRecord(PGOCtxProfileRecords::Guid, "GUID");
+ DescribeRecord(PGOCtxProfileRecords::Counters, "Counters");
}
Writer.ExitBlock();
Writer.EnterSubblock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID, CodeLen);
@@ -108,12 +114,27 @@ void PGOCtxProfileWriter::startContextSection() {
Writer.EnterSubblock(PGOCtxProfileBlockIDs::ContextsSectionBlockID, CodeLen);
}
+void PGOCtxProfileWriter::startFlatSection() {
+ Writer.EnterSubblock(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID,
+ CodeLen);
+}
+
void PGOCtxProfileWriter::endContextSection() { Writer.ExitBlock(); }
+void PGOCtxProfileWriter::endFlatSection() { Writer.ExitBlock(); }
void PGOCtxProfileWriter::writeContextual(const ContextNode &RootNode) {
writeImpl(std::nullopt, RootNode);
}
+void PGOCtxProfileWriter::writeFlatSection(ctx_profile::GUID Guid,
+ const uint64_t *Buffer,
+ size_t Size) {
+ Writer.EnterSubblock(PGOCtxProfileBlockIDs::FlatProfileBlockID, CodeLen);
+ writeGuid(Guid);
+ writeCounters({Buffer, Size});
+ Writer.ExitBlock();
+}
+
namespace {
/// Representation of the context node suitable for yaml serialization /
@@ -123,8 +144,13 @@ struct SerializableCtxRepresentation {
std::vector<uint64_t> Counters;
std::vector<std::vector<SerializableCtxRepresentation>> Callsites;
};
+
+using SerializableFlatProfileRepresentation =
+ std::pair<ctx_profile::GUID, std::vector<uint64_t>>;
+
struct SerializableProfileRepresentation {
std::vector<SerializableCtxRepresentation> Contexts;
+ std::vector<SerializableFlatProfileRepresentation> FlatProfiles;
};
ctx_profile::ContextNode *
@@ -164,6 +190,7 @@ createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableCtxRepresentation)
LLVM_YAML_IS_SEQUENCE_VECTOR(std::vector<SerializableCtxRepresentation>)
+LLVM_YAML_IS_SEQUENCE_VECTOR(SerializableFlatProfileRepresentation)
template <> struct yaml::MappingTraits<SerializableCtxRepresentation> {
static void mapping(yaml::IO &IO, SerializableCtxRepresentation &SCR) {
IO.mapRequired("Guid", SCR.Guid);
@@ -175,6 +202,15 @@ template <> struct yaml::MappingTraits<SerializableCtxRepresentation> {
template <> struct yaml::MappingTraits<SerializableProfileRepresentation> {
static void mapping(yaml::IO &IO, SerializableProfileRepresentation &SPR) {
IO.mapOptional("Contexts", SPR.Contexts);
+ IO.mapOptional("FlatProfiles", SPR.FlatProfiles);
+ }
+};
+
+template <> struct yaml::MappingTraits<SerializableFlatProfileRepresentation> {
+ static void mapping(yaml::IO &IO,
+ SerializableFlatProfileRepresentation &SFPR) {
+ IO.mapRequired("Guid", SFPR.first);
+ IO.mapRequired("Counters", SFPR.second);
}
};
@@ -201,6 +237,12 @@ Error llvm::createCtxProfFromYAML(StringRef Profile, raw_ostream &Out) {
}
Writer.endContextSection();
}
+ if (!SPR.FlatProfiles.empty()) {
+ Writer.startFlatSection();
+ for (const auto &[Guid, Counters] : SPR.FlatProfiles)
+ Writer.writeFlatSection(Guid, Counters.data(), Counters.size());
+ Writer.endFlatSection();
+ }
if (EC)
return createStringError(EC, "failed to write output");
return Error::success();
diff --git a/llvm/test/tools/llvm-ctxprof-util/Inputs/invalid-flat.yaml b/llvm/test/tools/llvm-ctxprof-util/Inputs/invalid-flat.yaml
new file mode 100644
index 0000000000000..c3bc89a9a3519
--- /dev/null
+++ b/llvm/test/tools/llvm-ctxprof-util/Inputs/invalid-flat.yaml
@@ -0,0 +1,2 @@
+FlatProfiles:
+ - Guid: 1
diff --git a/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-ctx-only.yaml b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-ctx-only.yaml
new file mode 100644
index 0000000000000..0de489dd0b1eb
--- /dev/null
+++ b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-ctx-only.yaml
@@ -0,0 +1,14 @@
+
+Contexts:
+ - Guid: 1000
+ Counters: [ 1, 2, 3 ]
+ Callsites:
+ - [ ]
+ - - Guid: 2000
+ Counters: [ 4, 5 ]
+ - Guid: 18446744073709551613
+ Counters: [ 6, 7, 8 ]
+ - - Guid: 3000
+ Counters: [ 40, 50 ]
+ - Guid: 18446744073709551612
+ Counters: [ 5, 9, 10 ]
diff --git a/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-flat-first.yaml b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-flat-first.yaml
new file mode 100644
index 0000000000000..5567faaa9e0a4
--- /dev/null
+++ b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-flat-first.yaml
@@ -0,0 +1,19 @@
+
+FlatProfiles:
+ - Guid: 1234
+ Counters: [ 5, 6, 7 ]
+ - Guid: 5555
+ Counters: [ 1 ]
+Contexts:
+ - Guid: 1000
+ Counters: [ 1, 2, 3 ]
+ Callsites:
+ - [ ]
+ - - Guid: 2000
+ Counters: [ 4, 5 ]
+ - Guid: 18446744073709551613
+ Counters: [ 6, 7, 8 ]
+ - - Guid: 3000
+ Counters: [ 40, 50 ]
+ - Guid: 18446744073709551612
+ Counters: [ 5, 9, 10 ]
diff --git a/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-flat-only.yaml b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-flat-only.yaml
new file mode 100644
index 0000000000000..98231ed70d0ec
--- /dev/null
+++ b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid-flat-only.yaml
@@ -0,0 +1,6 @@
+
+FlatProfiles:
+ - Guid: 1234
+ Counters: [ 5, 6, 7 ]
+ - Guid: 5555
+ Counters: [ 1 ]
diff --git a/llvm/test/tools/llvm-ctxprof-util/Inputs/valid.yaml b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid.yaml
index 0de489dd0b1eb..1541b0d136514 100644
--- a/llvm/test/tools/llvm-ctxprof-util/Inputs/valid.yaml
+++ b/llvm/test/tools/llvm-ctxprof-util/Inputs/valid.yaml
@@ -12,3 +12,8 @@ Contexts:
Counters: [ 40, 50 ]
- Guid: 18446744073709551612
Counters: [ 5, 9, 10 ]
+FlatProfiles:
+ - Guid: 1234
+ Counters: [ 5, 6, 7 ]
+ - Guid: 5555
+ Counters: [ 1 ]
diff --git a/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util-negative.test b/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util-negative.test
index 487d5ae1d17be..f312f50ffee8e 100644
--- a/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util-negative.test
+++ b/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util-negative.test
@@ -9,6 +9,7 @@
; RUN: not llvm-ctxprof-util fromYAML --input %S/Inputs/invalid-no-ctx.yaml 2>&1 | FileCheck %s --check-prefix=NO_CTX
; RUN: not llvm-ctxprof-util fromYAML --input %S/Inputs/invalid-no-counters.yaml 2>&1 | FileCheck %s --check-prefix=NO_COUNTERS
; RUN: not llvm-ctxprof-util fromYAML --input %S/Inputs/invalid-bad-subctx.yaml 2>&1 | FileCheck %s --check-prefix=BAD_SUBCTX
+; RUN: not llvm-ctxprof-util fromYAML --input %S/Inputs/invalid-flat.yaml 2>&1 | FileCheck %s --check-prefix=BAD_FLAT
; RUN: rm -rf %t
; RUN: not llvm-ctxprof-util fromYAML --input %S/Inputs/valid.yaml --output %t/output.bitstream 2>&1 | FileCheck %s --check-prefix=NO_DIR
@@ -21,4 +22,5 @@
; NO_CTX: YAML:1:1: error: not a mapping
; NO_COUNTERS: YAML:2:5: error: missing required key 'Counters'
; BAD_SUBCTX: YAML:4:18: error: not a sequence
+; BAD_FLAT: YAML:2:5: error: missing required key 'Counters'
; NO_DIR: failed to open output
diff --git a/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util.test b/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util.test
index 07cbdd97210fb..a9e350388577c 100644
--- a/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util.test
+++ b/llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util.test
@@ -8,6 +8,21 @@
; RUN: llvm-ctxprof-util toYAML -input %t/valid.bitstream -output %t/valid2.yaml
; RUN: diff %t/valid2.yaml %S/Inputs/valid.yaml
+
+; RUN: llvm-ctxprof-util fromYAML -input %S/Inputs/valid-ctx-only.yaml -output %t/valid-ctx-only.bitstream
+; RUN: llvm-ctxprof-util toYAML -input %t/valid-ctx-only.bitstream -output %t/valid-ctx-only.yaml
+; RUN: diff %t/valid-ctx-only.yaml %S/Inputs/valid-ctx-only.yaml
+
+; RUN: llvm-ctxprof-util fromYAML -input %S/Inputs/valid-flat-only.yaml -output %t/valid-flat-only.bitstream
+; RUN: llvm-ctxprof-util toYAML -input %t/valid-flat-only.bitstream -output %t/valid-flat-only.yaml
+; RUN: diff %t/valid-flat-only.yaml %S/Inputs/valid-flat-only.yaml
+
+; This case is the "valid.yaml" case but with the flat profile first.
+; The output, though, should match valid.yaml
+; RUN: llvm-ctxprof-util fromYAML -input %S/Inputs/valid-flat-first.yaml -output %t/valid-flat-first.bitstream
+; RUN: llvm-ctxprof-util toYAML -input %t/valid-flat-first.bitstream -output %t/valid-flat-first.yaml
+; RUN: diff %t/valid-flat-first.yaml %S/Inputs/valid.yaml
+
; For the valid case, check against a reference output.
; Note that uint64_t are printed as signed values by llvm-bcanalyzer:
; * 18446744073709551613 in yaml is -3 in the output
@@ -22,7 +37,7 @@
; EMPTY-NEXT: </Metadata>
; VALID: <BLOCKINFO_BLOCK/>
-; VALID-NEXT: <Metadata NumWords=33 BlockCodeSize=2>
+; VALID-NEXT: <Metadata NumWords=45 BlockCodeSize=2>
; VALID-NEXT: <Version op0=2/>
; VALID-NEXT: <Contexts NumWords=29 BlockCodeSize=2>
; VALID-NEXT: <Root NumWords=20 BlockCodeSize=2>
@@ -49,4 +64,14 @@
; VALID-NEXT: <Counters op0=5 op1=9 op2=10/>
; VALID-NEXT: </Root>
; VALID-NEXT: </Contexts>
+; VALID-NEXT: <FlatProfiles NumWords=10 BlockCodeSize=2>
+; VALID-NEXT: <Flat NumWords=3 BlockCodeSize=2>
+; VALID-NEXT: <GUID op0=1234/>
+; VALID-NEXT: <Counters op0=5 op1=6 op2=7/>
+; VALID-NEXT: </Flat>
+; VALID-NEXT: <Flat NumWords=2 BlockCodeSize=2>
+; VALID-NEXT: <GUID op0=5555/>
+; VALID-NEXT: <Counters op0=1/>
+; VALID-NEXT: </Flat>
+; VALID-NEXT: </FlatProfiles>
; VALID-NEXT: </Metadata>
\ No newline at end of file
|
08da240 to
afc850e
Compare
afc850e to
ae02425
Compare

A section for flat profiles (i.e. non-contextual). This is useful for debugging or for intentional cases where a root isn't identified.
This patch adds the reader/writer support.
compiler-rtchanges follow in a subsequent change.