How do you use JIT compilation to produce a C pointer callback from a captured lambda?

In C++11 and later converting a lambda expression with no captured variables is very simple, but for lambdas with captured variables to do it properly requires dynamic code generation as per this guys answer.

Basically what I figure will have to be dynamically generate is this. (Note the code here indicates my desired semantics not any real code)

UserData *userdata;
api_return_value callback(api_data arg) {
    return customized_callback(arg, userdata);

And I would generate this so I can bind the dynamically generated code to a corresponding function pointer callback defined in some api

api_return_value (*callback) (api_data);

Is there a reasonably clean and portable way to go about this using something like LLVM or NativeJIT? I'm stuck using a C api with no provided user data pointer so this seems to be my only alternative.

1 answer

  • answered 2020-01-14 21:26 Chris Dodd

    Rather than doing JIT code generation at runtime, you can "pre-create" a pool of (extern "C") functions at compile time and manage an allocation pool of them. Something like:

    #define REP10(P, M)  M(P##0) M(P##1) M(P##2) M(P##3) M(P##4) M(P##5) M(P##6) M(P##7) M(P##8) M(P##9)
    #define REP100(M) REP10(,M) REP10(1,M) REP10(2,M) REP10(3,M) REP10(4,M) REP10(5,M) REP10(6,M) REP10(7,M) REP10(8,M) REP10(9,M)
    extern struct func_wrap_t {
        func_wrap_t              *next;
        extern "C" void          (*c_fn)();
        std::function<void()>    fn;
    } func_wrap_table[];
    #define FUNC_WRAP_INIT(M) { M ? func_wrap_table+M-1 : 0, func_wrap_cfunc##M },
    #define FUNC_WRAP_DEF(M) extern "C" void func_wrap_cfunc##M() { func_wrap_table[M].fn(); }
    func_wrap_t func_wrap_table[] = { REP100(FUNC_WRAP_INIT) };
    func_wrap_t *func_wrap_freelist = &func_wrap_table[99];

    will create 100 such functions statically and link them up into a freelist. You can then write a wrapper RAII type that allocates them from this list and returns them when finished. As this is a POD type with a POD static declaration, you can safely use it in constructors and even multiple threads with an appropriate mutex.