Commit fc36dfb7 authored by Georg Neis's avatar Georg Neis Committed by Commit Bot

[turbofan] Serialize for ReduceKeyedLoadFromHeapConstant

Drive-by fix: In ProcessFeedbackForGlobalAccess, we had forgotten to
return the feedback when it already existed.

Bug: v8:7790, v8:9094
Change-Id: Ie4be6cef5755bbdd9d8ed472caaa2e32d243893d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1554680Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60705}
parent 38ce72ae
......@@ -182,23 +182,27 @@ class JSObjectData : public HeapObjectData {
JSObjectData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSObject> object);
// Recursively serializes all reachable JSObjects.
// Recursive serialization of all reachable JSObjects.
void SerializeAsBoilerplate(JSHeapBroker* broker);
const JSObjectField& GetInobjectField(int property_index) const;
// Shallow serialization of {elements}.
void SerializeElements(JSHeapBroker* broker);
const JSObjectField& GetInobjectField(int property_index) const;
bool serialized_elements() const { return serialized_elements_; }
FixedArrayBaseData* elements() const;
// This method is only used to assert our invariants.
bool cow_or_empty_elements_tenured() const;
void SerializeObjectCreateMap(JSHeapBroker* broker);
MapData* object_create_map() const { // Can be nullptr.
CHECK(serialized_object_create_map_);
return object_create_map_;
}
ObjectData* GetOwnConstantElement(JSHeapBroker* broker, uint32_t index,
bool serialize);
// This method is only used to assert our invariants.
bool cow_or_empty_elements_tenured() const;
private:
void SerializeRecursive(JSHeapBroker* broker, int max_depths);
......@@ -213,6 +217,12 @@ class JSObjectData : public HeapObjectData {
bool serialized_object_create_map_ = false;
MapData* object_create_map_ = nullptr;
// Elements (indexed properties) that either
// (1) are known to exist directly on the object as non-writable and
// non-configurable, 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<uint32_t, ObjectData*>> own_constant_elements_;
};
void JSObjectData::SerializeObjectCreateMap(JSHeapBroker* broker) {
......@@ -236,6 +246,40 @@ void JSObjectData::SerializeObjectCreateMap(JSHeapBroker* broker) {
}
}
namespace {
base::Optional<ObjectRef> GetOwnElementFromHeap(JSHeapBroker* broker,
Handle<Object> receiver,
uint32_t index,
bool constant_only) {
LookupIterator it(broker->isolate(), receiver, index, LookupIterator::OWN);
if (it.state() == LookupIterator::DATA &&
(!constant_only || (it.IsReadOnly() && !it.IsConfigurable()))) {
return ObjectRef(broker, it.GetDataValue());
}
return base::nullopt;
}
} // namespace
ObjectData* JSObjectData::GetOwnConstantElement(JSHeapBroker* broker,
uint32_t index,
bool serialize) {
for (auto const& p : own_constant_elements_) {
if (p.first == index) return p.second;
}
if (!serialize) {
TRACE(broker, "GetOwnConstantElement: missing index "
<< index << " on data " << this << "\n");
return nullptr;
}
base::Optional<ObjectRef> element =
GetOwnElementFromHeap(broker, object(), index, true);
ObjectData* result = element.has_value() ? element->data() : nullptr;
own_constant_elements_.push_back({index, result});
return result;
}
class JSTypedArrayData : public JSObjectData {
public:
JSTypedArrayData(JSHeapBroker* broker, ObjectData** storage,
......@@ -496,6 +540,9 @@ class StringData : public NameData {
bool is_external_string() const { return is_external_string_; }
bool is_seq_string() const { return is_seq_string_; }
StringData* GetCharAsString(JSHeapBroker* broker, uint32_t index,
bool serialize);
private:
int const length_;
uint16_t const first_char_;
......@@ -503,6 +550,11 @@ class StringData : public NameData {
bool const is_external_string_;
bool const is_seq_string_;
// Known individual characters as strings, corresponding to the semantics of
// element access (s[i]). The first pair component is always less than
// {length_}. The second component is never nullptr.
ZoneVector<std::pair<uint32_t, StringData*>> chars_as_strings_;
static constexpr int kMaxLengthForDoubleConversion = 23;
};
......@@ -518,7 +570,8 @@ StringData::StringData(JSHeapBroker* broker, ObjectData** storage,
length_(object->length()),
first_char_(length_ > 0 ? object->Get(0) : 0),
is_external_string_(object->IsExternalString()),
is_seq_string_(object->IsSeqString()) {
is_seq_string_(object->IsSeqString()),
chars_as_strings_(broker->zone()) {
int flags = ALLOW_HEX | ALLOW_OCTAL | ALLOW_BINARY;
if (length_ <= kMaxLengthForDoubleConversion) {
to_number_ = StringToDouble(broker->isolate(), object, flags);
......@@ -536,6 +589,28 @@ class InternalizedStringData : public StringData {
uint32_t array_index_;
};
StringData* StringData::GetCharAsString(JSHeapBroker* broker, uint32_t index,
bool serialize) {
if (index >= static_cast<uint32_t>(length())) return nullptr;
for (auto const& p : chars_as_strings_) {
if (p.first == index) return p.second;
}
if (!serialize) {
TRACE(broker, "GetCharAsString: missing index " << index << " on data "
<< this << "\n");
return nullptr;
}
base::Optional<ObjectRef> element =
GetOwnElementFromHeap(broker, object(), index, true);
StringData* result =
element.has_value() ? element->data()->AsString() : nullptr;
chars_as_strings_.push_back({index, result});
return result;
}
InternalizedStringData::InternalizedStringData(
JSHeapBroker* broker, ObjectData** storage,
Handle<InternalizedString> object)
......@@ -1067,7 +1142,8 @@ void JSBoundFunctionData::Serialize(JSHeapBroker* broker) {
JSObjectData::JSObjectData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSObject> object)
: HeapObjectData(broker, storage, object),
inobject_fields_(broker->zone()) {}
inobject_fields_(broker->zone()),
own_constant_elements_(broker->zone()) {}
FixedArrayData::FixedArrayData(JSHeapBroker* broker, ObjectData** storage,
Handle<FixedArray> object)
......@@ -1143,18 +1219,27 @@ class JSArrayData : public JSObjectData {
public:
JSArrayData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSArray> object);
void Serialize(JSHeapBroker* broker);
void Serialize(JSHeapBroker* broker);
ObjectData* length() const { return length_; }
ObjectData* GetOwnElement(JSHeapBroker* broker, uint32_t index,
bool serialize);
private:
bool serialized_ = false;
ObjectData* length_ = nullptr;
// Elements (indexed properties) that either
// (1) are known to exist directly on the 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<uint32_t, ObjectData*>> own_elements_;
};
JSArrayData::JSArrayData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSArray> object)
: JSObjectData(broker, storage, object) {}
: JSObjectData(broker, storage, object), own_elements_(broker->zone()) {}
void JSArrayData::Serialize(JSHeapBroker* broker) {
if (serialized_) return;
......@@ -1162,10 +1247,30 @@ void JSArrayData::Serialize(JSHeapBroker* broker) {
TraceScope tracer(broker, this, "JSArrayData::Serialize");
Handle<JSArray> jsarray = Handle<JSArray>::cast(object());
DCHECK_NULL(length_);
length_ = broker->GetOrCreateData(jsarray->length());
}
ObjectData* JSArrayData::GetOwnElement(JSHeapBroker* broker, uint32_t index,
bool serialize) {
for (auto const& p : own_elements_) {
if (p.first == index) return p.second;
}
if (!serialize) {
TRACE(broker, "GetOwnElement: missing index " << index << " on data "
<< this << "\n");
return nullptr;
}
base::Optional<ObjectRef> element =
GetOwnElementFromHeap(broker, object(), index, false);
ObjectData* result = element.has_value() ? element->data() : nullptr;
own_elements_.push_back({index, result});
return result;
}
class ScopeInfoData : public HeapObjectData {
public:
ScopeInfoData(JSHeapBroker* broker, ObjectData** storage,
......@@ -1353,6 +1458,7 @@ JSGlobalProxyData::JSGlobalProxyData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSGlobalProxy> object)
: JSObjectData(broker, storage, object), properties_(broker->zone()) {}
namespace {
base::Optional<PropertyCellRef> GetPropertyCellFromHeap(JSHeapBroker* broker,
Handle<Name> name) {
LookupIterator it(broker->isolate(),
......@@ -1366,6 +1472,7 @@ base::Optional<PropertyCellRef> GetPropertyCellFromHeap(JSHeapBroker* broker,
}
return base::nullopt;
}
} // namespace
PropertyCellData* JSGlobalProxyData::GetPropertyCell(JSHeapBroker* broker,
NameData* name,
......@@ -2625,6 +2732,46 @@ Maybe<double> ObjectRef::OddballToNumber() const {
}
}
base::Optional<ObjectRef> ObjectRef::GetOwnConstantElement(
uint32_t index, bool serialize) const {
if (broker()->mode() == JSHeapBroker::kDisabled) {
return (IsJSObject() || IsString())
? GetOwnElementFromHeap(broker(), object(), index, true)
: base::nullopt;
}
ObjectData* element = nullptr;
if (IsJSObject()) {
element =
data()->AsJSObject()->GetOwnConstantElement(broker(), index, serialize);
} else if (IsString()) {
element = data()->AsString()->GetCharAsString(broker(), index, serialize);
}
if (element == nullptr) return base::nullopt;
return ObjectRef(broker(), element);
}
base::Optional<ObjectRef> JSArrayRef::GetOwnCowElement(uint32_t index,
bool serialize) const {
if (broker()->mode() == JSHeapBroker::kDisabled) {
if (!object()->elements()->IsCowArray()) return base::nullopt;
return GetOwnElementFromHeap(broker(), object(), index, false);
}
if (serialize) {
data()->AsJSObject()->SerializeElements(broker());
} else if (!data()->AsJSObject()->serialized_elements()) {
TRACE(broker(),
"GetOwnCowElement: missing 'elements' on data " << this << ".\n");
return base::nullopt;
}
if (!elements().map().IsFixedCowArrayMap()) return base::nullopt;
ObjectData* element =
data()->AsJSArray()->GetOwnElement(broker(), index, serialize);
if (element == nullptr) return base::nullopt;
return ObjectRef(broker(), element);
}
double HeapNumberRef::value() const {
IF_BROKER_DISABLED_ACCESS_HANDLE_C(HeapNumber, value);
return data()->AsHeapNumber()->value();
......
......@@ -124,6 +124,11 @@ class V8_EXPORT_PRIVATE ObjectRef {
bool BooleanValue() const;
Maybe<double> OddballToNumber() const;
// Return the element at key {index} if {index} is known to be an own data
// property of the object that is non-writable and non-configurable.
base::Optional<ObjectRef> GetOwnConstantElement(uint32_t index,
bool serialize = false) const;
Isolate* isolate() const;
protected:
......@@ -132,8 +137,12 @@ class V8_EXPORT_PRIVATE ObjectRef {
ObjectData* data_; // Should be used only by object() getters.
private:
friend class JSArrayData;
friend class JSGlobalProxyRef;
friend class JSGlobalProxyData;
friend class JSObjectData;
friend class StringData;
JSHeapBroker* broker_;
};
......@@ -521,6 +530,11 @@ class JSArrayRef : public JSObjectRef {
Handle<JSArray> object() const;
ObjectRef length() const;
// Return the element at key {index} if the array has a copy-on-write elements
// storage and {index} is known to be an own data property.
base::Optional<ObjectRef> GetOwnCowElement(uint32_t index,
bool serialize = false) const;
};
class ScopeInfoRef : public HeapObjectRef {
......
......@@ -1742,58 +1742,38 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant(
return NoChange();
}
// Check whether we're accessing a known element on the {receiver}
// that is non-configurable, non-writable (e.g. the {receiver} was
// frozen using Object.freeze).
// Check whether we're accessing a known element on the {receiver} and can
// constant-fold the load.
NumberMatcher mindex(index);
if (mindex.IsInteger() && mindex.IsInRange(0.0, kMaxUInt32 - 1.0)) {
LookupIterator it(isolate(), receiver_ref.object(),
static_cast<uint32_t>(mindex.Value()),
LookupIterator::OWN);
if (it.state() == LookupIterator::DATA) {
if (it.IsReadOnly() && !it.IsConfigurable()) {
// We can safely constant-fold the {index} access to {receiver},
// since the element is non-configurable, non-writable and thus
// cannot change anymore.
Node* value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->Constant(it.GetDataValue());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// Check if the {receiver} is a known constant with a copy-on-write
// backing store, and whether {index} is within the appropriate
// bounds. In that case we can constant-fold the access and only
// check that the {elements} didn't change. This is sufficient as
// the backing store of a copy-on-write JSArray is defensively
// copied whenever the length or the elements (might) change.
//
// What's interesting here is that we don't need to map check the
// {receiver}, since JSArray's will always have their elements in
// the backing store.
if (receiver_ref.IsJSArray()) {
Handle<JSArray> array = receiver_ref.AsJSArray().object();
if (array->elements()->IsCowArray()) {
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
receiver, effect, control);
Handle<FixedArray> array_elements(FixedArray::cast(array->elements()),
isolate());
Node* check =
graph()->NewNode(simplified()->ReferenceEqual(), elements,
jsgraph()->HeapConstant(array_elements));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kCowArrayElementsChanged),
check, effect, control);
Node* value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->Constant(it.GetDataValue());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
uint32_t index_value = static_cast<uint32_t>(mindex.Value());
base::Optional<ObjectRef> element =
receiver_ref.GetOwnConstantElement(index_value);
if (!element.has_value() && receiver_ref.IsJSArray()) {
// We didn't find a constant element, but if the receiver is a cow-array
// we can exploit the fact that any future write to the element will
// replace the whole elements storage.
element = receiver_ref.AsJSArray().GetOwnCowElement(index_value);
if (element.has_value()) {
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
receiver, effect, control);
FixedArrayRef array_elements =
receiver_ref.AsJSArray().elements().AsFixedArray();
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), elements,
jsgraph()->Constant(array_elements));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kCowArrayElementsChanged),
check, effect, control);
}
}
if (element.has_value()) {
Node* value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->Constant(*element);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
}
// For constant Strings we can eagerly strength-reduce the keyed
......@@ -2272,7 +2252,6 @@ JSNativeContextSpecialization::BuildPropertyAccess(
return BuildPropertyTest(effect, control, access_info);
}
UNREACHABLE();
return ValueEffectControl();
}
JSNativeContextSpecialization::ValueEffectControl
......
......@@ -717,13 +717,15 @@ SerializerForBackgroundCompilation::ProcessFeedbackForGlobalAccess(
if (slot.IsInvalid()) return nullptr;
if (environment()->function().feedback_vector.is_null()) return nullptr;
FeedbackSource source(environment()->function().feedback_vector, slot);
if (!broker()->HasFeedback(source)) {
const GlobalAccessFeedback* feedback =
broker()->ProcessFeedbackForGlobalAccess(source);
broker()->SetFeedback(source, feedback);
return feedback;
if (broker()->HasFeedback(source)) {
return broker()->GetGlobalAccessFeedback(source);
}
return nullptr;
const GlobalAccessFeedback* feedback =
broker()->ProcessFeedbackForGlobalAccess(source);
broker()->SetFeedback(source, feedback);
return feedback;
}
void SerializerForBackgroundCompilation::VisitLdaGlobal(
......@@ -818,6 +820,43 @@ void SerializerForBackgroundCompilation::ProcessFeedbackForKeyedPropertyAccess(
}
}
void SerializerForBackgroundCompilation::ProcessKeyedPropertyAccess(
Hints const& receiver, Hints const& key, FeedbackSlot slot,
AccessMode mode) {
ProcessFeedbackForKeyedPropertyAccess(slot, mode);
for (Handle<Object> hint : receiver.constants()) {
ObjectRef receiver_ref(broker(), hint);
// For JSNativeContextSpecialization::ReduceElementAccess.
if (mode == AccessMode::kStore) {
if (receiver_ref.IsJSTypedArray()) {
receiver_ref.AsJSTypedArray().Serialize();
}
}
// For JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant.
if (mode == AccessMode::kLoad || mode == AccessMode::kHas) {
for (Handle<Object> hint : key.constants()) {
ObjectRef key_ref(broker(), hint);
// TODO(neis): Do this for integer-HeapNumbers too?
if (key_ref.IsSmi() && key_ref.AsSmi() >= 0) {
base::Optional<ObjectRef> element =
receiver_ref.GetOwnConstantElement(key_ref.AsSmi(), true);
if (!element.has_value() && receiver_ref.IsJSArray()) {
// We didn't find a constant element, but if the receiver is a
// cow-array we can exploit the fact that any future write to the
// element will replace the whole elements storage.
receiver_ref.AsJSArray().GetOwnCowElement(key_ref.AsSmi(), true);
}
}
}
}
}
environment()->accumulator_hints().Clear();
}
void SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
MapRef const& map, NameRef const& name) {
// For JSNativeContextSpecialization::ReduceNamedAccess.
......@@ -850,9 +889,11 @@ void SerializerForBackgroundCompilation::ProcessFeedbackForNamedPropertyAccess(
void SerializerForBackgroundCompilation::VisitLdaKeyedProperty(
BytecodeArrayIterator* iterator) {
Hints const& key = environment()->accumulator_hints();
Hints const& receiver =
environment()->register_hints(iterator->GetRegisterOperand(0));
FeedbackSlot slot = iterator->GetSlotOperand(1);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kLoad);
environment()->accumulator_hints().Clear();
ProcessKeyedPropertyAccess(receiver, key, slot, AccessMode::kLoad);
}
void SerializerForBackgroundCompilation::ProcessNamedPropertyAccess(
......@@ -907,32 +948,31 @@ void SerializerForBackgroundCompilation::VisitStaNamedProperty(
void SerializerForBackgroundCompilation::VisitTestIn(
BytecodeArrayIterator* iterator) {
Hints const& receiver = environment()->accumulator_hints();
Hints const& key =
environment()->register_hints(iterator->GetRegisterOperand(0));
FeedbackSlot slot = iterator->GetSlotOperand(1);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kHas);
environment()->accumulator_hints().Clear();
ProcessKeyedPropertyAccess(receiver, key, slot, AccessMode::kHas);
}
void SerializerForBackgroundCompilation::VisitStaKeyedProperty(
BytecodeArrayIterator* iterator) {
const Hints& receiver =
Hints const& receiver =
environment()->register_hints(iterator->GetRegisterOperand(0));
Hints const& key =
environment()->register_hints(iterator->GetRegisterOperand(1));
FeedbackSlot slot = iterator->GetSlotOperand(2);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kStore);
for (Handle<Object> object : receiver.constants()) {
// For JSNativeContextSpecialization::ReduceElementAccess.
if (object->IsJSTypedArray()) JSTypedArrayRef(broker(), object).Serialize();
}
environment()->accumulator_hints().Clear();
ProcessKeyedPropertyAccess(receiver, key, slot, AccessMode::kStore);
}
void SerializerForBackgroundCompilation::VisitStaInArrayLiteral(
BytecodeArrayIterator* iterator) {
Hints const& receiver =
environment()->register_hints(iterator->GetRegisterOperand(0));
Hints const& key =
environment()->register_hints(iterator->GetRegisterOperand(1));
FeedbackSlot slot = iterator->GetSlotOperand(2);
ProcessFeedbackForKeyedPropertyAccess(slot, AccessMode::kStoreInLiteral);
environment()->accumulator_hints().Clear();
ProcessKeyedPropertyAccess(receiver, key, slot, AccessMode::kStoreInLiteral);
}
#define DEFINE_CLEAR_ENVIRONMENT(name, ...) \
......
......@@ -251,6 +251,8 @@ class SerializerForBackgroundCompilation {
void ProcessJump(interpreter::BytecodeArrayIterator* iterator);
void MergeAfterJump(interpreter::BytecodeArrayIterator* iterator);
void ProcessKeyedPropertyAccess(Hints const& receiver, Hints const& key,
FeedbackSlot slot, AccessMode mode);
void ProcessNamedPropertyAccess(interpreter::BytecodeArrayIterator* iterator,
AccessMode mode);
void ProcessNamedPropertyAccess(Hints const& receiver, NameRef const& name,
......
......@@ -303,16 +303,15 @@ class JSObject : public JSReceiver {
// corresponds to a set of object representations of elements that
// have something in common.
//
// In the fast mode elements is a FixedArray and so each element can
// be quickly accessed. This fact is used in the generated code. The
// elements array can have one of three maps in this mode:
// fixed_array_map, sloppy_arguments_elements_map or
// fixed_cow_array_map (for copy-on-write arrays). In the latter case
// the elements array may be shared by a few objects and so before
// writing to any element the array must be copied. Use
// In the fast mode elements is a FixedArray and so each element can be
// quickly accessed. The elements array can have one of several maps in this
// mode: fixed_array_map, fixed_double_array_map,
// sloppy_arguments_elements_map or fixed_cow_array_map (for copy-on-write
// arrays). In the latter case the elements array may be shared by a few
// objects and so before writing to any element the array must be copied. Use
// EnsureWritableFastElements in this case.
//
// In the slow mode the elements is either a NumberDictionary, a
// In the slow mode the elements is either a NumberDictionary or a
// FixedArray parameter map for a (sloppy) arguments object.
DECL_ACCESSORS(elements, FixedArrayBase)
inline void initialize_elements();
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
var s = "hello";
function foo() {
return s[4];
}
assertTrue("o" === foo());
assertTrue("o" === foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue("o" === foo());
function bar() {
return s[5];
}
assertSame(undefined, bar());
assertSame(undefined, bar());
%OptimizeFunctionOnNextCall(bar);
assertSame(undefined, bar());
......@@ -1035,4 +1035,10 @@
'*': [SKIP],
}], # variant == jitless and not embedded_builtins
##############################################################################
['variant == future', {
# crbug.com/v8/9094
'compiler/constant-fold-cow-array': [SKIP],
}], # variant == future
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment