// Copyright 2016 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/compiler/store-store-elimination.h" #include "src/compiler/all-nodes.h" #include "src/compiler/js-graph.h" #include "src/compiler/node-properties.h" #include "src/compiler/simplified-operator.h" namespace v8 { namespace internal { namespace compiler { #define TRACE(fmt, ...) \ do { \ if (FLAG_trace_store_elimination) { \ PrintF("StoreStoreElimination::ReduceEligibleNode: " fmt "\n", \ ##__VA_ARGS__); \ } \ } while (false) // A simple store-store elimination. When the effect chain contains the // following sequence, // // - StoreField[[+off_1]](x1, y1) // - StoreField[[+off_2]](x2, y2) // - StoreField[[+off_3]](x3, y3) // ... // - StoreField[[+off_n]](xn, yn) // // where the xes are the objects and the ys are the values to be stored, then // we are going to say that a store is superfluous if the same offset of the // same object will be stored to in the future. If off_i == off_j and xi == xj // and i < j, then we optimize the i'th StoreField away. // // This optimization should be initiated on the last StoreField in such a // sequence. // // The algorithm works by walking the effect chain from the last StoreField // upwards. While walking, we maintain a map {futureStore} from offsets to // nodes; initially it is empty. As we walk the effect chain upwards, if // futureStore[off] = n, then any store to node {n} with offset {off} is // guaranteed to be useless because we do a full-width[1] store to that offset // of that object in the near future anyway. For example, for this effect // chain // // 71: StoreField(60, 0) // 72: StoreField(65, 8) // 73: StoreField(63, 8) // 74: StoreField(65, 16) // 75: StoreField(62, 8) // // just before we get to 72, we will have futureStore = {8: 63, 16: 65}. // // Here is the complete process. // // - We are at the end of a sequence of consecutive StoreFields. // - We start out with futureStore = empty. // - We then walk the effect chain upwards to find the next StoreField [2]. // // 1. If the offset is not a key of {futureStore} yet, we put it in. // 2. If the offset is a key of {futureStore}, but futureStore[offset] is a // different node, we overwrite futureStore[offset] with the current node. // 3. If the offset is a key of {futureStore} and futureStore[offset] equals // this node, we eliminate this StoreField. // // As long as the current effect input points to a node with a single effect // output, and as long as its opcode is StoreField, we keep traversing // upwards. // // [1] This optimization is unsound if we optimize away a store to an offset // because we store to the same offset in the future, even though the future // store is narrower than the store we optimize away. Therefore, in case (1) // and (2) we only add/overwrite to the dictionary when the field access has // maximal size. For simplicity of implementation, we do not try to detect // case (3). // // [2] We make sure that we only traverse the linear part, that is, the part // where every node has exactly one incoming and one outgoing effect edge. // Also, we only keep walking upwards as long as we keep finding consecutive // StoreFields on the same node. StoreStoreElimination::StoreStoreElimination(JSGraph* js_graph, Zone* temp_zone) : jsgraph_(js_graph), temp_zone_(temp_zone) {} StoreStoreElimination::~StoreStoreElimination() {} void StoreStoreElimination::Run() { // The store-store elimination performs work on chains of certain types of // nodes. The elimination must be invoked on the lowest node in such a // chain; we have a helper function IsEligibleNode that returns true // precisely on the lowest node in such a chain. // // Because the elimination removes nodes from the graph, even remove nodes // that the elimination was not invoked on, we cannot use a normal // AdvancedReducer but we manually find which nodes to invoke the // elimination on. Then in a next step, we invoke the elimination for each // node that was eligible. NodeVector eligible(temp_zone()); // loops over all nodes AllNodes all(temp_zone(), jsgraph()->graph()); for (Node* node : all.live) { if (IsEligibleNode(node)) { eligible.push_back(node); } } for (Node* node : eligible) { ReduceEligibleNode(node); } } namespace { // 16 bits was chosen fairly arbitrarily; it seems enough now. 8 bits is too // few. typedef uint16_t Offset; // To safely cast an offset from a FieldAccess, which has a wider range // (namely int). Offset ToOffset(int offset) { CHECK(0 <= offset && offset < (1 << 8 * sizeof(Offset))); return (Offset)offset; } Offset ToOffset(const FieldAccess& access) { return ToOffset(access.offset); } // If node has a single effect use, return that node. If node has no or // multiple effect uses, return nullptr. Node* SingleEffectUse(Node* node) { Node* last_use = nullptr; for (Edge edge : node->use_edges()) { if (!NodeProperties::IsEffectEdge(edge)) { continue; } if (last_use != nullptr) { // more than one return nullptr; } last_use = edge.from(); DCHECK_NOT_NULL(last_use); } return last_use; } // Return true if node is the last consecutive StoreField node in a linear // part of the effect chain. bool IsEndOfStoreFieldChain(Node* node) { Node* next_on_chain = SingleEffectUse(node); return (next_on_chain == nullptr || next_on_chain->op()->opcode() != IrOpcode::kStoreField); } // The argument must be a StoreField node. If there is a node before it in the // effect chain, and if this part of the effect chain is linear (no other // effect uses of that previous node), then return that previous node. // Otherwise, return nullptr. // // The returned node need not be a StoreField. Node* PreviousEffectBeforeStoreField(Node* node) { DCHECK_EQ(node->op()->opcode(), IrOpcode::kStoreField); DCHECK_EQ(node->op()->EffectInputCount(), 1); Node* previous = NodeProperties::GetEffectInput(node); if (previous != nullptr && node == SingleEffectUse(previous)) { return previous; } else { return nullptr; } } size_t rep_size_of(MachineRepresentation rep) { return ((size_t)1) << ElementSizeLog2Of(rep); } size_t rep_size_of(FieldAccess access) { return rep_size_of(access.machine_type.representation()); } } // namespace bool StoreStoreElimination::IsEligibleNode(Node* node) { return (node->op()->opcode() == IrOpcode::kStoreField) && IsEndOfStoreFieldChain(node); } void StoreStoreElimination::ReduceEligibleNode(Node* node) { DCHECK(IsEligibleNode(node)); // if (FLAG_trace_store_elimination) { // PrintF("** StoreStoreElimination::ReduceEligibleNode: activated: // #%d\n", // node->id()); // } TRACE("activated: #%d", node->id()); // Initialize empty futureStore. ZoneMap<Offset, Node*> futureStore(temp_zone()); Node* current_node = node; do { FieldAccess access = OpParameter<FieldAccess>(current_node->op()); Offset offset = ToOffset(access); Node* object_input = current_node->InputAt(0); Node* previous = PreviousEffectBeforeStoreField(current_node); CHECK(rep_size_of(access) <= rep_size_of(MachineRepresentation::kTagged)); if (rep_size_of(access) == rep_size_of(MachineRepresentation::kTagged)) { // Try to insert. If it was present, this will preserve the original // value. auto insert_result = futureStore.insert(std::make_pair(offset, object_input)); if (insert_result.second) { // Key was not present. This means that there is no matching // StoreField to this offset in the future, so we cannot optimize // current_node away. However, we will record the current StoreField // in futureStore, and continue ascending up the chain. TRACE("#%d[[+%d]] -- wide, key not present", current_node->id(), offset); } else if (insert_result.first->second != object_input) { // Key was present, and the value did not equal object_input. This // means // that there is a StoreField to this offset in the future, but the // object instance comes from a different Node. We pessimistically // assume that we cannot optimize current_node away. However, we will // record the current StoreField in futureStore, and continue // ascending up the chain. insert_result.first->second = object_input; TRACE("#%d[[+%d]] -- wide, diff object", current_node->id(), offset); } else { // Key was present, and the value equalled object_input. This means // that soon after in the effect chain, we will do a StoreField to the // same object with the same offset, therefore current_node can be // optimized away. We don't need to update futureStore. Node* previous_effect = NodeProperties::GetEffectInput(current_node); NodeProperties::ReplaceUses(current_node, nullptr, previous_effect, nullptr, nullptr); current_node->Kill(); TRACE("#%d[[+%d]] -- wide, eliminated", current_node->id(), offset); } } else { TRACE("#%d[[+%d]] -- narrow, not eliminated", current_node->id(), offset); } // Regardless of whether we eliminated node {current}, we want to // continue walking up the effect chain. current_node = previous; } while (current_node != nullptr && current_node->op()->opcode() == IrOpcode::kStoreField); TRACE("finished"); } } // namespace compiler } // namespace internal } // namespace v8