[inspector] Speed up DebugPropertyIterator.

This unblocks https://crrev.com/c/3099011 by speeding up the case for
the DebugPropertyIterator where only non-indexed properties (for large
arrays or typed arrays) are requested. Previously we'd walk through all
properties - including all indexed properties - and only filter out the
indexed properties in the end in `ValueMirror::getProperties()`.

Bug: chromium:1199701, chromium:1162229
Change-Id: I2555e3129fef29da347314eee400ea97ebf5e5b7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3114135
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Kim-Anh Tran <kimanh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76796}
This commit is contained in:
Benedikt Meurer 2021-09-13 13:49:04 +02:00 committed by V8 LUCI CQ
parent 210987a552
commit de46367d46
6 changed files with 108 additions and 58 deletions

View File

@ -1267,7 +1267,7 @@ MaybeLocal<Message> GetMessageFromPromise(Local<Promise> p) {
} }
std::unique_ptr<PropertyIterator> PropertyIterator::Create( std::unique_ptr<PropertyIterator> PropertyIterator::Create(
Local<Context> context, Local<Object> object) { Local<Context> context, Local<Object> object, bool skip_indices) {
internal::Isolate* isolate = internal::Isolate* isolate =
reinterpret_cast<i::Isolate*>(object->GetIsolate()); reinterpret_cast<i::Isolate*>(object->GetIsolate());
if (IsExecutionTerminatingCheck(isolate)) { if (IsExecutionTerminatingCheck(isolate)) {
@ -1275,8 +1275,8 @@ std::unique_ptr<PropertyIterator> PropertyIterator::Create(
} }
CallDepthScope<false> call_depth_scope(isolate, context); CallDepthScope<false> call_depth_scope(isolate, context);
auto result = auto result = i::DebugPropertyIterator::Create(
i::DebugPropertyIterator::Create(isolate, Utils::OpenHandle(*object)); isolate, Utils::OpenHandle(*object), skip_indices);
if (!result) { if (!result) {
DCHECK(isolate->has_pending_exception()); DCHECK(isolate->has_pending_exception());
call_depth_scope.Escape(); call_depth_scope.Escape();

View File

@ -621,7 +621,8 @@ class V8_EXPORT_PRIVATE PropertyIterator {
// Creating a PropertyIterator can potentially throw an exception. // Creating a PropertyIterator can potentially throw an exception.
// The returned std::unique_ptr is empty iff that happens. // The returned std::unique_ptr is empty iff that happens.
V8_WARN_UNUSED_RESULT static std::unique_ptr<PropertyIterator> Create( V8_WARN_UNUSED_RESULT static std::unique_ptr<PropertyIterator> Create(
v8::Local<v8::Context> context, v8::Local<v8::Object> object); v8::Local<v8::Context> context, v8::Local<v8::Object> object,
bool skip_indices = false);
virtual ~PropertyIterator() = default; virtual ~PropertyIterator() = default;

View File

@ -15,15 +15,14 @@ namespace v8 {
namespace internal { namespace internal {
std::unique_ptr<DebugPropertyIterator> DebugPropertyIterator::Create( std::unique_ptr<DebugPropertyIterator> DebugPropertyIterator::Create(
Isolate* isolate, Handle<JSReceiver> receiver) { Isolate* isolate, Handle<JSReceiver> receiver, bool skip_indices) {
// Can't use std::make_unique as Ctor is private. // Can't use std::make_unique as Ctor is private.
auto iterator = std::unique_ptr<DebugPropertyIterator>( auto iterator = std::unique_ptr<DebugPropertyIterator>(
new DebugPropertyIterator(isolate, receiver)); new DebugPropertyIterator(isolate, receiver, skip_indices));
if (receiver->IsJSProxy()) { if (receiver->IsJSProxy()) {
iterator->AdvanceToPrototype(); iterator->AdvanceToPrototype();
} }
if (iterator->Done()) return iterator;
if (!iterator->FillKeysForCurrentPrototypeAndStage()) return nullptr; if (!iterator->FillKeysForCurrentPrototypeAndStage()) return nullptr;
if (iterator->should_move_to_next_stage() && !iterator->AdvanceInternal()) { if (iterator->should_move_to_next_stage() && !iterator->AdvanceInternal()) {
@ -34,10 +33,15 @@ std::unique_ptr<DebugPropertyIterator> DebugPropertyIterator::Create(
} }
DebugPropertyIterator::DebugPropertyIterator(Isolate* isolate, DebugPropertyIterator::DebugPropertyIterator(Isolate* isolate,
Handle<JSReceiver> receiver) Handle<JSReceiver> receiver,
bool skip_indices)
: isolate_(isolate), : isolate_(isolate),
prototype_iterator_(isolate, receiver, kStartAtReceiver, prototype_iterator_(isolate, receiver, kStartAtReceiver,
PrototypeIterator::END_AT_NULL) {} PrototypeIterator::END_AT_NULL),
skip_indices_(skip_indices),
current_key_index_(0),
current_keys_(isolate_->factory()->empty_fixed_array()),
current_keys_length_(0) {}
bool DebugPropertyIterator::Done() const { return is_done_; } bool DebugPropertyIterator::Done() const { return is_done_; }
@ -54,13 +58,13 @@ bool DebugPropertyIterator::AdvanceInternal() {
calculated_native_accessor_flags_ = false; calculated_native_accessor_flags_ = false;
while (should_move_to_next_stage()) { while (should_move_to_next_stage()) {
switch (stage_) { switch (stage_) {
case Stage::kExoticIndices: case kExoticIndices:
stage_ = Stage::kEnumerableStrings; stage_ = kEnumerableStrings;
break; break;
case Stage::kEnumerableStrings: case kEnumerableStrings:
stage_ = Stage::kAllProperties; stage_ = kAllProperties;
break; break;
case Stage::kAllProperties: case kAllProperties:
AdvanceToPrototype(); AdvanceToPrototype();
break; break;
} }
@ -70,20 +74,17 @@ bool DebugPropertyIterator::AdvanceInternal() {
} }
bool DebugPropertyIterator::is_native_accessor() { bool DebugPropertyIterator::is_native_accessor() {
if (stage_ == kExoticIndices) return false;
CalculateNativeAccessorFlags(); CalculateNativeAccessorFlags();
return native_accessor_flags_; return native_accessor_flags_;
} }
bool DebugPropertyIterator::has_native_getter() { bool DebugPropertyIterator::has_native_getter() {
if (stage_ == kExoticIndices) return false;
CalculateNativeAccessorFlags(); CalculateNativeAccessorFlags();
return native_accessor_flags_ & return native_accessor_flags_ &
static_cast<int>(debug::NativeAccessorType::HasGetter); static_cast<int>(debug::NativeAccessorType::HasGetter);
} }
bool DebugPropertyIterator::has_native_setter() { bool DebugPropertyIterator::has_native_setter() {
if (stage_ == kExoticIndices) return false;
CalculateNativeAccessorFlags(); CalculateNativeAccessorFlags();
return native_accessor_flags_ & return native_accessor_flags_ &
static_cast<int>(debug::NativeAccessorType::HasSetter); static_cast<int>(debug::NativeAccessorType::HasSetter);
@ -95,7 +96,7 @@ Handle<Name> DebugPropertyIterator::raw_name() const {
return isolate_->factory()->SizeToString(current_key_index_); return isolate_->factory()->SizeToString(current_key_index_);
} else { } else {
return Handle<Name>::cast(FixedArray::get( return Handle<Name>::cast(FixedArray::get(
*keys_, static_cast<int>(current_key_index_), isolate_)); *current_keys_, static_cast<int>(current_key_index_), isolate_));
} }
} }
@ -140,42 +141,38 @@ bool DebugPropertyIterator::is_own() { return is_own_; }
bool DebugPropertyIterator::is_array_index() { bool DebugPropertyIterator::is_array_index() {
if (stage_ == kExoticIndices) return true; if (stage_ == kExoticIndices) return true;
uint32_t index = 0; PropertyKey key(isolate_, raw_name());
return raw_name()->AsArrayIndex(&index); return key.is_element();
} }
bool DebugPropertyIterator::FillKeysForCurrentPrototypeAndStage() { bool DebugPropertyIterator::FillKeysForCurrentPrototypeAndStage() {
current_key_index_ = 0; current_key_index_ = 0;
exotic_length_ = 0; current_keys_ = isolate_->factory()->empty_fixed_array();
keys_ = Handle<FixedArray>::null(); current_keys_length_ = 0;
if (is_done_) return true; if (is_done_) return true;
Handle<JSReceiver> receiver = Handle<JSReceiver> receiver =
PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_); PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_);
bool has_exotic_indices = receiver->IsJSTypedArray();
if (stage_ == kExoticIndices) { if (stage_ == kExoticIndices) {
if (!has_exotic_indices) return true; if (skip_indices_ || !receiver->IsJSTypedArray()) return true;
Handle<JSTypedArray> typed_array = Handle<JSTypedArray>::cast(receiver); Handle<JSTypedArray> typed_array = Handle<JSTypedArray>::cast(receiver);
exotic_length_ = typed_array->WasDetached() ? 0 : typed_array->length(); current_keys_length_ =
typed_array->WasDetached() ? 0 : typed_array->length();
return true; return true;
} }
bool skip_indices = has_exotic_indices;
PropertyFilter filter = PropertyFilter filter =
stage_ == kEnumerableStrings ? ENUMERABLE_STRINGS : ALL_PROPERTIES; stage_ == kEnumerableStrings ? ENUMERABLE_STRINGS : ALL_PROPERTIES;
if (!KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly, filter, if (KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly, filter,
GetKeysConversion::kConvertToString, false, GetKeysConversion::kConvertToString, false,
skip_indices) skip_indices_ || receiver->IsJSTypedArray())
.ToHandle(&keys_)) { .ToHandle(&current_keys_)) {
keys_ = Handle<FixedArray>::null(); current_keys_length_ = current_keys_->length();
return false; return true;
} }
return true; return false;
} }
bool DebugPropertyIterator::should_move_to_next_stage() const { bool DebugPropertyIterator::should_move_to_next_stage() const {
if (is_done_) return false; return !is_done_ && current_key_index_ >= current_keys_length_;
if (stage_ == kExoticIndices) return current_key_index_ >= exotic_length_;
return keys_.is_null() ||
current_key_index_ >= static_cast<size_t>(keys_->length());
} }
namespace { namespace {
@ -210,10 +207,14 @@ base::Flags<debug::NativeAccessorType, int> GetNativeAccessorDescriptorInternal(
void DebugPropertyIterator::CalculateNativeAccessorFlags() { void DebugPropertyIterator::CalculateNativeAccessorFlags() {
if (calculated_native_accessor_flags_) return; if (calculated_native_accessor_flags_) return;
Handle<JSReceiver> receiver = if (stage_ == kExoticIndices) {
PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_); native_accessor_flags_ = 0;
native_accessor_flags_ = } else {
GetNativeAccessorDescriptorInternal(receiver, raw_name()); Handle<JSReceiver> receiver =
PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_);
native_accessor_flags_ =
GetNativeAccessorDescriptorInternal(receiver, raw_name());
}
calculated_native_accessor_flags_ = true; calculated_native_accessor_flags_ = true;
} }
} // namespace internal } // namespace internal

View File

@ -24,7 +24,7 @@ class JSReceiver;
class DebugPropertyIterator final : public debug::PropertyIterator { class DebugPropertyIterator final : public debug::PropertyIterator {
public: public:
V8_WARN_UNUSED_RESULT static std::unique_ptr<DebugPropertyIterator> Create( V8_WARN_UNUSED_RESULT static std::unique_ptr<DebugPropertyIterator> Create(
Isolate* isolate, Handle<JSReceiver> receiver); Isolate* isolate, Handle<JSReceiver> receiver, bool skip_indices);
~DebugPropertyIterator() override = default; ~DebugPropertyIterator() override = default;
DebugPropertyIterator(const DebugPropertyIterator&) = delete; DebugPropertyIterator(const DebugPropertyIterator&) = delete;
DebugPropertyIterator& operator=(const DebugPropertyIterator&) = delete; DebugPropertyIterator& operator=(const DebugPropertyIterator&) = delete;
@ -43,7 +43,8 @@ class DebugPropertyIterator final : public debug::PropertyIterator {
bool is_array_index() override; bool is_array_index() override;
private: private:
DebugPropertyIterator(Isolate* isolate, Handle<JSReceiver> receiver); DebugPropertyIterator(Isolate* isolate, Handle<JSReceiver> receiver,
bool skip_indices);
V8_WARN_UNUSED_RESULT bool FillKeysForCurrentPrototypeAndStage(); V8_WARN_UNUSED_RESULT bool FillKeysForCurrentPrototypeAndStage();
bool should_move_to_next_stage() const; bool should_move_to_next_stage() const;
@ -54,12 +55,16 @@ class DebugPropertyIterator final : public debug::PropertyIterator {
Isolate* isolate_; Isolate* isolate_;
PrototypeIterator prototype_iterator_; PrototypeIterator prototype_iterator_;
enum Stage { kExoticIndices = 0, kEnumerableStrings = 1, kAllProperties = 2 }; enum {
Stage stage_ = kExoticIndices; kExoticIndices = 0,
kEnumerableStrings = 1,
kAllProperties = 2
} stage_ = kExoticIndices;
bool skip_indices_;
size_t current_key_index_ = 0; size_t current_key_index_;
Handle<FixedArray> keys_; Handle<FixedArray> current_keys_;
size_t exotic_length_ = 0; size_t current_keys_length_;
bool calculated_native_accessor_flags_ = false; bool calculated_native_accessor_flags_ = false;
int native_accessor_flags_ = 0; int native_accessor_flags_ = 0;

View File

@ -1211,7 +1211,8 @@ bool ValueMirror::getProperties(v8::Local<v8::Context> context,
} }
} }
auto iterator = v8::debug::PropertyIterator::Create(context, object); auto iterator = v8::debug::PropertyIterator::Create(context, object,
nonIndexedPropertiesOnly);
if (!iterator) { if (!iterator) {
CHECK(tryCatch.HasCaught()); CHECK(tryCatch.HasCaught());
return false; return false;
@ -1219,14 +1220,6 @@ bool ValueMirror::getProperties(v8::Local<v8::Context> context,
while (!iterator->Done()) { while (!iterator->Done()) {
bool isOwn = iterator->is_own(); bool isOwn = iterator->is_own();
if (!isOwn && ownProperties) break; if (!isOwn && ownProperties) break;
bool isIndex = iterator->is_array_index();
if (isIndex && nonIndexedPropertiesOnly) {
if (!iterator->Advance().FromMaybe(false)) {
CHECK(tryCatch.HasCaught());
return false;
}
continue;
}
v8::Local<v8::Name> v8Name = iterator->name(); v8::Local<v8::Name> v8Name = iterator->name();
v8::Maybe<bool> result = set->Has(context, v8Name); v8::Maybe<bool> result = set->Has(context, v8Name);
if (result.IsNothing()) return false; if (result.IsNothing()) return false;
@ -1324,7 +1317,7 @@ bool ValueMirror::getProperties(v8::Local<v8::Context> context,
configurable, configurable,
enumerable, enumerable,
isOwn, isOwn,
isIndex, iterator->is_array_index(),
isAccessorProperty && valueMirror, isAccessorProperty && valueMirror,
std::move(valueMirror), std::move(valueMirror),
std::move(getterMirror), std::move(getterMirror),

View File

@ -98,6 +98,56 @@ TEST_F(DebugPropertyIteratorTest, DoestWalksPrototypeChainIfInaccesible) {
ASSERT_TRUE(iterator->Done()); ASSERT_TRUE(iterator->Done());
} }
TEST_F(DebugPropertyIteratorTest, SkipsIndicesOnArrays) {
TryCatch try_catch(isolate());
Local<Value> elements[2] = {
Number::New(isolate(), 21),
Number::New(isolate(), 42),
};
auto array = Array::New(isolate(), elements, arraysize(elements));
auto iterator = PropertyIterator::Create(context(), array, true);
while (!iterator->Done()) {
ASSERT_FALSE(iterator->is_array_index());
ASSERT_TRUE(iterator->Advance().FromMaybe(false));
}
}
TEST_F(DebugPropertyIteratorTest, SkipsIndicesOnObjects) {
TryCatch try_catch(isolate());
Local<Name> names[2] = {
String::NewFromUtf8Literal(isolate(), "42"),
String::NewFromUtf8Literal(isolate(), "x"),
};
Local<Value> values[arraysize(names)] = {
Number::New(isolate(), 42),
Number::New(isolate(), 21),
};
Local<Object> object =
Object::New(isolate(), Null(isolate()), names, values, arraysize(names));
auto iterator = PropertyIterator::Create(context(), object, true);
while (!iterator->Done()) {
ASSERT_FALSE(iterator->is_array_index());
ASSERT_TRUE(iterator->Advance().FromMaybe(false));
}
}
TEST_F(DebugPropertyIteratorTest, SkipsIndicesOnTypedArrays) {
TryCatch try_catch(isolate());
auto buffer = ArrayBuffer::New(isolate(), 1024 * 1024);
auto array = Uint8Array::New(buffer, 0, 1024 * 1024);
auto iterator = PropertyIterator::Create(context(), array, true);
while (!iterator->Done()) {
ASSERT_FALSE(iterator->is_array_index());
ASSERT_TRUE(iterator->Advance().FromMaybe(false));
}
}
} // namespace } // namespace
} // namespace debug } // namespace debug
} // namespace v8 } // namespace v8