Improve Array.shift() performance for small arrays.

TEST=mjsunit/array-shift,mjsunit/array-shift2,mjsunit/array-shift3
R=svenpanne@chromium.org

Review URL: https://codereview.chromium.org/279743002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@21203 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
bmeurer@chromium.org 2014-05-09 08:28:25 +00:00
parent 44af185023
commit 7c45d49861
24 changed files with 401 additions and 0 deletions

View File

@ -305,6 +305,16 @@ void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor(
}
void ArrayShiftStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
static Register registers[] = { r0 };
descriptor->register_param_count_ = 1;
descriptor->register_params_ = registers;
descriptor->deoptimization_handler_ =
Builtins::c_function_address(Builtins::c_ArrayShift);
}
void BinaryOpICStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
static Register registers[] = { r1, r0 };

View File

@ -2258,6 +2258,14 @@ LInstruction* LChunkBuilder::DoTransitionElementsKind(
}
LInstruction* LChunkBuilder::DoArrayShift(HArrayShift* instr) {
LOperand* object = UseFixed(instr->object(), r0);
LOperand* context = UseFixed(instr->context(), cp);
LArrayShift* result = new(zone()) LArrayShift(context, object);
return MarkAsCall(DefineFixed(result, r0), instr, CANNOT_DEOPTIMIZE_EAGERLY);
}
LInstruction* LChunkBuilder::DoTrapAllocationMemento(
HTrapAllocationMemento* instr) {
LOperand* object = UseRegister(instr->object());

View File

@ -26,6 +26,7 @@ class LCodeGen;
V(ArgumentsLength) \
V(ArithmeticD) \
V(ArithmeticT) \
V(ArrayShift) \
V(BitI) \
V(BoundsCheck) \
V(Branch) \
@ -2281,6 +2282,21 @@ class LTransitionElementsKind V8_FINAL : public LTemplateInstruction<0, 2, 1> {
};
class LArrayShift V8_FINAL : public LTemplateInstruction<1, 2, 0> {
public:
LArrayShift(LOperand* context, LOperand* object) {
inputs_[0] = context;
inputs_[1] = object;
}
LOperand* context() const { return inputs_[0]; }
LOperand* object() const { return inputs_[1]; }
DECLARE_CONCRETE_INSTRUCTION(ArrayShift, "array-shift")
DECLARE_HYDROGEN_ACCESSOR(ArrayShift)
};
class LTrapAllocationMemento V8_FINAL : public LTemplateInstruction<0, 1, 1> {
public:
LTrapAllocationMemento(LOperand* object,

View File

@ -4435,6 +4435,15 @@ void LCodeGen::DoTransitionElementsKind(LTransitionElementsKind* instr) {
}
void LCodeGen::DoArrayShift(LArrayShift* instr) {
ASSERT(ToRegister(instr->context()).is(cp));
ASSERT(ToRegister(instr->object()).is(r0));
ASSERT(ToRegister(instr->result()).is(r0));
ArrayShiftStub stub(isolate(), instr->hydrogen()->kind());
CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr);
}
void LCodeGen::DoTrapAllocationMemento(LTrapAllocationMemento* instr) {
Register object = ToRegister(instr->object());
Register temp = ToRegister(instr->temp());

View File

@ -342,6 +342,17 @@ void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor(
}
void ArrayShiftStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
// x0: receiver
static Register registers[] = { x0 };
descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]);
descriptor->register_params_ = registers;
descriptor->deoptimization_handler_ =
Builtins::c_function_address(Builtins::c_ArrayShift);
}
void BinaryOpICStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
// x1: left operand

View File

@ -2509,6 +2509,14 @@ LInstruction* LChunkBuilder::DoTransitionElementsKind(
}
LInstruction* LChunkBuilder::DoArrayShift(HArrayShift* instr) {
LOperand* object = UseFixed(instr->object(), x0);
LOperand* context = UseFixed(instr->context(), cp);
LArrayShift* result = new(zone()) LArrayShift(context, object);
return MarkAsCall(DefineFixed(result, x0), instr, CANNOT_DEOPTIMIZE_EAGERLY);
}
LInstruction* LChunkBuilder::DoTrapAllocationMemento(
HTrapAllocationMemento* instr) {
LOperand* object = UseRegister(instr->object());

View File

@ -28,6 +28,7 @@ class LCodeGen;
V(ArgumentsLength) \
V(ArithmeticD) \
V(ArithmeticT) \
V(ArrayShift) \
V(BitI) \
V(BitS) \
V(BoundsCheck) \
@ -2852,6 +2853,21 @@ class LTransitionElementsKind V8_FINAL : public LTemplateInstruction<0, 2, 2> {
};
class LArrayShift V8_FINAL : public LTemplateInstruction<1, 2, 0> {
public:
LArrayShift(LOperand* context, LOperand* object) {
inputs_[0] = context;
inputs_[1] = object;
}
LOperand* context() const { return inputs_[0]; }
LOperand* object() const { return inputs_[1]; }
DECLARE_CONCRETE_INSTRUCTION(ArrayShift, "array-shift")
DECLARE_HYDROGEN_ACCESSOR(ArrayShift)
};
class LTrapAllocationMemento V8_FINAL : public LTemplateInstruction<0, 1, 2> {
public:
LTrapAllocationMemento(LOperand* object, LOperand* temp1, LOperand* temp2) {

View File

@ -5744,6 +5744,15 @@ void LCodeGen::DoTransitionElementsKind(LTransitionElementsKind* instr) {
}
void LCodeGen::DoArrayShift(LArrayShift* instr) {
ASSERT(ToRegister(instr->context()).is(cp));
ASSERT(ToRegister(instr->object()).is(x0));
ASSERT(ToRegister(instr->result()).is(x0));
ArrayShiftStub stub(isolate(), instr->hydrogen()->kind());
CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr);
}
void LCodeGen::DoTrapAllocationMemento(LTrapAllocationMemento* instr) {
Register object = ToRegister(instr->object());
Register temp1 = ToRegister(instr->temp1());

View File

@ -1113,6 +1113,86 @@ Handle<Code> ElementsTransitionAndStoreStub::GenerateCode() {
}
template <>
HValue* CodeStubGraphBuilder<ArrayShiftStub>::BuildCodeStub() {
HValue* receiver = GetParameter(ArrayShiftStub::kReceiver);
ElementsKind kind = casted_stub()->kind();
// We may use double registers for copying.
if (IsFastDoubleElementsKind(kind)) info()->MarkAsSavesCallerDoubles();
HValue* length = Add<HLoadNamedField>(
receiver, static_cast<HValue*>(NULL),
HObjectAccess::ForArrayLength(kind));
IfBuilder if_lengthiszero(this);
HValue* lengthiszero = if_lengthiszero.If<HCompareNumericAndBranch>(
length, graph()->GetConstant0(), Token::EQ);
if_lengthiszero.Then();
{
Push(graph()->GetConstantUndefined());
}
if_lengthiszero.Else();
{
// Check if array length is below threshold.
IfBuilder if_inline(this);
if_inline.If<HCompareNumericAndBranch>(
length, Add<HConstant>(ArrayShiftStub::kInlineThreshold), Token::LTE);
if_inline.Then();
if_inline.ElseDeopt("Array length exceeds threshold");
if_inline.End();
// We cannot handle copy-on-write backing stores here.
HValue* elements = AddLoadElements(receiver);
if (IsFastSmiOrObjectElementsKind(kind)) {
Add<HCheckMaps>(elements, isolate()->factory()->fixed_array_map());
}
// Remember the result.
Push(AddElementAccess(elements, graph()->GetConstant0(), NULL,
lengthiszero, kind, LOAD));
// Compute the new length.
HValue* new_length = AddUncasted<HSub>(length, graph()->GetConstant1());
new_length->ClearFlag(HValue::kCanOverflow);
// Copy the remaining elements.
LoopBuilder loop(this, context(), LoopBuilder::kPostIncrement);
{
HValue* new_key = loop.BeginBody(
graph()->GetConstant0(), new_length, Token::LT);
HValue* key = AddUncasted<HAdd>(new_key, graph()->GetConstant1());
key->ClearFlag(HValue::kCanOverflow);
HValue* element = AddUncasted<HLoadKeyed>(
elements, key, lengthiszero, kind, ALLOW_RETURN_HOLE);
HStoreKeyed* store = Add<HStoreKeyed>(elements, new_key, element, kind);
store->SetFlag(HValue::kAllowUndefinedAsNaN);
}
loop.EndBody();
// Put a hole at the end.
HValue* hole = IsFastSmiOrObjectElementsKind(kind)
? Add<HConstant>(isolate()->factory()->the_hole_value())
: Add<HConstant>(FixedDoubleArray::hole_nan_as_double());
if (IsFastSmiOrObjectElementsKind(kind)) kind = FAST_HOLEY_ELEMENTS;
Add<HStoreKeyed>(elements, new_length, hole, kind, INITIALIZING_STORE);
// Remember new length.
Add<HStoreNamedField>(
receiver, HObjectAccess::ForArrayLength(kind),
new_length, STORE_TO_INITIALIZED_ENTRY);
}
if_lengthiszero.End();
return Pop();
}
Handle<Code> ArrayShiftStub::GenerateCode() {
return DoGenerateCode(this);
}
void CodeStubGraphBuilderBase::BuildCheckAndInstallOptimizedCode(
HValue* js_function,
HValue* native_context,

View File

@ -16,6 +16,7 @@ namespace internal {
// List of code stubs used on all platforms.
#define CODE_STUB_LIST_ALL_PLATFORMS(V) \
V(ArrayShift) \
V(CallFunction) \
V(CallConstruct) \
V(BinaryOpIC) \
@ -2466,6 +2467,36 @@ class ElementsTransitionAndStoreStub : public HydrogenCodeStub {
};
class ArrayShiftStub V8_FINAL : public HydrogenCodeStub {
public:
ArrayShiftStub(Isolate* isolate, ElementsKind kind)
: HydrogenCodeStub(isolate), kind_(kind) { }
ElementsKind kind() const { return kind_; }
virtual Handle<Code> GenerateCode() V8_OVERRIDE;
virtual void InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) V8_OVERRIDE;
// Inline Array.shift() for arrays up to this length.
static const int kInlineThreshold = 16;
// Parameters accessed via CodeStubGraphBuilder::GetParameter()
static const int kReceiver = 0;
private:
Major MajorKey() { return ArrayShift; }
int NotMissMinorKey() {
return kind_;
}
ElementsKind kind_;
DISALLOW_COPY_AND_ASSIGN(ArrayShiftStub);
};
class StoreArrayLiteralElementStub : public PlatformCodeStub {
public:
explicit StoreArrayLiteralElementStub(Isolate* isolate)

View File

@ -817,6 +817,7 @@ bool HInstruction::CanDeoptimize() {
case HValue::kArgumentsElements:
case HValue::kArgumentsLength:
case HValue::kArgumentsObject:
case HValue::kArrayShift:
case HValue::kBlockEntry:
case HValue::kBoundsCheckBaseIndexInformation:
case HValue::kCallFunction:
@ -3637,6 +3638,12 @@ void HTransitionElementsKind::PrintDataTo(StringStream* stream) {
}
void HArrayShift::PrintDataTo(StringStream* stream) {
object()->PrintNameTo(stream);
stream->Add(" [%s]", ElementsAccessor::ForKind(kind())->name());
}
void HLoadGlobalCell::PrintDataTo(StringStream* stream) {
stream->Add("[%p]", *cell().handle());
if (!details_.IsDontDelete()) stream->Add(" (deleteable)");

View File

@ -50,6 +50,7 @@ class LChunkBuilder;
V(ArgumentsElements) \
V(ArgumentsLength) \
V(ArgumentsObject) \
V(ArrayShift) \
V(Bitwise) \
V(BlockEntry) \
V(BoundsCheck) \
@ -7077,6 +7078,51 @@ class HTransitionElementsKind V8_FINAL : public HTemplateInstruction<2> {
};
class HArrayShift V8_FINAL : public HTemplateInstruction<2> {
public:
static HArrayShift* New(Zone* zone,
HValue* context,
HValue* object,
ElementsKind kind) {
return new(zone) HArrayShift(context, object, kind);
}
virtual Representation RequiredInputRepresentation(int index) V8_OVERRIDE {
return Representation::Tagged();
}
HValue* context() const { return OperandAt(0); }
HValue* object() const { return OperandAt(1); }
ElementsKind kind() const { return kind_; }
virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE;
DECLARE_CONCRETE_INSTRUCTION(ArrayShift);
protected:
virtual bool DataEquals(HValue* other) V8_OVERRIDE {
HArrayShift* that = HArrayShift::cast(other);
return this->kind_ == that->kind_;
}
private:
HArrayShift(HValue* context, HValue* object, ElementsKind kind)
: kind_(kind) {
SetOperandAt(0, context);
SetOperandAt(1, object);
SetChangesFlag(kNewSpacePromotion);
set_representation(Representation::Tagged());
if (IsFastSmiOrObjectElementsKind(kind)) {
SetChangesFlag(kArrayElements);
} else {
SetChangesFlag(kDoubleArrayElements);
}
}
ElementsKind kind_;
};
class HStringAdd V8_FINAL : public HBinaryOperation {
public:
static HInstruction* New(Zone* zone,

View File

@ -7835,6 +7835,36 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall(
ast_context()->ReturnValue(new_size);
return true;
}
case kArrayShift: {
if (receiver_map.is_null()) return false;
if (receiver_map->instance_type() != JS_ARRAY_TYPE) return false;
ElementsKind kind = receiver_map->elements_kind();
if (!IsFastElementsKind(kind)) return false;
if (receiver_map->is_observed()) return false;
ASSERT(receiver_map->is_extensible());
// If there may be elements accessors in the prototype chain, the fast
// inlined version can't be used.
if (receiver_map->DictionaryElementsInPrototypeChainOnly()) return false;
// If there currently can be no elements accessors on the prototype chain,
// it doesn't mean that there won't be any later. Install a full prototype
// chain check to trap element accessors being installed on the prototype
// chain, which would cause elements to go to dictionary mode and result
// in a map change.
BuildCheckPrototypeMaps(
handle(JSObject::cast(receiver_map->prototype()), isolate()),
Handle<JSObject>::null());
Drop(expr->arguments()->length());
HValue* receiver = Pop();
Drop(1); // function
receiver = AddCheckMap(receiver, receiver_map);
HInstruction* result = NewUncasted<HArrayShift>(receiver, kind);
ast_context()->ReturnInstruction(result, expr->id());
return true;
}
default:
// Not yet supported for inlining.
break;

View File

@ -309,6 +309,16 @@ void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor(
}
void ArrayShiftStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
static Register registers[] = { eax };
descriptor->register_param_count_ = 1;
descriptor->register_params_ = registers;
descriptor->deoptimization_handler_ =
Builtins::c_function_address(Builtins::c_ArrayShift);
}
void BinaryOpICStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
static Register registers[] = { edx, eax };

View File

@ -4773,6 +4773,15 @@ void LCodeGen::DoTransitionElementsKind(LTransitionElementsKind* instr) {
}
void LCodeGen::DoArrayShift(LArrayShift* instr) {
ASSERT(ToRegister(instr->context()).is(esi));
ASSERT(ToRegister(instr->object()).is(eax));
ASSERT(ToRegister(instr->result()).is(eax));
ArrayShiftStub stub(isolate(), instr->hydrogen()->kind());
CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr);
}
void LCodeGen::DoStringCharCodeAt(LStringCharCodeAt* instr) {
class DeferredStringCharCodeAt V8_FINAL : public LDeferredCode {
public:

View File

@ -2355,6 +2355,14 @@ LInstruction* LChunkBuilder::DoTransitionElementsKind(
}
LInstruction* LChunkBuilder::DoArrayShift(HArrayShift* instr) {
LOperand* object = UseFixed(instr->object(), eax);
LOperand* context = UseFixed(instr->context(), esi);
LArrayShift* result = new(zone()) LArrayShift(context, object);
return MarkAsCall(DefineFixed(result, eax), instr, CANNOT_DEOPTIMIZE_EAGERLY);
}
LInstruction* LChunkBuilder::DoTrapAllocationMemento(
HTrapAllocationMemento* instr) {
LOperand* object = UseRegister(instr->object());

View File

@ -26,6 +26,7 @@ class LCodeGen;
V(ArgumentsLength) \
V(ArithmeticD) \
V(ArithmeticT) \
V(ArrayShift) \
V(BitI) \
V(BoundsCheck) \
V(Branch) \
@ -2304,6 +2305,21 @@ class LTransitionElementsKind V8_FINAL : public LTemplateInstruction<0, 2, 2> {
};
class LArrayShift V8_FINAL : public LTemplateInstruction<1, 2, 0> {
public:
LArrayShift(LOperand* context, LOperand* object) {
inputs_[0] = context;
inputs_[1] = object;
}
LOperand* context() const { return inputs_[0]; }
LOperand* object() const { return inputs_[1]; }
DECLARE_CONCRETE_INSTRUCTION(ArrayShift, "array-shift")
DECLARE_HYDROGEN_ACCESSOR(ArrayShift)
};
class LTrapAllocationMemento V8_FINAL : public LTemplateInstruction<0, 1, 1> {
public:
LTrapAllocationMemento(LOperand* object,

View File

@ -6810,6 +6810,7 @@ class Script: public Struct {
#define FUNCTIONS_WITH_ID_LIST(V) \
V(Array.prototype, push, ArrayPush) \
V(Array.prototype, pop, ArrayPop) \
V(Array.prototype, shift, ArrayShift) \
V(Function.prototype, apply, FunctionApply) \
V(String.prototype, charCodeAt, StringCharCodeAt) \
V(String.prototype, charAt, StringCharAt) \

View File

@ -305,6 +305,16 @@ void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor(
}
void ArrayShiftStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
static Register registers[] = { rax };
descriptor->register_param_count_ = 1;
descriptor->register_params_ = registers;
descriptor->deoptimization_handler_ =
Builtins::c_function_address(Builtins::c_ArrayShift);
}
void BinaryOpICStub::InitializeInterfaceDescriptor(
CodeStubInterfaceDescriptor* descriptor) {
static Register registers[] = { rdx, rax };

View File

@ -4400,6 +4400,15 @@ void LCodeGen::DoTransitionElementsKind(LTransitionElementsKind* instr) {
}
void LCodeGen::DoArrayShift(LArrayShift* instr) {
ASSERT(ToRegister(instr->context()).is(rsi));
ASSERT(ToRegister(instr->object()).is(rax));
ASSERT(ToRegister(instr->result()).is(rax));
ArrayShiftStub stub(isolate(), instr->hydrogen()->kind());
CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr);
}
void LCodeGen::DoTrapAllocationMemento(LTrapAllocationMemento* instr) {
Register object = ToRegister(instr->object());
Register temp = ToRegister(instr->temp());

View File

@ -2253,6 +2253,14 @@ LInstruction* LChunkBuilder::DoTransitionElementsKind(
}
LInstruction* LChunkBuilder::DoArrayShift(HArrayShift* instr) {
LOperand* object = UseFixed(instr->object(), rax);
LOperand* context = UseFixed(instr->context(), rsi);
LArrayShift* result = new(zone()) LArrayShift(context, object);
return MarkAsCall(DefineFixed(result, rax), instr, CANNOT_DEOPTIMIZE_EAGERLY);
}
LInstruction* LChunkBuilder::DoTrapAllocationMemento(
HTrapAllocationMemento* instr) {
LOperand* object = UseRegister(instr->object());

View File

@ -26,6 +26,7 @@ class LCodeGen;
V(ArgumentsLength) \
V(ArithmeticD) \
V(ArithmeticT) \
V(ArrayShift) \
V(BitI) \
V(BoundsCheck) \
V(Branch) \
@ -2245,6 +2246,21 @@ class LTransitionElementsKind V8_FINAL : public LTemplateInstruction<0, 2, 2> {
};
class LArrayShift V8_FINAL : public LTemplateInstruction<1, 2, 0> {
public:
LArrayShift(LOperand* context, LOperand* object) {
inputs_[0] = context;
inputs_[1] = object;
}
LOperand* context() const { return inputs_[0]; }
LOperand* object() const { return inputs_[1]; }
DECLARE_CONCRETE_INSTRUCTION(ArrayShift, "array-shift")
DECLARE_HYDROGEN_ACCESSOR(ArrayShift)
};
class LTrapAllocationMemento V8_FINAL : public LTemplateInstruction<0, 1, 1> {
public:
LTrapAllocationMemento(LOperand* object,

View File

@ -0,0 +1,18 @@
// Copyright 2014 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
Object.defineProperty(Array.prototype, "1", {
get: function() { return "element 1"; },
set: function(value) { }
});
function test(array) {
array.shift();
return array;
}
assertEquals(["element 1",2], test(["0",,2]));
assertEquals(["element 1",{}], test([{},,{}]));
%OptimizeFunctionOnNextCall(test);
assertEquals(["element 1",0], test([{},,0]));

View File

@ -0,0 +1,15 @@
// Copyright 2014 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
Array.prototype[1] = "element 1";
function test(a) {
a.shift();
return a;
}
assertEquals(["element 1",{}], test([0,,{}]));
assertEquals(["element 1",10], test([9,,10]));
%OptimizeFunctionOnNextCall(test);
assertEquals(["element 1",10], test([9,,10]));