【问题标题】:Can I bind an existing method to a LLVM Function* and use it from JIT-compiled code?我可以将现有方法绑定到 LLVM 函数* 并从 JIT 编译的代码中使用它吗?
【发布时间】:2011-03-07 11:19:52
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ jit llvm


    【解决方案1】:

    来自 LLVM 邮件列表的家伙是 helpful enough to provide a better solution。他们没有说如何从方法中获取指向函数的指针,但是我已经弄清楚了这部分,所以没关系。

    编辑 一种简洁的方法就是将您的方法包装到一个函数中:

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

    然后使用Foo_Bar 的地址而不是尝试获取Foo::bar 的地址。如下图使用llvm::ExecutionEngine::addGlobalMapping添加映射。

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


    你需要一些类似的东西来确定方法的地址(请注意,这是一个肮脏的黑客,可能只与 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));
    

    【讨论】:

    • 您能否向我们展示您最终是如何解决该问题的。我正在努力解决同样的问题。
    • @FFox:当然。我已经编辑了答案以提供一个有用的示例。
    • @zneak,在您传递给llvm::Function::Create 的函数类型中,您对第一个参数(对象指针)使用了什么类型?你用过 void* 吗?
    • @lurscher,不,因为这样会很麻烦。该模式是对类StructType&lt;typename T, bool xcompile&gt; 进行专门化,其中T 是您的结构,并在那里实现get 方法。然后您可以使用llvm::StructType&lt;MyStruct&gt;::get(context) 来获取类型,然后获取指向它的指针类型就很简单了。有关示例实现,请参阅 here
    • 您知道使用更现代的orc Jit api 的方法吗? (question here)
    【解决方案2】:

    一种方法是围绕所需方法的 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.6,包括 extern "C"

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

    【讨论】:

    • 我认为可以有更直接的方法。
    【解决方案3】:

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

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

    C++ 禁止将方法指针转换为 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"
    }
    

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

    编辑 对虚拟方法指针的处理方式进行了更深入的破解,我已经组合了一个更精细的函数,也适用于它们。只有最勇敢的人应该follow this link

    【讨论】:

    • @Paul Nathan:虽然它确实发出了一些警告(“控制到达非空函数的末尾”)。我已经美化了它以更好地与 C++ 集成。
    猜你喜欢
    • 1970-01-01
    • 2011-06-27
    • 2011-09-03
    • 2011-05-24
    • 1970-01-01
    • 2013-04-25
    • 2023-03-19
    • 1970-01-01
    • 2011-02-17
    相关资源
    最近更新 更多