Use a stub in crankshaft for grow store arrays.

We were deopting without learning anything.

BUG=v8:3417
LOG=N
R=danno@chromium.org

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

Cr-Commit-Position: refs/heads/master@{#25392}
This commit is contained in:
Michael Stanton 2014-11-18 13:32:05 +01:00
parent ab4d2b11c8
commit d40204f84c
19 changed files with 334 additions and 29 deletions

View File

@ -54,6 +54,12 @@ const Register MathPowIntegerDescriptor::exponent() {
}
// IC register specifications
const Register GrowArrayElementsDescriptor::ObjectRegister() { return r0; }
const Register GrowArrayElementsDescriptor::KeyRegister() { return r1; }
const Register GrowArrayElementsDescriptor::CapacityRegister() { return r2; }
void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
Register registers[] = {cp, r2};
data->Initialize(arraysize(registers), registers, NULL);

View File

@ -4015,8 +4015,12 @@ void LCodeGen::DoCallWithDescriptor(LCallWithDescriptor* instr) {
generator.BeforeCall(__ CallSize(code, RelocInfo::CODE_TARGET));
PlatformInterfaceDescriptor* call_descriptor =
instr->descriptor().platform_specific_descriptor();
__ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None(), al,
call_descriptor->storage_mode());
if (call_descriptor != NULL) {
__ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None(), al,
call_descriptor->storage_mode());
} else {
__ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None(), al);
}
} else {
DCHECK(instr->target()->IsRegister());
Register target = ToRegister(instr->target());

View File

@ -60,6 +60,12 @@ const Register MathPowTaggedDescriptor::exponent() { return x11; }
const Register MathPowIntegerDescriptor::exponent() { return x12; }
// IC register specifications
const Register GrowArrayElementsDescriptor::ObjectRegister() { return x0; }
const Register GrowArrayElementsDescriptor::KeyRegister() { return x1; }
const Register GrowArrayElementsDescriptor::CapacityRegister() { return x2; }
void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
// cp: context
// x2: function info

View File

@ -547,6 +547,31 @@ Handle<Code> StoreScriptContextFieldStub::GenerateCode() {
}
template <>
HValue* CodeStubGraphBuilder<GrowArrayElementsStub>::BuildCodeStub() {
HValue* object = GetParameter(GrowArrayElementsDescriptor::kObjectIndex);
HValue* key = GetParameter(GrowArrayElementsDescriptor::kKeyIndex);
HValue* current_capacity =
GetParameter(GrowArrayElementsDescriptor::kCapacityIndex);
ElementsKind kind = casted_stub()->elements_kind();
HValue* elements = AddLoadElements(object);
HValue* length =
casted_stub()->is_js_array()
? Add<HLoadNamedField>(object, static_cast<HValue*>(NULL),
HObjectAccess::ForArrayLength(kind))
: current_capacity;
return BuildCheckAndGrowElementsCapacity(object, elements, kind, length,
current_capacity, key);
}
Handle<Code> GrowArrayElementsStub::GenerateCode() {
return DoGenerateCode(this);
}
template <>
HValue* CodeStubGraphBuilder<LoadFastElementStub>::BuildCodeStub() {
HInstruction* load = BuildUncheckedMonomorphicElementAccess(

View File

@ -733,6 +733,13 @@ void StringAddStub::InitializeDescriptor(CodeStubDescriptor* descriptor) {
}
void GrowArrayElementsStub::InitializeDescriptor(
CodeStubDescriptor* descriptor) {
descriptor->Initialize(
Runtime::FunctionForId(Runtime::kGrowArrayElements)->entry);
}
void CreateAllocationSiteStub::GenerateAheadOfTime(Isolate* isolate) {
CreateAllocationSiteStub stub(isolate);
stub.GetCode();

View File

@ -65,6 +65,7 @@ namespace internal {
V(FastCloneShallowObject) \
V(FastNewClosure) \
V(FastNewContext) \
V(GrowArrayElements) \
V(InternalArrayNArgumentsConstructor) \
V(InternalArrayNoArgumentConstructor) \
V(InternalArraySingleArgumentConstructor) \
@ -662,6 +663,29 @@ class CreateAllocationSiteStub : public HydrogenCodeStub {
};
class GrowArrayElementsStub : public HydrogenCodeStub {
public:
GrowArrayElementsStub(Isolate* isolate, bool is_js_array, ElementsKind kind)
: HydrogenCodeStub(isolate) {
set_sub_minor_key(ElementsKindBits::encode(kind) |
IsJsArrayBits::encode(is_js_array));
}
ElementsKind elements_kind() const {
return ElementsKindBits::decode(sub_minor_key());
}
bool is_js_array() const { return IsJsArrayBits::decode(sub_minor_key()); }
private:
class ElementsKindBits : public BitField<ElementsKind, 0, 8> {};
class IsJsArrayBits : public BitField<bool, ElementsKindBits::kNext, 1> {};
DEFINE_CALL_INTERFACE_DESCRIPTOR(GrowArrayElements);
DEFINE_HYDROGEN_CODE_STUB(GrowArrayElements, HydrogenCodeStub);
};
class InstanceofStub: public PlatformCodeStub {
public:
enum Flags {

View File

@ -783,6 +783,9 @@ void HInstruction::Verify() {
bool HInstruction::CanDeoptimize() {
// TODO(titzer): make this a virtual method?
// TODO(all): Some of these may be incorrect, since any method that can
// collect can provoke lazy deoptimization. Methods like CallNew can
// certainly do that.
switch (opcode()) {
case HValue::kAbnormalExit:
case HValue::kAccessArgumentsAt:
@ -796,7 +799,6 @@ bool HInstruction::CanDeoptimize() {
case HValue::kCallNew:
case HValue::kCallNewArray:
case HValue::kCallStub:
case HValue::kCallWithDescriptor:
case HValue::kCapturedObject:
case HValue::kClassOfTestAndBranch:
case HValue::kCompareGeneric:
@ -863,6 +865,7 @@ bool HInstruction::CanDeoptimize() {
case HValue::kBranch:
case HValue::kCallJSFunction:
case HValue::kCallRuntime:
case HValue::kCallWithDescriptor:
case HValue::kChange:
case HValue::kCheckHeapObject:
case HValue::kCheckInstanceType:

View File

@ -1297,6 +1297,20 @@ HValue* HGraphBuilder::BuildWrapReceiver(HValue* object, HValue* function) {
}
HValue* HGraphBuilder::BuildCheckAndGrowElementsCapacity(
HValue* object, HValue* elements, ElementsKind kind, HValue* length,
HValue* capacity, HValue* key) {
HValue* max_gap = Add<HConstant>(static_cast<int32_t>(JSObject::kMaxGap));
HValue* max_capacity = AddUncasted<HAdd>(capacity, max_gap);
Add<HBoundsCheck>(key, max_capacity);
HValue* new_capacity = BuildNewElementsCapacity(key);
HValue* new_elements = BuildGrowElementsCapacity(object, elements, kind, kind,
length, new_capacity);
return new_elements;
}
HValue* HGraphBuilder::BuildCheckForCapacityGrow(
HValue* object,
HValue* elements,
@ -1320,17 +1334,26 @@ HValue* HGraphBuilder::BuildCheckForCapacityGrow(
Token::GTE);
capacity_checker.Then();
HValue* max_gap = Add<HConstant>(static_cast<int32_t>(JSObject::kMaxGap));
HValue* max_capacity = AddUncasted<HAdd>(current_capacity, max_gap);
// BuildCheckAndGrowElementsCapacity could de-opt without profitable feedback,
// therefore we defer calling it to a stub in optimized functions. It is
// okay to call directly in a code stub though, because a bailout to the
// runtime is tolerable in the corner cases.
if (top_info()->IsStub()) {
environment()->Push(BuildCheckAndGrowElementsCapacity(
object, elements, kind, length, current_capacity, key));
} else {
GrowArrayElementsStub stub(isolate(), is_js_array, kind);
GrowArrayElementsDescriptor descriptor(isolate());
HConstant* target = Add<HConstant>(stub.GetCode());
HValue* op_vals[] = {context(), object, key, current_capacity};
HValue* new_elements = Add<HCallWithDescriptor>(
target, 0, descriptor, Vector<HValue*>(op_vals, 4));
// If the object changed to a dictionary, GrowArrayElements will return a
// smi to signal that deopt is required.
Add<HCheckHeapObject>(new_elements);
environment()->Push(new_elements);
}
Add<HBoundsCheck>(key, max_capacity);
HValue* new_capacity = BuildNewElementsCapacity(key);
HValue* new_elements = BuildGrowElementsCapacity(object, elements,
kind, kind, length,
new_capacity);
environment()->Push(new_elements);
capacity_checker.Else();
environment()->Push(elements);

View File

@ -1335,6 +1335,10 @@ class HGraphBuilder {
bool is_js_array,
PropertyAccessType access_type);
HValue* BuildCheckAndGrowElementsCapacity(HValue* object, HValue* elements,
ElementsKind kind, HValue* length,
HValue* capacity, HValue* key);
HValue* BuildCopyElementsOnWrite(HValue* object,
HValue* elements,
ElementsKind kind,

View File

@ -56,6 +56,11 @@ const Register MathPowIntegerDescriptor::exponent() {
}
const Register GrowArrayElementsDescriptor::ObjectRegister() { return eax; }
const Register GrowArrayElementsDescriptor::KeyRegister() { return ebx; }
const Register GrowArrayElementsDescriptor::CapacityRegister() { return ecx; }
void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
Register registers[] = {esi, ebx};
data->Initialize(arraysize(registers), registers, NULL);

View File

@ -146,5 +146,12 @@ void ContextOnlyDescriptor::Initialize(CallInterfaceDescriptorData* data) {
data->Initialize(arraysize(registers), registers, NULL);
}
void GrowArrayElementsDescriptor::Initialize(
CallInterfaceDescriptorData* data) {
Register registers[] = {ContextRegister(), ObjectRegister(), KeyRegister(),
CapacityRegister()};
data->Initialize(arraysize(registers), registers, NULL);
}
}
} // namespace v8::internal

View File

@ -53,7 +53,8 @@ class PlatformInterfaceDescriptor;
V(StoreArrayLiteralElement) \
V(MathPowTagged) \
V(MathPowInteger) \
V(ContextOnly)
V(ContextOnly) \
V(GrowArrayElements)
class CallInterfaceDescriptorData {
@ -488,6 +489,17 @@ class ContextOnlyDescriptor : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR(ContextOnlyDescriptor, CallInterfaceDescriptor)
};
class GrowArrayElementsDescriptor : public CallInterfaceDescriptor {
public:
DECLARE_DESCRIPTOR(GrowArrayElementsDescriptor, CallInterfaceDescriptor)
enum RegisterInfo { kObjectIndex, kKeyIndex, kCapacityIndex };
static const Register ObjectRegister();
static const Register KeyRegister();
static const Register CapacityRegister();
};
#undef DECLARE_DESCRIPTOR

View File

@ -1827,6 +1827,12 @@ void JSObject::EnsureCanContainElements(Handle<JSObject> object,
}
bool JSObject::WouldConvertToSlowElements(Handle<Object> key) {
uint32_t index;
return key->ToArrayIndex(&index) && WouldConvertToSlowElements(index);
}
void JSObject::SetMapAndElements(Handle<JSObject> object,
Handle<Map> new_map,
Handle<FixedArrayBase> value) {

View File

@ -11267,10 +11267,8 @@ void Code::Disassemble(const char* name, std::ostream& os) { // NOLINT
#endif // ENABLE_DISASSEMBLER
Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
Handle<JSObject> object,
int capacity,
int length,
Handle<FixedArray> JSObject::SetFastElementsCapacity(
Handle<JSObject> object, int capacity,
SetFastElementsCapacitySmiMode smi_mode) {
// We should never end in here with a pixel or external array.
DCHECK(!object->HasExternalArrayElements());
@ -11322,6 +11320,15 @@ Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
object->GetElementsKind(), new_elements);
}
return new_elements;
}
Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
Handle<JSObject> object, int capacity, int length,
SetFastElementsCapacitySmiMode smi_mode) {
Handle<FixedArray> new_elements =
SetFastElementsCapacity(object, capacity, smi_mode);
if (object->IsJSArray()) {
Handle<JSArray>::cast(object)->set_length(Smi::FromInt(length));
}
@ -11329,9 +11336,8 @@ Handle<FixedArray> JSObject::SetFastElementsCapacityAndLength(
}
void JSObject::SetFastDoubleElementsCapacityAndLength(Handle<JSObject> object,
int capacity,
int length) {
Handle<FixedArrayBase> JSObject::SetFastDoubleElementsCapacity(
Handle<JSObject> object, int capacity) {
// We should never end in here with a pixel or external array.
DCHECK(!object->HasExternalArrayElements());
@ -11361,9 +11367,18 @@ void JSObject::SetFastDoubleElementsCapacityAndLength(Handle<JSObject> object,
object->GetElementsKind(), elems);
}
return elems;
}
Handle<FixedArrayBase> JSObject::SetFastDoubleElementsCapacityAndLength(
Handle<JSObject> object, int capacity, int length) {
Handle<FixedArrayBase> new_elements =
SetFastDoubleElementsCapacity(object, capacity);
if (object->IsJSArray()) {
Handle<JSArray>::cast(object)->set_length(Smi::FromInt(length));
}
return new_elements;
}
@ -13288,9 +13303,8 @@ void JSObject::GetElementsCapacityAndUsage(int* capacity, int* used) {
}
bool JSObject::WouldConvertToSlowElements(Handle<Object> key) {
uint32_t index;
if (HasFastElements() && key->ToArrayIndex(&index)) {
bool JSObject::WouldConvertToSlowElements(uint32_t index) {
if (HasFastElements()) {
Handle<FixedArrayBase> backing_store(FixedArrayBase::cast(elements()));
uint32_t capacity = static_cast<uint32_t>(backing_store->length());
if (index >= capacity) {

View File

@ -1906,7 +1906,8 @@ class JSObject: public JSReceiver {
// Would we convert a fast elements array to dictionary mode given
// an access at key?
bool WouldConvertToSlowElements(Handle<Object> key);
bool WouldConvertToSlowElements(uint32_t index);
inline bool WouldConvertToSlowElements(Handle<Object> key);
// Do we want to keep the elements in fast case when increasing the
// capacity?
bool ShouldConvertToSlowElements(int new_capacity);
@ -1966,6 +1967,12 @@ class JSObject: public JSReceiver {
kDontAllowSmiElements
};
static Handle<FixedArray> SetFastElementsCapacity(
Handle<JSObject> object, int capacity,
SetFastElementsCapacitySmiMode smi_mode);
static Handle<FixedArrayBase> SetFastDoubleElementsCapacity(
Handle<JSObject> object, int capacity);
// Replace the elements' backing store with fast elements of the given
// capacity. Update the length for JSArrays. Returns the new backing
// store.
@ -1974,10 +1981,8 @@ class JSObject: public JSReceiver {
int capacity,
int length,
SetFastElementsCapacitySmiMode smi_mode);
static void SetFastDoubleElementsCapacityAndLength(
Handle<JSObject> object,
int capacity,
int length);
static Handle<FixedArrayBase> SetFastDoubleElementsCapacityAndLength(
Handle<JSObject> object, int capacity, int length);
// Lookup interceptors are used for handling properties controlled by host
// objects.

View File

@ -1055,6 +1055,44 @@ RUNTIME_FUNCTION(Runtime_NormalizeElements) {
}
// GrowArrayElements returns a sentinel Smi if the object was normalized.
RUNTIME_FUNCTION(Runtime_GrowArrayElements) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
CONVERT_SMI_ARG_CHECKED(key, 1);
if (key < 0) {
return object->elements();
}
uint32_t capacity = static_cast<uint32_t>(object->elements()->length());
uint32_t index = static_cast<uint32_t>(key);
if (index >= capacity) {
if (object->WouldConvertToSlowElements(index)) {
JSObject::NormalizeElements(object);
return Smi::FromInt(0);
}
uint32_t new_capacity = JSObject::NewElementsCapacity(index + 1);
ElementsKind kind = object->GetElementsKind();
if (IsFastDoubleElementsKind(kind)) {
JSObject::SetFastDoubleElementsCapacity(object, new_capacity);
} else {
JSObject::SetFastElementsCapacitySmiMode set_capacity_mode =
object->HasFastSmiElements() ? JSObject::kAllowSmiElements
: JSObject::kDontAllowSmiElements;
JSObject::SetFastElementsCapacity(object, new_capacity,
set_capacity_mode);
}
}
// On success, return the fixed array elements.
return object->elements();
}
RUNTIME_FUNCTION(Runtime_HasComplexElements) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);

View File

@ -272,6 +272,7 @@ namespace internal {
F(MoveArrayContents, 2, 1) \
F(EstimateNumberOfElements, 1, 1) \
F(NormalizeElements, 1, 1) \
F(GrowArrayElements, 3, 1) \
F(HasComplexElements, 1, 1) \
\
/* Getters and Setters */ \

View File

@ -56,6 +56,12 @@ const Register MathPowIntegerDescriptor::exponent() {
}
// IC register specifications
const Register GrowArrayElementsDescriptor::ObjectRegister() { return rax; }
const Register GrowArrayElementsDescriptor::KeyRegister() { return rbx; }
const Register GrowArrayElementsDescriptor::CapacityRegister() { return rcx; }
void FastNewClosureDescriptor::Initialize(CallInterfaceDescriptorData* data) {
Register registers[] = {rsi, rbx};
data->Initialize(arraysize(registers), registers, NULL);

View File

@ -0,0 +1,109 @@
// Copyright 2010 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax --noverify-heap --noenable-slow-asserts
// --noverify-heap and --noenable-slow-asserts are set because the test is too
// slow with it on.
// Ensure that keyed stores work, and optimized functions learn if the
// store required change to dictionary mode. Verify that stores that grow
// the array into large object space don't cause a deopt.
(function() {
var a = [];
function foo(a, i) {
a[i] = 5.3;
}
foo(a, 1);
foo(a, 2);
foo(a, 3);
%OptimizeFunctionOnNextCall(foo);
a[3] = 0;
foo(a, 3);
assertEquals(a[3], 5.3);
foo(a, 50000);
assertUnoptimized(foo);
assertTrue(%HasDictionaryElements(a));
var b = [];
foo(b, 1);
foo(b, 2);
// Put b in dictionary mode.
b[10000] = 5;
assertTrue(%HasDictionaryElements(b));
foo(b, 3);
%OptimizeFunctionOnNextCall(foo);
foo(b, 50000);
assertOptimized(foo);
assertTrue(%HasDictionaryElements(b));
// Clearing feedback for the StoreIC in foo is important for runs with
// flag --stress-opt.
%ClearFunctionTypeFeedback(foo);
})();
(function() {
var a = new Array(10);
function foo2(a, i) {
a[i] = 50;
}
// The KeyedStoreIC will learn GROW_MODE.
foo2(a, 10);
foo2(a, 12);
foo2(a, 31);
%OptimizeFunctionOnNextCall(foo2);
foo2(a, 40);
// This test is way too slow without crankshaft.
if (4 != %GetOptimizationStatus(foo2)) {
assertOptimized(foo2);
assertTrue(%HasFastSmiElements(a));
// Grow a large array into large object space through the keyed store
// without deoptimizing. Grow by 10s. If we set elements too sparsely, the
// array will convert to dictionary mode.
a = new Array(99999);
assertTrue(%HasFastSmiElements(a));
for (var i = 0; i < 263000; i += 10) {
foo2(a, i);
}
// Verify that we are over 1 page in size, and foo2 remains optimized.
// This means we've smoothly transitioned to allocating in large object
// space.
assertTrue(%HasFastSmiElements(a));
assertTrue(a.length * 4 > (1024 * 1024));
assertOptimized(foo2);
}
%ClearFunctionTypeFeedback(foo2);
})();