Skip to content

Commit a737b87

Browse files
authored
[ctx_prof] test tool: generate ctxprof bistream from json (#100379)
This is a tool to simplify testing. It generates a valid contextual profile file from a json representation. The tool is authored to allow for future evolution, e.g. if we want to support profile merging or other tasks, not necessarily scoped to testing.
1 parent 8470a23 commit a737b87

14 files changed

+299
-0
lines changed

llvm/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ set(LLVM_TEST_DEPENDS
7676
llvm-cfi-verify
7777
llvm-config
7878
llvm-cov
79+
llvm-ctxprof-util
7980
llvm-cvtres
8081
llvm-cxxdump
8182
llvm-cxxfilt

llvm/test/lit.cfg.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def get_asan_rtlib():
182182
"llvm-bitcode-strip",
183183
"llvm-config",
184184
"llvm-cov",
185+
"llvm-ctxprof-util",
185186
"llvm-cxxdump",
186187
"llvm-cvtres",
187188
"llvm-debuginfod-find",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[{
2+
"Guid": 123,
3+
"Counters": [1, 2],
4+
"Callsites":
5+
[
6+
{"Guid": 1}
7+
]
8+
}]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
{
3+
"Guid": 1231
4+
}
5+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
{
3+
"Guid": 1000,
4+
"Counters": [
5+
1,
6+
2,
7+
3
8+
],
9+
"Callsites": [
10+
[],
11+
[
12+
{
13+
"Guid": 2000,
14+
"Counters": [
15+
4,
16+
5
17+
]
18+
},
19+
{
20+
"Guid": 18446744073709551613,
21+
"Counters": [
22+
6,
23+
7,
24+
8
25+
]
26+
}
27+
],
28+
[
29+
{
30+
"Guid": 3000,
31+
"Counters": [
32+
40,
33+
50
34+
]
35+
}
36+
]
37+
]
38+
},
39+
{
40+
"Guid": 18446744073709551612,
41+
"Counters": [
42+
5,
43+
9,
44+
10
45+
]
46+
}
47+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
; RUN: not llvm-ctxprof-util nofile.json 2>&1 | FileCheck %s --check-prefix=NO_CMD
2+
; RUN: not llvm-ctxprof-util invalidCmd --input nofile.json 2>&1 | FileCheck %s --check-prefix=INVALID_CMD
3+
; RUN: not llvm-ctxprof-util fromJSON nofile.json 2>&1 | FileCheck %s --check-prefix=NO_FLAG
4+
; RUN: not llvm-ctxprof-util fromJSON --input nofile.json 2>&1 | FileCheck %s --check-prefix=NO_FILE
5+
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/bad.json 2>&1 | FileCheck %s --check-prefix=BAD_JSON
6+
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-no-vector.json 2>&1 | FileCheck %s --check-prefix=NO_VECTOR
7+
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-no-ctx.json 2>&1 | FileCheck %s --check-prefix=NO_CTX
8+
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-no-counters.json 2>&1 | FileCheck %s --check-prefix=NO_COUNTERS
9+
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-bad-subctx.json 2>&1 | FileCheck %s --check-prefix=BAD_SUBCTX
10+
; RUN: rm -rf %t
11+
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/valid.json --output %t/output.bitstream 2>&1 | FileCheck %s --check-prefix=NO_DIR
12+
13+
; NO_CMD: Unknown subcommand 'nofile.json'
14+
; INVALID_CMD: Unknown subcommand 'invalidCmd'
15+
; NO_FLAG: Unknown command line argument 'nofile.json'.
16+
; NO_FILE: 'nofile.json': No such file or directory
17+
; BAD_JSON: Expected object key
18+
; NO_VECTOR: expected array
19+
; NO_CTX: missing value at (root)[0].Guid
20+
; NO_COUNTERS: missing value at (root)[0].Counters
21+
; BAD_SUBCTX: expected array at (root)[0].Callsites[0]
22+
; NO_DIR: failed to open output
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
; RUN: mkdir -p %t
2+
; RUN: llvm-ctxprof-util fromJSON --input %S/Inputs/empty.json -output %t/empty.bitstream
3+
; RUN: llvm-bcanalyzer --dump %t/empty.bitstream | FileCheck %s --check-prefix=EMPTY
4+
5+
; RUN: llvm-ctxprof-util fromJSON --input %S/Inputs/valid.json -output %t/valid.bitstream
6+
7+
; For the valid case, check against a reference output.
8+
; Note that uint64_t are printed as signed values by llvm-bcanalyzer:
9+
; * 18446744073709551613 in json is -3 in the output
10+
; * 18446744073709551612 in json is -4 in the output
11+
; Also we have no callee/context at index 0, 2 callsites for index 1, and one for
12+
; index 2.
13+
; RUN: llvm-bcanalyzer --dump %t/valid.bitstream | FileCheck %s --check-prefix=VALID
14+
15+
; EMPTY: <BLOCKINFO_BLOCK/>
16+
; EMPTY-NEXT: <Metadata NumWords=1 BlockCodeSize=2>
17+
; EMPTY-NEXT: <Version op0=1/>
18+
; EMPTY-NEXT: </Metadata>
19+
20+
; VALID: <BLOCKINFO_BLOCK/>
21+
; VALID-NEXT: <Metadata NumWords=30 BlockCodeSize=2>
22+
; VALID-NEXT: <Version op0=1/>
23+
; VALID-NEXT: <Context NumWords=20 BlockCodeSize=2>
24+
; VALID-NEXT: <GUID op0=1000/>
25+
; VALID-NEXT: <Counters op0=1 op1=2 op2=3/>
26+
; VALID-NEXT: <Context NumWords=5 BlockCodeSize=2>
27+
; VALID-NEXT: <GUID op0=-3/>
28+
; VALID-NEXT: <CalleeIndex op0=1/>
29+
; VALID-NEXT: <Counters op0=6 op1=7 op2=8/>
30+
; VALID-NEXT: </Context>
31+
; VALID-NEXT: <Context NumWords=3 BlockCodeSize=2>
32+
; VALID-NEXT: <GUID op0=2000/>
33+
; VALID-NEXT: <CalleeIndex op0=1/>
34+
; VALID-NEXT: <Counters op0=4 op1=5/>
35+
; VALID-NEXT: </Context>
36+
; VALID-NEXT: <Context NumWords=3 BlockCodeSize=2>
37+
; VALID-NEXT: <GUID op0=3000/>
38+
; VALID-NEXT: <CalleeIndex op0=2/>
39+
; VALID-NEXT: <Counters op0=40 op1=50/>
40+
; VALID-NEXT: </Context>
41+
; VALID-NEXT: </Context>
42+
; VALID-NEXT: <Context NumWords=4 BlockCodeSize=2>
43+
; VALID-NEXT: <GUID op0=-4/>
44+
; VALID-NEXT: <Counters op0=5 op1=9 op2=10/>
45+
; VALID-NEXT: </Context>
46+
; VALID-NEXT: </Metadata>

llvm/tools/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ add_llvm_tool_subdirectory(lto)
3232
add_llvm_tool_subdirectory(gold)
3333
add_llvm_tool_subdirectory(llvm-ar)
3434
add_llvm_tool_subdirectory(llvm-config)
35+
add_llvm_tool_subdirectory(llvm-ctxprof-util)
3536
add_llvm_tool_subdirectory(llvm-lto)
3637
add_llvm_tool_subdirectory(llvm-profdata)
3738

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
set(LLVM_LINK_COMPONENTS
2+
Core
3+
Object
4+
ProfileData
5+
Support
6+
)
7+
8+
add_llvm_tool(llvm-ctxprof-util
9+
llvm-ctxprof-util.cpp
10+
11+
DEPENDS
12+
intrinsics_gen
13+
GENERATE_DRIVER
14+
)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//===--- PGOCtxProfJSONReader.h - JSON format ------------------*- C++ -*-===//
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+
/// \file
10+
///
11+
/// JSON format for the contextual profile for testing.
12+
///
13+
//===----------------------------------------------------------------------===//
14+
15+
#include "llvm/ADT/STLExtras.h"
16+
#include "llvm/IR/GlobalValue.h"
17+
#include "llvm/ProfileData/CtxInstrContextNode.h"
18+
#include "llvm/ProfileData/PGOCtxProfWriter.h"
19+
#include "llvm/Support/CommandLine.h"
20+
#include "llvm/Support/Error.h"
21+
#include "llvm/Support/ErrorHandling.h"
22+
#include "llvm/Support/JSON.h"
23+
#include "llvm/Support/LLVMDriver.h"
24+
#include "llvm/Support/MemoryBuffer.h"
25+
#include "llvm/Support/raw_ostream.h"
26+
27+
using namespace llvm;
28+
29+
static cl::SubCommand FromJSON("fromJSON", "Convert from json");
30+
31+
static cl::opt<std::string> InputFilename(
32+
"input", cl::value_desc("input"), cl::init("-"),
33+
cl::desc(
34+
"Input file. The format is an array of contexts.\n"
35+
"Each context is a dictionary with the following keys:\n"
36+
"'Guid', mandatory. The value is a 64-bit integer.\n"
37+
"'Counters', mandatory. An array of 32-bit ints. These are the "
38+
"counter values.\n"
39+
"'Contexts', optional. An array containing arrays of contexts. The "
40+
"context array at a position 'i' is the set of callees at that "
41+
"callsite index. Use an empty array to indicate no callees."),
42+
cl::sub(FromJSON));
43+
44+
static cl::opt<std::string> OutputFilename("output", cl::value_desc("output"),
45+
cl::init("-"),
46+
cl::desc("Output file"),
47+
cl::sub(FromJSON));
48+
49+
namespace {
50+
// A structural representation of the JSON input.
51+
struct DeserializableCtx {
52+
GlobalValue::GUID Guid = 0;
53+
std::vector<uint64_t> Counters;
54+
std::vector<std::vector<DeserializableCtx>> Callsites;
55+
};
56+
57+
ctx_profile::ContextNode *
58+
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
59+
const std::vector<DeserializableCtx> &DCList);
60+
61+
// Convert a DeserializableCtx into a ContextNode, potentially linking it to
62+
// its sibling (e.g. callee at same callsite) "Next".
63+
ctx_profile::ContextNode *
64+
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
65+
const DeserializableCtx &DC,
66+
ctx_profile::ContextNode *Next = nullptr) {
67+
auto AllocSize = ctx_profile::ContextNode::getAllocSize(DC.Counters.size(),
68+
DC.Callsites.size());
69+
auto *Mem = Nodes.emplace_back(std::make_unique<char[]>(AllocSize)).get();
70+
std::memset(Mem, 0, AllocSize);
71+
auto *Ret = new (Mem) ctx_profile::ContextNode(DC.Guid, DC.Counters.size(),
72+
DC.Callsites.size(), Next);
73+
std::memcpy(Ret->counters(), DC.Counters.data(),
74+
sizeof(uint64_t) * DC.Counters.size());
75+
for (const auto &[I, DCList] : llvm::enumerate(DC.Callsites))
76+
Ret->subContexts()[I] = createNode(Nodes, DCList);
77+
return Ret;
78+
}
79+
80+
// Convert a list of DeserializableCtx into a linked list of ContextNodes.
81+
ctx_profile::ContextNode *
82+
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
83+
const std::vector<DeserializableCtx> &DCList) {
84+
ctx_profile::ContextNode *List = nullptr;
85+
for (const auto &DC : DCList)
86+
List = createNode(Nodes, DC, List);
87+
return List;
88+
}
89+
} // namespace
90+
91+
namespace llvm {
92+
namespace json {
93+
// Hook into the JSON deserialization.
94+
bool fromJSON(const Value &E, DeserializableCtx &R, Path P) {
95+
json::ObjectMapper Mapper(E, P);
96+
return Mapper && Mapper.map("Guid", R.Guid) &&
97+
Mapper.map("Counters", R.Counters) &&
98+
Mapper.mapOptional("Callsites", R.Callsites);
99+
}
100+
} // namespace json
101+
} // namespace llvm
102+
103+
// Save the bitstream profile from the JSON representation.
104+
Error convertFromJSON() {
105+
auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFilename);
106+
if (!BufOrError)
107+
return createFileError(InputFilename, BufOrError.getError());
108+
auto P = json::parse(BufOrError.get()->getBuffer());
109+
if (!P)
110+
return P.takeError();
111+
112+
std::vector<DeserializableCtx> DCList;
113+
json::Path::Root R("");
114+
if (!fromJSON(*P, DCList, R))
115+
return R.getError();
116+
// Nodes provides memory backing for the ContextualNodes.
117+
std::vector<std::unique_ptr<char[]>> Nodes;
118+
std::error_code EC;
119+
raw_fd_stream Out(OutputFilename, EC);
120+
if (EC)
121+
return createStringError(EC, "failed to open output");
122+
PGOCtxProfileWriter Writer(Out);
123+
for (const auto &DC : DCList) {
124+
auto *TopList = createNode(Nodes, DC);
125+
if (!TopList)
126+
return createStringError(
127+
"Unexpected error converting internal structure to ctx profile");
128+
Writer.write(*TopList);
129+
}
130+
if (EC)
131+
return createStringError(EC, "failed to write output");
132+
return Error::success();
133+
}
134+
135+
int llvm_ctxprof_util_main(int argc, char **argv, const llvm::ToolContext &) {
136+
cl::ParseCommandLineOptions(argc, argv, "LLVM Contextual Profile Utils\n");
137+
ExitOnError ExitOnErr("llvm-ctxprof-util: ");
138+
if (FromJSON) {
139+
if (auto E = convertFromJSON()) {
140+
handleAllErrors(std::move(E), [&](const ErrorInfoBase &E) {
141+
E.log(errs());
142+
errs() << "\n";
143+
});
144+
return 1;
145+
}
146+
return 0;
147+
}
148+
llvm_unreachable("Unknown subcommands should have been handled by the "
149+
"command line parser.");
150+
}

0 commit comments

Comments
 (0)