[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:
parent
3dbc04f72f
commit
8f1a92ce71
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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) \
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
@ -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());
|
||||||
|
Loading…
Reference in New Issue
Block a user