[vm/ffi] Optimize Pointer operations for statically known types
This CL optimizes Pointer operations in hot loops for Pointer<NativeInteger/NativeDouble/Pointer> (not for structs). Design: go/dart-ffi-pointers-il It provides roughly a 100x speedup for the FfiMemory benchmark. The next 5x speedup is to get rid of allocations due to `load` and `store` not being inlined. FFI API is changed to enable optimizations: * Disable dynamic invocations of Pointer.load / Pointer.store. * Disallow implicit downcast of argument passed to Pointer.store. * Stop zeroing out Pointer.address on Pointer.free(). Issue: https://github.com/dart-lang/sdk/issues/38172 Related issues: Closes: https://github.com/dart-lang/sdk/issues/35902 (Disallowing dynamic invocations of Pointer ops.) Closes: https://github.com/dart-lang/sdk/issues/37385 (Function variance checking.) Change-Id: I96058d8b5b49052eb6999f084372e6f08b4f6f17 Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-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-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/117547 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
ebc0a88154
commit
3712ed27ef
@@ -154,6 +154,22 @@ const nonSizeAlignment = <Abi, Map<NativeType, int>>{
|
||||
Abi.wordSize32Align64: {},
|
||||
};
|
||||
|
||||
/// Load, store, and elementAt are rewired to their static type for these types.
|
||||
const List<NativeType> optimizedTypes = [
|
||||
NativeType.kInt8,
|
||||
NativeType.kInt16,
|
||||
NativeType.kInt32,
|
||||
NativeType.kInt64,
|
||||
NativeType.kUint8,
|
||||
NativeType.kUint16,
|
||||
NativeType.kUint32,
|
||||
NativeType.kUnit64,
|
||||
NativeType.kIntptr,
|
||||
NativeType.kFloat,
|
||||
NativeType.kDouble,
|
||||
NativeType.kPointer,
|
||||
];
|
||||
|
||||
/// [FfiTransformer] contains logic which is shared between
|
||||
/// _FfiUseSiteTransformer and _FfiDefinitionTransformer.
|
||||
class FfiTransformer extends Transformer {
|
||||
@@ -178,6 +194,7 @@ class FfiTransformer extends Transformer {
|
||||
final Procedure loadMethod;
|
||||
final Procedure storeMethod;
|
||||
final Procedure offsetByMethod;
|
||||
final Procedure elementAtMethod;
|
||||
final Procedure asFunctionMethod;
|
||||
final Procedure asFunctionInternal;
|
||||
final Procedure lookupFunctionMethod;
|
||||
@@ -188,6 +205,10 @@ class FfiTransformer extends Transformer {
|
||||
final Procedure abiMethod;
|
||||
final Procedure pointerFromFunctionProcedure;
|
||||
final Procedure nativeCallbackFunctionProcedure;
|
||||
final Map<NativeType, Procedure> loadMethods;
|
||||
final Map<NativeType, Procedure> storeMethods;
|
||||
final Map<NativeType, Procedure> elementAtMethods;
|
||||
final Procedure loadStructMethod;
|
||||
|
||||
/// Classes corresponding to [NativeType], indexed by [NativeType].
|
||||
final List<Class> nativeTypesClasses;
|
||||
@@ -209,6 +230,7 @@ class FfiTransformer extends Transformer {
|
||||
loadMethod = index.getMember('dart:ffi', 'Pointer', 'load'),
|
||||
storeMethod = index.getMember('dart:ffi', 'Pointer', 'store'),
|
||||
offsetByMethod = index.getMember('dart:ffi', 'Pointer', 'offsetBy'),
|
||||
elementAtMethod = index.getMember('dart:ffi', 'Pointer', 'elementAt'),
|
||||
addressOfField = index.getMember('dart:ffi', 'Struct', 'addressOf'),
|
||||
structFromPointer =
|
||||
index.getMember('dart:ffi', 'Struct', 'fromPointer'),
|
||||
@@ -228,7 +250,20 @@ class FfiTransformer extends Transformer {
|
||||
index.getTopLevelMember('dart:ffi', '_nativeCallbackFunction'),
|
||||
nativeTypesClasses = nativeTypeClassNames
|
||||
.map((name) => index.getClass('dart:ffi', name))
|
||||
.toList();
|
||||
.toList(),
|
||||
loadMethods = Map.fromIterable(optimizedTypes, value: (t) {
|
||||
final name = nativeTypeClassNames[t.index];
|
||||
return index.getTopLevelMember('dart:ffi', "_load$name");
|
||||
}),
|
||||
storeMethods = Map.fromIterable(optimizedTypes, value: (t) {
|
||||
final name = nativeTypeClassNames[t.index];
|
||||
return index.getTopLevelMember('dart:ffi', "_store$name");
|
||||
}),
|
||||
elementAtMethods = Map.fromIterable(optimizedTypes, value: (t) {
|
||||
final name = nativeTypeClassNames[t.index];
|
||||
return index.getTopLevelMember('dart:ffi', "_elementAt$name");
|
||||
}),
|
||||
loadStructMethod = index.getTopLevelMember('dart:ffi', '_loadStruct');
|
||||
|
||||
/// Computes the Dart type corresponding to a ffi.[NativeType], returns null
|
||||
/// if it is not a valid NativeType.
|
||||
|
||||
@@ -30,7 +30,8 @@ import 'ffi.dart'
|
||||
NativeType,
|
||||
kNativeTypeIntStart,
|
||||
kNativeTypeIntEnd,
|
||||
FfiTransformer;
|
||||
FfiTransformer,
|
||||
optimizedTypes;
|
||||
|
||||
/// Checks and replaces calls to dart:ffi struct fields and methods.
|
||||
void transformLibraries(
|
||||
@@ -316,8 +317,6 @@ class _FfiUseSiteTransformer extends FfiTransformer {
|
||||
return StaticInvocation(asFunctionInternal,
|
||||
Arguments([node.receiver], types: [dartType, nativeSignature]));
|
||||
} else if (target == loadMethod) {
|
||||
// TODO(dacoharkes): should load and store be generic?
|
||||
// https://github.com/dart-lang/sdk/issues/35902
|
||||
final DartType dartType = node.arguments.types[0];
|
||||
final DartType pointerType = node.receiver.getStaticType(env);
|
||||
final DartType nativeType = _pointerTypeGetTypeArg(pointerType);
|
||||
@@ -327,11 +326,20 @@ class _FfiUseSiteTransformer extends FfiTransformer {
|
||||
_ensureNativeTypeSized(nativeType, node, target.name);
|
||||
_ensureNativeTypeToDartType(nativeType, dartType, node,
|
||||
allowStructs: true);
|
||||
|
||||
// TODO(37773): When moving to extension methods we can get rid of
|
||||
// this rewiring.
|
||||
final Class nativeClass = (nativeType as InterfaceType).classNode;
|
||||
final NativeType nt = getType(nativeClass);
|
||||
final typeArguments = [
|
||||
if (nt == NativeType.kPointer) _pointerTypeGetTypeArg(nativeType)
|
||||
];
|
||||
return StaticInvocation(
|
||||
optimizedTypes.contains(nt) ? loadMethods[nt] : loadStructMethod,
|
||||
Arguments([node.receiver], types: typeArguments));
|
||||
} else if (target == storeMethod) {
|
||||
// TODO(dacoharkes): should load and store permitted to be generic?
|
||||
// https://github.com/dart-lang/sdk/issues/35902
|
||||
final DartType dartType =
|
||||
node.arguments.positional[0].getStaticType(env);
|
||||
final Expression storeValue = node.arguments.positional.single;
|
||||
final DartType dartType = storeValue.getStaticType(env);
|
||||
final DartType pointerType = node.receiver.getStaticType(env);
|
||||
final DartType nativeType = _pointerTypeGetTypeArg(pointerType);
|
||||
|
||||
@@ -341,6 +349,32 @@ class _FfiUseSiteTransformer extends FfiTransformer {
|
||||
_ensureNativeTypeValid(nativeType, node);
|
||||
_ensureNativeTypeSized(nativeType, node, target.name);
|
||||
_ensureNativeTypeToDartType(nativeType, dartType, node);
|
||||
|
||||
// TODO(37773): When moving to extension methods we can get rid of
|
||||
// this rewiring.
|
||||
final Class nativeClass = (nativeType as InterfaceType).classNode;
|
||||
final NativeType nt = getType(nativeClass);
|
||||
final typeArguments = [
|
||||
if (nt == NativeType.kPointer) _pointerTypeGetTypeArg(nativeType)
|
||||
];
|
||||
return StaticInvocation(storeMethods[nt],
|
||||
Arguments([node.receiver, storeValue], types: typeArguments));
|
||||
} else if (target == elementAtMethod) {
|
||||
// TODO(37773): When moving to extension methods we can get rid of
|
||||
// this rewiring.
|
||||
final DartType pointerType = node.receiver.getStaticType(env);
|
||||
final DartType nativeType = _pointerTypeGetTypeArg(pointerType);
|
||||
final Class nativeClass = (nativeType as InterfaceType).classNode;
|
||||
final NativeType nt = getType(nativeClass);
|
||||
if (optimizedTypes.contains(nt)) {
|
||||
final typeArguments = [
|
||||
if (nt == NativeType.kPointer) _pointerTypeGetTypeArg(nativeType)
|
||||
];
|
||||
return StaticInvocation(
|
||||
elementAtMethods[nt],
|
||||
Arguments([node.receiver, node.arguments.positional[0]],
|
||||
types: typeArguments));
|
||||
}
|
||||
}
|
||||
} on _FfiStaticTypeError {
|
||||
// It's OK to swallow the exception because the diagnostics issued will
|
||||
@@ -361,9 +395,7 @@ class _FfiUseSiteTransformer extends FfiTransformer {
|
||||
final DartType shouldBeElementType =
|
||||
convertNativeTypeToDartType(containerTypeArg, allowStructs);
|
||||
if (elementType == shouldBeElementType) return;
|
||||
// Both subtypes and implicit downcasts are allowed statically.
|
||||
if (env.isSubtypeOf(shouldBeElementType, elementType,
|
||||
SubtypeCheckMode.ignoringNullabilities)) return;
|
||||
// We disable implicit downcasts, they will go away when NNBD lands.
|
||||
if (env.isSubtypeOf(elementType, shouldBeElementType,
|
||||
SubtypeCheckMode.ignoringNullabilities)) return;
|
||||
diagnosticReporter.report(
|
||||
|
||||
+94
-195
@@ -48,105 +48,6 @@ static void CheckSized(const AbstractType& type_arg) {
|
||||
}
|
||||
}
|
||||
|
||||
enum class FfiVariance { kCovariant = 0, kContravariant = 1 };
|
||||
|
||||
// Checks that a dart type correspond to a [NativeType].
|
||||
// Because this is checked already in a kernel transformation, it does not throw
|
||||
// an ArgumentException but a boolean which should be asserted.
|
||||
//
|
||||
// [Int8] -> [int]
|
||||
// [Int16] -> [int]
|
||||
// [Int32] -> [int]
|
||||
// [Int64] -> [int]
|
||||
// [Uint8] -> [int]
|
||||
// [Uint16] -> [int]
|
||||
// [Uint32] -> [int]
|
||||
// [Uint64] -> [int]
|
||||
// [IntPtr] -> [int]
|
||||
// [Double] -> [double]
|
||||
// [Float] -> [double]
|
||||
// [Pointer]<T> -> [Pointer]<T>
|
||||
// T extends [Struct] -> T
|
||||
// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
|
||||
// where DartRepresentationOf(Tn) -> Sn
|
||||
static bool DartAndCTypeCorrespond(const AbstractType& native_type,
|
||||
const AbstractType& dart_type,
|
||||
FfiVariance variance) {
|
||||
classid_t native_type_cid = native_type.type_class_id();
|
||||
if (RawObject::IsFfiTypeIntClassId(native_type_cid)) {
|
||||
return dart_type.IsSubtypeOf(AbstractType::Handle(Type::IntType()),
|
||||
Heap::kNew);
|
||||
}
|
||||
if (RawObject::IsFfiTypeDoubleClassId(native_type_cid)) {
|
||||
return dart_type.IsSubtypeOf(AbstractType::Handle(Type::Double()),
|
||||
Heap::kNew);
|
||||
}
|
||||
if (RawObject::IsFfiPointerClassId(native_type_cid)) {
|
||||
return (variance == FfiVariance::kCovariant &&
|
||||
dart_type.IsSubtypeOf(native_type, Heap::kNew)) ||
|
||||
(variance == FfiVariance::kContravariant &&
|
||||
native_type.IsSubtypeOf(dart_type, Heap::kNew)) ||
|
||||
dart_type.IsNullType();
|
||||
}
|
||||
if (RawObject::IsFfiTypeNativeFunctionClassId(native_type_cid)) {
|
||||
if (!dart_type.IsFunctionType()) {
|
||||
return false;
|
||||
}
|
||||
TypeArguments& nativefunction_type_args =
|
||||
TypeArguments::Handle(native_type.arguments());
|
||||
AbstractType& nativefunction_type_arg =
|
||||
AbstractType::Handle(nativefunction_type_args.TypeAt(0));
|
||||
if (!nativefunction_type_arg.IsFunctionType()) {
|
||||
return false;
|
||||
}
|
||||
Function& dart_function =
|
||||
Function::Handle((Type::Cast(dart_type)).signature());
|
||||
if (dart_function.NumTypeParameters() != 0 ||
|
||||
dart_function.HasOptionalPositionalParameters() ||
|
||||
dart_function.HasOptionalNamedParameters()) {
|
||||
return false;
|
||||
}
|
||||
Function& nativefunction_function =
|
||||
Function::Handle(((Type&)nativefunction_type_arg).signature());
|
||||
if (nativefunction_function.NumTypeParameters() != 0 ||
|
||||
nativefunction_function.HasOptionalPositionalParameters() ||
|
||||
nativefunction_function.HasOptionalNamedParameters()) {
|
||||
return false;
|
||||
}
|
||||
if (!(dart_function.NumParameters() ==
|
||||
nativefunction_function.NumParameters())) {
|
||||
return false;
|
||||
}
|
||||
if (!DartAndCTypeCorrespond(
|
||||
AbstractType::Handle(nativefunction_function.result_type()),
|
||||
AbstractType::Handle(dart_function.result_type()), variance)) {
|
||||
return false;
|
||||
}
|
||||
for (intptr_t i = 0; i < dart_function.NumParameters(); i++) {
|
||||
if (!DartAndCTypeCorrespond(
|
||||
AbstractType::Handle(nativefunction_function.ParameterTypeAt(i)),
|
||||
AbstractType::Handle(dart_function.ParameterTypeAt(i)),
|
||||
variance)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CheckDartAndCTypeCorrespond(const AbstractType& native_type,
|
||||
const AbstractType& dart_type,
|
||||
FfiVariance variance) {
|
||||
if (!DartAndCTypeCorrespond(native_type, dart_type, variance)) {
|
||||
const String& error = String::Handle(String::NewFormatted(
|
||||
"Expected type '%s' to be different, it should be "
|
||||
"DartRepresentationOf('%s').",
|
||||
String::Handle(dart_type.UserVisibleName()).ToCString(),
|
||||
String::Handle(native_type.UserVisibleName()).ToCString()));
|
||||
Exceptions::ThrowArgumentError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// The following functions are runtime checks on arguments.
|
||||
|
||||
static const Pointer& AsPointer(const Instance& instance) {
|
||||
@@ -222,38 +123,10 @@ DEFINE_NATIVE_ENTRY(Ffi_fromAddress, 1, 1) {
|
||||
return Pointer::New(type_arg, arg_ptr.AsInt64Value());
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_elementAt, 0, 2) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1));
|
||||
AbstractType& pointer_type_arg =
|
||||
AbstractType::Handle(zone, pointer.type_argument());
|
||||
CheckSized(pointer_type_arg);
|
||||
return Pointer::New(pointer_type_arg,
|
||||
pointer.NativeAddress() +
|
||||
index.AsInt64Value() * SizeOf(pointer_type_arg));
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_offsetBy, 0, 2) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Integer, offset, arguments->NativeArgAt(1));
|
||||
AbstractType& pointer_type_arg =
|
||||
AbstractType::Handle(pointer.type_argument());
|
||||
|
||||
return Pointer::New(pointer_type_arg,
|
||||
pointer.NativeAddress() + offset.AsInt64Value());
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_cast, 1, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
|
||||
return Pointer::New(type_arg, pointer.NativeAddress());
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_free, 0, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
|
||||
free(reinterpret_cast<void*>(pointer.NativeAddress()));
|
||||
pointer.SetNativeAddress(0);
|
||||
|
||||
return Object::null();
|
||||
}
|
||||
@@ -263,11 +136,10 @@ DEFINE_NATIVE_ENTRY(Ffi_address, 0, 1) {
|
||||
return Integer::New(pointer.NativeAddress());
|
||||
}
|
||||
|
||||
static RawObject* LoadValue(Zone* zone,
|
||||
const Pointer& target,
|
||||
const AbstractType& instance_type_arg) {
|
||||
classid_t type_cid = instance_type_arg.type_class_id();
|
||||
size_t address = target.NativeAddress();
|
||||
static RawObject* LoadValueNumeric(Zone* zone,
|
||||
const Pointer& target,
|
||||
classid_t type_cid) {
|
||||
const size_t address = target.NativeAddress();
|
||||
switch (type_cid) {
|
||||
case kFfiInt8Cid:
|
||||
return Integer::New(*reinterpret_cast<int8_t*>(address));
|
||||
@@ -291,61 +163,73 @@ static RawObject* LoadValue(Zone* zone,
|
||||
return Double::New(*reinterpret_cast<float_t*>(address));
|
||||
case kFfiDoubleCid:
|
||||
return Double::New(*reinterpret_cast<double_t*>(address));
|
||||
default: {
|
||||
if (IsPointerType(instance_type_arg)) {
|
||||
const AbstractType& type_arg = AbstractType::Handle(
|
||||
TypeArguments::Handle(instance_type_arg.arguments())
|
||||
.TypeAt(Pointer::kNativeTypeArgPos));
|
||||
return Pointer::New(type_arg, reinterpret_cast<size_t>(
|
||||
*reinterpret_cast<void**>(address)));
|
||||
} else {
|
||||
// Result is a struct class -- find <class name>.#fromPointer
|
||||
// constructor and call it.
|
||||
Class& cls = Class::Handle(zone, instance_type_arg.type_class());
|
||||
const Function& constructor =
|
||||
Function::Handle(cls.LookupFunctionAllowPrivate(String::Handle(
|
||||
String::Concat(String::Handle(String::Concat(
|
||||
String::Handle(cls.Name()), Symbols::Dot())),
|
||||
Symbols::StructFromPointer()))));
|
||||
ASSERT(!constructor.IsNull());
|
||||
ASSERT(constructor.IsGenerativeConstructor());
|
||||
ASSERT(!Object::Handle(constructor.VerifyCallEntryPoint()).IsError());
|
||||
Instance& new_object = Instance::Handle(Instance::New(cls));
|
||||
new_object.SetTypeArguments(
|
||||
TypeArguments::Handle(instance_type_arg.arguments()));
|
||||
ASSERT(cls.is_allocated() ||
|
||||
Dart::vm_snapshot_kind() != Snapshot::kFullAOT);
|
||||
const Array& args = Array::Handle(zone, Array::New(2));
|
||||
args.SetAt(0, new_object);
|
||||
args.SetAt(1, target);
|
||||
Object& constructorResult =
|
||||
Object::Handle(DartEntry::InvokeFunction(constructor, args));
|
||||
ASSERT(!constructorResult.IsError());
|
||||
return new_object.raw();
|
||||
}
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_load, 1, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0));
|
||||
AbstractType& pointer_type_arg =
|
||||
AbstractType::Handle(pointer.type_argument());
|
||||
CheckSized(pointer_type_arg);
|
||||
CheckDartAndCTypeCorrespond(pointer_type_arg, type_arg,
|
||||
FfiVariance::kContravariant);
|
||||
#define DEFINE_NATIVE_ENTRY_LOAD(type) \
|
||||
DEFINE_NATIVE_ENTRY(Ffi_load##type, 0, 1) { \
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); \
|
||||
return LoadValueNumeric(zone, pointer, kFfi##type##Cid); \
|
||||
}
|
||||
CLASS_LIST_FFI_NUMERIC(DEFINE_NATIVE_ENTRY_LOAD)
|
||||
#undef DEFINE_NATIVE_ENTRY_LOAD
|
||||
|
||||
return LoadValue(zone, pointer, pointer_type_arg);
|
||||
DEFINE_NATIVE_ENTRY(Ffi_loadPointer, 1, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
const auto& pointer_type_arg =
|
||||
AbstractType::Handle(zone, pointer.type_argument());
|
||||
|
||||
const auto& type_arg =
|
||||
AbstractType::Handle(TypeArguments::Handle(pointer_type_arg.arguments())
|
||||
.TypeAt(Pointer::kNativeTypeArgPos));
|
||||
|
||||
const size_t address = pointer.NativeAddress();
|
||||
|
||||
return Pointer::New(type_arg, *reinterpret_cast<uword*>(address));
|
||||
}
|
||||
|
||||
static void StoreValue(Zone* zone,
|
||||
const Pointer& pointer,
|
||||
classid_t type_cid,
|
||||
const Instance& new_value) {
|
||||
uint8_t* const address = reinterpret_cast<uint8_t*>(pointer.NativeAddress());
|
||||
AbstractType& pointer_type_arg =
|
||||
static RawObject* LoadValueStruct(Zone* zone,
|
||||
const Pointer& target,
|
||||
const AbstractType& instance_type_arg) {
|
||||
// Result is a struct class -- find <class name>.#fromPointer
|
||||
// constructor and call it.
|
||||
const Class& cls = Class::Handle(zone, instance_type_arg.type_class());
|
||||
const Function& constructor =
|
||||
Function::Handle(cls.LookupFunctionAllowPrivate(String::Handle(
|
||||
String::Concat(String::Handle(String::Concat(
|
||||
String::Handle(cls.Name()), Symbols::Dot())),
|
||||
Symbols::StructFromPointer()))));
|
||||
ASSERT(!constructor.IsNull());
|
||||
ASSERT(constructor.IsGenerativeConstructor());
|
||||
ASSERT(!Object::Handle(constructor.VerifyCallEntryPoint()).IsError());
|
||||
const Instance& new_object = Instance::Handle(Instance::New(cls));
|
||||
new_object.SetTypeArguments(
|
||||
TypeArguments::Handle(instance_type_arg.arguments()));
|
||||
ASSERT(cls.is_allocated() || Dart::vm_snapshot_kind() != Snapshot::kFullAOT);
|
||||
const Array& args = Array::Handle(zone, Array::New(2));
|
||||
args.SetAt(0, new_object);
|
||||
args.SetAt(1, target);
|
||||
const Object& constructorResult =
|
||||
Object::Handle(DartEntry::InvokeFunction(constructor, args));
|
||||
ASSERT(!constructorResult.IsError());
|
||||
return new_object.raw();
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_loadStruct, 0, 1) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
const AbstractType& pointer_type_arg =
|
||||
AbstractType::Handle(pointer.type_argument());
|
||||
|
||||
return LoadValueStruct(zone, pointer, pointer_type_arg);
|
||||
}
|
||||
|
||||
static void StoreValueNumeric(Zone* zone,
|
||||
const Pointer& pointer,
|
||||
classid_t type_cid,
|
||||
const Instance& new_value) {
|
||||
uint8_t* const address = reinterpret_cast<uint8_t*>(pointer.NativeAddress());
|
||||
switch (type_cid) {
|
||||
case kFfiInt8Cid:
|
||||
*reinterpret_cast<int8_t*>(address) = AsInteger(new_value).AsInt64Value();
|
||||
@@ -388,28 +272,31 @@ static void StoreValue(Zone* zone,
|
||||
case kFfiDoubleCid:
|
||||
*reinterpret_cast<double*>(address) = AsDouble(new_value).value();
|
||||
break;
|
||||
case kFfiPointerCid: {
|
||||
ASSERT(IsPointerType(pointer_type_arg));
|
||||
ASSERT(new_value.IsPointer());
|
||||
const void* const stored =
|
||||
reinterpret_cast<void*>(AsPointer(new_value).NativeAddress());
|
||||
*reinterpret_cast<const void**>(address) = stored;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_store, 0, 2) {
|
||||
#define DEFINE_NATIVE_ENTRY_STORE(type) \
|
||||
DEFINE_NATIVE_ENTRY(Ffi_store##type, 0, 2) { \
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); \
|
||||
GET_NATIVE_ARGUMENT(Instance, new_value, arguments->NativeArgAt(1)); \
|
||||
if (new_value.IsNull()) { \
|
||||
const String& error = String::Handle( \
|
||||
String::NewFormatted("Argument to Pointer.store is null.")); \
|
||||
Exceptions::ThrowArgumentError(error); \
|
||||
} \
|
||||
StoreValueNumeric(zone, pointer, kFfi##type##Cid, new_value); \
|
||||
return Object::null(); \
|
||||
}
|
||||
CLASS_LIST_FFI_NUMERIC(DEFINE_NATIVE_ENTRY_STORE)
|
||||
#undef DEFINE_NATIVE_ENTRY_STORE
|
||||
|
||||
DEFINE_NATIVE_ENTRY(Ffi_storePointer, 0, 2) {
|
||||
GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0));
|
||||
GET_NATIVE_ARGUMENT(Instance, new_value, arguments->NativeArgAt(1));
|
||||
AbstractType& arg_type = AbstractType::Handle(new_value.GetType(Heap::kNew));
|
||||
AbstractType& pointer_type_arg =
|
||||
AbstractType::Handle(pointer.type_argument());
|
||||
CheckSized(pointer_type_arg);
|
||||
CheckDartAndCTypeCorrespond(pointer_type_arg, arg_type,
|
||||
FfiVariance::kCovariant);
|
||||
|
||||
if (new_value.IsNull()) {
|
||||
const String& error = String::Handle(
|
||||
@@ -417,8 +304,20 @@ DEFINE_NATIVE_ENTRY(Ffi_store, 0, 2) {
|
||||
Exceptions::ThrowArgumentError(error);
|
||||
}
|
||||
|
||||
classid_t type_cid = pointer_type_arg.type_class_id();
|
||||
StoreValue(zone, pointer, type_cid, new_value);
|
||||
auto& new_value_type =
|
||||
AbstractType::Handle(zone, new_value.GetType(Heap::kNew));
|
||||
if (!new_value_type.IsSubtypeOf(pointer_type_arg, Heap::kNew)) {
|
||||
const String& error = String::Handle(String::NewFormatted(
|
||||
"New value (%s) is not a subtype of '%s'.",
|
||||
String::Handle(new_value_type.UserVisibleName()).ToCString(),
|
||||
String::Handle(pointer_type_arg.UserVisibleName()).ToCString()));
|
||||
Exceptions::ThrowArgumentError(error);
|
||||
}
|
||||
|
||||
ASSERT(IsPointerType(pointer_type_arg));
|
||||
ASSERT(new_value.IsPointer());
|
||||
uword* slot = reinterpret_cast<uword*>(pointer.NativeAddress());
|
||||
*slot = AsPointer(new_value).NativeAddress();
|
||||
return Object::null();
|
||||
}
|
||||
|
||||
|
||||
@@ -372,13 +372,33 @@ namespace dart {
|
||||
V(VMService_spawnUriNotify, 2) \
|
||||
V(Ffi_allocate, 1) \
|
||||
V(Ffi_free, 1) \
|
||||
V(Ffi_load, 1) \
|
||||
V(Ffi_store, 2) \
|
||||
V(Ffi_loadInt8, 1) \
|
||||
V(Ffi_loadInt16, 1) \
|
||||
V(Ffi_loadInt32, 1) \
|
||||
V(Ffi_loadInt64, 1) \
|
||||
V(Ffi_loadUint8, 1) \
|
||||
V(Ffi_loadUint16, 1) \
|
||||
V(Ffi_loadUint32, 1) \
|
||||
V(Ffi_loadUint64, 1) \
|
||||
V(Ffi_loadIntPtr, 1) \
|
||||
V(Ffi_loadFloat, 1) \
|
||||
V(Ffi_loadDouble, 1) \
|
||||
V(Ffi_loadPointer, 1) \
|
||||
V(Ffi_loadStruct, 1) \
|
||||
V(Ffi_storeInt8, 2) \
|
||||
V(Ffi_storeInt16, 2) \
|
||||
V(Ffi_storeInt32, 2) \
|
||||
V(Ffi_storeInt64, 2) \
|
||||
V(Ffi_storeUint8, 2) \
|
||||
V(Ffi_storeUint16, 2) \
|
||||
V(Ffi_storeUint32, 2) \
|
||||
V(Ffi_storeUint64, 2) \
|
||||
V(Ffi_storeIntPtr, 2) \
|
||||
V(Ffi_storeFloat, 2) \
|
||||
V(Ffi_storeDouble, 2) \
|
||||
V(Ffi_storePointer, 2) \
|
||||
V(Ffi_address, 1) \
|
||||
V(Ffi_fromAddress, 1) \
|
||||
V(Ffi_elementAt, 2) \
|
||||
V(Ffi_offsetBy, 2) \
|
||||
V(Ffi_cast, 1) \
|
||||
V(Ffi_sizeOf, 0) \
|
||||
V(Ffi_asFunctionInternal, 1) \
|
||||
V(Ffi_nativeCallbackFunction, 2) \
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace dart {
|
||||
V(Int32x4Array) \
|
||||
V(Float64x2Array)
|
||||
|
||||
#define CLASS_LIST_FFI_TYPE_MARKER(V) \
|
||||
#define CLASS_LIST_FFI_NUMERIC(V) \
|
||||
V(Int8) \
|
||||
V(Int16) \
|
||||
V(Int32) \
|
||||
@@ -120,7 +120,10 @@ namespace dart {
|
||||
V(Uint64) \
|
||||
V(IntPtr) \
|
||||
V(Float) \
|
||||
V(Double) \
|
||||
V(Double)
|
||||
|
||||
#define CLASS_LIST_FFI_TYPE_MARKER(V) \
|
||||
CLASS_LIST_FFI_NUMERIC(V) \
|
||||
V(Void)
|
||||
|
||||
#define CLASS_LIST_FFI(V) \
|
||||
|
||||
@@ -1379,6 +1379,7 @@ void FlowGraphCompiler::EmitMove(Location destination,
|
||||
__ movups(LocationToStackSlotAddress(destination), FpuTMP);
|
||||
}
|
||||
} else {
|
||||
ASSERT(!source.IsInvalid());
|
||||
ASSERT(source.IsConstant());
|
||||
if (destination.IsFpuRegister() || destination.IsDoubleStackSlot()) {
|
||||
Register scratch = tmp->AllocateTemporary();
|
||||
|
||||
@@ -2776,6 +2776,8 @@ bool LoadFieldInstr::IsImmutableLengthLoad() const {
|
||||
case Slot::Kind::kCapturedVariable:
|
||||
case Slot::Kind::kDartField:
|
||||
case Slot::Kind::kPointer_c_memory_address:
|
||||
case Slot::Kind::kType_arguments:
|
||||
case Slot::Kind::kTypeArgumentsIndex:
|
||||
return false;
|
||||
}
|
||||
UNREACHABLE();
|
||||
|
||||
@@ -2137,6 +2137,9 @@ bool FlowGraphDeserializer::ParseSlot(SExpList* list, const Slot** out) {
|
||||
case Slot::Kind::kTypeArguments:
|
||||
*out = &Slot::GetTypeArgumentsSlotAt(thread(), offset);
|
||||
break;
|
||||
case Slot::Kind::kTypeArgumentsIndex:
|
||||
*out = &Slot::GetTypeArgumentsIndexSlot(thread(), offset);
|
||||
break;
|
||||
case Slot::Kind::kCapturedVariable:
|
||||
StoreError(kind_sexp, "unhandled Slot kind");
|
||||
return false;
|
||||
|
||||
@@ -2670,6 +2670,8 @@ void LoadFieldInstr::InferRange(RangeAnalysis* analysis, Range* range) {
|
||||
case Slot::Kind::kPointer_c_memory_address:
|
||||
case Slot::Kind::kTypedDataBase_data_field:
|
||||
case Slot::Kind::kTypedDataView_data:
|
||||
case Slot::Kind::kType_arguments:
|
||||
case Slot::Kind::kTypeArgumentsIndex:
|
||||
// Not an integer valued field.
|
||||
UNREACHABLE();
|
||||
break;
|
||||
|
||||
@@ -171,6 +171,15 @@ const Slot& Slot::GetContextVariableSlotFor(Thread* thread,
|
||||
&variable.name(), /*static_type=*/nullptr));
|
||||
}
|
||||
|
||||
const Slot& Slot::GetTypeArgumentsIndexSlot(Thread* thread, intptr_t index) {
|
||||
const intptr_t offset =
|
||||
compiler::target::TypeArguments::type_at_offset(index);
|
||||
const Slot& slot =
|
||||
Slot(Kind::kTypeArgumentsIndex, IsImmutableBit::encode(true), kDynamicCid,
|
||||
offset, ":argument", /*static_type=*/nullptr);
|
||||
return SlotCache::Instance(thread).Canonicalize(slot);
|
||||
}
|
||||
|
||||
const Slot& Slot::Get(const Field& field,
|
||||
const ParsedFunction* parsed_function) {
|
||||
Thread* thread = Thread::Current();
|
||||
@@ -263,6 +272,7 @@ bool Slot::Equals(const Slot* other) const {
|
||||
|
||||
switch (kind_) {
|
||||
case Kind::kTypeArguments:
|
||||
case Kind::kTypeArgumentsIndex:
|
||||
return (offset_in_bytes_ == other->offset_in_bytes_);
|
||||
|
||||
case Kind::kCapturedVariable:
|
||||
|
||||
@@ -71,7 +71,8 @@ class ParsedFunction;
|
||||
V(ArgumentsDescriptor, type_args_len, Smi, FINAL) \
|
||||
V(ArgumentsDescriptor, positional_count, Smi, FINAL) \
|
||||
V(ArgumentsDescriptor, count, Smi, FINAL) \
|
||||
V(Pointer, c_memory_address, Integer, FINAL)
|
||||
V(Pointer, c_memory_address, Dynamic, FINAL) \
|
||||
V(Type, arguments, TypeArguments, FINAL)
|
||||
|
||||
// Slot is an abstraction that describes an readable (and possibly writeable)
|
||||
// location within an object.
|
||||
@@ -93,6 +94,9 @@ class Slot : public ZoneAllocated {
|
||||
// A slot used to store type arguments.
|
||||
kTypeArguments,
|
||||
|
||||
// A slot at a specific [index] in a [RawTypeArgument] vector.
|
||||
kTypeArgumentsIndex,
|
||||
|
||||
// A slot within a Context object that contains a value of a captured
|
||||
// local variable.
|
||||
kCapturedVariable,
|
||||
@@ -118,6 +122,9 @@ class Slot : public ZoneAllocated {
|
||||
static const Slot& GetTypeArgumentsSlotAt(Thread* thread, intptr_t offset);
|
||||
static const Slot& GetTypeArgumentsSlotFor(Thread* thread, const Class& cls);
|
||||
|
||||
// Returns a slot at a specific [index] in a [RawTypeArgument] vector.
|
||||
static const Slot& GetTypeArgumentsIndexSlot(Thread* thread, intptr_t index);
|
||||
|
||||
// Returns a slot that represents the given captured local variable.
|
||||
static const Slot& GetContextVariableSlotFor(Thread* thread,
|
||||
const LocalVariable& var);
|
||||
@@ -139,6 +146,7 @@ class Slot : public ZoneAllocated {
|
||||
bool IsDartField() const { return kind() == Kind::kDartField; }
|
||||
bool IsLocalVariable() const { return kind() == Kind::kCapturedVariable; }
|
||||
bool IsTypeArguments() const { return kind() == Kind::kTypeArguments; }
|
||||
bool IsArgumentOfType() const { return kind() == Kind::kTypeArgumentsIndex; }
|
||||
|
||||
const char* Name() const;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "platform/globals.h"
|
||||
#include "vm/compiler/backend/locations.h"
|
||||
#include "vm/compiler/method_recognizer.h"
|
||||
#include "vm/compiler/runtime_api.h"
|
||||
#include "vm/compiler/stub_code_compiler.h"
|
||||
#include "vm/growable_array.h"
|
||||
@@ -53,6 +54,56 @@ size_t ElementSizeInBytes(intptr_t class_id) {
|
||||
return element_size_table[index];
|
||||
}
|
||||
|
||||
classid_t ElementTypedDataCid(classid_t class_id) {
|
||||
ASSERT(class_id >= kFfiPointerCid);
|
||||
ASSERT(class_id < kFfiVoidCid);
|
||||
ASSERT(class_id != kFfiNativeFunctionCid);
|
||||
switch (class_id) {
|
||||
case kFfiInt8Cid:
|
||||
return kTypedDataInt8ArrayCid;
|
||||
case kFfiUint8Cid:
|
||||
return kTypedDataUint8ArrayCid;
|
||||
case kFfiInt16Cid:
|
||||
return kTypedDataInt16ArrayCid;
|
||||
case kFfiUint16Cid:
|
||||
return kTypedDataUint16ArrayCid;
|
||||
case kFfiInt32Cid:
|
||||
return kTypedDataInt32ArrayCid;
|
||||
case kFfiUint32Cid:
|
||||
return kTypedDataUint32ArrayCid;
|
||||
case kFfiInt64Cid:
|
||||
return kTypedDataInt64ArrayCid;
|
||||
case kFfiUint64Cid:
|
||||
return kTypedDataUint64ArrayCid;
|
||||
case kFfiIntPtrCid:
|
||||
return target::kWordSize == 4 ? kTypedDataInt32ArrayCid
|
||||
: kTypedDataInt64ArrayCid;
|
||||
case kFfiPointerCid:
|
||||
return target::kWordSize == 4 ? kTypedDataUint32ArrayCid
|
||||
: kTypedDataUint64ArrayCid;
|
||||
case kFfiFloatCid:
|
||||
return kTypedDataFloat32ArrayCid;
|
||||
case kFfiDoubleCid:
|
||||
return kTypedDataFloat64ArrayCid;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
classid_t RecognizedMethodTypeArgCid(MethodRecognizer::Kind kind) {
|
||||
switch (kind) {
|
||||
#define LOAD_STORE(type) \
|
||||
case MethodRecognizer::kFfiLoad##type: \
|
||||
case MethodRecognizer::kFfiStore##type: \
|
||||
return kFfi##type##Cid;
|
||||
CLASS_LIST_FFI_NUMERIC(LOAD_STORE)
|
||||
LOAD_STORE(Pointer)
|
||||
#undef LOAD_STORE
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
// See pkg/vm/lib/transformations/ffi.dart, which makes these assumptions.
|
||||
struct AbiAlignmentDouble {
|
||||
int8_t use_one_byte;
|
||||
@@ -124,8 +175,8 @@ Abi TargetAbi() {
|
||||
|
||||
#if !defined(DART_PRECOMPILED_RUNTIME)
|
||||
|
||||
Representation TypeRepresentation(const AbstractType& result_type) {
|
||||
switch (result_type.type_class_id()) {
|
||||
Representation TypeRepresentation(classid_t class_id) {
|
||||
switch (class_id) {
|
||||
case kFfiFloatCid:
|
||||
return kUnboxedFloat;
|
||||
case kFfiDoubleCid:
|
||||
@@ -142,6 +193,7 @@ Representation TypeRepresentation(const AbstractType& result_type) {
|
||||
case kFfiUint64Cid:
|
||||
return kUnboxedInt64;
|
||||
case kFfiIntPtrCid:
|
||||
return kUnboxedIntPtr;
|
||||
case kFfiPointerCid:
|
||||
case kFfiVoidCid:
|
||||
return kUnboxedFfiIntPtr;
|
||||
@@ -183,7 +235,7 @@ ZoneGrowableArray<Representation>* ArgumentRepresentationsBase(
|
||||
for (intptr_t i = 0; i < num_arguments; i++) {
|
||||
AbstractType& arg_type =
|
||||
AbstractType::Handle(signature.ParameterTypeAt(i + 1));
|
||||
Representation rep = TypeRepresentation(arg_type);
|
||||
Representation rep = TypeRepresentation(arg_type.type_class_id());
|
||||
// In non simulator mode host::CallingConventions == CallingConventions.
|
||||
// In simulator mode convert arguments to host representation.
|
||||
if (rep == kUnboxedFloat && CallingConventions::kAbiSoftFP) {
|
||||
@@ -199,7 +251,7 @@ ZoneGrowableArray<Representation>* ArgumentRepresentationsBase(
|
||||
template <class CallingConventions>
|
||||
Representation ResultRepresentationBase(const Function& signature) {
|
||||
AbstractType& arg_type = AbstractType::Handle(signature.result_type());
|
||||
Representation rep = TypeRepresentation(arg_type);
|
||||
Representation rep = TypeRepresentation(arg_type.type_class_id());
|
||||
if (rep == kUnboxedFloat && CallingConventions::kAbiSoftFP) {
|
||||
rep = kUnboxedInt32;
|
||||
} else if (rep == kUnboxedDouble && CallingConventions::kAbiSoftFP) {
|
||||
|
||||
@@ -25,6 +25,12 @@ constexpr intptr_t kMinimumArgumentWidth = 4;
|
||||
// Storage size for an FFI type (extends 'ffi.NativeType').
|
||||
size_t ElementSizeInBytes(intptr_t class_id);
|
||||
|
||||
// TypedData class id for a NativeType type, except for Void and NativeFunction.
|
||||
classid_t ElementTypedDataCid(classid_t class_id);
|
||||
|
||||
// Returns the kFFi<type>Cid for the recognized load/store method [kind].
|
||||
classid_t RecognizedMethodTypeArgCid(MethodRecognizer::Kind kind);
|
||||
|
||||
// These ABIs should be kept in sync with pkg/vm/lib/transformations/ffi.dart.
|
||||
enum class Abi {
|
||||
kWordSize64 = 0,
|
||||
@@ -36,7 +42,7 @@ enum class Abi {
|
||||
Abi TargetAbi();
|
||||
|
||||
// Unboxed representation of an FFI type (extends 'ffi.NativeType').
|
||||
Representation TypeRepresentation(const AbstractType& result_type);
|
||||
Representation TypeRepresentation(classid_t class_id);
|
||||
|
||||
// Unboxed representation of an FFI type (extends 'ffi.NativeType') for 8 and 16
|
||||
// bit integers.
|
||||
|
||||
@@ -339,6 +339,20 @@ Fragment BaseFlowGraphBuilder::LoadIndexed(intptr_t index_scale) {
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::LoadIndexedTypedData(classid_t class_id) {
|
||||
// We use C behavior when dereferencing pointers, we assume alignment.
|
||||
const AlignmentType alignment = kAlignedAccess;
|
||||
const intptr_t scale = compiler::target::Instance::ElementSizeFor(class_id);
|
||||
|
||||
Value* index = Pop();
|
||||
Value* c_pointer = Pop();
|
||||
LoadIndexedInstr* instr =
|
||||
new (Z) LoadIndexedInstr(c_pointer, index, scale, class_id, alignment,
|
||||
DeoptId::kNone, TokenPosition::kNoSource);
|
||||
Push(instr);
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::LoadUntagged(intptr_t offset) {
|
||||
Value* object = Pop();
|
||||
auto load = new (Z) LoadUntaggedInstr(object, offset);
|
||||
@@ -395,6 +409,21 @@ Fragment BaseFlowGraphBuilder::UnboxSmiToIntptr() {
|
||||
return Fragment(untagged);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::FloatToDouble() {
|
||||
Value* value = Pop();
|
||||
FloatToDoubleInstr* instr = new FloatToDoubleInstr(value, DeoptId::kNone);
|
||||
Push(instr);
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::DoubleToFloat() {
|
||||
Value* value = Pop();
|
||||
DoubleToFloatInstr* instr = new DoubleToFloatInstr(
|
||||
value, DeoptId::kNone, Instruction::SpeculativeMode::kNotSpeculative);
|
||||
Push(instr);
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::LoadField(const Field& field) {
|
||||
return LoadNativeField(Slot::Get(MayCloneField(field), parsed_function_));
|
||||
}
|
||||
@@ -533,7 +562,7 @@ Fragment BaseFlowGraphBuilder::StoreStaticField(TokenPosition position,
|
||||
new (Z) StoreStaticFieldInstr(MayCloneField(field), Pop(), position));
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::StoreIndexed(intptr_t class_id) {
|
||||
Fragment BaseFlowGraphBuilder::StoreIndexed(classid_t class_id) {
|
||||
Value* value = Pop();
|
||||
Value* index = Pop();
|
||||
const StoreBarrierType emit_store_barrier =
|
||||
@@ -546,6 +575,21 @@ Fragment BaseFlowGraphBuilder::StoreIndexed(intptr_t class_id) {
|
||||
return Fragment(store);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::StoreIndexedTypedData(classid_t class_id) {
|
||||
// We use C behavior when dereferencing pointers, we assume alignment.
|
||||
const AlignmentType alignment = kAlignedAccess;
|
||||
const intptr_t scale = compiler::target::Instance::ElementSizeFor(class_id);
|
||||
|
||||
Value* value = Pop();
|
||||
Value* index = Pop();
|
||||
Value* c_pointer = Pop();
|
||||
StoreIndexedInstr* instr = new (Z) StoreIndexedInstr(
|
||||
c_pointer, index, value, kNoStoreBarrier, scale, class_id, alignment,
|
||||
DeoptId::kNone, TokenPosition::kNoSource,
|
||||
Instruction::SpeculativeMode::kNotSpeculative);
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::StoreLocal(TokenPosition position,
|
||||
LocalVariable* variable) {
|
||||
if (variable->is_captured()) {
|
||||
@@ -892,6 +936,7 @@ Fragment BaseFlowGraphBuilder::CheckNull(TokenPosition position,
|
||||
CheckNullInstr* check_null =
|
||||
new (Z) CheckNullInstr(Pop(), function_name, GetNextDeoptId(), position);
|
||||
|
||||
// Does not use the redefinition, no `Push(check_null)`.
|
||||
instructions <<= check_null;
|
||||
|
||||
if (clear_the_temp) {
|
||||
@@ -905,6 +950,15 @@ Fragment BaseFlowGraphBuilder::CheckNull(TokenPosition position,
|
||||
return instructions;
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::CheckNullOptimized(TokenPosition position,
|
||||
const String& function_name) {
|
||||
Value* value = Pop();
|
||||
CheckNullInstr* check_null =
|
||||
new (Z) CheckNullInstr(value, function_name, GetNextDeoptId(), position);
|
||||
Push(check_null); // Use the redefinition.
|
||||
return Fragment(check_null);
|
||||
}
|
||||
|
||||
void BaseFlowGraphBuilder::RecordUncheckedEntryPoint(
|
||||
GraphEntryInstr* graph_entry,
|
||||
FunctionEntryInstr* unchecked_entry) {
|
||||
@@ -1009,6 +1063,27 @@ void BaseFlowGraphBuilder::reset_context_depth_for_deopt_id(intptr_t deopt_id) {
|
||||
}
|
||||
}
|
||||
|
||||
Fragment BaseFlowGraphBuilder::AssertAssignable(
|
||||
TokenPosition position,
|
||||
const AbstractType& dst_type,
|
||||
const String& dst_name,
|
||||
AssertAssignableInstr::Kind kind) {
|
||||
if (!I->should_emit_strong_mode_checks()) {
|
||||
return Drop() + Drop();
|
||||
}
|
||||
|
||||
Value* function_type_args = Pop();
|
||||
Value* instantiator_type_args = Pop();
|
||||
Value* value = Pop();
|
||||
|
||||
AssertAssignableInstr* instr = new (Z) AssertAssignableInstr(
|
||||
position, value, instantiator_type_args, function_type_args, dst_type,
|
||||
dst_name, GetNextDeoptId(), kind);
|
||||
Push(instr);
|
||||
|
||||
return Fragment(instr);
|
||||
}
|
||||
|
||||
} // namespace kernel
|
||||
} // namespace dart
|
||||
|
||||
|
||||
@@ -161,12 +161,16 @@ class BaseFlowGraphBuilder {
|
||||
Fragment LoadField(const Field& field);
|
||||
Fragment LoadNativeField(const Slot& native_field);
|
||||
Fragment LoadIndexed(intptr_t index_scale);
|
||||
// Takes a [class_id] valid for StoreIndexed.
|
||||
Fragment LoadIndexedTypedData(classid_t class_id);
|
||||
|
||||
Fragment LoadUntagged(intptr_t offset);
|
||||
Fragment StoreUntagged(intptr_t offset);
|
||||
Fragment ConvertUntaggedToIntptr();
|
||||
Fragment ConvertIntptrToUntagged();
|
||||
Fragment UnboxSmiToIntptr();
|
||||
Fragment FloatToDouble();
|
||||
Fragment DoubleToFloat();
|
||||
|
||||
Fragment AddIntptrIntegers();
|
||||
|
||||
@@ -192,7 +196,9 @@ class BaseFlowGraphBuilder {
|
||||
Fragment LoadStaticField();
|
||||
Fragment RedefinitionWithType(const AbstractType& type);
|
||||
Fragment StoreStaticField(TokenPosition position, const Field& field);
|
||||
Fragment StoreIndexed(intptr_t class_id);
|
||||
Fragment StoreIndexed(classid_t class_id);
|
||||
// Takes a [class_id] valid for StoreIndexed.
|
||||
Fragment StoreIndexedTypedData(classid_t class_id);
|
||||
|
||||
void Push(Definition* definition);
|
||||
Definition* Peek(intptr_t depth = 0);
|
||||
@@ -333,11 +339,23 @@ class BaseFlowGraphBuilder {
|
||||
// 'function_name' is a selector which is being called (reported in
|
||||
// NoSuchMethod message).
|
||||
// Sets 'receiver' to 'null' after the check if 'clear_the_temp'.
|
||||
// Note that this does _not_ use the result of the CheckNullInstr, so it does
|
||||
// not create a data depedency and might break with code motion.
|
||||
Fragment CheckNull(TokenPosition position,
|
||||
LocalVariable* receiver,
|
||||
const String& function_name,
|
||||
bool clear_the_temp = true);
|
||||
|
||||
// Pops the top of the stack, checks it for null, and pushes the result on
|
||||
// the stack to create a data dependency.
|
||||
// 'function_name' is a selector which is being called (reported in
|
||||
// NoSuchMethod message).
|
||||
// Note that the result can currently only be used in optimized code, because
|
||||
// optimized code uses FlowGraph::RemoveRedefinitions to remove the
|
||||
// redefinitions, while unoptimized code does not.
|
||||
Fragment CheckNullOptimized(TokenPosition position,
|
||||
const String& function_name);
|
||||
|
||||
// Records extra unchecked entry point 'unchecked_entry' in 'graph_entry'.
|
||||
void RecordUncheckedEntryPoint(GraphEntryInstr* graph_entry,
|
||||
FunctionEntryInstr* unchecked_entry);
|
||||
@@ -361,6 +379,14 @@ class BaseFlowGraphBuilder {
|
||||
// _StringBase._interpolate call.
|
||||
Fragment StringInterpolate(TokenPosition position);
|
||||
|
||||
// Pops function type arguments, instantiator type arguments and value; and
|
||||
// type checks value against the type arguments.
|
||||
Fragment AssertAssignable(
|
||||
TokenPosition position,
|
||||
const AbstractType& dst_type,
|
||||
const String& dst_name,
|
||||
AssertAssignableInstr::Kind kind = AssertAssignableInstr::kUnknown);
|
||||
|
||||
// Returns true if we're currently recording deopt_id -> context level
|
||||
// mapping.
|
||||
bool is_recording_context_levels() const {
|
||||
|
||||
@@ -180,17 +180,17 @@ void BytecodeFlowGraphBuilder::AllocateLocalVariables(
|
||||
}
|
||||
|
||||
local_vars_.EnsureLength(num_bytecode_locals, nullptr);
|
||||
for (intptr_t i = num_param_locals; i < num_bytecode_locals; ++i) {
|
||||
String& name =
|
||||
String::ZoneHandle(Z, Symbols::NewFormatted(thread(), "var%" Pd, i));
|
||||
intptr_t idx = num_param_locals;
|
||||
for (; idx < num_bytecode_locals; ++idx) {
|
||||
String& name = String::ZoneHandle(
|
||||
Z, Symbols::NewFormatted(thread(), "var%" Pd, idx));
|
||||
LocalVariable* local = new (Z)
|
||||
LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
||||
name, Object::dynamic_type());
|
||||
local->set_index(VariableIndex(-i));
|
||||
local_vars_[i] = local;
|
||||
local->set_index(VariableIndex(-idx));
|
||||
local_vars_[idx] = local;
|
||||
}
|
||||
|
||||
intptr_t idx = num_bytecode_locals;
|
||||
if (exception_var_ != nullptr) {
|
||||
exception_var_->set_index(VariableIndex(-idx));
|
||||
++idx;
|
||||
@@ -2143,8 +2143,9 @@ void BytecodeFlowGraphBuilder::CreateParameterVariables() {
|
||||
object_pool_ = bytecode.object_pool();
|
||||
bytecode_instr_ = reinterpret_cast<const KBCInstr*>(bytecode.PayloadStart());
|
||||
|
||||
scratch_var_ = parsed_function_->EnsureExpressionTemp();
|
||||
|
||||
if (KernelBytecode::IsEntryOptionalOpcode(bytecode_instr_)) {
|
||||
scratch_var_ = parsed_function_->EnsureExpressionTemp();
|
||||
AllocateParametersAndLocalsForEntryOptional();
|
||||
} else if (KernelBytecode::IsEntryOpcode(bytecode_instr_)) {
|
||||
AllocateLocalVariables(DecodeOperandD());
|
||||
@@ -2155,6 +2156,46 @@ void BytecodeFlowGraphBuilder::CreateParameterVariables() {
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (function().IsGeneric()) {
|
||||
// For recognized methods we generate the IL by hand. Yet we need to find
|
||||
// out which [LocalVariable] is holding the function type arguments. We
|
||||
// scan the bytecode for the CheckFunctionTypeArgs bytecode.
|
||||
//
|
||||
// Note that we cannot add an extra local variable for the type argument
|
||||
// in [AllocateLocalVariables]. We sometimes reuse the same ParsedFunction
|
||||
// multiple times. For non-recognized generic bytecode functions
|
||||
// ParsedFunction::RawTypeArgumentsVariable() is set during flow graph
|
||||
// construction (after local variables are allocated). So the next time,
|
||||
// if ParsedFunction is reused, we would allocate an extra local variable.
|
||||
// TODO(alexmarkov): revise how function type args variable is allocated
|
||||
// and avoid looking at CheckFunctionTypeArgs bytecode.
|
||||
const KBCInstr* instr =
|
||||
reinterpret_cast<const KBCInstr*>(bytecode.PayloadStart());
|
||||
const KBCInstr* end = reinterpret_cast<const KBCInstr*>(
|
||||
bytecode.PayloadStart() + bytecode.Size());
|
||||
|
||||
LocalVariable* type_args_var = nullptr;
|
||||
while (instr < end) {
|
||||
if (KernelBytecode::IsCheckFunctionTypeArgs(instr)) {
|
||||
const intptr_t expected_num_type_args = KernelBytecode::DecodeA(instr);
|
||||
if (expected_num_type_args > 0) { // Exclude weird closure case.
|
||||
type_args_var = LocalVariableAt(KernelBytecode::DecodeE(instr));
|
||||
break;
|
||||
}
|
||||
}
|
||||
instr = KernelBytecode::Next(instr);
|
||||
}
|
||||
|
||||
// Every generic function *must* have a kCheckFunctionTypeArgs bytecode.
|
||||
ASSERT(type_args_var != nullptr);
|
||||
|
||||
// Normally the flow graph building code of bytecode will, as a side-effect
|
||||
// of building the flow graph, register the function type arguments variable
|
||||
// in the [ParsedFunction] (see [BuildCheckFunctionTypeArgs]).
|
||||
parsed_function_->set_function_type_arguments(type_args_var);
|
||||
parsed_function_->SetRawTypeArgumentsVariable(type_args_var);
|
||||
}
|
||||
}
|
||||
|
||||
intptr_t BytecodeFlowGraphBuilder::UpdateScope(
|
||||
|
||||
@@ -3511,7 +3511,7 @@ Fragment StreamingFlowGraphBuilder::BuildAsExpression(TokenPosition* p) {
|
||||
// or explicitly written by the user, in both cases we use an assert
|
||||
// assignable.
|
||||
instructions += LoadLocal(MakeTemporary());
|
||||
instructions += B->AssertAssignable(
|
||||
instructions += B->AssertAssignableLoadTypeArguments(
|
||||
position, type,
|
||||
is_type_error ? Symbols::Empty() : Symbols::InTypeCast(),
|
||||
AssertAssignableInstr::kInsertedByFrontend);
|
||||
|
||||
@@ -726,6 +726,32 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
|
||||
case MethodRecognizer::kLinkedHashMap_getDeletedKeys:
|
||||
case MethodRecognizer::kLinkedHashMap_setDeletedKeys:
|
||||
case MethodRecognizer::kFfiAbi:
|
||||
case MethodRecognizer::kFfiLoadInt8:
|
||||
case MethodRecognizer::kFfiLoadInt16:
|
||||
case MethodRecognizer::kFfiLoadInt32:
|
||||
case MethodRecognizer::kFfiLoadInt64:
|
||||
case MethodRecognizer::kFfiLoadUint8:
|
||||
case MethodRecognizer::kFfiLoadUint16:
|
||||
case MethodRecognizer::kFfiLoadUint32:
|
||||
case MethodRecognizer::kFfiLoadUint64:
|
||||
case MethodRecognizer::kFfiLoadIntPtr:
|
||||
case MethodRecognizer::kFfiLoadFloat:
|
||||
case MethodRecognizer::kFfiLoadDouble:
|
||||
case MethodRecognizer::kFfiLoadPointer:
|
||||
case MethodRecognizer::kFfiStoreInt8:
|
||||
case MethodRecognizer::kFfiStoreInt16:
|
||||
case MethodRecognizer::kFfiStoreInt32:
|
||||
case MethodRecognizer::kFfiStoreInt64:
|
||||
case MethodRecognizer::kFfiStoreUint8:
|
||||
case MethodRecognizer::kFfiStoreUint16:
|
||||
case MethodRecognizer::kFfiStoreUint32:
|
||||
case MethodRecognizer::kFfiStoreUint64:
|
||||
case MethodRecognizer::kFfiStoreIntPtr:
|
||||
case MethodRecognizer::kFfiStoreFloat:
|
||||
case MethodRecognizer::kFfiStoreDouble:
|
||||
case MethodRecognizer::kFfiStorePointer:
|
||||
case MethodRecognizer::kFfiFromAddress:
|
||||
case MethodRecognizer::kFfiGetAddress:
|
||||
return true;
|
||||
case MethodRecognizer::kAsyncStackTraceHelper:
|
||||
return !FLAG_causal_async_stacks;
|
||||
@@ -1020,6 +1046,195 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
|
||||
ASSERT(function.NumParameters() == 0);
|
||||
body += IntConstant(static_cast<int64_t>(compiler::ffi::TargetAbi()));
|
||||
break;
|
||||
case MethodRecognizer::kFfiLoadInt8:
|
||||
case MethodRecognizer::kFfiLoadInt16:
|
||||
case MethodRecognizer::kFfiLoadInt32:
|
||||
case MethodRecognizer::kFfiLoadInt64:
|
||||
case MethodRecognizer::kFfiLoadUint8:
|
||||
case MethodRecognizer::kFfiLoadUint16:
|
||||
case MethodRecognizer::kFfiLoadUint32:
|
||||
case MethodRecognizer::kFfiLoadUint64:
|
||||
case MethodRecognizer::kFfiLoadIntPtr:
|
||||
case MethodRecognizer::kFfiLoadFloat:
|
||||
case MethodRecognizer::kFfiLoadDouble:
|
||||
case MethodRecognizer::kFfiLoadPointer: {
|
||||
const classid_t ffi_type_arg_cid =
|
||||
compiler::ffi::RecognizedMethodTypeArgCid(kind);
|
||||
const classid_t typed_data_cid =
|
||||
compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid);
|
||||
const Representation representation =
|
||||
compiler::ffi::TypeRepresentation(ffi_type_arg_cid);
|
||||
|
||||
// Check Dart signature type.
|
||||
const auto& receiver_type =
|
||||
AbstractType::Handle(function.ParameterTypeAt(0));
|
||||
const auto& type_args = TypeArguments::Handle(receiver_type.arguments());
|
||||
const auto& type_arg = AbstractType::Handle(type_args.TypeAt(0));
|
||||
ASSERT(ffi_type_arg_cid == type_arg.type_class_id());
|
||||
|
||||
ASSERT(function.NumParameters() == 1);
|
||||
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Pointer.
|
||||
body += LoadNativeField(Slot::Pointer_c_memory_address());
|
||||
body += UnboxTruncate(kUnboxedIntPtr); // Truncating, so signed is ok.
|
||||
body += ConvertIntptrToUntagged(); // Requires signed intptr.
|
||||
body += IntConstant(0); // Index.
|
||||
body += LoadIndexedTypedData(typed_data_cid);
|
||||
if (kind == MethodRecognizer::kFfiLoadFloat ||
|
||||
kind == MethodRecognizer::kFfiLoadDouble) {
|
||||
if (kind == MethodRecognizer::kFfiLoadFloat) {
|
||||
body += FloatToDouble();
|
||||
}
|
||||
body += Box(kUnboxedDouble);
|
||||
} else {
|
||||
body += Box(representation);
|
||||
if (kind == MethodRecognizer::kFfiLoadPointer) {
|
||||
const auto class_table = thread_->isolate()->class_table();
|
||||
ASSERT(class_table->HasValidClassAt(kFfiPointerCid));
|
||||
const auto& pointer_class =
|
||||
Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid));
|
||||
|
||||
// We find the reified type to use for the pointer allocation.
|
||||
//
|
||||
// Call sites to this recognized method are guaranteed to pass a
|
||||
// Pointer<Pointer<X>> as RawParameterVariable(0). This function
|
||||
// will return a Pointer<X> object - for which we inspect the
|
||||
// reified type on the argument.
|
||||
//
|
||||
// The following is safe to do, as (1) we are guaranteed to have a
|
||||
// Pointer<Pointer<X>> as argument, and (2) the bound on the pointer
|
||||
// type parameter guarantees X is an interface type.
|
||||
ASSERT(function.NumTypeParameters() == 1);
|
||||
LocalVariable* address = MakeTemporary();
|
||||
body += LoadLocal(parsed_function_->RawParameterVariable(0));
|
||||
body += LoadNativeField(
|
||||
Slot::GetTypeArgumentsSlotFor(thread_, pointer_class));
|
||||
body += LoadNativeField(Slot::GetTypeArgumentsIndexSlot(
|
||||
thread_, Pointer::kNativeTypeArgPos));
|
||||
body += LoadNativeField(Slot::Type_arguments());
|
||||
body += PushArgument(); // We instantiate a Pointer<X>.
|
||||
body += AllocateObject(TokenPosition::kNoSource, pointer_class, 1);
|
||||
LocalVariable* pointer = MakeTemporary();
|
||||
body += LoadLocal(pointer);
|
||||
body += LoadLocal(address);
|
||||
body += StoreInstanceField(TokenPosition::kNoSource,
|
||||
Slot::Pointer_c_memory_address());
|
||||
body += DropTempsPreserveTop(1); // Drop [address] keep [pointer].
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case MethodRecognizer::kFfiStoreInt8:
|
||||
case MethodRecognizer::kFfiStoreInt16:
|
||||
case MethodRecognizer::kFfiStoreInt32:
|
||||
case MethodRecognizer::kFfiStoreInt64:
|
||||
case MethodRecognizer::kFfiStoreUint8:
|
||||
case MethodRecognizer::kFfiStoreUint16:
|
||||
case MethodRecognizer::kFfiStoreUint32:
|
||||
case MethodRecognizer::kFfiStoreUint64:
|
||||
case MethodRecognizer::kFfiStoreIntPtr:
|
||||
case MethodRecognizer::kFfiStoreFloat:
|
||||
case MethodRecognizer::kFfiStoreDouble:
|
||||
case MethodRecognizer::kFfiStorePointer: {
|
||||
const classid_t ffi_type_arg_cid =
|
||||
compiler::ffi::RecognizedMethodTypeArgCid(kind);
|
||||
const classid_t typed_data_cid =
|
||||
compiler::ffi::ElementTypedDataCid(ffi_type_arg_cid);
|
||||
const Representation representation =
|
||||
compiler::ffi::TypeRepresentation(ffi_type_arg_cid);
|
||||
|
||||
// Check Dart signature type.
|
||||
const auto& receiver_type =
|
||||
AbstractType::Handle(function.ParameterTypeAt(0));
|
||||
const auto& type_args = TypeArguments::Handle(receiver_type.arguments());
|
||||
const auto& type_arg = AbstractType::Handle(type_args.TypeAt(0));
|
||||
ASSERT(ffi_type_arg_cid == type_arg.type_class_id());
|
||||
|
||||
LocalVariable* arg_pointer = parsed_function_->RawParameterVariable(0);
|
||||
LocalVariable* arg_value = parsed_function_->RawParameterVariable(1);
|
||||
|
||||
if (kind == MethodRecognizer::kFfiStorePointer) {
|
||||
// Do type check before anything untagged is on the stack.
|
||||
const auto class_table = thread_->isolate()->class_table();
|
||||
ASSERT(class_table->HasValidClassAt(kFfiPointerCid));
|
||||
const auto& pointer_class =
|
||||
Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid));
|
||||
const auto& pointer_type_args =
|
||||
TypeArguments::Handle(pointer_class.type_parameters());
|
||||
const auto& pointer_type_arg =
|
||||
AbstractType::Handle(pointer_type_args.TypeAt(0));
|
||||
|
||||
// The method _storePointer is a top level generic function, not an
|
||||
// instance method on a generic class.
|
||||
ASSERT(!type_arg.IsInstantiated(kFunctions));
|
||||
ASSERT(type_arg.IsInstantiated(kCurrentClass));
|
||||
// But we type check it as a method on a generic class at runtime.
|
||||
body += LoadLocal(arg_value);
|
||||
body += LoadLocal(arg_pointer);
|
||||
// We pass the Pointer type argument as instantiator_type_args.
|
||||
//
|
||||
// Call sites to this recognized method are guaranteed to pass a
|
||||
// Pointer<Pointer<X>> as RawParameterVariable(0). This function
|
||||
// will takes a Pointer<X> object - for which we inspect the
|
||||
// reified type on the argument.
|
||||
//
|
||||
// The following is safe to do, as (1) we are guaranteed to have a
|
||||
// Pointer<Pointer<X>> as argument, and (2) the bound on the pointer
|
||||
// type parameter guarantees X is an interface type.
|
||||
body += LoadNativeField(
|
||||
Slot::GetTypeArgumentsSlotFor(thread_, pointer_class));
|
||||
body += NullConstant(); // function_type_args.
|
||||
body += AssertAssignable(TokenPosition::kNoSource, pointer_type_arg,
|
||||
Symbols::Empty());
|
||||
body += Drop();
|
||||
}
|
||||
|
||||
ASSERT(function.NumParameters() == 2);
|
||||
body += LoadLocal(arg_pointer); // Pointer.
|
||||
body += LoadNativeField(Slot::Pointer_c_memory_address());
|
||||
body += UnboxTruncate(kUnboxedIntPtr); // Truncating, so signed is ok.
|
||||
body += ConvertIntptrToUntagged(); // Requires signed intptr.
|
||||
body += IntConstant(0); // Index.
|
||||
body += LoadLocal(arg_value); // Value.
|
||||
body += CheckNullOptimized(TokenPosition::kNoSource,
|
||||
String::ZoneHandle(Z, function.name()));
|
||||
if (kind == MethodRecognizer::kFfiStorePointer) {
|
||||
body += LoadNativeField(Slot::Pointer_c_memory_address());
|
||||
} else if (kind == MethodRecognizer::kFfiStoreFloat ||
|
||||
kind == MethodRecognizer::kFfiStoreDouble) {
|
||||
body += UnboxTruncate(kUnboxedDouble);
|
||||
if (kind == MethodRecognizer::kFfiStoreFloat) {
|
||||
body += DoubleToFloat();
|
||||
}
|
||||
} else {
|
||||
body += UnboxTruncate(representation);
|
||||
}
|
||||
body += StoreIndexedTypedData(typed_data_cid);
|
||||
body += NullConstant();
|
||||
} break;
|
||||
case MethodRecognizer::kFfiFromAddress: {
|
||||
const auto class_table = thread_->isolate()->class_table();
|
||||
ASSERT(class_table->HasValidClassAt(kFfiPointerCid));
|
||||
const auto& pointer_class =
|
||||
Class::ZoneHandle(H.zone(), class_table->At(kFfiPointerCid));
|
||||
|
||||
ASSERT(function.NumTypeParameters() == 1);
|
||||
ASSERT(function.NumParameters() == 1);
|
||||
body += LoadLocal(parsed_function_->RawTypeArgumentsVariable());
|
||||
body += PushArgument();
|
||||
body += AllocateObject(TokenPosition::kNoSource, pointer_class, 1);
|
||||
body += LoadLocal(MakeTemporary()); // Duplicate Pointer.
|
||||
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Address.
|
||||
body += CheckNullOptimized(TokenPosition::kNoSource,
|
||||
String::ZoneHandle(Z, function.name()));
|
||||
body += StoreInstanceField(TokenPosition::kNoSource,
|
||||
Slot::Pointer_c_memory_address());
|
||||
} break;
|
||||
case MethodRecognizer::kFfiGetAddress: {
|
||||
ASSERT(function.NumParameters() == 1);
|
||||
body += LoadLocal(parsed_function_->RawParameterVariable(0)); // Pointer.
|
||||
body += CheckNullOptimized(TokenPosition::kNoSource,
|
||||
String::ZoneHandle(Z, function.name()));
|
||||
body += LoadNativeField(Slot::Pointer_c_memory_address());
|
||||
} break;
|
||||
default: {
|
||||
UNREACHABLE();
|
||||
break;
|
||||
@@ -1210,44 +1425,37 @@ Fragment FlowGraphBuilder::CheckAssignable(const AbstractType& dst_type,
|
||||
!dst_type.IsVoidType()) {
|
||||
LocalVariable* top_of_stack = MakeTemporary();
|
||||
instructions += LoadLocal(top_of_stack);
|
||||
instructions +=
|
||||
AssertAssignable(TokenPosition::kNoSource, dst_type, dst_name, kind);
|
||||
instructions += AssertAssignableLoadTypeArguments(TokenPosition::kNoSource,
|
||||
dst_type, dst_name, kind);
|
||||
instructions += Drop();
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
Fragment FlowGraphBuilder::AssertAssignable(TokenPosition position,
|
||||
const AbstractType& dst_type,
|
||||
const String& dst_name,
|
||||
AssertAssignableInstr::Kind kind) {
|
||||
Fragment FlowGraphBuilder::AssertAssignableLoadTypeArguments(
|
||||
TokenPosition position,
|
||||
const AbstractType& dst_type,
|
||||
const String& dst_name,
|
||||
AssertAssignableInstr::Kind kind) {
|
||||
if (!I->should_emit_strong_mode_checks()) {
|
||||
return Fragment();
|
||||
}
|
||||
|
||||
Fragment instructions;
|
||||
Value* value = Pop();
|
||||
|
||||
if (!dst_type.IsInstantiated(kCurrentClass)) {
|
||||
instructions += LoadInstantiatorTypeArguments();
|
||||
} else {
|
||||
instructions += NullConstant();
|
||||
}
|
||||
Value* instantiator_type_args = Pop();
|
||||
|
||||
if (!dst_type.IsInstantiated(kFunctions)) {
|
||||
instructions += LoadFunctionTypeArguments();
|
||||
} else {
|
||||
instructions += NullConstant();
|
||||
}
|
||||
Value* function_type_args = Pop();
|
||||
|
||||
AssertAssignableInstr* instr = new (Z) AssertAssignableInstr(
|
||||
position, value, instantiator_type_args, function_type_args, dst_type,
|
||||
dst_name, GetNextDeoptId(), kind);
|
||||
Push(instr);
|
||||
|
||||
instructions += Fragment(instr);
|
||||
instructions += AssertAssignable(position, dst_type, dst_name, kind);
|
||||
|
||||
return instructions;
|
||||
}
|
||||
@@ -1936,8 +2144,8 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
|
||||
AbstractType& return_type = AbstractType::Handle(function.result_type());
|
||||
if (!return_type.IsDynamicType() && !return_type.IsVoidType() &&
|
||||
!return_type.IsObjectType()) {
|
||||
body += AssertAssignable(TokenPosition::kNoSource, return_type,
|
||||
Symbols::Empty());
|
||||
body += AssertAssignableLoadTypeArguments(TokenPosition::kNoSource,
|
||||
return_type, Symbols::Empty());
|
||||
}
|
||||
body += Return(TokenPosition::kNoSource);
|
||||
|
||||
@@ -2435,7 +2643,8 @@ Fragment FlowGraphBuilder::FfiConvertArgumentToDart(
|
||||
body += NullConstant();
|
||||
} else {
|
||||
const Representation from_rep = native_representation;
|
||||
const Representation to_rep = compiler::ffi::TypeRepresentation(ffi_type);
|
||||
const Representation to_rep =
|
||||
compiler::ffi::TypeRepresentation(ffi_type.type_class_id());
|
||||
if (from_rep != to_rep) {
|
||||
body += BitCast(from_rep, to_rep);
|
||||
} else {
|
||||
@@ -2453,15 +2662,15 @@ Fragment FlowGraphBuilder::FfiConvertArgumentToNative(
|
||||
Fragment body;
|
||||
|
||||
// Check for 'null'.
|
||||
body += LoadLocal(MakeTemporary());
|
||||
body <<= new (Z) CheckNullInstr(Pop(), String::ZoneHandle(Z, function.name()),
|
||||
GetNextDeoptId(), TokenPosition::kNoSource);
|
||||
body += CheckNullOptimized(TokenPosition::kNoSource,
|
||||
String::ZoneHandle(Z, function.name()));
|
||||
|
||||
if (compiler::ffi::NativeTypeIsPointer(ffi_type)) {
|
||||
body += LoadNativeField(Slot::Pointer_c_memory_address());
|
||||
body += UnboxTruncate(kUnboxedFfiIntPtr);
|
||||
} else {
|
||||
Representation from_rep = compiler::ffi::TypeRepresentation(ffi_type);
|
||||
Representation from_rep =
|
||||
compiler::ffi::TypeRepresentation(ffi_type.type_class_id());
|
||||
body += UnboxTruncate(from_rep);
|
||||
|
||||
Representation to_rep = native_representation;
|
||||
|
||||
@@ -82,6 +82,7 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
||||
Fragment NativeFunctionBody(const Function& function,
|
||||
LocalVariable* first_parameter);
|
||||
|
||||
// Every recognized method has a body expressed in IL.
|
||||
bool IsRecognizedMethodForFlowGraph(const Function& function);
|
||||
FlowGraph* BuildGraphOfRecognizedMethod(const Function& function);
|
||||
|
||||
@@ -164,7 +165,7 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
||||
const String& dst_name,
|
||||
AssertAssignableInstr::Kind kind = AssertAssignableInstr::kUnknown);
|
||||
|
||||
Fragment AssertAssignable(
|
||||
Fragment AssertAssignableLoadTypeArguments(
|
||||
TokenPosition position,
|
||||
const AbstractType& dst_type,
|
||||
const String& dst_name,
|
||||
|
||||
@@ -420,6 +420,7 @@ Fragment PrologueBuilder::BuildClosureContextHandling() {
|
||||
|
||||
Fragment PrologueBuilder::BuildTypeArgumentsHandling(JoinEntryInstr* nsm) {
|
||||
LocalVariable* type_args_var = parsed_function_->RawTypeArgumentsVariable();
|
||||
ASSERT(type_args_var != nullptr);
|
||||
|
||||
Fragment handling;
|
||||
|
||||
|
||||
@@ -145,6 +145,32 @@ namespace dart {
|
||||
V(::, _abi, FfiAbi, 0xf2e89620) \
|
||||
V(::, _asFunctionInternal, FfiAsFunctionInternal, 0x92a67518) \
|
||||
V(::, _nativeCallbackFunction, FfiNativeCallbackFunction, 0x59cc5edb) \
|
||||
V(::, _loadInt8, FfiLoadInt8, 0x8082c420) \
|
||||
V(::, _loadInt16, FfiLoadInt16, 0xf4edcd95) \
|
||||
V(::, _loadInt32, FfiLoadInt32, 0xe935ea8e) \
|
||||
V(::, _loadInt64, FfiLoadInt64, 0x2353b71f) \
|
||||
V(::, _loadUint8, FfiLoadUint8, 0x0da2cf74) \
|
||||
V(::, _loadUint16, FfiLoadUint16, 0xd255fce5) \
|
||||
V(::, _loadUint32, FfiLoadUint32, 0x2bfe4451) \
|
||||
V(::, _loadUint64, FfiLoadUint64, 0xbb18cddf) \
|
||||
V(::, _loadIntPtr, FfiLoadIntPtr, 0x859348ba) \
|
||||
V(::, _loadFloat, FfiLoadFloat, 0x02d8de15) \
|
||||
V(::, _loadDouble, FfiLoadDouble, 0xc71c7f70) \
|
||||
V(::, _loadPointer, FfiLoadPointer, 0x897ec967) \
|
||||
V(::, _storeInt8, FfiStoreInt8, 0x539453b2) \
|
||||
V(::, _storeInt16, FfiStoreInt16, 0xd5b1a53c) \
|
||||
V(::, _storeInt32, FfiStoreInt32, 0x0d225f8b) \
|
||||
V(::, _storeInt64, FfiStoreInt64, 0x8c85fbca) \
|
||||
V(::, _storeUint8, FfiStoreUint8, 0x4711d7d1) \
|
||||
V(::, _storeUint16, FfiStoreUint16, 0xce7b57eb) \
|
||||
V(::, _storeUint32, FfiStoreUint32, 0x3c78a960) \
|
||||
V(::, _storeUint64, FfiStoreUint64, 0xc6886757) \
|
||||
V(::, _storeIntPtr, FfiStoreIntPtr, 0x080db06a) \
|
||||
V(::, _storeFloat, FfiStoreFloat, 0x010e0700) \
|
||||
V(::, _storeDouble, FfiStoreDouble, 0x52d89324) \
|
||||
V(::, _storePointer, FfiStorePointer, 0x4ceb88ea) \
|
||||
V(::, _fromAddress, FfiFromAddress, 0x8eb74eb8) \
|
||||
V(Pointer, get:address, FfiGetAddress, 0x29a505a1) \
|
||||
|
||||
// List of intrinsics:
|
||||
// (class-name, function-name, intrinsification method, fingerprint).
|
||||
|
||||
@@ -918,6 +918,16 @@ class KernelBytecode {
|
||||
return DecodeOpcode(instr) == KernelBytecode::kCheckStack;
|
||||
}
|
||||
|
||||
DART_FORCE_INLINE static bool IsCheckFunctionTypeArgs(const KBCInstr* instr) {
|
||||
switch (DecodeOpcode(instr)) {
|
||||
case KernelBytecode::kCheckFunctionTypeArgs:
|
||||
case KernelBytecode::kCheckFunctionTypeArgs_Wide:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DART_FORCE_INLINE static bool IsEntryOpcode(const KBCInstr* instr) {
|
||||
switch (DecodeOpcode(instr)) {
|
||||
case KernelBytecode::kEntry:
|
||||
|
||||
+24
-1
@@ -2720,7 +2720,8 @@ class Function : public Object {
|
||||
// dependencies. It will be compiled into optimized code immediately when it's
|
||||
// run.
|
||||
bool ForceOptimize() const {
|
||||
if (IsFfiTrampoline()) {
|
||||
if (IsFfiTrampoline() || IsFfiLoad() || IsFfiStore() ||
|
||||
IsFfiFromAddress() || IsFfiGetAddress()) {
|
||||
return true;
|
||||
}
|
||||
// On DBC we use native calls instead of IR for the view factories (see
|
||||
@@ -2883,6 +2884,28 @@ class Function : public Object {
|
||||
RawFunction::kFfiTrampoline;
|
||||
}
|
||||
|
||||
bool IsFfiLoad() const {
|
||||
const auto kind = MethodRecognizer::RecognizeKind(*this);
|
||||
return MethodRecognizer::kFfiLoadInt8 <= kind &&
|
||||
kind <= MethodRecognizer::kFfiLoadPointer;
|
||||
}
|
||||
|
||||
bool IsFfiStore() const {
|
||||
const auto kind = MethodRecognizer::RecognizeKind(*this);
|
||||
return MethodRecognizer::kFfiStoreInt8 <= kind &&
|
||||
kind <= MethodRecognizer::kFfiStorePointer;
|
||||
}
|
||||
|
||||
bool IsFfiFromAddress() const {
|
||||
const auto kind = MethodRecognizer::RecognizeKind(*this);
|
||||
return kind == MethodRecognizer::kFfiFromAddress;
|
||||
}
|
||||
|
||||
bool IsFfiGetAddress() const {
|
||||
const auto kind = MethodRecognizer::RecognizeKind(*this);
|
||||
return kind == MethodRecognizer::kFfiGetAddress;
|
||||
}
|
||||
|
||||
bool IsAsyncFunction() const { return modifier() == RawFunction::kAsync; }
|
||||
|
||||
bool IsAsyncClosure() const {
|
||||
|
||||
@@ -7,11 +7,39 @@
|
||||
import "dart:_internal" show patch;
|
||||
import 'dart:typed_data' show TypedData;
|
||||
|
||||
const Map<Type, int> _knownSizes = {
|
||||
Int8: 1,
|
||||
Uint8: 1,
|
||||
Int16: 2,
|
||||
Uint16: 2,
|
||||
Int32: 4,
|
||||
Uint32: 4,
|
||||
Int64: 8,
|
||||
Uint64: 8,
|
||||
Float: 4,
|
||||
Double: 8,
|
||||
};
|
||||
|
||||
final int _intPtrSize = [8, 4, 4][_abi()];
|
||||
|
||||
@patch
|
||||
int sizeOf<T extends NativeType>() native "Ffi_sizeOf";
|
||||
int sizeOf<T extends NativeType>() {
|
||||
// This is not super fast, but it is faster than a runtime entry.
|
||||
// Hot loops with elementAt().load() do not use this sizeOf, elementAt is
|
||||
// optimized per NativeType statically to prevent use of sizeOf at runtime.
|
||||
final int knownSize = _knownSizes[T];
|
||||
if (knownSize != null) return knownSize;
|
||||
if (T == IntPtr) return _intPtrSize;
|
||||
if (T == Pointer) return _intPtrSize;
|
||||
// For structs we fall back to a runtime entry.
|
||||
return _sizeOf<T>();
|
||||
}
|
||||
|
||||
int _sizeOf<T extends NativeType>() native "Ffi_sizeOf";
|
||||
|
||||
Pointer<T> _allocate<T extends NativeType>(int count) native "Ffi_allocate";
|
||||
|
||||
// Implemented in the method recognizer, bytecode interpreter uses runtime.
|
||||
Pointer<T> _fromAddress<T extends NativeType>(int ptr) native "Ffi_fromAddress";
|
||||
|
||||
// The real implementation of this function (for interface calls) lives in
|
||||
@@ -66,32 +94,32 @@ class Pointer<T extends NativeType> {
|
||||
|
||||
// TODO(sjindel): When NNBD is available, we should change `value` to be
|
||||
// non-null.
|
||||
// For statically known types, this is rewired.
|
||||
@patch
|
||||
void store(Object value) native "Ffi_store";
|
||||
void store(Object value) =>
|
||||
throw UnsupportedError("Pointer.store cannot be called dynamically.");
|
||||
|
||||
// For statically known types, this is rewired.
|
||||
@patch
|
||||
R load<R>() native "Ffi_load";
|
||||
R load<R>() =>
|
||||
throw UnsupportedError("Pointer.load cannot be called dynamically.");
|
||||
|
||||
// Implemented in the method recognizer, bytecode interpreter uses runtime.
|
||||
@patch
|
||||
int get address native "Ffi_address";
|
||||
|
||||
// Note this could also be implmented without an extra native as offsetBy
|
||||
// (elementSize()*index). This would be 2 native calls rather than one. What
|
||||
// would be better?
|
||||
// For statically known types, this is rewired.
|
||||
// (Method sizeOf is slow, see notes above.)
|
||||
@patch
|
||||
Pointer<T> elementAt(int index) native "Ffi_elementAt";
|
||||
Pointer<T> elementAt(int index) =>
|
||||
Pointer.fromAddress(address + sizeOf<T>() * index);
|
||||
|
||||
// Note this could also be implmented without an extra native as
|
||||
// fromAddress(address). This would be 2 native calls rather than one.
|
||||
// What would be better?
|
||||
@patch
|
||||
Pointer<T> offsetBy(int offsetInBytes) native "Ffi_offsetBy";
|
||||
Pointer<T> offsetBy(int offsetInBytes) =>
|
||||
Pointer.fromAddress(address + offsetInBytes);
|
||||
|
||||
// Note this could also be implemented without an extra native as
|
||||
// fromAddress(address). This would be 2 native calls rather than one.
|
||||
// What would be better?
|
||||
@patch
|
||||
Pointer<U> cast<U extends NativeType>() native "Ffi_cast";
|
||||
Pointer<U> cast<U extends NativeType>() => Pointer.fromAddress(address);
|
||||
|
||||
@patch
|
||||
R asFunction<R extends Function>() {
|
||||
@@ -106,8 +134,105 @@ class Pointer<T extends NativeType> {
|
||||
_asExternalTypedData(this, count);
|
||||
}
|
||||
|
||||
// Returns the ABI used for size and alignment calculations.
|
||||
// See pkg/vm/lib/transformations/ffi.dart.
|
||||
/// Returns an integer encoding the ABI used for size and alignment
|
||||
/// calculations. See pkg/vm/lib/transformations/ffi.dart.
|
||||
@pragma('vm:prefer-inline')
|
||||
int _abi()
|
||||
native "Recognized method: method is directly interpreted by the bytecode interpreter or IR graph is built in the flow graph builder.";
|
||||
|
||||
// The following functions are implemented in the method recognizer, but the
|
||||
// bytecode interpreter uses native entries.
|
||||
//
|
||||
// TODO(38172): Since these are not inlined (force optimize), they force
|
||||
// allocating a Pointer with in elementAt/offsetBy. Allocating these pointers
|
||||
// and GCing new spaces takes a lot of the benchmark time. The next speedup is
|
||||
// getting rid of these allocations by inlining these functions.
|
||||
int _loadInt8(Pointer<Int8> pointer) native "Ffi_loadInt8";
|
||||
|
||||
int _loadInt16(Pointer<Int16> pointer) native "Ffi_loadInt16";
|
||||
|
||||
int _loadInt32(Pointer<Int32> pointer) native "Ffi_loadInt32";
|
||||
|
||||
int _loadInt64(Pointer<Int64> pointer) native "Ffi_loadInt64";
|
||||
|
||||
int _loadUint8(Pointer<Uint8> pointer) native "Ffi_loadUint8";
|
||||
|
||||
int _loadUint16(Pointer<Uint16> pointer) native "Ffi_loadUint16";
|
||||
|
||||
int _loadUint32(Pointer<Uint32> pointer) native "Ffi_loadUint32";
|
||||
|
||||
int _loadUint64(Pointer<Uint64> pointer) native "Ffi_loadUint64";
|
||||
|
||||
int _loadIntPtr(Pointer<IntPtr> pointer) native "Ffi_loadIntPtr";
|
||||
|
||||
double _loadFloat(Pointer<Float> pointer) native "Ffi_loadFloat";
|
||||
|
||||
double _loadDouble(Pointer<Double> pointer) native "Ffi_loadDouble";
|
||||
|
||||
Pointer<S> _loadPointer<S extends NativeType>(Pointer<Pointer<S>> pointer)
|
||||
native "Ffi_loadPointer";
|
||||
|
||||
S _loadStruct<S extends Struct>(Pointer<S> pointer) native "Ffi_loadStruct";
|
||||
|
||||
void _storeInt8(Pointer<Int8> pointer, int value) native "Ffi_storeInt8";
|
||||
|
||||
void _storeInt16(Pointer<Int16> pointer, int value) native "Ffi_storeInt16";
|
||||
|
||||
void _storeInt32(Pointer<Int32> pointer, int value) native "Ffi_storeInt32";
|
||||
|
||||
void _storeInt64(Pointer<Int64> pointer, int value) native "Ffi_storeInt64";
|
||||
|
||||
void _storeUint8(Pointer<Uint8> pointer, int value) native "Ffi_storeUint8";
|
||||
|
||||
void _storeUint16(Pointer<Uint16> pointer, int value) native "Ffi_storeUint16";
|
||||
|
||||
void _storeUint32(Pointer<Uint32> pointer, int value) native "Ffi_storeUint32";
|
||||
|
||||
void _storeUint64(Pointer<Uint64> pointer, int value) native "Ffi_storeUint64";
|
||||
|
||||
void _storeIntPtr(Pointer<IntPtr> pointer, int value) native "Ffi_storeIntPtr";
|
||||
|
||||
void _storeFloat(Pointer<Float> pointer, double value) native "Ffi_storeFloat";
|
||||
|
||||
void _storeDouble(Pointer<Double> pointer, double value)
|
||||
native "Ffi_storeDouble";
|
||||
|
||||
void _storePointer<S extends NativeType>(
|
||||
Pointer<Pointer<S>> pointer, Pointer<S> value) native "Ffi_storePointer";
|
||||
|
||||
Pointer<Int8> _elementAtInt8(Pointer<Int8> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 1 * index);
|
||||
|
||||
Pointer<Int16> _elementAtInt16(Pointer<Int16> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 2 * index);
|
||||
|
||||
Pointer<Int32> _elementAtInt32(Pointer<Int32> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 4 * index);
|
||||
|
||||
Pointer<Int64> _elementAtInt64(Pointer<Int64> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 8 * index);
|
||||
|
||||
Pointer<Uint8> _elementAtUint8(Pointer<Uint8> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 1 * index);
|
||||
|
||||
Pointer<Uint16> _elementAtUint16(Pointer<Uint16> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 2 * index);
|
||||
|
||||
Pointer<Uint32> _elementAtUint32(Pointer<Uint32> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 4 * index);
|
||||
|
||||
Pointer<Uint64> _elementAtUint64(Pointer<Uint64> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 8 * index);
|
||||
|
||||
Pointer<IntPtr> _elementAtIntPtr(Pointer<IntPtr> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + _intPtrSize * index);
|
||||
|
||||
Pointer<Float> _elementAtFloat(Pointer<Float> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 4 * index);
|
||||
|
||||
Pointer<Double> _elementAtDouble(Pointer<Double> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 8 * index);
|
||||
|
||||
Pointer<Pointer<S>> _elementAtPointer<S extends NativeType>(
|
||||
Pointer<Pointer<S>> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + _intPtrSize * index);
|
||||
|
||||
@@ -66,6 +66,8 @@ class Pointer<T extends NativeType> extends NativeType {
|
||||
/// The [value] is automatically marshalled into its native representation.
|
||||
/// Note that ints which do not fit in [T] are truncated and sign extended,
|
||||
/// and doubles stored into Pointer<[Float]> lose precision.
|
||||
///
|
||||
/// Note that `address` needs to be aligned to the size of `T`.
|
||||
external void store(@DartRepresentationOf("T") Object value);
|
||||
|
||||
/// Load a Dart value from this location.
|
||||
@@ -73,6 +75,8 @@ class Pointer<T extends NativeType> extends NativeType {
|
||||
/// The value is automatically unmarshalled from its native representation.
|
||||
/// Loading a [Struct] reference returns a reference backed by native memory
|
||||
/// (the same pointer as it's loaded from).
|
||||
///
|
||||
/// Note that `address` needs to be aligned to the size of `T`.
|
||||
external R load<@DartRepresentationOf("T") R>();
|
||||
|
||||
/// Access to the raw pointer value.
|
||||
@@ -98,8 +102,6 @@ class Pointer<T extends NativeType> extends NativeType {
|
||||
external R asFunction<@DartRepresentationOf("T") R extends Function>();
|
||||
|
||||
/// Free memory on the C heap pointed to by this pointer with free().
|
||||
///
|
||||
/// Note that this zeros out the address.
|
||||
external void free();
|
||||
|
||||
/// Creates an *external* typed data array backed by this pointer.
|
||||
|
||||
@@ -9,11 +9,39 @@
|
||||
import "dart:_internal" show patch;
|
||||
import 'dart:typed_data' show TypedData;
|
||||
|
||||
const Map<Type, int> _knownSizes = {
|
||||
Int8: 1,
|
||||
Uint8: 1,
|
||||
Int16: 2,
|
||||
Uint16: 2,
|
||||
Int32: 4,
|
||||
Uint32: 4,
|
||||
Int64: 8,
|
||||
Uint64: 8,
|
||||
Float: 4,
|
||||
Double: 8,
|
||||
};
|
||||
|
||||
final int _intPtrSize = [8, 4, 4][_abi()];
|
||||
|
||||
@patch
|
||||
int sizeOf<T extends NativeType>() native "Ffi_sizeOf";
|
||||
int sizeOf<T extends NativeType>() {
|
||||
// This is not super fast, but it is faster than a runtime entry.
|
||||
// Hot loops with elementAt().load() do not use this sizeOf, elementAt is
|
||||
// optimized per NativeType statically to prevent use of sizeOf at runtime.
|
||||
final int knownSize = _knownSizes[T];
|
||||
if (knownSize != null) return knownSize;
|
||||
if (T == IntPtr) return _intPtrSize;
|
||||
if (T == Pointer) return _intPtrSize;
|
||||
// For structs we fall back to a runtime entry.
|
||||
return _sizeOf<T>();
|
||||
}
|
||||
|
||||
int _sizeOf<T extends NativeType>() native "Ffi_sizeOf";
|
||||
|
||||
Pointer<T> _allocate<T extends NativeType>(int count) native "Ffi_allocate";
|
||||
|
||||
// Implemented in the method recognizer, bytecode interpreter uses runtime.
|
||||
Pointer<T> _fromAddress<T extends NativeType>(int ptr) native "Ffi_fromAddress";
|
||||
|
||||
// The real implementation of this function (for interface calls) lives in
|
||||
@@ -25,6 +53,25 @@ DS _asFunctionInternal<DS extends Function, NS extends Function>(
|
||||
dynamic _asExternalTypedData(Pointer ptr, int count)
|
||||
native "Ffi_asExternalTypedData";
|
||||
|
||||
// Returns a Function object for a native callback.
|
||||
//
|
||||
// Calls to [Pointer.fromFunction] are re-written by the FE into calls to this
|
||||
// method + _pointerFromFunction. All three arguments must be constants.
|
||||
//
|
||||
// In AOT we evaluate calls to this function during precompilation and replace
|
||||
// them with Constant instruction referencing the callback trampoline, to ensure
|
||||
// that it will be precompiled.
|
||||
//
|
||||
// In all JIT modes we call a native runtime entry. We *cannot* use the IL
|
||||
// implementation, since that would pull the callback trampoline into JIT
|
||||
// snapshots. The callback trampolines can only be serialized into AOT snapshots
|
||||
// because they embed the addresses of runtime routines in JIT mode.
|
||||
Object _nativeCallbackFunction<NS extends Function>(Function target,
|
||||
Object exceptionalReturn) native "Ffi_nativeCallbackFunction";
|
||||
|
||||
Pointer<NS> _pointerFromFunction<NS extends NativeFunction>(Object function)
|
||||
native "Ffi_pointerFromFunction";
|
||||
|
||||
@patch
|
||||
@pragma("vm:entry-point")
|
||||
class Pointer<T extends NativeType> {
|
||||
@@ -34,39 +81,47 @@ class Pointer<T extends NativeType> {
|
||||
@patch
|
||||
factory Pointer.fromAddress(int ptr) => _fromAddress(ptr);
|
||||
|
||||
// All static calls to this method are replaced by the FE into
|
||||
// _nativeCallbackFunction + _pointerFromFunction.
|
||||
//
|
||||
// We still need to throw an error on a dynamic invocations, invocations
|
||||
// through tearoffs or reflective calls.
|
||||
@patch
|
||||
static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
|
||||
@DartRepresentationOf("T") Function f,
|
||||
Object exceptionalReturn) native "Ffi_fromFunction";
|
||||
[Object exceptionalReturn]) {
|
||||
throw UnsupportedError(
|
||||
"Pointer.fromFunction cannot be called dynamically.");
|
||||
}
|
||||
|
||||
// TODO(sjindel): When NNBD is available, we should change `value` to be
|
||||
// non-null.
|
||||
// For statically known types, this is rewired.
|
||||
@patch
|
||||
void store(Object value) native "Ffi_store";
|
||||
void store(Object value) =>
|
||||
throw UnsupportedError("Pointer.store cannot be called dynamically.");
|
||||
|
||||
// For statically known types, this is rewired.
|
||||
@patch
|
||||
R load<R>() native "Ffi_load";
|
||||
R load<R>() =>
|
||||
throw UnsupportedError("Pointer.load cannot be called dynamically.");
|
||||
|
||||
// Implemented in the method recognizer, bytecode interpreter uses runtime.
|
||||
@patch
|
||||
int get address native "Ffi_address";
|
||||
|
||||
// Note this could also be implmented without an extra native as offsetBy
|
||||
// (elementSize()*index). This would be 2 native calls rather than one. What
|
||||
// would be better?
|
||||
// For statically known types, this is rewired.
|
||||
// (Method sizeOf is slow, see notes above.)
|
||||
@patch
|
||||
Pointer<T> elementAt(int index) native "Ffi_elementAt";
|
||||
Pointer<T> elementAt(int index) =>
|
||||
Pointer.fromAddress(address + sizeOf<T>() * index);
|
||||
|
||||
// Note this could also be implmented without an extra native as
|
||||
// fromAddress(address). This would be 2 native calls rather than one.
|
||||
// What would be better?
|
||||
@patch
|
||||
Pointer<T> offsetBy(int offsetInBytes) native "Ffi_offsetBy";
|
||||
Pointer<T> offsetBy(int offsetInBytes) =>
|
||||
Pointer.fromAddress(address + offsetInBytes);
|
||||
|
||||
// Note this could also be implemented without an extra native as
|
||||
// fromAddress(address). This would be 2 native calls rather than one.
|
||||
// What would be better?
|
||||
@patch
|
||||
Pointer<U> cast<U extends NativeType>() native "Ffi_cast";
|
||||
Pointer<U> cast<U extends NativeType>() => Pointer.fromAddress(address);
|
||||
|
||||
@patch
|
||||
R asFunction<R extends Function>() {
|
||||
@@ -81,8 +136,105 @@ class Pointer<T extends NativeType> {
|
||||
_asExternalTypedData(this, count);
|
||||
}
|
||||
|
||||
// Returns the ABI used for size and alignment calculations.
|
||||
// See pkg/vm/lib/transformations/ffi.dart.
|
||||
/// Returns an integer encoding the ABI used for size and alignment
|
||||
/// calculations. See pkg/vm/lib/transformations/ffi.dart.
|
||||
@pragma('vm:prefer-inline')
|
||||
int _abi()
|
||||
native "Recognized method: method is directly interpreted by the bytecode interpreter or IR graph is built in the flow graph builder.";
|
||||
|
||||
// The following functions are implemented in the method recognizer, but the
|
||||
// bytecode interpreter uses native entries.
|
||||
//
|
||||
// TODO(38172): Since these are not inlined (force optimize), they force
|
||||
// allocating a Pointer with in elementAt/offsetBy. Allocating these pointers
|
||||
// and GCing new spaces takes a lot of the benchmark time. The next speedup is
|
||||
// getting rid of these allocations by inlining these functions.
|
||||
int _loadInt8(Pointer<Int8> pointer) native "Ffi_loadInt8";
|
||||
|
||||
int _loadInt16(Pointer<Int16> pointer) native "Ffi_loadInt16";
|
||||
|
||||
int _loadInt32(Pointer<Int32> pointer) native "Ffi_loadInt32";
|
||||
|
||||
int _loadInt64(Pointer<Int64> pointer) native "Ffi_loadInt64";
|
||||
|
||||
int _loadUint8(Pointer<Uint8> pointer) native "Ffi_loadUint8";
|
||||
|
||||
int _loadUint16(Pointer<Uint16> pointer) native "Ffi_loadUint16";
|
||||
|
||||
int _loadUint32(Pointer<Uint32> pointer) native "Ffi_loadUint32";
|
||||
|
||||
int _loadUint64(Pointer<Uint64> pointer) native "Ffi_loadUint64";
|
||||
|
||||
int _loadIntPtr(Pointer<IntPtr> pointer) native "Ffi_loadIntPtr";
|
||||
|
||||
double _loadFloat(Pointer<Float> pointer) native "Ffi_loadFloat";
|
||||
|
||||
double _loadDouble(Pointer<Double> pointer) native "Ffi_loadDouble";
|
||||
|
||||
Pointer<S> _loadPointer<S extends NativeType>(Pointer<Pointer<S>> pointer)
|
||||
native "Ffi_loadPointer";
|
||||
|
||||
S _loadStruct<S extends Struct>(Pointer<S> pointer) native "Ffi_loadStruct";
|
||||
|
||||
void _storeInt8(Pointer<Int8> pointer, int value) native "Ffi_storeInt8";
|
||||
|
||||
void _storeInt16(Pointer<Int16> pointer, int value) native "Ffi_storeInt16";
|
||||
|
||||
void _storeInt32(Pointer<Int32> pointer, int value) native "Ffi_storeInt32";
|
||||
|
||||
void _storeInt64(Pointer<Int64> pointer, int value) native "Ffi_storeInt64";
|
||||
|
||||
void _storeUint8(Pointer<Uint8> pointer, int value) native "Ffi_storeUint8";
|
||||
|
||||
void _storeUint16(Pointer<Uint16> pointer, int value) native "Ffi_storeUint16";
|
||||
|
||||
void _storeUint32(Pointer<Uint32> pointer, int value) native "Ffi_storeUint32";
|
||||
|
||||
void _storeUint64(Pointer<Uint64> pointer, int value) native "Ffi_storeUint64";
|
||||
|
||||
void _storeIntPtr(Pointer<IntPtr> pointer, int value) native "Ffi_storeIntPtr";
|
||||
|
||||
void _storeFloat(Pointer<Float> pointer, double value) native "Ffi_storeFloat";
|
||||
|
||||
void _storeDouble(Pointer<Double> pointer, double value)
|
||||
native "Ffi_storeDouble";
|
||||
|
||||
void _storePointer<S extends NativeType>(
|
||||
Pointer<Pointer<S>> pointer, Pointer<S> value) native "Ffi_storePointer";
|
||||
|
||||
Pointer<Int8> _elementAtInt8(Pointer<Int8> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 1 * index);
|
||||
|
||||
Pointer<Int16> _elementAtInt16(Pointer<Int16> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 2 * index);
|
||||
|
||||
Pointer<Int32> _elementAtInt32(Pointer<Int32> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 4 * index);
|
||||
|
||||
Pointer<Int64> _elementAtInt64(Pointer<Int64> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 8 * index);
|
||||
|
||||
Pointer<Uint8> _elementAtUint8(Pointer<Uint8> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 1 * index);
|
||||
|
||||
Pointer<Uint16> _elementAtUint16(Pointer<Uint16> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 2 * index);
|
||||
|
||||
Pointer<Uint32> _elementAtUint32(Pointer<Uint32> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 4 * index);
|
||||
|
||||
Pointer<Uint64> _elementAtUint64(Pointer<Uint64> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 8 * index);
|
||||
|
||||
Pointer<IntPtr> _elementAtIntPtr(Pointer<IntPtr> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + _intPtrSize * index);
|
||||
|
||||
Pointer<Float> _elementAtFloat(Pointer<Float> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 4 * index);
|
||||
|
||||
Pointer<Double> _elementAtDouble(Pointer<Double> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + 8 * index);
|
||||
|
||||
Pointer<Pointer<S>> _elementAtPointer<S extends NativeType>(
|
||||
Pointer<Pointer<S>> pointer, int index) =>
|
||||
Pointer.fromAddress(pointer.address + _intPtrSize * index);
|
||||
|
||||
@@ -68,6 +68,8 @@ class Pointer<T extends NativeType> extends NativeType {
|
||||
/// The [value] is automatically marshalled into its native representation.
|
||||
/// Note that ints which do not fit in [T] are truncated and sign extended,
|
||||
/// and doubles stored into Pointer<[Float]> lose precision.
|
||||
///
|
||||
/// Note that `address` needs to be aligned to the size of `T`.
|
||||
external void store(@DartRepresentationOf("T") Object value);
|
||||
|
||||
/// Load a Dart value from this location.
|
||||
@@ -75,6 +77,8 @@ class Pointer<T extends NativeType> extends NativeType {
|
||||
/// The value is automatically unmarshalled from its native representation.
|
||||
/// Loading a [Struct] reference returns a reference backed by native memory
|
||||
/// (the same pointer as it's loaded from).
|
||||
///
|
||||
/// Note that `address` needs to be aligned to the size of `T`.
|
||||
external R load<@DartRepresentationOf("T") R>();
|
||||
|
||||
/// Access to the raw pointer value.
|
||||
@@ -100,8 +104,6 @@ class Pointer<T extends NativeType> extends NativeType {
|
||||
external R asFunction<@DartRepresentationOf("T") R extends Function>();
|
||||
|
||||
/// Free memory on the C heap pointed to by this pointer with free().
|
||||
///
|
||||
/// Note that this zeros out the address.
|
||||
external void free();
|
||||
|
||||
/// Creates an *external* typed data array backed by this pointer.
|
||||
|
||||
+11
-10
@@ -51,7 +51,7 @@ void main() {
|
||||
testSizeOfVoid();
|
||||
testSizeOfNativeFunction();
|
||||
testSizeOfNativeType();
|
||||
testFreeZeroOut();
|
||||
testDynamicInvocation();
|
||||
}
|
||||
|
||||
void testPointerBasic() {
|
||||
@@ -498,13 +498,14 @@ void testSizeOfNativeType() {
|
||||
});
|
||||
}
|
||||
|
||||
void testFreeZeroOut() {
|
||||
// at least one of these pointers should have address != 0 on all platforms
|
||||
ffi.Pointer<ffi.Int8> p1 = Pointer.allocate();
|
||||
ffi.Pointer<ffi.Int8> p2 = Pointer.allocate();
|
||||
Expect.notEquals(0, p1.address & p2.address);
|
||||
p1.free();
|
||||
p2.free();
|
||||
Expect.equals(0, p1.address);
|
||||
Expect.equals(0, p2.address);
|
||||
void testDynamicInvocation() {
|
||||
dynamic p = Pointer<ffi.Int8>.allocate();
|
||||
Expect.throws(() {
|
||||
final int i = p.load();
|
||||
});
|
||||
Expect.throws(() => p.store(1));
|
||||
p.elementAt(5); // Works, but is slow.
|
||||
final int addr = p.address;
|
||||
final Pointer<ffi.Int16> p2 = p.cast<ffi.Int16>();
|
||||
p.free();
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
// b P<I>//P<I> P<NT>//P<I> P<NT>//P<NT>
|
||||
// a
|
||||
// P<P<I>>//P<P<I>> 1 ok 2 implicit downcast 3 implicit downcast
|
||||
// of argument: ok of argument: fail
|
||||
// at runtime
|
||||
// of argument: of argument:
|
||||
// static error static error
|
||||
//
|
||||
// P<P<NT>>//P<P<I>> 4 ok 5 ok 6 fail at runtime
|
||||
//
|
||||
@@ -81,9 +81,8 @@ void store2() {
|
||||
final Pointer<NativeType> b =
|
||||
Pointer<Int8>.allocate(); // Reified Pointer<Int8> at runtime.
|
||||
|
||||
// Successful implicit downcast of argument at runtime.
|
||||
// Should succeed now, should statically be rejected when NNBD lands.
|
||||
a.store(b);
|
||||
// We disable implicit downcasts, they will go away when NNBD lands.
|
||||
a.store(b); //# 1: compile-time error
|
||||
|
||||
a.free();
|
||||
b.free();
|
||||
@@ -94,11 +93,8 @@ void store3() {
|
||||
final Pointer<NativeType> b =
|
||||
Pointer<Int8>.allocate().cast<Pointer<NativeType>>();
|
||||
|
||||
// Failing implicit downcast of argument at runtime.
|
||||
// Should fail now at runtime, should statically be rejected when NNBD lands.
|
||||
Expect.throws(() {
|
||||
a.store(b);
|
||||
});
|
||||
// We disable implicit downcasts, they will go away when NNBD lands.
|
||||
a.store(b); //# 2: compile-time error
|
||||
|
||||
a.free();
|
||||
b.free();
|
||||
@@ -245,19 +241,23 @@ void load6() {
|
||||
}
|
||||
|
||||
void main() {
|
||||
store1();
|
||||
store2();
|
||||
store3();
|
||||
store4();
|
||||
store5();
|
||||
store6();
|
||||
store7();
|
||||
store8();
|
||||
store9();
|
||||
load1();
|
||||
load2();
|
||||
load3();
|
||||
load4();
|
||||
load5();
|
||||
load6();
|
||||
// Trigger both the runtime entry and the IL in bytecode.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
print(i);
|
||||
store1();
|
||||
store2();
|
||||
store3();
|
||||
store4();
|
||||
store5();
|
||||
store6();
|
||||
store7();
|
||||
store8();
|
||||
store9();
|
||||
load1();
|
||||
load2();
|
||||
load3();
|
||||
load4();
|
||||
load5();
|
||||
load6();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -35,5 +35,5 @@ MINOR 6
|
||||
PATCH 0
|
||||
PRERELEASE 0
|
||||
PRERELEASE_PATCH 0
|
||||
ABI_VERSION 17
|
||||
OLDEST_SUPPORTED_ABI_VERSION 16
|
||||
ABI_VERSION 18
|
||||
OLDEST_SUPPORTED_ABI_VERSION 18
|
||||
|
||||
Reference in New Issue
Block a user