| Index: src/compiler/js-native-context-specialization.cc
|
| diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc
|
| index 53d6232eaa34752688592f1114f539b7daf81926..5718dbb8cc167b29b1ef753e5d67ae3661b2ed08 100644
|
| --- a/src/compiler/js-native-context-specialization.cc
|
| +++ b/src/compiler/js-native-context-specialization.cc
|
| @@ -22,6 +22,38 @@ namespace v8 {
|
| namespace internal {
|
| namespace compiler {
|
|
|
| +namespace {
|
| +
|
| +bool HasNumberMaps(MapList const& maps) {
|
| + for (auto map : maps) {
|
| + if (map->instance_type() == HEAP_NUMBER_TYPE) return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool HasOnlyJSArrayMaps(MapList const& maps) {
|
| + for (auto map : maps) {
|
| + if (!map->IsJSArrayMap()) return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool HasOnlyNumberMaps(MapList const& maps) {
|
| + for (auto map : maps) {
|
| + if (map->instance_type() != HEAP_NUMBER_TYPE) return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool HasOnlyStringMaps(MapList const& maps) {
|
| + for (auto map : maps) {
|
| + if (!map->IsStringMap()) return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| JSNativeContextSpecialization::JSNativeContextSpecialization(
|
| Editor* editor, JSGraph* jsgraph, Flags flags,
|
| MaybeHandle<Context> native_context, CompilationDependencies* dependencies,
|
| @@ -100,12 +132,6 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
| // Nothing to do if we have no non-deprecated maps.
|
| if (access_infos.empty()) return NoChange();
|
|
|
| - // The final states for every polymorphic branch. We join them with
|
| - // Merge++Phi+EffectPhi at the bottom.
|
| - ZoneVector<Node*> values(zone());
|
| - ZoneVector<Node*> effects(zone());
|
| - ZoneVector<Node*> controls(zone());
|
| -
|
| // Ensure that {index} matches the specified {name} (if {index} is given).
|
| if (index != nullptr) {
|
| Node* check = graph()->NewNode(simplified()->ReferenceEqual(Type::Name()),
|
| @@ -113,285 +139,168 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
| effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
|
| }
|
|
|
| - // Check if {receiver} may be a number.
|
| - bool receiverissmi_possible = false;
|
| - for (PropertyAccessInfo const& access_info : access_infos) {
|
| - if (access_info.receiver_type()->Is(Type::Number())) {
|
| - receiverissmi_possible = true;
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // Ensure that {receiver} is a heap object.
|
| - Node* receiverissmi_control = nullptr;
|
| - Node* receiverissmi_effect = effect;
|
| - if (receiverissmi_possible) {
|
| - Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
|
| - Node* branch = graph()->NewNode(common()->Branch(), check, control);
|
| - control = graph()->NewNode(common()->IfFalse(), branch);
|
| - receiverissmi_control = graph()->NewNode(common()->IfTrue(), branch);
|
| - receiverissmi_effect = effect;
|
| - } else if (access_infos.size() != 1 ||
|
| - !access_infos[0].receiver_type()->Is(Type::String())) {
|
| - // TODO(bmeurer): We omit the Smi check here if we are going to lower to
|
| - // the CheckString below; make this less horrible and adhoc.
|
| - receiver = effect = graph()->NewNode(simplified()->CheckTaggedPointer(),
|
| - receiver, effect, control);
|
| - }
|
| -
|
| - // Load the {receiver} map. The resulting effect is the dominating effect for
|
| - // all (polymorphic) branches.
|
| - Node* receiver_map = effect =
|
| - graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
| - receiver, effect, control);
|
| -
|
| - // Generate code for the various different property access patterns.
|
| - Node* fallthrough_control = control;
|
| - for (size_t j = 0; j < access_infos.size(); ++j) {
|
| - PropertyAccessInfo const& access_info = access_infos[j];
|
| - Node* this_value = value;
|
| - Node* this_receiver = receiver;
|
| - Node* this_effect = effect;
|
| - Node* this_control;
|
| + // Check for the monomorphic cases.
|
| + if (access_infos.size() == 1 &&
|
| + HasOnlyStringMaps(access_infos[0].receiver_maps())) {
|
| + // Monormorphic string access (ignoring the fact that there are multiple
|
| + // String maps).
|
| + receiver = effect = graph()->NewNode(simplified()->CheckString(), receiver,
|
| + effect, control);
|
|
|
| - // Perform map check on {receiver}.
|
| - Type* receiver_type = access_info.receiver_type();
|
| - if (receiver_type->Is(Type::String())) {
|
| - if (j == access_infos.size() - 1) {
|
| - this_receiver = this_effect =
|
| - graph()->NewNode(simplified()->CheckString(), receiver, this_effect,
|
| - fallthrough_control);
|
| - this_control = fallthrough_control;
|
| - fallthrough_control = nullptr;
|
| - } else {
|
| - Node* check =
|
| - graph()->NewNode(simplified()->ObjectIsString(), receiver);
|
| - Node* branch =
|
| - graph()->NewNode(common()->Branch(), check, fallthrough_control);
|
| - fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
| - this_control = graph()->NewNode(common()->IfTrue(), branch);
|
| - }
|
| - } else {
|
| - // Emit a (sequence of) map checks for other {receiver}s.
|
| - ZoneVector<Node*> this_controls(zone());
|
| - ZoneVector<Node*> this_effects(zone());
|
| - int num_classes = access_info.receiver_type()->NumClasses();
|
| - for (auto i = access_info.receiver_type()->Classes(); !i.Done();
|
| - i.Advance()) {
|
| - DCHECK_LT(0, num_classes);
|
| - Handle<Map> map = i.Current();
|
| - Node* check =
|
| - graph()->NewNode(simplified()->ReferenceEqual(Type::Internal()),
|
| - receiver_map, jsgraph()->Constant(map));
|
| - if (--num_classes == 0 && j == access_infos.size() - 1) {
|
| - check = graph()->NewNode(simplified()->CheckIf(), check, this_effect,
|
| - fallthrough_control);
|
| - this_controls.push_back(fallthrough_control);
|
| - this_effects.push_back(check);
|
| - fallthrough_control = nullptr;
|
| - } else {
|
| - Node* branch =
|
| - graph()->NewNode(common()->Branch(), check, fallthrough_control);
|
| - fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
| - this_controls.push_back(graph()->NewNode(common()->IfTrue(), branch));
|
| - this_effects.push_back(this_effect);
|
| - }
|
| - }
|
| + // Generate the actual property access.
|
| + ValueEffectControl continuation =
|
| + BuildPropertyAccess(receiver, value, effect, control, name,
|
| + native_context, access_infos[0], access_mode);
|
| + value = continuation.value();
|
| + effect = continuation.effect();
|
| + control = continuation.control();
|
| + } else if (access_infos.size() == 1 &&
|
| + HasOnlyNumberMaps(access_infos[0].receiver_maps())) {
|
| + // Monomorphic number access (we also deal with Smis here).
|
| + receiver = effect = graph()->NewNode(simplified()->CheckNumber(), receiver,
|
| + effect, control);
|
|
|
| - // The Number case requires special treatment to also deal with Smis.
|
| - if (receiver_type->Is(Type::Number())) {
|
| - // Join this check with the "receiver is smi" check above.
|
| - DCHECK_NOT_NULL(receiverissmi_effect);
|
| - DCHECK_NOT_NULL(receiverissmi_control);
|
| - this_effects.push_back(receiverissmi_effect);
|
| - this_controls.push_back(receiverissmi_control);
|
| - receiverissmi_effect = receiverissmi_control = nullptr;
|
| + // Generate the actual property access.
|
| + ValueEffectControl continuation =
|
| + BuildPropertyAccess(receiver, value, effect, control, name,
|
| + native_context, access_infos[0], access_mode);
|
| + value = continuation.value();
|
| + effect = continuation.effect();
|
| + control = continuation.control();
|
| + } else {
|
| + // The final states for every polymorphic branch. We join them with
|
| + // Merge+Phi+EffectPhi at the bottom.
|
| + ZoneVector<Node*> values(zone());
|
| + ZoneVector<Node*> effects(zone());
|
| + ZoneVector<Node*> controls(zone());
|
| +
|
| + // Check if {receiver} may be a number.
|
| + bool receiverissmi_possible = false;
|
| + for (PropertyAccessInfo const& access_info : access_infos) {
|
| + if (HasNumberMaps(access_info.receiver_maps())) {
|
| + receiverissmi_possible = true;
|
| + break;
|
| }
|
| -
|
| - // Create dominating Merge+EffectPhi for this {receiver} type.
|
| - int const this_control_count = static_cast<int>(this_controls.size());
|
| - this_control =
|
| - (this_control_count == 1)
|
| - ? this_controls.front()
|
| - : graph()->NewNode(common()->Merge(this_control_count),
|
| - this_control_count, &this_controls.front());
|
| - this_effects.push_back(this_control);
|
| - int const this_effect_count = static_cast<int>(this_effects.size());
|
| - this_effect =
|
| - (this_control_count == 1)
|
| - ? this_effects.front()
|
| - : graph()->NewNode(common()->EffectPhi(this_control_count),
|
| - this_effect_count, &this_effects.front());
|
| }
|
|
|
| - // Determine actual holder and perform prototype chain checks.
|
| - Handle<JSObject> holder;
|
| - if (access_info.holder().ToHandle(&holder)) {
|
| - AssumePrototypesStable(receiver_type, native_context, holder);
|
| + // Ensure that {receiver} is a heap object.
|
| + Node* receiverissmi_control = nullptr;
|
| + Node* receiverissmi_effect = effect;
|
| + if (receiverissmi_possible) {
|
| + Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
|
| + Node* branch = graph()->NewNode(common()->Branch(), check, control);
|
| + control = graph()->NewNode(common()->IfFalse(), branch);
|
| + receiverissmi_control = graph()->NewNode(common()->IfTrue(), branch);
|
| + receiverissmi_effect = effect;
|
| + } else {
|
| + receiver = effect = graph()->NewNode(simplified()->CheckTaggedPointer(),
|
| + receiver, effect, control);
|
| }
|
|
|
| - // Generate the actual property access.
|
| - if (access_info.IsNotFound()) {
|
| - DCHECK_EQ(AccessMode::kLoad, access_mode);
|
| - this_value = jsgraph()->UndefinedConstant();
|
| - } else if (access_info.IsDataConstant()) {
|
| - this_value = jsgraph()->Constant(access_info.constant());
|
| - if (access_mode == AccessMode::kStore) {
|
| - Node* check = graph()->NewNode(
|
| - simplified()->ReferenceEqual(Type::Tagged()), value, this_value);
|
| - this_effect = graph()->NewNode(simplified()->CheckIf(), check,
|
| - this_effect, this_control);
|
| - }
|
| - } else {
|
| - DCHECK(access_info.IsDataField());
|
| - FieldIndex const field_index = access_info.field_index();
|
| - Type* const field_type = access_info.field_type();
|
| - if (access_mode == AccessMode::kLoad &&
|
| - access_info.holder().ToHandle(&holder)) {
|
| - this_receiver = jsgraph()->Constant(holder);
|
| - }
|
| - Node* this_storage = this_receiver;
|
| - if (!field_index.is_inobject()) {
|
| - this_storage = this_effect = graph()->NewNode(
|
| - simplified()->LoadField(AccessBuilder::ForJSObjectProperties()),
|
| - this_storage, this_effect, this_control);
|
| - }
|
| - FieldAccess field_access = {
|
| - kTaggedBase, field_index.offset(), name,
|
| - field_type, MachineType::AnyTagged(), kFullWriteBarrier};
|
| - if (access_mode == AccessMode::kLoad) {
|
| - if (field_type->Is(Type::UntaggedFloat64())) {
|
| - if (!field_index.is_inobject() || field_index.is_hidden_field() ||
|
| - !FLAG_unbox_double_fields) {
|
| - this_storage = this_effect =
|
| - graph()->NewNode(simplified()->LoadField(field_access),
|
| - this_storage, this_effect, this_control);
|
| - field_access.offset = HeapNumber::kValueOffset;
|
| - field_access.name = MaybeHandle<Name>();
|
| - }
|
| - field_access.machine_type = MachineType::Float64();
|
| - }
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->LoadField(field_access),
|
| - this_storage, this_effect, this_control);
|
| - } else {
|
| - DCHECK_EQ(AccessMode::kStore, access_mode);
|
| - if (field_type->Is(Type::UntaggedFloat64())) {
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->CheckNumber(), this_value,
|
| - this_effect, this_control);
|
| -
|
| - if (!field_index.is_inobject() || field_index.is_hidden_field() ||
|
| - !FLAG_unbox_double_fields) {
|
| - if (access_info.HasTransitionMap()) {
|
| - // Allocate a MutableHeapNumber for the new property.
|
| - this_effect = graph()->NewNode(
|
| - common()->BeginRegion(RegionObservability::kNotObservable),
|
| - this_effect);
|
| - Node* this_box = this_effect =
|
| - graph()->NewNode(simplified()->Allocate(NOT_TENURED),
|
| - jsgraph()->Constant(HeapNumber::kSize),
|
| - this_effect, this_control);
|
| - this_effect = graph()->NewNode(
|
| - simplified()->StoreField(AccessBuilder::ForMap()), this_box,
|
| - jsgraph()->HeapConstant(factory()->mutable_heap_number_map()),
|
| - this_effect, this_control);
|
| - this_effect = graph()->NewNode(
|
| - simplified()->StoreField(AccessBuilder::ForHeapNumberValue()),
|
| - this_box, this_value, this_effect, this_control);
|
| - this_value = this_effect = graph()->NewNode(
|
| - common()->FinishRegion(), this_box, this_effect);
|
| -
|
| - field_access.type = Type::TaggedPointer();
|
| - } else {
|
| - // We just store directly to the MutableHeapNumber.
|
| - this_storage = this_effect =
|
| - graph()->NewNode(simplified()->LoadField(field_access),
|
| - this_storage, this_effect, this_control);
|
| - field_access.offset = HeapNumber::kValueOffset;
|
| - field_access.name = MaybeHandle<Name>();
|
| - field_access.machine_type = MachineType::Float64();
|
| - }
|
| - } else {
|
| - // Unboxed double field, we store directly to the field.
|
| - field_access.machine_type = MachineType::Float64();
|
| - }
|
| - } else if (field_type->Is(Type::TaggedSigned())) {
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->CheckTaggedSigned(), this_value,
|
| - this_effect, this_control);
|
| - } else if (field_type->Is(Type::TaggedPointer())) {
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->CheckTaggedPointer(), this_value,
|
| - this_effect, this_control);
|
| - if (field_type->NumClasses() == 1) {
|
| - // Emit a map check for the value.
|
| - Node* this_value_map = this_effect = graph()->NewNode(
|
| - simplified()->LoadField(AccessBuilder::ForMap()), this_value,
|
| - this_effect, this_control);
|
| - Node* check = graph()->NewNode(
|
| - simplified()->ReferenceEqual(Type::Internal()), this_value_map,
|
| - jsgraph()->Constant(field_type->Classes().Current()));
|
| - this_effect = graph()->NewNode(simplified()->CheckIf(), check,
|
| - this_effect, this_control);
|
| + // Load the {receiver} map. The resulting effect is the dominating effect
|
| + // for all (polymorphic) branches.
|
| + Node* receiver_map = effect =
|
| + graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
| + receiver, effect, control);
|
| +
|
| + // Generate code for the various different property access patterns.
|
| + Node* fallthrough_control = control;
|
| + for (size_t j = 0; j < access_infos.size(); ++j) {
|
| + PropertyAccessInfo const& access_info = access_infos[j];
|
| + Node* this_value = value;
|
| + Node* this_receiver = receiver;
|
| + Node* this_effect = effect;
|
| + Node* this_control;
|
| +
|
| + // Perform map check on {receiver}.
|
| + MapList const& receiver_maps = access_info.receiver_maps();
|
| + {
|
| + // Emit a (sequence of) map checks for other {receiver}s.
|
| + ZoneVector<Node*> this_controls(zone());
|
| + ZoneVector<Node*> this_effects(zone());
|
| + size_t num_classes = receiver_maps.size();
|
| + for (auto map : receiver_maps) {
|
| + DCHECK_LT(0u, num_classes);
|
| + Node* check =
|
| + graph()->NewNode(simplified()->ReferenceEqual(Type::Internal()),
|
| + receiver_map, jsgraph()->Constant(map));
|
| + if (--num_classes == 0 && j == access_infos.size() - 1) {
|
| + check = graph()->NewNode(simplified()->CheckIf(), check,
|
| + this_effect, fallthrough_control);
|
| + this_controls.push_back(fallthrough_control);
|
| + this_effects.push_back(check);
|
| + fallthrough_control = nullptr;
|
| } else {
|
| - DCHECK_EQ(0, field_type->NumClasses());
|
| + Node* branch = graph()->NewNode(common()->Branch(), check,
|
| + fallthrough_control);
|
| + fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
| + this_controls.push_back(
|
| + graph()->NewNode(common()->IfTrue(), branch));
|
| + this_effects.push_back(this_effect);
|
| }
|
| - } else {
|
| - DCHECK(field_type->Is(Type::Tagged()));
|
| - }
|
| - Handle<Map> transition_map;
|
| - if (access_info.transition_map().ToHandle(&transition_map)) {
|
| - this_effect = graph()->NewNode(
|
| - common()->BeginRegion(RegionObservability::kObservable),
|
| - this_effect);
|
| - this_effect = graph()->NewNode(
|
| - simplified()->StoreField(AccessBuilder::ForMap()), this_receiver,
|
| - jsgraph()->Constant(transition_map), this_effect, this_control);
|
| }
|
| - this_effect = graph()->NewNode(simplified()->StoreField(field_access),
|
| - this_storage, this_value, this_effect,
|
| - this_control);
|
| - if (access_info.HasTransitionMap()) {
|
| - this_effect =
|
| - graph()->NewNode(common()->FinishRegion(),
|
| - jsgraph()->UndefinedConstant(), this_effect);
|
| +
|
| + // The Number case requires special treatment to also deal with Smis.
|
| + if (HasNumberMaps(receiver_maps)) {
|
| + // Join this check with the "receiver is smi" check above.
|
| + DCHECK_NOT_NULL(receiverissmi_effect);
|
| + DCHECK_NOT_NULL(receiverissmi_control);
|
| + this_effects.push_back(receiverissmi_effect);
|
| + this_controls.push_back(receiverissmi_control);
|
| + receiverissmi_effect = receiverissmi_control = nullptr;
|
| }
|
| +
|
| + // Create dominating Merge+EffectPhi for this {receiver} type.
|
| + int const this_control_count = static_cast<int>(this_controls.size());
|
| + this_control =
|
| + (this_control_count == 1)
|
| + ? this_controls.front()
|
| + : graph()->NewNode(common()->Merge(this_control_count),
|
| + this_control_count, &this_controls.front());
|
| + this_effects.push_back(this_control);
|
| + int const this_effect_count = static_cast<int>(this_effects.size());
|
| + this_effect =
|
| + (this_control_count == 1)
|
| + ? this_effects.front()
|
| + : graph()->NewNode(common()->EffectPhi(this_control_count),
|
| + this_effect_count, &this_effects.front());
|
| }
|
| - }
|
|
|
| - // Remember the final state for this property access.
|
| - values.push_back(this_value);
|
| - effects.push_back(this_effect);
|
| - controls.push_back(this_control);
|
| - }
|
| + // Generate the actual property access.
|
| + ValueEffectControl continuation = BuildPropertyAccess(
|
| + this_receiver, this_value, this_effect, this_control, name,
|
| + native_context, access_info, access_mode);
|
| + values.push_back(continuation.value());
|
| + effects.push_back(continuation.effect());
|
| + controls.push_back(continuation.control());
|
| + }
|
|
|
| - DCHECK_NULL(fallthrough_control);
|
| + DCHECK_NULL(fallthrough_control);
|
|
|
| - // Generate the final merge point for all (polymorphic) branches.
|
| - int const control_count = static_cast<int>(controls.size());
|
| - if (control_count == 0) {
|
| - value = effect = control = jsgraph()->Dead();
|
| - } else if (control_count == 1) {
|
| - value = values.front();
|
| - effect = effects.front();
|
| - control = controls.front();
|
| - } else {
|
| - control = graph()->NewNode(common()->Merge(control_count), control_count,
|
| - &controls.front());
|
| - values.push_back(control);
|
| - value = graph()->NewNode(
|
| - common()->Phi(MachineRepresentation::kTagged, control_count),
|
| - control_count + 1, &values.front());
|
| - effects.push_back(control);
|
| - effect = graph()->NewNode(common()->EffectPhi(control_count),
|
| - control_count + 1, &effects.front());
|
| + // Generate the final merge point for all (polymorphic) branches.
|
| + int const control_count = static_cast<int>(controls.size());
|
| + if (control_count == 0) {
|
| + value = effect = control = jsgraph()->Dead();
|
| + } else if (control_count == 1) {
|
| + value = values.front();
|
| + effect = effects.front();
|
| + control = controls.front();
|
| + } else {
|
| + control = graph()->NewNode(common()->Merge(control_count), control_count,
|
| + &controls.front());
|
| + values.push_back(control);
|
| + value = graph()->NewNode(
|
| + common()->Phi(MachineRepresentation::kTagged, control_count),
|
| + control_count + 1, &values.front());
|
| + effects.push_back(control);
|
| + effect = graph()->NewNode(common()->EffectPhi(control_count),
|
| + control_count + 1, &effects.front());
|
| + }
|
| }
|
| ReplaceWithValue(node, value, effect, control);
|
| return Replace(value);
|
| }
|
|
|
| -
|
| Reduction JSNativeContextSpecialization::ReduceNamedAccess(
|
| Node* node, Node* value, FeedbackNexus const& nexus, Handle<Name> name,
|
| AccessMode access_mode, LanguageMode language_mode) {
|
| @@ -560,16 +469,13 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
| receiver, this_effect, this_control);
|
|
|
| // Perform map check on {receiver}.
|
| - Type* receiver_type = access_info.receiver_type();
|
| - bool receiver_is_jsarray = true;
|
| + MapList const& receiver_maps = access_info.receiver_maps();
|
| {
|
| ZoneVector<Node*> this_controls(zone());
|
| ZoneVector<Node*> this_effects(zone());
|
| - int num_classes = access_info.receiver_type()->NumClasses();
|
| - for (auto i = access_info.receiver_type()->Classes(); !i.Done();
|
| - i.Advance()) {
|
| - DCHECK_LT(0, num_classes);
|
| - Handle<Map> map = i.Current();
|
| + size_t num_classes = receiver_maps.size();
|
| + for (Handle<Map> map : receiver_maps) {
|
| + DCHECK_LT(0u, num_classes);
|
| Node* check =
|
| graph()->NewNode(simplified()->ReferenceEqual(Type::Any()),
|
| receiver_map, jsgraph()->Constant(map));
|
| @@ -591,7 +497,6 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
| this_effects.push_back(effect);
|
| fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
| }
|
| - if (!map->IsJSArrayMap()) receiver_is_jsarray = false;
|
| }
|
|
|
| // Create single chokepoint for the control.
|
| @@ -623,141 +528,16 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
| // not compatible with (monomorphic) keyed stores.
|
| Handle<JSObject> holder;
|
| if (access_info.holder().ToHandle(&holder)) {
|
| - AssumePrototypesStable(receiver_type, native_context, holder);
|
| + AssumePrototypesStable(receiver_maps, native_context, holder);
|
| }
|
|
|
| - // 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();
|
| -
|
| - // Load the elements for the {receiver}.
|
| - Node* this_elements = this_effect = graph()->NewNode(
|
| - simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
|
| - this_receiver, this_effect, this_control);
|
| -
|
| - // Don't try to store to a copy-on-write backing store.
|
| - if (access_mode == AccessMode::kStore &&
|
| - IsFastSmiOrObjectElementsKind(elements_kind)) {
|
| - Node* this_elements_map = this_effect =
|
| - graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
| - this_elements, this_effect, this_control);
|
| - Node* check = graph()->NewNode(
|
| - simplified()->ReferenceEqual(Type::Any()), this_elements_map,
|
| - jsgraph()->HeapConstant(factory()->fixed_array_map()));
|
| - this_effect = graph()->NewNode(simplified()->CheckIf(), check,
|
| - this_effect, this_control);
|
| - }
|
| -
|
| - // Load the length of the {receiver}.
|
| - Node* this_length = this_effect =
|
| - receiver_is_jsarray
|
| - ? graph()->NewNode(
|
| - simplified()->LoadField(
|
| - AccessBuilder::ForJSArrayLength(elements_kind)),
|
| - this_receiver, this_effect, this_control)
|
| - : graph()->NewNode(
|
| - simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
|
| - this_elements, this_effect, this_control);
|
| -
|
| - // Check that the {index} is in the valid range for the {receiver}.
|
| - this_index = this_effect =
|
| - graph()->NewNode(simplified()->CheckBounds(), this_index, this_length,
|
| - this_effect, this_control);
|
| -
|
| - // Compute the element access.
|
| - Type* element_type = Type::Any();
|
| - MachineType element_machine_type = MachineType::AnyTagged();
|
| - if (IsFastDoubleElementsKind(elements_kind)) {
|
| - element_type = Type::Number();
|
| - element_machine_type = MachineType::Float64();
|
| - } else if (IsFastSmiElementsKind(elements_kind)) {
|
| - element_type = type_cache_.kSmi;
|
| - }
|
| - ElementAccess element_access = {kTaggedBase, FixedArray::kHeaderSize,
|
| - element_type, element_machine_type,
|
| - kFullWriteBarrier};
|
| -
|
| // Access the actual element.
|
| - // TODO(bmeurer): Refactor this into separate methods or even a separate
|
| - // class that deals with the elements access.
|
| - if (access_mode == AccessMode::kLoad) {
|
| - // Compute the real element access type, which includes the hole in case
|
| - // of holey backing stores.
|
| - if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
| - elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
| - element_access.type = Type::Union(
|
| - element_type,
|
| - Type::Constant(factory()->the_hole_value(), graph()->zone()),
|
| - graph()->zone());
|
| - }
|
| - // Perform the actual backing store access.
|
| - this_value = this_effect = graph()->NewNode(
|
| - simplified()->LoadElement(element_access), this_elements, this_index,
|
| - this_effect, this_control);
|
| - // Handle loading from holey backing stores correctly, by either mapping
|
| - // the hole to undefined if possible, or deoptimizing otherwise.
|
| - if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
| - elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
| - // Perform the hole check on the result.
|
| - CheckTaggedHoleMode mode = CheckTaggedHoleMode::kNeverReturnHole;
|
| - // Check if we are allowed to turn the hole into undefined.
|
| - Type* initial_holey_array_type = Type::Class(
|
| - handle(isolate()->get_initial_js_array_map(elements_kind)),
|
| - graph()->zone());
|
| - if (receiver_type->NowIs(initial_holey_array_type) &&
|
| - isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
| - // Add a code dependency on the array protector cell.
|
| - AssumePrototypesStable(receiver_type, native_context,
|
| - isolate()->initial_object_prototype());
|
| - dependencies()->AssumePropertyCell(factory()->array_protector());
|
| - // Turn the hole into undefined.
|
| - mode = CheckTaggedHoleMode::kConvertHoleToUndefined;
|
| - }
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->CheckTaggedHole(mode), this_value,
|
| - this_effect, this_control);
|
| - } else if (elements_kind == FAST_HOLEY_DOUBLE_ELEMENTS) {
|
| - // Perform the hole check on the result.
|
| - CheckFloat64HoleMode mode = CheckFloat64HoleMode::kNeverReturnHole;
|
| - // Check if we are allowed to return the hole directly.
|
| - Type* initial_holey_array_type = Type::Class(
|
| - handle(isolate()->get_initial_js_array_map(elements_kind)),
|
| - graph()->zone());
|
| - if (receiver_type->NowIs(initial_holey_array_type) &&
|
| - isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
| - // Add a code dependency on the array protector cell.
|
| - AssumePrototypesStable(receiver_type, native_context,
|
| - isolate()->initial_object_prototype());
|
| - dependencies()->AssumePropertyCell(factory()->array_protector());
|
| - // Return the signaling NaN hole directly if all uses are truncating.
|
| - mode = CheckFloat64HoleMode::kAllowReturnHole;
|
| - }
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->CheckFloat64Hole(mode), this_value,
|
| - this_effect, this_control);
|
| - }
|
| - } else {
|
| - DCHECK_EQ(AccessMode::kStore, access_mode);
|
| - if (IsFastSmiElementsKind(elements_kind)) {
|
| - this_value = this_effect =
|
| - graph()->NewNode(simplified()->CheckTaggedSigned(), this_value,
|
| - this_effect, this_control);
|
| - } else if (IsFastDoubleElementsKind(elements_kind)) {
|
| - this_value = this_effect = graph()->NewNode(
|
| - simplified()->CheckNumber(), this_value, this_effect, this_control);
|
| - // Make sure we do not store signalling NaNs into double arrays.
|
| - this_value =
|
| - graph()->NewNode(simplified()->NumberSilenceNaN(), this_value);
|
| - }
|
| - this_effect = graph()->NewNode(simplified()->StoreElement(element_access),
|
| - this_elements, this_index, this_value,
|
| - this_effect, this_control);
|
| - }
|
| -
|
| - // Remember the final state for this element access.
|
| - values.push_back(this_value);
|
| - effects.push_back(this_effect);
|
| - controls.push_back(this_control);
|
| + ValueEffectControl continuation = BuildElementAccess(
|
| + this_receiver, this_index, this_value, this_effect, this_control,
|
| + native_context, access_info, access_mode);
|
| + values.push_back(continuation.value());
|
| + effects.push_back(continuation.effect());
|
| + controls.push_back(continuation.control());
|
| }
|
|
|
| DCHECK_NULL(fallthrough_control);
|
| @@ -904,13 +684,283 @@ Reduction JSNativeContextSpecialization::ReduceJSStoreProperty(Node* node) {
|
| p.language_mode(), store_mode);
|
| }
|
|
|
| +JSNativeContextSpecialization::ValueEffectControl
|
| +JSNativeContextSpecialization::BuildPropertyAccess(
|
| + Node* receiver, Node* value, Node* effect, Node* control, Handle<Name> name,
|
| + Handle<Context> native_context, PropertyAccessInfo const& access_info,
|
| + AccessMode access_mode) {
|
| + // Determine actual holder and perform prototype chain checks.
|
| + Handle<JSObject> holder;
|
| + if (access_info.holder().ToHandle(&holder)) {
|
| + AssumePrototypesStable(access_info.receiver_maps(), native_context, holder);
|
| + }
|
| +
|
| + // Generate the actual property access.
|
| + if (access_info.IsNotFound()) {
|
| + DCHECK_EQ(AccessMode::kLoad, access_mode);
|
| + value = jsgraph()->UndefinedConstant();
|
| + } else if (access_info.IsDataConstant()) {
|
| + value = jsgraph()->Constant(access_info.constant());
|
| + if (access_mode == AccessMode::kStore) {
|
| + Node* check = graph()->NewNode(
|
| + simplified()->ReferenceEqual(Type::Tagged()), value, value);
|
| + effect =
|
| + graph()->NewNode(simplified()->CheckIf(), check, effect, control);
|
| + }
|
| + } else {
|
| + DCHECK(access_info.IsDataField());
|
| + FieldIndex const field_index = access_info.field_index();
|
| + Type* const field_type = access_info.field_type();
|
| + if (access_mode == AccessMode::kLoad &&
|
| + access_info.holder().ToHandle(&holder)) {
|
| + receiver = jsgraph()->Constant(holder);
|
| + }
|
| + Node* storage = receiver;
|
| + if (!field_index.is_inobject()) {
|
| + storage = effect = graph()->NewNode(
|
| + simplified()->LoadField(AccessBuilder::ForJSObjectProperties()),
|
| + storage, effect, control);
|
| + }
|
| + FieldAccess field_access = {
|
| + kTaggedBase, field_index.offset(), name,
|
| + field_type, MachineType::AnyTagged(), kFullWriteBarrier};
|
| + if (access_mode == AccessMode::kLoad) {
|
| + if (field_type->Is(Type::UntaggedFloat64())) {
|
| + if (!field_index.is_inobject() || field_index.is_hidden_field() ||
|
| + !FLAG_unbox_double_fields) {
|
| + storage = effect = graph()->NewNode(
|
| + simplified()->LoadField(field_access), storage, effect, control);
|
| + field_access.offset = HeapNumber::kValueOffset;
|
| + field_access.name = MaybeHandle<Name>();
|
| + }
|
| + field_access.machine_type = MachineType::Float64();
|
| + }
|
| + value = effect = graph()->NewNode(simplified()->LoadField(field_access),
|
| + storage, effect, control);
|
| + } else {
|
| + DCHECK_EQ(AccessMode::kStore, access_mode);
|
| + if (field_type->Is(Type::UntaggedFloat64())) {
|
| + value = effect = graph()->NewNode(simplified()->CheckNumber(), value,
|
| + effect, control);
|
| +
|
| + if (!field_index.is_inobject() || field_index.is_hidden_field() ||
|
| + !FLAG_unbox_double_fields) {
|
| + if (access_info.HasTransitionMap()) {
|
| + // Allocate a MutableHeapNumber for the new property.
|
| + effect = graph()->NewNode(
|
| + common()->BeginRegion(RegionObservability::kNotObservable),
|
| + effect);
|
| + Node* box = effect = graph()->NewNode(
|
| + simplified()->Allocate(NOT_TENURED),
|
| + jsgraph()->Constant(HeapNumber::kSize), effect, control);
|
| + effect = graph()->NewNode(
|
| + simplified()->StoreField(AccessBuilder::ForMap()), box,
|
| + jsgraph()->HeapConstant(factory()->mutable_heap_number_map()),
|
| + effect, control);
|
| + effect = graph()->NewNode(
|
| + simplified()->StoreField(AccessBuilder::ForHeapNumberValue()),
|
| + box, value, effect, control);
|
| + value = effect =
|
| + graph()->NewNode(common()->FinishRegion(), box, effect);
|
| +
|
| + field_access.type = Type::TaggedPointer();
|
| + } else {
|
| + // We just store directly to the MutableHeapNumber.
|
| + storage = effect =
|
| + graph()->NewNode(simplified()->LoadField(field_access), storage,
|
| + effect, control);
|
| + field_access.offset = HeapNumber::kValueOffset;
|
| + field_access.name = MaybeHandle<Name>();
|
| + field_access.machine_type = MachineType::Float64();
|
| + }
|
| + } else {
|
| + // Unboxed double field, we store directly to the field.
|
| + field_access.machine_type = MachineType::Float64();
|
| + }
|
| + } else if (field_type->Is(Type::TaggedSigned())) {
|
| + value = effect = graph()->NewNode(simplified()->CheckTaggedSigned(),
|
| + value, effect, control);
|
| + } else if (field_type->Is(Type::TaggedPointer())) {
|
| + value = effect = graph()->NewNode(simplified()->CheckTaggedPointer(),
|
| + value, effect, control);
|
| + if (field_type->NumClasses() == 1) {
|
| + // Emit a map check for the value.
|
| + Node* value_map = effect =
|
| + graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
| + value, effect, control);
|
| + Node* check = graph()->NewNode(
|
| + simplified()->ReferenceEqual(Type::Internal()), value_map,
|
| + jsgraph()->Constant(field_type->Classes().Current()));
|
| + effect =
|
| + graph()->NewNode(simplified()->CheckIf(), check, effect, control);
|
| + } else {
|
| + DCHECK_EQ(0, field_type->NumClasses());
|
| + }
|
| + } else {
|
| + DCHECK(field_type->Is(Type::Tagged()));
|
| + }
|
| + Handle<Map> transition_map;
|
| + if (access_info.transition_map().ToHandle(&transition_map)) {
|
| + effect = graph()->NewNode(
|
| + common()->BeginRegion(RegionObservability::kObservable), effect);
|
| + effect = graph()->NewNode(
|
| + simplified()->StoreField(AccessBuilder::ForMap()), receiver,
|
| + jsgraph()->Constant(transition_map), effect, control);
|
| + }
|
| + effect = graph()->NewNode(simplified()->StoreField(field_access), storage,
|
| + value, effect, control);
|
| + if (access_info.HasTransitionMap()) {
|
| + effect = graph()->NewNode(common()->FinishRegion(),
|
| + jsgraph()->UndefinedConstant(), effect);
|
| + }
|
| + }
|
| + }
|
| +
|
| + return ValueEffectControl(value, effect, control);
|
| +}
|
| +
|
| +JSNativeContextSpecialization::ValueEffectControl
|
| +JSNativeContextSpecialization::BuildElementAccess(
|
| + Node* receiver, Node* index, Node* value, Node* effect, Node* control,
|
| + Handle<Context> native_context, ElementAccessInfo const& access_info,
|
| + AccessMode access_mode) {
|
| + // Determine actual holder and perform prototype chain checks.
|
| + Handle<JSObject> holder;
|
| + if (access_info.holder().ToHandle(&holder)) {
|
| + AssumePrototypesStable(access_info.receiver_maps(), native_context, holder);
|
| + }
|
| +
|
| + // 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();
|
| + MapList const& receiver_maps = access_info.receiver_maps();
|
| +
|
| + // Load the elements for the {receiver}.
|
| + Node* elements = effect = graph()->NewNode(
|
| + simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
|
| + effect, control);
|
| +
|
| + // Don't try to store to a copy-on-write backing store.
|
| + if (access_mode == AccessMode::kStore &&
|
| + IsFastSmiOrObjectElementsKind(elements_kind)) {
|
| + Node* elements_map = effect =
|
| + graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
| + elements, effect, control);
|
| + Node* check = graph()->NewNode(
|
| + simplified()->ReferenceEqual(Type::Any()), elements_map,
|
| + jsgraph()->HeapConstant(factory()->fixed_array_map()));
|
| + effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
|
| + }
|
| +
|
| + // Load the length of the {receiver}.
|
| + Node* length = effect =
|
| + HasOnlyJSArrayMaps(receiver_maps)
|
| + ? graph()->NewNode(
|
| + simplified()->LoadField(
|
| + AccessBuilder::ForJSArrayLength(elements_kind)),
|
| + receiver, effect, control)
|
| + : graph()->NewNode(
|
| + simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
|
| + elements, effect, control);
|
| +
|
| + // Check that the {index} is in the valid range for the {receiver}.
|
| + index = effect = graph()->NewNode(simplified()->CheckBounds(), index, length,
|
| + effect, control);
|
| +
|
| + // Compute the element access.
|
| + Type* element_type = Type::Any();
|
| + MachineType element_machine_type = MachineType::AnyTagged();
|
| + if (IsFastDoubleElementsKind(elements_kind)) {
|
| + element_type = Type::Number();
|
| + element_machine_type = MachineType::Float64();
|
| + } else if (IsFastSmiElementsKind(elements_kind)) {
|
| + element_type = type_cache_.kSmi;
|
| + }
|
| + ElementAccess element_access = {kTaggedBase, FixedArray::kHeaderSize,
|
| + element_type, element_machine_type,
|
| + kFullWriteBarrier};
|
| +
|
| + // Access the actual element.
|
| + // TODO(bmeurer): Refactor this into separate methods or even a separate
|
| + // class that deals with the elements access.
|
| + if (access_mode == AccessMode::kLoad) {
|
| + // Compute the real element access type, which includes the hole in case
|
| + // of holey backing stores.
|
| + if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
| + elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
| + element_access.type = Type::Union(
|
| + element_type,
|
| + Type::Constant(factory()->the_hole_value(), graph()->zone()),
|
| + graph()->zone());
|
| + }
|
| + // Perform the actual backing store access.
|
| + value = effect = graph()->NewNode(simplified()->LoadElement(element_access),
|
| + elements, index, effect, control);
|
| + // Handle loading from holey backing stores correctly, by either mapping
|
| + // the hole to undefined if possible, or deoptimizing otherwise.
|
| + if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
| + elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
| + // Perform the hole check on the result.
|
| + CheckTaggedHoleMode mode = CheckTaggedHoleMode::kNeverReturnHole;
|
| + // Check if we are allowed to turn the hole into undefined.
|
| + // TODO(bmeurer): We might check the JSArray map from a different
|
| + // context here; may need reinvestigation.
|
| + if (receiver_maps.size() == 1 &&
|
| + receiver_maps[0].is_identical_to(
|
| + handle(isolate()->get_initial_js_array_map(elements_kind))) &&
|
| + isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
| + // Add a code dependency on the array protector cell.
|
| + dependencies()->AssumePrototypeMapsStable(
|
| + receiver_maps[0], isolate()->initial_object_prototype());
|
| + dependencies()->AssumePropertyCell(factory()->array_protector());
|
| + // Turn the hole into undefined.
|
| + mode = CheckTaggedHoleMode::kConvertHoleToUndefined;
|
| + }
|
| + value = effect = graph()->NewNode(simplified()->CheckTaggedHole(mode),
|
| + value, effect, control);
|
| + } else if (elements_kind == FAST_HOLEY_DOUBLE_ELEMENTS) {
|
| + // Perform the hole check on the result.
|
| + CheckFloat64HoleMode mode = CheckFloat64HoleMode::kNeverReturnHole;
|
| + // Check if we are allowed to return the hole directly.
|
| + // TODO(bmeurer): We might check the JSArray map from a different
|
| + // context here; may need reinvestigation.
|
| + if (receiver_maps.size() == 1 &&
|
| + receiver_maps[0].is_identical_to(
|
| + handle(isolate()->get_initial_js_array_map(elements_kind))) &&
|
| + isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
| + // Add a code dependency on the array protector cell.
|
| + dependencies()->AssumePrototypeMapsStable(
|
| + receiver_maps[0], isolate()->initial_object_prototype());
|
| + dependencies()->AssumePropertyCell(factory()->array_protector());
|
| + // Return the signaling NaN hole directly if all uses are truncating.
|
| + mode = CheckFloat64HoleMode::kAllowReturnHole;
|
| + }
|
| + value = effect = graph()->NewNode(simplified()->CheckFloat64Hole(mode),
|
| + value, effect, control);
|
| + }
|
| + } else {
|
| + DCHECK_EQ(AccessMode::kStore, access_mode);
|
| + if (IsFastSmiElementsKind(elements_kind)) {
|
| + value = effect = graph()->NewNode(simplified()->CheckTaggedSigned(),
|
| + value, effect, control);
|
| + } else if (IsFastDoubleElementsKind(elements_kind)) {
|
| + value = effect =
|
| + graph()->NewNode(simplified()->CheckNumber(), value, effect, control);
|
| + // Make sure we do not store signalling NaNs into double arrays.
|
| + value = graph()->NewNode(simplified()->NumberSilenceNaN(), value);
|
| + }
|
| + effect = graph()->NewNode(simplified()->StoreElement(element_access),
|
| + elements, index, value, effect, control);
|
| + }
|
| +
|
| + return ValueEffectControl(value, effect, control);
|
| +}
|
|
|
| void JSNativeContextSpecialization::AssumePrototypesStable(
|
| - Type* receiver_type, Handle<Context> native_context,
|
| - Handle<JSObject> holder) {
|
| + std::vector<Handle<Map>> const& receiver_maps,
|
| + Handle<Context> native_context, Handle<JSObject> holder) {
|
| // Determine actual holder and perform prototype chain checks.
|
| - for (auto i = receiver_type->Classes(); !i.Done(); i.Advance()) {
|
| - Handle<Map> map = i.Current();
|
| + for (auto map : receiver_maps) {
|
| // Perform the implicit ToObject for primitives here.
|
| // Implemented according to ES6 section 7.3.2 GetV (V, P).
|
| Handle<JSFunction> constructor;
|
|
|