Commit 5c1fc7bd authored by Zhou, Zhiguo's avatar Zhou, Zhiguo Committed by Commit Bot

Add source map support of WasmModule

This CL adds a new class WasmModuleSourceMap for source map support of WasmModule,
which maps C/C++ source code to WASM bytecode. Via this support, V8 can build a
direct map of source code and JITted code and inform profilers of it, thus the
source-code-level profiling information is presented.

Change-Id: I346f6216809ce4f3bf8b27f1e839dd4efdb00ead
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1708029Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Zhiguo Zhou <zhiguo.zhou@intel.com>
Cr-Commit-Position: refs/heads/master@{#63401}
parent 3aa4f05d
......@@ -2952,6 +2952,8 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/wasm-memory.h",
"src/wasm/wasm-module-builder.cc",
"src/wasm/wasm-module-builder.h",
"src/wasm/wasm-module-sourcemap.cc",
"src/wasm/wasm-module-sourcemap.h",
"src/wasm/wasm-module.cc",
"src/wasm/wasm-module.h",
"src/wasm/wasm-objects-inl.h",
......
......@@ -7002,6 +7002,10 @@ typedef void (*WasmStreamingCallback)(const FunctionCallbackInfo<Value>&);
// --- Callback for checking if WebAssembly threads are enabled ---
typedef bool (*WasmThreadsEnabledCallback)(Local<Context> context);
// --- Callback for loading source map file for WASM profiling support
typedef Local<String> (*WasmLoadSourceMapCallback)(Isolate* isolate,
const char* name);
// --- Garbage Collection Callbacks ---
/**
......@@ -8661,6 +8665,8 @@ class V8_EXPORT Isolate {
void SetWasmThreadsEnabledCallback(WasmThreadsEnabledCallback callback);
void SetWasmLoadSourceMapCallback(WasmLoadSourceMapCallback callback);
/**
* Check if V8 is dead and therefore unusable. This is the case after
* fatal errors such as out-of-memory situations.
......
......@@ -8528,6 +8528,9 @@ CALLBACK_SETTER(WasmStreamingCallback, WasmStreamingCallback,
CALLBACK_SETTER(WasmThreadsEnabledCallback, WasmThreadsEnabledCallback,
wasm_threads_enabled_callback)
CALLBACK_SETTER(WasmLoadSourceMapCallback, WasmLoadSourceMapCallback,
wasm_load_source_map_callback)
void Isolate::AddNearHeapLimitCallback(v8::NearHeapLimitCallback callback,
void* data) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
......
......@@ -1944,6 +1944,9 @@ Local<Context> Shell::CreateEvaluationContext(Isolate* isolate) {
EscapableHandleScope handle_scope(isolate);
Local<Context> context = Context::New(isolate, nullptr, global_template);
DCHECK(!context.IsEmpty());
if (i::FLAG_perf_prof_annotate_wasm) {
isolate->SetWasmLoadSourceMapCallback(ReadFile);
}
InitializeModuleEmbedderData(context);
if (options.include_arguments) {
Context::Scope scope(context);
......
......@@ -404,6 +404,7 @@ using DebugObjectCache = std::vector<Handle<HeapObject>>;
V(ExtensionCallback, wasm_instance_callback, &NoExtension) \
V(WasmStreamingCallback, wasm_streaming_callback, nullptr) \
V(WasmThreadsEnabledCallback, wasm_threads_enabled_callback, nullptr) \
V(WasmLoadSourceMapCallback, wasm_load_source_map_callback, nullptr) \
/* State for Relocatable. */ \
V(Relocatable*, relocatable_top, nullptr) \
V(DebugObjectCache*, string_stream_debug_object_cache, nullptr) \
......
......@@ -1455,6 +1455,9 @@ DEFINE_BOOL(perf_basic_prof_only_functions, false,
DEFINE_IMPLICATION(perf_basic_prof_only_functions, perf_basic_prof)
DEFINE_BOOL(perf_prof, false,
"Enable perf linux profiler (experimental annotate support).")
DEFINE_BOOL(perf_prof_annotate_wasm, false,
"Used with --perf-prof, load wasm source map and provide annotate "
"support (experimental).")
DEFINE_NEG_IMPLICATION(perf_prof, compact_code_space)
// TODO(v8:8462) Remove implication once perf supports remapping.
DEFINE_NEG_IMPLICATION(perf_prof, write_protect_code_memory)
......
......@@ -25,6 +25,7 @@
#include "src/wasm/function-compiler.h"
#include "src/wasm/jump-table-assembler.h"
#include "src/wasm/wasm-import-wrapper-cache.h"
#include "src/wasm/wasm-module-sourcemap.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
......@@ -181,6 +182,19 @@ void WasmCode::LogCode(Isolate* isolate) const {
WireBytesRef name_ref =
native_module()->module()->LookupFunctionName(wire_bytes, index());
WasmName name_vec = wire_bytes.GetNameOrNull(name_ref);
const std::string& source_map_url = native_module()->module()->source_map_url;
auto load_wasm_source_map = isolate->wasm_load_source_map_callback();
auto source_map = native_module()->GetWasmSourceMap();
if (!source_map && !source_map_url.empty() && load_wasm_source_map) {
HandleScope scope(isolate);
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
Local<v8::String> source_map_str =
load_wasm_source_map(v8_isolate, source_map_url.c_str());
native_module()->SetWasmSourceMap(
base::make_unique<WasmModuleSourceMap>(v8_isolate, source_map_str));
}
if (!name_vec.empty()) {
HandleScope scope(isolate);
MaybeHandle<String> maybe_name = isolate->factory()->NewStringFromUtf8(
......@@ -1096,6 +1110,15 @@ bool NativeModule::HasCode(uint32_t index) const {
return code_table_[index - module_->num_imported_functions] != nullptr;
}
void NativeModule::SetWasmSourceMap(
std::unique_ptr<WasmModuleSourceMap> source_map) {
source_map_ = std::move(source_map);
}
WasmModuleSourceMap* NativeModule::GetWasmSourceMap() const {
return source_map_.get();
}
WasmCode* NativeModule::CreateEmptyJumpTableInRegion(
uint32_t jump_table_size, base::AddressRegion region) {
// Only call this if we really need a jump table.
......
......@@ -23,6 +23,7 @@
#include "src/wasm/compilation-environment.h"
#include "src/wasm/wasm-features.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-module-sourcemap.h"
#include "src/wasm/wasm-tier.h"
namespace v8 {
......@@ -405,6 +406,9 @@ class V8_EXPORT_PRIVATE NativeModule final {
WasmCode* GetCode(uint32_t index) const;
bool HasCode(uint32_t index) const;
void SetWasmSourceMap(std::unique_ptr<WasmModuleSourceMap> source_map);
WasmModuleSourceMap* GetWasmSourceMap() const;
Address runtime_stub_entry(WasmCode::RuntimeStubId index) const {
DCHECK_LT(index, WasmCode::kRuntimeStubCount);
Address entry_address = runtime_stub_entries_[index];
......@@ -570,6 +574,8 @@ class V8_EXPORT_PRIVATE NativeModule final {
// tasks can keep this alive.
std::shared_ptr<const WasmModule> module_;
std::unique_ptr<WasmModuleSourceMap> source_map_;
// Wire bytes, held in a shared_ptr so they can be kept alive by the
// {WireBytesStorage}, held by background compile tasks.
std::shared_ptr<OwnedVector<const uint8_t>> wire_bytes_;
......
// Copyright 2019 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/wasm/wasm-module-sourcemap.h"
#include <algorithm>
#include "include/v8.h"
#include "src/api/api.h"
#include "src/base/vlq-base64.h"
namespace v8 {
namespace internal {
namespace wasm {
WasmModuleSourceMap::WasmModuleSourceMap(v8::Isolate* v8_isolate,
v8::Local<v8::String> src_map_str) {
v8::HandleScope scope(v8_isolate);
v8::Local<v8::Context> context = v8::Context::New(v8_isolate);
v8::Local<v8::Value> src_map_value;
if (!v8::JSON::Parse(context, src_map_str).ToLocal(&src_map_value)) return;
v8::Local<v8::Object> src_map_obj =
v8::Local<v8::Object>::Cast(src_map_value);
v8::Local<v8::Value> version_value, sources_value, mappings_value;
bool has_valid_version =
src_map_obj
->Get(context,
v8::String::NewFromUtf8(v8_isolate, "version").ToLocalChecked())
.ToLocal(&version_value) &&
version_value->IsUint32();
uint32_t version = 0;
if (!has_valid_version || !version_value->Uint32Value(context).To(&version) ||
version != 3u)
return;
bool has_valid_sources =
src_map_obj
->Get(context,
v8::String::NewFromUtf8(v8_isolate, "sources").ToLocalChecked())
.ToLocal(&sources_value) &&
sources_value->IsArray();
if (!has_valid_sources) return;
v8::Local<v8::Object> sources_arr =
v8::Local<v8::Object>::Cast(sources_value);
v8::Local<v8::Value> sources_len_value;
if (!sources_arr
->Get(context,
v8::String::NewFromUtf8(v8_isolate, "length").ToLocalChecked())
.ToLocal(&sources_len_value))
return;
uint32_t sources_len = 0;
if (!sources_len_value->Uint32Value(context).To(&sources_len)) return;
for (uint32_t i = 0; i < sources_len; ++i) {
v8::Local<v8::Value> file_name_value;
if (!sources_arr->Get(context, i).ToLocal(&file_name_value) ||
!file_name_value->IsString())
return;
v8::Local<v8::String> file_name =
v8::Local<v8::String>::Cast(file_name_value);
auto file_name_sz = file_name->Utf8Length(v8_isolate);
std::unique_ptr<char[]> file_name_buf(new char[file_name_sz + 1]);
file_name->WriteUtf8(v8_isolate, file_name_buf.get());
file_name_buf.get()[file_name_sz] = '\0';
filenames.emplace_back(file_name_buf.get());
}
bool has_valid_mappings =
src_map_obj
->Get(
context,
v8::String::NewFromUtf8(v8_isolate, "mappings").ToLocalChecked())
.ToLocal(&mappings_value) &&
mappings_value->IsString();
if (!has_valid_mappings) return;
v8::Local<v8::String> mappings = v8::Local<v8::String>::Cast(mappings_value);
int mappings_sz = mappings->Utf8Length(v8_isolate);
std::unique_ptr<char[]> mappings_buf(new char[mappings_sz + 1]);
mappings->WriteUtf8(v8_isolate, mappings_buf.get());
mappings_buf.get()[mappings_sz] = '\0';
valid_ = DecodeMapping(mappings_buf.get());
}
size_t WasmModuleSourceMap::GetSourceLine(size_t wasm_offset) const {
std::vector<std::size_t>::const_iterator up =
std::upper_bound(offsets.begin(), offsets.end(), wasm_offset);
CHECK_NE(offsets.begin(), up);
size_t source_idx = up - offsets.begin() - 1;
return source_row[source_idx];
}
std::string WasmModuleSourceMap::GetFilename(size_t wasm_offset) const {
std::vector<size_t>::const_iterator up =
std::upper_bound(offsets.begin(), offsets.end(), wasm_offset);
CHECK_NE(offsets.begin(), up);
size_t offset_idx = up - offsets.begin() - 1;
size_t filename_idx = file_idxs[offset_idx];
return filenames[filename_idx];
}
bool WasmModuleSourceMap::HasSource(size_t start, size_t end) const {
return start <= *(offsets.end() - 1) && end > *offsets.begin();
}
bool WasmModuleSourceMap::HasValidEntry(size_t start, size_t addr) const {
std::vector<size_t>::const_iterator up =
std::upper_bound(offsets.begin(), offsets.end(), addr);
if (up == offsets.begin()) return false;
size_t offset_idx = up - offsets.begin() - 1;
size_t entry_offset = offsets[offset_idx];
if (entry_offset < start) return false;
return true;
}
bool WasmModuleSourceMap::DecodeMapping(const std::string& s) {
size_t pos = 0, gen_col = 0, file_idx = 0, ori_line = 0;
int32_t qnt = 0;
while (pos < s.size()) {
// Skip redundant commas.
if (s[pos] == ',') {
++pos;
continue;
}
if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
std::numeric_limits<int32_t>::min())
return false;
gen_col += qnt;
if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
std::numeric_limits<int32_t>::min())
return false;
file_idx += qnt;
if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
std::numeric_limits<int32_t>::min())
return false;
ori_line += qnt;
// Column number in source file is always 0 in source map generated by
// Emscripten. We just decode this value without further usage of it.
if ((qnt = base::VLQBase64Decode(s.c_str(), s.size(), &pos)) ==
std::numeric_limits<int32_t>::min())
return false;
if (pos < s.size() && s[pos] != ',') return false;
pos++;
file_idxs.push_back(file_idx);
source_row.push_back(ori_line);
offsets.push_back(gen_col);
}
return true;
}
} // namespace wasm
} // namespace internal
} // namespace v8
// Copyright 2019 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_WASM_WASM_MODULE_SOURCEMAP_H_
#define V8_WASM_WASM_MODULE_SOURCEMAP_H_
#include <string>
#include <vector>
#include "include/v8.h"
#include "src/base/macros.h"
namespace v8 {
namespace internal {
namespace wasm {
// The class is for decoding and managing source map generated by a WebAssembly
// toolchain (e.g. Emscripten). This implementation mostly complies with the
// specification (https://sourcemaps.info/spec.html), with the following
// accommodations:
// 1. "names" field is an empty array in current source maps of WASM, hence it
// is not handled;
// 2. The semicolons divides "mappings" field into groups, each of which
// represents a line in the generated code. As *.wasm is in binary format, there
// is one "line" of generated code, and ";" is treated as illegal symbol in
// "mappings".
// 3. Though each comma-separated section may contains 1, 4 or 5 fields, we only
// consider "mappings" with 4 fields, i.e. start line of generated code, index
// into "sources" fields, start line of source code and start column of source
// code.
class V8_EXPORT_PRIVATE WasmModuleSourceMap {
public:
WasmModuleSourceMap(v8::Isolate* v8_isolate,
v8::Local<v8::String> src_map_str);
// Member valid_ is true only if the source map complies with specification
// and can be correctly decoded.
bool IsValid() const { return valid_; }
// Given a function located at [start, end) in WASM Module, this function
// checks if this function has its corresponding source code.
bool HasSource(size_t start, size_t end) const;
// Given a function's base address start and an address addr within, this
// function checks if the address can be mapped to an offset in this function.
// For example, we have the following memory layout for WASM functions, foo
// and bar, and O1, O2, O3 and O4 are the decoded offsets of source map:
//
// O1 --- O2 ----- O3 ----- O4
// --->|<-foo->|<--bar->|<-----
// --------------A-------------
//
// Address A of function bar should be mapped to its nearest lower offset, O2.
// However, O2 is an address of function foo, thus, this mapping is treated as
// invalid.
bool HasValidEntry(size_t start, size_t addr) const;
// This function is responsible for looking up an offset's corresponding line
// number in source file. It should only be called when current function is
// checked with IsValid, HasSource and HasValidEntry.
size_t GetSourceLine(size_t wasm_offset) const;
// This function is responsible for looking up an offset's corresponding
// source file name. It should only be called when current function is checked
// with IsValid, HasSource and HasValidEntry.
std::string GetFilename(size_t wasm_offset) const;
private:
std::vector<size_t> offsets;
std::vector<std::string> filenames;
std::vector<size_t> file_idxs;
std::vector<size_t> source_row;
// As column number in source file is always 0 in source map generated by
// WebAssembly toolchain, we will not store this value.
bool valid_ = false;
bool DecodeMapping(const std::string& s);
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_WASM_MODULE_SOURCEMAP_H_
......@@ -224,6 +224,7 @@ v8_source_set("unittests_sources") {
"wasm/wasm-compiler-unittest.cc",
"wasm/wasm-macro-gen-unittest.cc",
"wasm/wasm-module-builder-unittest.cc",
"wasm/wasm-module-sourcemap-unittest.cc",
"wasm/wasm-opcodes-unittest.cc",
"wasm/wasm-text-unittest.cc",
"zone/zone-allocator-unittest.cc",
......
This diff is collapsed.
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