7be64db45f
This is a reland of cf93071c91
Original change's description:
> [interpreter] Short Star bytecode
>
> Design doc:
> https://docs.google.com/document/d/1g_NExMT78II_KnIYNa9MvyPYIj23qAiFUEsyemY5KRk/edit
>
> This change adds 16 new interpreter opcodes, kStar0 through kStar15, so
> that we can use a single byte to represent the common operation of
> storing to a low-numbered register. This generally reduces the quantity
> of bytecode generated on web sites by 8-9%.
>
> In order to not degrade speed, a couple of other changes are required:
>
> The existing lookahead logic to check for Star after certain other
> bytecode handlers is updated to check for these new short Star codes
> instead. Furthermore, that lookahead logic is updated to contain its own
> copy of the dispatch jump rather than merging control flow with the
> lookahead-failed case, to improve branch prediction.
>
> A bunch of constants use bytecode size in bytes as a proxy for the size
> or complexity of a function, and are adjusted downward proportionally to
> the decrease in generated bytecode size.
>
> Other small drive-by fix: update generate-bytecode-expectations to emit
> \n instead of \r\n on Windows.
>
> Change-Id: I6307c2b0f5794a3a1088bb0fb94f6e1615441ed5
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2641180
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
> Cr-Commit-Position: refs/heads/master@{#72773}
Change-Id: I1afb670c25694498b3989de615858f984a8c7f6f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2698057
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: Mythri Alle <mythria@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72821}
502 lines
15 KiB
C++
502 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;
|
|
BytecodeAnalysisTest(const BytecodeAnalysisTest&) = delete;
|
|
BytecodeAnalysisTest& operator=(const BytecodeAnalysisTest&) = delete;
|
|
|
|
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(), BytecodeOffset::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_;
|
|
};
|
|
|
|
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;
|
|
|
|
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_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
|