CAMLlocal for caml_callbackN arg array required?

I’m using caml_callbackN and need to create a value array to pass arguments. Looking trough OCaml compiler code I see that this array is not declared as GC roots but I am not sure about this would and would like to ask for advice. At least one value from that array is later returned, which is not the case for the test cases below and which might have been written with this knowledge in the background.

CAMLprim value
caml_polly_wait_fold(value val_epfd, value val_max, value val_timeout,
		     value val_init, value val_f)
{
	CAMLparam5(val_epfd, val_max, val_timeout, val_init, val_f);
	value args[4];

	struct epoll_event *events;
	int ready, i;

	if (Int_val(val_max) <= 0)
		uerror(__FUNCTION__, Nothing);
	events =
	    (struct epoll_event *)alloca(Int_val(val_max) *
					 sizeof(struct epoll_event));

	caml_enter_blocking_section();
	ready = epoll_wait(Int_val(val_epfd), events, Int_val(val_max),
			   Int_val(val_timeout));
	caml_leave_blocking_section();

	if (ready == -1)
		uerror(__FUNCTION__, Nothing);

	args[0] = val_epfd;
	args[3] = val_init;
	for (i = 0; i < ready; i++) {
		args[1] = Val_int(events[i].data.fd);
		args[2] = Val_int(events[i].events);
		args[3] = caml_callbackN(val_f, 4, args);
	}

	CAMLreturn(args[3]);
}

My understanding is that this is correct. Arguments to caml_callback* do not need to be declared as roots (either for the small argument count variants or the N variant). If a poll point occurs in the body of the callback, the argument will be recorded as root as needed, in the same manner as every other local variable.

Cheers,
Nicolas

1 Like

So I do not have to worry about args[3] being moved by the GC between it being returned by caml_callbackN and it being returned in CAMLreturn(args[3]) or used in another iteration of the loop? Because this is where I suspected things could go wrong. Is this still true in OCaml 5?

That’s right. In C code, a value can only be moved when allocating, when releasing the runtime lock or by calling into the GC in some other way. Since this cannot happen in those parts of the code, the value cannot be moved.

The same reasoning holds in OCaml 5.

Cheers,
Nicolas

3 Likes

Please correct me if I’m wrong, but the code pasted above has a for loop that calls caml_callbackN. Each of these calls can trigger a GC, so all values held in C memory around these calls (like the args array) need to be registered with the GC. I don’t know what is the correct way to register an array of values tough; using an OCaml array should work but then you have to use the proper macros instead of just array indexing.
Alternaltively, you can register four local variables and re-initialize the array from these variables before each call, but it’s less elegant.

args[1] and args[2] are reloaded on each iteration of the loop (and are also immediates) so they do not need to be registered, and args[3] is redefined to be the result of the callback, so it is still OK. I assumed that val_epfd was also an immediate, but if it isn’t then indeed args[0] needs to be registered as root or reloaded in each iteration of the loop.

Cheers,
Nicolas

@edwin pointed out to me that the documentation has been updated to explicitly not declaring the args as GC roots:

The array \var{args} must \emph{not} be declared with “CAMLlocalN”.
It should be declared as “value “\var{args}”[”\var{n}“];”.
Alternatively, a C99 compound literal can be used:
“caml_callbackN(”\var{f, n, }“(value){”\nth{arg}{1}, \ldots, \nth{arg}{n}“})”.