dart-lang / sdk

The Dart SDK, including the VM, dart2js, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
9.94k stars 1.53k forks source link

dart:ffi Struct bit fields #38954

Open ethanblake4 opened 4 years ago

ethanblake4 commented 4 years ago

How do we support bit fields in dart:ffi? Something like:

typedef struct {
    unsigned int bold      : 1;
    unsigned int underline : 2;
    unsigned int italic    : 1;
    unsigned int blink     : 1;
    unsigned int reverse   : 1;
    unsigned int strike    : 1;
    unsigned int font      : 4;
} ScreenCellAttrs;

what would be the correct way to represent this from within Dart?

dcharkes commented 4 years ago

We do not have support for that yet, but it's a good suggestion.

The current workaround would be to write a wrapper in Dart that does the bit-masking:

const _boldMask = 0x1;

const _underlineOffset = 1;
const _underlineMask = 0x6;

class ScreenCellAttrs extends Struct {
  @Int16()
  int bits;

  int get bold => bits & _boldMask;
  void set bold(int value) {
    bits = (bits & ~_boldMask) | (value & _boldMask);
  }

  int get underline => (bits & _underlineMask) >> _underlineOffset;
  void set underline(int value) {
    bits = (bits & ~_underlineMask) |
        ((value << _underlineOffset) & _underlineMask);
  }
}

main() {
  final p = allocate<ScreenCellAttrs>();
  p.ref.bold = 1;
  p.ref.underline = 2;
  print(p.ref.bold);
  print(p.ref.underline);
  free(p);
}
ethanblake4 commented 4 years ago

Thanks!

For anyone else who comes across this issue, here's a simple mixin I made to make it easier:

mixin Bitfield16 on Struct {
  @Int16()
  int bits;

  int extract(int offset, int size) => (bits & (((math.pow(2, size) as int) - 1) << offset)) >> offset;
}
dcharkes commented 4 years ago

I've added a full example in the commit above. It uses bit shifts instead of math functions, so it should be a bit faster.

We could make writing these bit fields by hand a bit more ergonomic by exposing the int extension, and a predefined set of fixed-size bit field mixins in package:ffi.

/// Extension to use a 64-bit integer as bit field.
extension IntBitField on int {
  static int _bitMask(int offset, int lenght) => ((1 << lenght) - 1) << offset;

  /// Read `length` bits at `offset`.
  ///
  /// Truncates everything.
  int getBits(int offset, int length) {
    final mask = _bitMask(offset, length);
    return (this & mask) >> offset;
  }

  /// Returns a new integer value in which `length` bits are overwritten at
  /// `offset`.
  ///
  /// Truncates everything.
  int setBits(int offset, int length, int value) {
    final mask = _bitMask(offset, length);
    return (this & ~mask) | ((value << offset) & mask);
  }
}
mixin BitField16 on Struct {
  @Int16()
  int bits;

  int getBits(int offset, int length) => bits.getBits(offset, length);

  void setBits(int offset, int length, int value) {
    bits = bits.setBits(offset, length, value);
  }
}

In the end we probably would like to have support for bitfields in a .h-to-.dart bindings generator as the most ergonomic option (#35843).

From that perspective, we do not need to add support for bit fields in dart:ffi directly.

dcharkes commented 4 years ago

The landed sample is a decent solution. If anyone has problems using that solution, please reopen this issue.

dcharkes commented 3 years ago

This needs to be supported in the VM for fully platform agnostic bitfield definitions, see https://github.com/dart-lang/native/issues/406.