Calling JL_GC_PUSH1 multiple times

Hi guys,

I am creating a C++ software that calls multiple functions of my Julia package. Something like:

    jl_init();

    jl_eval_string("using SatelliteToolbox");
    jl_module_t* SatelliteToolbox =
        (jl_module_t*)jl_eval_string("SatelliteToolbox");

    jl_function_t *read_tle = jl_get_function(SatelliteToolbox, "read_tle");

    jl_value_t *filename  = jl_eval_string("\"amz1.tle\"");
    jl_array_t *array_tle = (jl_array_t*)jl_call1(read_tle, filename);

    jl_function_t* getindex = jl_get_function(jl_main_module, "getindex");
    jl_value_t* tle =
        (jl_value_t*)jl_call2(getindex, (jl_value_t*)array_tle, jl_box_int64(1));

    jl_function_t* init_orbit_propagator =
        jl_get_function(SatelliteToolbox, "init_orbit_propagator");

    jl_value_t* tipo_prop = jl_eval_string("Val{:J2}");
    jl_value_t* orb_prop  = jl_call2(init_orbit_propagator, tipo_prop, tle);

    jl_function_t* propagate = jl_get_function(SatelliteToolbox, "propagate!");

    jl_value_t* t   = jl_box_float64(0.0);
    jl_value_t* ret = jl_call2(propagate, orb_prop, t);
    jl_value_t* orb = jl_fieldref(ret,0);

By reading the documentation, I saw that I need to use JL_GC_PUSH1 to avoid, for example, array_tle to be freed by the GC. However, when I call JL_GC_PUSH1 twice in this function, I get the following error:

main.cpp:43:5: error: redefinition of '__gc_stkf'
    JL_GC_PUSH1(&tle);
    ^
/Applications/Julia-1.0.app/Contents/Resources/julia/include/julia/julia.h:667:9: note: expanded from macro 'JL_GC_PUSH1'
  void *__gc_stkf[] = {(void*)3, jl_pgcstack, arg1};                      \
        ^
main.cpp:35:5: note: previous definition is here
    JL_GC_PUSH1(&array_tle);
    ^
/Applications/Julia-1.0.app/Contents/Resources/julia/include/julia/julia.h:667:9: note: expanded from macro 'JL_GC_PUSH1'
  void *__gc_stkf[] = {(void*)3, jl_pgcstack, arg1};                      \
        ^
1 error generated.

So, can anyone tell me what is the best practice here?

Another problem is that I would like that the variable orb_prop, which will reside in a class, to never be GCed, since it will be used many times. How can I do that?

Well, I am going to describe what I did here, because I am finding very difficult to obtain clear information about the process of embedding Julia into C++ (when I get better, I will submit a PR to improve the documentation).

(Please, correct me if I am wrong!)

In fact, Julia will not GCed a variable if it is referenced somewhere. Since I want a variable inside a C++ class to not be freed, I need to make it global or add it reference to a global object. Since the second option seems better, I create a global IdDict at the initialization:

    jl_value_t* refs = jl_eval_string("refs = IdDict()");

Then, when my variable inside the class is created, I add the reference to this IdDict by:

    jl_value_t* refs = jl_eval_string("refs");
    jl_function_t* setindex = jl_get_function(jl_main_module, "setindex!");

    ...

    tle = (jl_value_t*)jl_call2(getindex, (jl_value_t*)array_tle, jl_box_int64(1));

    jl_call3(setindex, refs, tle, tle);

In this case, since tle is referenced in the global variable refs, it seems that the GC will not delete it.

Seems right?

The JL_GC_PUSH1 macro (and the other variations) use the stack. In the past, I’ve just introduced a new scoping block for every push/pop pair.

jl_value_t *a = jl_eval_string("foo()");
JL_GC_PUSH1(&a);
{
    jl_value_t *b = jl_eval_string("bar()");
    JL_GC_PUSH1(&b);
    // do something with a and b
    JL_GC_POP();
}
JL_GC_POP();

As for keeping a value alive beyond a given scope, I think you are on the right track. I’ve only ever had to do it for instances of a mutable struct, so it sufficed to just generate a new global symbol and bind it to my value. If you are dealing with immutables, I think you need to do something more involved, like wrap it in a Ref.

1 Like

If this approach (to add the variable ref to a global container), can I submit a PR with a doc update? I am not sure if this is described in the section High-Level Embedding of the manual.

1 Like

I had a similar problem and I went for the same approach, which appears to be working just fine. I think you are on the right track with this.

1 Like

I created a PR to make this clear:

https://github.com/JuliaLang/julia/pull/30399

Any suggestions are welcome!