[Liftoff] Emit and test debug side table

This adds a method to generate the debug side table via Liftoff, and
adds first tests that check that the number of entries is as expected.
These tests will be extended in a follow-up CL to test the actual
content of the debug side table.

R=mstarzinger@chromium.org

Bug: v8:10019
Change-Id: I393ffabed3408463ffba232a66e2dffd7dd74f15
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1954390
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65370}
This commit is contained in:
Clemens Backes 2019-12-06 16:41:30 +01:00 committed by Commit Bot
parent 7cb7f77549
commit 31e9ebeea7
4 changed files with 208 additions and 60 deletions

View File

@ -245,11 +245,13 @@ class LiftoffCompiler {
LiftoffCompiler(compiler::CallDescriptor* call_descriptor,
CompilationEnv* env, Zone* compilation_zone,
std::unique_ptr<AssemblerBuffer> buffer)
std::unique_ptr<AssemblerBuffer> buffer,
DebugSideTableBuilder* debug_sidetable_builder)
: asm_(std::move(buffer)),
descriptor_(
GetLoweredCallDescriptor(compilation_zone, call_descriptor)),
env_(env),
debug_sidetable_builder_(debug_sidetable_builder),
compilation_zone_(compilation_zone),
safepoint_table_builder_(compilation_zone_) {}
@ -2172,8 +2174,7 @@ class LiftoffCompiler {
compiler::CallDescriptor* const descriptor_;
CompilationEnv* const env_;
// TODO(clemensb): Provide a DebugSideTableBuilder here.
DebugSideTableBuilder* const debug_sidetable_builder_ = nullptr;
DebugSideTableBuilder* const debug_sidetable_builder_;
LiftoffBailoutReason bailout_reason_ = kSuccess;
std::vector<OutOfLineCode> out_of_line_code_;
SourcePositionTableBuilder source_position_table_builder_;
@ -2225,7 +2226,6 @@ WasmCompilationResult ExecuteLiftoffCompilation(AccountingAllocator* allocator,
"body_size", func_body_size);
Zone zone(allocator, "LiftoffCompilationZone");
const WasmModule* module = env ? env->module : nullptr;
auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, func_body.sig);
base::Optional<TimedHistogramScope> liftoff_compile_time_scope(
base::in_place, counters->liftoff_compile_time());
@ -2235,9 +2235,11 @@ WasmCompilationResult ExecuteLiftoffCompilation(AccountingAllocator* allocator,
// generation.
std::unique_ptr<wasm::WasmInstructionBuffer> instruction_buffer =
wasm::WasmInstructionBuffer::New(128 + code_size_estimate * 4 / 3);
DebugSideTableBuilder* const kNoDebugSideTable = nullptr;
WasmFullDecoder<Decoder::kValidate, LiftoffCompiler> decoder(
&zone, module, env->enabled_features, detected, func_body,
call_descriptor, env, &zone, instruction_buffer->CreateView());
&zone, env->module, env->enabled_features, detected, func_body,
call_descriptor, env, &zone, instruction_buffer->CreateView(),
kNoDebugSideTable);
decoder.Decode();
liftoff_compile_time_scope.reset();
LiftoffCompiler* compiler = &decoder.interface();
@ -2272,6 +2274,24 @@ WasmCompilationResult ExecuteLiftoffCompilation(AccountingAllocator* allocator,
return result;
}
DebugSideTable GenerateLiftoffDebugSideTable(AccountingAllocator* allocator,
CompilationEnv* env,
const FunctionBody& func_body) {
Zone zone(allocator, "LiftoffDebugSideTableZone");
auto call_descriptor = compiler::GetWasmCallDescriptor(&zone, func_body.sig);
DebugSideTableBuilder debug_sidetable_builder;
WasmFeatures detected;
WasmFullDecoder<Decoder::kValidate, LiftoffCompiler> decoder(
&zone, env->module, env->enabled_features, &detected, func_body,
call_descriptor, env, &zone,
NewAssemblerBuffer(AssemblerBase::kDefaultBufferSize),
&debug_sidetable_builder);
decoder.Decode();
DCHECK(decoder.ok());
DCHECK(!decoder.interface().did_bailout());
return debug_sidetable_builder.GenerateDebugSideTable();
}
#undef __
#undef TRACE
#undef WASM_INSTANCE_OBJECT_FIELD_OFFSET

View File

@ -16,6 +16,7 @@ class Counters;
namespace wasm {
struct CompilationEnv;
class DebugSideTable;
struct FunctionBody;
class WasmFeatures;
@ -55,6 +56,9 @@ V8_EXPORT_PRIVATE WasmCompilationResult ExecuteLiftoffCompilation(
AccountingAllocator*, CompilationEnv*, const FunctionBody&, int func_index,
Counters*, WasmFeatures* detected_features);
V8_EXPORT_PRIVATE DebugSideTable GenerateLiftoffDebugSideTable(
AccountingAllocator*, CompilationEnv*, const FunctionBody&);
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -9,6 +9,7 @@
#include <vector>
#include "src/base/logging.h"
#include "src/base/macros.h"
namespace v8 {
namespace internal {
@ -67,6 +68,10 @@ class DebugSideTable {
std::vector<Constant> constants_;
};
// Technically it would be fine to copy this class, but there should not be a
// reason to do so, hence mark it move only.
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(DebugSideTable);
explicit DebugSideTable(std::vector<Entry> entries)
: entries_(std::move(entries)) {
DCHECK(
@ -80,6 +85,8 @@ class DebugSideTable {
return &*it;
}
size_t num_entries() const { return entries_.size(); }
private:
struct EntryPositionLess {
bool operator()(const Entry& a, const Entry& b) const {

View File

@ -13,93 +13,210 @@ namespace internal {
namespace wasm {
namespace {
void CheckDeterministicCompilation(
std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> raw_function_bytes) {
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope handle_scope(isolate);
Zone zone(isolate->allocator(), ZONE_NAME);
// Set up a module builder for the test module.
TestingModuleBuilder module_builder(&zone, nullptr, ExecutionTier::kLiftoff,
kNoRuntimeExceptionSupport, kNoLowerSimd);
std::vector<ValueType> sig_types(return_types);
sig_types.insert(sig_types.end(), param_types.begin(), param_types.end());
FunctionSig sig(return_types.size(), param_types.size(), sig_types.data());
module_builder.AddSignature(&sig);
// Add a table of length 1, for indirect calls.
module_builder.AddIndirectFunctionTable(nullptr, 1);
int func_index =
module_builder.AddFunction(&sig, "f", TestingModuleBuilder::kWasm);
WasmFunction* function = module_builder.GetFunctionAt(func_index);
// Build the function bytes by prepending the locals decl and appending an
// "end" opcode.
std::vector<uint8_t> function_bytes;
function_bytes.push_back(WASM_NO_LOCALS);
function_bytes.insert(function_bytes.end(), raw_function_bytes.begin(),
raw_function_bytes.end());
function_bytes.push_back(WASM_END);
FunctionBody func_body{&sig, 0, function_bytes.data(),
function_bytes.data() + function_bytes.size()};
function->code = {module_builder.AddBytes(VectorOf(function_bytes)),
static_cast<uint32_t>(function_bytes.size())};
class LiftoffCompileEnvironment {
public:
LiftoffCompileEnvironment()
: isolate_(CcTest::InitIsolateOnce()),
handle_scope_(isolate_),
zone_(isolate_->allocator(), ZONE_NAME),
module_builder_(&zone_, nullptr, ExecutionTier::kLiftoff,
kRuntimeExceptionSupport, kNoLowerSimd) {
// Add a table of length 1, for indirect calls.
module_builder_.AddIndirectFunctionTable(nullptr, 1);
}
// Now compile the function with Liftoff two times.
auto env = module_builder.CreateCompilationEnv();
WasmFeatures detected1;
WasmFeatures detected2;
WasmCompilationResult result1 = ExecuteLiftoffCompilation(
isolate->allocator(), &env, func_body, function->func_index,
isolate->counters(), &detected1);
WasmCompilationResult result2 = ExecuteLiftoffCompilation(
isolate->allocator(), &env, func_body, function->func_index,
isolate->counters(), &detected2);
struct TestFunction {
OwnedVector<uint8_t> body_bytes;
WasmFunction* function;
FunctionBody body;
};
CHECK(result1.succeeded());
CHECK(result2.succeeded());
void CheckDeterministicCompilation(
std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> raw_function_bytes) {
auto test_func = AddFunction(return_types, param_types, raw_function_bytes);
// Check that the generated code matches.
auto code1 = VectorOf(result1.code_desc.buffer, result1.code_desc.instr_size);
auto code2 = VectorOf(result2.code_desc.buffer, result2.code_desc.instr_size);
CHECK_EQ(code1, code2);
// TODO(clemensb): Add operator== to WasmFeatures and enable this check.
// CHECK_EQ(detected1, detected2);
}
// Now compile the function with Liftoff two times.
CompilationEnv env = module_builder_.CreateCompilationEnv();
WasmFeatures detected1;
WasmFeatures detected2;
WasmCompilationResult result1 = ExecuteLiftoffCompilation(
isolate_->allocator(), &env, test_func.body,
test_func.function->func_index, isolate_->counters(), &detected1);
WasmCompilationResult result2 = ExecuteLiftoffCompilation(
isolate_->allocator(), &env, test_func.body,
test_func.function->func_index, isolate_->counters(), &detected2);
CHECK(result1.succeeded());
CHECK(result2.succeeded());
// Check that the generated code matches.
auto code1 =
VectorOf(result1.code_desc.buffer, result1.code_desc.instr_size);
auto code2 =
VectorOf(result2.code_desc.buffer, result2.code_desc.instr_size);
CHECK_EQ(code1, code2);
CHECK_EQ(detected1, detected2);
}
DebugSideTable GenerateDebugSideTable(
std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> raw_function_bytes) {
auto test_func = AddFunction(return_types, param_types, raw_function_bytes);
CompilationEnv env = module_builder_.CreateCompilationEnv();
return GenerateLiftoffDebugSideTable(CcTest::i_isolate()->allocator(), &env,
test_func.body);
}
private:
OwnedVector<uint8_t> GenerateFunctionBody(
std::initializer_list<uint8_t> raw_function_bytes) {
// Build the function bytes by prepending the locals decl and appending an
// "end" opcode.
OwnedVector<uint8_t> function_bytes =
OwnedVector<uint8_t>::New(raw_function_bytes.size() + 2);
function_bytes[0] = WASM_NO_LOCALS;
std::copy(raw_function_bytes.begin(), raw_function_bytes.end(),
&function_bytes[1]);
function_bytes[raw_function_bytes.size() + 1] = WASM_END;
return function_bytes;
}
FunctionSig* AddSig(std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types) {
ValueType* storage =
zone_.NewArray<ValueType>(return_types.size() + param_types.size());
std::copy(return_types.begin(), return_types.end(), storage);
std::copy(param_types.begin(), param_types.end(),
storage + return_types.size());
FunctionSig* sig = new (&zone_)
FunctionSig{return_types.size(), param_types.size(), storage};
module_builder_.AddSignature(sig);
return sig;
}
TestFunction AddFunction(std::initializer_list<ValueType> return_types,
std::initializer_list<ValueType> param_types,
std::initializer_list<uint8_t> raw_function_bytes) {
OwnedVector<uint8_t> function_bytes =
GenerateFunctionBody(raw_function_bytes);
FunctionSig* sig = AddSig(return_types, param_types);
int func_index =
module_builder_.AddFunction(sig, "f", TestingModuleBuilder::kWasm);
WasmFunction* function = module_builder_.GetFunctionAt(func_index);
function->code = {module_builder_.AddBytes(function_bytes.as_vector()),
static_cast<uint32_t>(function_bytes.size())};
FunctionBody body{function->sig, 0, function_bytes.begin(),
function_bytes.end()};
return {std::move(function_bytes), function, body};
}
Isolate* isolate_;
HandleScope handle_scope_;
Zone zone_;
TestingModuleBuilder module_builder_;
};
} // namespace
TEST(Liftoff_deterministic_simple) {
CheckDeterministicCompilation(
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))});
}
TEST(Liftoff_deterministic_call) {
CheckDeterministicCompilation(
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_FUNCTION(0, WASM_GET_LOCAL(0)),
WASM_GET_LOCAL(0))});
}
TEST(Liftoff_deterministic_indirect_call) {
CheckDeterministicCompilation(
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_INDIRECT(0, WASM_GET_LOCAL(0), WASM_I32V_1(47)),
WASM_GET_LOCAL(0))});
}
TEST(Liftoff_deterministic_loop) {
CheckDeterministicCompilation(
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32},
{WASM_LOOP(WASM_BR_IF(0, WASM_GET_LOCAL(0))), WASM_GET_LOCAL(0)});
}
TEST(Liftoff_deterministic_trap) {
CheckDeterministicCompilation(
LiftoffCompileEnvironment env;
env.CheckDeterministicCompilation(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_DIVS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))});
}
TEST(Liftoff_debug_side_table_simple) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))});
// 1 entry for the stack check.
CHECK_EQ(1, debug_side_table.num_entries());
}
TEST(Liftoff_debug_side_table_call) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_FUNCTION(0, WASM_GET_LOCAL(0)),
WASM_GET_LOCAL(0))});
// 2 entries:
// - stack check
// - call
CHECK_EQ(2, debug_side_table.num_entries());
}
TEST(Liftoff_debug_side_table_indirect_call) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_I32_ADD(WASM_CALL_INDIRECT(0, WASM_I32V_1(47), WASM_GET_LOCAL(0)),
WASM_GET_LOCAL(0))});
// 4 entries:
// - stack check
// - trap for invalid function index
// - trap for signature mismatch
// - indirect call
CHECK_EQ(4, debug_side_table.num_entries());
}
TEST(Liftoff_debug_side_table_loop) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32},
{WASM_LOOP(WASM_BR_IF(0, WASM_GET_LOCAL(0))), WASM_GET_LOCAL(0)});
// 2 entries:
// - stack check at function entry
// - stack check in loop header
CHECK_EQ(2, debug_side_table.num_entries());
}
TEST(Liftoff_debug_side_table_trap) {
LiftoffCompileEnvironment env;
auto debug_side_table = env.GenerateDebugSideTable(
{kWasmI32}, {kWasmI32, kWasmI32},
{WASM_I32_DIVS(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))});
// 3 entries:
// - stack check
// - trap for division by zero
// - trap for division result unrepresentable
CHECK_EQ(3, debug_side_table.num_entries());
}
} // namespace wasm
} // namespace internal
} // namespace v8