[class] Add IC support for defining class fields to replace runtime call

Introduces several new runtime mechanics for defining private fields,
including:
  - Bytecode StaKeyedPropertyAsDefine
  - Builtins StoreOwnIC{Trampoline|Baseline|_NoFeedback}
  - Builtins KeyedDefineOwnIC{Trampoline|Baseline|_Megamorphic}
  - TurboFan IR opcode JSDefineProperty

These new operations can reduce a runtime call per class field into a
more traditional Store equivalent. In the microbenchmarks, this
results in a substantial win over the status quo (~8x benchmark score
for single fields with the changes, ~20x with multiple fields).

The TurboFan JSDefineProperty op is lowered in
JSNativeContextSpecialization, however this required some hacks.
Because private fields are defined as DONT_ENUM when added to the
object, we can't find a suitable transition using the typical data
property (NONE) flags. I've added a mechanism to specify the required
PropertyAttributes for the transition we want to look up.

Details:

New bytecodes:
  - StaKeyedPropertyAsDefine, which is essentially StaKeyedProperty
    but with a different IC builtin (KeyedDefineOwnIC). This is a
    bytecode rather than a flag for the existing StaKeyedProperty in
    order to avoid impacting typical keyed stores in any way due to
    additional branching and testing.

New builtins:
  - StoreOwnIC{TTrampoline|Baseline|_NoFeedback} is now used for
    StaNamedOwnProperty. Unlike the regular StoreIC, this variant will
    no longer look up the property name in the prototype.
    In adddition, this CL changes an assumption that
    StoreNamedOwnProperty can't result in a map transition, as we
    can't rely on the property already being present in the Map due
    to an object literal boilerplate.

    In the context of class features, this replaces the runtime
    function %CreateDataProperty().

  - KeyedDefineOwnIC{Trampoline|Baseline|_Megamorphic} is used by the
    new StaKeyedPropertyAsDefine bytecode. This is similar to an
    ordinary KeyedStoreIC, but will not check the prototype for
    setters, and for private fields, will take the slow path if the
    field already exists.

    In the context of class features, this replaces the runtime
    function %AddPrivateField().

TurboFan IR:
  - JSDefineProperty is introduced to represent a situation where we
    need to use "Define" semantics, in particular, it codifies that we
    do not consult the prototype chain, and the semantics relating to
    private fields are implied as well.

R=leszeks@chromium.org, syg@chromium.org, rmcilroy@chromium.org

Bug: v8:9888
Change-Id: Idcc947585c0e612f9e8533aa4e2e0f8f0df8875d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2795831
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Cr-Commit-Position: refs/heads/main@{#77377}
This commit is contained in:
Joyee Cheung 2021-10-12 02:01:36 +08:00 committed by V8 LUCI CQ
parent debf0896ce
commit 713ebae3b4
40 changed files with 905 additions and 93 deletions

View File

@ -930,6 +930,14 @@ void BaselineCompiler::VisitStaKeyedProperty() {
IndexAsTagged(2)); // slot
}
void BaselineCompiler::VisitStaKeyedPropertyAsDefine() {
CallBuiltin<Builtin::kKeyedDefineOwnICBaseline>(
RegisterOperand(0), // object
RegisterOperand(1), // key
kInterpreterAccumulatorRegister, // value
IndexAsTagged(2)); // slot
}
void BaselineCompiler::VisitStaInArrayLiteral() {
CallBuiltin<Builtin::kStoreInArrayLiteralICBaseline>(
RegisterOperand(0), // object

View File

@ -273,6 +273,7 @@ namespace internal {
/* Handlers */ \
TFH(KeyedLoadIC_PolymorphicName, LoadWithVector) \
TFH(KeyedStoreIC_Megamorphic, Store) \
TFH(KeyedDefineOwnIC_Megamorphic, Store) \
TFH(LoadGlobalIC_NoFeedback, LoadGlobalNoFeedback) \
TFH(LoadIC_FunctionPrototype, LoadWithVector) \
TFH(LoadIC_StringLength, LoadWithVector) \
@ -280,6 +281,7 @@ namespace internal {
TFH(LoadIC_NoFeedback, LoadNoFeedback) \
TFH(StoreGlobalIC_Slow, StoreWithVector) \
TFH(StoreIC_NoFeedback, Store) \
TFH(StoreOwnIC_NoFeedback, Store) \
TFH(KeyedLoadIC_SloppyArguments, LoadWithVector) \
TFH(LoadIndexedInterceptorIC, LoadWithVector) \
TFH(KeyedStoreIC_SloppyArguments_Standard, StoreWithVector) \
@ -630,9 +632,15 @@ namespace internal {
TFH(StoreIC, StoreWithVector) \
TFH(StoreICTrampoline, Store) \
TFH(StoreICBaseline, StoreBaseline) \
TFH(StoreOwnIC, StoreWithVector) \
TFH(StoreOwnICTrampoline, Store) \
TFH(StoreOwnICBaseline, StoreBaseline) \
TFH(KeyedStoreIC, StoreWithVector) \
TFH(KeyedStoreICTrampoline, Store) \
TFH(KeyedStoreICBaseline, StoreBaseline) \
TFH(KeyedDefineOwnIC, StoreWithVector) \
TFH(KeyedDefineOwnICTrampoline, Store) \
TFH(KeyedDefineOwnICBaseline, StoreBaseline) \
TFH(StoreInArrayLiteralIC, StoreWithVector) \
TFH(StoreInArrayLiteralICBaseline, StoreBaseline) \
TFH(LookupContextBaseline, LookupBaseline) \

View File

@ -58,11 +58,21 @@ void Builtins::Generate_KeyedStoreIC_Megamorphic(
KeyedStoreGenericGenerator::Generate(state);
}
void Builtins::Generate_KeyedDefineOwnIC_Megamorphic(
compiler::CodeAssemblerState* state) {
KeyedDefineOwnGenericGenerator::Generate(state);
}
void Builtins::Generate_StoreIC_NoFeedback(
compiler::CodeAssemblerState* state) {
StoreICNoFeedbackGenerator::Generate(state);
}
void Builtins::Generate_StoreOwnIC_NoFeedback(
compiler::CodeAssemblerState* state) {
StoreOwnICNoFeedbackGenerator::Generate(state);
}
// All possible fast-to-fast transitions. Transitions to dictionary mode are not
// handled by ElementsTransitionAndStore builtins.
#define ELEMENTS_KIND_TRANSITIONS(V) \

View File

@ -109,6 +109,20 @@ void Builtins::Generate_StoreICBaseline(compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateStoreICBaseline();
}
void Builtins::Generate_StoreOwnIC(compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateStoreOwnIC();
}
void Builtins::Generate_StoreOwnICTrampoline(
compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateStoreOwnICTrampoline();
}
void Builtins::Generate_StoreOwnICBaseline(
compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateStoreOwnICBaseline();
}
void Builtins::Generate_KeyedStoreIC(compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateKeyedStoreIC();
@ -123,6 +137,20 @@ void Builtins::Generate_KeyedStoreICBaseline(
AccessorAssembler assembler(state);
assembler.GenerateKeyedStoreICBaseline();
}
void Builtins::Generate_KeyedDefineOwnIC(compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateKeyedDefineOwnIC();
}
void Builtins::Generate_KeyedDefineOwnICTrampoline(
compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateKeyedDefineOwnICTrampoline();
}
void Builtins::Generate_KeyedDefineOwnICBaseline(
compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);
assembler.GenerateKeyedDefineOwnICBaseline();
}
void Builtins::Generate_StoreInArrayLiteralIC(
compiler::CodeAssemblerState* state) {
AccessorAssembler assembler(state);

View File

@ -96,15 +96,11 @@ Callable CodeFactory::LoadGlobalICInOptimizedCode(Isolate* isolate,
}
Callable CodeFactory::StoreOwnIC(Isolate* isolate) {
// TODO(ishell): Currently we use StoreOwnIC only for storing properties that
// already exist in the boilerplate therefore we can use StoreIC.
return Builtins::CallableFor(isolate, Builtin::kStoreICTrampoline);
return Builtins::CallableFor(isolate, Builtin::kStoreOwnICTrampoline);
}
Callable CodeFactory::StoreOwnICInOptimizedCode(Isolate* isolate) {
// TODO(ishell): Currently we use StoreOwnIC only for storing properties that
// already exist in the boilerplate therefore we can use StoreIC.
return Builtins::CallableFor(isolate, Builtin::kStoreIC);
return Builtins::CallableFor(isolate, Builtin::kStoreOwnIC);
}
// static

View File

@ -1,3 +1,4 @@
// Copyright 2015 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.
@ -78,6 +79,8 @@ std::ostream& operator<<(std::ostream& os, AccessMode access_mode) {
return os << "StoreInLiteral";
case AccessMode::kHas:
return os << "Has";
case AccessMode::kDefine:
return os << "Define";
}
UNREACHABLE();
}
@ -321,7 +324,8 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
break;
}
case AccessMode::kStore:
case AccessMode::kStoreInLiteral: {
case AccessMode::kStoreInLiteral:
case AccessMode::kDefine: {
// For stores, the field map and field representation information
// must match exactly, otherwise we cannot merge the stores. We
// also need to make sure that in case of transitioning stores,
@ -798,7 +802,7 @@ PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
// prototype. According to ES6 section 9.1.9 [[Set]], we need to
// create a new data property on the receiver. We can still optimize
// if such a transition already exists.
return LookupTransition(receiver_map, name, holder);
return LookupTransition(receiver_map, name, holder, NONE);
}
}
@ -873,9 +877,17 @@ PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
}
}
// Don't search on the prototype when storing in literals.
if (access_mode == AccessMode::kStoreInLiteral) {
return LookupTransition(receiver_map, name, holder);
// Don't search on the prototype when storing in literals, or performing a
// Define operation
if (access_mode == AccessMode::kStoreInLiteral ||
access_mode == AccessMode::kDefine) {
PropertyAttributes attrs = NONE;
if (name.object()->IsPrivate()) {
// When PrivateNames are added to an object, they are by definition
// non-enumerable.
attrs = DONT_ENUM;
}
return LookupTransition(receiver_map, name, holder, attrs);
}
// Don't lookup private symbols on the prototype chain.
@ -929,7 +941,7 @@ PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
// to transition to a new data property.
// Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver)
if (access_mode == AccessMode::kStore) {
return LookupTransition(receiver_map, name, holder);
return LookupTransition(receiver_map, name, holder, NONE);
}
// The property was not found (access returns undefined or throws
@ -1109,11 +1121,12 @@ PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
}
PropertyAccessInfo AccessInfoFactory::LookupTransition(
MapRef map, NameRef name, base::Optional<JSObjectRef> holder) const {
MapRef map, NameRef name, base::Optional<JSObjectRef> holder,
PropertyAttributes attrs) const {
// Check if the {map} has a data transition with the given {name}.
Map transition = TransitionsAccessor(isolate(), map.object(),
broker()->is_concurrent_inlining())
.SearchTransition(*name.object(), kData, NONE);
.SearchTransition(*name.object(), kData, attrs);
if (transition.is_null()) return Invalid();
base::Optional<MapRef> maybe_transition_map =

View File

@ -286,7 +286,8 @@ class AccessInfoFactory final {
ElementAccessFeedback const& feedback) const;
PropertyAccessInfo LookupSpecialFieldAccessor(MapRef map, NameRef name) const;
PropertyAccessInfo LookupTransition(MapRef map, NameRef name,
base::Optional<JSObjectRef> holder) const;
base::Optional<JSObjectRef> holder,
PropertyAttributes attrs) const;
PropertyAccessInfo ComputeDataFieldAccessInfo(
MapRef receiver_map, MapRef map, base::Optional<JSObjectRef> holder,
InternalIndex descriptor, AccessMode access_mode) const;

View File

@ -2152,6 +2152,40 @@ void BytecodeGraphBuilder::VisitStaKeyedProperty() {
environment()->RecordAfterState(node, Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitStaKeyedPropertyAsDefine() {
PrepareEagerCheckpoint();
Node* value = environment()->LookupAccumulator();
Node* object =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
Node* key =
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(1));
FeedbackSource source =
CreateFeedbackSource(bytecode_iterator().GetIndexOperand(2));
LanguageMode language_mode =
GetLanguageModeFromSlotKind(broker()->GetFeedbackSlotKind(source));
const Operator* op = javascript()->DefineProperty(language_mode, source);
JSTypeHintLowering::LoweringResult lowering =
TryBuildSimplifiedStoreKeyed(op, object, key, value, source.slot);
if (lowering.IsExit()) return;
Node* node = nullptr;
if (lowering.IsSideEffectFree()) {
node = lowering.value();
} else {
DCHECK(!lowering.Changed());
STATIC_ASSERT(JSDefinePropertyNode::ObjectIndex() == 0);
STATIC_ASSERT(JSDefinePropertyNode::KeyIndex() == 1);
STATIC_ASSERT(JSDefinePropertyNode::ValueIndex() == 2);
STATIC_ASSERT(JSDefinePropertyNode::FeedbackVectorIndex() == 3);
DCHECK(IrOpcode::IsFeedbackCollectingOpcode(op->opcode()));
node = NewNode(op, object, key, value, feedback_vector_node());
}
environment()->RecordAfterState(node, Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitLdaModuleVariable() {
int32_t cell_index = bytecode_iterator().GetImmediateOperand(0);
uint32_t depth = bytecode_iterator().GetUnsignedImmediateOperand(1);

View File

@ -49,7 +49,11 @@ class PropertyAccessInfo;
// 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, kHas };
// For a define operation, we behave similarly to kStoreInLiteral, but with
// distinct semantics for private class fields (in which private field
// accesses must throw when storing a field which does not exist, or
// adding/defining a field which already exists).
enum class AccessMode { kLoad, kStore, kStoreInLiteral, kHas, kDefine };
inline bool IsAnyStore(AccessMode mode) {
return mode == AccessMode::kStore || mode == AccessMode::kStoreInLiteral;

View File

@ -422,6 +422,24 @@ void JSGenericLowering::LowerJSStoreProperty(Node* node) {
}
}
void JSGenericLowering::LowerJSDefineProperty(Node* node) {
JSDefinePropertyNode n(node);
const PropertyAccess& p = n.Parameters();
FrameState frame_state = n.frame_state();
Node* outer_state = frame_state.outer_frame_state();
STATIC_ASSERT(n.FeedbackVectorIndex() == 3);
if (outer_state->opcode() != IrOpcode::kFrameState) {
n->RemoveInput(n.FeedbackVectorIndex());
node->InsertInput(zone(), 3,
jsgraph()->TaggedIndexConstant(p.feedback().index()));
ReplaceWithBuiltinCall(node, Builtin::kKeyedDefineOwnICTrampoline);
} else {
node->InsertInput(zone(), 3,
jsgraph()->TaggedIndexConstant(p.feedback().index()));
ReplaceWithBuiltinCall(node, Builtin::kKeyedDefineOwnIC);
}
}
void JSGenericLowering::LowerJSStoreNamed(Node* node) {
JSStoreNamedNode n(node);
NamedAccess const& p = n.Parameters();
@ -447,8 +465,8 @@ void JSGenericLowering::LowerJSStoreNamed(Node* node) {
}
void JSGenericLowering::LowerJSStoreNamedOwn(Node* node) {
JSStoreNamedOwnNode n(node);
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
JSStoreNamedOwnNode n(node);
StoreNamedOwnParameters const& p = n.Parameters();
FrameState frame_state = n.frame_state();
Node* outer_state = frame_state.outer_frame_state();

View File

@ -343,6 +343,10 @@ KeyedAccessMode KeyedAccessMode::FromNexus(FeedbackNexus const& nexus) {
if (IsKeyedHasICKind(kind)) {
return KeyedAccessMode(AccessMode::kHas, nexus.GetKeyedAccessLoadMode());
}
if (IsDefineOwnICKind(kind)) {
return KeyedAccessMode(AccessMode::kDefine,
nexus.GetKeyedAccessStoreMode());
}
if (IsKeyedStoreICKind(kind)) {
return KeyedAccessMode(AccessMode::kStore, nexus.GetKeyedAccessStoreMode());
}
@ -361,6 +365,7 @@ bool KeyedAccessMode::IsLoad() const {
}
bool KeyedAccessMode::IsStore() const {
return access_mode_ == AccessMode::kStore ||
access_mode_ == AccessMode::kDefine ||
access_mode_ == AccessMode::kStoreInLiteral;
}
@ -401,7 +406,8 @@ ElementAccessFeedback::ElementAccessFeedback(Zone* zone,
DCHECK(IsKeyedLoadICKind(slot_kind) || IsKeyedHasICKind(slot_kind) ||
IsStoreDataPropertyInLiteralKind(slot_kind) ||
IsKeyedStoreICKind(slot_kind) ||
IsStoreInArrayLiteralICKind(slot_kind));
IsStoreInArrayLiteralICKind(slot_kind) ||
IsDefineOwnICKind(slot_kind));
}
bool ElementAccessFeedback::HasOnlyStringMaps(JSHeapBroker* broker) const {
@ -435,7 +441,8 @@ NamedAccessFeedback::NamedAccessFeedback(NameRef const& name,
IsStoreOwnICKind(slot_kind) || IsKeyedLoadICKind(slot_kind) ||
IsKeyedHasICKind(slot_kind) || IsKeyedStoreICKind(slot_kind) ||
IsStoreInArrayLiteralICKind(slot_kind) ||
IsStoreDataPropertyInLiteralKind(slot_kind));
IsStoreDataPropertyInLiteralKind(slot_kind) ||
IsDefineOwnICKind(slot_kind));
}
void JSHeapBroker::SetFeedback(FeedbackSource const& source,

View File

@ -104,6 +104,8 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) {
return ReduceJSLoadProperty(node);
case IrOpcode::kJSStoreProperty:
return ReduceJSStoreProperty(node);
case IrOpcode::kJSDefineProperty:
return ReduceJSDefineProperty(node);
case IrOpcode::kJSStoreNamedOwn:
return ReduceJSStoreNamedOwn(node);
case IrOpcode::kJSStoreDataPropertyInLiteral:
@ -1095,7 +1097,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
node->opcode() == IrOpcode::kJSStoreNamedOwn ||
node->opcode() == IrOpcode::kJSStoreDataPropertyInLiteral ||
node->opcode() == IrOpcode::kJSHasProperty ||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper);
node->opcode() == IrOpcode::kJSLoadNamedFromSuper ||
node->opcode() == IrOpcode::kJSDefineProperty);
STATIC_ASSERT(JSLoadNamedNode::ObjectIndex() == 0 &&
JSStoreNamedNode::ObjectIndex() == 0 &&
JSLoadPropertyNode::ObjectIndex() == 0 &&
@ -1103,7 +1106,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
JSStoreNamedOwnNode::ObjectIndex() == 0 &&
JSStoreNamedNode::ObjectIndex() == 0 &&
JSStoreDataPropertyInLiteralNode::ObjectIndex() == 0 &&
JSHasPropertyNode::ObjectIndex() == 0);
JSHasPropertyNode::ObjectIndex() == 0 &&
JSDefinePropertyNode::ObjectIndex() == 0);
STATIC_ASSERT(JSLoadNamedFromSuperNode::ReceiverIndex() == 0);
Node* context = NodeProperties::GetContextInput(node);
@ -1667,7 +1671,8 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
node->opcode() == IrOpcode::kJSStoreProperty ||
node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||
node->opcode() == IrOpcode::kJSStoreDataPropertyInLiteral ||
node->opcode() == IrOpcode::kJSHasProperty);
node->opcode() == IrOpcode::kJSHasProperty ||
node->opcode() == IrOpcode::kJSDefineProperty);
STATIC_ASSERT(JSLoadPropertyNode::ObjectIndex() == 0 &&
JSStorePropertyNode::ObjectIndex() == 0 &&
JSStoreInArrayLiteralNode::ArrayIndex() == 0 &&
@ -1979,7 +1984,8 @@ Reduction JSNativeContextSpecialization::ReducePropertyAccess(
node->opcode() == IrOpcode::kJSLoadNamed ||
node->opcode() == IrOpcode::kJSStoreNamed ||
node->opcode() == IrOpcode::kJSStoreNamedOwn ||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper);
node->opcode() == IrOpcode::kJSLoadNamedFromSuper ||
node->opcode() == IrOpcode::kJSDefineProperty);
DCHECK_GE(node->op()->ControlOutputCount(), 1);
ProcessedFeedback const& feedback =
@ -2164,6 +2170,15 @@ Reduction JSNativeContextSpecialization::ReduceJSStoreProperty(Node* node) {
FeedbackSource(p.feedback()), AccessMode::kStore);
}
Reduction JSNativeContextSpecialization::ReduceJSDefineProperty(Node* node) {
JSDefinePropertyNode n(node);
PropertyAccess const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, n.key(), base::nullopt, n.value(),
FeedbackSource(p.feedback()),
AccessMode::kDefine);
}
Node* JSNativeContextSpecialization::InlinePropertyGetterCall(
Node* receiver, ConvertReceiverMode receiver_mode, Node* context,
Node* frame_state, Node** effect, Node** control,
@ -2373,6 +2388,7 @@ JSNativeContextSpecialization::BuildPropertyAccess(
if_exceptions, access_info);
case AccessMode::kStore:
case AccessMode::kStoreInLiteral:
case AccessMode::kDefine:
DCHECK_EQ(receiver, lookup_start_object);
return BuildPropertyStore(receiver, value, context, frame_state, effect,
control, name, if_exceptions, access_info,
@ -2394,6 +2410,7 @@ JSNativeContextSpecialization::BuildPropertyStore(
base::Optional<JSObjectRef> holder = access_info.holder();
if (holder.has_value()) {
DCHECK_NE(AccessMode::kStoreInLiteral, access_mode);
DCHECK_NE(AccessMode::kDefine, access_mode);
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
holder.value());
@ -2408,7 +2425,8 @@ JSNativeContextSpecialization::BuildPropertyStore(
} else {
DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant());
DCHECK(access_mode == AccessMode::kStore ||
access_mode == AccessMode::kStoreInLiteral);
access_mode == AccessMode::kStoreInLiteral ||
access_mode == AccessMode::kDefine);
FieldIndex const field_index = access_info.field_index();
Type const field_type = access_info.field_type();
MachineRepresentation const field_representation =
@ -2819,6 +2837,7 @@ JSNativeContextSpecialization::BuildElementAccess(
break;
}
case AccessMode::kStoreInLiteral:
case AccessMode::kDefine:
UNREACHABLE();
case AccessMode::kStore: {
// Ensure that the {value} is actually a Number or an Oddball,
@ -3144,7 +3163,8 @@ JSNativeContextSpecialization::BuildElementAccess(
}
} else {
DCHECK(keyed_mode.access_mode() == AccessMode::kStore ||
keyed_mode.access_mode() == AccessMode::kStoreInLiteral);
keyed_mode.access_mode() == AccessMode::kStoreInLiteral ||
keyed_mode.access_mode() == AccessMode::kDefine);
if (IsSmiElementsKind(elements_kind)) {
value = effect = graph()->NewNode(

View File

@ -90,6 +90,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Reduction ReduceJSHasProperty(Node* node);
Reduction ReduceJSLoadProperty(Node* node);
Reduction ReduceJSStoreProperty(Node* node);
Reduction ReduceJSDefineProperty(Node* node);
Reduction ReduceJSStoreNamedOwn(Node* node);
Reduction ReduceJSStoreDataPropertyInLiteral(Node* node);
Reduction ReduceJSStoreInArrayLiteral(Node* node);

View File

@ -314,7 +314,8 @@ bool operator!=(PropertyAccess const& lhs, PropertyAccess const& rhs) {
PropertyAccess const& PropertyAccessOf(const Operator* op) {
DCHECK(op->opcode() == IrOpcode::kJSHasProperty ||
op->opcode() == IrOpcode::kJSLoadProperty ||
op->opcode() == IrOpcode::kJSStoreProperty);
op->opcode() == IrOpcode::kJSStoreProperty ||
op->opcode() == IrOpcode::kJSDefineProperty);
return OpParameter<PropertyAccess>(op);
}
@ -1123,6 +1124,16 @@ const Operator* JSOperatorBuilder::StoreProperty(
access); // parameter
}
const Operator* JSOperatorBuilder::DefineProperty(
LanguageMode language_mode, FeedbackSource const& feedback) {
PropertyAccess access(language_mode, feedback);
return zone()->New<Operator1<PropertyAccess>>( // --
IrOpcode::kJSDefineProperty, Operator::kNoProperties, // opcode
"JSDefineProperty", // name
4, 1, 1, 0, 1, 2, // counts
access); // parameter
}
const Operator* JSOperatorBuilder::StoreNamedOwn(
const NameRef& name, FeedbackSource const& feedback) {
static constexpr int kObject = 1;

View File

@ -1022,6 +1022,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* StoreProperty(LanguageMode language_mode,
FeedbackSource const& feedback);
const Operator* DefineProperty(LanguageMode language_mode,
FeedbackSource const& feedback);
const Operator* StoreNamed(LanguageMode language_mode, const NameRef& name,
FeedbackSource const& feedback);
@ -1312,6 +1314,26 @@ class JSStorePropertyNode final : public JSNodeWrapperBase {
#undef INPUTS
};
class JSDefinePropertyNode final : public JSNodeWrapperBase {
public:
explicit constexpr JSDefinePropertyNode(Node* node)
: JSNodeWrapperBase(node) {
DCHECK_EQ(IrOpcode::kJSDefineProperty, node->opcode());
}
const PropertyAccess& Parameters() const {
return PropertyAccessOf(node()->op());
}
#define INPUTS(V) \
V(Object, object, 0, Object) \
V(Key, key, 1, Object) \
V(Value, value, 2, Object) \
V(FeedbackVector, feedback_vector, 3, HeapObject)
INPUTS(DEFINE_INPUT_ACCESSORS)
#undef INPUTS
};
namespace js_node_wrapper_utils {
// Avoids template definitions in the .cc file.
TNode<Oddball> UndefinedConstant(JSGraph* jsgraph);

View File

@ -553,7 +553,8 @@ JSTypeHintLowering::ReduceStoreKeyedOperation(const Operator* op, Node* obj,
FeedbackSlot slot) const {
DCHECK(op->opcode() == IrOpcode::kJSStoreProperty ||
op->opcode() == IrOpcode::kJSStoreInArrayLiteral ||
op->opcode() == IrOpcode::kJSStoreDataPropertyInLiteral);
op->opcode() == IrOpcode::kJSStoreDataPropertyInLiteral ||
op->opcode() == IrOpcode::kJSDefineProperty);
if (Node* node = TryBuildSoftDeopt(
slot, effect, control,
DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess)) {

View File

@ -175,6 +175,7 @@
V(JSLoadNamedFromSuper) \
V(JSLoadGlobal) \
V(JSStoreProperty) \
V(JSDefineProperty) \
V(JSStoreNamed) \
V(JSStoreNamedOwn) \
V(JSStoreGlobal) \
@ -1120,6 +1121,7 @@ class V8_EXPORT_PRIVATE IrOpcode {
case kJSCreateLiteralArray:
case kJSCreateLiteralObject:
case kJSCreateLiteralRegExp:
case kJSDefineProperty:
case kJSForInNext:
case kJSForInPrepare:
case kJSGetIterator:

View File

@ -72,6 +72,7 @@ bool OperatorProperties::NeedsExactContext(const Operator* op) {
case IrOpcode::kJSCreateCatchContext:
case IrOpcode::kJSCreateWithContext:
case IrOpcode::kJSDebugger:
case IrOpcode::kJSDefineProperty:
case IrOpcode::kJSDeleteProperty:
case IrOpcode::kJSGeneratorStore:
case IrOpcode::kJSGetImportMeta:
@ -204,6 +205,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSStoreNamed:
case IrOpcode::kJSStoreNamedOwn:
case IrOpcode::kJSStoreProperty:
case IrOpcode::kJSDefineProperty:
// Conversions
case IrOpcode::kJSToLength:

View File

@ -1420,6 +1420,8 @@ Type Typer::Visitor::Weaken(Node* node, Type current_type, Type previous_type) {
Type Typer::Visitor::TypeJSStoreProperty(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeJSDefineProperty(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeJSStoreNamed(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeJSStoreGlobal(Node* node) { UNREACHABLE(); }

View File

@ -740,6 +740,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckNotTyped(node);
CHECK(PropertyAccessOf(node->op()).feedback().IsValid());
break;
case IrOpcode::kJSDefineProperty:
CheckNotTyped(node);
CHECK(PropertyAccessOf(node->op()).feedback().IsValid());
break;
case IrOpcode::kJSStoreNamed:
CheckNotTyped(node);
break;

View File

@ -1212,6 +1212,7 @@ void FeedbackNexus::Print(std::ostream& os) {
switch (kind()) {
case FeedbackSlotKind::kCall:
case FeedbackSlotKind::kCloneObject:
case FeedbackSlotKind::kDefineOwnKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kInstanceOf:
case FeedbackSlotKind::kLoadGlobalInsideTypeof:

View File

@ -3608,14 +3608,18 @@ void AccessorAssembler::StoreIC(const StoreICParameters* p) {
BIND(&no_feedback);
{
TailCallBuiltin(Builtin::kStoreIC_NoFeedback, p->context(), p->receiver(),
p->name(), p->value(), p->slot());
auto builtin = p->IsStoreOwn() ? Builtin::kStoreOwnIC_NoFeedback
: Builtin::kStoreIC_NoFeedback;
TailCallBuiltin(builtin, p->context(), p->receiver(), p->name(), p->value(),
p->slot());
}
BIND(&miss);
{
TailCallRuntime(Runtime::kStoreIC_Miss, p->context(), p->value(), p->slot(),
p->vector(), p->receiver(), p->name());
auto runtime =
p->IsStoreOwn() ? Runtime::kStoreOwnIC_Miss : Runtime::kStoreIC_Miss;
TailCallRuntime(runtime, p->context(), p->value(), p->slot(), p->vector(),
p->receiver(), p->name());
}
}
@ -3653,7 +3657,8 @@ void AccessorAssembler::StoreGlobalIC(const StoreICParameters* pp) {
StoreICParameters p(
pp->context(),
LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX),
pp->name(), pp->value(), pp->slot(), pp->vector());
pp->name(), pp->value(), pp->slot(), pp->vector(),
StoreICMode::kDefault);
HandleStoreICHandlerCase(&p, handler, &miss, ICMode::kGlobalIC);
}
@ -3825,6 +3830,80 @@ void AccessorAssembler::KeyedStoreIC(const StoreICParameters* p) {
}
}
void AccessorAssembler::KeyedDefineOwnIC(const StoreICParameters* p) {
Label miss(this, Label::kDeferred);
{
TVARIABLE(MaybeObject, var_handler);
Label if_handler(this, &var_handler),
try_polymorphic(this, Label::kDeferred),
try_megamorphic(this, Label::kDeferred),
no_feedback(this, Label::kDeferred),
try_polymorphic_name(this, Label::kDeferred);
TNode<Map> receiver_map = LoadReceiverMap(p->receiver());
GotoIf(IsDeprecatedMap(receiver_map), &miss);
GotoIf(IsUndefined(p->vector()), &no_feedback);
// Check monomorphic case.
TNode<MaybeObject> feedback =
TryMonomorphicCase(p->slot(), CAST(p->vector()), receiver_map,
&if_handler, &var_handler, &try_polymorphic);
BIND(&if_handler);
{
Comment("KeyedDefineOwnIC_if_handler");
HandleStoreICHandlerCase(p, var_handler.value(), &miss,
ICMode::kNonGlobalIC, kSupportElements);
}
BIND(&try_polymorphic);
TNode<HeapObject> strong_feedback = GetHeapObjectIfStrong(feedback, &miss);
{
// CheckPolymorphic case.
Comment("KeyedDefineOwnIC_try_polymorphic");
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)),
&try_megamorphic);
HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler,
&var_handler, &miss);
}
BIND(&try_megamorphic);
{
// Check megamorphic case.
Comment("KeyedDefineOwnIC_try_megamorphic");
Branch(TaggedEqual(strong_feedback, MegamorphicSymbolConstant()),
&no_feedback, &try_polymorphic_name);
}
BIND(&no_feedback);
{
TailCallBuiltin(Builtin::kKeyedDefineOwnIC_Megamorphic, p->context(),
p->receiver(), p->name(), p->value(), p->slot());
}
BIND(&try_polymorphic_name);
{
// We might have a name in feedback, and a fixed array in the next slot.
Comment("KeyedDefineOwnIC_try_polymorphic_name");
GotoIfNot(TaggedEqual(strong_feedback, p->name()), &miss);
// If the name comparison succeeded, we know we have a feedback vector
// with at least one map/handler pair.
TNode<MaybeObject> feedback_element =
LoadFeedbackVectorSlot(CAST(p->vector()), p->slot(), kTaggedSize);
TNode<WeakFixedArray> array = CAST(feedback_element);
HandlePolymorphicCase(receiver_map, array, &if_handler, &var_handler,
&miss);
}
}
BIND(&miss);
{
Comment("KeyedDefineOwnIC_miss");
TailCallRuntime(Runtime::kKeyedDefineOwnIC_Miss, p->context(), p->value(),
p->slot(), p->vector(), p->receiver(), p->name());
}
}
void AccessorAssembler::StoreInArrayLiteralIC(const StoreICParameters* p) {
Label miss(this, Label::kDeferred), no_feedback(this, Label::kDeferred);
{
@ -4298,7 +4377,8 @@ void AccessorAssembler::GenerateStoreGlobalIC() {
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);
StoreICParameters p(context, base::nullopt, name, value, slot, vector);
StoreICParameters p(context, base::nullopt, name, value, slot, vector,
StoreICMode::kDefault);
StoreGlobalIC(&p);
}
@ -4336,7 +4416,8 @@ void AccessorAssembler::GenerateStoreIC() {
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);
StoreICParameters p(context, receiver, name, value, slot, vector);
StoreICParameters p(context, receiver, name, value, slot, vector,
StoreICMode::kDefault);
StoreIC(&p);
}
@ -4368,6 +4449,49 @@ void AccessorAssembler::GenerateStoreICBaseline() {
vector);
}
void AccessorAssembler::GenerateStoreOwnIC() {
using Descriptor = StoreWithVectorDescriptor;
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);
StoreICParameters p(context, receiver, name, value, slot, vector,
StoreICMode::kStoreOwn);
StoreIC(&p);
}
void AccessorAssembler::GenerateStoreOwnICTrampoline() {
using Descriptor = StoreDescriptor;
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto context = Parameter<Context>(Descriptor::kContext);
TNode<FeedbackVector> vector = LoadFeedbackVectorForStub();
TailCallBuiltin(Builtin::kStoreOwnIC, context, receiver, name, value, slot,
vector);
}
void AccessorAssembler::GenerateStoreOwnICBaseline() {
using Descriptor = StoreWithVectorDescriptor;
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
TNode<FeedbackVector> vector = LoadFeedbackVectorFromBaseline();
TNode<Context> context = LoadContextFromBaseline();
TailCallBuiltin(Builtin::kStoreOwnIC, context, receiver, name, value, slot,
vector);
}
void AccessorAssembler::GenerateKeyedStoreIC() {
using Descriptor = StoreWithVectorDescriptor;
@ -4378,7 +4502,8 @@ void AccessorAssembler::GenerateKeyedStoreIC() {
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);
StoreICParameters p(context, receiver, name, value, slot, vector);
StoreICParameters p(context, receiver, name, value, slot, vector,
StoreICMode::kDefault);
KeyedStoreIC(&p);
}
@ -4410,6 +4535,49 @@ void AccessorAssembler::GenerateKeyedStoreICBaseline() {
vector);
}
void AccessorAssembler::GenerateKeyedDefineOwnIC() {
using Descriptor = StoreWithVectorDescriptor;
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);
StoreICParameters p(context, receiver, name, value, slot, vector,
StoreICMode::kDefineOwn);
KeyedDefineOwnIC(&p);
}
void AccessorAssembler::GenerateKeyedDefineOwnICTrampoline() {
using Descriptor = StoreDescriptor;
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto context = Parameter<Context>(Descriptor::kContext);
TNode<FeedbackVector> vector = LoadFeedbackVectorForStub();
TailCallBuiltin(Builtin::kKeyedDefineOwnIC, context, receiver, name, value,
slot, vector);
}
void AccessorAssembler::GenerateKeyedDefineOwnICBaseline() {
using Descriptor = StoreBaselineDescriptor;
auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto value = Parameter<Object>(Descriptor::kValue);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
TNode<FeedbackVector> vector = LoadFeedbackVectorFromBaseline();
TNode<Context> context = LoadContextFromBaseline();
TailCallBuiltin(Builtin::kKeyedDefineOwnIC, context, receiver, name, value,
slot, vector);
}
void AccessorAssembler::GenerateStoreInArrayLiteralIC() {
using Descriptor = StoreWithVectorDescriptor;
@ -4420,7 +4588,8 @@ void AccessorAssembler::GenerateStoreInArrayLiteralIC() {
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);
StoreICParameters p(context, array, index, value, slot, vector);
StoreICParameters p(context, array, index, value, slot, vector,
StoreICMode::kDefault);
StoreInArrayLiteralIC(&p);
}

View File

@ -42,6 +42,9 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
void GenerateStoreIC();
void GenerateStoreICTrampoline();
void GenerateStoreICBaseline();
void GenerateStoreOwnIC();
void GenerateStoreOwnICTrampoline();
void GenerateStoreOwnICBaseline();
void GenerateStoreGlobalIC();
void GenerateStoreGlobalICTrampoline();
void GenerateStoreGlobalICBaseline();
@ -63,6 +66,10 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
void GenerateKeyedStoreICTrampoline();
void GenerateKeyedStoreICBaseline();
void GenerateKeyedDefineOwnIC();
void GenerateKeyedDefineOwnICTrampoline();
void GenerateKeyedDefineOwnICBaseline();
void GenerateStoreInArrayLiteralIC();
void GenerateStoreInArrayLiteralICBaseline();
@ -190,17 +197,24 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
int data_index);
protected:
enum class StoreICMode {
kDefault,
kStoreOwn,
kDefineOwn,
};
struct StoreICParameters {
StoreICParameters(TNode<Context> context,
base::Optional<TNode<Object>> receiver,
TNode<Object> name, TNode<Object> value,
TNode<TaggedIndex> slot, TNode<HeapObject> vector)
TNode<TaggedIndex> slot, TNode<HeapObject> vector,
StoreICMode mode)
: context_(context),
receiver_(receiver),
name_(name),
value_(value),
slot_(slot),
vector_(vector) {}
vector_(vector),
mode_(mode) {}
TNode<Context> context() const { return context_; }
TNode<Object> receiver() const { return receiver_.value(); }
@ -213,6 +227,9 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
bool receiver_is_null() const { return !receiver_.has_value(); }
bool IsStoreOwn() const { return mode_ == StoreICMode::kStoreOwn; }
bool IsDefineOwn() const { return mode_ == StoreICMode::kDefineOwn; }
private:
TNode<Context> context_;
base::Optional<TNode<Object>> receiver_;
@ -220,6 +237,7 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
TNode<Object> value_;
TNode<TaggedIndex> slot_;
TNode<HeapObject> vector_;
StoreICMode mode_;
};
enum class LoadAccessMode { kLoad, kHas };
@ -289,12 +307,14 @@ class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
void KeyedLoadICGeneric(const LoadICParameters* p);
void KeyedLoadICPolymorphicName(const LoadICParameters* p,
LoadAccessMode access_mode);
void StoreIC(const StoreICParameters* p);
void StoreGlobalIC(const StoreICParameters* p);
void StoreGlobalIC_PropertyCellCase(TNode<PropertyCell> property_cell,
TNode<Object> value,
ExitPoint* exit_point, Label* miss);
void KeyedStoreIC(const StoreICParameters* p);
void KeyedDefineOwnIC(const StoreICParameters* p);
void StoreInArrayLiteralIC(const StoreICParameters* p);
// IC dispatcher behavior.

View File

@ -114,7 +114,8 @@ void IC::TraceIC(const char* type, Handle<Object> name, State old_state,
} else if (IsKeyedLoadIC()) {
KeyedAccessLoadMode mode = nexus()->GetKeyedAccessLoadMode();
modifier = GetModifier(mode);
} else if (IsKeyedStoreIC() || IsStoreInArrayLiteralICKind(kind())) {
} else if (IsKeyedStoreIC() || IsStoreInArrayLiteralICKind(kind()) ||
IsDefineOwnIC()) {
KeyedAccessStoreMode mode = nexus()->GetKeyedAccessStoreMode();
modifier = GetModifier(mode);
}
@ -1723,7 +1724,10 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
// might deprecate the current map again, if value does not fit.
if (MigrateDeprecated(isolate(), object)) {
PropertyKey key(isolate(), name);
LookupIterator it(isolate(), object, key);
LookupIterator it(
isolate(), object, key,
IsAnyStoreOwn() ? LookupIterator::OWN : LookupIterator::DEFAULT);
DCHECK_IMPLIES(IsStoreOwnIC(), it.IsFound() && it.HolderIsReceiver());
MAYBE_RETURN_NULL(Object::SetProperty(&it, value, StoreOrigin::kNamed));
return value;
}
@ -1745,14 +1749,22 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
JSObject::MakePrototypesFast(object, kStartAtPrototype, isolate());
PropertyKey key(isolate(), name);
LookupIterator it(isolate(), object, key);
LookupIterator it(
isolate(), object, key,
IsAnyStoreOwn() ? LookupIterator::OWN : LookupIterator::DEFAULT);
if (name->IsPrivate()) {
if (name->IsPrivateName() && !it.IsFound()) {
bool exists = it.IsFound();
if (name->IsPrivateName() && exists == IsDefineOwnIC()) {
Handle<String> name_string(
String::cast(Symbol::cast(*name).description()), isolate());
return TypeError(MessageTemplate::kInvalidPrivateMemberWrite, object,
name_string);
if (exists) {
return TypeError(MessageTemplate::kInvalidPrivateFieldReitialization,
object, name_string);
} else {
return TypeError(MessageTemplate::kInvalidPrivateMemberWrite, object,
name_string);
}
}
// IC handling of private fields/symbols stores on JSProxy is not
@ -2332,8 +2344,11 @@ MaybeHandle<Object> KeyedStoreIC::Store(Handle<Object> object,
Handle<Object> result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate(), result,
Runtime::SetObjectProperty(isolate(), object, key, value,
StoreOrigin::kMaybeKeyed),
IsDefineOwnIC()
? Runtime::DefineClassField(isolate(), object, key, value,
StoreOrigin::kMaybeKeyed)
: Runtime::SetObjectProperty(isolate(), object, key, value,
StoreOrigin::kMaybeKeyed),
Object);
return result;
}
@ -2684,6 +2699,34 @@ RUNTIME_FUNCTION(Runtime_StoreIC_Miss) {
RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value));
}
RUNTIME_FUNCTION(Runtime_StoreOwnIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
// Runtime functions don't follow the IC's calling convention.
Handle<Object> value = args.at(0);
Handle<TaggedIndex> slot = args.at<TaggedIndex>(1);
Handle<HeapObject> maybe_vector = args.at<HeapObject>(2);
Handle<Object> receiver = args.at(3);
Handle<Name> key = args.at<Name>(4);
FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot->value());
// When there is no feedback vector it is OK to use the StoreOwnNamed
// feedback kind. There _should_ be a vector, though.
FeedbackSlotKind kind = FeedbackSlotKind::kStoreOwnNamed;
Handle<FeedbackVector> vector = Handle<FeedbackVector>();
if (!maybe_vector->IsUndefined()) {
DCHECK(maybe_vector->IsFeedbackVector());
vector = Handle<FeedbackVector>::cast(maybe_vector);
kind = vector->GetKind(vector_slot);
}
DCHECK(IsStoreOwnICKind(kind));
StoreIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value));
}
RUNTIME_FUNCTION(Runtime_StoreGlobalIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
@ -2795,7 +2838,7 @@ RUNTIME_FUNCTION(Runtime_KeyedStoreIC_Miss) {
// The elements store stubs miss into this function, but they are shared by
// different ICs.
if (IsKeyedStoreICKind(kind)) {
if (IsKeyedStoreICKind(kind) || IsKeyedDefineOwnICKind(kind)) {
KeyedStoreIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value));
@ -2810,6 +2853,33 @@ RUNTIME_FUNCTION(Runtime_KeyedStoreIC_Miss) {
}
}
RUNTIME_FUNCTION(Runtime_KeyedDefineOwnIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
// Runtime functions don't follow the IC's calling convention.
Handle<Object> value = args.at(0);
Handle<TaggedIndex> slot = args.at<TaggedIndex>(1);
Handle<HeapObject> maybe_vector = args.at<HeapObject>(2);
Handle<Object> receiver = args.at(3);
Handle<Object> key = args.at(4);
FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot->value());
FeedbackSlotKind kind = FeedbackSlotKind::kDefineOwnKeyed;
Handle<FeedbackVector> vector = Handle<FeedbackVector>();
if (!maybe_vector->IsUndefined()) {
DCHECK(maybe_vector->IsFeedbackVector());
vector = Handle<FeedbackVector>::cast(maybe_vector);
kind = vector->GetKind(vector_slot);
DCHECK(IsDefineOwnICKind(kind));
}
// The elements store stubs miss into this function, but they are shared by
// different ICs.
KeyedStoreIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value));
}
RUNTIME_FUNCTION(Runtime_StoreInArrayLiteralIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());

View File

@ -54,8 +54,10 @@ class IC {
}
bool IsAnyStore() const {
return IsStoreIC() || IsStoreOwnIC() || IsStoreGlobalIC() ||
IsKeyedStoreIC() || IsStoreInArrayLiteralICKind(kind());
IsKeyedStoreIC() || IsStoreInArrayLiteralICKind(kind()) ||
IsDefineOwnIC();
}
bool IsAnyStoreOwn() const { return IsStoreOwnIC() || IsDefineOwnIC(); }
static inline bool IsHandler(MaybeObject object);
@ -119,11 +121,13 @@ class IC {
bool IsStoreGlobalIC() const { return IsStoreGlobalICKind(kind_); }
bool IsStoreIC() const { return IsStoreICKind(kind_); }
bool IsStoreOwnIC() const { return IsStoreOwnICKind(kind_); }
bool IsDefineOwnIC() const { return IsDefineOwnICKind(kind_); }
bool IsKeyedStoreIC() const { return IsKeyedStoreICKind(kind_); }
bool IsKeyedHasIC() const { return IsKeyedHasICKind(kind_); }
bool is_keyed() const {
return IsKeyedLoadIC() || IsKeyedStoreIC() ||
IsStoreInArrayLiteralICKind(kind_) || IsKeyedHasIC();
IsStoreInArrayLiteralICKind(kind_) || IsKeyedHasIC() ||
IsKeyedDefineOwnICKind(kind_);
}
bool ShouldRecomputeHandler(Handle<String> name);

View File

@ -16,7 +16,22 @@
namespace v8 {
namespace internal {
enum class StoreMode { kOrdinary, kInLiteral };
enum class StoreMode {
kOrdinary,
kInLiteral,
// kStoreOwn performs an ordinary property store without traversing the
// prototype chain. In the case of private fields, it will throw if the
// field does not already exist.
// kDefineOwn is similar to kStoreOwn, but for private class fields, it
// will throw if the field does already exist.
kStoreOwn,
kDefineOwn
};
// With private symbols, 'define' semantics will throw if the field already
// exists, while 'update' semantics will throw if the field does not exist.
enum class PrivateNameSemantics { kUpdate, kDefine };
class KeyedStoreGenericAssembler : public AccessorAssembler {
public:
@ -131,14 +146,16 @@ class KeyedStoreGenericAssembler : public AccessorAssembler {
bool IsKeyedStore() const { return mode_ == StoreMode::kOrdinary; }
bool IsStoreInLiteral() const { return mode_ == StoreMode::kInLiteral; }
bool IsKeyedStoreOwn() const { return mode_ == StoreMode::kStoreOwn; }
bool IsKeyedDefineOwn() const { return mode_ == StoreMode::kDefineOwn; }
bool ShouldCheckPrototype() const { return IsKeyedStore(); }
bool ShouldReconfigureExisting() const { return IsStoreInLiteral(); }
bool ShouldCallSetter() const { return IsKeyedStore(); }
bool ShouldCallSetter() const { return IsKeyedStore() || IsKeyedStoreOwn(); }
bool ShouldCheckPrototypeValidity() const {
// We don't do this for "in-literal" stores, because it is impossible for
// the target object to be a "prototype"
return !IsStoreInLiteral();
return !IsStoreInLiteral() && !IsKeyedStoreOwn();
}
};
@ -147,11 +164,23 @@ void KeyedStoreGenericGenerator::Generate(compiler::CodeAssemblerState* state) {
assembler.KeyedStoreGeneric();
}
void KeyedDefineOwnGenericGenerator::Generate(
compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kDefineOwn);
assembler.KeyedStoreGeneric();
}
void StoreICNoFeedbackGenerator::Generate(compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kOrdinary);
assembler.StoreIC_NoFeedback();
}
void StoreOwnICNoFeedbackGenerator::Generate(
compiler::CodeAssemblerState* state) {
KeyedStoreGenericAssembler assembler(state, StoreMode::kStoreOwn);
assembler.StoreIC_NoFeedback();
}
void KeyedStoreGenericGenerator::SetProperty(
compiler::CodeAssemblerState* state, TNode<Context> context,
TNode<JSReceiver> receiver, TNode<BoolT> is_simple_receiver,
@ -783,6 +812,10 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&descriptor_found);
{
if (IsKeyedDefineOwn()) {
// Take slow path to throw if a private name already exists
GotoIf(IsPrivateSymbol(name), slow);
}
TNode<IntPtrT> name_index = var_name_index.value();
TNode<Uint32T> details = LoadDetailsByKeyIndex(descriptors, name_index);
Label data_property(this);
@ -841,6 +874,9 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&dictionary_found);
{
Label check_const(this), overwrite(this), done(this);
if (IsKeyedDefineOwn()) {
GotoIf(IsPrivateSymbol(name), slow);
}
TNode<Uint32T> details =
LoadDetailsByKeyIndex(properties, var_name_index.value());
JumpIfDataProperty(details, &check_const,
@ -1016,7 +1052,7 @@ void KeyedStoreGenericAssembler::KeyedStoreGeneric(
{
Comment("key is unique name");
StoreICParameters p(context, receiver, var_unique.value(), value, {},
UndefinedConstant());
UndefinedConstant(), StoreICMode::kDefault);
ExitPoint direct_exit(this);
EmitGenericPropertyStore(CAST(receiver), receiver_map, &p, &direct_exit,
&slow, language_mode);
@ -1034,10 +1070,15 @@ void KeyedStoreGenericAssembler::KeyedStoreGeneric(
BIND(&slow);
{
if (IsKeyedStore()) {
if (IsKeyedStore() || IsKeyedStoreOwn()) {
Comment("KeyedStoreGeneric_slow");
TailCallRuntime(Runtime::kSetKeyedProperty, context, receiver, key,
value);
} else if (IsKeyedDefineOwn()) {
// Currently, KeyedDefineOwn ICs are only used for class instance
// fields, hence %DefineClassField.
TailCallRuntime(Runtime::kDefineClassField, context, receiver, key,
value);
} else {
DCHECK(IsStoreInLiteral());
TailCallRuntime(Runtime::kStoreDataPropertyInLiteral, context, receiver,
@ -1086,16 +1127,19 @@ void KeyedStoreGenericAssembler::StoreIC_NoFeedback() {
// checks, strings and string wrappers, proxies) are handled in the runtime.
GotoIf(IsSpecialReceiverInstanceType(instance_type), &miss);
{
StoreICParameters p(context, receiver, name, value, slot,
UndefinedConstant());
StoreICParameters p(
context, receiver, name, value, slot, UndefinedConstant(),
IsKeyedStoreOwn() ? StoreICMode::kStoreOwn : StoreICMode::kDefault);
EmitGenericPropertyStore(CAST(receiver), receiver_map, &p, &miss);
}
}
BIND(&miss);
{
TailCallRuntime(Runtime::kStoreIC_Miss, context, value, slot,
UndefinedConstant(), receiver_maybe_smi, name);
auto runtime =
IsKeyedStoreOwn() ? Runtime::kStoreOwnIC_Miss : Runtime::kStoreIC_Miss;
TailCallRuntime(runtime, context, value, slot, UndefinedConstant(),
receiver_maybe_smi, name);
}
}
@ -1106,7 +1150,7 @@ void KeyedStoreGenericAssembler::SetProperty(TNode<Context> context,
TNode<Object> value,
LanguageMode language_mode) {
StoreICParameters p(context, receiver, unique_name, value, {},
UndefinedConstant());
UndefinedConstant(), StoreICMode::kDefault);
Label done(this), slow(this, Label::kDeferred);
ExitPoint exit_point(this, [&](TNode<Object> result) { Goto(&done); });

View File

@ -34,11 +34,21 @@ class KeyedStoreGenericGenerator {
TNode<Object> value);
};
class KeyedDefineOwnGenericGenerator {
public:
static void Generate(compiler::CodeAssemblerState* state);
};
class StoreICNoFeedbackGenerator {
public:
static void Generate(compiler::CodeAssemblerState* state);
};
class StoreOwnICNoFeedbackGenerator {
public:
static void Generate(compiler::CodeAssemblerState* state);
};
} // namespace internal
} // namespace v8

View File

@ -919,6 +919,17 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::StoreKeyedProperty(
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::DefineKeyedProperty(
Register object, Register key, int feedback_slot) {
// Ensure that the IC uses a strict language mode, as this is the only
// supported mode for this use case.
DCHECK_EQ(GetLanguageModeFromSlotKind(feedback_vector_spec()->GetKind(
FeedbackVector::ToSlot(feedback_slot))),
LanguageMode::kStrict);
OutputStaKeyedPropertyAsDefine(object, key, feedback_slot);
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::StoreInArrayLiteral(
Register array, Register index, int feedback_slot) {
OutputStaInArrayLiteral(array, index, feedback_slot);

View File

@ -187,6 +187,9 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final {
BytecodeArrayBuilder& StoreKeyedProperty(Register object, Register key,
int feedback_slot,
LanguageMode language_mode);
BytecodeArrayBuilder& DefineKeyedProperty(Register object, Register key,
int feedback_slot);
// Store an own element in an array literal. The value to be stored should be
// in the accumulator.
BytecodeArrayBuilder& StoreInArrayLiteral(Register array, Register index,

View File

@ -2759,41 +2759,51 @@ void BytecodeGenerator::VisitClassLiteral(ClassLiteral* expr, Register name) {
void BytecodeGenerator::BuildClassProperty(ClassLiteral::Property* property) {
RegisterAllocationScope register_scope(this);
RegisterList args = register_allocator()->NewRegisterList(3);
Register constructor = args[0], key = args[1], value = args[2];
builder()->MoveRegister(builder()->Receiver(), constructor);
Register key;
// Private methods are not initialized in BuildClassProperty.
DCHECK_IMPLIES(property->is_private(),
property->kind() == ClassLiteral::Property::FIELD);
if (property->is_computed_name()) {
DCHECK_EQ(property->kind(), ClassLiteral::Property::FIELD);
DCHECK(!property->is_private());
Variable* var = property->computed_name_var();
DCHECK_NOT_NULL(var);
// The computed name is already evaluated and stored in a variable at class
// definition time.
BuildVariableLoad(var, HoleCheckMode::kElided);
builder()->StoreAccumulatorInRegister(key);
} else if (property->is_private()) {
Variable* private_name_var = property->private_name_var();
DCHECK_NOT_NULL(private_name_var);
BuildVariableLoad(private_name_var, HoleCheckMode::kElided);
builder()->StoreAccumulatorInRegister(key);
} else {
BuildLoadPropertyKey(property, key);
bool is_literal_store = property->key()->IsPropertyName() &&
!property->is_computed_name() &&
!property->is_private();
if (!is_literal_store) {
key = register_allocator()->NewRegister();
if (property->is_computed_name()) {
DCHECK_EQ(property->kind(), ClassLiteral::Property::FIELD);
DCHECK(!property->is_private());
Variable* var = property->computed_name_var();
DCHECK_NOT_NULL(var);
// The computed name is already evaluated and stored in a variable at
// class definition time.
BuildVariableLoad(var, HoleCheckMode::kElided);
builder()->StoreAccumulatorInRegister(key);
} else if (property->is_private()) {
Variable* private_name_var = property->private_name_var();
DCHECK_NOT_NULL(private_name_var);
BuildVariableLoad(private_name_var, HoleCheckMode::kElided);
builder()->StoreAccumulatorInRegister(key);
} else {
VisitForRegisterValue(property->key(), key);
}
}
builder()->SetExpressionAsStatementPosition(property->value());
VisitForRegisterValue(property->value(), value);
VisitForAccumulatorValue(property->value());
Runtime::FunctionId function_id =
property->kind() == ClassLiteral::Property::FIELD &&
!property->is_private()
? Runtime::kCreateDataProperty
: Runtime::kAddPrivateField;
builder()->CallRuntime(function_id, args);
if (is_literal_store) {
FeedbackSlot slot = feedback_spec()->AddStoreOwnICSlot();
builder()->StoreNamedOwnProperty(
builder()->Receiver(),
property->key()->AsLiteral()->AsRawPropertyName(),
feedback_index(slot));
} else {
FeedbackSlot slot = feedback_spec()->AddKeyedDefineOwnICSlot();
builder()->DefineKeyedProperty(builder()->Receiver(), key,
feedback_index(slot));
}
}
void BytecodeGenerator::VisitInitializeClassMembersStatement(

View File

@ -151,6 +151,8 @@ namespace interpreter {
OperandType::kReg, OperandType::kIdx, OperandType::kIdx) \
V(StaKeyedProperty, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg, OperandType::kReg, OperandType::kIdx) \
V(StaKeyedPropertyAsDefine, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg, OperandType::kReg, OperandType::kIdx) \
V(StaInArrayLiteral, ImplicitRegisterUse::kReadWriteAccumulator, \
OperandType::kReg, OperandType::kReg, OperandType::kIdx) \
V(StaDataPropertyInLiteral, ImplicitRegisterUse::kReadAccumulator, \

View File

@ -632,7 +632,7 @@ IGNITION_HANDLER(StaNamedProperty, InterpreterStoreNamedPropertyAssembler) {
// the name in constant pool entry <name_index> with the value in the
// accumulator.
IGNITION_HANDLER(StaNamedOwnProperty, InterpreterStoreNamedPropertyAssembler) {
Callable ic = CodeFactory::StoreOwnICInOptimizedCode(isolate());
Callable ic = Builtins::CallableFor(isolate(), Builtin::kStoreOwnIC);
StaNamedProperty(ic, NamedPropertyType::kOwn);
}
@ -659,6 +659,33 @@ IGNITION_HANDLER(StaKeyedProperty, InterpreterAssembler) {
Dispatch();
}
// StaKeyedPropertyAsDefine <object> <key> <slot>
//
// Calls the KeyedDefineOwnIC at FeedbackVector slot <slot> for <object> and
// the key <key> with the value in the accumulator.
//
// This is similar to StaKeyedProperty, but avoids checking the prototype chain,
// and in the case of private names, throws if the private name already exists.
IGNITION_HANDLER(StaKeyedPropertyAsDefine, InterpreterAssembler) {
TNode<Object> object = LoadRegisterAtOperandIndex(0);
TNode<Object> name = LoadRegisterAtOperandIndex(1);
TNode<Object> value = GetAccumulator();
TNode<TaggedIndex> slot = BytecodeOperandIdxTaggedIndex(2);
TNode<HeapObject> maybe_vector = LoadFeedbackVector();
TNode<Context> context = GetContext();
TVARIABLE(Object, var_result);
var_result = CallBuiltin(Builtin::kKeyedDefineOwnIC, context, object, name,
value, slot, maybe_vector);
// To avoid special logic in the deoptimizer to re-materialize the value in
// the accumulator, we overwrite the accumulator after the IC call. It
// doesn't really matter what we write to the accumulator here, since we
// restore to the correct value on the outside. Storing the result means we
// don't need to keep unnecessary state alive across the callstub.
SetAccumulator(var_result.value());
Dispatch();
}
// StaInArrayLiteral <array> <index> <slot>
//
// Calls the StoreInArrayLiteralIC at FeedbackVector slot <slot> for <array> and

View File

@ -83,6 +83,7 @@ int FeedbackMetadata::GetSlotSize(FeedbackSlotKind kind) {
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kDefineOwnKeyed:
case FeedbackSlotKind::kStoreGlobalSloppy:
case FeedbackSlotKind::kStoreGlobalStrict:
case FeedbackSlotKind::kStoreKeyedSloppy:

View File

@ -164,6 +164,8 @@ const char* FeedbackMetadata::Kind2String(FeedbackSlotKind kind) {
return "StoreNamedStrict";
case FeedbackSlotKind::kStoreOwnNamed:
return "StoreOwnNamed";
case FeedbackSlotKind::kDefineOwnKeyed:
return "DefineOwnKeyed";
case FeedbackSlotKind::kStoreGlobalSloppy:
return "StoreGlobalSloppy";
case FeedbackSlotKind::kStoreGlobalStrict:
@ -304,6 +306,7 @@ Handle<FeedbackVector> FeedbackVector::New(
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kDefineOwnKeyed:
case FeedbackSlotKind::kStoreKeyedSloppy:
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral:
@ -608,6 +611,7 @@ void FeedbackNexus::ConfigureUninitialized() {
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kDefineOwnKeyed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
@ -646,6 +650,7 @@ bool FeedbackNexus::Clear() {
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kDefineOwnKeyed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
@ -742,6 +747,7 @@ InlineCacheState FeedbackNexus::ic_state() const {
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kDefineOwnKeyed:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed: {
@ -768,7 +774,7 @@ InlineCacheState FeedbackNexus::ic_state() const {
}
if (heap_object.IsName()) {
DCHECK(IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) ||
IsKeyedHasICKind(kind()));
IsKeyedHasICKind(kind()) || IsDefineOwnICKind(kind()));
Object extra_object = extra->GetHeapObjectAssumeStrong();
WeakFixedArray extra_array = WeakFixedArray::cast(extra_object);
return extra_array.length() > 2 ? POLYMORPHIC : MONOMORPHIC;
@ -1146,7 +1152,7 @@ MaybeObjectHandle FeedbackNexus::FindHandlerForMap(Handle<Map> map) const {
Name FeedbackNexus::GetName() const {
if (IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind()) ||
IsKeyedHasICKind(kind())) {
IsKeyedHasICKind(kind()) || IsKeyedDefineOwnICKind(kind())) {
MaybeObject feedback = GetFeedback();
if (IsPropertyNameFeedback(feedback)) {
return Name::cast(feedback->GetHeapObjectAssumeStrong());
@ -1229,7 +1235,7 @@ KeyedAccessStoreMode KeyedAccessStoreModeForBuiltin(Builtin builtin) {
KeyedAccessStoreMode FeedbackNexus::GetKeyedAccessStoreMode() const {
DCHECK(IsKeyedStoreICKind(kind()) || IsStoreInArrayLiteralICKind(kind()) ||
IsStoreDataPropertyInLiteralKind(kind()));
IsStoreDataPropertyInLiteralKind(kind()) || IsDefineOwnICKind(kind()));
KeyedAccessStoreMode mode = STANDARD_STORE;
if (GetKeyType() == PROPERTY) return mode;
@ -1264,6 +1270,10 @@ KeyedAccessStoreMode FeedbackNexus::GetKeyedAccessStoreMode() const {
mode = StoreHandler::GetKeyedAccessStoreMode(*maybe_code_handler);
if (mode != STANDARD_STORE) return mode;
continue;
} else if (IsDefineOwnICKind(kind())) {
mode = StoreHandler::GetKeyedAccessStoreMode(*maybe_code_handler);
if (mode != STANDARD_STORE) return mode;
continue;
} else {
// Element store without prototype chain check.
if (V8_EXTERNAL_CODE_SPACE_BOOL) {
@ -1289,7 +1299,7 @@ KeyedAccessStoreMode FeedbackNexus::GetKeyedAccessStoreMode() const {
IcCheckType FeedbackNexus::GetKeyType() const {
DCHECK(IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind()) ||
IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind()) ||
IsStoreDataPropertyInLiteralKind(kind()));
IsStoreDataPropertyInLiteralKind(kind()) || IsDefineOwnICKind(kind()));
auto pair = GetFeedbackPair();
MaybeObject feedback = pair.first;
if (feedback == MegamorphicSentinel()) {
@ -1297,7 +1307,9 @@ IcCheckType FeedbackNexus::GetKeyType() const {
Smi::ToInt(pair.second->template cast<Object>()));
}
MaybeObject maybe_name =
IsStoreDataPropertyInLiteralKind(kind()) ? pair.second : feedback;
IsStoreDataPropertyInLiteralKind(kind()) || IsDefineOwnICKind(kind())
? pair.second
: feedback;
return IsPropertyNameFeedback(maybe_name) ? PROPERTY : ELEMENT;
}
@ -1450,7 +1462,7 @@ FeedbackIterator::FeedbackIterator(const FeedbackNexus* nexus)
IsKeyedStoreICKind(nexus->kind()) || IsStoreOwnICKind(nexus->kind()) ||
IsStoreDataPropertyInLiteralKind(nexus->kind()) ||
IsStoreInArrayLiteralICKind(nexus->kind()) ||
IsKeyedHasICKind(nexus->kind()));
IsKeyedHasICKind(nexus->kind()) || IsDefineOwnICKind(nexus->kind()));
DisallowGarbageCollection no_gc;
auto pair = nexus->GetFeedbackPair();

View File

@ -50,6 +50,7 @@ enum class FeedbackSlotKind : uint8_t {
kStoreGlobalStrict,
kStoreNamedStrict,
kStoreOwnNamed,
kDefineOwnKeyed,
kStoreKeyedStrict,
kStoreInArrayLiteral,
kBinaryOp,
@ -102,6 +103,14 @@ inline bool IsStoreOwnICKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kStoreOwnNamed;
}
inline bool IsKeyedDefineOwnICKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kDefineOwnKeyed;
}
inline bool IsDefineOwnICKind(FeedbackSlotKind kind) {
return IsKeyedDefineOwnICKind(kind);
}
inline bool IsStoreDataPropertyInLiteralKind(FeedbackSlotKind kind) {
return kind == FeedbackSlotKind::kStoreDataPropertyInLiteral;
}
@ -136,7 +145,8 @@ inline TypeofMode GetTypeofModeFromSlotKind(FeedbackSlotKind kind) {
inline LanguageMode GetLanguageModeFromSlotKind(FeedbackSlotKind kind) {
DCHECK(IsStoreICKind(kind) || IsStoreOwnICKind(kind) ||
IsStoreGlobalICKind(kind) || IsKeyedStoreICKind(kind));
IsStoreGlobalICKind(kind) || IsKeyedStoreICKind(kind) ||
IsDefineOwnICKind(kind));
STATIC_ASSERT(FeedbackSlotKind::kStoreGlobalSloppy <=
FeedbackSlotKind::kLastSloppyKind);
STATIC_ASSERT(FeedbackSlotKind::kStoreKeyedSloppy <=
@ -424,6 +434,12 @@ class V8_EXPORT_PRIVATE FeedbackVectorSpec {
return AddSlot(FeedbackSlotKind::kStoreOwnNamed);
}
// Identical to StoreOwnKeyed, but will throw if a private field already
// exists.
FeedbackSlot AddKeyedDefineOwnICSlot() {
return AddSlot(FeedbackSlotKind::kDefineOwnKeyed);
}
FeedbackSlot AddStoreGlobalICSlot(LanguageMode language_mode) {
STATIC_ASSERT(LanguageModeSize == 2);
return AddSlot(is_strict(language_mode)

View File

@ -552,6 +552,41 @@ MaybeHandle<Object> Runtime::SetObjectProperty(
return value;
}
MaybeHandle<Object> Runtime::DefineClassField(Isolate* isolate,
Handle<Object> object,
Handle<Object> key,
Handle<Object> value,
StoreOrigin store_origin,
Maybe<ShouldThrow> should_throw) {
if (object->IsNullOrUndefined(isolate)) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kNonObjectPropertyStore, key, object),
Object);
}
// Check if the given key is an array index.
bool success = false;
PropertyKey lookup_key(isolate, key, &success);
if (!success) return MaybeHandle<Object>();
LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN);
if (it.IsFound() && key->IsSymbol() && Symbol::cast(*key).is_private_name()) {
Handle<Object> name_string(Symbol::cast(*key).description(), isolate);
DCHECK(name_string->IsString());
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalidPrivateFieldReitialization,
name_string),
Object);
}
MAYBE_RETURN_NULL(
Object::SetProperty(&it, value, store_origin, should_throw));
return value;
}
RUNTIME_FUNCTION(Runtime_InternalSetPrototype) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
@ -822,6 +857,19 @@ RUNTIME_FUNCTION(Runtime_SetKeyedProperty) {
StoreOrigin::kMaybeKeyed));
}
RUNTIME_FUNCTION(Runtime_DefineClassField) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, object, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, key, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 2);
RETURN_RESULT_OR_FAILURE(
isolate, Runtime::DefineClassField(isolate, object, key, value,
StoreOrigin::kMaybeKeyed));
}
RUNTIME_FUNCTION(Runtime_SetNamedProperty) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());

View File

@ -333,6 +333,7 @@ namespace internal {
F(OptimizeObjectForAddingMultipleProperties, 2, 1) \
F(SetDataProperties, 2, 1) \
F(SetKeyedProperty, 3, 1) \
F(DefineClassField, 3, 1) \
F(SetNamedProperty, 3, 1) \
F(SetOwnPropertyIgnoreAttributes, 4, 1) \
F(StoreDataPropertyInLiteral, 3, 1) \
@ -634,6 +635,7 @@ namespace internal {
F(ElementsTransitionAndStoreIC_Miss, 6, 1) \
F(KeyedLoadIC_Miss, 4, 1) \
F(KeyedStoreIC_Miss, 5, 1) \
F(KeyedDefineOwnIC_Miss, 5, 1) \
F(StoreInArrayLiteralIC_Miss, 5, 1) \
F(KeyedStoreIC_Slow, 3, 1) \
F(LoadElementWithInterceptor, 2, 1) \
@ -649,6 +651,7 @@ namespace internal {
F(StoreGlobalICNoFeedback_Miss, 2, 1) \
F(StoreGlobalIC_Slow, 5, 1) \
F(StoreIC_Miss, 5, 1) \
F(StoreOwnIC_Miss, 5, 1) \
F(StoreInArrayLiteralIC_Slow, 5, 1) \
F(StorePropertyWithInterceptor, 5, 1) \
F(CloneObjectIC_Miss, 4, 1) \
@ -794,6 +797,11 @@ class Runtime : public AllStatic {
Handle<Object> value, StoreOrigin store_origin,
Maybe<ShouldThrow> should_throw = Nothing<ShouldThrow>());
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
DefineClassField(Isolate* isolate, Handle<Object> object, Handle<Object> key,
Handle<Object> value, StoreOrigin store_origin,
Maybe<ShouldThrow> should_throw = Nothing<ShouldThrow>());
// When "receiver" is not passed, it defaults to "lookup_start_object".
V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object>
GetObjectProperty(Isolate* isolate, Handle<Object> lookup_start_object,

View File

@ -0,0 +1,60 @@
// Copyright 2021 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 --opt
let privateSymbol = %CreatePrivateSymbol("private");
let privateName = %CreatePrivateNameSymbol("privateName");
function test() {
"use strict";
// These computed properties are translated into JSStoreDataPropertyInLiteral
// ops, and AccessInfoFactory::ComputePropertyAccessInfo should find a
// suitable map transition when optimizing. Even if the implementation details
// are ignored, we still want to assert that these properties are installed as
// non-enumerable, due to being private symbols.
let obj = {
[privateSymbol]: "private",
[privateName]: "privateName",
};
assertPropertiesEqual(Object.getOwnPropertyDescriptor(obj, privateSymbol), {
value: "private",
writable: true,
configurable: true,
enumerable: false,
});
assertPropertiesEqual(Object.getOwnPropertyDescriptor(obj, privateName), {
value: "privateName",
writable: true,
configurable: true,
enumerable: false,
});
// They don't leak via Object.keys(), Object.getOwnPropertyNames, or
// Object.getOwnPropertySymbols
let props = Object.getOwnPropertyNames(obj);
assertFalse(props.includes(privateSymbol));
assertFalse(props.includes(privateName));
let symbols = Object.getOwnPropertySymbols(obj);
assertFalse(symbols.includes(privateSymbol));
assertFalse(symbols.includes(privateName));
let keys = Object.keys(obj);
assertFalse(keys.includes(privateSymbol));
assertFalse(keys.includes(privateName));
return obj;
}
%PrepareFunctionForOptimization(test);
test();
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
assertOptimized(test);

View File

@ -0,0 +1,103 @@
// Copyright 2021 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 --opt
// Single structure, field is inline in the object
{
class C {
#x;
constructor(x) { this.#x = x; }
x() { return this.#x; }
}
%PrepareFunctionForOptimization(C);
assertSame(new C(1).x(), 1);
assertSame(new C(2).x(), 2);
assertSame(new C(3).x(), 3);
assertSame(new C(4).x(), 4);
assertSame(new C(5).x(), 5);
%OptimizeFunctionOnNextCall(C);
assertSame(new C(10).x(), 10);
}
// Single structure, field is out of line
{
class C {
a = (() => this.z = this.q = this.alphabet =
this[Symbol.toStringTag] = this["ninteen eighty four"] = 42)()
b = (() => this[47] = this.extra = this.fortran = this.derble =
this["high quality zebras"] = 73)()
c = (() => this.moreprops = this.jimbo = this["flabberghast"] = 198)()
#x;
constructor(x) { this.#x = x; }
x() { return this.#x; }
}
%PrepareFunctionForOptimization(C);
assertSame(new C(1).x(), 1);
assertSame(new C(6000).x(), 6000);
assertSame(new C(37).x(), 37);
assertSame(new C(-1).x(), -1);
assertSame(new C(4900).x(), 4900);
%OptimizeFunctionOnNextCall(C);
assertSame(new C(9999).x(), 9999);
}
// Multiple structures, field is inline (probably)
{
let i = 0;
class C {
a = (() => {
if (i > 1000)
this.tenEtwo = i;
if ((i & 0x7F) > 64)
this.boxing = i;
if (i > 9000)
this["It's over nine thousand!"] = i;
})()
#x;
constructor(x) { this.#x = x; }
x() { return this.#x; }
}
%PrepareFunctionForOptimization(C);
assertSame(new C(1).x(), 1);
assertSame(new C(256).x(), 256);
assertSame(new C(36).x(), 36);
assertSame(new C(16384).x(), 16384);
assertSame(new C(17).x(), 17);
%OptimizeFunctionOnNextCall(C);
assertSame(new C(74).x(), 74);
}
// Multiple structures, field should be out of object
{
let i = 0;
class C {
a = (() => this.z = this.q = this.alphabet =
this[Symbol.toStringTag] = this["ninteen eighty four"] = 42)()
b = (() => this[47] = this.extra = this.fortran = this.derble =
this["high quality zebras"] = 73)()
c = (() => this.moreprops = this.jimbo = this["flabberghast"] = 198)()
d = (() => {
if (i > 1000)
this.tenEtwo = i;
if ((i & 0x7F) > 64)
this.boxing = i;
if (i > 9000)
this["It's over nine thousand again!"] = i;
})()
#x;
constructor(x) { this.#x = x; }
x() { return this.#x; }
}
%PrepareFunctionForOptimization(C);
assertSame(new C(1).x(), 1);
assertSame(new C(38000).x(), 38000);
assertSame(new C(9080).x(), 9080);
assertSame(new C(92).x(), 92);
assertSame(new C(4000).x(), 4000);
%OptimizeFunctionOnNextCall(C);
assertSame(new C(10000).x(), 10000);
}

View File

@ -151,6 +151,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.StoreKeyedProperty(reg, reg, strict_keyed_store_slot.ToInt(),
LanguageMode::kStrict)
.StoreNamedOwnProperty(reg, name, store_own_slot.ToInt())
.DefineKeyedProperty(reg, reg, store_own_slot.ToInt())
.StoreInArrayLiteral(reg, reg, store_array_element_slot.ToInt());
// Emit Iterator-protocol operations