# How to understand negative arity?

Inspecting the runtime representation (compiled with `ocamlopt`) of

``````let triple_sum (x,y,z) = x + y + z
``````

I got the data below.

``````OCaml object: 0X0000563E4A1190E8
is a block with header: 0X0000000000000FF7
with number of fields : 3
color : 3
tag : 247
(field 0) code pointer: 0X0000563E4A0D5190
(field 1) code info : 0XFD00000000000007
arity : -3
env offset : 3
(field 2) opt code pointer : 0X0000563E4A0D52D0
``````

There are two things that puzzle me:

1. The arity is minus three. The function takes a triple, why the arity is not one?
2. Field 2 is code for total application. Again, the only argument is a triple and there is no chance of partially applying this function. Why this field is needed?
1 Like

OCaml does not distinguish between

``````let triple_sum (x,y,z) = x + y + z
``````

and

``````let triple_sum x y z = x + y + z
``````

It always uses the latter, more optimized version.

But when considered as a closure, the runtime has to do some extra work to reconcile both versions, since the argument will then be a triple. So, the generated code approximately looks as follows:

``````let triple_sum x y z = x + y + z
let caml_tuplify3 f (x, y, z) = f x y z
let triple_sum_closure = (caml_tuplify3, (-3 | 3), triple_sum)
``````

So, the first code pointer is in fact `caml_tuplify3`, while the second one is the actual function.

As to why the arity is a negative number, I am not quite sure. Perhaps it is just to be on the safe side, in case some code would be confused by this peculiar closure otherwise.

3 Likes

I think the negative arity is actually necessary. Take the following example:

``````let tuple_sum_with_more (x, y, z) =
let sum = x + y + z in
fun a b -> sum + a + b

(* Prevent inlining/simplification *)
let f = Sys.opaque_identity tuple_sum_with_more
let s = f (1, 2, 3) 4 5
``````

If we had used 3 as the arity of the tupled function, then the last application of `f` would see a 3-argument application of a function of arity 3, and call the underlying function directly, with some unfortunate results.

On the other hand, if we used the real arity of 1, then the runtime wouldnâ€™t know how many code pointer fields there are in the closure (itâ€™s one code pointer for functions of arity 1 (or 0), and two otherwise, but the tupled functions need two code pointers). Itâ€™s less important since 4.12, as the arity field now also encodes information about the first environment slot so scanning the closure for GC doesnâ€™t need to look at the actual arity, but I think there is still a bit of code in the compactor which relies on this property.

I believe any negative arity would work (it could be -1 instead of -3 in this example), but I guess thereâ€™s no harm in storing the additional information.

As a side note, Flambda doesnâ€™t use the same compilation scheme and will never generate closures with negative arity. Instead, it generates a specialised version of the `caml_tuplify` function for each tupled function, which is then allocated in a regular closure of arity 1. Going back to your example, `let triple_sum (x, y, z) = x + y + z` is compiled to:

``````let triple_sum_direct x y z = x + y + z
let triple_sum (x, y, z) = triple_sum_direct x y z [@@inline always]
``````

The same scheme could be used with Closure, but with a performance cost (the tuple allocation wouldnâ€™t be removed anymore, and there are a few corner cases where the function would be inlined currently but fall outside the threshold with this version).

1 Like