决赛截止时代码保存为 master 分支。refine 分支为后续维护重构 / 优化迭代的分支。
GeeCeeCee 编译器由 北京航空航天大学 的四名本科生开发,使用 cpp 语言编写,总代码行数超过 30000 行。从头手工实现了词法分析、语法分析、中间代码生成、中端优化、汇编代码生成、后端优化完整的编译步骤,将 Sysy2022 文法的源代码程序翻译为 riscv64 架构的汇编代码。
该项目借鉴了 LLVM 编译器的设计思路,将编译划分为前中后三端。前端依次进行词法分析,语法分析与中间代码生成;中端使用仿制版 LLVM 并在此基础上进行了一系列体系结构无关优化;后端将中端代码翻译为 riscv64 指令,并进行系列体系结构相关优化。
仓库目录结构如下:
├── .clangd // clangd 配置文件
├── .clang-format // clang-format 配置
├── .clang-tidy // clang-tidy 配置
├── CMakeLists.txt // cmake 配置
├── .pre-commit-config.yaml // pre-commit 配置
├── .devcontainer // dev container 配置
│ ├── devcontainer.json
│ └── Dockerfile
├── .github // github ci
│ └── workflows
│ └── auto-test.yaml
├── .gitignore
├── lib // 库文件目录
├── src
│ ├── backend // 编译器后端
│ │ ├── opt
│ │ └── riscv
│ ├── config.hpp // 命令行配置解析
│ ├── frontend // 前端代码
│ ├── ir // 中端代码
│ ├── lib.hpp // 程序主逻辑
│ ├── log.hpp // 日志库
│ ├── main.cpp // 程序入口
│ ├── opt // 中端优化
│ │ ├── engine.cpp // 优化管理引擎
│ │ ├── engine.hpp
│ │ ├── exec // 常量函数求解器
│ │ ├── opt_util.cpp // 优化工具
│ │ ├── opt_util.hpp
│ │ ├── pass.hpp // 优化 pass 基类
│ │ └── pass_impl // 优化实现
│ └── util.hpp
└── tests
├── checker.py // 评测脚本
为保证团队开发的环境一致性以及代码规范一致性,我们从各个方面对工程进行了配置:
- dev container:为保证团队成员所使用的开发环境一致,我们按照 dev container 规范设计了 GeeCeeCee 的容器开发环境,使用 IDE 所带的 dev container 相关插件即可直接使用开发所需所有工具链;
- clangd clang-tidy clang-format:基础的语言 lint 工具等,保证了代码的规范与质量对齐;
- pre-commit:强制对代码风格进行约束,每次团队成员 commit 时都会自动检测预设的规范;
- github action:我们将评测集成到了 github ci 上,对提交的变更代码进行自动评测。
本项目区别于 LLVM 之类的老项目,可以称作现代的 C++ 工程,主要体现在几方面:
- 共享指针替代裸指针,突出在 ir 的设计上,对于这样的树形结构使用 share_ptr 和 weak_ptr 配合管理对象所有权,实现了内存自动管理。当然这一点设计在项目中随处可见;
- 使用高版本语义化类型:典型如大量使用 std::optional 来替代 nullptr 所表示的空置语义,这使得人为的逻辑判断处理错误导致的空指针等报错大量减少;以及 std::variant 结合 visit 实现了类似现代高级语言中模式匹配的特性,这一点在前端 Visitor 的设计里尤为突出;以及 std::function 代替函数指针实现多类型函数类型承接等;
- 使用模板实现声明式语法:我们大量利用了 C++ 函数/类模板的特性,实现了功能声明式的实现。比如语法分析中,利用 Collector 和 Generator 的函数抽象以及对 + * | 等运算符的重载,以及模拟解析栈的设计,实现了对语法成分只定义规则,而让程序自动完成解析/回溯等分析操作;以及在中端优化管理处根据程序优化进程自动进行状态机轮转等;
- 函数式编程思想:虽然 C++ 不完全支持函数式编程的特性,但我们大量使用了函数闭包(lambda 函数)迭代器等现代语言特性,简化了代码的编写逻辑;
- log 规范化:手工实现了一个简单的 log 库,支持定义等级以及彩色输出;并且用控制宏进行控制,非 debug 模式下该函数自动内联为 noop。
词法分析的目的是将源代码拆解为 Token 列表,提供给后面的编译步骤使用。
词法分析器 Lexer 对外主要提供的接口如下:
class Lexer {
class Iterator;
public:
// Iterator interface
[[nodiscard]] inline Iterator begin() { return Iterator(this, next_token()); }
[[nodiscard]] inline Iterator end() { return Iterator(this, std::nullopt); }
inline std::optional<Token> next();
inline std::optional<Token> next_assert(TokenType type);
// branch parsing
void branch();
// rollback to the last branch, if any, and remove it from the stack
void rollback();
// commit the current branch, if any, and remove it from the stack
void commit();
// call when parse finished, check if lexer reach the end
[[nodiscard]] bool is_end();
private:
[[nodiscard]] std::optional<Token> next_token();
[[nodiscard]] bool skip_whitespace();
[[nodiscard]] bool skip_comment();
};主要分为三类接口:
- 迭代器接口:Lexer 本身是对 TokenList 的封装,按照 C++ 的规范对其进行了接口包装,方便遍历;
- 解析类接口:next 会返回下一个 Token 解析结果,而 next_assert 会判断是否为特定类型的 Token,这里使用 std::optional 包装方便判断当此解析是否成功;
- 栈式范围解析/回退接口:借鉴了 git 的分支管理设计思路,我们将 Lexer 的解析步骤同样抽象为一个深度解析树,branch 接口进行尝试解析,commit 为解析成功后确认当此解析,rollback 调用在解析失败时回退到上一次解析位置。
不同于以往任何参赛队伍手工写的编译器,我们没有为每一条生成式规则编写一条递归下降的解析函数,而是利用了 C++ 模板,运算符重载等语言特性,实现了少量代码编写解析框架,而解析本身完全是声明式语法——简单说,如果要加一条语法规则,只需要多编写一行代码。
该步骤的解析结果是一颗抽象语法树,而语法树的节点在外面的项目中抽象如下:
class ASTNode;
using NodeType = ast_type::NodeType;
using Token = token::Token;
using ASTNodePtr = std::unique_ptr<ASTNode>;
using TokenPtr = std::unique_ptr<Token>;
using GrammarNode = std::variant<ASTNodePtr, TokenPtr>;
using GrammarNodeIterator = std::vector<GrammarNode>::const_iterator;
class ASTNode {
public:
explicit ASTNode(NodeType type) : type(type) {}
inline NodeType get_type() const { return type; }
inline void set_children(std::vector<GrammarNode> &&children) { this->children = std::move(children); }
std::string print_tree(int indent = 0, bool is_last = true, const std::vector<bool> &last_stack = {}) const;
template <typename Father>
void for_each_child(Father &&father, size_t from = 0) {
assert(from < children.size());
for (size_t i = from; i < children.size(); ++i) {
std::invoke(std::forward<Father>(father), children[i]);
}
}
std::vector<GrammarNode> &get_children() { return children; }
private:
NodeType type;
std::vector<GrammarNode> children;
};节点本身是这样一个枚举值:using GrammarNode = std::variant<ASTNodePtr, TokenPtr>;对于叶子节点,其就是 Token,对于非叶子节点,其本身是一个 ASTNode,并且 children 中记录了子节点的 unique_ptr
语法解析的精髓在于下面的这三个运算符重载以及 collector 的模板特化处理:
using namespace frontend::ast;
using namespace frontend::lexer;
using GrammarNodeCollector = std::function<std::optional<std::vector<GrammarNode>>(Lexer *)>;
using GrammarNodeGenerator = std::function<std::optional<ASTNodePtr>(Lexer *)>;
extern const std::unordered_map<NodeType, GrammarNodeCollector> COLLECTORS;
enum class CollectorOperator { OPTION, SEVERAL };
inline GrammarNodeCollector operator+(const GrammarNodeCollector &lhs, const GrammarNodeCollector &rhs);
inline GrammarNodeCollector operator|(const GrammarNodeCollector &lhs, const GrammarNodeCollector &rhs);
inline GrammarNodeCollector operator*(const GrammarNodeCollector &gen, const CollectorOperator &op);
template <NodeType type>
GrammarNodeGenerator generator() {
...
}
template <TokenType type>
GrammarNodeCollector collector() {
...
}
template <NodeType type>
GrammarNodeCollector collector() {
...
}这里就大量用到了 Lexer 中的栈式解析的接口。
当完成这几个函数后,我们的编译器可以使用如下的声明式语法进行自动解析:
// CompUnit -> {Decl | FuncDef}
{NodeType::COMP_UNIT, (NODE(DECL) | NODE(FUNC_DEF)) * SEVERAL},
// Decl -> ConstDecl | VarDecl
{NodeType::DECL, NODE(CONST_DECL) | NODE(VAR_DECL)},
// FuncDef -> FuncType Ident '(' [FuncFParams] ')' Block
{NodeType::FUNC_DEF,
NODE(FUNC_TYPE) + TOKEN(IDENFR) + TOKEN(LPARENT) + (NODE(FUNC_F_PARAMS)) * OPTION + TOKEN(RPARENT) + NODE(BLOCK)},
// ConstDecl -> 'const' BType ConstDef {',' ConstDef} ';'
{NodeType::CONST_DECL,
TOKEN(CONSTTK) + NODE(B_TYPE) + NODE(CONST_DEF) + (TOKEN(COMMA) + NODE(CONST_DEF)) * SEVERAL + TOKEN(SEMICN)},
// BType -> 'int' | 'float'
{NodeType::B_TYPE, TOKEN(INTTK) | TOKEN(FLOATTK)},
// ConstDef -> Ident {'[' ConstExp ']'} '=' ConstInitVal
{NodeType::CONST_DEF,
TOKEN(IDENFR) + (TOKEN(LBRACK) + NODE(CONST_EXP) + TOKEN(RBRACK)) * SEVERAL + TOKEN(ASSIGN) +
NODE(CONST_INIT_VAL)},我们的中间代码生成使用 Visitor 模式,提供的接口如下:
class Visitor {
private:
// Visit a specific ASTNode type and return a value.
// `node_type`: The type of the ASTNode to visit.
// `value`: The type of the value returned by the visitor. It must be a type that inherits from `Value`.
// `is_list`: A flag indicating whether the returned value is a list.
template <NodeType node_type, typename value, VisitPattern pattern>
[[nodiscard]] auto visit(ASTNodePtr &) -> std::enable_if_t<
std::is_base_of_v<Value, value>,
std::conditional_t<pattern == VisitPattern::LIST, std::list<std::shared_ptr<value>>, std::shared_ptr<value>>> {
logger::ERROR("[Visitor::visit<", node_type, ">] method not implemented");
__builtin_unreachable();
}
template <NodeType node_type>
[[nodiscard]] VisitExpsResult visit_exps(ASTNodePtr &) {
logger::ERROR("[Visitor::visit_exps<", node_type, ">] method not implemented");
__builtin_unreachable();
}
};几乎所有的语法成分的解析逻辑都是 visit 方法的重载,该模板参数为一个三元组:
- node_type:要解析的语法成分
- value:生成的 IR 成分,如 Value / Constant / Function / Instruction 等
- pattern:解析生成出来的 IR 成分为单个还是列表
同时对于 exp 这类特殊的语法成分,另有一个模板来做解析,会多返回解析后生成的 exp 的结果。
本项目的 IR 仿照 LLVM 的结构构造。所有的语法成分都继承自 Value,User 同样继承与 Value,User 可能使用多个 Value,Value 也可能被多个 User 使用。
整个 LLVM 代码的结构本质上其实还是一棵语法树,故而我们使用共享指针来维护其所有权。Function 中存放 BasicBlock 的共享指针的列表,BasicBlock 中对 Function 弱引用。
每一个 Value 都有一个 Type,在我们的系统中,type 进行了局部唯一化缓存,对外以工厂方法暴露:
std::shared_ptr<IntegerType> IntegerType::get(int bits) {
static std::unordered_map<int, std::shared_ptr<IntegerType>> cache;
auto it = cache.find(bits);
if (it != cache.end()) {
return it->second;
}
auto new_type = std::shared_ptr<IntegerType>(new IntegerType(bits));
cache[bits] = new_type;
return new_type;
}
std::shared_ptr<FloatType> FloatType::get() {
static std::shared_ptr<FloatType> instance(new FloatType());
return instance;
}中端 IR 中还需要记录许多优化相关的信息(如前驱后继等),如果直接维护在对应的结构内则略显臃肿,故而为了架构解耦,我们将每一种成分的优化信息都浓缩到了一个 opt_info 字段中,在 support.hpp中记录了对应的结构,如:
struct FunctionOptInfo {
ir::Function *parent_func;
// marked by FunctionAnalysis
bool has_inout = false;
bool has_global_memory_read = false;
bool has_global_memory_write = false;
bool has_local_memory_write = false;
// marked by PureFunctionAnalysis
bool is_pure = false;
// marked by PureFunctionAnalysis, the global vars that this function affects
std::unordered_set<std::shared_ptr<ir::Value>> effected_global_vars{};
// marked by SideEffectAnalyzation
bool has_side_effect = true;
// the functions that this function calls
std::set<std::weak_ptr<ir::Function>> callees{};
// the functions that call this function
std::set<std::weak_ptr<ir::Function>> callers{};
// marked by CallGraphAnalysis
bool is_recursive = false;
// loop forest contains all top-level loops
LoopForest loop_forest{};
};
struct BasicBlockOptInfo {
ir::BasicBlock *parent_block;
// control flow graph
std::vector<std::weak_ptr<BasicBlock>> successors{};
std::vector<std::weak_ptr<BasicBlock>> predecessors{};
// dominance
std::vector<std::weak_ptr<BasicBlock>> dominated{};
std::vector<std::weak_ptr<BasicBlock>> dominator{};
std::weak_ptr<BasicBlock> immediate_dominator{};
std::vector<std::weak_ptr<BasicBlock>> immediate_dominated{};
std::optional<int> dominance_depth{};
std::vector<std::weak_ptr<BasicBlock>> dominance_frontier{};
// loop
std::weak_ptr<LoopInfo> loop{};
bool is_loop_header = false;
void remove_successor(const std::shared_ptr<ir::BasicBlock> &successor);
void remove_predecessor(const std::shared_ptr<ir::BasicBlock> &predecessor);
bool dominates(std::shared_ptr<BasicBlock> block);
};
struct ModuleOptInfo {
AliasInfo alias_info;
SCEVInfo scev_info;
PointerBaseInfo pointer_base_info;
IntegerRangeInfo int_range_info;
};每一种结构都按照其自身等级拥有其它成分的所有权。对于同级或高级取 weak_ptr 弱引用,对低级成分取 share_ptr。保证不会因为循环引用的问题导致内存泄露。
上述中端与优化解耦开后,优化相关的代码都放到了 opt 命名空间里。所有的优化 pass 都继承于 Pass 基类,在我们的代码里有对应的宏包装。对每个优化的具体实现,其实就是单独的 cpp 文件,按照一些类别存放到了 pass_impl 目录下。
优化这里我们采用了统一的优化管理引擎实现声明式语法定义优化规则,加入流水线后自动进行状态管理与转移。
优化过程中会有一些特定的分析 pass 或构造特定循环形式等,抽象为一个状态 枚举:
enum OptStatus {
// clang-format off
CFG, DOM, SSA, CALL_GRAPH, PURE_FUNC, FUNC,
SIDE_EFFECT, SIMPLE_LOOP, LCSSA, ALIAS,
IND_VAR, SCEV, TRIP_COUNT, LOOP, POINTER,
END
// clang-format on
};对于一个优化单元,抽象为 OptGroup:
class OptGroup {
using OptNode = std::variant<pass::Pass *, OptGroup *>;
public:
OptGroup(std::string name);
~OptGroup() = default;
OptGroup *attach(OptStatus status);
OptGroup *execute(std::vector<std::pair<OptNode, bool>> nodes);
OptGroup *require(std::vector<OptStatus> requirements);
OptGroup *expose(std::vector<OptGroup *> exposures);
OptGroup *follow(std::vector<OptGroup *> followings);
OptGroup *disable(std::vector<OptStatus> destructions);
friend class OptEngine;
bool run(ir::Module *module);
};其对外提供的这些声明式函数可以按照如下方式使用:
DEFINE_GROUP(BuildCFG)
->execute({{RemoveUnreachableInstructions, false}, {ControlFlowGraphAnalyzation, false}})
->attach(CFG);
DEFINE_GROUP(UnrollLoop)
->execute({{LoopUnrolling, false}})
->require({LOOP, SIMPLE_LOOP, LCSSA, TRIP_COUNT, IND_VAR})
->disable({LOOP, SIMPLE_LOOP, LCSSA, TRIP_COUNT, IND_VAR, SCEV, ALIAS, CFG, DOM, FUNC})
->follow({MergeBlock, DCE})
->expose({SCCP, FoldConstant});然后在 pre-launch,cycle,defer 中声明要加入优化流水线的 group 即可自动完成状态分析与流转。
后端负责将中端生成的IR代码翻译为RISC-V汇编代码,并进行一系列体系结构相关的优化。后端主要包含以下几个部分:
后端实现了完整的RISC-V 64位指令集支持,包括:
- R型指令:ADD、SUB、SLL、SLT、SLTU、XOR、SRL、SRA、OR、AND等算术逻辑运算指令
- I型指令:ADDI、SLTI、SLTIU、XORI、ORI、ANDI、SLLI、SRLI、SRAI等立即数运算指令
- S型指令:SW、SH、SB、SD等存储指令
- B型指令:BEQ、BNE、BLT、BGE、BGT、BLE等分支指令
- U型指令:LUI、AUIPC等高位立即数指令
- J型指令:JAL、J等跳转指令
- 浮点指令:FADD、FSUB、FMUL、FDIV、FLW、FSW等浮点运算和访存指令
RVTranslator类负责将IR指令翻译为RISC-V汇编指令,翻译过程采用访问者模式,为每种IR指令类型提供专门的翻译方法,确保生成的汇编代码正确且高效。
RVRegAllocator类实现了基于图着色的寄存器分配算法,考虑了基于循环深度的寄存器溢出代价。
CalculateOpt类实现了多种后端优化技术:
常量优化:
- 常量值重用:在生存周期范围内复用相同的常量值
- 常量计算复用:利用已有的常量值计算新的常量
- 常量指针复用:复用相同标签的地址加载
指令优化:
- 无用指令删除:删除重复的load/store指令
- 指令合并:将连续的sraiw和slliw优化为andi指令
- 分支优化:优化li+分支指令的组合
- 移动指令优化:优化mv指令的位置和删除
循环优化:
- 单块循环优化:识别并优化简单的循环结构
- 循环不变量外提:将循环中的不变量计算提到循环外
基本块内联:
BlockInline类实现基本块的内联优化。
基本块重排序:
BlockReSort类使用Pettis-Hansen算法优化基本块布局。
控制流图简化:
SimplifyCFG类提供多种CFG简化优化,包括删除无用标签、条件转无条件、尾调用优化、分支重排序、块内联。
RVModule类负责最终的汇编代码生成。
后端优化采用分层流水线设计,确保优化的正确性和效率:
- 寄存器分配前优化:指令重排、窥孔优化、常量优化、指令优化、循环优化
- 寄存器分配:图着色算法分配物理寄存器
- 寄存器分配后优化:窥孔优化、块内联、块重排、控制流图简化
截至决赛截至日期,我们一共完成了如下54个有用的 pass:
按照目录结构功能划分如下:
| pass name | description |
|---|---|
| AliasAnalyzation | 别名分析,用于判断两个指针是否可能指向同一地址,为其它优化提供信息。本项目主要实现了基于内存空间(全局数据区/栈)的迭代分析,以及 TBAA(基于类型的别名分析)。 |
| CallGraphAnalyzation | 函数调用关系图分析,用于构建获取函数间 call 之类的调用图,以及递归函数的判断。 |
| ControlFlowGraphAnalyzation | CFG 流图分析,分析基本块的前驱与后继。 |
| DominanceAnalyzation / LengauerTarjanDominanceAnalyzation | DOM 支配关系构建。前者是比较易于理解的基础算法,后者是优化后的基于 Tarjan 算法实现的分析,效果等价。 |
| FunctionAnalyzation | 函数分析,提供函数是否读写全局/局部内存,是否有 IO 操作等。 |
| InductionVariableAnalyzation | 循环归纳变量分析,可以分析出循环中以特定 alu 算式变化的变量信息。 |
| IntegerRangeAnalyzation | 整数值域分析,按照 SCEV 信息以及指令本身维度进行综合分析以及不动点迭代。对有取值范围限定的变量分析出理论上下界。 |
| LoopAnalyzation | 循环分析,构建循环的基本信息,如 header,latch,exit 等,以及父子循环嵌套关系。 |
| PointerBaseAnalyzation | 指针基地址分析。对指针变量识别出其初始的全局变量 / 局部 alloca 变量。 |
| PureFunctionAnalysis | 纯函数分析,分析函数是否对全局变量等有修改。 |
| ScalarEvolutionAnalyzation | 标量归纳分析,特别地,识别出程序中以 AddRec 迭代式增长的变量,为循环等相关优化提供信息。 |
| SideEffectAnalyzation | 函数副作用分析,对函数是否调用/写内存等的综合分析。 |
| TripCountAnalyzation | 循环次数计算,对可能可以计算次数的循环计算出其循环次数,以归纳变量分析和标量归纳分析为基础。 |
| pass name | description |
|---|---|
| SimplifyCFG | 控制流简化,对一些基础可删除的 br 指令进行删除或替换/重定向。 |
| TailRecursionElimination | 尾递归消除,将尾递归函数通过头部插入 phi 节点的方式改写为循环,减少栈开销。 |
| pass name | description |
|---|---|
| ArithReduce | 归约并规范化算术指令。 |
| ConstArrayFold | 对常量数组存取进行折叠。 |
| ConstFold | 常量折叠,对一些基本的数学算式进行合并替换,或转化为更高效的指令 |
| ConstraintReduction | 约束缩减。对程序中可推断的布尔表达式进行化简。基础版本基于值域分析以及不动点迭代,本项目更进一步实现了一个简易的 sat 求解器实现复杂的布尔表达式自动求解。 |
| GepLift | 对 gep 指令进合并提升。 |
| GepSplitting | 将 gep 指令拆解,为后端做公共表达式删除做铺垫。 |
| LoopGEPCombine | 针对循环展开后一般的 gep 指令排布模式进行化简,将重新计算替换为地址迭代。 |
| Mem2Reg | 经典 SSA,对标量的 load store 进行 ssa 形式的替换。 |
| PtrAllocaReduction | 前端遗留的优化点,对数组传参时传入的二维指针进行替换。 |
| RangeFolding | 范围条件合并,基于值域分析,对编译时可求求值的 i1 类型变量进行替换。 |
| Reassociate | 针对加法/乘法进行重关联与规范化,扁平化并排序操作数。 |
| ScalarReplacementOfAggregates | SROA 优化,将数组拆分为标量,进一步可进行更完善的 ssa 构建。 |
| pass name | description |
|---|---|
| AggressiveDeadCodeElimination | 死代码删除激进版本,对不会对程序运行结果(return IO)产生影响的指令进行标记删除。 |
| AggressiveLoopElimination | 循环删除的激进版本。基于 LCSSA 的封闭性以及 SimpleLoop 的标准格式,对没有 LCSSA 的循环(外部不会使用内部的变量)以及没有副作用的循环,将 pre-header 重定向到 exit 。 |
| BlockMerge | 基础的块合并,对一些简单的可合并的基本块进行合并。进而暴露出更多块内优化。 |
| DeadCodeElimination | 基础死代码删除,对指令的 use 关系进行拓扑序标记,然后删除。 |
| DeadLoopEliminate | 死循环删除,对不可进入/没有作用的循环进行删除。 |
| LoadElimination / RedundantLoopElimination | load 指令删除,块内窥孔优化,防止单条指令多次取。 |
| PhiElimination | phi 删除,主要是对 LCSSA 形式的单 incooming phi 以及取值相等的 phi 进行替换。 |
| RedundantRetEliminate | 多余 ret 指令删除。 |
| RemovePhi | 中端优化收尾工作,消 phi 提供给后端。 |
| RemoveUnreachableBasicBlocks | 不可达块删除。BuildDom 的前置工作。 |
| RemoveUnreachableInstructions | 不可达指令删除,BuildCFG 的前置工作。 |
| RemoveUnusedFunctions | 无用函数删除。 |
| pass name | description |
|---|---|
| ConstexprFunctionEvaluation | 函数求值。对编译期内可求值的函数调用直接运算求值。 |
| FunctionInlining | 函数内联。 |
| GlobalScalarLocalization | 全局标量局部化,为 SSA 创造更多优化空间。 |
| SparseConditionalConstantPropagation | SCCP 稀疏条件常量传播。 |
| Unconditional_Br_Inlining | br 内联,思路类似于函数内联,将无条件 br 进行一定程度的合并。 |
| InductionVariableReplacement | 循环归纳变量替换,对可求值器的归纳变量,外部使用时直接替换为编译期算好的值。 |
| LoopClosedSSA | 构造 LCSSA,对循环外部使用到循环内的值,exit中插入 phi 搭一下,保证循环的封闭性。 |
| LoopInvariantCodeMotion | 循环不变量外提,减少重复计算。 |
| LoopSimplification | 循环标准化,完全按照 LLVM 官方实现步骤进行实现。 |
| LoopStrengthReduction | 循环强度削弱,把代码较高的乘法运算替换为加法。 |
| LoopUnrolling | 循环展开,对可计算次数的循环进行完全展开。 |
| pass name | description |
|---|---|
| FunctionMemoization | 函数记忆化,对纯计算函数的参数构建查找表缓存。 |
| GlobalCodeMotion | 全局代码移动,跨越基本块移动代码以减少代码执行次数 |
| GlobalValueNumbering | 全局值编号,对全局值进行公共子表达式消除 |
| LocalValueNumbering | 局部值变化,主要针对Load进行公共子表达式消除 |