[vm/ffi] NNBD use external fields for structs

This enables adding the `external` keyword to struct fields, which
enables use in nnbd (weak mode).

Closes: https://github.com/dart-lang/sdk/issues/40247

External fields are not implemented in the analyzer yet:
https://github.com/dart-lang/sdk/issues/41940

Change-Id: I9d88acbdabf73ca63a6ad3d549930aa3c97cb53f
Cq-Include-Trybots: luci.dart.try: analyzer-nnbd-linux-release-try,dart2js-nnbd-linux-x64-chrome-try,ddc-nnbd-linux-release-chrome-try,front-end-nnbd-linux-release-x64-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-nnbd-linux-release-x64-try,vm-kernel-precomp-nnbd-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/148242
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Daco Harkes 2020-05-18 12:22:50 +00:00 committed by commit-bot@chromium.org
parent c10b41a4e2
commit 40f7a11d89
6 changed files with 184 additions and 101 deletions

View file

@ -156,9 +156,9 @@ class _FfiDefinitionTransformer extends FfiTransformer {
}
}
bool _isPointerType(Field field) {
bool _isPointerType(DartType type) {
return env.isSubtypeOf(
field.type,
type,
InterfaceType(pointerClass, Nullability.legacy, [
InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
Nullability.legacy)
@ -166,18 +166,52 @@ class _FfiDefinitionTransformer extends FfiTransformer {
SubtypeCheckMode.ignoringNullabilities);
}
/// Returns members of [node] that correspond to struct fields.
///
/// Note that getters and setters that originate from an external field have
/// the same `fileOffset`, we always returns getters first.
List<Member> _structFieldMembers(Class node) {
final externalGetterSetters = [...node.procedures]
..retainWhere((p) => p.isExternal && (p.isGetter || p.isSetter));
final structMembers = [...node.fields, ...externalGetterSetters]
..sort((m1, m2) {
if (m1.fileOffset == m2.fileOffset) {
// Getter and setter have same offset, getter comes first.
return (m1 as Procedure).isGetter ? -1 : 1;
}
return m1.fileOffset - m2.fileOffset;
});
return structMembers;
}
DartType _structFieldMemberType(Member member) {
if (member is Field) {
return member.type;
}
final Procedure p = member;
if (p.isGetter) {
return p.function.returnType;
}
return p.function.positionalParameters.single.type;
}
bool _checkFieldAnnotations(Class node) {
bool success = true;
for (final Field f in node.fields) {
if (f.initializer is! NullLiteral) {
diagnosticReporter.report(
templateFfiFieldInitializer.withArguments(f.name.name),
f.fileOffset,
f.name.name.length,
f.fileUri);
final membersWithAnnotations = _structFieldMembers(node)
..retainWhere((m) => (m is Field) || (m is Procedure && m.isGetter));
for (final Member f in membersWithAnnotations) {
if (f is Field) {
if (f.initializer is! NullLiteral) {
diagnosticReporter.report(
templateFfiFieldInitializer.withArguments(f.name.name),
f.fileOffset,
f.name.name.length,
f.fileUri);
}
}
final nativeTypeAnnos = _getNativeTypeAnnotations(f).toList();
if (_isPointerType(f)) {
final type = _structFieldMemberType(f);
if (_isPointerType(type)) {
if (nativeTypeAnnos.length != 0) {
diagnosticReporter.report(
templateFfiFieldNoAnnotation.withArguments(f.name.name),
@ -192,7 +226,6 @@ class _FfiDefinitionTransformer extends FfiTransformer {
f.name.name.length,
f.fileUri);
} else {
final DartType dartType = f.type;
final DartType nativeType = InterfaceType(
nativeTypesClasses[nativeTypeAnnos.first.index],
Nullability.legacy);
@ -200,10 +233,10 @@ class _FfiDefinitionTransformer extends FfiTransformer {
final DartType shouldBeDartType =
convertNativeTypeToDartType(nativeType, /*allowStructs=*/ false);
if (shouldBeDartType == null ||
!env.isSubtypeOf(dartType, shouldBeDartType,
!env.isSubtypeOf(type, shouldBeDartType,
SubtypeCheckMode.ignoringNullabilities)) {
diagnosticReporter.report(
templateFfiTypeMismatch.withArguments(dartType, shouldBeDartType,
templateFfiTypeMismatch.withArguments(type, shouldBeDartType,
nativeType, node.enclosingLibrary.isNonNullableByDefault),
f.fileOffset,
1,
@ -261,19 +294,40 @@ class _FfiDefinitionTransformer extends FfiTransformer {
///
/// Returns the total size of the struct (for all ABIs).
Map<Abi, int> _replaceFields(Class node, IndexedClass indexedClass) {
final fields = <Field>[];
final types = <NativeType>[];
final fields = <int, Field>{};
final getters = <int, Procedure>{};
final setters = <int, Procedure>{};
for (final Field f in node.fields) {
if (_isPointerType(f)) {
fields.add(f);
types.add(NativeType.kPointer);
int i = 0;
for (final Member m in _structFieldMembers(node)) {
final dartType = _structFieldMemberType(m);
NativeType nativeType;
if (_isPointerType(dartType)) {
nativeType = NativeType.kPointer;
} else {
final nativeTypeAnnos = _getNativeTypeAnnotations(f).toList();
final nativeTypeAnnos = _getNativeTypeAnnotations(m).toList();
if (nativeTypeAnnos.length == 1) {
final NativeType t = nativeTypeAnnos.first;
fields.add(f);
types.add(t);
nativeType = nativeTypeAnnos.first;
}
}
if ((m is Field || (m is Procedure && m.isGetter)) &&
nativeType != null) {
types.add(nativeType);
if (m is Field) {
fields[i] = m;
}
if (m is Procedure) {
getters[i] = m;
}
i++;
}
if (m is Procedure && m.isSetter) {
final index = i - 1; // The corresponding getter's index.
if (getters.containsKey(index)) {
setters[i - 1] = m;
}
}
}
@ -283,7 +337,7 @@ class _FfiDefinitionTransformer extends FfiTransformer {
sizeAndOffsets[abi] = _calculateSizeAndOffsets(types, abi);
}
for (int i = 0; i < fields.length; i++) {
for (final i in fields.keys) {
final fieldOffsets = sizeAndOffsets
.map((Abi abi, SizeAndOffsets v) => MapEntry(abi, v.offsets[i]));
final methods = _generateMethodsForField(
@ -291,10 +345,35 @@ class _FfiDefinitionTransformer extends FfiTransformer {
methods.forEach((p) => node.addMember(p));
}
for (final Field f in fields) {
for (final Field f in fields.values) {
f.remove();
}
for (final i in getters.keys) {
final fieldOffsets = sizeAndOffsets
.map((Abi abi, SizeAndOffsets v) => MapEntry(abi, v.offsets[i]));
Procedure getter = getters[i];
getter.function.body = _generateGetterStatement(
getter.function.returnType,
types[i],
getter.fileOffset,
fieldOffsets);
getter.isExternal = false;
}
for (final i in setters.keys) {
final fieldOffsets = sizeAndOffsets
.map((Abi abi, SizeAndOffsets v) => MapEntry(abi, v.offsets[i]));
Procedure setter = setters[i];
setter.function.body = _generateSetterStatement(
setter.function.positionalParameters.single.type,
types[i],
setter.fileOffset,
fieldOffsets,
setter.function.positionalParameters.single);
setter.isExternal = false;
}
return sizeAndOffsets.map((k, v) => MapEntry(k, v.size));
}
@ -315,14 +394,9 @@ class _FfiDefinitionTransformer extends FfiTransformer {
listElementAt);
}
List<Procedure> _generateMethodsForField(Field field, NativeType type,
Map<Abi, int> offsets, IndexedClass indexedClass) {
final DartType nativeType = type == NativeType.kPointer
? field.type
: InterfaceType(nativeTypesClasses[type.index], Nullability.legacy);
final Class nativeClass = (nativeType as InterfaceType).classNode;
final NativeType nt = getType(nativeClass);
final bool isPointer = nt == NativeType.kPointer;
Statement _generateGetterStatement(DartType dartType, NativeType type,
int fileOffset, Map<Abi, int> offsets) {
final bool isPointer = type == NativeType.kPointer;
// Sample output:
// int get x => _loadInt8(pointer, offset);
@ -332,31 +406,27 @@ class _FfiDefinitionTransformer extends FfiTransformer {
// _fromAddress<Int8>(_loadIntPtr(pointer, offset));
final loadMethod = isPointer
? loadMethods[NativeType.kIntptr]
: optimizedTypes.contains(nt) ? loadMethods[nt] : loadStructMethod;
: optimizedTypes.contains(type) ? loadMethods[type] : loadStructMethod;
Expression getterReturnValue = StaticInvocation(
loadMethod,
Arguments([
PropertyGet(ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = field.fileOffset,
..fileOffset = fileOffset,
_runtimeBranchOnLayout(offsets)
]))
..fileOffset = field.fileOffset;
..fileOffset = fileOffset;
if (isPointer) {
final typeArg = (nativeType as InterfaceType).typeArguments.single;
final typeArg = (dartType as InterfaceType).typeArguments.single;
getterReturnValue = StaticInvocation(
fromAddressInternal, Arguments([getterReturnValue], types: [typeArg]))
..fileOffset = field.fileOffset;
..fileOffset = fileOffset;
}
final Procedure getter = Procedure(
field.name,
ProcedureKind.Getter,
FunctionNode(ReturnStatement(getterReturnValue),
returnType: field.type),
fileUri: field.fileUri,
reference:
indexedClass?.lookupProcedureNotSetter(field.name.name)?.reference)
..fileOffset = field.fileOffset
..isNonNullableByDefault = field.isNonNullableByDefault;
return ReturnStatement(getterReturnValue);
}
Statement _generateSetterStatement(DartType dartType, NativeType type,
int fileOffset, Map<Abi, int> offsets, VariableDeclaration argument) {
final bool isPointer = type == NativeType.kPointer;
// Sample output:
// set x(int v) => _storeInt8(pointer, offset, v);
@ -365,35 +435,48 @@ class _FfiDefinitionTransformer extends FfiTransformer {
// set x(Pointer<Int8> v) =>
// _storeIntPtr(pointer, offset, (v as Pointer<Int8>).address);
final storeMethod =
isPointer ? storeMethods[NativeType.kIntptr] : storeMethods[nt];
isPointer ? storeMethods[NativeType.kIntptr] : storeMethods[type];
Expression argumentExpression = VariableGet(argument)
..fileOffset = fileOffset;
if (isPointer) {
argumentExpression = DirectPropertyGet(argumentExpression, addressGetter)
..fileOffset = fileOffset;
}
return ReturnStatement(StaticInvocation(
storeMethod,
Arguments([
PropertyGet(ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = fileOffset,
_runtimeBranchOnLayout(offsets),
argumentExpression
]))
..fileOffset = fileOffset);
}
List<Procedure> _generateMethodsForField(Field field, NativeType type,
Map<Abi, int> offsets, IndexedClass indexedClass) {
final getterStatement =
_generateGetterStatement(field.type, type, field.fileOffset, offsets);
final Procedure getter = Procedure(field.name, ProcedureKind.Getter,
FunctionNode(getterStatement, returnType: field.type),
fileUri: field.fileUri,
reference:
indexedClass?.lookupProcedureNotSetter(field.name.name)?.reference)
..fileOffset = field.fileOffset
..isNonNullableByDefault = field.isNonNullableByDefault;
Procedure setter = null;
if (!field.isFinal) {
final VariableDeclaration argument =
VariableDeclaration('#v', type: field.type)
..fileOffset = field.fileOffset;
Expression argumentExpression = VariableGet(argument)
..fileOffset = field.fileOffset;
if (isPointer) {
argumentExpression =
DirectPropertyGet(argumentExpression, addressGetter)
..fileOffset = field.fileOffset;
}
final setterStatement = _generateSetterStatement(
field.type, type, field.fileOffset, offsets, argument);
setter = Procedure(
field.name,
ProcedureKind.Setter,
FunctionNode(
ReturnStatement(StaticInvocation(
storeMethod,
Arguments([
PropertyGet(
ThisExpression(), addressOfField.name, addressOfField)
..fileOffset = field.fileOffset,
_runtimeBranchOnLayout(offsets),
argumentExpression
]))
..fileOffset = field.fileOffset),
returnType: VoidType(),
positionalParameters: [argument]),
FunctionNode(setterStatement,
returnType: VoidType(), positionalParameters: [argument]),
fileUri: field.fileUri,
reference:
indexedClass?.lookupProcedureSetter(field.name.name)?.reference)
@ -485,7 +568,7 @@ class _FfiDefinitionTransformer extends FfiTransformer {
return fieldType;
}
Iterable<NativeType> _getNativeTypeAnnotations(Field node) {
Iterable<NativeType> _getNativeTypeAnnotations(Member node) {
return node.annotations
.whereType<ConstantExpression>()
.map((expr) => expr.constant)

View file

@ -10,12 +10,12 @@ import "package:ffi/ffi.dart";
/// Sample struct for dart:ffi library.
class Coordinate extends Struct {
@Double()
double x;
external double x;
@Double()
double y;
external double y;
Pointer<Coordinate> next;
external Pointer<Coordinate> next;
factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
return allocate<Coordinate>().ref

View file

@ -9,10 +9,10 @@ import 'dart:ffi';
/// Stripped down sample struct for dart:ffi library.
class Coordinate extends Struct {
@Double()
double x;
external double x;
@Double()
double y;
external double y;
Pointer<Coordinate> next;
external Pointer<Coordinate> next;
}

View file

@ -61,5 +61,5 @@ testReifiedGeneric() {
class Foo extends Struct {
@Int8()
int a;
external int a;
}

View file

@ -7,45 +7,45 @@ import 'dart:ffi';
/// Large sample struct for dart:ffi library.
class VeryLargeStruct extends Struct {
@Int8()
int a;
external int a;
@Int16()
int b;
external int b;
@Int32()
int c;
external int c;
@Int64()
int d;
external int d;
@Uint8()
int e;
external int e;
@Uint16()
int f;
external int f;
@Uint32()
int g;
external int g;
@Uint64()
int h;
external int h;
@IntPtr()
int i;
external int i;
@Double()
double j;
external double j;
@Float()
double k;
external double k;
Pointer<VeryLargeStruct> parent;
external Pointer<VeryLargeStruct> parent;
@IntPtr()
int numChildren;
external int numChildren;
Pointer<VeryLargeStruct> children;
external Pointer<VeryLargeStruct> children;
@Int8()
int smallLastField;
external int smallLastField;
}

View file

@ -309,9 +309,9 @@ void testNativeFunctionSignatureInvalidOptionalPositional() {
// error on missing field annotation
class TestStruct extends Struct {
@Double()
double x;
external double x;
double y; //# 50: compile-time error
external double y; //# 50: compile-time error
}
// Cannot extend structs.
@ -321,25 +321,25 @@ class TestStruct3 extends TestStruct {} //# 52: compile-time error
class TestStruct4 extends Struct {
@Double()
@Double() //# 53: compile-time error
double z;
external double z;
}
// error on annotation not matching up
class TestStruct5 extends Struct {
@Int64() //# 54: compile-time error
double z; //# 54: compile-time error
external double z; //# 54: compile-time error
}
// error on annotation not matching up
class TestStruct6 extends Struct {
@Void() //# 55: compile-time error
double z; //# 55: compile-time error
external double z; //# 55: compile-time error
}
// error on annotation not matching up
class TestStruct7 extends Struct {
@NativeType() //# 56: compile-time error
double z; //# 56: compile-time error
external double z; //# 56: compile-time error
}
// error on field initializer on field
@ -350,8 +350,8 @@ class TestStruct8 extends Struct {
// error on field initializer in constructor
class TestStruct9 extends Struct {
@Double()
double z;
@Double() //# 58: compile-time error
double z; //# 58: compile-time error
TestStruct9() : z = 0.0 {} //# 58: compile-time error
}
@ -364,7 +364,7 @@ class TestStruct11<T> extends //# 60: compile-time error
// annotation).
class TestStruct12 extends Struct {
@Pointer //# 61: compile-time error
TestStruct9 struct; //# 61: compile-time error
external TestStruct9 struct; //# 61: compile-time error
}
class DummyAnnotation {
@ -375,7 +375,7 @@ class DummyAnnotation {
class TestStruct13 extends Struct {
@DummyAnnotation()
@Double()
double z;
external double z;
}
// Cannot extend native types.