blob: 18e682a4fd5e53d588c97ae18286e49dde733ce8 [file] [log] [blame] [edit]
// 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::kProtectedLoad ||
opcode == IrOpcode::kLoadTrapOnNull ||
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 IsIntConstant(Node* const node) {
return node->opcode() == IrOpcode::kInt32Constant ||
node->opcode() == IrOpcode::kInt64Constant;
}
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 - Load.
case IrOpcode::kLoad:
case IrOpcode::kProtectedLoad:
case IrOpcode::kLoadTrapOnNull:
case IrOpcode::kUnalignedLoad:
case IrOpcode::kLoadImmutable:
DCHECK_EQ(node->op()->ValueInputCount(), 2);
// Mark addressing base pointer in compressed form to allow pointer
// decompression via complex addressing mode.
if (DECOMPRESS_POINTER_BY_ADDRESSING_MODE &&
node->InputAt(0)->OwnedBy(node) && IsIntConstant(node->InputAt(1))) {
MarkAddressingBase(node->InputAt(0));
} else {
MaybeMarkAndQueueForRevisit(
node->InputAt(0),
State::kEverythingObserved); // base pointer
MaybeMarkAndQueueForRevisit(node->InputAt(1),
State::kEverythingObserved); // index
}
break;
// SPECIAL CASES - Store.
case IrOpcode::kStore:
case IrOpcode::kProtectedStore:
case IrOpcode::kStoreTrapOnNull:
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);
}
}
// We mark the addressing base pointer as kOnly32BitsObserved so it can be
// optimized to compressed form. This allows us to move the decompression to
// use-site on X64.
void DecompressionOptimizer::MarkAddressingBase(Node* base) {
if (IsTaggedMachineLoad(base)) {
MaybeMarkAndQueueForRevisit(base,
State::kOnly32BitsObserved); // base pointer
} else if (IsTaggedPhi(base)) {
bool should_compress = true;
for (int i = 0; i < base->op()->ValueInputCount(); ++i) {
if (!IsTaggedMachineLoad(base->InputAt(i)) ||
!base->InputAt(i)->OwnedBy(base)) {
should_compress = false;
break;
}
}
MaybeMarkAndQueueForRevisit(
base,
should_compress ? State::kOnly32BitsObserved
: State::kEverythingObserved); // base pointer
} else {
MaybeMarkAndQueueForRevisit(base,
State::kEverythingObserved); // base pointer
}
}
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::kProtectedLoad:
NodeProperties::ChangeOp(node,
machine()->ProtectedLoad(compressed_load_rep));
break;
case IrOpcode::kLoadTrapOnNull:
NodeProperties::ChangeOp(node,
machine()->LoadTrapOnNull(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