[turbofan] Add support for keyed access to strings.
This introduces initial support to handle keyed load access to String primitives. This is accomplished via the existing operators StringCharCodeAt and StringFromCharCode, which we already use to optimize String.prototype.charCodeAt and String.fromCharCode. R=yangguo@chromium.org BUG=v8:5267 Review-Url: https://codereview.chromium.org/2232483002 Cr-Commit-Position: refs/heads/master@{#38512}
This commit is contained in:
parent
400f03ab97
commit
3909250a6c
@ -45,7 +45,8 @@ bool HasOnlyNumberMaps(MapList const& maps) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasOnlyStringMaps(MapList const& maps) {
|
template <typename T>
|
||||||
|
bool HasOnlyStringMaps(T const& maps) {
|
||||||
for (auto map : maps) {
|
for (auto map : maps) {
|
||||||
if (!map->IsStringMap()) return false;
|
if (!map->IsStringMap()) return false;
|
||||||
}
|
}
|
||||||
@ -426,184 +427,213 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
|||||||
// Not much we can do if deoptimization support is disabled.
|
// Not much we can do if deoptimization support is disabled.
|
||||||
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
|
if (!(flags() & kDeoptimizationEnabled)) return NoChange();
|
||||||
|
|
||||||
// Retrieve the native context from the given {node}.
|
// Check for keyed access to strings.
|
||||||
Handle<Context> native_context;
|
if (HasOnlyStringMaps(receiver_maps)) {
|
||||||
if (!GetNativeContext(node).ToHandle(&native_context)) return NoChange();
|
// Strings are immutable in JavaScript.
|
||||||
|
if (access_mode == AccessMode::kStore) return NoChange();
|
||||||
|
|
||||||
// Compute element access infos for the receiver maps.
|
// Ensure that the {receiver} is actually a String.
|
||||||
AccessInfoFactory access_info_factory(dependencies(), native_context,
|
receiver = effect = graph()->NewNode(simplified()->CheckString(), receiver,
|
||||||
graph()->zone());
|
effect, control);
|
||||||
ZoneVector<ElementAccessInfo> access_infos(zone());
|
|
||||||
if (!access_info_factory.ComputeElementAccessInfos(receiver_maps, access_mode,
|
|
||||||
&access_infos)) {
|
|
||||||
return NoChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing to do if we have no non-deprecated maps.
|
// Determine the {receiver} length.
|
||||||
if (access_infos.empty()) {
|
Node* length = effect = graph()->NewNode(
|
||||||
return ReduceSoftDeoptimize(
|
simplified()->LoadField(AccessBuilder::ForStringLength()), receiver,
|
||||||
node, DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess);
|
effect, control);
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that {receiver} is a heap object.
|
// Ensure that {index} is less than {receiver} length.
|
||||||
effect = BuildCheckTaggedPointer(receiver, effect, control);
|
index = effect = graph()->NewNode(simplified()->CheckBounds(), index,
|
||||||
|
length, effect, control);
|
||||||
|
|
||||||
// Check for the monomorphic case.
|
// Load the character from the {receiver}.
|
||||||
if (access_infos.size() == 1) {
|
value = graph()->NewNode(simplified()->StringCharCodeAt(), receiver, index,
|
||||||
ElementAccessInfo access_info = access_infos.front();
|
control);
|
||||||
|
|
||||||
// Perform possible elements kind transitions.
|
// Return it as a single character string.
|
||||||
for (auto transition : access_info.transitions()) {
|
value = graph()->NewNode(simplified()->StringFromCharCode(), value);
|
||||||
Handle<Map> const transition_source = transition.first;
|
} else {
|
||||||
Handle<Map> const transition_target = transition.second;
|
// Retrieve the native context from the given {node}.
|
||||||
effect = graph()->NewNode(
|
Handle<Context> native_context;
|
||||||
simplified()->TransitionElementsKind(
|
if (!GetNativeContext(node).ToHandle(&native_context)) return NoChange();
|
||||||
IsSimpleMapChangeTransition(transition_source->elements_kind(),
|
|
||||||
transition_target->elements_kind())
|
// Compute element access infos for the receiver maps.
|
||||||
? ElementsTransition::kFastTransition
|
AccessInfoFactory access_info_factory(dependencies(), native_context,
|
||||||
: ElementsTransition::kSlowTransition),
|
graph()->zone());
|
||||||
receiver, jsgraph()->HeapConstant(transition_source),
|
ZoneVector<ElementAccessInfo> access_infos(zone());
|
||||||
jsgraph()->HeapConstant(transition_target), effect, control);
|
if (!access_info_factory.ComputeElementAccessInfos(
|
||||||
|
receiver_maps, access_mode, &access_infos)) {
|
||||||
|
return NoChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(turbofan): The effect/control linearization will not find a
|
// Nothing to do if we have no non-deprecated maps.
|
||||||
// FrameState after the StoreField or Call that is generated for the
|
if (access_infos.empty()) {
|
||||||
// elements kind transition above. This is because those operators
|
return ReduceSoftDeoptimize(
|
||||||
// don't have the kNoWrite flag on it, even though they are not
|
node,
|
||||||
// observable by JavaScript.
|
DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess);
|
||||||
effect =
|
}
|
||||||
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
|
|
||||||
|
|
||||||
// Perform map check on the {receiver}.
|
// Ensure that {receiver} is a heap object.
|
||||||
effect =
|
effect = BuildCheckTaggedPointer(receiver, effect, control);
|
||||||
BuildCheckMaps(receiver, effect, control, access_info.receiver_maps());
|
|
||||||
|
|
||||||
// Access the actual element.
|
// Check for the monomorphic case.
|
||||||
ValueEffectControl continuation = BuildElementAccess(
|
if (access_infos.size() == 1) {
|
||||||
receiver, index, value, effect, control, native_context, access_info,
|
ElementAccessInfo access_info = access_infos.front();
|
||||||
access_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.
|
||||||
for (auto transition : access_info.transitions()) {
|
for (auto transition : access_info.transitions()) {
|
||||||
Handle<Map> const transition_source = transition.first;
|
Handle<Map> const transition_source = transition.first;
|
||||||
Handle<Map> const transition_target = transition.second;
|
Handle<Map> const transition_target = transition.second;
|
||||||
this_effect = graph()->NewNode(
|
effect = graph()->NewNode(
|
||||||
simplified()->TransitionElementsKind(
|
simplified()->TransitionElementsKind(
|
||||||
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),
|
||||||
receiver, jsgraph()->HeapConstant(transition_source),
|
receiver, jsgraph()->HeapConstant(transition_source),
|
||||||
jsgraph()->HeapConstant(transition_target), this_effect,
|
jsgraph()->HeapConstant(transition_target), effect, control);
|
||||||
this_control);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the {receiver} map.
|
// TODO(turbofan): The effect/control linearization will not find a
|
||||||
Node* receiver_map = this_effect =
|
// FrameState after the StoreField or Call that is generated for the
|
||||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
// elements kind transition above. This is because those operators
|
||||||
receiver, this_effect, this_control);
|
// 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(s) on {receiver}.
|
// Perform map check on the {receiver}.
|
||||||
MapList const& receiver_maps = access_info.receiver_maps();
|
effect = BuildCheckMaps(receiver, effect, control,
|
||||||
{
|
access_info.receiver_maps());
|
||||||
ZoneVector<Node*> this_controls(zone());
|
|
||||||
ZoneVector<Node*> this_effects(zone());
|
|
||||||
size_t num_classes = receiver_maps.size();
|
|
||||||
for (Handle<Map> map : receiver_maps) {
|
|
||||||
DCHECK_LT(0u, num_classes);
|
|
||||||
Node* check =
|
|
||||||
graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
|
|
||||||
jsgraph()->Constant(map));
|
|
||||||
if (--num_classes == 0 && j == access_infos.size() - 1) {
|
|
||||||
// Last map check on the fallthrough control path, do a conditional
|
|
||||||
// eager deoptimization exit here.
|
|
||||||
// TODO(turbofan): This is ugly as hell! We should probably
|
|
||||||
// introduce macro-ish operators for property access that
|
|
||||||
// encapsulate this whole mess.
|
|
||||||
check = graph()->NewNode(simplified()->CheckIf(), check,
|
|
||||||
this_effect, this_control);
|
|
||||||
this_controls.push_back(this_control);
|
|
||||||
this_effects.push_back(check);
|
|
||||||
fallthrough_control = nullptr;
|
|
||||||
} else {
|
|
||||||
Node* branch = graph()->NewNode(common()->Branch(), check,
|
|
||||||
fallthrough_control);
|
|
||||||
this_controls.push_back(
|
|
||||||
graph()->NewNode(common()->IfTrue(), branch));
|
|
||||||
this_effects.push_back(effect);
|
|
||||||
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create single chokepoint for the control.
|
|
||||||
int const this_control_count = static_cast<int>(this_controls.size());
|
|
||||||
if (this_control_count == 1) {
|
|
||||||
this_control = this_controls.front();
|
|
||||||
this_effect = this_effects.front();
|
|
||||||
} else {
|
|
||||||
this_control =
|
|
||||||
graph()->NewNode(common()->Merge(this_control_count),
|
|
||||||
this_control_count, &this_controls.front());
|
|
||||||
this_effects.push_back(this_control);
|
|
||||||
this_effect =
|
|
||||||
graph()->NewNode(common()->EffectPhi(this_control_count),
|
|
||||||
this_control_count + 1, &this_effects.front());
|
|
||||||
|
|
||||||
// TODO(turbofan): The effect/control linearization will not find a
|
|
||||||
// FrameState after the EffectPhi that is generated above.
|
|
||||||
this_effect = graph()->NewNode(common()->Checkpoint(), frame_state,
|
|
||||||
this_effect, this_control);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access the actual element.
|
// Access the actual element.
|
||||||
ValueEffectControl continuation = BuildElementAccess(
|
ValueEffectControl continuation = BuildElementAccess(
|
||||||
this_receiver, this_index, this_value, this_effect, this_control,
|
receiver, index, value, effect, control, native_context, access_info,
|
||||||
native_context, access_info, access_mode, store_mode);
|
access_mode, store_mode);
|
||||||
values.push_back(continuation.value());
|
value = continuation.value();
|
||||||
effects.push_back(continuation.effect());
|
effect = continuation.effect();
|
||||||
controls.push_back(continuation.control());
|
control = continuation.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 {
|
} else {
|
||||||
control = graph()->NewNode(common()->Merge(control_count), control_count,
|
// The final states for every polymorphic branch. We join them with
|
||||||
&controls.front());
|
// Merge+Phi+EffectPhi at the bottom.
|
||||||
values.push_back(control);
|
ZoneVector<Node*> values(zone());
|
||||||
value = graph()->NewNode(
|
ZoneVector<Node*> effects(zone());
|
||||||
common()->Phi(MachineRepresentation::kTagged, control_count),
|
ZoneVector<Node*> controls(zone());
|
||||||
control_count + 1, &values.front());
|
|
||||||
effects.push_back(control);
|
// Generate code for the various different element access patterns.
|
||||||
effect = graph()->NewNode(common()->EffectPhi(control_count),
|
Node* fallthrough_control = control;
|
||||||
control_count + 1, &effects.front());
|
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.
|
||||||
|
for (auto transition : access_info.transitions()) {
|
||||||
|
Handle<Map> const transition_source = transition.first;
|
||||||
|
Handle<Map> const transition_target = transition.second;
|
||||||
|
this_effect = graph()->NewNode(
|
||||||
|
simplified()->TransitionElementsKind(
|
||||||
|
IsSimpleMapChangeTransition(
|
||||||
|
transition_source->elements_kind(),
|
||||||
|
transition_target->elements_kind())
|
||||||
|
? ElementsTransition::kFastTransition
|
||||||
|
: ElementsTransition::kSlowTransition),
|
||||||
|
receiver, jsgraph()->HeapConstant(transition_source),
|
||||||
|
jsgraph()->HeapConstant(transition_target), this_effect,
|
||||||
|
this_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the {receiver} map.
|
||||||
|
Node* receiver_map = this_effect =
|
||||||
|
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
||||||
|
receiver, this_effect, this_control);
|
||||||
|
|
||||||
|
// Perform map check(s) on {receiver}.
|
||||||
|
MapList const& receiver_maps = access_info.receiver_maps();
|
||||||
|
{
|
||||||
|
ZoneVector<Node*> this_controls(zone());
|
||||||
|
ZoneVector<Node*> this_effects(zone());
|
||||||
|
size_t num_classes = receiver_maps.size();
|
||||||
|
for (Handle<Map> map : receiver_maps) {
|
||||||
|
DCHECK_LT(0u, num_classes);
|
||||||
|
Node* check =
|
||||||
|
graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
|
||||||
|
jsgraph()->Constant(map));
|
||||||
|
if (--num_classes == 0 && j == access_infos.size() - 1) {
|
||||||
|
// Last map check on the fallthrough control path, do a
|
||||||
|
// conditional eager deoptimization exit here.
|
||||||
|
// TODO(turbofan): This is ugly as hell! We should probably
|
||||||
|
// introduce macro-ish operators for property access that
|
||||||
|
// encapsulate this whole mess.
|
||||||
|
check = graph()->NewNode(simplified()->CheckIf(), check,
|
||||||
|
this_effect, this_control);
|
||||||
|
this_controls.push_back(this_control);
|
||||||
|
this_effects.push_back(check);
|
||||||
|
fallthrough_control = nullptr;
|
||||||
|
} else {
|
||||||
|
Node* branch = graph()->NewNode(common()->Branch(), check,
|
||||||
|
fallthrough_control);
|
||||||
|
this_controls.push_back(
|
||||||
|
graph()->NewNode(common()->IfTrue(), branch));
|
||||||
|
this_effects.push_back(effect);
|
||||||
|
fallthrough_control =
|
||||||
|
graph()->NewNode(common()->IfFalse(), branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create single chokepoint for the control.
|
||||||
|
int const this_control_count = static_cast<int>(this_controls.size());
|
||||||
|
if (this_control_count == 1) {
|
||||||
|
this_control = this_controls.front();
|
||||||
|
this_effect = this_effects.front();
|
||||||
|
} else {
|
||||||
|
this_control =
|
||||||
|
graph()->NewNode(common()->Merge(this_control_count),
|
||||||
|
this_control_count, &this_controls.front());
|
||||||
|
this_effects.push_back(this_control);
|
||||||
|
this_effect =
|
||||||
|
graph()->NewNode(common()->EffectPhi(this_control_count),
|
||||||
|
this_control_count + 1, &this_effects.front());
|
||||||
|
|
||||||
|
// TODO(turbofan): The effect/control linearization will not find a
|
||||||
|
// FrameState after the EffectPhi that is generated above.
|
||||||
|
this_effect = graph()->NewNode(common()->Checkpoint(), frame_state,
|
||||||
|
this_effect, this_control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access the actual element.
|
||||||
|
ValueEffectControl continuation = BuildElementAccess(
|
||||||
|
this_receiver, this_index, this_value, this_effect, this_control,
|
||||||
|
native_context, access_info, access_mode, store_mode);
|
||||||
|
values.push_back(continuation.value());
|
||||||
|
effects.push_back(continuation.effect());
|
||||||
|
controls.push_back(continuation.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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user