表达式值的存储
LLVM中基本数据类型及存储类型
值是编译器所需要处理的基本数据。它出现在各个角落,条件分支、表达式、返回语句。甚至是函数地址也可以被视作是值类型。
对于编译器而言,最基本的值是整数和浮点。其他的值都可以用这两者来表达,例如布尔和指针。如果你是从一个最基本的指令集开始写起,那么整数和浮点的数值运算、转换、基于整数寄存器的跳转和地址取值是一个寄存器机的最基本操作。所有更加高级的操作,例如数组、结构体、指针、函数、对象等,都可以建立在这一基础上。
如果一个指令系统在整数和浮点数之外,额外提供了布尔、分支、函数调用和结构体的支持,那么它与高级语言将会贴近更多,生成代码的方式也更加简单。
在高级语义的数据结构上,LLVM提供了相当良好的支持。它支持的原生类型(First class)包括: 各种精度的整型和浮点数,指针、向量,结构体和数组。这些类型的数据存取和运算都是有指令直接支撑,而不需要自行计算并生成更加原始的指令。
在存储类型上,LLVM提供了Value, Argument, Alloca, GlobalVariable, Pointer五种存储类型。Value是右值,它不可取引用,不可更改。Argument表示了函数实参,它是Value的一个派生类。所以对参数的任何更改行为实际上都是不被允许的。Alloca保存了栈地址,GlobalVariable保存了全局变量的地址,Pointer则是一般意义上的指针。
除了存储指令,LLVM所有的指令都是针对Value的操作,并返回一个Value。所以
Var a = Alloca int
Var b = Alloca int
Var c = Alloca int
c = ADD a, b
这样的操作,在LLVM中实际上是将a和b的地址相加,并把C从变量替换成一个左值(注意,是替换,变量的值没有任何变化)。
在LLVM中,正确的做法应当类似于下面这样:
a = Alloca int
b = Alloca int
c = Alloca int
a_v = load a
b_v = load b
c_v = ADD a, b
store c, c_v
要先将值从变量中读出,进行操作,再保存到另外一个变量中。
表达式值的数据结构
一个的表达式参数或结果可能是左值或右值。例如++x输入一个左值返回一个左值,而x++就返回一个右值。A+B则是需要两个右值并返回一个右值。
一个左值可以很方便的转化为右值,但是右值转化成左值通常是很困难的。地址信息被丢弃了,或者它根本就是一个字面常量,都会导致一个右值将永远是右值。将右值构造成左值的唯一办法,就是构造临时对象并将右值赋予左值。当这个左值被读取时,如果临时对象除了初始化之外从未被写过,并且它关联的右值依然有效,那么这个操作会被优化成直接返回那个原始的右值,从而避免临时左值的读写操作。
在Clang(一个C++编译器的前端)中对左值和右值进行了严格的区分。这是由于C++需要额外的处理临时对象。临时对象意味着尽管它有右值的语义,但是实际上是左值的存储。这是需要将真正的左值和临时的左值区分开,并提供特定语境下的转化。
SASL没有处理复杂的临时对象问题,因此它使用了一个相对简单的办法来解决左右值的判定和存储。
我们设计了一个数据结构,用于保存任何可能的值。
struct Data{
bool isRef;
Value* rval;
Alloca* local;
GlobalVariable* global;
struct Aggregated{
Data* parent;
int index;
} agg;
};
rval用于处理Argument和右值时的情况。Local意味着它是一个局部变量,global说明它是一个全局变量,agg则用于处理structure member。Parent指向包含当前变量的聚合变量,index则指明了当前变量在聚合变量中的位次。
SASL提供了load, load_ptr 和 store 来数据的存取,而不要关心它的具体存储类型。
左值/右值语义
在Data这个结构中,rval, local, global和agg四个值是互斥的。当然这里的我们也可以选择union+enum的方式来表达。
首先来看,这个结构如何表达左值/右值语义。
来看isRef,这是一个标记位。它表示了data存储的值究竟是值本身还是地址。如果是isref为真,那么data便可以被认为是一个左值。Isref为假,那么当它是rval的时候,它就是一个真正的右值了。如果是Alloca或者GlobalVariable,因为它们本身就代表了地址,那么它仍然是一个右值。如果是agg,那么要取决于它的聚合量是左值还是右值。
如果参数需要左值,那么可以直接从data拷贝,或者使用load_ptr + isRef创建一个新的右值Data。如果参数需要右值,那么可以通过load的方式获取一个右值。
数据存取的实现
llvm::Value* load( cgllvm_sctxt* data ){
assert(data);
Value* val = data->val;
do{
if( val ){ break; }
if( data->local ){ val = builder()->CreateLoad( data->local );
break;
}
if( data->global ){
val = builder()->CreateLoad( data->global );
break;
}
if( data.agg.parent ){
val = load( data->agg.parent );
val = builder()->CreateExtractValue( val, data->agg.index );
break;
}
} while(0);
if( data->is_ref ){val = builder()->CreateLoad( val );}
return val;
}
llvm::Value* load_ptr( cgllvm_sctxt* data ){
Value* addr = NULL;
if( data->val ){ addr = NULL; }
if( data->local ){
addr = data->local;
}
if( data->global ){
addr = data->global;
}
if( data->agg.parent ){
addr = builder()->CreateGEP( load_ptr(data->agg.parent), 0, data->arg.index );
}
if( data->is_ref ){
if( !addr ){
addr = data->val;
} else {
addr = builder()->CreateLoad( addr );
}
}
return addr;
}
void store( llvm::Value* v, cgllvm_sctxt* data ){
Value* addr = load_ptr( data );
builder()->CreateStore( v, addr );
}