[builtins] Array.map should transition output arrays.
If the input array is small, then the cost of a trip to the runtime to transition the ElementsKind is too expensive. Bug: Change-Id: Ib04f8567674a6f1f66f4c7263eba5fb4c58987aa Reviewed-on: https://chromium-review.googlesource.com/544866 Commit-Queue: Michael Stanton <mvstanton@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Cr-Commit-Position: refs/heads/master@{#46417}
This commit is contained in:
parent
a93188c63d
commit
c633282daf
@ -121,7 +121,7 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
||||
GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS),
|
||||
&object_push_pre);
|
||||
|
||||
BuildAppendJSArray(PACKED_SMI_ELEMENTS, a(), k_value, &runtime);
|
||||
BuildAppendJSArray(HOLEY_SMI_ELEMENTS, a(), k_value, &runtime);
|
||||
Goto(&after_work);
|
||||
}
|
||||
|
||||
@ -133,13 +133,13 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
||||
|
||||
BIND(&object_push);
|
||||
{
|
||||
BuildAppendJSArray(PACKED_ELEMENTS, a(), k_value, &runtime);
|
||||
BuildAppendJSArray(HOLEY_ELEMENTS, a(), k_value, &runtime);
|
||||
Goto(&after_work);
|
||||
}
|
||||
|
||||
BIND(&double_push);
|
||||
{
|
||||
BuildAppendJSArray(PACKED_DOUBLE_ELEMENTS, a(), k_value, &runtime);
|
||||
BuildAppendJSArray(HOLEY_DOUBLE_ELEMENTS, a(), k_value, &runtime);
|
||||
Goto(&after_work);
|
||||
}
|
||||
|
||||
@ -180,70 +180,101 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
||||
Node* SpecCompliantMapProcessor(Node* k_value, Node* k) {
|
||||
// i. Let kValue be ? Get(O, Pk). Performed by the caller of
|
||||
// SpecCompliantMapProcessor.
|
||||
// ii. Let mappedValue be ? Call(callbackfn, T, kValue, k, O).
|
||||
Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(),
|
||||
callbackfn(), this_arg(), k_value, k, o());
|
||||
// ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O).
|
||||
Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
|
||||
callbackfn(), this_arg(), k_value, k, o());
|
||||
|
||||
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
|
||||
CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mappedValue);
|
||||
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
|
||||
CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mapped_value);
|
||||
return a();
|
||||
}
|
||||
|
||||
Node* FastMapProcessor(Node* k_value, Node* k) {
|
||||
// i. Let kValue be ? Get(O, Pk). Performed by the caller of
|
||||
// FastMapProcessor.
|
||||
// ii. Let mappedValue be ? Call(callbackfn, T, kValue, k, O).
|
||||
Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(),
|
||||
callbackfn(), this_arg(), k_value, k, o());
|
||||
// ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O).
|
||||
Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
|
||||
callbackfn(), this_arg(), k_value, k, o());
|
||||
|
||||
Label finished(this);
|
||||
Node* kind = nullptr;
|
||||
Node* elements = nullptr;
|
||||
|
||||
// If a() is a JSArray, we can have a fast path.
|
||||
// mode is SMI_PARAMETERS because k has tagged representation.
|
||||
ParameterMode mode = SMI_PARAMETERS;
|
||||
Label fast(this);
|
||||
Label runtime(this);
|
||||
Label object_push_pre(this), object_push(this), double_push(this);
|
||||
BranchIfFastJSArray(a(), context(), FastJSArrayAccessMode::ANY_ACCESS,
|
||||
&fast, &runtime);
|
||||
Label runtime(this), finished(this);
|
||||
Label transition_pre(this), transition_smi_fast(this),
|
||||
transition_smi_double(this);
|
||||
Label array_not_smi(this), array_fast(this), array_double(this);
|
||||
|
||||
BIND(&fast);
|
||||
Node* kind = LoadMapElementsKind(LoadMap(a()));
|
||||
Node* elements = LoadElements(a());
|
||||
GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS), &array_not_smi);
|
||||
TryStoreArrayElement(HOLEY_SMI_ELEMENTS, mode, &transition_pre, elements, k,
|
||||
mapped_value);
|
||||
Goto(&finished);
|
||||
|
||||
BIND(&transition_pre);
|
||||
{
|
||||
kind = EnsureArrayPushable(a(), &runtime);
|
||||
// array is smi. Value is either tagged or a heap number.
|
||||
CSA_ASSERT(this, TaggedIsNotSmi(mapped_value));
|
||||
GotoIf(IsHeapNumberMap(LoadMap(mapped_value)), &transition_smi_double);
|
||||
Goto(&transition_smi_fast);
|
||||
}
|
||||
|
||||
BIND(&array_not_smi);
|
||||
{
|
||||
Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &array_double,
|
||||
&array_fast);
|
||||
}
|
||||
|
||||
BIND(&transition_smi_fast);
|
||||
{
|
||||
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
|
||||
Node* const native_context = LoadNativeContext(context());
|
||||
Node* const fast_map = LoadContextElement(
|
||||
native_context, Context::JS_ARRAY_HOLEY_ELEMENTS_MAP_INDEX);
|
||||
|
||||
// Since this transition is only a map change, just do it right here.
|
||||
// Since a() doesn't have an allocation site, it's safe to do the
|
||||
// map store directly, otherwise I'd call TransitionElementsKind().
|
||||
StoreMap(a(), fast_map);
|
||||
Goto(&array_fast);
|
||||
}
|
||||
|
||||
BIND(&array_fast);
|
||||
{
|
||||
TryStoreArrayElement(HOLEY_ELEMENTS, mode, &runtime, elements, k,
|
||||
mapped_value);
|
||||
Goto(&finished);
|
||||
}
|
||||
|
||||
BIND(&transition_smi_double);
|
||||
{
|
||||
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
|
||||
Node* const native_context = LoadNativeContext(context());
|
||||
Node* const double_map = LoadContextElement(
|
||||
native_context, Context::JS_ARRAY_HOLEY_DOUBLE_ELEMENTS_MAP_INDEX);
|
||||
CallStub(CodeFactory::TransitionElementsKind(
|
||||
isolate(), HOLEY_SMI_ELEMENTS, HOLEY_DOUBLE_ELEMENTS, true),
|
||||
context(), a(), double_map);
|
||||
Goto(&array_double);
|
||||
}
|
||||
|
||||
BIND(&array_double);
|
||||
{
|
||||
// TODO(mvstanton): If we use a variable for elements and bind it
|
||||
// appropriately, we can avoid an extra load of elements by binding the
|
||||
// value only after a transition from smi to double.
|
||||
elements = LoadElements(a());
|
||||
GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS),
|
||||
&object_push_pre);
|
||||
TryStoreArrayElement(PACKED_SMI_ELEMENTS, mode, &runtime, elements, k,
|
||||
mappedValue);
|
||||
Goto(&finished);
|
||||
}
|
||||
|
||||
BIND(&object_push_pre);
|
||||
{
|
||||
Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &double_push,
|
||||
&object_push);
|
||||
}
|
||||
|
||||
BIND(&object_push);
|
||||
{
|
||||
TryStoreArrayElement(PACKED_ELEMENTS, mode, &runtime, elements, k,
|
||||
mappedValue);
|
||||
Goto(&finished);
|
||||
}
|
||||
|
||||
BIND(&double_push);
|
||||
{
|
||||
TryStoreArrayElement(PACKED_DOUBLE_ELEMENTS, mode, &runtime, elements, k,
|
||||
mappedValue);
|
||||
// If the mapped_value isn't a number, this will bail out to the runtime
|
||||
// to make the transition.
|
||||
TryStoreArrayElement(HOLEY_DOUBLE_ELEMENTS, mode, &runtime, elements, k,
|
||||
mapped_value);
|
||||
Goto(&finished);
|
||||
}
|
||||
|
||||
BIND(&runtime);
|
||||
{
|
||||
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
|
||||
CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mappedValue);
|
||||
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
|
||||
CallRuntime(Runtime::kCreateDataProperty, context(), a(), k,
|
||||
mapped_value);
|
||||
Goto(&finished);
|
||||
}
|
||||
|
||||
@ -253,12 +284,12 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
||||
|
||||
// See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map.
|
||||
Node* TypedArrayMapProcessor(Node* k_value, Node* k) {
|
||||
// 8. c. Let mappedValue be ? Call(callbackfn, T, « kValue, k, O »).
|
||||
Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(),
|
||||
callbackfn(), this_arg(), k_value, k, o());
|
||||
// 8. c. Let mapped_value be ? Call(callbackfn, T, « kValue, k, O »).
|
||||
Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
|
||||
callbackfn(), this_arg(), k_value, k, o());
|
||||
Label fast(this), slow(this), done(this), detached(this, Label::kDeferred);
|
||||
|
||||
// 8. d. Perform ? Set(A, Pk, mappedValue, true).
|
||||
// 8. d. Perform ? Set(A, Pk, mapped_value, true).
|
||||
// Since we know that A is a TypedArray, this always ends up in
|
||||
// #sec-integer-indexed-exotic-objects-set-p-v-receiver and then
|
||||
// tc39.github.io/ecma262/#sec-integerindexedelementset .
|
||||
@ -266,14 +297,14 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
||||
|
||||
BIND(&fast);
|
||||
// #sec-integerindexedelementset 3. Let numValue be ? ToNumber(value).
|
||||
Node* num_value = ToNumber(context(), mappedValue);
|
||||
Node* num_value = ToNumber(context(), mapped_value);
|
||||
// The only way how this can bailout is because of a detached buffer.
|
||||
EmitElementStore(a(), k, num_value, false, source_elements_kind_,
|
||||
KeyedAccessStoreMode::STANDARD_STORE, &detached);
|
||||
Goto(&done);
|
||||
|
||||
BIND(&slow);
|
||||
CallRuntime(Runtime::kSetProperty, context(), a(), k, mappedValue,
|
||||
CallRuntime(Runtime::kSetProperty, context(), a(), k, mapped_value,
|
||||
SmiConstant(STRICT));
|
||||
Goto(&done);
|
||||
|
||||
@ -764,6 +795,9 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
||||
GotoIf(SmiAbove(len, SmiConstant(JSArray::kInitialMaxFastElementArray)),
|
||||
&runtime);
|
||||
|
||||
// We need to be conservative and start with holey because the builtins
|
||||
// that create output arrays aren't gauranteed to be called for every
|
||||
// element in the input array (maybe the callback deletes an element).
|
||||
const ElementsKind elements_kind =
|
||||
GetHoleyElementsKind(GetInitialFastElementsKind());
|
||||
Node* array_map = LoadJSArrayElementsMap(elements_kind, native_context);
|
||||
|
@ -448,5 +448,13 @@ Callable CodeFactory::FunctionPrototypeBind(Isolate* isolate) {
|
||||
BuiltinDescriptor(isolate));
|
||||
}
|
||||
|
||||
// static
|
||||
Callable CodeFactory::TransitionElementsKind(Isolate* isolate,
|
||||
ElementsKind from, ElementsKind to,
|
||||
bool is_jsarray) {
|
||||
TransitionElementsKindStub stub(isolate, from, to, is_jsarray);
|
||||
return make_callable(stub);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -117,6 +117,8 @@ class V8_EXPORT_PRIVATE CodeFactory final {
|
||||
static Callable ArrayPush(Isolate* isolate);
|
||||
static Callable ArrayShift(Isolate* isolate);
|
||||
static Callable FunctionPrototypeBind(Isolate* isolate);
|
||||
static Callable TransitionElementsKind(Isolate* isolate, ElementsKind from,
|
||||
ElementsKind to, bool is_jsarray);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -440,6 +440,23 @@ TF_STUB(StringLengthStub, CodeStubAssembler) {
|
||||
Return(result);
|
||||
}
|
||||
|
||||
TF_STUB(TransitionElementsKindStub, CodeStubAssembler) {
|
||||
Node* context = Parameter(Descriptor::kContext);
|
||||
Node* object = Parameter(Descriptor::kObject);
|
||||
Node* new_map = Parameter(Descriptor::kMap);
|
||||
|
||||
Label bailout(this);
|
||||
TransitionElementsKind(object, new_map, stub->from_kind(), stub->to_kind(),
|
||||
stub->is_jsarray(), &bailout);
|
||||
Return(object);
|
||||
|
||||
BIND(&bailout);
|
||||
{
|
||||
Comment("Call runtime");
|
||||
TailCallRuntime(Runtime::kTransitionElementsKind, context, object, new_map);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ishell): move to builtins.
|
||||
TF_STUB(NumberToStringStub, CodeStubAssembler) {
|
||||
Node* context = Parameter(Descriptor::kContext);
|
||||
|
@ -73,6 +73,7 @@ class Node;
|
||||
V(GetProperty) \
|
||||
V(StoreFastElement) \
|
||||
V(StoreInterceptor) \
|
||||
V(TransitionElementsKind) \
|
||||
V(LoadIndexedInterceptor) \
|
||||
V(GrowArrayElements)
|
||||
|
||||
@ -569,6 +570,37 @@ class StoreInterceptorStub : public TurboFanCodeStub {
|
||||
DEFINE_TURBOFAN_CODE_STUB(StoreInterceptor, TurboFanCodeStub);
|
||||
};
|
||||
|
||||
class TransitionElementsKindStub : public TurboFanCodeStub {
|
||||
public:
|
||||
TransitionElementsKindStub(Isolate* isolate, ElementsKind from_kind,
|
||||
ElementsKind to_kind, bool is_jsarray)
|
||||
: TurboFanCodeStub(isolate) {
|
||||
set_sub_minor_key(FromKindBits::encode(from_kind) |
|
||||
ToKindBits::encode(to_kind) |
|
||||
IsJSArrayBits::encode(is_jsarray));
|
||||
}
|
||||
|
||||
void set_sub_minor_key(uint32_t key) { minor_key_ = key; }
|
||||
|
||||
uint32_t sub_minor_key() const { return minor_key_; }
|
||||
|
||||
ElementsKind from_kind() const {
|
||||
return FromKindBits::decode(sub_minor_key());
|
||||
}
|
||||
|
||||
ElementsKind to_kind() const { return ToKindBits::decode(sub_minor_key()); }
|
||||
|
||||
bool is_jsarray() const { return IsJSArrayBits::decode(sub_minor_key()); }
|
||||
|
||||
private:
|
||||
class ToKindBits : public BitField<ElementsKind, 0, 8> {};
|
||||
class FromKindBits : public BitField<ElementsKind, ToKindBits::kNext, 8> {};
|
||||
class IsJSArrayBits : public BitField<bool, FromKindBits::kNext, 1> {};
|
||||
|
||||
DEFINE_CALL_INTERFACE_DESCRIPTOR(TransitionElementsKind);
|
||||
DEFINE_TURBOFAN_CODE_STUB(TransitionElementsKind, TurboFanCodeStub);
|
||||
};
|
||||
|
||||
class LoadIndexedInterceptorStub : public TurboFanCodeStub {
|
||||
public:
|
||||
explicit LoadIndexedInterceptorStub(Isolate* isolate)
|
||||
|
@ -11,6 +11,8 @@ function benchy(name, test, testSetup) {
|
||||
|
||||
benchy('NaiveMapReplacement', NaiveMap, NaiveMapSetup);
|
||||
benchy('DoubleMap', DoubleMap, DoubleMapSetup);
|
||||
benchy('SmallSmiToDoubleMap', SmiMap, SmiToDoubleMapSetup);
|
||||
benchy('SmallSmiToFastMap', SmiMap, SmiToFastMapSetup);
|
||||
benchy('SmiMap', SmiMap, SmiMapSetup);
|
||||
benchy('FastMap', FastMap, FastMapSetup);
|
||||
benchy('ObjectMap', GenericMap, ObjectMapSetup);
|
||||
@ -86,6 +88,18 @@ function SmiMapSetup() {
|
||||
func = (value, index, object) => { return value; };
|
||||
}
|
||||
|
||||
function SmiToDoubleMapSetup() {
|
||||
array = new Array();
|
||||
for (var i = 0; i < 1; i++) array[i] = i;
|
||||
func = (value, index, object) => { return value + 0.5; };
|
||||
}
|
||||
|
||||
function SmiToFastMapSetup() {
|
||||
array = new Array();
|
||||
for (var i = 0; i < 1; i++) array[i] = i;
|
||||
func = (value, index, object) => { return "hi" + value; };
|
||||
}
|
||||
|
||||
function DoubleMapSetup() {
|
||||
array = new Array();
|
||||
for (var i = 0; i < array_size; i++) array[i] = (i + 0.5);
|
||||
|
Loading…
Reference in New Issue
Block a user