LLVM Architecture Analysis & Learning Guide for Optimizer Engineers

一、LLVM整体架构概览 (LLVM Overall Architecture)

1.1 三段式编译器架构 (Three-Stage Compiler Architecture)

LLVM采用了革命性的三段式编译器设计,这种架构使得编译器具有极高的可扩展性和可维护性:

┌─────────────────────────────────────────────────────────────┐
│                    前端 (Frontend)                           │
│  C/C++/Obj-C/Rust/Fortran → LLVM IR                         │
│  组件: Clang, Rustc, Flang, etc.                            │
└────────────────────┬────────────────────────────────────────┘
                     │ LLVM IR (.ll / .bc)
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                中端优化器 (Middle/Optimizer)                  │
│  与平台无关的IR优化                                          │
│  - 标量优化: SROA, GVN, LICM                                │
│  - 向量化: Loop Vectorizer, SLP                             │
│  - IPO: Inliner, GlobalOpt                                  │
└────────────────────┬────────────────────────────────────────┘
                     │ Optimized LLVM IR
                     ▼
┌─────────────────────────────────────────────────────────────┐
│                 后端 (Backend)                               │
│  LLVM IR → 机器码 (Machine Code)                            │
│  支持30+架构: X86, AArch64, RISC-V, GPU, etc.              │
└─────────────────────────────────────────────────────────────┘

前端 (Frontend) - Clang

  • 位置: llvm-project/clang/
  • 职责: 将C/C++/Objective-C代码编译为LLVM IR
  • 核心源码目录:
    • clang/lib/AST/ - 抽象语法树 (Abstract Syntax Tree)
    • clang/lib/Sema/ - 语义分析 (Semantic Analysis)
    • clang/lib/CodeGen/ - IR代码生成 (关键目录!)
      • CodeGenFunction.cpp (135KB) - 函数级代码生成入口
      • CGExpr.cpp (294KB) - 表达式转IR
      • CGStmt.cpp (128KB) - 语句转IR
      • CGCall.cpp (255KB) - 函数调用与ABI处理

中端优化器 (Middle/Optimizer)

  • 位置: llvm-project/llvm/lib/Transforms/
  • 职责: 执行与目标平台无关的IR优化
  • 核心组件:
    • InstCombine/ - 指令合并与简化 (最基础的优化)
    • Scalar/ - 标量优化
      • SROA.cpp - Scalar Replacement of Aggregates
      • GVN.cpp - Global Value Numbering
      • LICM.cpp - Loop Invariant Code Motion
    • Vectorize/ - 自动向量化
    • IPO/ - 过程间优化 (Inter-Procedural Optimization)

后端 (Backend) - Target CodeGen

  • 位置: llvm-project/llvm/lib/Target/
  • 职责: 将优化后的IR转换为目标平台的机器码
  • 支持架构: X86, AArch64, RISC-V, ARM, PowerPC, NVPTX, AMDGPU等30+架构
  • 核心流程:
    1. Instruction Selection (指令选择) - IR → SelectionDAG → MachineInstr
    2. Register Allocation (寄存器分配) - 虚拟寄存器 → 物理寄存器
    3. Instruction Scheduling (指令调度) - 重排指令以优化流水线
    4. Code Emission (代码生成) - 生成汇编/机器码

1.2 核心IR表示 (Intermediate Representation)

LLVM IR是LLVM的核心创新,它是一种类型化的三地址码表示:

  • 位置: llvm-project/llvm/lib/IR/
  • 关键文件:
    • Instructions.cpp (162KB) - IR指令的实现
    • Function.cpp (40KB) - 函数IR结构
    • BasicBlock.cpp (36KB) - 基本块IR结构
    • Value.cpp (45KB) - IR值基类
    • Type.cpp (40KB) - 类型系统
    • Verifier.cpp (325KB) - IR验证器 (确保IR正确性,极其重要!)

IR的三种表示形式:

  1. 内存表示 - C++对象 (Value, Instruction, BasicBlock, Function, Module)
  2. 文本表示 - 人类可读的 .ll 文件
  3. 位码表示 - 紧凑的二进制 .bc 文件

IR示例:

define i32 @add(i32 %a, i32 %b) {
entry:
  %sum = add nsw i32 %a, %b
  ret i32 %sum
}

二、C++ → IR → 汇编 → 字节码完整流程

2.1 完整编译流程与关键源码追踪

让我们通过一个具体的例子追踪代码的完整转换过程:

C++源代码:

int sum_array(int* arr, int n) {
    int sum = 0;
    for (int i = 0; i < n; ++i) {
        sum += arr[i];
    }
    return sum;
}

阶段1: Clang前端 (C++ → LLVM IR)

源码入口: clang/lib/CodeGen/CodeGenFunction.cpp

关键学习文件:

  • clang/lib/CodeGen/CGExpr.cpp (294KB)

    • 学习C++表达式如何转换为IR指令
    • 例如: arr[i] 如何变为 getelementptr + load
  • clang/lib/CodeGen/CGStmt.cpp (128KB)

    • 学习控制流语句 (if, for, while) 如何转换为IR基本块和branch指令
  • clang/lib/CodeGen/CGCall.cpp (255KB)

    • 学习函数调用的ABI处理
    • 参数传递、返回值处理
  • clang/lib/CodeGen/CGClass.cpp (128KB)

    • 学习C++特有特性 (类、继承、虚函数) 的IR表示

生成的IR (未优化):

define i32 @sum_array(i32* %arr, i32 %n) {
entry:
  %sum = alloca i32, align 4
  %i = alloca i32, align 4
  store i32 0, i32* %sum, align 4
  store i32 0, i32* %i, align 4
  br label %for.cond

for.cond:
  %0 = load i32, i32* %i, align 4
  %cmp = icmp slt i32 %0, %n
  br i1 %cmp, label %for.body, label %for.end

for.body:
  %1 = load i32, i32* %i, align 4
  %idxprom = sext i32 %1 to i64
  %arrayidx = getelementptr inbounds i32, i32* %arr, i64 %idxprom
  %2 = load i32, i32* %arrayidx, align 4
  %3 = load i32, i32* %sum, align 4
  %add = add nsw i32 %3, %2
  store i32 %add, i32* %sum, align 4
  br label %for.inc

for.inc:
  %4 = load i32, i32* %i, align 4
  %inc = add nsw i32 %4, 1
  store i32 %inc, i32* %i, align 4
  br label %for.cond

for.end:
  %5 = load i32, i32* %sum, align 4
  ret i32 %5
}

阶段2: IR优化 (IR → Optimized IR)

源码入口: llvm/lib/Passes/PassBuilderPipelines.cpp (2508行)

这是定义优化流水线的核心文件,指定了不同优化级别 (-O1, -O2, -O3) 下Pass的执行顺序。

关键学习文件:

  • llvm/lib/Transforms/InstCombine/InstCombine.cpp

    • 局部指令优化,如常量折叠、死代码消除
  • llvm/lib/Transforms/Scalar/SROA.cpp

    • 将alloca内存操作提升为SSA寄存器
    • 消除上面IR中的load/store操作
  • llvm/lib/Transforms/Scalar/GVN.cpp

    • 全局值编号,消除冗余计算
  • llvm/lib/Transforms/Scalar/LICM.cpp

    • 循环不变量外提

优化后的IR (-O2):

define i32 @sum_array(i32* %arr, i32 %n) {
entry:
  %cmp = icmp sgt i32 %n, 0
  br i1 %cmp, label %for.body.preheader, label %for.end

for.body.preheader:
  br label %for.body

for.body:
  %i.0 = phi i32 [ 0, %for.body.preheader ], [ %inc, %for.body ]
  %sum.0 = phi i32 [ 0, %for.body.preheader ], [ %add, %for.body ]
  %idxprom = sext i32 %i.0 to i64
  %arrayidx = getelementptr inbounds i32, i32* %arr, i64 %idxprom
  %val = load i32, i32* %arrayidx, align 4
  %add = add nsw i32 %sum.0, %val
  %inc = add nuw nsw i32 %i.0, 1
  %exitcond = icmp eq i32 %inc, %n
  br i1 %exitcond, label %for.end, label %for.body

for.end:
  %sum.0.lcssa = phi i32 [ 0, %entry ], [ %add, %for.body ]
  ret i32 %sum.0.lcssa
}

阶段3: 代码生成 (Optimized IR → Assembly)

源码入口: llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp

关键学习文件:

  • llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (510KB)

    • 将LLVM IR转换为SelectionDAG (有向无环图)
    • 这是指令选择的第一步
  • llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp (1210KB!)

    • LLVM中最大的单个源文件
    • 对SelectionDAG进行优化和简化
  • llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp (253KB)

    • 将DAG中的操作合法化为目标平台支持的操作
    • 例如: 将i64操作转换为目标平台支持的i32操作
  • llvm/lib/CodeGen/SelectionDAG/InstrEmitter.cpp (56KB)

    • 将优化后的DAG转换为MachineInstr
  • llvm/lib/CodeGen/RegAllocGreedy.cpp (111KB)

    • 贪心寄存器分配算法
    • 将虚拟寄存器映射到物理寄存器

生成的X86-64汇编:

sum_array:
    test    esi, esi          # 检查n是否<=0
    jle     .LBB0_3           # 如果是,跳转到结尾
    
    xor     eax, eax          # sum = 0
    xor     ecx, ecx          # i = 0
    
.LBB0_2:                      # 循环体
    add     eax, dword ptr [rdi + 4*rcx]  # sum += arr[i]
    inc     ecx               # i++
    cmp     ecx, esi          # 比较i和n
    jl      .LBB0_2           # 如果i<n,继续循环
    
.LBB0_3:                      # 函数返回
    ret

阶段4: 汇编 → 对象文件/字节码

源码入口: llvm/lib/MC/ (Machine Code)

关键学习文件:

  • llvm/lib/MC/MCAsmBackend.cpp - 汇编后端
  • llvm/lib/MC/MCCodeEmitter.cpp - 机器码编码器
  • llvm/lib/MC/MCObjectWriter.cpp - 对象文件写入器
  • llvm/lib/CodeGen/AsmPrinter/ - 汇编代码打印

生成的对象文件: ELF格式的二进制文件,包含机器码、符号表、重定位信息等。

2.2 实践学习工具与命令

# 1. 查看C++到IR的转换 (前端输出)
clang -S -emit-llvm input.cpp -o output.ll
# 或生成未优化的IR
clang -S -emit-llvm -O0 input.cpp -o output_O0.ll

# 2. 查看优化后的IR
clang -S -emit-llvm -O2 input.cpp -o output_O2.ll

# 3. 使用opt工具单独运行优化Pass
opt -S -O2 input.ll -o output_O2.ll

# 4. 查看优化过程中每个Pass的效果
opt -S -O2 -mllvm -print-after-all input.ll 2>pass_output.txt

# 5. 查看IR到汇编的转换 (后端输出)
llc input.ll -o output.s
# 指定目标架构
llc -march=x86-64 input.ll -o output_x86.s
llc -march=aarch64 input.ll -o output_aarch64.s

# 6. 查看完整的编译阶段
clang -ccc-print-phases input.cpp
# 输出示例:
# 0: input.cpp, c++, {preprocess}
# 1: 0, c++, {compile}
# 2: 1, assembler, {assemble}
# 3: 2, object, {link}

# 7. 生成字节码文件 (bitcode)
clang -c -emit-llvm input.cpp -o output.bc

# 8. 字节码与文本IR互转
llvm-dis output.bc -o output.ll  # 字节码 → 文本
llvm-as output.ll -o output.bc   # 文本 → 字节码

三、各CPU架构指令处理异同

3.1 Target后端通用架构

每个CPU架构的后端都遵循相同的组织结构,这使得添加新架构变得系统化:

llvm/lib/Target/<ARCH>/
├── <ARCH>.td                      # 目标架构的顶级TableGen定义
├── <ARCH>ISelLowering.cpp         # IR → SelectionDAG lowering (通常是最大文件)
├── <ARCH>ISelDAGToDAG.cpp         # DAG模式匹配,选择具体指令
├── <ARCH>InstrInfo.td             # 指令集定义 (TableGen DSL)
├── <ARCH>RegisterInfo.td          # 寄存器文件定义
├── <ARCH>CallingConv.td           # 调用约定定义
├── <ARCH>Subtarget.cpp/h          # CPU子目标特性 (不同型号的差异)
├── <ARCH>TargetMachine.cpp        # TargetMachine实现 (入口点)
├── <ARCH>TargetTransformInfo.cpp  # TTI: 为优化器提供目标相关信息
├── <ARCH>FrameLowering.cpp        # 栈帧布局
├── <ARCH>InstrInfo.cpp            # 指令信息查询
├── <ARCH>RegisterInfo.cpp         # 寄存器信息查询
├── <ARCH>AsmPrinter.cpp           # 汇编代码输出
├── MCTargetDesc/                  # 机器代码层描述
├── TargetInfo/                    # 目标注册信息
└── Schedule*.td                   # 指令调度模型 (性能优化)

3.2 三大主流架构深度对比

X86架构 (llvm/lib/Target/X86/)

架构特点:

  • CISC (Complex Instruction Set Computer)
  • 寄存器数量少 (16个通用寄存器 x86-64)
  • 复杂的指令编码
  • 丰富的历史包袱 (16位/32位/64位兼容)
  • 复杂的SIMD演进 (MMX → SSE → AVX → AVX2 → AVX-512)

关键文件:

  • X86ISelLowering.cpp (2556KB!) - LLVM项目中最大的单个源文件

    • 处理X86特有的IR lowering逻辑
    • 包含大量平台特定的优化策略
  • X86InstrAVX512.td (716KB) - AVX-512指令定义

  • X86InstrSSE.td (414KB) - SSE指令定义

  • X86InstrInfo.cpp (355KB) - 指令信息查询

X86特有优化Pass:

X86CmovConversion.cpp      - 将条件分支转换为cmov指令
X86FixupBWInsts.cpp        - 修正字节/字指令的问题
X86AvoidStoreForwardingBlocks.cpp - 避免存储转发阻塞
X86DomainReassignment.cpp  - 寄存器域重分配 (X87 vs SSE)
X86FloatingPoint.cpp       - X87浮点栈管理
X86VZeroUpper.cpp          - 插入VZEROUPPER指令 (AVX/SSE转换)

调度模型 (Scheduling Models):

X86SchedBroadwell.td       - Broadwell微架构
X86SchedIceLake.td         - Ice Lake微架构
X86SchedSapphireRapids.td  - Sapphire Rapids (236KB!)
X86SchedZnver3.td          - AMD Zen 3

AArch64架构 (llvm/lib/Target/AArch64/)

架构特点:

  • RISC (Reduced Instruction Set Computer)
  • 31个通用寄存器 (64位)
  • 固定的指令长度 (32位)
  • 负载-存储架构 (load-store architecture)
  • SVE (Scalable Vector Extension) 可伸缩向量扩展

关键文件:

  • AArch64ISelLowering.cpp - IR lowering
  • AArch64InstrInfo.td - 指令定义
  • AArch64SVEInstrFormat.td - SVE向量指令
  • AArch64TargetTransformInfo.cpp - TTI实现

AArch64特有优化Pass:

AArch64A57FPLoadBalancing.cpp   - A57浮点负载均衡
AArch64CondBrTuning.cpp         - 条件分支调优
AArch64LoadStoreOptimizer.cpp   - 负载存储优化
AArch64SIMDInstructionOpt.cpp   - SIMD指令优化

RISC-V架构 (llvm/lib/Target/RISCV/)

架构特点:

  • 开源RISC架构
  • 模块化设计 (基础ISA + 扩展)
  • 31个通用寄存器
  • 可扩展的向量扩展 (RISC-V V)

关键文件:

  • RISCVISelLowering.cpp - IR lowering
  • RISCVInstrInfo.td - 基础指令
  • RISCVInstrInfoV.td - 向量扩展指令

RISC-V特有优化Pass:

RISCVInsertWriteVXRS.cpp      - 插入向量寄存器写回
RISCVVSetVliBypass.cpp        - VSetVLI旁路优化

3.3 架构差异关键学习点

1. 调用约定 (Calling Convention)

X86-64 (System V AMD64 ABI):

参数传递顺序:
1. RDI, RSI, RDX, RCX, R8, R9 (前6个整数/指针参数)
2. XMM0-XMM7 (前8个浮点参数)
3. 其余参数通过栈传递

文件: X86CallingConv.td (49KB)

AArch64 (AAPCS64):

参数传递顺序:
1. X0-X7 (前8个整数/指针参数)
2. V0-V7 (前8个浮点/向量参数)
3. 其余参数通过栈传递

文件: AArch64CallingConvention.td

关键差异:

  • X86有复杂的红色区域 (Red Zone) 概念
  • AArch64有更规则的寄存器使用
  • 栈对齐要求不同

2. 寄存器分配策略

X86:

  • 寄存器压力极大 (只有16个通用寄存器)
  • 频繁需要spill (溢出到内存)
  • 寄存器分配器的质量对性能影响巨大
  • 需要智能的live range splitting

AArch64/RISC-V:

  • 31个通用寄存器,压力较小
  • spill频率较低
  • 但仍需要优化以充分利用寄存器

3. 指令选择模式 (Instruction Selection)

学习TableGen DSL (Domain Specific Language):

// 示例: 定义一个加法指令
def ADDrr : Instruction {
  let Namespace = "X86";
  let DAG = (outs GR32:$dst), (ins GR32:$src1, GR32:$src2);
  let Inst{31-0} = 0x01C0;  // 机器码编码
  let Pattern = [(set GR32:$dst, (add GR32:$src1, GR32:$src2))];
  let Constraints = "$src1 = $dst";  // src1和dst必须是同一寄存器
}

关键概念:

  • Pattern: 描述这个指令可以匹配什么样的DAG模式
  • DAG: 定义操作数和结果
  • Inst: 机器码编码
  • Constraints: 操作数约束

4. 调度模型 (Scheduling Machine Model)

定义指令的延迟、吞吐量和执行端口:

// 示例: 定义指令的调度信息
def : InstRW<[WriteALU], (insts ADDrr)>;

def WriteALU : WriteRes<ALUUnit, [ALU]>;

关键指标:

  • Latency (延迟): 指令执行需要多少个周期
  • Throughput (吞吐量): 每个周期能执行多少条此类指令
  • Execution Ports (执行端口): 指令可以在哪些执行单元运行

四、基于IR中间表达的Codegen优化

4.1 Pass基础设施

LLVM的优化通过Pass机制实现,Pass是可组合的优化单元。

核心文件:

  • llvm/include/llvm/Pass.h - Pass基类定义
  • llvm/lib/Passes/PassBuilder.cpp (116KB) - 新Pass管理器构建器
  • llvm/lib/Passes/PassBuilderPipelines.cpp (105KB) - 优化流水线定义
  • llvm/lib/Passes/PassRegistry.def (39KB) - Pass注册表

Pass的分类:

按作用范围分:
├── FunctionPass      - 函数内优化 (不修改函数边界)
├── ModulePass        - 模块级优化 (可跨函数)
├── CallGraphSCCPass  - 调用图SCC顺序优化
├── LoopPass          - 循环级优化
└── RegionPass        - 区域级优化

按新旧分:
├── Legacy Pass Manager (旧)  - 基于PassManager类
└── New Pass Manager (新)     - 基于PassManager<T>模板

4.2 优化Pass分类学习路径

层级1: 函数内标量优化 (Scalar Optimizations)

位置: llvm/lib/Transforms/Scalar/

核心Pass:

  1. InstCombine (指令合并)

    • 文件: llvm/lib/Transforms/InstCombine/
    • 作用: 局部指令优化,最基本的优化
    • 示例:
      ; 优化前
      %1 = add i32 %x, 0
      ; 优化后
      %1 = %x  (直接消除)
      
  2. SROA (Scalar Replacement of Aggregates)

    • 文件: SROA.cpp
    • 作用: 将聚合体 (struct, array) 的内存操作转换为标量SSA
    • 重要性: 几乎是所有优化的前提
    • 示例:
      ; 优化前
      %p = alloca { i32, i32 }
      %q = getelementptr { i32, i32 }, { i32, i32 }* %p, i32 0, i32 0
      store i32 1, i32* %q
      ; 优化后
      ; 直接变成 SSA 寄存器,消除 alloca/store/load
      
  3. GVN (Global Value Numbering)

    • 文件: GVN.cpp
    • 作用: 消除全局冗余计算
    • 原理: 为每个计算分配编号,相同编号的计算只执行一次
  4. EarlyCSE (Early Common Subexpression Elimination)

    • 文件: EarlyCSE.cpp
    • 作用: 早期公共子表达式消除
    • 比GVN轻量,在优化早期运行
  5. LICM (Loop Invariant Code Motion)

    • 文件: LICM.cpp
    • 作用: 将循环不变量外提到循环外
    • 示例:
      // 优化前
      for (int i = 0; i < n; ++i) {
          sum += arr[i] * constant;  // constant每次循环都乘
      }
      // 优化后
      temp = constant;
      for (int i = 0; i < n; ++i) {
          sum += arr[i] * temp;  // 不需要每次乘
      }
      
  6. Loop Unroll (循环展开)

    • 文件: LoopUnrollPass.cpp
    • 作用: 展开循环以减少分支开销
    • 示例:
      // 优化前
      for (int i = 0; i < 4; ++i) {
          sum += arr[i];
      }
      // 优化后
      sum += arr[0];
      sum += arr[1];
      sum += arr[2];
      sum += arr[3];
      

层级2: 过程间优化 (Inter-Procedural Optimization, IPO)

位置: llvm/lib/Transforms/IPO/

核心Pass:

  1. Inliner (内联)

    • 文件: Inliner.cpp
    • 作用: 将函数调用替换为函数体
    • 最重要的优化之一,为后续优化创造机会
    • 启发式策略: 基于代码大小增长、调用频率等
  2. GlobalOpt (全局优化)

    • 文件: GlobalOpt.cpp
    • 作用: 全局变量优化
    • 例如: 将只被内部使用的全局变量转为局部变量
  3. GlobalDCE (Dead Code Elimination)

    • 文件: GlobalDCE.cpp
    • 作用: 消除未使用的全局符号
  4. ArgumentPromotion (参数提升)

    • 文件: ArgumentPromotion.cpp
    • 作用: 将指针参数拆分为多个标量参数
    • 示例:
      // 优化前
      void foo(struct {int a, int b}* s);
      // 优化后
      void foo(int a, int b);
      
  5. SCCP (Sparse Conditional Constant Propagation)

    • 文件: SCCP.cpp
    • 作用: 稀疏条件常量传播
    • 结合控制流分析进行常量传播

层级3: 向量化优化 (Vectorization)

位置: llvm/lib/Transforms/Vectorize/

核心Pass:

  1. Loop Vectorizer (循环向量化)

    • 文件: LoopVectorize.cpp
    • 作用: 将标量循环转换为向量指令
    • 示例:
      // 优化前 (标量)
      for (int i = 0; i < n; ++i) {
          c[i] = a[i] + b[i];
      }
      // 优化后 (SIMD向量)
      // 使用AVX/SSE指令,每次处理4/8个元素
      for (int i = 0; i < n; i += 4) {
          __m256 va = load(&a[i]);
          __m256 vb = load(&b[i]);
          __m256 vc = _mm256_add_ps(va, vb);
          store(&c[i], vc);
      }
      
  2. SLP Vectorizer (Superword-Level Parallelism)

    • 文件: SLPVectorizer.cpp
    • 作用: 在基本块内发现并行向量机会
    • 不依赖循环,寻找独立的标量操作组合为向量
  3. VectorCombine (向量指令合并)

    • 文件: VectorCombine.cpp
    • 作用: 优化已向量化的代码

4.3 TargetTransformInfo (TTI) 机制

TTI是连接前端优化器和后端目标架构的桥梁:

作用:

  • 前端优化器通过TTI查询目标架构的特性
  • 后端通过TTI告诉优化器"什么在这个平台上是便宜的/昂贵的"

关键文件:

  • llvm/include/llvm/Analysis/TargetTransformInfo.h - TTI接口定义
  • llvm/lib/Target/X86/X86TargetTransformInfo.cpp (353KB) - X86实现
  • llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp - AArch64实现

TTI提供的信息示例:

// 查询某种操作的代价
unsigned getArithmeticInstrCost(unsigned Opcode, Type *Ty, ...);

// 查询向量化因子
unsigned getNumberOfRegisters(bool Vector);

// 查询内存访问代价
unsigned getMemoryOpCost(unsigned Opcode, Type *Src, ...);

// 查询是否支持某种操作
bool hasVectorInstrs();

4.4 SelectionDAG优化流程

SelectionDAG是LLVM指令选择的核心数据结构:

完整流程:

LLVM IR
   ↓
SelectionDAGBuilder (IR → DAG)
   ↓ [SelectionDAGBuilder.cpp 510KB]
SelectionDAG
   ↓
DAGCombiner (DAG优化)
   ↓ [DAGCombiner.cpp 1210KB - LLVM最大文件!]
Optimized DAG
   ↓
LegalizeDAG (类型/操作合法化)
   ↓ [LegalizeDAG.cpp 253KB]
Legal DAG
   ↓
Instruction Selection (模式匹配选择指令)
   ↓ [ISelDAGToDAG.cpp]
Machine DAG
   ↓
ScheduleDAG (指令调度)
   ↓
Scheduled Machine DAG
   ↓
InstrEmitter (DAG → MachineInstr)
   ↓ [InstrEmitter.cpp 56KB]
MachineInstr

DAG节点示例:

    add
   /   \
 load  load
  |     |
 ptr   ptr

4.5 GlobalISel (新一代指令选择)

GlobalISel是LLVM正在推广的新一代指令选择框架:

位置: llvm/lib/CodeGen/GlobalISel/

优势:

  • 比SelectionDAG更模块化
  • 支持GISel Combiner (类似InstCombine的DAG优化)
  • 更好的类型合法性处理

核心文件:

  • InstructionSelect.cpp - 指令选择
  • Legalizer.cpp - 合法化
  • RegisterBankInfo.cpp - 寄存器银行信息

五、GPU异构处理源码学习

5.1 GPU编译整体架构

GPU编译与CPU编译有显著差异,主要体现在:

  • 不同的执行模型 (SIMT vs 标量)
  • 不同的内存层次结构
  • 不同的地址空间

CUDA/HIP支持 (Clang前端)

位置: clang/lib/CodeGen/

核心文件:

  • CGCUDANV.cpp (55KB) - CUDA NVPTX运行时支持
  • CGCUDARuntime.cpp/h - CUDA运行时接口
  • CGOpenMPRuntimeGPU.cpp (93KB) - OpenMP GPU offload支持
  • CGGPUBuiltin.cpp (7KB) - GPU内置函数处理
  • CGOpenMPRuntime.cpp (561KB!) - OpenMP运行时 (包含GPU支持)

GPU Target后端

1. NVPTX (NVIDIA GPU)

位置: llvm/lib/Target/NVPTX/

编译流程:

CUDA C++ → Clang → LLVM IR → NVPTX后端 → PTX → ptxas → SASS

关键文件:

  • NVPTXISelLowering.cpp (294KB) - IR lowering
  • NVPTXISelDAGToDAG.cpp (85KB) - 指令选择
  • NVPTXInstrInfo.td (100KB) - 指令定义
  • NVPTXIntrinsics.td (289KB) - NVIDIA内置函数
  • NVPTXAsmPrinter.cpp (71KB) - PTX汇编输出
  • NVPTXTargetTransformInfo.cpp - GPU优化提示

NVPTX特有优化:

NVPTXLowerArgs.cpp         - 参数lowering (GPU特有的ABI)
NVPTXImageOptimizer.cpp    - 图像/纹理优化
NVPTXAllocaHoisting.cpp    - alloca提升
NVVMReflect.cpp            - NVVM反射优化
NVVMIntrRange.cpp          - NVVM intrinsic范围优化

输出示例 (PTX代码):

.visible .entry sum_array(
    .param .u64 sum_array_param_0,
    .param .u32 sum_array_param_1
)
{
    .reg .s32 %r<5>;
    .reg .s64 %rd<6>;
    
    ld.param.u64 %rd1, [sum_array_param_0];
    ld.param.u32 %r1, [sum_array_param_1];
    // ... PTX指令
}
2. AMDGPU (AMD GPU)

位置: llvm/lib/Target/AMDGPU/ (300+ 文件,最复杂的GPU后端!)

编译流程:

OpenCL/HIP → Clang → LLVM IR → AMDGPU后端 → GCN ISA → HSACO

关键文件:

  • AMDGPUISelLowering.cpp (231KB) - IR lowering
  • AMDGPUInstructionSelector.cpp (259KB) - 指令选择
  • SIISelLowering.cpp (779KB!) - Southern Islands系列 lowering
  • AMDGPULegalizerInfo.cpp (306KB) - GlobalISel合法化
  • AMDGPURegisterBankInfo.cpp (222KB) - 寄存器银行

AMDGPU特有优化:

AMDGPUAnnotateUniformValues.cpp    - 统一值标注 (warp内所有线程相同)
AMDGPUPromoteAlloca.cpp            - 寄存器/共享内存提升
AMDGPUCodeGenPrepare.cpp           - 代码生成准备
SILoadStoreOptimizer.cpp           - 负载存储优化
SIOptimizeExecMasking.cpp          - 执行掩码优化 (warp divergence)
GCNHazardRecognizer.cpp            - GCN硬件冒险检测

5.2 OpenMP Offload机制

OpenMP Offload允许将计算任务offload到GPU等加速器:

整体架构:

位置: /home/admin/work/llvm-project/offload/
├── libomptarget/          # OpenMP目标端运行时
├── plugins-nextgen/       # GPU插件 (CUDA, AMD, Level Zero)
└── tools/                 # offload编译工具

Clang端的Offload支持:

  • clang/lib/CodeGen/CGOpenMPRuntime.cpp (561KB) - OpenMP运行时
  • clang/lib/CodeGen/CGOpenMPRuntimeGPU.cpp (93KB) - GPU专用OpenMP
  • clang/lib/CodeGen/CGStmtOpenMP.cpp (369KB) - OpenMP语句代码生成

编译流程示例:

#pragma omp target teams distribute parallel for
for (int i = 0; i < n; ++i) {
    c[i] = a[i] + b[i];
}

编译命令:

clang -fopenmp -fopenmp-targets=nvptx64-nvidia-cuda input.cpp -o output

5.3 GPU vs CPU优化差异关键点

1. 内存模型差异

CPU内存模型:

寄存器 → L1 Cache → L2 Cache → L3 Cache → 主存

GPU内存模型:

寄存器文件 → 共享内存 (Shared Memory) → 常量内存 → 全局内存 (Global Memory)
              (可编程,低延迟)            (只读,缓存)    (高延迟,高带宽)

LLVM IR中的地址空间:

addrspace(0) - 全局/默认地址空间
addrspace(1) - GPU全局内存
addrspace(3) - GPU共享内存
addrspace(4) - GPU常量内存
addrspace(5) - GPU局部内存

2. 执行模型差异

CPU: 标量执行,单线程或少量线程

GPU: SIMT (Single Instruction Multiple Threads)

  • Warp/Wavefront: 32 (NVIDIA) 或 64 (AMD) 个线程为一组
  • 所有线程执行相同指令,但处理不同数据
  • Branch Divergence: warp内线程走不同分支时性能下降

处理Divergence的优化:

AMDGPU:
- SIOptimizeExecMasking.cpp - 执行掩码优化
- AMDGPUUnifyDivergentExitNodes.cpp - 统一发散退出节点

NVPTX:
- 通过控制流重构减少divergence

3. 特殊GPU优化技术

内存合并 (Memory Coalescing):

// 好的访问模式 (合并)
for (int i = 0; i < 256; ++i) {
    data[i] = ...;  // 相邻线程访问相邻内存
}

// 差的访问模式 (不合并)
for (int i = 0; i < 256; ++i) {
    data[i * 32] = ...;  // 相邻线程访问间隔很大的内存
}

共享内存优化:

AMDGPUPromoteAlloca.cpp - 将频繁访问的alloca提升到共享内存
NVPTXSharedMemOpt - NVIDIA的共享内存优化

5.4 GPU学习实践路径

# 1. 编译CUDA代码到PTX
clang -cc1 -triple nvptx64-nvidia-cuda -emit-llvm input.cu -o output.ll

# 2. 使用NVPTX后端生成PTX
llc -march=nvptx64 output.ll -o output.ptx

# 3. 编译AMDGPU代码
llc -march=amdgcn -mcpu=gfx906 output.ll -o output.amdgcn

# 4. OpenMP offload完整编译
clang -fopenmp -fopenmp-targets=nvptx64-nvidia-cuda input.cpp -o output

# 5. 查看GPU特定的IR优化
opt -S -O3 -mtriple=nvptx64-nvidia-cuda input.ll -o output_gpu.ll

六、系统学习路线建议

阶段1: 基础入门 (1-2个月)

目标: 理解LLVM IR和基本的编译流程

学习内容:

  1. 理解IR表示

    • 阅读 llvm/docs/LangRef.rst - IR语言参考手册
    • 编写简单C++代码,用 clang -S -emit-llvm 观察IR
    • 学习 llvm/lib/IR/ 核心数据结构
  2. 学习Pass机制

    • 阅读 llvm/docs/WritingAnLLVMPass.rst
    • 编写简单的"Hello World" Pass
    • 学习使用 opt 工具
  3. 理解完整编译流程

    • 追踪一个函数从C++到汇编的完整路径
    • 使用 -mllvm -print-after-all 查看每个Pass后的IR变化

实践项目:

  • 编写一个简单的常量传播Pass
  • 用不同优化级别编译代码,对比IR差异

阶段2: 中端优化深入 (2-3个月)

目标: 掌握主要的IR优化技术

学习内容:

  1. 标量优化

    • 深入学习 SROA, GVN, EarlyCSE, LICM
    • 阅读源码: llvm/lib/Transforms/Scalar/
    • -O1, -O2, -O3 对比优化效果
  2. 向量化

    • 学习 Loop Vectorizer, SLP Vectorizer
    • 阅读 llvm/lib/Transforms/Vectorize/
    • 理解 TargetTransformInfo 的作用
  3. 过程间优化

    • 学习 Inliner, GlobalOpt
    • 理解 LTO (Link Time Optimization)
    • 阅读 llvm/lib/Linker/, llvm/lib/LTO/

实践项目:

  • 分析一个实际程序的优化报告 (-Rpass=*)
  • 修改某个优化的启发式策略,观察效果

阶段3: 后端CodeGen深入 (3-4个月)

目标: 理解指令选择、寄存器分配和指令调度

学习内容:

  1. 指令选择

    • 学习 TableGen 语法 (*.td 文件)
    • 理解 SelectionDAG 的完整流程
    • 选择一个相对简单的架构深入学习 (推荐 RISC-V)
  2. 寄存器分配

    • 学习 llvm/lib/CodeGen/RegAllocGreedy.cpp
    • 理解 LiveInterval, VirtRegMap
    • 学习 spill/reload 机制
  3. 指令调度

    • 学习 ScheduleDAG
    • 理解机器模型 (SchedMachineModel)
    • 对比不同CPU的调度表

实践项目:

  • 为一个简单的RISC-like架构添加新指令
  • 分析寄存器分配的效果

阶段4: GPU异构计算 (3-4个月)

目标: 掌握GPU编译和优化技术

学习内容:

  1. GPU架构基础

    • 学习 CUDA/OpenCL 编程模型
    • 理解 SIMT、warp、shared memory
    • 阅读 NVPTX/AMDGPU 后端文档
  2. GPU优化技术

    • 学习地址空间优化
    • 理解 warp divergence 处理
    • 学习内存合并 (coalescing)
  3. OpenMP Offload

    • 学习 offload/ 目录结构
    • 理解 target region 编译流程
    • 实践多GPU编译

实践项目:

  • 编写一个简单的GPU kernel,观察编译结果
  • 分析GPU代码的性能瓶颈

阶段5: 高级主题与社区参与 (持续)

目标: 成为LLVM贡献者

学习内容:

  1. 性能分析工具

    • 学习 llvm-mca (Machine Code Analyzer)
    • 使用 opt-viewer 分析优化决策
    • 学习 PGO (Profile Guided Optimization)
  2. 新特性开发

    • 参与 LLVM 社区 (Discourse, Discord)
    • 阅读 RFC (Request for Comments)
    • 学习提交 Patch 的流程

实践项目:

  • 提交第一个bug fix
  • 实现一个新的小优化Pass

七、关键源码文件索引

必读核心文件 (按学习优先级排序)

  1. llvm/docs/LangRef.rst - IR语言参考手册 (理解IR的基础)
  2. llvm/lib/Passes/PassBuilderPipelines.cpp - 优化流水线定义
  3. llvm/lib/Transforms/InstCombine/InstCombine.cpp - 指令合并实现
  4. llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp - 指令选择入口
  5. llvm/lib/Target/X86/X86ISelLowering.cpp - X86 lowering (最大文件)
  6. llvm/lib/Target/NVPTX/NVPTXISelLowering.cpp - GPU lowering示例
  7. llvm/lib/Transforms/Scalar/SROA.cpp - SROA优化
  8. llvm/lib/Transforms/Vectorize/LoopVectorize.cpp - 循环向量化

高级调试技巧

# 1. 查看Pass执行顺序
opt -O2 -debug-pass=Arguments input.ll

# 2. 查看每个Pass后的IR (极其有用!)
opt -O2 -mllvm -print-after-all input.ll 2>&1 | less

# 3. 查看DAG选择过程
llc -debug-only=isel input.ll 2>&1 | less

# 4. 查看寄存器分配过程
llc -debug-only=regalloc input.ll 2>&1 | less

# 5. 生成优化报告
opt -O2 -Rpass=.* -Rpass-analysis=.* input.ll -o output.ll

# 6. 使用opt-viewer可视化
opt-viewer.py opt_record.yaml

# 7. 分析机器码性能
llvm-mca -mcpu=skylake output.s

# 8. 查看特定Pass的统计信息
opt -O2 -stats input.ll -o output.ll 2>&1 | grep "PassName"

八、学习资源推荐

官方文档

  • LLVM Language Reference: llvm/docs/LangRef.rst - IR语法和语义的权威参考
  • LLVM Programmer's Manual: llvm/docs/ProgrammersManual.rst - 开发者手册
  • Writing an LLVM Pass: llvm/docs/WritingAnLLVMPass.rst - Pass编写教程
  • CodeGenerator Design: llvm/docs/CodeGenerator.rst - 后端设计文档
  • Target-independent Code Generator: 理解CodeGen架构

实践工具链

  • clang - C/C++/Obj-C前端编译器
  • opt - LLVM IR优化器和转换工具
  • llc - LLVM后端,生成汇编/机器码
  • llvm-dis / llvm-as - 字节码与文本IR互转
  • llvm-mca - 机器码性能分析器
  • lli - LLVM JIT执行器
  • opt-viewer - 优化决策可视化工具

社区与交流

推荐书籍

  1. "Getting Started with LLVM Core Libraries" - 入门必读
  2. "Optimizing Compilers for Modern Architectures" - 编译器优化经典
  3. "Engineering a Compiler" - 编译器工程

在线课程

  • CMU 15-745: Optimizing Compilers (使用LLVM)
  • UIUC CS 426: Compiler Construction

附录:LLVM项目结构快速参考

llvm-project/
├── clang/                    # C/C++/Obj-C前端
│   ├── lib/AST/             # 抽象语法树
│   ├── lib/Sema/            # 语义分析
│   └── lib/CodeGen/         # IR生成
│
├── llvm/                     # LLVM核心
│   ├── lib/IR/              # IR实现
│   ├── lib/Transforms/      # 优化Pass
│   │   ├── InstCombine/     # 指令合并
│   │   ├── Scalar/          # 标量优化
│   │   ├── Vectorize/       # 向量化
│   │   └── IPO/             # 过程间优化
│   ├── lib/CodeGen/         # 代码生成
│   │   ├── SelectionDAG/    # 指令选择
│   │   └── GlobalISel/      # 新一代指令选择
│   ├── lib/Target/          # 目标后端
│   │   ├── X86/             # X86后端
│   │   ├── AArch64/         # ARM 64位后端
│   │   ├── RISCV/           # RISC-V后端
│   │   ├── NVPTX/           # NVIDIA GPU后端
│   │   └── AMDGPU/          # AMD GPU后端
│   └── lib/Passes/          # Pass管理器
│
├── lld/                      # 链接器
├── libcxx/                   # C++标准库
├── compiler-rt/              # 运行时库
├── lldb/                     # 调试器
└── offload/                  # OpenMP offload运行时

学习建议:

  1. 从简单的例子开始,逐步深入
  2. 多用调试工具观察IR变化
  3. 阅读源码时结合文档
  4. 参与社区,提出问题
  5. 动手实践,编写自己的Pass

祝学习顺利!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容