Skip to content
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

Updated token docs for C runtime #233

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Changes from 1 commit
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
42 changes: 36 additions & 6 deletions docs/reference/target-language-details.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1118,12 +1118,6 @@ reactor Source {

The first reaction specifies the destructor and copy constructor (the latter of which will be used if any downstream reactor has a mutable input or wishes to make a writable copy).

**IMPORTANT:** The array constructed should be sent to only one output port using `lf_set`. If you need to send it to more than one output port or to use it as the payload of an action, you should use `lf_set_token`.

:::warning
**FIXME:** Show how to do this.
:::

A reactor receiving this array is straightforward. It just references the array elements as usual in C, as illustrated by this example:

```lf-c
Expand All @@ -1142,6 +1136,10 @@ reactor Print() {

The deallocation of memory for the data will occur automatically after the last reactor that receives a pointer to the data has finished using it, using the destructor specified by `lf_set_destructor` or `free` if none specified.

Sometimes, it is not necessary to explicitly provide a destructor or copy constructor for a data type.
Suppose your output port has type `foo*` for some data type `foo`.
If the dynamically allocated memory pointed to has size `sizeof(foo)` and resides in contiguous memory, then the default destructor and copy constructor will suffice.

Occasionally, you will want an input or output type to be a pointer, but you don't want the automatic memory allocation and deallocation. A simple example is a string type, which in C is `char*`. Consider the following (erroneous) reactor:

```lf-c
Expand Down Expand Up @@ -1186,6 +1184,38 @@ reactor SendsPointer {

The above technique can be used to abuse the reactor model of computation by communicating pointers to shared variables. This is generally a bad idea unless those shared variables are immutable. The result will likely be nondeterministic. Also, communicating pointers across machines that do not share memory will not work at all.

Finally, sometimes, you will want to use the same dynamically allocated data structure for multiple purposes over time.
In this case, you can explicitly create a token to carry the data, and the token mechanism will take care of reference counting and freeing the allocated memory only after all users are done with it.
For example, suppose that your reaction wishes to produce and output and schedule an action with the same payload.
edwardalee marked this conversation as resolved.
Show resolved Hide resolved
This can be accomplished as follows:

```lf-c
reactor TokenSource2 {
output out: int_array_t*
state count: int = 0
timer t(0, 2 ms)
logical action a(1 ms): int_array_t*

reaction(startup) -> out {=
lf_set_destructor(out, int_array_destructor);
lf_set_copy_constructor(out, int_array_copy_constructor);
=}

reaction(t, a) -> out, a {=
int_array_t* array = int_array_constructor(3);
for (size_t i = 0; i < array->length; i++) {
array->data[i] = self->count++;
}
lf_token_t* token = lf_new_token((lf_port_base_t*)out, array, 1);
lf_set_token(out, token);
lf_schedule_token(a, 0, token);
=}
}
```

The call to `lf_new_token` creates a token with the `int_array_t` struct as its payload (technically, it creates a token with an array of length 1, where the one element is the dynamically allocated array).
The cast in `(lf_port_base_t*)out` is necessary to suppress warnings because C does not support inheritance.

### Mutable Inputs

Although it cannot be enforced in C, a receiving reactor should not modify the values provided by an input. Inputs are logically _immutable_ because there may be several recipients. Any recipient that wishes to modify the input should make a copy of it. Fortunately, a utility is provided for this pattern. Consider the [ArrayScale](https://github.com/lf-lang/lingua-franca/blob/master/test/C/src/ArrayScale.lf) example, here modified to use the above `int_array_t` data type:
Expand Down
Loading