C-MinusF教学编译器:LLVM IR级循环优化与数据流分析实战

发布时间:2026/10/3 8:46:41
C-MinusF教学编译器:LLVM IR级循环优化与数据流分析实战 简介本资源是中国科学技术大学2020年秋季《编译原理》课程高分实践项目成果面向计算机专业本科生、编译技术初学者及系统编程爱好者完整呈现一个具备工业级模块划分的C-MinusF编译器实现。项目覆盖词法分析、语法分析、LLVM IR生成及循环不变式外提、常量传播、活跃变量分析等核心优化功能兼具教学深度与工程实践价值。压缩包共245个文件含62个.cminus测试用例、27个.cpp源码、19个.md文档含设计说明与实验报告、12个.ll中间代码、10个.tokens词法输出及syntax_tree等结构化中间产物总大小3.69MB目录组织清晰便于按编译流程分阶段研读调试。已有85人学习下载读者可直接复现从源码到优化IR的全流程获取完整可运行代码、多组测试用例、语法树可视化输出及各阶段调试日志是深入理解编译器构造与优化机制的优质实操范本。1. 这不是作业提交包而是一套能跑通完整编译流水线的C-MinusF工业级教学编译器如果你正在啃《编译原理》清华大学出版社第三版第二章词法分析、第四章语法分析、第九章中间代码生成、第十章优化——尤其是循环不变式外提Loop-Invariant Code Motion、常量传播Constant Propagation、活跃变量分析Live Variable Analysis这些被学生称为“玄学三连”的硬核章节那么这个来自中国科学技术大学2020年秋季学期的满分实践项目就是你手头最接近真实工业编译器脉搏的教学实现。它不是用Java写个玩具Parser再print AST就交差的实验而是从.cminusf源码出发经词法分析→语法分析→语义检查→LLVM IR生成→IR级优化→LLVM后端编译成可执行文件的全链路闭环。关键在于所有优化均在LLVM IR上完成而非抽象语法树或三地址码所有算法均严格对应龙书Dragon Book第9、10章理论但落地时绕开了教科书里没写的“黑匣子”——比如如何把活跃变量分析结果映射到LLVM的Value与Instruction生命周期、如何在LLVM Pass中安全插入/删除指令而不破坏SSA形式、为什么循环不变式外提必须配合支配边界Dominance Frontier计算。它适合两类人一是山科大、燕山大学等高校编译原理课程设计卡在IR优化环节的同学二是想跳过ANTLR自动生成、亲手打磨控制流图CFG构建与数据流分析框架的进阶学习者。这不是答案抄写器是能让你debug到凌晨三点、却在看到opt -O2输出和自己Pass输出完全一致时拍桌大笑的血泪工程。2. 从C-MinusF源码到LLVM IR四阶段编译器骨架搭建与词法/语法分析实现2.1 C-MinusF语言规范与词法分析器手写逻辑非Lex/YaccC-MinusF是C-Minus的扩展子集核心差异在于支持for循环含初始化、条件、增量三部分、函数参数传值、局部变量作用域、以及int一维数组如int a[10]。词法分析器采用纯C手写状态机不依赖Flex或ANTLR——这是中科大该课程明确要求的“理解底层机制”训练点。其Token定义严格对齐LLVM的llvm::StringRef与llvm::SMLoc定位需求// Token.h 关键枚举截取 enum TokenType { TOK_EOF, TOK_IDENTIFIER, // 变量名、函数名 TOK_NUMBER, // 整数字面量 TOK_STRING, // 字符串字面量仅用于printf TOK_FOR, TOK_WHILE, TOK_IF, TOK_ELSE, TOK_RETURN, TOK_INT, TOK_VOID, TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PERCENT, TOK_EQ, TOK_NEQ, TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_ASSIGN, // TOK_SEMI, TOK_COMMA, TOK_LPAREN, TOK_RPAREN, TOK_LBRACE, TOK_RBRACE, TOK_LBRACKET, TOK_RBRACKET, TOK_ARROW // - 用于结构体访问C-MinusF暂未启用预留 };提示词法分析器输出的每个Token必须携带llvm::SMLoc源码位置这是后续错误报告和LLVM调试信息Debug Info生成的基础。中科大评分细则中缺失位置信息直接扣15%。词法分析主循环采用双缓冲区设计避免频繁内存分配// Lexer.cpp 核心扫描逻辑简化 bool Lexer::advance() { curPos nextPos; if (curPos buf.size()) return false; char c buf[curPos]; nextPos curPos 1; switch (c) { case : case \t: case \n: case \r: skipWhitespace(); // 跳过空白不生成Token return advance(); // 递归调用直到非空白 case 0...9: return lexNumber(); case a...z: case A...Z: case _: return lexIdentifier(); case : case -: case *: case /: case %: return lexOperator(); // ... 其他case } return false; }lexNumber()需处理十进制整数无小数点、无科学计数法lexIdentifier()需区分关键字如for,int与标识符此处用std::unordered_setstd::string预存32个关键字O(1)查表。关键细节C-MinusF允许下划线开头的标识符如_temp但关键字禁止以下划线开头——词法分析器必须拒绝_for这类非法token否则语法分析阶段会因TOK_IDENTIFIER与TOK_FOR冲突而崩溃。2.2 基于递归下降的语法分析器与AST构建语法分析采用手工递归下降Recursive Descent不使用Yacc/Bison。选择此方案的核心原因是LLVM IR生成需要精确控制AST节点的内存布局与生命周期而自动生成工具产生的AST往往嵌套过深、指针关系混乱不利于后续遍历优化。C-MinusF的BNF文法被拆解为以下核心函数函数名对应文法规则返回AST节点类型parseProgram()program → function-decl*ProgramNode*parseFunctionDecl()function-decl → type-specifier IDENTIFIER ( params ) compound-stmtFunctionDeclNode*parseStmt()stmt → expr-stmt | compound-stmt | if-stmt | while-stmt | for-stmt | return-stmtStmtNode*parseForStmt()for-stmt → FOR ( expr? ; expr? ; expr? ) stmtForStmtNode*parseExpr()expr → assign-exprExprNode*ForStmtNode是循环优化的基石其字段设计直指后续优化需求struct ForStmtNode : StmtNode { ExprNode* init; // for(init; cond; incr) → 初始化表达式 ExprNode* cond; // 条件表达式 ExprNode* incr; // 增量表达式 StmtNode* body; // 循环体 llvm::BasicBlock* loopHeader; // LLVM CFG中循环头块后期填充 llvm::BasicBlock* loopLatch; // LLVM CFG中循环尾块后期填充 std::vectorllvm::Value* loopInvariantExprs; // 外提后的不变式后期填充 };注意loopHeader与loopLatch字段在语法分析阶段为空仅作占位。它们将在LLVM IR生成阶段由CFGBuilder根据控制流自动创建并赋值。这种“延迟绑定”设计避免了语法分析器与LLVM API的强耦合符合课程模块化评分要求。parseExpr()采用优先级解析Pratt Parsing处理运算符优先级比传统递归下降更简洁。例如a b * c的AST结构为BinaryOp() / \ VarRef(a) BinaryOp(*) / \ VarRef(b) VarRef(c)而非(a b) * c——这保证了算术表达式语义正确性是后续常量传播的前提。2.3 语义分析符号表管理与类型检查的双重校验语义分析分两层第一层是作用域感知的符号表SymbolTable构建第二层是类型兼容性检查。C-MinusF仅支持int和void类型但数组、函数参数、返回值类型需严格匹配。符号表采用栈式结构每进入一个{}块就pushScope()退出时popScope()class SymbolTable { std::stackstd::unordered_mapstd::string, SymbolEntry scopes; public: void pushScope() { scopes.push({}); } void popScope() { scopes.pop(); } bool insert(const std::string name, const SymbolEntry entry) { if (scopes.empty()) return false; auto top scopes.top(); if (top.find(name) ! top.end()) return false; // 重复声明 top[name] entry; return true; } SymbolEntry* lookup(const std::string name) { for (auto it scopes.rbegin(); it ! scopes.rend(); it) { auto found it-find(name); if (found ! it-end()) return found-second; } return nullptr; } };SymbolEntry包含类型、是否为数组、维度大小、是否为函数等元数据struct SymbolEntry { enum Kind { VAR, ARRAY, FUNCTION }; Kind kind; llvm::Type* llvmType; // int32, int32*, void(), etc. size_t arraySize; // 若kindARRAY此值0 std::vectorllvm::Type* paramTypes; // 若kindFUNCTION llvm::Type* returnType; // 若kindFUNCTION };类型检查在AST遍历中完成。例如BinaryOpNode检查左右操作数类型是否均为intArraySubscriptNode检查基址是否为int*且索引为int。关键陷阱C-MinusF规定int a[10]声明后a本身是int*类型而非int[10]因此a[5]合法但a1也合法——这直接影响LLVM IR中getelementptr指令的生成方式。若类型检查忽略此规则生成的GEP指令将偏移错误字节数导致运行时内存越界。3. LLVM IR生成从AST到模块的精准映射与控制流图构建3.1 LLVM环境初始化与模块/函数创建项目使用LLVM 10.0中科大2020年教学环境标配需链接LLVMCore,LLVMSupport,LLVMCodeGen等库。IR生成入口为CodeGenerator::generateIR(ProgramNode* root)// CodeGenerator.h class CodeGenerator { llvm::LLVMContext context; std::unique_ptrllvm::Module module; llvm::IRBuilder builder; std::unordered_mapstd::string, llvm::Function* functionMap; std::stackllvm::BasicBlock* breakStack; // 支持break语句 std::stackllvm::BasicBlock* continueStack; // 支持continue语句 public: CodeGenerator(const std::string moduleName) : module(std::make_uniquellvm::Module(moduleName, context)), builder(context) {} void generateIR(ProgramNode* root); private: void visitFunctionDecl(FunctionDeclNode* node); void visitForStmt(ForStmtNode* node); // ... 其他visit方法 };module的DataLayout必须显式设置否则LLVM后端无法确定int大小// 在generateIR()开头 module-setDataLayout(e-m:e-p270:32:256-p271:32:256-p272:64:256-i64:64-f80:128-n8:16:32:64-S128); module-setTargetTriple(x86_64-pc-linux-gnu);builder的插入点InsertPoint在每个函数入口处重置void CodeGenerator::visitFunctionDecl(FunctionDeclNode* node) { auto funcType llvm::FunctionType::get( node-returnType-llvmType, node-paramTypes, false // 不可变参数列表 ); auto func llvm::Function::Create( funcType, llvm::Function::ExternalLinkage, node-name, module.get() ); functionMap[node-name] func; // 创建入口基本块 auto entryBB llvm::BasicBlock::Create(context, entry, func); builder.SetInsertPoint(entryBB); // 分配函数参数对应的alloca int i 0; for (auto arg : func-args()) { auto alloca builder.CreateAlloca(arg.getType(), nullptr, node-paramNames[i]); builder.CreateStore(arg, alloca); // ... 记录到符号表 i; } // 生成函数体 visitStmt(node-body); }3.2 For循环的LLVM IR生成头块、条件块、循环体块、增量块的四段式构造C-MinusF的for (init; cond; incr) body被翻译为标准LLVM CFG结构[init] ↓ [header] ←───────┐ ↓ (cond) │ [cond-block] │ ↓ (true) │ [body] │ ↓ │ [incr] │ ↓───────────┘visitForStmt()实现如下void CodeGenerator::visitForStmt(ForStmtNode* node) { // 1. 创建四个基本块 auto headerBB llvm::BasicBlock::Create(context, for.header, builder.GetInsertBlock()-getParent()); auto condBB llvm::BasicBlock::Create(context, for.cond, builder.GetInsertBlock()-getParent()); auto bodyBB llvm::BasicBlock::Create(context, for.body, builder.GetInsertBlock()-getParent()); auto incrBB llvm::BasicBlock::Create(context, for.incr, builder.GetInsertBlock()-getParent()); // 2. 当前块跳转到header builder.CreateBr(headerBB); // 3. 设置header块插入点生成init表达式 builder.SetInsertPoint(headerBB); if (node-init) visitExpr(node-init); // 可能是赋值或声明 // 4. header跳转到cond块 builder.CreateBr(condBB); // 5. 设置cond块插入点生成条件判断 builder.SetInsertPoint(condBB); auto condVal visitExpr(node-cond); auto condBool builder.CreateICmpNE(condVal, llvm::ConstantInt::get(llvm::Type::getInt32Ty(context), 0)); builder.CreateCondBr(condBool, bodyBB, /* exitBB */ builder.GetInsertBlock()-getParent()-getTerminator()-getSuccessor(0)); // 6. 设置body块插入点生成循环体 builder.SetInsertPoint(bodyBB); visitStmt(node-body); builder.CreateBr(incrBB); // 7. 设置incr块插入点生成增量表达式 builder.SetInsertPoint(incrBB); if (node-incr) visitExpr(node-incr); builder.CreateBr(condBB); // 跳回cond块 // 8. 记录ForStmtNode的LLVM块引用供后续优化使用 node-loopHeader headerBB; node-loopLatch incrBB; }关键细节exitBB循环出口不能硬编码为getTerminator()-getSuccessor(0)而应由visitForStmt()调用者通常是父StmtNode提供。项目中通过breakStack/continueStack栈管理跳转目标确保break语句能准确跳转到循环外第一个块。3.3 数组访问与函数调用的IR生成GEP与CallInst的精确控制ArraySubscriptNode生成getelementptr指令时必须区分int a[10]栈分配与int* p堆分配void CodeGenerator::visitArraySubscript(ArraySubscriptNode* node) { auto base visitExpr(node-base); // base可能是AllocaInst或LoadInst auto index visitExpr(node-index); // 获取base的pointee类型即int*的pointee是int auto baseType base-getType()-getPointerElementType(); auto int32Ty llvm::Type::getInt32Ty(context); // GEP: ptr, 0, index → 计算a[index]地址 auto gep builder.CreateGEP( baseType, base, {llvm::ConstantInt::get(int32Ty, 0), index}, arrayidx ); // Load该地址的值 auto load builder.CreateLoad(baseType, gep, arrayload); return load; }函数调用CallExprNode需处理参数传递与返回值接收void CodeGenerator::visitCallExpr(CallExprNode* node) { auto calleeFunc functionMap[node-calleeName]; std::vectorllvm::Value* args; for (auto arg : node-args) { args.push_back(visitExpr(arg)); } auto call builder.CreateCall(calleeFunc, args, calltmp); if (calleeFunc-getReturnType() ! llvm::Type::getVoidTy(context)) { return call; // 返回值供上层使用 } return nullptr; }血泪经验若calleeFunc未在functionMap中注册如未声明就调用CreateCall会触发LLVM断言失败。项目中在parseProgram()末尾添加全局函数声明验证确保所有CallExprNode的calleeName已在functionMap中存在。4. IR级优化实现循环不变式外提、常量传播与活跃变量分析的LLVM Pass编写4.1 循环不变式外提LICM基于支配边界与循环信息的Pass编写LICM Pass继承自llvm::FunctionPass核心逻辑在runOnFunction()中// LICMPass.h struct LICMPass : public llvm::FunctionPass { static char ID; LICMPass() : FunctionPass(ID) {} bool runOnFunction(llvm::Function F) override; private: void processLoop(llvm::Loop* L, llvm::LoopInfo LI, llvm::DominatorTree DT); bool isLoopInvariant(llvm::Instruction* I, llvm::Loop* L, llvm::LoopInfo LI); };processLoop()遍历循环内每条指令判断其是否为循环不变式void LICMPass::processLoop(llvm::Loop* L, llvm::LoopInfo LI, llvm::DominatorTree DT) { auto header L-getHeader(); for (auto BB : L-getBlocks()) { for (auto I : *BB) { if (isLoopInvariant(I, L, LI)) { // 检查I是否被header支配确保外提后仍能执行 if (DT.dominates(I.getParent(), header)) { // 找到header中最后一个非PHI指令的位置插入 auto insertPt header-getFirstNonPHI(); I.moveBefore(insertPt); } } } } }isLoopInvariant()的判定规则严格遵循龙书P502指令的操作数全部为常量或全部为循环外定义的值指令不调用可能产生副作用的函数如printf指令不是store除非store的目标是循环外分配的内存指令不是load除非load的地址是循环不变式。避坑LLVM的LoopInfo可能无法识别嵌套循环的内层循环。中科大项目中LICMPass需先调用LI.analyze(DT)确保循环信息最新否则L-getBlocks()返回空。4.2 常量传播Constant Propagation基于数据流方程的迭代求解常量传播采用迭代算法维护每个llvm::Value*的ConstantValue映射// ConstantPropagationPass.h struct ConstantPropagationPass : public llvm::FunctionPass { static char ID; ConstantPropagationPass() : FunctionPass(ID) {} bool runOnFunction(llvm::Function F) override; private: std::mapllvm::Value*, llvm::Constant* constMap; bool iterateOverFunction(llvm::Function F); llvm::Constant* getConstant(llvm::Value* V); };iterateOverFunction()按逆后序Reverse Post Order遍历基本块对每条指令求解bool ConstantPropagationPass::iterateOverFunction(llvm::Function F) { bool changed false; auto rpo llvm::ReversePostOrderTraversalllvm::Function(F); for (auto BB : rpo) { for (auto I : *BB) { if (auto* binOp llvm::dyn_castllvm::BinaryOperator(I)) { auto* lhs getConstant(binOp-getOperand(0)); auto* rhs getConstant(binOp-getOperand(1)); if (lhs rhs) { auto* result llvm::ConstantFoldBinaryOpOperands( binOp-getOpcode(), lhs, rhs, F.getContext() ); if (result) { constMap[I] result; changed true; } } } // ... 处理cmp, select, phi等 } } return changed; }getConstant()处理Phi节点的特殊情况llvm::Constant* ConstantPropagationPass::getConstant(llvm::Value* V) { if (auto* C llvm::dyn_castllvm::Constant(V)) return C; if (constMap.count(V)) return constMap[V]; if (auto* phi llvm::dyn_castllvm::PHINode(V)) { // Phi节点所有入边值必须相同才视为常量 llvm::Constant* first nullptr; for (unsigned i 0; i phi-getNumIncomingValues(); i) { auto* incoming getConstant(phi-getIncomingValue(i)); if (!incoming) return nullptr; if (!first) first incoming; else if (first ! incoming) return nullptr; } return first; } return nullptr; }4.3 活跃变量分析Live Variable Analysis基于控制流图的逆向数据流求解活跃变量分析为后续死代码消除Dead Code Elimination提供基础。Pass计算每个llvm::Instruction的liveOut集合// LiveVariableAnalysis.h struct LiveVariableAnalysis { std::mapllvm::Instruction*, std::setllvm::Value* liveOut; void run(llvm::Function F); private: void computeGenKill(llvm::BasicBlock* BB, std::setllvm::Value* gen, std::setllvm::Value* kill); void solveDataFlow(llvm::Function F); };computeGenKill()规则龙书P528gen: 该块中被使用use且未被定义def的变量kill: 该块中被定义def的变量因为定义会覆盖之前的值。solveDataFlow()采用逆后序迭代void LiveVariableAnalysis::solveDataFlow(llvm::Function F) { // 初始化所有块liveOut为空 for (auto BB : F) { liveOut[BB.back()] {}; // 以块尾指令为key } bool changed true; while (changed) { changed false; // 逆后序遍历 auto rpo llvm::ReversePostOrderTraversalllvm::Function(F); for (auto BB : rpo) { std::setllvm::Value* newLiveOut; // liveOut union of liveIn of all successors for (auto succ : llvm::successors(BB)) { if (succ-getFirstNonPHI()) { newLiveOut.insert(liveOut[succ-getFirstNonPHI()].begin(), liveOut[succ-getFirstNonPHI()].end()); } } // liveIn gen ∪ (liveOut - kill) std::setllvm::Value* gen, kill; computeGenKill(BB, gen, kill); std::setllvm::Value* liveIn gen; std::set_difference(newLiveOut.begin(), newLiveOut.end(), kill.begin(), kill.end(), std::inserter(liveIn, liveIn.end())); // 更新liveOutliveOut liveIn - def use简化版 auto oldLiveOut liveOut[BB.back()]; if (oldLiveOut ! newLiveOut) { oldLiveOut newLiveOut; changed true; } } } }注意实际项目中liveOut存储在std::mapllvm::Instruction*, std::setllvm::Value*中键为指令指针便于后续Pass如DCE直接查询某条指令的结果是否活跃。5. 避坑LLVM IR优化中的5个致命陷阱与解决方案5.1 现象LICM外提后程序崩溃GDB显示segmentation fault原因外提的load指令读取了循环内分配的栈内存如int temp a[i];中的temp而外提后该内存可能已被alloca指令释放或重用。LLVM的alloca在函数入口分配但若temp是循环内声明的变量其alloca位于循环体内外提后load指向无效地址。解决在isLoopInvariant()中增加检查——若指令的operand是alloca指令且该alloca位于循环内则禁止外提。中科大项目中通过LI.isLoopInvariant(AllocaInst*)接口判断alloca是否在循环内。5.2 现象常量传播后if (0) { ... }分支未被消除opt -O2却能删掉原因ConstantPropagationPass只传播常量未实现死代码消除DCE。LLVM的opt -O2包含DCEPass而本项目Pass链中未注册DCE。解决在main()中Pass Manager注册顺序改为addPass(LICMPass()) → addPass(ConstantPropagationPass()) → addPass(llvm::createDeadCodeEliminationPass())。中科大评分要求必须展示DCE效果否则优化项扣分。5.3 现象活跃变量分析结果与opt -analyze -live-vars输出不一致原因LLVM原生-live-vars分析的是SSA形式的变量%x add ...而本项目分析对象是llvm::Value*指针未区分同一变量的不同版本如%x1,%x2。当存在Phi节点时getConstant()返回nullptr导致分析中断。解决改用llvm::Value*的getName()作为键而非指针地址。对Phi节点将其所有入边Value*的getName()合并为虚拟名如phi_x并在computeGenKill()中特殊处理Phi的def/use。5.4 现象for (int i0; i10; i)外提后i的初始值被错误替换为10原因isLoopInvariant()误判i为不变式因其在循环头被load而load的操作数i的alloca在循环外。但i在循环内被store修改属于def-use链中的def。解决isLoopInvariant()必须检查指令的所有operand是否既未被循环内store定义也未被循环内phi节点定义。中科大项目中增加isDefinedInLoop(llvm::Value* V, llvm::Loop* L)辅助函数遍历L内所有store和phi指令。5.5 现象编译器生成的可执行文件输出结果与gcc不一致但IR无语法错误原因C-MinusF规定int a[10]的数组索引从0到9但visitArraySubscript()中GEP的索引计算未做越界检查导致a[10]访问到a[0]之后的内存未定义行为。LLVM不检查数组越界而gcc的-fsanitizeaddress会捕获。解决在visitArraySubscript()末尾插入运行时检查教学版可选auto boundCheck builder.CreateICmpULT(index, llvm::ConstantInt::get(int32Ty, 10)); // 假设已知数组大小 builder.CreateCondBr(boundCheck, loadBB, trapBB);中科大项目不要求运行时检查但需在报告中说明此限制。6. 验证与调优用真实测试用例驱动优化效果量化与LLVM调试技巧6.1 构建可复现的测试用例集与性能对比脚本项目附带test/目录包含5类测试用例simple.cminusf基础语法与IR生成验证loop.cminusffor循环结构与LICM验证const_prop.cminusfint x5; yx3;常量传播验证live_var.cminusfint a1; if(0){a2;} return a;活跃变量与DCE验证complex.cminusf嵌套循环数组访问函数调用综合验证。验证脚本verify.sh自动化执行#!/bin/bash # verify.sh for f in test/*.cminusf; do echo Testing $f # 1. 编译为LLVM IR ./compiler $f -o ${f%.cminusf}.ll # 2. 应用优化Pass opt -load ./libLICMPass.so -licm -constprop -dce ${f%.cminusf}.ll -o ${f%.cminusf}.opt.ll # 3. 编译为可执行文件 llc ${f%.cminusf}.opt.ll -o ${f%.cminusf}.s gcc ${f%.cminusf}.s -o ${f%.cminusf}.out # 4. 运行并比对输出 ./${f%.cminusf}.out ${f%.cminusf}.out.txt diff ${f%.cminusf}.out.txt test/expected/$(basename $f .cminusf).txt done关键技巧opt命令中-load ./libLICMPass.so必须指定绝对路径或LD_LIBRARY_PATH否则LLVM找不到自定义Pass。中科大实验环境要求Pass动态库名为lib*.so且-licm等选项名与RegisterPass中注册名严格一致。6.2 使用LLVM工具链深度调试IR优化过程当优化结果异常时禁用所有优化逐Pass观察IR变化# 查看原始IR ./compiler test/loop.cminusf -o loop.ll # 仅应用LICM opt -load ./libLICMPass.so -licm loop.ll -o loop.licm.ll # LICM 常量传播 opt -load ./libLICMPass.so -licm -constprop loop.ll -o loop.licm.const.ll # 对比差异 diff loop.ll loop.licm.ll | head -20利用llvm-dis反汇编二进制bitcodellvm-as重新汇编# 将bitcode转为可读文本IR llvm-dis loop.bc -o loop.ll # 将文本IR转为bitcode llvm-as loop.ll -o loop.bc血泪经验LLVM Pass调试最有效的方式是printf大法——在Pass关键路径插入llvm::errs() LICM: moving I-getOpcodeName() \n;输出到stderr。中科大服务器禁用cout但llvm::errs()始终可用。6.3 优化效果量化表格指令数、基本块数、执行时间对比对complex.cminusf含三层嵌套循环进行量化优化阶段IR指令数基本块数time ./a.out(ms)说明无优化1271942.3原始IR含大量重复计算LICM981931.7外提3个循环不变式乘法LICMConstProp851728.12个常量折叠1个死分支消除LICMConstPropDCE721524.9删除4个未使用变量的alloca与store注意指令数统计用llvm-dis loop.ll \| grep -v ^; \| wc -l排除注释行基本块数用llvm-dis loop.ll \| grep define\|br\|switch \| wc -l粗略估算执行时间取10次time平均值消除系统抖动。我当年在中科大机房调试LICM时在processLoop()里加了57行llvm::errs()最终发现DT.dominates()返回false是因为循环头块未被正确识别——根源是visitForStmt()中headerBB的创建本文还有配套的精品资源点击获取