ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
LLVM平台,短短几年间,改变了众多编程语言的走向,也催生了一大批具有特色的编程语言的出现,不愧为编译器架构的王者,也荣获2012年ACM软件系统奖 —— 题记 版权声明:本文为 西风逍遥游 原创文章,转载请注明出处 西风世界 [http://blog.csdn.net/xfxyy_sxfancy](http://blog.csdn.net/xfxyy_sxfancy) # 深入理解GetElementPtr LLVM平台,和C语言极为类似,强类型,需要复杂的指针操作,基于系统的符号调用等。而LLVM的指针操作指令,GetElementPtr,几乎是所有指针计算的关键,而理解它个运作原理,正确的使用,非常的重要。 ### 强类型的LLVM 编写LLVM需要时刻记住,LLVM是强类型的,每一条语句,都有确定的类型,GetElementPtr也正是这样,不同的参数,会有不同类型的返回类型。 我们先来看一段LLVM官网上的示例: ~~~ struct munger_struct { int f1; int f2; }; void munge(struct munger_struct *P) { P[0].f1 = P[1].f1 + P[2].f2; } ... munger_struct Array[3]; ... munge(Array); ~~~ 我们用Clang以C格式编译这段代码,`munge`函数会编译成如下IR: ~~~ void %munge(%struct.munger_struct* %P) { entry: %tmp = getelementptr %struct.munger_struct* %P, i32 1, i32 0 %tmp = load i32* %tmp %tmp6 = getelementptr %struct.munger_struct* %P, i32 2, i32 1 %tmp7 = load i32* %tmp6 %tmp8 = add i32 %tmp7, %tmp %tmp9 = getelementptr %struct.munger_struct* %P, i32 0, i32 0 store i32 %tmp8, i32* %tmp9 ret void } ~~~ 我们仔细来观察一下,每一条指令,都有明确的指明 `P` 指针的类型为 `%struct.munger_struct*`, 而下面的load语句,也间接说明了返回类型为 `i32*` 我们在正确理解GetElementPtr的工作方式时,必须时刻了解对应的类型,这样才不会偏差。 ### GetElementPtr的指令规则 GetElementPtr指令其实是一条指针计算语句,本身并不进行任何数据的访问或修改,进行是计算指针,修改计算后指针的类型。 GetElementPtr至少有两个参数,第一个参数为要进行计算的原始指针,往往是一个结构体指针,或数组首地址指针。 第二个参数及以后的参数,都称为`indices`,表示要进行计算的参数,如结构体的第几个元素,数组的第几个元素。 下面我们结合示例,来对应看一下是如何工作的: ~~~ P[0].f1 ~~~ 这是示例代码中的被赋值指针,我们C语言的经验告诉我们,首先`P[0]`的地址就是数组的首地址,而`f1`又是结构体的第一个参数,那么P的地址就是我们最终要放置数据的结构地址。 这条地址计算对应如下语句: ~~~ %tmp9 = getelementptr %struct.munger_struct* %P, i32 0, i32 0 ~~~ 我们发现参数是两个0,这两个0含义不大一样,第一个0是数组计算符,并不会改变返回的类型,因为,我们任何一个指针都可以作为一个数组来使用,进行对应的指针计算,所以这个0并不会省略。 第二个0是结构体的计算地址,表示的是结构体的第0个元素的地址,这时,会根据结构体指针的类型,选取其中的元素长度,进行计算,最后返回的则是结构体成员的指针。 同理,我们可以对照参考这两条语句: ~~~ P[1].f1 P[2].f2 ~~~ 对应的计算翻译后为: ~~~ %tmp = getelementptr %struct.munger_struct* %P, i32 1, i32 0 %tmp6 = getelementptr %struct.munger_struct* %P, i32 2, i32 1 ~~~ ### 注意事项 首先,不是全部的`indices`都必须是i32,也可以是i64,但结构体的计算地址,也就是上面例子中的第二个数字,必须是i32 GEP x,1,0,0 和 GEP x,1 计算后的地址是一样的,但类型不一样,所以千万注意不要在语句后添加多余的0。 ### 其他情况 ### 仅有数组计算 如果仅有数组指针计算,那么就简单了许多,数组指针的移动只需要一个参数即可。 但如果是仅有结构体指针,那么还是必须两个参数才行 ### 多维数组 个人觉得LLVM的数组定义很难写,推荐自己用一维数组代替,比较计算也不复杂。这样高维数组统一化成一维后,都成了基本的指针计算,就非常简单了。 ### 连续选取 GetElementPtr基本上可以认为是不限参数长度的,可以连续选取,于是我们可以实现: ~~~ A->B->C ~~~ 这类连续指向的计算。 但个人不推荐这样做,尤其是语法驱动的编译时,也很难做到这点,建议分开一条一条的语句进行执行,分别选取。 ### 参考 [http://llvm.org/docs/GetElementPtr.html](http://llvm.org/docs/GetElementPtr.html) 最近研究的LLVM技术,大部分应用于在进行的ELite编译器开发,欢迎朋友们关注和参与。 [https://github.com/elite-lang/Elite](https://github.com/elite-lang/Elite)