// Copyright 2022 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. #ifndef V8_COMPILER_CONTROL_PATH_STATE_H_ #define V8_COMPILER_CONTROL_PATH_STATE_H_ #include "src/compiler/functional-list.h" #include "src/compiler/graph-reducer.h" #include "src/compiler/graph.h" #include "src/compiler/node-aux-data.h" #include "src/compiler/node-properties.h" #include "src/compiler/node.h" #include "src/compiler/persistent-map.h" #include "src/zone/zone.h" namespace v8 { namespace internal { namespace compiler { enum NodeUniqueness { kUniqueInstance, kMultipleInstances }; // Class for tracking information about path state. It is represented as a // linked list of {NodeState} blocks, each of which corresponds to a block of // code bewteen an IfTrue/IfFalse and a Merge. Each block is in turn represented // as a linked list of {NodeState}s. // If {node_uniqueness} is {kMultipleInstances}, different states can be // assigned to the same node. The most recent state always takes precedence. // States still belong to a block and will be removed if the block gets merged. template class ControlPathState { public: static_assert( std::is_member_function_pointer::value, "{NodeState} needs an {IsSet} method"); static_assert( std::is_member_object_pointer::value, "{NodeState} needs to hold a pointer to the {Node*} owner of the state"); explicit ControlPathState(Zone* zone) : states_(zone) {} // Returns the {NodeState} assigned to node, or the default value // {NodeState()} if it is not assigned. NodeState LookupState(Node* node) const; // Adds a state in the current code block, or a new block if the block list is // empty. void AddState(Zone* zone, Node* node, NodeState state, ControlPathState hint); // Adds a state in a new block. void AddStateInNewBlock(Zone* zone, Node* node, NodeState state); // Reset this {ControlPathState} to its longest prefix that is common with // {other}. void ResetToCommonAncestor(ControlPathState other); bool IsEmpty() { return blocks_.Size() == 0; } bool operator==(const ControlPathState& other) const { return blocks_ == other.blocks_; } bool operator!=(const ControlPathState& other) const { return blocks_ != other.blocks_; } private: using NodeWithPathDepth = std::pair; size_t depth(size_t depth_if_multiple_instances) { return node_uniqueness == kMultipleInstances ? depth_if_multiple_instances : 0; } #if DEBUG bool BlocksAndStatesInvariant(); #endif FunctionalList> blocks_; // This is an auxilliary data structure that provides fast lookups in the // set of states. It should hold at any point that the contents of {blocks_} // and {states_} is the same, which is implemented in // {BlocksAndStatesInvariant}. PersistentMap states_; }; template class AdvancedReducerWithControlPathState : public AdvancedReducer { protected: AdvancedReducerWithControlPathState(Editor* editor, Zone* zone, Graph* graph) : AdvancedReducer(editor), zone_(zone), node_states_(graph->NodeCount(), zone), reduced_(graph->NodeCount(), zone) {} Reduction TakeStatesFromFirstControl(Node* node); // Update the state of {state_owner} to {new_state}. Reduction UpdateStates( Node* state_owner, ControlPathState new_state); // Update the state of {state_owner} to {prev_states}, plus {additional_state} // assigned to {additional_node}. Force the new state in a new block if // {in_new_block}. Reduction UpdateStates( Node* state_owner, ControlPathState prev_states, Node* additional_node, NodeState additional_state, bool in_new_block); Zone* zone() { return zone_; } ControlPathState GetState(Node* node) { return node_states_.Get(node); } bool IsReduced(Node* node) { return reduced_.Get(node); } private: Zone* zone_; // Maps each control node to the node's current state. // If the information is nullptr, then we have not calculated the information // yet. NodeAuxData, ZoneConstruct>> node_states_; NodeAuxData reduced_; }; template NodeState ControlPathState::LookupState( Node* node) const { if (node_uniqueness == kUniqueInstance) return states_.Get({node, 0}); for (size_t depth = blocks_.Size(); depth > 0; depth--) { NodeState state = states_.Get({node, depth}); if (state.IsSet()) return state; } return {}; } template void ControlPathState::AddState( Zone* zone, Node* node, NodeState state, ControlPathState hint) { NodeState previous_state = LookupState(node); if (node_uniqueness == kUniqueInstance ? previous_state.IsSet() : previous_state == state) { return; } FunctionalList prev_front = blocks_.Front(); if (hint.blocks_.Size() > 0) { prev_front.PushFront(state, zone, hint.blocks_.Front()); } else { prev_front.PushFront(state, zone); } blocks_.DropFront(); blocks_.PushFront(prev_front, zone); states_.Set({node, depth(blocks_.Size())}, state); SLOW_DCHECK(BlocksAndStatesInvariant()); } template void ControlPathState::AddStateInNewBlock( Zone* zone, Node* node, NodeState state) { FunctionalList new_block; NodeState previous_state = LookupState(node); if (node_uniqueness == kUniqueInstance ? !previous_state.IsSet() : previous_state != state) { new_block.PushFront(state, zone); states_.Set({node, depth(blocks_.Size() + 1)}, state); } blocks_.PushFront(new_block, zone); SLOW_DCHECK(BlocksAndStatesInvariant()); } template void ControlPathState::ResetToCommonAncestor( ControlPathState other) { while (other.blocks_.Size() > blocks_.Size()) other.blocks_.DropFront(); while (blocks_.Size() > other.blocks_.Size()) { for (NodeState state : blocks_.Front()) { states_.Set({state.node, depth(blocks_.Size())}, {}); } blocks_.DropFront(); } while (blocks_ != other.blocks_) { for (NodeState state : blocks_.Front()) { states_.Set({state.node, depth(blocks_.Size())}, {}); } blocks_.DropFront(); other.blocks_.DropFront(); } SLOW_DCHECK(BlocksAndStatesInvariant()); } #if DEBUG template bool ControlPathState::BlocksAndStatesInvariant() { PersistentMap states_copy(states_); size_t current_depth = blocks_.Size(); for (auto block : blocks_) { std::unordered_set seen_this_block; for (NodeState state : block) { // Every element of blocks_ has to be in states_. if (seen_this_block.count(state.node) == 0) { if (states_copy.Get({state.node, depth(current_depth)}) != state) { return false; } states_copy.Set({state.node, depth(current_depth)}, {}); seen_this_block.emplace(state.node); } } current_depth--; } // Every element of {states_} has to be in {blocks_}. We removed all // elements of blocks_ from states_copy, so if it is not empty, the // invariant fails. return states_copy.begin() == states_copy.end(); } #endif template Reduction AdvancedReducerWithControlPathState< NodeState, node_uniqueness>::TakeStatesFromFirstControl(Node* node) { // We just propagate the information from the control input (ideally, // we would only revisit control uses if there is change). Node* input = NodeProperties::GetControlInput(node, 0); if (!reduced_.Get(input)) return NoChange(); return UpdateStates(node, node_states_.Get(input)); } template Reduction AdvancedReducerWithControlPathState::UpdateStates( Node* state_owner, ControlPathState new_state) { // Only signal that the node has {Changed} if its state has changed. bool reduced_changed = reduced_.Set(state_owner, true); bool node_states_changed = node_states_.Set(state_owner, new_state); if (reduced_changed || node_states_changed) { return Changed(state_owner); } return NoChange(); } template Reduction AdvancedReducerWithControlPathState::UpdateStates( Node* state_owner, ControlPathState prev_states, Node* additional_node, NodeState additional_state, bool in_new_block) { if (in_new_block || prev_states.IsEmpty()) { prev_states.AddStateInNewBlock(zone_, additional_node, additional_state); } else { ControlPathState original = node_states_.Get(state_owner); prev_states.AddState(zone_, additional_node, additional_state, original); } return UpdateStates(state_owner, prev_states); } } // namespace compiler } // namespace internal } // namespace v8 #endif // V8_COMPILER_CONTROL_PATH_STATE_H_