| // Copyright 2019 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_DECOMPRESSION_OPTIMIZER_H_ |
| #define V8_COMPILER_DECOMPRESSION_OPTIMIZER_H_ |
| |
| #include "src/compiler/common-operator.h" |
| #include "src/compiler/machine-operator.h" |
| #include "src/compiler/node-marker.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| // Forward declare. |
| class Graph; |
| |
| // DecompressionOptimizer purpose is to hide the distinction between 32 bit and |
| // 64 bit tagged values, while being able to use the compressed version of nodes |
| // whenever possible. Its scope is narrowed down to loads of TaggedPointer and |
| // AnyTagged (since TaggedSigned avoids full decompression always), and |
| // HeapConstants. |
| |
| // DecompressionOptimizer will run only when pointer compression is enabled. |
| |
| // The phase needs to be run when Machine are present in the graph, i.e |
| // at the very end of the pipeline. Also, since this phase may change |
| // the load's MachineRepresentation from Tagged to Compressed, it's best |
| // to run it as late as possible in order to keep the phases that know |
| // about Compressed MachineRepresentation to a minimum. |
| |
| // As an example, if we Load a Tagged value only to Store it back again (i.e |
| // Load -> Store nodes, with the Load's value being the Store's value) we don't |
| // need to fully decompress it since the Store will ignore the top bits. |
| class V8_EXPORT_PRIVATE DecompressionOptimizer final { |
| public: |
| DecompressionOptimizer(Zone* zone, Graph* graph, |
| CommonOperatorBuilder* common, |
| MachineOperatorBuilder* machine); |
| ~DecompressionOptimizer() = default; |
| DecompressionOptimizer(const DecompressionOptimizer&) = delete; |
| DecompressionOptimizer& operator=(const DecompressionOptimizer&) = delete; |
| |
| // Assign States to the nodes, and then change the node's Operator to use the |
| // compressed version if possible. |
| void Reduce(); |
| |
| private: |
| // State refers to the node's state as follows: |
| // * kUnvisited === This node has yet to be visited. |
| // * kOnly32BitsObserved === This node either has been visited, or is on |
| // to_visit_. We couldn't find a node that observes the upper bits. |
| // * kEverythingObserved === This node either has been visited, or is on |
| // to_visit_. We found at least one node that observes the upper bits. |
| enum class State : uint8_t { |
| kUnvisited = 0, |
| kOnly32BitsObserved, |
| kEverythingObserved, |
| kNumberOfStates |
| }; |
| |
| // Change node's op from HeapConstant to CompressedHeapConstant. |
| void ChangeHeapConstant(Node* const node); |
| |
| // Change the phi's representation from Tagged to Compressed. |
| void ChangePhi(Node* const node); |
| |
| // Change node's load into a compressed one. |
| void ChangeLoad(Node* const node); |
| |
| // Go through the already marked nodes and changed the operation for the nodes |
| // that can use compressed outputs. |
| void ChangeNodes(); |
| |
| // Goes through the nodes to mark them all as appropriate. It will visit each |
| // node at most twice: only when the node was unvisited, then marked as |
| // kOnly32BitsObserved and visited, and finally marked as kEverythingObserved |
| // and visited. |
| void MarkNodes(); |
| |
| // Mark node's input as appropriate, according to node's opcode. Some input |
| // State may be updated, and therefore has to be revisited. |
| void MarkNodeInputs(Node* node); |
| |
| // Mark node's State to be state. We only do this if we have new information, |
| // i.e either if: |
| // * We are marking an unvisited node, or |
| // * We are marking a node as needing 64 bits when we previously had the |
| // information that it could output 32 bits. Also, we store the HeapConstant |
| // and TaggedPointer and AnyTagged loads that have their state set as |
| // kOnly32BitsObserved. If the node's state changes, we queue it for revisit. |
| void MaybeMarkAndQueueForRevisit(Node* const node, State state); |
| |
| bool IsEverythingObserved(Node* const node) { |
| return states_.Get(node) == State::kEverythingObserved; |
| } |
| |
| bool IsOnly32BitsObserved(Node* const node) { |
| return states_.Get(node) == State::kOnly32BitsObserved; |
| } |
| |
| Graph* graph() const { return graph_; } |
| CommonOperatorBuilder* common() const { return common_; } |
| MachineOperatorBuilder* machine() const { return machine_; } |
| |
| Graph* const graph_; |
| CommonOperatorBuilder* const common_; |
| MachineOperatorBuilder* const machine_; |
| NodeMarker<State> states_; |
| // to_visit_ is a Deque but it's used as if it were a Queue. The reason why we |
| // are using NodeDeque is because it attempts to reuse 'freed' zone memory |
| // instead of always allocating a new region. |
| NodeDeque to_visit_; |
| // Contains the nodes that can be changed into a compressed version of |
| // themselves. In a way, it functions as a NodeSet since each node will be |
| // contained at most once. It's a Vector since we care about insertion speed. |
| NodeVector compressed_candidate_nodes_; |
| }; |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_COMPILER_DECOMPRESSION_OPTIMIZER_H_ |