Compare LLVM IR Modules with llvm-diff-20
You will compare two LLVM IR modules, see whether their function structure matches, and narrow the check to selected global names when a whole-module report is too noisy. The examples use the installed Ubuntu LLVM 20.1.8 build and temporary text IR files. Allow about fifteen minutes if you already have the two modules; the comparison itself does not modify either input.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the installed tool
Use the versioned command supplied by the llvm-20 package. This matters when several LLVM releases are installed, because a module and its diagnostic output may be tied to a particular toolchain.
$ command -v llvm-diff-20
/usr/bin/llvm-diff-20
$ llvm-diff-20 --version
Ubuntu LLVM version 20.1.8
Optimized build.
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-20
llvm-20 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139
The local manual describes llvm-diff as a debugging tool for LLVM pass and frontend writers. It is a structural comparison, not a general textual diff, and its output format is not stable enough to parse as an interface.
2. Confirm the input convention
The command takes two module paths, followed optionally by global names:
$ llvm-diff-20 [options] <first-file> <second-file> [global-name ...]
A path ending in .ll is read as LLVM assembly. Any other suffix is treated as bitcode. Do not rename a bitcode file to .ll merely to make its name look familiar. If you have text IR, keep the suffix and syntax consistent with that choice.
The installed build exposes the usual help and version switches, plus a switch for coloured output. There are no ordinary comparison modes such as a context or unified diff flag in this command's help output.
3. Compare two complete modules
For a reproducible smoke test, create two small modules with the same operation but different local value names. Local names are insignificant to this comparison:
$ cat > /tmp/add-a.ll <<'EOF'
define i32 @add(i32 %a, i32 %b) {
entry:
%sum = add i32 %a, %b
ret i32 %sum
}
EOF
$ cat > /tmp/add-b.ll <<'EOF'
define i32 @add(i32 %x, i32 %y) {
entry:
%result = add i32 %x, %y
ret i32 %result
}
EOF
$ llvm-diff-20 /tmp/add-a.ll /tmp/add-b.ll
$ printf 'exit status: %s\n' "$?"
exit status: 0
No output and status 0 mean that this comparison found no structural difference. The tool ignores changes such as local value names and global ordering, so it is useful after a transformation that rewrites presentation details without changing the relevant structure.
4. Read a structural difference
Change the operation in the second module, then run the same comparison. Capture both output streams if you are putting the result into a test log:
$ llvm-diff-20 /tmp/add-a.ll /tmp/add-sub.ll
in function add:
in block %entry:
> %sum = sub i32 %a, %b
> ret i32 %sum
< %sum = add i32 %a, %b
< ret i32 %sum
$ printf 'exit status: %s\n' "$?"
exit status: 1
The arrows identify the side of the comparison: > is the second module and < is the first. The report names the function and basic block before showing the differing instructions. A control-flow change can stop downstream matching, so a long report does not necessarily mean that every later instruction is independently different.
5. Limit the comparison to named globals
When you are investigating one function, add its global name after the two module paths. Use the LLVM name without the leading @:
$ llvm-diff-20 /tmp/add-a.ll /tmp/module-with-other-functions.ll add
With a global-name list, only those values are compared. Without it, the tool considers all global values and can report a function present in only one module. Multiple names can be supplied as separate arguments:
$ llvm-diff-20 old.ll new.ll parse_token lower_case
Quote a name only when your shell would otherwise interpret characters in it. Do not build the argument list by blindly expanding untrusted text.
6. Check status and diagnostics together
Do not use the exit code as your only evidence. The manual documents zero for no differences and a non-zero value for differences, but the installed 20.1.8 build can print a one-sided global diagnostic while returning zero:
$ llvm-diff-20 /tmp/add-a.ll /tmp/module-with-extra-function.ll
function @extra exists only in right module
$ printf 'exit status: %s\n' "$?"
exit status: 0
Treat this as a version-specific observation from the packaged binary: inspect captured output as well as status, especially when comparing complete modules. For an automated gate, decide explicitly whether one-sided globals are an error and write a wrapper that applies that policy. Do not parse the wording as a stable API, because the manual warns that output format can change.
7. Keep the comparison safe
llvm-diff-20 reads the modules and writes diagnostics. It does not rewrite IR, run the functions, load plugins, or change a service, so ordinary user privileges are normally sufficient. Avoid sudo; elevated access can hide an ownership or path problem instead of fixing it.
Keep the original modules when a result matters. The comparison does not prove that memory behaviour, linkage, or function attributes are equal: the manual lists several important differences that are not diagnosed. If the result is surprising, inspect the IR with the matching LLVM tools and compare the generated artefacts using a separate method. Remove only temporary files you created, and do not delete a source module as part of this check.
Done means
- You confirmed that
/usr/bin/llvm-diff-20is LLVM 20.1.8 fromllvm-20. - You matched
.lltext input and non-.llbitcode input to the tool's suffix rule. - You know that no output with status 0 is the clean result for the tested function comparison.
- You can read function, basic-block, and arrow markers in a structural report.
- You can restrict a noisy comparison with one or more global names.
- Your check reviews diagnostics as well as exit status and does not treat the output wording as stable.