[turbofan] Introduce a TransitionElementsKind simplified operator.
Instead of wriring the elements kind transitions into the control flow early on, we do instead put this marker into the effect chain, so that the elements transitions are visible to the LoadElimination and can thus be optimized properly there. This CL itself doesn't add any of those optimizations, but just adds the foundations to make them possible later. R=jarin@chromium.org BUG=v8:4930,v8:5141 Review-Url: https://codereview.chromium.org/2164573003 Cr-Commit-Position: refs/heads/master@{#37869}
This commit is contained in:
parent
3442519d56
commit
908cd09f3a
@ -706,6 +706,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
|
||||
case IrOpcode::kPlainPrimitiveToFloat64:
|
||||
state = LowerPlainPrimitiveToFloat64(node, *effect, *control);
|
||||
break;
|
||||
case IrOpcode::kTransitionElementsKind:
|
||||
state = LowerTransitionElementsKind(node, *effect, *control);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2121,6 +2124,65 @@ EffectControlLinearizer::LowerPlainPrimitiveToFloat64(Node* node, Node* effect,
|
||||
return ValueEffectControl(value, effect, control);
|
||||
}
|
||||
|
||||
EffectControlLinearizer::ValueEffectControl
|
||||
EffectControlLinearizer::LowerTransitionElementsKind(Node* node, Node* effect,
|
||||
Node* control) {
|
||||
ElementsTransition const transition = ElementsTransitionOf(node->op());
|
||||
Node* object = node->InputAt(0);
|
||||
Node* source_map = node->InputAt(1);
|
||||
Node* target_map = node->InputAt(2);
|
||||
|
||||
// Load the current map of {object}.
|
||||
Node* object_map = effect =
|
||||
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), object,
|
||||
effect, control);
|
||||
|
||||
// Check if {object_map} is the same as {source_map}.
|
||||
Node* check =
|
||||
graph()->NewNode(machine()->WordEqual(), object_map, source_map);
|
||||
Node* branch =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
|
||||
|
||||
// Migrate the {object} from {source_map} to {target_map}.
|
||||
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
||||
Node* etrue = effect;
|
||||
{
|
||||
switch (transition) {
|
||||
case ElementsTransition::kFastTransition: {
|
||||
// In-place migration of {object}, just store the {target_map}.
|
||||
etrue =
|
||||
graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()),
|
||||
object, target_map, etrue, if_true);
|
||||
break;
|
||||
}
|
||||
case ElementsTransition::kSlowTransition: {
|
||||
// Instance migration, call out to the runtime for {object}.
|
||||
Operator::Properties properties =
|
||||
Operator::kNoDeopt | Operator::kNoThrow;
|
||||
Runtime::FunctionId id = Runtime::kTransitionElementsKind;
|
||||
CallDescriptor const* desc = Linkage::GetRuntimeCallDescriptor(
|
||||
graph()->zone(), id, 2, properties, CallDescriptor::kNoFlags);
|
||||
etrue = graph()->NewNode(
|
||||
common()->Call(desc), jsgraph()->CEntryStubConstant(1), object,
|
||||
target_map,
|
||||
jsgraph()->ExternalConstant(ExternalReference(id, isolate())),
|
||||
jsgraph()->Int32Constant(2), jsgraph()->NoContextConstant(), etrue,
|
||||
if_true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing to do if the {object} doesn't have the {source_map}.
|
||||
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
||||
Node* efalse = effect;
|
||||
|
||||
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
||||
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
||||
|
||||
return ValueEffectControl(nullptr, effect, control);
|
||||
}
|
||||
|
||||
Factory* EffectControlLinearizer::factory() const {
|
||||
return isolate()->factory();
|
||||
}
|
||||
|
@ -122,6 +122,8 @@ class EffectControlLinearizer {
|
||||
Node* control);
|
||||
ValueEffectControl LowerPlainPrimitiveToFloat64(Node* node, Node* effect,
|
||||
Node* control);
|
||||
ValueEffectControl LowerTransitionElementsKind(Node* node, Node* effect,
|
||||
Node* control);
|
||||
|
||||
ValueEffectControl AllocateHeapNumberWithValue(Node* node, Node* effect,
|
||||
Node* control);
|
||||
|
@ -488,7 +488,6 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
|
||||
node->opcode() == IrOpcode::kJSStoreProperty);
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
||||
Node* context = NodeProperties::GetContextInput(node);
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
Node* frame_state = NodeProperties::FindFrameStateBefore(node);
|
||||
@ -525,12 +524,6 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
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 element access patterns.
|
||||
Node* fallthrough_control = control;
|
||||
for (size_t j = 0; j < access_infos.size(); ++j) {
|
||||
@ -538,8 +531,28 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
Node* this_receiver = receiver;
|
||||
Node* this_value = value;
|
||||
Node* this_index = index;
|
||||
Node* this_effect;
|
||||
Node* this_control;
|
||||
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 on {receiver}.
|
||||
Type* receiver_type = access_info.receiver_type();
|
||||
@ -547,7 +560,6 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
{
|
||||
ZoneVector<Node*> this_controls(zone());
|
||||
ZoneVector<Node*> this_effects(zone());
|
||||
size_t num_transitions = access_info.transitions().size();
|
||||
int num_classes = access_info.receiver_type()->NumClasses();
|
||||
for (auto i = access_info.receiver_type()->Classes(); !i.Done();
|
||||
i.Advance()) {
|
||||
@ -556,16 +568,15 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
Node* check =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(Type::Any()),
|
||||
receiver_map, jsgraph()->Constant(map));
|
||||
if (--num_classes == 0 && num_transitions == 0 &&
|
||||
j == access_infos.size() - 1) {
|
||||
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, effect,
|
||||
fallthrough_control);
|
||||
this_controls.push_back(fallthrough_control);
|
||||
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 {
|
||||
@ -578,57 +589,6 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
if (!map->IsJSArrayMap()) receiver_is_jsarray = false;
|
||||
}
|
||||
|
||||
// Generate possible elements kind transitions.
|
||||
for (auto transition : access_info.transitions()) {
|
||||
DCHECK_LT(0u, num_transitions);
|
||||
Handle<Map> transition_source = transition.first;
|
||||
Handle<Map> transition_target = transition.second;
|
||||
Node* transition_control;
|
||||
Node* transition_effect = effect;
|
||||
|
||||
// Check if {receiver} has the specified {transition_source} map.
|
||||
Node* check = graph()->NewNode(
|
||||
simplified()->ReferenceEqual(Type::Any()), receiver_map,
|
||||
jsgraph()->HeapConstant(transition_source));
|
||||
if (--num_transitions == 0 && j == access_infos.size() - 1) {
|
||||
transition_effect =
|
||||
graph()->NewNode(simplified()->CheckIf(), check,
|
||||
transition_effect, fallthrough_control);
|
||||
transition_control = fallthrough_control;
|
||||
fallthrough_control = nullptr;
|
||||
} else {
|
||||
Node* branch =
|
||||
graph()->NewNode(common()->Branch(), check, fallthrough_control);
|
||||
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
|
||||
transition_control = graph()->NewNode(common()->IfTrue(), branch);
|
||||
}
|
||||
|
||||
// Migrate {receiver} from {transition_source} to {transition_target}.
|
||||
if (IsSimpleMapChangeTransition(transition_source->elements_kind(),
|
||||
transition_target->elements_kind())) {
|
||||
// In-place migration, just store the {transition_target} map.
|
||||
transition_effect = graph()->NewNode(
|
||||
simplified()->StoreField(AccessBuilder::ForMap()), receiver,
|
||||
jsgraph()->HeapConstant(transition_target), transition_effect,
|
||||
transition_control);
|
||||
} else {
|
||||
// Instance migration, let the stub deal with the {receiver}.
|
||||
TransitionElementsKindStub stub(isolate(),
|
||||
transition_source->elements_kind(),
|
||||
transition_target->elements_kind());
|
||||
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
|
||||
isolate(), graph()->zone(), stub.GetCallInterfaceDescriptor(), 0,
|
||||
CallDescriptor::kNeedsFrameState, node->op()->properties());
|
||||
transition_effect = graph()->NewNode(
|
||||
common()->Call(desc), jsgraph()->HeapConstant(stub.GetCode()),
|
||||
receiver, jsgraph()->HeapConstant(transition_target), context,
|
||||
frame_state, transition_effect, transition_control);
|
||||
}
|
||||
|
||||
this_controls.push_back(transition_control);
|
||||
this_effects.push_back(transition_effect);
|
||||
}
|
||||
|
||||
// Create single chokepoint for the control.
|
||||
int const this_control_count = static_cast<int>(this_controls.size());
|
||||
if (this_control_count == 1) {
|
||||
@ -642,15 +602,15 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
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 StoreField or Call that is generated for the
|
||||
// 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.
|
||||
this_effect = graph()->NewNode(common()->Checkpoint(), frame_state,
|
||||
this_effect, this_control);
|
||||
// TODO(turbofan): The effect/control linearization will not find a
|
||||
// FrameState after the StoreField or Call that is generated for the
|
||||
// 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.
|
||||
this_effect = graph()->NewNode(common()->Checkpoint(), frame_state,
|
||||
this_effect, this_control);
|
||||
}
|
||||
}
|
||||
|
||||
// Certain stores need a prototype chain check because shape changes
|
||||
|
@ -281,7 +281,8 @@
|
||||
V(ObjectIsReceiver) \
|
||||
V(ObjectIsSmi) \
|
||||
V(ObjectIsString) \
|
||||
V(ObjectIsUndetectable)
|
||||
V(ObjectIsUndetectable) \
|
||||
V(TransitionElementsKind)
|
||||
|
||||
#define SIMPLIFIED_OP_LIST(V) \
|
||||
SIMPLIFIED_CHANGE_OP_LIST(V) \
|
||||
|
@ -2131,6 +2131,10 @@ class RepresentationSelector {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kTransitionElementsKind: {
|
||||
VisitInputs(node);
|
||||
return SetOutput(node, MachineRepresentation::kNone);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Machine-level operators.
|
||||
|
@ -241,6 +241,26 @@ CheckTaggedHoleMode CheckTaggedHoleModeOf(const Operator* op) {
|
||||
return OpParameter<CheckTaggedHoleMode>(op);
|
||||
}
|
||||
|
||||
size_t hash_value(ElementsTransition transition) {
|
||||
return static_cast<uint8_t>(transition);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ElementsTransition transition) {
|
||||
switch (transition) {
|
||||
case ElementsTransition::kFastTransition:
|
||||
return os << "fast-transition";
|
||||
case ElementsTransition::kSlowTransition:
|
||||
return os << "slow-transition";
|
||||
}
|
||||
UNREACHABLE();
|
||||
return os;
|
||||
}
|
||||
|
||||
ElementsTransition ElementsTransitionOf(const Operator* op) {
|
||||
DCHECK_EQ(IrOpcode::kTransitionElementsKind, op->opcode());
|
||||
return OpParameter<ElementsTransition>(op);
|
||||
}
|
||||
|
||||
BinaryOperationHints::Hint BinaryOperationHintOf(const Operator* op) {
|
||||
DCHECK(op->opcode() == IrOpcode::kSpeculativeNumberAdd ||
|
||||
op->opcode() == IrOpcode::kSpeculativeNumberSubtract ||
|
||||
@ -516,6 +536,16 @@ const Operator* SimplifiedOperatorBuilder::ReferenceEqual(Type* type) {
|
||||
"ReferenceEqual", 2, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
const Operator* SimplifiedOperatorBuilder::TransitionElementsKind(
|
||||
ElementsTransition transition) {
|
||||
return new (zone()) Operator1<ElementsTransition>( // --
|
||||
IrOpcode::kTransitionElementsKind, // opcode
|
||||
Operator::kNoDeopt | Operator::kNoThrow, // flags
|
||||
"TransitionElementsKind", // name
|
||||
3, 1, 1, 0, 1, 0, // counts
|
||||
transition); // parameter
|
||||
}
|
||||
|
||||
const Operator* SimplifiedOperatorBuilder::Allocate(PretenureFlag pretenure) {
|
||||
switch (pretenure) {
|
||||
case NOT_TENURED:
|
||||
|
@ -140,7 +140,15 @@ std::ostream& operator<<(std::ostream&, CheckForMinusZeroMode);
|
||||
|
||||
CheckForMinusZeroMode CheckMinusZeroModeOf(const Operator*) WARN_UNUSED_RESULT;
|
||||
|
||||
Type* TypeOf(const Operator* op) WARN_UNUSED_RESULT;
|
||||
// A descriptor for elements kind transitions.
|
||||
enum class ElementsTransition : uint8_t {
|
||||
kFastTransition, // simple transition, just updating the map.
|
||||
kSlowTransition // full transition, round-trip to the runtime.
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, ElementsTransition);
|
||||
|
||||
ElementsTransition ElementsTransitionOf(const Operator* op) WARN_UNUSED_RESULT;
|
||||
|
||||
BinaryOperationHints::Hint BinaryOperationHintOf(const Operator* op);
|
||||
|
||||
@ -288,6 +296,9 @@ class SimplifiedOperatorBuilder final : public ZoneObject {
|
||||
const Operator* ObjectIsString();
|
||||
const Operator* ObjectIsUndetectable();
|
||||
|
||||
// transition-elements-kind object, from-map, to-map
|
||||
const Operator* TransitionElementsKind(ElementsTransition transition);
|
||||
|
||||
const Operator* Allocate(PretenureFlag pretenure = NOT_TENURED);
|
||||
|
||||
const Operator* LoadField(FieldAccess const&);
|
||||
|
@ -725,6 +725,11 @@ Type* Typer::Visitor::TypeLoopExitEffect(Node* node) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeTransitionElementsKind(Node* node) {
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeCheckpoint(Node* node) {
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
|
@ -830,6 +830,12 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
CheckValueInputIs(node, 0, Type::PlainNumber());
|
||||
CheckUpperIs(node, Type::TaggedPointer());
|
||||
break;
|
||||
case IrOpcode::kTransitionElementsKind:
|
||||
CheckValueInputIs(node, 0, Type::Any());
|
||||
CheckValueInputIs(node, 1, Type::Internal());
|
||||
CheckValueInputIs(node, 2, Type::Internal());
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
|
||||
case IrOpcode::kChangeTaggedSignedToInt32: {
|
||||
// Signed32 /\ Tagged -> Signed32 /\ UntaggedInt32
|
||||
|
@ -783,6 +783,45 @@ class ElementsAccessorBase : public ElementsAccessor {
|
||||
return new_elements;
|
||||
}
|
||||
|
||||
static void TransitionElementsKindImpl(Handle<JSObject> object,
|
||||
Handle<Map> to_map) {
|
||||
Handle<Map> from_map = handle(object->map());
|
||||
ElementsKind from_kind = from_map->elements_kind();
|
||||
ElementsKind to_kind = to_map->elements_kind();
|
||||
if (IsFastHoleyElementsKind(from_kind)) {
|
||||
to_kind = GetHoleyElementsKind(to_kind);
|
||||
}
|
||||
if (from_kind != to_kind) {
|
||||
// This method should never be called for any other case.
|
||||
DCHECK(IsFastElementsKind(from_kind));
|
||||
DCHECK(IsFastElementsKind(to_kind));
|
||||
DCHECK_NE(TERMINAL_FAST_ELEMENTS_KIND, from_kind);
|
||||
|
||||
Handle<FixedArrayBase> from_elements(object->elements());
|
||||
if (object->elements() == object->GetHeap()->empty_fixed_array() ||
|
||||
IsFastDoubleElementsKind(from_kind) ==
|
||||
IsFastDoubleElementsKind(to_kind)) {
|
||||
// No change is needed to the elements() buffer, the transition
|
||||
// only requires a map change.
|
||||
JSObject::MigrateToMap(object, to_map);
|
||||
} else {
|
||||
DCHECK((IsFastSmiElementsKind(from_kind) &&
|
||||
IsFastDoubleElementsKind(to_kind)) ||
|
||||
(IsFastDoubleElementsKind(from_kind) &&
|
||||
IsFastObjectElementsKind(to_kind)));
|
||||
uint32_t capacity = static_cast<uint32_t>(object->elements()->length());
|
||||
Handle<FixedArrayBase> elements = ConvertElementsWithCapacity(
|
||||
object, from_elements, from_kind, capacity);
|
||||
JSObject::SetMapAndElements(object, to_map, elements);
|
||||
}
|
||||
if (FLAG_trace_elements_transitions) {
|
||||
JSObject::PrintElementsTransition(stdout, object, from_kind,
|
||||
from_elements, to_kind,
|
||||
handle(object->elements()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GrowCapacityAndConvertImpl(Handle<JSObject> object,
|
||||
uint32_t capacity) {
|
||||
ElementsKind from_kind = object->GetElementsKind();
|
||||
@ -822,6 +861,10 @@ class ElementsAccessorBase : public ElementsAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionElementsKind(Handle<JSObject> object, Handle<Map> map) final {
|
||||
Subclass::TransitionElementsKindImpl(object, map);
|
||||
}
|
||||
|
||||
void GrowCapacityAndConvert(Handle<JSObject> object,
|
||||
uint32_t capacity) final {
|
||||
Subclass::GrowCapacityAndConvertImpl(object, capacity);
|
||||
@ -2255,6 +2298,11 @@ class SloppyArgumentsElementsAccessor
|
||||
}
|
||||
}
|
||||
|
||||
static void TransitionElementsKindImpl(Handle<JSObject> object,
|
||||
Handle<Map> map) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
static void GrowCapacityAndConvertImpl(Handle<JSObject> object,
|
||||
uint32_t capacity) {
|
||||
UNREACHABLE();
|
||||
|
@ -110,6 +110,8 @@ class ElementsAccessor {
|
||||
KeyAccumulator* accumulator,
|
||||
AddKeyConversion convert) = 0;
|
||||
|
||||
virtual void TransitionElementsKind(Handle<JSObject> object,
|
||||
Handle<Map> map) = 0;
|
||||
virtual void GrowCapacityAndConvert(Handle<JSObject> object,
|
||||
uint32_t capacity) = 0;
|
||||
|
||||
|
@ -86,11 +86,12 @@ RUNTIME_FUNCTION(Runtime_FixedArraySet) {
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 2);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Map, map, 1);
|
||||
JSObject::TransitionElementsKind(array, map->elements_kind());
|
||||
return *array;
|
||||
DCHECK_EQ(2, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSArray, object, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Map, to_map, 1);
|
||||
ElementsKind to_kind = to_map->elements_kind();
|
||||
ElementsAccessor::ForKind(to_kind)->TransitionElementsKind(object, to_map);
|
||||
return *object;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user