dart-lang / dart-lang/language
Language features for FFI
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
This is a meta-issue for the language features which would simplify the FFI API.
/cc @dcharkes @mkustermann @mraleph @leafpetersen @lrhn
## Type-level correspondences
The FFI has as set of types which correspond to C types: `Int8`, `Int64`, `Double`, `Float`, `Pointer`, etc. Each of these types has a related Dart type, e.g. `int`, `double` or `Pointer`.
These types cannot be instantiated in Dart (exception as annotations), but are used as type variables and in function signatures to specify additional behavior. For example, the `Pointer` class has two methods `load` and `store`:
```Dart
class Pointer {
U load();
store(Object value)
}
```
Unfortunately, this API can't enforce the relationship between the `NativeType`s and corresponding `Dart` types (this is done via a custom pass in the front-end). So the following illegal code will type-check:
```Dart
Pointer x = ...;
x.load();
```
## Access to function return type
Enforcing the correspondence between these two sets of types is further complicated by the use of function types to represent native function signatures. The following API method creates a native function address which calls a Dart method:
```Dart
external Pointer> fromFunction(
Function f, {dynamic exceptionalReturn: 0});
```
Here, we want to enforce that the type of `exceptionalReturn` corresponds to the return type of `f`, and that all the types mentioned in the signature of `f` correspond piece-wise to the native types mentioned in the function type `T`.
The same holds for `asFunction`, although it's slightly simpler.
## Constant type arguments
We require that arguments to certain API functions are compile-time constants to ensure that we can compile all the required trampolines in AOT. For example:
```Dart
class Pointer {
external R asFunction();
}
```
In this case we need to ensure that at any call-site: `p.asFunction`, both `R` and the receiver type `T` need to be known at compile-time. The same applies to `fromFunction`.
## Sealed classes and virtual statics
We need to seal some classes including `NativeType` and all its children (`Int8`, `Double`, etc.) except `Struct`. In addition, we need to allow classes to extend `Struct` but not implement `Struct` (we only generate code for classes which extend `Struct` and the VM relies on having the generated code for any subtype of `Struct`).
Moreover, we need to prohibit extending a class which extends `Struct` (because we cannot generate meaningful field offsets), even though `Struct` itself can be extended.
Part of the reason we need to seal the hierarchy this way is that we cannot express the all the operations the VM needs to perform on one of these types in its signature. For example, the VM needs to manufacture Dart objects corresponding to a native-type in the return value of a native method or the argument to a callback:
```Dart
typedef NativeFn = Int8 Function();
typedef DartFn = int Function();
DartFn nativeMethod = Pointer>.fromAddress(0).asFunction();
```
If we had virtual statics, we could express the needed functionality in the interface of `NativeFunction`:
```Dart
class NativeType {
virtual static int get size;
virtual static NativeType convertWord(int word);
virtual static NativeType convertDoubleWord(int word1, int word2);
// ...
}
```
then:
```Dart
class Int64 extends NativeType {
external virtual static int get size;
// ...
}
```
The same goes for structs. Currently we disallow implementing `Struct` because we generate code like this:
```Dart
class Coord extends Struct {
@Double()
double x;
@Double()
double y;
@Pointer()
Coord next;
}
```
Gets elaborated to:
```
class Coord extends Struct {
Coord.#fromPointer(Pointer coord) : super._(coord);
Pointer get _xPtr => addressOf.cast();
set x(double v) => _xPtr.store(v);
double get x => _xPtr.load();
Pointer get _yPtr => addressOf.offsetBy(...).cast();
set y(double v) => _yPtr.store(v);
double get y => _yPtr.load();
ffi.Pointer get _nextPtr => addressof.offsetBy(...).cast();
set next(Coordinate v) => _nextPtr.store(v);
Coordinate get next => _nextPtr.load();
static final int #sizeOf = 24;
}
```
We prohibit implementing `Struct` because the VM needs to lookup the `#sizeOf` and `#fromPointer` members for any class which is a subtype of `Struct`, and we only generate them in classes which extend `Struct`. If we had virtual statics, we could express these in the interface:
```Dart
abstract class Struct {
virtual static Struct fromPointer(Pointer ptr);
virtual static int get sizeOf;
}
```
@dcharkes Are there others?
Contributor guide
Research direction
Start by reviewing the issue's sections on type-level correspondences, function return types, constant type arguments, and sealed classes or virtual statics. Compare the requested FFI language capabilities with the Dart language and VM constraints described here. Done means reaching an agreed design for the listed concerns, rather than making one isolated API edit.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100