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:
Martin Kustermann 2018-11-22 10:54:44 +00:00 committed by commit-bot@chromium.org
parent 4646e804dc
commit 94c325a889
15 changed files with 169 additions and 82 deletions

View file

@ -5,6 +5,7 @@
// part of "common_patch.dart";
@patch
@pragma("vm:entry-point")
class _Platform {
@patch
static int _numberOfProcessors() native "Platform_NumberOfProcessors";

View file

@ -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.

View file

@ -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

View file

@ -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;
}
```

View file

@ -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();

View file

@ -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);

View file

@ -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()) {

View file

@ -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;

View file

@ -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));

View file

@ -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) {

View file

@ -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);

View file

@ -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();
}

View file

@ -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);

View file

@ -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() ||

View file

@ -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;