从 LLVM IR 访问结构成员和结构数组
Accessing struct members and arrays of structs from LLVM IR
如果我有一个声明 struct
的 C++ 程序,请说:
struct S {
short s;
union U {
bool b;
void *v;
};
U u;
};
并且我通过 LLVM C++ API 生成了一些 LLVM IR 来镜像 C++ 声明:
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
// since LLVM doesn't support unions, just use an ArrayType that's the same size
members.push_back( ArrayType::get( IntegerType::get( ctx, 8 ), sizeof( S::U ) ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
如何确保C++代码中的sizeof(S)
与LLVM IR代码中的StructType
大小相同?单个成员的偏移量相同,即 u.b
.
也是这样,我在C++中分配了一个S
的数组:
S *s_array = new S[10];
然后我将 s_array
传递给 LLVM IR 代码,我在其中访问数组的各个元素。为了使其工作,sizeof(S)
在 C++ 和 LLVM IR 中必须相同,因此:
%elt = getelementptr %S* %ptr_to_start, i64 1
将正确访问 s_array[1]
。
当我编译和运行下面的程序时,它输出:
sizeof(S) = 16
allocSize(S) = 10
问题是 LLVM 在 S::s
和 S::u
之间缺少 6 个字节的填充。 C++ 编译器使 union
从 8 字节对齐的边界开始,而 LLVM 则不然。
我在玩 DataLayout
。对于我的机器 [Mac OS X 10.9.5,g++ Apple LLVM 版本 6.0 (clang-600.0.57)(基于 LLVM 3.5svn)],如果我打印数据布局字符串,我得到:
e-m:o-i64:64-f80:128-n8:16:32:64-S128
如果我将数据布局强制设置为:
e-m:o-i64:64-f80:128-n8:16:32:64-S128-a:64
其中加法是 a:64
,这意味着聚合类型的对象在 64 位边界上对齐,然后我得到 相同的 大小。那么为什么默认数据布局不正确?
下面是完整的工作程序
// LLVM
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/MCJIT.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Type.h>
#include <llvm/Support/TargetSelect.h>
// standard
#include <iostream>
#include <memory>
#include <string>
using namespace std;
using namespace llvm;
struct S {
short s;
union U {
bool b;
void *v;
};
U u;
};
ExecutionEngine* createEngine( Module *module ) {
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
unique_ptr<Module> u( module );
EngineBuilder eb( move( u ) );
string errStr;
eb.setErrorStr( &errStr );
eb.setEngineKind( EngineKind::JIT );
ExecutionEngine *const exec = eb.create();
if ( !exec ) {
cerr << "Could not create ExecutionEngine: " << errStr << endl;
exit( 1 );
}
return exec;
}
int main() {
LLVMContext ctx;
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
members.push_back( ArrayType::get( IntegerType::get( ctx, 8 ), sizeof( S::U ) ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
Module *const module = new Module( "size_test", ctx );
ExecutionEngine *const exec = createEngine( module );
DataLayout const *const layout = exec->getDataLayout();
module->setDataLayout( layout );
cout << "sizeof(S) = " << sizeof( S ) << endl;
cout << "allocSize(S) = " << layout->getTypeAllocSize( llvm_S ) << endl;
delete exec;
return 0;
}
DataLayout
的主要目的是了解元素的对齐方式。如果您不需要知道代码中元素的大小、对齐方式或偏移量 [并且 LLVM 除了 GEP 指令之外并没有真正有用的方法来查找偏移量,因此您几乎可以忽略偏移量部分],您在您从 IR 执行(或生成目标文件)之前不需要数据布局。
(当我为我的编译器实现 -m32 开关时,我尝试用 64 位 "native" 数据布局编译 32 位代码时确实遇到了一些非常有趣的错误 - 切换 DataLayout 不是一个好主意在编译过程中,我这样做是因为我使用了 "default" 一个,然后在创建实际目标文件时设置了一个不同的)。
因为原来的答案是 "pre-edit" 问题的正确答案,所以我正在为新问题写一个全新的答案(我猜这些结构实际上并不相同是相当不错的) .
问题不在于 DataLayout
[但您需要 DataLayout 来解决问题,因此您需要更新代码以在开始制作 LLVM-IR 之前创建模块],但事实上,您将 union
与 struct
中具有对齐限制的 union
相结合,并具有较小的对齐限制:
struct S {
short s; // Alignment = 2
union U {
bool b; // Alignment = 1
void *v; // Alignment = 4 or 8
};
U u; // = Alignment = 4 or 8
};
现在在您的 LLVM 代码生成中:
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
members.push_back( ArrayType::get( IntegerType::get( ctx, 8 ), sizeof( S::U ) ) );
结构中的第二个元素是 char dummy[sizeof(S::U)]
,它的对齐要求为 1。因此,当然,LLVM 将对齐 struct
与具有更严格的 C++ 编译器不同对齐标准。
在这种特殊情况下,使用 i8 *
(又名 void *
)代替 i8
的数组就可以解决问题 [显然与相关的 bitcast
在访问 b
]
的值时根据需要转换为其他类型
要解决此问题,以完全通用的方式,您需要生成一个 struct
,由 union
中具有最大对齐要求的元素组成,然后用足够的 [= 填充它26=]个元素来弥补最大尺寸。
我现在要去吃点东西,但我会带着一些正确解决它的代码回来,但它比我原先想象的要复杂一些。
这是上面发布的 main
修改为使用指针而不是 char
数组:
int main() {
LLVMContext ctx;
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
members.push_back( PointerType::getUnqual( IntegerType::get( ctx, 8 ) ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
Module *const module = new Module( "size_test", ctx );
ExecutionEngine *const exec = createEngine( module );
DataLayout const *const layout = exec->getDataLayout();
module->setDataLayout( *layout );
cout << "sizeof(S) = " << sizeof( S ) << endl;
cout << "allocSize(S) = " << layout->getTypeAllocSize( llvm_S ) << endl;
delete exec;
return 0;
}
还有一些微小的变化来弥补 setDataLayout
在您的 LLVM 版本和我使用的版本之间发生了变化这一事实。
最后是允许使用任何类型的通用版本:
Type* MakeUnionType( Module* module, LLVMContext& ctx, vector<Type*> um )
{
const DataLayout dl( module );
size_t maxSize = 0;
size_t maxAlign = 0;
Type* maxAlignTy = 0;
for( auto m : um )
{
size_t sz = dl.getTypeAllocSize( m );
size_t al = dl.getPrefTypeAlignment( m );
if( sz > maxSize )
maxSize = sz;
if( al > maxAlign)
{
maxAlign = al;
maxAlignTy = m;
}
}
vector<Type*> sv = { maxAlignTy };
size_t mas = dl.getTypeAllocSize( maxAlignTy );
if( mas < maxSize )
{
size_t n = maxSize - mas;
sv.push_back(ArrayType::get( IntegerType::get( ctx, 8 ), n ) );
}
StructType* u = StructType::create( ctx, "U" );
u->setBody( sv );
return u;
}
int main() {
LLVMContext ctx;
Module *const module = new Module( "size_test", ctx );
ExecutionEngine *const exec = createEngine( module );
DataLayout const *const layout = exec->getDataLayout();
module->setDataLayout( *layout );
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
vector<Type*> unionMembers = { PointerType::getUnqual( IntegerType::get( ctx, 8 ) ),
IntegerType::get( ctx, 1 ) };
members.push_back( MakeUnionType( module, ctx, unionMembers ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
cout << "sizeof(S) = " << sizeof( S ) << endl;
cout << "allocSize(S) = " << layout->getTypeAllocSize( llvm_S ) << endl;
delete exec;
return 0;
}
请注意,在这两种情况下,您都需要一个 bitcast
操作来转换 b
的地址 - 在第二种情况下,您还需要一个位转换来转换 struct
进入 void *
,但假设您确实需要通用 union
支持,那么无论如何您都必须这样做。
可以在此处找到生成 union
类型的完整代码,它适用于我的 Pascal 编译器的 variant
[这是 Pascal 生成 union
的方法]:
https://github.com/Leporacanthicus/lacsap/blob/master/types.cpp#L525
和代码生成,包括位播:
https://github.com/Leporacanthicus/lacsap/blob/master/expr.cpp#L520
如果我有一个声明 struct
的 C++ 程序,请说:
struct S {
short s;
union U {
bool b;
void *v;
};
U u;
};
并且我通过 LLVM C++ API 生成了一些 LLVM IR 来镜像 C++ 声明:
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
// since LLVM doesn't support unions, just use an ArrayType that's the same size
members.push_back( ArrayType::get( IntegerType::get( ctx, 8 ), sizeof( S::U ) ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
如何确保C++代码中的sizeof(S)
与LLVM IR代码中的StructType
大小相同?单个成员的偏移量相同,即 u.b
.
也是这样,我在C++中分配了一个S
的数组:
S *s_array = new S[10];
然后我将 s_array
传递给 LLVM IR 代码,我在其中访问数组的各个元素。为了使其工作,sizeof(S)
在 C++ 和 LLVM IR 中必须相同,因此:
%elt = getelementptr %S* %ptr_to_start, i64 1
将正确访问 s_array[1]
。
当我编译和运行下面的程序时,它输出:
sizeof(S) = 16
allocSize(S) = 10
问题是 LLVM 在 S::s
和 S::u
之间缺少 6 个字节的填充。 C++ 编译器使 union
从 8 字节对齐的边界开始,而 LLVM 则不然。
我在玩 DataLayout
。对于我的机器 [Mac OS X 10.9.5,g++ Apple LLVM 版本 6.0 (clang-600.0.57)(基于 LLVM 3.5svn)],如果我打印数据布局字符串,我得到:
e-m:o-i64:64-f80:128-n8:16:32:64-S128
如果我将数据布局强制设置为:
e-m:o-i64:64-f80:128-n8:16:32:64-S128-a:64
其中加法是 a:64
,这意味着聚合类型的对象在 64 位边界上对齐,然后我得到 相同的 大小。那么为什么默认数据布局不正确?
下面是完整的工作程序
// LLVM
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/MCJIT.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Type.h>
#include <llvm/Support/TargetSelect.h>
// standard
#include <iostream>
#include <memory>
#include <string>
using namespace std;
using namespace llvm;
struct S {
short s;
union U {
bool b;
void *v;
};
U u;
};
ExecutionEngine* createEngine( Module *module ) {
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
unique_ptr<Module> u( module );
EngineBuilder eb( move( u ) );
string errStr;
eb.setErrorStr( &errStr );
eb.setEngineKind( EngineKind::JIT );
ExecutionEngine *const exec = eb.create();
if ( !exec ) {
cerr << "Could not create ExecutionEngine: " << errStr << endl;
exit( 1 );
}
return exec;
}
int main() {
LLVMContext ctx;
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
members.push_back( ArrayType::get( IntegerType::get( ctx, 8 ), sizeof( S::U ) ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
Module *const module = new Module( "size_test", ctx );
ExecutionEngine *const exec = createEngine( module );
DataLayout const *const layout = exec->getDataLayout();
module->setDataLayout( layout );
cout << "sizeof(S) = " << sizeof( S ) << endl;
cout << "allocSize(S) = " << layout->getTypeAllocSize( llvm_S ) << endl;
delete exec;
return 0;
}
DataLayout
的主要目的是了解元素的对齐方式。如果您不需要知道代码中元素的大小、对齐方式或偏移量 [并且 LLVM 除了 GEP 指令之外并没有真正有用的方法来查找偏移量,因此您几乎可以忽略偏移量部分],您在您从 IR 执行(或生成目标文件)之前不需要数据布局。
(当我为我的编译器实现 -m32 开关时,我尝试用 64 位 "native" 数据布局编译 32 位代码时确实遇到了一些非常有趣的错误 - 切换 DataLayout 不是一个好主意在编译过程中,我这样做是因为我使用了 "default" 一个,然后在创建实际目标文件时设置了一个不同的)。
因为原来的答案是 "pre-edit" 问题的正确答案,所以我正在为新问题写一个全新的答案(我猜这些结构实际上并不相同是相当不错的) .
问题不在于 DataLayout
[但您需要 DataLayout 来解决问题,因此您需要更新代码以在开始制作 LLVM-IR 之前创建模块],但事实上,您将 union
与 struct
中具有对齐限制的 union
相结合,并具有较小的对齐限制:
struct S {
short s; // Alignment = 2
union U {
bool b; // Alignment = 1
void *v; // Alignment = 4 or 8
};
U u; // = Alignment = 4 or 8
};
现在在您的 LLVM 代码生成中:
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
members.push_back( ArrayType::get( IntegerType::get( ctx, 8 ), sizeof( S::U ) ) );
结构中的第二个元素是 char dummy[sizeof(S::U)]
,它的对齐要求为 1。因此,当然,LLVM 将对齐 struct
与具有更严格的 C++ 编译器不同对齐标准。
在这种特殊情况下,使用 i8 *
(又名 void *
)代替 i8
的数组就可以解决问题 [显然与相关的 bitcast
在访问 b
]
要解决此问题,以完全通用的方式,您需要生成一个 struct
,由 union
中具有最大对齐要求的元素组成,然后用足够的 [= 填充它26=]个元素来弥补最大尺寸。
我现在要去吃点东西,但我会带着一些正确解决它的代码回来,但它比我原先想象的要复杂一些。
这是上面发布的 main
修改为使用指针而不是 char
数组:
int main() {
LLVMContext ctx;
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
members.push_back( PointerType::getUnqual( IntegerType::get( ctx, 8 ) ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
Module *const module = new Module( "size_test", ctx );
ExecutionEngine *const exec = createEngine( module );
DataLayout const *const layout = exec->getDataLayout();
module->setDataLayout( *layout );
cout << "sizeof(S) = " << sizeof( S ) << endl;
cout << "allocSize(S) = " << layout->getTypeAllocSize( llvm_S ) << endl;
delete exec;
return 0;
}
还有一些微小的变化来弥补 setDataLayout
在您的 LLVM 版本和我使用的版本之间发生了变化这一事实。
最后是允许使用任何类型的通用版本:
Type* MakeUnionType( Module* module, LLVMContext& ctx, vector<Type*> um )
{
const DataLayout dl( module );
size_t maxSize = 0;
size_t maxAlign = 0;
Type* maxAlignTy = 0;
for( auto m : um )
{
size_t sz = dl.getTypeAllocSize( m );
size_t al = dl.getPrefTypeAlignment( m );
if( sz > maxSize )
maxSize = sz;
if( al > maxAlign)
{
maxAlign = al;
maxAlignTy = m;
}
}
vector<Type*> sv = { maxAlignTy };
size_t mas = dl.getTypeAllocSize( maxAlignTy );
if( mas < maxSize )
{
size_t n = maxSize - mas;
sv.push_back(ArrayType::get( IntegerType::get( ctx, 8 ), n ) );
}
StructType* u = StructType::create( ctx, "U" );
u->setBody( sv );
return u;
}
int main() {
LLVMContext ctx;
Module *const module = new Module( "size_test", ctx );
ExecutionEngine *const exec = createEngine( module );
DataLayout const *const layout = exec->getDataLayout();
module->setDataLayout( *layout );
vector<Type*> members;
members.push_back( IntegerType::get( ctx, sizeof( short ) * 8 ) );
vector<Type*> unionMembers = { PointerType::getUnqual( IntegerType::get( ctx, 8 ) ),
IntegerType::get( ctx, 1 ) };
members.push_back( MakeUnionType( module, ctx, unionMembers ) );
StructType *const llvm_S = StructType::create( ctx, "S" );
llvm_S->setBody( members );
cout << "sizeof(S) = " << sizeof( S ) << endl;
cout << "allocSize(S) = " << layout->getTypeAllocSize( llvm_S ) << endl;
delete exec;
return 0;
}
请注意,在这两种情况下,您都需要一个 bitcast
操作来转换 b
的地址 - 在第二种情况下,您还需要一个位转换来转换 struct
进入 void *
,但假设您确实需要通用 union
支持,那么无论如何您都必须这样做。
可以在此处找到生成 union
类型的完整代码,它适用于我的 Pascal 编译器的 variant
[这是 Pascal 生成 union
的方法]:
https://github.com/Leporacanthicus/lacsap/blob/master/types.cpp#L525 和代码生成,包括位播: https://github.com/Leporacanthicus/lacsap/blob/master/expr.cpp#L520