Commit 6452b26a authored by Daniel Clifford's avatar Daniel Clifford Committed by Commit Bot

Reimplement Array.prototype.slice in CSA and C++

Previously, V8's slice was implemented in a combination of C++ and a 
Javascript fallback. The disadvantage of this approach was that the
fast-path required a call through the CEntryStub, which introduced
considerable overhead for small arrays with fast elements kinds.

Now the implementation primarily uses the CSA to generate both the
full spec-complaint implementation as well as fast paths for argument
objects and arrays with fast elements kinds. The CSA implementation
uses a C++ implementation fallback in select situations where the the
complexity of a CSA implementation would be too great and the
CEntryStub overhead is not decisive (e.g. slices of dictionary
elements arrays).

Performance results on semi-random arrays with small number of
elements (old vs. new):

smi copy: 48.7 ms vs. 12 ms
smi slice: 43.5 ms 14.8 ms
object copy: 35.5 ms 7.7 ms
object slice: 38.7 ms 8.8 ms
dictionary slice: 2398.3 ms vs. 5.4 ms
fast sloppy arguments slice: 9.6 ms vs. 7.2 ms
slow sloppy arguments slice: 28.9 ms vs. 8.5 ms

As a bonus, the new implementation is fully spec-compliant and fixes
at least one existing bug.

The design document for Array.prototype builtin rework can be found
at https://goo.gl/wFHe2n

Bug: v8:1956,v8:6601,v8:6710,v8:6978
Change-Id: Ia0155bedcf39b4577605ff754f416c2af938efb7
Reviewed-on: https://chromium-review.googlesource.com/574710
Commit-Queue: Daniel Clifford <danno@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48853}
parent b422ba3e
......@@ -1657,7 +1657,12 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(proto, "push", Builtins::kFastArrayPush, 1, false);
SimpleInstallFunction(proto, "shift", Builtins::kFastArrayShift, 0, false);
SimpleInstallFunction(proto, "unshift", Builtins::kArrayUnshift, 1, false);
SimpleInstallFunction(proto, "slice", Builtins::kArraySlice, 2, false);
if (FLAG_enable_experimental_builtins) {
SimpleInstallFunction(proto, "slice", Builtins::kFastArraySlice, 2,
false);
} else {
SimpleInstallFunction(proto, "slice", Builtins::kArraySlice, 2, false);
}
SimpleInstallFunction(proto, "splice", Builtins::kArraySplice, 2, false);
SimpleInstallFunction(proto, "includes", Builtins::kArrayIncludes, 1,
false);
......
......@@ -1085,6 +1085,319 @@ TF_BUILTIN(FastArrayPush, CodeStubAssembler) {
}
}
class FastArraySliceCodeStubAssembler : public CodeStubAssembler {
public:
explicit FastArraySliceCodeStubAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
Node* HandleFastSlice(Node* context, Node* array, Node* from, Node* count,
Label* slow) {
VARIABLE(result, MachineRepresentation::kTagged);
Label done(this);
GotoIf(TaggedIsNotSmi(from), slow);
GotoIf(TaggedIsNotSmi(count), slow);
Label try_fast_arguments(this), try_simple_slice(this);
Node* map = LoadMap(array);
GotoIfNot(IsJSArrayMap(map), &try_fast_arguments);
// Check prototype chain if receiver does not have packed elements
GotoIfNot(IsPrototypeInitialArrayPrototype(context, map), slow);
GotoIf(IsArrayProtectorCellInvalid(), slow);
GotoIf(IsSpeciesProtectorCellInvalid(), slow);
// Bailout if receiver has slow elements.
Node* elements_kind = LoadMapElementsKind(map);
GotoIfNot(IsFastElementsKind(elements_kind), &try_simple_slice);
result.Bind(CallStub(CodeFactory::ExtractFastJSArray(isolate()), context,
array, from, count));
Goto(&done);
BIND(&try_fast_arguments);
Node* const native_context = LoadNativeContext(context);
Node* const fast_aliasted_arguments_map = LoadContextElement(
native_context, Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX);
GotoIf(WordNotEqual(map, fast_aliasted_arguments_map), &try_simple_slice);
Node* sloppy_elements = LoadElements(array);
Node* sloppy_elements_length = LoadFixedArrayBaseLength(sloppy_elements);
Node* parameter_map_length =
SmiSub(sloppy_elements_length,
SmiConstant(SloppyArgumentsElements::kParameterMapStart));
VARIABLE(index_out, MachineType::PointerRepresentation());
int max_fast_elements =
(kMaxRegularHeapObjectSize - FixedArray::kHeaderSize - JSArray::kSize -
AllocationMemento::kSize) /
kPointerSize;
GotoIf(SmiAboveOrEqual(count, SmiConstant(max_fast_elements)),
&try_simple_slice);
Node* end = SmiAdd(from, count);
Node* unmapped_elements = LoadFixedArrayElement(
sloppy_elements, SloppyArgumentsElements::kArgumentsIndex);
Node* unmapped_elements_length =
LoadFixedArrayBaseLength(unmapped_elements);
GotoIf(SmiGreaterThan(end, unmapped_elements_length), slow);
Node* array_map = LoadJSArrayElementsMap(HOLEY_ELEMENTS, native_context);
result.Bind(AllocateJSArray(HOLEY_ELEMENTS, array_map, count, count,
nullptr, SMI_PARAMETERS));
index_out.Bind(IntPtrConstant(0));
Node* result_elements = LoadElements(result.value());
Node* from_mapped = SmiMin(parameter_map_length, from);
Node* to = SmiMin(parameter_map_length, end);
Node* arguments_context = LoadFixedArrayElement(
sloppy_elements, SloppyArgumentsElements::kContextIndex);
VariableList var_list({&index_out}, zone());
BuildFastLoop(
var_list, from_mapped, to,
[this, result_elements, arguments_context, sloppy_elements,
&index_out](Node* current) {
Node* context_index = LoadFixedArrayElement(
sloppy_elements, current,
kPointerSize * SloppyArgumentsElements::kParameterMapStart,
SMI_PARAMETERS);
Node* argument =
LoadContextElement(arguments_context, SmiUntag(context_index));
StoreFixedArrayElement(result_elements, index_out.value(), argument,
SKIP_WRITE_BARRIER);
index_out.Bind(IntPtrAdd(index_out.value(), IntPtrConstant(1)));
},
1, SMI_PARAMETERS, IndexAdvanceMode::kPost);
Node* unmapped_from = SmiMin(SmiMax(parameter_map_length, from), end);
BuildFastLoop(
var_list, unmapped_from, end,
[this, unmapped_elements, result_elements, &index_out](Node* current) {
Node* argument = LoadFixedArrayElement(unmapped_elements, current, 0,
SMI_PARAMETERS);
StoreFixedArrayElement(result_elements, index_out.value(), argument,
SKIP_WRITE_BARRIER);
index_out.Bind(IntPtrAdd(index_out.value(), IntPtrConstant(1)));
},
1, SMI_PARAMETERS, IndexAdvanceMode::kPost);
Goto(&done);
BIND(&try_simple_slice);
Node* simple_result = CallRuntime(Runtime::kTrySliceSimpleNonFastElements,
context, array, from, count);
GotoIfNumber(simple_result, slow);
result.Bind(simple_result);
Goto(&done);
BIND(&done);
return result.value();
}
void CopyOneElement(Node* context, Node* o, Node* a, Node* p_k, Variable& n) {
// b. Let kPresent be HasProperty(O, Pk).
// c. ReturnIfAbrupt(kPresent).
Node* k_present = HasProperty(o, p_k, context, kHasProperty);
// d. If kPresent is true, then
Label done_element(this);
GotoIf(WordNotEqual(k_present, TrueConstant()), &done_element);
// i. Let kValue be Get(O, Pk).
// ii. ReturnIfAbrupt(kValue).
Node* k_value = GetProperty(context, o, p_k);
// iii. Let status be CreateDataPropertyOrThrow(A, ToString(n), kValue).
// iv. ReturnIfAbrupt(status).
CallRuntime(Runtime::kCreateDataProperty, context, a, n.value(), k_value);
Goto(&done_element);
BIND(&done_element);
}
};
TF_BUILTIN(FastArraySlice, FastArraySliceCodeStubAssembler) {
Node* const argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
Node* const context = Parameter(BuiltinDescriptor::kContext);
Label slow(this, Label::kDeferred), fast_elements_kind(this);
CodeStubArguments args(this, argc);
Node* receiver = args.GetReceiver();
VARIABLE(o, MachineRepresentation::kTagged);
VARIABLE(len, MachineRepresentation::kTagged);
Label length_done(this), generic_length(this), check_arguments_length(this),
load_arguments_length(this);
GotoIf(TaggedIsSmi(receiver), &generic_length);
GotoIfNot(IsJSArray(receiver), &check_arguments_length);
o.Bind(receiver);
len.Bind(LoadJSArrayLength(receiver));
// Check for the array clone case. There can be no arguments to slice, the
// array prototype chain must be intact and have no elements, the array has to
// have fast elements.
GotoIf(WordNotEqual(argc, IntPtrConstant(0)), &length_done);
Label clone(this);
BranchIfFastJSArrayForCopy(receiver, context, &clone, &length_done);
BIND(&clone);
args.PopAndReturn(
CallStub(CodeFactory::CloneFastJSArray(isolate()), context, receiver));
BIND(&check_arguments_length);
Node* map = LoadMap(receiver);
Node* native_context = LoadNativeContext(context);
GotoIfContextElementEqual(map, native_context,
Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX,
&load_arguments_length);
GotoIfContextElementEqual(map, native_context,
Context::SLOW_ALIASED_ARGUMENTS_MAP_INDEX,
&load_arguments_length);
GotoIfContextElementEqual(map, native_context,
Context::STRICT_ARGUMENTS_MAP_INDEX,
&load_arguments_length);
GotoIfContextElementEqual(map, native_context,
Context::SLOPPY_ARGUMENTS_MAP_INDEX,
&load_arguments_length);
Goto(&generic_length);
BIND(&load_arguments_length);
Node* arguments_length =
LoadObjectField(receiver, JSArgumentsObject::kLengthOffset);
GotoIf(TaggedIsNotSmi(arguments_length), &generic_length);
o.Bind(receiver);
len.Bind(arguments_length);
Goto(&length_done);
BIND(&generic_length);
// 1. Let O be ToObject(this value).
// 2. ReturnIfAbrupt(O).
o.Bind(CallBuiltin(Builtins::kToObject, context, receiver));
// 3. Let len be ToLength(Get(O, "length")).
// 4. ReturnIfAbrupt(len).
len.Bind(ToLength_Inline(
context,
GetProperty(context, o.value(), isolate()->factory()->length_string())));
Goto(&length_done);
BIND(&length_done);
// 5. Let relativeStart be ToInteger(start).
// 6. ReturnIfAbrupt(relativeStart).
Node* arg0 = args.GetOptionalArgumentValue(0, SmiConstant(0));
Node* relative_start = ToInteger(context, arg0);
// 7. If relativeStart < 0, let k be max((len + relativeStart),0);
// else let k be min(relativeStart, len.value()).
VARIABLE(k, MachineRepresentation::kTagged);
Label relative_start_positive(this), relative_start_done(this);
GotoIfNumberGreaterThanOrEqual(relative_start, SmiConstant(0),
&relative_start_positive);
k.Bind(NumberMax(NumberAdd(len.value(), relative_start), NumberConstant(0)));
Goto(&relative_start_done);
BIND(&relative_start_positive);
k.Bind(NumberMin(relative_start, len.value()));
Goto(&relative_start_done);
BIND(&relative_start_done);
// 8. If end is undefined, let relativeEnd be len;
// else let relativeEnd be ToInteger(end).
// 9. ReturnIfAbrupt(relativeEnd).
Node* end = args.GetOptionalArgumentValue(1, UndefinedConstant());
Label end_undefined(this), end_done(this);
VARIABLE(relative_end, MachineRepresentation::kTagged);
GotoIf(WordEqual(end, UndefinedConstant()), &end_undefined);
relative_end.Bind(ToInteger(context, end));
Goto(&end_done);
BIND(&end_undefined);
relative_end.Bind(len.value());
Goto(&end_done);
BIND(&end_done);
// 10. If relativeEnd < 0, let final be max((len + relativeEnd),0);
// else let final be min(relativeEnd, len).
VARIABLE(final, MachineRepresentation::kTagged);
Label relative_end_positive(this), relative_end_done(this);
GotoIfNumberGreaterThanOrEqual(relative_end.value(), NumberConstant(0),
&relative_end_positive);
final.Bind(NumberMax(NumberAdd(len.value(), relative_end.value()),
NumberConstant(0)));
Goto(&relative_end_done);
BIND(&relative_end_positive);
final.Bind(NumberMin(relative_end.value(), len.value()));
Goto(&relative_end_done);
BIND(&relative_end_done);
// 11. Let count be max(final – k, 0).
Node* count =
NumberMax(NumberSub(final.value(), k.value()), NumberConstant(0));
// Handle FAST_ELEMENTS
Label non_fast(this);
Node* fast_result =
HandleFastSlice(context, o.value(), k.value(), count, &non_fast);
args.PopAndReturn(fast_result);
// 12. Let A be ArraySpeciesCreate(O, count).
// 13. ReturnIfAbrupt(A).
BIND(&non_fast);
Node* constructor =
CallRuntime(Runtime::kArraySpeciesConstructor, context, o.value());
Node* a = ConstructJS(CodeFactory::Construct(isolate()), context, constructor,
count);
// 14. Let n be 0.
VARIABLE(n, MachineRepresentation::kTagged);
n.Bind(SmiConstant(0));
Label loop(this, {&k, &n});
Label after_loop(this);
Goto(&loop);
BIND(&loop);
{
// 15. Repeat, while k < final
GotoIfNumberGreaterThanOrEqual(k.value(), final.value(), &after_loop);
Node* p_k = k.value(); // ToString(context, k.value()) is no-op
CopyOneElement(context, o.value(), a, p_k, n);
// e. Increase k by 1.
k.Bind(NumberInc(k.value()));
// f. Increase n by 1.
n.Bind(NumberInc(n.value()));
Goto(&loop);
}
BIND(&after_loop);
// 16. Let setStatus be Set(A, "length", n, true).
// 17. ReturnIfAbrupt(setStatus).
CallRuntime(Runtime::kSetProperty, context, a,
HeapConstant(isolate()->factory()->length_string()), n.value(),
SmiConstant(static_cast<int>(LanguageMode::kStrict)));
args.PopAndReturn(a);
}
TF_BUILTIN(FastArrayShift, CodeStubAssembler) {
Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
Node* context = Parameter(BuiltinDescriptor::kContext);
......@@ -1243,6 +1556,30 @@ TF_BUILTIN(FastArrayShift, CodeStubAssembler) {
}
}
TF_BUILTIN(ExtractFastJSArray, ArrayBuiltinCodeStubAssembler) {
ParameterMode mode = OptimalParameterMode();
Node* context = Parameter(Descriptor::kContext);
Node* array = Parameter(Descriptor::kSource);
Node* begin = TaggedToParameter(Parameter(Descriptor::kBegin), mode);
Node* count = TaggedToParameter(Parameter(Descriptor::kCount), mode);
CSA_ASSERT(this, IsJSArray(array));
CSA_ASSERT(this, Word32BinaryNot(IsArrayProtectorCellInvalid()));
Return(ExtractFastJSArray(context, array, begin, count, mode));
}
TF_BUILTIN(CloneFastJSArray, ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* array = Parameter(Descriptor::kSource);
CSA_ASSERT(this, IsJSArray(array));
CSA_ASSERT(this, Word32BinaryNot(IsArrayProtectorCellInvalid()));
ParameterMode mode = OptimalParameterMode();
Return(CloneFastJSArray(context, array, mode));
}
TF_BUILTIN(ArrayForEachLoopContinuation, ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
......
......@@ -264,10 +264,14 @@ namespace internal {
TFJ(FastArrayShift, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.slice */ \
CPP(ArraySlice) \
TFJ(FastArraySlice, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.splice */ \
CPP(ArraySplice) \
/* ES6 #sec-array.prototype.unshift */ \
CPP(ArrayUnshift) \
/* Support for Array.from and other array-copying idioms */ \
TFS(CloneFastJSArray, kSource) \
TFS(ExtractFastJSArray, kSource, kBegin, kCount) \
/* ES6 #sec-array.prototype.foreach */ \
TFS(ArrayForEachLoopContinuation, kReceiver, kCallbackFn, kThisArg, kArray, \
kObject, kInitialK, kLength, kTo) \
......
......@@ -334,6 +334,18 @@ Callable CodeFactory::ArrayShift(Isolate* isolate) {
BuiltinDescriptor(isolate));
}
// static
Callable CodeFactory::ExtractFastJSArray(Isolate* isolate) {
return Callable(BUILTIN_CODE(isolate, ExtractFastJSArray),
ExtractFastJSArrayDescriptor(isolate));
}
// static
Callable CodeFactory::CloneFastJSArray(Isolate* isolate) {
return Callable(BUILTIN_CODE(isolate, CloneFastJSArray),
CloneFastJSArrayDescriptor(isolate));
}
// static
Callable CodeFactory::ArrayPush(Isolate* isolate) {
return Callable(BUILTIN_CODE(isolate, ArrayPush), BuiltinDescriptor(isolate));
......
......@@ -91,6 +91,8 @@ class V8_EXPORT_PRIVATE CodeFactory final {
static Callable ArrayPop(Isolate* isolate);
static Callable ArrayPush(Isolate* isolate);
static Callable ArrayShift(Isolate* isolate);
static Callable ExtractFastJSArray(Isolate* isolate);
static Callable CloneFastJSArray(Isolate* isolate);
static Callable FunctionPrototypeBind(Isolate* isolate);
static Callable TransitionElementsKind(Isolate* isolate, ElementsKind from,
ElementsKind to, bool is_jsarray);
......
......@@ -597,6 +597,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// Calling this is only valid if there's a module context in the chain.
TNode<Context> LoadModuleContext(SloppyTNode<Context> context);
void GotoIfContextElementEqual(Node* value, Node* native_context,
int slot_index, Label* if_equal) {
GotoIf(WordEqual(value, LoadContextElement(native_context, slot_index)),
if_equal);
}
TNode<Map> LoadJSArrayElementsMap(ElementsKind kind,
SloppyTNode<Context> native_context);
TNode<Map> LoadJSArrayElementsMap(SloppyTNode<Int32T> kind,
......
......@@ -274,6 +274,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
V(ArraySpeciesConstructor) \
V(NormalizeElements) \
V(GetArrayKeys) \
V(TrySliceSimpleNonFastElements) \
V(HasComplexElements) \
V(EstimateNumberOfElements) \
/* Errors */ \
......
......@@ -1429,6 +1429,37 @@ class DictionaryElementsAccessor
UNREACHABLE();
}
static Handle<JSObject> SliceImpl(Handle<JSObject> receiver, uint32_t start,
uint32_t end) {
Isolate* isolate = receiver->GetIsolate();
uint32_t result_length = end < start ? 0u : end - start;
// Result must also be a dictionary.
Handle<JSArray> result_array =
isolate->factory()->NewJSArray(0, HOLEY_ELEMENTS);
JSObject::NormalizeElements(result_array);
result_array->set_length(Smi::FromInt(result_length));
Handle<SeededNumberDictionary> source_dict(
SeededNumberDictionary::cast(receiver->elements()));
int entry_count = source_dict->Capacity();
for (int i = 0; i < entry_count; i++) {
Object* key = source_dict->KeyAt(i);
if (!key->IsUndefined(isolate)) {
uint64_t key_value = NumberToInt64(key);
if (key_value >= start && key_value < end) {
Handle<SeededNumberDictionary> dest_dict(
SeededNumberDictionary::cast(result_array->elements()));
Handle<Object> value(source_dict->ValueAt(i), isolate);
PropertyDetails details = source_dict->DetailsAt(i);
PropertyAttributes attr = details.attributes();
AddImpl(result_array, static_cast<uint32_t>(key_value), value, attr,
0);
}
}
}
return result_array;
}
static void DeleteImpl(Handle<JSObject> obj, uint32_t entry) {
Handle<SeededNumberDictionary> dict(
......@@ -3741,6 +3772,29 @@ class SloppyArgumentsElementsAccessor
}
return Just<int64_t>(-1);
}
static Handle<JSObject> SliceImpl(Handle<JSObject> receiver, uint32_t start,
uint32_t end) {
Isolate* isolate = receiver->GetIsolate();
uint32_t result_len = end < start ? 0u : end - start;
Handle<JSArray> result_array =
isolate->factory()->NewJSArray(HOLEY_ELEMENTS, result_len, result_len);
DisallowHeapAllocation no_gc;
FixedArray* elements = FixedArray::cast(result_array->elements());
FixedArray* parameters = FixedArray::cast(receiver->elements());
uint32_t insertion_index = 0;
for (uint32_t i = start; i < end; i++) {
uint32_t entry = GetEntryForIndexImpl(isolate, *receiver, parameters, i,
ALL_PROPERTIES);
if (entry != kMaxUInt32 && HasEntryImpl(isolate, parameters, entry)) {
elements->set(insertion_index, *GetImpl(isolate, parameters, entry));
} else {
elements->set_the_hole(isolate, insertion_index);
}
insertion_index++;
}
return result_array;
}
};
......@@ -3866,29 +3920,6 @@ class FastSloppyArgumentsElementsAccessor
return Handle<FixedArray>(elements->arguments(), isolate);
}
static Handle<JSObject> SliceImpl(Handle<JSObject> receiver, uint32_t start,
uint32_t end) {
Isolate* isolate = receiver->GetIsolate();
uint32_t result_len = end < start ? 0u : end - start;
Handle<JSArray> result_array =
isolate->factory()->NewJSArray(HOLEY_ELEMENTS, result_len, result_len);
DisallowHeapAllocation no_gc;
FixedArray* elements = FixedArray::cast(result_array->elements());
FixedArray* parameters = FixedArray::cast(receiver->elements());
uint32_t insertion_index = 0;
for (uint32_t i = start; i < end; i++) {
uint32_t entry = GetEntryForIndexImpl(isolate, *receiver, parameters, i,
ALL_PROPERTIES);
if (entry != kMaxUInt32 && HasEntryImpl(isolate, parameters, entry)) {
elements->set(insertion_index, *GetImpl(isolate, parameters, entry));
} else {
elements->set_the_hole(isolate, insertion_index);
}
insertion_index++;
}
return result_array;
}
static Handle<SeededNumberDictionary> NormalizeImpl(
Handle<JSObject> object, Handle<FixedArrayBase> elements) {
Handle<FixedArray> arguments =
......
......@@ -739,6 +739,8 @@ DEFINE_BOOL(expose_trigger_failure, false, "expose trigger-failure extension")
DEFINE_INT(stack_trace_limit, 10, "number of stack frames to capture")
DEFINE_BOOL(builtins_in_stack_traces, false,
"show built-in functions in stack traces")
DEFINE_BOOL(enable_experimental_builtins, false,
"enable new csa-based experimental builtins")
// builtins.cc
DEFINE_BOOL(allow_unsafe_function_constructor, false,
......
......@@ -378,6 +378,45 @@ RUNTIME_FUNCTION(Runtime_GetArrayKeys) {
return *isolate->factory()->NewJSArrayWithElements(keys);
}
RUNTIME_FUNCTION(Runtime_TrySliceSimpleNonFastElements) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
CONVERT_SMI_ARG_CHECKED(first, 1);
CONVERT_SMI_ARG_CHECKED(count, 2);
uint32_t length = first + count;
// Only handle elements kinds that have a ElementsAccessor Slice
// implementation.
if (receiver->IsJSArray()) {
// This "fastish" path must make sure the destination array is a JSArray.
if (!isolate->IsArraySpeciesLookupChainIntact() ||
!JSArray::cast(*receiver)->HasArrayPrototype(isolate)) {
return Smi::FromInt(0);
}
} else {
Map* map = receiver->map();
Context* context = *isolate->native_context();
if (map != context->sloppy_arguments_map() &&
map != context->strict_arguments_map() &&
map != context->fast_aliased_arguments_map() &&
map != context->slow_aliased_arguments_map()) {
return Smi::FromInt(0);
}
}
// This "fastish" path must also ensure that elements are simple (no
// geters/setters), no elements on prototype chain.
Handle<JSObject> object(Handle<JSObject>::cast(receiver));
if (!JSObject::PrototypeHasNoElements(isolate, *object) ||
object->HasComplexElements()) {
return Smi::FromInt(0);
}
ElementsAccessor* accessor = object->GetElementsAccessor();
return *accessor->Slice(object, first, length);
}
RUNTIME_FUNCTION(Runtime_NewArray) {
HandleScope scope(isolate);
DCHECK_LE(3, args.length());
......
......@@ -36,22 +36,23 @@ namespace internal {
// A variable number of arguments is specified by a -1, additional restrictions
// are specified by inline comments
#define FOR_EACH_INTRINSIC_ARRAY(F) \
F(TransitionElementsKind, 2, 1) \
F(RemoveArrayHoles, 2, 1) \
F(MoveArrayContents, 2, 1) \
F(EstimateNumberOfElements, 1, 1) \
F(GetArrayKeys, 2, 1) \
F(NewArray, -1 /* >= 3 */, 1) \
F(FunctionBind, -1, 1) \
F(NormalizeElements, 1, 1) \
F(GrowArrayElements, 2, 1) \
F(HasComplexElements, 1, 1) \
F(IsArray, 1, 1) \
F(ArrayIsArray, 1, 1) \
F(ArraySpeciesConstructor, 1, 1) \
F(ArrayIncludes_Slow, 3, 1) \
F(ArrayIndexOf, 3, 1) \
#define FOR_EACH_INTRINSIC_ARRAY(F) \
F(TransitionElementsKind, 2, 1) \
F(RemoveArrayHoles, 2, 1) \
F(MoveArrayContents, 2, 1) \
F(EstimateNumberOfElements, 1, 1) \
F(GetArrayKeys, 2, 1) \
F(TrySliceSimpleNonFastElements, 3, 1) \
F(NewArray, -1 /* >= 3 */, 1) \
F(FunctionBind, -1, 1) \
F(NormalizeElements, 1, 1) \
F(GrowArrayElements, 2, 1) \
F(HasComplexElements, 1, 1) \
F(IsArray, 1, 1) \
F(ArrayIsArray, 1, 1) \
F(ArraySpeciesConstructor, 1, 1) \
F(ArrayIncludes_Slow, 3, 1) \
F(ArrayIndexOf, 3, 1) \
F(SpreadIterablePrepare, 1, 1)
#define FOR_EACH_INTRINSIC_ATOMICS(F) \
......
......@@ -119,6 +119,119 @@ TEST(RunStringLengthStub) {
CHECK_EQ(static_cast<int>(strlen(testString)), Smi::ToInt(*result));
}
TEST(RunArrayExtractStubSimple) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
StubTester tester(isolate, zone, Builtins::kExtractFastJSArray);
// Actuall call through to the stub, verifying its result.
Handle<JSArray> source_array = isolate->factory()->NewJSArray(
PACKED_ELEMENTS, 5, 10, INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE);
static_cast<FixedArray*>(source_array->elements())->set(0, Smi::FromInt(5));
static_cast<FixedArray*>(source_array->elements())->set(1, Smi::FromInt(4));
static_cast<FixedArray*>(source_array->elements())->set(2, Smi::FromInt(3));
static_cast<FixedArray*>(source_array->elements())->set(3, Smi::FromInt(2));
static_cast<FixedArray*>(source_array->elements())->set(4, Smi::FromInt(1));
Handle<JSArray> result = Handle<JSArray>::cast(
tester.Call(source_array, Handle<Smi>(Smi::FromInt(0), isolate),
Handle<Smi>(Smi::FromInt(5), isolate)));
CHECK_NE(*source_array, *result);
CHECK_EQ(result->GetElementsKind(), PACKED_ELEMENTS);
CHECK_EQ(static_cast<FixedArray*>(result->elements())->get(0),
Smi::FromInt(5));
CHECK_EQ(static_cast<FixedArray*>(result->elements())->get(1),
Smi::FromInt(4));
CHECK_EQ(static_cast<FixedArray*>(result->elements())->get(2),
Smi::FromInt(3));
CHECK_EQ(static_cast<FixedArray*>(result->elements())->get(3),
Smi::FromInt(2));
CHECK_EQ(static_cast<FixedArray*>(result->elements())->get(4),
Smi::FromInt(1));
}
TEST(RunArrayExtractDoubleStubSimple) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
StubTester tester(isolate, zone, Builtins::kExtractFastJSArray);
// Actuall call through to the stub, verifying its result.
Handle<JSArray> source_array = isolate->factory()->NewJSArray(
PACKED_DOUBLE_ELEMENTS, 5, 10, INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE);
static_cast<FixedDoubleArray*>(source_array->elements())->set(0, 5);
static_cast<FixedDoubleArray*>(source_array->elements())->set(1, 4);
static_cast<FixedDoubleArray*>(source_array->elements())->set(2, 3);
static_cast<FixedDoubleArray*>(source_array->elements())->set(3, 2);
static_cast<FixedDoubleArray*>(source_array->elements())->set(4, 1);
Handle<JSArray> result = Handle<JSArray>::cast(
tester.Call(source_array, Handle<Smi>(Smi::FromInt(0), isolate),
Handle<Smi>(Smi::FromInt(5), isolate)));
CHECK_NE(*source_array, *result);
CHECK_EQ(result->GetElementsKind(), PACKED_DOUBLE_ELEMENTS);
CHECK_EQ(static_cast<FixedDoubleArray*>(result->elements())->get_scalar(0),
5);
CHECK_EQ(static_cast<FixedDoubleArray*>(result->elements())->get_scalar(1),
4);
CHECK_EQ(static_cast<FixedDoubleArray*>(result->elements())->get_scalar(2),
3);
CHECK_EQ(static_cast<FixedDoubleArray*>(result->elements())->get_scalar(3),
2);
CHECK_EQ(static_cast<FixedDoubleArray*>(result->elements())->get_scalar(4),
1);
}
TEST(RunArrayExtractStubTooBigForNewSpace) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
StubTester tester(isolate, zone, Builtins::kExtractFastJSArray);
// Actuall call through to the stub, verifying its result.
Handle<JSArray> source_array = isolate->factory()->NewJSArray(
PACKED_ELEMENTS, 500000, 500000, INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE);
for (int i = 0; i < 500000; ++i) {
static_cast<FixedArray*>(source_array->elements())->set(i, Smi::FromInt(i));
}
Handle<JSArray> result = Handle<JSArray>::cast(
tester.Call(source_array, Handle<Smi>(Smi::FromInt(0), isolate),
Handle<Smi>(Smi::FromInt(500000), isolate)));
CHECK_NE(*source_array, *result);
CHECK_EQ(result->GetElementsKind(), PACKED_ELEMENTS);
for (int i = 0; i < 500000; ++i) {
CHECK_EQ(static_cast<FixedArray*>(source_array->elements())->get(i),
static_cast<FixedArray*>(result->elements())->get(i));
}
}
TEST(RunArrayExtractDoubleStubTooBigForNewSpace) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
StubTester tester(isolate, zone, Builtins::kExtractFastJSArray);
// Actuall call through to the stub, verifying its result.
Handle<JSArray> source_array = isolate->factory()->NewJSArray(
PACKED_DOUBLE_ELEMENTS, 500000, 500000,
INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE, TENURED);
for (int i = 0; i < 500000; ++i) {
static_cast<FixedDoubleArray*>(source_array->elements())->set(i, i);
}
Handle<JSArray> result = Handle<JSArray>::cast(
tester.Call(source_array, Handle<Smi>(Smi::FromInt(0), isolate),
Handle<Smi>(Smi::FromInt(500000), isolate)));
CHECK_NE(*source_array, *result);
CHECK_EQ(result->GetElementsKind(), PACKED_DOUBLE_ELEMENTS);
for (int i = 0; i < 500000; ++i) {
CHECK_EQ(
static_cast<FixedDoubleArray*>(source_array->elements())->get_scalar(i),
static_cast<FixedDoubleArray*>(result->elements())->get_scalar(i));
}
}
} // namespace compiler
} // namespace internal
......
......@@ -48,6 +48,9 @@
# This test non-deterministically runs out of memory on Windows ia32.
'regress/regress-crbug-160010': [SKIP],
# This test should be skipped until the new CSA-based slice is activated.
'splice-proxy': [SKIP],
# Issue 3784: setters-on-elements is flaky
'setters-on-elements': [PASS, FAIL],
......
// 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.
var array = [];
var proxy = new Proxy(new Proxy(array, {}), {});
var Ctor = function() {};
var result;
array.constructor = function() {};
array.constructor[Symbol.species] = Ctor;
Array.prototype.slice.call(proxy);
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