Skip to content

Fix library import error in tests/small_numbers #2728

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ocaml/jane/doc/extensions/local/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ mark the local parameter in the other module's mli.
There are a number of other features that allow more precise control
over which values are locally allocated, including:

- **Local closures**
- **Local closures**:

```ocaml
let local_ f a b c = ...
Expand All @@ -141,7 +141,7 @@ over which values are locally allocated, including:
- **Local-returning functions**

```ocaml
let f a = exclave_
let f a b c = local_
...
```

Expand All @@ -158,4 +158,4 @@ over which values are locally allocated, including:
the GC heap (and may therefore freely escape regions), even though
the record itself may be locally allocated.

For more details, read [the reference](./reference.md).
For more details, read [the reference](./reference.md).
74 changes: 44 additions & 30 deletions ocaml/jane/doc/extensions/local/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,11 @@ value `x` remains available.

The region around the body of a function prevents local allocations inside that
function from escaping. Occasionally, it is useful to write a function that
allocates and returns value in caller's region. For instance, consider this code
that uses an `int ref` as a counter:
allows local allocations to escape, which can be done by explicitly marking
such functions.

This is useful particularly for constructor functions of abstract types. For
instance, consider this code that uses an `int ref` as a counter:

```ocaml
let f () =
Expand Down Expand Up @@ -499,38 +502,46 @@ let f () =
In this code, the counter will *not* be allocated locally. The reason is the
`Counter.make` function: the allocation of `ref 0` escapes the region of
`Counter.make`, and the compiler will therefore not allow it to be locally
allocated. This remains the case no matter how many `local_` annotations we
write inside `f`: the issue is the definition of `make`, not its uses.
allocated. This remains the case no matter how many local_ annotations we write
inside `f`: the issue is the definition of `make`, not its uses.

To allow the counter to be locally allocated, we need to make `Counter.make`
allocate and return in caller's region. This can be done by `exclave_`:
To allow the counter to be locally allocated, we need to specify that
`Counter.make` may return local allocations. This can be done by wrapping the
entire body of `make` with the `local_` keyword:

```ocaml
let make () = exclave_
let make () = local_
ref 0
```

The keyword `exclave_` terminates the current region and executes the subsequent
code in the outer region. Therefore, `ref 0` is executed in `f`'s region, which
allows its local allocation. The allocation will only be cleaned up when the
region of `f` ends.
The `local_` keyword around a function body like this specifies not only that
the allocation of the `ref` should be local, but more importantly that the
function `make` *should not have its own region*.

Instead, local allocations during `make` are considered part of `f`s region,
and will only be cleaned up when that region ends. Local allocations are
allocated as always in the nearest enclosing region. However if the current
function is a local-returning function, then the nearest enclosing region will
be the caller's (or that of the caller's caller, etc., if the caller is also
local-returning).

## Advanced usage of exclaves
In the previous section, the example function exits its own region immediately,
which allows allocating and returning in caller's region. This approach,
however, has certain disadvantages. Consider the following example:
## Exclave
In the previous section, we discussed that a function can return local values
without having its own region. Consequently, it operates within the caller's
region. This approach, however, has certain disadvantages. Consider the
following example:

```ocaml
let f (local_ x) = exclave_
let f (local_ x) = local_
let local_ y = (complex computation on x) in
if y then None
else (Some x)
if y then local_ None
else local_ (Some x)
```
The function `f` allocates memory within the caller's region to store
intermediate and temporary data for the complex computation. This allocation
remains in the region even after `f` returns and is released only when the
program exits the caller's region. To allow temporary allocations to be released
upon the function's return, we delay `exclave_` as follows:
upon the function's return, we can rewrite the example as follows:

```ocaml
let f (local_ x) =
Expand All @@ -539,17 +550,18 @@ let f (local_ x) =
else exclave_ Some x
```

In this example, the function `f` has a region where the allocation for the
complex computation occurs. This region is terminated by `exclave_`, releasing
all temporary allocations. Both `None` and `Some x` are considered "local"
relative to the outer region and are allowed to be returned. In summary, we have
temporary allocations in the `f`'s region that are promptly released and result
allocations in the caller's region that can be returned.
The new primitive `exclave_` terminates the current region early and executes
the subsequent code in the outer region. In this example, the function `f` has a
region where the allocation for the complex computation occurs. This region is
terminated by `exclave_`, releasing all temporary allocations. Both `None` and
`Some x` are considered "local" relative to the outer region and are allowed to
escape. In summary, we have temporary allocations on the stack that are promptly
released and result allocations on the stack that can escape.

Here is another example in which the stack usage can be improved asymptotically
by delaying `exclave_`:
by applying `exclave_`:
```ocaml
let rec maybe_length p l = exclave_
let rec maybe_length p l = local_
match l with
| [] -> Some 0
| x :: xs ->
Expand All @@ -567,9 +579,11 @@ This function is intended to have the type:
val maybe_length : ('a -> bool) -> 'a list -> local_ int option
```
It is designed not to allocate heap memory by using the stack for all `Some`
allocations. However, it will currently use O(N) stack space because all
allocations. However, it will currently use O(N) stack space because all
allocations occur in the original caller's stack frame. To improve its space
usage, we delay the `exclave_` annotation until returning result:
usage, we remove the `local_` annotation (so the function has its own region),
and wrap `Some (count + 1)` inside `exclave_` to release the region before the
allocation:
```ocaml
let rec maybe_length p l =
match l with
Expand Down Expand Up @@ -862,4 +876,4 @@ present or neither is. This allows for a limited form of mode-polymorphism for
though, so use this feature with much caution. In the case of `id`, all is well,
but if the two `[@local_opt]`s did not act in unison (that is, they varied
independently), it would not be: `id : local_ 'a -> 'a` allows a local value to
escape.
escape.
2 changes: 1 addition & 1 deletion tests/small_numbers/dune
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
float32_builtin_nodynlink
float32_lib_nodynlink
float32_u_lib_nodynlink)
(libraries beta)
(libraries stdlib_beta)
(foreign_archives stubs)
(ocamlopt_flags
(:standard -extension-universe beta -nodynlink)))
Expand Down
Loading