FIDL Tuning Proposal 025

Bit Flags — Just a Little Bit

Field Value
Status Accepted
Authors kulakowski@google.com
Submitted 2019-01-09
Reviewed 2019-01-24

Summary

Extend the FIDL language with bit flag declarations.

Motivation

There are several use cases for describing a set of flags over an integer in FIDL. Currently, users of FIDL are advised to make a set of constants of the same underlying type. Because these are all independent, creation of invalid values cannot be detected by the bindings at runtime.

Design

Source Language Changes

This proposal adds the bits keyword to FIDL.

bits introduce a top-level declaration, similar to enums. Formally, the productions in the grammar are as follows:

bits-declaration = ( attribute-list ) , "bits" , IDENTIFIER , ( ":" , type-constructor ) ,
                   "{" , ( bits-or-enum-member , ";" )+ , "}" ; [NOTE 1]

bits-or-enum-member = ( attribute-list ) , IDENTIFIER , ( "=" , bits-or-enum-member-value ) ;

bits-or-enum-member-value = IDENTIFIER | literal ; [NOTE 2]

Notes:

  1. The bits-declaration allows the more liberal type-constructor in the grammar, but the compiler limits this to unsigned integer types, see primitives.

  2. The bits-or-enum-member-value allows the more liberal literal in the grammar, but the compiler limits this to:

    • A NUMERIC-LITERAL in the context of an enum;
    • A NUMERIC-LITERAL which must be a power of two, in the context of a bits.

Each member in a bits declaration is a power of two. This proposal suggests not allowing more complicated expressions in the bits declaration itself, nor allowing them to be ORed together in bits constant expressions, for the sake of simplicity. They could be added to bits declarations in the future.

An example of a bits declaration, taken from constants currently in the fuchsia.io library:

bits OpenRights : uint32 {
    READABLE = 0x00000001;
    WRITABLE = 0x00000002;
    ADMIN = 0x00000004;
};

Furthermore, this proposal adds a binary literal syntax like so:

bits OpenRights : uint32 {
    READABLE = 0b0001;
    WRITABLE = 0b0010;
    ADMIN = 0b0100;
};

Semantics

Overflowing the underlying integer type is a compilation error.

Each bits member value must be distinct.

Serializing or deserializing a bits value which has a bit set that is not a member of the bits declaration is a validation error.

The semantics of bits are distinct from enum. An enum value must be exactly one of the values declared in FIDL, while bits may not. For instance, if OpenRights were an enum, the only valid values to send would be 1, 2, and 4. As a bits type, though, 0, 3, 5, 6, and 7 are all also valid.

Bindings

Each language binding will be extended to handle these values idiomatically. At worst, this simply generates a constant for each bits member as though it were an integral constant of the underlying type.

The wire format for a bits value is the same as the underlying integral value.

Serializing and deserializing code should ensure that the value is a subset of the described bits. For instance, attempting to use 8 as an OpenRights value should fail validation.

Signal and Rights Constants

I also propose adding signal and handle right values to the zx library. This includes a bits declaration of all the signal value and rights, and possibly a set of constants with default rights for each handle type.

Implementation Strategy

Phase 1

Add all of the source changes to the FIDL compiler, including parser tests.

Phase 2

Add support to all language bindings, and to the compatibility test suite.

Phase 3

Migrate existing pile-of-int-constants to bits.

Ergonomics

This change makes FIDL more ergonomic, by letting users explicitly express their intention, and readers see the explicit grouping.

Documentation and Examples

This proposal describes changes to the FIDL grammar above.

I would tweak the FIDL tutorial to include an example of this pattern.

Backwards Compatibility

This is a backwards compatible change to FIDL source.

The wire format is backwards compatible, in the sense that the value of (2 | 4), or 6, is the same on the wire whether sent as a uint32 or a bits Bits : uint32.

Performance

Changing a type in FIDL from a uint32 to a bits value adds some minor overhead in the bindings of checking that the serialized or deserialized value is valid.

This is a bitmask and a branch, and unlikely to be noticable.

Security

I do not see a security downside. The better type safety is perhaps a minor upside.

Testing

fidlc host unit tests will exercise the FIDL parser.

The compatibility test suite will be extended with bits types of various sizes and values to exercise the sending and receiving paths of all supported bindings.

Drawbacks, Alternatives, and Unknowns

This proposal suggests only allowing bits for unsigned integer types. I believe it would be possible to allow it for signed underlying types, but with more care than desirable necessary in all of the language bindings. I'd rather not have us accidentally shifting bits too far in C/C++ in particular.

More general bitfield patterns seem more complicated than worthwhile for this proposal. By this I mean carving an integer type up into ranges of several bits, and giving each range of bits a name as a field.

& and ~ expressions feel unnecessary, at least at first. Target languages could optionally support such arithmetic expressions on bit flag values, but I do not yet see a need for them in FIDL constants directly.

Prior Art and References

The hesitancy to do anything too complicated with member values, and to avoid signed types, is based on eternal confusion with those concepts in C and C++, whose bindings must support this concept.