Commit b8f47504 authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Add inlined Array.prototype.pop support.

This adds a very first version of inlined Array.prototype.pop into
TurboFan optimized code. We currently limit the inlining to fast
object or smi elements, until the unclear situation around hole NaNs
is resolved and we have a clear semantics inside the compiler.

It's also probably overly defensive in when it's safe to inline
the call to Array.prototype.pop, but we can always extend that
later once we have sufficient trust in the implementation and see
an actual need to extend it.

BUG=v8:2229,v8:3952,v8:5267
R=epertoso@chromium.org

Review-Url: https://codereview.chromium.org/2239703002
Cr-Commit-Position: refs/heads/master@{#38578}
parent 51e9d5ad
......@@ -612,6 +612,29 @@ ElementAccess AccessBuilder::ForFixedArrayElement() {
return access;
}
// static
ElementAccess AccessBuilder::ForFixedArrayElement(ElementsKind kind) {
ElementAccess access = {kTaggedBase, FixedArray::kHeaderSize, Type::Any(),
MachineType::AnyTagged(), kFullWriteBarrier};
switch (kind) {
case FAST_SMI_ELEMENTS:
access.type = TypeCache::Get().kSmi;
access.write_barrier_kind = kNoWriteBarrier;
break;
case FAST_HOLEY_SMI_ELEMENTS:
access.type = TypeCache::Get().kHoleySmi;
break;
case FAST_ELEMENTS:
access.type = Type::NonInternal();
break;
case FAST_HOLEY_ELEMENTS:
break;
default:
UNREACHABLE();
break;
}
return access;
}
// static
ElementAccess AccessBuilder::ForFixedDoubleArrayElement() {
......
......@@ -6,6 +6,7 @@
#define V8_COMPILER_ACCESS_BUILDER_H_
#include "src/compiler/simplified-operator.h"
#include "src/elements-kind.h"
namespace v8 {
namespace internal {
......@@ -191,6 +192,7 @@ class AccessBuilder final : public AllStatic {
// Provides access to FixedArray elements.
static ElementAccess ForFixedArrayElement();
static ElementAccess ForFixedArrayElement(ElementsKind kind);
// Provides access to FixedDoubleArray elements.
static ElementAccess ForFixedDoubleArrayElement();
......
......@@ -4,6 +4,7 @@
#include "src/compiler/js-builtin-reducer.h"
#include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-matchers.h"
......@@ -93,11 +94,157 @@ class JSCallReduction {
Node* node_;
};
JSBuiltinReducer::JSBuiltinReducer(Editor* editor, JSGraph* jsgraph)
JSBuiltinReducer::JSBuiltinReducer(Editor* editor, JSGraph* jsgraph,
Flags flags,
CompilationDependencies* dependencies)
: AdvancedReducer(editor),
dependencies_(dependencies),
flags_(flags),
jsgraph_(jsgraph),
type_cache_(TypeCache::Get()) {}
namespace {
MaybeHandle<Map> GetMapWitness(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
// Check if the {node} is dominated by a CheckMaps with a single map
// for the {receiver}, and if so use that map for the lowering below.
for (Node* dominator = effect;;) {
if (dominator->opcode() == IrOpcode::kCheckMaps &&
dominator->InputAt(0) == receiver) {
if (dominator->op()->ValueInputCount() == 2) {
HeapObjectMatcher m(dominator->InputAt(1));
if (m.HasValue()) return Handle<Map>::cast(m.Value());
}
return MaybeHandle<Map>();
}
if (dominator->op()->EffectInputCount() != 1) {
// Didn't find any appropriate CheckMaps node.
return MaybeHandle<Map>();
}
dominator = NodeProperties::GetEffectInput(dominator);
}
}
// TODO(turbofan): This was copied from Crankshaft, might be too restrictive.
bool IsReadOnlyLengthDescriptor(Handle<Map> jsarray_map) {
DCHECK(!jsarray_map->is_dictionary_map());
Isolate* isolate = jsarray_map->GetIsolate();
Handle<Name> length_string = isolate->factory()->length_string();
DescriptorArray* descriptors = jsarray_map->instance_descriptors();
int number =
descriptors->SearchWithCache(isolate, *length_string, *jsarray_map);
DCHECK_NE(DescriptorArray::kNotFound, number);
return descriptors->GetDetails(number).IsReadOnly();
}
// TODO(turbofan): This was copied from Crankshaft, might be too restrictive.
bool CanInlineArrayResizeOperation(Handle<Map> receiver_map) {
Isolate* const isolate = receiver_map->GetIsolate();
if (!receiver_map->prototype()->IsJSArray()) return false;
Handle<JSArray> receiver_prototype(JSArray::cast(receiver_map->prototype()),
isolate);
return receiver_map->instance_type() == JS_ARRAY_TYPE &&
IsFastElementsKind(receiver_map->elements_kind()) &&
!receiver_map->is_dictionary_map() && receiver_map->is_extensible() &&
(!receiver_map->is_prototype_map() || receiver_map->is_stable()) &&
receiver_prototype->map()->is_stable() &&
isolate->IsFastArrayConstructorPrototypeChainIntact() &&
isolate->IsAnyInitialArrayPrototype(receiver_prototype) &&
!IsReadOnlyLengthDescriptor(receiver_map);
}
} // namespace
// ES6 section 22.1.3.17 Array.prototype.pop ( )
Reduction JSBuiltinReducer::ReduceArrayPop(Node* node) {
Handle<Map> receiver_map;
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// TODO(turbofan): Extend this to also handle fast (holey) double elements
// once we got the hole NaN mess sorted out in TurboFan/V8.
if (GetMapWitness(node).ToHandle(&receiver_map) &&
CanInlineArrayResizeOperation(receiver_map) &&
IsFastSmiOrObjectElementsKind(receiver_map->elements_kind())) {
// Install code dependencies on the {receiver} prototype maps and the
// global array protector cell.
dependencies()->AssumePropertyCell(factory()->array_protector());
dependencies()->AssumePrototypeMapsStable(receiver_map);
// Load the "length" property of the {receiver}.
Node* length = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())),
receiver, effect, control);
// Check if the {receiver} has any elements.
Node* check = graph()->NewNode(simplified()->NumberEqual(), length,
jsgraph()->ZeroConstant());
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue = jsgraph()->UndefinedConstant();
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse;
{
// Load the elements backing store from the {receiver}.
Node* elements = efalse = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
receiver, efalse, if_false);
// Ensure that we aren't popping from a copy-on-write backing store.
elements = efalse =
graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver,
elements, efalse, if_false);
// Compute the new {length}.
length = graph()->NewNode(simplified()->NumberSubtract(), length,
jsgraph()->OneConstant());
// Store the new {length} to the {receiver}.
efalse = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())),
receiver, length, efalse, if_false);
// Load the last entry from the {elements}.
vfalse = efalse = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(
receiver_map->elements_kind())),
elements, length, efalse, if_false);
// Store a hole to the element we just removed from the {receiver}.
efalse = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(
GetHoleyElementsKind(receiver_map->elements_kind()))),
elements, length, jsgraph()->TheHoleConstant(), efalse, if_false);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
// Convert the hole to undefined. Do this last, so that we can optimize
// conversion operator via some smart strength reduction in many cases.
if (IsFastHoleyElementsKind(receiver_map->elements_kind())) {
value =
graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value);
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
return NoChange();
}
// ES6 section 20.2.2.1 Math.abs ( x )
Reduction JSBuiltinReducer::ReduceMathAbs(Node* node) {
JSCallReduction r(node);
......@@ -749,6 +896,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
// Dispatch according to the BuiltinFunctionId if present.
if (!r.HasBuiltinFunctionId()) return NoChange();
switch (r.GetBuiltinFunctionId()) {
case kArrayPop:
return ReduceArrayPop(node);
case kMathAbs:
reduction = ReduceMathAbs(node);
break;
......@@ -903,6 +1052,7 @@ Node* JSBuiltinReducer::ToUint32(Node* input) {
Graph* JSBuiltinReducer::graph() const { return jsgraph()->graph(); }
Factory* JSBuiltinReducer::factory() const { return isolate()->factory(); }
Isolate* JSBuiltinReducer::isolate() const { return jsgraph()->isolate(); }
......
......@@ -5,12 +5,15 @@
#ifndef V8_COMPILER_JS_BUILTIN_REDUCER_H_
#define V8_COMPILER_JS_BUILTIN_REDUCER_H_
#include "src/base/flags.h"
#include "src/compiler/graph-reducer.h"
namespace v8 {
namespace internal {
// Forward declarations.
class CompilationDependencies;
class Factory;
class TypeCache;
namespace compiler {
......@@ -24,12 +27,21 @@ class SimplifiedOperatorBuilder;
class JSBuiltinReducer final : public AdvancedReducer {
public:
explicit JSBuiltinReducer(Editor* editor, JSGraph* jsgraph);
// Flags that control the mode of operation.
enum Flag {
kNoFlags = 0u,
kDeoptimizationEnabled = 1u << 0,
};
typedef base::Flags<Flag> Flags;
JSBuiltinReducer(Editor* editor, JSGraph* jsgraph, Flags flags,
CompilationDependencies* dependencies);
~JSBuiltinReducer() final {}
Reduction Reduce(Node* node) final;
private:
Reduction ReduceArrayPop(Node* node);
Reduction ReduceMathAbs(Node* node);
Reduction ReduceMathAcos(Node* node);
Reduction ReduceMathAcosh(Node* node);
......@@ -74,16 +86,23 @@ class JSBuiltinReducer final : public AdvancedReducer {
Node* ToNumber(Node* value);
Node* ToUint32(Node* value);
Flags flags() const { return flags_; }
Graph* graph() const;
Factory* factory() const;
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const;
CommonOperatorBuilder* common() const;
SimplifiedOperatorBuilder* simplified() const;
CompilationDependencies* dependencies() const { return dependencies_; }
CompilationDependencies* const dependencies_;
Flags const flags_;
JSGraph* const jsgraph_;
TypeCache const& type_cache_;
};
DEFINE_OPERATORS_FOR_FLAGS(JSBuiltinReducer::Flags)
} // namespace compiler
} // namespace internal
} // namespace v8
......
......@@ -900,7 +900,12 @@ struct TypedLoweringPhase {
JSGraphReducer graph_reducer(data->jsgraph(), temp_zone);
DeadCodeElimination dead_code_elimination(&graph_reducer, data->graph(),
data->common());
JSBuiltinReducer builtin_reducer(&graph_reducer, data->jsgraph());
JSBuiltinReducer builtin_reducer(
&graph_reducer, data->jsgraph(),
data->info()->is_deoptimization_enabled()
? JSBuiltinReducer::kDeoptimizationEnabled
: JSBuiltinReducer::kNoFlags,
data->info()->dependencies());
MaybeHandle<LiteralsArray> literals_array =
data->info()->is_native_context_specializing()
? handle(data->info()->closure()->literals(), data->isolate())
......
......@@ -38,6 +38,7 @@ class TypeCache final {
Type* const kFloat64 = CreateNative(Type::Number(), Type::UntaggedFloat64());
Type* const kSmi = CreateNative(Type::SignedSmall(), Type::TaggedSigned());
Type* const kHoleySmi = Type::Union(kSmi, Type::Hole(), zone());
Type* const kHeapNumber = CreateNative(Type::Number(), Type::TaggedPointer());
Type* const kSingletonZero = CreateRange(0.0, 0.0);
......
// Copyright 2016 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
function foo(a) {
return a.pop();
}
var a = new Array(4);
assertEquals(undefined, foo(a));
assertEquals(undefined, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(a));
Object.prototype.__defineGetter__(0, function() { return 1; });
assertEquals(1, foo(a));
// Copyright 2016 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
var pop = Array.prototype.pop;
function foo(a) {
a.length;
return pop.call(a);
}
var a = new Array(4);
var o = {}
o.__defineGetter__(0, function() { return 1; });
assertEquals(undefined, foo(a));
assertEquals(undefined, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(a));
Array.prototype.__proto__ = o;
assertEquals(1, foo(a));
// Copyright 2016 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
(function() {
function foo(a) { return a.pop(); }
var x = {};
var a = [x,x,];
assertEquals(x, foo(a));
assertEquals(x, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(a));
assertOptimized(foo);
})();
(function() {
function foo(a) { return a.pop(); }
var x = 0;
var a = [x,x,];
assertEquals(x, foo(a));
assertEquals(x, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(a));
assertOptimized(foo);
})();
(function() {
function foo(a) { return a.pop(); }
var x = 0;
var a = [x,x,x];
assertEquals(x, foo(a));
assertEquals(x, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(x, foo(a));
assertOptimized(foo);
})();
(function() {
function foo(a) { return a.pop(); }
var x = {};
var a = [x,x,x];
assertEquals(x, foo(a));
assertEquals(x, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(x, foo(a));
assertOptimized(foo);
})();
(function() {
function foo(a) { return a.pop(); }
var a = [,,];
assertEquals(undefined, foo(a));
assertEquals(undefined, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(undefined, foo(a));
assertOptimized(foo);
})();
(function() {
var pop = Array.prototype.pop;
function foo(a) { return a.pop(); }
var a = [1, 2, 3];
assertEquals(3, foo(a));
assertEquals(2, foo(a));
%OptimizeFunctionOnNextCall(foo);
assertEquals(1, foo(a));
assertOptimized(foo);
})();
......@@ -33,7 +33,8 @@ class JSBuiltinReducerTest : public TypedGraphTest {
&machine);
// TODO(titzer): mock the GraphReducer here for better unit testing.
GraphReducer graph_reducer(zone(), graph());
JSBuiltinReducer reducer(&graph_reducer, &jsgraph);
JSBuiltinReducer reducer(&graph_reducer, &jsgraph,
JSBuiltinReducer::kNoFlags, nullptr);
return reducer.Reduce(node);
}
......
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