blob: 50a797bfbe44e030f49bcf264b86d6c9d97b0279 [file] [log] [blame] [view]
<!-- mdformat off(templates not supported) -->
{% set rfcid = "RFC-0114" %}
{% include "docs/contribute/governance/rfcs/_common/_rfc_header.md" %}
# {{ rfc.name }}: {{ rfc.title }}
<!-- SET the `rfcid` VAR ABOVE. DO NOT EDIT ANYTHING ELSE ABOVE THIS LINE. -->
<!-- mdformat on -->
<!-- This should begin with an H2 element (for example, ## Summary).-->
## Summary
This RFC proposes a FIDL wire format change that inlines values <= 4-bytes in
size into the body of envelopes.
## Motivation
The motivation for this change is to improve performance of FIDL tables and
unions (i.e. layouts which use envelopes today).
FIDL unions and tables use a shared representation for out-of-line objects
called an envelope. Out-of-line pointers are a known source of overhead
for encode and decode. Small objects can fit inline within the envelope
itself, avoiding the need for the out-of-line overhead.
Additionally, in some cases it may be possible to reduce allocations. Instead
of allocating an out-of-line location for an object and pointing to it from the
envelope, the object can be directly stored in the envelope.
## Design
This RFC design assumes the approval of
[RFC-0113](0113_efficient_envelopes.md), which introduces efficient envelopes.
A new inlined value format will be used for the following types:
- bool
- float32
- uint8, uint16, uint32
- int8, int16, int32
- enums with layout uint8, uint16, uint32, int8, int16, int32
- bits with layout uint8, uint16, uint32, int8, int16, int32
- handle, client_end, server_end
- structs <= 4-bytes in size
- arrays <= 4-bytes in size
If new types of values are added in the future that are <= 4 bytes in size,
they will also use the inlined value format unless otherwise stated.
The new format can be described through a C-struct representation:
```c++
// An envelope corresponds to a union value or an entry in a table.
struct Envelope {
union {
// Inlined values have the same envelope structure for both wire and
// decoded formats.
InlinedValueEnvelope inlined_value_envelope;
// Out-of-line values have a different structure on the wire and in
// decoded format.
union OutOfLineEnvelope {
// Wire representation.
OutOfLineWireEnvelope out_of_line_wire_envelope;
// Decoded representation.
void* decoded_data;
};
};
};
struct InlinedValueEnvelope {
// A <= 4-byte value stored little-endian and 0-padded up to 4-bytes.
uint8_t value[4];
// Number of handles within the envelope.
uint16_t num_handles;
// Bit 0 of flags is 1 to indicate the inline representation is used and
// the envelope is present.
uint16_t flags;
};
struct OutOfLineWireEnvelope {
// Number of bytes recursively within the envelope.
uint32_t num_bytes;
// Number of handles recursively within the envelope.
uint16_t num_handles;
// Bit 0 of flags is 0 to indicate the out-of-line representation is used.
uint16_t flags;
}
```
Both wire representations `InlinedValueEnvelope` and `OutOfLineWireEnvelope`
have overlapping `flags` fields. The LSB in the `flags` indicates if the
inline form is used or not: `1` for inlined and `0` for out-of-line. All
unused flag bits MUST be `0`.
There is only a single canonical representation of data in FIDL.
Present values that are up to 4 bytes in size MUST be inlined and values over 4
bytes MUST use the out-of-line representation. Receipt of a value of incorrect
representation MUST trigger a decoding error.
Absent envelopes continue to use the zero envelope representation, meaning they
are always represented by the out-of-line representation.
## Implementation
This change will require a complex migration. However, this migration can be
combined with other wire format migrations, making it much cheaper in
practice.
## Performance
There is a significant decrease in encode time in LLCPP when fields are inlined
([CL](https://fuchsia-review.googlesource.com/c/fuchsia/+/542901)):
Encode time (in nanoseconds) w/ all fields set:
| # Fields | Before | After |
|----------|---------|---------------------|
| 1 | 178 ns | 147 ns |
| 16 | 720 ns | 325 ns |
| 256 | 9396 ns | 2909 ns |
This chart shows encode time as a function of the number of fields a table has.
All fields in the table were set.
Decode time was not measured, but is also expected to have a significant
improvement as the decode algorithm follows a similar series of steps as
encode.
Additionally, bindings may in some cases be able to avoid making allocations
for small values which will further improve performance.
## Ergonomics
This RFC allows bindings to avoid allocations for small values, but does not
prescribe that they must do so. If bindings do change, the API for working
with these types could end up being different than the API for working with
other types that require allocations. This inconsistency could lead to poorer
ergonomics and care must be taken to avoid this.
## Backwards Compatibility
The migration needed for this change breaks ABI-compatibility.
However, once the change is in effect there is no effect on ABI-compatibility
for type changes. All <= 4-byte types provide no guarantees of ABI
compatibility for type changes both before and after this change.
This change MAY break source compatibility. No source compatibility breaking
changes are required by the RFC, but bindings MAY choose to make source
compatibility breaking changes if they improve the performance of the binding
or other reasons.
## Security considerations
This has no impact on security.
## Privacy considerations
This has no impact on privacy.
## Testing
Several strategies will be used to test the change:
- Custom unit tests in each binding.
- GIDL conformance suite.
- FIDL compatibility testing.
## Documentation
The wire format documentation needs to be updated.
The performance tradeoff needs to be documented in the API rubric to inform
field size decisions.
## Drawbacks, alternatives, and unknowns
### Drawbacks
The main drawback to this proposal is the increased complexity. Now there are
two representations of values - inline and out-of-line, depending on the type,
and it might be surprising that there is a 4-byte threshold for switching
behavior.
### Alternative: 8-byte inline values {#alternative-8-byte}
This RFC proposes inlining values that are 4-bytes or less.
The reason for this is that it does not appear to be possible to inline
8-byte values - at least when implemented in conjunction with efficient
envelopes.
The reason for this is that bindings must support unknown envelopes. When
an unknown envelope arrives, no type information is known. It is therefore
unknown whether it is pointing to an out-of-line object or not, which would
change the behavior of the decoder. Because of this, there needs to be some
information in the value itself that indicates if it is structured in the
inline or out-of-line format.
If the envelope size is 8-bytes and the value being inlined is 8-bytes, there
is no spare bit to store if the value is in the inline or out-of-line format.
Because of this, 8-byte inline values are incompatible with efficient
envelopes. A choice needs to be made to either not use efficient envelopes
or reduce the size of the value that can be inlined. This RFC makes the latter
choice, since this direction seems most likely to have the most significant
performance improvement.
## Prior art and references
[RFC-0113](0113_efficient_envelopes.md)
introduced efficient envelopes, which form the basis for the envelope structure
used in this RFC.