From d23c824435bb165355d1a473f7995e552bd43572 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Tue, 8 Oct 2019 13:04:39 +0000 Subject: [PATCH] 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 (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 Commit-Queue: Daco Harkes --- pkg/vm/lib/transformations/ffi.dart | 37 ++- pkg/vm/lib/transformations/ffi_use_sites.dart | 52 +++- runtime/lib/ffi.cc | 289 ++++++------------ runtime/vm/bootstrap_natives.h | 30 +- runtime/vm/class_id.h | 7 +- .../backend/flow_graph_compiler_x64.cc | 1 + runtime/vm/compiler/backend/il.cc | 2 + .../vm/compiler/backend/il_deserializer.cc | 3 + runtime/vm/compiler/backend/range_analysis.cc | 2 + runtime/vm/compiler/backend/slot.cc | 10 + runtime/vm/compiler/backend/slot.h | 10 +- runtime/vm/compiler/ffi.cc | 60 +++- runtime/vm/compiler/ffi.h | 8 +- .../frontend/base_flow_graph_builder.cc | 77 ++++- .../frontend/base_flow_graph_builder.h | 28 +- .../frontend/bytecode_flow_graph_builder.cc | 55 +++- .../frontend/kernel_binary_flowgraph.cc | 2 +- runtime/vm/compiler/frontend/kernel_to_il.cc | 253 +++++++++++++-- runtime/vm/compiler/frontend/kernel_to_il.h | 3 +- .../vm/compiler/frontend/prologue_builder.cc | 1 + runtime/vm/compiler/recognized_methods_list.h | 26 ++ runtime/vm/constants_kbc.h | 10 + runtime/vm/object.h | 25 +- sdk/lib/_internal/vm/lib/ffi_patch.dart | 159 ++++++++-- sdk/lib/ffi/ffi.dart | 6 +- sdk_nnbd/lib/_internal/vm/lib/ffi_patch.dart | 188 ++++++++++-- sdk_nnbd/lib/ffi/ffi.dart | 6 +- tests/ffi/data_test.dart | 21 +- tests/ffi/regress_37254_test.dart | 50 +-- tools/VERSION | 4 +- 30 files changed, 1096 insertions(+), 329 deletions(-) diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart index c4e6800565d..2444a5081e3 100644 --- a/pkg/vm/lib/transformations/ffi.dart +++ b/pkg/vm/lib/transformations/ffi.dart @@ -154,6 +154,22 @@ const nonSizeAlignment = >{ Abi.wordSize32Align64: {}, }; +/// Load, store, and elementAt are rewired to their static type for these types. +const List 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 loadMethods; + final Map storeMethods; + final Map elementAtMethods; + final Procedure loadStructMethod; /// Classes corresponding to [NativeType], indexed by [NativeType]. final List 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. diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart index d3fd73bf2f8..f3f74da32e3 100644 --- a/pkg/vm/lib/transformations/ffi_use_sites.dart +++ b/pkg/vm/lib/transformations/ffi_use_sites.dart @@ -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( diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index f103f2e7449..b751f760689 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -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] -> [Pointer] -// T extends [Struct] -> T -// [NativeFunction] 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(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(address)); @@ -291,61 +163,73 @@ static RawObject* LoadValue(Zone* zone, return Double::New(*reinterpret_cast(address)); case kFfiDoubleCid: return Double::New(*reinterpret_cast(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( - *reinterpret_cast(address))); - } else { - // Result is a struct class -- find .#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(address)); } -static void StoreValue(Zone* zone, - const Pointer& pointer, - classid_t type_cid, - const Instance& new_value) { - uint8_t* const address = reinterpret_cast(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 .#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(pointer.NativeAddress()); switch (type_cid) { case kFfiInt8Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); @@ -388,28 +272,31 @@ static void StoreValue(Zone* zone, case kFfiDoubleCid: *reinterpret_cast(address) = AsDouble(new_value).value(); break; - case kFfiPointerCid: { - ASSERT(IsPointerType(pointer_type_arg)); - ASSERT(new_value.IsPointer()); - const void* const stored = - reinterpret_cast(AsPointer(new_value).NativeAddress()); - *reinterpret_cast(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(pointer.NativeAddress()); + *slot = AsPointer(new_value).NativeAddress(); return Object::null(); } diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 37dcfdfd6e8..19f01c297fc 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -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) \ diff --git a/runtime/vm/class_id.h b/runtime/vm/class_id.h index 69d64878e67..19998825655 100644 --- a/runtime/vm/class_id.h +++ b/runtime/vm/class_id.h @@ -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) \ diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc index 78987ee6114..bd3e2512a60 100644 --- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc +++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc @@ -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(); diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc index 61eb9779cc2..14a7c61aee1 100644 --- a/runtime/vm/compiler/backend/il.cc +++ b/runtime/vm/compiler/backend/il.cc @@ -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(); diff --git a/runtime/vm/compiler/backend/il_deserializer.cc b/runtime/vm/compiler/backend/il_deserializer.cc index 97a98b1fe00..d66ce309e9c 100644 --- a/runtime/vm/compiler/backend/il_deserializer.cc +++ b/runtime/vm/compiler/backend/il_deserializer.cc @@ -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; diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc index 84e15576e7d..54952240dcd 100644 --- a/runtime/vm/compiler/backend/range_analysis.cc +++ b/runtime/vm/compiler/backend/range_analysis.cc @@ -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; diff --git a/runtime/vm/compiler/backend/slot.cc b/runtime/vm/compiler/backend/slot.cc index 4a26aa7ea0a..8b14388f1b2 100644 --- a/runtime/vm/compiler/backend/slot.cc +++ b/runtime/vm/compiler/backend/slot.cc @@ -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: diff --git a/runtime/vm/compiler/backend/slot.h b/runtime/vm/compiler/backend/slot.h index da23fbd370a..824b73a8975 100644 --- a/runtime/vm/compiler/backend/slot.h +++ b/runtime/vm/compiler/backend/slot.h @@ -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; diff --git a/runtime/vm/compiler/ffi.cc b/runtime/vm/compiler/ffi.cc index 3b3ce8c480d..596d43c164d 100644 --- a/runtime/vm/compiler/ffi.cc +++ b/runtime/vm/compiler/ffi.cc @@ -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* 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* ArgumentRepresentationsBase( template 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) { diff --git a/runtime/vm/compiler/ffi.h b/runtime/vm/compiler/ffi.h index 7922a80e2b1..ea352032c30 100644 --- a/runtime/vm/compiler/ffi.h +++ b/runtime/vm/compiler/ffi.h @@ -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 kFFiCid 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. diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc index fad3c15447d..43c4571f2c8 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc @@ -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 diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h index dbd2b681bdd..4b12b227f09 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h @@ -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 { diff --git a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc index 6edaba17873..a76f2e5ea18 100644 --- a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc @@ -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(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(bytecode.PayloadStart()); + const KBCInstr* end = reinterpret_cast( + 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( diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 80df23748ba..991475954c5 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -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); diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 4748658f24e..300bf4b3218 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -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(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> as RawParameterVariable(0). This function + // will return a Pointer 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> 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. + 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> as RawParameterVariable(0). This function + // will takes a Pointer 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> 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; diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index 37750007547..ca4e78622d6 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -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, diff --git a/runtime/vm/compiler/frontend/prologue_builder.cc b/runtime/vm/compiler/frontend/prologue_builder.cc index d9ce861e6e8..ee1f4ee5791 100644 --- a/runtime/vm/compiler/frontend/prologue_builder.cc +++ b/runtime/vm/compiler/frontend/prologue_builder.cc @@ -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; diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h index 848564d7a1b..d5ac76f41e1 100644 --- a/runtime/vm/compiler/recognized_methods_list.h +++ b/runtime/vm/compiler/recognized_methods_list.h @@ -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). diff --git a/runtime/vm/constants_kbc.h b/runtime/vm/constants_kbc.h index a4b5d21801c..f5005a74323 100644 --- a/runtime/vm/constants_kbc.h +++ b/runtime/vm/constants_kbc.h @@ -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: diff --git a/runtime/vm/object.h b/runtime/vm/object.h index eaab012c631..b7a61e66ddd 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -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 { diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart index 089f0c997d8..49ac805b447 100644 --- a/sdk/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart @@ -7,11 +7,39 @@ import "dart:_internal" show patch; import 'dart:typed_data' show TypedData; +const Map _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() native "Ffi_sizeOf"; +int sizeOf() { + // 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(); +} + +int _sizeOf() native "Ffi_sizeOf"; Pointer _allocate(int count) native "Ffi_allocate"; +// Implemented in the method recognizer, bytecode interpreter uses runtime. Pointer _fromAddress(int ptr) native "Ffi_fromAddress"; // The real implementation of this function (for interface calls) lives in @@ -66,32 +94,32 @@ class Pointer { // 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() native "Ffi_load"; + R load() => + 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 elementAt(int index) native "Ffi_elementAt"; + Pointer elementAt(int index) => + Pointer.fromAddress(address + sizeOf() * 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 offsetBy(int offsetInBytes) native "Ffi_offsetBy"; + Pointer 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 cast() native "Ffi_cast"; + Pointer cast() => Pointer.fromAddress(address); @patch R asFunction() { @@ -106,8 +134,105 @@ class Pointer { _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 pointer) native "Ffi_loadInt8"; + +int _loadInt16(Pointer pointer) native "Ffi_loadInt16"; + +int _loadInt32(Pointer pointer) native "Ffi_loadInt32"; + +int _loadInt64(Pointer pointer) native "Ffi_loadInt64"; + +int _loadUint8(Pointer pointer) native "Ffi_loadUint8"; + +int _loadUint16(Pointer pointer) native "Ffi_loadUint16"; + +int _loadUint32(Pointer pointer) native "Ffi_loadUint32"; + +int _loadUint64(Pointer pointer) native "Ffi_loadUint64"; + +int _loadIntPtr(Pointer pointer) native "Ffi_loadIntPtr"; + +double _loadFloat(Pointer pointer) native "Ffi_loadFloat"; + +double _loadDouble(Pointer pointer) native "Ffi_loadDouble"; + +Pointer _loadPointer(Pointer> pointer) + native "Ffi_loadPointer"; + +S _loadStruct(Pointer pointer) native "Ffi_loadStruct"; + +void _storeInt8(Pointer pointer, int value) native "Ffi_storeInt8"; + +void _storeInt16(Pointer pointer, int value) native "Ffi_storeInt16"; + +void _storeInt32(Pointer pointer, int value) native "Ffi_storeInt32"; + +void _storeInt64(Pointer pointer, int value) native "Ffi_storeInt64"; + +void _storeUint8(Pointer pointer, int value) native "Ffi_storeUint8"; + +void _storeUint16(Pointer pointer, int value) native "Ffi_storeUint16"; + +void _storeUint32(Pointer pointer, int value) native "Ffi_storeUint32"; + +void _storeUint64(Pointer pointer, int value) native "Ffi_storeUint64"; + +void _storeIntPtr(Pointer pointer, int value) native "Ffi_storeIntPtr"; + +void _storeFloat(Pointer pointer, double value) native "Ffi_storeFloat"; + +void _storeDouble(Pointer pointer, double value) + native "Ffi_storeDouble"; + +void _storePointer( + Pointer> pointer, Pointer value) native "Ffi_storePointer"; + +Pointer _elementAtInt8(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 1 * index); + +Pointer _elementAtInt16(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 2 * index); + +Pointer _elementAtInt32(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 4 * index); + +Pointer _elementAtInt64(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 8 * index); + +Pointer _elementAtUint8(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 1 * index); + +Pointer _elementAtUint16(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 2 * index); + +Pointer _elementAtUint32(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 4 * index); + +Pointer _elementAtUint64(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 8 * index); + +Pointer _elementAtIntPtr(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + _intPtrSize * index); + +Pointer _elementAtFloat(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 4 * index); + +Pointer _elementAtDouble(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 8 * index); + +Pointer> _elementAtPointer( + Pointer> pointer, int index) => + Pointer.fromAddress(pointer.address + _intPtrSize * index); diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart index a975a138447..8b9b37a71d2 100644 --- a/sdk/lib/ffi/ffi.dart +++ b/sdk/lib/ffi/ffi.dart @@ -66,6 +66,8 @@ class Pointer 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 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 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. diff --git a/sdk_nnbd/lib/_internal/vm/lib/ffi_patch.dart b/sdk_nnbd/lib/_internal/vm/lib/ffi_patch.dart index 25c3f411e03..2b3aa44cd65 100644 --- a/sdk_nnbd/lib/_internal/vm/lib/ffi_patch.dart +++ b/sdk_nnbd/lib/_internal/vm/lib/ffi_patch.dart @@ -9,11 +9,39 @@ import "dart:_internal" show patch; import 'dart:typed_data' show TypedData; +const Map _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() native "Ffi_sizeOf"; +int sizeOf() { + // 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(); +} + +int _sizeOf() native "Ffi_sizeOf"; Pointer _allocate(int count) native "Ffi_allocate"; +// Implemented in the method recognizer, bytecode interpreter uses runtime. Pointer _fromAddress(int ptr) native "Ffi_fromAddress"; // The real implementation of this function (for interface calls) lives in @@ -25,6 +53,25 @@ DS _asFunctionInternal( 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(Function target, + Object exceptionalReturn) native "Ffi_nativeCallbackFunction"; + +Pointer _pointerFromFunction(Object function) + native "Ffi_pointerFromFunction"; + @patch @pragma("vm:entry-point") class Pointer { @@ -34,39 +81,47 @@ class Pointer { @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> fromFunction( @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() native "Ffi_load"; + R load() => + 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 elementAt(int index) native "Ffi_elementAt"; + Pointer elementAt(int index) => + Pointer.fromAddress(address + sizeOf() * 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 offsetBy(int offsetInBytes) native "Ffi_offsetBy"; + Pointer 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 cast() native "Ffi_cast"; + Pointer cast() => Pointer.fromAddress(address); @patch R asFunction() { @@ -81,8 +136,105 @@ class Pointer { _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 pointer) native "Ffi_loadInt8"; + +int _loadInt16(Pointer pointer) native "Ffi_loadInt16"; + +int _loadInt32(Pointer pointer) native "Ffi_loadInt32"; + +int _loadInt64(Pointer pointer) native "Ffi_loadInt64"; + +int _loadUint8(Pointer pointer) native "Ffi_loadUint8"; + +int _loadUint16(Pointer pointer) native "Ffi_loadUint16"; + +int _loadUint32(Pointer pointer) native "Ffi_loadUint32"; + +int _loadUint64(Pointer pointer) native "Ffi_loadUint64"; + +int _loadIntPtr(Pointer pointer) native "Ffi_loadIntPtr"; + +double _loadFloat(Pointer pointer) native "Ffi_loadFloat"; + +double _loadDouble(Pointer pointer) native "Ffi_loadDouble"; + +Pointer _loadPointer(Pointer> pointer) + native "Ffi_loadPointer"; + +S _loadStruct(Pointer pointer) native "Ffi_loadStruct"; + +void _storeInt8(Pointer pointer, int value) native "Ffi_storeInt8"; + +void _storeInt16(Pointer pointer, int value) native "Ffi_storeInt16"; + +void _storeInt32(Pointer pointer, int value) native "Ffi_storeInt32"; + +void _storeInt64(Pointer pointer, int value) native "Ffi_storeInt64"; + +void _storeUint8(Pointer pointer, int value) native "Ffi_storeUint8"; + +void _storeUint16(Pointer pointer, int value) native "Ffi_storeUint16"; + +void _storeUint32(Pointer pointer, int value) native "Ffi_storeUint32"; + +void _storeUint64(Pointer pointer, int value) native "Ffi_storeUint64"; + +void _storeIntPtr(Pointer pointer, int value) native "Ffi_storeIntPtr"; + +void _storeFloat(Pointer pointer, double value) native "Ffi_storeFloat"; + +void _storeDouble(Pointer pointer, double value) + native "Ffi_storeDouble"; + +void _storePointer( + Pointer> pointer, Pointer value) native "Ffi_storePointer"; + +Pointer _elementAtInt8(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 1 * index); + +Pointer _elementAtInt16(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 2 * index); + +Pointer _elementAtInt32(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 4 * index); + +Pointer _elementAtInt64(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 8 * index); + +Pointer _elementAtUint8(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 1 * index); + +Pointer _elementAtUint16(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 2 * index); + +Pointer _elementAtUint32(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 4 * index); + +Pointer _elementAtUint64(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 8 * index); + +Pointer _elementAtIntPtr(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + _intPtrSize * index); + +Pointer _elementAtFloat(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 4 * index); + +Pointer _elementAtDouble(Pointer pointer, int index) => + Pointer.fromAddress(pointer.address + 8 * index); + +Pointer> _elementAtPointer( + Pointer> pointer, int index) => + Pointer.fromAddress(pointer.address + _intPtrSize * index); diff --git a/sdk_nnbd/lib/ffi/ffi.dart b/sdk_nnbd/lib/ffi/ffi.dart index eb1bdcde5c8..5a011f9ddf1 100644 --- a/sdk_nnbd/lib/ffi/ffi.dart +++ b/sdk_nnbd/lib/ffi/ffi.dart @@ -68,6 +68,8 @@ class Pointer 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 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 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. diff --git a/tests/ffi/data_test.dart b/tests/ffi/data_test.dart index a86f9af2e3f..3387731125a 100644 --- a/tests/ffi/data_test.dart +++ b/tests/ffi/data_test.dart @@ -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 p1 = Pointer.allocate(); - ffi.Pointer 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.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 p2 = p.cast(); + p.free(); } diff --git a/tests/ffi/regress_37254_test.dart b/tests/ffi/regress_37254_test.dart index faf98976c6b..6713d5e9082 100644 --- a/tests/ffi/regress_37254_test.dart +++ b/tests/ffi/regress_37254_test.dart @@ -22,8 +22,8 @@ // b P//P P//P P//P // a // P>//P> 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> 4 ok 5 ok 6 fail at runtime // @@ -81,9 +81,8 @@ void store2() { final Pointer b = Pointer.allocate(); // Reified Pointer 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 b = Pointer.allocate().cast>(); - // 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(); + } } diff --git a/tools/VERSION b/tools/VERSION index c7c3e89440d..2f4cd579f7c 100644 --- a/tools/VERSION +++ b/tools/VERSION @@ -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