// Copyright 2014 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/move-optimizer.h"
|
|
namespace v8 {
|
namespace internal {
|
namespace compiler {
|
|
namespace {
|
|
struct MoveKey {
|
InstructionOperand source;
|
InstructionOperand destination;
|
};
|
|
struct MoveKeyCompare {
|
bool operator()(const MoveKey& a, const MoveKey& b) const {
|
if (a.source.EqualsCanonicalized(b.source)) {
|
return a.destination.CompareCanonicalized(b.destination);
|
}
|
return a.source.CompareCanonicalized(b.source);
|
}
|
};
|
|
typedef ZoneMap<MoveKey, unsigned, MoveKeyCompare> MoveMap;
|
|
class OperandSet {
|
public:
|
explicit OperandSet(ZoneVector<InstructionOperand>* buffer)
|
: set_(buffer), fp_reps_(0) {
|
buffer->clear();
|
}
|
|
void InsertOp(const InstructionOperand& op) {
|
set_->push_back(op);
|
|
if (!kSimpleFPAliasing && op.IsFPRegister())
|
fp_reps_ |= RepBit(LocationOperand::cast(op).representation());
|
}
|
|
bool Contains(const InstructionOperand& op) const {
|
for (const InstructionOperand& elem : *set_) {
|
if (elem.EqualsCanonicalized(op)) return true;
|
}
|
return false;
|
}
|
|
bool ContainsOpOrAlias(const InstructionOperand& op) const {
|
if (Contains(op)) return true;
|
|
if (!kSimpleFPAliasing && op.IsFPRegister()) {
|
// Platforms where FP registers have complex aliasing need extra checks.
|
const LocationOperand& loc = LocationOperand::cast(op);
|
MachineRepresentation rep = loc.representation();
|
// If haven't encountered mixed rep FP registers, skip the extra checks.
|
if (!HasMixedFPReps(fp_reps_ | RepBit(rep))) return false;
|
|
// Check register against aliasing registers of other FP representations.
|
MachineRepresentation other_rep1, other_rep2;
|
switch (rep) {
|
case MachineRepresentation::kFloat32:
|
other_rep1 = MachineRepresentation::kFloat64;
|
other_rep2 = MachineRepresentation::kSimd128;
|
break;
|
case MachineRepresentation::kFloat64:
|
other_rep1 = MachineRepresentation::kFloat32;
|
other_rep2 = MachineRepresentation::kSimd128;
|
break;
|
case MachineRepresentation::kSimd128:
|
other_rep1 = MachineRepresentation::kFloat32;
|
other_rep2 = MachineRepresentation::kFloat64;
|
break;
|
default:
|
UNREACHABLE();
|
break;
|
}
|
const RegisterConfiguration* config = RegisterConfiguration::Default();
|
int base = -1;
|
int aliases =
|
config->GetAliases(rep, loc.register_code(), other_rep1, &base);
|
DCHECK(aliases > 0 || (aliases == 0 && base == -1));
|
while (aliases--) {
|
if (Contains(AllocatedOperand(LocationOperand::REGISTER, other_rep1,
|
base + aliases))) {
|
return true;
|
}
|
}
|
aliases = config->GetAliases(rep, loc.register_code(), other_rep2, &base);
|
DCHECK(aliases > 0 || (aliases == 0 && base == -1));
|
while (aliases--) {
|
if (Contains(AllocatedOperand(LocationOperand::REGISTER, other_rep2,
|
base + aliases))) {
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
private:
|
static int RepBit(MachineRepresentation rep) {
|
return 1 << static_cast<int>(rep);
|
}
|
|
static bool HasMixedFPReps(int reps) {
|
return reps && !base::bits::IsPowerOfTwo(reps);
|
}
|
|
ZoneVector<InstructionOperand>* set_;
|
int fp_reps_;
|
};
|
|
int FindFirstNonEmptySlot(const Instruction* instr) {
|
int i = Instruction::FIRST_GAP_POSITION;
|
for (; i <= Instruction::LAST_GAP_POSITION; i++) {
|
ParallelMove* moves = instr->parallel_moves()[i];
|
if (moves == nullptr) continue;
|
for (MoveOperands* move : *moves) {
|
if (!move->IsRedundant()) return i;
|
move->Eliminate();
|
}
|
moves->clear(); // Clear this redundant move.
|
}
|
return i;
|
}
|
|
} // namespace
|
|
MoveOptimizer::MoveOptimizer(Zone* local_zone, InstructionSequence* code)
|
: local_zone_(local_zone),
|
code_(code),
|
local_vector_(local_zone),
|
operand_buffer1(local_zone),
|
operand_buffer2(local_zone) {}
|
|
void MoveOptimizer::Run() {
|
for (Instruction* instruction : code()->instructions()) {
|
CompressGaps(instruction);
|
}
|
for (InstructionBlock* block : code()->instruction_blocks()) {
|
CompressBlock(block);
|
}
|
for (InstructionBlock* block : code()->instruction_blocks()) {
|
if (block->PredecessorCount() <= 1) continue;
|
if (!block->IsDeferred()) {
|
bool has_only_deferred = true;
|
for (RpoNumber& pred_id : block->predecessors()) {
|
if (!code()->InstructionBlockAt(pred_id)->IsDeferred()) {
|
has_only_deferred = false;
|
break;
|
}
|
}
|
// This would pull down common moves. If the moves occur in deferred
|
// blocks, and the closest common successor is not deferred, we lose the
|
// optimization of just spilling/filling in deferred blocks, when the
|
// current block is not deferred.
|
if (has_only_deferred) continue;
|
}
|
OptimizeMerge(block);
|
}
|
for (Instruction* gap : code()->instructions()) {
|
FinalizeMoves(gap);
|
}
|
}
|
|
void MoveOptimizer::RemoveClobberedDestinations(Instruction* instruction) {
|
if (instruction->IsCall()) return;
|
ParallelMove* moves = instruction->parallel_moves()[0];
|
if (moves == nullptr) return;
|
|
DCHECK(instruction->parallel_moves()[1] == nullptr ||
|
instruction->parallel_moves()[1]->empty());
|
|
OperandSet outputs(&operand_buffer1);
|
OperandSet inputs(&operand_buffer2);
|
|
// Outputs and temps are treated together as potentially clobbering a
|
// destination operand.
|
for (size_t i = 0; i < instruction->OutputCount(); ++i) {
|
outputs.InsertOp(*instruction->OutputAt(i));
|
}
|
for (size_t i = 0; i < instruction->TempCount(); ++i) {
|
outputs.InsertOp(*instruction->TempAt(i));
|
}
|
|
// Input operands block elisions.
|
for (size_t i = 0; i < instruction->InputCount(); ++i) {
|
inputs.InsertOp(*instruction->InputAt(i));
|
}
|
|
// Elide moves made redundant by the instruction.
|
for (MoveOperands* move : *moves) {
|
if (outputs.ContainsOpOrAlias(move->destination()) &&
|
!inputs.ContainsOpOrAlias(move->destination())) {
|
move->Eliminate();
|
}
|
}
|
|
// The ret instruction makes any assignment before it unnecessary, except for
|
// the one for its input.
|
if (instruction->IsRet() || instruction->IsTailCall()) {
|
for (MoveOperands* move : *moves) {
|
if (!inputs.ContainsOpOrAlias(move->destination())) {
|
move->Eliminate();
|
}
|
}
|
}
|
}
|
|
void MoveOptimizer::MigrateMoves(Instruction* to, Instruction* from) {
|
if (from->IsCall()) return;
|
|
ParallelMove* from_moves = from->parallel_moves()[0];
|
if (from_moves == nullptr || from_moves->empty()) return;
|
|
OperandSet dst_cant_be(&operand_buffer1);
|
OperandSet src_cant_be(&operand_buffer2);
|
|
// If an operand is an input to the instruction, we cannot move assignments
|
// where it appears on the LHS.
|
for (size_t i = 0; i < from->InputCount(); ++i) {
|
dst_cant_be.InsertOp(*from->InputAt(i));
|
}
|
// If an operand is output to the instruction, we cannot move assignments
|
// where it appears on the RHS, because we would lose its value before the
|
// instruction.
|
// Same for temp operands.
|
// The output can't appear on the LHS because we performed
|
// RemoveClobberedDestinations for the "from" instruction.
|
for (size_t i = 0; i < from->OutputCount(); ++i) {
|
src_cant_be.InsertOp(*from->OutputAt(i));
|
}
|
for (size_t i = 0; i < from->TempCount(); ++i) {
|
src_cant_be.InsertOp(*from->TempAt(i));
|
}
|
for (MoveOperands* move : *from_moves) {
|
if (move->IsRedundant()) continue;
|
// Assume dest has a value "V". If we have a "dest = y" move, then we can't
|
// move "z = dest", because z would become y rather than "V".
|
// We assume CompressMoves has happened before this, which means we don't
|
// have more than one assignment to dest.
|
src_cant_be.InsertOp(move->destination());
|
}
|
|
ZoneSet<MoveKey, MoveKeyCompare> move_candidates(local_zone());
|
// We start with all the moves that don't have conflicting source or
|
// destination operands are eligible for being moved down.
|
for (MoveOperands* move : *from_moves) {
|
if (move->IsRedundant()) continue;
|
if (!dst_cant_be.ContainsOpOrAlias(move->destination())) {
|
MoveKey key = {move->source(), move->destination()};
|
move_candidates.insert(key);
|
}
|
}
|
if (move_candidates.empty()) return;
|
|
// Stabilize the candidate set.
|
bool changed = false;
|
do {
|
changed = false;
|
for (auto iter = move_candidates.begin(); iter != move_candidates.end();) {
|
auto current = iter;
|
++iter;
|
InstructionOperand src = current->source;
|
if (src_cant_be.ContainsOpOrAlias(src)) {
|
src_cant_be.InsertOp(current->destination);
|
move_candidates.erase(current);
|
changed = true;
|
}
|
}
|
} while (changed);
|
|
ParallelMove to_move(local_zone());
|
for (MoveOperands* move : *from_moves) {
|
if (move->IsRedundant()) continue;
|
MoveKey key = {move->source(), move->destination()};
|
if (move_candidates.find(key) != move_candidates.end()) {
|
to_move.AddMove(move->source(), move->destination(), code_zone());
|
move->Eliminate();
|
}
|
}
|
if (to_move.empty()) return;
|
|
ParallelMove* dest =
|
to->GetOrCreateParallelMove(Instruction::GapPosition::START, code_zone());
|
|
CompressMoves(&to_move, dest);
|
DCHECK(dest->empty());
|
for (MoveOperands* m : to_move) {
|
dest->push_back(m);
|
}
|
}
|
|
void MoveOptimizer::CompressMoves(ParallelMove* left, MoveOpVector* right) {
|
if (right == nullptr) return;
|
|
MoveOpVector& eliminated = local_vector();
|
DCHECK(eliminated.empty());
|
|
if (!left->empty()) {
|
// Modify the right moves in place and collect moves that will be killed by
|
// merging the two gaps.
|
for (MoveOperands* move : *right) {
|
if (move->IsRedundant()) continue;
|
left->PrepareInsertAfter(move, &eliminated);
|
}
|
// Eliminate dead moves.
|
for (MoveOperands* to_eliminate : eliminated) {
|
to_eliminate->Eliminate();
|
}
|
eliminated.clear();
|
}
|
// Add all possibly modified moves from right side.
|
for (MoveOperands* move : *right) {
|
if (move->IsRedundant()) continue;
|
left->push_back(move);
|
}
|
// Nuke right.
|
right->clear();
|
DCHECK(eliminated.empty());
|
}
|
|
void MoveOptimizer::CompressGaps(Instruction* instruction) {
|
int i = FindFirstNonEmptySlot(instruction);
|
bool has_moves = i <= Instruction::LAST_GAP_POSITION;
|
USE(has_moves);
|
|
if (i == Instruction::LAST_GAP_POSITION) {
|
std::swap(instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION],
|
instruction->parallel_moves()[Instruction::LAST_GAP_POSITION]);
|
} else if (i == Instruction::FIRST_GAP_POSITION) {
|
CompressMoves(
|
instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION],
|
instruction->parallel_moves()[Instruction::LAST_GAP_POSITION]);
|
}
|
// We either have no moves, or, after swapping or compressing, we have
|
// all the moves in the first gap position, and none in the second/end gap
|
// position.
|
ParallelMove* first =
|
instruction->parallel_moves()[Instruction::FIRST_GAP_POSITION];
|
ParallelMove* last =
|
instruction->parallel_moves()[Instruction::LAST_GAP_POSITION];
|
USE(first);
|
USE(last);
|
|
DCHECK(!has_moves ||
|
(first != nullptr && (last == nullptr || last->empty())));
|
}
|
|
void MoveOptimizer::CompressBlock(InstructionBlock* block) {
|
int first_instr_index = block->first_instruction_index();
|
int last_instr_index = block->last_instruction_index();
|
|
// Start by removing gap assignments where the output of the subsequent
|
// instruction appears on LHS, as long as they are not needed by its input.
|
Instruction* prev_instr = code()->instructions()[first_instr_index];
|
RemoveClobberedDestinations(prev_instr);
|
|
for (int index = first_instr_index + 1; index <= last_instr_index; ++index) {
|
Instruction* instr = code()->instructions()[index];
|
// Migrate to the gap of prev_instr eligible moves from instr.
|
MigrateMoves(instr, prev_instr);
|
// Remove gap assignments clobbered by instr's output.
|
RemoveClobberedDestinations(instr);
|
prev_instr = instr;
|
}
|
}
|
|
|
const Instruction* MoveOptimizer::LastInstruction(
|
const InstructionBlock* block) const {
|
return code()->instructions()[block->last_instruction_index()];
|
}
|
|
|
void MoveOptimizer::OptimizeMerge(InstructionBlock* block) {
|
DCHECK_LT(1, block->PredecessorCount());
|
// Ensure that the last instruction in all incoming blocks don't contain
|
// things that would prevent moving gap moves across them.
|
for (RpoNumber& pred_index : block->predecessors()) {
|
const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
|
|
// If the predecessor has more than one successor, we shouldn't attempt to
|
// move down to this block (one of the successors) any of the gap moves,
|
// because their effect may be necessary to the other successors.
|
if (pred->SuccessorCount() > 1) return;
|
|
const Instruction* last_instr =
|
code()->instructions()[pred->last_instruction_index()];
|
if (last_instr->IsCall()) return;
|
if (last_instr->TempCount() != 0) return;
|
if (last_instr->OutputCount() != 0) return;
|
for (size_t i = 0; i < last_instr->InputCount(); ++i) {
|
const InstructionOperand* op = last_instr->InputAt(i);
|
if (!op->IsConstant() && !op->IsImmediate()) return;
|
}
|
}
|
// TODO(dcarney): pass a ZoneStats down for this?
|
MoveMap move_map(local_zone());
|
size_t correct_counts = 0;
|
// Accumulate set of shared moves.
|
for (RpoNumber& pred_index : block->predecessors()) {
|
const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
|
const Instruction* instr = LastInstruction(pred);
|
if (instr->parallel_moves()[0] == nullptr ||
|
instr->parallel_moves()[0]->empty()) {
|
return;
|
}
|
for (const MoveOperands* move : *instr->parallel_moves()[0]) {
|
if (move->IsRedundant()) continue;
|
InstructionOperand src = move->source();
|
InstructionOperand dst = move->destination();
|
MoveKey key = {src, dst};
|
auto res = move_map.insert(std::make_pair(key, 1));
|
if (!res.second) {
|
res.first->second++;
|
if (res.first->second == block->PredecessorCount()) {
|
correct_counts++;
|
}
|
}
|
}
|
}
|
if (move_map.empty() || correct_counts == 0) return;
|
|
// Find insertion point.
|
Instruction* instr = code()->instructions()[block->first_instruction_index()];
|
|
if (correct_counts != move_map.size()) {
|
// Moves that are unique to each predecessor won't be pushed to the common
|
// successor.
|
OperandSet conflicting_srcs(&operand_buffer1);
|
for (auto iter = move_map.begin(), end = move_map.end(); iter != end;) {
|
auto current = iter;
|
++iter;
|
if (current->second != block->PredecessorCount()) {
|
InstructionOperand dest = current->first.destination;
|
// Not all the moves in all the gaps are the same. Maybe some are. If
|
// there are such moves, we could move them, but the destination of the
|
// moves staying behind can't appear as a source of a common move,
|
// because the move staying behind will clobber this destination.
|
conflicting_srcs.InsertOp(dest);
|
move_map.erase(current);
|
}
|
}
|
|
bool changed = false;
|
do {
|
// If a common move can't be pushed to the common successor, then its
|
// destination also can't appear as source to any move being pushed.
|
changed = false;
|
for (auto iter = move_map.begin(), end = move_map.end(); iter != end;) {
|
auto current = iter;
|
++iter;
|
DCHECK_EQ(block->PredecessorCount(), current->second);
|
if (conflicting_srcs.ContainsOpOrAlias(current->first.source)) {
|
conflicting_srcs.InsertOp(current->first.destination);
|
move_map.erase(current);
|
changed = true;
|
}
|
}
|
} while (changed);
|
}
|
|
if (move_map.empty()) return;
|
|
DCHECK_NOT_NULL(instr);
|
bool gap_initialized = true;
|
if (instr->parallel_moves()[0] != nullptr &&
|
!instr->parallel_moves()[0]->empty()) {
|
// Will compress after insertion.
|
gap_initialized = false;
|
std::swap(instr->parallel_moves()[0], instr->parallel_moves()[1]);
|
}
|
ParallelMove* moves = instr->GetOrCreateParallelMove(
|
static_cast<Instruction::GapPosition>(0), code_zone());
|
// Delete relevant entries in predecessors and move everything to block.
|
bool first_iteration = true;
|
for (RpoNumber& pred_index : block->predecessors()) {
|
const InstructionBlock* pred = code()->InstructionBlockAt(pred_index);
|
for (MoveOperands* move : *LastInstruction(pred)->parallel_moves()[0]) {
|
if (move->IsRedundant()) continue;
|
MoveKey key = {move->source(), move->destination()};
|
auto it = move_map.find(key);
|
if (it != move_map.end()) {
|
if (first_iteration) {
|
moves->AddMove(move->source(), move->destination());
|
}
|
move->Eliminate();
|
}
|
}
|
first_iteration = false;
|
}
|
// Compress.
|
if (!gap_initialized) {
|
CompressMoves(instr->parallel_moves()[0], instr->parallel_moves()[1]);
|
}
|
CompressBlock(block);
|
}
|
|
|
namespace {
|
|
bool IsSlot(const InstructionOperand& op) {
|
return op.IsStackSlot() || op.IsFPStackSlot();
|
}
|
|
|
bool LoadCompare(const MoveOperands* a, const MoveOperands* b) {
|
if (!a->source().EqualsCanonicalized(b->source())) {
|
return a->source().CompareCanonicalized(b->source());
|
}
|
if (IsSlot(a->destination()) && !IsSlot(b->destination())) return false;
|
if (!IsSlot(a->destination()) && IsSlot(b->destination())) return true;
|
return a->destination().CompareCanonicalized(b->destination());
|
}
|
|
} // namespace
|
|
|
// Split multiple loads of the same constant or stack slot off into the second
|
// slot and keep remaining moves in the first slot.
|
void MoveOptimizer::FinalizeMoves(Instruction* instr) {
|
MoveOpVector& loads = local_vector();
|
DCHECK(loads.empty());
|
|
ParallelMove* parallel_moves = instr->parallel_moves()[0];
|
if (parallel_moves == nullptr) return;
|
// Find all the loads.
|
for (MoveOperands* move : *parallel_moves) {
|
if (move->IsRedundant()) continue;
|
if (move->source().IsConstant() || IsSlot(move->source())) {
|
loads.push_back(move);
|
}
|
}
|
if (loads.empty()) return;
|
// Group the loads by source, moving the preferred destination to the
|
// beginning of the group.
|
std::sort(loads.begin(), loads.end(), LoadCompare);
|
MoveOperands* group_begin = nullptr;
|
for (MoveOperands* load : loads) {
|
// New group.
|
if (group_begin == nullptr ||
|
!load->source().EqualsCanonicalized(group_begin->source())) {
|
group_begin = load;
|
continue;
|
}
|
// Nothing to be gained from splitting here.
|
if (IsSlot(group_begin->destination())) continue;
|
// Insert new move into slot 1.
|
ParallelMove* slot_1 = instr->GetOrCreateParallelMove(
|
static_cast<Instruction::GapPosition>(1), code_zone());
|
slot_1->AddMove(group_begin->destination(), load->destination());
|
load->Eliminate();
|
}
|
loads.clear();
|
}
|
|
} // namespace compiler
|
} // namespace internal
|
} // namespace v8
|