LLVM pass that reports loop nesting, trip counts from ScalarEvolution, and unroll opportunities. Reference only.
  • LLVM 50%
  • C++ 32.9%
  • Shell 7.9%
  • C 5.5%
  • CMake 3.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-01 09:25:15 -06:00
src Simplify pass: dedupe metadata helpers, fix stale comment 2026-05-31 17:50:06 -05:00
tests Add FileCheck tests; document -O1 requirement; regenerate fixtures 2026-05-31 17:51:22 -05:00
.gitignore Add FileCheck tests; document -O1 requirement; regenerate fixtures 2026-05-31 17:51:22 -05:00
CMakeLists.txt Fix plugin build: don't link LLVM component libraries 2026-05-31 17:50:03 -05:00
LICENSE Sunset: add not-maintained status note + LICENSE 2026-06-18 20:47:44 -06:00
README.md Sunset: add not-maintained status note + LICENSE 2026-06-18 20:47:44 -06:00

LLVM Loop Unroll Analyzer

Status: not actively maintained. Left up as a reference.

Custom LLVM pass that analyzes loop structures and identifies unroll opportunities.

Function

This pass traverses all functions in LLVM IR and reports the following:

  • Detected loops and their nesting depth
  • Loop trip-count analysis (via ScalarEvolution)
  • Unroll recommendations
  • Metadata attachment for unroll recommendations (llvm.loop.unroll.full / llvm.loop.unroll.partial)

Recommendation rules (innermost loops only):

  • Trip count unknown -> "Cannot determine unroll strategy"
  • Trip count <= 16 -> full unroll
  • Trip count > 16 -> partial unroll by factor of 4
  • Non-innermost loops are reported but never marked.

Prerequisites

  • LLVM 18 and Clang 18 (opt-18, clang-18)
  • CMake

Build

cmake -S . -B build
cmake --build build

This produces build/libUnrollAnalyzerPass.so. The plugin deliberately does not link the LLVM libraries; opt/clang supply them at load time.

Usage

The pass relies on ScalarEvolution to compute trip counts, which needs the loop in canonical (rotated, indvar-simplified) form. -O0 IR is not canonical, so trip counts come back "unknown" and nothing gets recommended. Compile the input at -O1 (or higher):

# 1. Compile C to canonical LLVM IR
clang-18 -O1 -emit-llvm -S tests/test.c -o tests/test.ll

# 2. Run the pass (report goes to stderr; transformed IR to output.ll)
opt-18 -load-pass-plugin=build/libUnrollAnalyzerPass.so \
    -passes="unroll-analyzer" tests/test.ll -S -o output.ll

If you must start from -O0 IR, run the canonicalization passes first:

opt-18 -load-pass-plugin=build/libUnrollAnalyzerPass.so \
    -passes="loop-rotate,indvars,unroll-analyzer" input.ll -S -o output.ll

Tests

tests/run_tests.sh runs the pass on the committed fixtures and checks both the analysis report and the attached metadata with FileCheck:

tests/run_tests.sh build/libUnrollAnalyzerPass.so

Requires FileCheck-18 on PATH. The expected output lives in tests/checks/. Regenerate the fixtures after changing the C sources with the clang-18 -O1 command above.

Project structure

unroll-analyzer
├── README.md
├── CMakeLists.txt
├── src
│   └── UnrollAnalyzer.cpp
└── tests
    ├── test.c / test.ll
    ├── test_edge_cases.c / test_edge_cases.ll
    ├── checks/            FileCheck expectations
    └── run_tests.sh

Notes

  • New pass manager: required for the -load-pass-plugin / -passes interface used here (LLVM 18).
  • ScalarEvolution: computes the loop trip count used for the unroll decision; getSmallConstantTripCount returns 0 when the count is not a known small constant, which the pass reports as "unknown".