Support with embedding julia in C++

We are embedding julia in C++. The first few calls to the function are completed successfully, but after a few times the following error comes up.

while loading no file, in expression starting on line 389
jl_subtype_le at /home/centos/buildbot/slave/package_tarball32/build/src/jltypes.c:2746
jl_f_isa at /home/centos/buildbot/slave/package_tarball32/build/src/builtins.c:396
isvarargtype at ./essentials.jl:63 [inlined]
Type at ./inference.jl:122
unknown function (ip: 0xf0c501f6)
jl_call_method_internal at /home/centos/buildbot/slave/package_tarball32/build/src/julia_internal.h:211 [inlined]
jl_apply_generic at /home/centos/buildbot/slave/package_tarball32/build/src/gf.c:1950
typeinf_edge at ./inference.jl:1577
jl_call_method_internal at /home/centos/buildbot/slave/package_tarball32/build/src/julia_internal.h:211 [inlined]
jl_apply_generic at /home/centos/buildbot/slave/package_tarball32/build/src/gf.c:1950
typeinf_ext at ./inference.jl:1621
jl_call_method_internal at /home/centos/buildbot/slave/package_tarball32/build/src/julia_internal.h:211 [inlined]
jl_apply_generic at /home/centos/buildbot/slave/package_tarball32/build/src/gf.c:1950
jl_apply at /home/centos/buildbot/slave/package_tarball32/build/src/julia.h:1388 [inlined]
jl_type_infer at /home/centos/buildbot/slave/package_tarball32/build/src/gf.c:213
jl_compile_for_dispatch at /home/centos/buildbot/slave/package_tarball32/build/src/gf.c:1298
jl_call_method_internal at /home/centos/buildbot/slave/package_tarball32/build/src/julia_internal.h:206 [inlined]
jl_apply_generic at /home/centos/buildbot/slave/package_tarball32/build/src/gf.c:1950
jl_apply at /home/centos/buildbot/slave/package_tarball32/build/src/julia.h:1388 [inlined]
jl_call3 at /home/centos/buildbot/slave/package_tarball32/build/src/jlapi.c:196
unknown function (ip: 0x8059bb6)
unknown function (ip: 0x805024f)
unknown function (ip: 0x805383e)
unknown function (ip: 0x805bd1c)
unknown function (ip: 0xf73dd37c)
start_thread at /build/eglibc-0jBZkF/eglibc-2.19/nptl/pthread_create.c:312
unknown function (ip: 0xf723b3ed)
Allocations: 75763006 (Pool: 75761422; Big: 1584); GC: 27
Segmentation fault (core dumped)

2 Likes

I think it would be helpful if you could show what function you are calling and how you are calling it.

1 Like

We are working with 32 bit Julia 0.5.2

Below is the structure of the C++ and Julia code.

// Code in c++

    jl_function_t *func1    = jl_get_function(jl_current_module, "func1");
    jl_function_t *func2    = jl_get_function(jl_current_module, "func2");

    jl_value_t* float_array_type    =  jl_apply_array_type(jl_float32_type, 1);
    jl_value_t* int_array_type      =  jl_apply_array_type(jl_int32_type, 1);
    jl_value_t* char_array_type     =  jl_apply_array_type(jl_int8_type, 1);

    jl_array_t* arr1_jl   = jl_ptr_to_array_1d( float_array_type, arr1, max_depth+1, 0); // all arrays have size max_depth+1
    jl_array_t* arr2_jl   = jl_ptr_to_array_1d( float_array_type, arr2, max_depth+1, 0);

    jl_array_t* bool_arr1_jl     = jl_ptr_to_array_1d( int_array_type,   bool_arr1, max_depth+1, 0);
    jl_array_t* bool_arr2_jl     = jl_ptr_to_array_1d( int_array_type,   bool_arr2, max_depth+1, 0);

    symbol_arr_jl    = jl_ptr_to_array_1d( char_array_type, symbol, symbol_size, 0);

    jl_call3(func1, (jl_value_t*)symbol_arr_jl, (jl_value_t*)arr1_jl, (jl_value_t*)bool_arr1_jl);
    jl_call3(func2, (jl_value_t*)symbol_arr_jl, (jl_value_t*)arr2_jl, (jl_value_t*)bool_arr2_jl);

// Julia code
function func1( symbol, input_arr, bool_res_arr)
symbol_str = map(symbol, Char)
…
end

function func2( symbol, input_arr, bool_res_arr)
symbol_str = map(symbol, Char)
…
end

We are performing basic operations on input_arr and return the result by modifying bool_res_arr. The c++ code is executed multiple times, and behaves as expected (returns correct result in bool_res_arr) but after a random number of runs crashes with the error posted above. If we add calls to gc before this snippet, the chances of error reduce.

1 Like

Not sure if that’s the reason but you are missing all of the GC roots. All of the arrays need to be rooted.

1 Like

I am not familiar by the notion of GC roots and the embedding julia docs page does not mention rooting. As the arrays are owned and managed in c++, I have passes last argument in jl_ptr_to_array_1d as 0.

1 Like

https://docs.julialang.org/en/latest/manual/embedding/#Memory-Management-1

1 Like

Thank you, I had read the Memory management section but was unaware it was also referred to GC rooting. I had assumed that for c++ managed arrays I do not need to push( and pop appropriately) jl_value_t objects, but from your comments it appears that this is not the case. Also, say that the array types(or jl function pointers) are initialized once and used at multiple times in other sections of code, then should they also be pushed onto the GC stack.

1 Like

Leaftypes and constant globals don’t need to be rooted…

That’s basically the first/only thing it talks about? PRs to make this more clear is welcome.

2 Likes

This kind of thing is somewhat simpler if CxxWrap.jl (or at least the jlcxx C++ part) is an acceptable dependency:

jlcxx::JuliaFunction func1("func1");
float arr1_jl[] = {1.0, 2.0, 3.0};
func1((jl_value_t*)jlcxx::ArrayRef<float, 1>(&arr1_jl[0], 3).wrapped());

The ArrayRef interface could still use some polishing, though, including auto-conversion when using in a JuliaFunction.

2 Likes

Thanks, I was unaware CxxWrap could be used for calling julia functions from C++. My issue was resolved when I started pushing the jl_value_t objects and popping them immediately after the julia call. While this works I also tried initializing the jl_value_t objects once, pushing them onto the stack and then reusing them for every call to julia. This approach however fails and crashes spectacularly. Could you explain why this fails?

1 Like

From what I understand, the wrapper around the array is allocated and can be garbage collected. I believe the allocation happens here:
https://github.com/JuliaLang/julia/blob/release-0.6/src/array.c#L281

To prevent this, the recommended method is to store a global reference to the object in an array. CxxWrap provides the (un)protect_from_gc function for this, or you could fairly easily roll out your own version, here is the code for reference:
https://github.com/JuliaInterop/CxxWrap.jl/blob/master/deps/src/jlcxx/include/jlcxx/type_conversion.hpp#L49-L99

2 Likes

Although it discusses the underlying issue, that chapter of the manual doesn’t actually use the term “root” at all with regard to GC. It would probably be helpful if the manual mentioned this technical terminology somewhere, so that people can understand Julia devs when they use the word.

3 Likes

If anyone is reading this later, go to these to links:

Calling Julia functions from C++:

and
Embedding Julia
Here is the example on GitHub:
embedding example

and these threads:

3 Likes