[ctxprof] dump profiles using yaml (for testing) (#123108)

This is a follow-up from PR #122545, which enabled converting yaml to contextual profiles.

This change uses the lower level yaml APIs because:
- the mapping APIs `llvm::yaml` offers don't work with `const` values, because they (the APIs) want to enable both serialization and deserialization
- building a helper data structure would be an alternative, but it'd be either memory-consuming or overly-complex design, given the recursive nature of the contextual profiles.
This commit is contained in:
Mircea Trofin 2025-01-15 16:49:59 -08:00 committed by GitHub
parent acf6072fae
commit b15845c005
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 175 additions and 200 deletions

View File

@ -140,7 +140,7 @@ public:
class CtxProfAnalysisPrinterPass
: public PassInfoMixin<CtxProfAnalysisPrinterPass> {
public:
enum class PrintMode { Everything, JSON };
enum class PrintMode { Everything, YAML };
explicit CtxProfAnalysisPrinterPass(raw_ostream &OS);
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);

View File

@ -183,5 +183,8 @@ public:
Expected<std::map<GlobalValue::GUID, PGOCtxProfContext>> loadContexts();
};
void convertCtxProfToYaml(raw_ostream &OS,
const PGOCtxProfContext::CallTargetMapTy &);
} // namespace llvm
#endif

View File

@ -81,14 +81,6 @@ public:
static constexpr StringRef ContainerMagic = "CTXP";
};
/// Representation of the context node suitable for yaml / json serialization /
/// deserialization.
struct SerializableCtxRepresentation {
ctx_profile::GUID Guid = 0;
std::vector<uint64_t> Counters;
std::vector<std::vector<SerializableCtxRepresentation>> Callsites;
};
Error createCtxProfFromYAML(StringRef Profile, raw_ostream &Out);
} // namespace llvm
#endif

View File

@ -19,7 +19,6 @@
#include "llvm/IR/PassManager.h"
#include "llvm/ProfileData/PGOCtxProfReader.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#define DEBUG_TYPE "ctx_prof"
@ -31,49 +30,13 @@ cl::opt<std::string>
static cl::opt<CtxProfAnalysisPrinterPass::PrintMode> PrintLevel(
"ctx-profile-printer-level",
cl::init(CtxProfAnalysisPrinterPass::PrintMode::JSON), cl::Hidden,
cl::init(CtxProfAnalysisPrinterPass::PrintMode::YAML), cl::Hidden,
cl::values(clEnumValN(CtxProfAnalysisPrinterPass::PrintMode::Everything,
"everything", "print everything - most verbose"),
clEnumValN(CtxProfAnalysisPrinterPass::PrintMode::JSON, "json",
"just the json representation of the profile")),
clEnumValN(CtxProfAnalysisPrinterPass::PrintMode::YAML, "yaml",
"just the yaml representation of the profile")),
cl::desc("Verbosity level of the contextual profile printer pass."));
namespace llvm {
namespace json {
Value toJSON(const PGOCtxProfContext &P) {
Object Ret;
Ret["Guid"] = P.guid();
Ret["Counters"] = Array(P.counters());
if (P.callsites().empty())
return Ret;
auto AllCS =
::llvm::map_range(P.callsites(), [](const auto &P) { return P.first; });
auto MaxIt = ::llvm::max_element(AllCS);
assert(MaxIt != AllCS.end() && "We should have a max value because the "
"callsites collection is not empty.");
Array CSites;
// Iterate to, and including, the maximum index.
for (auto I = 0U, Max = *MaxIt; I <= Max; ++I) {
CSites.push_back(Array());
Array &Targets = *CSites.back().getAsArray();
if (P.hasCallsite(I))
for (const auto &[_, Ctx] : P.callsite(I))
Targets.push_back(toJSON(Ctx));
}
Ret["Callsites"] = std::move(CSites);
return Ret;
}
Value toJSON(const PGOCtxProfContext::CallTargetMapTy &P) {
Array Ret;
for (const auto &[_, Ctx] : P)
Ret.push_back(toJSON(Ctx));
return Ret;
}
} // namespace json
} // namespace llvm
const char *AssignGUIDPass::GUIDMetadataName = "guid";
PreservedAnalyses AssignGUIDPass::run(Module &M, ModuleAnalysisManager &MAM) {
@ -214,15 +177,13 @@ PreservedAnalyses CtxProfAnalysisPrinterPass::run(Module &M,
<< ". MaxCallsiteID: " << FuncInfo.NextCallsiteIndex << "\n";
}
const auto JSONed = ::llvm::json::toJSON(C.profiles());
if (Mode == PrintMode::Everything)
OS << "\nCurrent Profile:\n";
OS << formatv("{0:2}", JSONed);
if (Mode == PrintMode::JSON)
convertCtxProfToYaml(OS, C.profiles());
OS << "\n";
if (Mode == PrintMode::YAML)
return PreservedAnalyses::all();
OS << "\n";
OS << "\nFlat Profile:\n";
auto Flat = C.flatten();
for (const auto &[Guid, Counters] : Flat) {

View File

@ -17,6 +17,10 @@
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/ProfileData/PGOCtxProfWriter.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/YAMLTraits.h"
#include <iterator>
#include <utility>
using namespace llvm;
@ -176,3 +180,86 @@ PGOCtxProfileReader::loadContexts() {
}
return std::move(Ret);
}
namespace {
// We want to pass `const` values PGOCtxProfContext references to the yaml
// converter, and the regular yaml mapping APIs are designed to handle both
// serialization and deserialization, which prevents using const for
// serialization. Using an intermediate datastructure is overkill, both
// space-wise and design complexity-wise. Instead, we use the lower-level APIs.
void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx);
void toYaml(yaml::Output &Out,
const PGOCtxProfContext::CallTargetMapTy &CallTargets) {
Out.beginSequence();
size_t Index = 0;
void *SaveData = nullptr;
for (const auto &[_, Ctx] : CallTargets) {
Out.preflightElement(Index++, SaveData);
toYaml(Out, Ctx);
Out.postflightElement(nullptr);
}
Out.endSequence();
}
void toYaml(yaml::Output &Out,
const PGOCtxProfContext::CallsiteMapTy &Callsites) {
auto AllCS = ::llvm::make_first_range(Callsites);
auto MaxIt = ::llvm::max_element(AllCS);
assert(MaxIt != AllCS.end() && "We should have a max value because the "
"callsites collection is not empty.");
void *SaveData = nullptr;
Out.beginSequence();
for (auto I = 0U; I <= *MaxIt; ++I) {
Out.preflightElement(I, SaveData);
auto It = Callsites.find(I);
if (It == Callsites.end()) {
// This will produce a `[ ]` sequence, which is what we want here.
Out.beginFlowSequence();
Out.endFlowSequence();
} else {
toYaml(Out, It->second);
}
Out.postflightElement(nullptr);
}
Out.endSequence();
}
void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx) {
yaml::EmptyContext Empty;
Out.beginMapping();
void *SaveInfo = nullptr;
bool UseDefault = false;
{
Out.preflightKey("Guid", /*Required=*/true, /*SameAsDefault=*/false,
UseDefault, SaveInfo);
auto Guid = Ctx.guid();
yaml::yamlize(Out, Guid, true, Empty);
Out.postflightKey(nullptr);
}
{
Out.preflightKey("Counters", true, false, UseDefault, SaveInfo);
Out.beginFlowSequence();
for (size_t I = 0U, E = Ctx.counters().size(); I < E; ++I) {
Out.preflightFlowElement(I, SaveInfo);
uint64_t V = Ctx.counters()[I];
yaml::yamlize(Out, V, true, Empty);
Out.postflightFlowElement(SaveInfo);
}
Out.endFlowSequence();
Out.postflightKey(nullptr);
}
if (!Ctx.callsites().empty()) {
Out.preflightKey("Callsites", true, false, UseDefault, SaveInfo);
toYaml(Out, Ctx.callsites());
Out.postflightKey(nullptr);
}
Out.endMapping();
}
} // namespace
void llvm::convertCtxProfToYaml(
raw_ostream &OS, const PGOCtxProfContext::CallTargetMapTy &Profiles) {
yaml::Output Out(OS);
toYaml(Out, Profiles);
}

View File

@ -14,7 +14,6 @@
#include "llvm/Bitstream/BitCodeEnums.h"
#include "llvm/ProfileData/CtxInstrContextNode.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
@ -89,6 +88,15 @@ void PGOCtxProfileWriter::write(const ContextNode &RootNode) {
}
namespace {
/// Representation of the context node suitable for yaml serialization /
/// deserialization.
struct SerializableCtxRepresentation {
ctx_profile::GUID Guid = 0;
std::vector<uint64_t> Counters;
std::vector<std::vector<SerializableCtxRepresentation>> Callsites;
};
ctx_profile::ContextNode *
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
const std::vector<SerializableCtxRepresentation> &DCList);

View File

@ -88,54 +88,20 @@ Function Info:
10507721908651011566 : entrypoint. MaxCounterID: 1. MaxCallsiteID: 2
Current Profile:
[
{
"Callsites": [
[
{
"Callsites": [
[
{
"Counters": [
10,
7
],
"Guid": 3087265239403591524
}
]
],
"Counters": [
7
],
"Guid": 2072045998141807037
}
],
[
{
"Callsites": [
[
{
"Counters": [
1,
2
],
"Guid": 3087265239403591524
}
]
],
"Counters": [
2
],
"Guid": 4197650231481825559
}
]
],
"Counters": [
1
],
"Guid": 10507721908651011566
}
]
- Guid: 10507721908651011566
Counters: [ 1 ]
Callsites:
- - Guid: 2072045998141807037
Counters: [ 7 ]
Callsites:
- - Guid: 3087265239403591524
Counters: [ 10, 7 ]
- - Guid: 4197650231481825559
Counters: [ 2 ]
Callsites:
- - Guid: 3087265239403591524
Counters: [ 1, 2 ]
Flat Profile:
2072045998141807037 : 7

View File

@ -1,11 +1,12 @@
; REQUIRES: x86_64-linux
; RUN: rm -rf %t
; RUN: split-file %s %t
; RUN: llvm-ctxprof-util fromYAML --input=%t/profile.yaml --output=%t/profile.ctxprofdata
; RUN: opt -passes='module-inline,print<ctx-prof-analysis>' -ctx-profile-printer-level=everything %t/module.ll -S \
; RUN: -use-ctx-profile=%t/profile.ctxprofdata -ctx-profile-printer-level=json \
; RUN: -o - 2> %t/profile-final.txt | FileCheck %s
; RUN: %python %S/json_equals.py %t/profile-final.txt %t/expected.json
; RUN: -use-ctx-profile=%t/profile.ctxprofdata -ctx-profile-printer-level=yaml \
; RUN: -o - 2> %t/profile-final.yaml | FileCheck %s
; RUN: diff %t/profile-final.yaml %t/expected.yaml
; There are 2 calls to @a from @entrypoint. We only inline the one callsite
; marked as alwaysinline, the rest are blocked (marked noinline). After the inline,
@ -109,17 +110,16 @@ define i32 @b() !guid !2 {
Callsites: -
- Guid: 1002
Counters: [500]
;--- expected.json
[
{ "Guid": 1000,
"Counters": [10, 2, 8, 100],
"Callsites": [
[],
[ { "Guid": 1001,
"Counters": [8, 500],
"Callsites": [[{"Guid": 1002, "Counters": [500]}]]}
],
[{ "Guid": 1002, "Counters": [100]}]
]
}
]
;--- expected.yaml
- Guid: 1000
Counters: [ 10, 2, 8, 100 ]
Callsites:
- [ ]
- - Guid: 1001
Counters: [ 8, 500 ]
Callsites:
- - Guid: 1002
Counters: [ 500 ]
- - Guid: 1002
Counters: [ 100 ]

View File

@ -1,15 +0,0 @@
import json
import sys
def to_json(fname: str):
with open(fname) as f:
return json.load(f)
a = to_json(sys.argv[1])
b = to_json(sys.argv[2])
if a == b:
exit(0)
exit(1)

View File

@ -40,31 +40,14 @@ Function Info:
12074870348631550642 : another_entrypoint_no_callees. MaxCounterID: 1. MaxCallsiteID: 0
Current Profile:
[
{
"Callsites": [
[
{
"Counters": [
6,
7
],
"Guid": 728453322856651412
}
]
],
"Counters": [
1
],
"Guid": 11872291593386833696
},
{
"Counters": [
5
],
"Guid": 12074870348631550642
}
]
- Guid: 11872291593386833696
Counters: [ 1 ]
Callsites:
- - Guid: 728453322856651412
Counters: [ 6, 7 ]
- Guid: 12074870348631550642
Counters: [ 5 ]
Flat Profile:
728453322856651412 : 6 7

View File

@ -18,7 +18,6 @@
#include "llvm/IR/PassInstrumentation.h"
#include "llvm/ProfileData/PGOCtxProfReader.h"
#include "llvm/ProfileData/PGOCtxProfWriter.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/SupportHelpers.h"
@ -572,43 +571,34 @@ define i32 @f4() !guid !3 {
raw_string_ostream OS(Str);
CtxProfAnalysisPrinterPass Printer(OS);
Printer.run(*M, MAM);
const char *Expected = R"json(
[
{
"Guid": 1000,
"Counters": [1, 11, 22],
"Callsites": [
[{ "Guid": 1001,
"Counters": [10]},
{ "Guid": 1003,
"Counters": [12]
}],
[{ "Guid": 1002,
"Counters": [11],
"Callsites": [
[{ "Guid": 1004,
"Counters": [13] }]]}]]
},
{
"Guid": 1005,
"Counters": [2],
"Callsites": [
[{ "Guid": 1000,
"Counters": [1, 102, 204],
"Callsites": [
[{ "Guid": 1001,
"Counters": [101]},
{ "Guid": 1003,
"Counters": [103]}],
[{ "Guid": 1002,
"Counters": [102],
"Callsites": [
[{ "Guid": 1004,
"Counters": [104]}]]}]]}]]}
])json";
auto ExpectedJSON = json::parse(Expected);
ASSERT_TRUE(!!ExpectedJSON);
auto ProducedJSON = json::parse(Str);
ASSERT_TRUE(!!ProducedJSON);
EXPECT_EQ(*ProducedJSON, *ExpectedJSON);
const char *Expected = R"yaml(
- Guid: 1000
Counters: [ 1, 11, 22 ]
Callsites:
- - Guid: 1001
Counters: [ 10 ]
- Guid: 1003
Counters: [ 12 ]
- - Guid: 1002
Counters: [ 11 ]
Callsites:
- - Guid: 1004
Counters: [ 13 ]
- Guid: 1005
Counters: [ 2 ]
Callsites:
- - Guid: 1000
Counters: [ 1, 102, 204 ]
Callsites:
- - Guid: 1001
Counters: [ 101 ]
- Guid: 1003
Counters: [ 103 ]
- - Guid: 1002
Counters: [ 102 ]
Callsites:
- - Guid: 1004
Counters: [ 104 ]
)yaml";
EXPECT_EQ(Expected, Str);
}