Reland "[vm/ffi] Optimize Pointer operations for statically known types"

Original CL in patchset 1.
Fix for simdbc in patchset 4.
Fix for arm32 precompiled in: https://dart-review.googlesource.com/c/sdk/+/120660/

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: I3921a595fd05026d6ca565ace496771d7c1d877b
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,vm-kernel-asan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-mac-debug-simdbc64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-reload-mac-release-simdbc64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-precomp-mac-release-simarm_x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/120661
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
Daco Harkes
2019-10-08 13:04:39 +00:00
committed by commit-bot@chromium.org
parent 15e8c12bc2
commit d23c824435
30 changed files with 1096 additions and 329 deletions
+36 -1
View File
@@ -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.
+42 -10
View File
@@ -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
View File
@@ -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();
}
+25 -5
View File
@@ -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) \
+5 -2
View File
@@ -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();
+2
View File
@@ -2780,6 +2780,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;
+10
View File
@@ -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:
+9 -1
View File
@@ -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;
+56 -4
View File
@@ -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) {
+7 -1
View File
@@ -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);
+231 -22
View File
@@ -692,6 +692,32 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kTypedData_Float32x4ArrayView_factory:
case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
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:
#endif // !defined(TARGET_ARCH_DBC)
// This list must be kept in sync with BytecodeReaderHelper::NativeEntry in
// runtime/vm/compiler/frontend/bytecode_reader.cc and implemented in the
@@ -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;
+2 -1
View File
@@ -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).
+10
View File
@@ -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
View File
@@ -2726,7 +2726,8 @@ class Function : public Object {
// On DBC we use native calls instead of IR for the view factories (see
// kernel_to_il.cc)
#if !defined(TARGET_ARCH_DBC)
if (IsTypedDataViewFactory()) {
if (IsTypedDataViewFactory() || IsFfiLoad() || IsFfiStore() ||
IsFfiFromAddress() || IsFfiGetAddress()) {
return true;
}
#endif
@@ -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 {
+142 -17
View File
@@ -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);
+4 -2
View File
@@ -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.
+170 -18
View File
@@ -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);
+4 -2
View File
@@ -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
View File
@@ -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();
}
+25 -25
View File
@@ -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
View File
@@ -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