Commit 06753c64 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Optimize O.p.hasOwnProperty inside for-in.

Optimize the common pattern

  for (var i in o) {
    if (Object.prototype.hasOwnProperty.call(o, i)) {
      // do something
    }
  }

which is part of the guard-for-in style in ESLint (see the documentation
at https://eslint.org/docs/rules/guard-for-in for details). This pattern
also shows up in React and Ember applications quite a lot (and is tested
by the appropriate Speedometer benchmarks, although not dominating those
benchmarks, since they spent a lot of time in non-TurboFan'ed code).

This improves the forInHasOwnProperty and forInHasOwnPropertySafe micro-
benchmarks in v8:6702, which look like this

  function forInHasOwnProperty(o) {
    var result = 0;
    for (var i in o) {
      if (o.hasOwnProperty(i)) {
        result += 1;
      }
    }
    return result;
  }

  function forInHasOwnPropertySafe(o) {
    var result = 0;
    for (var i in o) {
      if (Object.prototype.hasOwnProperty.call(o, i)) {
        result += 1;
      }
    }
    return result;
  }

by around 4x and allows for additional optimizations in the future, by
also elimiating the megamorphic load when accessing the enumerated
properties.

This changes the interpreter ForInNext bytecode to collect more precise
feedback about the for-in state, which now consists of three individual
states: UNINITIALIZED, MEGAMORPHIC and GENERIC. The MEGAMORPHIC state
means that the ForInNext has only seen objects with a usable enum cache
thus far, whereas GENERIC means that we have seen some slow-mode for..in
objects as well.

R=jarin@chromium.org

Bug: v8:6702
Change-Id: Ibcd75ea9b58c3b4f9219f11bc37eb04a2b985604
Reviewed-on: https://chromium-review.googlesource.com/636964
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47632}
parent d1768823
...@@ -1373,7 +1373,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, ...@@ -1373,7 +1373,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
"__defineSetter__", Builtins::kObjectDefineSetter, 2, "__defineSetter__", Builtins::kObjectDefineSetter, 2,
true); true);
SimpleInstallFunction(isolate->initial_object_prototype(), "hasOwnProperty", SimpleInstallFunction(isolate->initial_object_prototype(), "hasOwnProperty",
Builtins::kObjectHasOwnProperty, 1, true); Builtins::kObjectPrototypeHasOwnProperty, 1, true);
SimpleInstallFunction(isolate->initial_object_prototype(), SimpleInstallFunction(isolate->initial_object_prototype(),
"__lookupGetter__", Builtins::kObjectLookupGetter, 1, "__lookupGetter__", Builtins::kObjectLookupGetter, 1,
true); true);
......
...@@ -707,8 +707,6 @@ namespace internal { ...@@ -707,8 +707,6 @@ namespace internal {
CPP(ObjectGetOwnPropertySymbols) \ CPP(ObjectGetOwnPropertySymbols) \
CPP(ObjectGetPrototypeOf) \ CPP(ObjectGetPrototypeOf) \
CPP(ObjectSetPrototypeOf) \ CPP(ObjectSetPrototypeOf) \
/* ES6 #sec-object.prototype.hasownproperty */ \
TFJ(ObjectHasOwnProperty, 1, kKey) \
CPP(ObjectIs) \ CPP(ObjectIs) \
CPP(ObjectIsExtensible) \ CPP(ObjectIsExtensible) \
CPP(ObjectIsFrozen) \ CPP(ObjectIsFrozen) \
...@@ -721,6 +719,8 @@ namespace internal { ...@@ -721,6 +719,8 @@ namespace internal {
TFJ(ObjectPrototypeToString, 0) \ TFJ(ObjectPrototypeToString, 0) \
/* ES6 #sec-object.prototype.valueof */ \ /* ES6 #sec-object.prototype.valueof */ \
TFJ(ObjectPrototypeValueOf, 0) \ TFJ(ObjectPrototypeValueOf, 0) \
/* ES6 #sec-object.prototype.hasownproperty */ \
TFJ(ObjectPrototypeHasOwnProperty, 1, kKey) \
TFJ(ObjectPrototypeIsPrototypeOf, 1, kValue) \ TFJ(ObjectPrototypeIsPrototypeOf, 1, kValue) \
CPP(ObjectPrototypePropertyIsEnumerable) \ CPP(ObjectPrototypePropertyIsEnumerable) \
CPP(ObjectPrototypeGetProto) \ CPP(ObjectPrototypeGetProto) \
......
...@@ -35,7 +35,7 @@ void ObjectBuiltinsAssembler::ReturnToStringFormat(Node* context, ...@@ -35,7 +35,7 @@ void ObjectBuiltinsAssembler::ReturnToStringFormat(Node* context,
rhs)); rhs));
} }
TF_BUILTIN(ObjectHasOwnProperty, ObjectBuiltinsAssembler) { TF_BUILTIN(ObjectPrototypeHasOwnProperty, ObjectBuiltinsAssembler) {
Node* object = Parameter(Descriptor::kReceiver); Node* object = Parameter(Descriptor::kReceiver);
Node* key = Parameter(Descriptor::kKey); Node* key = Parameter(Descriptor::kKey);
Node* context = Parameter(Descriptor::kContext); Node* context = Parameter(Descriptor::kContext);
......
...@@ -529,7 +529,6 @@ FieldAccess AccessBuilder::ForDescriptorArrayEnumCacheBridgeCache() { ...@@ -529,7 +529,6 @@ FieldAccess AccessBuilder::ForDescriptorArrayEnumCacheBridgeCache() {
return access; return access;
} }
// static // static
FieldAccess AccessBuilder::ForMapBitField() { FieldAccess AccessBuilder::ForMapBitField() {
FieldAccess access = { FieldAccess access = {
...@@ -940,6 +939,13 @@ ElementAccess AccessBuilder::ForFixedDoubleArrayElement() { ...@@ -940,6 +939,13 @@ ElementAccess AccessBuilder::ForFixedDoubleArrayElement() {
return access; return access;
} }
// static
ElementAccess AccessBuilder::ForDescriptorArrayEnumCacheBridgeCacheElement() {
ElementAccess access = {kTaggedBase, FixedArray::kHeaderSize,
Type::InternalizedString(),
MachineType::TaggedPointer(), kPointerWriteBarrier};
return access;
}
// static // static
ElementAccess AccessBuilder::ForTypedArrayElement(ExternalArrayType type, ElementAccess AccessBuilder::ForTypedArrayElement(ExternalArrayType type,
......
...@@ -285,6 +285,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final ...@@ -285,6 +285,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to FixedDoubleArray elements. // Provides access to FixedDoubleArray elements.
static ElementAccess ForFixedDoubleArrayElement(); static ElementAccess ForFixedDoubleArrayElement();
// Provides access to EnumCache elements.
static ElementAccess ForDescriptorArrayEnumCacheBridgeCacheElement();
// Provides access to Fixed{type}TypedArray and External{type}Array elements. // Provides access to Fixed{type}TypedArray and External{type}Array elements.
static ElementAccess ForTypedArrayElement(ExternalArrayType type, static ElementAccess ForTypedArrayElement(ExternalArrayType type,
bool is_external); bool is_external);
......
...@@ -2550,9 +2550,24 @@ void BytecodeGraphBuilder::VisitForInNext() { ...@@ -2550,9 +2550,24 @@ void BytecodeGraphBuilder::VisitForInNext() {
Node* cache_array = environment()->LookupRegister( Node* cache_array = environment()->LookupRegister(
interpreter::Register(catch_reg_pair_index + 1)); interpreter::Register(catch_reg_pair_index + 1));
Node* value = NewNode(javascript()->ForInNext(), receiver, cache_array, // We need to rename the {index} here, as in case of OSR we loose the
cache_type, index); // information that the {index} is always a valid unsigned Smi value.
environment()->BindAccumulator(value, Environment::kAttachFrameState); index = graph()->NewNode(common()->TypeGuard(Type::UnsignedSmall()), index,
environment()->GetControlDependency());
Node* node = nullptr;
FeedbackSlot slot =
feedback_vector()->ToSlot(bytecode_iterator().GetIndexOperand(3));
if (Node* simplified = TryBuildSimplifiedForInNext(receiver, cache_array,
cache_type, index, slot)) {
if (environment() == nullptr) return;
node = simplified;
} else {
node = NewNode(javascript()->ForInNext(), receiver, cache_array, cache_type,
index);
}
environment()->BindAccumulator(node, Environment::kAttachFrameState);
} }
void BytecodeGraphBuilder::VisitForInStep() { void BytecodeGraphBuilder::VisitForInStep() {
...@@ -2828,6 +2843,22 @@ Node* BytecodeGraphBuilder::TryBuildSimplifiedBinaryOp(const Operator* op, ...@@ -2828,6 +2843,22 @@ Node* BytecodeGraphBuilder::TryBuildSimplifiedBinaryOp(const Operator* op,
return nullptr; return nullptr;
} }
Node* BytecodeGraphBuilder::TryBuildSimplifiedForInNext(Node* receiver,
Node* cache_array,
Node* cache_type,
Node* index,
FeedbackSlot slot) {
Node* effect = environment()->GetEffectDependency();
Node* control = environment()->GetControlDependency();
Reduction early_reduction = type_hint_lowering().ReduceForInNextOperation(
receiver, cache_array, cache_type, index, effect, control, slot);
if (early_reduction.Changed()) {
ApplyEarlyReduction(early_reduction);
return early_reduction.replacement();
}
return nullptr;
}
Node* BytecodeGraphBuilder::TryBuildSimplifiedToNumber(Node* value, Node* BytecodeGraphBuilder::TryBuildSimplifiedToNumber(Node* value,
FeedbackSlot slot) { FeedbackSlot slot) {
Node* effect = environment()->GetEffectDependency(); Node* effect = environment()->GetEffectDependency();
......
...@@ -184,6 +184,9 @@ class BytecodeGraphBuilder { ...@@ -184,6 +184,9 @@ class BytecodeGraphBuilder {
// any other invocation of {NewNode} would do. // any other invocation of {NewNode} would do.
Node* TryBuildSimplifiedBinaryOp(const Operator* op, Node* left, Node* right, Node* TryBuildSimplifiedBinaryOp(const Operator* op, Node* left, Node* right,
FeedbackSlot slot); FeedbackSlot slot);
Node* TryBuildSimplifiedForInNext(Node* receiver, Node* cache_array,
Node* cache_type, Node* index,
FeedbackSlot slot);
Node* TryBuildSimplifiedToNumber(Node* input, FeedbackSlot slot); Node* TryBuildSimplifiedToNumber(Node* input, FeedbackSlot slot);
Node* TryBuildSimplifiedCall(const Operator* op, Node* const* args, Node* TryBuildSimplifiedCall(const Operator* op, Node* const* args,
int arg_count, FeedbackSlot slot); int arg_count, FeedbackSlot slot);
......
...@@ -629,6 +629,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node, ...@@ -629,6 +629,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kCheckMaps: case IrOpcode::kCheckMaps:
result = LowerCheckMaps(node, frame_state); result = LowerCheckMaps(node, frame_state);
break; break;
case IrOpcode::kCheckMapValue:
LowerCheckMapValue(node, frame_state);
break;
case IrOpcode::kCheckNumber: case IrOpcode::kCheckNumber:
result = LowerCheckNumber(node, frame_state); result = LowerCheckNumber(node, frame_state);
break; break;
...@@ -1284,6 +1287,19 @@ Node* EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) { ...@@ -1284,6 +1287,19 @@ Node* EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
return value; return value;
} }
void EffectControlLinearizer::LowerCheckMapValue(Node* node,
Node* frame_state) {
Node* value = node->InputAt(0);
Node* map = node->InputAt(1);
// Load the current map of the {value}.
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
// Check if the {value}s map matches the expected {map}.
Node* check = __ WordEqual(value_map, map);
__ DeoptimizeUnless(DeoptimizeReason::kWrongMap, check, frame_state);
}
Node* EffectControlLinearizer::LowerCheckNumber(Node* node, Node* frame_state) { Node* EffectControlLinearizer::LowerCheckNumber(Node* node, Node* frame_state) {
Node* value = node->InputAt(0); Node* value = node->InputAt(0);
......
...@@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer { ...@@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckBounds(Node* node, Node* frame_state); Node* LowerCheckBounds(Node* node, Node* frame_state);
Node* LowerCheckInternalizedString(Node* node, Node* frame_state); Node* LowerCheckInternalizedString(Node* node, Node* frame_state);
Node* LowerCheckMaps(Node* node, Node* frame_state); Node* LowerCheckMaps(Node* node, Node* frame_state);
void LowerCheckMapValue(Node* node, Node* frame_state);
Node* LowerCheckNumber(Node* node, Node* frame_state); Node* LowerCheckNumber(Node* node, Node* frame_state);
Node* LowerCheckReceiver(Node* node, Node* frame_state); Node* LowerCheckReceiver(Node* node, Node* frame_state);
Node* LowerCheckString(Node* node, Node* frame_state); Node* LowerCheckString(Node* node, Node* frame_state);
......
...@@ -381,6 +381,95 @@ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) { ...@@ -381,6 +381,95 @@ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
return ReduceObjectGetPrototype(node, receiver); return ReduceObjectGetPrototype(node, receiver);
} }
// ES #sec-object.prototype.hasownproperty
Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& params = CallParametersOf(node->op());
int const argc = static_cast<int>(params.arity() - 2);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* name = (argc >= 1) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// We can optimize a call to Object.prototype.hasOwnProperty if it's being
// used inside a fast-mode for..in, so for code like this:
//
// for (name in receiver) {
// if (receiver.hasOwnProperty(name)) {
// ...
// }
// }
//
// If the for..in is in fast-mode, we know that the {receiver} has {name}
// as own property, otherwise the enumeration wouldn't include it. The graph
// constructed by the BytecodeGraphBuilder in this case looks like this:
// receiver
// ^ ^
// | |
// | +-+
// | |
// | JSToObject
// | ^ ^
// | | |
// | | +--- JSForInPrepare
// | | ^ ^
// | | | |
// | | +----+ +------+
// | | | |
// | | Projection[0] Projection[1]
// | | (cache_type) (cache_array)
// | | ^ ^
// | | | |
// | | +--------+ |
// | | | |
// | CheckMapValue |
// | ^ |
// | : +------------------+
// | : |
// | LoadElement
// | ^
// +----+ |
// | |
// JSCall[hasOwnProperty]
// We can constant-fold the {node} to True in this case, and insert a
// (potentially redundant) CheckMapValue to guard the fact that the
// {receiver} map didn't change since the initial CheckMapValue, which
// was inserted by the BytecodeGraphBuilder for the ForInNext bytecode.
//
// Also note that it's safe to look through the {JSToObject}, since the
// Object.prototype.hasOwnProperty does an implicit ToObject anyway, and
// these operations are not observable.
if (name->opcode() == IrOpcode::kLoadElement) {
Node* cache_array = NodeProperties::GetValueInput(name, 0);
Node* check_map = NodeProperties::GetEffectInput(name);
if (cache_array->opcode() == IrOpcode::kProjection &&
ProjectionIndexOf(cache_array->op()) == 1 &&
check_map->opcode() == IrOpcode::kCheckMapValue) {
Node* prepare = NodeProperties::GetValueInput(cache_array, 0);
Node* object = NodeProperties::GetValueInput(check_map, 0);
Node* cache_type = NodeProperties::GetValueInput(check_map, 1);
if (cache_type->opcode() == IrOpcode::kProjection &&
prepare->opcode() == IrOpcode::kJSForInPrepare &&
ProjectionIndexOf(cache_type->op()) == 0 &&
NodeProperties::GetValueInput(cache_type, 0) == prepare &&
(object == receiver ||
(object->opcode() == IrOpcode::kJSToObject &&
receiver == NodeProperties::GetValueInput(object, 0)))) {
effect = graph()->NewNode(check_map->op(), object, cache_type, effect,
control);
Node* value = jsgraph()->TrueConstant();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
}
}
return NoChange();
}
// ES #sec-object.prototype.isprototypeof // ES #sec-object.prototype.isprototypeof
Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) { Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
...@@ -1241,6 +1330,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { ...@@ -1241,6 +1330,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceObjectGetPrototypeOf(node); return ReduceObjectGetPrototypeOf(node);
case Builtins::kObjectPrototypeGetProto: case Builtins::kObjectPrototypeGetProto:
return ReduceObjectPrototypeGetProto(node); return ReduceObjectPrototypeGetProto(node);
case Builtins::kObjectPrototypeHasOwnProperty:
return ReduceObjectPrototypeHasOwnProperty(node);
case Builtins::kObjectPrototypeIsPrototypeOf: case Builtins::kObjectPrototypeIsPrototypeOf:
return ReduceObjectPrototypeIsPrototypeOf(node); return ReduceObjectPrototypeIsPrototypeOf(node);
case Builtins::kReflectApply: case Builtins::kReflectApply:
......
...@@ -63,6 +63,7 @@ class JSCallReducer final : public AdvancedReducer { ...@@ -63,6 +63,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceObjectGetPrototype(Node* node, Node* object); Reduction ReduceObjectGetPrototype(Node* node, Node* object);
Reduction ReduceObjectGetPrototypeOf(Node* node); Reduction ReduceObjectGetPrototypeOf(Node* node);
Reduction ReduceObjectPrototypeGetProto(Node* node); Reduction ReduceObjectPrototypeGetProto(Node* node);
Reduction ReduceObjectPrototypeHasOwnProperty(Node* node);
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node); Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
Reduction ReduceReflectApply(Node* node); Reduction ReduceReflectApply(Node* node);
Reduction ReduceReflectConstruct(Node* node); Reduction ReduceReflectConstruct(Node* node);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "src/compiler/js-type-hint-lowering.h" #include "src/compiler/js-type-hint-lowering.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h" #include "src/compiler/js-graph.h"
#include "src/compiler/operator-properties.h" #include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h" #include "src/compiler/simplified-operator.h"
...@@ -240,6 +241,29 @@ Reduction JSTypeHintLowering::ReduceBinaryOperation(const Operator* op, ...@@ -240,6 +241,29 @@ Reduction JSTypeHintLowering::ReduceBinaryOperation(const Operator* op,
return Reduction(); return Reduction();
} }
Reduction JSTypeHintLowering::ReduceForInNextOperation(
Node* receiver, Node* cache_array, Node* cache_type, Node* index,
Node* effect, Node* control, FeedbackSlot slot) const {
DCHECK(!slot.IsInvalid());
ForInICNexus nexus(feedback_vector(), slot);
if (Node* node = TryBuildSoftDeopt(
nexus, effect, control,
DeoptimizeReason::kInsufficientTypeFeedbackForForIn)) {
return Reduction(node);
}
if (!nexus.IsGeneric()) {
effect =
jsgraph()->graph()->NewNode(jsgraph()->simplified()->CheckMapValue(),
receiver, cache_type, effect, control);
Node* node = jsgraph()->graph()->NewNode(
jsgraph()->simplified()->LoadElement(
AccessBuilder::ForDescriptorArrayEnumCacheBridgeCacheElement()),
cache_array, index, effect, control);
return Reduction(node);
}
return Reduction();
}
Reduction JSTypeHintLowering::ReduceToNumberOperation(Node* input, Node* effect, Reduction JSTypeHintLowering::ReduceToNumberOperation(Node* input, Node* effect,
Node* control, Node* control,
FeedbackSlot slot) const { FeedbackSlot slot) const {
......
...@@ -55,6 +55,12 @@ class JSTypeHintLowering { ...@@ -55,6 +55,12 @@ class JSTypeHintLowering {
Node* effect, Node* control, Node* effect, Node* control,
FeedbackSlot slot) const; FeedbackSlot slot) const;
// Potential reduction to ForInNext operations
Reduction ReduceForInNextOperation(Node* receiver, Node* cache_array,
Node* cache_type, Node* index,
Node* effect, Node* control,
FeedbackSlot slot) const;
// Potential reduction to ToNumber operations // Potential reduction to ToNumber operations
Reduction ReduceToNumberOperation(Node* value, Node* effect, Node* control, Reduction ReduceToNumberOperation(Node* value, Node* effect, Node* control,
FeedbackSlot slot) const; FeedbackSlot slot) const;
......
...@@ -1908,15 +1908,6 @@ Reduction JSTypedLowering::ReduceJSForInNext(Node* node) { ...@@ -1908,15 +1908,6 @@ Reduction JSTypedLowering::ReduceJSForInNext(Node* node) {
Node* effect = NodeProperties::GetEffectInput(node); Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node); Node* control = NodeProperties::GetControlInput(node);
// We know that the {index} is in Unsigned32 range here, otherwise executing
// the JSForInNext wouldn't be valid. Unfortunately due to OSR and generators
// this is not always reflected in the types, hence we might need to rename
// the {index} here.
if (!NodeProperties::GetType(index)->Is(Type::Unsigned32())) {
index = graph()->NewNode(common()->TypeGuard(Type::Unsigned32()), index,
control);
}
// Load the next {key} from the {cache_array}. // Load the next {key} from the {cache_array}.
Node* key = effect = graph()->NewNode( Node* key = effect = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()), simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
......
...@@ -328,6 +328,7 @@ ...@@ -328,6 +328,7 @@
V(CheckBounds) \ V(CheckBounds) \
V(CheckIf) \ V(CheckIf) \
V(CheckMaps) \ V(CheckMaps) \
V(CheckMapValue) \
V(CheckNumber) \ V(CheckNumber) \
V(CheckInternalizedString) \ V(CheckInternalizedString) \
V(CheckReceiver) \ V(CheckReceiver) \
......
...@@ -2765,6 +2765,7 @@ class RepresentationSelector { ...@@ -2765,6 +2765,7 @@ class RepresentationSelector {
// Eliminate MapGuard nodes here. // Eliminate MapGuard nodes here.
return VisitUnused(node); return VisitUnused(node);
case IrOpcode::kCheckMaps: case IrOpcode::kCheckMaps:
case IrOpcode::kCheckMapValue:
case IrOpcode::kTransitionElementsKind: { case IrOpcode::kTransitionElementsKind: {
VisitInputs(node); VisitInputs(node);
return SetOutput(node, MachineRepresentation::kNone); return SetOutput(node, MachineRepresentation::kNone);
......
...@@ -691,6 +691,16 @@ struct SimplifiedOperatorGlobalCache final { ...@@ -691,6 +691,16 @@ struct SimplifiedOperatorGlobalCache final {
CheckedTruncateTaggedToWord32Operator<CheckTaggedInputMode::kNumberOrOddball> CheckedTruncateTaggedToWord32Operator<CheckTaggedInputMode::kNumberOrOddball>
kCheckedTruncateTaggedToWord32NumberOrOddballOperator; kCheckedTruncateTaggedToWord32NumberOrOddballOperator;
struct CheckMapValueOperator final : public Operator {
CheckMapValueOperator()
: Operator( // --
IrOpcode::kCheckMapValue, // opcode
Operator::kNoThrow | Operator::kNoWrite, // flags
"CheckMapValue", // name
2, 1, 1, 0, 1, 0) {} // counts
};
CheckMapValueOperator kCheckMapValue;
template <CheckFloat64HoleMode kMode> template <CheckFloat64HoleMode kMode>
struct CheckFloat64HoleNaNOperator final struct CheckFloat64HoleNaNOperator final
: public Operator1<CheckFloat64HoleMode> { : public Operator1<CheckFloat64HoleMode> {
...@@ -769,6 +779,7 @@ GET_FROM_CACHE(ArrayBufferWasNeutered) ...@@ -769,6 +779,7 @@ GET_FROM_CACHE(ArrayBufferWasNeutered)
GET_FROM_CACHE(ArgumentsFrame) GET_FROM_CACHE(ArgumentsFrame)
GET_FROM_CACHE(LookupHashStorageIndex) GET_FROM_CACHE(LookupHashStorageIndex)
GET_FROM_CACHE(LoadHashMapValue) GET_FROM_CACHE(LoadHashMapValue)
GET_FROM_CACHE(CheckMapValue)
GET_FROM_CACHE(NewUnmappedArgumentsElements) GET_FROM_CACHE(NewUnmappedArgumentsElements)
#undef GET_FROM_CACHE #undef GET_FROM_CACHE
......
...@@ -395,6 +395,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final ...@@ -395,6 +395,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* CheckIf(); const Operator* CheckIf();
const Operator* CheckBounds(); const Operator* CheckBounds();
const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>); const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>);
const Operator* CheckMapValue();
const Operator* CheckHeapObject(); const Operator* CheckHeapObject();
const Operator* CheckInternalizedString(); const Operator* CheckInternalizedString();
......
...@@ -1862,6 +1862,11 @@ Type* Typer::Visitor::TypeCheckMaps(Node* node) { ...@@ -1862,6 +1862,11 @@ Type* Typer::Visitor::TypeCheckMaps(Node* node) {
UNREACHABLE(); UNREACHABLE();
} }
Type* Typer::Visitor::TypeCheckMapValue(Node* node) {
UNREACHABLE();
return nullptr;
}
Type* Typer::Visitor::TypeCheckNumber(Node* node) { Type* Typer::Visitor::TypeCheckNumber(Node* node) {
return typer_->operation_typer_.CheckNumber(Operand(node, 0)); return typer_->operation_typer_.CheckNumber(Operand(node, 0));
} }
......
...@@ -1187,11 +1187,12 @@ void Verifier::Visitor::Check(Node* node) { ...@@ -1187,11 +1187,12 @@ void Verifier::Visitor::Check(Node* node) {
CheckTypeIs(node, Type::InternalizedString()); CheckTypeIs(node, Type::InternalizedString());
break; break;
case IrOpcode::kCheckMaps: case IrOpcode::kCheckMaps:
// (Any, Internal, ..., Internal) -> Any
CheckValueInputIs(node, 0, Type::Any()); CheckValueInputIs(node, 0, Type::Any());
for (int i = 1; i < node->op()->ValueInputCount(); ++i) { CheckNotTyped(node);
CheckValueInputIs(node, i, Type::Internal()); break;
} case IrOpcode::kCheckMapValue:
CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::Any());
CheckNotTyped(node); CheckNotTyped(node);
break; break;
case IrOpcode::kCheckNumber: case IrOpcode::kCheckNumber:
......
...@@ -477,7 +477,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) { ...@@ -477,7 +477,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
case Builtins::kObjectIsSealed: case Builtins::kObjectIsSealed:
case Builtins::kObjectPrototypeValueOf: case Builtins::kObjectPrototypeValueOf:
case Builtins::kObjectValues: case Builtins::kObjectValues:
case Builtins::kObjectHasOwnProperty: case Builtins::kObjectPrototypeHasOwnProperty:
case Builtins::kObjectPrototypeIsPrototypeOf: case Builtins::kObjectPrototypeIsPrototypeOf:
case Builtins::kObjectPrototypePropertyIsEnumerable: case Builtins::kObjectPrototypePropertyIsEnumerable:
case Builtins::kObjectPrototypeToString: case Builtins::kObjectPrototypeToString:
......
...@@ -10,73 +10,74 @@ ...@@ -10,73 +10,74 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
#define DEOPTIMIZE_REASON_LIST(V) \ #define DEOPTIMIZE_REASON_LIST(V) \
V(AccessCheck, "Access check needed") \ V(AccessCheck, "Access check needed") \
V(NoReason, "no reason") \ V(NoReason, "no reason") \
V(ConstantGlobalVariableAssignment, "Constant global variable assignment") \ V(ConstantGlobalVariableAssignment, "Constant global variable assignment") \
V(ConversionOverflow, "conversion overflow") \ V(ConversionOverflow, "conversion overflow") \
V(DivisionByZero, "division by zero") \ V(DivisionByZero, "division by zero") \
V(ExpectedHeapNumber, "Expected heap number") \ V(ExpectedHeapNumber, "Expected heap number") \
V(ExpectedSmi, "Expected smi") \ V(ExpectedSmi, "Expected smi") \
V(ForcedDeoptToRuntime, "Forced deopt to runtime") \ V(ForcedDeoptToRuntime, "Forced deopt to runtime") \
V(Hole, "hole") \ V(Hole, "hole") \
V(InstanceMigrationFailed, "instance migration failed") \ V(InstanceMigrationFailed, "instance migration failed") \
V(InsufficientTypeFeedbackForCall, "Insufficient type feedback for call") \ V(InsufficientTypeFeedbackForCall, "Insufficient type feedback for call") \
V(InsufficientTypeFeedbackForCallWithArguments, \ V(InsufficientTypeFeedbackForCallWithArguments, \
"Insufficient type feedback for call with arguments") \ "Insufficient type feedback for call with arguments") \
V(InsufficientTypeFeedbackForConstruct, \ V(InsufficientTypeFeedbackForConstruct, \
"Insufficient type feedback for construct") \ "Insufficient type feedback for construct") \
V(FastPathFailed, "Falling off the fast path") \ V(FastPathFailed, "Falling off the fast path") \
V(InsufficientTypeFeedbackForCombinedTypeOfBinaryOperation, \ V(InsufficientTypeFeedbackForForIn, "Insufficient type feedback for for-in") \
"Insufficient type feedback for combined type of binary operation") \ V(InsufficientTypeFeedbackForCombinedTypeOfBinaryOperation, \
V(InsufficientTypeFeedbackForGenericNamedAccess, \ "Insufficient type feedback for combined type of binary operation") \
"Insufficient type feedback for generic named access") \ V(InsufficientTypeFeedbackForGenericNamedAccess, \
V(InsufficientTypeFeedbackForGenericKeyedAccess, \ "Insufficient type feedback for generic named access") \
"Insufficient type feedback for generic keyed access") \ V(InsufficientTypeFeedbackForGenericKeyedAccess, \
V(InsufficientTypeFeedbackForLHSOfBinaryOperation, \ "Insufficient type feedback for generic keyed access") \
"Insufficient type feedback for LHS of binary operation") \ V(InsufficientTypeFeedbackForLHSOfBinaryOperation, \
V(InsufficientTypeFeedbackForRHSOfBinaryOperation, \ "Insufficient type feedback for LHS of binary operation") \
"Insufficient type feedback for RHS of binary operation") \ V(InsufficientTypeFeedbackForRHSOfBinaryOperation, \
V(KeyIsNegative, "key is negative") \ "Insufficient type feedback for RHS of binary operation") \
V(LostPrecision, "lost precision") \ V(KeyIsNegative, "key is negative") \
V(LostPrecisionOrNaN, "lost precision or NaN") \ V(LostPrecision, "lost precision") \
V(MementoFound, "memento found") \ V(LostPrecisionOrNaN, "lost precision or NaN") \
V(MinusZero, "minus zero") \ V(MementoFound, "memento found") \
V(NaN, "NaN") \ V(MinusZero, "minus zero") \
V(NegativeKeyEncountered, "Negative key encountered") \ V(NaN, "NaN") \
V(NegativeValue, "negative value") \ V(NegativeKeyEncountered, "Negative key encountered") \
V(NoCache, "no cache") \ V(NegativeValue, "negative value") \
V(NotAHeapNumber, "not a heap number") \ V(NoCache, "no cache") \
V(NotAHeapNumberUndefined, "not a heap number/undefined") \ V(NotAHeapNumber, "not a heap number") \
V(NotAJavaScriptObject, "not a JavaScript object") \ V(NotAHeapNumberUndefined, "not a heap number/undefined") \
V(NotANumberOrOddball, "not a Number or Oddball") \ V(NotAJavaScriptObject, "not a JavaScript object") \
V(NotASmi, "not a Smi") \ V(NotANumberOrOddball, "not a Number or Oddball") \
V(NotASymbol, "not a Symbol") \ V(NotASmi, "not a Smi") \
V(OutOfBounds, "out of bounds") \ V(NotASymbol, "not a Symbol") \
V(OutsideOfRange, "Outside of range") \ V(OutOfBounds, "out of bounds") \
V(Overflow, "overflow") \ V(OutsideOfRange, "Outside of range") \
V(Proxy, "proxy") \ V(Overflow, "overflow") \
V(ReceiverWasAGlobalObject, "receiver was a global object") \ V(Proxy, "proxy") \
V(Smi, "Smi") \ V(ReceiverWasAGlobalObject, "receiver was a global object") \
V(TooManyArguments, "too many arguments") \ V(Smi, "Smi") \
V(TracingElementsTransitions, "Tracing elements transitions") \ V(TooManyArguments, "too many arguments") \
V(TypeMismatchBetweenFeedbackAndConstant, \ V(TracingElementsTransitions, "Tracing elements transitions") \
"Type mismatch between feedback and constant") \ V(TypeMismatchBetweenFeedbackAndConstant, \
V(UnexpectedCellContentsInConstantGlobalStore, \ "Type mismatch between feedback and constant") \
"Unexpected cell contents in constant global store") \ V(UnexpectedCellContentsInConstantGlobalStore, \
V(UnexpectedCellContentsInGlobalStore, \ "Unexpected cell contents in constant global store") \
"Unexpected cell contents in global store") \ V(UnexpectedCellContentsInGlobalStore, \
V(UnexpectedObject, "unexpected object") \ "Unexpected cell contents in global store") \
V(UnexpectedRHSOfBinaryOperation, "Unexpected RHS of binary operation") \ V(UnexpectedObject, "unexpected object") \
V(UnknownMapInPolymorphicAccess, "Unknown map in polymorphic access") \ V(UnexpectedRHSOfBinaryOperation, "Unexpected RHS of binary operation") \
V(UnknownMapInPolymorphicCall, "Unknown map in polymorphic call") \ V(UnknownMapInPolymorphicAccess, "Unknown map in polymorphic access") \
V(UnknownMapInPolymorphicElementAccess, \ V(UnknownMapInPolymorphicCall, "Unknown map in polymorphic call") \
"Unknown map in polymorphic element access") \ V(UnknownMapInPolymorphicElementAccess, \
V(UnknownMap, "Unknown map") \ "Unknown map in polymorphic element access") \
V(ValueMismatch, "value mismatch") \ V(UnknownMap, "Unknown map") \
V(WrongInstanceType, "wrong instance type") \ V(ValueMismatch, "value mismatch") \
V(WrongMap, "wrong map") \ V(WrongInstanceType, "wrong instance type") \
V(UndefinedOrNullInForIn, "null or undefined in for-in") \ V(WrongMap, "wrong map") \
V(UndefinedOrNullInForIn, "null or undefined in for-in") \
V(UndefinedOrNullInToObject, "null or undefined in ToObject") V(UndefinedOrNullInToObject, "null or undefined in ToObject")
enum class DeoptimizeReason : uint8_t { enum class DeoptimizeReason : uint8_t {
......
...@@ -318,6 +318,10 @@ Handle<Symbol> FeedbackVector::UninitializedSentinel(Isolate* isolate) { ...@@ -318,6 +318,10 @@ Handle<Symbol> FeedbackVector::UninitializedSentinel(Isolate* isolate) {
return isolate->factory()->uninitialized_symbol(); return isolate->factory()->uninitialized_symbol();
} }
Handle<Symbol> FeedbackVector::GenericSentinel(Isolate* isolate) {
return isolate->factory()->generic_symbol();
}
Handle<Symbol> FeedbackVector::MegamorphicSentinel(Isolate* isolate) { Handle<Symbol> FeedbackVector::MegamorphicSentinel(Isolate* isolate) {
return isolate->factory()->megamorphic_symbol(); return isolate->factory()->megamorphic_symbol();
} }
......
...@@ -922,6 +922,16 @@ CompareOperationHint CompareICNexus::GetCompareOperationFeedback() const { ...@@ -922,6 +922,16 @@ CompareOperationHint CompareICNexus::GetCompareOperationFeedback() const {
return CompareOperationHintFromFeedback(feedback); return CompareOperationHintFromFeedback(feedback);
} }
InlineCacheState ForInICNexus::StateFromFeedback() const {
Object* feedback = GetFeedback();
if (feedback == *FeedbackVector::UninitializedSentinel(GetIsolate())) {
return UNINITIALIZED;
} else if (feedback == *FeedbackVector::MegamorphicSentinel(GetIsolate())) {
return MEGAMORPHIC;
}
return GENERIC;
}
InlineCacheState StoreDataPropertyInLiteralICNexus::StateFromFeedback() const { InlineCacheState StoreDataPropertyInLiteralICNexus::StateFromFeedback() const {
Isolate* isolate = GetIsolate(); Isolate* isolate = GetIsolate();
Object* feedback = GetFeedback(); Object* feedback = GetFeedback();
......
...@@ -229,6 +229,9 @@ class FeedbackVector : public HeapObject { ...@@ -229,6 +229,9 @@ class FeedbackVector : public HeapObject {
// The object that indicates an uninitialized cache. // The object that indicates an uninitialized cache.
static inline Handle<Symbol> UninitializedSentinel(Isolate* isolate); static inline Handle<Symbol> UninitializedSentinel(Isolate* isolate);
// The object that indicates a generic state.
static inline Handle<Symbol> GenericSentinel(Isolate* isolate);
// The object that indicates a megamorphic state. // The object that indicates a megamorphic state.
static inline Handle<Symbol> MegamorphicSentinel(Isolate* isolate); static inline Handle<Symbol> MegamorphicSentinel(Isolate* isolate);
...@@ -539,6 +542,8 @@ class FeedbackNexus { ...@@ -539,6 +542,8 @@ class FeedbackNexus {
InlineCacheState ic_state() const { return StateFromFeedback(); } InlineCacheState ic_state() const { return StateFromFeedback(); }
bool IsUninitialized() const { return StateFromFeedback() == UNINITIALIZED; } bool IsUninitialized() const { return StateFromFeedback() == UNINITIALIZED; }
bool IsMegamorphic() const { return StateFromFeedback() == MEGAMORPHIC; }
bool IsGeneric() const { return StateFromFeedback() == GENERIC; }
Map* FindFirstMap() const { Map* FindFirstMap() const {
MapHandles maps; MapHandles maps;
ExtractMaps(&maps); ExtractMaps(&maps);
...@@ -787,6 +792,25 @@ class CompareICNexus final : public FeedbackNexus { ...@@ -787,6 +792,25 @@ class CompareICNexus final : public FeedbackNexus {
} }
}; };
class ForInICNexus final : public FeedbackNexus {
public:
ForInICNexus(Handle<FeedbackVector> vector, FeedbackSlot slot)
: FeedbackNexus(vector, slot) {
DCHECK_EQ(FeedbackSlotKind::kGeneral, vector->GetKind(slot));
}
InlineCacheState StateFromFeedback() const final;
int ExtractMaps(MapHandles* maps) const final { return 0; }
MaybeHandle<Object> FindHandlerForMap(Handle<Map> map) const final {
return MaybeHandle<Code>();
}
bool FindHandlers(List<Handle<Object>>* code_list,
int length = -1) const final {
return length == 0;
}
};
class StoreDataPropertyInLiteralICNexus : public FeedbackNexus { class StoreDataPropertyInLiteralICNexus : public FeedbackNexus {
public: public:
StoreDataPropertyInLiteralICNexus(Handle<FeedbackVector> vector, StoreDataPropertyInLiteralICNexus(Handle<FeedbackVector> vector,
......
...@@ -220,6 +220,7 @@ ...@@ -220,6 +220,7 @@
V(error_script_symbol) \ V(error_script_symbol) \
V(error_start_pos_symbol) \ V(error_start_pos_symbol) \
V(frozen_symbol) \ V(frozen_symbol) \
V(generic_symbol) \
V(home_object_symbol) \ V(home_object_symbol) \
V(intl_initialized_marker_symbol) \ V(intl_initialized_marker_symbol) \
V(intl_pattern_symbol) \ V(intl_pattern_symbol) \
......
...@@ -3142,6 +3142,8 @@ IGNITION_HANDLER(ForInNext, InterpreterAssembler) { ...@@ -3142,6 +3142,8 @@ IGNITION_HANDLER(ForInNext, InterpreterAssembler) {
Node* cache_type = LoadRegister(cache_type_reg); Node* cache_type = LoadRegister(cache_type_reg);
Node* cache_array_reg = NextRegister(cache_type_reg); Node* cache_array_reg = NextRegister(cache_type_reg);
Node* cache_array = LoadRegister(cache_array_reg); Node* cache_array = LoadRegister(cache_array_reg);
Node* vector_index = BytecodeOperandIdx(3);
Node* feedback_vector = LoadFeedbackVector();
// Load the next key from the enumeration array. // Load the next key from the enumeration array.
Node* key = LoadFixedArrayElement(cache_array, index, 0, Node* key = LoadFixedArrayElement(cache_array, index, 0,
...@@ -3153,18 +3155,33 @@ IGNITION_HANDLER(ForInNext, InterpreterAssembler) { ...@@ -3153,18 +3155,33 @@ IGNITION_HANDLER(ForInNext, InterpreterAssembler) {
Branch(WordEqual(receiver_map, cache_type), &if_fast, &if_slow); Branch(WordEqual(receiver_map, cache_type), &if_fast, &if_slow);
BIND(&if_fast); BIND(&if_fast);
{ {
// Check if we need to transition to megamorphic state.
Node* feedback_value =
LoadFeedbackVectorSlot(feedback_vector, vector_index);
Node* uninitialized_sentinel =
HeapConstant(FeedbackVector::UninitializedSentinel(isolate()));
Label if_done(this);
GotoIfNot(WordEqual(feedback_value, uninitialized_sentinel), &if_done);
{
// Transition to megamorphic state.
Node* megamorphic_sentinel =
HeapConstant(FeedbackVector::MegamorphicSentinel(isolate()));
StoreFeedbackVectorSlot(feedback_vector, vector_index,
megamorphic_sentinel, SKIP_WRITE_BARRIER);
}
Goto(&if_done);
// Enum cache in use for {receiver}, the {key} is definitely valid. // Enum cache in use for {receiver}, the {key} is definitely valid.
BIND(&if_done);
SetAccumulator(key); SetAccumulator(key);
Dispatch(); Dispatch();
} }
BIND(&if_slow); BIND(&if_slow);
{ {
// Record the fact that we hit the for-in slow path. // Record the fact that we hit the for-in slow path.
Node* vector_index = BytecodeOperandIdx(3); Node* generic_sentinel =
Node* feedback_vector = LoadFeedbackVector(); HeapConstant(FeedbackVector::GenericSentinel(isolate()));
Node* megamorphic_sentinel = StoreFeedbackVectorSlot(feedback_vector, vector_index, generic_sentinel,
HeapConstant(FeedbackVector::MegamorphicSentinel(isolate()));
StoreFeedbackVectorSlot(feedback_vector, vector_index, megamorphic_sentinel,
SKIP_WRITE_BARRIER); SKIP_WRITE_BARRIER);
// Need to filter the {key} for the {receiver}. // Need to filter the {key} for the {receiver}.
......
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