[dataview] Introduce JSDataView::data_pointer field.

This is a preparation for doing a similar change to JSTypedArrays to
be able to finally access huge ArrayBuffers with TypedArrays. This CL
itself improves the performance of DataViews, sometimes to be even
faster than TypedArrays now. On the test case[1] we go from

  testDataViewGetUint8: 711 ms.
  testUint8Array: 654 ms.
  testDataViewGetUint16: 801 ms.
  testUint16Array: 649 ms.
  testDataViewGetInt32: 699 ms.
  testInt32Array: 648 ms.
  testDataViewGetFloat64: 701 ms.
  testFloat64Array: 650 ms.

to

  testDataViewGetUint8: 622 ms.
  testUint8Array: 656 ms.
  testDataViewGetUint16: 634 ms.
  testUint16Array: 656 ms.
  testDataViewGetInt32: 629 ms.
  testInt32Array: 655 ms.
  testDataViewGetFloat64: 631 ms.
  testFloat64Array: 661 ms.

so the performance improves by up to **20%**.

[1] https://github.com/bmeurer/js-micro-benchmarks/blob/master/bench-dataview.js

Tbr: ulan@chromium.org
Bug: chromium:225811, v8:4153, v8:8383
Change-Id: Ie4409e2fe96e5085ddcf5eb3f24f3cacfb3afe02
Cq-Include-Trybots: luci.chromium.try:linux-rel,win7-rel
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1601144
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61464}
This commit is contained in:
Benedikt Meurer 2019-05-14 07:37:09 +02:00 committed by Commit Bot
parent e4e0d1c6d1
commit 4ef714a619
14 changed files with 137 additions and 72 deletions

View File

@ -101,6 +101,8 @@ BUILTIN(DataViewConstructor) {
// 13. Set O's [[ByteOffset]] internal slot to offset.
Handle<JSDataView>::cast(result)->set_byte_offset(view_byte_offset);
Handle<JSDataView>::cast(result)->set_data_pointer(
static_cast<uint8_t*>(array_buffer->backing_store()) + view_byte_offset);
// 14. Return O.
return *result;

View File

@ -427,6 +427,15 @@ FieldAccess AccessBuilder::ForJSTypedArrayLength() {
return access;
}
// static
FieldAccess AccessBuilder::ForJSDataViewDataPointer() {
FieldAccess access = {kTaggedBase, JSDataView::kDataPointerOffset,
MaybeHandle<Name>(), MaybeHandle<Map>(),
Type::OtherInternal(), MachineType::Pointer(),
kNoWriteBarrier};
return access;
}
// static
FieldAccess AccessBuilder::ForJSDateValue() {
FieldAccess access = {kTaggedBase,

View File

@ -139,6 +139,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to JSTypedArray::length() field.
static FieldAccess ForJSTypedArrayLength();
// Provides access to JSDataView::data_pointer() field.
static FieldAccess ForJSDataViewDataPointer();
// Provides access to JSDate::value() field.
static FieldAccess ForJSDateValue();

View File

@ -4701,23 +4701,20 @@ Node* EffectControlLinearizer::BuildReverseBytes(ExternalArrayType type,
Node* EffectControlLinearizer::LowerLoadDataViewElement(Node* node) {
ExternalArrayType element_type = ExternalArrayTypeOf(node->op());
Node* buffer = node->InputAt(0);
Node* object = node->InputAt(0);
Node* storage = node->InputAt(1);
Node* byte_offset = node->InputAt(2);
Node* index = node->InputAt(3);
Node* is_little_endian = node->InputAt(4);
Node* index = node->InputAt(2);
Node* is_little_endian = node->InputAt(3);
// We need to keep the {buffer} alive so that the GC will not release the
// ArrayBuffer (if there's any) as long as we are still operating on it.
__ Retain(buffer);
// Compute the effective offset.
Node* offset = __ IntAdd(byte_offset, index);
// We need to keep the {object} (either the JSArrayBuffer or the JSDataView)
// alive so that the GC will not release the JSArrayBuffer (if there's any)
// as long as we are still operating on it.
__ Retain(object);
MachineType const machine_type =
AccessBuilder::ForTypedArrayElement(element_type, true).machine_type;
Node* value = __ LoadUnaligned(machine_type, storage, offset);
Node* value = __ LoadUnaligned(machine_type, storage, index);
auto big_endian = __ MakeLabel();
auto done = __ MakeLabel(machine_type.representation());
@ -4746,19 +4743,16 @@ Node* EffectControlLinearizer::LowerLoadDataViewElement(Node* node) {
void EffectControlLinearizer::LowerStoreDataViewElement(Node* node) {
ExternalArrayType element_type = ExternalArrayTypeOf(node->op());
Node* buffer = node->InputAt(0);
Node* object = node->InputAt(0);
Node* storage = node->InputAt(1);
Node* byte_offset = node->InputAt(2);
Node* index = node->InputAt(3);
Node* value = node->InputAt(4);
Node* is_little_endian = node->InputAt(5);
Node* index = node->InputAt(2);
Node* value = node->InputAt(3);
Node* is_little_endian = node->InputAt(4);
// We need to keep the {buffer} alive so that the GC will not release the
// ArrayBuffer (if there's any) as long as we are still operating on it.
__ Retain(buffer);
// Compute the effective offset.
Node* offset = __ IntAdd(byte_offset, index);
// We need to keep the {object} (either the JSArrayBuffer or the JSDataView)
// alive so that the GC will not release the JSArrayBuffer (if there's any)
// as long as we are still operating on it.
__ Retain(object);
MachineType const machine_type =
AccessBuilder::ForTypedArrayElement(element_type, true).machine_type;
@ -4785,7 +4779,7 @@ void EffectControlLinearizer::LowerStoreDataViewElement(Node* node) {
}
__ Bind(&done);
__ StoreUnaligned(machine_type.representation(), storage, offset,
__ StoreUnaligned(machine_type.representation(), storage, index,
done.PhiAt(0));
}

View File

@ -6513,8 +6513,6 @@ Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
return NoChange();
}
Node* byte_offset;
// Check that the {offset} is within range for the {receiver}.
HeapObjectMatcher m(receiver);
if (m.HasValue()) {
@ -6528,9 +6526,6 @@ Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
jsgraph()->Constant(dataview.byte_length() - (element_size - 1));
offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()),
offset, byte_length, effect, control);
// Load the [[ByteOffset]] from the {dataview}.
byte_offset = jsgraph()->Constant(dataview.byte_offset());
} else {
// We only deal with DataViews here that have Smi [[ByteLength]]s.
Node* byte_length = effect =
@ -6554,12 +6549,6 @@ Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
// Check that the {offset} is within range of the {byte_length}.
offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()),
offset, byte_length, effect, control);
// Also load the [[ByteOffset]] from the {receiver}.
byte_offset = effect =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSArrayBufferViewByteOffset()),
receiver, effect, control);
}
// Coerce {is_little_endian} to boolean.
@ -6574,12 +6563,18 @@ Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
value, effect, control);
}
// Get the underlying buffer and check that it has not been detached.
Node* buffer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()),
receiver, effect, control);
// We need to retain either the {receiver} itself or it's backing
// JSArrayBuffer to make sure that the GC doesn't collect the raw
// memory. We default to {receiver} here, and only use the buffer
// if we anyways have to load it (to reduce register pressure).
Node* buffer_or_receiver = receiver;
if (!dependencies()->DependOnArrayBufferDetachingProtector()) {
// Get the underlying buffer and check that it has not been detached.
Node* buffer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()),
receiver, effect, control);
// Bail out if the {buffer} was detached.
Node* buffer_bit_field = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()),
@ -6594,27 +6589,29 @@ Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached,
p.feedback()),
check, effect, control);
// We can reduce register pressure by holding on to the {buffer}
// now to retain the backing store memory.
buffer_or_receiver = buffer;
}
// Get the buffer's backing store.
Node* backing_store = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferBackingStore()),
buffer, effect, control);
// Load the {receiver}s data pointer.
Node* data_pointer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSDataViewDataPointer()),
receiver, effect, control);
switch (access) {
case DataViewAccess::kGet:
// Perform the load.
value = effect =
graph()->NewNode(simplified()->LoadDataViewElement(element_type),
buffer, backing_store, byte_offset, offset,
is_little_endian, effect, control);
value = effect = graph()->NewNode(
simplified()->LoadDataViewElement(element_type), buffer_or_receiver,
data_pointer, offset, is_little_endian, effect, control);
break;
case DataViewAccess::kSet:
// Perform the store.
effect =
graph()->NewNode(simplified()->StoreDataViewElement(element_type),
buffer, backing_store, byte_offset, offset, value,
is_little_endian, effect, control);
effect = graph()->NewNode(
simplified()->StoreDataViewElement(element_type), buffer_or_receiver,
data_pointer, offset, value, is_little_endian, effect, control);
value = jsgraph()->UndefinedConstant();
break;
}

View File

@ -2905,12 +2905,11 @@ class RepresentationSelector {
case IrOpcode::kLoadDataViewElement: {
MachineRepresentation const rep =
MachineRepresentationFromArrayType(ExternalArrayTypeOf(node->op()));
ProcessInput(node, 0, UseInfo::AnyTagged()); // buffer
ProcessInput(node, 1, UseInfo::Word()); // external pointer
ProcessInput(node, 2, UseInfo::Word()); // byte offset
ProcessInput(node, 3, UseInfo::Word()); // index
ProcessInput(node, 4, UseInfo::Bool()); // little-endian
ProcessRemainingInputs(node, 5);
ProcessInput(node, 0, UseInfo::AnyTagged()); // object
ProcessInput(node, 1, UseInfo::Word()); // base
ProcessInput(node, 2, UseInfo::Word()); // index
ProcessInput(node, 3, UseInfo::Bool()); // little-endian
ProcessRemainingInputs(node, 4);
SetOutput(node, rep);
return;
}
@ -2930,14 +2929,13 @@ class RepresentationSelector {
case IrOpcode::kStoreDataViewElement: {
MachineRepresentation const rep =
MachineRepresentationFromArrayType(ExternalArrayTypeOf(node->op()));
ProcessInput(node, 0, UseInfo::AnyTagged()); // buffer
ProcessInput(node, 1, UseInfo::Word()); // external pointer
ProcessInput(node, 2, UseInfo::Word()); // byte offset
ProcessInput(node, 3, UseInfo::Word()); // index
ProcessInput(node, 4,
ProcessInput(node, 0, UseInfo::AnyTagged()); // object
ProcessInput(node, 1, UseInfo::Word()); // base
ProcessInput(node, 2, UseInfo::Word()); // index
ProcessInput(node, 3,
TruncatingUseInfoFromRepresentation(rep)); // value
ProcessInput(node, 5, UseInfo::Bool()); // little-endian
ProcessRemainingInputs(node, 6);
ProcessInput(node, 4, UseInfo::Bool()); // little-endian
ProcessRemainingInputs(node, 5);
SetOutput(node, MachineRepresentation::kNone);
return;
}

View File

@ -1685,8 +1685,8 @@ SPECULATIVE_NUMBER_BINOP_LIST(SPECULATIVE_NUMBER_BINOP)
V(StoreElement, ElementAccess, Operator::kNoRead, 3, 1, 0) \
V(LoadTypedElement, ExternalArrayType, Operator::kNoWrite, 4, 1, 1) \
V(StoreTypedElement, ExternalArrayType, Operator::kNoRead, 5, 1, 0) \
V(LoadDataViewElement, ExternalArrayType, Operator::kNoWrite, 5, 1, 1) \
V(StoreDataViewElement, ExternalArrayType, Operator::kNoRead, 6, 1, 0)
V(LoadDataViewElement, ExternalArrayType, Operator::kNoWrite, 4, 1, 1) \
V(StoreDataViewElement, ExternalArrayType, Operator::kNoRead, 5, 1, 0)
#define ACCESS(Name, Type, properties, value_input_count, control_input_count, \
output_count) \

View File

@ -828,13 +828,13 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
// load-typed-element buffer, [base + external + index]
const Operator* LoadTypedElement(ExternalArrayType const&);
// load-data-view-element buffer, [base + byte_offset + index]
// load-data-view-element object, [base + index]
const Operator* LoadDataViewElement(ExternalArrayType const&);
// store-typed-element buffer, [base + external + index], value
const Operator* StoreTypedElement(ExternalArrayType const&);
// store-data-view-element buffer, [base + byte_offset + index], value
// store-data-view-element object, [base + index], value
const Operator* StoreDataViewElement(ExternalArrayType const&);
// Abort (for terminating execution on internal error).

View File

@ -3351,6 +3351,8 @@ Handle<JSDataView> Factory::NewJSDataView(Handle<JSArrayBuffer> buffer,
isolate());
Handle<JSDataView> obj = Handle<JSDataView>::cast(NewJSObjectFromMap(map));
SetupArrayBufferView(isolate(), obj, buffer, byte_offset, byte_length);
obj->set_data_pointer(static_cast<uint8_t*>(buffer->backing_store()) +
byte_offset);
return obj;
}

View File

@ -328,7 +328,7 @@ class JSArrayBuffer::BodyDescriptor final : public BodyDescriptorBase {
}
};
class JSArrayBufferView::BodyDescriptor final : public BodyDescriptorBase {
class JSTypedArray::BodyDescriptor final : public BodyDescriptorBase {
public:
static bool IsValidSlot(Map map, HeapObject obj, int offset) {
if (offset < kEndOfTaggedFieldsOffset) return true;
@ -339,7 +339,28 @@ class JSArrayBufferView::BodyDescriptor final : public BodyDescriptorBase {
template <typename ObjectVisitor>
static inline void IterateBody(Map map, HeapObject obj, int object_size,
ObjectVisitor* v) {
// JSArrayBufferView contains raw data that the GC does not know about.
// JSTypedArray contains raw data that the GC does not know about.
IteratePointers(obj, kPropertiesOrHashOffset, kEndOfTaggedFieldsOffset, v);
IterateJSObjectBodyImpl(map, obj, kHeaderSize, object_size, v);
}
static inline int SizeOf(Map map, HeapObject object) {
return map->instance_size();
}
};
class JSDataView::BodyDescriptor final : public BodyDescriptorBase {
public:
static bool IsValidSlot(Map map, HeapObject obj, int offset) {
if (offset < kEndOfTaggedFieldsOffset) return true;
if (offset < kHeaderSize) return false;
return IsValidJSObjectSlotImpl(map, obj, offset);
}
template <typename ObjectVisitor>
static inline void IterateBody(Map map, HeapObject obj, int object_size,
ObjectVisitor* v) {
// JSDataView contains raw data that the GC does not know about.
IteratePointers(obj, kPropertiesOrHashOffset, kEndOfTaggedFieldsOffset, v);
IterateJSObjectBodyImpl(map, obj, kHeaderSize, object_size, v);
}

View File

@ -1549,7 +1549,15 @@ void JSTypedArray::JSTypedArrayVerify(Isolate* isolate) {
VerifyPointer(isolate, elements());
}
USE_TORQUE_VERIFIER(JSDataView)
void JSDataView::JSDataViewVerify(Isolate* isolate) {
ClassVerifiersFromDSL::JSDataViewVerify(*this, isolate);
if (!WasDetached()) {
CHECK_EQ(reinterpret_cast<uint8_t*>(
JSArrayBuffer::cast(buffer()).backing_store()) +
byte_offset(),
data_pointer());
}
}
USE_TORQUE_VERIFIER(Foreign)

View File

@ -177,6 +177,16 @@ MaybeHandle<JSTypedArray> JSTypedArray::Validate(Isolate* isolate,
ACCESSORS(JSTypedArray, raw_length, Object, kLengthOffset)
void* JSDataView::data_pointer() const {
intptr_t ptr = READ_INTPTR_FIELD(*this, kDataPointerOffset);
return reinterpret_cast<void*>(ptr);
}
void JSDataView::set_data_pointer(void* value) {
intptr_t ptr = reinterpret_cast<intptr_t>(value);
WRITE_INTPTR_FIELD(*this, kDataPointerOffset, ptr);
}
} // namespace internal
} // namespace v8

View File

@ -172,8 +172,6 @@ class JSArrayBufferView : public JSObject {
JS_ARRAY_BUFFER_VIEW_FIELDS)
#undef JS_ARRAY_BUFFER_VIEW_FIELDS
class BodyDescriptor;
OBJECT_CONSTRUCTORS(JSArrayBufferView, JSObject);
};
@ -220,6 +218,8 @@ class JSTypedArray : public JSArrayBufferView {
kHeaderSize +
v8::ArrayBufferView::kEmbedderFieldCount * kEmbedderDataSlotSize;
class BodyDescriptor;
private:
static Handle<JSArrayBuffer> MaterializeArrayBuffer(
Handle<JSTypedArray> typed_array);
@ -231,6 +231,9 @@ class JSTypedArray : public JSArrayBufferView {
class JSDataView : public JSArrayBufferView {
public:
// [data_pointer]: pointer to the actual data.
DECL_PRIMITIVE_ACCESSORS(data_pointer, void*)
DECL_CAST(JSDataView)
// Dispatched behavior.
@ -238,10 +241,22 @@ class JSDataView : public JSArrayBufferView {
DECL_VERIFIER(JSDataView)
// Layout description.
#define JS_DATA_VIEW_FIELDS(V) \
/* Raw data fields. */ \
V(kDataPointerOffset, kIntptrSize) \
/* Header size. */ \
V(kHeaderSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(JSArrayBufferView::kHeaderSize,
JS_DATA_VIEW_FIELDS)
#undef JS_DATA_VIEW_FIELDS
static const int kSizeWithEmbedderFields =
kHeaderSize +
v8::ArrayBufferView::kEmbedderFieldCount * kEmbedderDataSlotSize;
class BodyDescriptor;
OBJECT_CONSTRUCTORS(JSDataView, JSArrayBufferView);
};

View File

@ -281,6 +281,12 @@ HeapObject Deserializer::PostProcessNewObject(HeapObject obj, int space) {
string->ExternalPayloadSize());
}
isolate_->heap()->RegisterExternalString(String::cast(obj));
} else if (obj->IsJSDataView()) {
JSDataView data_view = JSDataView::cast(obj);
JSArrayBuffer buffer = JSArrayBuffer::cast(data_view.buffer());
data_view.set_data_pointer(
reinterpret_cast<uint8_t*>(buffer.backing_store()) +
data_view.byte_offset());
} else if (obj->IsJSTypedArray()) {
JSTypedArray typed_array = JSTypedArray::cast(obj);
CHECK_LE(typed_array->byte_offset(), Smi::kMaxValue);