Julia crashes when using a @cfunction from another module

I’m trying to define some Julia callbacks to give to a C library, but hit some strange crashes when those functions are invoked. In an attempt to pin down the problem I have created a very simple project which still crashes Julia.

This happens in both 1.7.2 and 1.8.5.

The buggy module is defined like this:

module bug_ccall
    @noinline get_value(i::Int64)::Int64 = i * 27
    const GET_VALUE = @cfunction(get_value, Int64, (Int64, ))

    pretend_c_call(i) = ccall(GET_VALUE, Int64, (Int64, ), Int64(i))
    println("While compiling module: ", pretend_c_call(2))
end # module

When I compile this module, I get the expected printout:

While compiling module: 54

However, when I invoke the ccall from outside the module, I get a crash:

PS C:\Users\manni\Documents\tep\bug_ccall> julia --project=. -e 'using bug_ccall; println(:outside_the_module_, bug_ccall.pretend_c_call(4))'
While compiling module: 54

Please submit a bug report with steps to reproduce this fault, and any error messages that follow (in their entirety). Thanks.
Exception: EXCEPTION_ACCESS_VIOLATION at 0x5ece0ac0 -- unknown function (ip: 000000005ece0ac0)
in expression starting at none:1
unknown function (ip: 000000005ece0ac0)
Allocations: 2723 (Pool: 2712; Big: 11); GC: 0

Am I misunderstanding something? What is the correct way to use a @cfunction from another module?

I don’t have the picture fully clear but it seems possible that your module gets precompiled and stores a pointer to your @cfunction, which is stale when you load the module the next time. If this is the case the solution is to instead create the pointer in the module’s __init__ function. See Modules · The Julia Language and the docstring for __init__.

2 Likes

Thank you, this did it! Here is the fixed code, that doesn’t crash:

module bug_ccall
    @noinline get_value(i::Int64)::Int64 = i * 27

    const GET_VALUE = Ref{Ptr{Cvoid}}()
    function __init__()
        GET_VALUE[] = @cfunction(get_value, Int64, (Int64, ))
    end

    make_c_call(i) = ccall(GET_VALUE[], Int64, (Int64, ), Int64(i))
end # module

...

> julia --project=. -e 'using bug_ccall; println(bug_ccall.make_c_call(5))'
135

The call during compilation crashes, but that makes sense because __init__() hasn’t been called yet.

Now I just have to port this change into my real project.