Commit 9d98af91 authored by Georg Neis's avatar Georg Neis Committed by Commit Bot

[turbofan] Move a special case out of ReduceElementAccess

...to make things easier to read.

R=jarin@chromium.org

Change-Id: I0e53ef67e34f696b5977d4e091c7bc7bdf0ec145
Reviewed-on: https://chromium-review.googlesource.com/c/1477739Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59706}
parent f23712f9
...@@ -1473,6 +1473,32 @@ Reduction JSNativeContextSpecialization::ReduceJSStoreNamedOwn(Node* node) { ...@@ -1473,6 +1473,32 @@ Reduction JSNativeContextSpecialization::ReduceJSStoreNamedOwn(Node* node) {
AccessMode::kStoreInLiteral); AccessMode::kStoreInLiteral);
} }
Reduction JSNativeContextSpecialization::ReduceElementAccessOnString(
Node* node, Node* index, Node* value, AccessMode access_mode,
KeyedAccessLoadMode load_mode) {
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Strings are immutable in JavaScript.
if (access_mode == AccessMode::kStore) return NoChange();
// Ensure that the {receiver} is actually a String.
receiver = effect = graph()->NewNode(
simplified()->CheckString(VectorSlotPair()), receiver, effect, control);
// Determine the {receiver} length.
Node* length = graph()->NewNode(simplified()->StringLength(), receiver);
// Load the single character string from {receiver} or yield undefined
// if the {index} is out of bounds (depending on the {load_mode}).
value = BuildIndexedStringLoad(receiver, index, length, &effect, &control,
load_mode);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceElementAccess( Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* node, Node* index, Node* value, MapHandles const& receiver_maps, Node* node, Node* index, Node* value, MapHandles const& receiver_maps,
AccessMode access_mode, KeyedAccessLoadMode load_mode, AccessMode access_mode, KeyedAccessLoadMode load_mode,
...@@ -1485,207 +1511,191 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess( ...@@ -1485,207 +1511,191 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* control = NodeProperties::GetControlInput(node); Node* control = NodeProperties::GetControlInput(node);
Node* frame_state = NodeProperties::FindFrameStateBefore(node); Node* frame_state = NodeProperties::FindFrameStateBefore(node);
// Check for keyed access to strings.
if (HasOnlyStringMaps(receiver_maps)) { if (HasOnlyStringMaps(receiver_maps)) {
// Strings are immutable in JavaScript. return ReduceElementAccessOnString(node, index, value, access_mode,
if (access_mode == AccessMode::kStore) return NoChange(); load_mode);
}
// Ensure that the {receiver} is actually a String. // Compute element access infos for the receiver maps.
receiver = effect = graph()->NewNode( AccessInfoFactory access_info_factory(
simplified()->CheckString(VectorSlotPair()), receiver, effect, control); broker(), dependencies(), native_context().object(), graph()->zone());
ZoneVector<ElementAccessInfo> access_infos(zone());
if (!access_info_factory.ComputeElementAccessInfos(receiver_maps, access_mode,
&access_infos)) {
return NoChange();
}
// Determine the {receiver} length. // Nothing to do if we have no non-deprecated maps.
Node* length = graph()->NewNode(simplified()->StringLength(), receiver); if (access_infos.empty()) {
return ReduceSoftDeoptimize(
node, DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess);
}
// Load the single character string from {receiver} or yield undefined // For holey stores or growing stores, we need to check that the prototype
// if the {index} is out of bounds (depending on the {load_mode}). // chain contains no setters for elements, and we need to guard those checks
value = BuildIndexedStringLoad(receiver, index, length, &effect, &control, // via code dependencies on the relevant prototype maps.
load_mode); if (access_mode == AccessMode::kStore) {
} else { // TODO(turbofan): We could have a fast path here, that checks for the
// Compute element access infos for the receiver maps. // common case of Array or Object prototype only and therefore avoids
AccessInfoFactory access_info_factory( // the zone allocation of this vector.
broker(), dependencies(), native_context().object(), graph()->zone()); ZoneVector<Handle<Map>> prototype_maps(zone());
ZoneVector<ElementAccessInfo> access_infos(zone()); for (ElementAccessInfo const& access_info : access_infos) {
if (!access_info_factory.ComputeElementAccessInfos( for (Handle<Map> receiver_map : access_info.receiver_maps()) {
receiver_maps, access_mode, &access_infos)) { // If the {receiver_map} has a prototype and its elements backing
return NoChange(); // store is either holey, or we have a potentially growing store,
// then we need to check that all prototypes have stable maps with
// fast elements (and we need to guard against changes to that below).
if (IsHoleyOrDictionaryElementsKind(receiver_map->elements_kind()) ||
IsGrowStoreMode(store_mode)) {
// Make sure all prototypes are stable and have fast elements.
for (Handle<Map> map = receiver_map;;) {
Handle<Object> map_prototype(map->prototype(), isolate());
if (map_prototype->IsNull(isolate())) break;
if (!map_prototype->IsJSObject()) return NoChange();
map =
handle(Handle<JSObject>::cast(map_prototype)->map(), isolate());
if (!map->is_stable()) return NoChange();
if (!IsFastElementsKind(map->elements_kind())) return NoChange();
prototype_maps.push_back(map);
}
}
}
} }
// Nothing to do if we have no non-deprecated maps. // Install dependencies on the relevant prototype maps.
if (access_infos.empty()) { for (Handle<Map> prototype_map : prototype_maps) {
return ReduceSoftDeoptimize( dependencies()->DependOnStableMap(MapRef(broker(), prototype_map));
node,
DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess);
} }
}
// For holey stores or growing stores, we need to check that the prototype // Ensure that {receiver} is a heap object.
// chain contains no setters for elements, and we need to guard those checks PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies());
// via code dependencies on the relevant prototype maps. receiver = access_builder.BuildCheckHeapObject(receiver, &effect, control);
if (access_mode == AccessMode::kStore) {
// TODO(turbofan): We could have a fast path here, that checks for the
// common case of Array or Object prototype only and therefore avoids
// the zone allocation of this vector.
ZoneVector<Handle<Map>> prototype_maps(zone());
for (ElementAccessInfo const& access_info : access_infos) {
for (Handle<Map> receiver_map : access_info.receiver_maps()) {
// If the {receiver_map} has a prototype and its elements backing
// store is either holey, or we have a potentially growing store,
// then we need to check that all prototypes have stable maps with
// fast elements (and we need to guard against changes to that below).
if (IsHoleyOrDictionaryElementsKind(receiver_map->elements_kind()) ||
IsGrowStoreMode(store_mode)) {
// Make sure all prototypes are stable and have fast elements.
for (Handle<Map> map = receiver_map;;) {
Handle<Object> map_prototype(map->prototype(), isolate());
if (map_prototype->IsNull(isolate())) break;
if (!map_prototype->IsJSObject()) return NoChange();
map = handle(Handle<JSObject>::cast(map_prototype)->map(),
isolate());
if (!map->is_stable()) return NoChange();
if (!IsFastElementsKind(map->elements_kind())) return NoChange();
prototype_maps.push_back(map);
}
}
}
}
// Install dependencies on the relevant prototype maps. // Check for the monomorphic case.
for (Handle<Map> prototype_map : prototype_maps) { if (access_infos.size() == 1) {
dependencies()->DependOnStableMap(MapRef(broker(), prototype_map)); ElementAccessInfo access_info = access_infos.front();
}
// Perform possible elements kind transitions.
Handle<Map> const transition_target = access_info.receiver_maps().front();
for (auto transition_source : access_info.transition_sources()) {
DCHECK_EQ(access_info.receiver_maps().size(), 1);
effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition(
IsSimpleMapChangeTransition(transition_source->elements_kind(),
transition_target->elements_kind())
? ElementsTransition::kFastTransition
: ElementsTransition::kSlowTransition,
transition_source, transition_target)),
receiver, effect, control);
} }
// Ensure that {receiver} is a heap object. // TODO(turbofan): The effect/control linearization will not find a
PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies()); // FrameState after the StoreField or Call that is generated for the
receiver = access_builder.BuildCheckHeapObject(receiver, &effect, control); // elements kind transition above. This is because those operators
// don't have the kNoWrite flag on it, even though they are not
// observable by JavaScript.
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
// Perform map check on the {receiver}.
access_builder.BuildCheckMaps(receiver, &effect, control,
access_info.receiver_maps());
// Check for the monomorphic case. // Access the actual element.
if (access_infos.size() == 1) { ValueEffectControl continuation =
ElementAccessInfo access_info = access_infos.front(); BuildElementAccess(receiver, index, value, effect, control, access_info,
access_mode, load_mode, store_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());
// Generate code for the various different element access patterns.
Node* fallthrough_control = control;
for (size_t j = 0; j < access_infos.size(); ++j) {
ElementAccessInfo const& access_info = access_infos[j];
Node* this_receiver = receiver;
Node* this_value = value;
Node* this_index = index;
Node* this_effect = effect;
Node* this_control = fallthrough_control;
// Perform possible elements kind transitions. // Perform possible elements kind transitions.
Handle<Map> const transition_target = access_info.receiver_maps().front(); Handle<Map> const transition_target = access_info.receiver_maps().front();
for (auto transition_source : access_info.transition_sources()) { for (auto transition_source : access_info.transition_sources()) {
DCHECK_EQ(access_info.receiver_maps().size(), 1); DCHECK_EQ(access_info.receiver_maps().size(), 1);
effect = graph()->NewNode( this_effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition( simplified()->TransitionElementsKind(ElementsTransition(
IsSimpleMapChangeTransition(transition_source->elements_kind(), IsSimpleMapChangeTransition(transition_source->elements_kind(),
transition_target->elements_kind()) transition_target->elements_kind())
? ElementsTransition::kFastTransition ? ElementsTransition::kFastTransition
: ElementsTransition::kSlowTransition, : ElementsTransition::kSlowTransition,
transition_source, transition_target)), transition_source, transition_target)),
receiver, effect, control); receiver, this_effect, this_control);
} }
// TODO(turbofan): The effect/control linearization will not find a // Perform map check(s) on {receiver}.
// FrameState after the StoreField or Call that is generated for the MapHandles const& receiver_maps = access_info.receiver_maps();
// elements kind transition above. This is because those operators if (j == access_infos.size() - 1) {
// don't have the kNoWrite flag on it, even though they are not // Last map check on the fallthrough control path, do a
// observable by JavaScript. // conditional eager deoptimization exit here.
effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, access_builder.BuildCheckMaps(receiver, &this_effect, this_control,
control); receiver_maps);
fallthrough_control = nullptr;
// Perform map check on the {receiver}. } else {
access_builder.BuildCheckMaps(receiver, &effect, control, // Explicitly branch on the {receiver_maps}.
access_info.receiver_maps()); ZoneHandleSet<Map> maps;
for (Handle<Map> map : receiver_maps) {
// Access the actual element. maps.insert(map, graph()->zone());
ValueEffectControl continuation =
BuildElementAccess(receiver, index, value, effect, control,
access_info, access_mode, load_mode, store_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());
// Generate code for the various different element access patterns.
Node* fallthrough_control = control;
for (size_t j = 0; j < access_infos.size(); ++j) {
ElementAccessInfo const& access_info = access_infos[j];
Node* this_receiver = receiver;
Node* this_value = value;
Node* this_index = index;
Node* this_effect = effect;
Node* this_control = fallthrough_control;
// Perform possible elements kind transitions.
Handle<Map> const transition_target =
access_info.receiver_maps().front();
for (auto transition_source : access_info.transition_sources()) {
DCHECK_EQ(access_info.receiver_maps().size(), 1);
this_effect = graph()->NewNode(
simplified()->TransitionElementsKind(
ElementsTransition(IsSimpleMapChangeTransition(
transition_source->elements_kind(),
transition_target->elements_kind())
? ElementsTransition::kFastTransition
: ElementsTransition::kSlowTransition,
transition_source, transition_target)),
receiver, this_effect, this_control);
}
// Perform map check(s) on {receiver}.
MapHandles const& receiver_maps = access_info.receiver_maps();
if (j == access_infos.size() - 1) {
// Last map check on the fallthrough control path, do a
// conditional eager deoptimization exit here.
access_builder.BuildCheckMaps(receiver, &this_effect, this_control,
receiver_maps);
fallthrough_control = nullptr;
} else {
// Explicitly branch on the {receiver_maps}.
ZoneHandleSet<Map> maps;
for (Handle<Map> map : receiver_maps) {
maps.insert(map, graph()->zone());
}
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps), receiver,
this_effect, fallthrough_control);
Node* branch =
graph()->NewNode(common()->Branch(), check, fallthrough_control);
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
this_control = graph()->NewNode(common()->IfTrue(), branch);
// Introduce a MapGuard to learn from this on the effect chain.
this_effect = graph()->NewNode(simplified()->MapGuard(maps), receiver,
this_effect, this_control);
} }
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps), receiver,
this_effect, fallthrough_control);
Node* branch =
graph()->NewNode(common()->Branch(), check, fallthrough_control);
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
this_control = graph()->NewNode(common()->IfTrue(), branch);
// Access the actual element. // Introduce a MapGuard to learn from this on the effect chain.
ValueEffectControl continuation = BuildElementAccess( this_effect = graph()->NewNode(simplified()->MapGuard(maps), receiver,
this_receiver, this_index, this_value, this_effect, this_control, this_effect, this_control);
access_info, access_mode, load_mode, store_mode);
values.push_back(continuation.value());
effects.push_back(continuation.effect());
controls.push_back(continuation.control());
} }
DCHECK_NULL(fallthrough_control); // Access the actual element.
ValueEffectControl continuation = BuildElementAccess(
this_receiver, this_index, this_value, this_effect, this_control,
access_info, access_mode, load_mode, store_mode);
values.push_back(continuation.value());
effects.push_back(continuation.effect());
controls.push_back(continuation.control());
}
// Generate the final merge point for all (polymorphic) branches. DCHECK_NULL(fallthrough_control);
int const control_count = static_cast<int>(controls.size());
if (control_count == 0) { // Generate the final merge point for all (polymorphic) branches.
value = effect = control = jsgraph()->Dead(); int const control_count = static_cast<int>(controls.size());
} else if (control_count == 1) { if (control_count == 0) {
value = values.front(); value = effect = control = jsgraph()->Dead();
effect = effects.front(); } else if (control_count == 1) {
control = controls.front(); value = values.front();
} else { effect = effects.front();
control = graph()->NewNode(common()->Merge(control_count), control = controls.front();
control_count, &controls.front()); } else {
values.push_back(control); control = graph()->NewNode(common()->Merge(control_count), control_count,
value = graph()->NewNode( &controls.front());
common()->Phi(MachineRepresentation::kTagged, control_count), values.push_back(control);
control_count + 1, &values.front()); value = graph()->NewNode(
effects.push_back(control); common()->Phi(MachineRepresentation::kTagged, control_count),
effect = graph()->NewNode(common()->EffectPhi(control_count), control_count + 1, &values.front());
control_count + 1, &effects.front()); effects.push_back(control);
} effect = graph()->NewNode(common()->EffectPhi(control_count),
control_count + 1, &effects.front());
} }
} }
......
...@@ -118,6 +118,9 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final ...@@ -118,6 +118,9 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Reduction ReduceKeyedLoadFromHeapConstant(Node* node, Node* index, Reduction ReduceKeyedLoadFromHeapConstant(Node* node, Node* index,
FeedbackNexus const& nexus, FeedbackNexus const& nexus,
KeyedAccessLoadMode load_mode); KeyedAccessLoadMode load_mode);
Reduction ReduceElementAccessOnString(Node* node, Node* index, Node* value,
AccessMode access_mode,
KeyedAccessLoadMode load_mode);
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason); Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
Reduction ReduceJSToString(Node* node); Reduction ReduceJSToString(Node* node);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment