Add TraceForCategory* methods for legacy macros

Introduces additional TrackEvent template methods and redefines legacy
macros using them. The goal is to avoid instantiating templates taking
a lambda as a parameter, because this leads to binary size bloat.

This change saves ~220KB of binary size in Chrome Android build with
Perfetto client library.

Bug: 268328757
Change-Id: I1437e1348b7ab0667e029145063760b427c4011c
diff --git a/BUILD b/BUILD
index cc7ec83..716126d 100644
--- a/BUILD
+++ b/BUILD
@@ -676,6 +676,7 @@
         "include/perfetto/tracing/internal/track_event_data_source.h",
         "include/perfetto/tracing/internal/track_event_internal.h",
         "include/perfetto/tracing/internal/track_event_interned_fields.h",
+        "include/perfetto/tracing/internal/track_event_legacy.h",
         "include/perfetto/tracing/internal/track_event_macros.h",
         "include/perfetto/tracing/internal/write_track_event_args.h",
         "include/perfetto/tracing/locked_handle.h",
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 21abc8e..d417668 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -48,6 +48,7 @@
     "internal/track_event_data_source.h",
     "internal/track_event_internal.h",
     "internal/track_event_interned_fields.h",
+    "internal/track_event_legacy.h",
     "internal/track_event_macros.h",
     "internal/write_track_event_args.h",
     "locked_handle.h",
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 4f96fe9..0a636ef 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -24,6 +24,7 @@
 #include "perfetto/tracing/data_source.h"
 #include "perfetto/tracing/event_context.h"
 #include "perfetto/tracing/internal/track_event_internal.h"
+#include "perfetto/tracing/internal/track_event_legacy.h"
 #include "perfetto/tracing/internal/write_track_event_args.h"
 #include "perfetto/tracing/track.h"
 #include "perfetto/tracing/track_event_category_registry.h"
@@ -144,6 +145,22 @@
 
 }  // namespace
 
+inline ::perfetto::DynamicString DecayEventNameType(
+    ::perfetto::DynamicString name) {
+  return name;
+}
+
+inline ::perfetto::StaticString DecayEventNameType(
+    ::perfetto::StaticString name) {
+  return name;
+}
+
+// Convert all static strings of different length to StaticString to avoid
+// unnecessary template instantiations.
+inline ::perfetto::StaticString DecayEventNameType(const char* name) {
+  return ::perfetto::StaticString{name};
+}
+
 // Traits for dynamic categories.
 template <typename CategoryType>
 struct CategoryTraits {
@@ -432,6 +449,135 @@
         });
   }
 
+// Additional trace points used in legacy macros.
+// It's possible to implement legacy macros using a common TraceForCategory,
+// by supplying a lambda that sets all necessary legacy fields. But this
+// results in a binary size bloat because every trace point generates its own
+// template instantiation with its own lambda. ICF can't eliminate those as
+// each lambda captures different variables and so the code is not completely
+// identical.
+// What we do instead is define additional TraceForCategoryLegacy templates
+// that take legacy arguments directly. Their instantiations can have the same
+// binary code for at least some macro invocations and so can be successfully
+// folded by the linker.
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacy(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type, track,
+                         TrackEventInternal::GetTraceTime(),
+                         [&](perfetto::EventContext ctx)
+                             PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+                               using ::perfetto::internal::TrackEventLegacy;
+                               TrackEventLegacy::WriteLegacyEvent(
+                                   std::move(ctx), phase, flags, args...);
+                             });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacy(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track, timestamp,
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,
+                                             args...);
+        });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacyWithId(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track,
+        TrackEventInternal::GetTraceTime(),
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
+          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
+              std::move(ctx), phase, flags, trace_id, thread_id, args...);
+        });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacyWithId(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track, timestamp,
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
+          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
+              std::move(ctx), phase, flags, trace_id, thread_id, args...);
+        });
+  }
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
   // Initialize the track event library. Should be called before tracing is
   // enabled.
   static bool Register() {
diff --git a/include/perfetto/tracing/internal/track_event_legacy.h b/include/perfetto/tracing/internal/track_event_legacy.h
new file mode 100644
index 0000000..95b40ca
--- /dev/null
+++ b/include/perfetto/tracing/internal/track_event_legacy.h
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_LEGACY_H_
+#define INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_LEGACY_H_
+
+#ifndef PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+#define PERFETTO_ENABLE_LEGACY_TRACE_EVENTS 0
+#endif
+
+// ----------------------------------------------------------------------------
+// Constants.
+// ----------------------------------------------------------------------------
+
+namespace perfetto {
+namespace legacy {
+
+enum TraceEventFlag {
+  kTraceEventFlagNone = 0,
+  kTraceEventFlagCopy = 1u << 0,
+  kTraceEventFlagHasId = 1u << 1,
+  kTraceEventFlagScopeOffset = 1u << 2,
+  kTraceEventFlagScopeExtra = 1u << 3,
+  kTraceEventFlagExplicitTimestamp = 1u << 4,
+  kTraceEventFlagAsyncTTS = 1u << 5,
+  kTraceEventFlagBindToEnclosing = 1u << 6,
+  kTraceEventFlagFlowIn = 1u << 7,
+  kTraceEventFlagFlowOut = 1u << 8,
+  kTraceEventFlagHasContextId = 1u << 9,
+  kTraceEventFlagHasProcessId = 1u << 10,
+  kTraceEventFlagHasLocalId = 1u << 11,
+  kTraceEventFlagHasGlobalId = 1u << 12,
+  // TODO(eseckler): Remove once we have native support for typed proto events
+  // in TRACE_EVENT macros.
+  kTraceEventFlagTypedProtoArgs = 1u << 15,
+  kTraceEventFlagJavaStringLiterals = 1u << 16,
+};
+
+enum PerfettoLegacyCurrentThreadId { kCurrentThreadId };
+
+// The following user-provided adaptors are used to serialize user-defined
+// thread id and time types into track events. For full compatibility, the user
+// should also define the following macros appropriately:
+//
+//   #define TRACE_TIME_TICKS_NOW() ...
+//   #define TRACE_TIME_NOW() ...
+
+// User-provided function to convert an abstract thread id into a thread track.
+template <typename T>
+ThreadTrack ConvertThreadId(const T&);
+
+// Built-in implementation for events referring to the current thread.
+template <>
+ThreadTrack PERFETTO_EXPORT_COMPONENT
+ConvertThreadId(const PerfettoLegacyCurrentThreadId&);
+
+}  // namespace legacy
+}  // namespace perfetto
+
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+// The following constants are defined in the global namespace, since they were
+// originally implemented as macros.
+
+// Event phases.
+static constexpr char TRACE_EVENT_PHASE_BEGIN = 'B';
+static constexpr char TRACE_EVENT_PHASE_END = 'E';
+static constexpr char TRACE_EVENT_PHASE_COMPLETE = 'X';
+static constexpr char TRACE_EVENT_PHASE_INSTANT = 'I';
+static constexpr char TRACE_EVENT_PHASE_ASYNC_BEGIN = 'S';
+static constexpr char TRACE_EVENT_PHASE_ASYNC_STEP_INTO = 'T';
+static constexpr char TRACE_EVENT_PHASE_ASYNC_STEP_PAST = 'p';
+static constexpr char TRACE_EVENT_PHASE_ASYNC_END = 'F';
+static constexpr char TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN = 'b';
+static constexpr char TRACE_EVENT_PHASE_NESTABLE_ASYNC_END = 'e';
+static constexpr char TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT = 'n';
+static constexpr char TRACE_EVENT_PHASE_FLOW_BEGIN = 's';
+static constexpr char TRACE_EVENT_PHASE_FLOW_STEP = 't';
+static constexpr char TRACE_EVENT_PHASE_FLOW_END = 'f';
+static constexpr char TRACE_EVENT_PHASE_METADATA = 'M';
+static constexpr char TRACE_EVENT_PHASE_COUNTER = 'C';
+static constexpr char TRACE_EVENT_PHASE_SAMPLE = 'P';
+static constexpr char TRACE_EVENT_PHASE_CREATE_OBJECT = 'N';
+static constexpr char TRACE_EVENT_PHASE_SNAPSHOT_OBJECT = 'O';
+static constexpr char TRACE_EVENT_PHASE_DELETE_OBJECT = 'D';
+static constexpr char TRACE_EVENT_PHASE_MEMORY_DUMP = 'v';
+static constexpr char TRACE_EVENT_PHASE_MARK = 'R';
+static constexpr char TRACE_EVENT_PHASE_CLOCK_SYNC = 'c';
+
+// Flags for changing the behavior of TRACE_EVENT_API_ADD_TRACE_EVENT.
+static constexpr uint32_t TRACE_EVENT_FLAG_NONE =
+    perfetto::legacy::kTraceEventFlagNone;
+static constexpr uint32_t TRACE_EVENT_FLAG_COPY =
+    perfetto::legacy::kTraceEventFlagCopy;
+static constexpr uint32_t TRACE_EVENT_FLAG_HAS_ID =
+    perfetto::legacy::kTraceEventFlagHasId;
+static constexpr uint32_t TRACE_EVENT_FLAG_SCOPE_OFFSET =
+    perfetto::legacy::kTraceEventFlagScopeOffset;
+static constexpr uint32_t TRACE_EVENT_FLAG_SCOPE_EXTRA =
+    perfetto::legacy::kTraceEventFlagScopeExtra;
+static constexpr uint32_t TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP =
+    perfetto::legacy::kTraceEventFlagExplicitTimestamp;
+static constexpr uint32_t TRACE_EVENT_FLAG_ASYNC_TTS =
+    perfetto::legacy::kTraceEventFlagAsyncTTS;
+static constexpr uint32_t TRACE_EVENT_FLAG_BIND_TO_ENCLOSING =
+    perfetto::legacy::kTraceEventFlagBindToEnclosing;
+static constexpr uint32_t TRACE_EVENT_FLAG_FLOW_IN =
+    perfetto::legacy::kTraceEventFlagFlowIn;
+static constexpr uint32_t TRACE_EVENT_FLAG_FLOW_OUT =
+    perfetto::legacy::kTraceEventFlagFlowOut;
+static constexpr uint32_t TRACE_EVENT_FLAG_HAS_CONTEXT_ID =
+    perfetto::legacy::kTraceEventFlagHasContextId;
+static constexpr uint32_t TRACE_EVENT_FLAG_HAS_PROCESS_ID =
+    perfetto::legacy::kTraceEventFlagHasProcessId;
+static constexpr uint32_t TRACE_EVENT_FLAG_HAS_LOCAL_ID =
+    perfetto::legacy::kTraceEventFlagHasLocalId;
+static constexpr uint32_t TRACE_EVENT_FLAG_HAS_GLOBAL_ID =
+    perfetto::legacy::kTraceEventFlagHasGlobalId;
+static constexpr uint32_t TRACE_EVENT_FLAG_TYPED_PROTO_ARGS =
+    perfetto::legacy::kTraceEventFlagTypedProtoArgs;
+static constexpr uint32_t TRACE_EVENT_FLAG_JAVA_STRING_LITERALS =
+    perfetto::legacy::kTraceEventFlagJavaStringLiterals;
+
+static constexpr uint32_t TRACE_EVENT_FLAG_SCOPE_MASK =
+    TRACE_EVENT_FLAG_SCOPE_OFFSET | TRACE_EVENT_FLAG_SCOPE_EXTRA;
+
+// Type values for identifying types in the TraceValue union.
+static constexpr uint8_t TRACE_VALUE_TYPE_BOOL = 1;
+static constexpr uint8_t TRACE_VALUE_TYPE_UINT = 2;
+static constexpr uint8_t TRACE_VALUE_TYPE_INT = 3;
+static constexpr uint8_t TRACE_VALUE_TYPE_DOUBLE = 4;
+static constexpr uint8_t TRACE_VALUE_TYPE_POINTER = 5;
+static constexpr uint8_t TRACE_VALUE_TYPE_STRING = 6;
+static constexpr uint8_t TRACE_VALUE_TYPE_COPY_STRING = 7;
+static constexpr uint8_t TRACE_VALUE_TYPE_CONVERTABLE = 8;
+static constexpr uint8_t TRACE_VALUE_TYPE_PROTO = 9;
+
+// Enum reflecting the scope of an INSTANT event. Must fit within
+// TRACE_EVENT_FLAG_SCOPE_MASK.
+static constexpr uint8_t TRACE_EVENT_SCOPE_GLOBAL = 0u << 2;
+static constexpr uint8_t TRACE_EVENT_SCOPE_PROCESS = 1u << 2;
+static constexpr uint8_t TRACE_EVENT_SCOPE_THREAD = 2u << 2;
+
+static constexpr char TRACE_EVENT_SCOPE_NAME_GLOBAL = 'g';
+static constexpr char TRACE_EVENT_SCOPE_NAME_PROCESS = 'p';
+static constexpr char TRACE_EVENT_SCOPE_NAME_THREAD = 't';
+
+#define TRACE_EVENT_API_CURRENT_THREAD_ID ::perfetto::legacy::kCurrentThreadId
+
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
+namespace perfetto {
+namespace internal {
+
+// LegacyTraceId encapsulates an ID that can either be an integer or pointer.
+class PERFETTO_EXPORT_COMPONENT LegacyTraceId {
+ public:
+  // Can be combined with WithScope.
+  class LocalId {
+   public:
+    explicit LocalId(const void* raw_id)
+        : raw_id_(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(raw_id))) {}
+    explicit LocalId(uint64_t raw_id) : raw_id_(raw_id) {}
+    uint64_t raw_id() const { return raw_id_; }
+
+   private:
+    uint64_t raw_id_;
+  };
+
+  // Can be combined with WithScope.
+  class GlobalId {
+   public:
+    explicit GlobalId(uint64_t raw_id) : raw_id_(raw_id) {}
+    uint64_t raw_id() const { return raw_id_; }
+
+   private:
+    uint64_t raw_id_;
+  };
+
+  class WithScope {
+   public:
+    WithScope(const char* scope, uint64_t raw_id)
+        : scope_(scope), raw_id_(raw_id) {}
+    WithScope(const char* scope, LocalId local_id)
+        : scope_(scope), raw_id_(local_id.raw_id()) {
+      id_flags_ = legacy::kTraceEventFlagHasLocalId;
+    }
+    WithScope(const char* scope, GlobalId global_id)
+        : scope_(scope), raw_id_(global_id.raw_id()) {
+      id_flags_ = legacy::kTraceEventFlagHasGlobalId;
+    }
+    WithScope(const char* scope, uint64_t prefix, uint64_t raw_id)
+        : scope_(scope), has_prefix_(true), prefix_(prefix), raw_id_(raw_id) {}
+    WithScope(const char* scope, uint64_t prefix, GlobalId global_id)
+        : scope_(scope),
+          has_prefix_(true),
+          prefix_(prefix),
+          raw_id_(global_id.raw_id()) {
+      id_flags_ = legacy::kTraceEventFlagHasGlobalId;
+    }
+    uint64_t raw_id() const { return raw_id_; }
+    const char* scope() const { return scope_; }
+    bool has_prefix() const { return has_prefix_; }
+    uint64_t prefix() const { return prefix_; }
+    uint32_t id_flags() const { return id_flags_; }
+
+   private:
+    const char* scope_ = nullptr;
+    bool has_prefix_ = false;
+    uint64_t prefix_;
+    uint64_t raw_id_;
+    uint32_t id_flags_ = legacy::kTraceEventFlagHasId;
+  };
+
+  explicit LegacyTraceId(const void* raw_id)
+      : raw_id_(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(raw_id))) {
+    id_flags_ = legacy::kTraceEventFlagHasLocalId;
+  }
+  explicit LegacyTraceId(uint64_t raw_id) : raw_id_(raw_id) {}
+  explicit LegacyTraceId(uint32_t raw_id) : raw_id_(raw_id) {}
+  explicit LegacyTraceId(uint16_t raw_id) : raw_id_(raw_id) {}
+  explicit LegacyTraceId(uint8_t raw_id) : raw_id_(raw_id) {}
+  explicit LegacyTraceId(int64_t raw_id)
+      : raw_id_(static_cast<uint64_t>(raw_id)) {}
+  explicit LegacyTraceId(int32_t raw_id)
+      : raw_id_(static_cast<uint64_t>(raw_id)) {}
+  explicit LegacyTraceId(int16_t raw_id)
+      : raw_id_(static_cast<uint64_t>(raw_id)) {}
+  explicit LegacyTraceId(int8_t raw_id)
+      : raw_id_(static_cast<uint64_t>(raw_id)) {}
+// Different platforms disagree on which integer types are same and which
+// are different. E.g. on Mac size_t is considered a different type from
+// uint64_t even though it has the same size and signedness.
+// Below we add overloads for those types that are known to cause ambiguity.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+  explicit LegacyTraceId(size_t raw_id) : raw_id_(raw_id) {}
+  explicit LegacyTraceId(intptr_t raw_id)
+      : raw_id_(static_cast<uint64_t>(raw_id)) {}
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  explicit LegacyTraceId(unsigned long raw_id) : raw_id_(raw_id) {}
+#endif
+  explicit LegacyTraceId(LocalId raw_id) : raw_id_(raw_id.raw_id()) {
+    id_flags_ = legacy::kTraceEventFlagHasLocalId;
+  }
+  explicit LegacyTraceId(GlobalId raw_id) : raw_id_(raw_id.raw_id()) {
+    id_flags_ = legacy::kTraceEventFlagHasGlobalId;
+  }
+  explicit LegacyTraceId(WithScope scoped_id)
+      : scope_(scoped_id.scope()),
+        has_prefix_(scoped_id.has_prefix()),
+        prefix_(scoped_id.prefix()),
+        raw_id_(scoped_id.raw_id()),
+        id_flags_(scoped_id.id_flags()) {}
+
+  uint64_t raw_id() const { return raw_id_; }
+  const char* scope() const { return scope_; }
+  bool has_prefix() const { return has_prefix_; }
+  uint64_t prefix() const { return prefix_; }
+  uint32_t id_flags() const { return id_flags_; }
+
+  void Write(protos::pbzero::TrackEvent::LegacyEvent*,
+             uint32_t event_flags) const;
+
+ private:
+  const char* scope_ = nullptr;
+  bool has_prefix_ = false;
+  uint64_t prefix_;
+  uint64_t raw_id_;
+  uint32_t id_flags_ = legacy::kTraceEventFlagHasId;
+};
+
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+template <typename T>
+bool IsEqual(T x, T y) {
+  return x == y;
+}
+
+template <typename T, typename U>
+bool IsEqual(T, U) {
+  return false;
+}
+
+class PERFETTO_EXPORT_COMPONENT TrackEventLegacy {
+ public:
+  static constexpr protos::pbzero::TrackEvent::Type PhaseToType(char phase) {
+    // clang-format off
+    return (phase == TRACE_EVENT_PHASE_BEGIN) ?
+               protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN :
+           (phase == TRACE_EVENT_PHASE_END) ?
+               protos::pbzero::TrackEvent::TYPE_SLICE_END :
+           (phase == TRACE_EVENT_PHASE_INSTANT) ?
+               protos::pbzero::TrackEvent::TYPE_INSTANT :
+           protos::pbzero::TrackEvent::TYPE_UNSPECIFIED;
+    // clang-format on
+  }
+
+  // Reduce binary size overhead by outlining most of the code for writing a
+  // legacy trace event.
+  template <typename... Args>
+  static void WriteLegacyEvent(EventContext ctx,
+                               char phase,
+                               uint32_t flags,
+                               Args&&... args) PERFETTO_NO_INLINE {
+    PERFETTO_DCHECK(!(flags & TRACE_EVENT_FLAG_HAS_PROCESS_ID));
+    AddDebugAnnotations(&ctx, std::forward<Args>(args)...);
+    if (NeedLegacyFlags(phase, flags)) {
+      auto legacy_event = ctx.event()->set_legacy_event();
+      SetLegacyFlags(legacy_event, phase, flags);
+    }
+  }
+
+  template <typename ThreadIdType, typename... Args>
+  static void WriteLegacyEventWithIdAndTid(EventContext ctx,
+                                           char phase,
+                                           uint32_t flags,
+                                           const LegacyTraceId& id,
+                                           const ThreadIdType& thread_id,
+                                           Args&&... args) PERFETTO_NO_INLINE {
+    //
+    // Overrides to consider:
+    //
+    // 1. If we have an id, we need to write {unscoped,local,global}_id and/or
+    //    bind_id.
+    // 2. If we have a thread id, we need to write track_uuid() or
+    //    {pid,tid}_override if the id represents another process.  The
+    //    conversion from |thread_id| happens in embedder code since the type is
+    //    embedder-specified.
+    // 3. If we have a timestamp, we need to write a different timestamp in the
+    //    trace packet itself and make sure TrackEvent won't write one
+    //    internally. This is already done at the call site.
+    //
+    PERFETTO_DCHECK(PhaseToType(phase) ==
+                        protos::pbzero::TrackEvent::TYPE_UNSPECIFIED ||
+                    !(flags & TRACE_EVENT_FLAG_HAS_PROCESS_ID));
+    flags |= id.id_flags();
+    AddDebugAnnotations(&ctx, std::forward<Args>(args)...);
+    if (NeedLegacyFlags(phase, flags)) {
+      auto legacy_event = ctx.event()->set_legacy_event();
+      SetLegacyFlags(legacy_event, phase, flags);
+      if (id.id_flags())
+        id.Write(legacy_event, flags);
+      if (flags & TRACE_EVENT_FLAG_HAS_PROCESS_ID) {
+        // The thread identifier actually represents a process id. Let's set an
+        // override for it.
+        int32_t pid_override =
+            static_cast<int32_t>(legacy::ConvertThreadId(thread_id).tid);
+        legacy_event->set_pid_override(pid_override);
+        legacy_event->set_tid_override(-1);
+      } else {
+        // Only synchronous phases are supported for other threads. These phases
+        // are supported in TrackEvent types and receive a track_uuid
+        // association via TrackEventDataSource::TraceForCategoryImpl().
+        PERFETTO_DCHECK(PhaseToType(phase) !=
+                            protos::pbzero::TrackEvent::TYPE_UNSPECIFIED ||
+                        IsEqual(thread_id, TRACE_EVENT_API_CURRENT_THREAD_ID) ||
+                        legacy::ConvertThreadId(thread_id).tid ==
+                            ThreadTrack::Current().tid);
+      }
+    }
+  }
+
+  // No arguments.
+  static void AddDebugAnnotations(EventContext*) {}
+
+  // N number of debug arguments.
+  template <typename ArgNameType, typename ArgType, typename... OtherArgs>
+  static void AddDebugAnnotations(EventContext* ctx,
+                                  ArgNameType&& arg_name,
+                                  ArgType&& arg_value,
+                                  OtherArgs&&... more_args) {
+    TrackEventInternal::AddDebugAnnotation(ctx,
+                                           std::forward<ArgNameType>(arg_name),
+                                           std::forward<ArgType>(arg_value));
+    AddDebugAnnotations(ctx, std::forward<OtherArgs>(more_args)...);
+  }
+
+ private:
+  static bool NeedLegacyFlags(char phase, uint32_t flags) {
+    if (PhaseToType(phase) == protos::pbzero::TrackEvent::TYPE_UNSPECIFIED)
+      return true;
+    // TODO(skyostil): Implement/deprecate:
+    // - TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP
+    // - TRACE_EVENT_FLAG_HAS_CONTEXT_ID
+    // - TRACE_EVENT_FLAG_TYPED_PROTO_ARGS
+    // - TRACE_EVENT_FLAG_JAVA_STRING_LITERALS
+    return flags &
+           (TRACE_EVENT_FLAG_HAS_ID | TRACE_EVENT_FLAG_HAS_LOCAL_ID |
+            TRACE_EVENT_FLAG_HAS_GLOBAL_ID | TRACE_EVENT_FLAG_ASYNC_TTS |
+            TRACE_EVENT_FLAG_BIND_TO_ENCLOSING | TRACE_EVENT_FLAG_FLOW_IN |
+            TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_HAS_PROCESS_ID);
+  }
+
+  static void SetLegacyFlags(
+      protos::pbzero::TrackEvent::LegacyEvent* legacy_event,
+      char phase,
+      uint32_t flags) {
+    if (PhaseToType(phase) == protos::pbzero::TrackEvent::TYPE_UNSPECIFIED)
+      legacy_event->set_phase(phase);
+    if (flags & TRACE_EVENT_FLAG_ASYNC_TTS)
+      legacy_event->set_use_async_tts(true);
+    if (flags & TRACE_EVENT_FLAG_BIND_TO_ENCLOSING)
+      legacy_event->set_bind_to_enclosing(true);
+
+    const auto kFlowIn = TRACE_EVENT_FLAG_FLOW_IN;
+    const auto kFlowOut = TRACE_EVENT_FLAG_FLOW_OUT;
+    const auto kFlowInOut = kFlowIn | kFlowOut;
+    if ((flags & kFlowInOut) == kFlowInOut) {
+      legacy_event->set_flow_direction(
+          protos::pbzero::TrackEvent::LegacyEvent::FLOW_INOUT);
+    } else if (flags & kFlowIn) {
+      legacy_event->set_flow_direction(
+          protos::pbzero::TrackEvent::LegacyEvent::FLOW_IN);
+    } else if (flags & kFlowOut) {
+      legacy_event->set_flow_direction(
+          protos::pbzero::TrackEvent::LegacyEvent::FLOW_OUT);
+    }
+  }
+};
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
+}  // namespace internal
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_INTERNAL_TRACK_EVENT_LEGACY_H_
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index 3078f88..6d2777e 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -112,7 +112,7 @@
 
 // Efficiently determines whether tracing is enabled for the given category, and
 // if so, emits one trace event with the given arguments.
-#define PERFETTO_INTERNAL_TRACK_EVENT(category, name, ...)                     \
+#define PERFETTO_INTERNAL_TRACK_EVENT_WITH_METHOD(method, category, name, ...) \
   do {                                                                         \
     ::perfetto::internal::ValidateEventNameType<decltype(name)>();             \
     namespace tns = PERFETTO_TRACK_EVENT_NAMESPACE;                            \
@@ -125,14 +125,13 @@
             category)) {                                                       \
       tns::TrackEvent::CallIfEnabled(                                          \
           [&](uint32_t instances) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {         \
-            tns::TrackEvent::TraceForCategory(instances, category, name,       \
-                                              ##__VA_ARGS__);                  \
+            tns::TrackEvent::method(instances, category, name, ##__VA_ARGS__); \
           });                                                                  \
     } else {                                                                   \
       tns::TrackEvent::CallIfCategoryEnabled(                                  \
           PERFETTO_UID(kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_), \
           [&](uint32_t instances) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {         \
-            tns::TrackEvent::TraceForCategory(                                 \
+            tns::TrackEvent::method(                                           \
                 instances,                                                     \
                 PERFETTO_UID(                                                  \
                     kCatIndex_ADD_TO_PERFETTO_DEFINE_CATEGORIES_IF_FAILS_),    \
@@ -141,6 +140,19 @@
     }                                                                          \
   } while (false)
 
+#define PERFETTO_INTERNAL_TRACK_EVENT(...) \
+  PERFETTO_INTERNAL_TRACK_EVENT_WITH_METHOD(TraceForCategory, ##__VA_ARGS__)
+
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+#define PERFETTO_INTERNAL_LEGACY_TRACK_EVENT(...)                   \
+  PERFETTO_INTERNAL_TRACK_EVENT_WITH_METHOD(TraceForCategoryLegacy, \
+                                            ##__VA_ARGS__)
+
+#define PERFETTO_INTERNAL_LEGACY_TRACK_EVENT_WITH_ID(...)                 \
+  PERFETTO_INTERNAL_TRACK_EVENT_WITH_METHOD(TraceForCategoryLegacyWithId, \
+                                            ##__VA_ARGS__)
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
 // C++17 doesn't like a move constructor being defined for the EventFinalizer
 // class but C++11 and MSVC doesn't compile without it being defined so support
 // both.
@@ -150,7 +162,7 @@
 #define PERFETTO_INTERNAL_EVENT_FINALIZER_KEYWORD default
 #endif
 
-#define PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(category, name, ...)             \
+#define PERFETTO_INTERNAL_SCOPED_EVENT_FINALIZER(category)                    \
   struct PERFETTO_UID(ScopedEvent) {                                          \
     struct EventFinalizer {                                                   \
       /* The parameter is an implementation detail. It allows the          */ \
@@ -170,13 +182,34 @@
           PERFETTO_INTERNAL_EVENT_FINALIZER_KEYWORD;                          \
       EventFinalizer& operator=(EventFinalizer&&) = delete;                   \
     } finalizer;                                                              \
-  } PERFETTO_UID(scoped_event) {                                              \
-    [&]() {                                                                   \
-      TRACE_EVENT_BEGIN(category, name, ##__VA_ARGS__);                       \
-      return 0;                                                               \
-    }()                                                                       \
   }
 
+#define PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(category, name, ...) \
+  PERFETTO_INTERNAL_SCOPED_EVENT_FINALIZER(category)              \
+  PERFETTO_UID(scoped_event) {                                    \
+    [&]() {                                                       \
+      TRACE_EVENT_BEGIN(category, name, ##__VA_ARGS__);           \
+      return 0;                                                   \
+    }()                                                           \
+  }
+
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+// Required for TRACE_EVENT_WITH_FLOW legacy macros, which pass the bind_id as
+// id.
+#define PERFETTO_INTERNAL_SCOPED_LEGACY_TRACK_EVENT_WITH_ID(               \
+    category, name, track, flags, thread_id, id, ...)                      \
+  PERFETTO_INTERNAL_SCOPED_EVENT_FINALIZER(category)                       \
+  PERFETTO_UID(scoped_event) {                                             \
+    [&]() {                                                                \
+      PERFETTO_INTERNAL_LEGACY_TRACK_EVENT_WITH_ID(                        \
+          category, name,                                                  \
+          ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN, track, \
+          'B', flags, thread_id, id, ##__VA_ARGS__);                       \
+      return 0;                                                            \
+    }()                                                                    \
+  }
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
 #if PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC)
 // On GCC versions <9 there's a bug that prevents using captured constant
 // variables in constexpr evaluation inside a lambda:
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index 7366b85..f95d42c 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -126,6 +126,7 @@
 // If the program uses multiple track event namespaces, category & track event
 // registration (see quickstart above) needs to happen for both namespaces
 // separately.
+
 #ifndef PERFETTO_TRACK_EVENT_NAMESPACE
 #define PERFETTO_TRACK_EVENT_NAMESPACE perfetto_track_event
 #endif
@@ -350,9 +351,9 @@
 //   TRACE_EVENT("category", "Name", perfetto::Track(1234),
 //               "arg", value, "arg2", value2);
 //
-#define TRACE_EVENT_BEGIN(category, name, ...) \
-  PERFETTO_INTERNAL_TRACK_EVENT(               \
-      category, name,                          \
+#define TRACE_EVENT_BEGIN(category, name, ...)                  \
+  PERFETTO_INTERNAL_TRACK_EVENT(                                \
+      category, ::perfetto::internal::DecayEventNameType(name), \
       ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN, ##__VA_ARGS__)
 
 // End a slice under |category|.
@@ -363,13 +364,14 @@
 
 // Begin a slice which gets automatically closed when going out of scope.
 #define TRACE_EVENT(category, name, ...) \
-  PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(category, name, ##__VA_ARGS__)
+  PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(  \
+      category, ::perfetto::internal::DecayEventNameType(name), ##__VA_ARGS__)
 
 // Emit a slice which has zero duration.
-#define TRACE_EVENT_INSTANT(category, name, ...)                            \
-  PERFETTO_INTERNAL_TRACK_EVENT(                                            \
-      category, name, ::perfetto::protos::pbzero::TrackEvent::TYPE_INSTANT, \
-      ##__VA_ARGS__)
+#define TRACE_EVENT_INSTANT(category, name, ...)                \
+  PERFETTO_INTERNAL_TRACK_EVENT(                                \
+      category, ::perfetto::internal::DecayEventNameType(name), \
+      ::perfetto::protos::pbzero::TrackEvent::TYPE_INSTANT, ##__VA_ARGS__)
 
 // Efficiently determine if the given static or dynamic trace category or
 // category group is enabled for tracing.
diff --git a/include/perfetto/tracing/track_event_legacy.h b/include/perfetto/tracing/track_event_legacy.h
index 373e2a5..7111733 100644
--- a/include/perfetto/tracing/track_event_legacy.h
+++ b/include/perfetto/tracing/track_event_legacy.h
@@ -37,130 +37,6 @@
 #endif
 
 // ----------------------------------------------------------------------------
-// Constants.
-// ----------------------------------------------------------------------------
-
-namespace perfetto {
-namespace legacy {
-
-enum TraceEventFlag {
-  kTraceEventFlagNone = 0,
-  kTraceEventFlagCopy = 1u << 0,
-  kTraceEventFlagHasId = 1u << 1,
-  kTraceEventFlagScopeOffset = 1u << 2,
-  kTraceEventFlagScopeExtra = 1u << 3,
-  kTraceEventFlagExplicitTimestamp = 1u << 4,
-  kTraceEventFlagAsyncTTS = 1u << 5,
-  kTraceEventFlagBindToEnclosing = 1u << 6,
-  kTraceEventFlagFlowIn = 1u << 7,
-  kTraceEventFlagFlowOut = 1u << 8,
-  kTraceEventFlagHasContextId = 1u << 9,
-  kTraceEventFlagHasProcessId = 1u << 10,
-  kTraceEventFlagHasLocalId = 1u << 11,
-  kTraceEventFlagHasGlobalId = 1u << 12,
-  // TODO(eseckler): Remove once we have native support for typed proto events
-  // in TRACE_EVENT macros.
-  kTraceEventFlagTypedProtoArgs = 1u << 15,
-  kTraceEventFlagJavaStringLiterals = 1u << 16,
-};
-
-enum PerfettoLegacyCurrentThreadId { kCurrentThreadId };
-
-}  // namespace legacy
-}  // namespace perfetto
-
-#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
-// The following constants are defined in the global namespace, since they were
-// originally implemented as macros.
-
-// Event phases.
-static constexpr char TRACE_EVENT_PHASE_BEGIN = 'B';
-static constexpr char TRACE_EVENT_PHASE_END = 'E';
-static constexpr char TRACE_EVENT_PHASE_COMPLETE = 'X';
-static constexpr char TRACE_EVENT_PHASE_INSTANT = 'I';
-static constexpr char TRACE_EVENT_PHASE_ASYNC_BEGIN = 'S';
-static constexpr char TRACE_EVENT_PHASE_ASYNC_STEP_INTO = 'T';
-static constexpr char TRACE_EVENT_PHASE_ASYNC_STEP_PAST = 'p';
-static constexpr char TRACE_EVENT_PHASE_ASYNC_END = 'F';
-static constexpr char TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN = 'b';
-static constexpr char TRACE_EVENT_PHASE_NESTABLE_ASYNC_END = 'e';
-static constexpr char TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT = 'n';
-static constexpr char TRACE_EVENT_PHASE_FLOW_BEGIN = 's';
-static constexpr char TRACE_EVENT_PHASE_FLOW_STEP = 't';
-static constexpr char TRACE_EVENT_PHASE_FLOW_END = 'f';
-static constexpr char TRACE_EVENT_PHASE_METADATA = 'M';
-static constexpr char TRACE_EVENT_PHASE_COUNTER = 'C';
-static constexpr char TRACE_EVENT_PHASE_SAMPLE = 'P';
-static constexpr char TRACE_EVENT_PHASE_CREATE_OBJECT = 'N';
-static constexpr char TRACE_EVENT_PHASE_SNAPSHOT_OBJECT = 'O';
-static constexpr char TRACE_EVENT_PHASE_DELETE_OBJECT = 'D';
-static constexpr char TRACE_EVENT_PHASE_MEMORY_DUMP = 'v';
-static constexpr char TRACE_EVENT_PHASE_MARK = 'R';
-static constexpr char TRACE_EVENT_PHASE_CLOCK_SYNC = 'c';
-
-// Flags for changing the behavior of TRACE_EVENT_API_ADD_TRACE_EVENT.
-static constexpr uint32_t TRACE_EVENT_FLAG_NONE =
-    perfetto::legacy::kTraceEventFlagNone;
-static constexpr uint32_t TRACE_EVENT_FLAG_COPY =
-    perfetto::legacy::kTraceEventFlagCopy;
-static constexpr uint32_t TRACE_EVENT_FLAG_HAS_ID =
-    perfetto::legacy::kTraceEventFlagHasId;
-static constexpr uint32_t TRACE_EVENT_FLAG_SCOPE_OFFSET =
-    perfetto::legacy::kTraceEventFlagScopeOffset;
-static constexpr uint32_t TRACE_EVENT_FLAG_SCOPE_EXTRA =
-    perfetto::legacy::kTraceEventFlagScopeExtra;
-static constexpr uint32_t TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP =
-    perfetto::legacy::kTraceEventFlagExplicitTimestamp;
-static constexpr uint32_t TRACE_EVENT_FLAG_ASYNC_TTS =
-    perfetto::legacy::kTraceEventFlagAsyncTTS;
-static constexpr uint32_t TRACE_EVENT_FLAG_BIND_TO_ENCLOSING =
-    perfetto::legacy::kTraceEventFlagBindToEnclosing;
-static constexpr uint32_t TRACE_EVENT_FLAG_FLOW_IN =
-    perfetto::legacy::kTraceEventFlagFlowIn;
-static constexpr uint32_t TRACE_EVENT_FLAG_FLOW_OUT =
-    perfetto::legacy::kTraceEventFlagFlowOut;
-static constexpr uint32_t TRACE_EVENT_FLAG_HAS_CONTEXT_ID =
-    perfetto::legacy::kTraceEventFlagHasContextId;
-static constexpr uint32_t TRACE_EVENT_FLAG_HAS_PROCESS_ID =
-    perfetto::legacy::kTraceEventFlagHasProcessId;
-static constexpr uint32_t TRACE_EVENT_FLAG_HAS_LOCAL_ID =
-    perfetto::legacy::kTraceEventFlagHasLocalId;
-static constexpr uint32_t TRACE_EVENT_FLAG_HAS_GLOBAL_ID =
-    perfetto::legacy::kTraceEventFlagHasGlobalId;
-static constexpr uint32_t TRACE_EVENT_FLAG_TYPED_PROTO_ARGS =
-    perfetto::legacy::kTraceEventFlagTypedProtoArgs;
-static constexpr uint32_t TRACE_EVENT_FLAG_JAVA_STRING_LITERALS =
-    perfetto::legacy::kTraceEventFlagJavaStringLiterals;
-
-static constexpr uint32_t TRACE_EVENT_FLAG_SCOPE_MASK =
-    TRACE_EVENT_FLAG_SCOPE_OFFSET | TRACE_EVENT_FLAG_SCOPE_EXTRA;
-
-// Type values for identifying types in the TraceValue union.
-static constexpr uint8_t TRACE_VALUE_TYPE_BOOL = 1;
-static constexpr uint8_t TRACE_VALUE_TYPE_UINT = 2;
-static constexpr uint8_t TRACE_VALUE_TYPE_INT = 3;
-static constexpr uint8_t TRACE_VALUE_TYPE_DOUBLE = 4;
-static constexpr uint8_t TRACE_VALUE_TYPE_POINTER = 5;
-static constexpr uint8_t TRACE_VALUE_TYPE_STRING = 6;
-static constexpr uint8_t TRACE_VALUE_TYPE_COPY_STRING = 7;
-static constexpr uint8_t TRACE_VALUE_TYPE_CONVERTABLE = 8;
-static constexpr uint8_t TRACE_VALUE_TYPE_PROTO = 9;
-
-// Enum reflecting the scope of an INSTANT event. Must fit within
-// TRACE_EVENT_FLAG_SCOPE_MASK.
-static constexpr uint8_t TRACE_EVENT_SCOPE_GLOBAL = 0u << 2;
-static constexpr uint8_t TRACE_EVENT_SCOPE_PROCESS = 1u << 2;
-static constexpr uint8_t TRACE_EVENT_SCOPE_THREAD = 2u << 2;
-
-static constexpr char TRACE_EVENT_SCOPE_NAME_GLOBAL = 'g';
-static constexpr char TRACE_EVENT_SCOPE_NAME_PROCESS = 'p';
-static constexpr char TRACE_EVENT_SCOPE_NAME_THREAD = 't';
-
-#define TRACE_EVENT_API_CURRENT_THREAD_ID ::perfetto::legacy::kCurrentThreadId
-
-#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
-
-// ----------------------------------------------------------------------------
 // Internal legacy trace point implementation.
 // ----------------------------------------------------------------------------
 
@@ -184,310 +60,33 @@
 ConvertThreadId(const PerfettoLegacyCurrentThreadId&);
 
 }  // namespace legacy
-
-namespace internal {
-
-// LegacyTraceId encapsulates an ID that can either be an integer or pointer.
-class PERFETTO_EXPORT_COMPONENT LegacyTraceId {
- public:
-  // Can be combined with WithScope.
-  class LocalId {
-   public:
-    explicit LocalId(const void* raw_id)
-        : raw_id_(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(raw_id))) {}
-    explicit LocalId(uint64_t raw_id) : raw_id_(raw_id) {}
-    uint64_t raw_id() const { return raw_id_; }
-
-   private:
-    uint64_t raw_id_;
-  };
-
-  // Can be combined with WithScope.
-  class GlobalId {
-   public:
-    explicit GlobalId(uint64_t raw_id) : raw_id_(raw_id) {}
-    uint64_t raw_id() const { return raw_id_; }
-
-   private:
-    uint64_t raw_id_;
-  };
-
-  class WithScope {
-   public:
-    WithScope(const char* scope, uint64_t raw_id)
-        : scope_(scope), raw_id_(raw_id) {}
-    WithScope(const char* scope, LocalId local_id)
-        : scope_(scope), raw_id_(local_id.raw_id()) {
-      id_flags_ = legacy::kTraceEventFlagHasLocalId;
-    }
-    WithScope(const char* scope, GlobalId global_id)
-        : scope_(scope), raw_id_(global_id.raw_id()) {
-      id_flags_ = legacy::kTraceEventFlagHasGlobalId;
-    }
-    WithScope(const char* scope, uint64_t prefix, uint64_t raw_id)
-        : scope_(scope), has_prefix_(true), prefix_(prefix), raw_id_(raw_id) {}
-    WithScope(const char* scope, uint64_t prefix, GlobalId global_id)
-        : scope_(scope),
-          has_prefix_(true),
-          prefix_(prefix),
-          raw_id_(global_id.raw_id()) {
-      id_flags_ = legacy::kTraceEventFlagHasGlobalId;
-    }
-    uint64_t raw_id() const { return raw_id_; }
-    const char* scope() const { return scope_; }
-    bool has_prefix() const { return has_prefix_; }
-    uint64_t prefix() const { return prefix_; }
-    uint32_t id_flags() const { return id_flags_; }
-
-   private:
-    const char* scope_ = nullptr;
-    bool has_prefix_ = false;
-    uint64_t prefix_;
-    uint64_t raw_id_;
-    uint32_t id_flags_ = legacy::kTraceEventFlagHasId;
-  };
-
-  explicit LegacyTraceId(const void* raw_id)
-      : raw_id_(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(raw_id))) {
-    id_flags_ = legacy::kTraceEventFlagHasLocalId;
-  }
-  explicit LegacyTraceId(uint64_t raw_id) : raw_id_(raw_id) {}
-  explicit LegacyTraceId(uint32_t raw_id) : raw_id_(raw_id) {}
-  explicit LegacyTraceId(uint16_t raw_id) : raw_id_(raw_id) {}
-  explicit LegacyTraceId(uint8_t raw_id) : raw_id_(raw_id) {}
-  explicit LegacyTraceId(int64_t raw_id)
-      : raw_id_(static_cast<uint64_t>(raw_id)) {}
-  explicit LegacyTraceId(int32_t raw_id)
-      : raw_id_(static_cast<uint64_t>(raw_id)) {}
-  explicit LegacyTraceId(int16_t raw_id)
-      : raw_id_(static_cast<uint64_t>(raw_id)) {}
-  explicit LegacyTraceId(int8_t raw_id)
-      : raw_id_(static_cast<uint64_t>(raw_id)) {}
-// Different platforms disagree on which integer types are same and which
-// are different. E.g. on Mac size_t is considered a different type from
-// uint64_t even though it has the same size and signedness.
-// Below we add overloads for those types that are known to cause ambiguity.
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
-  explicit LegacyTraceId(size_t raw_id) : raw_id_(raw_id) {}
-  explicit LegacyTraceId(intptr_t raw_id)
-      : raw_id_(static_cast<uint64_t>(raw_id)) {}
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  explicit LegacyTraceId(unsigned long raw_id) : raw_id_(raw_id) {}
-#endif
-  explicit LegacyTraceId(LocalId raw_id) : raw_id_(raw_id.raw_id()) {
-    id_flags_ = legacy::kTraceEventFlagHasLocalId;
-  }
-  explicit LegacyTraceId(GlobalId raw_id) : raw_id_(raw_id.raw_id()) {
-    id_flags_ = legacy::kTraceEventFlagHasGlobalId;
-  }
-  explicit LegacyTraceId(WithScope scoped_id)
-      : scope_(scoped_id.scope()),
-        has_prefix_(scoped_id.has_prefix()),
-        prefix_(scoped_id.prefix()),
-        raw_id_(scoped_id.raw_id()),
-        id_flags_(scoped_id.id_flags()) {}
-
-  uint64_t raw_id() const { return raw_id_; }
-  const char* scope() const { return scope_; }
-  bool has_prefix() const { return has_prefix_; }
-  uint64_t prefix() const { return prefix_; }
-  uint32_t id_flags() const { return id_flags_; }
-
-  void Write(protos::pbzero::TrackEvent::LegacyEvent*,
-             uint32_t event_flags) const;
-
- private:
-  const char* scope_ = nullptr;
-  bool has_prefix_ = false;
-  uint64_t prefix_;
-  uint64_t raw_id_;
-  uint32_t id_flags_ = legacy::kTraceEventFlagHasId;
-};
-
-}  // namespace internal
 }  // namespace perfetto
 
 #if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
 
-namespace perfetto {
-namespace internal {
-
-template <typename T>
-bool IsEqual(T x, T y) {
-  return x == y;
-}
-
-template <typename T, typename U>
-bool IsEqual(T x, U y) {
-  return false;
-}
-
-class PERFETTO_EXPORT_COMPONENT TrackEventLegacy {
- public:
-  static constexpr protos::pbzero::TrackEvent::Type PhaseToType(char phase) {
-    // clang-format off
-    return (phase == TRACE_EVENT_PHASE_BEGIN) ?
-               protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN :
-           (phase == TRACE_EVENT_PHASE_END) ?
-               protos::pbzero::TrackEvent::TYPE_SLICE_END :
-           (phase == TRACE_EVENT_PHASE_INSTANT) ?
-               protos::pbzero::TrackEvent::TYPE_INSTANT :
-           protos::pbzero::TrackEvent::TYPE_UNSPECIFIED;
-    // clang-format on
-  }
-
-  // Reduce binary size overhead by outlining most of the code for writing a
-  // legacy trace event.
-  template <typename... Args>
-  static void WriteLegacyEvent(EventContext ctx,
-                               char phase,
-                               uint32_t flags,
-                               Args&&... args) PERFETTO_NO_INLINE {
-    PERFETTO_DCHECK(!(flags & TRACE_EVENT_FLAG_HAS_PROCESS_ID));
-    AddDebugAnnotations(&ctx, std::forward<Args>(args)...);
-    if (NeedLegacyFlags(phase, flags)) {
-      auto legacy_event = ctx.event()->set_legacy_event();
-      SetLegacyFlags(legacy_event, phase, flags);
-    }
-  }
-
-  template <typename ThreadIdType, typename... Args>
-  static void WriteLegacyEventWithIdAndTid(EventContext ctx,
-                                           char phase,
-                                           uint32_t flags,
-                                           const LegacyTraceId& id,
-                                           const ThreadIdType& thread_id,
-                                           Args&&... args) PERFETTO_NO_INLINE {
-    //
-    // Overrides to consider:
-    //
-    // 1. If we have an id, we need to write {unscoped,local,global}_id and/or
-    //    bind_id.
-    // 2. If we have a thread id, we need to write track_uuid() or
-    //    {pid,tid}_override if the id represents another process.  The
-    //    conversion from |thread_id| happens in embedder code since the type is
-    //    embedder-specified.
-    // 3. If we have a timestamp, we need to write a different timestamp in the
-    //    trace packet itself and make sure TrackEvent won't write one
-    //    internally. This is already done at the call site.
-    //
-    PERFETTO_DCHECK(PhaseToType(phase) ==
-                        protos::pbzero::TrackEvent::TYPE_UNSPECIFIED ||
-                    !(flags & TRACE_EVENT_FLAG_HAS_PROCESS_ID));
-    flags |= id.id_flags();
-    AddDebugAnnotations(&ctx, std::forward<Args>(args)...);
-    if (NeedLegacyFlags(phase, flags)) {
-      auto legacy_event = ctx.event()->set_legacy_event();
-      SetLegacyFlags(legacy_event, phase, flags);
-      if (id.id_flags())
-        id.Write(legacy_event, flags);
-      if (flags & TRACE_EVENT_FLAG_HAS_PROCESS_ID) {
-        // The thread identifier actually represents a process id. Let's set an
-        // override for it.
-        int32_t pid_override =
-            static_cast<int32_t>(legacy::ConvertThreadId(thread_id).tid);
-        legacy_event->set_pid_override(pid_override);
-        legacy_event->set_tid_override(-1);
-      } else {
-        // Only synchronous phases are supported for other threads. These phases
-        // are supported in TrackEvent types and receive a track_uuid
-        // association via TrackEventDataSource::TraceForCategoryImpl().
-        PERFETTO_DCHECK(PhaseToType(phase) !=
-                            protos::pbzero::TrackEvent::TYPE_UNSPECIFIED ||
-                        IsEqual(thread_id, TRACE_EVENT_API_CURRENT_THREAD_ID) ||
-                        legacy::ConvertThreadId(thread_id).tid ==
-                            ThreadTrack::Current().tid);
-      }
-    }
-  }
-
-  // No arguments.
-  static void AddDebugAnnotations(EventContext*) {}
-
-  // N number of debug arguments.
-  template <typename ArgNameType, typename ArgType, typename... OtherArgs>
-  static void AddDebugAnnotations(EventContext* ctx,
-                                  ArgNameType&& arg_name,
-                                  ArgType&& arg_value,
-                                  OtherArgs&&... more_args) {
-    TrackEventInternal::AddDebugAnnotation(ctx,
-                                           std::forward<ArgNameType>(arg_name),
-                                           std::forward<ArgType>(arg_value));
-    AddDebugAnnotations(ctx, std::forward<OtherArgs>(more_args)...);
-  }
-
- private:
-  static bool NeedLegacyFlags(char phase, uint32_t flags) {
-    if (PhaseToType(phase) == protos::pbzero::TrackEvent::TYPE_UNSPECIFIED)
-      return true;
-    // TODO(skyostil): Implement/deprecate:
-    // - TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP
-    // - TRACE_EVENT_FLAG_HAS_CONTEXT_ID
-    // - TRACE_EVENT_FLAG_TYPED_PROTO_ARGS
-    // - TRACE_EVENT_FLAG_JAVA_STRING_LITERALS
-    return flags &
-           (TRACE_EVENT_FLAG_HAS_ID | TRACE_EVENT_FLAG_HAS_LOCAL_ID |
-            TRACE_EVENT_FLAG_HAS_GLOBAL_ID | TRACE_EVENT_FLAG_ASYNC_TTS |
-            TRACE_EVENT_FLAG_BIND_TO_ENCLOSING | TRACE_EVENT_FLAG_FLOW_IN |
-            TRACE_EVENT_FLAG_FLOW_OUT | TRACE_EVENT_FLAG_HAS_PROCESS_ID);
-  }
-
-  static void SetLegacyFlags(
-      protos::pbzero::TrackEvent::LegacyEvent* legacy_event,
-      char phase,
-      uint32_t flags) {
-    if (PhaseToType(phase) == protos::pbzero::TrackEvent::TYPE_UNSPECIFIED)
-      legacy_event->set_phase(phase);
-    if (flags & TRACE_EVENT_FLAG_ASYNC_TTS)
-      legacy_event->set_use_async_tts(true);
-    if (flags & TRACE_EVENT_FLAG_BIND_TO_ENCLOSING)
-      legacy_event->set_bind_to_enclosing(true);
-
-    const auto kFlowIn = TRACE_EVENT_FLAG_FLOW_IN;
-    const auto kFlowOut = TRACE_EVENT_FLAG_FLOW_OUT;
-    const auto kFlowInOut = kFlowIn | kFlowOut;
-    if ((flags & kFlowInOut) == kFlowInOut) {
-      legacy_event->set_flow_direction(
-          protos::pbzero::TrackEvent::LegacyEvent::FLOW_INOUT);
-    } else if (flags & kFlowIn) {
-      legacy_event->set_flow_direction(
-          protos::pbzero::TrackEvent::LegacyEvent::FLOW_IN);
-    } else if (flags & kFlowOut) {
-      legacy_event->set_flow_direction(
-          protos::pbzero::TrackEvent::LegacyEvent::FLOW_OUT);
-    }
-  }
-};
-
-inline ::perfetto::DynamicString GetEventNameTypeForLegacyEvents(
-    ::perfetto::DynamicString name) {
-  return name;
-}
-
-inline ::perfetto::StaticString GetEventNameTypeForLegacyEvents(
-    ::perfetto::StaticString name) {
-  return name;
-}
-
-// In legacy macro, `const char*` is considered static by default, unless it's
-// wrapped in `TRACE_STR_COPY`.
-inline ::perfetto::StaticString GetEventNameTypeForLegacyEvents(
-    const char* name) {
-  return ::perfetto::StaticString{name};
-}
-
-}  // namespace internal
-}  // namespace perfetto
-
 // Implementations for the INTERNAL_* adapter macros used by the trace points
 // below.
 #define PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(phase, category, name, track, \
                                                 ...)                          \
   PERFETTO_INTERNAL_TRACK_EVENT(                                              \
-      category, ::perfetto::internal::GetEventNameTypeForLegacyEvents(name),  \
+      category, ::perfetto::internal::DecayEventNameType(name),               \
       ::perfetto::internal::TrackEventLegacy::PhaseToType(phase), track,      \
       ##__VA_ARGS__);
 
+#define PERFETTO_INTERNAL_LEGACY_EVENT_WITH_FLAGS_ON_TRACK(              \
+    phase, category, name, track, flags, ...)                            \
+  PERFETTO_INTERNAL_LEGACY_TRACK_EVENT(                                  \
+      category, ::perfetto::internal::DecayEventNameType(name),          \
+      ::perfetto::internal::TrackEventLegacy::PhaseToType(phase), track, \
+      phase, flags, ##__VA_ARGS__);
+
+#define PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID_ON_TRACK(                 \
+    phase, category, name, track, flags, thread_id, id, ...)             \
+  PERFETTO_INTERNAL_LEGACY_TRACK_EVENT_WITH_ID(                          \
+      category, ::perfetto::internal::DecayEventNameType(name),          \
+      ::perfetto::internal::TrackEventLegacy::PhaseToType(phase), track, \
+      phase, flags, thread_id, id, ##__VA_ARGS__);
+
 // The main entrypoint for writing unscoped legacy events.  This macro
 // determines the right track to write the event on based on |flags| and
 // |thread_id|.
@@ -502,14 +101,14 @@
       auto scope = (flags)&TRACE_EVENT_FLAG_SCOPE_MASK;                    \
       switch (scope) {                                                     \
         case TRACE_EVENT_SCOPE_GLOBAL:                                     \
-          PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(                         \
-              phase, category, name, ::perfetto::Track::Global(0),         \
+          PERFETTO_INTERNAL_LEGACY_EVENT_WITH_FLAGS_ON_TRACK(              \
+              phase, category, name, ::perfetto::Track::Global(0), flags,  \
               ##__VA_ARGS__);                                              \
           return;                                                          \
         case TRACE_EVENT_SCOPE_PROCESS:                                    \
-          PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(                         \
+          PERFETTO_INTERNAL_LEGACY_EVENT_WITH_FLAGS_ON_TRACK(              \
               phase, category, name, ::perfetto::ProcessTrack::Current(),  \
-              ##__VA_ARGS__);                                              \
+              flags, ##__VA_ARGS__);                                       \
           return;                                                          \
         default:                                                           \
         case TRACE_EVENT_SCOPE_THREAD:                                     \
@@ -524,84 +123,94 @@
             decltype(thread_id),                                           \
             ::perfetto::legacy::PerfettoLegacyCurrentThreadId>::value ||   \
         ((flags)&TRACE_EVENT_FLAG_HAS_PROCESS_ID)) {                       \
-      PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(                             \
-          phase, category, name, TrackEventInternal::kDefaultTrack,        \
+      PERFETTO_INTERNAL_LEGACY_EVENT_WITH_FLAGS_ON_TRACK(                  \
+          phase, category, name, TrackEventInternal::kDefaultTrack, flags, \
           ##__VA_ARGS__);                                                  \
     } else {                                                               \
-      PERFETTO_INTERNAL_LEGACY_EVENT_ON_TRACK(                             \
+      PERFETTO_INTERNAL_LEGACY_EVENT_WITH_FLAGS_ON_TRACK(                  \
           phase, category, name,                                           \
-          ::perfetto::legacy::ConvertThreadId(thread_id), ##__VA_ARGS__);  \
+          ::perfetto::legacy::ConvertThreadId(thread_id), flags,           \
+          ##__VA_ARGS__);                                                  \
     }                                                                      \
   }()
 
-#define INTERNAL_TRACE_EVENT_ADD(phase, category, name, flags, ...)        \
-  PERFETTO_INTERNAL_LEGACY_EVENT(                                          \
-      phase, category, name, flags, ::perfetto::legacy::kCurrentThreadId,  \
-      [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS { \
-        using ::perfetto::internal::TrackEventLegacy;                      \
-        TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,   \
-                                           ##__VA_ARGS__);                 \
-      })
+#define PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID(phase, category, name, flags, \
+                                               thread_id, id, ...)           \
+  [&]() {                                                                    \
+    using ::perfetto::internal::TrackEventInternal;                          \
+    PERFETTO_DCHECK(!(flags & TRACE_EVENT_FLAG_COPY));                       \
+    /* First check the scope for instant events. */                          \
+    if ((phase) == TRACE_EVENT_PHASE_INSTANT) {                              \
+      /* Note: Avoids the need to set LegacyEvent::instant_event_scope. */   \
+      auto scope = (flags)&TRACE_EVENT_FLAG_SCOPE_MASK;                      \
+      switch (scope) {                                                       \
+        case TRACE_EVENT_SCOPE_GLOBAL:                                       \
+          PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID_ON_TRACK(                   \
+              phase, category, name, ::perfetto::Track::Global(0), flags,    \
+              thread_id, id, ##__VA_ARGS__);                                 \
+          return;                                                            \
+        case TRACE_EVENT_SCOPE_PROCESS:                                      \
+          PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID_ON_TRACK(                   \
+              phase, category, name, ::perfetto::ProcessTrack::Current(),    \
+              flags, thread_id, id, ##__VA_ARGS__);                          \
+          return;                                                            \
+        default:                                                             \
+        case TRACE_EVENT_SCOPE_THREAD:                                       \
+          /* Fallthrough. */                                                 \
+          break;                                                             \
+      }                                                                      \
+    }                                                                        \
+    /* If an event targets the current thread or another process, write      \
+     * it on the current thread's track. The process override case is        \
+     * handled through |pid_override| in WriteLegacyEvent. */                \
+    if (std::is_same<                                                        \
+            decltype(thread_id),                                             \
+            ::perfetto::legacy::PerfettoLegacyCurrentThreadId>::value ||     \
+        ((flags)&TRACE_EVENT_FLAG_HAS_PROCESS_ID)) {                         \
+      PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID_ON_TRACK(                       \
+          phase, category, name, TrackEventInternal::kDefaultTrack, flags,   \
+          thread_id, id, ##__VA_ARGS__);                                     \
+    } else {                                                                 \
+      PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID_ON_TRACK(                       \
+          phase, category, name,                                             \
+          ::perfetto::legacy::ConvertThreadId(thread_id), flags, thread_id,  \
+          id, ##__VA_ARGS__);                                                \
+    }                                                                        \
+  }()
 
-// PERFETTO_INTERNAL_SCOPED_TRACK_EVENT does not require GetStaticString, as it
-// uses TRACE_EVENT_BEGIN/END internally, which already have this call.
-#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name, ...)                 \
-  PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(                                      \
-      category, ::perfetto::internal::GetEventNameTypeForLegacyEvents(name), \
-      [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {   \
-        using ::perfetto::internal::TrackEventLegacy;                        \
-        TrackEventLegacy::AddDebugAnnotations(&ctx, ##__VA_ARGS__);          \
-      })
+#define INTERNAL_TRACE_EVENT_ADD(phase, category, name, flags, ...)           \
+  PERFETTO_INTERNAL_LEGACY_EVENT(                                             \
+      phase, category, ::perfetto::internal::DecayEventNameType(name), flags, \
+      ::perfetto::legacy::kCurrentThreadId, ##__VA_ARGS__)
 
-// PERFETTO_INTERNAL_SCOPED_TRACK_EVENT does not require GetStaticString, as it
-// uses TRACE_EVENT_BEGIN/END internally, which already have this call.
-#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(category, name, bind_id,   \
-                                                  flags, ...)                \
-  PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(                                      \
-      category, ::perfetto::internal::GetEventNameTypeForLegacyEvents(name), \
-      [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {   \
-        using ::perfetto::internal::TrackEventLegacy;                        \
-        ::perfetto::internal::LegacyTraceId PERFETTO_UID(trace_id){bind_id}; \
-        TrackEventLegacy::WriteLegacyEventWithIdAndTid(                      \
-            std::move(ctx), TRACE_EVENT_PHASE_BEGIN, flags,                  \
-            PERFETTO_UID(trace_id), TRACE_EVENT_API_CURRENT_THREAD_ID,       \
-            ##__VA_ARGS__);                                                  \
-      })
+#define INTERNAL_TRACE_EVENT_ADD_SCOPED(category, name, ...) \
+  PERFETTO_INTERNAL_SCOPED_TRACK_EVENT(                      \
+      category, ::perfetto::internal::DecayEventNameType(name), ##__VA_ARGS__)
 
-#define INTERNAL_TRACE_EVENT_ADD_WITH_TIMESTAMP(phase, category, name,     \
-                                                timestamp, flags, ...)     \
-  PERFETTO_INTERNAL_LEGACY_EVENT(                                          \
-      phase, category, name, flags, ::perfetto::legacy::kCurrentThreadId,  \
-      timestamp,                                                           \
-      [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS { \
-        using ::perfetto::internal::TrackEventLegacy;                      \
-        TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,   \
-                                           ##__VA_ARGS__);                 \
-      })
+#define INTERNAL_TRACE_EVENT_ADD_SCOPED_WITH_FLOW(category, name, bind_id, \
+                                                  flags, ...)              \
+  PERFETTO_INTERNAL_SCOPED_LEGACY_TRACK_EVENT_WITH_ID(                     \
+      category, ::perfetto::internal::DecayEventNameType(name),            \
+      ::perfetto::internal::TrackEventInternal::kDefaultTrack, flags,      \
+      TRACE_EVENT_API_CURRENT_THREAD_ID, bind_id, ##__VA_ARGS__)
 
-#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(                  \
-    phase, category, name, id, thread_id, timestamp, flags, ...)             \
-  PERFETTO_INTERNAL_LEGACY_EVENT(                                            \
-      phase, category, name, flags, thread_id, timestamp,                    \
-      [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {   \
-        using ::perfetto::internal::TrackEventLegacy;                        \
-        ::perfetto::internal::LegacyTraceId PERFETTO_UID(trace_id){id};      \
-        TrackEventLegacy::WriteLegacyEventWithIdAndTid(                      \
-            std::move(ctx), phase, flags, PERFETTO_UID(trace_id), thread_id, \
-            ##__VA_ARGS__);                                                  \
-      })
+#define INTERNAL_TRACE_EVENT_ADD_WITH_TIMESTAMP(phase, category, name,        \
+                                                timestamp, flags, ...)        \
+  PERFETTO_INTERNAL_LEGACY_EVENT(                                             \
+      phase, category, ::perfetto::internal::DecayEventNameType(name), flags, \
+      ::perfetto::legacy::kCurrentThreadId, timestamp, ##__VA_ARGS__)
 
-#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category, name, id, flags, \
-                                         ...)                              \
-  PERFETTO_INTERNAL_LEGACY_EVENT(                                          \
-      phase, category, name, flags, ::perfetto::legacy::kCurrentThreadId,  \
-      [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS { \
-        using ::perfetto::internal::TrackEventLegacy;                      \
-        ::perfetto::internal::LegacyTraceId PERFETTO_UID(trace_id){id};    \
-        TrackEventLegacy::WriteLegacyEventWithIdAndTid(                    \
-            std::move(ctx), phase, flags, PERFETTO_UID(trace_id),          \
-            TRACE_EVENT_API_CURRENT_THREAD_ID, ##__VA_ARGS__);             \
-      })
+#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP(                   \
+    phase, category, name, id, thread_id, timestamp, flags, ...)              \
+  PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID(                                     \
+      phase, category, ::perfetto::internal::DecayEventNameType(name), flags, \
+      thread_id, id, timestamp, ##__VA_ARGS__)
+
+#define INTERNAL_TRACE_EVENT_ADD_WITH_ID(phase, category, name, id, flags,    \
+                                         ...)                                 \
+  PERFETTO_INTERNAL_LEGACY_EVENT_WITH_ID(                                     \
+      phase, category, ::perfetto::internal::DecayEventNameType(name), flags, \
+      ::perfetto::legacy::kCurrentThreadId, id, ##__VA_ARGS__)
 
 #define INTERNAL_TRACE_EVENT_METADATA_ADD(category, name, ...)         \
   INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_METADATA, category, name, \