Skip to content

Commit 23fccfd

Browse files
EkdohibslthlsGbury
authored
Functions with unboxed parameters and returns (oxcaml#1271)
Co-authored-by: Vincent Laviron <vincent.laviron@gmail.com> Co-authored-by: Guillaume Bury <guillaume.bury@gmail.com>
1 parent c903157 commit 23fccfd

18 files changed

+920
-127
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Unboxed function parameters and returns
2+
3+
WARNING: unboxed function parameters and returns only work with Flambda2,
4+
the closure and flambda1 backends ignore the annotations.
5+
6+
## What can be unboxed ?
7+
8+
Function parameters as well as function's return values can be unboxed. However,
9+
this only applies when the type/layout of the unboxed value is one of the following:
10+
11+
- boxed numbers (Float, Int32, Int64, Nativeint, Vec128)
12+
- records/pairs[1]
13+
14+
15+
## How does one unbox a parameter/return value ?
16+
17+
The `[@unboxed]` annotations are used to specify what to unbox in this context.
18+
These annotations can be put either on a function parameter, or on a function declaration
19+
to unbox its return value.
20+
21+
Here are some examples:
22+
23+
```ocaml
24+
(* A regular function with boxed floats as arguments, and returning a boxed float *)
25+
let f_boxed x y = x +. y
26+
27+
(* The same function, but this time with the return value unboxed *)
28+
let[@unboxed] f_return x y = x +. y
29+
30+
(* Again, `f`, but with only the first argument unboxed.
31+
Note the parentheses around `x`, so that the annotation attaches to the parameter *)
32+
let f_first (x[@unboxed]) y = x +. y
33+
34+
(* Let's define a version of `f` with the first parameter unboxed, and the return
35+
value unboxed, and mark it as never inline. *)
36+
let[@unboxed] f (x[@unboxed]) y = x +. y [@@inline never]
37+
38+
(* Using the definition of `f` just above, this `main` function does not allocate,
39+
even with `f` not being inlined. *)
40+
let main t y =
41+
let x = t +. 1. in
42+
f x y = 0.
43+
```
44+
45+
46+
## What exactly happens ? What does it mean to unbox a parameter/return ?
47+
48+
Contrary to layouts/kinds/jkinds, the `[@unboxed]` annotation does not really change
49+
the calling convention of functions. However, these annotations alter the compilation
50+
strategy just enough so that the annotated function actually becomes a thin wrapper
51+
around the unboxed version of the function. Later, that wrapper can be inlined (without
52+
inlining the unboxed function itself), so that the boxing of arguments/return values
53+
can be simplified away if possible.
54+
55+
For instance, considering our `f` and `main` function above, here is what the actual generated
56+
code will look like:
57+
```ocaml
58+
(* ***** Source code ***** *)
59+
60+
(* here are the original `f` and `main` functions *)
61+
let[@unboxed] f (x[@unboxed]) y = x +. y [@@inline never]
62+
63+
let main t y =
64+
let x = t +. 1. in
65+
f x y = 0.
66+
67+
68+
(* ***** Before Simplification ***** *)
69+
(*
70+
* Here is the equivalent generated code in pseudo-code, before simplification.
71+
*
72+
* Note that the [@inline never] annotation only applies to the inner `f_unboxed`
73+
* version, therefore allowing `f`, which is actually a small wrapper, to be inlined.
74+
*
75+
* The code of `f_unboxed` appear to be bigger and more complex than the original
76+
* code of `f`, and in general it is true that the transformation applied to the code
77+
* is not beneficial. However, if the code of `f` only uses the unboxed versions
78+
* of its unboxed parameters (and/or direclty allocates its return value), then
79+
* the Flambda2 simplifier will be able to adequately simplify the code.
80+
*)
81+
let f_unboxed (x : unboxed_float) (y: boxed_float) : unboxed_float =
82+
let x = box_float unboxed_x in
83+
let ret =
84+
(* original code of `f` in pseudo code with explicit allocations *)
85+
box_float (unbox_float x +. unbox_float y)
86+
(* end of original code of `f` *)
87+
in
88+
unbox_float ret
89+
[@@inline never]
90+
91+
let f (x: boxed_float) (y: boxed_float) : boxed_float =
92+
let unboxed_x = load_float x in
93+
box_float (f_unboxed unboxed_x y)
94+
95+
96+
97+
(* ***** After Simplification ***** *)
98+
(*
99+
* Here is the code after simplification. Notice that the code for the `f_unboxed`
100+
* function is much better (and simpler) than it was before simplification.
101+
*
102+
* Also note that the code for the wrapper of `f` has not changed.
103+
*)
104+
let f_unboxed (unboxed_x : unboxed_float) (y: boxed_float) : unboxed_float =
105+
unboxed_x +. (unbox_float y) (* this returns an unboxed float *)
106+
[@@inline never]
107+
108+
let f (x: boxed_float) (y: boxed_float) : boxed_float =
109+
let unboxed_x = unbox_float x in
110+
box_float (f_unboxed unboxed_x y)
111+
112+
let main t y =
113+
let unboxed_x = unbox_float t +. 1. in
114+
(f_unboxed unboxed_x y) = 0.
115+
```
116+
117+
118+
[1]: The exact criterion is that the type/layout is that of a variant with no
119+
constant cases, and exactly one block case with tag zero.
120+

0 commit comments

Comments
 (0)