New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
callback.c: unrooted implementations of caml_callback*_exn #12121
Conversation
runtime/callback.c
Outdated
CAMLexport value caml_callbackN_exn(value closure, int narg, value args[]) { | ||
switch (narg) { | ||
case 0: | ||
return closure; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: the previous implementation gracefully handles the case narg == 0
, so I kept this property in the new implementation. I suspect that it is not handled well in the bytecode implementation, so presumably there is an implicit precondition that narg > 0
. We could clarify this condition, or ensure that the bytecode version also works well with narg == 0
, I went with keeping the behavior unchanged in both versions.
154bb1c
to
1a3da96
Compare
Notes:
|
Some general comments:
|
"Callbacks whose argument (or closure) are not kept alive" is a bit of a mouthful compared to "unrooted callbacks". Any suggestion? "Non-leaking callbacks"? "Memory-preserving callbacks"? "Liveness-preserving callbacks"?
I will use your suggestion for |
For the terminology, I'll offer a rewrite of some of your comments later, when we've agreed about the functionality and the code. Come to think of it, my suggestion for
I agree that |
Another approach to callbackN would be to have a recursive function that:
This would give the property that each argument is registered at most once. But I don't see any performance benefit compared to the version I propose, my understanding is that registering |
1a3da96
to
32cefba
Compare
@xavierleroy I pushed a further commit to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reviewed the new functionality and they look correct to me.
runtime/callback.c
Outdated
closure = caml_callback3_exn(closure, args[0], args[1], args[2]); | ||
End_roots(); | ||
|
||
return caml_callbackN_exn(closure, remaining_narg, remaining_args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it is better to avoid recursion in C and retain the loop structure (perhaps a while(1) { }
loop). Do we introduce the risk of stack overflow by introducing recursion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a tail-recursive function, so I expect both gcc
and clang
to optimize it to take constant stack space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kayceesrk: I pushed a new commit which rewrites this function to use an explicit loop instead of a recursive function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes look correct to me. Thanks.
e8c996e
to
9814d3b
Compare
@xavierleroy this is good to merge as far as I'm concerned. When would you like to reword the comments? |
Suggested-by: Xavier Leroy <xavier.leroy@college-de-france.fr>
9814d3b
to
c1b152c
Compare
I wanted to send a PR over your branch with my proposed rewording, but my Github kung-fu is weak and I could not find how to do it. So I took the liberty to push directly to your branch. Feel free to ignore or reword again to your liking, it's just an example of what can be written without neologisms :-) |
@xavierleroy thanks! The new wording is allright with me, so I will go ahead and merge. In passing: GHC has a formalized system of "notes", see its coding style documentation, which are basically explanatory comments with an explicit name |
This is a follow-up to a discussion initiated by @kayceesrk in #12112 (comment) : when calling an OCaml callback from C (
caml_callback_exn(closure, arg)
), we don't want the callback closure and argument to be rooted during the OCaml-side computation, they should be unrooted so that they can be collected by the GC as soon as they are not needed in the computation anymore -- just like function calls written in OCaml itself, thanks to liveness analysis.The runtime code previously ensured this property (except for
caml_callbackN_exn
in native code, see below), which I call "unrooted callbacks", but it regressed in 20d107c : the Multicore logic adds an allocation before the callback itself gets called (for correctness reasons in presence of effects performed in the OCaml callback), and everything got rooted at this point for correctness.The present PR restores the property that callbacks are unrooted in both bytecode and native code, following the suggestion in the discussion #12112 (comment) .
The trickiest change of the PR is the implementation of
caml_callbackN_exn
in native code. Architecture-specific runtimes only implement calls of arity up to 3, and the N-ary version forN > 3
is implemented by repeated partial application. The liveness property that we should preserve is that each argument is rooted up to just before the corresponding partial application.This function would in fact not implement unrooted callbacks in its 4.x implementation, see https://github.com/ocaml/ocaml/blob/4.14.0/runtime/callback.c#L128-L157 . In this respect the present PR also improves on 4.x.