blob: 6af13fe8cf29c4ab10fab2107d10de7df8722f10 [file] [log] [blame]
// Copyright 2016 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/diagnostics/perf-jit.h"
#include "src/common/assert-scope.h"
#include "src/flags/flags.h"
// Only compile the {LinuxPerfJitLogger} on Linux.
#if V8_OS_LINUX
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <memory>
#include "src/base/platform/wrappers.h"
#include "src/codegen/assembler.h"
#include "src/codegen/source-position-table.h"
#include "src/diagnostics/eh-frame.h"
#include "src/objects/code-kind.h"
#include "src/objects/objects-inl.h"
#include "src/objects/shared-function-info.h"
#include "src/snapshot/embedded/embedded-data.h"
#include "src/utils/ostreams.h"
#if V8_ENABLE_WEBASSEMBLY
#include "src/wasm/wasm-code-manager.h"
#endif // V8_ENABLE_WEBASSEMBLY
namespace v8 {
namespace internal {
base::LazyRecursiveMutex& GetFileMutex() {
static base::LazyRecursiveMutex file_mutex;
return file_mutex;
}
struct PerfJitHeader {
uint32_t magic_;
uint32_t version_;
uint32_t size_;
uint32_t elf_mach_target_;
uint32_t reserved_;
uint32_t process_id_;
uint64_t time_stamp_;
uint64_t flags_;
static const uint32_t kMagic = 0x4A695444;
static const uint32_t kVersion = 1;
};
struct PerfJitBase {
enum PerfJitEvent {
kLoad = 0,
kMove = 1,
kDebugInfo = 2,
kClose = 3,
kUnwindingInfo = 4
};
uint32_t event_;
uint32_t size_;
uint64_t time_stamp_;
};
struct PerfJitCodeLoad : PerfJitBase {
uint32_t process_id_;
uint32_t thread_id_;
uint64_t vma_;
uint64_t code_address_;
uint64_t code_size_;
uint64_t code_id_;
};
struct PerfJitDebugEntry {
uint64_t address_;
int line_number_;
int column_;
// Followed by null-terminated name or \0xFF\0 if same as previous.
};
struct PerfJitCodeDebugInfo : PerfJitBase {
uint64_t address_;
uint64_t entry_count_;
// Followed by entry_count_ instances of PerfJitDebugEntry.
};
struct PerfJitCodeUnwindingInfo : PerfJitBase {
uint64_t unwinding_size_;
uint64_t eh_frame_hdr_size_;
uint64_t mapped_size_;
// Followed by size_ - sizeof(PerfJitCodeUnwindingInfo) bytes of data.
};
const char LinuxPerfJitLogger::kFilenameFormatString[] = "%s/jit-%d.dump";
// Extra padding for the PID in the filename
const int LinuxPerfJitLogger::kFilenameBufferPadding = 16;
static const char kStringTerminator[] = {'\0'};
// The following static variables are protected by
// GetFileMutex().
int LinuxPerfJitLogger::process_id_ = 0;
uint64_t LinuxPerfJitLogger::reference_count_ = 0;
void* LinuxPerfJitLogger::marker_address_ = nullptr;
uint64_t LinuxPerfJitLogger::code_index_ = 0;
FILE* LinuxPerfJitLogger::perf_output_handle_ = nullptr;
void LinuxPerfJitLogger::OpenJitDumpFile() {
// Open the perf JIT dump file.
perf_output_handle_ = nullptr;
size_t bufferSize = strlen(v8_flags.perf_prof_path) +
sizeof(kFilenameFormatString) + kFilenameBufferPadding;
base::ScopedVector<char> perf_dump_name(bufferSize);
int size = SNPrintF(perf_dump_name, kFilenameFormatString,
v8_flags.perf_prof_path.value(), process_id_);
CHECK_NE(size, -1);
int fd = open(perf_dump_name.begin(), O_CREAT | O_TRUNC | O_RDWR, 0666);
if (fd == -1) return;
// If --perf-prof-delete-file is given, unlink the file right after opening
// it. This keeps the file handle to the file valid. This only works on Linux,
// which is the only platform supported for --perf-prof anyway.
if (v8_flags.perf_prof_delete_file)
CHECK_EQ(0, unlink(perf_dump_name.begin()));
marker_address_ = OpenMarkerFile(fd);
if (marker_address_ == nullptr) return;
perf_output_handle_ = fdopen(fd, "w+");
if (perf_output_handle_ == nullptr) return;
setvbuf(perf_output_handle_, nullptr, _IOFBF, kLogBufferSize);
}
void LinuxPerfJitLogger::CloseJitDumpFile() {
if (perf_output_handle_ == nullptr) return;
base::Fclose(perf_output_handle_);
perf_output_handle_ = nullptr;
}
void* LinuxPerfJitLogger::OpenMarkerFile(int fd) {
long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
if (page_size == -1) return nullptr;
// Mmap the file so that there is a mmap record in the perf_data file.
//
// The map must be PROT_EXEC to ensure it is not ignored by perf record.
void* marker_address =
mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
return (marker_address == MAP_FAILED) ? nullptr : marker_address;
}
void LinuxPerfJitLogger::CloseMarkerFile(void* marker_address) {
if (marker_address == nullptr) return;
long page_size = sysconf(_SC_PAGESIZE); // NOLINT(runtime/int)
if (page_size == -1) return;
munmap(marker_address, page_size);
}
LinuxPerfJitLogger::LinuxPerfJitLogger(Isolate* isolate)
: CodeEventLogger(isolate) {
base::LockGuard<base::RecursiveMutex> guard_file(GetFileMutex().Pointer());
process_id_ = base::OS::GetCurrentProcessId();
reference_count_++;
// If this is the first logger, open the file and write the header.
if (reference_count_ == 1) {
OpenJitDumpFile();
if (perf_output_handle_ == nullptr) return;
LogWriteHeader();
}
}
LinuxPerfJitLogger::~LinuxPerfJitLogger() {
base::LockGuard<base::RecursiveMutex> guard_file(GetFileMutex().Pointer());
reference_count_--;
// If this was the last logger, close the file.
if (reference_count_ == 0) {
CloseJitDumpFile();
}
}
uint64_t LinuxPerfJitLogger::GetTimestamp() {
struct timespec ts;
int result = clock_gettime(CLOCK_MONOTONIC, &ts);
DCHECK_EQ(0, result);
USE(result);
static const uint64_t kNsecPerSec = 1000000000;
return (ts.tv_sec * kNsecPerSec) + ts.tv_nsec;
}
void LinuxPerfJitLogger::LogRecordedBuffer(
Tagged<AbstractCode> abstract_code,
MaybeDirectHandle<SharedFunctionInfo> maybe_sfi, const char* name,
size_t length) {
DisallowGarbageCollection no_gc;
if (v8_flags.perf_basic_prof_only_functions) {
CodeKind code_kind = abstract_code->kind(isolate_);
if (!CodeKindIsJSFunction(code_kind)) {
return;
}
}
base::LockGuard<base::RecursiveMutex> guard_file(GetFileMutex().Pointer());
if (perf_output_handle_ == nullptr) return;
// We only support non-interpreted functions.
if (!IsCode(abstract_code, isolate_)) return;
Tagged<Code> code = Cast<Code>(abstract_code);
// Debug info has to be emitted first.
DirectHandle<SharedFunctionInfo> sfi;
if (v8_flags.perf_prof && maybe_sfi.ToHandle(&sfi)) {
// TODO(herhut): This currently breaks for js2wasm/wasm2js functions.
CodeKind kind = code->kind();
if (kind != CodeKind::JS_TO_WASM_FUNCTION &&
kind != CodeKind::WASM_TO_JS_FUNCTION) {
DCHECK_IMPLIES(IsScript(sfi->script()),
Cast<Script>(sfi->script())->has_line_ends());
LogWriteDebugInfo(code, sfi);
}
}
const char* code_name = name;
uint8_t* code_pointer = reinterpret_cast<uint8_t*>(code->instruction_start());
// Unwinding info comes right after debug info.
if (v8_flags.perf_prof_unwinding_info) LogWriteUnwindingInfo(code);
WriteJitCodeLoadEntry(code_pointer, code->instruction_size(), code_name,
length);
}
#if V8_ENABLE_WEBASSEMBLY
void LinuxPerfJitLogger::LogRecordedBuffer(const wasm::WasmCode* code,
const char* name, size_t length) {
base::LockGuard<base::RecursiveMutex> guard_file(GetFileMutex().Pointer());
if (perf_output_handle_ == nullptr) return;
if (v8_flags.perf_prof_annotate_wasm) LogWriteDebugInfo(code);
WriteJitCodeLoadEntry(code->instructions().begin(),
code->instructions().length(), name, length);
}
#endif // V8_ENABLE_WEBASSEMBLY
void LinuxPerfJitLogger::WriteJitCodeLoadEntry(const uint8_t* code_pointer,
uint32_t code_size,
const char* name,
size_t name_length) {
PerfJitCodeLoad code_load;
code_load.event_ = PerfJitCodeLoad::kLoad;
code_load.size_ =
static_cast<uint32_t>(sizeof(code_load) + name_length + 1 + code_size);
code_load.time_stamp_ = GetTimestamp();
code_load.process_id_ = static_cast<uint32_t>(process_id_);
code_load.thread_id_ = static_cast<uint32_t>(base::OS::GetCurrentThreadId());
code_load.vma_ = reinterpret_cast<uint64_t>(code_pointer);
code_load.code_address_ = reinterpret_cast<uint64_t>(code_pointer);
code_load.code_size_ = code_size;
code_load.code_id_ = code_index_;
code_index_++;
LogWriteBytes(reinterpret_cast<const char*>(&code_load), sizeof(code_load));
LogWriteBytes(name, name_length);
LogWriteBytes(kStringTerminator, sizeof(kStringTerminator));
LogWriteBytes(reinterpret_cast<const char*>(code_pointer), code_size);
}
namespace {
constexpr char kUnknownScriptNameString[] = "<unknown>";
constexpr size_t kUnknownScriptNameStringLen =
arraysize(kUnknownScriptNameString) - 1;
namespace {
base::Vector<const char> GetScriptName(Tagged<Object> maybeScript,
std::unique_ptr<char[]>* storage,
const DisallowGarbageCollection& no_gc) {
if (IsScript(maybeScript)) {
Tagged<Object> name_or_url =
Cast<Script>(maybeScript)->GetNameOrSourceURL();
if (IsSeqOneByteString(name_or_url)) {
Tagged<SeqOneByteString> str = Cast<SeqOneByteString>(name_or_url);
return {reinterpret_cast<char*>(str->GetChars(no_gc)),
static_cast<size_t>(str->length())};
} else if (IsString(name_or_url)) {
size_t length;
*storage = Cast<String>(name_or_url)->ToCString(&length);
return {storage->get(), length};
}
}
return {kUnknownScriptNameString, kUnknownScriptNameStringLen};
}
} // namespace
SourcePositionInfo GetSourcePositionInfo(
Isolate* isolate, Tagged<Code> code,
DirectHandle<SharedFunctionInfo> function, SourcePosition pos) {
DisallowGarbageCollection disallow;
if (code->is_turbofanned()) {
return pos.FirstInfo(isolate, code);
} else {
return SourcePositionInfo(isolate, pos, function);
}
}
} // namespace
void LinuxPerfJitLogger::LogWriteDebugInfo(
Tagged<Code> code, DirectHandle<SharedFunctionInfo> shared) {
// Line ends of all scripts have been initialized prior to this.
DisallowGarbageCollection no_gc;
// The WasmToJS wrapper stubs have source position entries.
Tagged<SharedFunctionInfo> raw_shared = *shared;
if (!raw_shared->HasSourceCode()) return;
PerfJitCodeDebugInfo debug_info;
uint32_t size = sizeof(debug_info);
Tagged<TrustedByteArray> source_position_table =
code->SourcePositionTable(isolate_, raw_shared);
// Compute the entry count and get the names of all scripts.
// Avoid additional work if the script name is repeated. Multiple script
// names only occur for cross-script inlining.
uint32_t entry_count = 0;
Tagged<Object> last_script = Smi::zero();
size_t last_script_name_size = 0;
std::vector<base::Vector<const char>> script_names;
for (SourcePositionTableIterator iterator(source_position_table);
!iterator.done(); iterator.Advance()) {
SourcePositionInfo info(GetSourcePositionInfo(isolate_, code, shared,
iterator.source_position()));
Tagged<Object> current_script = *info.script;
if (current_script != last_script) {
std::unique_ptr<char[]> name_storage;
auto name = GetScriptName(raw_shared->script(), &name_storage, no_gc);
script_names.push_back(name);
// Add the size of the name after each entry.
last_script_name_size = name.size() + sizeof(kStringTerminator);
size += last_script_name_size;
last_script = current_script;
} else {
DCHECK_LT(0, last_script_name_size);
size += last_script_name_size;
}
entry_count++;
}
if (entry_count == 0) return;
debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
debug_info.time_stamp_ = GetTimestamp();
debug_info.address_ = code->instruction_start();
debug_info.entry_count_ = entry_count;
// Add the sizes of fixed parts of entries.
size += entry_count * sizeof(PerfJitDebugEntry);
int padding = ((size + 7) & (~7)) - size;
debug_info.size_ = size + padding;
LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
Address code_start = code->instruction_start();
last_script = Smi::zero();
int script_names_index = 0;
for (SourcePositionTableIterator iterator(source_position_table);
!iterator.done(); iterator.Advance()) {
SourcePositionInfo info(GetSourcePositionInfo(isolate_, code, shared,
iterator.source_position()));
PerfJitDebugEntry entry;
// The entry point of the function will be placed straight after the ELF
// header when processed by "perf inject". Adjust the position addresses
// accordingly.
entry.address_ = code_start + iterator.code_offset() + kElfHeaderSize;
entry.line_number_ = info.line + 1;
entry.column_ = info.column + 1;
LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
Tagged<Object> current_script = *info.script;
auto name_string = script_names[script_names_index];
LogWriteBytes(name_string.begin(), name_string.size());
LogWriteBytes(kStringTerminator, sizeof(kStringTerminator));
if (current_script != last_script) {
if (last_script != Smi::zero()) script_names_index++;
last_script = current_script;
}
}
char padding_bytes[8] = {0};
LogWriteBytes(padding_bytes, padding);
}
#if V8_ENABLE_WEBASSEMBLY
void LinuxPerfJitLogger::LogWriteDebugInfo(const wasm::WasmCode* code) {
if (code->IsAnonymous()) {
return;
}
wasm::WasmModuleSourceMap* source_map =
code->native_module()->GetWasmSourceMap();
wasm::WireBytesRef code_ref =
code->native_module()->module()->functions[code->index()].code;
uint32_t code_offset = code_ref.offset();
uint32_t code_end_offset = code_ref.end_offset();
uint32_t entry_count = 0;
uint32_t size = 0;
if (!source_map || !source_map->IsValid() ||
!source_map->HasSource(code_offset, code_end_offset)) {
return;
}
for (SourcePositionTableIterator iterator(code->source_positions());
!iterator.done(); iterator.Advance()) {
uint32_t offset = iterator.source_position().ScriptOffset() + code_offset;
if (!source_map->HasValidEntry(code_offset, offset)) continue;
entry_count++;
size += source_map->GetFilename(offset).size() + 1;
}
if (entry_count == 0) return;
PerfJitCodeDebugInfo debug_info;
debug_info.event_ = PerfJitCodeLoad::kDebugInfo;
debug_info.time_stamp_ = GetTimestamp();
debug_info.address_ =
reinterpret_cast<uintptr_t>(code->instructions().begin());
debug_info.entry_count_ = entry_count;
size += sizeof(debug_info);
// Add the sizes of fixed parts of entries.
size += entry_count * sizeof(PerfJitDebugEntry);
int padding = ((size + 7) & (~7)) - size;
debug_info.size_ = size + padding;
LogWriteBytes(reinterpret_cast<const char*>(&debug_info), sizeof(debug_info));
uintptr_t code_begin =
reinterpret_cast<uintptr_t>(code->instructions().begin());
for (SourcePositionTableIterator iterator(code->source_positions());
!iterator.done(); iterator.Advance()) {
uint32_t offset = iterator.source_position().ScriptOffset() + code_offset;
if (!source_map->HasValidEntry(code_offset, offset)) continue;
PerfJitDebugEntry entry;
// The entry point of the function will be placed straight after the ELF
// header when processed by "perf inject". Adjust the position addresses
// accordingly.
entry.address_ = code_begin + iterator.code_offset() + kElfHeaderSize;
entry.line_number_ =
static_cast<int>(source_map->GetSourceLine(offset)) + 1;
entry.column_ = 1;
LogWriteBytes(reinterpret_cast<const char*>(&entry), sizeof(entry));
std::string name_string = source_map->GetFilename(offset);
LogWriteBytes(name_string.c_str(), name_string.size());
LogWriteBytes(kStringTerminator, sizeof(kStringTerminator));
}
char padding_bytes[8] = {0};
LogWriteBytes(padding_bytes, padding);
}
#endif // V8_ENABLE_WEBASSEMBLY
void LinuxPerfJitLogger::LogWriteUnwindingInfo(Tagged<Code> code) {
PerfJitCodeUnwindingInfo unwinding_info_header;
unwinding_info_header.event_ = PerfJitCodeLoad::kUnwindingInfo;
unwinding_info_header.time_stamp_ = GetTimestamp();
unwinding_info_header.eh_frame_hdr_size_ = EhFrameConstants::kEhFrameHdrSize;
if (code->has_unwinding_info()) {
unwinding_info_header.unwinding_size_ = code->unwinding_info_size();
unwinding_info_header.mapped_size_ = unwinding_info_header.unwinding_size_;
} else {
unwinding_info_header.unwinding_size_ = EhFrameConstants::kEhFrameHdrSize;
unwinding_info_header.mapped_size_ = 0;
}
int content_size = static_cast<int>(sizeof(unwinding_info_header) +
unwinding_info_header.unwinding_size_);
int padding_size = RoundUp(content_size, 8) - content_size;
unwinding_info_header.size_ = content_size + padding_size;
LogWriteBytes(reinterpret_cast<const char*>(&unwinding_info_header),
sizeof(unwinding_info_header));
if (code->has_unwinding_info()) {
LogWriteBytes(reinterpret_cast<const char*>(code->unwinding_info_start()),
code->unwinding_info_size());
} else {
OFStream perf_output_stream(perf_output_handle_);
EhFrameWriter::WriteEmptyEhFrame(perf_output_stream);
}
char padding_bytes[] = "\0\0\0\0\0\0\0\0";
DCHECK_LT(padding_size, static_cast<int>(sizeof(padding_bytes)));
LogWriteBytes(padding_bytes, padding_size);
}
void LinuxPerfJitLogger::LogWriteBytes(const char* bytes, size_t size) {
size_t rv = fwrite(bytes, 1, size, perf_output_handle_);
DCHECK_EQ(size, rv);
USE(rv);
}
void LinuxPerfJitLogger::LogWriteHeader() {
DCHECK_NOT_NULL(perf_output_handle_);
PerfJitHeader header;
header.magic_ = PerfJitHeader::kMagic;
header.version_ = PerfJitHeader::kVersion;
header.size_ = sizeof(header);
header.elf_mach_target_ = GetElfMach();
header.reserved_ = 0xDEADBEEF;
header.process_id_ = process_id_;
header.time_stamp_ = static_cast<uint64_t>(
V8::GetCurrentPlatform()->CurrentClockTimeMillisecondsHighResolution() *
base::Time::kMicrosecondsPerMillisecond);
header.flags_ = 0;
LogWriteBytes(reinterpret_cast<const char*>(&header), sizeof(header));
}
} // namespace internal
} // namespace v8
#endif // V8_OS_LINUX