dmd源码指南
原文在此
所有D编译器分为两部分:前端和后端
前端:词法和解析d语法,实例化模板.ldc(llvm)/gdc(gcc)/dmd(boost)共享.
后端:生成代码,优化,输出目标文件
胶水层:连接前后端.
编译周期
词法(令牌数组(lexer))–构建简单语法树(parser)–然后三阶段语义处理(mars中,语义1,2,3)依次接近最终表示(解析类型,实例化模板)–
阶段 | 任务 |
|---|---|
1语义 | 分析所有声明的完整签名(聚集类型成员,函数参数和返回及变量类型,求值(pragma(msg))). |
2语义 | 声明的其他部分(如变量声明初化器,静断条件下求值) |
3语义 | 分析函数声明主体.如果函数不在直接声明模块中编译(不在命令行),则不分析 |
由于解析前向引用,每个阶段可能调用后阶段函数.
immutable string x = "hello";
static if (x == "hello") { ... }
//static if调用`x`的2语义阶段
auto foo() { ... }
typeof(&foo) fp;
//1阶段`fp`运行`3阶段foo`来推导返回类型
string foo() { ... }
mixin(foo());//ctfe,调用`3阶段foo`.
最后,传递语法树至胶水层,然后交给后端产生机器码/目标文件.
运行时互操作
在d运行时中分配内存/操作数组,编译器用_d_样的勾挂函数来整合运行时.细节在此
细节(可能过时)
类型 | 意义 |
|---|---|
符号 | 链接器相关 |
dt_t | 目标文件中待加数据. |
元素 | 内部表示节点 |
在各种语法树节点中生成代码
节点类型 | 必须定义方法 |
|---|---|
语句类 | toIR, |
表达式 | toElem. |
初化器和特定式子类 | toDt. |
声明 | toObjFile. |
D符号 | toSymbol. |
在跑了3个语义段后,如何快速遍历dmd的ir.只晓得有个符号表.
内联器
内联器是前端,其遍历语法树来查找函数.通过计算是否值得再内联.
其中,一些语句不能为表达式(如循环/抛),因而,内联分两种,分别转换函数为语句(忽略返回值/调用(空/void)函数)/表达式(返回值时).这两种按不同路径内联.因为一些语句不能转成表达式.
内联器,分为四部分.
部分 | 细节 |
|---|---|
主入口点 | 利用InlineScanVisitor类和expandInline函数的inlineScan. |
分析成本(确定是否内联) | canInline和InlineCostVisitor类 |
按语句 | inlineAsStatement及其嵌入类 |
按表达式 | doInline及其嵌入类InlineStatement |
inlineScan为入口,主要工作由expandInline完成.InlineScanVisitor找到可内联函数时,调用扩展内联.根据式/语句来相应内联.如ExpStatement/CallExp.最后由inlineAsStatement(语句)/doInline(式)来搞.
后端
DMD用el元素来表示,枚举OPER看操作.列举表达式树中所有节点类型,用-O --c编译.得到各种优化.其余未文档标志:
标志 | 意思 |
|---|---|
--b | 显示优化块 |
--f | 完整输出 |
--r | 显示分配寄存器 |
--x | 抑制预定义的C++内容 |
--y | 显示中间语言(IL)缓冲区输出 |
前端到后端最重要入口为优化及生成代码的writefunc
序号 | 动作 |
|---|---|
1 | writefunc设置参数,然后调用codgen来生成函数体代码. |
2 | 为每块生成代码,然后把变量放进寄存器. |
3 | 生成启动代码,窥孔优化. |
4 | 优化跳 |
5 | codout中发射代码. |
6 | 写开关表 |
7 | 写异常表 |
cgcod中blcodgen生成块代码.处理块结束中/猜/如
生成块中,cod1.gencodelem,只是调用codelem.
cgcod.codelem根据类型,为元素生成代码.
cod1,cod2, cod3, cod4, and cod5.c生成x86整.cg87生成浮点.x87比较简单,不能处理公共子表达式,因而效率低.
优化器
主要在go中的optfunc中.调用:
函数 | 作用 |
|---|---|
blockopt(iter) | 优化基本块 |
constprop | 常折叠 |
copyprop | 复制 |
rmdeadass | 删死码 |
verybusyexp | 忙式 |
deadvar(gother.c) | 死变量 |
loopopt | 优化循环,删不变归纳变量,旋转循环 |
boolopt | 优化极 |
builddags | 公共子 |
el_convert | 浮串至数据段 |
el_combine | 合并两表达式 |
localize | 改进式的本地性 |
pinholeopt | 窥孔优化 |
生成代码
单独生成每个函数代码.每个函数,放进自己的COMDAT段.划分函数为块,按跳/其他控制指令连接.
注意idgen用来生成id.h/c,表示内置符号.
impcvngen用来描述原语间转换规则.生成impcnvtab表.root.h定义基类.用指针传递类实例,在堆上分配.
常见缩写
缩写 | 意思 |
|---|---|
stc | 存储类 |
ILS | 内联状态 |
ir | 中间表示 |
AE | 可用式 |
CP | 复制传播 |
CSE | 消公子,消除公共子表达式 |
VBE | 很忙式 |
FFI | 外部接口 |
DbI | 自省设计 |
ICE | 内部编译错误 |
IFTI | 隐式函数模板实例化 |
NVI | 非虚口 |
UDA | 用定属 |
UDT | 用定型 |
技巧
用instantiatingModule(模板字段)查找,哪个模块实例化了模板.
-vcg-ast输出生成代码前的最后语法树表示.用来调试模板/查找问题.dmd -vcg-ast test.d
用resolve来确定Type是实际类型/表达式/符号.traits.d中有示例.
DSymbol有toChars/toPrettyChars来调试.
DSymbol有kind来得到类型.
用asttypename打印语法树节点动态名.
表达式有个令牌的op字段,可Token.toChars(e.op)这样输出.
打印浮字面:toReal.
用isConst检查式是否为编译时已知字面.
注意编译时声明与语句的区别.
mixin("int x;");
//上面为声明
void main()
{
mixin("int y;");//为语句.
}
浙公网安备 33010602011771号