Optimize in operator

This change implements optimizations for the `in` operator for packed array
elements and object properties. It adds a new feedback slot kind and an IC
path similar to KeyedLoadIC for handling the lookups. TurboFan uses the
feedback to optimize based on the maps and keys.

For more details see:
https://docs.google.com/document/d/1tIfzywY8AeNVcy_sen-5Xev21MeZwjcU8QhSdzHvXig

This can provide 10x performance improvements of on loops of the form:

    for (let i = 0; i < ary.length; ++i) {
      if (i in ary) {
        ...
      }
    }


Bug: v8:8733
Change-Id: I766bf865a547a059e5bce5399bb6112e5d9a85c8
Reviewed-on: https://chromium-review.googlesource.com/c/1432598
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Matt Gardner <magardn@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#59843}
This commit is contained in:
Matt Gardner 2019-02-22 13:07:39 -08:00 committed by Commit Bot
parent 7fbce1ad4b
commit 32fc0acfef
38 changed files with 1619 additions and 362 deletions

View File

@ -251,6 +251,10 @@ namespace internal {
TFH(ElementsTransitionAndStore_GrowNoTransitionHandleCOW, StoreTransition) \
TFH(ElementsTransitionAndStore_NoTransitionIgnoreOOB, StoreTransition) \
TFH(ElementsTransitionAndStore_NoTransitionHandleCOW, StoreTransition) \
TFH(KeyedHasIC_PolymorphicName, LoadWithVector) \
TFH(KeyedHasIC_SloppyArguments, LoadWithVector) \
TFH(HasIndexedInterceptorIC, LoadWithVector) \
TFH(HasIC_Slow, LoadWithVector) \
\
/* Microtask helpers */ \
TFS(EnqueueMicrotask, kMicrotask) \
@ -626,6 +630,8 @@ namespace internal {
TFH(LoadGlobalICInsideTypeofTrampoline, LoadGlobal) \
TFH(CloneObjectIC, CloneObjectWithVector) \
TFH(CloneObjectIC_Slow, CloneObjectWithVector) \
TFH(KeyedHasIC, LoadWithVector) \
TFH(KeyedHasIC_Megamorphic, LoadWithVector) \
\
/* IterableToList */ \
/* ES #sec-iterabletolist */ \

View File

@ -493,5 +493,50 @@ TF_BUILTIN(LoadIndexedInterceptorIC, CodeStubAssembler) {
vector);
}
TF_BUILTIN(KeyedHasIC_SloppyArguments, CodeStubAssembler) {
Node* receiver = Parameter(Descriptor::kReceiver);
Node* key = Parameter(Descriptor::kName);
Node* slot = Parameter(Descriptor::kSlot);
Node* vector = Parameter(Descriptor::kVector);
Node* context = Parameter(Descriptor::kContext);
Label miss(this);
Node* result = HasKeyedSloppyArguments(receiver, key, &miss);
Return(result);
BIND(&miss);
{
Comment("Miss");
TailCallRuntime(Runtime::kKeyedHasIC_Miss, context, receiver, key, slot,
vector);
}
}
TF_BUILTIN(HasIndexedInterceptorIC, CodeStubAssembler) {
Node* receiver = Parameter(Descriptor::kReceiver);
Node* key = Parameter(Descriptor::kName);
Node* slot = Parameter(Descriptor::kSlot);
Node* vector = Parameter(Descriptor::kVector);
Node* context = Parameter(Descriptor::kContext);
Label if_keyispositivesmi(this), if_keyisinvalid(this);
Branch(TaggedIsPositiveSmi(key), &if_keyispositivesmi, &if_keyisinvalid);
BIND(&if_keyispositivesmi);
TailCallRuntime(Runtime::kHasElementWithInterceptor, context, receiver, key);
BIND(&if_keyisinvalid);
TailCallRuntime(Runtime::kKeyedHasIC_Miss, context, receiver, key, slot,
vector);
}
TF_BUILTIN(HasIC_Slow, CodeStubAssembler) {
Node* receiver = Parameter(Descriptor::kReceiver);
Node* name = Parameter(Descriptor::kName);
Node* context = Parameter(Descriptor::kContext);
TailCallRuntime(Runtime::kHasProperty, context, receiver, name);
}
} // namespace internal
} // namespace v8

View File

@ -40,6 +40,9 @@ IC_BUILTIN(KeyedStoreICTrampoline)
IC_BUILTIN(StoreInArrayLiteralIC)
IC_BUILTIN(CloneObjectIC)
IC_BUILTIN(CloneObjectIC_Slow)
IC_BUILTIN(KeyedHasIC)
IC_BUILTIN(KeyedHasIC_Megamorphic)
IC_BUILTIN(KeyedHasIC_PolymorphicName)
IC_BUILTIN_PARAM(LoadGlobalIC, LoadGlobalIC, NOT_INSIDE_TYPEOF)
IC_BUILTIN_PARAM(LoadGlobalICInsideTypeof, LoadGlobalIC, INSIDE_TYPEOF)

View File

@ -10123,8 +10123,9 @@ TNode<IntPtrT> CodeStubAssembler::TryToIntptr(Node* key, Label* miss) {
return var_intptr_key.value();
}
Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key,
Node* value, Label* bailout) {
Node* CodeStubAssembler::EmitKeyedSloppyArguments(
Node* receiver, Node* key, Node* value, Label* bailout,
ArgumentsAccessMode access_mode) {
// Mapped arguments are actual arguments. Unmapped arguments are values added
// to the arguments object after it was created for the call. Mapped arguments
// are stored in the context at indexes given by elements[key + 2]. Unmapped
@ -10151,8 +10152,6 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key,
// index into the context array given at elements[0]. Return the value at
// context[t].
bool is_load = value == nullptr;
GotoIfNot(TaggedIsSmi(key), bailout);
key = SmiUntag(key);
GotoIf(IntPtrLessThan(key, IntPtrConstant(0)), bailout);
@ -10161,8 +10160,11 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key,
TNode<IntPtrT> elements_length = LoadAndUntagFixedArrayBaseLength(elements);
VARIABLE(var_result, MachineRepresentation::kTagged);
if (!is_load) {
if (access_mode == ArgumentsAccessMode::kStore) {
var_result.Bind(value);
} else {
DCHECK(access_mode == ArgumentsAccessMode::kLoad ||
access_mode == ArgumentsAccessMode::kHas);
}
Label if_mapped(this), if_unmapped(this), end(this, &var_result);
Node* intptr_two = IntPtrConstant(2);
@ -10178,10 +10180,14 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key,
{
TNode<IntPtrT> mapped_index_intptr = SmiUntag(CAST(mapped_index));
TNode<Context> the_context = CAST(LoadFixedArrayElement(elements, 0));
if (is_load) {
if (access_mode == ArgumentsAccessMode::kLoad) {
Node* result = LoadContextElement(the_context, mapped_index_intptr);
CSA_ASSERT(this, WordNotEqual(result, TheHoleConstant()));
var_result.Bind(result);
} else if (access_mode == ArgumentsAccessMode::kHas) {
CSA_ASSERT(this, Word32BinaryNot(IsTheHole(LoadContextElement(
the_context, mapped_index_intptr))));
var_result.Bind(TrueConstant());
} else {
StoreContextElement(the_context, mapped_index_intptr, value);
}
@ -10198,17 +10204,31 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key,
TNode<IntPtrT> backing_store_length =
LoadAndUntagFixedArrayBaseLength(backing_store);
GotoIf(UintPtrGreaterThanOrEqual(key, backing_store_length), bailout);
// The key falls into unmapped range.
if (is_load) {
if (access_mode == ArgumentsAccessMode::kHas) {
Label out_of_bounds(this);
GotoIf(UintPtrGreaterThanOrEqual(key, backing_store_length),
&out_of_bounds);
Node* result = LoadFixedArrayElement(backing_store, key);
GotoIf(WordEqual(result, TheHoleConstant()), bailout);
var_result.Bind(result);
var_result.Bind(
SelectBooleanConstant(WordNotEqual(result, TheHoleConstant())));
Goto(&end);
BIND(&out_of_bounds);
var_result.Bind(FalseConstant());
Goto(&end);
} else {
StoreFixedArrayElement(backing_store, key, value);
GotoIf(UintPtrGreaterThanOrEqual(key, backing_store_length), bailout);
// The key falls into unmapped range.
if (access_mode == ArgumentsAccessMode::kLoad) {
Node* result = LoadFixedArrayElement(backing_store, key);
GotoIf(WordEqual(result, TheHoleConstant()), bailout);
var_result.Bind(result);
} else {
StoreFixedArrayElement(backing_store, key, value);
}
Goto(&end);
}
Goto(&end);
}
BIND(&end);
@ -11012,63 +11032,63 @@ void CodeStubAssembler::BranchIfNumberRelationalComparison(
TVARIABLE(Float64T, var_left_float);
TVARIABLE(Float64T, var_right_float);
Branch(TaggedIsSmi(left),
[&] {
TNode<Smi> smi_left = CAST(left);
Branch(
TaggedIsSmi(left),
[&] {
TNode<Smi> smi_left = CAST(left);
Branch(TaggedIsSmi(right),
[&] {
TNode<Smi> smi_right = CAST(right);
Branch(
TaggedIsSmi(right),
[&] {
TNode<Smi> smi_right = CAST(right);
// Both {left} and {right} are Smi, so just perform a fast
// Smi comparison.
switch (op) {
case Operation::kEqual:
BranchIfSmiEqual(smi_left, smi_right, if_true,
if_false);
break;
case Operation::kLessThan:
BranchIfSmiLessThan(smi_left, smi_right, if_true,
if_false);
break;
case Operation::kLessThanOrEqual:
BranchIfSmiLessThanOrEqual(smi_left, smi_right, if_true,
if_false);
break;
case Operation::kGreaterThan:
BranchIfSmiLessThan(smi_right, smi_left, if_true,
if_false);
break;
case Operation::kGreaterThanOrEqual:
BranchIfSmiLessThanOrEqual(smi_right, smi_left, if_true,
if_false);
break;
default:
UNREACHABLE();
}
},
[&] {
CSA_ASSERT(this, IsHeapNumber(right));
var_left_float = SmiToFloat64(smi_left);
var_right_float = LoadHeapNumberValue(right);
Goto(&do_float_comparison);
});
},
[&] {
CSA_ASSERT(this, IsHeapNumber(left));
var_left_float = LoadHeapNumberValue(left);
// Both {left} and {right} are Smi, so just perform a fast
// Smi comparison.
switch (op) {
case Operation::kEqual:
BranchIfSmiEqual(smi_left, smi_right, if_true, if_false);
break;
case Operation::kLessThan:
BranchIfSmiLessThan(smi_left, smi_right, if_true, if_false);
break;
case Operation::kLessThanOrEqual:
BranchIfSmiLessThanOrEqual(smi_left, smi_right, if_true,
if_false);
break;
case Operation::kGreaterThan:
BranchIfSmiLessThan(smi_right, smi_left, if_true, if_false);
break;
case Operation::kGreaterThanOrEqual:
BranchIfSmiLessThanOrEqual(smi_right, smi_left, if_true,
if_false);
break;
default:
UNREACHABLE();
}
},
[&] {
CSA_ASSERT(this, IsHeapNumber(right));
var_left_float = SmiToFloat64(smi_left);
var_right_float = LoadHeapNumberValue(right);
Goto(&do_float_comparison);
});
},
[&] {
CSA_ASSERT(this, IsHeapNumber(left));
var_left_float = LoadHeapNumberValue(left);
Branch(TaggedIsSmi(right),
[&] {
var_right_float = SmiToFloat64(right);
Goto(&do_float_comparison);
},
[&] {
CSA_ASSERT(this, IsHeapNumber(right));
var_right_float = LoadHeapNumberValue(right);
Goto(&do_float_comparison);
});
});
Branch(
TaggedIsSmi(right),
[&] {
var_right_float = SmiToFloat64(right);
Goto(&do_float_comparison);
},
[&] {
CSA_ASSERT(this, IsHeapNumber(right));
var_right_float = LoadHeapNumberValue(right);
Goto(&do_float_comparison);
});
});
BIND(&do_float_comparison);
{

View File

@ -2892,16 +2892,26 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<Map> LoadReceiverMap(SloppyTNode<Object> receiver);
enum class ArgumentsAccessMode { kLoad, kStore, kHas };
// Emits keyed sloppy arguments has. Returns whether the key is in the
// arguments.
Node* HasKeyedSloppyArguments(Node* receiver, Node* key, Label* bailout) {
return EmitKeyedSloppyArguments(receiver, key, nullptr, bailout,
ArgumentsAccessMode::kHas);
}
// Emits keyed sloppy arguments load. Returns either the loaded value.
Node* LoadKeyedSloppyArguments(Node* receiver, Node* key, Label* bailout) {
return EmitKeyedSloppyArguments(receiver, key, nullptr, bailout);
return EmitKeyedSloppyArguments(receiver, key, nullptr, bailout,
ArgumentsAccessMode::kLoad);
}
// Emits keyed sloppy arguments store.
void StoreKeyedSloppyArguments(Node* receiver, Node* key, Node* value,
Label* bailout) {
DCHECK_NOT_NULL(value);
EmitKeyedSloppyArguments(receiver, key, value, bailout);
EmitKeyedSloppyArguments(receiver, key, value, bailout,
ArgumentsAccessMode::kStore);
}
// Loads script context from the script context table.
@ -3409,7 +3419,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
// Emits keyed sloppy arguments load if the |value| is nullptr or store
// otherwise. Returns either the loaded value or |value|.
Node* EmitKeyedSloppyArguments(Node* receiver, Node* key, Node* value,
Label* bailout);
Label* bailout,
ArgumentsAccessMode access_mode);
TNode<String> AllocateSlicedString(RootIndex map_root_index,
TNode<Uint32T> length,

View File

@ -63,6 +63,8 @@ std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
return os << "Store";
case AccessMode::kStoreInLiteral:
return os << "StoreInLiteral";
case AccessMode::kHas:
return os << "Has";
}
UNREACHABLE();
}
@ -177,6 +179,7 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
if (this->field_index_.GetFieldAccessStubKey() ==
that->field_index_.GetFieldAccessStubKey()) {
switch (access_mode) {
case AccessMode::kHas:
case AccessMode::kLoad: {
if (this->field_representation_ != that->field_representation_) {
if (!IsAnyTagged(this->field_representation_) ||
@ -306,7 +309,7 @@ void ProcessFeedbackMaps(Isolate* isolate, MapHandles const& maps,
bool AccessInfoFactory::ComputeElementAccessInfos(
MapHandles const& maps, AccessMode access_mode,
ZoneVector<ElementAccessInfo>* access_infos) const {
if (access_mode == AccessMode::kLoad) {
if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
// For polymorphic loads of similar elements kinds (i.e. all tagged or all
// double), always use the "worst case" code without a transition. This is
// much faster than transitioning the elements to the worst case, trading a
@ -414,6 +417,12 @@ bool AccessInfoFactory::ComputeAccessorDescriptorAccessInfo(
PropertyAccessInfo::ModuleExport(MapHandles{receiver_map}, cell);
return true;
}
if (access_mode == AccessMode::kHas) {
// HasProperty checks don't call getter/setters, existence is sufficient.
*access_info = PropertyAccessInfo::AccessorConstant(
MapHandles{receiver_map}, Handle<Object>(), holder);
return true;
}
Handle<Object> accessors(descriptors->GetStrongValue(number), isolate());
if (!accessors->IsAccessorPair()) return false;
Handle<Object> accessor(access_mode == AccessMode::kLoad
@ -456,11 +465,13 @@ bool AccessInfoFactory::ComputePropertyAccessInfo(
PropertyAccessInfo* access_info) const {
CHECK(name->IsUniqueName());
if (access_mode == AccessMode::kHas && !map->IsJSReceiverMap()) return false;
// Check if it is safe to inline property access for the {map}.
if (!CanInlinePropertyAccess(map)) return false;
// We support fast inline cases for certain JSObject getters.
if (access_mode == AccessMode::kLoad &&
if ((access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) &&
LookupSpecialFieldAccessor(map, name, access_info)) {
return true;
}

View File

@ -29,7 +29,7 @@ class TypeCache;
// Whether we are loading a property or storing to a property.
// For a store during literal creation, do not walk up the prototype chain.
enum class AccessMode { kLoad, kStore, kStoreInLiteral };
enum class AccessMode { kLoad, kStore, kStoreInLiteral, kHas };
std::ostream& operator<<(std::ostream&, AccessMode);

View File

@ -2542,7 +2542,9 @@ void BytecodeGraphBuilder::VisitTestIn() {
Node* object = environment()->LookupAccumulator();
Node* key =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
Node* node = NewNode(javascript()->HasProperty(), object, key);
VectorSlotPair feedback =
CreateVectorSlotPair(bytecode_iterator().GetIndexOperand(1));
Node* node = NewNode(javascript()->HasProperty(feedback), object, key);
environment()->BindAccumulator(node, Environment::kAttachFrameState);
}

View File

@ -944,9 +944,10 @@ Reduction JSCallReducer::ReduceReflectHas(Node* node) {
Node* etrue = effect;
Node* vtrue;
{
// TODO(magardn): collect feedback so this can be optimized
vtrue = etrue = if_true =
graph()->NewNode(javascript()->HasProperty(), target, key, context,
frame_state, etrue, if_true);
graph()->NewNode(javascript()->HasProperty(VectorSlotPair()), target,
key, context, frame_state, etrue, if_true);
}
// Rewire potential exception edges.

View File

@ -106,6 +106,8 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) {
return ReduceJSLoadNamed(node);
case IrOpcode::kJSStoreNamed:
return ReduceJSStoreNamed(node);
case IrOpcode::kJSHasProperty:
return ReduceJSHasProperty(node);
case IrOpcode::kJSLoadProperty:
return ReduceJSLoadProperty(node);
case IrOpcode::kJSStoreProperty:
@ -208,7 +210,7 @@ bool IsStringConstant(JSHeapBroker* broker, Node* node) {
HeapObjectMatcher matcher(node);
return matcher.HasValue() && matcher.Ref(broker).IsString();
}
}
} // namespace
Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionEnter(
Node* node) {
@ -1108,7 +1110,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
node->opcode() == IrOpcode::kJSStoreNamed ||
node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSStoreProperty ||
node->opcode() == IrOpcode::kJSStoreNamedOwn);
node->opcode() == IrOpcode::kJSStoreNamedOwn ||
node->opcode() == IrOpcode::kJSHasProperty);
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
@ -1444,7 +1447,6 @@ Reduction JSNativeContextSpecialization::ReduceJSLoadNamed(Node* node) {
AccessMode::kLoad);
}
Reduction JSNativeContextSpecialization::ReduceJSStoreNamed(Node* node) {
DCHECK_EQ(IrOpcode::kJSStoreNamed, node->opcode());
NamedAccess const& p = NamedAccessOf(node->op());
@ -1505,7 +1507,8 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
KeyedAccessStoreMode store_mode) {
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSStoreProperty ||
node->opcode() == IrOpcode::kJSStoreInArrayLiteral);
node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||
node->opcode() == IrOpcode::kJSHasProperty);
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
@ -1566,6 +1569,17 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
for (Handle<Map> prototype_map : prototype_maps) {
dependencies()->DependOnStableMap(MapRef(broker(), prototype_map));
}
} else if (access_mode == AccessMode::kHas) {
// If we have any fast arrays, we need to check and depend on
// NoElementsProtector.
for (ElementAccessInfo const& access_info : access_infos) {
if (IsFastElementsKind(access_info.elements_kind())) {
if (!isolate()->IsNoElementsProtectorIntact()) return NoChange();
dependencies()->DependOnProtector(
PropertyCellRef(broker(), factory()->no_elements_protector()));
break;
}
}
}
// Ensure that {receiver} is a heap object.
@ -1704,7 +1718,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
}
Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant(
Node* node, Node* index, FeedbackNexus const& nexus,
Node* node, Node* index, FeedbackNexus const& nexus, AccessMode access_mode,
KeyedAccessLoadMode load_mode) {
DCHECK_EQ(node->opcode(), IrOpcode::kJSLoadProperty);
Node* receiver = NodeProperties::GetValueInput(node, 0);
@ -1715,7 +1729,8 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant(
HeapObjectRef receiver_ref = mreceiver.Ref(broker()).AsHeapObject();
if (receiver_ref.map().oddball_type() == OddballType::kHole ||
receiver_ref.map().oddball_type() == OddballType::kNull ||
receiver_ref.map().oddball_type() == OddballType::kUndefined) {
receiver_ref.map().oddball_type() == OddballType::kUndefined ||
(receiver_ref.map().IsString() && access_mode == AccessMode::kHas)) {
return NoChange();
}
@ -1732,7 +1747,9 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant(
// We can safely constant-fold the {index} access to {receiver},
// since the element is non-configurable, non-writable and thus
// cannot change anymore.
Node* value = jsgraph()->Constant(it.GetDataValue());
Node* value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->Constant(it.GetDataValue());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
@ -1761,7 +1778,9 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant(
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kCowArrayElementsChanged),
check, effect, control);
Node* value = jsgraph()->Constant(it.GetDataValue());
Node* value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->Constant(it.GetDataValue());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
@ -1771,7 +1790,7 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant(
// For constant Strings we can eagerly strength-reduce the keyed
// accesses using the known length, which doesn't change.
if (receiver_ref.IsString()) {
if (receiver_ref.IsString() && access_mode != AccessMode::kHas) {
// We can only assume that the {index} is a valid array index if the
// IC is in element access mode and not MEGAMORPHIC, otherwise there's
// no guard for the bounds check below.
@ -1797,14 +1816,16 @@ Reduction JSNativeContextSpecialization::ReduceKeyedAccess(
AccessMode access_mode, KeyedAccessLoadMode load_mode,
KeyedAccessStoreMode store_mode) {
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSStoreProperty);
node->opcode() == IrOpcode::kJSStoreProperty ||
node->opcode() == IrOpcode::kJSHasProperty);
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
if (access_mode == AccessMode::kLoad &&
receiver->opcode() == IrOpcode::kHeapConstant) {
Reduction reduction =
ReduceKeyedLoadFromHeapConstant(node, index, nexus, load_mode);
Reduction reduction = ReduceKeyedLoadFromHeapConstant(
node, index, nexus, access_mode, load_mode);
if (reduction.Changed()) return reduction;
}
@ -1879,6 +1900,21 @@ Reduction JSNativeContextSpecialization::ReduceSoftDeoptimize(
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSHasProperty(Node* node) {
DCHECK_EQ(IrOpcode::kJSHasProperty, node->opcode());
PropertyAccess const& p = PropertyAccessOf(node->op());
Node* index = NodeProperties::GetValueInput(node, 1);
Node* value = jsgraph()->Dead();
// Extract receiver maps from the has property IC using the FeedbackNexus.
if (!p.feedback().IsValid()) return NoChange();
FeedbackNexus nexus(p.feedback().vector(), p.feedback().slot());
// Try to lower the keyed access based on the {nexus}.
return ReduceKeyedAccess(node, index, value, nexus, AccessMode::kHas,
STANDARD_LOAD, STANDARD_STORE);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
Node* node) {
// We can optimize a property load if it's being used inside a for..in:
@ -2188,6 +2224,22 @@ JSNativeContextSpecialization::BuildPropertyLoad(
return ValueEffectControl(value, effect, control);
}
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildPropertyTest(
Node* effect, Node* control, PropertyAccessInfo const& access_info) {
// Determine actual holder and perform prototype chain checks.
Handle<JSObject> holder;
if (access_info.holder().ToHandle(&holder)) {
dependencies()->DependOnStablePrototypeChains(
broker(), access_info.receiver_maps(), JSObjectRef(broker(), holder));
}
Node* value = access_info.IsNotFound() ? jsgraph()->FalseConstant()
: jsgraph()->TrueConstant();
return ValueEffectControl(value, effect, control);
}
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildPropertyAccess(
Node* receiver, Node* value, Node* context, Node* frame_state, Node* effect,
@ -2202,6 +2254,8 @@ JSNativeContextSpecialization::BuildPropertyAccess(
return BuildPropertyStore(receiver, value, context, frame_state, effect,
control, name, if_exceptions, access_info,
access_mode);
case AccessMode::kHas:
return BuildPropertyTest(effect, control, access_info);
}
UNREACHABLE();
return ValueEffectControl();
@ -2570,7 +2624,6 @@ JSNativeContextSpecialization::BuildElementAccess(
Node* receiver, Node* index, Node* value, Node* effect, Node* control,
ElementAccessInfo const& access_info, AccessMode access_mode,
KeyedAccessLoadMode load_mode, KeyedAccessStoreMode store_mode) {
// TODO(bmeurer): We currently specialize based on elements kind. We should
// also be able to properly support strings and other JSObjects here.
ElementsKind elements_kind = access_info.elements_kind();
@ -2670,7 +2723,7 @@ JSNativeContextSpecialization::BuildElementAccess(
// below are performed on unsigned values, which means that all the
// Negative32 values are treated as out-of-bounds.
index = graph()->NewNode(simplified()->NumberToUint32(), index);
} else {
} else if (access_mode != AccessMode::kHas) {
// Check that the {index} is in the valid range for the {receiver}.
index = effect =
graph()->NewNode(simplified()->CheckBounds(VectorSlotPair()), index,
@ -2775,6 +2828,13 @@ JSNativeContextSpecialization::BuildElementAccess(
}
break;
}
case AccessMode::kHas:
// For has property on a typed array, all we need is a bounds check.
value = effect =
graph()->NewNode(simplified()->SpeculativeNumberLessThan(
NumberOperationHint::kSignedSmall),
index, length, effect, control);
break;
}
} else {
// Load the elements for the {receiver}.
@ -2821,7 +2881,7 @@ JSNativeContextSpecialization::BuildElementAccess(
index = effect = graph()->NewNode(
simplified()->CheckBounds(VectorSlotPair()), index,
jsgraph()->Constant(Smi::kMaxValue), effect, control);
} else {
} else if (access_mode != AccessMode::kHas) {
// Check that the {index} is in the valid range for the {receiver}.
index = effect =
graph()->NewNode(simplified()->CheckBounds(VectorSlotPair()), index,
@ -2941,6 +3001,58 @@ JSNativeContextSpecialization::BuildElementAccess(
effect, control);
}
}
} else if (access_mode == AccessMode::kHas) {
// For packed arrays with NoElementsProctector valid, a bound check
// is equivalent to HasProperty.
value = effect = graph()->NewNode(simplified()->SpeculativeNumberLessThan(
NumberOperationHint::kSignedSmall),
index, length, effect, control);
if (IsHoleyElementsKind(elements_kind)) {
// If the index is in bounds, do a load and hole check.
Node* branch = graph()->NewNode(common()->Branch(), value, control);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = jsgraph()->FalseConstant();
element_access.type =
Type::Union(element_type, Type::Hole(), graph()->zone());
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
element_access.machine_type = MachineType::AnyTagged();
}
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* checked = etrue =
graph()->NewNode(simplified()->CheckBounds(VectorSlotPair()), index,
length, etrue, if_true);
Node* element = etrue =
graph()->NewNode(simplified()->LoadElement(element_access),
elements, checked, etrue, if_true);
Node* vtrue;
if (IsDoubleElementsKind(elements_kind)) {
vtrue =
graph()->NewNode(simplified()->NumberIsFloat64Hole(), element);
} else {
vtrue = graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
}
vtrue = graph()->NewNode(simplified()->BooleanNot(), vtrue);
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
}
} else {
DCHECK(access_mode == AccessMode::kStore ||
access_mode == AccessMode::kStoreInLiteral);

View File

@ -84,6 +84,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Reduction ReduceJSStoreGlobal(Node* node);
Reduction ReduceJSLoadNamed(Node* node);
Reduction ReduceJSStoreNamed(Node* node);
Reduction ReduceJSHasProperty(Node* node);
Reduction ReduceJSLoadProperty(Node* node);
Reduction ReduceJSStoreProperty(Node* node);
Reduction ReduceJSStoreNamedOwn(Node* node);
@ -117,6 +118,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Node* index, Handle<PropertyCell> property_cell);
Reduction ReduceKeyedLoadFromHeapConstant(Node* node, Node* index,
FeedbackNexus const& nexus,
AccessMode access_mode,
KeyedAccessLoadMode load_mode);
Reduction ReduceElementAccessOnString(Node* node, Node* index, Node* value,
AccessMode access_mode,
@ -169,6 +171,9 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
PropertyAccessInfo const& access_info,
AccessMode access_mode);
ValueEffectControl BuildPropertyTest(Node* effect, Node* control,
PropertyAccessInfo const& access_info);
// Helpers for accessor inlining.
Node* InlinePropertyGetterCall(Node* receiver, Node* context,
Node* frame_state, Node** effect,

View File

@ -282,7 +282,8 @@ bool operator!=(PropertyAccess const& lhs, PropertyAccess const& rhs) {
PropertyAccess const& PropertyAccessOf(const Operator* op) {
DCHECK(op->opcode() == IrOpcode::kJSLoadProperty ||
DCHECK(op->opcode() == IrOpcode::kJSHasProperty ||
op->opcode() == IrOpcode::kJSLoadProperty ||
op->opcode() == IrOpcode::kJSStoreProperty);
return OpParameter<PropertyAccess>(op);
}
@ -624,7 +625,6 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
V(CreateTypedArray, Operator::kNoProperties, 5, 1) \
V(CreateObject, Operator::kNoProperties, 1, 1) \
V(ObjectIsArray, Operator::kNoProperties, 1, 1) \
V(HasProperty, Operator::kNoProperties, 2, 1) \
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
V(ForInEnumerate, Operator::kNoProperties, 1, 1) \
@ -950,6 +950,15 @@ const Operator* JSOperatorBuilder::LoadProperty(
access); // parameter
}
const Operator* JSOperatorBuilder::HasProperty(VectorSlotPair const& feedback) {
PropertyAccess access(LanguageMode::kSloppy, feedback);
return new (zone()) Operator1<PropertyAccess>( // --
IrOpcode::kJSHasProperty, Operator::kNoProperties, // opcode
"JSHasProperty", // name
2, 1, 1, 1, 1, 2, // counts
access); // parameter
}
const Operator* JSOperatorBuilder::InstanceOf(VectorSlotPair const& feedback) {
FeedbackParameter parameter(feedback);
return new (zone()) Operator1<FeedbackParameter>( // --

View File

@ -789,7 +789,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* DeleteProperty();
const Operator* HasProperty();
const Operator* HasProperty(VectorSlotPair const& feedback);
const Operator* GetSuperConstructor();

View File

@ -71,6 +71,7 @@ int FeedbackMetadata::GetSlotSize(FeedbackSlotKind kind) {
case FeedbackSlotKind::kLoadGlobalInsideTypeof:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:
@ -267,6 +268,7 @@ void FeedbackVector::ComputeCounts(int* with_type_info, int* generic,
case FeedbackSlotKind::kLoadGlobalInsideTypeof:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:

View File

@ -143,6 +143,8 @@ const char* FeedbackMetadata::Kind2String(FeedbackSlotKind kind) {
return "LoadGlobalNotInsideTypeof";
case FeedbackSlotKind::kLoadKeyed:
return "LoadKeyed";
case FeedbackSlotKind::kHasKeyed:
return "HasKeyed";
case FeedbackSlotKind::kStoreNamedSloppy:
return "StoreNamedSloppy";
case FeedbackSlotKind::kStoreNamedStrict:
@ -264,6 +266,7 @@ Handle<FeedbackVector> FeedbackVector::New(Isolate* isolate,
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:
@ -444,6 +447,7 @@ void FeedbackNexus::ConfigureUninitialized() {
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kStoreDataPropertyInLiteral: {
SetFeedback(*FeedbackVector::UninitializedSentinel(isolate),
SKIP_WRITE_BARRIER);
@ -484,6 +488,7 @@ bool FeedbackNexus::Clear() {
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kStoreGlobalSloppy:
case FeedbackSlotKind::kStoreGlobalStrict:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
@ -594,7 +599,8 @@ InlineCacheState FeedbackNexus::ic_state() const {
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed: {
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed: {
if (feedback == MaybeObject::FromObject(
*FeedbackVector::UninitializedSentinel(isolate))) {
return UNINITIALIZED;
@ -619,7 +625,8 @@ InlineCacheState FeedbackNexus::ic_state() const {
return POLYMORPHIC;
}
if (heap_object->IsName()) {
DCHECK(IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()));
DCHECK(IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) ||
IsKeyedHasICKind(kind()));
Object extra = GetFeedbackExtra()->GetHeapObjectAssumeStrong();
WeakFixedArray extra_array = WeakFixedArray::cast(extra);
return extra_array->length() > 2 ? POLYMORPHIC : MONOMORPHIC;
@ -926,7 +933,7 @@ int FeedbackNexus::ExtractMaps(MapHandles* maps) const {
DCHECK(IsLoadICKind(kind()) || IsStoreICKind(kind()) ||
IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) ||
IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()) ||
IsStoreInArrayLiteralICKind(kind()));
IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind()));
Isolate* isolate = GetIsolate();
MaybeObject feedback = GetFeedback();
@ -974,7 +981,8 @@ int FeedbackNexus::ExtractMaps(MapHandles* maps) const {
MaybeObjectHandle FeedbackNexus::FindHandlerForMap(Handle<Map> map) const {
DCHECK(IsLoadICKind(kind()) || IsStoreICKind(kind()) ||
IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) ||
IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()));
IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()) ||
IsKeyedHasICKind(kind()));
MaybeObject feedback = GetFeedback();
Isolate* isolate = GetIsolate();
@ -1020,7 +1028,7 @@ bool FeedbackNexus::FindHandlers(MaybeObjectHandles* code_list,
DCHECK(IsLoadICKind(kind()) || IsStoreICKind(kind()) ||
IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) ||
IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()) ||
IsStoreInArrayLiteralICKind(kind()));
IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind()));
MaybeObject feedback = GetFeedback();
Isolate* isolate = GetIsolate();
@ -1062,7 +1070,8 @@ bool FeedbackNexus::FindHandlers(MaybeObjectHandles* code_list,
}
Name FeedbackNexus::GetName() const {
if (IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind())) {
if (IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind()) ||
IsKeyedHasICKind(kind())) {
MaybeObject feedback = GetFeedback();
if (IsPropertyNameFeedback(feedback)) {
return Name::cast(feedback->GetHeapObjectAssumeStrong());
@ -1196,7 +1205,7 @@ KeyedAccessStoreMode FeedbackNexus::GetKeyedAccessStoreMode() const {
IcCheckType FeedbackNexus::GetKeyType() const {
DCHECK(IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind()) ||
IsStoreInArrayLiteralICKind(kind()));
IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind()));
MaybeObject feedback = GetFeedback();
if (feedback == MaybeObject::FromObject(
*FeedbackVector::MegamorphicSentinel(GetIsolate()))) {

View File

@ -40,6 +40,7 @@ enum class FeedbackSlotKind {
kLoadGlobalNotInsideTypeof,
kLoadGlobalInsideTypeof,
kLoadKeyed,
kHasKeyed,
kStoreGlobalStrict,
kStoreNamedStrict,
kStoreOwnNamed,
@ -75,6 +76,10 @@ inline bool IsKeyedLoadICKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kLoadKeyed;
}
inline bool IsKeyedHasICKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kHasKeyed;
}
inline bool IsStoreGlobalICKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kStoreGlobalSloppy ||
kind == FeedbackSlotKind::kStoreGlobalStrict;
@ -348,6 +353,10 @@ class V8_EXPORT_PRIVATE FeedbackVectorSpec {
return AddSlot(FeedbackSlotKind::kLoadKeyed);
}
FeedbackSlot AddKeyedHasICSlot() {
return AddSlot(FeedbackSlotKind::kHasKeyed);
}
FeedbackSlotKind GetStoreICSlot(LanguageMode language_mode) {
STATIC_ASSERT(LanguageModeSize == 2);
return is_strict(language_mode) ? FeedbackSlotKind::kStoreNamedStrict

View File

@ -578,6 +578,7 @@ static ObjectStats::VirtualInstanceType GetFeedbackSlotType(
case FeedbackSlotKind::kLoadGlobalInsideTypeof:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
if (obj == *isolate->factory()->uninitialized_symbol() ||
obj == *isolate->factory()->premonomorphic_symbol()) {
return ObjectStats::FEEDBACK_VECTOR_SLOT_LOAD_UNUSED_TYPE;

View File

@ -140,7 +140,7 @@ void AccessorAssembler::HandlePolymorphicCase(
void AccessorAssembler::HandleLoadICHandlerCase(
const LoadICParameters* p, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
ElementSupport support_elements) {
ElementSupport support_elements, LoadAccessMode access_mode) {
Comment("have_handler");
VARIABLE(var_holder, MachineRepresentation::kTagged, p->holder);
@ -159,14 +159,15 @@ void AccessorAssembler::HandleLoadICHandlerCase(
{
HandleLoadICSmiHandlerCase(p, var_holder.value(), var_smi_handler.value(),
handler, miss, exit_point, on_nonexistent,
support_elements);
support_elements, access_mode);
}
BIND(&try_proto_handler);
{
GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler);
HandleLoadICProtoHandler(p, handler, &var_holder, &var_smi_handler,
&if_smi_handler, miss, exit_point, ic_mode);
&if_smi_handler, miss, exit_point, ic_mode,
access_mode);
}
BIND(&call_handler);
@ -307,7 +308,8 @@ TNode<MaybeObject> AccessorAssembler::LoadDescriptorValueOrFieldType(
void AccessorAssembler::HandleLoadICSmiHandlerCase(
const LoadICParameters* p, Node* holder, SloppyTNode<Smi> smi_handler,
SloppyTNode<Object> handler, Label* miss, ExitPoint* exit_point,
OnNonExistent on_nonexistent, ElementSupport support_elements) {
OnNonExistent on_nonexistent, ElementSupport support_elements,
LoadAccessMode access_mode) {
VARIABLE(var_double_value, MachineRepresentation::kFloat64);
Label rebox_double(this, &var_double_value);
@ -318,8 +320,17 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
Label if_element(this), if_indexed_string(this), if_property(this);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kElement)),
&if_element);
Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)),
&if_indexed_string, &if_property);
if (access_mode == LoadAccessMode::kHas) {
CSA_ASSERT(this,
WordNotEqual(handler_kind,
IntPtrConstant(LoadHandler::kIndexedString)));
Goto(&if_property);
} else {
Branch(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)),
&if_indexed_string, &if_property);
}
BIND(&if_element);
Comment("element_load");
@ -334,7 +345,7 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
EmitElementLoad(holder, elements, elements_kind, intptr_index,
is_jsarray_condition, &if_hole, &rebox_double,
&var_double_value, &unimplemented_elements_kind, &if_oob,
miss, exit_point);
miss, exit_point, access_mode);
BIND(&unimplemented_elements_kind);
{
@ -368,41 +379,65 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
miss);
BIND(&return_undefined);
exit_point->Return(UndefinedConstant());
exit_point->Return(access_mode == LoadAccessMode::kHas
? FalseConstant()
: UndefinedConstant());
}
BIND(&if_hole);
{
Comment("convert hole");
GotoIfNot(IsSetWord<LoadHandler::ConvertHoleBits>(handler_word), miss);
if (access_mode != LoadAccessMode::kHas) {
GotoIfNot(IsSetWord<LoadHandler::ConvertHoleBits>(handler_word), miss);
}
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
exit_point->Return(UndefinedConstant());
exit_point->Return(access_mode == LoadAccessMode::kHas
? FalseConstant()
: UndefinedConstant());
}
BIND(&if_indexed_string);
{
Label if_oob(this, Label::kDeferred);
if (access_mode != LoadAccessMode::kHas) {
BIND(&if_indexed_string);
{
Label if_oob(this, Label::kDeferred);
Comment("indexed string");
Node* intptr_index = TryToIntptr(p->name, miss);
Node* length = LoadStringLengthAsWord(holder);
GotoIf(UintPtrGreaterThanOrEqual(intptr_index, length), &if_oob);
TNode<Int32T> code = StringCharCodeAt(holder, intptr_index);
TNode<String> result = StringFromSingleCharCode(code);
Return(result);
Comment("indexed string");
Node* intptr_index = TryToIntptr(p->name, miss);
Node* length = LoadStringLengthAsWord(holder);
GotoIf(UintPtrGreaterThanOrEqual(intptr_index, length), &if_oob);
TNode<Int32T> code = StringCharCodeAt(holder, intptr_index);
TNode<String> result = StringFromSingleCharCode(code);
Return(result);
BIND(&if_oob);
Node* allow_out_of_bounds =
IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word);
GotoIfNot(allow_out_of_bounds, miss);
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
Return(UndefinedConstant());
BIND(&if_oob);
Node* allow_out_of_bounds =
IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word);
GotoIfNot(allow_out_of_bounds, miss);
GotoIf(IsNoElementsProtectorCellInvalid(), miss);
Return(UndefinedConstant());
}
}
BIND(&if_property);
Comment("property_load");
}
if (access_mode == LoadAccessMode::kHas) {
HandleLoadICSmiHandlerHasNamedCase(p, holder, handler_kind, miss,
exit_point);
} else {
HandleLoadICSmiHandlerLoadNamedCase(
p, holder, handler_kind, handler_word, &rebox_double, &var_double_value,
handler, miss, exit_point, on_nonexistent, support_elements);
}
}
void AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase(
const LoadICParameters* p, Node* holder, TNode<IntPtrT> handler_kind,
TNode<WordT> handler_word, Label* rebox_double, Variable* var_double_value,
SloppyTNode<Object> handler, Label* miss, ExitPoint* exit_point,
OnNonExistent on_nonexistent, ElementSupport support_elements) {
Label constant(this), field(this), normal(this, Label::kDeferred),
interceptor(this, Label::kDeferred), nonexistent(this),
accessor(this, Label::kDeferred), global(this, Label::kDeferred),
@ -442,7 +477,7 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
&module_export, &interceptor);
BIND(&field);
HandleLoadField(holder, handler_word, &var_double_value, &rebox_double,
HandleLoadField(holder, handler_word, var_double_value, rebox_double,
exit_point);
BIND(&nonexistent);
@ -589,8 +624,88 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
}
}
BIND(&rebox_double);
exit_point->Return(AllocateHeapNumberWithValue(var_double_value.value()));
BIND(rebox_double);
exit_point->Return(AllocateHeapNumberWithValue(var_double_value->value()));
}
void AccessorAssembler::HandleLoadICSmiHandlerHasNamedCase(
const LoadICParameters* p, Node* holder, TNode<IntPtrT> handler_kind,
Label* miss, ExitPoint* exit_point) {
Label return_true(this), return_false(this), return_lookup(this),
normal(this), global(this);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)),
&return_true);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstant)),
&return_true);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)),
&return_false);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)),
&normal);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)),
&return_true);
GotoIf(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)),
&return_true);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)),
&return_true);
GotoIf(WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)),
&return_true);
Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)), &global,
&return_lookup);
BIND(&return_true);
exit_point->Return(TrueConstant());
BIND(&return_false);
exit_point->Return(FalseConstant());
BIND(&return_lookup);
{
CSA_ASSERT(
this,
Word32Or(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kInterceptor)),
Word32Or(
WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)),
WordEqual(handler_kind,
IntPtrConstant(LoadHandler::kModuleExport)))));
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kHasProperty), p->context,
p->receiver, p->name);
}
BIND(&normal);
{
Comment("has_normal");
TNode<NameDictionary> properties = CAST(LoadSlowProperties(holder));
TVARIABLE(IntPtrT, var_name_index);
Label found(this);
NameDictionaryLookup<NameDictionary>(properties, CAST(p->name), &found,
&var_name_index, miss);
BIND(&found);
exit_point->Return(TrueConstant());
}
BIND(&global);
{
CSA_ASSERT(this, IsPropertyCell(holder));
// Ensure the property cell doesn't contain the hole.
Node* value = LoadObjectField(holder, PropertyCell::kValueOffset);
GotoIf(IsTheHole(value), miss);
exit_point->Return(TrueConstant());
}
}
// Performs actions common to both load and store handlers:
@ -707,7 +822,7 @@ Node* AccessorAssembler::HandleProtoHandler(
void AccessorAssembler::HandleLoadICProtoHandler(
const LoadICParameters* p, Node* handler, Variable* var_holder,
Variable* var_smi_handler, Label* if_smi_handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode) {
ExitPoint* exit_point, ICMode ic_mode, LoadAccessMode access_mode) {
DCHECK_EQ(MachineRepresentation::kTagged, var_holder->rep());
DCHECK_EQ(MachineRepresentation::kTagged, var_smi_handler->rep());
@ -717,14 +832,18 @@ void AccessorAssembler::HandleLoadICProtoHandler(
nullptr,
// on_found_on_receiver
[=](Node* properties, Node* name_index) {
VARIABLE(var_details, MachineRepresentation::kWord32);
VARIABLE(var_value, MachineRepresentation::kTagged);
LoadPropertyFromNameDictionary(properties, name_index, &var_details,
&var_value);
Node* value =
CallGetterIfAccessor(var_value.value(), var_details.value(),
p->context, p->receiver, miss);
exit_point->Return(value);
if (access_mode == LoadAccessMode::kHas) {
exit_point->Return(TrueConstant());
} else {
VARIABLE(var_details, MachineRepresentation::kWord32);
VARIABLE(var_value, MachineRepresentation::kTagged);
LoadPropertyFromNameDictionary(properties, name_index, &var_details,
&var_value);
Node* value =
CallGetterIfAccessor(var_value.value(), var_details.value(),
p->context, p->receiver, miss);
exit_point->Return(value);
}
},
miss, ic_mode);
@ -1764,7 +1883,7 @@ void AccessorAssembler::EmitElementLoad(
SloppyTNode<IntPtrT> intptr_index, Node* is_jsarray_condition,
Label* if_hole, Label* rebox_double, Variable* var_double_value,
Label* unimplemented_elements_kind, Label* out_of_bounds, Label* miss,
ExitPoint* exit_point) {
ExitPoint* exit_point, LoadAccessMode access_mode) {
Label if_typed_array(this), if_fast_packed(this), if_fast_holey(this),
if_fast_double(this), if_fast_holey_double(this), if_nonfast(this),
if_dictionary(this);
@ -1797,7 +1916,9 @@ void AccessorAssembler::EmitElementLoad(
{
Comment("fast packed elements");
exit_point->Return(
UnsafeLoadFixedArrayElement(CAST(elements), intptr_index));
access_mode == LoadAccessMode::kHas
? TrueConstant()
: UnsafeLoadFixedArrayElement(CAST(elements), intptr_index));
}
BIND(&if_fast_holey);
@ -1805,15 +1926,20 @@ void AccessorAssembler::EmitElementLoad(
Comment("fast holey elements");
Node* element = UnsafeLoadFixedArrayElement(CAST(elements), intptr_index);
GotoIf(WordEqual(element, TheHoleConstant()), if_hole);
exit_point->Return(element);
exit_point->Return(access_mode == LoadAccessMode::kHas ? TrueConstant()
: element);
}
BIND(&if_fast_double);
{
Comment("packed double elements");
var_double_value->Bind(LoadFixedDoubleArrayElement(elements, intptr_index,
MachineType::Float64()));
Goto(rebox_double);
if (access_mode == LoadAccessMode::kHas) {
exit_point->Return(TrueConstant());
} else {
var_double_value->Bind(LoadFixedDoubleArrayElement(
elements, intptr_index, MachineType::Float64()));
Goto(rebox_double);
}
}
BIND(&if_fast_holey_double);
@ -1822,8 +1948,12 @@ void AccessorAssembler::EmitElementLoad(
Node* value = LoadFixedDoubleArrayElement(elements, intptr_index,
MachineType::Float64(), 0,
INTPTR_PARAMETERS, if_hole);
var_double_value->Bind(value);
Goto(rebox_double);
if (access_mode == LoadAccessMode::kHas) {
exit_point->Return(TrueConstant());
} else {
var_double_value->Bind(value);
Goto(rebox_double);
}
}
BIND(&if_nonfast);
@ -1845,7 +1975,8 @@ void AccessorAssembler::EmitElementLoad(
TNode<Object> value = BasicLoadNumberDictionaryElement(
CAST(elements), intptr_index, miss, if_hole);
exit_point->Return(value);
exit_point->Return(access_mode == LoadAccessMode::kHas ? TrueConstant()
: value);
}
BIND(&if_typed_array);
@ -1858,97 +1989,101 @@ void AccessorAssembler::EmitElementLoad(
// Bounds check.
Node* length = SmiUntag(LoadJSTypedArrayLength(CAST(object)));
GotoIfNot(UintPtrLessThan(intptr_index, length), out_of_bounds);
if (access_mode == LoadAccessMode::kHas) {
exit_point->Return(TrueConstant());
} else {
Node* backing_store = LoadFixedTypedArrayBackingStore(CAST(elements));
Node* backing_store = LoadFixedTypedArrayBackingStore(CAST(elements));
Label uint8_elements(this), int8_elements(this), uint16_elements(this),
int16_elements(this), uint32_elements(this), int32_elements(this),
float32_elements(this), float64_elements(this), bigint64_elements(this),
biguint64_elements(this);
Label* elements_kind_labels[] = {
&uint8_elements, &uint8_elements, &int8_elements,
&uint16_elements, &int16_elements, &uint32_elements,
&int32_elements, &float32_elements, &float64_elements,
&bigint64_elements, &biguint64_elements};
int32_t elements_kinds[] = {
UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS,
UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS,
INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS,
BIGINT64_ELEMENTS, BIGUINT64_ELEMENTS};
const size_t kTypedElementsKindCount =
LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND -
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1;
DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kinds));
DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kind_labels));
Switch(elements_kind, miss, elements_kinds, elements_kind_labels,
kTypedElementsKindCount);
BIND(&uint8_elements);
{
Comment("UINT8_ELEMENTS"); // Handles UINT8_CLAMPED_ELEMENTS too.
Node* element = Load(MachineType::Uint8(), backing_store, intptr_index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&int8_elements);
{
Comment("INT8_ELEMENTS");
Node* element = Load(MachineType::Int8(), backing_store, intptr_index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&uint16_elements);
{
Comment("UINT16_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(1));
Node* element = Load(MachineType::Uint16(), backing_store, index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&int16_elements);
{
Comment("INT16_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(1));
Node* element = Load(MachineType::Int16(), backing_store, index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&uint32_elements);
{
Comment("UINT32_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(2));
Node* element = Load(MachineType::Uint32(), backing_store, index);
exit_point->Return(ChangeUint32ToTagged(element));
}
BIND(&int32_elements);
{
Comment("INT32_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(2));
Node* element = Load(MachineType::Int32(), backing_store, index);
exit_point->Return(ChangeInt32ToTagged(element));
}
BIND(&float32_elements);
{
Comment("FLOAT32_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(2));
Node* element = Load(MachineType::Float32(), backing_store, index);
var_double_value->Bind(ChangeFloat32ToFloat64(element));
Goto(rebox_double);
}
BIND(&float64_elements);
{
Comment("FLOAT64_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(3));
Node* element = Load(MachineType::Float64(), backing_store, index);
var_double_value->Bind(element);
Goto(rebox_double);
}
BIND(&bigint64_elements);
{
Comment("BIGINT64_ELEMENTS");
exit_point->Return(LoadFixedTypedArrayElementAsTagged(
backing_store, intptr_index, BIGINT64_ELEMENTS, INTPTR_PARAMETERS));
}
BIND(&biguint64_elements);
{
Comment("BIGUINT64_ELEMENTS");
exit_point->Return(LoadFixedTypedArrayElementAsTagged(
backing_store, intptr_index, BIGUINT64_ELEMENTS, INTPTR_PARAMETERS));
Label uint8_elements(this), int8_elements(this), uint16_elements(this),
int16_elements(this), uint32_elements(this), int32_elements(this),
float32_elements(this), float64_elements(this),
bigint64_elements(this), biguint64_elements(this);
Label* elements_kind_labels[] = {
&uint8_elements, &uint8_elements, &int8_elements,
&uint16_elements, &int16_elements, &uint32_elements,
&int32_elements, &float32_elements, &float64_elements,
&bigint64_elements, &biguint64_elements};
int32_t elements_kinds[] = {
UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS,
UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS,
INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS,
BIGINT64_ELEMENTS, BIGUINT64_ELEMENTS};
const size_t kTypedElementsKindCount =
LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND -
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1;
DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kinds));
DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kind_labels));
Switch(elements_kind, miss, elements_kinds, elements_kind_labels,
kTypedElementsKindCount);
BIND(&uint8_elements);
{
Comment("UINT8_ELEMENTS"); // Handles UINT8_CLAMPED_ELEMENTS too.
Node* element = Load(MachineType::Uint8(), backing_store, intptr_index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&int8_elements);
{
Comment("INT8_ELEMENTS");
Node* element = Load(MachineType::Int8(), backing_store, intptr_index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&uint16_elements);
{
Comment("UINT16_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(1));
Node* element = Load(MachineType::Uint16(), backing_store, index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&int16_elements);
{
Comment("INT16_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(1));
Node* element = Load(MachineType::Int16(), backing_store, index);
exit_point->Return(SmiFromInt32(element));
}
BIND(&uint32_elements);
{
Comment("UINT32_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(2));
Node* element = Load(MachineType::Uint32(), backing_store, index);
exit_point->Return(ChangeUint32ToTagged(element));
}
BIND(&int32_elements);
{
Comment("INT32_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(2));
Node* element = Load(MachineType::Int32(), backing_store, index);
exit_point->Return(ChangeInt32ToTagged(element));
}
BIND(&float32_elements);
{
Comment("FLOAT32_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(2));
Node* element = Load(MachineType::Float32(), backing_store, index);
var_double_value->Bind(ChangeFloat32ToFloat64(element));
Goto(rebox_double);
}
BIND(&float64_elements);
{
Comment("FLOAT64_ELEMENTS");
Node* index = WordShl(intptr_index, IntPtrConstant(3));
Node* element = Load(MachineType::Float64(), backing_store, index);
var_double_value->Bind(element);
Goto(rebox_double);
}
BIND(&bigint64_elements);
{
Comment("BIGINT64_ELEMENTS");
exit_point->Return(LoadFixedTypedArrayElementAsTagged(
backing_store, intptr_index, BIGINT64_ELEMENTS, INTPTR_PARAMETERS));
}
BIND(&biguint64_elements);
{
Comment("BIGUINT64_ELEMENTS");
exit_point->Return(LoadFixedTypedArrayElementAsTagged(
backing_store, intptr_index, BIGUINT64_ELEMENTS,
INTPTR_PARAMETERS));
}
}
}
}
@ -2661,7 +2796,8 @@ void AccessorAssembler::LoadGlobalIC_TryHandlerCase(
on_nonexistent);
}
void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) {
void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p,
LoadAccessMode access_mode) {
ExitPoint direct_exit(this);
TVARIABLE(MaybeObject, var_handler);
@ -2681,9 +2817,9 @@ void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) {
&var_handler, &try_polymorphic);
BIND(&if_handler);
{
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit,
ICMode::kNonGlobalIC,
OnNonExistent::kReturnUndefined, kSupportElements);
HandleLoadICHandlerCase(
p, CAST(var_handler.value()), &miss, &direct_exit, ICMode::kNonGlobalIC,
OnNonExistent::kReturnUndefined, kSupportElements, access_mode);
}
BIND(&try_polymorphic);
@ -2707,8 +2843,10 @@ void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) {
BIND(&generic);
{
// TODO(jkummerow): Inline this? Or some of it?
TailCallBuiltin(Builtins::kKeyedLoadIC_Megamorphic, p->context, p->receiver,
p->name, p->slot, p->vector);
TailCallBuiltin(access_mode == LoadAccessMode::kLoad
? Builtins::kKeyedLoadIC_Megamorphic
: Builtins::kKeyedHasIC_Megamorphic,
p->context, p->receiver, p->name, p->slot, p->vector);
}
BIND(&try_polymorphic_name);
@ -2754,16 +2892,20 @@ void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) {
// If the name comparison succeeded, we know we have a weak fixed array
// with at least one map/handler pair.
Node* name = var_name.value();
TailCallBuiltin(Builtins::kKeyedLoadIC_PolymorphicName, p->context,
p->receiver, name, p->slot, p->vector);
TailCallBuiltin(access_mode == LoadAccessMode::kLoad
? Builtins::kKeyedLoadIC_PolymorphicName
: Builtins::kKeyedHasIC_PolymorphicName,
p->context, p->receiver, name, p->slot, p->vector);
}
}
BIND(&miss);
{
Comment("KeyedLoadIC_miss");
TailCallRuntime(Runtime::kKeyedLoadIC_Miss, p->context, p->receiver,
p->name, p->slot, p->vector);
TailCallRuntime(access_mode == LoadAccessMode::kLoad
? Runtime::kKeyedLoadIC_Miss
: Runtime::kKeyedHasIC_Miss,
p->context, p->receiver, p->name, p->slot, p->vector);
}
}
@ -2846,7 +2988,8 @@ void AccessorAssembler::KeyedLoadICGeneric(const LoadICParameters* p) {
}
}
void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p) {
void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p,
LoadAccessMode access_mode) {
TVARIABLE(MaybeObject, var_handler);
Label if_handler(this, &var_handler), miss(this, Label::kDeferred);
@ -2874,16 +3017,18 @@ void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p) {
BIND(&if_handler);
{
ExitPoint direct_exit(this);
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit,
ICMode::kNonGlobalIC,
OnNonExistent::kReturnUndefined, kOnlyProperties);
HandleLoadICHandlerCase(
p, CAST(var_handler.value()), &miss, &direct_exit, ICMode::kNonGlobalIC,
OnNonExistent::kReturnUndefined, kOnlyProperties, access_mode);
}
BIND(&miss);
{
Comment("KeyedLoadIC_miss");
TailCallRuntime(Runtime::kKeyedLoadIC_Miss, context, receiver, name, slot,
vector);
TailCallRuntime(access_mode == LoadAccessMode::kLoad
? Runtime::kKeyedLoadIC_Miss
: Runtime::kKeyedHasIC_Miss,
context, receiver, name, slot, vector);
}
}
@ -3380,7 +3525,7 @@ void AccessorAssembler::GenerateKeyedLoadIC() {
Node* context = Parameter(Descriptor::kContext);
LoadICParameters p(context, receiver, name, slot, vector);
KeyedLoadIC(&p);
KeyedLoadIC(&p, LoadAccessMode::kLoad);
}
void AccessorAssembler::GenerateKeyedLoadIC_Megamorphic() {
@ -3432,7 +3577,7 @@ void AccessorAssembler::GenerateKeyedLoadIC_PolymorphicName() {
Node* context = Parameter(Descriptor::kContext);
LoadICParameters p(context, receiver, name, slot, vector);
KeyedLoadICPolymorphicName(&p);
KeyedLoadICPolymorphicName(&p, LoadAccessMode::kLoad);
}
void AccessorAssembler::GenerateStoreGlobalIC() {
@ -3754,5 +3899,42 @@ void AccessorAssembler::GenerateCloneObjectIC() {
}
}
void AccessorAssembler::GenerateKeyedHasIC() {
typedef LoadWithVectorDescriptor Descriptor;
Node* receiver = Parameter(Descriptor::kReceiver);
Node* name = Parameter(Descriptor::kName);
Node* slot = Parameter(Descriptor::kSlot);
Node* vector = Parameter(Descriptor::kVector);
Node* context = Parameter(Descriptor::kContext);
LoadICParameters p(context, receiver, name, slot, vector);
KeyedLoadIC(&p, LoadAccessMode::kHas);
}
void AccessorAssembler::GenerateKeyedHasIC_Megamorphic() {
typedef LoadWithVectorDescriptor Descriptor;
Node* receiver = Parameter(Descriptor::kReceiver);
Node* name = Parameter(Descriptor::kName);
Node* context = Parameter(Descriptor::kContext);
// TODO(magardn): implement HasProperty handling in KeyedLoadICGeneric
Return(HasProperty(context, receiver, name,
HasPropertyLookupMode::kHasProperty));
}
void AccessorAssembler::GenerateKeyedHasIC_PolymorphicName() {
typedef LoadWithVectorDescriptor Descriptor;
Node* receiver = Parameter(Descriptor::kReceiver);
Node* name = Parameter(Descriptor::kName);
Node* slot = Parameter(Descriptor::kSlot);
Node* vector = Parameter(Descriptor::kVector);
Node* context = Parameter(Descriptor::kContext);
LoadICParameters p(context, receiver, name, slot, vector);
KeyedLoadICPolymorphicName(&p, LoadAccessMode::kHas);
}
} // namespace internal
} // namespace v8

View File

@ -44,6 +44,9 @@ class AccessorAssembler : public CodeStubAssembler {
void GenerateStoreGlobalICTrampoline();
void GenerateCloneObjectIC();
void GenerateCloneObjectIC_Slow();
void GenerateKeyedHasIC();
void GenerateKeyedHasIC_Megamorphic();
void GenerateKeyedHasIC_PolymorphicName();
void GenerateLoadGlobalIC(TypeofMode typeof_mode);
void GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode);
@ -105,6 +108,7 @@ class AccessorAssembler : public CodeStubAssembler {
SloppyTNode<Object> value;
};
enum class LoadAccessMode { kLoad, kHas };
enum class ICMode { kNonGlobalIC, kGlobalIC };
enum ElementSupport { kOnlyProperties, kSupportElements };
void HandleStoreICHandlerCase(
@ -153,9 +157,10 @@ class AccessorAssembler : public CodeStubAssembler {
void LoadIC_Uninitialized(const LoadICParameters* p);
void KeyedLoadIC(const LoadICParameters* p);
void KeyedLoadIC(const LoadICParameters* p, LoadAccessMode access_mode);
void KeyedLoadICGeneric(const LoadICParameters* p);
void KeyedLoadICPolymorphicName(const LoadICParameters* p);
void KeyedLoadICPolymorphicName(const LoadICParameters* p,
LoadAccessMode access_mode);
void StoreIC(const StoreICParameters* p);
void StoreGlobalIC(const StoreICParameters* p);
void StoreGlobalIC_PropertyCellCase(Node* property_cell, Node* value,
@ -180,19 +185,22 @@ class AccessorAssembler : public CodeStubAssembler {
const LoadICParameters* p, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode = ICMode::kNonGlobalIC,
OnNonExistent on_nonexistent = OnNonExistent::kReturnUndefined,
ElementSupport support_elements = kOnlyProperties);
ElementSupport support_elements = kOnlyProperties,
LoadAccessMode access_mode = LoadAccessMode::kLoad);
void HandleLoadICSmiHandlerCase(const LoadICParameters* p, Node* holder,
SloppyTNode<Smi> smi_handler,
SloppyTNode<Object> handler, Label* miss,
ExitPoint* exit_point,
OnNonExistent on_nonexistent,
ElementSupport support_elements);
ElementSupport support_elements,
LoadAccessMode access_mode);
void HandleLoadICProtoHandler(const LoadICParameters* p, Node* handler,
Variable* var_holder, Variable* var_smi_handler,
Label* if_smi_handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode);
ExitPoint* exit_point, ICMode ic_mode,
LoadAccessMode access_mode);
void HandleLoadCallbackProperty(const LoadICParameters* p,
TNode<JSObject> holder,
@ -211,6 +219,18 @@ class AccessorAssembler : public CodeStubAssembler {
void EmitAccessCheck(Node* expected_native_context, Node* context,
Node* receiver, Label* can_access, Label* miss);
void HandleLoadICSmiHandlerLoadNamedCase(
const LoadICParameters* p, Node* holder, TNode<IntPtrT> handler_kind,
TNode<WordT> handler_word, Label* rebox_double,
Variable* var_double_value, SloppyTNode<Object> handler, Label* miss,
ExitPoint* exit_point, OnNonExistent on_nonexistent,
ElementSupport support_elements);
void HandleLoadICSmiHandlerHasNamedCase(const LoadICParameters* p,
Node* holder,
TNode<IntPtrT> handler_kind,
Label* miss, ExitPoint* exit_point);
// LoadGlobalIC implementation.
void LoadGlobalIC_TryPropertyCellCase(
@ -296,7 +316,8 @@ class AccessorAssembler : public CodeStubAssembler {
Label* if_hole, Label* rebox_double,
Variable* var_double_value,
Label* unimplemented_elements_kind, Label* out_of_bounds,
Label* miss, ExitPoint* exit_point);
Label* miss, ExitPoint* exit_point,
LoadAccessMode access_mode = LoadAccessMode::kLoad);
void NameDictionaryNegativeLookup(Node* object, SloppyTNode<Name> name,
Label* miss);
TNode<BoolT> IsPropertyDetailsConst(Node* details);

View File

@ -216,7 +216,7 @@ JSFunction IC::GetHostFunction() const {
return frame->function();
}
static void LookupForRead(Isolate* isolate, LookupIterator* it) {
static void LookupForRead(LookupIterator* it, bool is_has_property) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
case LookupIterator::NOT_FOUND:
@ -227,7 +227,13 @@ static void LookupForRead(Isolate* isolate, LookupIterator* it) {
case LookupIterator::INTERCEPTOR: {
// If there is a getter, return; otherwise loop to perform the lookup.
Handle<JSObject> holder = it->GetHolder<JSObject>();
if (!holder->GetNamedInterceptor()->getter()->IsUndefined(isolate)) {
if (!holder->GetNamedInterceptor()->getter()->IsUndefined(
it->isolate())) {
return;
}
if (is_has_property &&
!holder->GetNamedInterceptor()->query()->IsUndefined(
it->isolate())) {
return;
}
break;
@ -284,7 +290,6 @@ bool IC::RecomputeHandlerForName(Handle<Object> name) {
return true;
}
void IC::UpdateState(Handle<Object> receiver, Handle<Object> name) {
if (state() == NO_FEEDBACK) return;
update_receiver_map(receiver);
@ -306,7 +311,6 @@ MaybeHandle<Object> IC::TypeError(MessageTemplate index, Handle<Object> object,
THROW_NEW_ERROR(isolate(), NewTypeError(index, key, object), Object);
}
MaybeHandle<Object> IC::ReferenceError(Handle<Name> name) {
HandleScope scope(isolate());
THROW_NEW_ERROR(
@ -429,7 +433,8 @@ MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name) {
// If the object is undefined or null it's illegal to try to get any
// of its properties; throw a TypeError in that case.
if (object->IsNullOrUndefined(isolate())) {
if (IsAnyHas() ? !object->IsJSReceiver()
: object->IsNullOrUndefined(isolate())) {
if (use_ic && state() != PREMONOMORPHIC) {
// Ensure the IC state progresses.
TRACE_HANDLER_STATS(isolate(), LoadIC_NonReceiver);
@ -441,7 +446,9 @@ MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name) {
if (*name == ReadOnlyRoots(isolate()).iterator_symbol()) {
return Runtime::ThrowIteratorError(isolate(), object);
}
return TypeError(MessageTemplate::kNonObjectPropertyLoad, object, name);
return TypeError(IsAnyHas() ? MessageTemplate::kInvalidInOperatorUse
: MessageTemplate::kNonObjectPropertyLoad,
object, name);
}
if (MigrateDeprecated(object)) use_ic = false;
@ -450,9 +457,11 @@ MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name) {
JSObject::MakePrototypesFast(object, kStartAtReceiver, isolate());
update_receiver_map(object);
}
// Named lookup in the object.
LookupIterator it(isolate(), object, name);
LookupForRead(isolate(), &it);
// Named lookup in the object.
LookupForRead(&it, IsAnyHas());
if (name->IsPrivate()) {
if (name->IsPrivateName() && !it.IsFound()) {
@ -473,6 +482,14 @@ MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name) {
// Update inline cache and stub cache.
if (use_ic) UpdateCaches(&it);
if (IsAnyHas()) {
// Named lookup in the object.
Maybe<bool> maybe = JSReceiver::HasProperty(&it);
if (maybe.IsNothing()) return MaybeHandle<Object>();
return maybe.FromJust() ? ReadOnlyRoots(isolate()).true_value_handle()
: ReadOnlyRoots(isolate()).false_value_handle();
}
// Get the property.
Handle<Object> result;
@ -619,7 +636,6 @@ void IC::UpdateMonomorphicIC(const MaybeObjectHandle& handler,
ConfigureVectorState(name, receiver_map(), handler);
}
void IC::CopyICToMegamorphicCache(Handle<Name> name) {
MapHandles maps;
MaybeObjectHandles handlers;
@ -654,7 +670,7 @@ void IC::PatchCache(Handle<Name> name, Handle<Object> handler) {
void IC::PatchCache(Handle<Name> name, const MaybeObjectHandle& handler) {
DCHECK(IsHandler(*handler));
// Currently only load and store ICs support non-code handlers.
DCHECK(IsAnyLoad() || IsAnyStore());
DCHECK(IsAnyLoad() || IsAnyStore() || IsAnyHas());
switch (state()) {
case NO_FEEDBACK:
break;
@ -728,6 +744,7 @@ void LoadIC::UpdateCaches(LookupIterator* lookup) {
}
StubCache* IC::stub_cache() {
DCHECK(!IsAnyHas());
if (IsAnyLoad()) {
return isolate()->load_stub_cache();
} else {
@ -738,13 +755,15 @@ StubCache* IC::stub_cache() {
void IC::UpdateMegamorphicCache(Handle<Map> map, Handle<Name> name,
const MaybeObjectHandle& handler) {
stub_cache()->Set(*name, *map, *handler);
if (!IsAnyHas()) {
stub_cache()->Set(*name, *map, *handler);
}
}
void IC::TraceHandlerCacheHitStats(LookupIterator* lookup) {
DCHECK_EQ(LookupIterator::ACCESSOR, lookup->state());
if (V8_LIKELY(!FLAG_runtime_stats)) return;
if (IsAnyLoad()) {
if (IsAnyLoad() || IsAnyHas()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_HandlerCacheHit_Accessor);
} else {
DCHECK(IsAnyStore());
@ -755,22 +774,29 @@ void IC::TraceHandlerCacheHitStats(LookupIterator* lookup) {
Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
Handle<Object> receiver = lookup->GetReceiver();
ReadOnlyRoots roots(isolate());
if (receiver->IsString() && *lookup->name() == roots.length_string()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength);
return BUILTIN_CODE(isolate(), LoadIC_StringLength);
}
if (receiver->IsStringWrapper() && *lookup->name() == roots.length_string()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength);
return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength);
}
// `in` cannot be called on strings, and will always return true for string
// wrapper length and function prototypes. The latter two cases are given
// LoadHandler::LoadNativeDataProperty below.
if (!IsAnyHas()) {
if (receiver->IsString() && *lookup->name() == roots.length_string()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength);
return BUILTIN_CODE(isolate(), LoadIC_StringLength);
}
// Use specialized code for getting prototype of functions.
if (receiver->IsJSFunction() && *lookup->name() == roots.prototype_string() &&
!JSFunction::cast(*receiver)->PrototypeRequiresRuntimeLookup()) {
Handle<Code> stub;
TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub);
return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype);
if (receiver->IsStringWrapper() &&
*lookup->name() == roots.length_string()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength);
return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength);
}
// Use specialized code for getting prototype of functions.
if (receiver->IsJSFunction() &&
*lookup->name() == roots.prototype_string() &&
!JSFunction::cast(*receiver)->PrototypeRequiresRuntimeLookup()) {
TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub);
return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype);
}
}
Handle<Map> map = receiver_map();
@ -1118,22 +1144,30 @@ bool AllowConvertHoleElementToUndefined(Isolate* isolate,
Handle<Object> KeyedLoadIC::LoadElementHandler(Handle<Map> receiver_map,
KeyedAccessLoadMode load_mode) {
// Has a getter interceptor, or is any has and has a query interceptor.
if (receiver_map->has_indexed_interceptor() &&
!receiver_map->GetIndexedInterceptor()->getter()->IsUndefined(
isolate()) &&
(!receiver_map->GetIndexedInterceptor()->getter()->IsUndefined(
isolate()) ||
(IsAnyHas() &&
!receiver_map->GetIndexedInterceptor()->query()->IsUndefined(
isolate()))) &&
!receiver_map->GetIndexedInterceptor()->non_masking()) {
// TODO(jgruber): Update counter name.
TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub);
return BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC);
return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC)
: BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC);
}
InstanceType instance_type = receiver_map->instance_type();
if (instance_type < FIRST_NONSTRING_TYPE) {
TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedStringDH);
if (IsAnyHas()) return BUILTIN_CODE(isolate(), HasIC_Slow);
return LoadHandler::LoadIndexedString(isolate(), load_mode);
}
if (instance_type < FIRST_JS_RECEIVER_TYPE) {
TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_SlowStub);
return BUILTIN_CODE(isolate(), KeyedLoadIC_Slow);
return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIC_Slow)
: BUILTIN_CODE(isolate(), KeyedLoadIC_Slow);
}
if (instance_type == JS_PROXY_TYPE) {
return LoadHandler::LoadProxy(isolate());
@ -1143,7 +1177,8 @@ Handle<Object> KeyedLoadIC::LoadElementHandler(Handle<Map> receiver_map,
if (IsSloppyArgumentsElementsKind(elements_kind)) {
// TODO(jgruber): Update counter name.
TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_KeyedLoadSloppyArgumentsStub);
return BUILTIN_CODE(isolate(), KeyedLoadIC_SloppyArguments);
return IsAnyHas() ? BUILTIN_CODE(isolate(), KeyedHasIC_SloppyArguments)
: BUILTIN_CODE(isolate(), KeyedLoadIC_SloppyArguments);
}
bool is_js_array = instance_type == JS_ARRAY_TYPE;
if (elements_kind == DICTIONARY_ELEMENTS) {
@ -1244,14 +1279,27 @@ KeyedAccessLoadMode GetLoadMode(Isolate* isolate, Handle<Object> receiver,
} // namespace
MaybeHandle<Object> KeyedLoadIC::Load(Handle<Object> object,
Handle<Object> key) {
if (MigrateDeprecated(object)) {
Handle<Object> result;
MaybeHandle<Object> KeyedLoadIC::RuntimeLoad(Handle<Object> object,
Handle<Object> key) {
Handle<Object> result;
if (IsKeyedLoadIC()) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate(), result, Runtime::GetObjectProperty(isolate(), object, key),
Object);
return result;
} else {
DCHECK(IsKeyedHasIC());
ASSIGN_RETURN_ON_EXCEPTION(isolate(), result,
Runtime::HasProperty(isolate(), object, key),
Object);
}
return result;
}
MaybeHandle<Object> KeyedLoadIC::Load(Handle<Object> object,
Handle<Object> key) {
if (MigrateDeprecated(object)) {
return RuntimeLoad(object, key);
}
Handle<Object> load_handle;
@ -1282,11 +1330,7 @@ MaybeHandle<Object> KeyedLoadIC::Load(Handle<Object> object,
if (!load_handle.is_null()) return load_handle;
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(isolate(), result,
Runtime::GetObjectProperty(isolate(), object, key),
Object);
return result;
return RuntimeLoad(object, key);
}
bool StoreIC::LookupForWrite(LookupIterator* it, Handle<Object> value,
@ -1845,7 +1889,6 @@ void KeyedStoreIC::UpdateStoreElement(Handle<Map> receiver_map,
}
}
Handle<Map> KeyedStoreIC::ComputeTransitionedMap(
Handle<Map> map, KeyedAccessStoreMode store_mode) {
switch (store_mode) {
@ -1983,7 +2026,6 @@ void KeyedStoreIC::StoreElementPolymorphicHandlers(
}
}
static KeyedAccessStoreMode GetStoreMode(Handle<JSObject> receiver,
uint32_t index, Handle<Object> value) {
bool oob_access = IsOutOfBoundsAccess(receiver, index);
@ -2028,7 +2070,6 @@ static KeyedAccessStoreMode GetStoreMode(Handle<JSObject> receiver,
}
}
MaybeHandle<Object> KeyedStoreIC::Store(Handle<Object> object,
Handle<Object> key,
Handle<Object> value) {
@ -2822,7 +2863,6 @@ RUNTIME_FUNCTION(Runtime_LoadPropertyWithInterceptor) {
isolate, NewReferenceError(MessageTemplate::kNotDefined, it.name()));
}
RUNTIME_FUNCTION(Runtime_StorePropertyWithInterceptor) {
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
@ -2870,7 +2910,6 @@ RUNTIME_FUNCTION(Runtime_StorePropertyWithInterceptor) {
return *value;
}
RUNTIME_FUNCTION(Runtime_LoadElementWithInterceptor) {
// TODO(verwaest): This should probably get the holder and receiver as input.
HandleScope scope(isolate);
@ -2896,5 +2935,61 @@ RUNTIME_FUNCTION(Runtime_LoadElementWithInterceptor) {
return *result;
}
RUNTIME_FUNCTION(Runtime_KeyedHasIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
// Runtime functions don't follow the IC's calling convention.
Handle<Object> receiver = args.at(0);
Handle<Object> key = args.at(1);
Handle<Smi> slot = args.at<Smi>(2);
Handle<HeapObject> maybe_vector = args.at<HeapObject>(3);
Handle<FeedbackVector> vector = Handle<FeedbackVector>();
if (!maybe_vector->IsUndefined()) {
DCHECK(maybe_vector->IsFeedbackVector());
vector = Handle<FeedbackVector>::cast(maybe_vector);
}
FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot->value());
KeyedLoadIC ic(isolate, vector, vector_slot, FeedbackSlotKind::kHasKeyed);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));
}
RUNTIME_FUNCTION(Runtime_HasElementWithInterceptor) {
HandleScope scope(isolate);
Handle<JSObject> receiver = args.at<JSObject>(0);
DCHECK_GE(args.smi_at(1), 0);
uint32_t index = args.smi_at(1);
Handle<InterceptorInfo> interceptor(receiver->GetIndexedInterceptor(),
isolate);
PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver,
*receiver, Just(kDontThrow));
if (!interceptor->query()->IsUndefined(isolate)) {
Handle<Object> result = arguments.CallIndexedQuery(interceptor, index);
if (!result.is_null()) {
int32_t value;
CHECK(result->ToInt32(&value));
return value == ABSENT ? ReadOnlyRoots(isolate).false_value()
: ReadOnlyRoots(isolate).true_value();
}
} else if (!interceptor->getter()->IsUndefined(isolate)) {
Handle<Object> result = arguments.CallIndexedGetter(interceptor, index);
if (!result.is_null()) {
return ReadOnlyRoots(isolate).true_value();
}
}
LookupIterator it(isolate, receiver, index, receiver);
DCHECK_EQ(LookupIterator::INTERCEPTOR, it.state());
it.Next();
Maybe<bool> maybe = JSReceiver::HasProperty(&it);
if (maybe.IsNothing()) return ReadOnlyRoots(isolate).exception();
return maybe.FromJust() ? ReadOnlyRoots(isolate).true_value()
: ReadOnlyRoots(isolate).false_value();
}
} // namespace internal
} // namespace v8

View File

@ -50,6 +50,7 @@ class IC {
state_ = RECOMPUTE_HANDLER;
}
bool IsAnyHas() const { return IsKeyedHasIC(); }
bool IsAnyLoad() const {
return IsLoadIC() || IsLoadGlobalIC() || IsKeyedLoadIC();
}
@ -130,9 +131,10 @@ class IC {
bool IsStoreIC() const { return IsStoreICKind(kind_); }
bool IsStoreOwnIC() const { return IsStoreOwnICKind(kind_); }
bool IsKeyedStoreIC() const { return IsKeyedStoreICKind(kind_); }
bool IsKeyedHasIC() const { return IsKeyedHasICKind(kind_); }
bool is_keyed() const {
return IsKeyedLoadIC() || IsKeyedStoreIC() ||
IsStoreInArrayLiteralICKind(kind_);
IsStoreInArrayLiteralICKind(kind_) || IsKeyedHasIC();
}
bool ShouldRecomputeHandler(Handle<String> name);
@ -200,13 +202,12 @@ class IC {
DISALLOW_IMPLICIT_CONSTRUCTORS(IC);
};
class LoadIC : public IC {
public:
LoadIC(Isolate* isolate, Handle<FeedbackVector> vector, FeedbackSlot slot,
FeedbackSlotKind kind)
: IC(isolate, vector, slot, kind) {
DCHECK(IsAnyLoad());
DCHECK(IsAnyLoad() || IsAnyHas());
}
static bool ShouldThrowReferenceError(FeedbackSlotKind kind) {
@ -222,7 +223,8 @@ class LoadIC : public IC {
protected:
virtual Handle<Code> slow_stub() const {
return BUILTIN_CODE(isolate(), LoadIC_Slow);
return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIC_Slow)
: BUILTIN_CODE(isolate(), LoadIC_Slow);
}
// Update the inline cache and the global stub cache based on the
@ -260,6 +262,9 @@ class KeyedLoadIC : public LoadIC {
Handle<Object> key);
protected:
V8_WARN_UNUSED_RESULT MaybeHandle<Object> RuntimeLoad(Handle<Object> object,
Handle<Object> key);
// receiver is HeapObject because it could be a String or a JSObject
void UpdateLoadElement(Handle<HeapObject> receiver,
KeyedAccessLoadMode load_mode);
@ -280,7 +285,6 @@ class KeyedLoadIC : public LoadIC {
bool CanChangeToAllowOutOfBounds(Handle<Map> receiver_map);
};
class StoreIC : public IC {
public:
StoreIC(Isolate* isolate, Handle<FeedbackVector> vector, FeedbackSlot slot,
@ -331,10 +335,8 @@ class StoreGlobalIC : public StoreIC {
enum KeyedStoreCheckMap { kDontCheckMap, kCheckMap };
enum KeyedStoreIncrementLength { kDontIncrementLength, kIncrementLength };
class KeyedStoreIC : public StoreIC {
public:
KeyedAccessStoreMode GetKeyedAccessStoreMode() {

View File

@ -506,17 +506,8 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CompareOperation(
case Token::Value::INSTANCEOF:
OutputTestInstanceOf(reg, feedback_slot);
break;
default:
UNREACHABLE();
}
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::CompareOperation(Token::Value op,
Register reg) {
switch (op) {
case Token::Value::IN:
OutputTestIn(reg);
OutputTestIn(reg, feedback_slot);
break;
default:
UNREACHABLE();

View File

@ -379,7 +379,6 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final {
// Tests.
BytecodeArrayBuilder& CompareOperation(Token::Value op, Register reg,
int feedback_slot);
BytecodeArrayBuilder& CompareOperation(Token::Value op, Register reg);
BytecodeArrayBuilder& CompareReference(Register reg);
BytecodeArrayBuilder& CompareUndetectable();
BytecodeArrayBuilder& CompareUndefined();

View File

@ -4850,15 +4850,15 @@ void BytecodeGenerator::VisitCompareOperation(CompareOperation* expr) {
Register lhs = VisitForRegisterValue(expr->left());
VisitForAccumulatorValue(expr->right());
builder()->SetExpressionPosition(expr);
FeedbackSlot slot;
if (expr->op() == Token::IN) {
builder()->CompareOperation(expr->op(), lhs);
slot = feedback_spec()->AddKeyedHasICSlot();
} else if (expr->op() == Token::INSTANCEOF) {
FeedbackSlot slot = feedback_spec()->AddInstanceOfSlot();
builder()->CompareOperation(expr->op(), lhs, feedback_index(slot));
slot = feedback_spec()->AddInstanceOfSlot();
} else {
FeedbackSlot slot = feedback_spec()->AddCompareICSlot();
builder()->CompareOperation(expr->op(), lhs, feedback_index(slot));
slot = feedback_spec()->AddCompareICSlot();
}
builder()->CompareOperation(expr->op(), lhs, feedback_index(slot));
}
// Always returns a boolean value.
execution_result()->SetResultIsBoolean();

View File

@ -235,7 +235,7 @@ namespace interpreter {
V(TestReferenceEqual, AccumulatorUse::kReadWrite, OperandType::kReg) \
V(TestInstanceOf, AccumulatorUse::kReadWrite, OperandType::kReg, \
OperandType::kIdx) \
V(TestIn, AccumulatorUse::kReadWrite, OperandType::kReg) \
V(TestIn, AccumulatorUse::kReadWrite, OperandType::kReg, OperandType::kIdx) \
V(TestUndetectable, AccumulatorUse::kReadWrite) \
V(TestNull, AccumulatorUse::kReadWrite) \
V(TestUndefined, AccumulatorUse::kReadWrite) \

View File

@ -1876,16 +1876,22 @@ IGNITION_HANDLER(TestReferenceEqual, InterpreterAssembler) {
Dispatch();
}
// TestIn <src>
// TestIn <src> <feedback_slot>
//
// Test if the object referenced by the register operand is a property of the
// object referenced by the accumulator.
IGNITION_HANDLER(TestIn, InterpreterAssembler) {
Node* property = LoadRegisterAtOperandIndex(0);
Node* name = LoadRegisterAtOperandIndex(0);
Node* object = GetAccumulator();
Node* raw_slot = BytecodeOperandIdx(1);
Node* smi_slot = SmiTag(raw_slot);
Node* feedback_vector = LoadFeedbackVectorUnchecked();
Node* context = GetContext();
SetAccumulator(HasProperty(context, object, property, kHasProperty));
VARIABLE(var_result, MachineRepresentation::kTagged);
var_result.Bind(CallBuiltin(Builtins::kKeyedHasIC, context, object, name,
smi_slot, feedback_vector));
SetAccumulator(var_result.value());
Dispatch();
}

View File

@ -1128,21 +1128,22 @@ void FeedbackVector::FeedbackSlotPrint(std::ostream& os,
void FeedbackNexus::Print(std::ostream& os) { // NOLINT
switch (kind()) {
case FeedbackSlotKind::kCall:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kInstanceOf:
case FeedbackSlotKind::kLoadGlobalInsideTypeof:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kStoreDataPropertyInLiteral:
case FeedbackSlotKind::kStoreGlobalSloppy:
case FeedbackSlotKind::kStoreGlobalStrict:
case FeedbackSlotKind::kStoreKeyedSloppy:
case FeedbackSlotKind::kInstanceOf:
case FeedbackSlotKind::kStoreDataPropertyInLiteral:
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kCloneObject: {
case FeedbackSlotKind::kStoreKeyedSloppy:
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed: {
os << InlineCacheState2String(ic_state());
break;
}

View File

@ -53,6 +53,30 @@ MaybeHandle<Object> Runtime::GetObjectProperty(Isolate* isolate,
return result;
}
MaybeHandle<Object> Runtime::HasProperty(Isolate* isolate,
Handle<Object> object,
Handle<Object> key) {
// Check that {object} is actually a receiver.
if (!object->IsJSReceiver()) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalidInOperatorUse, key, object),
Object);
}
Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(object);
// Convert the {key} to a name.
Handle<Name> name;
ASSIGN_RETURN_ON_EXCEPTION(isolate, name, Object::ToName(isolate, key),
Object);
// Lookup the {name} on {receiver}.
Maybe<bool> maybe = JSReceiver::HasProperty(receiver, name);
if (maybe.IsNothing()) return MaybeHandle<Object>();
return maybe.FromJust() ? ReadOnlyRoots(isolate).true_value_handle()
: ReadOnlyRoots(isolate).false_value_handle();
}
namespace {
bool DeleteObjectPropertyFast(Isolate* isolate, Handle<JSReceiver> receiver,

View File

@ -573,7 +573,9 @@ namespace internal {
F(StoreIC_Miss, 5, 1) \
F(StoreInArrayLiteralIC_Slow, 5, 1) \
F(StorePropertyWithInterceptor, 5, 1) \
F(CloneObjectIC_Miss, 4, 1)
F(CloneObjectIC_Miss, 4, 1) \
F(KeyedHasIC_Miss, 4, 1) \
F(HasElementWithInterceptor, 2, 1)
#define FOR_EACH_INTRINSIC_RETURN_OBJECT_IMPL(F, I) \
FOR_EACH_INTRINSIC_ARRAY(F, I) \
@ -708,6 +710,9 @@ class Runtime : public AllStatic {
Isolate* isolate, Handle<Object> object, Handle<Object> key,
bool* is_found_out = nullptr);
V8_WARN_UNUSED_RESULT static MaybeHandle<Object> HasProperty(
Isolate* isolate, Handle<Object> object, Handle<Object> key);
V8_WARN_UNUSED_RESULT static MaybeHandle<JSArray> GetInternalProperties(
Isolate* isolate, Handle<Object>);

View File

@ -171,11 +171,11 @@ snippet: "
"
frame size: 0
parameter count: 3
bytecode array length: 14
bytecode array length: 15
bytecodes: [
/* 10 E> */ B(StackCheck),
/* 19 S> */ B(Ldar), R(arg1),
/* 25 E> */ B(TestIn), R(arg0),
/* 25 E> */ B(TestIn), R(arg0), U8(0),
B(JumpIfFalse), U8(7),
/* 33 S> */ B(Wide), B(LdaSmi), I16(200),
/* 44 S> */ B(Return),
@ -396,7 +396,7 @@ snippet: "
"
frame size: 0
parameter count: 3
bytecode array length: 82
bytecode array length: 83
bytecodes: [
/* 10 E> */ B(StackCheck),
/* 21 S> */ B(Ldar), R(arg1),
@ -430,12 +430,12 @@ bytecodes: [
/* 174 S> */ B(LdaSmi), I8(1),
/* 183 S> */ B(Return),
/* 188 S> */ B(Ldar), R(arg1),
/* 194 E> */ B(TestIn), R(arg0),
/* 194 E> */ B(TestIn), R(arg0), U8(6),
B(JumpIfFalse), U8(5),
/* 202 S> */ B(LdaSmi), I8(1),
/* 211 S> */ B(Return),
/* 216 S> */ B(Ldar), R(arg1),
/* 222 E> */ B(TestInstanceOf), R(arg0), U8(6),
/* 222 E> */ B(TestInstanceOf), R(arg0), U8(8),
B(JumpIfFalse), U8(5),
/* 238 S> */ B(LdaSmi), I8(1),
/* 247 S> */ B(Return),

View File

@ -2275,21 +2275,26 @@ TEST(InterpreterTestIn) {
const char* properties[] = {"length", "fuzzle", "x", "0"};
for (size_t i = 0; i < arraysize(properties); i++) {
bool expected_value = (i == 0);
BytecodeArrayBuilder builder(zone, 1, 1);
FeedbackVectorSpec feedback_spec(zone);
BytecodeArrayBuilder builder(zone, 1, 1, &feedback_spec);
Register r0(0);
builder.LoadLiteral(ast_factory.GetOneByteString(properties[i]))
.StoreAccumulatorInRegister(r0);
FeedbackSlot slot = feedback_spec.AddKeyedHasICSlot();
Handle<i::FeedbackMetadata> metadata =
NewFeedbackMetadata(isolate, &feedback_spec);
size_t array_entry = builder.AllocateDeferredConstantPoolEntry();
builder.SetDeferredConstantPoolEntry(array_entry, array);
builder.LoadConstantPoolEntry(array_entry)
.CompareOperation(Token::Value::IN, r0)
.CompareOperation(Token::Value::IN, r0, GetIndex(slot))
.Return();
ast_factory.Internalize(isolate);
Handle<BytecodeArray> bytecode_array = builder.ToBytecodeArray(isolate);
InterpreterTester tester(isolate, bytecode_array);
InterpreterTester tester(isolate, bytecode_array, metadata);
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->IsBoolean());

View File

@ -470,6 +470,9 @@ THREADED_TEST(QueryInterceptor) {
v8_compile("Object.isFrozen('obj.x');")->Run(env.local()).ToLocalChecked();
CHECK_EQ(8, query_counter_int);
v8_compile("'x' in obj;")->Run(env.local()).ToLocalChecked();
CHECK_EQ(9, query_counter_int);
}
namespace {
@ -874,15 +877,14 @@ THREADED_TEST(InterceptorHasOwnPropertyCausingGC) {
CHECK(!value->BooleanValue(isolate));
}
static void CheckInterceptorLoadIC(
v8::GenericNamedPropertyGetterCallback getter, const char* source,
int expected) {
static void CheckInterceptorIC(v8::GenericNamedPropertyGetterCallback getter,
v8::GenericNamedPropertyQueryCallback query,
const char* source, int expected) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
getter, nullptr, nullptr, nullptr, nullptr, v8_str("data")));
getter, nullptr, query, nullptr, nullptr, v8_str("data")));
LocalContext context;
context->Global()
->Set(context.local(), v8_str("o"),
@ -892,6 +894,11 @@ static void CheckInterceptorLoadIC(
CHECK_EQ(expected, value->Int32Value(context.local()).FromJust());
}
static void CheckInterceptorLoadIC(
v8::GenericNamedPropertyGetterCallback getter, const char* source,
int expected) {
CheckInterceptorIC(getter, nullptr, source, expected);
}
static void InterceptorLoadICGetter(
Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
@ -1432,6 +1439,92 @@ THREADED_TEST(InterceptorReturningZero) {
0);
}
namespace {
template <typename TKey, v8::internal::PropertyAttributes attribute>
void HasICQuery(TKey name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
info.GetReturnValue().Set(v8::Integer::New(isolate, attribute));
}
template <typename TKey>
void HasICQueryToggle(TKey name,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
static bool toggle = false;
toggle = !toggle;
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
info.GetReturnValue().Set(v8::Integer::New(
isolate, toggle ? v8::internal::ABSENT : v8::internal::NONE));
}
int named_query_counter = 0;
void NamedQueryCallback(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
named_query_counter++;
}
} // namespace
THREADED_TEST(InterceptorHasIC) {
named_query_counter = 0;
CheckInterceptorIC(nullptr, NamedQueryCallback,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" 'x' in o;"
"}",
0);
CHECK_EQ(1000, named_query_counter);
}
THREADED_TEST(InterceptorHasICQueryAbsent) {
CheckInterceptorIC(nullptr, HasICQuery<Local<Name>, v8::internal::ABSENT>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if ('x' in o) ++result;"
"}",
0);
}
THREADED_TEST(InterceptorHasICQueryNone) {
CheckInterceptorIC(nullptr, HasICQuery<Local<Name>, v8::internal::NONE>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if ('x' in o) ++result;"
"}",
1000);
}
THREADED_TEST(InterceptorHasICGetter) {
CheckInterceptorIC(InterceptorLoadICGetter, nullptr,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if ('x' in o) ++result;"
"}",
1000);
}
THREADED_TEST(InterceptorHasICQueryGetter) {
CheckInterceptorIC(InterceptorLoadICGetter,
HasICQuery<Local<Name>, v8::internal::ABSENT>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if ('x' in o) ++result;"
"}",
0);
}
THREADED_TEST(InterceptorHasICQueryToggle) {
CheckInterceptorIC(InterceptorLoadICGetter, HasICQueryToggle<Local<Name>>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if ('x' in o) ++result;"
"}",
500);
}
static void InterceptorStoreICSetter(
Local<Name> key, Local<Value> value,
@ -3273,6 +3366,101 @@ THREADED_TEST(IndexedInterceptorOnProto) {
ExpectString(code, "PASSED");
}
namespace {
void CheckIndexedInterceptorHasIC(v8::IndexedPropertyGetterCallback getter,
v8::IndexedPropertyQueryCallback query,
const char* source, int expected) {
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
getter, nullptr, query, nullptr, nullptr, v8_str("data")));
LocalContext context;
context->Global()
->Set(context.local(), v8_str("o"),
templ->NewInstance(context.local()).ToLocalChecked())
.FromJust();
v8::Local<Value> value = CompileRun(source);
CHECK_EQ(expected, value->Int32Value(context.local()).FromJust());
}
int indexed_query_counter = 0;
void IndexedQueryCallback(uint32_t index,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
indexed_query_counter++;
}
void IndexHasICQueryAbsent(uint32_t index,
const v8::PropertyCallbackInfo<v8::Integer>& info) {
ApiTestFuzzer::Fuzz();
v8::Isolate* isolate = CcTest::isolate();
CHECK_EQ(isolate, info.GetIsolate());
info.GetReturnValue().Set(v8::Integer::New(isolate, v8::internal::ABSENT));
}
} // namespace
THREADED_TEST(IndexedInterceptorHasIC) {
indexed_query_counter = 0;
CheckIndexedInterceptorHasIC(nullptr, IndexedQueryCallback,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" i in o;"
"}",
0);
CHECK_EQ(1000, indexed_query_counter);
}
THREADED_TEST(IndexedInterceptorHasICQueryAbsent) {
CheckIndexedInterceptorHasIC(nullptr,
// HasICQuery<uint32_t, v8::internal::ABSENT>,
IndexHasICQueryAbsent,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if (i in o) ++result;"
"}",
0);
}
THREADED_TEST(IndexedInterceptorHasICQueryNone) {
CheckIndexedInterceptorHasIC(nullptr,
HasICQuery<uint32_t, v8::internal::NONE>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if (i in o) ++result;"
"}",
1000);
}
THREADED_TEST(IndexedInterceptorHasICGetter) {
CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter, nullptr,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if (i in o) ++result;"
"}",
1000);
}
THREADED_TEST(IndexedInterceptorHasICQueryGetter) {
CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter,
HasICQuery<uint32_t, v8::internal::ABSENT>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if (i in o) ++result;"
"}",
0);
}
THREADED_TEST(IndexedInterceptorHasICQueryToggle) {
CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter,
HasICQueryToggle<uint32_t>,
"var result = 0;"
"for (var i = 0; i < 1000; i++) {"
" if (i in o) ++result;"
"}",
500);
}
static void NoBlockGetterX(Local<Name> name,
const v8::PropertyCallbackInfo<v8::Value>&) {}

View File

@ -366,3 +366,14 @@ assertEquals(117, arg_set(0xFFFFFFFF));
f7(1,2,3,4,5,6,7);
f7(1,2,3,4,5,6,7,8);
})();
(function testArgumentsHole() {
function f(a) {
arguments[3] = 1;
return arguments[2];
};
assertEquals(undefined, f(1));
assertEquals(undefined, f(1));
assertEquals(undefined, f(1));
})();

View File

@ -0,0 +1,9 @@
// Copyright 2019 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.
// MODULE
export var a = "A";
export var b = "B";
export var c = "C";

View File

@ -0,0 +1,70 @@
// Copyright 2019 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
// MODULE
import * as mod from "keyed-has-ic-module-export.js";
function testIn(obj, key) {
return key in obj;
}
function expectTrue(obj, key) {
assertTrue(testIn(obj, key));
}
function expectFalse(obj, key) {
assertFalse(testIn(obj, key));
}
var tests = {
monomporphicTrue: function() {
expectTrue(mod, "a");
expectTrue(mod, "a");
expectTrue(mod, "a");
},
polymprohicKeyTrue: function() {
expectTrue(mod, "a");
expectTrue(mod, "b");
expectTrue(mod, "c");
},
monomorphicFalse: function() {
expectFalse(mod, "d");
expectFalse(mod, "d");
expectFalse(mod, "d");
},
polymorphicKeyFalse: function() {
expectFalse(mod, "d");
expectFalse(mod, "e");
expectFalse(mod, "f");
},
polymorphicTrue: function() {
var o = {a: "A"};
expectTrue(mod, "a");
expectTrue(o, "a");
expectTrue(mod, "a");
expectTrue(o, "a");
},
polymorphicFalse: function() {
var o = {a: "A"};
expectFalse(mod, "d");
expectFalse(o, "d");
expectFalse(mod, "d");
expectFalse(o, "d");
}
};
for (let test in tests) {
%DeoptimizeFunction(testIn);
%ClearFunctionFeedback(testIn);
tests[test]();
%OptimizeFunctionOnNextCall(testIn);
tests[test]();
}

View File

@ -0,0 +1,402 @@
// Copyright 2019 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 testIn(obj, key) {
return key in obj;
}
function expectTrue(obj, key) {
assertTrue(testIn(obj, key));
}
function expectFalse(obj, key) {
assertFalse(testIn(obj, key));
}
var tests = {
TestMonomorphicPackedSMIArray: function() {
var a = [0, 1, 2];
expectTrue(a, 0);
expectTrue(a, 1);
expectTrue(a, 2);
expectFalse(a, 3);
},
TestMonomorphicPackedArrayPrototypeProperty: function()
{
var a = [0, 1, 2];
expectTrue(a, 0);
expectTrue(a, 1);
expectFalse(a, 3);
Array.prototype[3] = 3;
expectTrue(a, 3);
// ensure the prototype change doesn't affect later tests
delete Array.prototype[3];
assertFalse((3 in Array.prototype));
expectFalse(a, 3);
},
TestMonomorphicPackedDoubleArray: function() {
var a = [0.0, 1.1, 2.2];
expectTrue(a, 0);
expectTrue(a, 1);
expectTrue(a, 2);
expectFalse(a, 3);
},
TestMonomorphicPackedArray: function() {
var a = ["A", "B", {}];
expectTrue(a, 0);
expectTrue(a, 1);
expectTrue(a, 2);
expectFalse(a, 3);
},
TestMonomorphicHoleyArray: function() {
var a = [0, 1, 2];
a[4] = 4;
expectTrue(a, 0);
expectTrue(a, 1);
expectTrue(a, 2);
expectFalse(a, 3);
expectTrue(a, 4);
},
TestMonomorphicTypedArray: function() {
var a = new Int32Array(3);
expectTrue(a, 0);
expectTrue(a, 1);
expectTrue(a, 2);
expectFalse(a, 3);
expectFalse(a, 4);
},
TestPolymorphicPackedArrays: function() {
var a = [0, 1, 2];
var b = [0.0, 1.1, 2.2];
var c = ["A", "B", {}];
expectTrue(c, 0);
expectTrue(b, 1);
expectTrue(a, 2);
expectTrue(c, 1);
expectTrue(b, 2);
expectTrue(a, 0);
expectFalse(c, 3);
expectFalse(b, 4);
expectFalse(a, 5);
},
TestPolymorphicMixedArrays: function() {
var a = new Array(3); // holey SMI
var b = [0.0,1.1,2.2]; // packed double
var c = new Int8Array(3); // typed array
expectFalse(a, 0);
expectTrue(b, 1);
expectTrue(c, 2);
expectFalse(a, 1);
expectTrue(b, 2);
expectTrue(c, 0);
expectFalse(a, 3);
expectFalse(b, 4);
expectFalse(c, 5);
},
TestMegamorphicArrays: function() {
var a = [0,1,2,3] // packed SMI
var b = new Array(3); // holey SMI
var c = [0.0,1.1,2.2]; // packed double
var d = ['a', 'b', 'c'] // packed
var e = new Int8Array(3); // typed array
var f = new Uint8Array(3); // typed array
var g = new Int32Array(3); // typed array
expectTrue(a, 0);
expectFalse(b, 1);
expectTrue(c, 2);
expectFalse(d, 3);
expectFalse(e, 4);
expectFalse(f, 5);
expectFalse(g, 6);
expectFalse(a, 5);
expectFalse(b, 4);
expectFalse(c, 3);
expectTrue(d, 2);
expectTrue(e, 1);
expectTrue(f, 0);
expectTrue(g, 0);
},
TestMonomorphicObject: function() {
var a = { a: "A", b: "B" };
expectTrue(a, 'a');
expectTrue(a, 'a');
expectTrue(a, 'a');
},
TestMonomorphicProxyHasPropertyNoTrap: function() {
var a = new Proxy({a: 'A'}, {});
expectTrue(a, 'a');
expectTrue(a, 'a');
expectTrue(a, 'a');
},
TestMonomorphicProxyNoPropertyNoTrap: function() {
var a = new Proxy({}, {});
expectFalse(a, 'a');
expectFalse(a, 'a');
expectFalse(a, 'a');
},
TestMonomorphicProxyHasPropertyHasTrap: function() {
var a = new Proxy({a: 'A'}, { has: function() {return false;}});
expectFalse(a, 'a');
expectFalse(a, 'a');
expectFalse(a, 'a');
},
TestMonomorphicProxyNoPropertyHasTrap: function() {
var a = new Proxy({}, { has: function() { return true; }});
expectTrue(a, 'a');
expectTrue(a, 'a');
expectTrue(a, 'a');
},
TestMonomorphicObjectPrototype: function() {
var a = { b: "B" };
expectFalse(a, 'a');
expectFalse(a, 'a');
expectFalse(a, 'a');
Object.prototype.a = 'A';
expectTrue(a, 'a');
delete Object.prototype.a;
assertFalse((a in Object.prototype));
expectFalse(a, 'a');
},
TestPolymorphicObject: function() {
var a = { a: "A" };
var b = { a: "A", b: "B" };
var c = { b: "B", c: "C" };
expectTrue(a, 'a');
expectTrue(a, 'a');
expectTrue(b, 'a');
expectFalse(c, 'a');
expectTrue(a, 'a');
expectTrue(b, 'a');
expectFalse(c, 'a');
},
TestMegamorphicObject: function() {
var a = { a: "A" };
var b = { a: "A", b: "B" };
var c = { b: "B", c: "C" };
var d = { b: "A", a: "B" };
var e = { e: "E", a: "A" };
var f = { f: "F", b: "B", c: "C" };
expectTrue(a, 'a');
expectTrue(a, 'a');
expectTrue(b, 'a');
expectFalse(c, 'a');
expectTrue(d, 'a');
expectTrue(e, 'a');
expectFalse(f, 'a');
expectTrue(a, 'a');
expectTrue(b, 'a');
expectFalse(c, 'a');
expectTrue(d, 'a');
expectTrue(e, 'a');
expectFalse(f, 'a');
},
TestPolymorphicKeys: function() {
var a = { a: "A", b: "B" };
expectTrue(a, 'a');
expectTrue(a, 'b');
expectFalse(a, 'c');
expectTrue(a, 'a');
expectTrue(a, 'b');
expectFalse(a, 'c');
expectTrue(a, 'a');
expectTrue(a, 'b');
expectFalse(a, 'c');
},
TestPolymorphicMixed: function() {
var a = { a: "A" };
var b = new Proxy({}, {});
var c = new Int32Array(3);
expectTrue(a, 'a');
expectTrue(a, 'a');
expectFalse(b, 'a');
expectFalse(c, 'a');
expectTrue(a, 'a');
expectFalse(b, 'a');
expectFalse(c, 'a');
},
};
for (test in tests) {
%DeoptimizeFunction(testIn);
%ClearFunctionFeedback(testIn);
tests[test]();
%OptimizeFunctionOnNextCall(testIn);
tests[test]();
}
// test function prototypes.
(function() {
var o = function() {};
var proto = function() {
assertTrue("prototype" in o);
o.prototype;
}
proto();
proto();
proto();
})();
// `in` is not allowed on string
(function() {
function test() {
0 in "string"
};
assertThrows(test, TypeError);
})();
// `in` is allowed on `this` even when `this` is a string
(function() {
function test() {
assertTrue("length" in this);
};
test.call("");
test.call("");
test.call("");
})();
(function() {
var index = 0;
function test(i) {
return index in arguments;
};
assertFalse(test())
assertFalse(test())
assertTrue(test(0));
assertTrue(test(0,1));
index = 2;
assertFalse(test())
assertFalse(test(0));
assertFalse(test(0,1));
assertTrue(test(0,1,2));
})();
(function() {
function test(a) {
arguments[3] = 1;
return 2 in arguments;
};
assertFalse(test(1));
assertFalse(test(1));
assertFalse(test(1));
})();
(function() {
function test(o, k) {
try {
k in o;
} catch (e) {
return false;
}
return true;
}
var str = "string";
// this will place slow_stub in the IC for strings.
assertFalse(test(str, "length"));
assertFalse(test(str, "length"));
// this turns the cache polymorphic, and causes generats LoadElement
// handlers for everything in the cache. This test ensures that
// KeyedLoadIC::LoadElementHandler can handle seeing string maps.
var ary = [0,1,2,3];
assertTrue(test(ary, 1));
assertTrue(test(ary, 1));
assertFalse(test(str, 0));
assertFalse(test(str, 0));
})();
(function() {
function test(o, k) {
try {
k in o;
} catch (e) {
return false;
}
return true;
}
var str = "string";
assertFalse(test(str, "length"));
assertFalse(test(str, "length"));
assertFalse(test(str, "length"));
})();
(function() {
function test(o, k) {
try {
k in o;
} catch (e) {
return false;
}
return true;
}
var str = "string";
assertFalse(test(str, 0));
assertFalse(test(str, 0));
assertFalse(test(str, 0));
})();
(function() {
function test(o, k) {
try {
k in o;
} catch (e) {
return false;
}
return true;
}
var ary = [0,1,2,3];
assertTrue(test(ary, 1));
assertTrue(test(ary, 1));
var str = "string";
assertFalse(test(str, 0));
assertFalse(test(str, 0));
assertFalse(test(str, 0));
})();

View File

@ -261,7 +261,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.CompareOperation(Token::Value::GTE, reg, 6)
.CompareTypeOf(TestTypeOfFlags::LiteralFlag::kNumber)
.CompareOperation(Token::Value::INSTANCEOF, reg, 7)
.CompareOperation(Token::Value::IN, reg)
.CompareOperation(Token::Value::IN, reg, 8)
.CompareReference(reg)
.CompareUndetectable()
.CompareUndefined()

View File

@ -89,7 +89,7 @@ TEST(OperandScaling, ScalableAndNonScalable) {
1 + 2 + 2 * scale);
CHECK_EQ(Bytecodes::Size(Bytecode::kCreateObjectLiteral, operand_scale),
1 + 2 * scale + 1);
CHECK_EQ(Bytecodes::Size(Bytecode::kTestIn, operand_scale), 1 + scale);
CHECK_EQ(Bytecodes::Size(Bytecode::kTestIn, operand_scale), 1 + 2 * scale);
}
}