[es2015] Optimize TypedArray.prototype[Symbol.toStringTag].

The TypedArray.prototype[Symbol.toStringTag] getter is currently the best (and
as far as I can tell only definitely side-effect free) way to check whether an
arbitrary object is a TypedArray - either generally TypedArray or a specific
one like Uint8Array. Using the getter is thus emerging as the general pattern
to detect TypedArrays, even Node.js now adapted it starting with

  https://github.com/nodejs/node/pull/15663

for the isTypedArray and isUint8Array type checks in lib/internal/util/types.js
now.

The getter returns either the string with the TypedArray subclass name
(i.e. "Uint8Array") or undefined if the receiver is not a TypedArray.
This can be implemented with a simple elements kind dispatch, instead of
checking the instance type and then loading the class name from the
constructor, which requires a loop walking up the transition tree. This
CL ports the builtin to CSA and TurboFan, and changes the logic to a
simple elements kind check. On the micro-benchmark mentioned in the
referenced bug, the time goes from

  testIsArrayBufferView: 565 ms.
  testIsTypedArray: 2403 ms.
  testIsUint8Array: 3847 ms.

to

  testIsArrayBufferView: 566 ms.
  testIsTypedArray: 965 ms.
  testIsUint8Array: 965 ms.

which presents an up to 4x improvement.

Bug: v8:6874
Change-Id: I9c330b4529d9631df2f052acf023c6a4fae69611
Reviewed-on: https://chromium-review.googlesource.com/695021
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48254}
This commit is contained in:
Benedikt Meurer 2017-10-02 07:28:41 +02:00 committed by Commit Bot
parent 0543782147
commit b8b76ebaac
9 changed files with 215 additions and 15 deletions

View File

@ -520,7 +520,7 @@ V8_NOINLINE void SimpleInstallGetterSetter(Handle<JSObject> base,
}
V8_NOINLINE Handle<JSFunction> SimpleInstallGetter(Handle<JSObject> base,
Handle<String> name,
Handle<Name> name,
Handle<Name> property_name,
Builtins::Name call,
bool adapt) {
@ -541,14 +541,14 @@ V8_NOINLINE Handle<JSFunction> SimpleInstallGetter(Handle<JSObject> base,
}
V8_NOINLINE Handle<JSFunction> SimpleInstallGetter(Handle<JSObject> base,
Handle<String> name,
Handle<Name> name,
Builtins::Name call,
bool adapt) {
return SimpleInstallGetter(base, name, name, call, adapt);
}
V8_NOINLINE Handle<JSFunction> SimpleInstallGetter(Handle<JSObject> base,
Handle<String> name,
Handle<Name> name,
Builtins::Name call,
bool adapt,
BuiltinFunctionId id) {
@ -2937,8 +2937,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
JSObject::cast(typed_array_fun->instance_prototype()));
native_context()->set_typed_array_prototype(*prototype);
// Install the "buffer", "byteOffset", "byteLength" and "length"
// getters on the {prototype}.
// Install the "buffer", "byteOffset", "byteLength", "length"
// and @@toStringTag getters on the {prototype}.
SimpleInstallGetter(prototype, factory->buffer_string(),
Builtins::kTypedArrayPrototypeBuffer, false);
SimpleInstallGetter(prototype, factory->byte_length_string(),
@ -2950,6 +2950,9 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallGetter(prototype, factory->length_string(),
Builtins::kTypedArrayPrototypeLength, true,
kTypedArrayLength);
SimpleInstallGetter(prototype, factory->to_string_tag_symbol(),
Builtins::kTypedArrayPrototypeToStringTag, true,
kTypedArrayToStringTag);
// Install "keys", "values" and "entries" methods on the {prototype}.
SimpleInstallFunction(prototype, "entries",

View File

@ -1029,6 +1029,8 @@ namespace internal {
CPP(TypedArrayPrototypeSet) \
/* ES6 #sec-%typedarray%.prototype.slice */ \
CPP(TypedArrayPrototypeSlice) \
/* ES6 #sec-get-%typedarray%.prototype-@@tostringtag */ \
TFJ(TypedArrayPrototypeToStringTag, 0) \
/* ES6 %TypedArray%.prototype.every */ \
TFJ(TypedArrayPrototypeEvery, \
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \

View File

@ -674,6 +674,49 @@ TF_BUILTIN(TypedArrayPrototypeLength, TypedArrayBuiltinsAssembler) {
JSTypedArray::kLengthOffset);
}
// ES #sec-get-%typedarray%.prototype-@@tostringtag
TF_BUILTIN(TypedArrayPrototypeToStringTag, TypedArrayBuiltinsAssembler) {
Node* receiver = Parameter(Descriptor::kReceiver);
Label if_receiverisheapobject(this), return_undefined(this);
Branch(TaggedIsSmi(receiver), &return_undefined, &if_receiverisheapobject);
// Dispatch on the elements kind, offset by
// FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND.
size_t const kTypedElementsKindCount = LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND -
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND +
1;
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
Label return_##type##array(this); \
BIND(&return_##type##array); \
Return(StringConstant(#Type "Array"));
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
Label* elements_kind_labels[kTypedElementsKindCount] = {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) &return_##type##array,
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
};
int32_t elements_kinds[kTypedElementsKindCount] = {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
TYPE##_ELEMENTS - FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND,
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
};
// We offset the dispatch by FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND, so
// that this can be turned into a non-sparse table switch for ideal
// performance.
BIND(&if_receiverisheapobject);
Node* elements_kind =
Int32Sub(LoadMapElementsKind(LoadMap(receiver)),
Int32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND));
Switch(elements_kind, &return_undefined, elements_kinds, elements_kind_labels,
kTypedElementsKindCount);
BIND(&return_undefined);
Return(UndefinedConstant());
}
void TypedArrayBuiltinsAssembler::GenerateTypedArrayPrototypeIterationMethod(
Node* context, Node* receiver, const char* method_name,
IterationKind iteration_kind) {

View File

@ -663,6 +663,77 @@ Reduction JSBuiltinReducer::ReduceTypedArrayIteratorNext(
return Replace(value);
}
// ES #sec-get-%typedarray%.prototype-@@tostringtag
Reduction JSBuiltinReducer::ReduceTypedArrayToStringTag(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
NodeVector values(graph()->zone());
NodeVector effects(graph()->zone());
NodeVector controls(graph()->zone());
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
control =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
values.push_back(jsgraph()->UndefinedConstant());
effects.push_back(effect);
controls.push_back(graph()->NewNode(common()->IfTrue(), control));
control = graph()->NewNode(common()->IfFalse(), control);
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* receiver_bit_field2 = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map,
effect, control);
Node* receiver_elements_kind = graph()->NewNode(
simplified()->NumberShiftRightLogical(),
graph()->NewNode(simplified()->NumberBitwiseAnd(), receiver_bit_field2,
jsgraph()->Constant(Map::ElementsKindBits::kMask)),
jsgraph()->Constant(Map::ElementsKindBits::kShift));
// Offset the elements kind by FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND,
// so that the branch cascade below is turned into a simple table
// switch by the ControlFlowOptimizer later.
receiver_elements_kind = graph()->NewNode(
simplified()->NumberSubtract(), receiver_elements_kind,
jsgraph()->Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND));
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
do { \
Node* check = graph()->NewNode( \
simplified()->NumberEqual(), receiver_elements_kind, \
jsgraph()->Constant(TYPE##_ELEMENTS - \
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); \
control = graph()->NewNode(common()->Branch(), check, control); \
values.push_back(jsgraph()->HeapConstant( \
factory()->InternalizeUtf8String(#Type "Array"))); \
effects.push_back(effect); \
controls.push_back(graph()->NewNode(common()->IfTrue(), control)); \
control = graph()->NewNode(common()->IfFalse(), control); \
} while (false);
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
values.push_back(jsgraph()->UndefinedConstant());
effects.push_back(effect);
controls.push_back(control);
int const count = static_cast<int>(controls.size());
control = graph()->NewNode(common()->Merge(count), count, &controls.front());
effects.push_back(control);
effect =
graph()->NewNode(common()->EffectPhi(count), count + 1, &effects.front());
values.push_back(control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values.front());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSBuiltinReducer::ReduceArrayIteratorNext(Node* node) {
Handle<Map> receiver_map;
if (GetMapWitness(node).ToHandle(&receiver_map)) {
@ -3114,6 +3185,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
return ReduceTypedArrayIterator(node, IterationKind::kKeys);
case kTypedArrayValues:
return ReduceTypedArrayIterator(node, IterationKind::kValues);
case kTypedArrayToStringTag:
return ReduceTypedArrayToStringTag(node);
default:
break;
}

View File

@ -51,6 +51,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
IterationKind kind);
Reduction ReduceTypedArrayIteratorNext(Handle<Map> iterator_map, Node* node,
IterationKind kind);
Reduction ReduceTypedArrayToStringTag(Node* node);
Reduction ReduceArrayIsArray(Node* node);
Reduction ReduceArrayPop(Node* node);
Reduction ReduceArrayPush(Node* node);

View File

@ -1495,6 +1495,9 @@ Type* Typer::Visitor::JSCallTyper(Type* fun, Typer* t) {
case kMapIteratorNext:
case kSetIteratorNext:
return Type::OtherObject();
case kTypedArrayToStringTag:
return Type::Union(Type::InternalizedString(), Type::Undefined(),
t->zone());
// Array functions.
case kArrayIsArray:

View File

@ -247,16 +247,6 @@ TYPED_ARRAYS(TYPED_ARRAY_SUBARRAY_CASE)
);
DEFINE_METHOD(
GlobalTypedArray.prototype,
get [toStringTagSymbol]() {
if (!IS_TYPEDARRAY(this)) return;
var name = %_ClassOf(this);
if (IS_UNDEFINED(name)) return;
return name;
}
);
// The following functions cannot be made efficient on sparse arrays while
// preserving the semantics, since the calls to the receiver function can add
// or delete elements from the array.

View File

@ -4662,6 +4662,7 @@ enum BuiltinFunctionId {
kTypedArrayEntries,
kTypedArrayKeys,
kTypedArrayLength,
kTypedArrayToStringTag,
kTypedArrayValues,
kSharedArrayBufferByteLength,
kStringIterator,

View File

@ -0,0 +1,84 @@
// Copyright 2017 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
const Classes = [
Uint8Array,
Int8Array,
Uint16Array,
Int16Array,
Uint32Array,
Int32Array,
Uint8ClampedArray,
Float32Array,
Float64Array
];
const TypedArrayPrototype_toStringTag =
Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(Uint8Array.prototype),
Symbol.toStringTag).get;
(function() {
function foo(o) {
return TypedArrayPrototype_toStringTag.call(o);
}
assertEquals(undefined, foo(1));
assertEquals(undefined, foo({}));
assertEquals(undefined, foo([]));
Classes.forEach(C => assertEquals(C.name, foo(new C(1))));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(1));
assertEquals(undefined, foo({}));
assertEquals(undefined, foo([]));
Classes.forEach(C => assertEquals(C.name, foo(new C(1))));
})();
(function() {
const ReflectApply = Reflect.apply;
const uncurryThis = func => (thisArg, ...args) =>
ReflectApply(func, thisArg, args);
const TypedArrayProto_toStringTag =
uncurryThis(TypedArrayPrototype_toStringTag);
function isTypedArray(value) {
return TypedArrayProto_toStringTag(value) !== undefined;
}
assertFalse(isTypedArray(1));
assertFalse(isTypedArray({}));
assertFalse(isTypedArray([]));
assertFalse(isTypedArray('Lorem ipsum'));
Classes.forEach(C => assertTrue(isTypedArray(new C(1))));
%OptimizeFunctionOnNextCall(isTypedArray);
assertFalse(isTypedArray(1));
assertFalse(isTypedArray({}));
assertFalse(isTypedArray([]));
assertFalse(isTypedArray('Lorem ipsum'));
Classes.forEach(C => assertTrue(isTypedArray(new C(1))));
})();
(function() {
const ReflectApply = Reflect.apply;
const uncurryThis = func => (thisArg, ...args) =>
ReflectApply(func, thisArg, args);
const TypedArrayProto_toStringTag =
uncurryThis(TypedArrayPrototype_toStringTag);
function isUint8Array(value) {
return TypedArrayProto_toStringTag(value) === 'Uint8Array';
}
assertFalse(isUint8Array(1));
assertFalse(isUint8Array({}));
assertFalse(isUint8Array([]));
assertFalse(isUint8Array('Lorem ipsum'));
Classes.forEach(C => assertEquals(C === Uint8Array, isUint8Array(new C(1))));
%OptimizeFunctionOnNextCall(isUint8Array);
assertFalse(isUint8Array(1));
assertFalse(isUint8Array({}));
assertFalse(isUint8Array([]));
assertFalse(isUint8Array('Lorem ipsum'));
Classes.forEach(C => assertEquals(C === Uint8Array, isUint8Array(new C(1))));
})();