[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:
bmeurer 2016-07-19 06:38:12 -07:00 committed by Commit bot
parent 3442519d56
commit 908cd09f3a
12 changed files with 213 additions and 81 deletions

View File

@ -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();
}

View File

@ -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);

View File

@ -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

View File

@ -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) \

View File

@ -2131,6 +2131,10 @@ class RepresentationSelector {
}
return;
}
case IrOpcode::kTransitionElementsKind: {
VisitInputs(node);
return SetOutput(node, MachineRepresentation::kNone);
}
//------------------------------------------------------------------
// Machine-level operators.

View File

@ -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:

View File

@ -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&);

View File

@ -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;

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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;
}