LLVM-EXEGESIS(1)

๐ŸŽ€ LLVM-EXEGESIS ๐ŸŽ€

okay bestie, buckle up, because this is llvm-exegesis from the LLVM manual pages, and she is that girl ๐Ÿ’…


โœจ NAME โœจ

llvm-exegesis โ€” literally the LLVM Machine Instruction Benchmark ๐Ÿ‘‘


โœจ SYNOPSIS โœจ

llvm-exegesis [options]

โœจ DESCRIPTION โœจ

so llvm-exegesis is a benchmarking tool, right? and what she does is take all the info LLVM already has and use it to measure host machine instruction characteristics โ€” think latency, throughput, port decomposition, the whole vibe check ๐Ÿ”ฅ

here's the tea: you give her an LLVM opcode name plus a benchmarking mode, and she generates a code snippet that makes execution as serial (or, respectively, as parallel) as possible ๐Ÿงš that way we can measure the latency (or, respectively, the inverse throughput / uop decomposition) of the instruction. iconic behavior, honestly.

the code snippet gets jitted and โ€” unless you specifically asked her not to โ€” executed on the host subtarget. the time taken (or, respectively, resource usage) is measured using hardware performance counters. and the result? printed out as YAML to standard output ๐Ÿ“

the main character energy goal of this whole tool is to automatically (in)validate LLVM's TableDef scheduling models โœจ and to that end, she also provides analysis of the results.

oh, and llvm-exegesis can also benchmark arbitrary user-provided code snippets. no cap ๐Ÿซถ


โœจ SUPPORTED PLATFORMS โœจ

bestie, listen, she's kind of picky about where she'll actually benchmark ๐Ÿ’€ right now llvm-exegesis only supports these on Linux for benchmarking:

  • X86 (64-bit only)
  • ARM (AArch64 only, and snippet generation is sparse)
  • MIPS
  • PowerPC (PowerPC64LE only)
  • RISC-V (RV64I/E and RV32I/E)

and lowkey, not all benchmarking functionality is guaranteed to work on every platform ๐Ÿ™ˆ

BUT โ€” good news โ€” llvm-exegesis also has a separate analysis mode that's supported on every platform that LLVM is on ๐ŸŒˆ so she's flexible, just not that flexible.

now, to enable benchmarking in llvm-exegesis, LLVM must be configured and built with LLVM_ENABLE_LIBPFM enabled, because llvm-exegesis depends on libpfm4 for accessing performance counters. benchmarking may fail if the target CPU is unsupported by libpfm ๐Ÿ˜ญ this can be verified by setting the LIBPFM_VERBOSE and LIBPFM_DEBUG environment variables to enable verbose or debug mode for libpfm.

if libpfm is installed in a non-standard directory, LLVM can be configured to locate the necessary library and header files by setting the LIBRARY_PATH, C_INCLUDE_PATH, and CPLUS_INCLUDE_PATH environment variables. additionally, LD_LIBRARY_PATH should be set so that llvm-exegesis can locate the libpfm library during execution ๐Ÿซถ


๐ŸŒธ SNIPPET ANNOTATIONS ๐ŸŒธ

llvm-exegesis supports benchmarking arbitrary snippets of assembly, which is honestly so fun of her. however โ€” and this is the important part โ€” benchmarking these snippets often requires some setup so that they can execute properly. llvm-exegesis has five annotations and some additional utilities to help with setup so that snippets can be benchmarked properly โœจ

  • LLVM-EXEGESIS-DEFREG <register name> โ€” adding this annotation to the text assembly snippet to be benchmarked marks the register as requiring a definition. a value will automatically be provided unless a second parameter, a hex value, is passed in. this is done with the LLVM-EXEGESIS-DEFREG <register name> <hex value> format. <hex value> is a bit pattern used to fill the register. if it is a value smaller than the register, it is sign extended to match the size of the register.
  • LLVM-EXEGESIS-LIVEIN <register name> โ€” this annotation allows specifying registers that should keep their value upon starting the benchmark. values can be passed through registers from the benchmarking setup in some cases. the registers and the values assigned to them that can be utilized in the benchmarking script with a LLVM-EXEGESIS-LIVEIN are as follows:
    • Scratch memory register โ€” the specific register that this value is put in is platform dependent (e.g., it is the RDI register on X86 Linux). setting this register as a live in ensures that a pointer to a block of memory (1MB) is placed within this register that can be used by the snippet.
  • LLVM-EXEGESIS-MEM-DEF <value name> <size> <value> โ€” this annotation allows specifying memory definitions that can later be mapped into the execution process of a snippet with the LLVM-EXEGESIS-MEM-MAP annotation. each value is named using the <value name> argument so that it can be referenced later within a map annotation. the size is specified in a decimal number of bytes and the value is given in hexadecimal. if the size of the value is less than the specified size, the value will be repeated until it fills the entire section of memory. using this annotation requires using the subprocess execution mode โš ๏ธ
  • LLVM-EXEGESIS-MEM-MAP <value name> <address> โ€” this annotation allows for mapping previously defined memory definitions into the execution context of a process. the value name refers to a previously defined memory definition and the address is a decimal number that specifies the address the memory definition should start at. note that a single memory definition can be mapped multiple times. using this annotation requires the subprocess execution mode โš ๏ธ
  • LLVM-EXEGESIS-SNIPPET-ADDRESS <address> โ€” this annotation allows for setting the address where the beginning of the snippet to be executed will be mapped in at. the address is given in hexadecimal. note that the snippet also includes setup code, so the instruction exactly at the specified address will not be the first instruction in the snippet. using this annotation requires the subprocess execution mode. this is useful in cases where the memory accessed by the snippet depends on the location of the snippet, like RIP-relative addressing ๐Ÿง 
  • LLVM-EXEGESIS-LOOP-REGISTER <register name> โ€” this annotation specifies the loop register to use for keeping track of the current iteration when using the loop repetition mode. llvm-exegesis needs to keep track of the current loop iteration within the loop repetition mode in a performant manner (i.e., no memory accesses), and uses a register to do this. this register has an architecture specific default (e.g., R8 on X86), but this might conflict with some snippets. this annotation allows changing the register to prevent interference between the loop index register and the snippet ๐Ÿ’œ

๐Ÿ”ฅ EXAMPLE 1: BENCHMARKING INSTRUCTIONS ๐Ÿ”ฅ

assume you have an X86-64 machine (iykyk). to measure the latency of a single instruction, run:

$ llvm-exegesis --mode=latency --opcode-name=ADD64rr

measuring the uop decomposition or inverse throughput of an instruction works similarly:

$ llvm-exegesis --mode=uops --opcode-name=ADD64rr
$ llvm-exegesis --mode=inverse_throughput --opcode-name=ADD64rr

the output is a YAML document (the default is to write to stdout, but you can redirect the output to a file using --benchmarks-file):

---
key:
  opcode_name:     ADD64rr
  mode:            latency
  config:          ''
cpu_name:        haswell
llvm_triple:     x86_64-unknown-linux-gnu
num_repetitions: 10000
measurements:
  - { key: latency, value: 1.0058, debug_string: '' }
error:           ''
info:            'explicit self cycles, selecting one aliasing configuration.
Snippet:
ADD64rr R8, R8, R10
'
...

to measure the latency of all instructions for the host architecture, run:

$ llvm-exegesis --mode=latency --opcode-index=-1

๐Ÿฆ‹ EXAMPLE 2: BENCHMARKING A CUSTOM CODE SNIPPET ๐Ÿฆ‹

to measure the latency/uops of a custom piece of code, you can specify the snippets-file option (- reads from standard input):

$ echo "vzeroupper" | llvm-exegesis --mode=uops --snippets-file=-

real-life code snippets typically depend on registers or memory. llvm-exegesis checks the liveliness of registers (i.e. any register use has a corresponding def or is a "live in"). if your code depends on the value of some registers, you need to use snippet annotations to ensure setup is performed properly ๐Ÿซถ

for example, the following code snippet depends on the values of XMM1 (which will be set by the tool) and the memory buffer passed in RDI (live in):

# LLVM-EXEGESIS-LIVEIN RDI
# LLVM-EXEGESIS-DEFREG XMM1 42
vmulps        (%rdi), %xmm1, %xmm2
vhaddps       %xmm2, %xmm2, %xmm3
addq $0x10, %rdi

๐Ÿ“ EXAMPLE 3: BENCHMARKING WITH MEMORY ANNOTATIONS ๐Ÿ“

some snippets require memory setup in specific places to execute without crashing ๐Ÿ’€ setting up memory can be accomplished with the LLVM-EXEGESIS-MEM-DEF and LLVM-EXEGESIS-MEM-MAP annotations. to execute the following snippet:

movq $8192, %rax
movq (%rax), %rdi

we need to have at least eight bytes of memory allocated starting 0x2000. we can create the necessary execution environment with the following annotations added to the snippet:

# LLVM-EXEGESIS-MEM-DEF test1 4096 7fffffff
# LLVM-EXEGESIS-MEM-MAP test1 8192

movq $8192, %rax
movq (%rax), %rdi

๐ŸŒˆ EXAMPLE 4: ANALYSIS ๐ŸŒˆ

assuming you have a set of benchmarked instructions (either latency or uops) as YAML in file /tmp/benchmarks.yaml, you can analyze the results using the following command:

$ llvm-exegesis --mode=analysis \
--benchmarks-file=/tmp/benchmarks.yaml \
--analysis-clusters-output-file=/tmp/clusters.csv \
--analysis-inconsistencies-output-file=/tmp/inconsistencies.html

this will group the instructions into clusters with the same performance characteristics. the clusters will be written out to /tmp/clusters.csv in the following format:

cluster_id,opcode_name,config,sched_class
...
2,ADD32ri8_DB,,WriteALU,1.00
2,ADD32ri_DB,,WriteALU,1.01
2,ADD32rr,,WriteALU,1.01
2,ADD32rr_DB,,WriteALU,1.00
2,ADD32rr_REV,,WriteALU,1.00
2,ADD64i32,,WriteALU,1.01
2,ADD64ri32,,WriteALU,1.01
2,MOVSX64rr32,,BSWAP32r_BSWAP64r_MOVSX64rr32,1.00
2,VPADDQYrr,,VPADDBYrr_VPADDDYrr_VPADDQYrr_VPADDWYrr_VPSUBBYrr_VPSUBDYrr_VPSUBQYrr_VPSUBWYrr,1.02
2,VPSUBQYrr,,VPADDBYrr_VPADDDYrr_VPADDQYrr_VPADDWYrr_VPSUBBYrr_VPSUBDYrr_VPSUBQYrr_VPSUBWYrr,1.01
2,ADD64ri8,,WriteALU,1.00
2,SETBr,,WriteSETCC,1.01
...

llvm-exegesis will also analyze the clusters to point out inconsistencies in the scheduling information. the output is an html file. for example, /tmp/inconsistencies.html will contain messages like the following : [image]

heads up bestie ๐Ÿซถ note that the scheduling class names will be resolved only when llvm-exegesis is compiled in debug mode, else only the class id will be shown. this does not invalidate any of the analysis results though โœจ

๐Ÿ’… OPTIONS ๐Ÿ’…

  • --help โ€” print a summary of command line options.
  • --opcode-index=<LLVM opcode index> โ€” specify the opcode to measure, by index. specifying -1 will result in measuring every existing opcode. see example 1 for details. either opcode-index, opcode-name or snippets-file must be set ๐Ÿ‘‘
  • --opcode-name=<opcode name 1>,<opcode name 2>,... โ€” this is how you tell it which opcode you wanna measure, by name bestie ๐Ÿ’–. You can queue up several of them as a comma-separated list โ€” go off. Peep example 1 for the deets. Also, real talk: one of opcode-index, opcode-name or snippets-file must be set, no exceptions, that's the vibe check.
  • --snippets-file=<filename> โ€” points at your very own custom code snippet to measure โœจ. Example 2 has the whole story. And again, either opcode-index, opcode-name or snippets-file must be set โ€” pick one, bestie, it's not optional.
  • --mode=[latency|uops|inverse_throughput|analysis] โ€” picks the run mode ๐ŸŽ€. Heads up though: some modes come with extra requirements and options, it's giving "read the fine print":
    • latency mode can make use of either RDTSC or LBR. latency[LBR] is only available on X86 (at least Skylake). To run in latency mode, a positive value must be specified for x86-lbr-sample-period and --repetition-mode=loop. Both. Together. That's the rule ๐Ÿ’….
    • In analysis mode you also gotta specify at least one of -analysis-clusters-output-file= and -analysis-inconsistencies-output-file= โ€” otherwise it's got nothing to do with itself ๐Ÿ˜ญ.
  • --benchmark-phase=[prepare-snippet|prepare-and-assemble-snippet|assemble-measured-code|measure] โ€” okay so by default, when -mode= is specified, the generated snippet gets executed and measured ๐Ÿซถ. But that means you have to actually be running on the hardware the snippet was generated for, and that hardware has to support performance measurements. If you can't swing that, you can stop at some stage before measuring. Your choices, bestie:
    • prepare-snippet: only generate the minimal instruction sequence. Baby steps ๐ŸŒฑ.
    • prepare-and-assemble-snippet: same as prepare-snippet, but it also dumps an excerpt of the sequence (hex encoded).
    • assemble-measured-code: same as prepare-and-assemble-snippet, but it also creates the full sequence that can be dumped to a file using --dump-object-to-disk.
    • measure: same as assemble-measured-code, but it also runs the measurement. Full send ๐Ÿ”ฅ.
  • --x86-lbr-sample-period=<nBranches/sample> โ€” sets the LBR sampling period, i.e. how many branches go by before we take a sample ๐Ÿฆ‹. When a positive value is specified here and the mode is latency, we use LBRs for measuring. On picking the "right" sampling period: a small value is preferred, but ngl throttling could occur if the sampling is too frequent ๐Ÿ˜ฌ. Also use a prime number so you avoid consistently skipping certain blocks โ€” that's the trick, iykyk.
  • --x86-disable-upper-sse-registers โ€” so using the upper xmm registers (xmm8-xmm15) forces a longer instruction encoding, which may put greater pressure on the frontend fetch and decode stages, potentially reducing the rate that instructions are dispatched to the backend โ€” especially on older hardware ๐Ÿ’€. Comparing baseline results with this mode enabled can help determine the effects of the frontend, and it can be used to improve latency and throughput estimates. Lowkey a glow-up tool for your numbers โœจ.
  • --repetition-mode=[duplicate|loop|min|middle-half-duplicate|middle-half-loop] โ€” this one's the whole personality of your benchmark, let me break it down ๐ŸŽ€:
    • duplicate will create a large, straight line basic block with min-instructions instructions (repeating the snippet min-instructions/snippet size times).
    • loop will, optionally, duplicate the snippet until the loop body contains at least loop-body-size instructions, and then wrap the result in a loop which will execute min-instructions instructions (thus, again, repeating the snippet min-instructions/snippet size times).
    • The loop mode, especially with loop unrolling, tends to better hide the effects of the CPU frontend on architectures that cache decoded instructions โ€” but it consumes a register for counting iterations ๐Ÿ˜ญ.
    • If you're doing an analysis over many opcodes, it may be best to instead use the min mode, which will run each other mode and produce the minimal measured result. Iconic behaviour honestly ๐ŸŒˆ.
    • The middle half repetition modes will either duplicate or run the snippet in a loop depending upon the specific mode. And they run two benchmarks, one twice the length of the first one, then subtract the difference between them to get values without overhead. Math! Slay ๐Ÿงฎ๐Ÿ’….
  • --min-instructions=<Number of instructions> โ€” the target number of executed instructions ๐Ÿ‘‘. Note that the actual repetition count of the snippet will be min-instructions/snippet size. Higher values lead to more accurate measurements, but they lengthen the benchmark โ€” the eternal tradeoff, fr fr.
  • --loop-body-size=<Preferred loop body size> โ€” only effective for -repetition-mode=[loop|min] ๐Ÿฆ‹. Instead of looping over the snippet directly, it first duplicates it so the loop body contains at least this many instructions. This potentially results in the loop body being cached in the CPU Op Cache / Loop Cache, which allows for higher throughput than the CPU decoders. Cached and thriving ๐Ÿ’–.
  • --max-configs-per-opcode=<value> โ€” caps how many configurations can be generated for each opcode ๐ŸŽ€. By default this is 1, meaning we assume a single measurement is enough to characterize an opcode. That might not be true of all instructions though: for example, the performance characteristics of the LEA instruction on X86 depend on the value of assigned registers and immediates ๐Ÿ‘€. Setting a value of -max-configs-per-opcode larger than 1 lets llvm-exegesis explore more configurations to discover if some register or immediate assignments lead to different performance characteristics. Detective mode ๐Ÿ”โœจ.
  • --benchmarks-file=</path/to/file> โ€” the file to read (in analysis mode) or write (in latency/uops/inverse_throughput modes) benchmark results ๐Ÿ’œ. Pass "-" and it uses stdin/stdout. Flexible queen.
  • --analysis-clusters-output-file=</path/to/file> โ€” if provided, write the analysis clusters as CSV to this file ๐Ÿ’…. "-" prints to stdout. By default this analysis is not run โ€” you gotta opt in.
  • --analysis-inconsistencies-output-file=</path/to/file> โ€” if non-empty, write the inconsistencies found during analysis to this file ๐ŸŒธ. - prints to stdout. By default this analysis is not run either.
  • --analysis-filter=[all|reg-only|mem-only] โ€” by default, all benchmark results get analysed, but sometimes it's useful to only look at the ones that do not involve memory, or the other way round ๐Ÿซถ. This option lets you either keep all benchmarks, or filter out (ignore) either all the ones that do involve memory (i.e. involve instructions that may read or write to memory), or the opposite โ€” only keep such benchmarks. Choose your fighter ๐Ÿ’€.
  • --analysis-clustering=[dbscan,naive] โ€” picks the clustering algorithm ๐ŸŒˆ. By default DBSCAN will be used. The naive clustering algorithm is better for doing further work on the -analysis-inconsistencies-output-file= output โ€” it creates one cluster per opcode and checks that the cluster is stable (all points are neighbours). Neighbourly behaviour, we love to see it ๐Ÿซถ.
  • --analysis-numpoints=<dbscan numPoints parameter> โ€” sets the numPoints parameter used for DBSCAN clustering (analysis mode, DBSCAN only) ๐Ÿ’–.
  • --analysis-clustering-epsilon=<dbscan epsilon parameter> โ€” sets the epsilon parameter used for clustering of benchmark points (analysis mode) โœจ.
  • --analysis-inconsistency-epsilon=<epsilon> โ€” sets the epsilon parameter used for detection of when the cluster is different from the LLVM schedule profile values (analysis mode) ๐Ÿ‘‘.
  • --analysis-display-unstable-clusters โ€” so if there's more than one benchmark for an opcode, those benchmarks may end up not being clustered into the same cluster if the measured performance characteristics are different ๐Ÿ˜ญ. By default all such opcodes are filtered out. This flag instead shows only such unstable opcodes. Chaotic spotlight, honestly ๐Ÿ’€.
  • --ignore-invalid-sched-class=false โ€” if set, ignore instructions that do not have a sched class (class idx = 0) ๐Ÿ™ˆ.
  • --mtriple=<triple name> โ€” the target triple ๐ŸŽ€. See -version for available targets.
  • --mcpu=<cpu name> โ€” if set, measure the cpu characteristics using the counters for this CPU ๐Ÿ”ฅ. This is useful when creating new sched models (i.e. when the host CPU is unknown to LLVM). (-mcpu=help for details) โ€” go ask, it'll tell you.
  • --analysis-override-benchmark-triple-and-cpu โ€” by default, llvm-exegesis analyzes the benchmarks for the triple/CPU they were measured for ๐Ÿฆ‹. But if you wanna analyze them for some other combination (specified via -mtriple/-mcpu), you can pass this flag. Main character energy, rewriting the context ๐Ÿ’….
  • --dump-object-to-disk=<filename> โ€” if set, llvm-exegesis will dump the generated code to a temporary file to enable code inspection ๐Ÿ‘€. Disabled by default.
  • --use-dummy-perf-counters โ€” if set, llvm-exegesis will not read any real performance counters and returns a dummy value instead ๐Ÿ™ˆ. This can be used to make sure a snippet doesn't crash when hardware performance counters are unavailable, and also for debugging llvm-exegesis itself. Fake it till you make it โœจ.
  • --execution-mode=[inprocess,subprocess] โ€” says what execution mode to use ๐ŸŽ€. inprocess is the default. subprocess unlocks additional features like memory annotations, but it's currently restricted to X86-64 on Linux. Exclusive club, ngl.
  • --benchmark-repeat-count=<repeat-count> โ€” lets you set the number of times to repeat the measurement when doing latency measurements ๐Ÿ”. By default, llvm-exegesis repeats a latency measurement enough times to balance run-time and noise reduction. Balance is everything, bestie ๐Ÿงš.
  • --validation-counter=[instructions-retired,l1d-cache-load-misses,l1d-cache-store-misses,l1i-cache-load-misses,data-tlb-load-misses,data-tld-store-misses,instruction-tlb-load-misses] โ€” enables the use of validation counters, which measure additional microarchitectural events like cache misses to validate snippet execution conditions ๐ŸŒธ. These events are measured using the perf subsystem in a group with the performance counter used to measure the value of interest. This flag can be specified multiple times to measure multiple events ๐Ÿซถ. The maximum number of validation counters is platform dependent โ€” so like, don't go wild and assume.
  • --benchmark-process-cpu=<cpu id> โ€” specifies the number of the CPU that should be used to run the benchmarking subprocess ๐Ÿ‘‘. When starting the subprocess, llvm-exegesis will set the affinity of the subprocess to only include the specified CPU. This option only works in the subprocess execution mode, periodt ๐Ÿ’….

๐Ÿ’… EXIT STATUS

okay so bestie, here's the vibe check on how llvm-exegesis tells you whether it slayed or flopped:

  • On success it returns 0 โ€” iconic, flawless, no notes. ๐Ÿ’–
  • Otherwise, an error message gets printed to standard error, and the tool returns a non-0 value. So if you see a nonzero exit code, that's your cue to go read stderr, bestie โ€” the answer is literally right there. ๐Ÿ˜ญ
fr fr, always check that exit code. 0 is the only one that means "we ate." Anything else and something went sideways. ๐Ÿซถ

๐Ÿ‘‘ Author

Maintained by the LLVM Team (https://llvm.org/).

Like, the whole squad over at the LLVM Team keeps this thing alive. Respect. ๐Ÿซกโœจ


2003-2026, LLVM Project

That's the whole copyright line, character for character โ€” don't come for me, I'm just the messenger. ๐Ÿ’…


and that's a wrap on the whole page, bestie!! ๐Ÿฆ‹๐ŸŒธ you made it to the very end, which is honestly main character energy. this was page 21 of the manual, dated 2026-05-15, and the whole thing lives under the title LLVM-EXEGESIS(1) โ€” go forth and benchmark your little heart out. slay, periodt, bye!! ๐Ÿ’–โœจ๐ŸŽ€๐Ÿ”ฅ๐Ÿ‘‘

๐Ÿ“ฆ where this page comes from