anza-xyz / move

Move compiler targeting llvm supported backends
https://discord.gg/wFgfjG9J
Apache License 2.0
107 stars 32 forks source link

Manage lifetimes of LLVM modules #379

Open brson opened 10 months ago

brson commented 10 months ago

From our GlobalContext we create a raw LLVM module (global_cx.llvm_cx.create_module), then pass a reference to that module into various other contexts. From comments in the code:

    // FIXME: this should be handled with lifetimes.
    // Context (global_cx) must outlive llvm module (entry_llmod).
    drop(entry_llmod);
    drop(global_cx);

This is true of all calls to create_module - the lifetime of the llvm module is manually ensured to be less than the GlobalContext.

The obvious solution is to have one of the existing other contexts own the llvm module, but this doesn't work because it would require self-referential lifetimes, e.g.:

pub struct EntrypointGenerator<'mm, 'up> {
    pub env: &'mm mm::GlobalEnv,
    pub llvm_cx: &'up llvm::Context,
    pub llvm_module: &'up llvm::Module,
    pub llvm_builder: llvm::Builder,
    pub options: &'up Options,
    pub rtty_cx: RttyContext<'mm, 'up>,

    fn_decls: RefCell<BTreeMap<String, llvm::Function>>,

    ll_fn_solana_entrypoint: llvm::Function,
    entry_slice_ptr: llvm::AnyValue,
    entry_slice_len: llvm::AnyValue,
    insn_data_ptr: llvm::AnyValue,
    offset: llvm::Alloca,
    retval: llvm::AnyValue,
    exit_bb: llvm::BasicBlock,

    entries: Cell<u64>,
    target_machine: &'up llvm::TargetMachine,
}

In the above it would nice to convert llvm_module from &'up Module to just Module. This can't be done though because the RttyContext contains a reference to llvm_module that must be named in EntrypointGenerator.

Without doing significant restructuring, the best way I can think of to ensure the LLVM module goes out of scope before GlobalContext is to introduce a new type responsible just for containing both the GlobalContext lifetime and the llvm Module:

struct LlvmContext<'g> {
  global_context: &'g GlobalContext, // or PhantomData<&'g GlobalContext>
  llmod: llvm::Module,
}

impl GlobalContext {
  fn create_llvm_module(&self) -> LlvmContext {
    ...
  }
}

LlvmContext exists just to tie the lifetime of the LLVM module and GlobalContext through a stack variable. It doesn't need to be passed anywhere else, just live on the stack. Then a reference to the LLVM module can continue to be passed everywhere it already is.

This only works if GlobalContext never needs accessed through a mutable reference, which appears to be the case, since this will hold an immutable reference to it.

cc https://github.com/solana-labs/move/pull/374/files#r1330870435