[VM interpreter] Support implicit getters and setters without compiling them.

Change-Id: I1e61cd79f66efe55c963d970e27f81a9d4084600
Reviewed-on: https://dart-review.googlesource.com/61860
Commit-Queue: Régis Crelier <regis@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/class_finalizer.cc b/runtime/vm/class_finalizer.cc
index 5bdfcb8..7577fb7 100644
--- a/runtime/vm/class_finalizer.cc
+++ b/runtime/vm/class_finalizer.cc
@@ -1555,7 +1555,7 @@
   AbstractType& type = AbstractType::Handle(zone);
   String& name = String::Handle(zone);
   String& getter_name = String::Handle(zone);
-  String& setter_name = String::Handle(zone);
+  String& other_name = String::Handle(zone);
   Class& super_class = Class::Handle(zone);
   const intptr_t num_fields = array.Length();
   for (intptr_t i = 0; i < num_fields; i++) {
@@ -1579,8 +1579,8 @@
       // An implicit setter is not generated for a static field, therefore, we
       // cannot rely on the code below handling the static setter case to report
       // a conflict with an instance setter. So we check explicitly here.
-      setter_name = Field::SetterSymbol(name);
-      super_class = FindSuperOwnerOfFunction(cls, setter_name);
+      other_name = Field::SetterSymbol(name);
+      super_class = FindSuperOwnerOfFunction(cls, other_name);
       if (!super_class.IsNull()) {
         const String& class_name = String::Handle(zone, cls.Name());
         const String& super_cls_name = String::Handle(zone, super_class.Name());
@@ -1708,6 +1708,19 @@
         }
       }
     }
+    if (function.IsImplicitGetterFunction() ||
+        function.IsImplicitSetterFunction() ||
+        function.IsImplicitStaticFieldInitializer()) {
+      // Cache the field object in the function data_ field.
+      if (function.IsImplicitSetterFunction()) {
+        other_name = Field::NameFromSetter(name);
+      } else {
+        other_name = Field::NameFromGetter(name);
+      }
+      field = cls.LookupFieldAllowPrivate(other_name);
+      ASSERT(!field.IsNull());
+      function.set_accessor_field(field);
+    }
     if (function.IsSetterFunction() || function.IsImplicitSetterFunction()) {
       if (function.is_static()) {
         super_class = FindSuperOwnerOfFunction(cls, name);
diff --git a/runtime/vm/compiler/aot/aot_call_specializer.cc b/runtime/vm/compiler/aot/aot_call_specializer.cc
index 1b9b8e5..93bcf21bf 100644
--- a/runtime/vm/compiler/aot/aot_call_specializer.cc
+++ b/runtime/vm/compiler/aot/aot_call_specializer.cc
@@ -253,8 +253,7 @@
 
 bool AotCallSpecializer::TryInlineFieldAccess(StaticCallInstr* call) {
   if (call->function().IsImplicitGetterFunction()) {
-    Field& field =
-        Field::ZoneHandle(call->function().LookupImplicitGetterSetterField());
+    Field& field = Field::ZoneHandle(call->function().accessor_field());
     if (should_clone_fields_) {
       field = field.CloneFromOriginal();
     }
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index 0430ef9..875983c 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -1495,9 +1495,15 @@
       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()) {
-        const Function& initializer = parsed_function->function();
         code.set_var_descriptors(Object::empty_var_descriptors());
+#if defined(DART_USE_INTERPRETER)
+      }
+      // In case the initializer has bytecode, the compilation step above only
+      // loaded the bytecode without generating code.
+      if (!code.IsNull() || initializer.HasBytecode()) {
+#endif
         // Invoke the function to evaluate the expression.
         return DartEntry::InvokeFunction(initializer, Object::empty_array());
       }
diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc
index 2d4305b..1e31f0f 100644
--- a/runtime/vm/interpreter.cc
+++ b/runtime/vm/interpreter.cc
@@ -887,7 +887,6 @@
 
 DART_NOINLINE bool Interpreter::InvokeCompiled(Thread* thread,
                                                RawFunction* function,
-                                               RawArray* argdesc,
                                                RawObject** call_base,
                                                RawObject** call_top,
                                                uint32_t** pc,
@@ -897,85 +896,139 @@
   // TODO(regis): Revisit.
   UNIMPLEMENTED();
 #endif
-  if (!Function::HasCode(function)) {
-    ASSERT(!Function::HasBytecode(function));
-    call_top[1] = 0;  // Code result.
-    call_top[2] = function;
-    Exit(thread, *FP, call_top + 3, *pc);
-    NativeArguments native_args(thread, 1, call_top + 2, call_top + 1);
-    if (!InvokeRuntime(thread, this, DRT_CompileFunction, native_args)) {
-      return false;
-    }
-  }
-  if (Function::HasCode(function)) {
-    RawCode* code = function->ptr()->code_;
-    ASSERT(code != StubCode::LazyCompile_entry()->code());
-    // TODO(regis): Once we share the same stack, try to invoke directly.
-#if defined(DEBUG)
-    if (IsTracingExecution()) {
-      THR_Print("%" Pu64 " ", icount_);
-      THR_Print("invoking compiled %s\n",
-                Function::Handle(function).ToCString());
-    }
-#endif
-    // On success, returns a RawInstance.  On failure, a RawError.
-    typedef RawObject* (*invokestub)(RawCode * code, RawArray * argdesc,
-                                     RawObject * *arg0, Thread * thread);
-    invokestub entrypoint = reinterpret_cast<invokestub>(
-        StubCode::InvokeDartCodeFromBytecode_entry()->EntryPoint());
-    RawObject* result;
-    Exit(thread, *FP, call_top + 1, *pc);
-    {
-      InterpreterSetjmpBuffer buffer(this);
-      if (!setjmp(buffer.buffer_)) {
-        thread->set_vm_tag(reinterpret_cast<uword>(entrypoint));
-        result = entrypoint(code, argdesc, call_base, thread);
-        thread->set_vm_tag(VMTag::kDartTagId);
-        thread->set_top_exit_frame_info(0);
-        ASSERT(thread->execution_state() == Thread::kThreadInGenerated);
-      } else {
-        return false;
-      }
-    }
-    // Pop args and push result.
-    *SP = call_base;
-    **SP = result;
-
-    // It is legit to call the constructor of an error object, however a
-    // result of class UnhandledException must be propagated.
-    if (result->IsHeapObject() &&
-        result->GetClassId() == kUnhandledExceptionCid) {
-      (*SP)[0] = UnhandledException::RawCast(result)->ptr()->exception_;
-      (*SP)[1] = UnhandledException::RawCast(result)->ptr()->stacktrace_;
-      (*SP)[2] = 0;  // Space for result.
-      Exit(thread, *FP, *SP + 3, *pc);
-      NativeArguments args(thread, 2, *SP, *SP + 2);
-      if (!InvokeRuntime(thread, this, DRT_ReThrow, args)) {
-        return false;
-      }
-    }
-    return true;
-  }
-  ASSERT(Function::HasBytecode(function));
-  // Bytecode was loaded in the above compilation step.
-  // Stay in interpreter.
+  ASSERT(Function::HasCode(function));
+  RawCode* code = function->ptr()->code_;
+  ASSERT(code != StubCode::LazyCompile_entry()->code());
+  // TODO(regis): Once we share the same stack, try to invoke directly.
 #if defined(DEBUG)
   if (IsTracingExecution()) {
     THR_Print("%" Pu64 " ", icount_);
-    THR_Print("invoking %s\n", Function::Handle(function).ToCString());
+    THR_Print("invoking compiled %s\n", Function::Handle(function).ToCString());
   }
 #endif
-  RawCode* bytecode = function->ptr()->bytecode_;
-  RawObject** callee_fp = call_top + kKBCDartFrameFixedSize;
-  callee_fp[kKBCPcMarkerSlotFromFp] = bytecode;
-  callee_fp[kKBCSavedCallerPcSlotFromFp] = reinterpret_cast<RawObject*>(*pc);
-  callee_fp[kKBCSavedCallerFpSlotFromFp] = reinterpret_cast<RawObject*>(*FP);
-  pp_ = bytecode->ptr()->object_pool_;
-  *pc = reinterpret_cast<uint32_t*>(bytecode->ptr()->entry_point_);
-  pc_ = reinterpret_cast<uword>(*pc);  // For the profiler.
-  *FP = callee_fp;
-  *SP = *FP - 1;
-  // Dispatch will interpret function.
+  // On success, returns a RawInstance.  On failure, a RawError.
+  typedef RawObject* (*invokestub)(RawCode * code, RawArray * argdesc,
+                                   RawObject * *arg0, Thread * thread);
+  invokestub entrypoint = reinterpret_cast<invokestub>(
+      StubCode::InvokeDartCodeFromBytecode_entry()->EntryPoint());
+  RawObject* result;
+  Exit(thread, *FP, call_top + 1, *pc);
+  {
+    InterpreterSetjmpBuffer buffer(this);
+    if (!setjmp(buffer.buffer_)) {
+      thread->set_vm_tag(reinterpret_cast<uword>(entrypoint));
+      result = entrypoint(code, argdesc_, call_base, thread);
+      thread->set_vm_tag(VMTag::kDartTagId);
+      thread->set_top_exit_frame_info(0);
+      ASSERT(thread->execution_state() == Thread::kThreadInGenerated);
+    } else {
+      return false;
+    }
+  }
+  // Pop args and push result.
+  *SP = call_base;
+  **SP = result;
+  pp_ = InterpreterHelpers::FrameCode(*FP)->ptr()->object_pool_;
+
+  // It is legit to call the constructor of an error object, however a
+  // result of class UnhandledException must be propagated.
+  if (result->IsHeapObject() &&
+      result->GetClassId() == kUnhandledExceptionCid) {
+    (*SP)[0] = UnhandledException::RawCast(result)->ptr()->exception_;
+    (*SP)[1] = UnhandledException::RawCast(result)->ptr()->stacktrace_;
+    (*SP)[2] = 0;  // Space for result.
+    Exit(thread, *FP, *SP + 3, *pc);
+    NativeArguments args(thread, 2, *SP, *SP + 2);
+    if (!InvokeRuntime(thread, this, DRT_ReThrow, args)) {
+      return false;
+    }
+    UNREACHABLE();
+  }
+  return true;
+}
+
+DART_NOINLINE bool Interpreter::ProcessInvocation(bool* invoked,
+                                                  Thread* thread,
+                                                  RawFunction* function,
+                                                  RawObject** call_base,
+                                                  RawObject** call_top,
+                                                  uint32_t** pc,
+                                                  RawObject*** FP,
+                                                  RawObject*** SP) {
+  ASSERT(!Function::HasCode(function) && !Function::HasBytecode(function));
+  // If the function is an implicit getter or setter, process its invocation
+  // here without code or bytecode.
+  RawFunction::Kind kind = Function::kind(function);
+  switch (kind) {
+    case RawFunction::kImplicitGetter: {
+      // Field offset in words is cached as a Smi in function's data_.
+      RawInstance* instance = reinterpret_cast<RawInstance*>((*SP)[0]);
+      RawField* field = reinterpret_cast<RawField*>(function->ptr()->data_);
+      intptr_t offset_in_words = Smi::Value(field->ptr()->value_.offset_);
+      (*SP)[0] =
+          reinterpret_cast<RawObject**>(instance->ptr())[offset_in_words];
+      *invoked = true;
+      return true;
+    }
+    case RawFunction::kImplicitSetter: {
+      // Field offset in words is cached as a Smi in function's data_.
+      // TODO(regis): We currently ignore field.guarded_cid() and the type
+      // test of the setter value. Either execute these tests here or fall
+      // back to compiling the setter when required.
+      RawInstance* instance = reinterpret_cast<RawInstance*>((*SP)[-1]);
+      RawField* field = reinterpret_cast<RawField*>(function->ptr()->data_);
+      intptr_t offset_in_words = Smi::Value(field->ptr()->value_.offset_);
+      reinterpret_cast<RawObject**>(instance->ptr())[offset_in_words] =
+          (*SP)[0];
+      *--(*SP) = Object::null();
+      *invoked = true;
+      return true;
+    }
+    case RawFunction::kImplicitStaticFinalGetter: {
+      // Field object is cached in function's data_.
+      RawField* field = reinterpret_cast<RawField*>(function->ptr()->data_);
+      RawInstance* value = field->ptr()->value_.static_value_;
+      if (value == Object::sentinel().raw() ||
+          value == Object::transition_sentinel().raw()) {
+        (*SP)[1] = 0;  // Result of invoking the initializer.
+        (*SP)[2] = field;
+        Exit(thread, *FP, *SP + 3, *pc);
+        NativeArguments native_args(thread, 1, *SP + 2, *SP + 1);
+        if (!InvokeRuntime(thread, this, DRT_InitStaticField, native_args)) {
+          return false;
+        }
+        pp_ = InterpreterHelpers::FrameCode(*FP)->ptr()->object_pool_;
+        // The field is initialized by the runtime call, but not returned.
+        value = field->ptr()->value_.static_value_;
+      }
+      // Field was initialized. Return its value.
+      *++(*SP) = value;
+      *invoked = true;
+      return true;
+    }
+    case RawFunction::kNoSuchMethodDispatcher:
+    case RawFunction::kInvokeFieldDispatcher:
+      // TODO(regis): Implement. For now, use jitted version.
+      break;
+    default:
+      break;
+  }
+  // Compile the function to either generate code or load bytecode.
+  call_top[1] = 0;  // Code result.
+  call_top[2] = function;
+  Exit(thread, *FP, call_top + 3, *pc);
+  NativeArguments native_args(thread, 1, call_top + 2, call_top + 1);
+  if (!InvokeRuntime(thread, this, DRT_CompileFunction, native_args)) {
+    return false;
+  }
+  if (Function::HasCode(function)) {
+    *invoked = true;
+    return InvokeCompiled(thread, function, call_base, call_top, pc, FP, SP);
+  }
+  ASSERT(Function::HasBytecode(function));
+  // Bytecode was loaded in the above compilation step.
+  // The caller will dispatch to the function's bytecode.
+  *invoked = false;
   ASSERT(thread->vm_tag() == VMTag::kDartTagId);
   ASSERT(thread->top_exit_frame_info() == 0);
   return true;
@@ -990,10 +1043,17 @@
   RawObject** callee_fp = call_top + kKBCDartFrameFixedSize;
 
   RawFunction* function = FrameFunction(callee_fp);
-  if (Function::HasCode(function) || !Function::HasBytecode(function)) {
-    // TODO(regis): If the function is a dispatcher, execute the dispatch here.
-    return InvokeCompiled(thread, function, argdesc_, call_base, call_top, pc,
-                          FP, SP);
+  if (Function::HasCode(function)) {
+    return InvokeCompiled(thread, function, call_base, call_top, pc, FP, SP);
+  }
+  if (!Function::HasBytecode(function)) {
+    bool invoked = false;
+    bool result = ProcessInvocation(&invoked, thread, function, call_base,
+                                    call_top, pc, FP, SP);
+    if (invoked || !result) {
+      return result;
+    }
+    ASSERT(Function::HasBytecode(function));
   }
 #if defined(DEBUG)
   if (IsTracingExecution()) {
@@ -3234,6 +3294,7 @@
 
   {
     BYTECODE(InitStaticTOS, 0);
+    UNREACHABLE();  // Not used. TODO(regis): Remove this bytecode.
     RawField* field = static_cast<RawField*>(*SP--);
     RawObject* value = field->ptr()->value_.static_value_;
     if ((value == Object::sentinel().raw()) ||
diff --git a/runtime/vm/interpreter.h b/runtime/vm/interpreter.h
index ef71e71..04b3caa 100644
--- a/runtime/vm/interpreter.h
+++ b/runtime/vm/interpreter.h
@@ -135,9 +135,17 @@
               RawObject*** FP,
               RawObject*** SP);
 
+  bool ProcessInvocation(bool* invoked,
+                         Thread* thread,
+                         RawFunction* function,
+                         RawObject** call_base,
+                         RawObject** call_top,
+                         uint32_t** pc,
+                         RawObject*** FP,
+                         RawObject*** SP);
+
   bool InvokeCompiled(Thread* thread,
                       RawFunction* function,
-                      RawArray* argdesc,
                       RawObject** call_base,
                       RawObject** call_top,
                       uint32_t** pc,
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index c1e7654..e31beec 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5912,25 +5912,20 @@
   set_data(value);
 }
 
-RawField* Function::LookupImplicitGetterSetterField() const {
-  // TODO(27590) Store Field object inside RawFunction::data_ if possible.
-  Zone* Z = Thread::Current()->zone();
-  String& field_name = String::Handle(Z, name());
-  switch (kind()) {
-    case RawFunction::kImplicitGetter:
-    case RawFunction::kImplicitStaticFinalGetter:
-      field_name = Field::NameFromGetter(field_name);
-      break;
-    case RawFunction::kImplicitSetter:
-      field_name = Field::NameFromSetter(field_name);
-      break;
-    default:
-      UNREACHABLE();
-  }
-  ASSERT(field_name.IsSymbol());
-  const Class& owner = Class::Handle(Z, Owner());
-  ASSERT(!owner.IsNull());
-  return owner.LookupField(field_name);
+RawField* Function::accessor_field() const {
+  ASSERT(kind() == RawFunction::kImplicitGetter ||
+         kind() == RawFunction::kImplicitSetter ||
+         kind() == RawFunction::kImplicitStaticFinalGetter);
+  return Field::RawCast(raw_ptr()->data_);
+}
+
+void Function::set_accessor_field(const Field& value) const {
+  ASSERT(kind() == RawFunction::kImplicitGetter ||
+         kind() == RawFunction::kImplicitSetter ||
+         kind() == RawFunction::kImplicitStaticFinalGetter);
+  // Top level classes may be finalized multiple times.
+  ASSERT(raw_ptr()->data_ == Object::null() || raw_ptr()->data_ == value.raw());
+  set_data(value);
 }
 
 RawFunction* Function::parent_function() const {
@@ -5974,7 +5969,8 @@
 }
 
 RawFunction* Function::implicit_closure_function() const {
-  if (IsClosureFunction() || IsSignatureFunction() || IsFactory()) {
+  if (IsClosureFunction() || IsSignatureFunction() || IsFactory() ||
+      IsDispatcherOrImplicitAccessor() || IsImplicitStaticFieldInitializer()) {
     return Function::null();
   }
   const Object& obj = Object::Handle(raw_ptr()->data_);
@@ -6193,6 +6189,9 @@
 //                            Array[2] = Kernel offset of enclosing library
 //   signature function:      SignatureData
 //   method extractor:        Function extracted closure function
+//   implicit getter:         Field
+//   implicit setter:         Field
+//   impl. static final gttr: Field
 //   noSuchMethod dispatcher: Array arguments descriptor
 //   invoke-field dispatcher: Array arguments descriptor
 //   redirecting constructor: RedirectionData
@@ -7217,6 +7216,7 @@
   clone.set_owner(clone_owner);
   clone.ClearICDataArray();
   clone.ClearCode();
+  clone.set_data(Object::null_object());
   clone.set_usage_counter(0);
   clone.set_deoptimization_counter(0);
   clone.set_optimized_instruction_count(0);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 8c69cb2..ba4f605 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -2283,8 +2283,6 @@
   RawContextScope* context_scope() const;
   void set_context_scope(const ContextScope& value) const;
 
-  RawField* LookupImplicitGetterSetterField() const;
-
   // Enclosing function of this local function.
   RawFunction* parent_function() const;
 
@@ -2294,6 +2292,9 @@
   void set_saved_args_desc(const Array& array) const;
   RawArray* saved_args_desc() const;
 
+  void set_accessor_field(const Field& value) const;
+  RawField* accessor_field() const;
+
   bool IsMethodExtractor() const {
     return kind() == RawFunction::kMethodExtractor;
   }
@@ -2348,6 +2349,9 @@
   RawFunction::Kind kind() const {
     return KindBits::decode(raw_ptr()->kind_tag_);
   }
+  static RawFunction::Kind kind(RawFunction* function) {
+    return KindBits::decode(function->ptr()->kind_tag_);
+  }
 
   RawFunction::AsyncModifier modifier() const {
     return ModifierBits::decode(raw_ptr()->kind_tag_);
@@ -2646,6 +2650,12 @@
     return kind() == RawFunction::kImplicitSetter;
   }
 
+  // Returns true if this function represents an implicit static field
+  // initializer function.
+  bool IsImplicitStaticFieldInitializer() const {
+    return kind() == RawFunction::kImplicitStaticFinalGetter;
+  }
+
   // Returns true if this function represents a (possibly implicit) closure
   // function.
   bool IsClosureFunction() const {
diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc
index 89661ef..6dd1d5b 100644
--- a/runtime/vm/object_service.cc
+++ b/runtime/vm/object_service.cc
@@ -330,7 +330,7 @@
   if ((kind() == RawFunction::kImplicitGetter) ||
       (kind() == RawFunction::kImplicitSetter) ||
       (kind() == RawFunction::kImplicitStaticFinalGetter)) {
-    const Field& field = Field::Handle(LookupImplicitGetterSetterField());
+    const Field& field = Field::Handle(accessor_field());
     if (!field.IsNull()) {
       jsobj.AddProperty("_field", field);
     }
diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc
index a7a67b3..eb810c7 100644
--- a/runtime/vm/parser.cc
+++ b/runtime/vm/parser.cc
@@ -1361,7 +1361,7 @@
 
 SequenceNode* Parser::ParseStaticInitializer() {
   ExpectIdentifier("field name expected");
-  CheckToken(Token::kASSIGN, "field initialier expected");
+  CheckToken(Token::kASSIGN, "field initializer expected");
   ConsumeToken();
   OpenFunctionBlock(parsed_function()->function());
   TokenPosition expr_pos = TokenPos();
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 38d5fa1..bcbb625 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -1782,6 +1782,7 @@
     }
     Exceptions::PropagateError(Error::Cast(result));
   }
+  arguments.SetReturn(result);
 #else
   UNREACHABLE();
 #endif  // defined(DART_USE_INTERPRETER)