Compile LLVM IR to Assembly or Object Code with llc-18
You will turn a small LLVM IR module into x86-64 assembly, then produce an ELF relocatable object when that is the better hand-off to a linker. The examples use Ubuntu LLVM 18.1.3 from package llvm-18 on Linux. Allow about fifteen minutes if you already have valid LLVM IR.
The route
Jump straight to the step you need, or tick off Done means at the end.
llc is a code generator, not a complete compiler driver. It accepts LLVM assembly (.ll) or LLVM bitcode (.bc) and emits assembly by default. A native assembler and linker are separate steps. These examples are ordinary user commands: no elevated privileges are needed.
1. Check the installed command
Start by confirming the version and the target back ends available on this machine:
$ dpkg-query -W -f='${Package} ${Version}\n' llvm-18
llvm-18 1:18.1.3-1ubuntu1
$ llc-18 --version
Ubuntu LLVM version 18.1.3
Default target: x86_64-pc-linux-gnu
The full --version output also lists registered targets. Do not assume that a target named in a build script is installed here. If you need a target-specific check, use llc-18 -help and inspect its --filetype, --march, --mcpu and --mtriple entries.
Checkpoint
The command is ready when llc-18 --version reports the expected LLVM release and the target you intend to use appears in its registered-target list.
2. Compile LLVM IR from standard input
For a quick test, pipe valid LLVM assembly into llc-18. The function below returns zero, so it is safe to compile and does not run:
$ printf '%s\n' 'define i32 @main() { ret i32 0 }' | llc-18 -o -
.text
.file "<stdin>"
.globl main
.type main,@function
main:
xorl %eax, %eax
retq
The input name is - when reading standard input, and an omitted input filename means the same thing. An explicit -o - keeps the assembly on standard output. Without that option, standard-input compilation also writes output to standard output.
The exact directives and spacing depend on the target and LLVM release. Check the exit status immediately after a compile:
$ printf '%s\n' 'define i32 @main() { ret i32 0 }' | llc-18 -o /dev/null
$ printf 'llc exit status: %s\n' "$?"
llc exit status: 0
3. Compile a file without losing the input
For a real module, replace /path/to/module.ll with your own readable file and choose the output explicitly:
$ llc-18 -O=2 -o module.s /path/to/module.ll
$ test -s module.s && head -12 module.s
LLVM 18's command help reports -O2 as the default optimisation level. Setting -O=2 makes the choice visible in scripts; use -O=0 through -O=3 when you need a different level. Optimisation can change register allocation and instruction selection, so compare output only after keeping the level constant.
If you omit -o for a named input, llc derives a name by removing an existing .bc suffix and adding .s. Do not rely on that rule for a carefully named .ll output. An explicit destination is clearer and avoids accidentally writing over a file you meant to preserve.
Safety boundary
-o module.s can replace an existing file. Before running it in a build directory, check test ! -e module.s or choose a new output path. If you did overwrite an output, recover it from version control or your normal backup; llc has no undo operation. The input module is read, not modified.
4. Select the target deliberately
By default, the target is inferred from the module's target triple or from the current architecture. To make a host build explicit, pass a target triple:
$ printf '%s\n' 'define i32 @main() { ret i32 0 }' \
| llc-18 -mtriple=x86_64-pc-linux-gnu -o module.s
$ head -6 module.s
.text
.file "<stdin>"
.globl main
Use -march=<arch> to override the architecture and -mcpu=<cpuname> to tune for a particular chip. These are not interchangeable: the architecture selects the instruction set family, while the CPU selects a processor within that family. Use -march=help or consult the architectures listed by llc-18 --version instead of guessing a name.
Target attributes are similarly specific. For example, -mattr=+sse2 enables a named target feature where the selected back end supports it. A feature or CPU accepted on one target is not automatically valid on another. When portability matters, keep the triple, CPU and attributes in the build configuration and verify them on the machine that will run the build.
5. Emit an object file when assembly is not the hand-off
Ask for a native object with -filetype=obj and give it a new filename:
$ printf '%s\n' 'define i32 @main() { ret i32 0 }' \
| llc-18 -filetype=obj -o module.o
$ file module.o
module.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
The object format is target-dependent, and not every target supports every output type. A successful llc run means code generation succeeded; it does not mean the object is linked, executable, ABI-compatible with your libraries, or suitable for the deployment CPU. Let the linker and a test run establish those later properties.
To measure a code-generation path without writing an output file, the manpage documents -filetype=null. That is useful for timing or pass experiments, but it cannot verify an assembly or object artefact. For a repeatable build, retain the explicit output and inspect it.
6. Diagnose the usual failures
A non-zero exit status means that an error occurred. The most common distractions are easy to separate:
- A parse error usually means the input is not valid LLVM assembly or is the wrong bitcode version. Test the module with the matching LLVM tools before changing target flags.
- A missing architecture, CPU or attribute means the installed LLVM build does not recognise that selection. Recheck
llc-18 --versionandllc-18 -help. - A missing output file is often a shell or path problem. Check the destination directory and write permission; do not add
sudoto compensate for an incorrect path. - An assembly file that looks different from a previous run may reflect a different target, CPU or optimisation level. Record those three choices before comparing output.
If you need the available CPU names for a target, the installed manpage gives this pattern:
$ llvm-as-18 < /dev/null | llc-18 -march=xyz -mcpu=help
...
Replace xyz with the architecture you are investigating. The command prints a list or a target diagnostic; it does not alter files. Use the same pattern with -mattr=help for target attributes.
Done means
llc-18 --versionreports the installed LLVM release and a suitable registered target.- A known-valid LLVM IR module compiles with exit status 0.
- The output type is explicit:
.sfor assembly or.owith-filetype=obj. - The target triple, CPU and optimisation level are recorded when reproducibility matters.
- No input was overwritten, and any existing output was backed up or deliberately replaced.