Подтвердить что ты не робот

Можно ли связать существующий метод с функцией LLVM * и использовать его из JIT-скомпилированного кода?

Я играю с LLVM С++ API. Я хотел бы JIT компилировать код и запускать его.

Однако мне нужно вызвать метод С++ из указанного JIT-скомпилированного кода. Обычно LLVM обрабатывает вызовы методов как вызовы функций с указателем объекта, переданным как первый аргумент, поэтому вызов не должен быть проблемой. Реальная проблема заключается в том, чтобы получить эту функцию в LLVM.

Насколько я вижу, можно использовать внешнюю связь для функций и получить ее по ее имени. Проблема в том, что, поскольку это метод C++, его имя будет искалечено, поэтому я не думаю, что это хорошая идея пойти таким образом.

Создание объекта FunctionType достаточно просто. Но оттуда, как я могу сообщить LLVM моего метода и получить для него объект Function?

4b9b3361

Ответ 1

Чуваки из списка рассылки LLVM были достаточно полезны, чтобы обеспечить лучшее решение. Они не сказали, как получить указатель от метода к функции, но я уже понял эту часть, так что все в порядке.

РЕДАКТИРОВАТЬ Чистый способ сделать это - просто превратить ваш метод в функцию:

int Foo_Bar(Foo* foo)
{
    return foo->bar();
}

Затем используйте Foo_Bar адрес вместо того, чтобы пытаться получить Foo::bar. Используйте llvm::ExecutionEngine::addGlobalMapping, чтобы добавить отображение, как показано ниже.

Как обычно, простейшее решение имеет некоторые интересные преимущества. Например, он работает с виртуальными функциями без икоты. (Но это гораздо менее интересно. Остальная часть ответа хранится в исторических целях, главным образом потому, что мне было очень весело возиться с внутренними средами моей С++. Также обратите внимание, что она не переносима.)


Вам понадобится что-то в этих строках, чтобы указать адрес метода (будьте осторожны, что грязный хак, который, вероятно, будет совместим только с Itanium ABI):

template<typename T>
const void* void_cast(const T& object)
{
    union Retyper
    {
        const T object;
        void* pointer;
        Retyper(T obj) : object(obj) { }
    };

    return Retyper(object).pointer;
}

template<typename T, typename M>
const void* getMethodPointer(const T* object, M method) // will work for virtual methods
{
    union MethodEntry
    {
        intptr_t offset;
        void* function;
    };

    const MethodEntry* entry = static_cast<const MethodEntry*>(void_cast(&method));

    if (entry->offset % sizeof(intptr_t) == 0) // looks like that how the runtime guesses virtual from static
        return getMethodPointer(method);

    const void* const* const vtable = *reinterpret_cast<const void* const* const* const>(object);
    return vtable[(entry->offset - 1) / sizeof(void*)];
}

template<typename M>
const void* getMethodPointer(M method) // will only work with non-virtual methods
{
    union MethodEntry
    {
        intptr_t offset;
        void* function;
    };

    return static_cast<const MethodEntry*>(void_cast(&method))->function;
}

Затем используйте llvm::ExecutionEngine::addGlobalMapping для сопоставления функции с адресом, который вы получили. Чтобы вызвать его, передайте ему свой объект в качестве первого параметра, а остальные, как обычно. Вот краткий пример.

class Foo
{
    void Bar();
    virtual void Baz();
};

class FooFoo : public Foo
{
    virtual void Baz();
};

Foo* foo = new FooFoo;

const void* barMethodPointer = getMethodPointer(&Foo::Bar);
const void* bazMethodPointer = getMethodPointer(foo, &Foo::Baz); // will get FooFoo::Baz

llvm::ExecutionEngine* engine = llvm::EngineBuilder(module).Create();

llvm::Function* bar = llvm::Function::Create(/* function type */, Function::ExternalLinkage, "foo", module);
llvm::Function* baz = llvm::Function::Create(/* function type */, Function::ExternalLinkage, "baz", module);
engine->addGlobalMapping(bar, const_cast<void*>(barMethodPointer)); // LLVM always takes non-const pointers
engine->addGlobalMapping(baz, const_cast<void*>(bazMethodPointer));

Ответ 2

Один из способов - это обертка C вокруг желаемого метода, то есть

extern "C" {
  void wrapped_foo(bar *b, int arg1, int arg2) {
    b->foo(arg1, arg2);
  }
}

Бит extern "C" заставляет функцию использовать соглашения о вызовах C и предотвращает любое изменение имени. См. http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html#faq-32.6 для получения подробной информации о взаимодействии C/С++, включая extern "C"

Вероятно, вы также сможете получить адрес функции в коде С++, а затем сохранить этот адрес в глобальном, известном LLVM.

Ответ 3

Да, используя нестандартный dladdr и смехотворно запутанный и небезопасный способ бросить указатели меток на указатели void, кажется, есть способ получить имя метода из его указателя.

Это, безусловно, более опасно, чем огнестрельное оружие. Не делайте это дома (или на работе, если на то пошло).

С++ запрещает использовать указатели методов для void * (что требуется для работы dladdr) даже при всемогущем C, но вы можете обмануть это.

#include <string>
#include <dlfcn.h>

template<typename T>
static void* voidify(T method)
{
    asm ("movq %rdi, %rax"); // should work on x86_64 ABI compliant platforms
}

template<typename T>
const char* getMethodName(T method)
{
    Dl_info info;
    if (dladdr(voidify(method), &info))
        return info.dli_sname;
    return "";
}

Оттуда:

int main()
{
    std::cout << getMethodName(&Foo::bar) << std::endl;
    // prints something like "_ZN3Foo3barEv"
}

... aaaand вы должны иметь возможность использовать это имя символа в LLVM. Но он не будет работать с виртуальными методами (еще одна веская причина не использовать его).

РЕДАКТИРОВАТЬ Взлом намного, гораздо глубже в том, как обрабатываются указатели виртуальных методов, я собрал более сложную функцию, которая работает и для них. Только самые смелые из вас должны следовать этой ссылке.