[vm] Support definition of entry-points via @pragma('vm.extern') annotations.

The `@pragma` annotations are evaluated by the constants transformation and
visible to TFA and the precompiler, which match on the "options" field of the
annotation to determine whether to mark the class/procedure as a root.

This required enabling the transformation of annotation constants by default.

# Test Plan

The "vmservice_io.main" entry-point is removed from `main.cc` and annotated with
`@pragma`. All precompiler tests will crash if "vmservice_io.main" is not
available at runtime.

Debug/release precompiler bots are visible in "cl-linux" button.

Change-Id: I03c5d6ba7918672ed9905fcaee8dabe675a93a5d
Reviewed-on: https://dart-review.googlesource.com/56660
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index 13b28bf..6eb4d87 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -900,6 +900,7 @@
 
 type InstanceConstant extends Constant {
   Byte tag = 7;
+  CanonicalNameReference class;
   List<DartType> typeArguments;
   List<[FieldReference, ConstantReference]> values;
 }
diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart
index e0fe377..de05b25 100644
--- a/pkg/kernel/lib/core_types.dart
+++ b/pkg/kernel/lib/core_types.dart
@@ -98,6 +98,10 @@
   /// The `dart:mirrors` library, or `null` if the component does not use it.
   Library _mirrorsLibrary;
 
+  Class _pragmaClass;
+  Field _pragmaName;
+  Field _pragmaOptions;
+
   CoreTypes(Component component)
       : index = new LibraryIndex.coreLibraries(component);
 
@@ -318,6 +322,18 @@
     return _objectEquals ??= index.getMember('dart:core', 'Object', '==');
   }
 
+  Class get pragmaClass {
+    return _pragmaClass ??= index.getClass('dart:core', 'pragma');
+  }
+
+  Field get pragmaName {
+    return _pragmaName ??= index.getMember('dart:core', 'pragma', 'name');
+  }
+
+  Field get pragmaOptions {
+    return _pragmaOptions ??= index.getMember('dart:core', 'pragma', 'options');
+  }
+
   Procedure get printProcedure {
     return _printProcedure ??= index.getTopLevelMember('dart:core', 'print');
   }
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index 332017e..72a6607 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -163,7 +163,7 @@
     constants.transformComponent(component, vmConstants,
         keepFields: true,
         strongMode: true,
-        evaluateAnnotations: false,
+        evaluateAnnotations: true,
         enableAsserts: enableAsserts,
         errorReporter:
             new ForwardConstantEvaluationErrors(context, typeEnvironment));
diff --git a/pkg/vm/lib/transformations/type_flow/analysis.dart b/pkg/vm/lib/transformations/type_flow/analysis.dart
index 40a3e79..4c6eff1 100644
--- a/pkg/vm/lib/transformations/type_flow/analysis.dart
+++ b/pkg/vm/lib/transformations/type_flow/analysis.dart
@@ -12,6 +12,7 @@
 import 'package:kernel/ast.dart' hide Statement, StatementVisitor;
 import 'package:kernel/class_hierarchy.dart' show ClosedWorldClassHierarchy;
 import 'package:kernel/library_index.dart' show LibraryIndex;
+import 'package:kernel/core_types.dart' show CoreTypes;
 import 'package:kernel/type_environment.dart';
 
 import 'calls.dart';
@@ -1148,7 +1149,7 @@
   final Map<Member, Summary> _summaries = <Member, Summary>{};
   final Map<Field, _FieldValue> _fieldValues = <Field, _FieldValue>{};
 
-  TypeFlowAnalysis(
+  TypeFlowAnalysis(Component component, CoreTypes coreTypes,
       ClosedWorldClassHierarchy hierarchy, this.environment, this.libraryIndex,
       {List<String> entryPointsJSONFiles})
       : nativeCodeOracle = new NativeCodeOracle(libraryIndex) {
@@ -1161,6 +1162,8 @@
     if (entryPointsJSONFiles != null) {
       nativeCodeOracle.processEntryPointsJSONFiles(entryPointsJSONFiles, this);
     }
+
+    component.accept(new PragmaEntryPointsVisitor(coreTypes, this));
   }
 
   _Invocation get currentInvocation => workList.callStack.last;
diff --git a/pkg/vm/lib/transformations/type_flow/native_code.dart b/pkg/vm/lib/transformations/type_flow/native_code.dart
index d3f72d6..8e4a171 100644
--- a/pkg/vm/lib/transformations/type_flow/native_code.dart
+++ b/pkg/vm/lib/transformations/type_flow/native_code.dart
@@ -11,6 +11,7 @@
 
 import 'package:kernel/ast.dart';
 import 'package:kernel/library_index.dart' show LibraryIndex;
+import 'package:kernel/core_types.dart' show CoreTypes;
 
 // TODO(alexmarkov): Move findNativeName out of treeshaker and avoid dependency
 // on unrelated transformation.
@@ -31,6 +32,66 @@
   ConcreteType addAllocatedClass(Class c);
 }
 
+/// Some entry points are not listed in any JSON file but are marked with the
+/// `@pragma('vm.entry_point', ...)` annotation instead.
+///
+/// Currently Procedure`s (action "call") can be annotated in this way.
+//
+// TODO(sjindel): Support all types of entry points.
+class PragmaEntryPointsVisitor extends RecursiveVisitor {
+  final EntryPointsListener entryPoints;
+  final CoreTypes coreTypes;
+
+  PragmaEntryPointsVisitor(this.coreTypes, this.entryPoints);
+
+  bool _definesRoot(InstanceConstant constant) {
+    if (constant.classReference.node != coreTypes.pragmaClass) return false;
+
+    Constant name = constant.fieldValues[coreTypes.pragmaName.reference];
+    assertx(name != null);
+    if (name is! StringConstant ||
+        (name as StringConstant).value != "vm.entry_point") {
+      return false;
+    }
+
+    Constant options = constant.fieldValues[coreTypes.pragmaOptions.reference];
+    assertx(options != null);
+    if (options is NullConstant) return true;
+    return options is BoolConstant && options.value;
+  }
+
+  bool _annotationsDefineRoot(List<Expression> annotations) {
+    for (var annotation in annotations) {
+      if (annotation is ConstantExpression) {
+        Constant constant = annotation.constant;
+        if (constant is InstanceConstant) {
+          if (_definesRoot(constant)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  @override
+  visitClass(Class klass) {
+    if (_annotationsDefineRoot(klass.annotations)) {
+      entryPoints.addAllocatedClass(klass);
+    }
+    klass.visitChildren(this);
+  }
+
+  @override
+  visitProcedure(Procedure proc) {
+    if (_annotationsDefineRoot(proc.annotations)) {
+      entryPoints.addRawCall(proc.isInstanceMember
+          ? new InterfaceSelector(proc, callKind: CallKind.Method)
+          : new DirectSelector(proc, callKind: CallKind.Method));
+    }
+  }
+}
+
 /// Provides insights into the behavior of native code.
 class NativeCodeOracle {
   final Map<String, List<Map<String, dynamic>>> _nativeMethods =
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index b3a037a..cfcbec7 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -51,7 +51,8 @@
   Statistics.reset();
   final analysisStopWatch = new Stopwatch()..start();
 
-  final typeFlowAnalysis = new TypeFlowAnalysis(hierarchy, types, libraryIndex,
+  final typeFlowAnalysis = new TypeFlowAnalysis(
+      component, coreTypes, hierarchy, types, libraryIndex,
       entryPointsJSONFiles: entryPoints);
 
   Procedure main = component.mainMethod;
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index d46910b..7b8346b 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -857,7 +857,6 @@
     {"dart:isolate", "::", "_getIsolateScheduleImmediateClosure"},
     {"dart:isolate", "::", "_setupHooks"},
     {"dart:isolate", "::", "_startMainIsolate"},
-    {"dart:vmservice_io", "::", "main"},
     // Fields
     {"dart:_builtin", "::", "_isolateId"},
     {"dart:_builtin", "::", "_loadPort"},
diff --git a/runtime/bin/vmservice/vmservice_io.dart b/runtime/bin/vmservice/vmservice_io.dart
index 14f6cb6..9dc5954 100644
--- a/runtime/bin/vmservice/vmservice_io.dart
+++ b/runtime/bin/vmservice/vmservice_io.dart
@@ -220,6 +220,7 @@
   _signalSubscription = _signalWatch(ProcessSignal.SIGQUIT).listen(_onSignal);
 }
 
+@pragma("vm.entry_point")
 main() {
   // Set embedder hooks.
   VMServiceEmbedderHooks.cleanup = cleanupCallback;
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 5b2415a..414b63a 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -313,6 +313,7 @@
 
         // Start with the allocations and invocations that happen from C++.
         AddRoots(embedder_entry_points);
+        AddAnnotatedRoots();
 
         // Compile newly found targets and add their callees until we reach a
         // fixed point.
@@ -339,6 +340,7 @@
       Class& null_class = Class::Handle(Z);
       Function& null_function = Function::Handle(Z);
       I->object_store()->set_future_class(null_class);
+      I->object_store()->set_pragma_class(null_class);
       I->object_store()->set_completer_class(null_class);
       I->object_store()->set_stream_iterator_class(null_class);
       I->object_store()->set_symbol_class(null_class);
@@ -732,6 +734,8 @@
 }
 
 void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) {
+  PrecompilerEntryPointsPrinter entry_points_printer(zone());
+
   // Note that <rootlibrary>.main is not a root. The appropriate main will be
   // discovered through _getMainClosure.
 
@@ -739,8 +743,6 @@
 
   AddSelector(Symbols::Call());  // For speed, not correctness.
 
-  PrecompilerEntryPointsPrinter entry_points_printer(zone());
-
   // Allocated from C++.
   Class& cls = Class::Handle(Z);
   for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
@@ -1505,6 +1507,61 @@
   }
 }
 
+// Adds all values annotated with @pragma('vm.entry_point') as roots.
+void Precompiler::AddAnnotatedRoots() {
+  auto& lib = Library::Handle(Z);
+  auto& cls = Class::Handle(isolate()->object_store()->pragma_class());
+  auto& functions = Array::Handle(Z);
+  auto& function = Function::Handle(Z);
+  auto& metadata = Array::Handle(Z);
+  auto& pragma = Object::Handle(Z);
+  auto& pragma_options = Object::Handle(Z);
+  auto& pragma_name_field = Field::Handle(Z, cls.LookupField(Symbols::name()));
+  auto& pragma_options_field =
+      Field::Handle(Z, cls.LookupField(Symbols::options()));
+
+  for (intptr_t i = 0; i < libraries_.Length(); i++) {
+    lib ^= libraries_.At(i);
+    ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
+    while (it.HasNext()) {
+      cls = it.GetNextClass();
+      functions = cls.functions();
+      for (intptr_t k = 0; k < functions.Length(); k++) {
+        function ^= functions.At(k);
+        if (!function.has_pragma()) continue;
+        metadata ^= lib.GetMetadata(function);
+        if (metadata.IsNull()) continue;
+
+        bool is_entry_point = false;
+        for (intptr_t i = 0; i < metadata.Length(); i++) {
+          pragma = metadata.At(i);
+          if (pragma.clazz() != isolate()->object_store()->pragma_class()) {
+            continue;
+          }
+          if (Instance::Cast(pragma).GetField(pragma_name_field) !=
+              Symbols::vm_entry_point().raw()) {
+            continue;
+          }
+          pragma_options =
+              Instance::Cast(pragma).GetField(pragma_options_field);
+          if (pragma_options.raw() == Bool::null() ||
+              pragma_options.raw() == Bool::True().raw()) {
+            is_entry_point = true;
+            break;
+          }
+        }
+
+        if (!is_entry_point) continue;
+
+        AddFunction(function);
+        if (function.IsGenerativeConstructor()) {
+          AddInstantiatedClass(cls);
+        }
+      }
+    }
+  }
+}
+
 void Precompiler::CheckForNewDynamicFunctions() {
   Library& lib = Library::Handle(Z);
   Class& cls = Class::Handle(Z);
diff --git a/runtime/vm/compiler/aot/precompiler.h b/runtime/vm/compiler/aot/precompiler.h
index 93e8326..ccbac6c 100644
--- a/runtime/vm/compiler/aot/precompiler.h
+++ b/runtime/vm/compiler/aot/precompiler.h
@@ -351,6 +351,7 @@
 
   void DoCompileAll(Dart_QualifiedFunctionName embedder_entry_points[]);
   void AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]);
+  void AddAnnotatedRoots();
   void AddEntryPoints(Dart_QualifiedFunctionName entry_points[],
                       PrecompilerEntryPointsPrinter* entry_points_printer);
   void Iterate();
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 93d70f3..fc17971 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -11097,10 +11097,10 @@
   intptr_t list_length = ReadListLength();  // read list length.
   const Array& metadata_values =
       Array::Handle(Z, Array::New(list_length, H.allocation_space()));
+  Instance& value = Instance::Handle(Z);
   for (intptr_t i = 0; i < list_length; ++i) {
     // this will (potentially) read the expression, but reset the position.
-    Instance& value = Instance::ZoneHandle(
-        Z, constant_evaluator_.EvaluateExpression(ReaderOffset()));
+    value = constant_evaluator_.EvaluateExpression(ReaderOffset());
     SkipExpression();  // read (actual) initializer.
     metadata_values.SetAt(i, value);
   }
diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc
index 607c1e9..cabed58 100644
--- a/runtime/vm/kernel_loader.cc
+++ b/runtime/vm/kernel_loader.cc
@@ -432,23 +432,14 @@
   ASSERT(constant_table.Release().raw() == constant_table_array.raw());
 }
 
-RawString* KernelLoader::DetectExternalName() {
+RawString* KernelLoader::DetectExternalNameCtor() {
   builder_.ReadTag();
   builder_.ReadPosition();
   NameIndex annotation_class = H.EnclosingName(
       builder_.ReadCanonicalNameReference());  // read target reference,
-  ASSERT(H.IsClass(annotation_class));
-  StringIndex class_name_index = H.CanonicalNameString(annotation_class);
 
-  // Just compare by name, do not generate the annotation class.
-  if (!H.StringEquals(class_name_index, "ExternalName")) {
-    builder_.SkipArguments();
-    return String::null();
-  }
-  ASSERT(H.IsLibrary(H.CanonicalNameParent(annotation_class)));
-  StringIndex library_name_index =
-      H.CanonicalNameString(H.CanonicalNameParent(annotation_class));
-  if (!H.StringEquals(library_name_index, "dart:_internal")) {
+  if (!IsClassName(annotation_class, Symbols::DartInternal(),
+                   Symbols::ExternalName())) {
     builder_.SkipArguments();
     return String::null();
   }
@@ -471,6 +462,30 @@
   return result.raw();
 }
 
+bool KernelLoader::IsClassName(NameIndex name,
+                               const String& library,
+                               const String& klass) {
+  ASSERT(H.IsClass(name));
+  StringIndex class_name_index = H.CanonicalNameString(name);
+
+  if (!H.StringEquals(class_name_index, klass.ToCString())) {
+    return false;
+  }
+  ASSERT(H.IsLibrary(H.CanonicalNameParent(name)));
+  StringIndex library_name_index =
+      H.CanonicalNameString(H.CanonicalNameParent(name));
+  return H.StringEquals(library_name_index, library.ToCString());
+}
+
+bool KernelLoader::DetectPragmaCtor() {
+  builder_.ReadTag();
+  builder_.ReadPosition();
+  NameIndex annotation_class = H.EnclosingName(
+      builder_.ReadCanonicalNameReference());  // read target reference
+  builder_.SkipArguments();
+  return IsClassName(annotation_class, Symbols::DartCore(), Symbols::Pragma());
+}
+
 void KernelLoader::LoadNativeExtensionLibraries(
     const Array& constant_table_array) {
   const intptr_t length = !potential_extension_libraries_.IsNull()
@@ -510,7 +525,7 @@
         }
       } else if (tag == kConstructorInvocation ||
                  tag == kConstConstructorInvocation) {
-        uri_path = DetectExternalName();
+        uri_path = DetectExternalNameCtor();
       } else {
         builder_.SkipExpression();
       }
@@ -1288,6 +1303,106 @@
                                    class_index, &class_helper);
 }
 
+// Read annotations on a procedure to identify potential VM-specific directives.
+//
+// Output parameters:
+//
+//   `native_name`: non-null if `ExternalName(<name>)` was identified.
+//
+//   `is_potential_native`: non-null if there may be an `ExternalName`
+//   annotation and we need to re-try after reading the constants table.
+//
+//   `has_pragma_annotation`: non-null if @pragma(...) was found (no information
+//   is given on the kind of pragma directive).
+//
+void KernelLoader::ReadProcedureAnnotations(intptr_t annotation_count,
+                                            String* native_name,
+                                            bool* is_potential_native,
+                                            bool* has_pragma_annotation) {
+  *is_potential_native = false;
+  *has_pragma_annotation = false;
+  String& detected_name = String::Handle(Z);
+  Class& pragma_class = Class::Handle(Z, I->object_store()->pragma_class());
+  for (intptr_t i = 0; i < annotation_count; ++i) {
+    const intptr_t tag = builder_.PeekTag();
+    if (tag == kConstructorInvocation || tag == kConstConstructorInvocation) {
+      const intptr_t start = builder_.ReaderOffset();
+      detected_name = DetectExternalNameCtor();
+      if (!detected_name.IsNull()) {
+        *native_name = detected_name.raw();
+        continue;
+      }
+
+      builder_.SetOffset(start);
+      if (DetectPragmaCtor()) {
+        *has_pragma_annotation = true;
+      }
+    } else if (tag == kConstantExpression) {
+      const Array& constant_table_array =
+          Array::Handle(kernel_program_info_.constants());
+      if (constant_table_array.IsNull()) {
+        // We can only read in the constant table once all classes have been
+        // finalized (otherwise we can't create instances of the classes!).
+        //
+        // We therefore delay the scanning for `ExternalName {name: ... }`
+        // constants in the annotation list to later.
+        *is_potential_native = true;
+
+        if (program_ == nullptr) {
+          builder_.SkipExpression();
+          continue;
+        }
+
+        // For pragma annotations, we seek into the constants table and peek
+        // into the Kernel representation of the constant.
+        //
+        // TODO(sjindel): Refactor `ExternalName` handling to do this as well
+        // and avoid the "potential natives" list.
+
+        builder_.ReadByte();  // Skip the tag.
+
+        const intptr_t offset_in_constant_table = builder_.ReadUInt();
+
+        AlternativeReadingScope scope(&builder_.reader_,
+                                      program_->constant_table_offset());
+
+        // Seek into the position within the constant table where we can inspect
+        // this constant's Kernel representation.
+        builder_.ReadUInt();  // skip constant table size
+        builder_.SetOffset(builder_.ReaderOffset() + offset_in_constant_table);
+        uint8_t tag = builder_.ReadTag();
+        if (tag == kInstanceConstant) {
+          *has_pragma_annotation =
+              IsClassName(builder_.ReadCanonicalNameReference(),
+                          Symbols::DartCore(), Symbols::Pragma());
+        }
+      } else {
+        KernelConstantsMap constant_table(constant_table_array.raw());
+        builder_.ReadByte();  // Skip the tag.
+
+        // Obtain `dart:_internal::ExternalName.name`.
+        EnsureExternalClassIsLookedUp();
+
+        const intptr_t constant_table_index = builder_.ReadUInt();
+        const Object& constant =
+            Object::Handle(constant_table.GetOrDie(constant_table_index));
+        if (constant.clazz() == external_name_class_.raw()) {
+          const Instance& instance =
+              Instance::Handle(Instance::RawCast(constant.raw()));
+          *native_name =
+              String::RawCast(instance.GetField(external_name_field_));
+        } else if (constant.clazz() == pragma_class.raw()) {
+          *has_pragma_annotation = true;
+        }
+        ASSERT(constant_table.Release().raw() == constant_table_array.raw());
+      }
+    } else {
+      builder_.SkipExpression();
+      continue;
+    }
+  }
+}
+
 void KernelLoader::LoadProcedure(const Library& library,
                                  const Class& owner,
                                  bool in_class,
@@ -1304,78 +1419,14 @@
   bool is_method = in_class && !procedure_helper.IsStatic();
   bool is_abstract = procedure_helper.IsAbstract();
   bool is_external = procedure_helper.IsExternal();
-  String* native_name = NULL;
-  intptr_t annotation_count;
-  bool is_potential_native = false;
-  if (is_external) {
-    // Maybe it has a native implementation, which is not external as far as
-    // the VM is concerned because it does have an implementation.  Check for
-    // an ExternalName annotation and extract the string from it.
-    annotation_count = builder_.ReadListLength();  // read list length.
-    for (int i = 0; i < annotation_count; ++i) {
-      const intptr_t tag = builder_.PeekTag();
-      if (tag == kConstructorInvocation || tag == kConstConstructorInvocation) {
-        String& detected_name = String::Handle(DetectExternalName());
-        if (detected_name.IsNull()) continue;
-
-        is_external = false;
-        native_name = &detected_name;
-
-        // Skip remaining annotations
-        for (++i; i < annotation_count; ++i) {
-          builder_.SkipExpression();  // read ith annotation.
-        }
-      } else if (tag == kConstantExpression) {
-        if (kernel_program_info_.constants() == Array::null()) {
-          // We can only read in the constant table once all classes have been
-          // finalized (otherwise we can't create instances of the classes!).
-          //
-          // We therefore delay the scanning for `ExternalName {name: ... }`
-          // constants in the annotation list to later.
-          is_potential_native = true;
-          builder_.SkipExpression();
-        } else {
-          builder_.ReadByte();  // Skip the tag.
-
-          // Obtain `dart:_internal::ExternalName.name`.
-          EnsureExternalClassIsLookedUp();
-
-          const Array& constant_table_array =
-              Array::Handle(kernel_program_info_.constants());
-          KernelConstantsMap constant_table(constant_table_array.raw());
-
-          // We have a candiate.  Let's look if it's an instance of the
-          // ExternalName class.
-          const intptr_t constant_table_index = builder_.ReadUInt();
-          const Object& constant =
-              Object::Handle(constant_table.GetOrDie(constant_table_index));
-          ASSERT(constant_table.Release().raw() == constant_table_array.raw());
-          if (constant.clazz() == external_name_class_.raw()) {
-            const Instance& instance =
-                Instance::Handle(Instance::RawCast(constant.raw()));
-
-            // We found the annotation, let's flag the function as native and
-            // set the native name!
-            native_name = &String::Handle(
-                String::RawCast(instance.GetField(external_name_field_)));
-
-            // Skip remaining annotations
-            for (++i; i < annotation_count; ++i) {
-              builder_.SkipExpression();  // read ith annotation.
-            }
-            break;
-          }
-        }
-      } else {
-        builder_.SkipExpression();
-        continue;
-      }
-    }
-    procedure_helper.SetJustRead(ProcedureHelper::kAnnotations);
-  } else {
-    procedure_helper.ReadUntilIncluding(ProcedureHelper::kAnnotations);
-    annotation_count = procedure_helper.annotation_count_;
-  }
+  String& native_name = String::Handle(Z);
+  bool is_potential_native;
+  bool has_pragma_annotation;
+  const intptr_t annotation_count = builder_.ReadListLength();
+  ReadProcedureAnnotations(annotation_count, &native_name, &is_potential_native,
+                           &has_pragma_annotation);
+  is_external = is_external && native_name.IsNull();
+  procedure_helper.SetJustRead(ProcedureHelper::kAnnotations);
   const Object& script_class =
       ClassForScriptAt(owner, procedure_helper.source_uri_index_);
   RawFunction::Kind kind = GetFunctionType(procedure_helper.kind_);
@@ -1384,8 +1435,9 @@
                        !is_method,  // is_static
                        false,       // is_const
                        is_abstract, is_external,
-                       native_name != NULL,  // is_native
+                       !native_name.IsNull(),  // is_native
                        script_class, procedure_helper.position_));
+  function.set_has_pragma(has_pragma_annotation);
   function.set_end_token_pos(procedure_helper.end_position_);
   functions_.Add(&function);
   function.set_kernel_offset(procedure_offset);
@@ -1426,8 +1478,8 @@
   }
   ASSERT(function_node_helper.async_marker_ == FunctionNodeHelper::kSync);
 
-  if (native_name != NULL) {
-    function.set_native_name(*native_name);
+  if (!native_name.IsNull()) {
+    function.set_native_name(native_name);
   }
   if (is_potential_native) {
     EnsurePotentialNatives();
@@ -1451,7 +1503,7 @@
                 .IsNull());
   }
 
-  if (FLAG_enable_mirrors && annotation_count > 0) {
+  if (annotation_count > 0) {
     library.AddFunctionMetadata(function, TokenPosition::kNoSource,
                                 procedure_offset);
   }
diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h
index 21f4bb2..b8a70a9 100644
--- a/runtime/vm/kernel_loader.h
+++ b/runtime/vm/kernel_loader.h
@@ -142,10 +142,26 @@
   static void FinishLoading(const Class& klass);
 
   const Array& ReadConstantTable();
-  RawString* DetectExternalName();
+
+  // Check for the presence of a (possibly const) constructor for the
+  // 'ExternalName' class. If found, returns the name parameter to the
+  // constructor.
+  RawString* DetectExternalNameCtor();
+
+  // Check for the presence of a (possibly const) constructor for the 'pragma'
+  // class. Returns whether it was found (no details about the type of pragma).
+  bool DetectPragmaCtor();
+
+  bool IsClassName(NameIndex name, const String& library, const String& klass);
+
   void AnnotateNativeProcedures(const Array& constant_table);
   void LoadNativeExtensionLibraries(const Array& constant_table);
 
+  void ReadProcedureAnnotations(intptr_t annotation_count,
+                                String* native_name,
+                                bool* is_potential_native,
+                                bool* has_pragma_annotation);
+
   const String& DartSymbolPlain(StringIndex index) {
     return translation_helper_.DartSymbolPlain(index);
   }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index de61db2..ad5aff3 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -7079,6 +7079,7 @@
   result.set_is_intrinsic(false);
   result.set_is_redirecting(false);
   result.set_is_generated_body(false);
+  result.set_has_pragma(false);
   result.set_always_inline(false);
   result.set_is_polymorphic_target(false);
   NOT_IN_PRECOMPILED(result.set_state_bits(0));
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 1d8dc12..78e036a 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2870,7 +2870,8 @@
   V(External, is_external)                                                     \
   V(GeneratedBody, is_generated_body)                                          \
   V(AlwaysInline, always_inline)                                               \
-  V(PolymorphicTarget, is_polymorphic_target)
+  V(PolymorphicTarget, is_polymorphic_target)                                  \
+  V(HasPragma, has_pragma)
 
 #define DEFINE_ACCESSORS(name, accessor_name)                                  \
   void set_##accessor_name(bool value) const {                                 \
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index 2d62110..e2d676d 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -220,6 +220,10 @@
   ASSERT(!cls.IsNull());
   set_compiletime_error_class(cls);
 
+  cls = core_lib.LookupClassAllowPrivate(Symbols::Pragma());
+  ASSERT(!cls.IsNull());
+  set_pragma_class(cls);
+
   // Cache the core private functions used for fast instance of checks.
   simple_instance_of_function_ =
       PrivateObjectLookup(Symbols::_simpleInstanceOf());
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 891760f..5c9e90f 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -56,6 +56,7 @@
   RW(TypeArguments, type_argument_string)                                      \
   RW(TypeArguments, type_argument_int)                                         \
   RW(Class, compiletime_error_class)                                           \
+  RW(Class, pragma_class)                                                      \
   RW(Class, future_class)                                                      \
   RW(Class, completer_class)                                                   \
   RW(Class, stream_iterator_class)                                             \
diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc
index ac5cd08..0226504 100644
--- a/runtime/vm/parser.cc
+++ b/runtime/vm/parser.cc
@@ -5585,6 +5585,16 @@
   return IsSymbol(Symbols::Patch());
 }
 
+bool Parser::IsPragmaAnnotation(TokenPosition pos) {
+  if (pos == TokenPosition::kNoSource) {
+    return false;
+  }
+  TokenPosScope saved_pos(this);
+  SetPosition(pos);
+  ExpectToken(Token::kAT);
+  return IsSymbol(Symbols::Pragma());
+}
+
 TokenPosition Parser::SkipMetadata() {
   if (CurrentToken() != Token::kAT) {
     return TokenPosition::kNoSource;
@@ -5966,6 +5976,8 @@
     ConsumeToken();
     is_external = true;
   }
+  const bool has_pragma = IsPragmaAnnotation(metadata_pos);
+
   // Parse optional result type.
   if (IsFunctionReturnType()) {
     // It is too early to resolve the type here, since it can be a result type
@@ -5999,7 +6011,7 @@
                        /* is_abstract = */ false, is_external,
                        /* is_native = */ false,  // May change.
                        owner, decl_begin_pos));
-
+  func.set_has_pragma(has_pragma);
   ASSERT(innermost_function().IsNull());
   innermost_function_ = func.raw();
 
diff --git a/runtime/vm/parser.h b/runtime/vm/parser.h
index 12d0557..ca45d6c 100644
--- a/runtime/vm/parser.h
+++ b/runtime/vm/parser.h
@@ -414,6 +414,7 @@
   void SkipBlock();
   TokenPosition SkipMetadata();
   bool IsPatchAnnotation(TokenPosition pos);
+  bool IsPragmaAnnotation(TokenPosition pos);
   void SkipTypeArguments();
   void SkipTypeParameters();
   void SkipType(bool allow_void);
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index a68bce5..f220ca3 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -170,6 +170,7 @@
   V(_MixinAppType, "_MixinAppType")                                            \
   V(TypeArguments, "TypeArguments")                                            \
   V(Patch, "patch")                                                            \
+  V(Pragma, "pragma")                                                          \
   V(PatchClass, "PatchClass")                                                  \
   V(Function, "Function")                                                      \
   V(_Closure, "_Closure")                                                      \
@@ -444,6 +445,7 @@
   V(DartLibraryMirrors, "dart.library.mirrors")                                \
   V(_name, "_name")                                                            \
   V(name, "name")                                                              \
+  V(options, "options")                                                        \
   V(_classRangeCheck, "_classRangeCheck")                                      \
   V(_classRangeCheckNegative, "_classRangeCheckNegative")                      \
   V(_classRangeAssert, "_classRangeAssert")                                    \
@@ -454,7 +456,8 @@
   V(DartDeveloperCausalAsyncStacks, "dart.developer.causal_async_stacks")      \
   V(_AsyncStarListenHelper, "_asyncStarListenHelper")                          \
   V(GrowRegExpStack, "_growRegExpStack")                                       \
-  V(DebugProcedureName, ":Eval")
+  V(DebugProcedureName, ":Eval")                                               \
+  V(vm_entry_point, "vm.entry_point")
 
 // Contains a list of frequently used strings in a canonicalized form. This
 // list is kept in the vm_isolate in order to share the copy across isolates