Commit 4ef714a6 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[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: 's avatarPeter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61464}
parent e4e0d1c6
......@@ -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;
......
......@@ -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,
......
......@@ -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();
......
......@@ -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);
// 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);
Node* index = node->InputAt(2);
Node* is_little_endian = node->InputAt(3);
// 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);
// 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);
Node* index = node->InputAt(2);
Node* value = node->InputAt(3);
Node* is_little_endian = node->InputAt(4);
// 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));
}
......
......@@ -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;
}
......
......@@ -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;
}
......
......@@ -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) \
......
......@@ -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).
......
......@@ -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;
}
......
......@@ -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);
}
......
......@@ -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)
......
......@@ -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
......
......@@ -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);
};
......
......@@ -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);
......
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