blob: 9e3ceb140fe4e2a1565636f5c1629d5c3376ce61 [file] [log] [blame]
// Copyright 2016 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/v8.h"
#include "src/interpreter/bytecode-label.h"
#include "src/interpreter/bytecode-register-optimizer.h"
#include "test/unittests/interpreter/bytecode-utils.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
namespace interpreter {
class BytecodeRegisterOptimizerTest
: public BytecodeRegisterOptimizer::BytecodeWriter,
public TestWithIsolateAndZone {
public:
struct RegisterTransfer {
Bytecode bytecode;
Register input;
Register output;
};
BytecodeRegisterOptimizerTest() {}
~BytecodeRegisterOptimizerTest() override { delete register_allocator_; }
void Initialize(int number_of_parameters, int number_of_locals) {
register_allocator_ = new BytecodeRegisterAllocator(number_of_locals);
register_optimizer_ = new (zone())
BytecodeRegisterOptimizer(zone(), register_allocator_, number_of_locals,
number_of_parameters, this);
}
void EmitLdar(Register input) override {
output_.push_back({Bytecode::kLdar, input, Register()});
}
void EmitStar(Register output) override {
output_.push_back({Bytecode::kStar, Register(), output});
}
void EmitMov(Register input, Register output) override {
output_.push_back({Bytecode::kMov, input, output});
}
BytecodeRegisterAllocator* allocator() { return register_allocator_; }
BytecodeRegisterOptimizer* optimizer() { return register_optimizer_; }
Register NewTemporary() { return allocator()->NewRegister(); }
void ReleaseTemporaries(Register reg) {
allocator()->ReleaseRegisters(reg.index());
}
size_t write_count() const { return output_.size(); }
const RegisterTransfer& last_written() const { return output_.back(); }
const std::vector<RegisterTransfer>* output() { return &output_; }
private:
BytecodeRegisterAllocator* register_allocator_;
BytecodeRegisterOptimizer* register_optimizer_;
std::vector<RegisterTransfer> output_;
};
// Sanity tests.
TEST_F(BytecodeRegisterOptimizerTest, TemporaryMaterializedForFlush) {
Initialize(1, 1);
Register temp = NewTemporary();
optimizer()->DoStar(temp);
CHECK_EQ(write_count(), 0u);
optimizer()->Flush();
CHECK_EQ(write_count(), 1u);
CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
CHECK_EQ(output()->at(0).output.index(), temp.index());
}
TEST_F(BytecodeRegisterOptimizerTest, TemporaryMaterializedForJump) {
Initialize(1, 1);
Register temp = NewTemporary();
optimizer()->DoStar(temp);
CHECK_EQ(write_count(), 0u);
optimizer()->PrepareForBytecode<Bytecode::kJump, AccumulatorUse::kNone>();
CHECK_EQ(write_count(), 1u);
CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
CHECK_EQ(output()->at(0).output.index(), temp.index());
}
// Basic Register Optimizations
TEST_F(BytecodeRegisterOptimizerTest, TemporaryNotEmitted) {
Initialize(3, 1);
Register parameter = Register::FromParameterIndex(1, 3);
optimizer()->DoLdar(parameter);
CHECK_EQ(write_count(), 0u);
Register temp = NewTemporary();
optimizer()->DoStar(temp);
ReleaseTemporaries(temp);
CHECK_EQ(write_count(), 0u);
optimizer()->PrepareForBytecode<Bytecode::kReturn, AccumulatorUse::kRead>();
CHECK_EQ(output()->at(0).bytecode, Bytecode::kLdar);
CHECK_EQ(output()->at(0).input.index(), parameter.index());
}
TEST_F(BytecodeRegisterOptimizerTest, ReleasedRegisterUsed) {
Initialize(3, 1);
optimizer()->PrepareForBytecode<Bytecode::kLdaSmi, AccumulatorUse::kWrite>();
Register temp0 = NewTemporary();
Register temp1 = NewTemporary();
optimizer()->DoStar(temp1);
CHECK_EQ(write_count(), 0u);
optimizer()->PrepareForBytecode<Bytecode::kLdaSmi, AccumulatorUse::kWrite>();
CHECK_EQ(write_count(), 1u);
CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
CHECK_EQ(output()->at(0).output.index(), temp1.index());
optimizer()->DoMov(temp1, temp0);
CHECK_EQ(write_count(), 1u);
ReleaseTemporaries(temp1);
CHECK_EQ(write_count(), 1u);
optimizer()->DoLdar(temp0);
CHECK_EQ(write_count(), 1u);
optimizer()->PrepareForBytecode<Bytecode::kReturn, AccumulatorUse::kRead>();
CHECK_EQ(write_count(), 2u);
CHECK_EQ(output()->at(1).bytecode, Bytecode::kLdar);
CHECK_EQ(output()->at(1).input.index(), temp1.index());
}
TEST_F(BytecodeRegisterOptimizerTest, ReleasedRegisterNotFlushed) {
Initialize(3, 1);
optimizer()->PrepareForBytecode<Bytecode::kLdaSmi, AccumulatorUse::kWrite>();
Register temp0 = NewTemporary();
Register temp1 = NewTemporary();
optimizer()->DoStar(temp0);
CHECK_EQ(write_count(), 0u);
optimizer()->DoStar(temp1);
CHECK_EQ(write_count(), 0u);
ReleaseTemporaries(temp1);
optimizer()->Flush();
CHECK_EQ(write_count(), 1u);
CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
CHECK_EQ(output()->at(0).output.index(), temp0.index());
}
TEST_F(BytecodeRegisterOptimizerTest, StoresToLocalsImmediate) {
Initialize(3, 1);
Register parameter = Register::FromParameterIndex(1, 3);
optimizer()->DoLdar(parameter);
CHECK_EQ(write_count(), 0u);
Register local = Register(0);
optimizer()->DoStar(local);
CHECK_EQ(write_count(), 1u);
CHECK_EQ(output()->at(0).bytecode, Bytecode::kMov);
CHECK_EQ(output()->at(0).input.index(), parameter.index());
CHECK_EQ(output()->at(0).output.index(), local.index());
optimizer()->PrepareForBytecode<Bytecode::kReturn, AccumulatorUse::kRead>();
CHECK_EQ(write_count(), 2u);
CHECK_EQ(output()->at(1).bytecode, Bytecode::kLdar);
CHECK_EQ(output()->at(1).input.index(), local.index());
}
TEST_F(BytecodeRegisterOptimizerTest, SingleTemporaryNotMaterializedForInput) {
Initialize(3, 1);
Register parameter = Register::FromParameterIndex(1, 3);
Register temp0 = NewTemporary();
Register temp1 = NewTemporary();
optimizer()->DoMov(parameter, temp0);
optimizer()->DoMov(parameter, temp1);
CHECK_EQ(write_count(), 0u);
Register reg = optimizer()->GetInputRegister(temp0);
RegisterList reg_list = optimizer()->GetInputRegisterList(
BytecodeUtils::NewRegisterList(temp0.index(), 1));
CHECK_EQ(write_count(), 0u);
CHECK_EQ(parameter.index(), reg.index());
CHECK_EQ(parameter.index(), reg_list.first_register().index());
CHECK_EQ(1, reg_list.register_count());
}
TEST_F(BytecodeRegisterOptimizerTest, RangeOfTemporariesMaterializedForInput) {
Initialize(3, 1);
Register parameter = Register::FromParameterIndex(1, 3);
Register temp0 = NewTemporary();
Register temp1 = NewTemporary();
optimizer()->PrepareForBytecode<Bytecode::kLdaSmi, AccumulatorUse::kWrite>();
optimizer()->DoStar(temp0);
optimizer()->DoMov(parameter, temp1);
CHECK_EQ(write_count(), 0u);
optimizer()
->PrepareForBytecode<Bytecode::kCallJSRuntime, AccumulatorUse::kWrite>();
RegisterList reg_list = optimizer()->GetInputRegisterList(
BytecodeUtils::NewRegisterList(temp0.index(), 2));
CHECK_EQ(temp0.index(), reg_list.first_register().index());
CHECK_EQ(2, reg_list.register_count());
CHECK_EQ(write_count(), 2u);
CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
CHECK_EQ(output()->at(0).output.index(), temp0.index());
CHECK_EQ(output()->at(1).bytecode, Bytecode::kMov);
CHECK_EQ(output()->at(1).input.index(), parameter.index());
CHECK_EQ(output()->at(1).output.index(), temp1.index());
}
} // namespace interpreter
} // namespace internal
} // namespace v8