[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:
parent
0543782147
commit
b8b76ebaac
@ -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",
|
||||
|
@ -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) \
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -4662,6 +4662,7 @@ enum BuiltinFunctionId {
|
||||
kTypedArrayEntries,
|
||||
kTypedArrayKeys,
|
||||
kTypedArrayLength,
|
||||
kTypedArrayToStringTag,
|
||||
kTypedArrayValues,
|
||||
kSharedArrayBufferByteLength,
|
||||
kStringIterator,
|
||||
|
84
test/mjsunit/compiler/typedarray-prototype-tostringtag.js
Normal file
84
test/mjsunit/compiler/typedarray-prototype-tostringtag.js
Normal 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))));
|
||||
})();
|
Loading…
Reference in New Issue
Block a user