[vm] Decouple bytecode reading from compilation

This way requests to compile won't end prematurely after reading
bytecode if interpreter is enabled.

Change-Id: Ic06cae98ea65bc4656ef491fbd306d34b0e1ef4c
Reviewed-on: https://dart-review.googlesource.com/75628
Reviewed-by: Zach Anderson <zra@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.cc b/runtime/vm/compiler/frontend/bytecode_reader.cc
index bac50ed..c68b03f 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.cc
+++ b/runtime/vm/compiler/frontend/bytecode_reader.cc
@@ -10,6 +10,7 @@
 #include "vm/compiler/assembler/disassembler_kbc.h"
 #include "vm/constants_kbc.h"
 #include "vm/dart_entry.h"
+#include "vm/longjump.h"
 #include "vm/object_store.h"
 #include "vm/timeline.h"
 
@@ -711,6 +712,57 @@
   return NativeEntryData::New(kind, trampoline, native_function, argc_tag);
 }
 
+RawError* BytecodeReader::ReadFunctionBytecode(Thread* thread,
+                                               const Function& function) {
+  ASSERT(!FLAG_precompiled_mode);
+  ASSERT(!function.HasBytecode());
+  ASSERT(thread->sticky_error() == Error::null());
+
+  LongJumpScope jump;
+  if (setjmp(*jump.Set()) == 0) {
+    StackZone stack_zone(thread);
+    Zone* const zone = stack_zone.GetZone();
+    HANDLESCOPE(thread);
+    CompilerState compiler_state(thread);
+
+    const Script& script = Script::Handle(zone, function.script());
+    TranslationHelper translation_helper(thread);
+    translation_helper.InitFromScript(script);
+
+    KernelReaderHelper reader_helper(
+        zone, &translation_helper, script,
+        ExternalTypedData::Handle(zone, function.KernelData()),
+        function.KernelDataProgramOffset());
+    ActiveClass active_class;
+    TypeTranslator type_translator(&reader_helper, &active_class,
+                                   /* finalize= */ true);
+
+    BytecodeMetadataHelper bytecode_metadata_helper(
+        &reader_helper, &type_translator, &active_class);
+
+    // Setup a [ActiveClassScope] and a [ActiveMemberScope] which will be used
+    // e.g. for type translation.
+    const Class& klass = Class::Handle(zone, function.Owner());
+    Function& outermost_function =
+        Function::Handle(zone, function.GetOutermostFunction());
+
+    ActiveClassScope active_class_scope(&active_class, &klass);
+    ActiveMemberScope active_member(&active_class, &outermost_function);
+    ActiveTypeParametersScope active_type_params(&active_class, function, zone);
+
+    bytecode_metadata_helper.ReadMetadata(function);
+
+    return Error::null();
+  } else {
+    StackZone stack_zone(thread);
+    Error& error = Error::Handle();
+    // We got an error during bytecode reading.
+    error = thread->sticky_error();
+    thread->clear_sticky_error();
+    return error.raw();
+  }
+}
+
 }  // namespace kernel
 }  // namespace dart
 
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.h b/runtime/vm/compiler/frontend/bytecode_reader.h
index 769ad61..1ea8c46 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.h
+++ b/runtime/vm/compiler/frontend/bytecode_reader.h
@@ -41,6 +41,14 @@
   DISALLOW_COPY_AND_ASSIGN(BytecodeMetadataHelper);
 };
 
+class BytecodeReader : public AllStatic {
+ public:
+  // Reads bytecode for the given function and sets its bytecode field.
+  // Returns error (if any), or null.
+  static RawError* ReadFunctionBytecode(Thread* thread,
+                                        const Function& function);
+};
+
 }  // namespace kernel
 }  // namespace dart
 
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index e8b948c..5f0e767 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -9,6 +9,7 @@
 #include "vm/compiler/frontend/flow_graph_builder.h"  // For dart::FlowGraphBuilder::SimpleInstanceOfType.
 #include "vm/compiler/frontend/prologue_builder.h"
 #include "vm/compiler/jit/compiler.h"
+#include "vm/kernel.h"  // For IsFieldInitializer.
 #include "vm/object_store.h"
 #include "vm/stack_frame.h"
 
@@ -1908,43 +1909,17 @@
 
   SetOffset(kernel_offset);
 
-  if (FLAG_enable_interpreter || FLAG_use_bytecode_compiler) {
-    // TODO(regis): For now, we skip bytecode loading for functions that were
-    // synthesized and that do not have bytecode. Since they inherited the
-    // kernel offset of a concrete function, the wrong bytecode would be loaded.
-    switch (function.kind()) {
-      case RawFunction::kImplicitGetter:
-      case RawFunction::kImplicitSetter:
-      case RawFunction::kMethodExtractor:
-      case RawFunction::kNoSuchMethodDispatcher:
-      case RawFunction::kInvokeFieldDispatcher:
-      case RawFunction::kDynamicInvocationForwarder:
-      case RawFunction::kImplicitClosureFunction:
-        break;
-      case RawFunction::kImplicitStaticFinalGetter:
-        if (!IsFieldInitializer(function, Z)) {
-          break;
-        }
-        // Fallthrough.
-      default: {
-        // TODO(regis): Clean up this logic of when to compile.
-        // If the bytecode was previously loaded, we really want to compile.
-        if (!function.HasBytecode()) {
-          bytecode_metadata_helper_.ReadMetadata(function);
-        }
-        if (function.HasBytecode()) {
-          if (FLAG_use_bytecode_compiler) {
-            BytecodeFlowGraphBuilder bytecode_compiler(
-                flow_graph_builder_, parsed_function(),
-                &(flow_graph_builder_->ic_data_array_));
-            FlowGraph* flow_graph = bytecode_compiler.BuildGraph();
-            if (flow_graph != nullptr) {
-              return flow_graph;
-            }
-          } else {
-            return nullptr;
-          }
-        }
+  if (FLAG_use_bytecode_compiler && function.IsBytecodeAllowed(Z)) {
+    if (!function.HasBytecode()) {
+      bytecode_metadata_helper_.ReadMetadata(function);
+    }
+    if (function.HasBytecode()) {
+      BytecodeFlowGraphBuilder bytecode_compiler(
+          flow_graph_builder_, parsed_function(),
+          &(flow_graph_builder_->ic_data_array_));
+      FlowGraph* flow_graph = bytecode_compiler.BuildGraph();
+      if (flow_graph != nullptr) {
+        return flow_graph;
       }
     }
   }
diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc
index da11764..a6f15f9 100644
--- a/runtime/vm/compiler/frontend/scope_builder.cc
+++ b/runtime/vm/compiler/frontend/scope_builder.cc
@@ -5,6 +5,7 @@
 #include "vm/compiler/frontend/scope_builder.h"
 
 #include "vm/compiler/backend/il.h"  // For CompileType.
+#include "vm/kernel.h"               // For IsFieldInitializer.
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
 
@@ -16,12 +17,6 @@
 #define T (type_translator_)
 #define I Isolate::Current()
 
-bool IsFieldInitializer(const Function& function, Zone* zone) {
-  return (function.kind() == RawFunction::kImplicitStaticFinalGetter) &&
-         String::Handle(zone, function.name())
-             .StartsWith(Symbols::InitPrefix());
-}
-
 // Returns true if the given method can skip type checks for all arguments
 // that are not covariant or generic covariant in its implementation.
 bool MethodCanSkipTypeChecksForNonCovariantArguments(
diff --git a/runtime/vm/compiler/frontend/scope_builder.h b/runtime/vm/compiler/frontend/scope_builder.h
index 210c51b..12521be 100644
--- a/runtime/vm/compiler/frontend/scope_builder.h
+++ b/runtime/vm/compiler/frontend/scope_builder.h
@@ -228,8 +228,6 @@
   DISALLOW_COPY_AND_ASSIGN(ScopeBuildingResult);
 };
 
-bool IsFieldInitializer(const Function& function, Zone* zone);
-
 // Returns true if the given method can skip type checks for all arguments
 // that are not covariant or generic covariant in its implementation.
 bool MethodCanSkipTypeChecksForNonCovariantArguments(
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index 50ff623..8c9eff9 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -24,6 +24,7 @@
 #include "vm/compiler/cha.h"
 #include "vm/compiler/compiler_pass.h"
 #include "vm/compiler/compiler_state.h"
+#include "vm/compiler/frontend/bytecode_reader.h"
 #include "vm/compiler/frontend/flow_graph_builder.h"
 #include "vm/compiler/frontend/kernel_to_il.h"
 #include "vm/compiler/jit/jit_call_specializer.h"
@@ -164,8 +165,7 @@
                                      /* not inlining */ NULL, optimized,
                                      osr_id);
     FlowGraph* graph = builder.BuildGraph();
-    ASSERT((graph != NULL) || (FLAG_enable_interpreter &&
-                               parsed_function->function().HasBytecode()));
+    ASSERT(graph != NULL);
     return graph;
   }
   FlowGraphBuilder builder(*parsed_function, *ic_data_array,
@@ -249,9 +249,25 @@
 //   Arg0: function object.
 DEFINE_RUNTIME_ENTRY(CompileFunction, 1) {
   const Function& function = Function::CheckedHandle(arguments.ArgAt(0));
+  Object& result = Object::Handle(zone);
   ASSERT(!function.HasCode());
-  const Object& result =
-      Object::Handle(Compiler::CompileFunction(thread, function));
+
+  if (FLAG_enable_interpreter && function.IsBytecodeAllowed(zone)) {
+    if (!function.HasBytecode()) {
+      result = kernel::BytecodeReader::ReadFunctionBytecode(thread, function);
+      if (!result.IsNull()) {
+        Exceptions::PropagateError(Error::Cast(result));
+      }
+    }
+    if (function.HasBytecode()) {
+      // Verify that InterpretCall stub code was installed.
+      ASSERT(function.CurrentCode() == StubCode::InterpretCall_entry()->code());
+      return;
+    }
+    // No bytecode, fall back to compilation.
+  }
+
+  result = Compiler::CompileFunction(thread, function);
   if (result.IsError()) {
     if (result.IsLanguageError()) {
       Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
@@ -259,13 +275,6 @@
     }
     Exceptions::PropagateError(Error::Cast(result));
   }
-  // TODO(regis): Revisit.
-  if (FLAG_enable_interpreter && !function.HasCode() &&
-      function.HasBytecode()) {
-    // Function was not actually compiled, but its bytecode was loaded.
-    // Verify that InterpretCall stub code was installed.
-    ASSERT(function.CurrentCode() == StubCode::InterpretCall_entry()->code());
-  }
 }
 
 bool Compiler::CanOptimizeFunction(Thread* thread, const Function& function) {
@@ -830,12 +839,6 @@
             zone, parsed_function(), ic_data_array, osr_id(), optimized());
       }
 
-      // TODO(regis): Revisit.
-      if (FLAG_enable_interpreter && (flow_graph == NULL) &&
-          function.HasBytecode()) {
-        return Code::null();
-      }
-
       const bool print_flow_graph =
           (FLAG_print_flow_graph ||
            (optimized() && FLAG_print_flow_graph_optimized)) &&
@@ -1019,11 +1022,6 @@
 
     const Code& result = Code::Handle(helper.Compile(pipeline));
 
-    // TODO(regis): Revisit.
-    if (FLAG_enable_interpreter && result.IsNull() && function.HasBytecode()) {
-      return Object::null();
-    }
-
     if (!result.IsNull()) {
       if (!optimized) {
         function.SetWasCompiled(true);
@@ -1254,10 +1252,6 @@
 
 RawError* Compiler::EnsureUnoptimizedCode(Thread* thread,
                                           const Function& function) {
-  if (FLAG_enable_interpreter && function.HasBytecode()) {
-    // TODO(regis): This may not be sufficient when deoptimizing. Revisit then.
-    return Error::null();
-  }
   if (function.unoptimized_code() != Object::null()) {
     return Error::null();
   }
@@ -1273,9 +1267,6 @@
   if (result.IsError()) {
     return Error::Cast(result).raw();
   }
-  if (FLAG_enable_interpreter && function.HasBytecode()) {
-    return Error::null();
-  }
   // Since CompileFunctionHelper replaces the current code, re-attach the
   // the original code if the function was already compiled.
   if (!original_code.IsNull() && result.raw() == function.CurrentCode() &&
@@ -1411,9 +1402,6 @@
     func ^= functions.At(i);
     ASSERT(!func.IsNull());
     if (!func.HasCode() &&
-        // TODO(regis): Revisit.
-        // Do not compile function if its bytecode is already loaded.
-        (!FLAG_enable_interpreter || !func.HasBytecode()) &&
         !func.is_abstract() && !func.IsRedirectingFactory()) {
       if ((cls.is_mixin_app_alias() || cls.IsMixinApplication()) &&
           func.HasOptionalParameters()) {
@@ -1424,10 +1412,7 @@
       if (result.IsError()) {
         return Error::Cast(result).raw();
       }
-      // TODO(regis): Revisit.
-      // The compiler may load bytecode and return Code::null().
-      ASSERT(!result.IsNull() ||
-             (FLAG_enable_interpreter && func.HasBytecode()));
+      ASSERT(!result.IsNull());
     }
   }
   return Error::null();
@@ -1508,20 +1493,30 @@
         parsed_function = Parser::ParseStaticFieldInitializer(field);
         parsed_function->AllocateVariables();
       }
+      const Function& initializer = parsed_function->function();
+
+      if (FLAG_enable_interpreter) {
+        ASSERT(initializer.IsBytecodeAllowed(zone));
+        if (!initializer.HasBytecode()) {
+          RawError* error =
+              kernel::BytecodeReader::ReadFunctionBytecode(thread, initializer);
+          if (error != Error::null()) {
+            return error;
+          }
+        }
+        if (initializer.HasBytecode()) {
+          return DartEntry::InvokeFunction(initializer, Object::empty_array());
+        }
+      }
 
       // Non-optimized code generator.
       DartCompilationPipeline pipeline;
       CompileParsedFunctionHelper helper(parsed_function, false, kNoOSRDeoptId);
       const Code& code = Code::Handle(helper.Compile(&pipeline));
-      const Function& initializer = parsed_function->function();
 
       if (!code.IsNull()) {
         code.set_var_descriptors(Object::empty_var_descriptors());
         return DartEntry::InvokeFunction(initializer, Object::empty_array());
-      } else if (FLAG_enable_interpreter && initializer.HasBytecode()) {
-        // In case the initializer has bytecode, the compilation step above only
-        // loaded the bytecode without generating code.
-        return DartEntry::InvokeFunction(initializer, Object::empty_array());
       }
     }
   }
@@ -1770,8 +1765,14 @@
       while (running_ && !function.IsNull() && !isolate_->IsTopLevelParsing()) {
         // Check that we have aggregated and cleared the stats.
         ASSERT(thread->compiler_stats()->IsCleared());
-        Compiler::CompileOptimizedFunction(thread, function,
-                                           Compiler::kNoOSRDeoptId);
+        // If running with interpreter, do the unoptimized compilation first.
+        if (FLAG_enable_interpreter &&
+            (function.unoptimized_code() == Code::null())) {
+          Compiler::EnsureUnoptimizedCode(thread, function);
+        } else {
+          Compiler::CompileOptimizedFunction(thread, function,
+                                             Compiler::kNoOSRDeoptId);
+        }
 #ifndef PRODUCT
         Isolate* isolate = thread->isolate();
         isolate->aggregate_compiler_stats()->Add(*thread->compiler_stats());
diff --git a/runtime/vm/dart_entry.cc b/runtime/vm/dart_entry.cc
index a49749d..5708a0d 100644
--- a/runtime/vm/dart_entry.cc
+++ b/runtime/vm/dart_entry.cc
@@ -6,6 +6,7 @@
 
 #include "platform/safe_stack.h"
 #include "vm/class_finalizer.h"
+#include "vm/compiler/frontend/bytecode_reader.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/debugger.h"
 #include "vm/heap/safepoint.h"
@@ -130,29 +131,35 @@
   ScopedIsolateStackLimits stack_limit(thread, current_sp);
 #if !defined(DART_PRECOMPILED_RUNTIME)
   if (!function.HasCode()) {
-    // There's no native code. If we're not using the interpreter, then we
-    // compile to native code. If we are using the interpreter, but there's no
-    // native code and no bytecode, then we invoke the compiler to extract the
-    // bytecode.
-    if (!FLAG_enable_interpreter || !function.HasBytecode()) {
-      const Object& result =
-          Object::Handle(zone, Compiler::CompileFunction(thread, function));
-      if (result.IsError()) {
-        return Error::Cast(result).raw();
+    if (FLAG_enable_interpreter && function.IsBytecodeAllowed(zone)) {
+      if (!function.HasBytecode()) {
+        RawError* error =
+            kernel::BytecodeReader::ReadFunctionBytecode(thread, function);
+        if (error != Error::null()) {
+          return error;
+        }
       }
+
+      // If we have bytecode but no native code then invoke the interpreter.
+      if (function.HasBytecode()) {
+        ASSERT(thread->no_callback_scope_depth() == 0);
+        SuspendLongJumpScope suspend_long_jump_scope(thread);
+        TransitionToGenerated transition(thread);
+        return Interpreter::Current()->Call(function, arguments_descriptor,
+                                            arguments, thread);
+      }
+
+      // No bytecode, fall back to compilation.
     }
 
-    // At this point we should have either native code or bytecode.
-    ASSERT(function.HasCode() || function.HasBytecode());
-
-    // If we have bytecode but no native code then invoke the interpreter.
-    if (!function.HasCode() && function.HasBytecode()) {
-      ASSERT(thread->no_callback_scope_depth() == 0);
-      SuspendLongJumpScope suspend_long_jump_scope(thread);
-      TransitionToGenerated transition(thread);
-      return Interpreter::Current()->Call(function, arguments_descriptor,
-                                          arguments, thread);
+    const Object& result =
+        Object::Handle(zone, Compiler::CompileFunction(thread, function));
+    if (result.IsError()) {
+      return Error::Cast(result).raw();
     }
+
+    // At this point we should have native code.
+    ASSERT(function.HasCode());
   }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index 30f52a8..6827707 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -319,6 +319,8 @@
   // Extract the function and the code from the asynchronous activation.
   function_ = async_activation.function();
 #if !defined(DART_PRECOMPILED_RUNTIME)
+  // TODO(regis): Revise debugger functionality when running a mix of
+  // interpreted and compiled code.
   if (!FLAG_enable_interpreter || !function_.HasBytecode()) {
     function_.EnsureHasCompiledUnoptimizedCode();
   }
diff --git a/runtime/vm/kernel.cc b/runtime/vm/kernel.cc
index 7bbc8f8..335a140 100644
--- a/runtime/vm/kernel.cc
+++ b/runtime/vm/kernel.cc
@@ -686,6 +686,12 @@
   return false;
 }
 
+bool IsFieldInitializer(const Function& function, Zone* zone) {
+  return (function.kind() == RawFunction::kImplicitStaticFinalGetter) &&
+         String::Handle(zone, function.name())
+             .StartsWith(Symbols::InitPrefix());
+}
+
 }  // namespace kernel
 }  // namespace dart
 
diff --git a/runtime/vm/kernel.h b/runtime/vm/kernel.h
index 5b8e9a2..534d18f 100644
--- a/runtime/vm/kernel.h
+++ b/runtime/vm/kernel.h
@@ -198,6 +198,8 @@
 // as such function already checks all of its parameters.
 bool NeedsDynamicInvocationForwarder(const Function& function);
 
+bool IsFieldInitializer(const Function& function, Zone* zone);
+
 }  // namespace kernel
 }  // namespace dart
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index be89708..256aaae 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5966,6 +5966,24 @@
 }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
+bool Function::IsBytecodeAllowed(Zone* zone) const {
+  switch (kind()) {
+    case RawFunction::kImplicitGetter:
+    case RawFunction::kImplicitSetter:
+    case RawFunction::kMethodExtractor:
+    case RawFunction::kNoSuchMethodDispatcher:
+    case RawFunction::kInvokeFieldDispatcher:
+    case RawFunction::kDynamicInvocationForwarder:
+    case RawFunction::kImplicitClosureFunction:
+    case RawFunction::kIrregexpFunction:
+      return false;
+    case RawFunction::kImplicitStaticFinalGetter:
+      return kernel::IsFieldInitializer(*this, zone);
+    default:
+      return true;
+  }
+}
+
 void Function::AttachBytecode(const Code& value) const {
   DEBUG_ASSERT(IsMutatorOrAtSafepoint());
   // Finish setting up code before activating it.
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 11ea398..8d6ad6a 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2376,6 +2376,7 @@
   }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
+  bool IsBytecodeAllowed(Zone* zone) const;
   void AttachBytecode(const Code& bytecode) const;
   RawCode* Bytecode() const { return raw_ptr()->bytecode_; }
   bool HasBytecode() const;
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index eca3f35..330f368 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -7,6 +7,7 @@
 #include "vm/ast.h"
 #include "vm/code_patcher.h"
 #include "vm/compiler/assembler/assembler.h"
+#include "vm/compiler/frontend/bytecode_reader.h"
 #include "vm/compiler/jit/compiler.h"
 #include "vm/dart_api_impl.h"
 #include "vm/dart_entry.h"