Nano Cpp Compiler(NCC)是一个C++子集的编译器,该子集涵盖了C++的部分基本特性。相比C++03,去除了许多C++语言特性,以简化编译器的实现。总体而言,语言保留的特性主要为C与C++共有的部分,加上C++的面向对象语法与一些C++常见的语言特性(如引用)。
目前共有三个可执行程序:
- 词法分析测试(
./bin/lextest
) - 语法分析测试(
./bin/parsetest
) - NCC编译器(
./bin/ncc
)
使用make编译:
make lextest
make parsetest
make ncc
这两个程序的输入均为标准输入流。程序的正常运行输出为标准输出流、错误信息输出在标准错误流,输出的中文字符编码为UTF-8(在windows命令行查看时需要先切换代码页chcp 65001
)
NCC编译器目前可用的命令行参数:
-t
:输出分析符号表(-ft
,输出完整符号表)-ir
:输出LLVM IR-o
:针对LLVM IR进行优化-s [file]
:输出LLVM汇编器-ss [file]
:输出自定义汇编器结果-d
:输出调试信息
采用多表结构,每次退出一个局部作用域时打印该作用域的符号。
最后打印全局符号表中的全部符号定义。
在程序后面加入-t
或-ft
可以打印全局符号表或全部符号表。
C++的语义错误种类众多,目前实现的语义错误(不完整)包括:
- 使用未定义的标识符
- 使用未定义的类型或类型别名
- 使用未定义的类成员
- 定义不完整的类型变量(如
void
、未知长度的数组、无完整定义的类类型) - 定义非法类型(如长度非正数的数组、不完整类型的数组、引用的数组、引用的引用、引用的指针)
- 重复定义标识符、函数、类、枚举、类型别名
- case语句、default语句不在switch中
- case表达式不是整数常量
- switch条件语句类型不能转换为整数
- break语句不在循环或switch中
- continue语句不在循环中
- 函数返回值与函数定义不符,或void函数中有返回值,或非void函数没有返回值
- 对表达式左值赋值或修改
- 对表达式右值使用取地址运算符
- 对不完整的类型使用
sizeof
运算符 - 赋值表达式左右类型不相符,操作数类型不相符
- 对(没有相应运算符重载的)类类型使用操作符
- 条件表达式的值不能转换为
bool
- 数组下标不是整数类型
- 下标操作的对象不是数组或指针类型
- 成员访问操作(
.
、.*
)对象不是完整定义的类类型 - 成员指针访问操作(
->
、->*
)对象不是完整定义的类类型的指针 - 函数调用对象的类型不是函数或函数指针
- 函数调用不匹配(参数个数不匹配,或参数类型不匹配)
- 提前声明枚举类型(只允许提前声明类类型)
- 类定义的基类为不完整的类类型
- 只允许虚函数定义为纯虚函数
- 局部域中定义的类不能有静态成员
由于C++语言定义与类型系统较为复杂,这里采用了LLVM IR的作为中间语言进行代码生成。使用LLVM作为IR有许多好处,如LLVM IR是一类强类型的中间语言,其本身在设计上贴近C类语言,可以让类与结构体、数组等对象得到完整的表示,不会损失信息。此外,LLVM IR采用的是SSA(静态单赋值)的表示,使得数据流可以很轻易的获取,让后续的优化与汇编代码生成可以更好的进行。
LLVM IR的代码结构依次可以看做模块(Module)->函数(Function)->基本块(Basic Block)->指令(Instruction)。基本块是一个除了最后一条指令,内部没有分支指令的基本单元,在一个基本块内程序保证顺序执行。在每个基本块尾,需要有一条明确的语句指出下一条指令的位置,可以是另一个基本块,也可以是返回语句。如果一个基本块可以到达多个基本块,其末尾的调整语句可以是条件跳转br
,或switch跳转switch
。
(目前有部分功能仍待实现)
目标指令集为MIPS 32核心指令集。
利用LLVM的module pass对IR进行遍历,逐语句翻译为MIPS汇编指令。寄存器分配采用基于块的存活集分析算法。