我们都知道lcc是一个可指定目标的编译器,具体目标平台可以用-target指定,有点类似嵌入式开发中的交叉编译器的味道。
我们知道编译器通常分为前端和后端,前端复制词法和语法的解析,后端负责具体平台上的执行代码的生成,
对于lcc而言,编译器启动时会依据用户的输入-target来识别目标,做好相关初始化,这个初始化实际上就是初始化一个结构体,填充其中的函数指针。
填充的数据来源于src文件夹中的*.md文件。
这个结构就是本文即将分析的interface及一些关联结构,对于不同的平台不见得会使用所有的接口。
同时对于编译性语言,在编译器获取一些重要的调试信息对于程序后期的debug至关重要,而lcc为debugger提供的结构也包含在interface(c.h)结构中。
具体如下:
/*
前端和后端的相互调用:
前端调用后端生成并发送执行代码
后端调用前端完成输出,分配空间,类型查询,以及节点,符号,串的管理
*/
#include "config.h"//定义了Xinterface结构
//为了方便注释,下面这个结构体稍微改了一下,与源代码并不一致
typedef struct metrics {
unsigned char size;
unsigned char align;//对齐字节数
unsigned char outofline;//控制相关类型的常量的放置,如果为1,则该类型的常量不放入dag而是存放在一个匿名的static中。
} Metrics;
//定义了输出汇编目标代码需要的函数和数据成员
typedef struct interface {
Metrics charmetric;
Metrics shortmetric;
Metrics intmetric;
Metrics longmetric;
Metrics longlongmetric;
Metrics floatmetric;
Metrics doublemetric;
Metrics longdoublemetric;
Metrics ptrmetric;
Metrics structmetric;
//以上为数据类型,不同类型的数组度量,包括对齐等信息
unsigned little_endian:1;小端格式,还是大端格式. 1是小端格式
unsigned mulops_calls:1;//硬件实现乘法,除法和取余数为1
unsigned wants_callb:1;//设置为1时,要求前端生成CALL+B节点,否则后端自己实现
unsigned wants_argb:1;//设置为1时,要求前端生成ARG+B节点
unsigned left_to_right:1;//设置为1时,传递参数的顺序为从左到右,否则为从右到左
unsigned wants_dag:1;//设置为1时,后端自己处理DAG节点.否则前端改引用次数大于1的节点
unsigned unsigned_char:1;//设置为1时,char类型为无符号数,否则char为有符号数
void (*address)(Symbol p, Symbol q, long n);//生成地址符号.
void (*blockbeg)(Env *);//块初始化.
void (*blockend)(Env *); //块结束处理
void (*defaddress)(Symbol);//定义指针地址
void (*defconst) (int suffix, int size, Value v);//定义常量
void (*defstring)(int n, char *s); //定义字符串
void (*defsymbol)(Symbol);//定义常量,标号,全局变量,或者静态变量的符号
void (*emit) (Node);//从中间代码生成最终代码
void (*export)(Symbol);//输出处理函数
void (*function)(Symbol, Symbol[], Symbol[], int);//生成函数的代码
Node (*gen) (Node);//生成代码
void (*global)(Symbol);//生成全局变量的代码
void (*import)(Symbol);//输入处理函数
void (*local)(Symbol);//局部变量声明
void (*progbeg)(int argc, char *argv[]);//整个编译开始,argv[*]通常为前端不能识别的选项
void (*progend)(void);//编译结束
void (*segment)(int);//设置到相应的段代码
void (*space)(int);//按字节分配储存空间
//一下函数接口提供给调试器,编译器前端通过这些函数给debugger提供符号表
void (*stabblock)(int, int, Symbol*);
void (*stabend) (Coordinate *, Symbol, Coordinate **, Symbol *, Symbol *);
void (*stabfend) (Symbol, int);
void (*stabinit) (char *, int, char *[]);
void (*stabline) (Coordinate *);
void (*stabsym) (Symbol);
void (*stabtype) (Symbol);
Xinterface x;//对interface的扩展,用于后端存放和目标机器相关的接口数据和函数,为后端私有,各个后端不同
} Interface;
//后端绑定结构,通过-target指定
typedef struct binding {
char *name;
Interface *ir;
} Binding;
extern Binding bindings[];
extern Interface *IR;//-target指定
以x86为例,x86.md中初始化的代码如下
Interface x86IR = {
1, 1, 0, /* char */
2, 2, 0, /* short */
4, 4, 0, /* int */
4, 4, 0, /* long */
4, 4, 0, /* long long */
4, 4, 1, /* float */
8, 4, 1, /* double */
8, 4, 1, /* long double */
4, 4, 0, /* T * */
0, 1, 0, /* struct */
1, /* little_endian */
0, /* mulops_calls */
0, /* wants_callb */
1, /* wants_argb */
0, /* left_to_right */
0, /* wants_dag */
0, /* unsigned_char */
address,
blockbeg,
blockend,
defaddress,
defconst,
defstring,
defsymbol,
emit,
export,
function,
gen,
global,
import,
local,
progbeg,
progend,
segment,
space,
0, 0, 0, 0, 0, 0, 0,
{1, rmap,
blkfetch, blkstore, blkloop,
_label,
_rule,
_nts,
_kids,
_string,
_templates,
_isinstruction,
_ntname,
emit2,
doarg,
target,
clobber,
}
};
//有向无环图(dag)节点,可执行代码的的描述结构
struct node {
short op;//dag操作符,
short count;//节点的值被使用或被其他节点引用的引用的次数,只有来之kids的引用才计数。
//count大于1的为共享节点,
Symbol syms[3];//一些dag操作也使用一个或两个符号表指针作为操作数,
//即syms[0] syms[1],后端可能会使用syms[2],前端也有可能临时使用
Node kids[2];//指向操作数节点
Node link;//指向森林中下一个dag的根
Xnode x;//后端对节点的扩展
};