- 论坛徽章:
- 3
|
第三部: 设计一个良好的类型系统
任何语言都有类型,别管是动态类型还是静态,统统都是类型。
那么类型说到底是什么东西呢?
确切的来说,对编译器而言,一个“类型”就是其内存布局和绑定在这块内存上的操作。
整数,占用一个字长,能执行加减乘除运算,还能执行 和、或、异或 运算。更是能左移右移。神码运算都支持了。更重要的是,整数这个类型居然是可以不占用内存的:放到寄存器就可以了。
浮点数:能执行的运算和整数区别很小。除了不能执行二进制运算。分单精度和双精度。我这个BASIC编译器就只打算支持双精度浮点数。
字符串: 是的,BASIC语言里字符串是内置类型。既然是内置类型就能定义运算了。字符串能做加法,和到整数的自动转换(C语言不支持,可BASIC支持)。
数组:BASIC数组是可变长度的,能执行的运算只有一种,下标访问。可变数组只能是一维的。
矩阵:矩阵是固定长度的多维数组。能执行矩阵数学里定义的四则运算。
结构体:结构体只能执行一种运算:访问结构成员。
变量的定义的时候编译器需要为其安排存储空间(不考虑寄存器优化先)。全局变量的空间是分配在 可执行文件的.data区域,而对于函数局部变量,则是安排在栈上。编译器需要知道2个信息来进行
内存分配:变量大小和对齐。变量定义的时候要求一个类型(先不讨论QBASIC支持的 any 类型)类型就隐含了这2个信息。 那么变量名就是这个分配的内存的一个别名了。
如果是在栈上分配,则这个别名指的就是到栈基址(x86 下的ebp, x86_64 下是 rbp ) 的偏移。所以你需要在函数的语法树里支持一个“符号表“ 来保存这种映射关系。
类型的实现是通过 operator* 进行的。比如 A 和 B 两个变量是 整数,表达式 A + B , 对应的汇编应该是 load A , Load B , add A B 这样的。
那么,从语法树的考虑就是 "A" 是个 reference , 到符号表找到分配的内存,应该是使用llvm::AllocaInst 分配的栈,然后对这个栈指针执行一个 LoadInst ,就获得了一个 llvm::Value*指针
这就是 A 这个变量了,然后同理对B进行操作。获得两个 llvm::Value* 指针,这个时候调用 AddInst 生成一个加法指令。结果就行了。
所以对 AddCacluationExpressionAST 的 codegen() 函数的写法应该是类似这样的
- AddCacluationExpressionAST::codegen()
- {
- llvm::Value * lhs = this->lhs->codegen();
- llvm::Value * rhs = this->rhs->codegen();
- return builder.CreateAdd(lhs,rhs);
- }
复制代码 可是同样的语法树,对应的可能是不同类型的变量,所以这样的代码只能处理整数这种 llvm 直接支持的变量。对字符串就无能为力了。为此,我们需要进行一次抽象。
创建一个 TypeOperator 抽象类。为整数实现 NumberTypeOperator
- NumberTypeOperator::operator_add( ExprAST * lhs, ExprAST* rhs )
- {
- llvm::Value * lhs = this->lhs->codegen();
- llvm::Value * rhs = this->rhs->codegen();
- llvm::Value * val = builder.CreateAdd(lhs,rhs);
- return NumberExprAST(val); //用这个结果重新构造一个 表达式语句节点。
- }
复制代码 这样表达式语句的代码生成就是这样实现的了:
- AddCacluationExpressionAST::codegen()
- {
- return lhs->type->getop()->operator_add(this->lhs,this->rhs)->codegen();
- }
复制代码 依据其类型( lhs->type)调用其操作符表,然后调用对应的操作符。
甚至如果在 用户定义的结构体的 operator_add() 处理里生成对用户重载的 operator + () 函数的调用,就实现了 类似 C++的操作符重载了。
|
|