Paul J. Lucas
2012-Mar-15 00:02 UTC
[LLVMdev] Using JIT code to code a program to call C++
My project has a C++ library that I want to allow the user to use via some programming language to be JIT'd to call functions in said library. For the sake of simplicity, assume the library has classes like: class item { public: item(); item( int ); ~item(); // ... }; class item_iterator { public: virtual ~item_iterator(); virtual bool next( item *result ) = 0; }; class singleton_iterator : public item_iterator { public: singleton_iterator( item const &i ); // ... }; I'm aware that LLVM doesn't know anything about C++ and that one way to call C++ functions is to wrap them in C thunks: extern "C" { void thunk_item_M_new( item *addr ) { new( addr ) item; } void thunk_singleton_iterator_M_new( singleton_iterator *addr, item *i ) { new( addr ) singleton_iterator( *i ); } bool thunk_iterator_M_next( item_iterator *that, item *result ) { return that->next( result ); } } // extern "C" The first problem is how to allocate an item from LLVM. I know how to create StructTypes and add fields to them, but I don't have to have to parallel the C++ class layout -- that's tedious and error-prone. The idea I got was simply to add a char[sizeof(T)] as the only field to a StructType for a C++ class type: StructType *const llvm_item_type = StructType::create( llvm_ctx, "item" ); vector<Type*> llvm_struct_types; llvm_struct_types.push_back( ArrayType::get( IntegerType::get( llvm_ctx, 8 ), sizeof( item ) ) ); llvm_item_type->setBody( llvm_struct_types, false ); PointerType *const llvm_item_ptr_type = PointerType::getUnqual( llvm_item_type ); I would think that, because it's a StructType, the alignment would be correct and the sizeof(item) would get the size right. Will that work? Is there a better way? The second problem is that, unlike the C++ class hierarchy, there's no inheritance relationship between StructTypes. If I create a Function that takes an llvm_iterator_type but try to build a Function object using an llvm_singleton_iterator_type, the LLVM verifyModule() function complains at me:> Call parameter type does not match function signature!So then I thought I'd simply use void* everywhere: Type *const llvm_void_type = Type::getVoidTy( llvm_ctx ); PointerType *const llvm_void_ptr_type = PointerType::getUnqual( llvm_void_type ); but verifyModule() still complains at me because, apparently, there's no automatic casting to void* types in LLVM. How can I solve this problem? - Paul
Óscar Fuentes
2012-Mar-15 00:51 UTC
[LLVMdev] Using JIT code to code a program to call C++
"Paul J. Lucas" <paul at lucasmail.org> writes: [snip]> I'm aware that LLVM doesn't know anything about C++ and that one way > to call C++ functions is to wrap them in C thunks:Yes, this is the only sane way unless you are willing to replicate the ABI of your chosen C++ implementation onto your system. Let's forget about dealing with more than one C++ ABI ;-) [snip]> I would think that, because it's a StructType, the alignment would be > correct and the sizeof(item) would get the size right. Will that > work?Yes. I do the same on my compiler. Please note that this trick is only necessary if you pretend to put C++ objects on your stack. If all C++ objects your JITted code creates are on the heap, you can simply use `new' instead of placement `new' in the C function that creates the C++ object and deal with the returned pointer..> Is there a better way?It all depends on the charateristics of your language. [snip]> but verifyModule() still complains at me because, apparently, there's > no automatic casting to void* types in LLVM. How can I solve this > problem?You must do the cast yourself, or simply use void* for all pointers for all your C++ objects and deal with its real type on the upper part of the compiler. As said above, it all depends on the details of your language.
Paul J. Lucas
2012-Mar-21 02:38 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
To recap, on Mar 14, 2012, I wrote:> My project has a C++ library that I want to allow the user to use via some programming language to be JIT'd to call functions in said library. For the sake of simplicity, assume the library has classes like: > > class item_iterator { > public: > virtual ~item_iterator(); > virtual bool next( item *result ) = 0; > }; > > I'm aware that LLVM doesn't know anything about C++ and that one way to call C++ functions is to wrap them in C thunks: > > extern "C" bool thunk_iterator_M_next( void *v_that, void *v_result ) { > item_iterator *const that = static_cast<item_iterator*>( v_that ); > item *const result = static_cast<item*>( v_result ); > return that->next( result ); > } > > extern "C" void thunk_iterator_M_delete( void *v_that ) { > item_iterator *const that = static_cast<item_iterator*>( v_that ); > that->~item_iterator(); > }Thanks to a previous answer, I now have everything working. My next problem is to deal with exceptions that may be thrown from the C++ functions that are called via the thunks, e.g., what if that->next() throws an exception? I need to be able to catch it, call a clean-up function, and rethrow the exception so the code calling the JIT'd code can deal with the exception. I've read the docs on LLVM exceptions, but I don't see any examples. A little help? One thought might be to try to handle all the C++ exception code in the thunks. The JIT'd code would create/maintain a simple array-of-structs like: struct dtor_pair { void (*dtor_fn)(void*); void *that; }; dtor_pair dtor_pairs[10]; that contain a pointer to the thunk for a destructor and a pointer to the object to be destructed. As the JIT'd code creates objects on the stack, it populates the dtor_pairs array. This array could then be passed to every thunk: extern "C" bool thunk_iterator_M_next( void *v_that, void *v_result, dtor_pairs *dtors ) { try { item_iterator *const that = static_cast<item_iterator*>( v_that ); item *const result = static_cast<item*>( v_result ); return that->next( result ); } catch ( ... ) { run_dtors( dtors ); throw; } } where run_dtors() would run through the array backwards calling the destructor functions in reverse order of construction. Would this work? If so, then I wouldn't have to mess with handing C++ exceptions from LLVM. But is there a better "LLVM way" to do what I want? - Paul
Bill Wendling
2012-Mar-22 07:28 UTC
[LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
On Mar 20, 2012, at 7:38 PM, Paul J. Lucas wrote:> To recap, on Mar 14, 2012, I wrote: > >> My project has a C++ library that I want to allow the user to use via some programming language to be JIT'd to call functions in said library. For the sake of simplicity, assume the library has classes like: >> >> class item_iterator { >> public: >> virtual ~item_iterator(); >> virtual bool next( item *result ) = 0; >> }; >> >> I'm aware that LLVM doesn't know anything about C++ and that one way to call C++ functions is to wrap them in C thunks: >> >> extern "C" bool thunk_iterator_M_next( void *v_that, void *v_result ) { >> item_iterator *const that = static_cast<item_iterator*>( v_that ); >> item *const result = static_cast<item*>( v_result ); >> return that->next( result ); >> } >> >> extern "C" void thunk_iterator_M_delete( void *v_that ) { >> item_iterator *const that = static_cast<item_iterator*>( v_that ); >> that->~item_iterator(); >> } > > Thanks to a previous answer, I now have everything working. My next problem is to deal with exceptions that may be thrown from the C++ functions that are called via the thunks, e.g., what if that->next() throws an exception? I need to be able to catch it, call a clean-up function, and rethrow the exception so the code calling the JIT'd code can deal with the exception. > > I've read the docs on LLVM exceptions, but I don't see any examples. A little help? >I don't think this has anything to do with LLVM's IR-level exception system. It sounds to me like you just need a way to handle C++ exceptions inside of the C++ code and then rethrow so that the JIT's caller can do its thing. (Right?) You could move the C++ code into a C++ function that catches all exceptions. The C functions you provide would call the small bit of C++ code that would then execute the "real" functionality. You would have to wrap/unwrap the variables, of course. (There are examples of wrapping/unwrapping of variables in LLVM's source tree.) That way you will get to use C++'s exception handling system instead of creating your own, which is a huge massive undertaking full of pitfalls. When you rethrow the exception, it will propagate past the C function to the code calling the JIT'ed code.> One thought might be to try to handle all the C++ exception code in the thunks. The JIT'd code would create/maintain a simple array-of-structs like: > > struct dtor_pair { > void (*dtor_fn)(void*); > void *that; > }; > dtor_pair dtor_pairs[10]; > > that contain a pointer to the thunk for a destructor and a pointer to the object to be destructed. > > As the JIT'd code creates objects on the stack, it populates the dtor_pairs array. This array could then be passed to every thunk: > > extern "C" bool thunk_iterator_M_next( void *v_that, void *v_result, > dtor_pairs *dtors ) { > try { > item_iterator *const that = static_cast<item_iterator*>( v_that ); > item *const result = static_cast<item*>( v_result ); > return that->next( result ); > } > catch ( ... ) { > run_dtors( dtors ); > throw; > } > } > > where run_dtors() would run through the array backwards calling the destructor functions in reverse order of construction. > > Would this work? If so, then I wouldn't have to mess with handing C++ exceptions from LLVM. But is there a better "LLVM way" to do what I want? >What you're doing is recreating what the personality function and DWARF unwinding library do. -bw
Possibly Parallel Threads
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing
- [LLVMdev] Catching C++ exceptions, cleaning up, rethrowing