[tracing] Improve tracing signals for compilation/optimization.

This adds OBJECT/SNAPSHOT trace events for Script and SharedFunctionInfo
objects, logging their creation with appropriate information to make
sense of them.

Based on that we introduces five flow events to model the optimized
compilation via tracing in the "disabled-by-default-v8.compile" category:

  - "v8.optimizingCompile.start" logs the creation of the
    PipelineCompilationJob (for TurboFan JavaScript optimization)
    with the "function" argument referring to the trace event
    object created for the SharedFunctionInfo.
  - "v8.optimzingCompile.prepare" logs the preparation of the
    PipelineCompilationJob on the main thread, also carrying the
    "function" argument. This connects the flow event to the actual
    tracing duration event associated with the preparation phases.
  - "v8.optimizingCompile.execute" logs the (usually concurrent)
    optimization of the TurboFan graph (again with "function").
  - "v8.optimizingCompile.finalize" logs the main thread phase which
    finalizes the optimized code and eventually installs it (in case
    of success).
  - "v8.optimizingCompile.end" signals the end of the
    PipelineCompilationJob, which carries the "compilationInfo",
    that contains the interesting bits of the OptimizedCompilationInfo,
    specifically whether the compile was successfull and which functions
    were inlined for example.

This also adds two instant events "V8.AbortOptimization" and
"V8.RetryOptimization" in "disabled-by-default-v8.compile" category
that are emitted when TurboFan cannot optimize a certain function.
In case of "V8.RetryOptimization", TurboFan might be able to optimize
it later, whereas "V8.AbortOptimization" permanently disables the
optimization of a given function. The JSON representation of this is

```js
{
  "pid": 256639,
  "tid": 256639,
  "ts": 6935411377801,
  "tts": 159116,
  "ph": "I",
  "cat": "disabled-by-default-v8.compile",
  "name": "V8.AbortOptimization",
  "dur": 0,
  "tdur": 0,
  "args": {
    "reason": "Function is too big to be optimized",
    "function": {
      "id_ref": "0x600000001",
      "scope": "v8::internal::SharedFunctionInfo"
    }
  }
},
```

where the "function" refers to a previously emitted SNAPSHOT for the
function in question. In the trace viewer it will show up as instant
event under "v8.optimizingCompile.prepare" in case of the relevant
example where optimization is disabled due to reaching the bytecode
limit (as in the JSON above), i.e. it'll look something like this

  https://i.paste.pics/aafc2de9df10ea8f5acc1a761d80f07b.png

for the example highlighted in the recent blog post

  https://ponyfoo.com/articles/javascript-performance-pitfalls-v8

that describes the optimization limit. The "v8.optimizingCompile.end"
duration event will also carry this information as part of the
"compilationInfo" object, but specifically for CI tools, etc. it might
be a whole lot easier to just look for the "V8.AbortOptimization"
instant event.

Bug: v8:8598, v8:9039
Tbr: ulan@chromium.org
Doc: bit.ly/v8-tracing-signals
Change-Id: Ic87ac336004690c65b6b15ad73bc6fbd4b5f12c4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1511483
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60448}
diff --git a/src/compiler.cc b/src/compiler.cc
index 25d5e90..2c31acf 100644
--- a/src/compiler.cc
+++ b/src/compiler.cc
@@ -1472,6 +1472,10 @@
       }
     }
     script->set_eval_from_position(eval_position);
+    TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+        TRACE_DISABLED_BY_DEFAULT("v8.compile"), "Script",
+        TRACE_ID_WITH_SCOPE(Script::kTraceScope, script->id()),
+        script->ToTracedValue());
 
     parse_info.set_eval();
     parse_info.set_language_mode(language_mode);
@@ -1795,6 +1799,10 @@
     script->set_host_defined_options(*host_defined_options);
   }
   LOG(isolate, ScriptDetails(*script));
+  TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "Script",
+      TRACE_ID_WITH_SCOPE(Script::kTraceScope, script->id()),
+      script->ToTracedValue());
   return script;
 }
 
diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc
index 417cffb..8b0a576 100644
--- a/src/compiler/pipeline.cc
+++ b/src/compiler/pipeline.cc
@@ -84,6 +84,7 @@
 #include "src/parsing/parse-info.h"
 #include "src/register-configuration.h"
 #include "src/tracing/trace-event.h"
+#include "src/tracing/traced-value.h"
 #include "src/utils.h"
 #include "src/wasm/function-body-decoder.h"
 #include "src/wasm/function-compiler.h"
@@ -862,24 +863,8 @@
  public:
   PipelineCompilationJob(Isolate* isolate,
                          Handle<SharedFunctionInfo> shared_info,
-                         Handle<JSFunction> function)
-      // Note that the OptimizedCompilationInfo is not initialized at the time
-      // we pass it to the CompilationJob constructor, but it is not
-      // dereferenced there.
-      : OptimizedCompilationJob(
-            function->GetIsolate()->stack_guard()->real_climit(),
-            &compilation_info_, "TurboFan"),
-        zone_(function->GetIsolate()->allocator(), ZONE_NAME),
-        zone_stats_(function->GetIsolate()->allocator()),
-        compilation_info_(&zone_, function->GetIsolate(), shared_info,
-                          function),
-        pipeline_statistics_(CreatePipelineStatistics(
-            handle(Script::cast(shared_info->script()), isolate),
-            compilation_info(), function->GetIsolate(), &zone_stats_)),
-        data_(&zone_stats_, function->GetIsolate(), compilation_info(),
-              pipeline_statistics_.get()),
-        pipeline_(&data_),
-        linkage_(nullptr) {}
+                         Handle<JSFunction> function);
+  ~PipelineCompilationJob();
 
  protected:
   Status PrepareJobImpl(Isolate* isolate) final;
@@ -901,8 +886,43 @@
   DISALLOW_COPY_AND_ASSIGN(PipelineCompilationJob);
 };
 
+PipelineCompilationJob::PipelineCompilationJob(
+    Isolate* isolate, Handle<SharedFunctionInfo> shared_info,
+    Handle<JSFunction> function)
+    // Note that the OptimizedCompilationInfo is not initialized at the time
+    // we pass it to the CompilationJob constructor, but it is not
+    // dereferenced there.
+    : OptimizedCompilationJob(
+          function->GetIsolate()->stack_guard()->real_climit(),
+          &compilation_info_, "TurboFan"),
+      zone_(function->GetIsolate()->allocator(), ZONE_NAME),
+      zone_stats_(function->GetIsolate()->allocator()),
+      compilation_info_(&zone_, function->GetIsolate(), shared_info, function),
+      pipeline_statistics_(CreatePipelineStatistics(
+          handle(Script::cast(shared_info->script()), isolate),
+          compilation_info(), function->GetIsolate(), &zone_stats_)),
+      data_(&zone_stats_, function->GetIsolate(), compilation_info(),
+            pipeline_statistics_.get()),
+      pipeline_(&data_),
+      linkage_(nullptr) {
+  TRACE_EVENT_WITH_FLOW1(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "v8.optimizingCompile.start",
+      this, TRACE_EVENT_FLAG_FLOW_OUT, "function", shared_info->TraceIDRef());
+}
+
+PipelineCompilationJob::~PipelineCompilationJob() {
+  TRACE_EVENT_WITH_FLOW1(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
+                         "v8.optimizingCompile.end", this,
+                         TRACE_EVENT_FLAG_FLOW_IN, "compilationInfo",
+                         compilation_info()->ToTracedValue());
+}
+
 PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl(
     Isolate* isolate) {
+  TRACE_EVENT_WITH_FLOW1(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "v8.optimizingCompile.prepare",
+      this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "function",
+      compilation_info()->shared_info()->TraceIDRef());
   if (compilation_info()->bytecode_array()->length() >
       kMaxBytecodeSizeForTurbofan) {
     return AbortOptimization(BailoutReason::kFunctionTooBig);
@@ -968,6 +988,10 @@
 }
 
 PipelineCompilationJob::Status PipelineCompilationJob::ExecuteJobImpl() {
+  TRACE_EVENT_WITH_FLOW1(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "v8.optimizingCompile.execute",
+      this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "function",
+      compilation_info()->shared_info()->TraceIDRef());
   if (!pipeline_.OptimizeGraph(linkage_)) return FAILED;
   pipeline_.AssembleCode(linkage_);
   return SUCCEEDED;
@@ -975,6 +999,10 @@
 
 PipelineCompilationJob::Status PipelineCompilationJob::FinalizeJobImpl(
     Isolate* isolate) {
+  TRACE_EVENT_WITH_FLOW1(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "v8.optimizingCompile.finalize",
+      this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "function",
+      compilation_info()->shared_info()->TraceIDRef());
   MaybeHandle<Code> maybe_code = pipeline_.FinalizeCode();
   Handle<Code> code;
   if (!maybe_code.ToHandle(&code)) {
diff --git a/src/function-kind.h b/src/function-kind.h
index c7a0837..fce82b0 100644
--- a/src/function-kind.h
+++ b/src/function-kind.h
@@ -142,48 +142,52 @@
                    FunctionKind::kDerivedConstructor);
 }
 
-inline std::ostream& operator<<(std::ostream& os, FunctionKind kind) {
+inline const char* FunctionKind2String(FunctionKind kind) {
   switch (kind) {
     case FunctionKind::kNormalFunction:
-      return os << "NormalFunction";
+      return "NormalFunction";
     case FunctionKind::kArrowFunction:
-      return os << "ArrowFunction";
+      return "ArrowFunction";
     case FunctionKind::kGeneratorFunction:
-      return os << "GeneratorFunction";
+      return "GeneratorFunction";
     case FunctionKind::kConciseMethod:
-      return os << "ConciseMethod";
+      return "ConciseMethod";
     case FunctionKind::kDerivedConstructor:
-      return os << "DerivedConstructor";
+      return "DerivedConstructor";
     case FunctionKind::kBaseConstructor:
-      return os << "BaseConstructor";
+      return "BaseConstructor";
     case FunctionKind::kGetterFunction:
-      return os << "GetterFunction";
+      return "GetterFunction";
     case FunctionKind::kSetterFunction:
-      return os << "SetterFunction";
+      return "SetterFunction";
     case FunctionKind::kAsyncFunction:
-      return os << "AsyncFunction";
+      return "AsyncFunction";
     case FunctionKind::kModule:
-      return os << "Module";
+      return "Module";
     case FunctionKind::kClassMembersInitializerFunction:
-      return os << "ClassMembersInitializerFunction";
+      return "ClassMembersInitializerFunction";
     case FunctionKind::kDefaultBaseConstructor:
-      return os << "DefaultBaseConstructor";
+      return "DefaultBaseConstructor";
     case FunctionKind::kDefaultDerivedConstructor:
-      return os << "DefaultDerivedConstructor";
+      return "DefaultDerivedConstructor";
     case FunctionKind::kAsyncArrowFunction:
-      return os << "AsyncArrowFunction";
+      return "AsyncArrowFunction";
     case FunctionKind::kAsyncConciseMethod:
-      return os << "AsyncConciseMethod";
+      return "AsyncConciseMethod";
     case FunctionKind::kConciseGeneratorMethod:
-      return os << "ConciseGeneratorMethod";
+      return "ConciseGeneratorMethod";
     case FunctionKind::kAsyncConciseGeneratorMethod:
-      return os << "AsyncConciseGeneratorMethod";
+      return "AsyncConciseGeneratorMethod";
     case FunctionKind::kAsyncGeneratorFunction:
-      return os << "AsyncGeneratorFunction";
+      return "AsyncGeneratorFunction";
   }
   UNREACHABLE();
 }
 
+inline std::ostream& operator<<(std::ostream& os, FunctionKind kind) {
+  return os << FunctionKind2String(kind);
+}
+
 }  // namespace internal
 }  // namespace v8
 
diff --git a/src/globals.h b/src/globals.h
index f71ff81..3cc96f1 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -334,16 +334,20 @@
   return static_cast<size_t>(mode);
 }
 
-inline std::ostream& operator<<(std::ostream& os, const LanguageMode& mode) {
+inline const char* LanguageMode2String(LanguageMode mode) {
   switch (mode) {
     case LanguageMode::kSloppy:
-      return os << "sloppy";
+      return "sloppy";
     case LanguageMode::kStrict:
-      return os << "strict";
+      return "strict";
   }
   UNREACHABLE();
 }
 
+inline std::ostream& operator<<(std::ostream& os, LanguageMode mode) {
+  return os << LanguageMode2String(mode);
+}
+
 inline bool is_sloppy(LanguageMode language_mode) {
   return language_mode == LanguageMode::kSloppy;
 }
diff --git a/src/heap/factory.cc b/src/heap/factory.cc
index eb0add8..8bbee56 100644
--- a/src/heap/factory.cc
+++ b/src/heap/factory.cc
@@ -1683,6 +1683,9 @@
                                     MaybeObjectHandle::Weak(script));
   heap->set_script_list(*scripts);
   LOG(isolate(), ScriptEvent(Logger::ScriptEventType::kCreate, script_id));
+  TRACE_EVENT_OBJECT_CREATED_WITH_ID(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "Script",
+      TRACE_ID_WITH_SCOPE(Script::kTraceScope, script_id));
   return script;
 }
 
@@ -3590,6 +3593,13 @@
   SharedFunctionInfo::InitFromFunctionLiteral(shared, literal, is_toplevel);
   SharedFunctionInfo::SetScript(shared, script, literal->function_literal_id(),
                                 false);
+  TRACE_EVENT_OBJECT_CREATED_WITH_ID(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "SharedFunctionInfo",
+      TRACE_ID_WITH_SCOPE(SharedFunctionInfo::kTraceScope, shared->TraceID()));
+  TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "SharedFunctionInfo",
+      TRACE_ID_WITH_SCOPE(SharedFunctionInfo::kTraceScope, shared->TraceID()),
+      shared->ToTracedValue());
   return shared;
 }
 
diff --git a/src/objects.cc b/src/objects.cc
index dd7af93..e635bbe 100644
--- a/src/objects.cc
+++ b/src/objects.cc
@@ -4878,6 +4878,37 @@
   return handle(SharedFunctionInfo::cast(heap_object), isolate);
 }
 
+std::unique_ptr<v8::tracing::TracedValue> Script::ToTracedValue() {
+  auto value = v8::tracing::TracedValue::Create();
+  if (name()->IsString()) {
+    value->SetString("name", String::cast(name())->ToCString());
+  }
+  value->SetInteger("lineOffset", line_offset());
+  value->SetInteger("columnOffset", column_offset());
+  if (source_mapping_url()->IsString()) {
+    value->SetString("sourceMappingURL",
+                     String::cast(source_mapping_url())->ToCString());
+  }
+  if (source()->IsString()) {
+    value->SetString("source", String::cast(source())->ToCString());
+  }
+  return value;
+}
+
+// static
+const char* Script::kTraceScope = "v8::internal::Script";
+
+uint64_t Script::TraceID() const { return id(); }
+
+std::unique_ptr<v8::tracing::TracedValue> Script::TraceIDRef() const {
+  auto value = v8::tracing::TracedValue::Create();
+  std::ostringstream ost;
+  ost << "0x" << std::hex << TraceID();
+  value->SetString("id_ref", ost.str());
+  value->SetString("scope", kTraceScope);
+  return value;
+}
+
 Script::Iterator::Iterator(Isolate* isolate)
     : iterator_(isolate->heap()->script_list()) {}
 
@@ -4898,6 +4929,66 @@
   return static_cast<uint32_t>(base::hash_combine(start_pos, script_id));
 }
 
+std::unique_ptr<v8::tracing::TracedValue> SharedFunctionInfo::ToTracedValue() {
+  auto value = v8::tracing::TracedValue::Create();
+  if (HasSharedName()) {
+    value->SetString("name", Name()->ToCString());
+  }
+  if (HasInferredName()) {
+    value->SetString("inferredName", inferred_name()->ToCString());
+  }
+  if (is_toplevel()) {
+    value->SetBoolean("isToplevel", true);
+  }
+  value->SetInteger("formalParameterCount", internal_formal_parameter_count());
+  value->SetString("languageMode", LanguageMode2String(language_mode()));
+  value->SetString("kind", FunctionKind2String(kind()));
+  if (script()->IsScript()) {
+    value->SetValue("script", Script::cast(script())->TraceIDRef());
+    value->BeginDictionary("sourcePosition");
+    Script::PositionInfo info;
+    if (Script::cast(script())->GetPositionInfo(StartPosition(), &info,
+                                                Script::WITH_OFFSET)) {
+      value->SetInteger("line", info.line + 1);
+      value->SetInteger("column", info.column + 1);
+    }
+    value->EndDictionary();
+  }
+  return value;
+}
+
+// static
+const char* SharedFunctionInfo::kTraceScope =
+    "v8::internal::SharedFunctionInfo";
+
+uint64_t SharedFunctionInfo::TraceID() const {
+  // TODO(bmeurer): We use a combination of Script ID and function literal
+  // ID (within the Script) to uniquely identify SharedFunctionInfos. This
+  // can add significant overhead, and we should probably find a better way
+  // to uniquely identify SharedFunctionInfos over time.
+  Script script = Script::cast(this->script());
+  WeakFixedArray script_functions = script->shared_function_infos();
+  for (int i = 0; i < script_functions->length(); ++i) {
+    HeapObject script_function;
+    if (script_functions->Get(i).GetHeapObjectIfWeak(&script_function) &&
+        script_function->address() == address()) {
+      return (static_cast<uint64_t>(script->id() + 1) << 32) |
+             (static_cast<uint64_t>(i));
+    }
+  }
+  UNREACHABLE();
+}
+
+std::unique_ptr<v8::tracing::TracedValue> SharedFunctionInfo::TraceIDRef()
+    const {
+  auto value = v8::tracing::TracedValue::Create();
+  std::ostringstream ost;
+  ost << "0x" << std::hex << TraceID();
+  value->SetString("id_ref", ost.str());
+  value->SetString("scope", kTraceScope);
+  return value;
+}
+
 Code SharedFunctionInfo::GetCode() const {
   // ======
   // NOTE: This chain of checks MUST be kept in sync with the equivalent CSA
diff --git a/src/objects/script.h b/src/objects/script.h
index 8e4e6c7..e17deff 100644
--- a/src/objects/script.h
+++ b/src/objects/script.h
@@ -13,6 +13,11 @@
 #include "src/objects/object-macros.h"
 
 namespace v8 {
+
+namespace tracing {
+class TracedValue;
+}
+
 namespace internal {
 
 // Script describes a script which has been added to the VM.
@@ -174,6 +179,18 @@
   MaybeHandle<SharedFunctionInfo> FindSharedFunctionInfo(
       Isolate* isolate, const FunctionLiteral* fun);
 
+  // Returns the Script in a format tracing can support.
+  std::unique_ptr<v8::tracing::TracedValue> ToTracedValue();
+
+  // The tracing scope for Script objects.
+  static const char* kTraceScope;
+
+  // Returns the unique TraceID for this Script (within the kTraceScope).
+  uint64_t TraceID() const;
+
+  // Returns the unique trace ID reference for this Script.
+  std::unique_ptr<v8::tracing::TracedValue> TraceIDRef() const;
+
   // Iterate over all script objects on the heap.
   class Iterator {
    public:
diff --git a/src/objects/shared-function-info.h b/src/objects/shared-function-info.h
index 54171ab..085ac36 100644
--- a/src/objects/shared-function-info.h
+++ b/src/objects/shared-function-info.h
@@ -20,6 +20,11 @@
 #include "src/objects/object-macros.h"
 
 namespace v8 {
+
+namespace tracing {
+class TracedValue;
+}
+
 namespace internal {
 
 class AsmWasmData;
@@ -596,6 +601,21 @@
   void PrintSourceCode(std::ostream& os);
 #endif
 
+  // Returns the SharedFunctionInfo in a format tracing can support.
+  std::unique_ptr<v8::tracing::TracedValue> ToTracedValue();
+
+  // The tracing scope for SharedFunctionInfo objects.
+  static const char* kTraceScope;
+
+  // Returns the unique TraceID for this SharedFunctionInfo (within the
+  // kTraceScope, works only for functions that have a Script and start/end
+  // position).
+  uint64_t TraceID() const;
+
+  // Returns the unique trace ID reference for this SharedFunctionInfo
+  // (based on the |TraceID()| above).
+  std::unique_ptr<v8::tracing::TracedValue> TraceIDRef() const;
+
   // Iterate over all shared function infos in a given script.
   class ScriptIterator {
    public:
diff --git a/src/optimized-compilation-info.cc b/src/optimized-compilation-info.cc
index db8d8ba..38185cd 100644
--- a/src/optimized-compilation-info.cc
+++ b/src/optimized-compilation-info.cc
@@ -10,6 +10,8 @@
 #include "src/objects-inl.h"
 #include "src/objects/shared-function-info.h"
 #include "src/source-position.h"
+#include "src/tracing/trace-event.h"
+#include "src/tracing/traced-value.h"
 #include "src/wasm/function-compiler.h"
 
 namespace v8 {
@@ -125,6 +127,28 @@
   }
 }
 
+void OptimizedCompilationInfo::AbortOptimization(BailoutReason reason) {
+  DCHECK_NE(reason, BailoutReason::kNoReason);
+  if (bailout_reason_ == BailoutReason::kNoReason) {
+    TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
+                         "V8.AbortOptimization", TRACE_EVENT_SCOPE_THREAD,
+                         "reason", GetBailoutReason(reason), "function",
+                         shared_info()->TraceIDRef());
+    bailout_reason_ = reason;
+  }
+  SetFlag(kDisableFutureOptimization);
+}
+
+void OptimizedCompilationInfo::RetryOptimization(BailoutReason reason) {
+  DCHECK_NE(reason, BailoutReason::kNoReason);
+  if (GetFlag(kDisableFutureOptimization)) return;
+  TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
+                       "V8.RetryOptimization", TRACE_EVENT_SCOPE_THREAD,
+                       "reason", GetBailoutReason(reason), "function",
+                       shared_info()->TraceIDRef());
+  bailout_reason_ = reason;
+}
+
 std::unique_ptr<char[]> OptimizedCompilationInfo::GetDebugName() const {
   if (!shared_info().is_null()) {
     return shared_info()->DebugName()->ToCString();
@@ -220,5 +244,33 @@
   position.inlined_function_id = DeoptimizationData::kNotInlinedIndex;
 }
 
+std::unique_ptr<v8::tracing::TracedValue>
+OptimizedCompilationInfo::ToTracedValue() {
+  auto value = v8::tracing::TracedValue::Create();
+  value->SetBoolean("osr", is_osr());
+  value->SetBoolean("functionContextSpecialized",
+                    is_function_context_specializing());
+  if (has_shared_info()) {
+    value->SetValue("function", shared_info()->TraceIDRef());
+  }
+  if (bailout_reason() != BailoutReason::kNoReason) {
+    value->SetString("bailoutReason", GetBailoutReason(bailout_reason()));
+    value->SetBoolean("disableFutureOptimization",
+                      is_disable_future_optimization());
+  } else {
+    value->SetInteger("optimizationId", optimization_id());
+    value->BeginArray("inlinedFunctions");
+    for (auto const& inlined_function : inlined_functions()) {
+      value->BeginDictionary();
+      value->SetValue("function", inlined_function.shared_info->TraceIDRef());
+      // TODO(bmeurer): Also include the source position from the
+      // {inlined_function} here as dedicated "sourcePosition" field.
+      value->EndDictionary();
+    }
+    value->EndArray();
+  }
+  return value;
+}
+
 }  // namespace internal
 }  // namespace v8
diff --git a/src/optimized-compilation-info.h b/src/optimized-compilation-info.h
index 153dd94..c722a92 100644
--- a/src/optimized-compilation-info.h
+++ b/src/optimized-compilation-info.h
@@ -17,6 +17,11 @@
 #include "src/vector.h"
 
 namespace v8 {
+
+namespace tracing {
+class TracedValue;
+}
+
 namespace internal {
 
 class DeferredHandles;
@@ -225,20 +230,16 @@
 
   void ReopenHandlesInNewHandleScope(Isolate* isolate);
 
-  void AbortOptimization(BailoutReason reason) {
-    DCHECK_NE(reason, BailoutReason::kNoReason);
-    if (bailout_reason_ == BailoutReason::kNoReason) bailout_reason_ = reason;
-    SetFlag(kDisableFutureOptimization);
-  }
+  void AbortOptimization(BailoutReason reason);
 
-  void RetryOptimization(BailoutReason reason) {
-    DCHECK_NE(reason, BailoutReason::kNoReason);
-    if (GetFlag(kDisableFutureOptimization)) return;
-    bailout_reason_ = reason;
-  }
+  void RetryOptimization(BailoutReason reason);
 
   BailoutReason bailout_reason() const { return bailout_reason_; }
 
+  bool is_disable_future_optimization() const {
+    return GetFlag(kDisableFutureOptimization);
+  }
+
   int optimization_id() const {
     DCHECK(IsOptimizing());
     return optimization_id_;
@@ -278,6 +279,8 @@
     trace_turbo_filename_ = std::move(filename);
   }
 
+  std::unique_ptr<v8::tracing::TracedValue> ToTracedValue();
+
  private:
   OptimizedCompilationInfo(Code::Kind code_kind, Zone* zone);
   void ConfigureFlags();
diff --git a/src/snapshot/deserializer.cc b/src/snapshot/deserializer.cc
index 515ebbf..77f1526 100644
--- a/src/snapshot/deserializer.cc
+++ b/src/snapshot/deserializer.cc
@@ -24,6 +24,8 @@
 #include "src/roots.h"
 #include "src/snapshot/natives.h"
 #include "src/snapshot/snapshot.h"
+#include "src/tracing/trace-event.h"
+#include "src/tracing/traced-value.h"
 
 namespace v8 {
 namespace internal {
@@ -154,6 +156,13 @@
   LOG(isolate_,
       ScriptEvent(Logger::ScriptEventType::kDeserialize, script->id()));
   LOG(isolate_, ScriptDetails(script));
+  TRACE_EVENT_OBJECT_CREATED_WITH_ID(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "Script",
+      TRACE_ID_WITH_SCOPE("v8::internal::Script", script->id()));
+  TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+      TRACE_DISABLED_BY_DEFAULT("v8.compile"), "Script",
+      TRACE_ID_WITH_SCOPE("v8::internal::Script", script->id()),
+      script->ToTracedValue());
 }
 
 StringTableInsertionKey::StringTableInsertionKey(String string)
diff --git a/src/tracing/traced-value.cc b/src/tracing/traced-value.cc
index eeb41c4..67279c2 100644
--- a/src/tracing/traced-value.cc
+++ b/src/tracing/traced-value.cc
@@ -67,6 +67,7 @@
 
 }  // namespace
 
+// static
 std::unique_ptr<TracedValue> TracedValue::Create() {
   return std::unique_ptr<TracedValue>(new TracedValue());
 }
@@ -106,6 +107,14 @@
   EscapeAndAppendString(value, &data_);
 }
 
+void TracedValue::SetValue(const char* name, TracedValue* value) {
+  DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
+  WriteName(name);
+  std::string tmp;
+  value->AppendAsTraceFormat(&tmp);
+  data_ += tmp;
+}
+
 void TracedValue::BeginDictionary(const char* name) {
   DCHECK_CURRENT_CONTAINER_IS(kStackTypeDict);
   DEBUG_PUSH_CONTAINER(kStackTypeDict);
diff --git a/src/tracing/traced-value.h b/src/tracing/traced-value.h
index 7de4c23..d342451 100644
--- a/src/tracing/traced-value.h
+++ b/src/tracing/traced-value.h
@@ -33,6 +33,13 @@
   void SetString(const char* name, const std::string& value) {
     SetString(name, value.c_str());
   }
+  void SetString(const char* name, std::unique_ptr<char[]> value) {
+    SetString(name, value.get());
+  }
+  void SetValue(const char* name, TracedValue* value);
+  void SetValue(const char* name, std::unique_ptr<TracedValue> value) {
+    SetValue(name, value.get());
+  }
   void BeginDictionary(const char* name);
   void BeginArray(const char* name);
 
diff --git a/test/cctest/test-traced-value.cc b/test/cctest/test-traced-value.cc
index 101779d..7ad8c43 100644
--- a/test/cctest/test-traced-value.cc
+++ b/test/cctest/test-traced-value.cc
@@ -75,6 +75,19 @@
       json);
 }
 
+TEST(Nesting) {
+  auto value = TracedValue::Create();
+  auto v0 = TracedValue::Create();
+  auto v2 = TracedValue::Create();
+  v0->SetString("s1", std::string("Hello World!"));
+  v2->SetValue("v0", v0.get());
+  value->SetValue("v2", v2.get());
+
+  std::string json;
+  value->AppendAsTraceFormat(&json);
+  CHECK_EQ("{\"v2\":{\"v0\":{\"s1\":\"Hello World!\"}}}", json);
+}
+
 TEST(LongStrings) {
   std::string long_string = "supercalifragilisticexpialidocious";
   std::string long_string2 = "0123456789012345678901234567890123456789";