Repository navigation
refactor: module and execution engine - #724
Conversation
ErisianArchitect
left a comment
There was a problem hiding this comment.
I haven't finished the review because I'm on my phone, but I left a couple comments.
| /// A data layout string, as attached to a module or described by a `TargetData`. | ||
| #[derive(Eq)] | ||
| pub struct DataLayout { | ||
| pub(crate) data_layout: LLVMStringOrRaw, |
There was a problem hiding this comment.
I'm not sure why LLVMStringOrRaw was replaced here, could you explain that?
There was a problem hiding this comment.
LLVMStringOrRaw::Borrowed only existed so that Module::get_data_layout could return a Ref<DataLayout>. The module cached a DataLayout wrapping the raw pointer from LLVMGetDataLayoutStr. this is a pointer into the module's own std::string. LLVM replaces that string every time it calls Module::setDataLayout, but the cache was only refreshed by inkwell's set_data_layout.
when llvm changes datalayout internally the borrowed datalayout is stale or freed memory, even while a Ref is held, example:
let m = context.create_module("m");
m.set_data_layout(&TargetData::create("e-i64:64-n8:16:32:64-S128").get_data_layout());
let dl = m.get_data_layout();
target_machine.write_to_memory_buffer(&m, FileType::Object).unwrap();
println!("{:?}", dl.as_str()); // invalidyou can try doublecheck with something like valgrind
so I made it owned. I think it requires a a better rewrite but it's too invasive for this pr and out of scope
| target_data: Option<TargetData>, | ||
| /// The modules the engine currently owns. `LLVMRemoveModule` does not check ownership, so | ||
| /// this is what `remove_module` validates an `EngineModule` against. | ||
| modules: RefCell<Vec<NonNull<LLVMModule>>>, |
There was a problem hiding this comment.
I have doubts that this is even necessary. Is this being done to keep the modules alive?
There was a problem hiding this comment.
the llvm C remove module function is bugged:
LLVMBool LLVMRemoveModule(LLVMExecutionEngineRef EE, LLVMModuleRef M,
LLVMModuleRef *OutMod, char **OutError) {
Module *Mod = unwrap(M);
unwrap(EE)->removeModule(Mod);
*OutMod = wrap(Mod);
return 0;
}always return 0;, it discards the the return value of unwrap(EE)->removeModule(Mod); so it's not possible to know if that module was inside the ee
| } | ||
|
|
||
| /// Records a module LLVM has taken ownership of and returns its handle. | ||
| pub(crate) fn track_module(&self, module: LLVMModuleRef) -> EngineModule<'ctx> { |
There was a problem hiding this comment.
Shouldn't the lifetime of the EngineModule be the module, not the context?
There was a problem hiding this comment.
This an internal helper to avoid breaking too much in this PR. The real reason is on a limitation I found on the public api of Module::create_execution_engine.
In a ideal world EngineModule should have the lifetime EngineModule<'ee> so tied to the ExecutionEngine that has ownership of the module you added to it.
The problem is that in the public function
pub fn create_execution_engine(self) -> Result<(ExecutionEngine<'ctx>, EngineModule<'ctx>), LLVMString>rust doesn't allow you to return in the tuple an ExecutionEngine<'ctx> and an EngineModule<'ee> where the EngineModule have the lifetime of the ExecutionEngine part of the same tuple.
So I had this compromise: EngineModule can only be consumed by an ExecutionEngine to eventually return again a Module after removal from the ee if the module is really owned by that ee. If you drop the ExecutionEngine before the EngineModule, the EngineModule is inert even if it has the lifetime of the context. It can't be consumed (and turned back into a Module) anymore because of the check in ExecutionEngine::remove_module.
I am open to other proposals
There was a problem hiding this comment.
Sorry, yes, I meant the ee lifetime not module
There was a problem hiding this comment.
rust doesn't allow you to return in the tuple an ExecutionEngine<'ctx> and an EngineModule<'ee>
Are you sure? I feel like this should be possible
There was a problem hiding this comment.
Oh I see - because it's returned side by side
There was a problem hiding this comment.
but couldnt track_module / add_module still return with 'ee?
There was a problem hiding this comment.
yes but I don't like either having different lifetime between the first and the following added modules.
we could remove the first engine module from the tuple and add something like:
impl ExecutionEngine {
pub fn initial_module(&self) -> Option<EngineModule<'_>>;
}in this way the lifetime is correct.
in the end remove_module is not a commonly used api and having some frictions is probably fine.
while basically everyone will discards the engine module from the tuple I added. this will become the standard:
let (ee, _) = module.create_jit_execution_engine(OptimizationLevel::None)?;c36796e to
db60557
Compare
|
major changes since last time:
|
db60557 to
e7a479c
Compare
|
I discoved 2 other issues already present on main branch:
tell me how do you prefer to tackle these, here or in a next pr after this? asking because this pr already has changes to the api. implementing on main first doubles the work. plus the second is impossible to completely solve on the current main, the only fix possible is to limit |
|
Separate PR please, almost finished reviewing this PR |
| #[repr(transparent)] | ||
| pub struct DataLayout { | ||
| pub(crate) data_layout: LLVMStringOrRaw, | ||
| data_layout: NonNull<LLVMOpaqueTargetData>, |
There was a problem hiding this comment.
It'd probably be simpler to just have only DataLayout<'> type, where 'static = owned, 'static: 'a for borrowed, then drop is only implemented for 'static
There was a problem hiding this comment.
it's not possible to specialize with lifetime, rust doesn't allow that: Drop impls cannot be specialized
other libraries do the same, serde for example has Deserialize<'de> and DeserializeOwned
There was a problem hiding this comment.
Shoot, that's annoying
One of the reasons I've been experimenting with switching to Zig. Hence why I haven't been working on Orc2 lately. I've been increasingly frustrated with Rust in the last few months.
There was a problem hiding this comment.
Zig is really cool but I find the lack of safety guarantees less compelling
Reversed the relationship between module and ee.
Made the module independent from ee.
ee owns the modules added to it, giving back a handle to eventually remove them.
Description
owned_by_eefrom a module.Related Issue
Closes #723
How This Has Been Tested
llvm 22 on my pc
Option<Breaking Changes>
selfinstead of&selfResult<(ExecutionEngine<'ctx>, EngineModule<'ctx>), LLVMString>instead ofResult<ExecutionEngine<'ctx>, LLVMString>ExecutionEngine::add_module(&self, &Module) -> Result<(), ()>becomesadd_module(&self, Module<'ctx>) -> EngineModule<'ctx>returing the handle to an engine moduleExecutionEngine::remove_module(&self, &Module) -> Result<(), RemoveModuleError>becomesremove_module(&self, EngineModule<'ctx>) -> Result<Module<'ctx>, RemoveModuleError>consuming the handle and returning the moduleJitFunction<'ctx, F>becomesJitFunction<'ee, F>borrowing the engine instead of holding an Rc to itChecklist