3. 使用Clang编译C++程序并查看优化后的IR

使用Clang编译C++程序并查看优化后的IR 学习卡片

任务目标

编写一个简单的C++程序,使用Clang分别以-O0和-O2优化级别生成LLVM IR,通过对比两者差异理解优化器对代码的变换效果。

环境准备

需要已安装Clang/LLVM(版本≥10)。验证安装:

clang++ --version

示例程序

创建文件 sum.cpp,内容为一个包含循环和常量折叠机会的简单函数:

int sum(int n) {
    int total = 0;
    for (int i = 0; i < n; ++i) {
        total += i * 2 + 1;
    }
    return total;
}

int main() {
    return sum(10);
}

生成并对比IR

分别生成-O0和-O2的IR文件:

# 生成未优化的IR
clang++ -S -emit-llvm -O0 sum.cpp -o sum_O0.ll

# 生成O2优化的IR
clang++ -S -emit-llvm -O2 sum.cpp -o sum_O2.ll

查看两个文件的关键差异:

# 对比差异(忽略注释行)
diff -u <(grep -v '^;' sum_O0.ll) <(grep -v '^;' sum_O2.ll) | head -80

预期差异与判定标准

通过观察两个IR文件,应看到以下关键区别:

  • 循环结构:-O0的IR中包含完整的循环控制流(br条件跳转、phi节点或icmp比较);-O2的IR中循环被完全展开并常量折叠,直接计算出结果ret i32 100。
  • 指令简化:-O0中mul、add等运算逐条保留;-O2中乘2操作被优化为移位或加法,循环内计算被常量传播消除。
  • 函数内联:-O2的IR中main函数直接内联了sum(10)的调用,不再包含call指令。

验证通过条件:sum_O2.ll中main函数体仅包含类似ret i32 100的指令,且整个文件无call指令和循环跳转;sum_O0.ll保留完整的循环和函数调用结构。

IR可视化对比

-O0 IR(循环保留) entry br label %for.cond for.cond icmp slt i32 for.body mul/add/store for.inc add i32 1 for.end ret i32 %total -O2 IR(常量折叠) main ret i32 100 (无循环、无call指令) (sum函数被内联并常量折叠)

常见问题

  • IR文件为空或报错:确认源码无语法错误,且clang++命令在当前目录有写权限。
  • diff输出过多:两个IR的元数据(!llvm.module.flags等)不同属正常现象,关注函数体define部分即可。
  • 想查看优化过程细节:可加-Rpass=loop-vectorize等参数查看优化器报告,但本案例核心只需对比最终IR差异。