Reland "[vm/ffi] Support structs on 32bit architectures"
Fixed Flutter iOS build. Fixes: https://github.com/dart-lang/sdk/issues/36334 Change-Id: Idee38671cf0f33797824b37f08a92f32f931d8e0 Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try, app-kernel-linux-debug-x64-try, vm-kernel-linux-debug-simdbc64-try,vm-kernel-linux-debug-ia32-try,vm-dartkb-linux-debug-simarm64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-dartkb-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-ffi-android-product-arm-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/108818 Reviewed-by: Samir Jindel <sjindel@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
d76238dfca
commit
52cace8160
@@ -86,6 +86,74 @@ const List<int> nativeTypeSizes = [
|
||||
UNKNOWN, // Struct
|
||||
];
|
||||
|
||||
/// The struct layout in various ABIs.
|
||||
///
|
||||
/// ABIs differ per architectures and with different compilers.
|
||||
/// We pick the default struct layout based on the architecture and OS.
|
||||
///
|
||||
/// Compilers _can_ deviate from the default layout, but this prevents
|
||||
/// executables from making system calls. So this seems rather uncommon.
|
||||
///
|
||||
/// In the future, we might support custom struct layouts. For more info see
|
||||
/// https://github.com/dart-lang/sdk/issues/35768.
|
||||
enum Abi {
|
||||
/// Layout in all 64bit ABIs (x64 and arm64).
|
||||
wordSize64,
|
||||
|
||||
/// Layout in System V ABI for x386 (ia32 on Linux) and in iOS Arm 32 bit.
|
||||
wordSize32Align32,
|
||||
|
||||
/// Layout in both the Arm 32 bit ABI and the Windows ia32 ABI.
|
||||
wordSize32Align64,
|
||||
}
|
||||
|
||||
/// WORD_SIZE in bytes.
|
||||
const wordSize = <Abi, int>{
|
||||
Abi.wordSize64: 8,
|
||||
Abi.wordSize32Align32: 4,
|
||||
Abi.wordSize32Align64: 4,
|
||||
};
|
||||
|
||||
/// Elements that are not aligned to their size.
|
||||
///
|
||||
/// Has an entry for all Abis. Empty entries document that every native
|
||||
/// type is aligned to it's own size in this ABI.
|
||||
///
|
||||
/// See runtime/vm/compiler/ffi.cc for asserts in the VM that verify these
|
||||
/// alignments.
|
||||
///
|
||||
/// TODO(37470): Add uncommon primitive data types when we want to support them.
|
||||
const nonSizeAlignment = <Abi, Map<NativeType, int>>{
|
||||
Abi.wordSize64: {},
|
||||
|
||||
// x86 System V ABI:
|
||||
// > uint64_t | size 8 | alignment 4
|
||||
// > double | size 8 | alignment 4
|
||||
// https://github.com/hjl-tools/x86-psABI/wiki/intel386-psABI-1.1.pdf page 8.
|
||||
//
|
||||
// iOS 32 bit alignment:
|
||||
// https://developer.apple.com/documentation/uikit/app_and_environment/updating_your_app_from_32-bit_to_64-bit_architecture/updating_data_structures
|
||||
Abi.wordSize32Align32: {NativeType.kDouble: 4, NativeType.kInt64: 4},
|
||||
|
||||
// The default for MSVC x86:
|
||||
// > The alignment-requirement for all data except structures, unions, and
|
||||
// > arrays is either the size of the object or the current packing size
|
||||
// > (specified with either /Zp or the pack pragma, whichever is less).
|
||||
// https://docs.microsoft.com/en-us/cpp/c-language/padding-and-alignment-of-structure-members?view=vs-2019
|
||||
//
|
||||
// GCC _can_ compile on Linux to this alignment with -malign-double, but does
|
||||
// not do so by default:
|
||||
// > Warning: if you use the -malign-double switch, structures containing the
|
||||
// > above types are aligned differently than the published application
|
||||
// > binary interface specifications for the x86-32 and are not binary
|
||||
// > compatible with structures in code compiled without that switch.
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
|
||||
//
|
||||
// Arm always requires 8 byte alignment for 8 byte values:
|
||||
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf 4.1 Fundamental Data Types
|
||||
Abi.wordSize32Align64: {},
|
||||
};
|
||||
|
||||
/// [FfiTransformer] contains logic which is shared between
|
||||
/// _FfiUseSiteTransformer and _FfiDefinitionTransformer.
|
||||
class FfiTransformer extends Transformer {
|
||||
@@ -98,6 +166,7 @@ class FfiTransformer extends Transformer {
|
||||
final Class intClass;
|
||||
final Class doubleClass;
|
||||
final Constructor pragmaConstructor;
|
||||
final Procedure listElementAt;
|
||||
|
||||
final Library ffiLibrary;
|
||||
final Class nativeFunctionClass;
|
||||
@@ -114,6 +183,7 @@ class FfiTransformer extends Transformer {
|
||||
final Field addressOfField;
|
||||
final Constructor structFromPointer;
|
||||
final Procedure libraryLookupMethod;
|
||||
final Procedure abiMethod;
|
||||
|
||||
/// Classes corresponding to [NativeType], indexed by [NativeType].
|
||||
final List<Class> nativeTypesClasses;
|
||||
@@ -124,6 +194,7 @@ class FfiTransformer extends Transformer {
|
||||
intClass = coreTypes.intClass,
|
||||
doubleClass = coreTypes.doubleClass,
|
||||
pragmaConstructor = coreTypes.pragmaConstructor,
|
||||
listElementAt = coreTypes.index.getMember('dart:core', 'List', '[]'),
|
||||
ffiLibrary = index.getLibrary('dart:ffi'),
|
||||
nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
|
||||
pointerClass = index.getClass('dart:ffi', 'Pointer'),
|
||||
@@ -144,6 +215,7 @@ class FfiTransformer extends Transformer {
|
||||
index.getMember('dart:ffi', 'Pointer', 'fromFunction'),
|
||||
libraryLookupMethod =
|
||||
index.getMember('dart:ffi', 'DynamicLibrary', 'lookup'),
|
||||
abiMethod = index.getTopLevelMember('dart:ffi', '_abi'),
|
||||
nativeTypesClasses = nativeTypeClassNames
|
||||
.map((name) => index.getClass('dart:ffi', name))
|
||||
.toList();
|
||||
|
||||
@@ -15,19 +15,13 @@ import 'package:front_end/src/api_unstable/vm.dart'
|
||||
templateFfiStructGeneric,
|
||||
templateFfiWrongStructInheritance;
|
||||
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/ast.dart' hide MapEntry;
|
||||
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/library_index.dart' show LibraryIndex;
|
||||
import 'package:kernel/target/targets.dart' show DiagnosticReporter;
|
||||
|
||||
import 'ffi.dart'
|
||||
show
|
||||
ReplacedMembers,
|
||||
NativeType,
|
||||
FfiTransformer,
|
||||
nativeTypeSizes,
|
||||
WORD_SIZE;
|
||||
import 'ffi.dart';
|
||||
|
||||
/// Checks and elaborates the dart:ffi structs and fields.
|
||||
///
|
||||
@@ -66,8 +60,8 @@ ReplacedMembers transformLibraries(
|
||||
ClassHierarchy hierarchy,
|
||||
List<Library> libraries,
|
||||
DiagnosticReporter diagnosticReporter) {
|
||||
final LibraryIndex index = LibraryIndex(
|
||||
component, const ["dart:ffi", "dart:_internal", "dart:core"]);
|
||||
final LibraryIndex index =
|
||||
LibraryIndex(component, const ["dart:ffi", "dart:core"]);
|
||||
if (!index.containsLibrary("dart:ffi")) {
|
||||
// If dart:ffi is not loaded, do not do the transformation.
|
||||
return ReplacedMembers({}, {});
|
||||
@@ -82,27 +76,13 @@ ReplacedMembers transformLibraries(
|
||||
/// Checks and elaborates the dart:ffi structs and fields.
|
||||
class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
final LibraryIndex index;
|
||||
final Field _internalIs64Bit;
|
||||
final Constructor _unimplementedErrorCtor;
|
||||
static const String _errorOn32BitMessage =
|
||||
"Code-gen for FFI structs is not supported on 32-bit platforms.";
|
||||
|
||||
Map<Field, Procedure> replacedGetters = {};
|
||||
Map<Field, Procedure> replacedSetters = {};
|
||||
|
||||
_FfiDefinitionTransformer(this.index, CoreTypes coreTypes,
|
||||
ClassHierarchy hierarchy, DiagnosticReporter diagnosticReporter)
|
||||
: _internalIs64Bit = index.getTopLevelMember('dart:_internal', 'is64Bit'),
|
||||
_unimplementedErrorCtor =
|
||||
index.getMember('dart:core', 'UnimplementedError', ''),
|
||||
super(index, coreTypes, hierarchy, diagnosticReporter) {}
|
||||
|
||||
Statement guardOn32Bit(Statement body) {
|
||||
final Throw error = Throw(ConstructorInvocation(_unimplementedErrorCtor,
|
||||
Arguments([StringLiteral(_errorOn32BitMessage)])));
|
||||
return IfStatement(
|
||||
StaticGet(_internalIs64Bit), body, ExpressionStatement(error));
|
||||
}
|
||||
: super(index, coreTypes, hierarchy, diagnosticReporter) {}
|
||||
|
||||
@override
|
||||
visitClass(Class node) {
|
||||
@@ -119,8 +99,8 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
final bool fieldsValid = _checkFieldAnnotations(node);
|
||||
|
||||
if (fieldsValid) {
|
||||
int size = _replaceFields(node);
|
||||
_replaceSizeOfMethod(node, size);
|
||||
final structSize = _replaceFields(node);
|
||||
_replaceSizeOfMethod(node, structSize);
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -169,27 +149,27 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
f.name.name.length,
|
||||
f.fileUri);
|
||||
}
|
||||
List<NativeType> annos = _getAnnotations(f).toList();
|
||||
final nativeTypeAnnos = _getNativeTypeAnnotations(f).toList();
|
||||
if (_isPointerType(f)) {
|
||||
if (annos.length != 0) {
|
||||
if (nativeTypeAnnos.length != 0) {
|
||||
diagnosticReporter.report(
|
||||
templateFfiFieldNoAnnotation.withArguments(f.name.name),
|
||||
f.fileOffset,
|
||||
f.name.name.length,
|
||||
f.fileUri);
|
||||
}
|
||||
} else if (annos.length != 1) {
|
||||
} else if (nativeTypeAnnos.length != 1) {
|
||||
diagnosticReporter.report(
|
||||
templateFfiFieldAnnotation.withArguments(f.name.name),
|
||||
f.fileOffset,
|
||||
f.name.name.length,
|
||||
f.fileUri);
|
||||
} else {
|
||||
DartType dartType = f.type;
|
||||
DartType nativeType =
|
||||
InterfaceType(nativeTypesClasses[annos.first.index]);
|
||||
final DartType dartType = f.type;
|
||||
final DartType nativeType =
|
||||
InterfaceType(nativeTypesClasses[nativeTypeAnnos.first.index]);
|
||||
// TODO(36730): Support structs inside structs.
|
||||
DartType shouldBeDartType =
|
||||
final DartType shouldBeDartType =
|
||||
convertNativeTypeToDartType(nativeType, /*allowStructs=*/ false);
|
||||
if (shouldBeDartType == null ||
|
||||
!env.isSubtypeOf(dartType, shouldBeDartType)) {
|
||||
@@ -207,7 +187,7 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
}
|
||||
|
||||
void _checkConstructors(Class node) {
|
||||
List<Initializer> toRemove = [];
|
||||
final toRemove = <Initializer>[];
|
||||
|
||||
// Constructors cannot have initializers because initializers refer to
|
||||
// fields, and the fields were replaced with getter/setter pairs.
|
||||
@@ -241,44 +221,62 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
node.addMember(ctor);
|
||||
}
|
||||
|
||||
/// Computes the field offsets in the struct and replaces the fields with
|
||||
/// getters and setters using these offsets.
|
||||
/// Computes the field offsets (for all ABIs) in the struct and replaces the
|
||||
/// fields with getters and setters using these offsets.
|
||||
///
|
||||
/// Returns the total size of the struct.
|
||||
int _replaceFields(Class node) {
|
||||
List<Field> fields = [];
|
||||
List<NativeType> types = [];
|
||||
/// Returns the total size of the struct (for all ABIs).
|
||||
Map<Abi, int> _replaceFields(Class node) {
|
||||
final fields = <Field>[];
|
||||
final types = <NativeType>[];
|
||||
|
||||
for (Field f in node.fields) {
|
||||
if (_isPointerType(f)) {
|
||||
fields.add(f);
|
||||
types.add(NativeType.kPointer);
|
||||
} else {
|
||||
List<NativeType> annos = _getAnnotations(f).toList();
|
||||
if (annos.length == 1) {
|
||||
NativeType t = annos.first;
|
||||
final nativeTypeAnnos = _getNativeTypeAnnotations(f).toList();
|
||||
if (nativeTypeAnnos.length == 1) {
|
||||
NativeType t = nativeTypeAnnos.first;
|
||||
fields.add(f);
|
||||
types.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<int> offsets = _calculateOffsets(types);
|
||||
int size = _calculateSize(offsets, types);
|
||||
final sizeAndOffsets = <Abi, SizeAndOffsets>{};
|
||||
for (Abi abi in Abi.values) {
|
||||
sizeAndOffsets[abi] = _calculateSizeAndOffsets(types, abi);
|
||||
}
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
List<Procedure> methods =
|
||||
_generateMethodsForField(fields[i], types[i], offsets[i]);
|
||||
for (Procedure p in methods) {
|
||||
node.addMember(p);
|
||||
}
|
||||
final fieldOffsets = sizeAndOffsets
|
||||
.map((Abi abi, SizeAndOffsets v) => MapEntry(abi, v.offsets[i]));
|
||||
final methods =
|
||||
_generateMethodsForField(fields[i], types[i], fieldOffsets);
|
||||
methods.forEach((p) => node.addMember(p));
|
||||
}
|
||||
|
||||
for (Field f in fields) {
|
||||
f.remove();
|
||||
}
|
||||
|
||||
return size;
|
||||
return sizeAndOffsets.map((k, v) => MapEntry(k, v.size));
|
||||
}
|
||||
|
||||
/// Expression that queries VM internals at runtime to figure out on which ABI
|
||||
/// we are.
|
||||
Expression _runtimeBranchOnLayout(Map<Abi, int> values) {
|
||||
return MethodInvocation(
|
||||
ConstantExpression(
|
||||
ListConstant(InterfaceType(intClass), [
|
||||
IntConstant(values[Abi.wordSize64]),
|
||||
IntConstant(values[Abi.wordSize32Align32]),
|
||||
IntConstant(values[Abi.wordSize32Align64])
|
||||
]),
|
||||
InterfaceType(intClass)),
|
||||
Name("[]"),
|
||||
Arguments([StaticInvocation(abiMethod, Arguments([]))]),
|
||||
listElementAt);
|
||||
}
|
||||
|
||||
/// Sample output:
|
||||
@@ -286,60 +284,58 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
/// double get x => _xPtr.load();
|
||||
/// set x(double v) => _xPtr.store(v);
|
||||
List<Procedure> _generateMethodsForField(
|
||||
Field field, NativeType type, int offset) {
|
||||
DartType nativeType = type == NativeType.kPointer
|
||||
Field field, NativeType type, Map<Abi, int> offsets) {
|
||||
final DartType nativeType = type == NativeType.kPointer
|
||||
? field.type
|
||||
: InterfaceType(nativeTypesClasses[type.index]);
|
||||
DartType pointerType = InterfaceType(pointerClass, [nativeType]);
|
||||
Name pointerName = Name('#_ptr_${field.name.name}');
|
||||
final DartType pointerType = InterfaceType(pointerClass, [nativeType]);
|
||||
final Name pointerName = Name('#_ptr_${field.name.name}');
|
||||
|
||||
// Sample output:
|
||||
// ffi.Pointer<ffi.Double> get _xPtr => addressOf.offsetBy(...).cast<ffi.Pointer<ffi.Double>>();
|
||||
Expression pointer =
|
||||
PropertyGet(ThisExpression(), addressOfField.name, addressOfField);
|
||||
if (offset != 0) {
|
||||
final hasNonZero = offsets.values.skipWhile((i) => i == 0).isNotEmpty;
|
||||
if (hasNonZero) {
|
||||
pointer = MethodInvocation(pointer, offsetByMethod.name,
|
||||
Arguments([IntLiteral(offset)]), offsetByMethod);
|
||||
Arguments([_runtimeBranchOnLayout(offsets)]), offsetByMethod);
|
||||
}
|
||||
Procedure pointerGetter = Procedure(
|
||||
final Procedure pointerGetter = Procedure(
|
||||
pointerName,
|
||||
ProcedureKind.Getter,
|
||||
FunctionNode(
|
||||
guardOn32Bit(ReturnStatement(MethodInvocation(
|
||||
pointer,
|
||||
castMethod.name,
|
||||
Arguments([], types: [nativeType]),
|
||||
castMethod))),
|
||||
ReturnStatement(MethodInvocation(pointer, castMethod.name,
|
||||
Arguments([], types: [nativeType]), castMethod)),
|
||||
returnType: pointerType));
|
||||
|
||||
// Sample output:
|
||||
// double get x => _xPtr.load<double>();
|
||||
Procedure getter = Procedure(
|
||||
final Procedure getter = Procedure(
|
||||
field.name,
|
||||
ProcedureKind.Getter,
|
||||
FunctionNode(
|
||||
guardOn32Bit(ReturnStatement(MethodInvocation(
|
||||
ReturnStatement(MethodInvocation(
|
||||
PropertyGet(ThisExpression(), pointerName, pointerGetter),
|
||||
loadMethod.name,
|
||||
Arguments([], types: [field.type]),
|
||||
loadMethod))),
|
||||
loadMethod)),
|
||||
returnType: field.type));
|
||||
|
||||
// Sample output:
|
||||
// set x(double v) => _xPtr.store(v);
|
||||
Procedure setter = null;
|
||||
if (!field.isFinal) {
|
||||
VariableDeclaration argument =
|
||||
final VariableDeclaration argument =
|
||||
VariableDeclaration('#v', type: field.type);
|
||||
setter = Procedure(
|
||||
field.name,
|
||||
ProcedureKind.Setter,
|
||||
FunctionNode(
|
||||
guardOn32Bit(ReturnStatement(MethodInvocation(
|
||||
ReturnStatement(MethodInvocation(
|
||||
PropertyGet(ThisExpression(), pointerName, pointerGetter),
|
||||
storeMethod.name,
|
||||
Arguments([VariableGet(argument)]),
|
||||
storeMethod))),
|
||||
storeMethod)),
|
||||
returnType: VoidType(),
|
||||
positionalParameters: [argument]));
|
||||
}
|
||||
@@ -347,66 +343,61 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
replacedGetters[field] = getter;
|
||||
replacedSetters[field] = setter;
|
||||
|
||||
if (setter != null) {
|
||||
return [pointerGetter, getter, setter];
|
||||
} else {
|
||||
return [pointerGetter, getter];
|
||||
}
|
||||
return [pointerGetter, getter, if (setter != null) setter];
|
||||
}
|
||||
|
||||
/// Sample output:
|
||||
/// static int #sizeOf() => 24;
|
||||
void _replaceSizeOfMethod(Class struct, int size) {
|
||||
void _replaceSizeOfMethod(Class struct, Map<Abi, int> sizes) {
|
||||
final Field sizeOf = Field(Name("#sizeOf"),
|
||||
isStatic: true, isFinal: true, initializer: IntLiteral(size));
|
||||
isStatic: true,
|
||||
isFinal: true,
|
||||
initializer: _runtimeBranchOnLayout(sizes),
|
||||
type: InterfaceType(intClass));
|
||||
_makeEntryPoint(sizeOf);
|
||||
struct.addMember(sizeOf);
|
||||
}
|
||||
|
||||
// TODO(dacoharkes): move to VM, take into account architecture
|
||||
// https://github.com/dart-lang/sdk/issues/35768
|
||||
int _sizeInBytes(NativeType t) {
|
||||
int size = nativeTypeSizes[t.index];
|
||||
int _sizeInBytes(NativeType type, Abi abi) {
|
||||
final int size = nativeTypeSizes[type.index];
|
||||
if (size == WORD_SIZE) {
|
||||
size = 8;
|
||||
return wordSize[abi];
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
int _align(int offset, int size) {
|
||||
int remainder = offset % size;
|
||||
int _alignmentOf(NativeType type, Abi abi) {
|
||||
final int alignment = nonSizeAlignment[abi][type];
|
||||
if (alignment != null) return alignment;
|
||||
return _sizeInBytes(type, abi);
|
||||
}
|
||||
|
||||
int _alignOffset(int offset, int alignment) {
|
||||
final int remainder = offset % alignment;
|
||||
if (remainder != 0) {
|
||||
offset -= remainder;
|
||||
offset += size;
|
||||
offset += alignment;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
// TODO(dacoharkes): move to VM, take into account architecture
|
||||
// https://github.com/dart-lang/sdk/issues/35768
|
||||
List<int> _calculateOffsets(List<NativeType> types) {
|
||||
// TODO(37271): Support nested structs.
|
||||
SizeAndOffsets _calculateSizeAndOffsets(List<NativeType> types, Abi abi) {
|
||||
int offset = 0;
|
||||
List<int> offsets = [];
|
||||
final offsets = <int>[];
|
||||
for (NativeType t in types) {
|
||||
int size = _sizeInBytes(t);
|
||||
offset = _align(offset, size);
|
||||
final int size = _sizeInBytes(t, abi);
|
||||
final int alignment = _alignmentOf(t, abi);
|
||||
offset = _alignOffset(offset, alignment);
|
||||
offsets.add(offset);
|
||||
offset += size;
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
// TODO(dacoharkes): move to VM, take into account architecture
|
||||
// https://github.com/dart-lang/sdk/issues/35768
|
||||
int _calculateSize(List<int> offsets, List<NativeType> types) {
|
||||
if (offsets.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
int largestElement = types.map((e) => _sizeInBytes(e)).reduce(math.max);
|
||||
int highestOffsetIndex = types.length - 1;
|
||||
int highestOffset = offsets[highestOffsetIndex];
|
||||
int highestOffsetSize = _sizeInBytes(types[highestOffsetIndex]);
|
||||
return _align(highestOffset + highestOffsetSize, largestElement);
|
||||
final int minimumAlignment = 1;
|
||||
final sizeAlignment = types
|
||||
.map((t) => _alignmentOf(t, abi))
|
||||
.followedBy([minimumAlignment]).reduce(math.max);
|
||||
final int size = _alignOffset(offset, sizeAlignment);
|
||||
return SizeAndOffsets(size, offsets);
|
||||
}
|
||||
|
||||
void _makeEntryPoint(Annotatable node) {
|
||||
@@ -415,7 +406,7 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
}
|
||||
|
||||
NativeType _getFieldType(Class c) {
|
||||
NativeType fieldType = getType(c);
|
||||
final fieldType = getType(c);
|
||||
|
||||
if (fieldType == NativeType.kVoid) {
|
||||
// Fields cannot have Void types.
|
||||
@@ -424,13 +415,13 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
Iterable<NativeType> _getAnnotations(Field node) {
|
||||
Iterable<NativeType> preConstant2018 = node.annotations
|
||||
Iterable<NativeType> _getNativeTypeAnnotations(Field node) {
|
||||
final Iterable<NativeType> preConstant2018 = node.annotations
|
||||
.whereType<ConstructorInvocation>()
|
||||
.map((expr) => expr.target.parent)
|
||||
.map((klass) => _getFieldType(klass))
|
||||
.where((type) => type != null);
|
||||
Iterable<NativeType> postConstant2018 = node.annotations
|
||||
final Iterable<NativeType> postConstant2018 = node.annotations
|
||||
.whereType<ConstantExpression>()
|
||||
.map((expr) => expr.constant)
|
||||
.whereType<InstanceConstant>()
|
||||
@@ -441,3 +432,13 @@ class _FfiDefinitionTransformer extends FfiTransformer {
|
||||
return postConstant2018.followedBy(preConstant2018);
|
||||
}
|
||||
}
|
||||
|
||||
class SizeAndOffsets {
|
||||
/// Size of the entire struct.
|
||||
final int size;
|
||||
|
||||
/// Offset in bytes for each field, indexed by field number.
|
||||
final List<int> offsets;
|
||||
|
||||
SizeAndOffsets(this.size, this.offsets);
|
||||
}
|
||||
|
||||
@@ -343,22 +343,29 @@ DART_EXPORT int64_t* NullableInt64ElemAt1(int64_t* a) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
// A struct designed to exercise all kinds of alignment rules.
|
||||
// Note that offset32A (System V ia32) aligns doubles on 4 bytes while offset32B
|
||||
// (Arm 32 bit and MSVC ia32) aligns on 8 bytes.
|
||||
// TODO(37271): Support nested structs.
|
||||
// TODO(37470): Add uncommon primitive data types when we want to support them.
|
||||
struct VeryLargeStruct {
|
||||
int8_t a;
|
||||
int16_t b;
|
||||
int32_t c;
|
||||
int64_t d;
|
||||
uint8_t e;
|
||||
uint16_t f;
|
||||
uint32_t g;
|
||||
uint64_t h;
|
||||
intptr_t i;
|
||||
float j;
|
||||
double k;
|
||||
VeryLargeStruct* parent;
|
||||
intptr_t numChildren;
|
||||
VeryLargeStruct* children;
|
||||
int8_t smallLastField;
|
||||
// size32 size64 offset32A offset32B offset64
|
||||
int8_t a; // 1 0 0 0
|
||||
int16_t b; // 2 2 2 2
|
||||
int32_t c; // 4 4 4 4
|
||||
int64_t d; // 8 8 8 8
|
||||
uint8_t e; // 1 16 16 16
|
||||
uint16_t f; // 2 18 18 18
|
||||
uint32_t g; // 4 20 20 20
|
||||
uint64_t h; // 8 24 24 24
|
||||
intptr_t i; // 4 8 32 32 32
|
||||
double j; // 8 36 40 40
|
||||
float k; // 4 44 48 48
|
||||
VeryLargeStruct* parent; // 4 8 48 52 56
|
||||
intptr_t numChildren; // 4 8 52 56 64
|
||||
VeryLargeStruct* children; // 4 8 56 60 72
|
||||
int8_t smallLastField; // 1 60 64 80
|
||||
// sizeof 64 72 88
|
||||
};
|
||||
|
||||
// Sums the fields of a very large struct, including the first field (a) from
|
||||
|
||||
@@ -68,3 +68,8 @@ class Pointer<T extends NativeType> {
|
||||
@patch
|
||||
void free() native "Ffi_free";
|
||||
}
|
||||
|
||||
// Returns the ABI used for size and alignment calculations.
|
||||
// See pkg/vm/lib/transformations/ffi.dart.
|
||||
int _abi()
|
||||
native "Recognized method: method is directly interpreted by the bytecode interpreter or IR graph is built in the flow graph builder.";
|
||||
|
||||
@@ -52,6 +52,60 @@ size_t ElementSizeInBytes(intptr_t class_id) {
|
||||
return element_size_table[index];
|
||||
}
|
||||
|
||||
// See pkg/vm/lib/transformations/ffi.dart, which makes these assumptions.
|
||||
#if defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64)
|
||||
static_assert(alignof(double) == 8, "FFI transformation alignment");
|
||||
static_assert(alignof(uint64_t) == 8, "FFI transformation alignment");
|
||||
#elif defined(HOST_ARCH_IA32) && \
|
||||
(defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) || \
|
||||
defined(HOST_OS_ANDROID)) || \
|
||||
defined(HOST_ARCH_ARM) && defined(HOST_OS_IOS)
|
||||
static_assert(alignof(double) == 4, "FFI transformation alignment");
|
||||
static_assert(alignof(uint64_t) == 4, "FFI transformation alignment");
|
||||
#elif defined(HOST_ARCH_IA32) && defined(HOST_OS_WINDOWS) || \
|
||||
defined(HOST_ARCH_ARM)
|
||||
static_assert(alignof(double) == 8, "FFI transformation alignment");
|
||||
static_assert(alignof(uint64_t) == 8, "FFI transformation alignment");
|
||||
#else
|
||||
#error "Unknown platform. Please add alignment requirements for ABI."
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_ARCH_DBC)
|
||||
static Abi HostAbi() {
|
||||
#if defined(HOST_ARCH_X64) || defined(HOST_ARCH_ARM64)
|
||||
return Abi::kWordSize64;
|
||||
#elif defined(HOST_ARCH_IA32) && \
|
||||
(defined(HOST_OS_LINUX) || defined(HOST_OS_MACOS) || \
|
||||
defined(HOST_OS_ANDROID)) || \
|
||||
defined(HOST_ARCH_ARM) && defined(HOST_OS_IOS)
|
||||
return Abi::kWordSize32Align32;
|
||||
#elif defined(HOST_ARCH_IA32) && defined(HOST_OS_WINDOWS) || \
|
||||
defined(HOST_ARCH_ARM)
|
||||
return Abi::kWordSize32Align64;
|
||||
#else
|
||||
#error "Unknown platform. Please add alignment requirements for ABI."
|
||||
#endif
|
||||
}
|
||||
#endif // defined(TARGET_ARCH_DBC)
|
||||
|
||||
Abi TargetAbi() {
|
||||
#if defined(TARGET_ARCH_DBC)
|
||||
return HostAbi();
|
||||
#elif defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64)
|
||||
return Abi::kWordSize64;
|
||||
#elif defined(TARGET_ARCH_IA32) && \
|
||||
(defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS) || \
|
||||
defined(TARGET_OS_ANDROID)) || \
|
||||
defined(TARGET_ARCH_ARM) && defined(TARGET_OS_IOS)
|
||||
return Abi::kWordSize32Align32;
|
||||
#elif defined(TARGET_ARCH_IA32) && defined(TARGET_OS_WINDOWS) || \
|
||||
defined(TARGET_ARCH_ARM)
|
||||
return Abi::kWordSize32Align64;
|
||||
#else
|
||||
#error "Unknown platform. Please add alignment requirements for ABI."
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
||||
Representation TypeRepresentation(const AbstractType& result_type) {
|
||||
|
||||
@@ -25,6 +25,16 @@ constexpr intptr_t kMinimumArgumentWidth = 4;
|
||||
// Storage size for an FFI type (extends 'ffi.NativeType').
|
||||
size_t ElementSizeInBytes(intptr_t class_id);
|
||||
|
||||
// These ABIs should be kept in sync with pkg/vm/lib/transformations/ffi.dart.
|
||||
enum class Abi {
|
||||
kWordSize64 = 0,
|
||||
kWordSize32Align32 = 1,
|
||||
kWordSize32Align64 = 2
|
||||
};
|
||||
|
||||
// The target ABI. Defines sizes and alignment of native types.
|
||||
Abi TargetAbi();
|
||||
|
||||
// Unboxed representation of an FFI type (extends 'ffi.NativeType').
|
||||
Representation TypeRepresentation(const AbstractType& result_type);
|
||||
|
||||
|
||||
@@ -1036,6 +1036,7 @@ RawTypedData* BytecodeReaderHelper::NativeEntry(const Function& function,
|
||||
case MethodRecognizer::kLinkedHashMap_setUsedData:
|
||||
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
||||
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
||||
case MethodRecognizer::kFfiAbi:
|
||||
break;
|
||||
default:
|
||||
kind = MethodRecognizer::kUnknown;
|
||||
|
||||
@@ -755,7 +755,7 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
|
||||
const MethodRecognizer::Kind kind = MethodRecognizer::RecognizeKind(function);
|
||||
|
||||
switch (kind) {
|
||||
// On simdbc we fall back to natives.
|
||||
// On simdbc and the bytecode interpreter we fall back to natives.
|
||||
#if !defined(TARGET_ARCH_DBC)
|
||||
case MethodRecognizer::kTypedData_ByteDataView_factory:
|
||||
case MethodRecognizer::kTypedData_Int8ArrayView_factory:
|
||||
@@ -773,6 +773,11 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
|
||||
case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
|
||||
case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
|
||||
#endif // !defined(TARGET_ARCH_DBC)
|
||||
// This list must be kept in sync with BytecodeReaderHelper::NativeEntry in
|
||||
// runtime/vm/compiler/frontend/bytecode_reader.cc and implemented in the
|
||||
// bytecode interpreter in runtime/vm/interpreter.cc. Alternatively, these
|
||||
// methods must work in their original form (a Dart body or native entry) in
|
||||
// the bytecode interpreter.
|
||||
case MethodRecognizer::kObjectEquals:
|
||||
case MethodRecognizer::kStringBaseLength:
|
||||
case MethodRecognizer::kStringBaseIsEmpty:
|
||||
@@ -800,6 +805,7 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
|
||||
case MethodRecognizer::kLinkedHashMap_setUsedData:
|
||||
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
||||
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
||||
case MethodRecognizer::kFfiAbi:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -1062,6 +1068,9 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
|
||||
kNoStoreBarrier);
|
||||
body += NullConstant();
|
||||
break;
|
||||
case MethodRecognizer::kFfiAbi:
|
||||
body += IntConstant(static_cast<int64_t>(compiler::ffi::TargetAbi()));
|
||||
break;
|
||||
default: {
|
||||
UNREACHABLE();
|
||||
break;
|
||||
|
||||
@@ -247,6 +247,7 @@ void MethodRecognizer::Libraries(GrowableArray<Library*>* libs) {
|
||||
libs->Add(&Library::ZoneHandle(Library::InternalLibrary()));
|
||||
libs->Add(&Library::ZoneHandle(Library::DeveloperLibrary()));
|
||||
libs->Add(&Library::ZoneHandle(Library::AsyncLibrary()));
|
||||
libs->Add(&Library::ZoneHandle(Library::FfiLibrary()));
|
||||
}
|
||||
|
||||
RawGrowableObjectArray* MethodRecognizer::QueryRecognizedMethods(Zone* zone) {
|
||||
|
||||
@@ -9,9 +9,11 @@ namespace dart {
|
||||
|
||||
// clang-format off
|
||||
// (class-name, function-name, recognized enum, fingerprint).
|
||||
// When adding a new function add a 0 as fingerprint, build and run to get the
|
||||
// correct fingerprint from the mismatch error (or use Library::GetFunction()
|
||||
// and print func.SourceFingerprint()).
|
||||
// When adding a new function add a 0 as fingerprint, build and run with
|
||||
// `tools/test.py vm/dart/reused_instructions_test` to get the correct
|
||||
// fingerprint from the mismatch error (or use Library::GetFunction() and print
|
||||
// func.SourceFingerprint()).
|
||||
// TODO(36376): Restore checking fingerprints of recognized methods.
|
||||
#define OTHER_RECOGNIZED_LIST(V) \
|
||||
V(::, identical, ObjectIdentical, 0x49c6e96a) \
|
||||
V(ClassID, getID, ClassIDgetID, 0x7b18b257) \
|
||||
@@ -141,6 +143,7 @@ namespace dart {
|
||||
V(_HashVMBase, get:_deletedKeys, LinkedHashMap_getDeletedKeys, 0x558481c2) \
|
||||
V(_HashVMBase, set:_deletedKeys, LinkedHashMap_setDeletedKeys, 0x5aa9888d) \
|
||||
V(::, _classRangeCheck, ClassRangeCheck, 0x2ae76b84) \
|
||||
V(::, _abi, FfiAbi, 0x0) \
|
||||
|
||||
// List of intrinsics:
|
||||
// (class-name, function-name, intrinsification method, fingerprint).
|
||||
@@ -465,6 +468,7 @@ namespace dart {
|
||||
V(_HashVMBase, set:_hashMask, LinkedHashMap_setHashMask, 0x7219c45b) \
|
||||
V(_HashVMBase, get:_deletedKeys, LinkedHashMap_getDeletedKeys, 0x558481c2) \
|
||||
V(_HashVMBase, set:_deletedKeys, LinkedHashMap_setDeletedKeys, 0x5aa9888d) \
|
||||
V(::, _abi, FfiAbi, 0x0) \
|
||||
|
||||
// A list of core function that should never be inlined.
|
||||
#define INLINE_BLACK_LIST(V) \
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <setjmp.h> // NOLINT
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "vm/compiler/ffi.h"
|
||||
#include "vm/globals.h"
|
||||
#if !defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
||||
@@ -1386,7 +1387,7 @@ DART_NOINLINE bool Interpreter::AllocateMint(Thread* thread,
|
||||
} else {
|
||||
SP[0] = 0; // Space for the result.
|
||||
SP[1] = thread->isolate()->object_store()->mint_class(); // Class object.
|
||||
SP[2] = Object::null(); // Type arguments.
|
||||
SP[2] = Object::null(); // Type arguments.
|
||||
Exit(thread, FP, SP + 3, pc);
|
||||
NativeArguments args(thread, 2, SP + 1, SP);
|
||||
if (!InvokeRuntime(thread, this, DRT_AllocateObject, args)) {
|
||||
@@ -1574,8 +1575,8 @@ RawObject* Interpreter::Call(RawFunction* function,
|
||||
Thread* thread) {
|
||||
// Interpreter state (see constants_kbc.h for high-level overview).
|
||||
const KBCInstr* pc; // Program Counter: points to the next op to execute.
|
||||
RawObject** FP; // Frame Pointer.
|
||||
RawObject** SP; // Stack Pointer.
|
||||
RawObject** FP; // Frame Pointer.
|
||||
RawObject** SP; // Stack Pointer.
|
||||
|
||||
uint32_t op; // Currently executing op.
|
||||
|
||||
@@ -2204,6 +2205,9 @@ SwitchDispatch:
|
||||
SP[0];
|
||||
*--SP = null_value;
|
||||
} break;
|
||||
case MethodRecognizer::kFfiAbi: {
|
||||
*++SP = Smi::New(static_cast<int64_t>(compiler::ffi::TargetAbi()));
|
||||
} break;
|
||||
default: {
|
||||
NativeEntryData::Payload* payload =
|
||||
NativeEntryData::FromTypedArray(data);
|
||||
|
||||
@@ -8065,8 +8065,9 @@ void Function::SetDeoptReasonForAll(intptr_t deopt_id,
|
||||
}
|
||||
|
||||
bool Function::CheckSourceFingerprint(const char* prefix, int32_t fp) const {
|
||||
// TODO(alexmarkov): '(kernel_offset() <= 0)' looks like an impossible
|
||||
// condition, fix this and re-enable fingerprints checking.
|
||||
// TODO(36376): Restore checking fingerprints of recognized methods.
|
||||
// '(kernel_offset() <= 0)' looks like an impossible condition, fix this and
|
||||
// re-enable fingerprints checking.
|
||||
if (!Isolate::Current()->obfuscate() && !is_declared_in_bytecode() &&
|
||||
(kernel_offset() <= 0) && (SourceFingerprint() != fp)) {
|
||||
const bool recalculatingFingerprints = false;
|
||||
@@ -12494,6 +12495,7 @@ void Library::CheckFunctionFingerprints() {
|
||||
all_libs.Add(&Library::ZoneHandle(Library::TypedDataLibrary()));
|
||||
all_libs.Add(&Library::ZoneHandle(Library::CollectionLibrary()));
|
||||
all_libs.Add(&Library::ZoneHandle(Library::InternalLibrary()));
|
||||
all_libs.Add(&Library::ZoneHandle(Library::FfiLibrary()));
|
||||
OTHER_RECOGNIZED_LIST(CHECK_FINGERPRINTS2);
|
||||
INLINE_WHITE_LIST(CHECK_FINGERPRINTS);
|
||||
INLINE_BLACK_LIST(CHECK_FINGERPRINTS);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// Dart test program for testing that structs are locked out on 32-bit platforms.
|
||||
|
||||
library FfiTest;
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
class C extends Struct<C> {
|
||||
@IntPtr()
|
||||
int x;
|
||||
}
|
||||
|
||||
void main() {
|
||||
final C c = nullptr.cast<C>().load();
|
||||
Expect.throws<UnimplementedError>(() => c.x);
|
||||
Expect.throws<UnimplementedError>(() => c.x = 0);
|
||||
}
|
||||
@@ -33,13 +33,5 @@ function_callbacks_test: Skip # Issue dartbug.com/37295
|
||||
[ $system != android && $system != linux && $system != macos && $system != windows ]
|
||||
*: Skip # FFI not yet supported on other OSes.
|
||||
|
||||
# dartbug.com/35768: Structs not supported on 32-bit.
|
||||
[ $arch == arm || $arch == ia32 || $arch == simdbc ]
|
||||
function_structs_test: Skip
|
||||
structs_test: Skip
|
||||
|
||||
[ $arch == arm64 || $arch == simdbc64 || $arch == x64 ]
|
||||
enable_structs_test: SkipByDesign # Tests that structs don't work on 32-bit systems.
|
||||
|
||||
[ $arch == simarm || $arch == simarm64 ]
|
||||
*: Skip # FFI not yet supported on the arm simulator.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// Dart test program for testing dart:ffi struct pointers.
|
||||
//
|
||||
// VMOptions=--deterministic --optimization-counter-threshold=50 --enable-inlining-annotations
|
||||
|
||||
library FfiTest;
|
||||
|
||||
@@ -15,12 +17,14 @@ import 'coordinate.dart';
|
||||
import 'utf8.dart';
|
||||
|
||||
void main() {
|
||||
testStructAllocate();
|
||||
testStructFromAddress();
|
||||
testStructWithNulls();
|
||||
testBareStruct();
|
||||
testTypeTest();
|
||||
testUtf8();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
testStructAllocate();
|
||||
testStructFromAddress();
|
||||
testStructWithNulls();
|
||||
testBareStruct();
|
||||
testTypeTest();
|
||||
testUtf8();
|
||||
}
|
||||
}
|
||||
|
||||
/// allocates each coordinate separately in c memory
|
||||
|
||||
@@ -35,10 +35,10 @@ class VeryLargeStruct extends Struct<VeryLargeStruct> {
|
||||
@IntPtr()
|
||||
int i;
|
||||
|
||||
@Float()
|
||||
@Double()
|
||||
double j;
|
||||
|
||||
@Double()
|
||||
@Float()
|
||||
double k;
|
||||
|
||||
Pointer<VeryLargeStruct> parent;
|
||||
|
||||
Reference in New Issue
Block a user