【问题标题】:Accessing struct members and arrays of structs from LLVM IR从 LLVM IR 访问结构成员和结构数组
【发布时间】:2015-09-01 02:16:27
【问题描述】:

如果我有一个声明 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 version 6.0 (clang-600.0.57) (based on 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 位边界上对齐,然后我得到 same 大小。那么为什么默认数据布局不正确呢?


下面的完整工作程序

// 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;
}

【问题讨论】:

  • 好的,这告诉我它有多大。在这种情况下,大小匹配。那么如何让它们匹配呢?

标签: c++ llvm jit


【解决方案1】:

由于原始答案是“预编辑”问题的正确答案,因此我正在为新问题编写一个全新的答案(我猜测结构实际上并不相同是非常好的)。

问题不在于DataLayout 本身 [但您将需要 DataLayout 来解决问题,因此您需要在开始制作 LLVM-IR 之前更新代码以创建模块],但事实上您正在将具有对齐限制的 unionstruct 中的对齐限制较小的对齐限制相结合:

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的值]

要解决这个问题,以一种完全通用的方式,您需要生成一个由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 &lt; maxSize,而 Pascal 版本检查 maxAlignElt != maxSizeElt
  • 因为最终结果是相同的——如果最大对齐元素 >= maxSize 那么我们不需要填充。如果最大对齐的元素与最大尺寸元素相同,我们不需要填充。 [公平地说,我认为我的编译器代码可以简化,但我正在修复一些其他的东西,我不想破坏一些东西,只是为了删除三行并非绝对必要但作品]
  • 顺便说一句,如果我想让我的struct 包含std::string,我宁愿 必须详细描述其布局(即无论如何都将依赖于实现)。我认为有可能做类似于union 的事情,方法是拥有一个包含类型T 的模板结构(再说一次,比如说std::string)并找出它的偏移量,然后相应地构造一个StructType。想法?
  • 不确定,您必须找出对齐方式,然后找到与该对齐方式匹配的另一种类型[假设您不关心访问std::string 的内容]。您可以使用alignof 找到对齐方式,但我无法找到一种方法来设置结构中成员的对齐方式。
  • 您不必设置它。如果您的探测结构是struct { char c; T t; } s;,您可以使用offsetof 来了解对齐方式(在 C++11 之前的版本中)。然后,您选择具有匹配对齐方式的内置类型(您可以预先探测所有内置 C++ 类型)并在 StructType 中使用它,然后用填充填充其余部分。
【解决方案2】:

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

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

【讨论】:

  • 我的问题是我需要正确调整尺寸和对齐方式。
  • 但是,DataLayout 仍然只在生成代码或获取大小时很重要,所以我认为您需要显示更多代码,或者更准确地解释什么不起作用。怎么错了?会发生什么,它与您的预期有何不同? C++ 中的sizeof(S) 是什么,DataLayout::getTypeAllocSize(t) 是什么?什么是DataLayout::getPrefTypeAlignment(x);,其中xt的元素?
  • 我的实际结构比此处显示的玩具示例要复杂一些。只要说sizeof(S) DataLayout::getTypeAllocSize(s) 就足够了,其中s 是等效StructType 的LLVM 实例。我需要后者的大小与前者相同,以便 LLVM IR JIT 代码可以使用 C++ 编译器创建的 C++ 对象。这样做的全部意义在于生成可以与 C++ 编译器生成的代码互操作的 JIT'd LLVM IR 代码。
  • 我明白这一点,但它确实需要是一个可重现的例子,说明它在你的问题中出错,以便能够提供比“你需要对两种情况都具有相同的 DataLayout”更多的帮助。 (包括由于某种原因您的结构在两种情况下实际上并不完全相同的可能性!)
  • 好的,有一个完整的工作程序可以显示大小差异。
猜你喜欢
  • 2017-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-30
  • 1970-01-01
相关资源
最近更新 更多