Commit c633282d authored by Mike Stanton's avatar Mike Stanton Committed by Commit Bot

[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: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46417}
parent a93188c6
...@@ -121,7 +121,7 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { ...@@ -121,7 +121,7 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS), GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS),
&object_push_pre); &object_push_pre);
BuildAppendJSArray(PACKED_SMI_ELEMENTS, a(), k_value, &runtime); BuildAppendJSArray(HOLEY_SMI_ELEMENTS, a(), k_value, &runtime);
Goto(&after_work); Goto(&after_work);
} }
...@@ -133,13 +133,13 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { ...@@ -133,13 +133,13 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
BIND(&object_push); BIND(&object_push);
{ {
BuildAppendJSArray(PACKED_ELEMENTS, a(), k_value, &runtime); BuildAppendJSArray(HOLEY_ELEMENTS, a(), k_value, &runtime);
Goto(&after_work); Goto(&after_work);
} }
BIND(&double_push); BIND(&double_push);
{ {
BuildAppendJSArray(PACKED_DOUBLE_ELEMENTS, a(), k_value, &runtime); BuildAppendJSArray(HOLEY_DOUBLE_ELEMENTS, a(), k_value, &runtime);
Goto(&after_work); Goto(&after_work);
} }
...@@ -180,70 +180,101 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { ...@@ -180,70 +180,101 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
Node* SpecCompliantMapProcessor(Node* k_value, Node* k) { Node* SpecCompliantMapProcessor(Node* k_value, Node* k) {
// i. Let kValue be ? Get(O, Pk). Performed by the caller of // i. Let kValue be ? Get(O, Pk). Performed by the caller of
// SpecCompliantMapProcessor. // SpecCompliantMapProcessor.
// ii. Let mappedValue be ? Call(callbackfn, T, kValue, k, O). // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O).
Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(), Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
callbackfn(), this_arg(), k_value, k, o()); callbackfn(), this_arg(), k_value, k, o());
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mappedValue); CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mapped_value);
return a(); return a();
} }
Node* FastMapProcessor(Node* k_value, Node* k) { Node* FastMapProcessor(Node* k_value, Node* k) {
// i. Let kValue be ? Get(O, Pk). Performed by the caller of // i. Let kValue be ? Get(O, Pk). Performed by the caller of
// FastMapProcessor. // FastMapProcessor.
// ii. Let mappedValue be ? Call(callbackfn, T, kValue, k, O). // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O).
Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(), Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
callbackfn(), this_arg(), k_value, k, o()); 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. // mode is SMI_PARAMETERS because k has tagged representation.
ParameterMode mode = SMI_PARAMETERS; ParameterMode mode = SMI_PARAMETERS;
Label fast(this); Label runtime(this), finished(this);
Label runtime(this); Label transition_pre(this), transition_smi_fast(this),
Label object_push_pre(this), object_push(this), double_push(this); transition_smi_double(this);
BranchIfFastJSArray(a(), context(), FastJSArrayAccessMode::ANY_ACCESS, Label array_not_smi(this), array_fast(this), array_double(this);
&fast, &runtime);
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);
{
// 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(&fast); BIND(&array_not_smi);
{ {
kind = EnsureArrayPushable(a(), &runtime); Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &array_double,
elements = LoadElements(a()); &array_fast);
GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS),
&object_push_pre);
TryStoreArrayElement(PACKED_SMI_ELEMENTS, mode, &runtime, elements, k,
mappedValue);
Goto(&finished);
} }
BIND(&object_push_pre); BIND(&transition_smi_fast);
{ {
Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &double_push, // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
&object_push); 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(&object_push); BIND(&array_fast);
{ {
TryStoreArrayElement(PACKED_ELEMENTS, mode, &runtime, elements, k, TryStoreArrayElement(HOLEY_ELEMENTS, mode, &runtime, elements, k,
mappedValue); mapped_value);
Goto(&finished); Goto(&finished);
} }
BIND(&double_push); BIND(&transition_smi_double);
{ {
TryStoreArrayElement(PACKED_DOUBLE_ELEMENTS, mode, &runtime, elements, k, // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
mappedValue); 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());
// 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); Goto(&finished);
} }
BIND(&runtime); BIND(&runtime);
{ {
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mappedValue); CallRuntime(Runtime::kCreateDataProperty, context(), a(), k,
mapped_value);
Goto(&finished); Goto(&finished);
} }
...@@ -253,12 +284,12 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { ...@@ -253,12 +284,12 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
// See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map. // See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map.
Node* TypedArrayMapProcessor(Node* k_value, Node* k) { Node* TypedArrayMapProcessor(Node* k_value, Node* k) {
// 8. c. Let mappedValue be ? Call(callbackfn, T, « kValue, k, O »). // 8. c. Let mapped_value be ? Call(callbackfn, T, « kValue, k, O »).
Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(), Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
callbackfn(), this_arg(), k_value, k, o()); callbackfn(), this_arg(), k_value, k, o());
Label fast(this), slow(this), done(this), detached(this, Label::kDeferred); 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 // Since we know that A is a TypedArray, this always ends up in
// #sec-integer-indexed-exotic-objects-set-p-v-receiver and then // #sec-integer-indexed-exotic-objects-set-p-v-receiver and then
// tc39.github.io/ecma262/#sec-integerindexedelementset . // tc39.github.io/ecma262/#sec-integerindexedelementset .
...@@ -266,14 +297,14 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { ...@@ -266,14 +297,14 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
BIND(&fast); BIND(&fast);
// #sec-integerindexedelementset 3. Let numValue be ? ToNumber(value). // #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. // The only way how this can bailout is because of a detached buffer.
EmitElementStore(a(), k, num_value, false, source_elements_kind_, EmitElementStore(a(), k, num_value, false, source_elements_kind_,
KeyedAccessStoreMode::STANDARD_STORE, &detached); KeyedAccessStoreMode::STANDARD_STORE, &detached);
Goto(&done); Goto(&done);
BIND(&slow); BIND(&slow);
CallRuntime(Runtime::kSetProperty, context(), a(), k, mappedValue, CallRuntime(Runtime::kSetProperty, context(), a(), k, mapped_value,
SmiConstant(STRICT)); SmiConstant(STRICT));
Goto(&done); Goto(&done);
...@@ -764,6 +795,9 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { ...@@ -764,6 +795,9 @@ class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
GotoIf(SmiAbove(len, SmiConstant(JSArray::kInitialMaxFastElementArray)), GotoIf(SmiAbove(len, SmiConstant(JSArray::kInitialMaxFastElementArray)),
&runtime); &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 = const ElementsKind elements_kind =
GetHoleyElementsKind(GetInitialFastElementsKind()); GetHoleyElementsKind(GetInitialFastElementsKind());
Node* array_map = LoadJSArrayElementsMap(elements_kind, native_context); Node* array_map = LoadJSArrayElementsMap(elements_kind, native_context);
......
...@@ -448,5 +448,13 @@ Callable CodeFactory::FunctionPrototypeBind(Isolate* isolate) { ...@@ -448,5 +448,13 @@ Callable CodeFactory::FunctionPrototypeBind(Isolate* isolate) {
BuiltinDescriptor(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 internal
} // namespace v8 } // namespace v8
...@@ -117,6 +117,8 @@ class V8_EXPORT_PRIVATE CodeFactory final { ...@@ -117,6 +117,8 @@ class V8_EXPORT_PRIVATE CodeFactory final {
static Callable ArrayPush(Isolate* isolate); static Callable ArrayPush(Isolate* isolate);
static Callable ArrayShift(Isolate* isolate); static Callable ArrayShift(Isolate* isolate);
static Callable FunctionPrototypeBind(Isolate* isolate); static Callable FunctionPrototypeBind(Isolate* isolate);
static Callable TransitionElementsKind(Isolate* isolate, ElementsKind from,
ElementsKind to, bool is_jsarray);
}; };
} // namespace internal } // namespace internal
......
...@@ -440,6 +440,23 @@ TF_STUB(StringLengthStub, CodeStubAssembler) { ...@@ -440,6 +440,23 @@ TF_STUB(StringLengthStub, CodeStubAssembler) {
Return(result); 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. // TODO(ishell): move to builtins.
TF_STUB(NumberToStringStub, CodeStubAssembler) { TF_STUB(NumberToStringStub, CodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext); Node* context = Parameter(Descriptor::kContext);
......
...@@ -73,6 +73,7 @@ class Node; ...@@ -73,6 +73,7 @@ class Node;
V(GetProperty) \ V(GetProperty) \
V(StoreFastElement) \ V(StoreFastElement) \
V(StoreInterceptor) \ V(StoreInterceptor) \
V(TransitionElementsKind) \
V(LoadIndexedInterceptor) \ V(LoadIndexedInterceptor) \
V(GrowArrayElements) V(GrowArrayElements)
...@@ -569,6 +570,37 @@ class StoreInterceptorStub : public TurboFanCodeStub { ...@@ -569,6 +570,37 @@ class StoreInterceptorStub : public TurboFanCodeStub {
DEFINE_TURBOFAN_CODE_STUB(StoreInterceptor, 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 { class LoadIndexedInterceptorStub : public TurboFanCodeStub {
public: public:
explicit LoadIndexedInterceptorStub(Isolate* isolate) explicit LoadIndexedInterceptorStub(Isolate* isolate)
......
...@@ -11,6 +11,8 @@ function benchy(name, test, testSetup) { ...@@ -11,6 +11,8 @@ function benchy(name, test, testSetup) {
benchy('NaiveMapReplacement', NaiveMap, NaiveMapSetup); benchy('NaiveMapReplacement', NaiveMap, NaiveMapSetup);
benchy('DoubleMap', DoubleMap, DoubleMapSetup); benchy('DoubleMap', DoubleMap, DoubleMapSetup);
benchy('SmallSmiToDoubleMap', SmiMap, SmiToDoubleMapSetup);
benchy('SmallSmiToFastMap', SmiMap, SmiToFastMapSetup);
benchy('SmiMap', SmiMap, SmiMapSetup); benchy('SmiMap', SmiMap, SmiMapSetup);
benchy('FastMap', FastMap, FastMapSetup); benchy('FastMap', FastMap, FastMapSetup);
benchy('ObjectMap', GenericMap, ObjectMapSetup); benchy('ObjectMap', GenericMap, ObjectMapSetup);
...@@ -86,6 +88,18 @@ function SmiMapSetup() { ...@@ -86,6 +88,18 @@ function SmiMapSetup() {
func = (value, index, object) => { return value; }; 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() { function DoubleMapSetup() {
array = new Array(); array = new Array();
for (var i = 0; i < array_size; i++) array[i] = (i + 0.5); for (var i = 0; i < array_size; i++) array[i] = (i + 0.5);
......
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