[turbofan] Support named access on global proxy in serializer

Process feedback and hints for Lda/StaNamed bytecodes w.r.t. access on
the global proxy. This stores the property cells (or their absence) on
the JSGlobalProxyData.

Bug: v8:7790
Change-Id: Iadedea5494611c1b2ed38b6ce75687e084cc27f9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1499499
Commit-Queue: Jaroslav Sevcik <jarin@chromium.org>
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60411}
diff --git a/src/compiler/js-heap-broker.cc b/src/compiler/js-heap-broker.cc
index 0f91cc8..f764cdce7 100644
--- a/src/compiler/js-heap-broker.cc
+++ b/src/compiler/js-heap-broker.cc
@@ -702,6 +702,9 @@
   bool supports_fast_array_resize() const {
     return supports_fast_array_resize_;
   }
+  bool IsMapOfCurrentGlobalProxy() const {
+    return is_map_of_current_global_proxy_;
+  }
 
   // Extra information.
 
@@ -750,6 +753,7 @@
   int const unused_property_fields_;
   bool const supports_fast_array_iteration_;
   bool const supports_fast_array_resize_;
+  bool const is_map_of_current_global_proxy_;
 
   bool serialized_elements_kind_generalizations_ = false;
   ZoneVector<MapData*> elements_kind_generalizations_;
@@ -865,6 +869,8 @@
           SupportsFastArrayIteration(broker->isolate(), object)),
       supports_fast_array_resize_(
           SupportsFastArrayResize(broker->isolate(), object)),
+      is_map_of_current_global_proxy_(
+          object->IsMapOfGlobalProxy(broker->isolate()->native_context())),
       elements_kind_generalizations_(broker->zone()) {}
 
 JSFunctionData::JSFunctionData(JSHeapBroker* broker, ObjectData** storage,
@@ -1328,10 +1334,65 @@
 class JSGlobalProxyData : public JSObjectData {
  public:
   JSGlobalProxyData(JSHeapBroker* broker, ObjectData** storage,
-                    Handle<JSGlobalProxy> object)
-      : JSObjectData(broker, storage, object) {}
+                    Handle<JSGlobalProxy> object);
+
+  PropertyCellData* GetPropertyCell(JSHeapBroker* broker, NameData* name,
+                                    bool serialize);
+
+ private:
+  // Properties that either
+  // (1) are known to exist as property cells on the global object, or
+  // (2) are known not to (possibly they don't exist at all).
+  // In case (2), the second pair component is nullptr.
+  ZoneVector<std::pair<NameData*, PropertyCellData*>> properties_;
 };
 
+JSGlobalProxyData::JSGlobalProxyData(JSHeapBroker* broker, ObjectData** storage,
+                                     Handle<JSGlobalProxy> object)
+    : JSObjectData(broker, storage, object), properties_(broker->zone()) {}
+
+base::Optional<PropertyCellRef> GetPropertyCellFromHeap(JSHeapBroker* broker,
+                                                        Handle<Name> name) {
+  LookupIterator it(broker->isolate(),
+                    handle(broker->native_context().object()->global_object(),
+                           broker->isolate()),
+                    name, LookupIterator::OWN);
+  it.TryLookupCachedProperty();
+  if (it.state() == LookupIterator::DATA &&
+      it.GetHolder<JSObject>()->IsJSGlobalObject()) {
+    return PropertyCellRef(broker, it.GetPropertyCell());
+  }
+  return base::nullopt;
+}
+
+PropertyCellData* JSGlobalProxyData::GetPropertyCell(JSHeapBroker* broker,
+                                                     NameData* name,
+                                                     bool serialize) {
+  CHECK_NOT_NULL(name);
+  for (auto const& p : properties_) {
+    if (p.first == name) return p.second;
+  }
+
+  if (!serialize) {
+    AllowHandleAllocation handle_allocation_;
+    AllowHandleDereference handle_dereference_;
+    AllowHeapAllocation heap_allocation_;
+    TRACE(broker, "GetPropertyCell: missing knowledge about global property "
+                      << Brief(*name->object()) << "\n");
+    return nullptr;
+  }
+
+  PropertyCellData* result = nullptr;
+  base::Optional<PropertyCellRef> cell =
+      GetPropertyCellFromHeap(broker, Handle<Name>::cast(name->object()));
+  if (cell.has_value()) {
+    cell->Serialize();
+    result = cell->data()->AsPropertyCell();
+  }
+  properties_.push_back({name, result});
+  return result;
+}
+
 class CodeData : public HeapObjectData {
  public:
   CodeData(JSHeapBroker* broker, ObjectData** storage, Handle<Code> object)
@@ -2019,6 +2080,15 @@
   return data()->AsMap()->supports_fast_array_resize();
 }
 
+bool MapRef::IsMapOfCurrentGlobalProxy() const {
+  if (broker()->mode() == JSHeapBroker::kDisabled) {
+    AllowHandleDereference allow_handle_dereference;
+    AllowHandleAllocation handle_allocation;
+    return object()->IsMapOfGlobalProxy(broker()->isolate()->native_context());
+  }
+  return data()->AsMap()->IsMapOfCurrentGlobalProxy();
+}
+
 int JSFunctionRef::InitialMapInstanceSizeWithMinSlack() const {
   if (broker()->mode() == JSHeapBroker::kDisabled) {
     AllowHandleDereference allow_handle_dereference;
@@ -2875,6 +2945,18 @@
   data()->AsPropertyCell()->Serialize(broker());
 }
 
+base::Optional<PropertyCellRef> JSGlobalProxyRef::GetPropertyCell(
+    NameRef const& name, bool serialize) const {
+  if (broker()->mode() == JSHeapBroker::kDisabled) {
+    return GetPropertyCellFromHeap(broker(), name.object());
+  }
+  PropertyCellData* property_cell_data =
+      data()->AsJSGlobalProxy()->GetPropertyCell(
+          broker(), name.data()->AsName(), serialize);
+  if (property_cell_data == nullptr) return base::nullopt;
+  return PropertyCellRef(broker(), property_cell_data);
+}
+
 bool CanInlineElementAccess(MapRef const& map) {
   if (!map.IsJSObjectMap()) return false;
   if (map.is_access_check_needed()) return false;
@@ -2911,7 +2993,6 @@
   DCHECK(IsPropertyCell());
   return cell_or_context_.AsPropertyCell();
 }
-
 ContextRef GlobalAccessFeedback::script_context() const {
   DCHECK(IsScriptContextSlot());
   return cell_or_context_.AsContext();
@@ -2925,11 +3006,11 @@
   return FeedbackNexus::ImmutabilityBit::decode(index_and_immutable_);
 }
 
-base::Optional<ObjectRef> GlobalAccessFeedback::GetConstantValue() const {
-  if (IsScriptContextSlot() && immutable()) {
-    // Return the value of this global variable if it's guaranteed to be
-    // constant.
-    return script_context().get(slot_index());
+base::Optional<ObjectRef> GlobalAccessFeedback::GetConstantHint() const {
+  if (IsScriptContextSlot()) {
+    if (immutable()) return script_context().get(slot_index());
+  } else {
+    return property_cell().value();
   }
   return {};
 }
diff --git a/src/compiler/js-heap-broker.h b/src/compiler/js-heap-broker.h
index 02e8bf8..dd17fc8 100644
--- a/src/compiler/js-heap-broker.h
+++ b/src/compiler/js-heap-broker.h
@@ -132,6 +132,8 @@
   ObjectData* data_;  // Should be used only by object() getters.
 
  private:
+  friend class JSGlobalProxyRef;
+  friend class JSGlobalProxyData;
   JSHeapBroker* broker_;
 };
 
@@ -448,6 +450,7 @@
   bool is_migration_target() const;
   bool supports_fast_array_iteration() const;
   bool supports_fast_array_resize() const;
+  bool IsMapOfCurrentGlobalProxy() const;
 
 #define DEF_TESTER(Type, ...) bool Is##Type##Map() const;
   INSTANCE_TYPE_CHECKERS(DEF_TESTER)
@@ -610,6 +613,16 @@
  public:
   using JSObjectRef::JSObjectRef;
   Handle<JSGlobalProxy> object() const;
+
+  // If {serialize} is false:
+  //   If the property is known to exist as a property cell (on the global
+  //   object), return that property cell. Otherwise (not known to exist as a
+  //   property cell or known not to exist as a property cell) return nothing.
+  // If {serialize} is true:
+  //   Like above but potentially access the heap and serialize the necessary
+  //   information.
+  base::Optional<PropertyCellRef> GetPropertyCell(NameRef const& name,
+                                                  bool serialize = false) const;
 };
 
 class CodeRef : public HeapObjectRef {
@@ -653,7 +666,7 @@
   int slot_index() const;
   bool immutable() const;
 
-  base::Optional<ObjectRef> GetConstantValue() const;
+  base::Optional<ObjectRef> GetConstantHint() const;
 
  private:
   ObjectRef const cell_or_context_;
diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc
index cb26017..0bda91e 100644
--- a/src/compiler/js-native-context-specialization.cc
+++ b/src/compiler/js-native-context-specialization.cc
@@ -785,16 +785,12 @@
 Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
     Node* node, Node* receiver, Node* value, Handle<Name> name,
     AccessMode access_mode, Node* index) {
-  // Lookup on the global object. We only deal with own data properties
-  // of the global object here (represented as PropertyCell).
-  LookupIterator it(isolate(), global_object(), name, LookupIterator::OWN);
-  it.TryLookupCachedProperty();
-  if (it.state() != LookupIterator::DATA) return NoChange();
-  if (!it.GetHolder<JSObject>()->IsJSGlobalObject()) return NoChange();
-  PropertyCellRef property_cell(broker(), it.GetPropertyCell());
-  property_cell.Serialize();
-  return ReduceGlobalAccess(node, receiver, value, name, access_mode, index,
-                            property_cell);
+  NameRef name_ref(broker(), name);
+  base::Optional<PropertyCellRef> cell =
+      native_context().global_proxy_object().GetPropertyCell(name_ref);
+  return cell.has_value() ? ReduceGlobalAccess(node, receiver, value, name,
+                                               access_mode, index, *cell)
+                          : NoChange();
 }
 
 Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
@@ -1095,16 +1091,10 @@
   // native contexts' global proxy, and turn that into a direct access
   // to the current native contexts' global object instead.
   if (receiver_maps.size() == 1) {
-    Handle<Map> receiver_map = receiver_maps.front();
-    if (receiver_map->IsJSGlobalProxyMap()) {
-      Object maybe_constructor = receiver_map->GetConstructor();
-      // Detached global proxies have |null| as their constructor.
-      if (maybe_constructor->IsJSFunction() &&
-          JSFunction::cast(maybe_constructor)->native_context() ==
-              *native_context().object()) {
-        return ReduceGlobalAccess(node, receiver, value, name, access_mode,
-                                  index);
-      }
+    MapRef receiver_map(broker(), receiver_maps.front());
+    if (receiver_map.IsMapOfCurrentGlobalProxy()) {
+      return ReduceGlobalAccess(node, receiver, value, name, access_mode,
+                                index);
     }
   }
 
@@ -1358,7 +1348,8 @@
 
   // Check if we are accessing the current native contexts' global proxy.
   HeapObjectMatcher m(receiver);
-  if (m.HasValue() && m.Value().is_identical_to(global_proxy())) {
+  if (m.HasValue() &&
+      m.Ref(broker()).equals(native_context().global_proxy_object())) {
     // Optimize accesses to the current native contexts' global proxy.
     return ReduceGlobalAccess(node, nullptr, value, name, access_mode);
   }
diff --git a/src/compiler/serializer-for-background-compilation.cc b/src/compiler/serializer-for-background-compilation.cc
index ebe12a3..2a74d79 100644
--- a/src/compiler/serializer-for-background-compilation.cc
+++ b/src/compiler/serializer-for-background-compilation.cc
@@ -705,7 +705,7 @@
   GlobalAccessFeedback const* feedback = ProcessFeedbackForGlobalAccess(slot);
   if (feedback != nullptr) {
     // We may be able to contribute to accumulator constant hints.
-    base::Optional<ObjectRef> value = feedback->GetConstantValue();
+    base::Optional<ObjectRef> value = feedback->GetConstantHint();
     if (value.has_value()) {
       environment()->accumulator_hints().AddConstant(value->object());
     }
@@ -733,7 +733,22 @@
   ProcessFeedbackForGlobalAccess(slot);
 }
 
-// Note: We never use the same feeedback slot for multiple access modes.
+namespace {
+template <class MapContainer>
+MapHandles GetRelevantReceiverMaps(Isolate* isolate, MapContainer const& maps) {
+  MapHandles result;
+  for (Handle<Map> map : maps) {
+    if (Map::TryUpdate(isolate, map).ToHandle(&map) &&
+        !map->is_abandoned_prototype_map()) {
+      DCHECK(!map->is_deprecated());
+      result.push_back(map);
+    }
+  }
+  return result;
+}
+}  // namespace
+
+// Note: We never use the same feedback slot for multiple access modes.
 void SerializerForBackgroundCompilation::ProcessFeedbackForKeyedPropertyAccess(
     FeedbackSlot slot, AccessMode mode) {
   if (slot.IsInvalid()) return;
@@ -743,8 +758,6 @@
   FeedbackSource source(nexus);
   if (broker()->HasFeedback(source)) return;
 
-  if (nexus.ic_state() == MEGAMORPHIC) return;
-
   if (nexus.GetKeyType() == PROPERTY) {
     CHECK_NE(mode, AccessMode::kStoreInLiteral);
     return;  // TODO(neis): Support named access.
@@ -755,7 +768,8 @@
   MapHandles maps;
   nexus.ExtractMaps(&maps);
   ElementAccessFeedback const* processed =
-      broker()->ProcessFeedbackMapsForElementAccess(maps);
+      broker()->ProcessFeedbackMapsForElementAccess(
+          GetRelevantReceiverMaps(broker()->isolate(), maps));
   broker()->SetFeedback(source, processed);
   if (processed == nullptr) return;
 
@@ -776,6 +790,36 @@
   }
 }
 
+void SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
+    MapRef const& map, NameRef const& name) {
+  // For JSNativeContextSpecialization::ReduceNamedAccess.
+  if (map.IsMapOfCurrentGlobalProxy()) {
+    broker()->native_context().global_proxy_object().GetPropertyCell(name,
+                                                                     true);
+  }
+}
+
+// Note: We never use the same feedback slot for multiple names.
+void SerializerForBackgroundCompilation::ProcessFeedbackForNamedPropertyAccess(
+    FeedbackSlot slot, NameRef const& name) {
+  if (slot.IsInvalid()) return;
+  if (environment()->function().feedback_vector.is_null()) return;
+
+  FeedbackNexus nexus(environment()->function().feedback_vector, slot);
+  FeedbackSource source(nexus);
+  if (broker()->HasFeedback(source)) return;
+
+  MapHandles maps;
+  nexus.ExtractMaps(&maps);
+  for (Handle<Map> map : GetRelevantReceiverMaps(broker()->isolate(), maps)) {
+    ProcessMapForNamedPropertyAccess(MapRef(broker(), map), name);
+  }
+
+  // NamedProperty support is still WIP. For now we don't have any actual data
+  // to store, so use nullptr to at least record that we processed the feedback.
+  broker()->SetFeedback(source, nullptr);
+}
+
 void SerializerForBackgroundCompilation::VisitLdaKeyedProperty(
     BytecodeArrayIterator* iterator) {
   FeedbackSlot slot = iterator->GetSlotOperand(1);
@@ -783,6 +827,42 @@
   environment()->accumulator_hints().Clear();
 }
 
+void SerializerForBackgroundCompilation::ProcessNamedPropertyAccess(
+    Hints const& receiver, NameRef const& name, FeedbackSlot slot) {
+  if (!slot.IsInvalid()) ProcessFeedbackForNamedPropertyAccess(slot, name);
+
+  for (Handle<Map> map :
+       GetRelevantReceiverMaps(broker()->isolate(), receiver.maps())) {
+    ProcessMapForNamedPropertyAccess(MapRef(broker(), map), name);
+  }
+
+  JSGlobalProxyRef global_proxy =
+      broker()->native_context().global_proxy_object();
+  for (Handle<Object> object : receiver.constants()) {
+    // For JSNativeContextSpecialization::ReduceNamedAccessFromNexus.
+    if (object.equals(global_proxy.object())) {
+      global_proxy.GetPropertyCell(name, true);
+    }
+  }
+
+  environment()->accumulator_hints().Clear();
+}
+
+void SerializerForBackgroundCompilation::VisitLdaNamedProperty(
+    BytecodeArrayIterator* iterator) {
+  Hints const& receiver =
+      environment()->register_hints(iterator->GetRegisterOperand(0));
+  Handle<Name> name(Name::cast(iterator->GetConstantForIndexOperand(1)),
+                    broker()->isolate());
+  FeedbackSlot slot = iterator->GetSlotOperand(2);
+  ProcessNamedPropertyAccess(receiver, NameRef(broker(), name), slot);
+}
+
+void SerializerForBackgroundCompilation::VisitStaNamedProperty(
+    BytecodeArrayIterator* iterator) {
+  VisitLdaNamedProperty(iterator);
+}
+
 void SerializerForBackgroundCompilation::VisitTestIn(
     BytecodeArrayIterator* iterator) {
   FeedbackSlot slot = iterator->GetSlotOperand(1);
@@ -798,8 +878,8 @@
 
   ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kStore);
 
-  // Process hints about constants.
   for (Handle<Object> object : receiver.constants()) {
+    // For JSNativeContextSpecialization::ReduceElementAccess.
     if (object->IsJSTypedArray()) JSTypedArrayRef(broker(), object).Serialize();
   }
 
diff --git a/src/compiler/serializer-for-background-compilation.h b/src/compiler/serializer-for-background-compilation.h
index cd837c0..7cb70cf 100644
--- a/src/compiler/serializer-for-background-compilation.h
+++ b/src/compiler/serializer-for-background-compilation.h
@@ -48,17 +48,15 @@
   V(SwitchOnGeneratorState)       \
   V(Throw)
 
-#define CLEAR_ACCUMULATOR_LIST(V)   \
-  V(CreateEmptyObjectLiteral)       \
-  V(CreateMappedArguments)          \
-  V(CreateRestParameter)            \
-  V(CreateUnmappedArguments)        \
-  V(LdaContextSlot)                 \
-  V(LdaCurrentContextSlot)          \
-  V(LdaImmutableContextSlot)        \
-  V(LdaImmutableCurrentContextSlot) \
-  V(LdaNamedProperty)               \
-  V(LdaNamedPropertyNoFeedback)
+#define CLEAR_ACCUMULATOR_LIST(V) \
+  V(CreateEmptyObjectLiteral)     \
+  V(CreateMappedArguments)        \
+  V(CreateRestParameter)          \
+  V(CreateUnmappedArguments)      \
+  V(LdaContextSlot)               \
+  V(LdaCurrentContextSlot)        \
+  V(LdaImmutableContextSlot)      \
+  V(LdaImmutableCurrentContextSlot)
 
 #define UNCONDITIONAL_JUMPS_LIST(V) \
   V(Jump)                           \
@@ -86,6 +84,8 @@
   V(JumpIfUndefinedConstant)
 
 #define INGORED_BYTECODE_LIST(V) \
+  V(LdaNamedPropertyNoFeedback)  \
+  V(StaNamedPropertyNoFeedback)  \
   V(TestEqual)                   \
   V(TestEqualStrict)             \
   V(TestLessThan)                \
@@ -125,6 +125,7 @@
   V(LdaKeyedProperty)                \
   V(LdaLookupGlobalSlot)             \
   V(LdaLookupGlobalSlotInsideTypeof) \
+  V(LdaNamedProperty)                \
   V(LdaNull)                         \
   V(Ldar)                            \
   V(LdaSmi)                          \
@@ -136,6 +137,7 @@
   V(StaGlobal)                       \
   V(StaInArrayLiteral)               \
   V(StaKeyedProperty)                \
+  V(StaNamedProperty)                \
   V(Star)                            \
   V(TestIn)                          \
   V(Wide)                            \
@@ -243,6 +245,8 @@
                           bool with_spread = false);
   void ProcessJump(interpreter::BytecodeArrayIterator* iterator);
   void MergeAfterJump(interpreter::BytecodeArrayIterator* iterator);
+  void ProcessNamedPropertyAccess(Hints const& receiver, NameRef const& name,
+                                  FeedbackSlot slot);
 
   Hints RunChildSerializer(CompilationSubject function,
                            base::Optional<Hints> new_target,
@@ -251,6 +255,9 @@
   GlobalAccessFeedback const* ProcessFeedbackForGlobalAccess(FeedbackSlot slot);
   void ProcessFeedbackForKeyedPropertyAccess(FeedbackSlot slot,
                                              AccessMode mode);
+  void ProcessFeedbackForNamedPropertyAccess(FeedbackSlot slot,
+                                             NameRef const& name);
+  void ProcessMapForNamedPropertyAccess(MapRef const& map, NameRef const& name);
 
   JSHeapBroker* broker() const { return broker_; }
   Zone* zone() const { return zone_; }
diff --git a/src/objects/map.cc b/src/objects/map.cc
index 4f6452f..4352a09 100644
--- a/src/objects/map.cc
+++ b/src/objects/map.cc
@@ -56,6 +56,18 @@
   return MaybeHandle<JSFunction>();
 }
 
+bool Map::IsMapOfGlobalProxy(Handle<NativeContext> native_context) const {
+  DisallowHeapAllocation no_gc;
+  if (IsJSGlobalProxyMap()) {
+    Object maybe_constructor = GetConstructor();
+    // Detached global proxies have |null| as their constructor.
+    return maybe_constructor.IsJSFunction() &&
+           JSFunction::cast(maybe_constructor).native_context() ==
+               *native_context;
+  }
+  return false;
+}
+
 void Map::PrintReconfiguration(Isolate* isolate, FILE* file, int modify_index,
                                PropertyKind kind,
                                PropertyAttributes attributes) {
diff --git a/src/objects/map.h b/src/objects/map.h
index 7bdbe00..2bf09c6 100644
--- a/src/objects/map.h
+++ b/src/objects/map.h
@@ -895,6 +895,9 @@
       InstanceType instance_type);
   inline bool CanHaveFastTransitionableElementsKind() const;
 
+  // Whether this is the map of the given native context's global proxy.
+  bool IsMapOfGlobalProxy(Handle<NativeContext> native_context) const;
+
  private:
   // This byte encodes either the instance size without the in-object slack or
   // the slack size in properties backing store.
diff --git a/test/mjsunit/compiler/named-store.js b/test/mjsunit/compiler/named-store.js
index 8d1306a..037d859 100644
--- a/test/mjsunit/compiler/named-store.js
+++ b/test/mjsunit/compiler/named-store.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Flags: --allow-natives-syntax
+
 function Foo(a, b) {
   var bname = "b";
   this["a"] = a;
@@ -29,3 +31,29 @@
   var f = new Foo(i + "", (i + 2) + "");
   assertEquals((i + "") + ((i + 2) + ""), f.x);
 }
+
+
+{
+  function Global(i) { this.bla = i }
+  Global(0);
+  Global(1);
+  %OptimizeFunctionOnNextCall(Global);
+  Global(2);
+  assertEquals(bla, 2);
+}
+
+
+{
+  function access(obj) { obj.bla = 42 }
+  access({a: 0});
+  access({b: 0});
+  access({c: 0});
+  access({d: 0});
+  access({e: 0});
+  var global = this;
+  function foo() { access(global) };
+  foo();
+  foo();
+  %OptimizeFunctionOnNextCall(foo);
+  foo();
+}