22

我正在玩弄 LLVM C++ API。我想 JIT 编译代码并运行它。

但是,我需要从所述 JIT 编译的代码中调用 C++ 方法。通常,LLVM 将方法调用视为函数调用,并将对象指针作为第一个参数传递,因此调用应该不是问题。真正的问题是将该功能放入 LLVM。

据我所知,可以对函数使用外部链接并通过其名称获取它。问题是,因为它是一个 C++ 方法,它的名字会被破坏,所以我不认为这样做是个好主意。

制作FunctionType对象很容易。但是从那里,我如何通知 LLVM 我的方法并Function为其获取对象?

4

3 回答 3

16

LLVM 邮件列表中的帅哥提供了更好的解决方案。他们没有说如何从方法中获取指向函数的指针,但是我已经弄清楚了这部分,所以没关系。

编辑一个干净的方法就是将你的方法包装成一个函数:

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

然后使用Foo_Bar'地址而不是尝试获取Foo::bar'。用于llvm::ExecutionEngine::addGlobalMapping添加映射,如下所示。

像往常一样,最简单的解决方案有一些有趣的好处。例如,它可以与虚拟函数一起使用而不会出现问题。(但它的娱乐性要低得多。剩下的答案是出于历史目的而保留的,主要是因为我在 C++ 运行时的内部玩得很开心。还要注意它是不可移植的。)


您将需要一些类似的东西来计算方法的地址(请注意,这是一个肮脏的 hack,可能只与 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's 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));
于 2010-07-04T17:30:12.333 回答
8

一种方法是围绕所需方法的 C 包装器,即

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

extern "C"位使函数使用 C 调用约定并防止任何名称修改。有关C/C++ 互操作的详细信息,请参见http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html#faq-32.6extern "C"

您还应该能够在 C++ 代码中获取函数的地址,然后将该地址存储在 LLVM 已知的全局中。

于 2010-06-23T18:36:17.523 回答
5

嗯,使用非标准dladdr且非常复杂且不安全的方式将方法指针转换为 void 指针,似乎有一种方法可以从其指针中获取方法的名称。

这肯定比枪支更危险。不要在家里(或在工作中)这样做。

即使使用全能的 C 强制转换, C++ 也禁止将方法指针转换为 void*(这是dladdr工作所必需的),但您可以作弊。

#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"
}

...aaa 并且您应该能够将该符号名称与 LLVM 一起使用。但它不适用于虚拟方法(另一个不使用它的好理由)。

编辑更深入地了解如何处理虚拟方法指针,我已经组合了一个更精细的函数,也适用于它们。只有最勇敢的人才能访问此链接

于 2010-06-23T19:19:48.837 回答