从LLVM IR访问结构体成员和结构体数组

7
如果我有一个声明了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则没有这样做。
我正在尝试使用。对于我的机器[Mac OS X 10.9.5,g++ Apple LLVM版本6.0(基于LLVM 3.5svn的clang-600.0.57)],如果我打印数据布局字符串,我会得到:
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;
}

getTypeAllocSize() 是怎么样的呢? - Marco A.
好的,这告诉我它有多大。在这种情况下,大小不匹配。那么我该如何使它们匹配呢? - Paul J. Lucas
2个回答

7
由于原先回答是针对“修改前”的问题的正确答案,所以我将为新问题编写全新的答案(我的猜测结构体实际上并不相同是很好的)。问题不在于DataLayout本身[但你需要DataLayout来解决问题,因此你需要更新代码以在开始制作LLVM-IR之前创建模块],而是因为你将一个具有对齐要求的union与具有较小对齐要求的struct结合使用:
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将与具有更严格对齐标准的C++编译器对struct进行不同的对齐。

在这种特定情况下,使用i8 *(又名void *)替换i8数组即可解决问题[在访问b的值时需要相应的bitcast进行翻译为其他类型]

要以完全通用的方式修复此问题,您需要产生一个由union中具有最大对齐需求的元素组成的struct,然后用足够多的char元素填充它以弥补最大尺寸。

我现在要吃点东西了,但我会带回一些能够正确解决它的代码,但比我最初想象的要复杂一些。

这里是上面的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

是的,那似乎可以工作。:) 但是为什么两个版本的代码存在差异呢?例如,这个版本检查 mas < maxSize,而 Pascal 版本检查 maxAlignElt != maxSizeElt - Paul J. Lucas
因为最终结果是相同的——如果最大对齐元素 >= maxSize,则我们不需要填充。如果最大对齐元素与最大尺寸元素相同,则我们不需要填充。[我认为我的编译器代码可以简化,但公平地说,我正在修复其他一些东西,我不想因为要删除三行不太必要但可用的代码而破坏其他东西。] - Mats Petersson
顺便提一下,如果我想让我的 struct 包含 std::string,我宁愿不要详细描述它的布局(那样也会依赖于实现)。我认为可以通过使用包含类型 T(比如 std::string)的模板结构体,并找出其偏移量,然后相应地构造一个 StructType 来完成类似于 union 的操作。你有什么想法吗? - Paul J. Lucas
不确定,您需要找出对齐方式,然后找到与该对齐方式匹配的另一种类型[假设您不关心访问std::string的内容]。您可以使用alignof找到对齐方式,但我无法想出一种在结构体成员中设置对齐方式的方法。 - Mats Petersson
您不需要进行设置。如果您的探针结构体是struct { char c; T t; } s; ,则可以在C++11之前使用offsetof来知道其对齐方式。然后选择一个具有相匹配对齐方式的内置类型(您可以为所有内置C ++类型预先探测),并将其用于StructType,然后只需用填充物填充剩余部分即可。 - Paul J. Lucas
是的,类似那样。因为自LLVM 3.5起需要C++11,所以我会使用 alignof - 没有必要让事情变得复杂。而且你可能可以制作一个具有各种对齐方式的类型表,只需查找给定对齐方式的相关类型即可。 - Mats Petersson

1
DataLayout的主要目的是了解元素的对齐方式。如果您的代码中不需要知道元素的大小、对齐或偏移量[除了使用GEP指令之外,LLVM没有真正有用的方法来查找偏移量,所以您可以基本忽略偏移量部分],则在执行(或生成对象文件)IR之前将不需要数据布局。

(当我为我的编译器实现-m32开关时,尝试使用64位“本机”数据布局编译32位代码导致了一些非常有趣的错误 - 在编译过程中切换DataLayout不是一个好主意,因为我在使用“默认”数据布局时进行了设置,然后在创建实际的对象文件时设置了不同的数据布局)。


我的问题说我需要正确获取大小和对齐方式。 - Paul J. Lucas
但是,DataLayout只在生成代码或获取大小的时候才起作用,所以我认为你需要展示更多的代码,或者更详细地解释一下什么不起作用。它错在哪里?发生了什么,与你的期望有何不同?在C++中,sizeof(S)是多少?DataLayout::getTypeAllocSize(t)又是什么?DataLayout::getPrefTypeAlignment(x);中的xt的元素是什么? - Mats Petersson
我的实际结构比这里展示的玩具示例要复杂一些。可以说 sizeof(S) < DataLayout::getTypeAllocSize(s),其中s是等效于StructType的LLVM实例。我需要后者与前者的大小相同,以便C++编译器创建的C++对象可以被LLVM IR JIT'd代码使用。而所有这些的目的就是生成JIT'd LLVM IR代码,使其能够与C++编译器生成的代码进行交互。 - Paul J. Lucas
我理解您的情况,但是为了能够提供更多帮助,您需要在问题中提供一个可重现的错误示例,而不仅仅是告诉您“您需要在两种情况下具有相同的DataLayout”(包括某些情况下您的结构体实际上可能并不相同!)。 - Mats Petersson
好的,这里有一个完整的可以展示大小差异的工作程序。 - Paul J. Lucas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接