Isbits object lifetimes on 0.7

I’ve been chasing down a segfault in SCS.jl on 0.7 (0.7.0-DEV.2725), and I believe I’ve narrowed it down to the following issue:

# Not standalone code
struct A
val::Float64
... # lots of plain data fields (isbits(A) == true)
end

struct B
a::Ptr{A}
end

function foo_()
    a = Ref(A(10.0, #= extra fields... =#))
    b = Ref(B(Base.unsafe_convert(Ptr{A}, a)))

    # foo modifies b and a
    ccall(:foo, Void, (Ptr{B},), b)
    # the value of unsafe_load(b[].a).val is corrupted when foo first reads it ...
    # but not if the following line in uncommented
    # @show a
end
foo_()

I’m inferring from this that the compiler is seeing that a is not needed after the construction of b, and hence it aggressively “frees” the memory that backs a even within the scope of foo_. Is there a proper way to extend the lifetime of a past the ccall?

Edit: related discussion at Help with ccall to a clib function

I am kinda surprised that this compiles at all. I always thought that taking addresses of stack-objects (local variables) was forbidden/undefined in julia.

A very simple workaround is to create a boxed A and B, e.g. by putting them into an array. This costs an allocation, so you should store them someplace for reuse if you plan to call foo_() often.

A more explicit workaround would be to @gc_preserve a until the ccall is finished. But until someone more knowledgeable chimes in to tell us that this is allowed, I would not trust taking addresses of any stack objects…

@gc_preserve seems to be used in similar ccall situations within Base, and it fixes the segfaults, thanks! I’m surprised it’s not mentioned in the “Calling C and Fortran code” documentation (unless there’s a more official workaround).

My understanding was that using Ref should be equivalent to but more efficient than creating a 1-element array.

IIUC the problem is the Base.unsafe_convert(Ptr{A}, a) without the @gc_preserve since the GC “loses” track of the Ptr. Still the definition of B is problematic since I don’t think you can use @gc_preserve in situations like

function f()
    a = Ref(A(10.0, #= extra fields... =#))
    b = Ref(B(Base.unsafe_convert(Ptr{A}, a)))
    return b
end

The GC is now free to get rid of a

Yes, but that’s not the situation here. The fix is now in action at https://github.com/JuliaOpt/SCS.jl/blob/f1e41834ef8488c5d26dee16e71b39626f0b84f5/src/c_wrapper.jl#L66 for anyone who wants to see a more complex example.