|
| 1 | +# Introduction to Stack Allocations |
| 2 | + |
| 3 | +See also the full feature [reference](reference.md) and [common pitfalls](pitfalls.md). |
| 4 | + |
| 5 | +This page describes how OCaml sometimes allocates values on a stack, |
| 6 | +as opposed to its usual behavior of allocating on the heap. |
| 7 | +This helps performance in a couple of ways: first, the same few hot |
| 8 | +cache lines are constantly reused, so the cache footprint is lower than |
| 9 | +usual. More importantly, stack allocations will never trigger a GC, |
| 10 | +and so they're safe to use in low-latency code that must currently be |
| 11 | +zero-alloc. |
| 12 | + |
| 13 | +Because of these advantages, values are allocated on a stack whenever |
| 14 | +possible. Of course, not all values can be allocated on a stack: a value that is |
| 15 | +used beyond the scope of its introduction must be on the heap. Accordingly, |
| 16 | +the compiler uses the _locality_ of a value to determine where it will be |
| 17 | +allocated: _local_ values go on the stack, while _global_ ones must go on the |
| 18 | +heap. |
| 19 | + |
| 20 | +Though type inference will infer where stack allocation is possible, it is |
| 21 | +often wise to annotate places where you wish to enforce locality and stack |
| 22 | +allocation. This can be done by labeling an allocation as a `stack_` allocation: |
| 23 | + |
| 24 | +```ocaml |
| 25 | +let x2 = stack_ { foo; bar } in |
| 26 | +... |
| 27 | +``` |
| 28 | + |
| 29 | +However, for this to be safe, stack-allocated values must not be used after |
| 30 | +their stack frame is freed. This is ensured by the type-checker as follows. |
| 31 | +A stack frames is represented as a _region_ at compile time, and each |
| 32 | +stack-allocated value lives in the surrounding region (usually a function body). |
| 33 | +Stack-allocated values are not allowed to escape their region. If they do, |
| 34 | +you'll see error messages: |
| 35 | + |
| 36 | +```ocaml |
| 37 | +let foo () = |
| 38 | + let thing = stack_ { foo; bar } in |
| 39 | + thing |
| 40 | + ^^^^^ |
| 41 | +Error: This value escapes its region |
| 42 | +``` |
| 43 | + |
| 44 | +Most allocations in OCaml can be stack-allocated: tuples, records, variants, |
| 45 | +closures, boxed numbers, etc. Stack allocations are also possible from C stubs, |
| 46 | +although this requires code changes to use the new `caml_alloc_local` instead of |
| 47 | +`caml_alloc`. A few types of allocation cannot be stack-allocated, though, |
| 48 | +including first-class modules, classes and objects, and exceptions. The contents |
| 49 | +of mutable fields (inside `ref`s, `array`s and mutable record fields) also |
| 50 | +cannot be stack-allocated. |
| 51 | + |
| 52 | +The `stack_` keyword works shallowly: it only forces the immediately following allocation |
| 53 | + to be on stack. Putting it before an expression that is not an allocation (such as a |
| 54 | + complete function application) leads to a type error. |
| 55 | + |
| 56 | +## Local parameters |
| 57 | + |
| 58 | +Generally, OCaml functions can do whatever they like with their |
| 59 | +arguments: use them, return them, capture them in closures or store |
| 60 | +them in globals, etc. This is a problem when trying to pass around |
| 61 | +stack-allocated values, since we need to guarantee they do not |
| 62 | +escape. |
| 63 | + |
| 64 | +The remedy is that we allow the `local_` keyword to appear on |
| 65 | +function parameters: |
| 66 | + |
| 67 | +```ocaml |
| 68 | +let f (local_ x) = ... |
| 69 | +``` |
| 70 | + |
| 71 | +A local parameter is a promise by a function not to let a particular |
| 72 | +argument escape its region. In the body of f, you'll get a type error |
| 73 | +if x escapes, but when calling f you can freely pass stack-allocated values as |
| 74 | +the argument. This promise is visible in the type of f: |
| 75 | + |
| 76 | +```ocaml |
| 77 | +val f : local_ 'a -> ... |
| 78 | +``` |
| 79 | + |
| 80 | +The function f may be equally be called with stack- or |
| 81 | +heap-allocated values: the `local_` annotation places obligations only on the |
| 82 | +definition of f, not its uses. |
| 83 | + |
| 84 | +<!-- CR zqian: factor the generic mode stuff into a dedicated document. --> |
| 85 | + |
| 86 | +Even if you're not interested in performance benefits, local |
| 87 | +parameters are a useful new tool for structuring APIs. For instance, |
| 88 | +consider a function that accepts a callback, to which it passes some |
| 89 | +mutable value: |
| 90 | + |
| 91 | +```ocaml |
| 92 | +let uses_callback ~f = |
| 93 | + let tbl = Foo.Table.create () in |
| 94 | + fill_table tbl; |
| 95 | + let result = f tbl in |
| 96 | + add_table_to_global_registry tbl; |
| 97 | + result |
| 98 | +``` |
| 99 | + |
| 100 | +Part of the contract of `uses_callback` is that it expects `f` not to |
| 101 | +capture its argument: unexpected results could ensue if `f` stored a |
| 102 | +reference to this table somewhere, and it was later used and modified |
| 103 | +after it was added to the global registry. Using `local_` |
| 104 | +annotations allows this constraint to be made explicit and checked at |
| 105 | +compile time, by giving `uses_callback` the signature: |
| 106 | + |
| 107 | +```ocaml |
| 108 | +val uses_callback : f:(local_ int Foo.Table.t -> 'a) -> 'a |
| 109 | +``` |
| 110 | + |
| 111 | +## Inference |
| 112 | + |
| 113 | +The examples above use the `stack_` keyword to mark stack |
| 114 | +allocations. In fact, this is not necessary, and the compiler will use |
| 115 | +stack allocations by default where possible. |
| 116 | + |
| 117 | +The only effect of the keyword on an allocation is to change the |
| 118 | +behavior for escaping values: if the allocated value looks like it escapes |
| 119 | +and therefore cannot be stack-allocated, then without the keyword |
| 120 | +the compiler will allocate this value on the GC heap as usual, while |
| 121 | +with the keyword it will instead report an error. |
| 122 | + |
| 123 | +Inference can even determine whether parameters are local, which is |
| 124 | +useful for helper functions. It's less useful for top-level functions, |
| 125 | +though, as whether their parameters are local is generally forced by |
| 126 | +their signature in the mli file, where no inference is performed. |
| 127 | + |
| 128 | +Inference does not work across files: if you want e.g. to pass a local |
| 129 | +argument to a function in another module, you'll need to explicitly |
| 130 | +mark the local parameter in the other module's mli. |
| 131 | + |
| 132 | +## More control |
| 133 | + |
| 134 | +There are a number of other features that allow more precise control |
| 135 | +over which values are stack-allocated, including: |
| 136 | + |
| 137 | + - **Stack-allocated closures** |
| 138 | + |
| 139 | + ```ocaml |
| 140 | + let f = stack_ (fun a b c -> ...) |
| 141 | + ``` |
| 142 | +
|
| 143 | + defines a function `f` whose closure is itself stack-allocated. |
| 144 | +
|
| 145 | + - **Local-returning functions** |
| 146 | +
|
| 147 | + ```ocaml |
| 148 | + let f a = exclave_ |
| 149 | + ... |
| 150 | + ``` |
| 151 | +
|
| 152 | + defines a function `f` which puts its stack-allocated values in its |
| 153 | + caller's region. |
| 154 | +
|
| 155 | + - **Global fields** |
| 156 | +
|
| 157 | + ```ocaml |
| 158 | + type 'a t = { global_ g : 'a } |
| 159 | + ``` |
| 160 | +
|
| 161 | + defines a record type `t` whose `g` field is always known to be |
| 162 | + heap-allocated (and may freely escape regions), even though the record |
| 163 | + itself may be stack-allocated. |
| 164 | +
|
| 165 | +For more details, read [the reference](./reference.md). |
0 commit comments