Skip to content

Commit d35686b

Browse files
authored
[llvm-exegesis] Print generated assembly snippet (llvm#142540)
Debug generated disassembly by passing argument `debug-only="print-gen-assembly"` or `debug-only=preview-gen-assembly` of exegesis call. `--debug-only="print-gen-assembly"` debugs the whole generated assembly snippet . `--debug-only=preview-gen-assembly` debugs the initial 10 instructions and ending 3 lines. Thus, We can in glance see the initial setup code like registers setup and instruction followed by truncated middle and finally print out the last 3 instructions. This helps us look into assembly that exegesis is execution in hardware, Thus, it is simply functionally alias to separate objdump command on the dumped object file.
1 parent 91fff70 commit d35686b

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
REQUIRES: aarch64-registered-target, asserts
2+
3+
RUN: llvm-exegesis -mcpu=neoverse-v2 --use-dummy-perf-counters --min-instructions=100 --mode=latency --debug-only=preview-gen-assembly --opcode-name=ADDVv4i16v 2>&1 | FileCheck %s -check-prefix=PREVIEW
4+
5+
PREVIEW: Generated assembly snippet:
6+
PREVIEW-NEXT: ```
7+
PREVIEW: {{[04]}}: {{.*}} movi d{{[0-9]+}}, #0000000000000000
8+
PREVIEW-NEXT: {{[48]}}: {{.*}} addv h{{[0-9]+}}, v{{[0-9]+}}.4h
9+
PREVIEW: ... ({{[0-9]+}} more instructions)
10+
PREVIEW-NEXT: {{.*}} addv h{{[0-9]+}}, v{{[0-9]+}}.4h
11+
PREVIEW: {{.*}} ret
12+
PREVIEW-NEXT:```
13+
14+
RUN: llvm-exegesis -mcpu=neoverse-v2 --use-dummy-perf-counters --min-instructions=100 --mode=latency --debug-only=print-gen-assembly --opcode-name=ADDVv4i16v 2>&1 | FileCheck %s -check-prefix=PRINT
15+
PRINT: Generated assembly snippet:
16+
PRINT-NEXT: ```
17+
PRINT: {{[04]}}: {{.*}} movi d{{[0-9]+}}, #0000000000000000
18+
PRINT-NEXT: {{[48]}}: {{.*}} addv h{{[0-9]+}}, v{{[0-9]+}}.4h
19+
PRINT-NEXT: {{.*}} addv h{{[0-9]+}}, v{{[0-9]+}}.4h
20+
PRINT: {{.*}} ret
21+
PRINT-NEXT:```

llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "BenchmarkRunner.h"
1010
#include "Assembler.h"
11+
#include "DisassemblerHelper.h"
1112
#include "Error.h"
1213
#include "MCInstrDescView.h"
1314
#include "MmapUtils.h"
@@ -20,6 +21,7 @@
2021
#include "llvm/ADT/Twine.h"
2122
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
2223
#include "llvm/Support/CrashRecoveryContext.h"
24+
#include "llvm/Support/Debug.h"
2325
#include "llvm/Support/Error.h"
2426
#include "llvm/Support/FileSystem.h"
2527
#include "llvm/Support/MemoryBuffer.h"
@@ -588,6 +590,89 @@ class SubProcessFunctionExecutorImpl
588590
const std::optional<int> BenchmarkProcessCPU;
589591
};
590592
#endif // __linux__
593+
594+
// Structure to hold instruction information for assembly printing
595+
struct InstructionInfo {
596+
std::string Text;
597+
uint64_t Address;
598+
std::string HexBytes;
599+
};
600+
601+
// Helper function to print generated assembly snippets
602+
void printInstructions(const std::vector<InstructionInfo> &Instructions,
603+
int InitialLinesCount, int LastLinesCount) {
604+
int N = Instructions.size();
605+
dbgs() << "Generated assembly snippet:\n```\n";
606+
607+
// Print initial lines
608+
for (int i = 0; i < InitialLinesCount; ++i)
609+
dbgs() << format_hex_no_prefix(Instructions[i].Address, 0) << ":\t"
610+
<< Instructions[i].HexBytes << Instructions[i].Text << '\n';
611+
612+
// Show truncation message if needed
613+
int SkippedInstructions = N - InitialLinesCount - LastLinesCount;
614+
if (SkippedInstructions > 0)
615+
dbgs() << "...\t(" << SkippedInstructions << " more instructions)\n";
616+
617+
// Print last min(PreviewLast, N - PreviewFirst) lines
618+
int LastLinesToPrint = std::min(
619+
LastLinesCount, N > InitialLinesCount ? N - InitialLinesCount : 0);
620+
for (int i = N - LastLinesToPrint; i < N; ++i)
621+
dbgs() << format_hex_no_prefix(Instructions[i].Address, 0) << ":\t"
622+
<< Instructions[i].HexBytes << Instructions[i].Text << '\n';
623+
dbgs() << "```\n";
624+
}
625+
626+
// Function to extract and print assembly from snippet
627+
Error printAssembledSnippet(const LLVMState &State,
628+
const SmallString<0> &Snippet) {
629+
// Extract the actual function bytes from the object file
630+
std::vector<uint8_t> FunctionBytes;
631+
if (auto Err = getBenchmarkFunctionBytes(Snippet, FunctionBytes))
632+
return make_error<Failure>("Failed to extract function bytes: " +
633+
toString(std::move(Err)));
634+
635+
// Decode all instructions first
636+
DisassemblerHelper DisHelper(State);
637+
uint64_t Address = 0;
638+
std::vector<InstructionInfo> Instructions;
639+
const size_t FunctionBytesSize = FunctionBytes.size();
640+
641+
while (Address < FunctionBytesSize) {
642+
MCInst Inst;
643+
uint64_t Size;
644+
ArrayRef<uint8_t> Bytes(FunctionBytes.data() + Address,
645+
FunctionBytesSize - Address);
646+
647+
if (!DisHelper.decodeInst(Inst, Size, Bytes)) {
648+
Instructions.push_back({"<decode error>", Address, ""});
649+
break;
650+
}
651+
652+
// Format instruction text
653+
std::string InstStr;
654+
raw_string_ostream OS(InstStr);
655+
DisHelper.printInst(&Inst, OS);
656+
657+
// Create hex string for this instruction (big-endian order)
658+
std::string HexStr;
659+
raw_string_ostream HexOS(HexStr);
660+
for (int i = Size - 1; i >= 0; --i)
661+
HexOS << format_hex_no_prefix(Bytes[i], 2);
662+
663+
Instructions.push_back({OS.str(), Address, HexOS.str()});
664+
Address += Size;
665+
}
666+
667+
#undef DEBUG_TYPE
668+
#define DEBUG_TYPE "preview-gen-assembly"
669+
LLVM_DEBUG(printInstructions(Instructions, 10, 3));
670+
#undef DEBUG_TYPE
671+
#define DEBUG_TYPE "print-gen-assembly"
672+
LLVM_DEBUG(printInstructions(Instructions, Instructions.size(), 0));
673+
#undef DEBUG_TYPE
674+
return Error::success();
675+
}
591676
} // namespace
592677

593678
Expected<SmallString<0>> BenchmarkRunner::assembleSnippet(
@@ -655,6 +740,10 @@ BenchmarkRunner::getRunnableConfiguration(
655740
if (Error E = Snippet.takeError())
656741
return std::move(E);
657742
RC.ObjectFile = getObjectFromBuffer(*Snippet);
743+
744+
// Print the assembled snippet by disassembling the binary data
745+
if (Error E = printAssembledSnippet(State, *Snippet))
746+
return std::move(E);
658747
}
659748

660749
return std::move(RC);

llvm/tools/llvm-exegesis/llvm-exegesis.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,8 @@ void benchmarkMain() {
482482
InitializeAllExegesisTargets();
483483
#define LLVM_EXEGESIS(TargetName) \
484484
LLVMInitialize##TargetName##AsmPrinter(); \
485-
LLVMInitialize##TargetName##AsmParser();
485+
LLVMInitialize##TargetName##AsmParser(); \
486+
LLVMInitialize##TargetName##Disassembler();
486487
#include "llvm/Config/TargetExegesis.def"
487488

488489
const LLVMState State = ExitOnErr(

0 commit comments

Comments
 (0)