mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:20:36 +00:00
Reland "[VM] Make @pragma annotations work generally"
Currently the @pramga('vm:exact-result-type') annotation only works if the function is a recognized method. This change changes that to make the VM just look if a function has the annotation (no matter if it's also in the list of recognized methods or not). Furthermore this CL lets the type propgagator use @pragma('vm:exact-result-type') annotations to narrow the [CompileType] set on [LoadFieldInstr]s. Since the @pragma is a general feature, this CL moves the `Function::FindPragma()` to `Library::FindPragma` (which is where any other metadata lookup happens). We also let the `FindPragma` accept any of Class/Function/Field objects. Furthermore the `FindPragma()` function is fixed to handle the case when the evaluation of the metadata results in an error. In this case we simply claim to not have found a pragma annotation. Issue https://github.com/dart-lang/sdk/issues/31954 Change-Id: If03f566e334cd53549985823ee3dd6b5e9672969 Reviewed-on: https://dart-review.googlesource.com/c/85163 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
This commit is contained in:
parent
4646e804dc
commit
94c325a889
|
@ -5,6 +5,7 @@
|
|||
// part of "common_patch.dart";
|
||||
|
||||
@patch
|
||||
@pragma("vm:entry-point")
|
||||
class _Platform {
|
||||
@patch
|
||||
static int _numberOfProcessors() native "Platform_NumberOfProcessors";
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
```
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() ||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue