blob: bb1b898e52b33ac362c794002712fd24c9af9467 [file] [log] [blame] [view]
# Memory ownership of wire domain objects
This document provides an overview of the tools available to manage memory when
using the wire domain objects from the new C++ bindings.
## Wire domain objects trade safety for performance
The wire domain objects of the C++ bindings, also termed "wire types", are thin
types whose memory layout matches that of the
[FIDL wire format][fidl-wire-format] of their source `*.fidl` types as closely
as possible. Convenience wrappers and accessors are provided, but they do not
attempt to encapsulate the structure of the underlying wire format. With this
design, fields oftentimes may be accessed in-place and serialized with less
copying. Understanding the layout of the [FIDL wire format][fidl-wire-format]
would greatly assist in interacting with wire types. In particular, wire types
do not own their out-of-line children, as defined by the FIDL wire format.
The wire types keep unowned references to objects using:
* For a string, `fidl::StringView`.
* For a vector of objects, `fidl::VectorView`.
* For an out-of-line object, `fidl::ObjectView`.
* For a table named `Foo`, a `Foo` class that wraps a vector view, referencing
a collection of envelope headers represented by a `fidl::WireTableFrame<Foo>`,
which in turn references the fields of the table.
* For a union named `Foo`, a `Foo` class that stores the ordinal and an
[envelope header][envelope-header]:
* Small fields <= 4 bytes use the *inline representation* and are stored
inside the header.
* Larger fields > 4 bytes use the *out-of-line representation* are stored
out-of-line and referenced by the header.
* For a `MyMethod` request message, a `MyMethodRequestView` that wraps a pointer
to the request. This definition is scoped to the containing
`fidl::WireServer<Protocol>` class.
With the exception of a union with an active member using the *inline
representation*, these are non-owning views that only keep a reference and do
not manage the object lifetime. The lifetime of the objects must be managed
externally. This means that the referenced objects must outlive the views.
Copying a view will alias the references within the view. Moving a view is
equivalent to a copy and does not clear the source view.
For memory safety reasons tables are immutable. The default constructor for a
table returns an empty table. To create a table with fields you must use a
builder. The members of tables may be mutable but you can't add or remove
members after creation.
For simplicity and consistency with tables, unions are also immutable. Their
default constructor puts them in the absent state. It's a runtime error to send
an absent union unless the union is marked `optional` in the library definition.
To get a union `Foo` with a member `bar` call the static factory function
`Foo::WithBar(...)`. The arguments are either the value (for values inlined into
the envelope), a `fidl::ObjectView` of the value (for larger values) or an
arena and constructor arguments for the value.
### fidl::StringView
Defined in [lib/fidl/cpp/wire/string_view.h][string-view-src]
Holds a reference to a variable-length string stored within the buffer. C++
wrapper of **fidl_string**. Does not own the memory of the contents.
`fidl::StringView` may be constructed by supplying the pointer and number of
UTF-8 bytes (excluding trailing `\0`) separately. Alternatively, one could pass
a C++ string literal, or any value that implements `[const] char* data()`
and `size()`. The string view would borrow the contents of the container.
It is memory layout compatible with **fidl_string**.
### fidl::VectorView\<T\>
Defined in [lib/fidl/cpp/wire/vector_view.h][vector-view-src]
Holds a reference to a variable-length vector of elements stored within the
buffer. C++ wrapper of **fidl_vector**. Does not own the memory of elements.
`fidl::VectorView` may be constructed by supplying the pointer and number of
elements separately. Alternatively, one could pass any value that supports
[`std::data`](https://en.cppreference.com/w/cpp/iterator/data), such as a
standard container, or an array. The vector view would borrow the contents of
the container.
It is memory layout compatible with **fidl_vector**.
### fidl::Array\<T, N\>
Defined in [lib/fidl/cpp/wire/array.h][array-src]
Owns a fixed-length array of elements. It is similar to `std::array<T, N>`, but
designed to be memory layout compatible with FIDL arrays, and standard-layout.
The destructor closes handles if applicable e.g. a `fidl::Array` of handles.
### Message views in request/response handlers
The request handlers in server implementations receive a view of the request
message. They do not own the buffer backing the view.
The data behind the request view is only guaranteed to live until the end of the
method handler. Therefore, if the server wishes to make a reply asynchronously,
and the reply makes use of the request message, the user needs to copy relevant
fields from the request message to owned storage:
```c++
// A FIDL method called "StartGame".
virtual void StartGame(
StartGameRequestView request, StartGameCompleter::Sync completer) {
// Suppose the request has a `foo` field that is a string view,
// we need to copy it to an owning type e.g. |std::string|.
auto foo = std::string(request->foo.get());
// Make an asynchronous reply using the owning type.
async::PostDelayedTask(
dispatcher_,
[foo = std::move(foo), completer = completer.ToAsync()]() mutable {
// As an example, we simply echo back the string.
completer.Reply(fidl::StringView::FromExternal(foo));
});
}
```
Similarly, the response handlers and event handlers passed to a client also only
receive a view of the response/event message. Copying to user-owned storage is
required if they need to be accessed after the handler returns:
```c++
// Suppose the response has a `bar` field that is a table:
//
// type Bar = table {
// 1: a uint32;
// 2: b string;
// };
//
// we need to copy the table to an owned type by copying each element.
struct OwnedBar {
std::optional<uint32_t> a;
std::optional<std::string> b;
};
// Suppose we are in a class that has a `OwnedBar bar_` member.
client_->MakeMove(args).Then([](fidl::WireUnownedResult<TicTacToe::MakeMove>& result) {
assert(result.ok());
auto* response = result.Unwrap();
// Create an owned value and copy the wire table into it.
OwnedBar bar;
if (response->bar.has_a())
bar.a = response->bar.a();
if (response->bar.has_b())
bar.b = std::string(response->bar.b().get());
bar_ = std::move(bar);
});
```
## Creating wire views and objects
At the high level there are two ways to create wire objects: using an arena, or
unsafely borrowing memory. Using arenas is the safer and is performant in most
cases. Unsafely borrowing memory is very prone to errors and corruptions, but
may be called for when one needs to control every single byte of allocation.
### Create wire objects using arenas
Wire objects integrate with an arena interface `fidl::AnyArena`, typically in
their constructors or factory functions, which lets users inject custom
allocation behavior. The FIDL runtime provides a standard implementation of the
arena interface, `fidl::Arena`. The arena manages the lifetime of the allocated
wire objects (it owns the objects). As soon as the arena is destroyed, all the
objects it has allocated are deallocated and their destructors are called.
`fidl::Arena` is defined in [lib/fidl/cpp/wire/arena.h][arena-src].
The objects are first allocated within a buffer which belongs to the arena (an
inline field of the arena). The default size of the buffer is 512 bytes. A
different size can be selected using `fidl::Arena<size>`. By tweaking the size,
one could make all their allocated objects created during a request fit on the
stack, thus avoiding costlier heap allocations.
When the inline buffer is full, the arena allocates more buffers on the heap.
Each of these buffers is 16 KiB. If one needs an object larger than 16 KiB, the
arena will use a bespoke buffer with enough space to accommodate the necessary
size.
The standard pattern for using the arena is:
* Define a local variable arena of type `fidl::Arena`.
* Allocate objects using the arena.
* Send the allocated objects by making a FIDL method call or making a reply
via a completer.
* Upon exiting the function scope, all of these local variables are
automatically de-allocated.
<!-- TODO(https://fxbug.dev/42054534): Write an example for the above pattern, and link
to that. -->
The arena needs to outlive all the view types that refer to objects within it.
See [wire domain object tutorial][wire-domain-object-tutorial] for annotated
examples of how to use the arena in practice to build tables, unions, etc.
### Create wire views borrowing unowned data
In addition to the managed allocation strategies, it is also possible to
directly create pointers to memory unowned by FIDL. This is discouraged, as it
is easy to accidentally create use-after-free bugs. Most view types offer an
`FromExternal` factory function to explicitly borrow pointers to objects that
are not managed by the FIDL runtime.
To create an `ObjectView` from an external object using
`fidl::ObjectView<T>::FromExternal`:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-external-object" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
To create a `VectorView` from an external collection using
`fidl::VectorView<T>::FromExternal`:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-external-vector" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
To create a `StringView` from an external buffer using
`fidl::StringView::FromExternal`:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-external-string" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
A `StringView` can also be created directly from string literals without using
`FromExternal`. This is safe since [string literals][string-literals] have
[static lifetime][static-storage-duration].
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-external-string-literal" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
To create a wire union borrowing a member stored externally, pass an
`ObjectView` referencing the member to the corresponding union factory function:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-union-external-member" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
Wire tables store references to a `fidl::WireTableFrame<SomeTable>`, which is
responsible for keeping track of field metadata. To create a wire table
borrowing an external frame, pass an `ObjectView` to an `ExternalBuilder`.
An example of setting a field that is inlined into the frame:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-table-external-frame-inline" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
An example of setting a field that is stored out-of-line from the frame:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/advanced.cc" region_tag="wire-table-external-frame-out-of-line" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
<!-- xrefs -->
[arena-src]: /sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/arena.h
[array-src]: /sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/array.h
[envelope-header]: /docs/reference/fidl/language/wire-format/README.md#envelopes
[string-view-src]: /sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/string_view.h
[vector-view-src]: /sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/vector_view.h
[fidl-wire-format]: /docs/reference/fidl/language/wire-format/README.md
[wire-domain-object-tutorial]: /docs/development/languages/fidl/tutorials/cpp/basics/domain-objects.md#using-wire
[string-literals]: https://en.cppreference.com/w/cpp/language/string_literal
[static-storage-duration]: https://en.cppreference.com/w/c/language/static_storage_duration