Skip to content

[ctx_prof] test tool: generate ctxprof bistream from json #100379

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

Merged
merged 5 commits into from
Jul 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions llvm/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -76,6 +76,7 @@ set(LLVM_TEST_DEPENDS
llvm-cfi-verify
llvm-config
llvm-cov
llvm-ctxprof-util
llvm-cvtres
llvm-cxxdump
llvm-cxxfilt
1 change: 1 addition & 0 deletions llvm/test/lit.cfg.py
Original file line number Diff line number Diff line change
@@ -182,6 +182,7 @@ def get_asan_rtlib():
"llvm-bitcode-strip",
"llvm-config",
"llvm-cov",
"llvm-ctxprof-util",
"llvm-cxxdump",
"llvm-cvtres",
"llvm-debuginfod-find",
1 change: 1 addition & 0 deletions llvm/test/tools/llvm-ctxprof-util/Inputs/bad.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{]
1 change: 1 addition & 0 deletions llvm/test/tools/llvm-ctxprof-util/Inputs/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[{
"Guid": 123,
"Counters": [1, 2],
"Callsites":
[
{"Guid": 1}
]
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{
"Guid": 1231
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{}]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
47 changes: 47 additions & 0 deletions llvm/test/tools/llvm-ctxprof-util/Inputs/valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[
{
"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
]
}
]
22 changes: 22 additions & 0 deletions llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util-negative.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
; RUN: not llvm-ctxprof-util nofile.json 2>&1 | FileCheck %s --check-prefix=NO_CMD
; RUN: not llvm-ctxprof-util invalidCmd --input nofile.json 2>&1 | FileCheck %s --check-prefix=INVALID_CMD
; RUN: not llvm-ctxprof-util fromJSON nofile.json 2>&1 | FileCheck %s --check-prefix=NO_FLAG
; RUN: not llvm-ctxprof-util fromJSON --input nofile.json 2>&1 | FileCheck %s --check-prefix=NO_FILE
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/bad.json 2>&1 | FileCheck %s --check-prefix=BAD_JSON
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-no-vector.json 2>&1 | FileCheck %s --check-prefix=NO_VECTOR
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-no-ctx.json 2>&1 | FileCheck %s --check-prefix=NO_CTX
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-no-counters.json 2>&1 | FileCheck %s --check-prefix=NO_COUNTERS
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/invalid-bad-subctx.json 2>&1 | FileCheck %s --check-prefix=BAD_SUBCTX
; RUN: rm -rf %t
; RUN: not llvm-ctxprof-util fromJSON --input %S/Inputs/valid.json --output %t/output.bitstream 2>&1 | FileCheck %s --check-prefix=NO_DIR

; NO_CMD: Unknown subcommand 'nofile.json'
; INVALID_CMD: Unknown subcommand 'invalidCmd'
; NO_FLAG: Unknown command line argument 'nofile.json'.
; NO_FILE: 'nofile.json': No such file or directory
; BAD_JSON: Expected object key
; NO_VECTOR: expected array
; NO_CTX: missing value at (root)[0].Guid
; NO_COUNTERS: missing value at (root)[0].Counters
; BAD_SUBCTX: expected array at (root)[0].Callsites[0]
; NO_DIR: failed to open output
46 changes: 46 additions & 0 deletions llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
; RUN: mkdir -p %t
; RUN: llvm-ctxprof-util fromJSON --input %S/Inputs/empty.json -output %t/empty.bitstream
; RUN: llvm-bcanalyzer --dump %t/empty.bitstream | FileCheck %s --check-prefix=EMPTY

; RUN: llvm-ctxprof-util fromJSON --input %S/Inputs/valid.json -output %t/valid.bitstream

; For the valid case, check against a reference output.
; Note that uint64_t are printed as signed values by llvm-bcanalyzer:
; * 18446744073709551613 in json is -3 in the output
; * 18446744073709551612 in json is -4 in the output
; Also we have no callee/context at index 0, 2 callsites for index 1, and one for
; index 2.
; RUN: llvm-bcanalyzer --dump %t/valid.bitstream | FileCheck %s --check-prefix=VALID

; EMPTY: <BLOCKINFO_BLOCK/>
; EMPTY-NEXT: <Metadata NumWords=1 BlockCodeSize=2>
; EMPTY-NEXT: <Version op0=1/>
; EMPTY-NEXT: </Metadata>

; VALID: <BLOCKINFO_BLOCK/>
; VALID-NEXT: <Metadata NumWords=30 BlockCodeSize=2>
; VALID-NEXT: <Version op0=1/>
; VALID-NEXT: <Context NumWords=20 BlockCodeSize=2>
; VALID-NEXT: <GUID op0=1000/>
; VALID-NEXT: <Counters op0=1 op1=2 op2=3/>
; VALID-NEXT: <Context NumWords=5 BlockCodeSize=2>
; VALID-NEXT: <GUID op0=-3/>
; VALID-NEXT: <CalleeIndex op0=1/>
; VALID-NEXT: <Counters op0=6 op1=7 op2=8/>
; VALID-NEXT: </Context>
; VALID-NEXT: <Context NumWords=3 BlockCodeSize=2>
; VALID-NEXT: <GUID op0=2000/>
; VALID-NEXT: <CalleeIndex op0=1/>
; VALID-NEXT: <Counters op0=4 op1=5/>
; VALID-NEXT: </Context>
; VALID-NEXT: <Context NumWords=3 BlockCodeSize=2>
; VALID-NEXT: <GUID op0=3000/>
; VALID-NEXT: <CalleeIndex op0=2/>
; VALID-NEXT: <Counters op0=40 op1=50/>
; VALID-NEXT: </Context>
; VALID-NEXT: </Context>
; VALID-NEXT: <Context NumWords=4 BlockCodeSize=2>
; VALID-NEXT: <GUID op0=-4/>
; VALID-NEXT: <Counters op0=5 op1=9 op2=10/>
; VALID-NEXT: </Context>
; VALID-NEXT: </Metadata>
1 change: 1 addition & 0 deletions llvm/tools/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ add_llvm_tool_subdirectory(lto)
add_llvm_tool_subdirectory(gold)
add_llvm_tool_subdirectory(llvm-ar)
add_llvm_tool_subdirectory(llvm-config)
add_llvm_tool_subdirectory(llvm-ctxprof-util)
add_llvm_tool_subdirectory(llvm-lto)
add_llvm_tool_subdirectory(llvm-profdata)

14 changes: 14 additions & 0 deletions llvm/tools/llvm-ctxprof-util/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
set(LLVM_LINK_COMPONENTS
Core
Object
ProfileData
Support
)

add_llvm_tool(llvm-ctxprof-util
llvm-ctxprof-util.cpp

DEPENDS
intrinsics_gen
GENERATE_DRIVER
)
150 changes: 150 additions & 0 deletions llvm/tools/llvm-ctxprof-util/llvm-ctxprof-util.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//===--- PGOCtxProfJSONReader.h - JSON format ------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// JSON format for the contextual profile for testing.
///
//===----------------------------------------------------------------------===//

#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/ProfileData/CtxInstrContextNode.h"
#include "llvm/ProfileData/PGOCtxProfWriter.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/LLVMDriver.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

static cl::SubCommand FromJSON("fromJSON", "Convert from json");

static cl::opt<std::string> InputFilename(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in #89884 (comment) , cl:: is incompatible with GENERATE_DRIVER. Either remove GENERATE_DRIVER, or move this to Opt.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, I'll take care of of it. Thanks.

"input", cl::value_desc("input"), cl::init("-"),
cl::desc(
"Input file. The format is an array of contexts.\n"
"Each context is a dictionary with the following keys:\n"
"'Guid', mandatory. The value is a 64-bit integer.\n"
"'Counters', mandatory. An array of 32-bit ints. These are the "
"counter values.\n"
"'Contexts', optional. An array containing arrays of contexts. The "
"context array at a position 'i' is the set of callees at that "
"callsite index. Use an empty array to indicate no callees."),
cl::sub(FromJSON));

static cl::opt<std::string> OutputFilename("output", cl::value_desc("output"),
cl::init("-"),
cl::desc("Output file"),
cl::sub(FromJSON));

namespace {
// A structural representation of the JSON input.
struct DeserializableCtx {
GlobalValue::GUID Guid = 0;
std::vector<uint64_t> Counters;
std::vector<std::vector<DeserializableCtx>> Callsites;
};

ctx_profile::ContextNode *
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
const std::vector<DeserializableCtx> &DCList);

// Convert a DeserializableCtx into a ContextNode, potentially linking it to
// its sibling (e.g. callee at same callsite) "Next".
ctx_profile::ContextNode *
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
const DeserializableCtx &DC,
ctx_profile::ContextNode *Next = nullptr) {
auto AllocSize = ctx_profile::ContextNode::getAllocSize(DC.Counters.size(),
DC.Callsites.size());
auto *Mem = Nodes.emplace_back(std::make_unique<char[]>(AllocSize)).get();
std::memset(Mem, 0, AllocSize);
auto *Ret = new (Mem) ctx_profile::ContextNode(DC.Guid, DC.Counters.size(),
DC.Callsites.size(), Next);
std::memcpy(Ret->counters(), DC.Counters.data(),
sizeof(uint64_t) * DC.Counters.size());
for (const auto &[I, DCList] : llvm::enumerate(DC.Callsites))
Ret->subContexts()[I] = createNode(Nodes, DCList);
return Ret;
}

// Convert a list of DeserializableCtx into a linked list of ContextNodes.
ctx_profile::ContextNode *
createNode(std::vector<std::unique_ptr<char[]>> &Nodes,
const std::vector<DeserializableCtx> &DCList) {
ctx_profile::ContextNode *List = nullptr;
for (const auto &DC : DCList)
List = createNode(Nodes, DC, List);
return List;
}
} // namespace

namespace llvm {
namespace json {
// Hook into the JSON deserialization.
bool fromJSON(const Value &E, DeserializableCtx &R, Path P) {
json::ObjectMapper Mapper(E, P);
return Mapper && Mapper.map("Guid", R.Guid) &&
Mapper.map("Counters", R.Counters) &&
Mapper.mapOptional("Callsites", R.Callsites);
}
} // namespace json
} // namespace llvm

// Save the bitstream profile from the JSON representation.
Error convertFromJSON() {
auto BufOrError = MemoryBuffer::getFileOrSTDIN(InputFilename);
if (!BufOrError)
return createFileError(InputFilename, BufOrError.getError());
auto P = json::parse(BufOrError.get()->getBuffer());
if (!P)
return P.takeError();

std::vector<DeserializableCtx> DCList;
json::Path::Root R("");
if (!fromJSON(*P, DCList, R))
return R.getError();
// Nodes provides memory backing for the ContextualNodes.
std::vector<std::unique_ptr<char[]>> Nodes;
std::error_code EC;
raw_fd_stream Out(OutputFilename, EC);
if (EC)
return createStringError(EC, "failed to open output");
PGOCtxProfileWriter Writer(Out);
for (const auto &DC : DCList) {
auto *TopList = createNode(Nodes, DC);
if (!TopList)
return createStringError(
"Unexpected error converting internal structure to ctx profile");
Writer.write(*TopList);
}
if (EC)
return createStringError(EC, "failed to write output");
return Error::success();
}

int llvm_ctxprof_util_main(int argc, char **argv, const llvm::ToolContext &) {
cl::ParseCommandLineOptions(argc, argv, "LLVM Contextual Profile Utils\n");
ExitOnError ExitOnErr("llvm-ctxprof-util: ");
if (FromJSON) {
if (auto E = convertFromJSON()) {
handleAllErrors(std::move(E), [&](const ErrorInfoBase &E) {
E.log(errs());
errs() << "\n";
});
return 1;
}
return 0;
}
llvm_unreachable("Unknown subcommands should have been handled by the "
"command line parser.");
}