Optimise LLVM IR safely with opt-18
You will take an LLVM assembly file, run a named optimisation pipeline with Ubuntu's opt-18, and write a separate, readable .ll file. The same command can read LLVM bitcode and produce bitcode, but keeping the input untouched makes experiments and recovery straightforward.
The route
Jump straight to the step you need, or tick off Done means at the end.
Plan for about 10 minutes if the input already exists. You need the llvm-18 package and an LLVM IR file in either assembly (.ll) or bitcode (.bc) form. These examples were checked with llvm-18 version 1:18.1.3-1ubuntu1, whose executable is /usr/bin/opt-18.
1. Check the installed tool
Run this as your ordinary user. No elevated privileges are needed: opt-18 reads and writes files you can access.
command -v opt-18
dpkg-query -W -f='${Package} ${Version}\n' llvm-18
opt-18 --version
On this machine, the package query reports llvm-18 1:18.1.3-1ubuntu1. The version command prints LLVM's build version. If the command is missing, install or repair the distribution package through your normal system administration process; do not guess a different opt binary is interchangeable.
Checkpoint
Continue when command -v resolves opt-18 and the package is installed.
2. Inspect the input and available passes
Start by looking at the file without changing it. LLVM assembly is text; bitcode is binary and should not be sent directly to a terminal.
sed -n '1,120p' /path/to/input.ll
opt-18 -print-passes | sed -n '1,40p'
The first command is only for a readable .ll file. For a .bc file, use file /path/to/input.bc to identify it, then let opt-18 read it. The pass list is build-dependent, so use the installed list rather than copying a pipeline from an unrelated LLVM release.
LLVM 18 uses the textual new pass manager pipeline. The current installed command accepts -passes=<string>, such as -passes='sroa,instcombine'. The local man page also documents individual pass options and says that command-line order controls pass order within pass constraints. For repeatable LLVM 18 work, naming the pipeline explicitly is clearer.
Checkpoint
Choose a pass that appears in your own -print-passes output. instcombine is a small, useful example for simplifying instructions.
3. Optimise into a new LLVM assembly file
Use -S to request LLVM intermediate language rather than bitcode, and -o to make the destination explicit. Replace the placeholder paths, but keep the output separate from the input.
opt-18 -passes='instcombine' -S \
/path/to/input.ll \
-o /path/to/output.optimised.ll
A successful run normally prints nothing and exits with status 0. Capture the status before inspecting the result:
opt-18 -passes='instcombine' -S \
/path/to/input.ll -o /path/to/output.optimised.ll
status=$?
printf 'opt-18 exit status: %s\n' "$status"
sed -n '1,160p' /path/to/output.optimised.ll
For example, an input instruction that adds the two constants 2 and 3 can become ret i32 5. The exact changes depend on the IR, its attributes and the pipeline. opt-18 is an IR transformation and analysis tool; it does not turn the result into a native executable.
Safety warning
Do not set -o to the same path as the input unless you have a tested, recoverable workflow. A failed transformation or an interrupted write can destroy the only copy. If you did overwrite an input, recovery means restoring it from version control or a backup; opt-18 has no undo command.
4. Add verification while diagnosing a pipeline
Use -verify-each when you suspect a pass is producing invalid IR. It adds a verifier after every specified pass, including an explicitly requested -verify pass. This helps locate the stage that first fails.
opt-18 -verify-each -passes='instcombine' -S \
/path/to/input.ll \
-o /path/to/output.checked.ll
No diagnostic and an exit status of 0 indicate that this invocation completed. A malformed input, unknown pass or failed verifier produces a non-zero status and an error on standard error. Capture it when investigating:
if ! opt-18 -verify-each -passes='instcombine' -S \
/path/to/input.ll -o /path/to/output.checked.ll \
2>/tmp/opt-18-error.log; then
sed -n '1,80p' /tmp/opt-18-error.log
exit 1
fi
Do not treat a non-zero status as a partial success. Keep the original file, read the diagnostic, and correct the input or pipeline before trying again.
5. Choose the output format deliberately
With -S, the output is text LLVM assembly. Without it, the normal output is bitcode, which is compact but not suitable for casual inspection.
# Produce bitcode for another LLVM tool
opt-18 -passes='instcombine' \
/path/to/input.ll -o /path/to/output.optimised.bc
# Convert that bitcode back to readable LLVM assembly
opt-18 -S /path/to/output.optimised.bc \
-o /path/to/output.optimised.ll
When standard output is a terminal, opt-18 refuses raw bitcode by default. That protects you from filling a terminal with binary data. The man page documents -f to override this, but redirecting to a named file is safer and easier to inspect later.
Input may also be standard input: omit the filename or use -. For example, this keeps the transformed assembly in a file:
cat /path/to/input.ll | opt-18 -passes='instcombine' -S \
-o /path/to/output.optimised.ll
Prefer a named input during routine work. It makes the command history and failure message identify the exact source file.
Done means
opt-18resolves to the intended LLVM 18 installation.- The selected pass is present in
opt-18 -print-passes. - The input remains available and the output has the intended
.llor.bcformat. - The command exits 0, and
-verify-eachsucceeds when you need validation after every pass. - A non-zero run leaves the original intact while you investigate its diagnostic.