Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I copy a OCaml closure?

Tags:

c

closures

ocaml

I want to store a OCaml closure for later use by an external C-library. I am able to do the following:

<TARGET> = caml_alloc(Wosize_val(<SOURCE>), Tag_val(<SOURCE>));
caml_register_global_root(<TARGET>);
Code_val(<TARGET>) = Code_val(<SOURCE>);

But as the name "closure" suggests, it does not suffice to copy just the code location.

How do I make a (garbage collector friendly) copy of <SOURCE>?

like image 387
choeger Avatar asked Oct 01 '22 23:10

choeger


1 Answers

In our work on using OCaml in iOS, we often need to save OCaml closures and call them later (when interfacing to CocoaTouch libraries). So I have code that has been working for years. However, it's too complicated to make a good example (and it's written in Objective C). Here's some code I just wrote that captures the essence of what we're doing.

First some C code that saves some number of closures of type unit -> unit and lets you call them later by chronological index. (It's just an example.)

$ cat saveclo.c
#include "caml/mlvalues.h"
#include "caml/memory.h"
#include "caml/callback.h"

static value saved_closures[10];
static int saved_closure_count = 0;


value save_closure(value clo)
{
    CAMLparam1(clo);
    saved_closures[saved_closure_count] = clo;
    caml_register_global_root(&saved_closures[saved_closure_count]);
    saved_closure_count++;
    CAMLreturn(Val_unit);
}


value call_closure(value index)
{
    CAMLparam1(index);
    int ix = Int_val(index);
    // For simplicity assume closure : unit -> unit
    (void) caml_callback(saved_closures[ix], Val_unit);
    CAMLreturn(Val_unit);
}

Then some OCaml code that exercises these functions:

$ cat clo.ml
external save_closure : (unit -> unit) -> unit = "save_closure"
external call_closure : int -> unit = "call_closure"

let save alist =
    let howlong () =
        Printf.printf "list length %d\n" (List.length alist)
    in
    save_closure howlong

let call () =
    call_closure 1;
    call_closure 0

let () =
    save [1;2;3;4;5];
    save ['a'; 'b'; 'c'; 'd'; 'e'; 'f'];
    Gc.full_major();
    call ()

A test run looks like this:

$ cc -I /usr/local/lib/ocaml -c -o saveclo.o saveclo.c
$ ocamlopt -c clo.ml
$ ocamlopt -o clo clo.cmx saveclo.o
$ ./clo
list length 6
list length 5
$ 

I think the salient points are (a) the OCaml object representing a closure already contains what you need (a code reference of some kind, and the data). (b) You don't need to copy it, you just need to make sure it doesn't get garbage collected. (c) The call to caml_register_global_root creates a reference to the closure so the GC knows not to collect it.

I hope this is helpful. If anybody sees problems with this code, let me know and I'll be more than happy to correct errors. But I believe it's correct.

like image 77
Jeffrey Scofield Avatar answered Oct 13 '22 12:10

Jeffrey Scofield