inkwell icon indicating copy to clipboard operation
inkwell copied to clipboard

Segmentation Fault when printing `FunctionValue` which lives longer than `Module`

Open ytoml opened this issue 3 years ago • 9 comments

Describe the Bug When trying to print the content of FuncitonValue that originates in already dropped Module, it leads to segmentation fault.

To Reproduce

use inkwell::context::Context;

fn main() {
    let ctx = Context::create();

    // fn_value lives longer than module.
    let fn_value = 
    {
        let module = ctx.create_module("test");
        let fn_type = ctx.f64_type().fn_type(&[], false);
        let fn_value = module.add_function("test_fn", fn_type, None);

        // This is OK, it prints "define double @test_fn()\n"
        println!("{fn_value}");
        fn_value
    };

    // Segmentation fault
    println!("{fn_value}");
}

Expected Behavior Expected to print "define double @test_fn()\n" again. (or print nothing because the module already dropped? Actually I'm not confident because I'm LLVM newbie...)

LLVM Version (please complete the following information):

  • LLVM Version: Homebrew LLVM 14.0.6
  • Inkwell Branch Used: [e.g. llvm14-0]

Desktop (please complete the following information):

  • OS: macOS 12.5 (with Apple M1, 2020 macbook pro)

Additional Context Nothing special.

ytoml avatar Aug 21 '22 15:08 ytoml

I met this bug when following Kareidoscope tutorial. In my first implementation, I wrapped compiling procedure like:

fn compile<'ctx>(
    ctx: &'ctx Context,
    mod_name: &str,
    function: &Function,
) -> compiler::Result<FunctionValue<'ctx>> {
    let builder = ctx.create_builder();
    let module = ctx.create_module(mod_name);
    let fpm = PassManager::create(&module);
    fpm.add_instruction_combining_pass();
    fpm.add_reassociate_pass();
    fpm.add_gvn_pass();
    fpm.add_cfg_simplification_pass();
    fpm.initialize();

    Compiler::default()
        .builder(&builder)
        .context(ctx)
        .module(&module)
        .function_pass_manager(&fpm)
        .compile(function)
}

and the Module instance drops this after this function but FunctionValue lives. Then, printing the returned FunctionValue led to segmentation fault.

With lldb, I found it triggers EXC_BAD_ACCESS(code=1, address=0x17), but I couldn't figure out further details. It seems to touch freed memory address after dropping Module.

I feel like it would be better to force FunctionValue not to outlive its originating Module's lifetime, just make panicking when such usage detected, or confirm freeings are triggered after all related values are dropped (via Rc?).

ytoml avatar Aug 21 '22 15:08 ytoml

Good catch, the fn value lifetime should probably be tied to the module not the context

TheDan64 avatar Aug 21 '22 21:08 TheDan64

I can't seem to reproduce the segfault in LLVM10, I wonder if something changed in LLVM14? 🤔

TheDan64 avatar Aug 24 '22 03:08 TheDan64

Prints out:

"declare double @test_fn()\n"
"declare double <badref>() addrspace(0)\n"

TheDan64 avatar Aug 24 '22 03:08 TheDan64

I see. It seems to be hard to find out what exactly occurs and I feel like it would be better to make such difference invisible from users by introducing additional lifetimes...

ytoml avatar Aug 24 '22 05:08 ytoml

I suspect that GlobalValues might have this same issue. Would you be able to confirm for me?

TheDan64 avatar Aug 26 '22 19:08 TheDan64

Yes, exactly.

let ctx = Context::create();

// gvalue lives longer than module.
let gvalue = 
{
    let module = ctx.create_module("test");
    let gvalue = module.add_global(ctx.i32_type(), Some(AddressSpace::Const), "global");

    // This is OK, it prints ""@global = external addrspace(4) global i32"
    println!("{gvalue}");
    gvalue
};

// Segmentation fault
println!("{gvalue}");

ytoml avatar Aug 27 '22 03:08 ytoml

Okay, even though I wasn't able to reproduce the issue in gdb, I think it was maybe just luck? Like maybe the memory was never overridden on my system. I think I might have repro'd in valgrind:

==27295== 1 errors in context 43 of 43:
==27295== Invalid read of size 1
==27295==    at 0x119B865: llvm::Value::print(llvm::raw_ostream&, bool) const (in /home/dkolsoi/repos/inkwell/target/release/deps/all-99ce1af85ee5a8d2)
==27295==    by 0x11E763C: LLVMPrintValueToString (in /home/dkolsoi/repos/inkwell/target/release/deps/all-99ce1af85ee5a8d2)
==27295==    by 0x42E3B0: <inkwell::values::fn_value::FunctionValue as core::fmt::Display>::fmt (in /home/dkolsoi/repos/inkwell/target/release/deps/all-99ce1af85ee5a8d2)

TheDan64 avatar Aug 27 '22 15:08 TheDan64

See comment here as well: https://github.com/TheDan64/inkwell/issues/347#issuecomment-1249920735 will probably hold off until 0.2.0

TheDan64 avatar Sep 17 '22 21:09 TheDan64