diff --git a/runtime/bin/platform_patch.dart b/runtime/bin/platform_patch.dart index dd7d88aaca1..99b4e7cbac3 100644 --- a/runtime/bin/platform_patch.dart +++ b/runtime/bin/platform_patch.dart @@ -5,6 +5,7 @@ // part of "common_patch.dart"; @patch +@pragma("vm:entry-point") class _Platform { @patch static int _numberOfProcessors() native "Platform_NumberOfProcessors"; diff --git a/runtime/bin/process_patch.dart b/runtime/bin/process_patch.dart index 375fb933df9..373eb7d6ff5 100644 --- a/runtime/bin/process_patch.dart +++ b/runtime/bin/process_patch.dart @@ -192,6 +192,7 @@ class ProcessInfo { static _currentRss() native "ProcessInfo_CurrentRSS"; } +@pragma("vm:entry-point") class _ProcessStartStatus { @pragma("vm:entry-point", "set") int _errorCode; // Set to OS error code if process start failed. diff --git a/runtime/bin/secure_socket_patch.dart b/runtime/bin/secure_socket_patch.dart index 5d61f88d2c6..876e789fc15 100644 --- a/runtime/bin/secure_socket_patch.dart +++ b/runtime/bin/secure_socket_patch.dart @@ -64,6 +64,7 @@ class _SecureSocket extends _Socket implements SecureSocket { * are backed by an external C array of bytes, so that both Dart code and * native code can access the same data. */ +@pragma("vm:entry-point") class _SecureFilterImpl extends NativeFieldWrapperClass1 implements _SecureFilter { // Performance is improved if a full buffer of plaintext fits diff --git a/runtime/docs/compiler/result_type_pragma.md b/runtime/docs/compiler/result_type_pragma.md index 2bc5032099e..8c72b13d424 100644 --- a/runtime/docs/compiler/result_type_pragma.md +++ b/runtime/docs/compiler/result_type_pragma.md @@ -5,6 +5,9 @@ the pragma `vm:exact-result-type` to declare an exact return type different than the return type in the signature of the method. There are three limitations on this pragma: +Similarly if a field is marked with the same annotation it must be guaranteed +that a load from the field returns in the specified exact result type. + 0. The Dart object returned by the method at runtime must have exactly the type specified in the annotation (not a subtype). @@ -32,6 +35,11 @@ class B extends A {} @pragma('vm:exact-result-type', B) A foo() native 'foo_impl'; + +class C { + @pragma('vm:exact-result-type', int) + final int value; +} ``` ### Reference to type via path @@ -39,4 +47,10 @@ A foo() native 'foo_impl'; ```dart @pragma('vm:exact-result-type', 'dart:core#_Smi'); int foo() native 'foo_impl'; + +class C { + @pragma('vm:exact-result-type', 'dart:core#_Smi') + final int value; +} + ``` diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc index 2e91e3c5482..934a14337bb 100644 --- a/runtime/vm/compiler/aot/precompiler.cc +++ b/runtime/vm/compiler/aot/precompiler.cc @@ -1019,20 +1019,22 @@ void Precompiler::AddAnnotatedRoots() { while (it.HasNext()) { cls = it.GetNextClass(); + // Check for @pragma on the class itself. if (cls.has_pragma()) { - // Check for @pragma on the class itself. metadata ^= lib.GetMetadata(cls); if (metadata_defines_entrypoint() == EntryPointPragma::kAlways) { AddInstantiatedClass(cls); } + } - // Check for @pragma on any fields in the class. - members = cls.fields(); - implicit_getters = GrowableObjectArray::New(members.Length()); - implicit_setters = GrowableObjectArray::New(members.Length()); - implicit_static_getters = GrowableObjectArray::New(members.Length()); - for (intptr_t k = 0; k < members.Length(); ++k) { - field ^= members.At(k); + // Check for @pragma on any fields in the class. + members = cls.fields(); + implicit_getters = GrowableObjectArray::New(members.Length()); + implicit_setters = GrowableObjectArray::New(members.Length()); + implicit_static_getters = GrowableObjectArray::New(members.Length()); + for (intptr_t k = 0; k < members.Length(); ++k) { + field ^= members.At(k); + if (field.has_pragma()) { metadata ^= lib.GetMetadata(field); if (metadata.IsNull()) continue; EntryPointPragma pragma = metadata_defines_entrypoint(); diff --git a/runtime/vm/compiler/backend/slot.cc b/runtime/vm/compiler/backend/slot.cc index c1d69a5c687..7db389f57b6 100644 --- a/runtime/vm/compiler/backend/slot.cc +++ b/runtime/vm/compiler/backend/slot.cc @@ -126,10 +126,19 @@ const Slot& Slot::Get(const Field& field, intptr_t nullable_cid = kDynamicCid; bool is_nullable = true; + if (field.has_pragma()) { + const intptr_t cid = MethodRecognizer::ResultCidFromPragma(field); + if (cid != kDynamicCid) { + nullable_cid = cid; + is_nullable = false; + } + } + if (field.guarded_cid() != kIllegalCid && field.guarded_cid() != kDynamicCid) { - nullable_cid = field.guarded_cid(); - is_nullable = field.is_nullable(); + nullable_cid = + nullable_cid != kDynamicCid ? nullable_cid : field.guarded_cid(); + is_nullable = is_nullable && field.is_nullable(); if (thread->isolate()->use_field_guards()) { ASSERT(parsed_function != nullptr); diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc index f17daa6c08e..88c8e70f4e1 100644 --- a/runtime/vm/compiler/backend/type_propagator.cc +++ b/runtime/vm/compiler/backend/type_propagator.cc @@ -1184,8 +1184,11 @@ CompileType InstanceCallInstr::ComputeType() const { CompileType PolymorphicInstanceCallInstr::ComputeType() const { if (IsSureToCallSingleRecognizedTarget()) { const Function& target = *targets_.TargetAt(0)->target; - if (target.recognized_kind() != MethodRecognizer::kUnknown) { - return CompileType::FromCid(MethodRecognizer::ResultCid(target)); + if (target.has_pragma()) { + const intptr_t cid = MethodRecognizer::ResultCidFromPragma(target); + if (cid != kDynamicCid) { + return CompileType::FromCid(cid); + } } } @@ -1207,8 +1210,11 @@ CompileType StaticCallInstr::ComputeType() const { return *inferred_type; } - if (function_.recognized_kind() != MethodRecognizer::kUnknown) { - return CompileType::FromCid(MethodRecognizer::ResultCid(function_)); + if (function_.has_pragma()) { + const intptr_t cid = MethodRecognizer::ResultCidFromPragma(function_); + if (cid != kDynamicCid) { + return CompileType::FromCid(cid); + } } if (Isolate::Current()->can_use_strong_mode_types()) { diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index e5b3cf07499..4f860f86e9d 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -1633,25 +1633,30 @@ Fragment StreamingFlowGraphBuilder::BuildFirstTimePrologue( Fragment StreamingFlowGraphBuilder::BuildEntryPointsIntrospection() { if (!FLAG_enable_testing_pragmas) return Drop(); - Function& function = Function::Handle(parsed_function()->function().raw()); + auto& function = Function::Handle(Z, parsed_function()->function().raw()); if (function.IsImplicitClosureFunction()) { - const Function& parent = - Function::ZoneHandle(Z, function.parent_function()); - const String& func_name = String::ZoneHandle(Z, parent.name()); - const Class& owner = Class::ZoneHandle(Z, parent.Owner()); + const auto& parent = Function::Handle(Z, function.parent_function()); + const auto& func_name = String::Handle(Z, parent.name()); + const auto& owner = Class::Handle(Z, parent.Owner()); function = owner.LookupFunction(func_name); } - Object& options = Object::Handle(); - if (!function.FindPragma(I, Symbols::vm_trace_entrypoints(), &options) || + auto& tmp = Object::Handle(Z); + tmp = function.Owner(); + tmp = Class::Cast(tmp).library(); + auto& library = Library::Cast(tmp); + + Object& options = Object::Handle(Z); + if (!library.FindPragma(H.thread(), function, Symbols::vm_trace_entrypoints(), + &options) || options.IsNull() || !options.IsClosure()) { return Drop(); } - Closure& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw()); + auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw()); LocalVariable* entry_point_num = MakeTemporary(); - String& function_name = String::ZoneHandle( + auto& function_name = String::ZoneHandle( Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld)); if (parsed_function()->function().IsImplicitClosureFunction()) { function_name = String::Concat( @@ -1669,7 +1674,7 @@ Fragment StreamingFlowGraphBuilder::BuildEntryPointsIntrospection() { call_hook += Constant(Function::ZoneHandle(Z, closure.function())); call_hook += B->ClosureCall(TokenPosition::kNoSource, /*type_args_len=*/0, /*argument_count=*/3, - /*argument_names=*/Array::Handle()); + /*argument_names=*/Array::ZoneHandle(Z)); call_hook += Drop(); // result of closure call call_hook += Drop(); // entrypoint number return call_hook; diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 64485a3d379..348d98f9400 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -546,8 +546,8 @@ void FlowGraphBuilder::SetResultTypeForStaticCall( call->set_is_known_list_constructor(true); return; } - if (target.recognized_kind() != MethodRecognizer::kUnknown) { - intptr_t recognized_cid = MethodRecognizer::ResultCid(target); + if (target.has_pragma()) { + intptr_t recognized_cid = MethodRecognizer::ResultCidFromPragma(target); if (recognized_cid != kDynamicCid) { ASSERT((result_type == NULL) || (result_type->cid == kDynamicCid) || (result_type->cid == recognized_cid)); diff --git a/runtime/vm/compiler/method_recognizer.cc b/runtime/vm/compiler/method_recognizer.cc index d3dbb9089a2..267c1d812af 100644 --- a/runtime/vm/compiler/method_recognizer.cc +++ b/runtime/vm/compiler/method_recognizer.cc @@ -35,20 +35,32 @@ intptr_t MethodRecognizer::NumArgsCheckedForStaticCall( } } -intptr_t MethodRecognizer::ResultCid(const Function& function) { - // Use the 'vm:exact-result-type' annotation if available. This can only be - // used within the core library, see 'result_type_pragma.md', detail 1.2 for - // explanation. - Class& cls = Thread::Current()->ClassHandle(); - Library& lib = Thread::Current()->LibraryHandle(); - cls = function.Owner(); - lib = cls.library(); - const bool can_use_pragma = lib.IsAnyCoreLibrary(); - cls = Class::null(); +intptr_t MethodRecognizer::ResultCidFromPragma( + const Object& function_or_field) { + // TODO(vm-team): The caller should only call us if the + // function_or_field.has_pragma(). If this method turns out to be a + // performance problem nonetheless, we could consider adding a cache. + auto T = Thread::Current(); + auto Z = T->zone(); + bool is_recognized_method = false; + auto& klass = Class::Handle(Z); + if (function_or_field.IsFunction()) { + auto& function = Function::Cast(function_or_field); + ASSERT(function.has_pragma()); + is_recognized_method = + function.recognized_kind() != MethodRecognizer::kUnknown; + klass = function.Owner(); + } else { + auto& field = Field::Cast(function_or_field); + ASSERT(field.has_pragma()); + klass = field.Owner(); + } + auto& library = Library::Handle(Z, klass.library()); + const bool can_use_pragma = library.IsAnyCoreLibrary(); if (can_use_pragma) { - Isolate* I = Isolate::Current(); - auto& option = Object::Handle(); - if (function.FindPragma(I, Symbols::vm_exact_result_type(), &option)) { + auto& option = Object::Handle(Z); + if (library.FindPragma(T, function_or_field, + Symbols::vm_exact_result_type(), &option)) { if (option.IsType()) { return Type::Cast(option).type_class_id(); } else if (option.IsString()) { @@ -68,17 +80,13 @@ intptr_t MethodRecognizer::ResultCid(const Function& function) { } } if (!parse_failure && library_end > 0) { - auto& libraryUri = String::Handle( - String::SubString(str, 0, library_end, Heap::kOld)); - auto& className = String::Handle( - String::SubString(str, library_end + 1, - str.Length() - library_end - 1, Heap::kOld)); - - Library& lib = Library::Handle( - Library::LookupLibrary(Thread::Current(), libraryUri)); - if (!lib.IsNull()) { - Class& klass = - Class::Handle(lib.LookupClassAllowPrivate(className)); + auto& tmp = String::Handle(Z); + tmp = String::SubString(str, 0, library_end, Heap::kOld); + library = Library::LookupLibrary(Thread::Current(), tmp); + if (!library.IsNull()) { + tmp = String::SubString(str, library_end + 1, + str.Length() - library_end - 1, Heap::kOld); + klass = library.LookupClassAllowPrivate(tmp); if (!klass.IsNull()) { return klass.id(); } @@ -88,9 +96,11 @@ intptr_t MethodRecognizer::ResultCid(const Function& function) { } } - // No result-type annotation can be used, so fall back on the table of - // recognized methods. - switch (function.recognized_kind()) { + // Sanity check that all recognized methods which have a non-kDynamicCid were + // already recognized via @pragmas()s. + if (is_recognized_method) { + const auto& function = Function::Cast(function_or_field); + switch (function.recognized_kind()) { #define DEFINE_CASE(cname, fname, ename, result_type, fingerprint) \ case k##ename: { \ const intptr_t cid = k##result_type##Cid; \ @@ -108,11 +118,14 @@ intptr_t MethodRecognizer::ResultCid(const Function& function) { } \ return cid; \ } - RECOGNIZED_LIST(DEFINE_CASE) + RECOGNIZED_LIST(DEFINE_CASE) #undef DEFINE_CASE - default: - return kDynamicCid; + default: + return kDynamicCid; + } } + + return kDynamicCid; } intptr_t MethodRecognizer::MethodKindToReceiverCid(Kind kind) { diff --git a/runtime/vm/compiler/method_recognizer.h b/runtime/vm/compiler/method_recognizer.h index d53528c6291..f25d177453b 100644 --- a/runtime/vm/compiler/method_recognizer.h +++ b/runtime/vm/compiler/method_recognizer.h @@ -560,7 +560,15 @@ class MethodRecognizer : public AllStatic { static bool AlwaysInline(const Function& function); static bool PolymorphicTarget(const Function& function); static intptr_t NumArgsCheckedForStaticCall(const Function& function); - static intptr_t ResultCid(const Function& function); + + // Try to find an annotation of the form + // @pragma("vm:exact-result-type", int) + // @pragma("vm:exact-result-type", "dart:core#_Smi") + // and return the exact cid if found or kDynamicCid otherwise. + // + // See [result_type_pragma.md]. + static intptr_t ResultCidFromPragma(const Object& function_or_field); + static intptr_t MethodKindToReceiverCid(Kind kind); static const char* KindToCString(Kind kind); diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 13ad3fc2a69..c6a5a932bcb 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -5064,7 +5064,7 @@ static void CheckIsEntryPoint(const Class& klass) { "because it was not annotated with @pragma('vm:entry-point').\n" "ERROR: See " "https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/" - "aot/entry_point_pragma.md", + "aot/entry_point_pragma.md\n", String::Handle(klass.UserVisibleName()).ToCString()); UNREACHABLE(); } diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index aab97e8a19c..b68203a6d0f 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -953,9 +953,6 @@ RawLibrary* KernelLoader::LoadLibrary(intptr_t index) { ReadVMAnnotations(annotation_count, &native_name_unused, &is_potential_native_unused, &has_pragma_annotation); } - if (has_pragma_annotation) { - toplevel_class.set_has_pragma(true); - } field_helper.SetJustRead(FieldHelper::kAnnotations); field_helper.ReadUntilExcluding(FieldHelper::kType); @@ -969,6 +966,7 @@ RawLibrary* KernelLoader::LoadLibrary(intptr_t index) { Field::NewTopLevel(name, is_final, field_helper.IsConst(), script_class, field_helper.position_, field_helper.end_position_)); field.set_kernel_offset(field_offset); + field.set_has_pragma(has_pragma_annotation); const AbstractType& type = T.BuildType(); // read type. field.SetFieldType(type); ReadInferredType(field, field_offset + library_kernel_offset_); @@ -1344,9 +1342,6 @@ void KernelLoader::FinishClassLoading(const Class& klass, ReadVMAnnotations(annotation_count, &native_name_unused, &is_potential_native_unused, &has_pragma_annotation); } - if (has_pragma_annotation) { - klass.set_has_pragma(true); - } field_helper.SetJustRead(FieldHelper::kAnnotations); field_helper.ReadUntilExcluding(FieldHelper::kType); diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 84032914668..d3c58bd1841 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -2911,27 +2911,49 @@ RawFunction* Function::GetMethodExtractor(const String& getter_name) const { return result.raw(); } -bool Function::FindPragma(Isolate* I, - const String& pragma_name, - Object* options) const { - if (!has_pragma()) return false; +bool Library::FindPragma(Thread* T, + const Object& obj, + const String& pragma_name, + Object* options) const { + auto I = T->isolate(); + auto Z = T->zone(); + auto& lib = Library::Handle(Z); + if (obj.IsClass()) { + auto& klass = Class::Cast(obj); + if (!klass.has_pragma()) return false; + lib = klass.library(); + } else if (obj.IsFunction()) { + auto& function = Function::Cast(obj); + if (!function.has_pragma()) return false; + lib = Class::Handle(Z, function.Owner()).library(); + } else if (obj.IsField()) { + auto& field = Field::Cast(obj); + if (!field.has_pragma()) return false; + lib = Class::Handle(Z, field.Owner()).library(); + } else { + UNREACHABLE(); + } - auto& klass = Class::Handle(Owner()); - auto& lib = Library::Handle(klass.library()); + Object& metadata_obj = Object::Handle(Z, lib.GetMetadata(obj)); + if (metadata_obj.IsUnwindError()) { + Report::LongJump(UnwindError::Cast(metadata_obj)); + } - auto& pragma_class = - Class::Handle(Isolate::Current()->object_store()->pragma_class()); + // If there is a compile-time error while evaluating the metadata, we will + // simply claim there was no @pramga annotation. + if (metadata_obj.IsNull() || metadata_obj.IsLanguageError()) { + return false; + } + ASSERT(metadata_obj.IsArray()); + + auto& metadata = Array::Cast(metadata_obj); + auto& pragma_class = Class::Handle(Z, I->object_store()->pragma_class()); auto& pragma_name_field = - Field::Handle(pragma_class.LookupField(Symbols::name())); + Field::Handle(Z, pragma_class.LookupField(Symbols::name())); auto& pragma_options_field = - Field::Handle(pragma_class.LookupField(Symbols::options())); + Field::Handle(Z, pragma_class.LookupField(Symbols::options())); - Array& metadata = Array::Handle(); - metadata ^= lib.GetMetadata(Function::Handle(raw())); - - if (metadata.IsNull()) return false; - - auto& pragma = Object::Handle(); + auto& pragma = Object::Handle(Z); for (intptr_t i = 0; i < metadata.Length(); ++i) { pragma = metadata.At(i); if (pragma.clazz() != pragma_class.raw() || diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 5e7267767b9..89290618822 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -2280,8 +2280,6 @@ class Function : public Object { // Return true if any parent function of this function is generic. bool HasGenericParent() const; - bool FindPragma(Isolate* I, const String& pragma_name, Object* options) const; - // Not thread-safe; must be called in the main thread. // Sets function's code and code's function. void InstallOptimizedCode(const Code& code) const; @@ -3825,6 +3823,18 @@ class Library : public Object { const Function& to_fun) const; RawObject* GetMetadata(const Object& obj) const; + // Tries to finds a @pragma annotation on [object]. + // + // If successful returns `true`. If an error happens during constant + // evaluation, returns `false. + // + // WARNING: If the isolate received an [UnwindError] this function will not + // return and rather unwinds until the enclosing setjmp() handler. + bool FindPragma(Thread* T, + const Object& object, + const String& pragma_name, + Object* options) const; + RawClass* toplevel_class() const { return raw_ptr()->toplevel_class_; } void set_toplevel_class(const Class& value) const;