Skip to content

Commit 0b20098

Browse files
ncik-robertsxclerc
andauthored
flambda-backend: Add documentation for mixed blocks (#2667)
* Add mixed block docs * Reword * Update ocaml/jane/doc/extensions/unboxed-types/index.md Co-authored-by: Xavier Clerc <xclerc@users.noreply.github.com> * Suggestions from review * Fix from @TheNumbat's review --------- Co-authored-by: Xavier Clerc <xclerc@users.noreply.github.com>
1 parent 91f1c2c commit 0b20098

File tree

1 file changed

+145
-1
lines changed
  • jane/doc/extensions/unboxed-types

1 file changed

+145
-1
lines changed

jane/doc/extensions/unboxed-types/index.md

+145-1
Original file line numberDiff line numberDiff line change
@@ -284,4 +284,148 @@ Here's the list of primitives that currently support `[@layout_poly]`:
284284
* `%array_safe_get`
285285
* `%array_safe_set`
286286
* `%array_unsafe_get`
287-
* `%array_unsafe_set`
287+
* `%array_unsafe_set`
288+
289+
# Using unboxed types in structures
290+
291+
Unboxed types can usually be put in structures, though there are some restrictions.
292+
293+
These structures may contain unboxed types, but have some restrictions on field
294+
orders:
295+
* Records
296+
* Constructors
297+
298+
Unboxed numbers can't be put in these structures:
299+
* Constructors with inline record fields
300+
* Exceptions
301+
* Extensible variant constructors
302+
* Top-level fields of modules
303+
* Tuples
304+
305+
There aren't fundamental issues with the structures that lack support. They will
306+
just take some work to implement.
307+
308+
Here's an example of a record with an unboxed field. We call such a record
309+
a "mixed record".
310+
311+
```ocaml
312+
type t =
313+
{ str : string;
314+
i : int;
315+
f : float#;
316+
}
317+
```
318+
319+
## Restrictions on field ordering
320+
321+
The below is written about record fields but equally applies to constructor
322+
arguments.
323+
324+
Suppose a record contains any unboxed field `fld` whose layout is not `value`[^or-combination-of-values]. Then, the following restriction applies: All
325+
fields occurring after `fld` in the record must be "flat", i.e. the GC can
326+
skip looking at them. The only options for flat fields are immediates (i.e. things
327+
represented as ints at runtime) and other unboxed numbers.
328+
329+
[^or-combination-of-values]: Technically, there are some non-value layouts that don't hit this restriction, like unboxed products and unboxed sums consisting only of values.
330+
331+
The following definition is rejected, as the boxed field `s : string` appears
332+
after the unboxed float field `f`:
333+
334+
```ocaml
335+
type t_rejected =
336+
{ f : float#;
337+
s : string;
338+
}
339+
(* Error: Expected all flat fields after non-value field, f,
340+
but found boxed field, s. *)
341+
```
342+
343+
The only relaxation of the above restriction is for records that consist
344+
solely of `float` and `float#` fields. Any ordering of `float` and `float#`
345+
fields is permitted. The "flat float record optimization" applies to any
346+
such record&mdash;all of the fields are stored flat, even the `float` ones
347+
that will require boxing upon projection. The ordering restriction is relaxed
348+
in this case to provide a better migration story for all-`float` records
349+
to which the flat float record optimization currently applies.
350+
351+
```ocaml
352+
type t_flat_float =
353+
{ x1 : float;
354+
x2 : float#;
355+
x3 : float;
356+
}
357+
```
358+
359+
The ordering restriction has to do with the "mixed block" runtime
360+
representation. Read on for more detail about that.
361+
362+
## Generic operations aren't supported
363+
364+
Some operations built in to the OCaml runtime aren't supported for structures
365+
containing unboxed types.
366+
367+
These operations aren't supported:
368+
* polymorphic comparison and equality
369+
* polymorphic hash
370+
* marshaling
371+
372+
These operations raise an exception at runtime, similar to how polymorphic
373+
comparison raises when called on a function.
374+
375+
You should use ppx-derived versions of these operations instead.
376+
377+
## Runtime representation: mixed blocks
378+
379+
As a general principle: The compiler should not change the user-specified
380+
field ordering when deciding the runtime representation.
381+
382+
Abiding by this principle allows you to write C bindings and
383+
predict hardware cache performance.
384+
385+
A structure containing unboxed types is represented at runtime as a "mixed
386+
block". A mixed block always consists of fields the GC can-or-must scan followed by
387+
fields the GC can-or-must skip[^can-or-must]. The garbage collector must be kept
388+
informed of which fields of the block it should scan. A portion of the header
389+
word is reserved to track the length of the prefix of the block that should be
390+
scanned by the garbage collector.
391+
392+
[^can-or-must]: "Can-or-must" is a bit of a mouthful, but it captures the right nuance. Pointer values *must* be scanned, unboxed number fields *must* be skipped, and immediate values *can* be scanned or skipped.
393+
394+
The ordering constraint on structure fields is a reflection of the same
395+
ordering restriction in the runtime representation.
396+
397+
## C bindings for mixed blocks
398+
399+
The implementation of field layout in a mixed block is not finalized. For example, we'd like for int32 fields to be packed efficiently (two to a word) on 64 bit platforms. Currently that's not the case: each one takes up a word.
400+
401+
Users who write C bindings might want to be notified when we change this layout. To ensure that your code will need to be updated when the layout changes, use the `Assert_mixed_block_layout_v#` family of macros. For example,
402+
403+
```
404+
Assert_mixed_block_layout_v1;
405+
```
406+
407+
Write the above in statement context, i.e. either at the top-level of a file or
408+
within a function.
409+
410+
Here's a full example. Say you're writing C bindings against this OCaml type:
411+
412+
```ocaml
413+
(** foo.ml *)
414+
type t =
415+
{ x : int32#;
416+
y : int32#;
417+
}
418+
```
419+
420+
Here is the recommend way to access fields:
421+
422+
```c
423+
Assert_mixed_block_layout_v1;
424+
#define Foo_t_x(foo) (*(int32_t*)&Field(foo, 0))
425+
#define Foo_t_y(foo) (*(int32_t*)&Field(foo, 1))
426+
```
427+
428+
We would bump the version number in either of these cases, which would prompt you to think about the code:
429+
430+
* We change what word half the int32 is stored in
431+
* We start packing int32s more efficiently

0 commit comments

Comments
 (0)