Commit 60af073a authored by dcarney's avatar dcarney Committed by Commit bot

[turbofan] add initial move optimizer

R=bmeurer@chromium.org

BUG=

Review URL: https://codereview.chromium.org/750813004

Cr-Commit-Position: refs/heads/master@{#25533}
parent f89acb64
......@@ -544,6 +544,8 @@ source_set("v8_base") {
"src/compiler/machine-operator.h",
"src/compiler/machine-type.cc",
"src/compiler/machine-type.h",
"src/compiler/move-optimizer.cc",
"src/compiler/move-optimizer.h",
"src/compiler/node-aux-data-inl.h",
"src/compiler/node-aux-data.h",
"src/compiler/node-cache.cc",
......
......@@ -616,6 +616,8 @@ class GapInstruction : public Instruction {
bool IsRedundant() const;
ParallelMove** parallel_moves() { return parallel_moves_; }
static GapInstruction* New(Zone* zone) {
void* buffer = zone->New(sizeof(GapInstruction));
return new (buffer) GapInstruction(kGapInstruction);
......
// 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 {
MoveOptimizer::MoveOptimizer(Zone* local_zone, InstructionSequence* code)
: local_zone_(local_zone),
code_(code),
temp_vector_0_(local_zone),
temp_vector_1_(local_zone) {}
void MoveOptimizer::Run() {
// First smash all consecutive moves into the left most move slot.
for (auto* block : code()->instruction_blocks()) {
GapInstruction* prev_gap = nullptr;
for (int index = block->code_start(); index < block->code_end(); ++index) {
auto instr = code()->instructions()[index];
if (!instr->IsGapMoves()) {
if (instr->IsSourcePosition() || instr->IsNop()) continue;
FinalizeMoves(&temp_vector_0_, &temp_vector_1_, prev_gap);
prev_gap = nullptr;
continue;
}
auto gap = GapInstruction::cast(instr);
// Find first non-empty slot.
int i = GapInstruction::FIRST_INNER_POSITION;
for (; i <= GapInstruction::LAST_INNER_POSITION; i++) {
auto move = gap->parallel_moves()[i];
if (move == nullptr) continue;
auto move_ops = move->move_operands();
auto op = move_ops->begin();
for (; op != move_ops->end(); ++op) {
if (!op->IsRedundant()) break;
}
if (op == move_ops->end()) {
move_ops->Rewind(0); // Clear this redundant move.
} else {
break; // Found index of first non-redundant move.
}
}
// Nothing to do here.
if (i == GapInstruction::LAST_INNER_POSITION + 1) {
if (prev_gap != nullptr) {
// Slide prev_gap down so we always know where to look for it.
std::swap(prev_gap->parallel_moves()[0], gap->parallel_moves()[0]);
prev_gap = gap;
}
continue;
}
// Move the first non-empty gap to position 0.
std::swap(gap->parallel_moves()[0], gap->parallel_moves()[i]);
auto left = gap->parallel_moves()[0];
// Compress everything into position 0.
for (++i; i <= GapInstruction::LAST_INNER_POSITION; ++i) {
auto move = gap->parallel_moves()[i];
if (move == nullptr) continue;
CompressMoves(&temp_vector_0_, left, move);
}
if (prev_gap != nullptr) {
// Smash left into prev_gap, killing left.
auto pred_moves = prev_gap->parallel_moves()[0];
CompressMoves(&temp_vector_0_, pred_moves, left);
std::swap(prev_gap->parallel_moves()[0], gap->parallel_moves()[0]);
}
prev_gap = gap;
}
FinalizeMoves(&temp_vector_0_, &temp_vector_1_, prev_gap);
}
}
static MoveOperands* PrepareInsertAfter(ParallelMove* left, MoveOperands* move,
Zone* zone) {
auto move_ops = left->move_operands();
MoveOperands* replacement = nullptr;
MoveOperands* to_eliminate = nullptr;
for (auto curr = move_ops->begin(); curr != move_ops->end(); ++curr) {
if (curr->IsEliminated()) continue;
if (curr->destination()->Equals(move->source())) {
DCHECK_EQ(nullptr, replacement);
replacement = curr;
if (to_eliminate != nullptr) break;
} else if (curr->destination()->Equals(move->destination())) {
DCHECK_EQ(nullptr, to_eliminate);
to_eliminate = curr;
if (replacement != nullptr) break;
}
}
DCHECK(!(replacement == to_eliminate && replacement != nullptr));
if (replacement != nullptr) {
auto new_source = new (zone) InstructionOperand(
replacement->source()->kind(), replacement->source()->index());
move->set_source(new_source);
}
return to_eliminate;
}
void MoveOptimizer::CompressMoves(MoveOpVector* eliminated, ParallelMove* left,
ParallelMove* right) {
DCHECK(eliminated->empty());
auto move_ops = right->move_operands();
// Modify the right moves in place and collect moves that will be killed by
// merging the two gaps.
for (auto op = move_ops->begin(); op != move_ops->end(); ++op) {
if (op->IsRedundant()) continue;
MoveOperands* to_eliminate = PrepareInsertAfter(left, op, code_zone());
if (to_eliminate != nullptr) {
eliminated->push_back(to_eliminate);
}
}
// Eliminate dead moves. Must happen before insertion of new moves as the
// contents of eliminated are pointers into a list.
for (auto to_eliminate : *eliminated) {
to_eliminate->Eliminate();
}
eliminated->clear();
// Add all possibly modified moves from right side.
for (auto op = move_ops->begin(); op != move_ops->end(); ++op) {
if (op->IsRedundant()) continue;
left->move_operands()->Add(*op, code_zone());
}
// Nuke right.
move_ops->Rewind(0);
}
void MoveOptimizer::FinalizeMoves(MoveOpVector* loads, MoveOpVector* new_moves,
GapInstruction* gap) {
DCHECK(loads->empty());
DCHECK(new_moves->empty());
if (gap == nullptr) return;
// Split multiple loads of the same constant or stack slot off into the second
// slot and keep remaining moves in the first slot.
auto move_ops = gap->parallel_moves()[0]->move_operands();
for (auto move = move_ops->begin(); move != move_ops->end(); ++move) {
if (move->IsRedundant()) {
move->Eliminate();
continue;
}
if (!(move->source()->IsConstant() || move->source()->IsStackSlot() ||
move->source()->IsDoubleStackSlot()))
continue;
// Search for existing move to this slot.
MoveOperands* found = nullptr;
for (auto load : *loads) {
if (load->source()->Equals(move->source())) {
found = load;
break;
}
}
// Not found so insert.
if (found == nullptr) {
loads->push_back(move);
// Replace source with copy for later use.
auto dest = move->destination();
move->set_destination(new (code_zone())
InstructionOperand(dest->kind(), dest->index()));
continue;
}
if ((found->destination()->IsStackSlot() ||
found->destination()->IsDoubleStackSlot()) &&
!(move->destination()->IsStackSlot() ||
move->destination()->IsDoubleStackSlot())) {
// Found a better source for this load. Smash it in place to affect other
// loads that have already been split.
InstructionOperand::Kind found_kind = found->destination()->kind();
int found_index = found->destination()->index();
auto next_dest =
new (code_zone()) InstructionOperand(found_kind, found_index);
auto dest = move->destination();
found->destination()->ConvertTo(dest->kind(), dest->index());
move->set_destination(next_dest);
}
// move from load destination.
move->set_source(found->destination());
new_moves->push_back(move);
}
loads->clear();
if (new_moves->empty()) return;
// Insert all new moves into slot 1.
auto slot_1 = gap->GetOrCreateParallelMove(
static_cast<GapInstruction::InnerPosition>(1), code_zone());
DCHECK(slot_1->move_operands()->is_empty());
slot_1->move_operands()->AddBlock(MoveOperands(nullptr, nullptr),
static_cast<int>(new_moves->size()),
code_zone());
auto it = slot_1->move_operands()->begin();
for (auto new_move : *new_moves) {
std::swap(*new_move, *it);
++it;
}
DCHECK_EQ(it, slot_1->move_operands()->end());
new_moves->clear();
}
} // namespace compiler
} // namespace internal
} // namespace v8
// 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.
#ifndef V8_COMPILER_MOVE_OPTIMIZER_
#define V8_COMPILER_MOVE_OPTIMIZER_
#include "src/compiler/instruction.h"
#include "src/zone-containers.h"
namespace v8 {
namespace internal {
namespace compiler {
class MoveOptimizer FINAL {
public:
MoveOptimizer(Zone* local_zone, InstructionSequence* code);
void Run();
private:
typedef ZoneVector<MoveOperands*> MoveOpVector;
InstructionSequence* code() const { return code_; }
Zone* local_zone() const { return local_zone_; }
Zone* code_zone() const { return code()->zone(); }
void CompressMoves(MoveOpVector* eliminated, ParallelMove* left,
ParallelMove* right);
void FinalizeMoves(MoveOpVector* loads, MoveOpVector* new_moves,
GapInstruction* gap);
Zone* const local_zone_;
InstructionSequence* const code_;
MoveOpVector temp_vector_0_;
MoveOpVector temp_vector_1_;
DISALLOW_COPY_AND_ASSIGN(MoveOptimizer);
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_MOVE_OPTIMIZER_
......@@ -23,6 +23,7 @@
#include "src/compiler/js-typed-lowering.h"
#include "src/compiler/jump-threading.h"
#include "src/compiler/machine-operator-reducer.h"
#include "src/compiler/move-optimizer.h"
#include "src/compiler/pipeline-statistics.h"
#include "src/compiler/register-allocator.h"
#include "src/compiler/register-allocator-verifier.h"
......@@ -579,6 +580,16 @@ struct ResolveControlFlowPhase {
};
struct OptimizeMovesPhase {
static const char* phase_name() { return "optimize moves"; }
void Run(PipelineData* data, Zone* temp_zone) {
MoveOptimizer move_optimizer(temp_zone, data->sequence());
move_optimizer.Run();
}
};
struct JumpThreadingPhase {
static const char* phase_name() { return "jump threading"; }
......@@ -987,6 +998,7 @@ void Pipeline::AllocateRegisters(const RegisterConfiguration* config,
Run<PopulatePointerMapsPhase>();
Run<ConnectRangesPhase>();
Run<ResolveControlFlowPhase>();
Run<OptimizeMovesPhase>();
if (FLAG_trace_turbo) {
OFStream os(stdout);
......
......@@ -2483,6 +2483,6 @@ void RegisterAllocator::SetLiveRangeAssignedRegister(LiveRange* range,
range->set_assigned_register(reg, code_zone());
}
}
}
} // namespace v8::internal::compiler
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -585,8 +585,8 @@ class RegisterAllocator FINAL : public ZoneObject {
DISALLOW_COPY_AND_ASSIGN(RegisterAllocator);
};
}
}
} // namespace v8::internal::compiler
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_REGISTER_ALLOCATOR_H_
This diff is collapsed.
// 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.
#ifndef V8_UNITTESTS_COMPILER_INSTRUCTION_SEQUENCE_UNITTEST_H_
#define V8_UNITTESTS_COMPILER_INSTRUCTION_SEQUENCE_UNITTEST_H_
#include "src/compiler/instruction.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace v8 {
namespace internal {
namespace compiler {
class InstructionSequenceTest : public TestWithZone {
public:
static const int kDefaultNRegs = 4;
static const int kNoValue = kMinInt;
typedef BasicBlock::RpoNumber Rpo;
struct VReg {
VReg() : value_(kNoValue) {}
VReg(PhiInstruction* phi) : value_(phi->virtual_register()) {} // NOLINT
explicit VReg(int value) : value_(value) {}
int value_;
};
enum TestOperandType {
kInvalid,
kSameAsFirst,
kRegister,
kFixedRegister,
kSlot,
kFixedSlot,
kImmediate,
kNone,
kConstant
};
struct TestOperand {
TestOperand() : type_(kInvalid), vreg_(), value_(kNoValue) {}
TestOperand(TestOperandType type, int imm)
: type_(type), vreg_(), value_(imm) {}
TestOperand(TestOperandType type, VReg vreg, int value = kNoValue)
: type_(type), vreg_(vreg), value_(value) {}
TestOperandType type_;
VReg vreg_;
int value_;
};
static TestOperand Same() { return TestOperand(kSameAsFirst, VReg()); }
static TestOperand Reg(VReg vreg, int index = kNoValue) {
TestOperandType type = kRegister;
if (index != kNoValue) type = kFixedRegister;
return TestOperand(type, vreg, index);
}
static TestOperand Reg(int index = kNoValue) { return Reg(VReg(), index); }
static TestOperand Slot(VReg vreg, int index = kNoValue) {
TestOperandType type = kSlot;
if (index != kNoValue) type = kFixedSlot;
return TestOperand(type, vreg, index);
}
static TestOperand Slot(int index = kNoValue) { return Slot(VReg(), index); }
static TestOperand Const(int index) {
CHECK_NE(kNoValue, index);
return TestOperand(kConstant, VReg(), index);
}
static TestOperand Use(VReg vreg) { return TestOperand(kNone, vreg); }
static TestOperand Use() { return Use(VReg()); }
enum BlockCompletionType { kBlockEnd, kFallThrough, kBranch, kJump };
struct BlockCompletion {
BlockCompletionType type_;
TestOperand op_;
int offset_0_;
int offset_1_;
};
static BlockCompletion FallThrough() {
BlockCompletion completion = {kFallThrough, TestOperand(), 1, kNoValue};
return completion;
}
static BlockCompletion Jump(int offset) {
BlockCompletion completion = {kJump, TestOperand(), offset, kNoValue};
return completion;
}
static BlockCompletion Branch(TestOperand op, int left_offset,
int right_offset) {
BlockCompletion completion = {kBranch, op, left_offset, right_offset};
return completion;
}
static BlockCompletion Last() {
BlockCompletion completion = {kBlockEnd, TestOperand(), kNoValue, kNoValue};
return completion;
}
InstructionSequenceTest();
void SetNumRegs(int num_general_registers, int num_double_registers);
RegisterConfiguration* config();
InstructionSequence* sequence();
void StartLoop(int loop_blocks);
void EndLoop();
void StartBlock();
int EndBlock(BlockCompletion completion = FallThrough());
TestOperand Imm(int32_t imm = 0);
VReg Define(TestOperand output_op);
VReg Parameter(TestOperand output_op = Reg()) { return Define(output_op); }
int Return(TestOperand input_op_0);
int Return(VReg vreg) { return Return(Reg(vreg, 0)); }
PhiInstruction* Phi(VReg incoming_vreg_0 = VReg(),
VReg incoming_vreg_1 = VReg(),
VReg incoming_vreg_2 = VReg(),
VReg incoming_vreg_3 = VReg());
void Extend(PhiInstruction* phi, VReg vreg);
VReg DefineConstant(int32_t imm = 0);
int EmitNop();
int EmitI(TestOperand input_op_0);
VReg EmitOI(TestOperand output_op, TestOperand input_op_0);
VReg EmitOII(TestOperand output_op, TestOperand input_op_0,
TestOperand input_op_1);
VReg EmitCall(TestOperand output_op, size_t input_size, TestOperand* inputs);
VReg EmitCall(TestOperand output_op, TestOperand input_op_0 = TestOperand(),
TestOperand input_op_1 = TestOperand(),
TestOperand input_op_2 = TestOperand(),
TestOperand input_op_3 = TestOperand());
// Get defining instruction vreg or value returned at instruction creation
// time when there is no return value.
const Instruction* GetInstruction(int instruction_index);
InstructionBlock* current_block() const { return current_block_; }
int num_general_registers() const { return num_general_registers_; }
int num_double_registers() const { return num_double_registers_; }
// Called after all instructions have been inserted.
void WireBlocks();
private:
VReg NewReg() { return VReg(sequence()->NextVirtualRegister()); }
int NewIndex() { return current_instruction_index_--; }
static TestOperand Invalid() { return TestOperand(kInvalid, VReg()); }
int EmitBranch(TestOperand input_op);
int EmitFallThrough();
int EmitJump();
Instruction* NewInstruction(InstructionCode code, size_t outputs_size,
InstructionOperand** outputs,
size_t inputs_size = 0,
InstructionOperand* *inputs = nullptr,
size_t temps_size = 0,
InstructionOperand* *temps = nullptr);
InstructionOperand* Unallocated(TestOperand op,
UnallocatedOperand::ExtendedPolicy policy);
InstructionOperand* Unallocated(TestOperand op,
UnallocatedOperand::ExtendedPolicy policy,
UnallocatedOperand::Lifetime lifetime);
InstructionOperand* Unallocated(TestOperand op,
UnallocatedOperand::ExtendedPolicy policy,
int index);
InstructionOperand* Unallocated(TestOperand op,
UnallocatedOperand::BasicPolicy policy,
int index);
InstructionOperand* ConvertInputOp(TestOperand op);
InstructionOperand* ConvertOutputOp(VReg vreg, TestOperand op);
InstructionBlock* NewBlock();
void WireBlock(size_t block_offset, int jump_offset);
int Emit(int instruction_index, InstructionCode code, size_t outputs_size = 0,
InstructionOperand* *outputs = nullptr, size_t inputs_size = 0,
InstructionOperand* *inputs = nullptr, size_t temps_size = 0,
InstructionOperand* *temps = nullptr, bool is_call = false);
int AddInstruction(int instruction_index, Instruction* instruction);
struct LoopData {
Rpo loop_header_;
int expected_blocks_;
};
typedef std::vector<LoopData> LoopBlocks;
typedef std::map<int, const Instruction*> Instructions;
typedef std::vector<BlockCompletion> Completions;
SmartPointer<RegisterConfiguration> config_;
InstructionSequence* sequence_;
int num_general_registers_;
int num_double_registers_;
// Block building state.
InstructionBlocks instruction_blocks_;
Instructions instructions_;
int current_instruction_index_;
Completions completions_;
LoopBlocks loop_blocks_;
InstructionBlock* current_block_;
bool block_returns_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_UNITTESTS_COMPILER_INSTRUCTION_SEQUENCE_UNITTEST_H_
// 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"
#include "test/unittests/compiler/instruction-sequence-unittest.h"
namespace v8 {
namespace internal {
namespace compiler {
class MoveOptimizerTest : public InstructionSequenceTest {
public:
GapInstruction* LastGap() {
auto instruction = sequence()->instructions().back();
if (!instruction->IsGapMoves()) {
instruction = *(sequence()->instructions().rbegin() + 1);
}
return GapInstruction::cast(instruction);
}
void AddMove(GapInstruction* gap, TestOperand from, TestOperand to,
GapInstruction::InnerPosition pos = GapInstruction::START) {
auto parallel_move = gap->GetOrCreateParallelMove(pos, zone());
parallel_move->AddMove(ConvertMoveArg(from), ConvertMoveArg(to), zone());
}
int NonRedundantSize(ParallelMove* move) {
int i = 0;
auto ops = move->move_operands();
for (auto op = ops->begin(); op != ops->end(); ++op) {
if (op->IsRedundant()) continue;
i++;
}
return i;
}
bool Contains(ParallelMove* move, TestOperand from_op, TestOperand to_op) {
auto from = ConvertMoveArg(from_op);
auto to = ConvertMoveArg(to_op);
auto ops = move->move_operands();
for (auto op = ops->begin(); op != ops->end(); ++op) {
if (op->IsRedundant()) continue;
if (op->source()->Equals(from) && op->destination()->Equals(to)) {
return true;
}
}
return false;
}
// TODO(dcarney): add a verifier.
void Optimize() {
WireBlocks();
if (FLAG_trace_turbo) {
OFStream os(stdout);
PrintableInstructionSequence printable = {config(), sequence()};
os << "----- Instruction sequence before move optimization -----\n"
<< printable;
}
MoveOptimizer move_optimizer(zone(), sequence());
move_optimizer.Run();
if (FLAG_trace_turbo) {
OFStream os(stdout);
PrintableInstructionSequence printable = {config(), sequence()};
os << "----- Instruction sequence after move optimization -----\n"
<< printable;
}
}
private:
InstructionOperand* ConvertMoveArg(TestOperand op) {
CHECK_EQ(kNoValue, op.vreg_.value_);
CHECK_NE(kNoValue, op.value_);
switch (op.type_) {
case kConstant:
return ConstantOperand::Create(op.value_, zone());
case kFixedSlot:
return StackSlotOperand::Create(op.value_, zone());
case kFixedRegister:
CHECK(0 <= op.value_ && op.value_ < num_general_registers());
return RegisterOperand::Create(op.value_, zone());
default:
break;
}
CHECK(false);
return nullptr;
}
};
TEST_F(MoveOptimizerTest, RemovesRedundant) {
StartBlock();
AddMove(LastGap(), Reg(0), Reg(1));
EmitNop();
AddMove(LastGap(), Reg(1), Reg(0));
EmitNop();
EndBlock(Last());
Optimize();
auto gap = LastGap();
auto move = gap->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(move));
CHECK(Contains(move, Reg(0), Reg(1)));
}
TEST_F(MoveOptimizerTest, SplitsConstants) {
StartBlock();
EndBlock(Last());
auto gap = LastGap();
AddMove(gap, Const(1), Slot(0));
AddMove(gap, Const(1), Slot(1));
AddMove(gap, Const(1), Reg(0));
AddMove(gap, Const(1), Slot(2));
Optimize();
auto move = gap->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(move));
CHECK(Contains(move, Const(1), Reg(0)));
move = gap->parallel_moves()[1];
CHECK_EQ(3, NonRedundantSize(move));
CHECK(Contains(move, Reg(0), Slot(0)));
CHECK(Contains(move, Reg(0), Slot(1)));
CHECK(Contains(move, Reg(0), Slot(2)));
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -45,10 +45,13 @@
'compiler/graph-unittest.h',
'compiler/instruction-selector-unittest.cc',
'compiler/instruction-selector-unittest.h',
'compiler/instruction-sequence-unittest.cc',
'compiler/instruction-sequence-unittest.h',
'compiler/js-builtin-reducer-unittest.cc',
'compiler/js-operator-unittest.cc',
'compiler/js-typed-lowering-unittest.cc',
'compiler/machine-operator-reducer-unittest.cc',
'compiler/move-optimizer-unittest.cc',
'compiler/node-matchers-unittest.cc',
'compiler/node-test-utils.cc',
'compiler/node-test-utils.h',
......
......@@ -476,6 +476,8 @@
'../../src/compiler/machine-operator.h',
'../../src/compiler/machine-type.cc',
'../../src/compiler/machine-type.h',
'../../src/compiler/move-optimizer.cc',
'../../src/compiler/move-optimizer.h',
'../../src/compiler/node-aux-data-inl.h',
'../../src/compiler/node-aux-data.h',
'../../src/compiler/node-cache.cc',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment