从已运行过的 instcombine 命令进入 llvm-project,固定提交,找到入口、实现与测试;记录一次最小源码构建及测试依赖缺失,区分各项证据的范围。
先带着一个小问题进源码
第一次打开 llvm-project,最容易做的事是把目录从上往下翻一遍。这样很快会记住不少名字,却未必能回答一次实际编译发生了什么。更合适的起点,是带着上一期已经运行过的命令:opt -passes=instcombine。
这次只追三件事:命令从哪里进入;instcombine 在哪里注册和实现;哪份测试描述了预期结果。先把这条短路径走通,再扩展到其他 Pass、前端和后端。
先固定提交,再谈代码位置
本次使用 LLVM 18.1.3 的固定提交:
c13b7485b87909fcf739f62cfa382b55407433c0构建环境:Ubuntu 24.04.4(WSL2,x86-64),Clang/LLVM 18.1.3、LLD 18.1.3、CMake 3.28.3、Ninja 1.11.1。
下面用相对目录展示命令。长期实验请在 WSL 的 Linux 文件系统中选择可保留的工作目录,不把 /tmp 当交付仓库;退出环境后能否保留临时文件,不应成为证据链的前提。先保存版本与构建日志。
git clone --depth 1 --branch llvmorg-18.1.3 --single-branch --filter=blob:none --sparse https://github.com/llvm/llvm-project.git
git -C llvm-project sparse-checkout set llvm cmake third-party
git -C llvm-project checkout c13b7485b87909fcf739f62cfa382b55407433c0
git -C llvm-project rev-parse HEAD本次实际取得的 HEAD 与上面的值一致。浅克隆限制历史,稀疏检出限制工作树;需要 Clang、LLD 等其他源码时再加入相应目录。不要因为本次没有检出某个目录,就认为仓库中没有那个项目。
只记与这个问题有关的目录
| 位置 | 这里先看什么 |
|---|---|
| llvm/tools/opt | 命令行工具入口与新Pass管理器驱动 |
| llvm/lib/Passes | Pass名称解析、注册与流水线组织 |
| llvm/lib/Transforms/InstCombine | 指令组合变换实现 |
| llvm/include/llvm/IR | IR相关类型与接口声明 |
| llvm/test/Transforms/InstCombine | 输入IR与对应回归预期 |
| llvm/unittests | 以代码形式编写的单元测试;与test目录的用例不同 |
| llvm/lib/Target | 目标后端;本次只在构建配置中选择X86 |
仓库顶层的 clang、lld、mlir 等分别承担其他项目的工作,不能把仓库名 llvm-project 和其中的 llvm 子目录当成同一个层次。本次观察 IR 变换,先把注意力放在 llvm 内部。
构建需要的工具,保留配置边界
先将 LLD设为可用的 ld.lld 绝对路径,并检查版本。若系统已安装 lld-18,可通过 command -v ld.lld-18查找;本次使用的是本地解压路径。然后配置:
cmake -S llvm-project/llvm -B build -G Ninja -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_TARGETS_TO_BUILD=X86 -DLLVM_ENABLE_PROJECTS= -DLLVM_INCLUDE_TESTS=ON -DLLVM_INCLUDE_BENCHMARKS=OFF -DLLVM_BUILD_TOOLS=ON -DLLVM_USE_LINKER="$LLD"
cmake --build build --target opt FileCheck --parallel 2
build/bin/opt --version
build/bin/FileCheck --version本次 opt 与 FileCheck 均构建成功,版本输出为 18.1.3,并显示带断言的优化构建。配置中只选择 X86,未构建 Clang、全部目标或完整工具集合。opt 仍会依赖许多库,“只构建两个工具”不等于只编译两个源文件。
Release 加断言适合这次命令行观察,不能据此说已经有适合逐行调试的完整调试信息。如果后续确实需要断点调试,应另建 Debug 或 RelWithDebInfo 构建,并重新记录配置、磁盘与内存需求。这里没有声称已经完成断点调试。
测试入口失败,要分清失败发生在哪里
构建之后,本次尝试:
python3 build/bin/llvm-lit -v llvm-project/llvm/test/Transforms/InstCombine/add.ll它没有进入用例执行,先报缺少 build/bin/count。这不是 add.ll 检查发现优化错误,而是测试配置需要的辅助工具尚未生成。LLVM_INCLUDE_TESTS=ON也不代表只构建 opt 和 FileCheck 就自动补齐所有测试依赖。
针对这个缺项,可继续补建测试辅助工具后重试:
cmake --build build --target count not --parallel 2
python3 build/bin/llvm-lit -v llvm-project/llvm/test/Transforms/InstCombine/add.ll这两行是补齐缺项后的继续步骤,本次没有将其登记为通过:原临时构建目录在后续会话中已不可用,未为补记一个成功结果重新进行完整构建。原始构建成功日志与 lit 失败日志均保留。
为了单独核对用例内容,我另取同一固定提交的 add.ll,使用系统已有的 Ubuntu LLVM18.1.3工具,直接执行其 RUN 行对应的操作:
set -o pipefail
opt-18 < add.ll -passes=instcombine -S | FileCheck-18 add.ll这条管道本次通过,opt 与 FileCheck 的独立检查均成功。它是发行包工具对一个官方测试文件的检查,不是刚才源码构建二进制的 lit 通过,更不是完整 LLVM 测试套件通过。两条记录要分别保留。
从名字定位到实现和用例
以下路径已按上面的提交核对。先看 opt.cpp和 NewPMDriver.cpp,了解工具怎样进入新Pass管理器;然后在 PassRegistry.def查找字符串 instcombine。
继续找到 InstructionCombining.cpp中的 InstCombinePass::run。加减法相关处理可从 InstCombineAddSub.cpp中的 InstCombinerImpl::visitAdd进入。这里给的是入口,不是说所有加零折叠都只发生在这一个函数里;公共简化逻辑也会参与。
rg -n '"instcombine"' llvm-project/llvm/lib/Passes
rg -n 'InstCombinePass::run' llvm-project/llvm/lib/Transforms/InstCombine
rg -n 'visitAdd' llvm-project/llvm/lib/Transforms/InstCombine
rg -n '^; RUN:|^; CHECK' llvm-project/llvm/test/Transforms/InstCombine/add.ll最后回到 add.ll。RUN 指定工具命令,%s 在测试系统中指当前测试文件;CHECK 一类指令描述输出应该出现的模式。FileCheck 验证这些模式,不执行被优化的程序,也不代替语义正确性证明。本例的源码、命令和预期通过这一文件连起来。
先缩小输入,再加调试信息
调试从保存问题开始:固定提交、完整命令、最小输入、预期和实际输出。若问题是“某个 Pass 没运行”,先看调度日志;若是某一步生成非法 IR,先在流水线中加入验证;只有这些证据还不够,再进入源码断点。
opt-18 -passes=instcombine -debug-pass-manager -verify-each \
-S input.ll -o output.ll这里的 input.ll 应替换成你保存的最小复现文件。源码构建环境保留完整时,再将命令开头换成对应 build/bin/opt,确保调试的二进制与正在阅读的提交一致。不要一边读主分支源码,一边用系统另一版本的工具推断具体调用路径。
本期建立的是一条可追溯的阅读路线:工具入口、Pass注册、实现入口、回归预期。今后定位设备专用变换或兼容问题,也应先找清这几项关系,而不是从整个仓库规模开始。
构建身份为什么必须同时包括源码与配置
同一提交不等于同一二进制。目标集合、断言、构建类型、可选项目、宿主编译器和依赖,都可能影响可用功能与诊断。只写“使用LLVM18”无法让另一位读者判断正在调试哪个实现。
建议把源码提交、CMakeCache中的关键配置、实际工具版本、二进制身份与运行命令连在同一份记录里。源码树与构建树分离,可以让不同配置共存;调试前必须确认命令实际启动了想要的build/bin/opt,而非PATH里的发行包工具。
Release+assertions启用内部一致性检查,但不自动提供完整调试信息,也不意味着被优化掉的局部变量可以按源码直觉观察。切换Debug或RelWithDebInfo是另一个构建条件,应记录资源成本与差异,而不偷偷把不同二进制的结果合并成同一次实验。
怎样读一份回归测试,而不把FileCheck当解释器
RUN行说明执行什么命令,CHECK类指令说明工具输出应满足哪些文本模式。一个测试可用于防止某种代码形状回归,但它不必运行被编译程序,也未必覆盖业务语义的全部输入。模式匹配通过与程序正确是不同命题。
检查时要看模式是否过弱或过强:只要求出现add,可能遗漏错误操作数;死盯无关编号与排列,又可能把无害打印变化当成失败。CHECK-NOT等否定条件也有受前后匹配范围约束的语义,不能凭字面猜覆盖了整份输出。细节见FileCheck参考。
测试框架配置失败,还没有进入这类预期检查。本篇缺count的lit失败、源码构建工具成功、发行包工具直接跑RUN管道成功,分别证明不同事情。补建count/not后重跑仍是待办;临时目录后来不可用的原因也未确认。LLVM测试基础设施说明可帮助恢复完整条件,不能替代本机运行记录。
2026-10-18内容修订:深化构建身份与测试判据,保持lit未通过及无调试器会话事实。本次为文献与语义审校,未新增实验;首次发布日期、原网址及历史运行版本保持。
来源、验证范围与阅读路线
源码固定为18.1.3提交;版本化文档为18.1.8。实际结果:源码构建opt/FileCheck与版本检查成功;源码构建环境的lit因缺count未运行用例;发行包工具直接检查同一add.ll通过。未运行全套回归、断点调试或性能测试。
勘误:2026-10-07,初版。构建与测试按不同状态登记,后续补验应附配置与日志。
写下你的评论
要发表评论,您必须先登录。