// 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. #include "src/compiler/decompression-optimizer.h" #include "src/compiler/graph.h" #include "src/compiler/node-properties.h" namespace v8 { namespace internal { namespace compiler { namespace { bool IsMachineLoad(Node* const node) { const IrOpcode::Value opcode = node->opcode(); return opcode == IrOpcode::kLoad || opcode == IrOpcode::kPoisonedLoad || opcode == IrOpcode::kProtectedLoad || opcode == IrOpcode::kUnalignedLoad || opcode == IrOpcode::kLoadImmutable; } bool IsTaggedMachineLoad(Node* const node) { return IsMachineLoad(node) && CanBeTaggedPointer(LoadRepresentationOf(node->op()).representation()); } bool IsHeapConstant(Node* const node) { return node->opcode() == IrOpcode::kHeapConstant; } bool IsTaggedPhi(Node* const node) { if (node->opcode() == IrOpcode::kPhi) { return CanBeTaggedPointer(PhiRepresentationOf(node->op())); } return false; } bool CanBeCompressed(Node* const node) { return IsHeapConstant(node) || IsTaggedMachineLoad(node) || IsTaggedPhi(node); } } // anonymous namespace DecompressionOptimizer::DecompressionOptimizer(Zone* zone, Graph* graph, CommonOperatorBuilder* common, MachineOperatorBuilder* machine) : graph_(graph), common_(common), machine_(machine), states_(graph, static_cast<uint32_t>(State::kNumberOfStates)), to_visit_(zone), compressed_candidate_nodes_(zone) {} void DecompressionOptimizer::MarkNodes() { MaybeMarkAndQueueForRevisit(graph()->end(), State::kOnly32BitsObserved); while (!to_visit_.empty()) { Node* const node = to_visit_.front(); to_visit_.pop_front(); MarkNodeInputs(node); } } void DecompressionOptimizer::MarkNodeInputs(Node* node) { // Mark the value inputs. switch (node->opcode()) { // UNOPS. case IrOpcode::kBitcastTaggedToWord: case IrOpcode::kBitcastTaggedToWordForTagAndSmiBits: // Replicate the bitcast's state for its input. DCHECK_EQ(node->op()->ValueInputCount(), 1); MaybeMarkAndQueueForRevisit(node->InputAt(0), states_.Get(node)); // value break; case IrOpcode::kTruncateInt64ToInt32: DCHECK_EQ(node->op()->ValueInputCount(), 1); MaybeMarkAndQueueForRevisit(node->InputAt(0), State::kOnly32BitsObserved); // value break; // BINOPS. case IrOpcode::kInt32LessThan: case IrOpcode::kInt32LessThanOrEqual: case IrOpcode::kUint32LessThan: case IrOpcode::kUint32LessThanOrEqual: case IrOpcode::kWord32And: case IrOpcode::kWord32Equal: case IrOpcode::kWord32Shl: DCHECK_EQ(node->op()->ValueInputCount(), 2); MaybeMarkAndQueueForRevisit(node->InputAt(0), State::kOnly32BitsObserved); // value_0 MaybeMarkAndQueueForRevisit(node->InputAt(1), State::kOnly32BitsObserved); // value_1 break; // SPECIAL CASES. // SPECIAL CASES - Store. case IrOpcode::kStore: case IrOpcode::kProtectedStore: case IrOpcode::kUnalignedStore: { DCHECK_EQ(node->op()->ValueInputCount(), 3); MaybeMarkAndQueueForRevisit(node->InputAt(0), State::kEverythingObserved); // base pointer MaybeMarkAndQueueForRevisit(node->InputAt(1), State::kEverythingObserved); // index // TODO(v8:7703): When the implementation is done, check if this ternary // operator is too restrictive, since we only mark Tagged stores as 32 // bits. MachineRepresentation representation = node->opcode() == IrOpcode::kUnalignedStore ? UnalignedStoreRepresentationOf(node->op()) : StoreRepresentationOf(node->op()).representation(); MaybeMarkAndQueueForRevisit(node->InputAt(2), IsAnyTagged(representation) ? State::kOnly32BitsObserved : State::kEverythingObserved); // value } break; // SPECIAL CASES - Variable inputs. // The deopt code knows how to handle Compressed inputs, both // MachineRepresentation kCompressed values and CompressedHeapConstants. case IrOpcode::kFrameState: // Fall through. // TODO(v8:7703): kStateValues doesn't appear in any test linked to Loads or // HeapConstants. Do we care about this case? case IrOpcode::kStateValues: // Fall through. case IrOpcode::kTypedStateValues: for (int i = 0; i < node->op()->ValueInputCount(); ++i) { MaybeMarkAndQueueForRevisit(node->InputAt(i), State::kOnly32BitsObserved); } break; case IrOpcode::kPhi: { // Replicate the phi's state for its inputs. State curr_state = states_.Get(node); for (int i = 0; i < node->op()->ValueInputCount(); ++i) { MaybeMarkAndQueueForRevisit(node->InputAt(i), curr_state); } break; } default: // To be conservative, we assume that all value inputs need to be 64 bits // unless noted otherwise. for (int i = 0; i < node->op()->ValueInputCount(); ++i) { MaybeMarkAndQueueForRevisit(node->InputAt(i), State::kEverythingObserved); } break; } // We always mark the non-value input nodes as kOnly32BitsObserved so that // they will be visited. If they need to be kEverythingObserved, they will be // marked as such in a future pass. for (int i = node->op()->ValueInputCount(); i < node->InputCount(); ++i) { MaybeMarkAndQueueForRevisit(node->InputAt(i), State::kOnly32BitsObserved); } } void DecompressionOptimizer::MaybeMarkAndQueueForRevisit(Node* const node, State state) { DCHECK_NE(state, State::kUnvisited); State previous_state = states_.Get(node); // Only update the state if we have relevant new information. if (previous_state == State::kUnvisited || (previous_state == State::kOnly32BitsObserved && state == State::kEverythingObserved)) { states_.Set(node, state); to_visit_.push_back(node); if (state == State::kOnly32BitsObserved && CanBeCompressed(node)) { compressed_candidate_nodes_.push_back(node); } } } void DecompressionOptimizer::ChangeHeapConstant(Node* const node) { DCHECK(IsHeapConstant(node)); NodeProperties::ChangeOp( node, common()->CompressedHeapConstant(HeapConstantOf(node->op()))); } void DecompressionOptimizer::ChangePhi(Node* const node) { DCHECK(IsTaggedPhi(node)); MachineRepresentation mach_rep = PhiRepresentationOf(node->op()); if (mach_rep == MachineRepresentation::kTagged) { mach_rep = MachineRepresentation::kCompressed; } else { DCHECK_EQ(mach_rep, MachineRepresentation::kTaggedPointer); mach_rep = MachineRepresentation::kCompressedPointer; } NodeProperties::ChangeOp( node, common()->Phi(mach_rep, node->op()->ValueInputCount())); } void DecompressionOptimizer::ChangeLoad(Node* const node) { DCHECK(IsMachineLoad(node)); // Change to a Compressed MachRep to avoid the full decompression. LoadRepresentation load_rep = LoadRepresentationOf(node->op()); LoadRepresentation compressed_load_rep; if (load_rep == MachineType::AnyTagged()) { compressed_load_rep = MachineType::AnyCompressed(); } else { DCHECK_EQ(load_rep, MachineType::TaggedPointer()); compressed_load_rep = MachineType::CompressedPointer(); } // Change to the Operator with the Compressed MachineRepresentation. switch (node->opcode()) { case IrOpcode::kLoad: NodeProperties::ChangeOp(node, machine()->Load(compressed_load_rep)); break; case IrOpcode::kLoadImmutable: NodeProperties::ChangeOp(node, machine()->LoadImmutable(compressed_load_rep)); break; case IrOpcode::kPoisonedLoad: NodeProperties::ChangeOp(node, machine()->PoisonedLoad(compressed_load_rep)); break; case IrOpcode::kProtectedLoad: NodeProperties::ChangeOp(node, machine()->ProtectedLoad(compressed_load_rep)); break; case IrOpcode::kUnalignedLoad: NodeProperties::ChangeOp(node, machine()->UnalignedLoad(compressed_load_rep)); break; default: UNREACHABLE(); } } void DecompressionOptimizer::ChangeNodes() { for (Node* const node : compressed_candidate_nodes_) { // compressed_candidate_nodes_ contains all the nodes that once had the // State::kOnly32BitsObserved. If we later updated the state to be // State::IsEverythingObserved, then we have to ignore them. This is less // costly than removing them from the compressed_candidate_nodes_ NodeVector // when we update them to State::IsEverythingObserved. if (IsEverythingObserved(node)) continue; switch (node->opcode()) { case IrOpcode::kHeapConstant: ChangeHeapConstant(node); break; case IrOpcode::kPhi: ChangePhi(node); break; default: ChangeLoad(node); break; } } } void DecompressionOptimizer::Reduce() { MarkNodes(); ChangeNodes(); } } // namespace compiler } // namespace internal } // namespace v8