Доступ к элементам структуры и массивам структур из 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 в середине компиляции, что я сделал, потому что я использовал "по умолчанию", а затем установить другой, когда дело дошло до создания фактического объектного файла).