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,
"__defineSetter__", Builtins::kObjectDefineSetter, 2,
true);
SimpleInstallFunction(isolate->initial_object_prototype(), "hasOwnProperty",
Builtins::kObjectHasOwnProperty, 1, true);
Builtins::kObjectPrototypeHasOwnProperty, 1, true);
SimpleInstallFunction(isolate->initial_object_prototype(),
"__lookupGetter__", Builtins::kObjectLookupGetter, 1,
true);
......
......@@ -707,8 +707,6 @@ namespace internal {
CPP(ObjectGetOwnPropertySymbols) \
CPP(ObjectGetPrototypeOf) \
CPP(ObjectSetPrototypeOf) \
/* ES6 #sec-object.prototype.hasownproperty */ \
TFJ(ObjectHasOwnProperty, 1, kKey) \
CPP(ObjectIs) \
CPP(ObjectIsExtensible) \
CPP(ObjectIsFrozen) \
......@@ -721,6 +719,8 @@ namespace internal {
TFJ(ObjectPrototypeToString, 0) \
/* ES6 #sec-object.prototype.valueof */ \
TFJ(ObjectPrototypeValueOf, 0) \
/* ES6 #sec-object.prototype.hasownproperty */ \
TFJ(ObjectPrototypeHasOwnProperty, 1, kKey) \
TFJ(ObjectPrototypeIsPrototypeOf, 1, kValue) \
CPP(ObjectPrototypePropertyIsEnumerable) \
CPP(ObjectPrototypeGetProto) \
......
......@@ -35,7 +35,7 @@ void ObjectBuiltinsAssembler::ReturnToStringFormat(Node* context,
rhs));
}
TF_BUILTIN(ObjectHasOwnProperty, ObjectBuiltinsAssembler) {
TF_BUILTIN(ObjectPrototypeHasOwnProperty, ObjectBuiltinsAssembler) {
Node* object = Parameter(Descriptor::kReceiver);
Node* key = Parameter(Descriptor::kKey);
Node* context = Parameter(Descriptor::kContext);
......
......@@ -529,7 +529,6 @@ FieldAccess AccessBuilder::ForDescriptorArrayEnumCacheBridgeCache() {
return access;
}
// static
FieldAccess AccessBuilder::ForMapBitField() {
FieldAccess access = {
......@@ -940,6 +939,13 @@ ElementAccess AccessBuilder::ForFixedDoubleArrayElement() {
return access;
}
// static
ElementAccess AccessBuilder::ForDescriptorArrayEnumCacheBridgeCacheElement() {
ElementAccess access = {kTaggedBase, FixedArray::kHeaderSize,
Type::InternalizedString(),
MachineType::TaggedPointer(), kPointerWriteBarrier};
return access;
}
// static
ElementAccess AccessBuilder::ForTypedArrayElement(ExternalArrayType type,
......
......@@ -285,6 +285,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to FixedDoubleArray elements.
static ElementAccess ForFixedDoubleArrayElement();
// Provides access to EnumCache elements.
static ElementAccess ForDescriptorArrayEnumCacheBridgeCacheElement();
// Provides access to Fixed{type}TypedArray and External{type}Array elements.
static ElementAccess ForTypedArrayElement(ExternalArrayType type,
bool is_external);
......
......@@ -2550,9 +2550,24 @@ void BytecodeGraphBuilder::VisitForInNext() {
Node* cache_array = environment()->LookupRegister(
interpreter::Register(catch_reg_pair_index + 1));
Node* value = NewNode(javascript()->ForInNext(), receiver, cache_array,
cache_type, index);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
// We need to rename the {index} here, as in case of OSR we loose the
// information that the {index} is always a valid unsigned Smi value.
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() {
......@@ -2828,6 +2843,22 @@ Node* BytecodeGraphBuilder::TryBuildSimplifiedBinaryOp(const Operator* op,
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,
FeedbackSlot slot) {
Node* effect = environment()->GetEffectDependency();
......
......@@ -184,6 +184,9 @@ class BytecodeGraphBuilder {
// any other invocation of {NewNode} would do.
Node* TryBuildSimplifiedBinaryOp(const Operator* op, Node* left, Node* right,
FeedbackSlot slot);
Node* TryBuildSimplifiedForInNext(Node* receiver, Node* cache_array,
Node* cache_type, Node* index,
FeedbackSlot slot);
Node* TryBuildSimplifiedToNumber(Node* input, FeedbackSlot slot);
Node* TryBuildSimplifiedCall(const Operator* op, Node* const* args,
int arg_count, FeedbackSlot slot);
......
......@@ -629,6 +629,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kCheckMaps:
result = LowerCheckMaps(node, frame_state);
break;
case IrOpcode::kCheckMapValue:
LowerCheckMapValue(node, frame_state);
break;
case IrOpcode::kCheckNumber:
result = LowerCheckNumber(node, frame_state);
break;
......@@ -1284,6 +1287,19 @@ Node* EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
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* value = node->InputAt(0);
......
......@@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckBounds(Node* node, Node* frame_state);
Node* LowerCheckInternalizedString(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* LowerCheckReceiver(Node* node, Node* frame_state);
Node* LowerCheckString(Node* node, Node* frame_state);
......
......@@ -381,6 +381,95 @@ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
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
Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
......@@ -1241,6 +1330,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceObjectGetPrototypeOf(node);
case Builtins::kObjectPrototypeGetProto:
return ReduceObjectPrototypeGetProto(node);
case Builtins::kObjectPrototypeHasOwnProperty:
return ReduceObjectPrototypeHasOwnProperty(node);
case Builtins::kObjectPrototypeIsPrototypeOf:
return ReduceObjectPrototypeIsPrototypeOf(node);
case Builtins::kReflectApply:
......
......@@ -63,6 +63,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceObjectGetPrototype(Node* node, Node* object);
Reduction ReduceObjectGetPrototypeOf(Node* node);
Reduction ReduceObjectPrototypeGetProto(Node* node);
Reduction ReduceObjectPrototypeHasOwnProperty(Node* node);
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
Reduction ReduceReflectApply(Node* node);
Reduction ReduceReflectConstruct(Node* node);
......
......@@ -4,6 +4,7 @@
#include "src/compiler/js-type-hint-lowering.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h"
......@@ -240,6 +241,29 @@ Reduction JSTypeHintLowering::ReduceBinaryOperation(const Operator* op,
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,
Node* control,
FeedbackSlot slot) const {
......
......@@ -55,6 +55,12 @@ class JSTypeHintLowering {
Node* effect, Node* control,
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
Reduction ReduceToNumberOperation(Node* value, Node* effect, Node* control,
FeedbackSlot slot) const;
......
......@@ -1908,15 +1908,6 @@ Reduction JSTypedLowering::ReduceJSForInNext(Node* node) {
Node* effect = NodeProperties::GetEffectInput(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}.
Node* key = effect = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
......
......@@ -328,6 +328,7 @@
V(CheckBounds) \
V(CheckIf) \
V(CheckMaps) \
V(CheckMapValue) \
V(CheckNumber) \
V(CheckInternalizedString) \
V(CheckReceiver) \
......
......@@ -2765,6 +2765,7 @@ class RepresentationSelector {
// Eliminate MapGuard nodes here.
return VisitUnused(node);
case IrOpcode::kCheckMaps:
case IrOpcode::kCheckMapValue:
case IrOpcode::kTransitionElementsKind: {
VisitInputs(node);
return SetOutput(node, MachineRepresentation::kNone);
......
......@@ -691,6 +691,16 @@ struct SimplifiedOperatorGlobalCache final {
CheckedTruncateTaggedToWord32Operator<CheckTaggedInputMode::kNumberOrOddball>
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>
struct CheckFloat64HoleNaNOperator final
: public Operator1<CheckFloat64HoleMode> {
......@@ -769,6 +779,7 @@ GET_FROM_CACHE(ArrayBufferWasNeutered)
GET_FROM_CACHE(ArgumentsFrame)
GET_FROM_CACHE(LookupHashStorageIndex)
GET_FROM_CACHE(LoadHashMapValue)
GET_FROM_CACHE(CheckMapValue)
GET_FROM_CACHE(NewUnmappedArgumentsElements)
#undef GET_FROM_CACHE
......
......@@ -395,6 +395,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* CheckIf();
const Operator* CheckBounds();
const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>);
const Operator* CheckMapValue();
const Operator* CheckHeapObject();
const Operator* CheckInternalizedString();
......
......@@ -1862,6 +1862,11 @@ Type* Typer::Visitor::TypeCheckMaps(Node* node) {
UNREACHABLE();
}
Type* Typer::Visitor::TypeCheckMapValue(Node* node) {
UNREACHABLE();
return nullptr;
}
Type* Typer::Visitor::TypeCheckNumber(Node* node) {
return typer_->operation_typer_.CheckNumber(Operand(node, 0));
}
......
......@@ -1187,11 +1187,12 @@ void Verifier::Visitor::Check(Node* node) {
CheckTypeIs(node, Type::InternalizedString());
break;
case IrOpcode::kCheckMaps:
// (Any, Internal, ..., Internal) -> Any
CheckValueInputIs(node, 0, Type::Any());
for (int i = 1; i < node->op()->ValueInputCount(); ++i) {
CheckValueInputIs(node, i, Type::Internal());
}
CheckNotTyped(node);
break;
case IrOpcode::kCheckMapValue:
CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::Any());
CheckNotTyped(node);
break;
case IrOpcode::kCheckNumber:
......
......@@ -477,7 +477,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
case Builtins::kObjectIsSealed:
case Builtins::kObjectPrototypeValueOf:
case Builtins::kObjectValues:
case Builtins::kObjectHasOwnProperty:
case Builtins::kObjectPrototypeHasOwnProperty:
case Builtins::kObjectPrototypeIsPrototypeOf:
case Builtins::kObjectPrototypePropertyIsEnumerable:
case Builtins::kObjectPrototypeToString:
......
......@@ -10,73 +10,74 @@
namespace v8 {
namespace internal {
#define DEOPTIMIZE_REASON_LIST(V) \
V(AccessCheck, "Access check needed") \
V(NoReason, "no reason") \
V(ConstantGlobalVariableAssignment, "Constant global variable assignment") \
V(ConversionOverflow, "conversion overflow") \
V(DivisionByZero, "division by zero") \
V(ExpectedHeapNumber, "Expected heap number") \
V(ExpectedSmi, "Expected smi") \
V(ForcedDeoptToRuntime, "Forced deopt to runtime") \
V(Hole, "hole") \
V(InstanceMigrationFailed, "instance migration failed") \
V(InsufficientTypeFeedbackForCall, "Insufficient type feedback for call") \
V(InsufficientTypeFeedbackForCallWithArguments, \
"Insufficient type feedback for call with arguments") \
V(InsufficientTypeFeedbackForConstruct, \
"Insufficient type feedback for construct") \
V(FastPathFailed, "Falling off the fast path") \
V(InsufficientTypeFeedbackForCombinedTypeOfBinaryOperation, \
"Insufficient type feedback for combined type of binary operation") \
V(InsufficientTypeFeedbackForGenericNamedAccess, \
"Insufficient type feedback for generic named access") \
V(InsufficientTypeFeedbackForGenericKeyedAccess, \
"Insufficient type feedback for generic keyed access") \
V(InsufficientTypeFeedbackForLHSOfBinaryOperation, \
"Insufficient type feedback for LHS of binary operation") \
V(InsufficientTypeFeedbackForRHSOfBinaryOperation, \
"Insufficient type feedback for RHS of binary operation") \
V(KeyIsNegative, "key is negative") \
V(LostPrecision, "lost precision") \
V(LostPrecisionOrNaN, "lost precision or NaN") \
V(MementoFound, "memento found") \
V(MinusZero, "minus zero") \
V(NaN, "NaN") \
V(NegativeKeyEncountered, "Negative key encountered") \
V(NegativeValue, "negative value") \
V(NoCache, "no cache") \
V(NotAHeapNumber, "not a heap number") \
V(NotAHeapNumberUndefined, "not a heap number/undefined") \
V(NotAJavaScriptObject, "not a JavaScript object") \
V(NotANumberOrOddball, "not a Number or Oddball") \
V(NotASmi, "not a Smi") \
V(NotASymbol, "not a Symbol") \
V(OutOfBounds, "out of bounds") \
V(OutsideOfRange, "Outside of range") \
V(Overflow, "overflow") \
V(Proxy, "proxy") \
V(ReceiverWasAGlobalObject, "receiver was a global object") \
V(Smi, "Smi") \
V(TooManyArguments, "too many arguments") \
V(TracingElementsTransitions, "Tracing elements transitions") \
V(TypeMismatchBetweenFeedbackAndConstant, \
"Type mismatch between feedback and constant") \
V(UnexpectedCellContentsInConstantGlobalStore, \
"Unexpected cell contents in constant global store") \
V(UnexpectedCellContentsInGlobalStore, \
"Unexpected cell contents in global store") \
V(UnexpectedObject, "unexpected object") \
V(UnexpectedRHSOfBinaryOperation, "Unexpected RHS of binary operation") \
V(UnknownMapInPolymorphicAccess, "Unknown map in polymorphic access") \
V(UnknownMapInPolymorphicCall, "Unknown map in polymorphic call") \
V(UnknownMapInPolymorphicElementAccess, \
"Unknown map in polymorphic element access") \
V(UnknownMap, "Unknown map") \
V(ValueMismatch, "value mismatch") \
V(WrongInstanceType, "wrong instance type") \
V(WrongMap, "wrong map") \
V(UndefinedOrNullInForIn, "null or undefined in for-in") \
#define DEOPTIMIZE_REASON_LIST(V) \
V(AccessCheck, "Access check needed") \
V(NoReason, "no reason") \
V(ConstantGlobalVariableAssignment, "Constant global variable assignment") \
V(ConversionOverflow, "conversion overflow") \
V(DivisionByZero, "division by zero") \
V(ExpectedHeapNumber, "Expected heap number") \
V(ExpectedSmi, "Expected smi") \
V(ForcedDeoptToRuntime, "Forced deopt to runtime") \
V(Hole, "hole") \
V(InstanceMigrationFailed, "instance migration failed") \
V(InsufficientTypeFeedbackForCall, "Insufficient type feedback for call") \
V(InsufficientTypeFeedbackForCallWithArguments, \
"Insufficient type feedback for call with arguments") \
V(InsufficientTypeFeedbackForConstruct, \
"Insufficient type feedback for construct") \
V(FastPathFailed, "Falling off the fast path") \
V(InsufficientTypeFeedbackForForIn, "Insufficient type feedback for for-in") \
V(InsufficientTypeFeedbackForCombinedTypeOfBinaryOperation, \
"Insufficient type feedback for combined type of binary operation") \
V(InsufficientTypeFeedbackForGenericNamedAccess, \
"Insufficient type feedback for generic named access") \
V(InsufficientTypeFeedbackForGenericKeyedAccess, \
"Insufficient type feedback for generic keyed access") \
V(InsufficientTypeFeedbackForLHSOfBinaryOperation, \
"Insufficient type feedback for LHS of binary operation") \
V(InsufficientTypeFeedbackForRHSOfBinaryOperation, \
"Insufficient type feedback for RHS of binary operation") \
V(KeyIsNegative, "key is negative") \
V(LostPrecision, "lost precision") \
V(LostPrecisionOrNaN, "lost precision or NaN") \
V(MementoFound, "memento found") \
V(MinusZero, "minus zero") \
V(NaN, "NaN") \
V(NegativeKeyEncountered, "Negative key encountered") \
V(NegativeValue, "negative value") \
V(NoCache, "no cache") \
V(NotAHeapNumber, "not a heap number") \
V(NotAHeapNumberUndefined, "not a heap number/undefined") \
V(NotAJavaScriptObject, "not a JavaScript object") \
V(NotANumberOrOddball, "not a Number or Oddball") \
V(NotASmi, "not a Smi") \
V(NotASymbol, "not a Symbol") \
V(OutOfBounds, "out of bounds") \
V(OutsideOfRange, "Outside of range") \
V(Overflow, "overflow") \
V(Proxy, "proxy") \
V(ReceiverWasAGlobalObject, "receiver was a global object") \
V(Smi, "Smi") \
V(TooManyArguments, "too many arguments") \
V(TracingElementsTransitions, "Tracing elements transitions") \
V(TypeMismatchBetweenFeedbackAndConstant, \
"Type mismatch between feedback and constant") \
V(UnexpectedCellContentsInConstantGlobalStore, \
"Unexpected cell contents in constant global store") \
V(UnexpectedCellContentsInGlobalStore, \
"Unexpected cell contents in global store") \
V(UnexpectedObject, "unexpected object") \
V(UnexpectedRHSOfBinaryOperation, "Unexpected RHS of binary operation") \
V(UnknownMapInPolymorphicAccess, "Unknown map in polymorphic access") \
V(UnknownMapInPolymorphicCall, "Unknown map in polymorphic call") \
V(UnknownMapInPolymorphicElementAccess, \
"Unknown map in polymorphic element access") \
V(UnknownMap, "Unknown map") \
V(ValueMismatch, "value mismatch") \
V(WrongInstanceType, "wrong instance type") \
V(WrongMap, "wrong map") \
V(UndefinedOrNullInForIn, "null or undefined in for-in") \
V(UndefinedOrNullInToObject, "null or undefined in ToObject")
enum class DeoptimizeReason : uint8_t {
......
......@@ -318,6 +318,10 @@ Handle<Symbol> FeedbackVector::UninitializedSentinel(Isolate* isolate) {
return isolate->factory()->uninitialized_symbol();
}
Handle<Symbol> FeedbackVector::GenericSentinel(Isolate* isolate) {
return isolate->factory()->generic_symbol();
}
Handle<Symbol> FeedbackVector::MegamorphicSentinel(Isolate* isolate) {
return isolate->factory()->megamorphic_symbol();
}
......
......@@ -922,6 +922,16 @@ CompareOperationHint CompareICNexus::GetCompareOperationFeedback() const {
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 {
Isolate* isolate = GetIsolate();
Object* feedback = GetFeedback();
......
......@@ -229,6 +229,9 @@ class FeedbackVector : public HeapObject {
// The object that indicates an uninitialized cache.
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.
static inline Handle<Symbol> MegamorphicSentinel(Isolate* isolate);
......@@ -539,6 +542,8 @@ class FeedbackNexus {
InlineCacheState ic_state() const { return StateFromFeedback(); }
bool IsUninitialized() const { return StateFromFeedback() == UNINITIALIZED; }
bool IsMegamorphic() const { return StateFromFeedback() == MEGAMORPHIC; }
bool IsGeneric() const { return StateFromFeedback() == GENERIC; }
Map* FindFirstMap() const {
MapHandles maps;
ExtractMaps(&maps);
......@@ -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 {
public:
StoreDataPropertyInLiteralICNexus(Handle<FeedbackVector> vector,
......
......@@ -220,6 +220,7 @@
V(error_script_symbol) \
V(error_start_pos_symbol) \
V(frozen_symbol) \
V(generic_symbol) \
V(home_object_symbol) \
V(intl_initialized_marker_symbol) \
V(intl_pattern_symbol) \
......
......@@ -3142,6 +3142,8 @@ IGNITION_HANDLER(ForInNext, InterpreterAssembler) {
Node* cache_type = LoadRegister(cache_type_reg);
Node* cache_array_reg = NextRegister(cache_type_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.
Node* key = LoadFixedArrayElement(cache_array, index, 0,
......@@ -3153,18 +3155,33 @@ IGNITION_HANDLER(ForInNext, InterpreterAssembler) {
Branch(WordEqual(receiver_map, cache_type), &if_fast, &if_slow);
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.
BIND(&if_done);
SetAccumulator(key);
Dispatch();
}
BIND(&if_slow);
{
// Record the fact that we hit the for-in slow path.
Node* vector_index = BytecodeOperandIdx(3);
Node* feedback_vector = LoadFeedbackVector();
Node* megamorphic_sentinel =
HeapConstant(FeedbackVector::MegamorphicSentinel(isolate()));
StoreFeedbackVectorSlot(feedback_vector, vector_index, megamorphic_sentinel,
Node* generic_sentinel =
HeapConstant(FeedbackVector::GenericSentinel(isolate()));
StoreFeedbackVectorSlot(feedback_vector, vector_index, generic_sentinel,
SKIP_WRITE_BARRIER);
// 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