[turbofan] Introduce a dedicated CompareMaps operator.

Instead of introducing a lot of explicit branching in the
JSNativeContextSpecialization for polymorphic property accesses
that cannot be folded into a single LoadField/StoreField, and which
are mostly invisible and not optimizable for later passes, we now
have a single CompareMaps operator that takes a set of maps (like the
CheckMaps operator) and produces a boolean indicating the result of
the comparison.

R=jarin@chromium.org

Bug: v8:6761
Change-Id: Iee8788e915b762d542acb54feb9931346e442dc0
Reviewed-on: https://chromium-review.googlesource.com/636365
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47635}
This commit is contained in:
Benedikt Meurer 2017-08-28 09:27:27 +02:00 committed by Commit Bot
parent 3dbc04f72f
commit 8f1a92ce71
11 changed files with 95 additions and 39 deletions

View File

@ -629,6 +629,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kCheckMaps: case IrOpcode::kCheckMaps:
result = LowerCheckMaps(node, frame_state); result = LowerCheckMaps(node, frame_state);
break; break;
case IrOpcode::kCompareMaps:
result = LowerCompareMaps(node);
break;
case IrOpcode::kCheckMapValue: case IrOpcode::kCheckMapValue:
LowerCheckMapValue(node, frame_state); LowerCheckMapValue(node, frame_state);
break; break;
@ -1287,6 +1290,28 @@ Node* EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
return value; return value;
} }
Node* EffectControlLinearizer::LowerCompareMaps(Node* node) {
ZoneHandleSet<Map> const& maps = CompareMapsParametersOf(node->op());
size_t const map_count = maps.size();
Node* value = node->InputAt(0);
auto done = __ MakeLabelFor(GraphAssemblerLabelType::kNonDeferred,
map_count + 1, MachineRepresentation::kBit);
// Load the current map of the {value}.
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
for (size_t i = 0; i < map_count; ++i) {
Node* map = __ HeapConstant(maps[i]);
Node* check = __ WordEqual(value_map, map);
__ GotoIf(check, &done, __ Int32Constant(1));
}
__ Goto(&done, __ Int32Constant(0));
__ Bind(&done);
return done.PhiAt(0);
}
void EffectControlLinearizer::LowerCheckMapValue(Node* node, void EffectControlLinearizer::LowerCheckMapValue(Node* node,
Node* frame_state) { Node* frame_state) {
Node* value = node->InputAt(0); Node* value = node->InputAt(0);

View File

@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckBounds(Node* node, Node* frame_state); Node* LowerCheckBounds(Node* node, Node* frame_state);
Node* LowerCheckInternalizedString(Node* node, Node* frame_state); Node* LowerCheckInternalizedString(Node* node, Node* frame_state);
Node* LowerCheckMaps(Node* node, Node* frame_state); Node* LowerCheckMaps(Node* node, Node* frame_state);
Node* LowerCompareMaps(Node* node);
void LowerCheckMapValue(Node* node, Node* frame_state); void LowerCheckMapValue(Node* node, Node* frame_state);
Node* LowerCheckNumber(Node* node, Node* frame_state); Node* LowerCheckNumber(Node* node, Node* frame_state);
Node* LowerCheckReceiver(Node* node, Node* frame_state); Node* LowerCheckReceiver(Node* node, Node* frame_state);

View File

@ -765,12 +765,6 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
access_builder.BuildCheckHeapObject(receiver, &effect, control); access_builder.BuildCheckHeapObject(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. // Generate code for the various different property access patterns.
Node* fallthrough_control = control; Node* fallthrough_control = control;
for (size_t j = 0; j < access_infos.size(); ++j) { for (size_t j = 0; j < access_infos.size(); ++j) {
@ -787,16 +781,12 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
// effect to be able to learn from the control flow. // effect to be able to learn from the control flow.
bool insert_map_guard = true; bool insert_map_guard = true;
// Emit a (sequence of) map checks for other {receiver}s. // Check maps for the {receiver}s.
ZoneVector<Node*> this_controls(zone());
ZoneVector<Node*> this_effects(zone());
if (j == access_infos.size() - 1) { if (j == access_infos.size() - 1) {
// Last map check on the fallthrough control path, do a // Last map check on the fallthrough control path, do a
// conditional eager deoptimization exit here. // conditional eager deoptimization exit here.
access_builder.BuildCheckMaps(receiver, &this_effect, this_control, access_builder.BuildCheckMaps(receiver, &this_effect, this_control,
receiver_maps); receiver_maps);
this_effects.push_back(this_effect);
this_controls.push_back(fallthrough_control);
fallthrough_control = nullptr; fallthrough_control = nullptr;
// Don't insert a MapGuard in this case, as the CheckMaps // Don't insert a MapGuard in this case, as the CheckMaps
@ -804,17 +794,18 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
// along the effect chain. // along the effect chain.
insert_map_guard = false; insert_map_guard = false;
} else { } else {
for (auto map : receiver_maps) { // Explicitly branch on the {receiver_maps}.
Node* check = ZoneHandleSet<Map> maps;
graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, for (Handle<Map> map : receiver_maps) {
jsgraph()->Constant(map)); maps.insert(map, graph()->zone());
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);
} }
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps), receiver,
this_effect, this_control);
Node* branch =
graph()->NewNode(common()->Branch(), check, this_control);
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
this_control = graph()->NewNode(common()->IfTrue(), branch);
} }
// The Number case requires special treatment to also deal with Smis. // The Number case requires special treatment to also deal with Smis.
@ -822,8 +813,10 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
// Join this check with the "receiver is smi" check above. // Join this check with the "receiver is smi" check above.
DCHECK_NOT_NULL(receiverissmi_effect); DCHECK_NOT_NULL(receiverissmi_effect);
DCHECK_NOT_NULL(receiverissmi_control); DCHECK_NOT_NULL(receiverissmi_control);
this_effects.push_back(receiverissmi_effect); this_control = graph()->NewNode(common()->Merge(2), this_control,
this_controls.push_back(receiverissmi_control); receiverissmi_control);
this_effect = graph()->NewNode(common()->EffectPhi(2), this_effect,
receiverissmi_effect, this_control);
receiverissmi_effect = receiverissmi_control = nullptr; receiverissmi_effect = receiverissmi_control = nullptr;
// The {receiver} can also be a Smi in this case, so // The {receiver} can also be a Smi in this case, so
@ -831,21 +824,6 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
insert_map_guard = false; insert_map_guard = false;
} }
// 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());
}
// Introduce a MapGuard to learn from this on the effect chain. // Introduce a MapGuard to learn from this on the effect chain.
if (insert_map_guard) { if (insert_map_guard) {
ZoneHandleSet<Map> maps; ZoneHandleSet<Map> maps;

View File

@ -102,6 +102,8 @@ Reduction LoadElimination::Reduce(Node* node) {
return ReduceMapGuard(node); return ReduceMapGuard(node);
case IrOpcode::kCheckMaps: case IrOpcode::kCheckMaps:
return ReduceCheckMaps(node); return ReduceCheckMaps(node);
case IrOpcode::kCompareMaps:
return ReduceCompareMaps(node);
case IrOpcode::kEnsureWritableFastElements: case IrOpcode::kEnsureWritableFastElements:
return ReduceEnsureWritableFastElements(node); return ReduceEnsureWritableFastElements(node);
case IrOpcode::kMaybeGrowFastElements: case IrOpcode::kMaybeGrowFastElements:
@ -695,6 +697,24 @@ Reduction LoadElimination::ReduceCheckMaps(Node* node) {
return UpdateState(node, state); return UpdateState(node, state);
} }
Reduction LoadElimination::ReduceCompareMaps(Node* node) {
ZoneHandleSet<Map> const maps = CompareMapsParametersOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
ZoneHandleSet<Map> object_maps;
if (state->LookupMaps(object, &object_maps)) {
if (maps.contains(object_maps)) {
Node* value = jsgraph()->TrueConstant();
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// TODO(turbofan): Compute the intersection.
}
return UpdateState(node, state);
}
Reduction LoadElimination::ReduceEnsureWritableFastElements(Node* node) { Reduction LoadElimination::ReduceEnsureWritableFastElements(Node* node) {
Node* const object = NodeProperties::GetValueInput(node, 0); Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const elements = NodeProperties::GetValueInput(node, 1); Node* const elements = NodeProperties::GetValueInput(node, 1);

View File

@ -266,6 +266,7 @@ class V8_EXPORT_PRIVATE LoadElimination final
Reduction ReduceArrayBufferWasNeutered(Node* node); Reduction ReduceArrayBufferWasNeutered(Node* node);
Reduction ReduceCheckMaps(Node* node); Reduction ReduceCheckMaps(Node* node);
Reduction ReduceCompareMaps(Node* node);
Reduction ReduceMapGuard(Node* node); Reduction ReduceMapGuard(Node* node);
Reduction ReduceEnsureWritableFastElements(Node* node); Reduction ReduceEnsureWritableFastElements(Node* node);
Reduction ReduceMaybeGrowFastElements(Node* node); Reduction ReduceMaybeGrowFastElements(Node* node);

View File

@ -339,6 +339,7 @@
V(CheckHeapObject) \ V(CheckHeapObject) \
V(CheckFloat64Hole) \ V(CheckFloat64Hole) \
V(CheckNotTaggedHole) \ V(CheckNotTaggedHole) \
V(CompareMaps) \
V(ConvertTaggedHoleToUndefined) \ V(ConvertTaggedHoleToUndefined) \
V(Allocate) \ V(Allocate) \
V(LoadField) \ V(LoadField) \

View File

@ -2770,6 +2770,9 @@ class RepresentationSelector {
VisitInputs(node); VisitInputs(node);
return SetOutput(node, MachineRepresentation::kNone); return SetOutput(node, MachineRepresentation::kNone);
} }
case IrOpcode::kCompareMaps:
return VisitUnop(node, UseInfo::AnyTagged(),
MachineRepresentation::kBit);
case IrOpcode::kEnsureWritableFastElements: case IrOpcode::kEnsureWritableFastElements:
return VisitBinop(node, UseInfo::AnyTagged(), return VisitBinop(node, UseInfo::AnyTagged(),
MachineRepresentation::kTaggedPointer); MachineRepresentation::kTaggedPointer);

View File

@ -8,6 +8,8 @@
#include "src/compiler/opcodes.h" #include "src/compiler/opcodes.h"
#include "src/compiler/operator.h" #include "src/compiler/operator.h"
#include "src/compiler/types.h" #include "src/compiler/types.h"
#include "src/handles-inl.h"
#include "src/objects-inl.h"
#include "src/objects/map.h" #include "src/objects/map.h"
#include "src/objects/name.h" #include "src/objects/name.h"
@ -213,6 +215,11 @@ CheckMapsParameters const& CheckMapsParametersOf(Operator const* op) {
return OpParameter<CheckMapsParameters>(op); return OpParameter<CheckMapsParameters>(op);
} }
ZoneHandleSet<Map> const& CompareMapsParametersOf(Operator const* op) {
DCHECK_EQ(IrOpcode::kCompareMaps, op->opcode());
return OpParameter<ZoneHandleSet<Map>>(op);
}
size_t hash_value(CheckTaggedInputMode mode) { size_t hash_value(CheckTaggedInputMode mode) {
return static_cast<size_t>(mode); return static_cast<size_t>(mode);
} }
@ -860,6 +867,16 @@ const Operator* SimplifiedOperatorBuilder::CheckMaps(CheckMapsFlags flags,
parameters); // parameter parameters); // parameter
} }
const Operator* SimplifiedOperatorBuilder::CompareMaps(
ZoneHandleSet<Map> maps) {
return new (zone()) Operator1<ZoneHandleSet<Map>>( // --
IrOpcode::kCompareMaps, // opcode
Operator::kEliminatable, // flags
"CompareMaps", // name
1, 1, 1, 1, 1, 0, // counts
maps); // parameter
}
const Operator* SimplifiedOperatorBuilder::CheckFloat64Hole( const Operator* SimplifiedOperatorBuilder::CheckFloat64Hole(
CheckFloat64HoleMode mode) { CheckFloat64HoleMode mode) {
switch (mode) { switch (mode) {

View File

@ -157,6 +157,10 @@ std::ostream& operator<<(std::ostream&, CheckMapsParameters const&);
CheckMapsParameters const& CheckMapsParametersOf(Operator const*) CheckMapsParameters const& CheckMapsParametersOf(Operator const*)
WARN_UNUSED_RESULT; WARN_UNUSED_RESULT;
// Parameters for CompareMaps operator.
ZoneHandleSet<Map> const& CompareMapsParametersOf(Operator const*)
WARN_UNUSED_RESULT;
// A descriptor for growing elements backing stores. // A descriptor for growing elements backing stores.
enum class GrowFastElementsFlag : uint8_t { enum class GrowFastElementsFlag : uint8_t {
kNone = 0u, kNone = 0u,
@ -396,6 +400,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* CheckBounds(); const Operator* CheckBounds();
const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>); const Operator* CheckMaps(CheckMapsFlags, ZoneHandleSet<Map>);
const Operator* CheckMapValue(); const Operator* CheckMapValue();
const Operator* CompareMaps(ZoneHandleSet<Map>);
const Operator* CheckHeapObject(); const Operator* CheckHeapObject();
const Operator* CheckInternalizedString(); const Operator* CheckInternalizedString();

View File

@ -1862,9 +1862,10 @@ Type* Typer::Visitor::TypeCheckMaps(Node* node) {
UNREACHABLE(); UNREACHABLE();
} }
Type* Typer::Visitor::TypeCompareMaps(Node* node) { return Type::Boolean(); }
Type* Typer::Visitor::TypeCheckMapValue(Node* node) { Type* Typer::Visitor::TypeCheckMapValue(Node* node) {
UNREACHABLE(); UNREACHABLE();
return nullptr;
} }
Type* Typer::Visitor::TypeCheckNumber(Node* node) { Type* Typer::Visitor::TypeCheckNumber(Node* node) {

View File

@ -1190,6 +1190,10 @@ void Verifier::Visitor::Check(Node* node) {
CheckValueInputIs(node, 0, Type::Any()); CheckValueInputIs(node, 0, Type::Any());
CheckNotTyped(node); CheckNotTyped(node);
break; break;
case IrOpcode::kCompareMaps:
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::Boolean());
break;
case IrOpcode::kCheckMapValue: case IrOpcode::kCheckMapValue:
CheckValueInputIs(node, 0, Type::Any()); CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::Any()); CheckValueInputIs(node, 1, Type::Any());