从 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::sS::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 之前创建模块],但事实上,您将 unionstruct 中具有对齐限制的 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