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