Commit 92edf9a1 authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[runtime] Optimise paired instance type checks

Clang doesn't optimise over handle derefs. Change the ValueSerializer
and the JsonStringifier to use InstanceType directly for checks.
This CL squeezes another 1.5% of JSON.stringify in local benchmarks.

Drive-by-fix:
- Avoid a few more derefs in the JsonStringifier
- Make JsonStringifier::SerializeJSArray a bit more readable

Change-Id: I37626a6d92a8d9275611a4e6d1d908f2e0c6d43b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3247637
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77697}
parent a985da39
......@@ -102,6 +102,7 @@ class JsonStringifier {
V8_INLINE static bool DoNotEscape(Char c);
V8_INLINE void NewLine();
V8_NOINLINE void NewLineOutline();
V8_INLINE void Indent() { indent_++; }
V8_INLINE void Unindent() { indent_--; }
V8_INLINE void Separator(bool first);
......@@ -383,8 +384,10 @@ JsonStringifier::Result JsonStringifier::StackPush(Handle<Object> object,
{
DisallowGarbageCollection no_gc;
for (size_t i = 0; i < stack_.size(); ++i) {
if (*stack_[i].second == *object) {
Object raw_obj = *object;
size_t size = stack_.size();
for (size_t i = 0; i < size; ++i) {
if (*stack_[i].second == raw_obj) {
AllowGarbageCollection allow_to_return_error;
Handle<String> circle_description =
ConstructCircularStructureErrorMessage(key, i);
......@@ -522,9 +525,14 @@ JsonStringifier::Result JsonStringifier::Serialize_(Handle<Object> object,
isolate_->stack_guard()->HandleInterrupts().IsException(isolate_)) {
return EXCEPTION;
}
if (object->IsJSReceiver() || object->IsBigInt()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate_, object, ApplyToJsonFunction(object, key), EXCEPTION);
if (!object->IsSmi()) {
InstanceType instance_type =
HeapObject::cast(*object).map().instance_type();
if (InstanceTypeChecker::IsJSReceiver(instance_type) ||
InstanceTypeChecker::IsBigInt(instance_type)) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate_, object, ApplyToJsonFunction(object, key), EXCEPTION);
}
}
if (!replacer_function_.is_null()) {
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
......@@ -537,7 +545,8 @@ JsonStringifier::Result JsonStringifier::Serialize_(Handle<Object> object,
return SerializeSmi(Smi::cast(*object));
}
switch (HeapObject::cast(*object).map().instance_type()) {
InstanceType instance_type = HeapObject::cast(*object).map().instance_type();
switch (instance_type) {
case HEAP_NUMBER_TYPE:
if (deferred_string_key) SerializeDeferredKey(comma, key);
return SerializeHeapNumber(Handle<HeapNumber>::cast(object));
......@@ -572,16 +581,16 @@ JsonStringifier::Result JsonStringifier::Serialize_(Handle<Object> object,
case SYMBOL_TYPE:
return UNCHANGED;
default:
if (object->IsString()) {
if (InstanceTypeChecker::IsString(instance_type)) {
if (deferred_string_key) SerializeDeferredKey(comma, key);
SerializeString(Handle<String>::cast(object));
return SUCCESS;
} else {
DCHECK(object->IsJSReceiver());
if (object->IsCallable()) return UNCHANGED;
if (HeapObject::cast(*object).IsCallable()) return UNCHANGED;
// Go to slow path for global proxy and objects requiring access checks.
if (deferred_string_key) SerializeDeferredKey(comma, key);
if (object->IsJSProxy()) {
if (InstanceTypeChecker::IsJSProxy(instance_type)) {
return SerializeJSProxy(Handle<JSProxy>::cast(object), key);
}
return SerializeJSObject(Handle<JSObject>::cast(object), key);
......@@ -649,45 +658,55 @@ JsonStringifier::Result JsonStringifier::SerializeJSArray(
builder_.AppendCharacter('[');
Indent();
uint32_t i = 0;
if (replacer_function_.is_null()) {
if (replacer_function_.is_null() && length > 0) {
StackLimitCheck interrupt_check(isolate_);
const uint32_t kInterruptLength = 4000;
uint32_t limit = std::min(length, kInterruptLength);
const uint32_t kMaxAllowedFastPackedLength =
std::numeric_limits<uint32_t>::max() - kInterruptLength;
STATIC_ASSERT(FixedArray::kMaxLength < kMaxAllowedFastPackedLength);
switch (object->GetElementsKind()) {
case PACKED_SMI_ELEMENTS: {
Handle<FixedArray> elements(FixedArray::cast(object->elements()),
isolate_);
StackLimitCheck interrupt_check(isolate_);
while (i < length) {
while (true) {
for (; i < limit; i++) {
Separator(i == 0);
SerializeSmi(Smi::cast(elements->get(i)));
}
if (i >= length) break;
DCHECK_LT(limit, kMaxAllowedFastPackedLength);
limit = std::min(length, limit + kInterruptLength);
if (interrupt_check.InterruptRequested() &&
isolate_->stack_guard()->HandleInterrupts().IsException(
isolate_)) {
return EXCEPTION;
}
Separator(i == 0);
SerializeSmi(Smi::cast(elements->get(i)));
i++;
}
break;
}
case PACKED_DOUBLE_ELEMENTS: {
// Empty array is FixedArray but not FixedDoubleArray.
if (length == 0) break;
Handle<FixedDoubleArray> elements(
FixedDoubleArray::cast(object->elements()), isolate_);
StackLimitCheck interrupt_check(isolate_);
while (i < length) {
while (true) {
for (; i < limit; i++) {
Separator(i == 0);
SerializeDouble(elements->get_scalar(i));
}
if (i >= length) break;
DCHECK_LT(limit, kMaxAllowedFastPackedLength);
limit = std::min(length, limit + kInterruptLength);
if (interrupt_check.InterruptRequested() &&
isolate_->stack_guard()->HandleInterrupts().IsException(
isolate_)) {
return EXCEPTION;
}
Separator(i == 0);
SerializeDouble(elements->get_scalar(i));
i++;
}
break;
}
case PACKED_ELEMENTS: {
Handle<Object> old_length(object->length(), isolate_);
while (i < length) {
for (i = 0; i < length; i++) {
if (object->length() != *old_length ||
object->GetElementsKind() != PACKED_ELEMENTS) {
// Fall back to slow path.
......@@ -696,20 +715,15 @@ JsonStringifier::Result JsonStringifier::SerializeJSArray(
Separator(i == 0);
Result result = SerializeElement(
isolate_,
Handle<Object>(FixedArray::cast(object->elements()).get(i),
isolate_),
i);
handle(FixedArray::cast(object->elements()).get(i), isolate_), i);
if (result == UNCHANGED) {
builder_.AppendCString("null");
} else if (result != SUCCESS) {
return result;
}
i++;
}
break;
}
// The FAST_HOLEY_* cases could be handled in a faster way. They resemble
// the non-holey cases except that a lookup is necessary for holes.
default:
break;
}
......@@ -753,39 +767,43 @@ JsonStringifier::Result JsonStringifier::SerializeArrayLikeSlow(
return SUCCESS;
}
namespace {
V8_INLINE bool CanFastSerializeJSObject(JSObject raw_object, Isolate* isolate) {
DisallowGarbageCollection no_gc;
if (raw_object.map().IsCustomElementsReceiverMap()) return false;
if (!raw_object.HasFastProperties()) return false;
auto roots = ReadOnlyRoots(isolate);
return raw_object.elements() == roots.empty_fixed_array() ||
raw_object.elements() == roots.empty_slow_element_dictionary();
}
} // namespace
JsonStringifier::Result JsonStringifier::SerializeJSObject(
Handle<JSObject> object, Handle<Object> key) {
HandleScope handle_scope(isolate_);
Result stack_push = StackPush(object, key);
if (stack_push != SUCCESS) return stack_push;
if (property_list_.is_null() &&
!object->map().IsCustomElementsReceiverMap() &&
object->HasFastProperties() &&
(object->elements() == ReadOnlyRoots(isolate_).empty_fixed_array() ||
object->elements() ==
ReadOnlyRoots(isolate_).empty_slow_element_dictionary())) {
if (property_list_.is_null() && CanFastSerializeJSObject(*object, isolate_)) {
DCHECK(!object->IsJSGlobalProxy());
DCHECK(!object->HasIndexedInterceptor());
DCHECK(!object->HasNamedInterceptor());
Handle<Map> map(object->map(), isolate_);
Map map = object->map();
builder_.AppendCharacter('{');
Indent();
bool comma = false;
for (InternalIndex i : map->IterateOwnDescriptors()) {
Handle<Name> name(map->instance_descriptors(isolate_).GetKey(i),
isolate_);
for (InternalIndex i : map.IterateOwnDescriptors()) {
Handle<Name> name(map.instance_descriptors(isolate_).GetKey(i), isolate_);
// TODO(rossberg): Should this throw?
if (!name->IsString()) continue;
Handle<String> key_name = Handle<String>::cast(name);
PropertyDetails details =
map->instance_descriptors(isolate_).GetDetails(i);
map.instance_descriptors(isolate_).GetDetails(i);
if (details.IsDontEnum()) continue;
Handle<Object> property;
if (details.location() == PropertyLocation::kField &&
*map == object->map()) {
map == object->map()) {
DCHECK_EQ(kData, details.kind());
FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
FieldIndex field_index = FieldIndex::ForDescriptor(map, i);
property = JSObject::FastPropertyAt(object, details.representation(),
field_index);
} else {
......@@ -1011,6 +1029,10 @@ bool JsonStringifier::DoNotEscape(uint16_t c) {
void JsonStringifier::NewLine() {
if (gap_ == nullptr) return;
NewLineOutline();
}
void JsonStringifier::NewLineOutline() {
builder_.AppendCharacter('\n');
for (int i = 0; i < indent_; i++) builder_.AppendCString(gap_);
}
......
......@@ -988,12 +988,12 @@ class V8_EXPORT_PRIVATE FlatStringReader : public Relocatable {
inline base::uc32 Get(int index) const;
template <typename Char>
inline Char Get(int index) const;
int length() { return length_; }
int length() const { return length_; }
private:
Handle<String> str_;
bool is_one_byte_;
int length_;
int const length_;
const void* start_;
};
......
......@@ -413,7 +413,8 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
}
DCHECK(object->IsHeapObject());
switch (HeapObject::cast(*object).map().instance_type()) {
InstanceType instance_type = HeapObject::cast(*object).map().instance_type();
switch (instance_type) {
case ODDBALL_TYPE:
WriteOddball(Oddball::cast(*object));
return ThrowIfOutOfMemory();
......@@ -433,7 +434,7 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
Handle<JSArrayBufferView> view = Handle<JSArrayBufferView>::cast(object);
if (!id_map_.Find(view) && !treat_array_buffer_views_as_host_objects_) {
Handle<JSArrayBuffer> buffer(
view->IsJSTypedArray()
InstanceTypeChecker::IsJSTypedArray(instance_type)
? Handle<JSTypedArray>::cast(view)->GetBuffer()
: handle(JSArrayBuffer::cast(view->buffer()), isolate_));
if (!WriteJSReceiver(buffer).FromMaybe(false)) return Nothing<bool>();
......@@ -441,10 +442,10 @@ Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
return WriteJSReceiver(view);
}
default:
if (object->IsString()) {
if (InstanceTypeChecker::IsString(instance_type)) {
WriteString(Handle<String>::cast(object));
return ThrowIfOutOfMemory();
} else if (object->IsJSReceiver()) {
} else if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
return WriteJSReceiver(Handle<JSReceiver>::cast(object));
} else {
ThrowDataCloneError(MessageTemplate::kDataCloneError, object);
......@@ -687,20 +688,20 @@ Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
// structure of the elements changing.
switch (array->GetElementsKind()) {
case PACKED_SMI_ELEMENTS: {
Handle<FixedArray> elements(FixedArray::cast(array->elements()),
isolate_);
for (; i < length; i++) WriteSmi(Smi::cast(elements->get(i)));
DisallowGarbageCollection no_gc;
FixedArray elements = FixedArray::cast(array->elements());
for (i = 0; i < length; i++) WriteSmi(Smi::cast(elements.get(i)));
break;
}
case PACKED_DOUBLE_ELEMENTS: {
// Elements are empty_fixed_array, not a FixedDoubleArray, if the array
// is empty. No elements to encode in this case anyhow.
if (length == 0) break;
Handle<FixedDoubleArray> elements(
FixedDoubleArray::cast(array->elements()), isolate_);
for (; i < length; i++) {
DisallowGarbageCollection no_gc;
FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
for (i = 0; i < length; i++) {
WriteTag(SerializationTag::kDouble);
WriteDouble(elements->get_scalar(i));
WriteDouble(elements.get_scalar(i));
}
break;
}
......
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