[turbofan] Pseudo-inline 'instanceof'
This patch extends the typed lowering with a specialized version of 'instanceof' that is used if the "class", i.e. the constructor function, is a known constant. Unittests check that replacement occurs as intended. Functional correctness is ensured by extensive unit tests covering instanceof already in the testsuite. TESTS=unittests/JSTypedLoweringTest.{JSInstanceOfSpecializationWithSmiCheck,JSInstanceOfSpecializationWithoutSmiCheck,JSInstanceOfNoSpecialization} Review URL: https://codereview.chromium.org/1407413014 Cr-Commit-Position: refs/heads/master@{#31916}
This commit is contained in:
parent
8e09ee1dba
commit
45787501e5
@ -130,6 +130,14 @@ FieldAccess AccessBuilder::ForMapInstanceType() {
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
FieldAccess AccessBuilder::ForMapPrototype() {
|
||||
FieldAccess access = {kTaggedBase, Map::kPrototypeOffset, Handle<Name>(),
|
||||
Type::TaggedPointer(), kMachAnyTagged};
|
||||
return access;
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
FieldAccess AccessBuilder::ForStringLength() {
|
||||
FieldAccess access = {kTaggedBase, String::kLengthOffset, Handle<Name>(),
|
||||
|
@ -61,6 +61,9 @@ class AccessBuilder final : public AllStatic {
|
||||
// Provides access to Map::instance_type() field.
|
||||
static FieldAccess ForMapInstanceType();
|
||||
|
||||
// Provides access to Map::prototype() field.
|
||||
static FieldAccess ForMapPrototype();
|
||||
|
||||
// Provides access to String::length() field.
|
||||
static FieldAccess ForStringLength();
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/code-factory.h"
|
||||
#include "src/compilation-dependencies.h"
|
||||
#include "src/compiler/access-builder.h"
|
||||
#include "src/compiler/js-graph.h"
|
||||
#include "src/compiler/js-typed-lowering.h"
|
||||
@ -22,8 +23,13 @@ namespace compiler {
|
||||
// - relax effects from generic but not-side-effecting operations
|
||||
|
||||
|
||||
JSTypedLowering::JSTypedLowering(Editor* editor, JSGraph* jsgraph, Zone* zone)
|
||||
: AdvancedReducer(editor), jsgraph_(jsgraph) {
|
||||
JSTypedLowering::JSTypedLowering(Editor* editor,
|
||||
CompilationDependencies* dependencies,
|
||||
Flags flags, JSGraph* jsgraph, Zone* zone)
|
||||
: AdvancedReducer(editor),
|
||||
dependencies_(dependencies),
|
||||
flags_(flags),
|
||||
jsgraph_(jsgraph) {
|
||||
for (size_t k = 0; k < arraysize(shifted_int32_ranges_); ++k) {
|
||||
double min = kMinInt / (1 << k);
|
||||
double max = kMaxInt / (1 << k);
|
||||
@ -1059,6 +1065,117 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSInstanceOf(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSInstanceOf, node->opcode());
|
||||
|
||||
// If deoptimization is disabled, we cannot optimize.
|
||||
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
|
||||
|
||||
JSBinopReduction r(this, node);
|
||||
Node* effect = r.effect();
|
||||
Node* control = r.control();
|
||||
|
||||
if (r.right_type()->IsConstant() &&
|
||||
r.right_type()->AsConstant()->Value()->IsJSFunction()) {
|
||||
Handle<JSFunction> function =
|
||||
Handle<JSFunction>::cast(r.right_type()->AsConstant()->Value());
|
||||
Handle<SharedFunctionInfo> shared(function->shared(), isolate());
|
||||
if (!function->map()->has_non_instance_prototype()) {
|
||||
JSFunction::EnsureHasInitialMap(function);
|
||||
DCHECK(function->has_initial_map());
|
||||
Handle<Map> initial_map(function->initial_map(), isolate());
|
||||
this->dependencies()->AssumeInitialMapCantChange(initial_map);
|
||||
Node* prototype =
|
||||
jsgraph()->Constant(handle(initial_map->prototype(), isolate()));
|
||||
|
||||
Node* if_is_smi = nullptr;
|
||||
Node* e_is_smi = nullptr;
|
||||
// If the left hand side is an object, no smi check is needed.
|
||||
if (r.left_type()->Maybe(Type::TaggedSigned())) {
|
||||
Node* is_smi = graph()->NewNode(simplified()->ObjectIsSmi(), r.left());
|
||||
Node* branch_is_smi = graph()->NewNode(
|
||||
common()->Branch(BranchHint::kFalse), is_smi, control);
|
||||
if_is_smi = graph()->NewNode(common()->IfTrue(), branch_is_smi);
|
||||
e_is_smi = effect;
|
||||
control = graph()->NewNode(common()->IfFalse(), branch_is_smi);
|
||||
}
|
||||
|
||||
Node* object_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
r.left(), effect, control);
|
||||
|
||||
// Loop through the {object}s prototype chain looking for the {prototype}.
|
||||
Node* loop = control =
|
||||
graph()->NewNode(common()->Loop(2), control, control);
|
||||
|
||||
Node* loop_effect = effect =
|
||||
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
|
||||
|
||||
Node* loop_object_map = graph()->NewNode(common()->Phi(kMachAnyTagged, 2),
|
||||
object_map, r.left(), loop);
|
||||
|
||||
|
||||
Node* object_prototype = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForMapPrototype()),
|
||||
loop_object_map, loop_effect, control);
|
||||
|
||||
// Check if object prototype is equal to function prototype.
|
||||
Node* eq_proto =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(r.right_type()),
|
||||
object_prototype, prototype);
|
||||
Node* branch_eq_proto = graph()->NewNode(
|
||||
common()->Branch(BranchHint::kFalse), eq_proto, control);
|
||||
Node* if_eq_proto = graph()->NewNode(common()->IfTrue(), branch_eq_proto);
|
||||
Node* e_eq_proto = effect;
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch_eq_proto);
|
||||
|
||||
// If not, check if object prototype is the null prototype.
|
||||
Node* null_proto =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(r.right_type()),
|
||||
object_prototype, jsgraph()->NullConstant());
|
||||
Node* branch_null_proto = graph()->NewNode(
|
||||
common()->Branch(BranchHint::kFalse), null_proto, control);
|
||||
Node* if_null_proto =
|
||||
graph()->NewNode(common()->IfTrue(), branch_null_proto);
|
||||
Node* e_null_proto = effect;
|
||||
|
||||
control = graph()->NewNode(common()->IfFalse(), branch_null_proto);
|
||||
Node* load_object_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||
object_prototype, effect, control);
|
||||
// Close the loop.
|
||||
loop_effect->ReplaceInput(1, effect);
|
||||
loop_object_map->ReplaceInput(1, load_object_map);
|
||||
loop->ReplaceInput(1, control);
|
||||
|
||||
control =
|
||||
graph()->NewNode(common()->Merge(2), if_eq_proto, if_null_proto);
|
||||
effect = graph()->NewNode(common()->EffectPhi(2), e_eq_proto,
|
||||
e_null_proto, control);
|
||||
|
||||
|
||||
Node* result = graph()->NewNode(common()->Phi(kTypeBool, 2),
|
||||
jsgraph()->TrueConstant(),
|
||||
jsgraph()->FalseConstant(), control);
|
||||
|
||||
if (if_is_smi != nullptr) {
|
||||
DCHECK(e_is_smi != nullptr);
|
||||
control = graph()->NewNode(common()->Merge(2), if_is_smi, control);
|
||||
effect =
|
||||
graph()->NewNode(common()->EffectPhi(2), e_is_smi, effect, control);
|
||||
result = graph()->NewNode(common()->Phi(kTypeBool, 2),
|
||||
jsgraph()->FalseConstant(), result, control);
|
||||
}
|
||||
ReplaceWithValue(node, result, effect, control);
|
||||
return Changed(result);
|
||||
}
|
||||
}
|
||||
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
|
||||
Reduction JSTypedLowering::ReduceJSLoadContext(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
|
||||
ContextAccess const& access = ContextAccessOf(node->op());
|
||||
@ -2028,6 +2145,8 @@ Reduction JSTypedLowering::Reduce(Node* node) {
|
||||
return ReduceJSLoadProperty(node);
|
||||
case IrOpcode::kJSStoreProperty:
|
||||
return ReduceJSStoreProperty(node);
|
||||
case IrOpcode::kJSInstanceOf:
|
||||
return ReduceJSInstanceOf(node);
|
||||
case IrOpcode::kJSLoadContext:
|
||||
return ReduceJSLoadContext(node);
|
||||
case IrOpcode::kJSStoreContext:
|
||||
@ -2175,6 +2294,11 @@ MachineOperatorBuilder* JSTypedLowering::machine() const {
|
||||
return jsgraph()->machine();
|
||||
}
|
||||
|
||||
|
||||
CompilationDependencies* JSTypedLowering::dependencies() const {
|
||||
return dependencies_;
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef V8_COMPILER_JS_TYPED_LOWERING_H_
|
||||
#define V8_COMPILER_JS_TYPED_LOWERING_H_
|
||||
|
||||
#include "src/base/flags.h"
|
||||
#include "src/compiler/graph-reducer.h"
|
||||
#include "src/compiler/opcodes.h"
|
||||
|
||||
@ -12,6 +13,7 @@ namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// Forward declarations.
|
||||
class CompilationDependencies;
|
||||
class Factory;
|
||||
|
||||
|
||||
@ -28,7 +30,15 @@ class SimplifiedOperatorBuilder;
|
||||
// Lowers JS-level operators to simplified operators based on types.
|
||||
class JSTypedLowering final : public AdvancedReducer {
|
||||
public:
|
||||
JSTypedLowering(Editor* editor, JSGraph* jsgraph, Zone* zone);
|
||||
// Flags that control the mode of operation.
|
||||
enum Flag {
|
||||
kNoFlags = 0u,
|
||||
kDeoptimizationEnabled = 1u << 0,
|
||||
};
|
||||
typedef base::Flags<Flag> Flags;
|
||||
|
||||
JSTypedLowering(Editor* editor, CompilationDependencies* dependencies,
|
||||
Flags flags, JSGraph* jsgraph, Zone* zone);
|
||||
~JSTypedLowering() final {}
|
||||
|
||||
Reduction Reduce(Node* node) final;
|
||||
@ -44,6 +54,7 @@ class JSTypedLowering final : public AdvancedReducer {
|
||||
Reduction ReduceJSLoadNamed(Node* node);
|
||||
Reduction ReduceJSLoadProperty(Node* node);
|
||||
Reduction ReduceJSStoreProperty(Node* node);
|
||||
Reduction ReduceJSInstanceOf(Node* node);
|
||||
Reduction ReduceJSLoadContext(Node* node);
|
||||
Reduction ReduceJSStoreContext(Node* node);
|
||||
Reduction ReduceJSEqual(Node* node, bool invert);
|
||||
@ -87,15 +98,21 @@ class JSTypedLowering final : public AdvancedReducer {
|
||||
CommonOperatorBuilder* common() const;
|
||||
SimplifiedOperatorBuilder* simplified() const;
|
||||
MachineOperatorBuilder* machine() const;
|
||||
CompilationDependencies* dependencies() const;
|
||||
Flags flags() const { return flags_; }
|
||||
|
||||
// Limits up to which context allocations are inlined.
|
||||
static const int kFunctionContextAllocationLimit = 16;
|
||||
static const int kBlockContextAllocationLimit = 16;
|
||||
|
||||
CompilationDependencies* dependencies_;
|
||||
Flags flags_;
|
||||
JSGraph* jsgraph_;
|
||||
Type* shifted_int32_ranges_[4];
|
||||
};
|
||||
|
||||
DEFINE_OPERATORS_FOR_FLAGS(JSTypedLowering::Flags)
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -591,7 +591,11 @@ struct TypedLoweringPhase {
|
||||
data->common());
|
||||
LoadElimination load_elimination(&graph_reducer);
|
||||
JSBuiltinReducer builtin_reducer(&graph_reducer, data->jsgraph());
|
||||
JSTypedLowering typed_lowering(&graph_reducer, data->jsgraph(), temp_zone);
|
||||
JSTypedLowering typed_lowering(&graph_reducer, data->info()->dependencies(),
|
||||
data->info()->is_deoptimization_enabled()
|
||||
? JSTypedLowering::kDeoptimizationEnabled
|
||||
: JSTypedLowering::kNoFlags,
|
||||
data->jsgraph(), temp_zone);
|
||||
JSIntrinsicLowering intrinsic_lowering(
|
||||
&graph_reducer, data->jsgraph(),
|
||||
data->info()->is_deoptimization_enabled()
|
||||
|
@ -5,6 +5,7 @@
|
||||
// TODO(jochen): Remove this after the setting is turned on globally.
|
||||
#define V8_IMMINENT_DEPRECATION_WARNINGS
|
||||
|
||||
#include "src/compilation-dependencies.h"
|
||||
#include "src/compiler/js-graph.h"
|
||||
#include "src/compiler/js-typed-lowering.h"
|
||||
#include "src/compiler/machine-operator.h"
|
||||
@ -42,6 +43,7 @@ class JSTypedLoweringTester : public HandleAndZoneScope {
|
||||
machine(main_zone()),
|
||||
simplified(main_zone()),
|
||||
common(main_zone()),
|
||||
deps(main_isolate(), main_zone()),
|
||||
graph(main_zone()),
|
||||
typer(main_isolate(), &graph),
|
||||
context_node(NULL) {
|
||||
@ -57,6 +59,7 @@ class JSTypedLoweringTester : public HandleAndZoneScope {
|
||||
MachineOperatorBuilder machine;
|
||||
SimplifiedOperatorBuilder simplified;
|
||||
CommonOperatorBuilder common;
|
||||
CompilationDependencies deps;
|
||||
Graph graph;
|
||||
Typer typer;
|
||||
Node* context_node;
|
||||
@ -94,7 +97,9 @@ class JSTypedLoweringTester : public HandleAndZoneScope {
|
||||
&machine);
|
||||
// TODO(titzer): mock the GraphReducer here for better unit testing.
|
||||
GraphReducer graph_reducer(main_zone(), &graph);
|
||||
JSTypedLowering reducer(&graph_reducer, &jsgraph, main_zone());
|
||||
JSTypedLowering reducer(&graph_reducer, &deps,
|
||||
JSTypedLowering::kDeoptimizationEnabled, &jsgraph,
|
||||
main_zone());
|
||||
Reduction reduction = reducer.Reduce(node);
|
||||
if (reduction.Changed()) return reduction.replacement();
|
||||
return node;
|
||||
|
@ -74,7 +74,8 @@ const LanguageMode kLanguageModes[] = {SLOPPY, STRICT, STRONG};
|
||||
|
||||
class JSTypedLoweringTest : public TypedGraphTest {
|
||||
public:
|
||||
JSTypedLoweringTest() : TypedGraphTest(3), javascript_(zone()) {}
|
||||
JSTypedLoweringTest()
|
||||
: TypedGraphTest(3), javascript_(zone()), deps_(isolate(), zone()) {}
|
||||
~JSTypedLoweringTest() override {}
|
||||
|
||||
protected:
|
||||
@ -85,7 +86,9 @@ class JSTypedLoweringTest : public TypedGraphTest {
|
||||
&machine);
|
||||
// TODO(titzer): mock the GraphReducer here for better unit testing.
|
||||
GraphReducer graph_reducer(zone(), graph());
|
||||
JSTypedLowering reducer(&graph_reducer, &jsgraph, zone());
|
||||
JSTypedLowering reducer(&graph_reducer, &deps_,
|
||||
JSTypedLowering::kDeoptimizationEnabled, &jsgraph,
|
||||
zone());
|
||||
return reducer.Reduce(node);
|
||||
}
|
||||
|
||||
@ -116,6 +119,7 @@ class JSTypedLoweringTest : public TypedGraphTest {
|
||||
|
||||
private:
|
||||
JSOperatorBuilder javascript_;
|
||||
CompilationDependencies deps_;
|
||||
};
|
||||
|
||||
|
||||
@ -1168,6 +1172,70 @@ TEST_F(JSTypedLoweringTest, JSCreateWithContext) {
|
||||
_));
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// JSInstanceOf
|
||||
// Test that instanceOf is reduced if and only if the right-hand side is a
|
||||
// function constant. Functional correctness is ensured elsewhere.
|
||||
|
||||
|
||||
TEST_F(JSTypedLoweringTest, JSInstanceOfSpecializationWithoutSmiCheck) {
|
||||
Node* const context = Parameter(Type::Any());
|
||||
Node* const frame_state = EmptyFrameState();
|
||||
Node* const effect = graph()->start();
|
||||
Node* const control = graph()->start();
|
||||
|
||||
// Reduce if left-hand side is known to be an object.
|
||||
Node* instanceOf =
|
||||
graph()->NewNode(javascript()->InstanceOf(), Parameter(Type::Object(), 0),
|
||||
HeapConstant(isolate()->object_function()), context,
|
||||
frame_state, effect, control);
|
||||
Node* dummy = graph()->NewNode(javascript()->ToObject(), instanceOf, context,
|
||||
frame_state, effect, control);
|
||||
Reduction r = Reduce(instanceOf);
|
||||
ASSERT_TRUE(r.Changed());
|
||||
ASSERT_EQ(r.replacement(), dummy->InputAt(0));
|
||||
ASSERT_NE(instanceOf, dummy->InputAt(0));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(JSTypedLoweringTest, JSInstanceOfSpecializationWithSmiCheck) {
|
||||
Node* const context = Parameter(Type::Any());
|
||||
Node* const frame_state = EmptyFrameState();
|
||||
Node* const effect = graph()->start();
|
||||
Node* const control = graph()->start();
|
||||
|
||||
// Reduce if left-hand side could be a Smi.
|
||||
Node* instanceOf =
|
||||
graph()->NewNode(javascript()->InstanceOf(), Parameter(Type::Any(), 0),
|
||||
HeapConstant(isolate()->object_function()), context,
|
||||
frame_state, effect, control);
|
||||
Node* dummy = graph()->NewNode(javascript()->ToObject(), instanceOf, context,
|
||||
frame_state, effect, control);
|
||||
Reduction r = Reduce(instanceOf);
|
||||
ASSERT_TRUE(r.Changed());
|
||||
ASSERT_EQ(r.replacement(), dummy->InputAt(0));
|
||||
ASSERT_NE(instanceOf, dummy->InputAt(0));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(JSTypedLoweringTest, JSInstanceOfNoSpecialization) {
|
||||
Node* const context = Parameter(Type::Any());
|
||||
Node* const frame_state = EmptyFrameState();
|
||||
Node* const effect = graph()->start();
|
||||
Node* const control = graph()->start();
|
||||
|
||||
// Do not reduce if right-hand side is not a function constant.
|
||||
Node* instanceOf = graph()->NewNode(
|
||||
javascript()->InstanceOf(), Parameter(Type::Any(), 0),
|
||||
Parameter(Type::Any()), context, frame_state, effect, control);
|
||||
Node* dummy = graph()->NewNode(javascript()->ToObject(), instanceOf, context,
|
||||
frame_state, effect, control);
|
||||
Reduction r = Reduce(instanceOf);
|
||||
ASSERT_FALSE(r.Changed());
|
||||
ASSERT_EQ(instanceOf, dummy->InputAt(0));
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user