Skip to content

Latest commit

 

History

History
338 lines (265 loc) · 15.8 KB

File metadata and controls

338 lines (265 loc) · 15.8 KB

LLTFI LLVM Upgrade Migration Guide

This document describes the work required to upgrade LLTFI from LLVM 15 to a current LLVM release (17–20). Tasks are split between those that require a human and those that Claude Code can handle autonomously once the build environment is ready.

Migrated from: LLVM 15.0 (/usr/lib/llvm-15)
Current LLVM version: LLVM 20.1 (/usr/lib/llvm-20)


Status: further along than it appears

The most difficult part of a major LLVM upgrade — migrating from the legacy pass manager (PM) to the new PM — is already done for all core LLTFI passes. RegisterPasses.cpp exposes llvmGetPassPluginInfo(), every core pass has a PassInfoMixin wrapper, and instrument.py already uses -load-pass-plugin. What remains is a set of API-level fixups and one incomplete pass migration.


Human tasks (cannot be delegated to Claude Code)

H-1 — Install LLVM 17+ and update the build configuration ✅ DONE

Estimated time: 2–4 hours

This is the only hard prerequisite. Claude Code cannot change what is installed on the machine, so the compile-verify-fix loop that de-risks the code changes cannot begin until this is done.

Steps:

  1. Install a current LLVM release via the LLVM apt repository:
    wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh
    sudo ./llvm.sh 20   # or 17/18/19
  2. Update the build configuration to point at the new installation:
    # Delete the old build root first
    rm -rf /home/karthik/Programs/LLTFI-build
    
    ./setup -LLFI_BUILD_ROOT /home/karthik/Programs/LLTFI-build \
            -LLVM_SRC_ROOT /home/karthik/Programs/llvm-project \
            -LLVM_DST_ROOT /usr/lib/llvm-20 \
            -LLVM_GXX_BIN_DIR /usr/lib/llvm-20/bin
  3. Attempt an initial build (cd LLTFI-build && make) and record the errors. The output of this first build attempt is the input for Claude Code's work.

H-2 — Review IRBuilder insertion-point correctness

Estimated time: 2–3 hours

The instruction-construction API fixes (task C-1 below) replace new AllocaInst/StoreInst/LoadInst calls with IRBuilder equivalents. These compile cleanly whether correct or not, but an incorrect insertion point places an instruction in the wrong basic block or wrong position, producing miscompiled IR. A human who understands the intended pass semantics should review these diffs specifically before merging. The key files to scrutinise are FaultInjectionPass.cpp and InstTracePass.cpp.

Claude Code preliminary review (2026-04-14): Both files were inspected and the insertion points appear semantically correct:

  • FaultInjectionPass.cpp lines 229–230, 266: uses the BasicBlock* insertAtEnd form (new AllocaInst(type, 0, name, block)), which inserts at the end of the given block. Allocas go in the entry block (standard practice); the store and load go in the respective exit block immediately after the injected value is computed. This is the same logical placement as before the API change.
  • InstTracePass.cpp lines 141–142, 144, 152, 170: uses BasicBlock::iterator from getFirstNonPHIOrDbgOrLifetime() for alloca insertion (correct: before any non-PHI/dbg/lifetime instruction in the entry block), and insertPoint->getIterator() for stores (correct: inserts immediately before the trace call). All 21 tests pass with these changes.

The remaining human task is to verify the logical placement makes sense for the pass's intended semantics, not just that it compiles and passes tests.

H-3 — Provide onnx-mlir environment for real-model validation

Estimated time: 1–2 hours

The only step that requires a human is making onnx-mlir available:

  1. Install onnx-mlir or set ONNX_MLIR_BUILD to point at an existing build.
  2. Run compile.sh in sample_programs/ml_sample_programs/vision_models/mnist/ to produce model.ll.
  3. Build SIDHelperFunctions.ll (needed for end-to-end numerical run):
    cd llvm_passes/instruction_duplication/shared_lib
    sh compile_shrd_lib.sh

Once model.ll and SIDHelperFunctions.ll exist, Claude Code (or the test suite) takes over automatically. Two tests in test_instruction_duplication.py cover the rest:

  • real_model_structural — applies the pass to the real onnx-mlir IR and verifies that compareFloatValues calls are inserted and arithmetic instructions are duplicated. This proves the pass handles genuine onnx-mlir IR patterns, not just the synthetic fixtures used in the seven other tests.
  • real_model_end_to_end — runs both the baseline model.ll and the duplicated+inlined model through lli, then asserts their outputs are identical. Because compareFloatValues(x, x) == x (bitwise AND of equal floats is the float itself), the outputs must match when no fault is injected. Any divergence indicates a pass transformation bug.

Both tests SKIP gracefully when their prerequisites are absent, so the suite continues to report 0 failures even before onnx-mlir is set up. Run them via:

cd /path/to/LLTFI-build/test_suite
python3 SCRIPTS/test_instruction_duplication.py

H-4 — Final test sign-off ✅ DONE

Run the full test suite against the new LLVM version and confirm no regressions:

cd /path/to/LLTFI-build/test_suite
python3 SCRIPTS/llfi_test --all_cpp    # expect 21/21
python3 SCRIPTS/llfi_test --all_ml     # expect all non-SKIP to pass

Result: 21/21 PASS. All hardware fault, trace tool, makefile generation tests pass against LLVM 20.1. The --all_ml tests that require optional dependencies (onnx-mlir, TensorFlow, PyTorch) are reported as SKIP (not FAIL) on machines where those are not installed.


Claude Code tasks (can be done autonomously once LLVM 17+ is installed)

C-1 — Replace deprecated instruction-construction API (9 sites) ✅ DONE

Estimated time: 1 day

LLVM 16/17 removed the InsertBefore Instruction-pointer parameter from AllocaInst, StoreInst, and LoadInst constructors. All 9 affected sites must be replaced with IRBuilder equivalents.

Affected files and sites:

File Sites Pattern
llvm_passes/core/FaultInjectionPass.cpp 3 new AllocaInst(...), new StoreInst(...), new LoadInst(...)
llvm_passes/core/InstTracePass.cpp 6 new AllocaInst(...) ×3, new StoreInst(...) ×3

Example replacement:

// Before (LLVM 15)
AllocaInst *tmploc = new AllocaInst(fitype, 0, "tmploc", entryblock);

// After (LLVM 17+)
IRBuilder<> builder(&entryblock->front());
AllocaInst *tmploc = builder.CreateAlloca(fitype, nullptr, "tmploc");

Note: CallInst::Create(func, args, "", insertPoint) with a raw Instruction * as the last argument may also need updating to use a BasicBlock::iterator; check each call site after the build reveals errors.

C-2 — Fix iterator return-type changes ✅ DONE

Estimated time: 2–3 hours

Two methods changed their return type from Instruction * to BasicBlock::iterator in LLVM 17:

  • getFirstNonPHIOrDbgOrLifetime() — used in InstTracePass.cpp:123
  • getNextNonDebugInstruction() — used in Utils.cpp:125 and InstructionDuplication.cpp:440–443

At each call site, either dereference the iterator (&*iter) or update the surrounding code to work with iterators directly.

C-3 — Migrate InstructionDuplication to the new pass manager ✅ DONE

Estimated time: 2–3 days

InstructionDuplication.cpp is the only remaining pass still using the legacy PM. It needs to be migrated to PassInfoMixin to match all other LLTFI passes.

Changes required:

  1. InstructionDuplication.cpp: Change class base from FunctionPass to PassInfoMixin<InstructionDuplicationPass>. Rename runOnFunction(Function &F) to run(Function &F, FunctionAnalysisManager &AM) returning PreservedAnalyses. Remove static char ID and RegisterPass<>. The five existing PassInfoMixin passes in the codebase serve as templates.

  2. RegisterPasses.cpp: Add a registerPipelineParsingCallback entry for InstructionDuplicationPass, following the existing pattern for the other passes. Decide whether SEDPasses.so should be merged into the main llfi-passes.so or kept separate; keeping it separate is simpler.

  3. llvm_passes/instruction_duplication/CMakeLists.txt: Update to add the llvmGetPassPluginInfo export if SEDPasses.so stays separate, or adjust linking if merged.

  4. shared_lib/build.sh and compile_shrd_lib.sh: Update opt invocation to remove -load / --enable-new-pm=0 and use -load-pass-plugin with --passes=InstructionDuplicationPass.

  5. README.md (in instruction_duplication/): Update example opt command.

  6. test_suite/SCRIPTS/test_instruction_duplication.py: Update _run_pass to use -load-pass-plugin and --passes=InstructionDuplicationPass instead of -load / --InstructionDuplicationPass / --enable-new-pm=0.

C-4 — Fix Module::getGlobalList().push_back() in Utils.cpp ✅ DONE

Estimated time: 30 minutes

getGlobalList() was removed in LLVM 17. The one call site in Utils.cpp:206 creates a GlobalVariable and appends it to the module. Replace with the GlobalVariable constructor overload that takes a Module * directly (which inserts the variable into the module automatically):

// Before (LLVM 15)
nameStr = new GlobalVariable(name_c->getType(), true,
    GlobalVariable::InternalLinkage, name_c, gv_nameStr.c_str());
M.getGlobalList().push_back(nameStr);

// After (LLVM 17+)
nameStr = new GlobalVariable(M, name_c->getType(), true,
    GlobalVariable::InternalLinkage, name_c, gv_nameStr.c_str());

C-6 — Iterative build-fix loop ✅ DONE

Estimated time: 1 week (wall clock; most of this is Claude Code running builds)

After applying C-1 through C-5, run make and address any remaining compiler errors introduced by LLVM 17–20 API changes not captured above. LLVM releases between 17 and 20 introduce additional deprecations (e.g. changes to Value::use_iterator, DebugLoc APIs, MDNode helpers) that may surface depending on the exact target version. Claude Code can drive this loop: read the error, identify the fix, apply it, rebuild.

C-7 — C++ static analysis and formatting cleanup ✅ DONE

After the build was clean, clang-format-20 and clang-tidy-20 were run across all hand-written C++ sources under llvm_passes/. In addition to style issues, clang-tidy surfaced several real bugs:

Bug File Fix
Double-free in singleton destructor Controller.cpp Removed delete ctrl from ~Controller() — object is not heap-allocated by the time the destructor runs
File stream leak LLFIDotGraphPass.cpp Added missing fclose(outputFile)
Null dereference via unchecked fopen GenLLFIIndexPass.cpp Moved fclose inside the if (outputFile) block
Uninitialized field isChainDuplication InstructionDuplicationPass constructor Added explicit isChainDuplication = false initializer
Null getCalledFunction() dereference ProfilingPass.cpp, InstructionDuplication.cpp, CustomTensorOperatorInstSelector.cpp Added null checks before name comparison
Unchecked null dyn_cast results Utils.cpp, multiple Changed to cast<> (asserting) where type is guaranteed by a prior opcode check; added null checks elsewhere

Style fixes applied across 26 files: override on all overriding methods, virtual ~Base() = default on abstract base classes, .empty() replacing .size() == 0, initialized-at-declaration for all local pointers, strncpy/strncat replacing unbounded strcpy/strcat, const T& in range-for loops, and cl::opt<T>::getValue() to avoid slicing.

Infrastructure added:

  • .clang-tidy — project tidy config with intentionally disabled checks documented
  • lint.sh — unified C++ and Python lint runner (./lint.sh --fix auto-formats)
  • CODING_GUIDELINES.md — expanded with override, variable initialisation, container emptiness, and cast<> vs dyn_cast<> sections

C-8 — Secondary pass on ML/SID code ✅ DONE

ML fault injection and instruction duplication passes:

File Fix
ProfilingPass.cpp dyn_cast<CallInst>cast<CallInst> after isa<> check in insertCallForMLFIStats()
InstructionDuplication.cpp for (auto insVector :for (const auto& insVector : to avoid copying inner vectors; removed dead return false; after exhaustive if/else

Documentation fixes:

File Fix
caveats.txt LLVM version references updated 15 → 20; duplicate item number fixed
llvm_passes/instruction_duplication/README.md Final opt invocation updated from legacy PM -always-inline to --passes=always-inline (legacy PM removed in LLVM 17)
llvm_passes/instruction_duplication/shared_lib/build.sh Uses LLVM_GXX_BIN_DIR env var to find versioned clang (fixes failure on Ubuntu with apt-installed LLVM where only clang-20 exists)
llvm_passes/instruction_duplication/shared_lib/compile_shrd_lib.sh Same fix for clang++

Recommended order of work

H-1  Install LLVM 17+ and attempt initial build           ✅ DONE
  └─> C-1  Fix instruction-construction API               ✅ DONE
  └─> C-2  Fix iterator return-type changes               ✅ DONE
  └─> C-4  Fix getGlobalList                              ✅ DONE
  └─> C-5  Iterative build-fix loop                       ✅ DONE
  └─> C-6  C++ static analysis and formatting cleanup     ✅ DONE
  └─> C-7  ML/SID secondary pass, doc fixes               ✅ DONE
H-2  Review IRBuilder insertion-point diffs               Pending
  └─> C-3  Migrate InstructionDuplication pass            ✅ DONE
H-3  Validate InstructionDuplication on onnx-mlir IR      Pending
H-4  Final test sign-off                                  ✅ DONE (21/21)

Effort summary

Task Owner Status Estimated time
H-1: Install LLVM 20 and update build config Human ✅ Done 2–4 hours
H-2: Review IRBuilder insertion-point correctness Human Pending 2–3 hours
H-3: Provide onnx-mlir environment (install + compile.sh) Human Pending 1–2 hours
H-4: Final test sign-off Human ✅ Done (21/21)
Total human time remaining ~3–5 hours
C-1: Deprecated instruction-construction API Claude Code ✅ Done
C-2: Iterator return-type fixes Claude Code ✅ Done
C-3: InstructionDuplication new PM migration Claude Code ✅ Done
C-4: getGlobalList fix Claude Code ✅ Done
C-5: Iterative build-fix loop Claude Code ✅ Done
C-6: C++ static analysis and formatting cleanup Claude Code ✅ Done
C-7: ML/SID, doc fixes Claude Code ✅ Done
Total Claude Code time remaining None — all done

Without Claude Code, a human developer would need approximately 2–3 weeks of active work. With Claude Code handling the mechanical fixes and the build-fix loop, human involvement drops to roughly 1.5–2 days of active effort, with Claude Code running largely autonomously in between.


What could go wrong

  • Subtle IRBuilder insertion-point bugs: compile successfully but produce miscompiled IR. Mitigated by H-2 (human review) and the existing test suite.
  • New PM semantics differences: the new PM runs passes in a different order and does not support inter-pass mutable state in the same way. The Controller singleton used by the FI selectors should be checked for thread-safety assumptions that the new PM may violate.
  • onnx-mlir compatibility: onnx-mlir targets a specific LLVM version internally. If the onnx-mlir build and the LLTFI build target different LLVM versions, llvm-link may refuse to link the bitcode. This is an environment concern, not a code concern, but it could block H-3.