Nano Cpp Compiler - NCC

简介

Nano Cpp Compiler(NCC)是一个C++子集的编译器,该子集涵盖了C++的部分基本特性。相比C++03,去除了许多C++语言特性,以简化编译器的实现。总体而言,语言保留的特性主要为C与C++共有的部分,加上C++的面向对象语法与一些C++常见的语言特性(如引用)。

使用

目前共有三个可执行程序:

  1. 词法分析测试(./bin/lextest
  2. 语法分析测试(./bin/parsetest
  3. 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++的语义错误种类众多,目前实现的语义错误(不完整)包括:

  1. 使用未定义的标识符
  2. 使用未定义的类型或类型别名
  3. 使用未定义的类成员
  4. 定义不完整的类型变量(如void、未知长度的数组、无完整定义的类类型)
  5. 定义非法类型(如长度非正数的数组、不完整类型的数组、引用的数组、引用的引用、引用的指针)
  6. 重复定义标识符、函数、类、枚举、类型别名
  7. case语句、default语句不在switch中
  8. case表达式不是整数常量
  9. switch条件语句类型不能转换为整数
  10. break语句不在循环或switch中
  11. continue语句不在循环中
  12. 函数返回值与函数定义不符,或void函数中有返回值,或非void函数没有返回值
  13. 对表达式左值赋值或修改
  14. 对表达式右值使用取地址运算符
  15. 对不完整的类型使用sizeof运算符
  16. 赋值表达式左右类型不相符,操作数类型不相符
  17. 对(没有相应运算符重载的)类类型使用操作符
  18. 条件表达式的值不能转换为bool
  19. 数组下标不是整数类型
  20. 下标操作的对象不是数组或指针类型
  21. 成员访问操作(..*)对象不是完整定义的类类型
  22. 成员指针访问操作(->->*)对象不是完整定义的类类型的指针
  23. 函数调用对象的类型不是函数或函数指针
  24. 函数调用不匹配(参数个数不匹配,或参数类型不匹配)
  25. 提前声明枚举类型(只允许提前声明类类型)
  26. 类定义的基类为不完整的类类型
  27. 只允许虚函数定义为纯虚函数
  28. 局部域中定义的类不能有静态成员

代码生成

由于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汇编指令。寄存器分配采用基于块的存活集分析算法。