v8/test/unittests/compiler/bytecode-analysis-unittest.cc
Santiago Aboy Solanes 18b4b6b93c [interpreter] Merge nested loops that share the same header offset
This CL merges nested loops that share the same header offset with its
parent loop, by not emitting JumpLoop bytecode for these inner loops.
Instead, we generate a Jump to its parent's JumpToHeader (which in
turn can be a JumpLoop or another Jump to its parent's JumpToHeader).

Originally, every loop had a unique first Bytecode to jump to. Since
IterationBody StackChecks are going to become implicit this will no
longer be the case.

As a note, this CL just sets the foundation that the follow-up CLs
will build on top of. Since we have explicit StackChecks, and they
are at the beginning of loops we do not have nested loops as of now.

Bug: v8:10149, v8:9960
Change-Id: I6daee4d2c6d6216f022228c87c4aa74e163997b2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2062390
Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66626}
2020-03-09 12:41:27 +00:00

505 lines
15 KiB
C++

// Copyright 2015 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/init/v8.h"
#include "src/compiler/bytecode-analysis.h"
#include "src/interpreter/bytecode-array-builder.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/bytecode-decoder.h"
#include "src/interpreter/bytecode-label.h"
#include "src/interpreter/control-flow-builders.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/interpreter/bytecode-utils.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
using ToBooleanMode = interpreter::BytecodeArrayBuilder::ToBooleanMode;
class BytecodeAnalysisTest : public TestWithIsolateAndZone {
public:
BytecodeAnalysisTest() = default;
~BytecodeAnalysisTest() override = default;
static void SetUpTestCase() {
CHECK_NULL(save_flags_);
save_flags_ = new SaveFlags();
i::FLAG_ignition_elide_noneffectful_bytecodes = false;
i::FLAG_ignition_reo = false;
TestWithIsolateAndZone::SetUpTestCase();
}
static void TearDownTestCase() {
TestWithIsolateAndZone::TearDownTestCase();
delete save_flags_;
save_flags_ = nullptr;
}
std::string ToLivenessString(const BytecodeLivenessState* liveness) const {
const BitVector& bit_vector = liveness->bit_vector();
std::string out;
out.resize(bit_vector.length());
for (int i = 0; i < bit_vector.length(); ++i) {
if (bit_vector.Contains(i)) {
out[i] = 'L';
} else {
out[i] = '.';
}
}
return out;
}
void EnsureLivenessMatches(
Handle<BytecodeArray> bytecode,
const std::vector<std::pair<std::string, std::string>>&
expected_liveness) {
BytecodeAnalysis analysis(bytecode, zone(), BailoutId::None(), true);
interpreter::BytecodeArrayIterator iterator(bytecode);
for (auto liveness : expected_liveness) {
std::stringstream ss;
ss << std::setw(4) << iterator.current_offset() << " : ";
iterator.PrintTo(ss);
EXPECT_EQ(liveness.first, ToLivenessString(analysis.GetInLivenessFor(
iterator.current_offset())))
<< " at bytecode " << ss.str();
EXPECT_EQ(liveness.second, ToLivenessString(analysis.GetOutLivenessFor(
iterator.current_offset())))
<< " at bytecode " << ss.str();
iterator.Advance();
}
EXPECT_TRUE(iterator.done());
}
private:
static SaveFlags* save_flags_;
DISALLOW_COPY_AND_ASSIGN(BytecodeAnalysisTest);
};
SaveFlags* BytecodeAnalysisTest::save_flags_ = nullptr;
TEST_F(BytecodeAnalysisTest, EmptyBlock) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, SimpleLoad) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L...", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, StoreThenLoad) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back("...L", "L...");
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L...", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, DiamondLoad) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
interpreter::Register reg_1(1);
interpreter::Register reg_2(2);
interpreter::BytecodeLabel ld1_label;
interpreter::BytecodeLabel end_label;
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &ld1_label);
expected_liveness.emplace_back("LLLL", "LLL.");
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L.L.", "..L.");
builder.Jump(&end_label);
expected_liveness.emplace_back("..L.", "..L.");
builder.Bind(&ld1_label);
builder.LoadAccumulatorWithRegister(reg_1);
expected_liveness.emplace_back(".LL.", "..L.");
builder.Bind(&end_label);
builder.LoadAccumulatorWithRegister(reg_2);
expected_liveness.emplace_back("..L.", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, DiamondLookupsAndBinds) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
interpreter::Register reg_1(1);
interpreter::Register reg_2(2);
interpreter::BytecodeLabel ld1_label;
interpreter::BytecodeLabel end_label;
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back(".LLL", "LLLL");
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &ld1_label);
expected_liveness.emplace_back("LLLL", "LLL.");
{
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L...", "...L");
builder.StoreAccumulatorInRegister(reg_2);
expected_liveness.emplace_back("...L", "..L.");
builder.Jump(&end_label);
expected_liveness.emplace_back("..L.", "..L.");
}
builder.Bind(&ld1_label);
{
builder.LoadAccumulatorWithRegister(reg_1);
expected_liveness.emplace_back(".LL.", "..L.");
}
builder.Bind(&end_label);
builder.LoadAccumulatorWithRegister(reg_2);
expected_liveness.emplace_back("..L.", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, SimpleLoop) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
interpreter::Register reg_1(1);
interpreter::Register reg_2(2);
// Kill r0.
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back("..LL", "L.L.");
{
interpreter::LoopBuilder loop_builder(&builder, nullptr, nullptr);
loop_builder.LoopHeader();
builder.LoadUndefined();
expected_liveness.emplace_back("L.L.", "L.LL");
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean,
loop_builder.break_labels()->New());
expected_liveness.emplace_back("L.LL", "L.L.");
// Gen r0.
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L...", "L..L");
// Kill r2.
builder.StoreAccumulatorInRegister(reg_2);
expected_liveness.emplace_back("L..L", "L.L.");
loop_builder.BindContinueTarget();
loop_builder.JumpToHeader(0, nullptr);
expected_liveness.emplace_back("L.L.", "L.L.");
}
// Gen r2.
builder.LoadAccumulatorWithRegister(reg_2);
expected_liveness.emplace_back("..L.", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, TryCatch) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
interpreter::Register reg_1(1);
interpreter::Register reg_context(2);
// Kill r0.
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back(".LLL", "LLL.");
interpreter::TryCatchBuilder try_builder(&builder, nullptr, nullptr,
HandlerTable::CAUGHT);
try_builder.BeginTry(reg_context);
{
// Gen r0.
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("LLL.", ".LLL");
// Kill r0.
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back(".LLL", ".LL.");
builder.CallRuntime(Runtime::kThrow);
expected_liveness.emplace_back(".LL.", ".LLL");
builder.StoreAccumulatorInRegister(reg_0);
// Star can't throw, so doesn't take handler liveness
expected_liveness.emplace_back("...L", "...L");
}
try_builder.EndTry();
expected_liveness.emplace_back("...L", "...L");
// Catch
{
builder.LoadAccumulatorWithRegister(reg_1);
expected_liveness.emplace_back(".L..", "...L");
}
try_builder.EndCatch();
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, DiamondInLoop) {
// For a logic diamond inside a loop, the liveness down one path of the
// diamond should eventually propagate up the other path when the loop is
// reprocessed.
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
{
interpreter::LoopBuilder loop_builder(&builder, nullptr, nullptr);
loop_builder.LoopHeader();
builder.LoadUndefined();
expected_liveness.emplace_back("L...", "L..L");
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean,
loop_builder.break_labels()->New());
expected_liveness.emplace_back("L..L", "L..L");
interpreter::BytecodeLabel ld1_label;
interpreter::BytecodeLabel end_label;
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean, &ld1_label);
expected_liveness.emplace_back("L..L", "L...");
{
builder.Jump(&end_label);
expected_liveness.emplace_back("L...", "L...");
}
builder.Bind(&ld1_label);
{
// Gen r0.
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L...", "L...");
}
builder.Bind(&end_label);
loop_builder.BindContinueTarget();
loop_builder.JumpToHeader(0, nullptr);
expected_liveness.emplace_back("L...", "L...");
}
builder.LoadUndefined();
expected_liveness.emplace_back("....", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, KillingLoopInsideLoop) {
// For a loop inside a loop, the inner loop has to be processed after the
// outer loop has been processed, to ensure that it can propagate the
// information in its header. Consider
//
// 0: do {
// 1: acc = r0;
// 2: acc = r1;
// 3: do {
// 4: r0 = acc;
// 5: break;
// 6: } while(true);
// 7: } while(true);
//
// r0 should should be dead at 3 and 6, while r1 is live throughout. On the
// initial pass, r1 is dead from 3-7. On the outer loop pass, it becomes live
// in 3 and 7 (but not 4-6 because 6 only reads liveness from 3). Only after
// the inner loop pass does it become live in 4-6. It's necessary, however, to
// still process the inner loop when processing the outer loop, to ensure that
// r1 becomes live in 3 (via 5), but r0 stays dead (because of 4).
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
interpreter::Register reg_1(1);
{
interpreter::LoopBuilder loop_builder(&builder, nullptr, nullptr);
loop_builder.LoopHeader();
// Gen r0.
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("LL..", ".L..");
// Gen r1.
builder.LoadAccumulatorWithRegister(reg_1);
expected_liveness.emplace_back(".L..", ".L.L");
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean,
loop_builder.break_labels()->New());
expected_liveness.emplace_back(".L.L", ".L..");
{
interpreter::LoopBuilder inner_loop_builder(&builder, nullptr, nullptr);
inner_loop_builder.LoopHeader();
// Kill r0.
builder.LoadUndefined();
expected_liveness.emplace_back(".L..", ".L.L");
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back(".L.L", "LL.L");
builder.JumpIfTrue(ToBooleanMode::kConvertToBoolean,
inner_loop_builder.break_labels()->New());
expected_liveness.emplace_back("LL.L", "LL..");
inner_loop_builder.BindContinueTarget();
inner_loop_builder.JumpToHeader(1, &loop_builder);
expected_liveness.emplace_back(".L..", ".L..");
}
loop_builder.BindContinueTarget();
loop_builder.JumpToHeader(0, nullptr);
expected_liveness.emplace_back("LL..", "LL..");
}
builder.LoadUndefined();
expected_liveness.emplace_back("....", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
TEST_F(BytecodeAnalysisTest, SuspendPoint) {
interpreter::BytecodeArrayBuilder builder(zone(), 3, 3);
std::vector<std::pair<std::string, std::string>> expected_liveness;
interpreter::Register reg_0(0);
interpreter::Register reg_1(1);
interpreter::Register reg_gen(2);
interpreter::BytecodeJumpTable* gen_jump_table =
builder.AllocateJumpTable(1, 0);
builder.StoreAccumulatorInRegister(reg_gen);
expected_liveness.emplace_back("L..L", "L.LL");
// Note: technically, r0 should be dead here since the resume will write it,
// but in practice the bytecode analysis doesn't bother to special case it,
// since the generator switch is close to the top of the function anyway.
builder.SwitchOnGeneratorState(reg_gen, gen_jump_table);
expected_liveness.emplace_back("L.LL", "L.LL");
builder.StoreAccumulatorInRegister(reg_0);
expected_liveness.emplace_back("..LL", "L.LL");
// Reg 1 is never read, so should be dead.
builder.StoreAccumulatorInRegister(reg_1);
expected_liveness.emplace_back("L.LL", "L.LL");
builder.SuspendGenerator(
reg_gen, interpreter::BytecodeUtils::NewRegisterList(0, 3), 0);
expected_liveness.emplace_back("L.LL", "L.L.");
builder.Bind(gen_jump_table, 0);
builder.ResumeGenerator(reg_gen,
interpreter::BytecodeUtils::NewRegisterList(0, 1));
expected_liveness.emplace_back("L.L.", "L...");
builder.LoadAccumulatorWithRegister(reg_0);
expected_liveness.emplace_back("L...", "...L");
builder.Return();
expected_liveness.emplace_back("...L", "....");
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
EnsureLivenessMatches(bytecode, expected_liveness);
}
} // namespace compiler
} // namespace internal
} // namespace v8