TheDan64 / inkwell

It's a New Kind of Wrapper for Exposing LLVM (Safely)
https://thedan64.github.io/inkwell/
Apache License 2.0
2.24k stars 221 forks source link

ExecutionEngine is not thread-safe #242

Open programmerjake opened 3 years ago

programmerjake commented 3 years ago

Describe the Bug The ExecutionEngine class in LLVM is thread safe and intended to be used from multiple threads. The ExecutionEngine wrapper in inkwell is not -- it uses Rc<...> internally.

https://github.com/llvm/llvm-project/blob/6ce1067f2ddd8623b163175ee9488673b9ced8d2/llvm/include/llvm/ExecutionEngine/ExecutionEngine.h#L159

LLVM Version (please complete the following information):

TheDan64 commented 3 years ago

We don't currently support using Inkwell types across multiple threads as it would be a pretty big undertaking to get right. Thanks for pointing it out, though. https://github.com/TheDan64/inkwell/issues/28 is related

I'm not even sure this is possible to do safely in rust as the EE needs to have a lifetime associated with the parent Context that it is derived from, and you wouldn't be able to send that EE<'ctx> to another thread because of that lifetime, I think?

programmerjake commented 3 years ago

Hmm, maybe it could be done the other way around: Contexts would have the lifetime of the ExecutionEngine, since the ExecutionEngine handles multiple Contexts.

Alternative idea: the safe ExecutionEngine wrapper could hold on to Vec<Arc<ContextInsides>> for all the contexts it needs to keep live, and a Context would be more like struct Context(Option<ContextInsides>) where the ExecutionEngine would take out the insides and prevent using the context while LLVM could be using the Context from another thread internally.

BadBastion commented 3 months ago

I have vested interest in this usecase and would be more than happy to take a crack at solving it.

I believe ExecutionEngine doesn't directly depend on the lifetime of any specific Context, it is specifically the function pointers returned by execution engine that require both Context and ExecutionEngine to outlive the function pointer.

The most direct way to represent this in rust would be changing JitFunction to hold an Arc to both Context & ExecutionEngine objects.

Unfortunately I don't see any way this isn't a breaking change to the API. Even if it is just removing <'ctx> lifetimes.

TheDan64 commented 3 months ago

Feel free to take a crack!

Its worth mentioning though, that EEs can be used multi threaded today with scoped threads!

Just wanted to throw that out there for anyone looking for a more immediate solution

BadBastion commented 3 months ago

Created #487 for discussion.

For the best user experience JitFunction would be Send, Sync, and have no lifetime. Having an Arc for Execution Engine is pretty easy. Using an Arc for Context would require additional changes. This likely means also changing Context to be Arc<ContextImpl>, but explicitly marking it !Send since it's unsafe to call methods on context across threads. The Arc is just used for ownership.

Great, but this bring us to where the changes start to cascade:

Problem: How do we get the context inside the ExecutionEngine.get_function so we can clone the Arc?