Доступ к элементам структуры и массивам структур из LLVM IR

если у меня есть программа на языке C++, объявлен struct, говорят:

struct S {
    short s;
    union U {
        bool b;
        void *v;
    };
    U u;
};

и я генерирую некоторый LLVM IR через LLVM C++ API, чтобы отразить объявление 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 );

как я могу гарантировать, что sizeof(S) в коде C++ имеет тот же размер, что и StructType в LLVM ИК-код? То же самое для смещений отдельных членов, т. е. u.b.

это также случай, когда у меня есть массив S выделено в C++:

S *s_array = new S[10];

и я пас s_array к ИК-коду LLVM, в котором я получаю доступ к отдельным элементам массива. Чтобы это сработало, sizeof(S) должен быть одинаковым как в C++, так и в LLVM IR, так что это:

%elt = getelementptr %S* %ptr_to_start, i64 1

доступ s_array[1] правильно.

когда я компилирую и запускаю программу ниже, она выводит:

sizeof(S) = 16
allocSize(S) = 10

проблема в том, что LLVM отсутствует 6 байт заполнения между S::s и S::u. Компилятор C++ делает union начать с 8-байтовой выровненной границы а кода LLVM не.

я играл с DataLayout. Для моей машины [Mac OS X 10.9.5, g++ Apple LLVM версии 6.0 (clang-600.0.57) (на основе LLVM 3.5 svn)], если я напечатаю строку макета данных, я получу:

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

2 ответов


поскольку исходный ответ является правильным ответом на вопрос" предварительно редактировать", я пишу совершенно новый ответ на новый вопрос (и я предполагаю, что структуры на самом деле не то же самое было довольно хорошо).

проблема не в 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 code-gen:

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, а затем pad его с достаточно 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 - и во втором случае вам также нужен bitcast для преобразования struct на void *, но предполагая, что вы действительно хотите generic union поддержка, это было бы так, как вы должны были бы сделать это в любом случае.

полный кусок кода для генерации union тип можно найти здесь, что для моего компилятора Pascal variant [это способ Паскаля сделать union]:

https://github.com/Leporacanthicus/lacsap/blob/master/types.cpp#L525 и генерация кода, включая bitcast: https://github.com/Leporacanthicus/lacsap/blob/master/expr.cpp#L520


основная цель DataLayout знать выравнивание элементов. Если вам не нужно знать размер, выравнивание или смещения элементов в коде [и LLVM на самом деле не имеет полезного способа за пределами инструкции GEP, чтобы найти смещение, поэтому вы можете в значительной степени игнорировать часть смещения], вам не понадобится datalayout, пока вы не приступите к выполнению (или созданию объектного файла) из ИК.

(У меня были очень интересные ошибки при попытке скомпилировать 32-битный код с 64-битным "родной" datalayout когда я реализовал-M32 переключатель для моего компилятора-не очень хорошая идея, чтобы переключить DataLayout в середине компиляции, что я сделал, потому что я использовал "по умолчанию", а затем установить другой, когда дело дошло до создания фактического объектного файла).