blob: 0e76b7f3a3c9b716f94998d25aa9956eaafea61d [file] [log] [blame]
// Copyright 2024 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/tracing/perfetto-logger.h"
#include <memory>
#include "absl/container/flat_hash_map.h"
#include "protos/perfetto/common/builtin_clock.pbzero.h"
#include "protos/perfetto/trace/chrome/v8.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
#include "src/base/logging.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
#include "src/builtins/builtins.h"
#include "src/execution/isolate.h"
#include "src/handles/handles.h"
#include "src/heap/spaces.h"
#include "src/logging/log.h"
#include "src/objects/abstract-code.h"
#include "src/objects/code-kind.h"
#include "src/objects/heap-object.h"
#include "src/objects/objects-inl.h"
#include "src/objects/oddball.h"
#include "src/objects/script.h"
#include "src/objects/string.h"
#include "src/objects/tagged.h"
#include "src/tracing/code-data-source.h"
#include "src/tracing/code-trace-context.h"
#include "src/tracing/perfetto-utils.h"
namespace v8 {
namespace internal {
namespace {
using ::perfetto::protos::pbzero::BuiltinClock;
using ::perfetto::protos::pbzero::TracePacket;
using ::perfetto::protos::pbzero::V8InternalCode;
using ::perfetto::protos::pbzero::V8JsCode;
CodeDataSource::TraceContext::TracePacketHandle NewTracePacket(
CodeDataSource::TraceContext& context) {
CodeDataSourceIncrementalState* inc_state = context.GetIncrementalState();
auto packet = context.NewTracePacket();
packet->set_timestamp(base::TimeTicks::Now().since_origin().InNanoseconds());
if (inc_state->is_initialized()) {
packet->set_sequence_flags(TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
return packet;
}
inc_state->Init(context);
packet->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
auto* defaults = packet->set_trace_packet_defaults();
defaults->set_timestamp_clock_id(BuiltinClock::BUILTIN_CLOCK_MONOTONIC);
auto* v8_defaults = defaults->set_v8_code_defaults();
v8_defaults->set_tid(base::OS::GetCurrentThreadId());
return packet;
}
CodeTraceContext NewCodeTraceContext(CodeDataSource::TraceContext& ctx) {
return CodeTraceContext(NewTracePacket(ctx), ctx.GetIncrementalState());
}
class IsolateRegistry {
public:
static IsolateRegistry& GetInstance() {
static IsolateRegistry* g_instance = new IsolateRegistry();
return *g_instance;
}
void Register(Isolate* isolate) {
auto logger = std::make_unique<PerfettoLogger>(isolate);
base::MutexGuard lock(&mutex_);
if (num_active_data_sources_ != 0) {
isolate->logger()->AddListener(logger.get());
}
CHECK(isolates_.emplace(isolate, std::move(logger)).second);
}
void Unregister(Isolate* isolate) {
base::MutexGuard lock(&mutex_);
auto it = isolates_.find(isolate);
CHECK(it != isolates_.end());
if (num_active_data_sources_ != 0) {
isolate->logger()->RemoveListener(it->second.get());
}
isolates_.erase(it);
}
void OnCodeDataSourceStart() {
base::MutexGuard lock(&mutex_);
++num_active_data_sources_;
if (num_active_data_sources_ == 1) {
StartLogging(lock);
}
LogExistingCodeForAllIsolates(lock);
}
void OnCodeDataSourceStop() {
base::MutexGuard lock(&mutex_);
DCHECK_LT(0, num_active_data_sources_);
--num_active_data_sources_;
if (num_active_data_sources_ == 0) {
StopLogging(lock);
}
}
private:
void StartLogging(const base::MutexGuard&) {
for (const auto& [isolate, logger] : isolates_) {
isolate->logger()->AddListener(logger.get());
}
}
void StopLogging(const base::MutexGuard&) {
for (const auto& [isolate, logger] : isolates_) {
isolate->logger()->RemoveListener(logger.get());
}
}
void LogExistingCodeForAllIsolates(const base::MutexGuard&) {
for (const auto& [isolate, listener] : isolates_) {
isolate->RequestInterrupt(
[](v8::Isolate*, void* data) {
PerfettoLogger* listener = reinterpret_cast<PerfettoLogger*>(data);
listener->LogExistingCode();
},
listener.get());
}
}
base::Mutex mutex_;
int num_active_data_sources_ = 0;
absl::flat_hash_map<Isolate*, std::unique_ptr<PerfettoLogger>> isolates_;
};
void WriteJsCode(const CodeTraceContext& ctx,
Tagged<AbstractCode> abstract_code, V8JsCode& code_proto) {
if (IsBytecodeArray(abstract_code)) {
Tagged<BytecodeArray> bytecode = abstract_code->GetBytecodeArray();
code_proto.set_tier(V8JsCode::TIER_IGNITION);
code_proto.set_instruction_start(bytecode->GetFirstBytecodeAddress());
code_proto.set_instruction_size_bytes(bytecode->length());
if (ctx.log_instructions()) {
code_proto.set_bytecode(
reinterpret_cast<const uint8_t*>(bytecode->GetFirstBytecodeAddress()),
bytecode->length());
}
return;
}
DCHECK(IsCode(abstract_code));
Tagged<Code> code = abstract_code->GetCode();
V8JsCode::Tier tier = V8JsCode::TIER_UNKNOWN;
switch (code->kind()) {
case CodeKind::BUILTIN:
if (code->builtin_id() == Builtin::kInterpreterEntryTrampoline) {
DCHECK(v8_flags.interpreted_frames_native_stack);
DCHECK(code->has_instruction_stream());
tier = V8JsCode::TIER_IGNITION;
break;
}
// kEmptyFunction is used as a placeholder sometimes.
DCHECK_EQ(code->builtin_id(), Builtin::kEmptyFunction);
DCHECK(!code->has_instruction_stream());
return;
case CodeKind::INTERPRETED_FUNCTION:
// Handled above.
UNREACHABLE();
case CodeKind::BASELINE:
tier = V8JsCode::TIER_SPARKPLUG;
break;
case CodeKind::MAGLEV:
tier = V8JsCode::TIER_MAGLEV;
break;
case CodeKind::TURBOFAN:
tier = V8JsCode::TIER_TURBOFAN;
break;
case CodeKind::BYTECODE_HANDLER:
case CodeKind::FOR_TESTING:
case CodeKind::REGEXP:
case CodeKind::WASM_FUNCTION:
case CodeKind::WASM_TO_CAPI_FUNCTION:
case CodeKind::WASM_TO_JS_FUNCTION:
case CodeKind::JS_TO_WASM_FUNCTION:
case CodeKind::C_WASM_ENTRY:
UNREACHABLE();
}
code_proto.set_tier(tier);
code_proto.set_instruction_start(code->instruction_start());
code_proto.set_instruction_size_bytes(code->instruction_size());
if (ctx.log_instructions()) {
code_proto.set_machine_code(
reinterpret_cast<const uint8_t*>(code->instruction_start()),
code->instruction_size());
}
}
} // namespace
// static
void PerfettoLogger::RegisterIsolate(Isolate* isolate) {
IsolateRegistry::GetInstance().Register(isolate);
// TODO(carlscab): Actually if both perfetto and file logging are active the
// builtins will be logged twice to the file (EmitCodeCreateEvents is called
// somewhere in the isolate setup code). Probably not very likely to happen
// but we should find a better way.
CodeDataSource::CallIfEnabled(
[isolate](uint32_t) { Builtins::EmitCodeCreateEvents(isolate); });
}
// static
void PerfettoLogger::UnregisterIsolate(Isolate* isolate) {
IsolateRegistry::GetInstance().Unregister(isolate);
}
// static
void PerfettoLogger::OnCodeDataSourceStart() {
IsolateRegistry::GetInstance().OnCodeDataSourceStart();
}
// static
void PerfettoLogger::OnCodeDataSourceStop() {
IsolateRegistry::GetInstance().OnCodeDataSourceStop();
}
void PerfettoLogger::LogExistingCode() {
HandleScope scope(&isolate_);
ExistingCodeLogger logger(&isolate_, this);
logger.LogBuiltins();
logger.LogCodeObjects();
logger.LogCompiledFunctions();
}
PerfettoLogger::PerfettoLogger(Isolate* isolate) : isolate_(*isolate) {}
PerfettoLogger::~PerfettoLogger() {}
void PerfettoLogger::CodeCreateEvent(CodeTag tag,
Handle<AbstractCode> abstract_code,
const char* name) {
DisallowGarbageCollection no_gc;
if (!IsCode(*abstract_code)) return;
Tagged<Code> code = abstract_code->GetCode();
V8InternalCode::Type type = V8InternalCode::TYPE_UNKNOWN;
switch (code->kind()) {
case CodeKind::REGEXP:
RegExpCodeCreateEvent(abstract_code, Handle<String>());
break;
case CodeKind::BYTECODE_HANDLER:
type = V8InternalCode::TYPE_BYTECODE_HANDLER;
break;
case CodeKind::FOR_TESTING:
type = V8InternalCode::TYPE_FOR_TESTING;
break;
case CodeKind::BUILTIN:
type = V8InternalCode::TYPE_BUILTIN;
break;
case CodeKind::WASM_FUNCTION:
type = V8InternalCode::TYPE_WASM_FUNCTION;
break;
case CodeKind::WASM_TO_CAPI_FUNCTION:
type = V8InternalCode::TYPE_WASM_TO_CAPI_FUNCTION;
break;
case CodeKind::WASM_TO_JS_FUNCTION:
type = V8InternalCode::TYPE_WASM_TO_JS_FUNCTION;
break;
case CodeKind::JS_TO_WASM_FUNCTION:
type = V8InternalCode::TYPE_JS_TO_WASM_FUNCTION;
break;
case CodeKind::C_WASM_ENTRY:
type = V8InternalCode::TYPE_C_WASM_ENTRY;
break;
case CodeKind::INTERPRETED_FUNCTION:
case CodeKind::BASELINE:
case CodeKind::MAGLEV:
case CodeKind::TURBOFAN:
UNREACHABLE();
}
CodeDataSource::Trace(
[&](v8::internal::CodeDataSource::TraceContext trace_context) {
CodeTraceContext ctx = NewCodeTraceContext(trace_context);
auto* code_proto = ctx.set_v8_internal_code();
code_proto->set_v8_isolate_iid(ctx.InternIsolate(isolate_));
code_proto->set_name(name);
code_proto->set_type(type);
if (code->is_builtin()) {
code_proto->set_builtin_id(static_cast<int32_t>(code->builtin_id()));
}
code_proto->set_instruction_start(code->instruction_start());
code_proto->set_instruction_size_bytes(code->instruction_size());
if (ctx.log_instructions()) {
code_proto->set_machine_code(
reinterpret_cast<const uint8_t*>(code->instruction_start()),
code->instruction_size());
}
});
}
void PerfettoLogger::CodeCreateEvent(CodeTag tag,
Handle<AbstractCode> abstract_code,
Handle<Name> name) {
DisallowGarbageCollection no_gc;
if (!IsString(*name)) return;
CodeCreateEvent(tag, abstract_code, Cast<String>(*name)->ToCString().get());
}
void PerfettoLogger::CodeCreateEvent(CodeTag tag,
Handle<AbstractCode> abstract_code,
Handle<SharedFunctionInfo> info,
Handle<Name> script_name) {
CodeCreateEvent(tag, abstract_code, info, script_name, 0, 0);
}
void PerfettoLogger::CodeCreateEvent(CodeTag tag,
Handle<AbstractCode> abstract_code,
Handle<SharedFunctionInfo> info,
Handle<Name> script_name, int line,
int column) {
DisallowGarbageCollection no_gc;
DCHECK(IsScript(info->script()));
CodeDataSource::Trace(
[&](v8::internal::CodeDataSource::TraceContext trace_context) {
CodeTraceContext ctx = NewCodeTraceContext(trace_context);
auto* code_proto = ctx.set_v8_js_code();
code_proto->set_v8_isolate_iid(ctx.InternIsolate(isolate_));
code_proto->set_v8_js_function_iid(ctx.InternJsFunction(
isolate_, info,
ctx.InternJsScript(isolate_, Cast<Script>(info->script())), line,
column));
WriteJsCode(ctx, *abstract_code, *code_proto);
});
}
#if V8_ENABLE_WEBASSEMBLY
void PerfettoLogger::CodeCreateEvent(CodeTag tag, const wasm::WasmCode* code,
wasm::WasmName name,
const char* source_url, int code_offset,
int script_id) {
DisallowGarbageCollection no_gc;
CodeDataSource::Trace(
[&](v8::internal::CodeDataSource::TraceContext trace_context) {
CodeTraceContext ctx = NewCodeTraceContext(trace_context);
auto* code_proto = ctx.set_v8_wasm_code();
code_proto->set_v8_isolate_iid(ctx.InternIsolate(isolate_));
code_proto->set_v8_wasm_script_iid(
ctx.InternWasmScript(isolate_, script_id, source_url));
code_proto->set_function_name(name.begin(), name.size());
// TODO(carlscab): Set tier
code_proto->set_instruction_start(code->instruction_start());
code_proto->set_instruction_size_bytes(code->instructions_size());
if (ctx.log_instructions()) {
code_proto->set_machine_code(
reinterpret_cast<const uint8_t*>(code->instruction_start()),
code->instructions_size());
}
});
}
#endif // V8_ENABLE_WEBASSEMBLY
void PerfettoLogger::CallbackEvent(Handle<Name> name, Address entry_point) {}
void PerfettoLogger::GetterCallbackEvent(Handle<Name> name,
Address entry_point) {}
void PerfettoLogger::SetterCallbackEvent(Handle<Name> name,
Address entry_point) {}
void PerfettoLogger::RegExpCodeCreateEvent(Handle<AbstractCode> abstract_code,
Handle<String> pattern) {
DisallowGarbageCollection no_gc;
DCHECK(IsCode(*abstract_code));
Tagged<Code> code = abstract_code->GetCode();
DCHECK(code->kind() == CodeKind::REGEXP);
CodeDataSource::Trace(
[&](v8::internal::CodeDataSource::TraceContext trace_context) {
CodeTraceContext ctx = NewCodeTraceContext(trace_context);
auto* code_proto = ctx.set_v8_reg_exp_code();
code_proto->set_v8_isolate_iid(ctx.InternIsolate(isolate_));
if (!pattern.is_null()) {
PerfettoV8String(*pattern).WriteToProto(*code_proto->set_pattern());
}
code_proto->set_instruction_start(code->instruction_start());
code_proto->set_instruction_size_bytes(code->instruction_size());
if (ctx.log_instructions()) {
code_proto->set_machine_code(
reinterpret_cast<const uint8_t*>(code->instruction_start()),
code->instruction_size());
}
});
}
void PerfettoLogger::CodeMoveEvent(Tagged<InstructionStream> from,
Tagged<InstructionStream> to) {
CodeDataSource::Trace(
[&](v8::internal::CodeDataSource::TraceContext trace_context) {
CodeTraceContext ctx = NewCodeTraceContext(trace_context);
auto* code_move = ctx.set_code_move();
code_move->set_isolate_iid(ctx.InternIsolate(isolate_));
code_move->set_from_instruction_start_address(
from->instruction_start());
code_move->set_to_instruction_start_address(to->instruction_start());
Tagged<Code> code = to->code(AcquireLoadTag());
code_move->set_instruction_size_bytes(code->instruction_size());
if (ctx.log_instructions()) {
code_move->set_to_machine_code(
reinterpret_cast<const uint8_t*>(code->instruction_start()),
code->instruction_size());
}
});
}
void PerfettoLogger::BytecodeMoveEvent(Tagged<BytecodeArray> from,
Tagged<BytecodeArray> to) {
CodeDataSource::Trace(
[&](v8::internal::CodeDataSource::TraceContext trace_context) {
CodeTraceContext ctx = NewCodeTraceContext(trace_context);
auto* code_move = ctx.set_code_move();
code_move->set_isolate_iid(ctx.InternIsolate(isolate_));
code_move->set_from_instruction_start_address(
from->GetFirstBytecodeAddress());
code_move->set_to_instruction_start_address(
to->GetFirstBytecodeAddress());
code_move->set_instruction_size_bytes(to->length());
if (ctx.log_instructions()) {
code_move->set_to_bytecode(
reinterpret_cast<const uint8_t*>(to->GetFirstBytecodeAddress()),
to->length());
}
});
}
void PerfettoLogger::SharedFunctionInfoMoveEvent(Address from, Address to) {}
void PerfettoLogger::NativeContextMoveEvent(Address from, Address to) {}
void PerfettoLogger::CodeMovingGCEvent() {}
void PerfettoLogger::CodeDisableOptEvent(Handle<AbstractCode> code,
Handle<SharedFunctionInfo> shared) {}
void PerfettoLogger::CodeDeoptEvent(Handle<Code> code, DeoptimizeKind kind,
Address pc, int fp_to_sp_delta) {}
void PerfettoLogger::CodeDependencyChangeEvent(
Handle<Code> code, Handle<SharedFunctionInfo> shared, const char* reason) {}
void PerfettoLogger::WeakCodeClearEvent() {}
bool PerfettoLogger::is_listening_to_code_events() { return true; }
} // namespace internal
} // namespace v8