-
Notifications
You must be signed in to change notification settings - Fork 68
C API changes
Note: This page is preserved for historical reasons. We no longer require changes to the C API with the "parallel minor collector" that is the default in multicore OCaml now. See this paper for more details about the experiments that lead to this decision. The below text applies only to the old concurrent minor collector that is no longer being actively upstreamed.
While we aim to preserve backwards-compatibility with OCaml code, the multicore OCaml implementation breaks some of the API used to interface C and OCaml code. For background on this API, see Chapter 19 of the OCaml Manual.
In order to port C bindings to use the multicore API, these replacements need to be made:
Stock OCaml | Multicore OCaml |
---|---|
a = Field(x, i); |
a = Field_imm(x, i); (if known immutable), or caml_read_field(x, i, &a); (otherwise) |
caml_modify(&Field(x, i), a); |
caml_initialize_field(x, i, a); (if initializing)caml_modify_field(x, i, a); (if modifying) |
caml_initialize(&Field(x, i), a); |
caml_initialize_field(x, i, a); |
Field(x, i) = a; |
caml_initialize_field(x, i, a); |
NB: Unlike stock OCaml's caml_initialize
, multicore's caml_initialize_field
may call the GC, and so all values must be protected (with CAMLparamN
or similar) before using it.
Use of the following convenience functions is not strictly necessary, but they will perform slightly better than the alternative in cases where they apply:
Stock OCaml | Multicore OCaml |
---|---|
n = Int_val(Field(x, i)); |
n = Int_field(x, i); (whether mutable or not) |
n = Long_val(Field(x, i)); |
n = Long_field(x, i); (whether mutable or not) |
pair = caml_alloc_small(2, 0); Field(pair, 0) = a; Field(pair, 1) = b;
|
pair = caml_alloc_2(0, a, b); |
The convenience functions caml_alloc_<N>
are defined for N
< 10.
The major change to the C API is the removal of Field(x, i)
.
In stock OCaml, local roots are exposed to the GC (using CAMLlocalN
and similar), so that if a minor collection occurs during a call to
e.g. caml_alloc
, all pointers which need to be moved are visible to
the GC. In multicore OCaml, the roots must also be exposed to the GC when
reading a mutable field, since the read barrier may need to promote
objects.
This presents a problem for the Field(x, i)
API, since code like this
is currently valid:
foo(Field(x, 0), Field(x, 1));
If both fields are mutable, then the call to Field(x,1)
could require
that some values be promoted. If Field(x, 0)
has already been evaluated
into some temporary location, then that location will not be visible to
the GC, and will not be relocated should promotion require it.
So, in the multicore API, Field(x, i)
is split into two separate
operations Field_imm
and caml_read_field
, depending on whether the
field in question is immutable or mutable. For immutable fields, the
change is simply to replace Field
with Field_imm
:
foo(Field_imm(x, 0), Field_imm(x, 1));
For mutable fields, calls to caml_read_field
must be
inserted. Instead of returning a value, this function returns void
but takes a value*
into which the result is stored. This style of
API forces the user to declare a value
variable (using
CAMLlocalN
), ensuring that the temporary value is exposed to the GC:
CAMLlocal2(a,b);
caml_read_field(x, 0, &a);
caml_read_field(x, 1, &b);
foo(a, b);
If it is unclear in a given context whether a field is mutable,
caml_read_field
can always be used. Field_imm
is an optimised
version for fields known to be immutable.
As a convenience for fields known to contain integer values (to which promotion does not apply), the
function Int_field(x, i)
is provided, which can operate on mutable
fields as well as immutable, and is equivalent to
Int_val(Field(x,i))
in the stock OCaml API. Long_field
works similarly.
Field
is also used as an lvalue, when modifying fields:
caml_modify(&Field(x, i), a);
In the multicore API, this instead uses the new function caml_modify_field
:
caml_modify_field(x, i, a);
Similarly, caml_initialize
becomes caml_initialize_field
. Finally,
there is one case in which Field
is directly assigned to, which is
when initialising blocks allocated with the low-level
caml_alloc_small
function:
pair = caml_alloc_small(2, 0);
Field(pair, 0) = a;
Field(pair, 1) = b;
In the multicore API, such initialisations use caml_initialize_field
instead:
pair = caml_alloc_small(2, 0);
caml_initialize_field(pair, 0, a);
caml_initialize_field(pair, 1, a);
For initialising small fixed-size structures like pairs, this is marginally less efficient than in stock OCaml. However, efficient convenience functions are provided for this use, which are also more concise than the stock OCaml API:
pair = caml_alloc_2(0, a, b);
There are a smaller number of breaking changes in less-commonly-used parts of the API. The most important of these is changes to how global roots are defined, and how values registered for callbacks are accessed. These changes are still slightly in flux, but you can see the current versions in memory.h
and callback.h