Merge "[stdlib]: Fix typo in oom_adjuster bucket" into main
diff --git a/Android.bp b/Android.bp
index a4b4894..4267433 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2323,10 +2323,9 @@
         ":perfetto_src_shared_lib_test_utils",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
-        ":perfetto_src_trace_processor_db_column_make_chain",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
-        ":perfetto_src_trace_processor_export_json_sources",
+        ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_common_common",
         ":perfetto_src_trace_processor_importers_common_parser_types",
@@ -2376,7 +2375,6 @@
         ":perfetto_src_trace_processor_util_gzip",
         ":perfetto_src_trace_processor_util_interned_message_view",
         ":perfetto_src_trace_processor_util_profile_builder",
-        ":perfetto_src_trace_processor_util_profiler_util",
         ":perfetto_src_trace_processor_util_proto_profiler",
         ":perfetto_src_trace_processor_util_proto_to_args_parser",
         ":perfetto_src_trace_processor_util_protozero_to_json",
@@ -10956,14 +10954,6 @@
     ],
 }
 
-// GN: //src/trace_processor/db/column:make_chain
-filegroup {
-    name: "perfetto_src_trace_processor_db_column_make_chain",
-    srcs: [
-        "src/trace_processor/db/column/make_chain.cc",
-    ],
-}
-
 // GN: //src/trace_processor/db/column:unittests
 filegroup {
     name: "perfetto_src_trace_processor_db_column_unittests",
@@ -11008,7 +10998,6 @@
 filegroup {
     name: "perfetto_src_trace_processor_db_unittests",
     srcs: [
-        "src/trace_processor/db/column_storage_overlay_unittest.cc",
         "src/trace_processor/db/compare_unittest.cc",
         "src/trace_processor/db/query_executor_unittest.cc",
         "src/trace_processor/db/runtime_table_unittest.cc",
@@ -11032,9 +11021,9 @@
     ],
 }
 
-// GN: //src/trace_processor:export_json_sources
+// GN: //src/trace_processor:export_json
 filegroup {
-    name: "perfetto_src_trace_processor_export_json_sources",
+    name: "perfetto_src_trace_processor_export_json",
     srcs: [
         "src/trace_processor/export_json.cc",
     ],
@@ -11089,7 +11078,6 @@
         "src/trace_processor/importers/common/process_tracker.cc",
         "src/trace_processor/importers/common/slice_tracker.cc",
         "src/trace_processor/importers/common/slice_translation_table.cc",
-        "src/trace_processor/importers/common/stack_profile_tracker.cc",
         "src/trace_processor/importers/common/system_info_tracker.cc",
         "src/trace_processor/importers/common/trace_parser.cc",
         "src/trace_processor/importers/common/track_tracker.cc",
@@ -11409,6 +11397,7 @@
         "src/trace_processor/importers/proto/chrome_system_probes_module.cc",
         "src/trace_processor/importers/proto/chrome_system_probes_parser.cc",
         "src/trace_processor/importers/proto/default_modules.cc",
+        "src/trace_processor/importers/proto/heap_profile_tracker.cc",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc",
         "src/trace_processor/importers/proto/metadata_minimal_module.cc",
@@ -11417,12 +11406,12 @@
         "src/trace_processor/importers/proto/packet_sequence_state_generation.cc",
         "src/trace_processor/importers/proto/perf_sample_tracker.cc",
         "src/trace_processor/importers/proto/profile_module.cc",
-        "src/trace_processor/importers/proto/profile_packet_sequence_state.cc",
         "src/trace_processor/importers/proto/profile_packet_utils.cc",
+        "src/trace_processor/importers/proto/profiler_util.cc",
         "src/trace_processor/importers/proto/proto_trace_parser.cc",
         "src/trace_processor/importers/proto/proto_trace_reader.cc",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
-        "src/trace_processor/importers/proto/stack_profile_sequence_state.cc",
+        "src/trace_processor/importers/proto/stack_profile_tracker.cc",
         "src/trace_processor/importers/proto/track_event_module.cc",
         "src/trace_processor/importers/proto/track_event_parser.cc",
         "src/trace_processor/importers/proto/track_event_tokenizer.cc",
@@ -11449,9 +11438,9 @@
     srcs: [
         "src/trace_processor/importers/proto/active_chrome_processes_tracker_unittest.cc",
         "src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc",
+        "src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc",
         "src/trace_processor/importers/proto/network_trace_module_unittest.cc",
         "src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc",
-        "src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc",
         "src/trace_processor/importers/proto/proto_trace_parser_unittest.cc",
         "src/trace_processor/importers/proto/string_encoding_utils_unittests.cc",
     ],
@@ -12331,14 +12320,6 @@
     ],
 }
 
-// GN: //src/trace_processor/util:profiler_util
-filegroup {
-    name: "perfetto_src_trace_processor_util_profiler_util",
-    srcs: [
-        "src/trace_processor/util/profiler_util.cc",
-    ],
-}
-
 // GN: //src/trace_processor/util:proto_profiler
 filegroup {
     name: "perfetto_src_trace_processor_util_proto_profiler",
@@ -13813,13 +13794,12 @@
         ":perfetto_src_trace_processor_containers_unittests",
         ":perfetto_src_trace_processor_db_column_column",
         ":perfetto_src_trace_processor_db_column_fake_storage",
-        ":perfetto_src_trace_processor_db_column_make_chain",
         ":perfetto_src_trace_processor_db_column_unittests",
         ":perfetto_src_trace_processor_db_compare",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
         ":perfetto_src_trace_processor_db_unittests",
-        ":perfetto_src_trace_processor_export_json_sources",
+        ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_android_bugreport_unittests",
         ":perfetto_src_trace_processor_importers_common_common",
@@ -13891,7 +13871,6 @@
         ":perfetto_src_trace_processor_util_gzip",
         ":perfetto_src_trace_processor_util_interned_message_view",
         ":perfetto_src_trace_processor_util_profile_builder",
-        ":perfetto_src_trace_processor_util_profiler_util",
         ":perfetto_src_trace_processor_util_proto_profiler",
         ":perfetto_src_trace_processor_util_proto_to_args_parser",
         ":perfetto_src_trace_processor_util_protozero_to_json",
@@ -14534,10 +14513,9 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
-        ":perfetto_src_trace_processor_db_column_make_chain",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
-        ":perfetto_src_trace_processor_export_json_sources",
+        ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_common_common",
         ":perfetto_src_trace_processor_importers_common_parser_types",
@@ -14590,7 +14568,6 @@
         ":perfetto_src_trace_processor_util_gzip",
         ":perfetto_src_trace_processor_util_interned_message_view",
         ":perfetto_src_trace_processor_util_profile_builder",
-        ":perfetto_src_trace_processor_util_profiler_util",
         ":perfetto_src_trace_processor_util_proto_profiler",
         ":perfetto_src_trace_processor_util_proto_to_args_parser",
         ":perfetto_src_trace_processor_util_protozero_to_json",
@@ -14771,10 +14748,9 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_column_column",
-        ":perfetto_src_trace_processor_db_column_make_chain",
         ":perfetto_src_trace_processor_db_db",
         ":perfetto_src_trace_processor_db_minimal",
-        ":perfetto_src_trace_processor_export_json_sources",
+        ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_common_common",
         ":perfetto_src_trace_processor_importers_common_parser_types",
@@ -14824,7 +14800,6 @@
         ":perfetto_src_trace_processor_util_gzip",
         ":perfetto_src_trace_processor_util_interned_message_view",
         ":perfetto_src_trace_processor_util_profile_builder",
-        ":perfetto_src_trace_processor_util_profiler_util",
         ":perfetto_src_trace_processor_util_proto_profiler",
         ":perfetto_src_trace_processor_util_proto_to_args_parser",
         ":perfetto_src_trace_processor_util_protozero_to_json",
diff --git a/BUILD b/BUILD
index fe8598c..e6649d9 100644
--- a/BUILD
+++ b/BUILD
@@ -217,10 +217,9 @@
         ":src_kernel_utils_syscall_table",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
-        ":src_trace_processor_db_column_make_chain",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
-        ":src_trace_processor_export_json_sources",
+        ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
         ":src_trace_processor_importers_common_parser_types",
@@ -273,7 +272,6 @@
         ":src_trace_processor_util_gzip",
         ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_profile_builder",
-        ":src_trace_processor_util_profiler_util",
         ":src_trace_processor_util_proto_profiler",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_json",
@@ -1403,14 +1401,6 @@
     ],
 )
 
-# GN target: //src/trace_processor/db/column:make_chain
-perfetto_filegroup(
-    name = "src_trace_processor_db_column_make_chain",
-    srcs = [
-        "src/trace_processor/db/column/make_chain.cc",
-    ],
-)
-
 # GN target: //src/trace_processor/db:db
 perfetto_filegroup(
     name = "src_trace_processor_db_db",
@@ -1483,8 +1473,6 @@
         "src/trace_processor/importers/common/slice_tracker.h",
         "src/trace_processor/importers/common/slice_translation_table.cc",
         "src/trace_processor/importers/common/slice_translation_table.h",
-        "src/trace_processor/importers/common/stack_profile_tracker.cc",
-        "src/trace_processor/importers/common/stack_profile_tracker.h",
         "src/trace_processor/importers/common/system_info_tracker.cc",
         "src/trace_processor/importers/common/system_info_tracker.h",
         "src/trace_processor/importers/common/trace_parser.cc",
@@ -1833,6 +1821,8 @@
         "src/trace_processor/importers/proto/chrome_system_probes_parser.h",
         "src/trace_processor/importers/proto/default_modules.cc",
         "src/trace_processor/importers/proto/default_modules.h",
+        "src/trace_processor/importers/proto/heap_profile_tracker.cc",
+        "src/trace_processor/importers/proto/heap_profile_tracker.h",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_module.cc",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_module.h",
         "src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc",
@@ -1849,10 +1839,10 @@
         "src/trace_processor/importers/proto/perf_sample_tracker.h",
         "src/trace_processor/importers/proto/profile_module.cc",
         "src/trace_processor/importers/proto/profile_module.h",
-        "src/trace_processor/importers/proto/profile_packet_sequence_state.cc",
-        "src/trace_processor/importers/proto/profile_packet_sequence_state.h",
         "src/trace_processor/importers/proto/profile_packet_utils.cc",
         "src/trace_processor/importers/proto/profile_packet_utils.h",
+        "src/trace_processor/importers/proto/profiler_util.cc",
+        "src/trace_processor/importers/proto/profiler_util.h",
         "src/trace_processor/importers/proto/proto_incremental_state.h",
         "src/trace_processor/importers/proto/proto_trace_parser.cc",
         "src/trace_processor/importers/proto/proto_trace_parser.h",
@@ -1860,8 +1850,8 @@
         "src/trace_processor/importers/proto/proto_trace_reader.h",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.cc",
         "src/trace_processor/importers/proto/proto_trace_tokenizer.h",
-        "src/trace_processor/importers/proto/stack_profile_sequence_state.cc",
-        "src/trace_processor/importers/proto/stack_profile_sequence_state.h",
+        "src/trace_processor/importers/proto/stack_profile_tracker.cc",
+        "src/trace_processor/importers/proto/stack_profile_tracker.h",
         "src/trace_processor/importers/proto/track_event_module.cc",
         "src/trace_processor/importers/proto/track_event_module.h",
         "src/trace_processor/importers/proto/track_event_parser.cc",
@@ -2723,15 +2713,6 @@
     ],
 )
 
-# GN target: //src/trace_processor/util:profiler_util
-perfetto_filegroup(
-    name = "src_trace_processor_util_profiler_util",
-    srcs = [
-        "src/trace_processor/util/profiler_util.cc",
-        "src/trace_processor/util/profiler_util.h",
-    ],
-)
-
 # GN target: //src/trace_processor/util:proto_profiler
 perfetto_filegroup(
     name = "src_trace_processor_util_proto_profiler",
@@ -2841,9 +2822,9 @@
     linkstatic = True,
 )
 
-# GN target: //src/trace_processor:export_json_sources
+# GN target: //src/trace_processor:export_json
 perfetto_filegroup(
-    name = "src_trace_processor_export_json_sources",
+    name = "src_trace_processor_export_json",
     srcs = [
         "src/trace_processor/export_json.cc",
         "src/trace_processor/export_json.h",
@@ -5555,10 +5536,9 @@
     srcs = [
         ":src_kernel_utils_syscall_table",
         ":src_trace_processor_db_column_column",
-        ":src_trace_processor_db_column_make_chain",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
-        ":src_trace_processor_export_json_sources",
+        ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
         ":src_trace_processor_importers_common_parser_types",
@@ -5610,7 +5590,6 @@
         ":src_trace_processor_util_gzip",
         ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_profile_builder",
-        ":src_trace_processor_util_profiler_util",
         ":src_trace_processor_util_proto_profiler",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_json",
@@ -5723,10 +5702,9 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
-        ":src_trace_processor_db_column_make_chain",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
-        ":src_trace_processor_export_json_sources",
+        ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
         ":src_trace_processor_importers_common_parser_types",
@@ -5781,7 +5759,6 @@
         ":src_trace_processor_util_gzip",
         ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_profile_builder",
-        ":src_trace_processor_util_profiler_util",
         ":src_trace_processor_util_proto_profiler",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_json",
@@ -5949,10 +5926,9 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_column_column",
-        ":src_trace_processor_db_column_make_chain",
         ":src_trace_processor_db_db",
         ":src_trace_processor_db_minimal",
-        ":src_trace_processor_export_json_sources",
+        ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
         ":src_trace_processor_importers_common_parser_types",
@@ -6004,7 +5980,6 @@
         ":src_trace_processor_util_gzip",
         ":src_trace_processor_util_interned_message_view",
         ":src_trace_processor_util_profile_builder",
-        ":src_trace_processor_util_profiler_util",
         ":src_trace_processor_util_proto_profiler",
         ":src_trace_processor_util_proto_to_args_parser",
         ":src_trace_processor_util_protozero_to_json",
diff --git a/protos/perfetto/config/android/protolog_config.proto b/protos/perfetto/config/android/protolog_config.proto
index 6798bd3..c73256e 100644
--- a/protos/perfetto/config/android/protolog_config.proto
+++ b/protos/perfetto/config/android/protolog_config.proto
@@ -47,4 +47,7 @@
   // e.g. if ProtoLogLevel.WARN is specified only warning, errors and fatal log
   // message will be traced.
   optional ProtoLogLevel log_from = 2;
+  // When set to true we will collect the stacktrace for each protolog message
+  // in this group that we are tracing.
+  optional bool collect_stacktrace = 3;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index ab07107..d91246b 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -600,6 +600,9 @@
   // e.g. if ProtoLogLevel.WARN is specified only warning, errors and fatal log
   // message will be traced.
   optional ProtoLogLevel log_from = 2;
+  // When set to true we will collect the stacktrace for each protolog message
+  // in this group that we are tracing.
+  optional bool collect_stacktrace = 3;
 }
 
 // End of protos/perfetto/config/android/protolog_config.proto
diff --git a/protos/perfetto/trace/android/protolog.proto b/protos/perfetto/trace/android/protolog.proto
index 49e5521..b520c7a 100644
--- a/protos/perfetto/trace/android/protolog.proto
+++ b/protos/perfetto/trace/android/protolog.proto
@@ -25,13 +25,16 @@
   /* log statement identifier, created from message string and log level. */
   optional fixed64 message_id = 1;
   /* string parameters passed to the log call that have been interned. */
-  repeated uint32 interned_str_params = 2;
+  repeated uint32 str_param_iids = 2;
   /* integer parameters passed to the log call. */
   repeated sint64 sint64_params = 3;
   /* floating point parameters passed to the log call. */
   repeated double double_params = 4;
   /* boolean parameters passed to the log call. */
   repeated int32 boolean_params = 5;
+  // id of the interned stacktrace string
+  // (only dumped if explicitly confuigured to do so)
+  optional uint32 stacktrace_iid = 6;
 }
 
 /* contains all the data required to fully decode the protolog messages */
diff --git a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
index 99a6e26..5bfab9dd 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
@@ -31,10 +31,6 @@
   // Set to true if there was data loss between the last time we've read from
   // the corresponding per-cpu kernel buffer, and the earliest event recorded
   // in this bundle.
-  // TODO(rsavitski): consider replicating in the newer FtraceError. This field
-  // has been set since the original implementation, but used only for manual
-  // debugging. Trace processor instead looks at the FtraceStats totals for the
-  // tracing session.
   optional bool lost_events = 3;
 
   // Optionally-enabled compact encoding of a batch of scheduling events. Only
diff --git a/protos/perfetto/trace/ftrace/ftrace_stats.proto b/protos/perfetto/trace/ftrace/ftrace_stats.proto
index c5f06b9..f921779 100644
--- a/protos/perfetto/trace/ftrace/ftrace_stats.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_stats.proto
@@ -40,6 +40,8 @@
 
   // Size of entries currently in the kernel buffer (see |entries|) in bytes.
   // The field should be named "bytes", but is misnamed for historical reasons.
+  // This value has known inaccuracies before Linux v6.6:
+  // https://github.com/torvalds/linux/commit/45d99ea
   optional uint64 bytes_read = 5;
 
   // The timestamp for the oldest event still in the ring buffer.
diff --git a/protos/perfetto/trace/interned_data/interned_data.proto b/protos/perfetto/trace/interned_data/interned_data.proto
index 401ea13..2cae9c6 100644
--- a/protos/perfetto/trace/interned_data/interned_data.proto
+++ b/protos/perfetto/trace/interned_data/interned_data.proto
@@ -135,4 +135,6 @@
 
   // Interned protolog strings args.
   repeated InternedString protolog_string_args = 36;
+  // Interned protolog stacktraces.
+  repeated InternedString protolog_stacktrace = 37;
 }
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 41a0a31..f9370cd 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -600,6 +600,9 @@
   // e.g. if ProtoLogLevel.WARN is specified only warning, errors and fatal log
   // message will be traced.
   optional ProtoLogLevel log_from = 2;
+  // When set to true we will collect the stacktrace for each protolog message
+  // in this group that we are tracing.
+  optional bool collect_stacktrace = 3;
 }
 
 // End of protos/perfetto/config/android/protolog_config.proto
@@ -4848,13 +4851,16 @@
   /* log statement identifier, created from message string and log level. */
   optional fixed64 message_id = 1;
   /* string parameters passed to the log call that have been interned. */
-  repeated uint32 interned_str_params = 2;
+  repeated uint32 str_param_iids = 2;
   /* integer parameters passed to the log call. */
   repeated sint64 sint64_params = 3;
   /* floating point parameters passed to the log call. */
   repeated double double_params = 4;
   /* boolean parameters passed to the log call. */
   repeated int32 boolean_params = 5;
+  // id of the interned stacktrace string
+  // (only dumped if explicitly confuigured to do so)
+  optional uint32 stacktrace_iid = 6;
 }
 
 /* contains all the data required to fully decode the protolog messages */
@@ -10444,6 +10450,8 @@
 
   // Size of entries currently in the kernel buffer (see |entries|) in bytes.
   // The field should be named "bytes", but is misnamed for historical reasons.
+  // This value has known inaccuracies before Linux v6.6:
+  // https://github.com/torvalds/linux/commit/45d99ea
   optional uint64 bytes_read = 5;
 
   // The timestamp for the oldest event still in the ring buffer.
@@ -10555,10 +10563,6 @@
   // Set to true if there was data loss between the last time we've read from
   // the corresponding per-cpu kernel buffer, and the earliest event recorded
   // in this bundle.
-  // TODO(rsavitski): consider replicating in the newer FtraceError. This field
-  // has been set since the original implementation, but used only for manual
-  // debugging. Trace processor instead looks at the FtraceStats totals for the
-  // tracing session.
   optional bool lost_events = 3;
 
   // Optionally-enabled compact encoding of a batch of scheduling events. Only
@@ -12455,6 +12459,8 @@
 
   // Interned protolog strings args.
   repeated InternedString protolog_string_args = 36;
+  // Interned protolog stacktraces.
+  repeated InternedString protolog_stacktrace = 37;
 }
 
 // End of protos/perfetto/trace/interned_data/interned_data.proto
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index 1f92f44..2881ce0 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -675,16 +675,14 @@
   Iterator IterateRows() {{ return Iterator(this, Table::IterateRows()); }}
 
   ConstIterator FilterToIterator(
-      const std::vector<Constraint>& cs,
-      RowMap::OptimizeFor opt = RowMap::OptimizeFor::kMemory) const {{
+      const std::vector<Constraint>& cs) const {{
     return ConstIterator(
-      this, ApplyAndIterateRows(QueryToRowMap(cs, {{}}, opt)));
+      this, ApplyAndIterateRows(QueryToRowMap(cs, {{}})));
   }}
 
   Iterator FilterToIterator(
-      const std::vector<Constraint>& cs,
-      RowMap::OptimizeFor opt = RowMap::OptimizeFor::kMemory) {{
-    return Iterator(this, ApplyAndIterateRows(QueryToRowMap(cs, {{}}, opt)));
+      const std::vector<Constraint>& cs) {{
+    return Iterator(this, ApplyAndIterateRows(QueryToRowMap(cs, {{}})));
   }}
 
   void ShrinkToFit() {{
diff --git a/src/base/debug_crash_stack_trace.cc b/src/base/debug_crash_stack_trace.cc
index 4a16f29..9e1ef80 100644
--- a/src/base/debug_crash_stack_trace.cc
+++ b/src/base/debug_crash_stack_trace.cc
@@ -149,7 +149,11 @@
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  auto bt_error = [](void*, const char* msg, int) { Print(msg); };
+  auto bt_error = [](void*, const char* msg, int) {
+    Print("libbacktrace error: ");
+    perfetto::base::WriteAll(STDERR_FILENO, msg, strlen(msg));
+    Print("\n");
+  };
   struct backtrace_state* bt_state =
       backtrace_create_state(nullptr, 0, bt_error, nullptr);
 #endif
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index dab8c3f..694b515 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -46,11 +46,19 @@
 # Depended upon by Chrome to do proto -> JSON conversion of traces.
 # Must be small binary size as all code here needs to be shipped in
 # Chrome.
-static_library("export_json") {
+source_set("export_json") {
+  sources = [
+    "export_json.cc",
+    "export_json.h",
+  ]
   deps = [
-    ":export_json_sources",
+    ":storage_minimal",
     "../../gn:default_deps",
-    "db/column:make_chain_minimal",
+    "../../include/perfetto/ext/trace_processor:export_json",
+    "../base",
+    "importers/json:minimal",
+    "storage",
+    "types",
   ]
   public_deps = [ "../../include/perfetto/ext/trace_processor:export_json" ]
 }
@@ -129,22 +137,6 @@
   public_deps = [ "../../include/perfetto/trace_processor:storage" ]
 }
 
-source_set("export_json_sources") {
-  sources = [
-    "export_json.cc",
-    "export_json.h",
-  ]
-  deps = [
-    ":storage_minimal",
-    "../../gn:default_deps",
-    "../../include/perfetto/ext/trace_processor:export_json",
-    "../base",
-    "importers/json:minimal",
-    "storage",
-    "types",
-  ]
-}
-
 if (enable_perfetto_trace_processor_sqlite) {
   source_set("lib") {
     sources = [
@@ -168,7 +160,6 @@
       "../base",
       "../protozero",
       "db",
-      "db/column:make_chain",
       "importers/android_bugreport",
       "importers/common",
       "importers/etw:full",
@@ -262,7 +253,7 @@
     # windows.
     sources += [ "export_json_unittest.cc" ]
     deps += [
-      ":export_json_sources",
+      ":export_json",
       "../../gn:jsoncpp",
       "../../include/perfetto/ext/trace_processor:export_json",
       "containers",
@@ -364,6 +355,5 @@
     ":storage_minimal",
     "../../gn:default_deps",
     "../base",
-    "db/column:make_chain_minimal",
   ]
 }
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index 541bed2..884e1ca 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -299,11 +299,10 @@
   // Creates a BitVector of size |end| with the bits between |start| and |end|
   // filled by calling the filler function |f(index of bit)|.
   //
-  // As an example, suppose Range(3, 7, [](x) { return x < 5 }). This would
-  // result in the following BitVector:
-  // [0 0 0 1 1 0 0]
+  // As an example, suppose RangeForTesting(3, 7, [](x) { return x < 5 }). This
+  // would result in the following BitVector: [0 0 0 1 1 0 0]
   template <typename Filler = bool(uint32_t)>
-  static BitVector Range(uint32_t start, uint32_t end, Filler f) {
+  static BitVector RangeForTesting(uint32_t start, uint32_t end, Filler f) {
     // Compute the block index and BitVector index where we start and end
     // working one block at a time.
     uint32_t start_fast_block = BlockCount(start);
diff --git a/src/trace_processor/containers/bit_vector_benchmark.cc b/src/trace_processor/containers/bit_vector_benchmark.cc
index 87d29af..fd723a6 100644
--- a/src/trace_processor/containers/bit_vector_benchmark.cc
+++ b/src/trace_processor/containers/bit_vector_benchmark.cc
@@ -222,28 +222,6 @@
 }
 BENCHMARK(BM_BitVectorResize);
 
-static void BM_BitVectorRangeFixedSize(benchmark::State& state) {
-  static constexpr uint32_t kRandomSeed = 42;
-  std::minstd_rand0 rnd_engine(kRandomSeed);
-
-  uint32_t size = static_cast<uint32_t>(state.range(0));
-  uint32_t set_percentage = static_cast<uint32_t>(state.range(1));
-
-  std::vector<uint32_t> resize_fill_pool(size);
-  for (uint32_t i = 0; i < size; ++i) {
-    resize_fill_pool[i] = rnd_engine() % 100 < set_percentage ? 90 : 100;
-  }
-
-  for (auto _ : state) {
-    auto filler = [&resize_fill_pool](uint32_t i) PERFETTO_ALWAYS_INLINE {
-      return resize_fill_pool[i] < 95;
-    };
-    BitVector bv = BitVector::Range(0, size, filler);
-    benchmark::ClobberMemory();
-  }
-}
-BENCHMARK(BM_BitVectorRangeFixedSize)->Apply(BitVectorArgs);
-
 static void BM_BitVectorUpdateSetBits(benchmark::State& state) {
   static constexpr uint32_t kRandomSeed = 42;
   std::minstd_rand0 rnd_engine(kRandomSeed);
diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
index 47a8aff..d803cef 100644
--- a/src/trace_processor/containers/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -455,7 +455,8 @@
 }
 
 TEST(BitVectorUnittest, IntersectRange) {
-  BitVector bv = BitVector::Range(1, 20, [](uint32_t t) { return t % 2 == 0; });
+  BitVector bv =
+      BitVector::RangeForTesting(1, 20, [](uint32_t t) { return t % 2 == 0; });
   BitVector intersected = bv.IntersectRange(3, 10);
 
   ASSERT_EQ(intersected.IndexOfNthSet(0), 4u);
@@ -463,7 +464,8 @@
 }
 
 TEST(BitVectorUnittest, IntersectRangeFromStart) {
-  BitVector bv = BitVector::Range(1, 20, [](uint32_t t) { return t % 2 == 0; });
+  BitVector bv =
+      BitVector::RangeForTesting(1, 20, [](uint32_t t) { return t % 2 == 0; });
   BitVector intersected = bv.IntersectRange(0, 10);
 
   ASSERT_EQ(intersected.IndexOfNthSet(0), 2u);
@@ -478,8 +480,8 @@
 }
 
 TEST(BitVectorUnittest, IntersectRangeAfterWord) {
-  BitVector bv =
-      BitVector::Range(64 + 1, 64 + 20, [](uint32_t t) { return t % 2 == 0; });
+  BitVector bv = BitVector::RangeForTesting(
+      64 + 1, 64 + 20, [](uint32_t t) { return t % 2 == 0; });
   BitVector intersected = bv.IntersectRange(64 + 3, 64 + 10);
 
   ASSERT_EQ(intersected.IndexOfNthSet(0), 64 + 4u);
@@ -487,7 +489,8 @@
 }
 
 TEST(BitVectorUnittest, IntersectRangeSetBitsBeforeRange) {
-  BitVector bv = BitVector::Range(10, 30, [](uint32_t t) { return t < 15; });
+  BitVector bv =
+      BitVector::RangeForTesting(10, 30, [](uint32_t t) { return t < 15; });
   BitVector intersected = bv.IntersectRange(16, 50);
 
   ASSERT_FALSE(intersected.CountSetBits());
@@ -503,8 +506,8 @@
 }
 
 TEST(BitVectorUnittest, IntersectRangeStressTest) {
-  BitVector bv =
-      BitVector::Range(65, 1024 + 1, [](uint32_t t) { return t % 2 == 0; });
+  BitVector bv = BitVector::RangeForTesting(
+      65, 1024 + 1, [](uint32_t t) { return t % 2 == 0; });
   BitVector intersected = bv.IntersectRange(30, 500);
 
   ASSERT_EQ(intersected.IndexOfNthSet(0), 66u);
@@ -512,7 +515,8 @@
 }
 
 TEST(BitVectorUnittest, Range) {
-  BitVector bv = BitVector::Range(1, 9, [](uint32_t t) { return t % 3 == 0; });
+  BitVector bv =
+      BitVector::RangeForTesting(1, 9, [](uint32_t t) { return t % 3 == 0; });
   ASSERT_EQ(bv.size(), 9u);
 
   ASSERT_FALSE(bv.IsSet(0));
@@ -523,8 +527,8 @@
 }
 
 TEST(BitVectorUnittest, RangeStressTest) {
-  BitVector bv =
-      BitVector::Range(1, 1025, [](uint32_t t) { return t % 3 == 0; });
+  BitVector bv = BitVector::RangeForTesting(
+      1, 1025, [](uint32_t t) { return t % 3 == 0; });
   ASSERT_EQ(bv.size(), 1025u);
   ASSERT_FALSE(bv.IsSet(0));
   for (uint32_t i = 1; i < 1025; ++i) {
@@ -654,8 +658,8 @@
 }
 
 TEST(BitVectorUnittest, NotBig) {
-  BitVector bv =
-      BitVector::Range(0, 1026, [](uint32_t i) { return i % 5 == 0; });
+  BitVector bv = BitVector::RangeForTesting(
+      0, 1026, [](uint32_t i) { return i % 5 == 0; });
   bv.Not();
 
   EXPECT_EQ(bv.CountSetBits(), 820u);
@@ -673,13 +677,13 @@
 }
 
 TEST(BitVectorUnittest, OrBig) {
-  BitVector bv =
-      BitVector::Range(0, 1025, [](uint32_t i) { return i % 5 == 0; });
-  BitVector bv_sec =
-      BitVector::Range(0, 1025, [](uint32_t i) { return i % 3 == 0; });
+  BitVector bv = BitVector::RangeForTesting(
+      0, 1025, [](uint32_t i) { return i % 5 == 0; });
+  BitVector bv_sec = BitVector::RangeForTesting(
+      0, 1025, [](uint32_t i) { return i % 3 == 0; });
   bv.Or(bv_sec);
 
-  BitVector bv_or = BitVector::Range(
+  BitVector bv_or = BitVector::RangeForTesting(
       0, 1025, [](uint32_t i) { return i % 5 == 0 || i % 3 == 0; });
 
   ASSERT_EQ(bv.CountSetBits(), bv_or.CountSetBits());
diff --git a/src/trace_processor/containers/row_map.cc b/src/trace_processor/containers/row_map.cc
index 4ec4e8d..f7925ae 100644
--- a/src/trace_processor/containers/row_map.cc
+++ b/src/trace_processor/containers/row_map.cc
@@ -217,8 +217,7 @@
 
 RowMap::RowMap() : RowMap(Range()) {}
 
-RowMap::RowMap(uint32_t start, uint32_t end, OptimizeFor optimize_for)
-    : data_(Range{start, end}), optimize_for_(optimize_for) {}
+RowMap::RowMap(uint32_t start, uint32_t end) : data_(Range{start, end}) {}
 
 RowMap::RowMap(Variant def) : data_(std::move(def)) {}
 
diff --git a/src/trace_processor/containers/row_map.h b/src/trace_processor/containers/row_map.h
index a4e31b6..b8f1d3d 100644
--- a/src/trace_processor/containers/row_map.h
+++ b/src/trace_processor/containers/row_map.h
@@ -174,22 +174,13 @@
     const RowMap* rm_ = nullptr;
   };
 
-  // Enum to allow users of RowMap to decide whether they want to optimize for
-  // memory usage or for speed of lookups.
-  enum class OptimizeFor {
-    kMemory,
-    kLookupSpeed,
-  };
-
   // Creates an empty RowMap.
   // By default this will be implemented using a range.
   RowMap();
 
   // Creates a RowMap containing the range of indices between |start| and |end|
   // i.e. all indices between |start| (inclusive) and |end| (exclusive).
-  RowMap(OutputIndex start,
-         OutputIndex end,
-         OptimizeFor optimize_for = OptimizeFor::kMemory);
+  RowMap(OutputIndex start, OutputIndex end);
 
   // Creates a RowMap backed by a BitVector.
   explicit RowMap(BitVector);
@@ -514,58 +505,6 @@
 
   explicit RowMap(Variant);
 
-  // TODO(lalitm): remove this when the coupling between RowMap and
-  // ColumnStorage Selector is broken (after filtering is moved out of here).
-  friend class ColumnStorageOverlay;
-
-  template <typename Predicate>
-  Variant FilterRange(Predicate p, Range r) {
-    uint32_t count = r.size();
-
-    // Optimization: if we are only going to scan a few indices, it's not
-    // worth the haslle of working with a BitVector.
-    constexpr uint32_t kSmallRangeLimit = 2048;
-    bool is_small_range = count < kSmallRangeLimit;
-
-    // Optimization: weif the cost of a BitVector is more than the highest
-    // possible cost an index vector could have, use the index vector.
-    uint32_t bit_vector_cost = BitVector::ApproxBytesCost(r.end);
-    uint32_t index_vector_cost_ub = sizeof(uint32_t) * count;
-
-    // If either of the conditions hold which make it better to use an
-    // index vector, use it instead. Alternatively, if we are optimizing for
-    // lookup speed, we also want to use an index vector.
-    if (is_small_range || index_vector_cost_ub <= bit_vector_cost ||
-        optimize_for_ == OptimizeFor::kLookupSpeed) {
-      // Try and strike a good balance between not making the vector too
-      // big and good performance.
-      IndexVector iv(std::min(kSmallRangeLimit, count));
-
-      uint32_t out_i = 0;
-      for (uint32_t i = 0; i < count; ++i) {
-        // If we reach the capacity add another small set of indices.
-        if (PERFETTO_UNLIKELY(out_i == iv.size()))
-          iv.resize(iv.size() + kSmallRangeLimit);
-
-        // We keep this branch free by always writing the index but only
-        // incrementing the out index if the return value is true.
-        bool value = p(i + r.start);
-        iv[out_i] = i + r.start;
-        out_i += value;
-      }
-
-      // Make the vector the correct size and as small as possible.
-      iv.resize(out_i);
-      iv.shrink_to_fit();
-
-      return std::move(iv);
-    }
-
-    // Otherwise, create a bitvector which spans the full range using
-    // |p| as the filler for the bits between start and end.
-    return BitVector::Range(r.start, r.end, p);
-  }
-
   PERFETTO_ALWAYS_INLINE static OutputIndex GetRange(Range r, InputRow row) {
     return r.start + row;
   }
@@ -596,7 +535,6 @@
   }
 
   Variant data_;
-  OptimizeFor optimize_for_ = OptimizeFor::kMemory;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/containers/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
index 5af8b8c..28d5e0e 100644
--- a/src/trace_processor/containers/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -143,7 +143,7 @@
   RowMap rm(10, 20);
   // BitVector with values at 16, 18, 20 and so on.
   RowMap selector(
-      BitVector::Range(4, 8, [](uint32_t x) { return x % 2 == 0; }));
+      BitVector::RangeForTesting(4, 8, [](uint32_t x) { return x % 2 == 0; }));
   RowMap selected = rm.SelectRows(selector);
   ASSERT_EQ(selected.size(), 2u);
   ASSERT_EQ(selected.Get(0), 14u);
@@ -158,7 +158,8 @@
 }
 
 TEST(RowMapUnittest, SelectRowsFromBVWithRange) {
-  RowMap rm(BitVector::Range(10, 50, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap rm(BitVector::RangeForTesting(10, 50,
+                                       [](uint32_t x) { return x % 2 == 0; }));
 
   RowMap selector(4, 8);
   RowMap selected = rm.SelectRows(selector);
@@ -167,17 +168,19 @@
 }
 
 TEST(RowMapUnittest, SelectRowsFromBVWithBV) {
-  RowMap rm(BitVector::Range(10, 50, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap rm(BitVector::RangeForTesting(10, 50,
+                                       [](uint32_t x) { return x % 2 == 0; }));
   // BitVector with values at 16, 18, 20 and so on.
   RowMap selector(
-      BitVector::Range(4, 8, [](uint32_t x) { return x % 2 == 0; }));
+      BitVector::RangeForTesting(4, 8, [](uint32_t x) { return x % 2 == 0; }));
   RowMap selected = rm.SelectRows(selector);
   ASSERT_EQ(selected.size(), 2u);
   ASSERT_EQ(selected.Get(0), 18u);
 }
 
 TEST(RowMapUnittest, SelectRowsFromBVWithIV) {
-  RowMap rm(BitVector::Range(10, 50, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap rm(BitVector::RangeForTesting(10, 50,
+                                       [](uint32_t x) { return x % 2 == 0; }));
   RowMap selector(std::vector<uint32_t>{4, 6});
   RowMap selected = rm.SelectRows(selector);
   ASSERT_EQ(selected.size(), 2u);
@@ -196,7 +199,7 @@
 TEST(RowMapUnittest, SelectRowsFromIVWithBV) {
   RowMap rm(std::vector<uint32_t>{10, 12, 14, 16, 18, 20, 22, 24});
   RowMap selector(
-      BitVector::Range(4, 8, [](uint32_t x) { return x % 2 == 0; }));
+      BitVector::RangeForTesting(4, 8, [](uint32_t x) { return x % 2 == 0; }));
   RowMap selected = rm.SelectRows(selector);
   ASSERT_EQ(selected.size(), 2u);
   ASSERT_EQ(selected.Get(0), 18u);
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index cbd4304..7609e25 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -69,7 +69,6 @@
 perfetto_unittest_source_set("unittests") {
   testonly = true
   sources = [
-    "column_storage_overlay_unittest.cc",
     "compare_unittest.cc",
     "query_executor_unittest.cc",
     "runtime_table_unittest.cc",
@@ -103,9 +102,6 @@
       "../tables:tables_python",
       "column",
     ]
-    sources = [
-      "column_storage_overlay_benchmark.cc",
-      "query_executor_benchmark.cc",
-    ]
+    sources = [ "query_executor_benchmark.cc" ]
   }
 }
diff --git a/src/trace_processor/db/column.cc b/src/trace_processor/db/column.cc
index b675695..1eb7c05 100644
--- a/src/trace_processor/db/column.cc
+++ b/src/trace_processor/db/column.cc
@@ -15,14 +15,16 @@
  */
 #include "src/trace_processor/db/column.h"
 
-#include "perfetto/base/logging.h"
-#include "src/trace_processor/db/compare.h"
-#include "src/trace_processor/db/table.h"
-#include "src/trace_processor/util/glob.h"
-#include "src/trace_processor/util/regex.h"
+#include <cstdint>
+#include <limits>
 
-namespace perfetto {
-namespace trace_processor {
+#include "column/types.h"
+#include "column_storage.h"
+#include "column_storage_overlay.h"
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/db/table.h"
+
+namespace perfetto::trace_processor {
 
 ColumnLegacy::ColumnLegacy(const ColumnLegacy& column,
                            uint32_t col_idx,
@@ -50,446 +52,19 @@
 
 ColumnLegacy ColumnLegacy::DummyColumn(const char* name,
                                        uint32_t col_idx_in_table) {
-  return ColumnLegacy(name, ColumnType::kDummy, Flag::kNoFlag, col_idx_in_table,
-                      std::numeric_limits<uint32_t>::max(), nullptr);
+  return {name,
+          ColumnType::kDummy,
+          Flag::kNoFlag,
+          col_idx_in_table,
+          std::numeric_limits<uint32_t>::max(),
+          nullptr};
 }
 
 ColumnLegacy ColumnLegacy::IdColumn(uint32_t col_idx,
                                     uint32_t overlay_idx,
                                     const char* name,
                                     uint32_t flags) {
-  return ColumnLegacy(name, ColumnType::kId, flags, col_idx, overlay_idx,
-                      nullptr);
-}
-
-void ColumnLegacy::StableSort(bool desc, std::vector<uint32_t>* idx) const {
-  if (desc) {
-    StableSort<true /* desc */>(idx);
-  } else {
-    StableSort<false /* desc */>(idx);
-  }
-}
-
-void ColumnLegacy::FilterIntoSlow(FilterOp op,
-                                  SqlValue value,
-                                  RowMap* rm) const {
-  switch (type_) {
-    case ColumnType::kInt32: {
-      if (IsNullable()) {
-        FilterIntoNumericSlow<int32_t, true /* is_nullable */>(op, value, rm);
-      } else {
-        FilterIntoNumericSlow<int32_t, false /* is_nullable */>(op, value, rm);
-      }
-      break;
-    }
-    case ColumnType::kUint32: {
-      if (IsNullable()) {
-        FilterIntoNumericSlow<uint32_t, true /* is_nullable */>(op, value, rm);
-      } else {
-        FilterIntoNumericSlow<uint32_t, false /* is_nullable */>(op, value, rm);
-      }
-      break;
-    }
-    case ColumnType::kInt64: {
-      if (IsNullable()) {
-        FilterIntoNumericSlow<int64_t, true /* is_nullable */>(op, value, rm);
-      } else {
-        FilterIntoNumericSlow<int64_t, false /* is_nullable */>(op, value, rm);
-      }
-      break;
-    }
-    case ColumnType::kDouble: {
-      if (IsNullable()) {
-        FilterIntoNumericSlow<double, true /* is_nullable */>(op, value, rm);
-      } else {
-        FilterIntoNumericSlow<double, false /* is_nullable */>(op, value, rm);
-      }
-      break;
-    }
-    case ColumnType::kString: {
-      FilterIntoStringSlow(op, value, rm);
-      break;
-    }
-    case ColumnType::kId: {
-      FilterIntoIdSlow(op, value, rm);
-      break;
-    }
-    case ColumnType::kDummy:
-      PERFETTO_FATAL("FilterIntoSlow not allowed on dummy column");
-  }
-}
-
-template <typename T, bool is_nullable>
-void ColumnLegacy::FilterIntoNumericSlow(FilterOp op,
-                                         SqlValue value,
-                                         RowMap* rm) const {
-  PERFETTO_DCHECK(IsNullable() == is_nullable);
-  PERFETTO_DCHECK(type_ == ColumnTypeHelper<T>::ToColumnType());
-  PERFETTO_DCHECK(std::is_arithmetic<T>::value);
-
-  if (op == FilterOp::kIsNull) {
-    PERFETTO_DCHECK(value.is_null());
-    if (is_nullable) {
-      overlay().FilterInto(rm, [this](uint32_t row) {
-        return !storage<std::optional<T>>().Get(row).has_value();
-      });
-    } else {
-      rm->Clear();
-    }
-    return;
-  } else if (op == FilterOp::kIsNotNull) {
-    PERFETTO_DCHECK(value.is_null());
-    if (is_nullable) {
-      overlay().FilterInto(rm, [this](uint32_t row) {
-        return storage<std::optional<T>>().Get(row).has_value();
-      });
-    }
-    return;
-  }
-
-  if (value.type == SqlValue::Type::kDouble) {
-    double double_value = value.double_value;
-    if (std::is_same<T, double>::value) {
-      auto fn = [double_value](T v) {
-        // We static cast here as this code will be compiled even when T ==
-        // int64_t as we don't have if constexpr in C++11. In reality the cast
-        // is a noop but we cannot statically verify that for the compiler.
-        return compare::Numeric(static_cast<double>(v), double_value);
-      };
-      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
-    } else {
-      auto fn = [double_value](T v) {
-        // We static cast here as this code will be compiled even when T ==
-        // double as we don't have if constexpr in C++11. In reality the cast
-        // is a noop but we cannot statically verify that for the compiler.
-        return compare::LongToDouble(static_cast<int64_t>(v), double_value);
-      };
-      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
-    }
-  } else if (value.type == SqlValue::Type::kLong) {
-    int64_t long_value = value.long_value;
-    if (std::is_same<T, double>::value) {
-      auto fn = [long_value](T v) {
-        // We negate the return value as the long is always the first
-        // parameter for this function even though the LHS of the comparator
-        // should actually be |v|. This saves us having a duplicate
-        // implementation of the comparision function.
-        return -compare::LongToDouble(long_value, static_cast<double>(v));
-      };
-      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
-    } else {
-      auto fn = [long_value](T v) {
-        // We static cast here as this code will be compiled even when T ==
-        // double as we don't have if constexpr in C++11. In reality the cast
-        // is a noop but we cannot statically verify that for the compiler.
-        return compare::Numeric(static_cast<int64_t>(v), long_value);
-      };
-      FilterIntoNumericWithComparatorSlow<T, is_nullable>(op, rm, fn);
-    }
-  } else {
-    rm->Clear();
-  }
-}
-
-template <typename T, bool is_nullable, typename Comparator>
-void ColumnLegacy::FilterIntoNumericWithComparatorSlow(FilterOp op,
-                                                       RowMap* rm,
-                                                       Comparator cmp) const {
-  switch (op) {
-    case FilterOp::kLt:
-      overlay().FilterInto(rm, [this, &cmp](uint32_t idx) {
-        if (is_nullable) {
-          auto opt_value = storage<std::optional<T>>().Get(idx);
-          return opt_value && cmp(*opt_value) < 0;
-        }
-        return cmp(storage<T>().Get(idx)) < 0;
-      });
-      break;
-    case FilterOp::kEq:
-      overlay().FilterInto(rm, [this, &cmp](uint32_t idx) {
-        if (is_nullable) {
-          auto opt_value = storage<std::optional<T>>().Get(idx);
-          return opt_value && cmp(*opt_value) == 0;
-        }
-        return cmp(storage<T>().Get(idx)) == 0;
-      });
-      break;
-    case FilterOp::kGt:
-      overlay().FilterInto(rm, [this, &cmp](uint32_t idx) {
-        if (is_nullable) {
-          auto opt_value = storage<std::optional<T>>().Get(idx);
-          return opt_value && cmp(*opt_value) > 0;
-        }
-        return cmp(storage<T>().Get(idx)) > 0;
-      });
-      break;
-    case FilterOp::kNe:
-      overlay().FilterInto(rm, [this, &cmp](uint32_t idx) {
-        if (is_nullable) {
-          auto opt_value = storage<std::optional<T>>().Get(idx);
-          return opt_value && cmp(*opt_value) != 0;
-        }
-        return cmp(storage<T>().Get(idx)) != 0;
-      });
-      break;
-    case FilterOp::kLe:
-      overlay().FilterInto(rm, [this, &cmp](uint32_t idx) {
-        if (is_nullable) {
-          auto opt_value = storage<std::optional<T>>().Get(idx);
-          return opt_value && cmp(*opt_value) <= 0;
-        }
-        return cmp(storage<T>().Get(idx)) <= 0;
-      });
-      break;
-    case FilterOp::kGe:
-      overlay().FilterInto(rm, [this, &cmp](uint32_t idx) {
-        if (is_nullable) {
-          auto opt_value = storage<std::optional<T>>().Get(idx);
-          return opt_value && cmp(*opt_value) >= 0;
-        }
-        return cmp(storage<T>().Get(idx)) >= 0;
-      });
-      break;
-    case FilterOp::kGlob:
-      rm->Clear();
-      break;
-    case FilterOp::kRegex:
-    case FilterOp::kIsNull:
-    case FilterOp::kIsNotNull:
-      PERFETTO_FATAL("Should be handled above");
-  }
-}
-
-void ColumnLegacy::FilterIntoStringSlow(FilterOp op,
-                                        SqlValue value,
-                                        RowMap* rm) const {
-  PERFETTO_DCHECK(type_ == ColumnType::kString);
-
-  if (op == FilterOp::kIsNull) {
-    PERFETTO_DCHECK(value.is_null());
-    overlay().FilterInto(rm, [this](uint32_t row) {
-      return GetStringPoolStringAtIdx(row).data() == nullptr;
-    });
-    return;
-  } else if (op == FilterOp::kIsNotNull) {
-    PERFETTO_DCHECK(value.is_null());
-    overlay().FilterInto(rm, [this](uint32_t row) {
-      return GetStringPoolStringAtIdx(row).data() != nullptr;
-    });
-    return;
-  }
-
-  if (value.type != SqlValue::Type::kString) {
-    rm->Clear();
-    return;
-  }
-
-  NullTermStringView str_value = value.string_value;
-  PERFETTO_DCHECK(str_value.data() != nullptr);
-
-  switch (op) {
-    case FilterOp::kLt:
-      overlay().FilterInto(rm, [this, str_value](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && compare::String(v, str_value) < 0;
-      });
-      break;
-    case FilterOp::kEq:
-      overlay().FilterInto(rm, [this, str_value](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && compare::String(v, str_value) == 0;
-      });
-      break;
-    case FilterOp::kGt:
-      overlay().FilterInto(rm, [this, str_value](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && compare::String(v, str_value) > 0;
-      });
-      break;
-    case FilterOp::kNe:
-      overlay().FilterInto(rm, [this, str_value](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && compare::String(v, str_value) != 0;
-      });
-      break;
-    case FilterOp::kLe:
-      overlay().FilterInto(rm, [this, str_value](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && compare::String(v, str_value) <= 0;
-      });
-      break;
-    case FilterOp::kGe:
-      overlay().FilterInto(rm, [this, str_value](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && compare::String(v, str_value) >= 0;
-      });
-      break;
-    case FilterOp::kGlob: {
-      util::GlobMatcher matcher = util::GlobMatcher::FromPattern(str_value);
-      overlay().FilterInto(rm, [this, &matcher](uint32_t idx) {
-        auto v = GetStringPoolStringAtIdx(idx);
-        return v.data() != nullptr && matcher.Matches(v);
-      });
-      break;
-    }
-    case FilterOp::kRegex: {
-      if constexpr (regex::IsRegexSupported()) {
-        auto regex = regex::Regex::Create(str_value.c_str());
-        if (!regex.status().ok()) {
-          rm->Clear();
-          break;
-        }
-        overlay().FilterInto(rm, [this, &regex](uint32_t idx) {
-          auto v = GetStringPoolStringAtIdx(idx);
-          return v.data() != nullptr && regex->Search(v.c_str());
-        });
-      } else {
-        PERFETTO_FATAL("Regex not supported");
-      }
-      break;
-    }
-    case FilterOp::kIsNull:
-    case FilterOp::kIsNotNull:
-      PERFETTO_FATAL("Should be handled above");
-  }
-}
-
-void ColumnLegacy::FilterIntoIdSlow(FilterOp op,
-                                    SqlValue value,
-                                    RowMap* rm) const {
-  PERFETTO_DCHECK(type_ == ColumnType::kId);
-
-  if (op == FilterOp::kIsNull) {
-    PERFETTO_DCHECK(value.is_null());
-    rm->Clear();
-    return;
-  } else if (op == FilterOp::kIsNotNull) {
-    PERFETTO_DCHECK(value.is_null());
-    return;
-  }
-
-  if (value.type != SqlValue::Type::kLong) {
-    rm->Clear();
-    return;
-  }
-
-  uint32_t id_value = static_cast<uint32_t>(value.long_value);
-  switch (op) {
-    case FilterOp::kLt:
-      overlay().FilterInto(rm, [id_value](uint32_t idx) {
-        return compare::Numeric(idx, id_value) < 0;
-      });
-      break;
-    case FilterOp::kEq:
-      overlay().FilterInto(rm, [id_value](uint32_t idx) {
-        return compare::Numeric(idx, id_value) == 0;
-      });
-      break;
-    case FilterOp::kGt:
-      overlay().FilterInto(rm, [id_value](uint32_t idx) {
-        return compare::Numeric(idx, id_value) > 0;
-      });
-      break;
-    case FilterOp::kNe:
-      overlay().FilterInto(rm, [id_value](uint32_t idx) {
-        return compare::Numeric(idx, id_value) != 0;
-      });
-      break;
-    case FilterOp::kLe:
-      overlay().FilterInto(rm, [id_value](uint32_t idx) {
-        return compare::Numeric(idx, id_value) <= 0;
-      });
-      break;
-    case FilterOp::kGe:
-      overlay().FilterInto(rm, [id_value](uint32_t idx) {
-        return compare::Numeric(idx, id_value) >= 0;
-      });
-      break;
-    case FilterOp::kGlob:
-    case FilterOp::kRegex:
-      rm->Clear();
-      break;
-    case FilterOp::kIsNull:
-    case FilterOp::kIsNotNull:
-      PERFETTO_FATAL("Should be handled above");
-  }
-}
-
-template <bool desc>
-void ColumnLegacy::StableSort(std::vector<uint32_t>* out) const {
-  switch (type_) {
-    case ColumnType::kInt32: {
-      if (IsNullable()) {
-        StableSortNumeric<desc, int32_t, true /* is_nullable */>(out);
-      } else {
-        StableSortNumeric<desc, int32_t, false /* is_nullable */>(out);
-      }
-      break;
-    }
-    case ColumnType::kUint32: {
-      if (IsNullable()) {
-        StableSortNumeric<desc, uint32_t, true /* is_nullable */>(out);
-      } else {
-        StableSortNumeric<desc, uint32_t, false /* is_nullable */>(out);
-      }
-      break;
-    }
-    case ColumnType::kInt64: {
-      if (IsNullable()) {
-        StableSortNumeric<desc, int64_t, true /* is_nullable */>(out);
-      } else {
-        StableSortNumeric<desc, int64_t, false /* is_nullable */>(out);
-      }
-      break;
-    }
-    case ColumnType::kDouble: {
-      if (IsNullable()) {
-        StableSortNumeric<desc, double, true /* is_nullable */>(out);
-      } else {
-        StableSortNumeric<desc, double, false /* is_nullable */>(out);
-      }
-      break;
-    }
-    case ColumnType::kString: {
-      overlay().StableSort(out, [this](uint32_t a_idx, uint32_t b_idx) {
-        auto a_str = GetStringPoolStringAtIdx(a_idx);
-        auto b_str = GetStringPoolStringAtIdx(b_idx);
-
-        int res = compare::NullableString(a_str, b_str);
-        return desc ? res > 0 : res < 0;
-      });
-      break;
-    }
-    case ColumnType::kId:
-      overlay().StableSort(out, [](uint32_t a_idx, uint32_t b_idx) {
-        int res = compare::Numeric(a_idx, b_idx);
-        return desc ? res > 0 : res < 0;
-      });
-      break;
-    case ColumnType::kDummy:
-      PERFETTO_FATAL("StableSort not allowed on dummy column");
-  }
-}
-
-template <bool desc, typename T, bool is_nullable>
-void ColumnLegacy::StableSortNumeric(std::vector<uint32_t>* out) const {
-  PERFETTO_DCHECK(IsNullable() == is_nullable);
-  PERFETTO_DCHECK(ColumnTypeHelper<T>::ToColumnType() == type_);
-
-  overlay().StableSort(out, [this](uint32_t a_idx, uint32_t b_idx) {
-    if (is_nullable) {
-      auto a_val = storage<std::optional<T>>().Get(a_idx);
-      auto b_val = storage<std::optional<T>>().Get(b_idx);
-
-      int res = compare::NullableNumeric(a_val, b_val);
-      return desc ? res > 0 : res < 0;
-    }
-    auto a_val = storage<T>().Get(a_idx);
-    auto b_val = storage<T>().Get(b_idx);
-
-    int res = compare::Numeric(a_val, b_val);
-    return desc ? res > 0 : res < 0;
-  });
+  return {name, ColumnType::kId, flags, col_idx, overlay_idx, nullptr};
 }
 
 const ColumnStorageOverlay& ColumnLegacy::overlay() const {
@@ -497,5 +72,4 @@
   return table_->overlays_[overlay_index()];
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 988b12f..262b346 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -242,45 +242,6 @@
     PERFETTO_FATAL("For GCC");
   }
 
-  // Sorts |idx| in ascending or descending order (determined by |desc|) based
-  // on the contents of this column.
-  void StableSort(bool desc, std::vector<uint32_t>* idx) const;
-
-  // Updates the given RowMap by only keeping rows where this column meets the
-  // given filter constraint.
-  void FilterInto(FilterOp op, SqlValue value, RowMap* rm) const {
-    if (IsId() && op == FilterOp::kEq) {
-      // If this is an equality constraint on an id column, try and find the
-      // single row with the id (if it exists).
-      auto opt_idx = IndexOf(value);
-      if (opt_idx) {
-        rm->IntersectExact(*opt_idx);
-      } else {
-        rm->Clear();
-      }
-      return;
-    }
-
-    if (IsSetId() && op == FilterOp::kEq && value.type == SqlValue::kLong) {
-      // If the column is sorted and the value has the same type as the column,
-      // we should be able to just do a binary search to find the range of rows
-      // instead of a full table scan.
-      FilterIntoSetIdEq(value.AsLong(), rm);
-      return;
-    }
-
-    if (IsSorted() && value.type == type()) {
-      // If the column is sorted and the value has the same type as the column,
-      // we should be able to just do a binary search to find the range of rows
-      // instead of a full table scan.
-      bool handled = FilterIntoSorted(op, value, rm);
-      if (handled)
-        return;
-    }
-
-    FilterIntoSlow(op, value, rm);
-  }
-
   // Returns the minimum value in this column. Returns std::nullopt if this
   // column is empty.
   std::optional<SqlValue> Min() const {
@@ -495,122 +456,6 @@
     return ToSqlValue(storage<T>().Get(idx));
   }
 
-  // Optimized filter method for sorted columns.
-  // Returns whether the constraint was handled by the method.
-  bool FilterIntoSorted(FilterOp op, SqlValue value, RowMap* rm) const {
-    PERFETTO_DCHECK(IsSorted());
-    PERFETTO_DCHECK(value.type == type());
-
-    Iterator b(this, 0);
-    Iterator e(this, overlay().size());
-    switch (op) {
-      case FilterOp::kEq: {
-        uint32_t beg = std::distance(
-            b, std::lower_bound(b, e, value, &compare::SqlValueComparator));
-        uint32_t end = std::distance(
-            b, std::upper_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect({beg, end});
-        return true;
-      }
-      case FilterOp::kLe: {
-        uint32_t end = std::distance(
-            b, std::upper_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect({0, end});
-        return true;
-      }
-      case FilterOp::kLt: {
-        uint32_t end = std::distance(
-            b, std::lower_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect({0, end});
-        return true;
-      }
-      case FilterOp::kGe: {
-        uint32_t beg = std::distance(
-            b, std::lower_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect({beg, overlay().size()});
-        return true;
-      }
-      case FilterOp::kGt: {
-        uint32_t beg = std::distance(
-            b, std::upper_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect({beg, overlay().size()});
-        return true;
-      }
-      case FilterOp::kNe:
-      case FilterOp::kIsNull:
-      case FilterOp::kIsNotNull:
-      case FilterOp::kGlob:
-      case FilterOp::kRegex:
-        break;
-    }
-    return false;
-  }
-
-  void FilterIntoSetIdEq(int64_t value, RowMap* rm) const {
-    PERFETTO_DCHECK(!IsNullable());
-
-    uint32_t filter_set_id = static_cast<uint32_t>(value);
-    const auto& st = storage<uint32_t>();
-    const ColumnStorageOverlay& ov = overlay();
-
-    // If the set id is beyond the end of the column, there's no chance that
-    // it exists.
-    if (PERFETTO_UNLIKELY(filter_set_id >= st.size())) {
-      rm->Clear();
-      return;
-    }
-
-    uint32_t set_id = st.Get(ov.Get(filter_set_id));
-
-    // If the set at that index does not equal the set id we're looking for, the
-    // set id doesn't exist either.
-    if (PERFETTO_UNLIKELY(set_id != filter_set_id)) {
-      PERFETTO_DCHECK(set_id < filter_set_id);
-      rm->Clear();
-      return;
-    }
-
-    // Otherwise, find the end of the set and return the intersection for this.
-    for (uint32_t i = set_id + 1; i < ov.size(); ++i) {
-      if (st.Get(ov.Get(i)) != filter_set_id) {
-        RowMap r(set_id, i);
-        rm->Intersect(r);
-        return;
-      }
-    }
-    RowMap r(set_id, ov.size());
-    rm->Intersect(r);
-  }
-
-  // Slow path filter method which will perform a full table scan.
-  void FilterIntoSlow(FilterOp op, SqlValue value, RowMap* rm) const;
-
-  // Slow path filter method for numerics which will perform a full table scan.
-  template <typename T, bool is_nullable>
-  void FilterIntoNumericSlow(FilterOp op, SqlValue value, RowMap* rm) const;
-
-  // Slow path filter method for numerics with a comparator which will perform a
-  // full table scan.
-  template <typename T, bool is_nullable, typename Comparator = int(T)>
-  void FilterIntoNumericWithComparatorSlow(FilterOp op,
-                                           RowMap* rm,
-                                           Comparator cmp) const;
-
-  // Slow path filter method for strings which will perform a full table scan.
-  void FilterIntoStringSlow(FilterOp op, SqlValue value, RowMap* rm) const;
-
-  // Slow path filter method for ids which will perform a full table scan.
-  void FilterIntoIdSlow(FilterOp op, SqlValue value, RowMap* rm) const;
-
-  // Stable sorts this column storing the result in |out|.
-  template <bool desc>
-  void StableSort(std::vector<uint32_t>* out) const;
-
-  // Stable sorts this column storing the result in |out|.
-  // |T| and |is_nullable| should match the type and nullability of this column.
-  template <bool desc, typename T, bool is_nullable>
-  void StableSortNumeric(std::vector<uint32_t>* out) const;
-
   static constexpr bool IsDense(uint32_t flags) {
     return (flags & Flag::kDense) != 0;
   }
diff --git a/src/trace_processor/db/column/BUILD.gn b/src/trace_processor/db/column/BUILD.gn
index 786fb91..74d1611 100644
--- a/src/trace_processor/db/column/BUILD.gn
+++ b/src/trace_processor/db/column/BUILD.gn
@@ -55,22 +55,6 @@
   ]
 }
 
-source_set("make_chain_minimal") {
-  sources = [ "make_chain_minimal.cc" ]
-  deps = [
-    ":column",
-    "../../../../gn:default_deps",
-  ]
-}
-
-source_set("make_chain") {
-  sources = [ "make_chain.cc" ]
-  deps = [
-    ":column",
-    "../../../../gn:default_deps",
-  ]
-}
-
 perfetto_unittest_source_set("fake_storage") {
   testonly = true
   sources = [
diff --git a/src/trace_processor/db/column/arrangement_overlay.cc b/src/trace_processor/db/column/arrangement_overlay.cc
index 7325689..281071f 100644
--- a/src/trace_processor/db/column/arrangement_overlay.cc
+++ b/src/trace_processor/db/column/arrangement_overlay.cc
@@ -34,10 +34,6 @@
 
 namespace perfetto::trace_processor::column {
 
-ArrangementOverlay::ArrangementOverlay(const std::vector<uint32_t>* arrangement,
-                                       Indices::State arrangement_state)
-    : arrangement_(arrangement), arrangement_state_(arrangement_state) {}
-
 ArrangementOverlay::ChainImpl::ChainImpl(
     std::unique_ptr<DataLayerChain> inner,
     const std::vector<uint32_t>* arrangement,
@@ -58,6 +54,13 @@
   return inner_->SingleSearch(op, sql_val, (*arrangement_)[index]);
 }
 
+UniqueSearchResult ArrangementOverlay::ChainImpl::UniqueSearch(
+    FilterOp,
+    SqlValue,
+    uint32_t*) const {
+  return UniqueSearchResult::kNeedsFullSearch;
+}
+
 SearchValidationResult ArrangementOverlay::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue value) const {
diff --git a/src/trace_processor/db/column/arrangement_overlay.h b/src/trace_processor/db/column/arrangement_overlay.h
index 9a52aa0..6138918 100644
--- a/src/trace_processor/db/column/arrangement_overlay.h
+++ b/src/trace_processor/db/column/arrangement_overlay.h
@@ -36,10 +36,11 @@
  public:
   ArrangementOverlay(const std::vector<uint32_t>* arrangement,
                      Indices::State arrangement_state);
+  ~ArrangementOverlay() override;
 
   std::unique_ptr<DataLayerChain> MakeChain(
       std::unique_ptr<DataLayerChain>,
-      ChainCreationArgs = ChainCreationArgs()) override;
+      ChainCreationArgs = ChainCreationArgs());
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -53,6 +54,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/arrangement_overlay_unittest.cc b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
index dd9a28e..07f13c0 100644
--- a/src/trace_processor/db/column/arrangement_overlay_unittest.cc
+++ b/src/trace_processor/db/column/arrangement_overlay_unittest.cc
@@ -37,9 +37,9 @@
 
 TEST(ArrangementOverlay, SingleSearch) {
   std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  auto fake = FakeStorage::SearchSubset(5, std::vector<uint32_t>{1, 2});
+  auto fake = FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2});
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0u), 8),
             SingleSearchResult::kMatch);
@@ -49,9 +49,9 @@
 
 TEST(ArrangementOverlay, SearchAll) {
   std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  auto fake = FakeStorage::SearchAll(5);
+  auto fake = FakeStorageChain::SearchAll(5);
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(2, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(2u, 3u));
@@ -59,9 +59,9 @@
 
 TEST(ArrangementOverlay, SearchNone) {
   std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  auto fake = FakeStorage::SearchNone(5);
+  auto fake = FakeStorageChain::SearchNone(5);
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(2, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), IsEmpty());
@@ -69,9 +69,9 @@
 
 TEST(ArrangementOverlay, DISABLED_SearchLimited) {
   std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  auto fake = FakeStorage::SearchSubset(5, Range(4, 5));
+  auto fake = FakeStorageChain::SearchSubset(5, Range(4, 5));
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(2, 7));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(6u));
@@ -79,10 +79,10 @@
 
 TEST(ArrangementOverlay, SearchBitVector) {
   std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  auto fake = FakeStorage::SearchSubset(
+  auto fake = FakeStorageChain::SearchSubset(
       5, BitVector({false, true, false, true, false}));
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   // Table bv:
   // 1, 1, 0, 0, 1, 1, 0, 0, 1, 1
@@ -92,10 +92,10 @@
 
 TEST(ArrangementOverlay, IndexSearch) {
   std::vector<uint32_t> arrangement{1, 1, 2, 2, 3, 3, 4, 4, 1, 1};
-  auto fake = FakeStorage::SearchSubset(
+  auto fake = FakeStorageChain::SearchSubset(
       5, BitVector({false, true, false, true, false}));
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{7u, 1u, 3u};
   RangeOrBitVector res = chain->IndexSearch(
@@ -108,11 +108,11 @@
 
 TEST(ArrangementOverlay, OrderingSearch) {
   std::vector<uint32_t> arrangement{0, 2, 4, 1, 3};
-  auto fake = FakeStorage::SearchSubset(
+  auto fake = FakeStorageChain::SearchSubset(
       5, BitVector({false, true, false, true, false}));
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
   auto chain =
-      storage.MakeChain(fake->MakeChain(), DataLayer::ChainCreationArgs(true));
+      storage.MakeChain(std::move(fake), DataLayer::ChainCreationArgs(true));
 
   RangeOrBitVector res =
       chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 5));
diff --git a/src/trace_processor/db/column/data_layer.cc b/src/trace_processor/db/column/data_layer.cc
index d4bbcba..c8f8b68 100644
--- a/src/trace_processor/db/column/data_layer.cc
+++ b/src/trace_processor/db/column/data_layer.cc
@@ -16,9 +16,213 @@
 
 #include "src/trace_processor/db/column/data_layer.h"
 
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column/arrangement_overlay.h"
+#include "src/trace_processor/db/column/dense_null_overlay.h"
+#include "src/trace_processor/db/column/dummy_storage.h"
+#include "src/trace_processor/db/column/id_storage.h"
+#include "src/trace_processor/db/column/null_overlay.h"
+#include "src/trace_processor/db/column/numeric_storage.h"
+#include "src/trace_processor/db/column/range_overlay.h"
+#include "src/trace_processor/db/column/selector_overlay.h"
+#include "src/trace_processor/db/column/set_id_storage.h"
+#include "src/trace_processor/db/column/string_storage.h"
+#include "src/trace_processor/db/column/types.h"
+
 namespace perfetto::trace_processor::column {
 
 DataLayer::~DataLayer() = default;
 DataLayerChain::~DataLayerChain() = default;
 
+// All the below code exists as machinery to allow dead-code-elimination
+// and linker symbol stripping to work for trace processor built into Chrome. It
+// is ugly and hacky but is the only way we could come up with to actually meet
+// both performance constraints and saving binary size in Chrome.
+//
+// TODO(b/325583551): investigate whether we can improve this at some point,
+// potentially removing this if Chrome no longer relies on trace processor for
+// JSON export.
+
+std::unique_ptr<DataLayerChain> DataLayer::MakeChain() {
+  switch (impl_) {
+    case Impl::kDummy:
+      return static_cast<DummyStorage*>(this)->MakeChain();
+    case Impl::kId:
+      return static_cast<IdStorage*>(this)->MakeChain();
+    case Impl::kNumericDouble:
+      return static_cast<NumericStorage<double>*>(this)->MakeChain();
+    case Impl::kNumericUint32:
+      return static_cast<NumericStorage<uint32_t>*>(this)->MakeChain();
+    case Impl::kNumericInt32:
+      return static_cast<NumericStorage<int32_t>*>(this)->MakeChain();
+    case Impl::kNumericInt64:
+      return static_cast<NumericStorage<int64_t>*>(this)->MakeChain();
+    case Impl::kSetId:
+      return static_cast<SetIdStorage*>(this)->MakeChain();
+    case Impl::kString:
+      return static_cast<StringStorage*>(this)->MakeChain();
+    case Impl::kArrangement:
+    case Impl::kDenseNull:
+    case Impl::kNull:
+    case Impl::kRange:
+    case Impl::kSelector:
+      PERFETTO_FATAL(
+          "Unexpected call to MakeChain(). MakeChain(DataLayerChain) should be "
+          "called instead");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+std::unique_ptr<DataLayerChain> DataLayer::MakeChain(
+    std::unique_ptr<DataLayerChain> inner,
+    ChainCreationArgs args) {
+  switch (impl_) {
+    case Impl::kArrangement:
+      return static_cast<ArrangementOverlay*>(this)->MakeChain(std::move(inner),
+                                                               args);
+    case Impl::kDenseNull:
+      return static_cast<DenseNullOverlay*>(this)->MakeChain(std::move(inner),
+                                                             args);
+    case Impl::kNull:
+      return static_cast<NullOverlay*>(this)->MakeChain(std::move(inner), args);
+    case Impl::kRange:
+      return static_cast<RangeOverlay*>(this)->MakeChain(std::move(inner),
+                                                         args);
+    case Impl::kSelector:
+      return static_cast<SelectorOverlay*>(this)->MakeChain(std::move(inner),
+                                                            args);
+    case Impl::kDummy:
+    case Impl::kId:
+    case Impl::kNumericDouble:
+    case Impl::kNumericUint32:
+    case Impl::kNumericInt32:
+    case Impl::kNumericInt64:
+    case Impl::kSetId:
+    case Impl::kString:
+      PERFETTO_FATAL(
+          "Unexpected call to MakeChain(DataLayerChain). MakeChain() should be "
+          "called instead");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+ArrangementOverlay::ArrangementOverlay(const std::vector<uint32_t>* arrangement,
+                                       Indices::State arrangement_state)
+    : DataLayer(Impl::kArrangement),
+      arrangement_(arrangement),
+      arrangement_state_(arrangement_state) {}
+ArrangementOverlay::~ArrangementOverlay() = default;
+
+std::unique_ptr<DataLayerChain> ArrangementOverlay::MakeChain(
+    std::unique_ptr<DataLayerChain> inner,
+    ChainCreationArgs args) {
+  return std::make_unique<ChainImpl>(std::move(inner), arrangement_,
+                                     arrangement_state_,
+                                     args.does_layer_order_chain_contents);
+}
+
+DenseNullOverlay::DenseNullOverlay(const BitVector* non_null)
+    : DataLayer(Impl::kDenseNull), non_null_(non_null) {}
+DenseNullOverlay::~DenseNullOverlay() = default;
+
+std::unique_ptr<DataLayerChain> DenseNullOverlay::MakeChain(
+    std::unique_ptr<DataLayerChain> inner,
+    ChainCreationArgs) {
+  return std::make_unique<ChainImpl>(std::move(inner), non_null_);
+}
+
+std::unique_ptr<DataLayerChain> DummyStorage::MakeChain() {
+  return std::make_unique<ChainImpl>();
+}
+
+IdStorage::IdStorage() : DataLayer(Impl::kId) {}
+IdStorage::~IdStorage() = default;
+
+std::unique_ptr<DataLayerChain> IdStorage::MakeChain() {
+  return std::make_unique<ChainImpl>();
+}
+
+NullOverlay::NullOverlay(const BitVector* non_null)
+    : DataLayer(Impl::kNull), non_null_(non_null) {}
+NullOverlay::~NullOverlay() = default;
+
+std::unique_ptr<DataLayerChain> NullOverlay::MakeChain(
+    std::unique_ptr<DataLayerChain> inner,
+    ChainCreationArgs) {
+  return std::make_unique<ChainImpl>(std::move(inner), non_null_);
+}
+
+NumericStorageBase::NumericStorageBase(ColumnType type,
+                                       bool is_sorted,
+                                       Impl impl)
+    : DataLayer(impl), storage_type_(type), is_sorted_(is_sorted) {}
+
+NumericStorageBase::~NumericStorageBase() = default;
+
+template <typename T>
+std::unique_ptr<DataLayerChain> NumericStorage<T>::MakeChain() {
+  return std::make_unique<ChainImpl>(vector_, storage_type_, is_sorted_);
+}
+
+template <typename T>
+NumericStorage<T>::NumericStorage(const std::vector<T>* vec,
+                                  ColumnType type,
+                                  bool is_sorted)
+    : NumericStorageBase(type, is_sorted, GetImpl()), vector_(vec) {}
+
+// Define explicit instantiation of the necessary templates here to reduce
+// binary size bloat.
+template class NumericStorage<double>;
+template class NumericStorage<uint32_t>;
+template class NumericStorage<int32_t>;
+template class NumericStorage<int64_t>;
+
+RangeOverlay::RangeOverlay(const Range* range)
+    : DataLayer(Impl::kRange), range_(range) {}
+RangeOverlay::~RangeOverlay() = default;
+
+std::unique_ptr<DataLayerChain> RangeOverlay::MakeChain(
+    std::unique_ptr<DataLayerChain> inner,
+    ChainCreationArgs) {
+  return std::make_unique<ChainImpl>(std::move(inner), range_);
+}
+
+SelectorOverlay::SelectorOverlay(const BitVector* selector)
+    : DataLayer(Impl::kSelector), selector_(selector) {}
+SelectorOverlay::~SelectorOverlay() = default;
+
+std::unique_ptr<DataLayerChain> SelectorOverlay::MakeChain(
+    std::unique_ptr<DataLayerChain> inner,
+    ChainCreationArgs) {
+  return std::make_unique<ChainImpl>(std::move(inner), selector_);
+}
+
+SetIdStorage::SetIdStorage(const std::vector<uint32_t>* values)
+    : DataLayer(Impl::kSetId), values_(values) {}
+SetIdStorage::~SetIdStorage() = default;
+
+std::unique_ptr<DataLayerChain> SetIdStorage::MakeChain() {
+  return std::make_unique<ChainImpl>(values_);
+}
+
+StringStorage::StringStorage(StringPool* string_pool,
+                             const std::vector<StringPool::Id>* data,
+                             bool is_sorted)
+    : DataLayer(Impl::kString),
+      data_(data),
+      string_pool_(string_pool),
+      is_sorted_(is_sorted) {}
+StringStorage::~StringStorage() = default;
+
+std::unique_ptr<DataLayerChain> StringStorage::MakeChain() {
+  return std::make_unique<ChainImpl>(string_pool_, data_, is_sorted_);
+}
+
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/data_layer.h b/src/trace_processor/db/column/data_layer.h
index 809a4e3..5ec7909 100644
--- a/src/trace_processor/db/column/data_layer.h
+++ b/src/trace_processor/db/column/data_layer.h
@@ -50,22 +50,39 @@
     // orders a given chain.
     bool does_layer_order_chain_contents;
   };
-
   virtual ~DataLayer();
 
   // Creates a DataLayerChain for a terminal DataLayer. This means the
   // DataLayer directly should return the data it contains inside.
-  virtual std::unique_ptr<DataLayerChain> MakeChain() {
-    PERFETTO_FATAL("Unimplemented");
-  }
+  std::unique_ptr<DataLayerChain> MakeChain();
 
   // Creates a DataLayerChain for a non-terminal DataLayer. This means
   // the DataLayer should transform the contents of the inner chain.
-  virtual std::unique_ptr<DataLayerChain> MakeChain(
+  std::unique_ptr<DataLayerChain> MakeChain(
       std::unique_ptr<DataLayerChain>,
-      ChainCreationArgs = ChainCreationArgs()) {
-    PERFETTO_FATAL("Unimplemented");
-  }
+      ChainCreationArgs = ChainCreationArgs());
+
+ protected:
+  // TODO(b/325583551): remove this when possible.
+  enum class Impl {
+    kArrangement,
+    kDenseNull,
+    kDummy,
+    kId,
+    kNull,
+    kNumericDouble,
+    kNumericUint32,
+    kNumericInt32,
+    kNumericInt64,
+    kRange,
+    kSelector,
+    kSetId,
+    kString,
+  };
+  explicit DataLayer(Impl impl) : impl_(impl) {}
+
+ private:
+  Impl impl_;
 };
 
 // Corresponds to a series of DataLayer chained together. Provides
@@ -96,11 +113,27 @@
 
   // Checks whether element at the the provided index match |op| and |value|.
   //
-  // Returns true if the element matches, false otherwise.
+  // Returns one of the following:
+  //  * kMatch if the element matches.
+  //  * kNoMatch if the element does not match.
+  //  * kNeedsFullSearch if one of the "full" search algorithms need to be
+  //    run to determine if the element matches.
   virtual SingleSearchResult SingleSearch(FilterOp op,
                                           SqlValue value,
                                           uint32_t row) const = 0;
 
+  // Searches for a *unique* element in chain which matches |op| and |value|.
+  //
+  // The return value is one of the following:
+  //  * kMatch if an element matches. |row| should be set to the index of
+  //    the element.
+  //  * kNoMatch if no element matches.
+  //  * kNeedsFullSearch if one of the "full" search algorithms need to be
+  //    run for this constraint.
+  virtual UniqueSearchResult UniqueSearch(FilterOp op,
+                                          SqlValue value,
+                                          uint32_t* row) const = 0;
+
   // Searches for elements which match |op| and |value| between |range.start|
   // and |range.end|.
   //
diff --git a/src/trace_processor/db/column/dense_null_overlay.cc b/src/trace_processor/db/column/dense_null_overlay.cc
index fcd22bb..e7251a3 100644
--- a/src/trace_processor/db/column/dense_null_overlay.cc
+++ b/src/trace_processor/db/column/dense_null_overlay.cc
@@ -34,9 +34,6 @@
 
 namespace perfetto::trace_processor::column {
 
-DenseNullOverlay::DenseNullOverlay(const BitVector* non_null)
-    : non_null_(non_null) {}
-
 DenseNullOverlay::ChainImpl::ChainImpl(std::unique_ptr<DataLayerChain> inner,
                                        const BitVector* non_null)
     : inner_(std::move(inner)), non_null_(non_null) {}
@@ -64,6 +61,27 @@
   PERFETTO_FATAL("For GCC");
 }
 
+UniqueSearchResult DenseNullOverlay::ChainImpl::UniqueSearch(
+    FilterOp op,
+    SqlValue sql_val,
+    uint32_t* index) const {
+  switch (inner_->UniqueSearch(op, sql_val, index)) {
+    case UniqueSearchResult::kMatch:
+      if (*index >= non_null_->size()) {
+        return UniqueSearchResult::kNoMatch;
+      }
+      // If non_null_[index] is not set, then any result returned by |inner_| is
+      // meaningless as the value in |inner_| is not a "real" value.
+      return non_null_->IsSet(*index) ? UniqueSearchResult::kMatch
+                                      : UniqueSearchResult::kNeedsFullSearch;
+    case UniqueSearchResult::kNoMatch:
+      return UniqueSearchResult::kNoMatch;
+    case UniqueSearchResult::kNeedsFullSearch:
+      return UniqueSearchResult::kNeedsFullSearch;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 SearchValidationResult DenseNullOverlay::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue sql_val) const {
diff --git a/src/trace_processor/db/column/dense_null_overlay.h b/src/trace_processor/db/column/dense_null_overlay.h
index f2e4480..4acec46 100644
--- a/src/trace_processor/db/column/dense_null_overlay.h
+++ b/src/trace_processor/db/column/dense_null_overlay.h
@@ -34,10 +34,11 @@
 class DenseNullOverlay final : public DataLayer {
  public:
   explicit DenseNullOverlay(const BitVector* non_null);
+  ~DenseNullOverlay() override;
 
   std::unique_ptr<DataLayerChain> MakeChain(
       std::unique_ptr<DataLayerChain>,
-      ChainCreationArgs = ChainCreationArgs()) override;
+      ChainCreationArgs = ChainCreationArgs());
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -48,6 +49,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/dense_null_overlay_unittest.cc b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
index af6c9c7..1678857 100644
--- a/src/trace_processor/db/column/dense_null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/dense_null_overlay_unittest.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/column/dense_null_overlay.h"
 
 #include <cstdint>
+#include <limits>
 #include <memory>
 #include <vector>
 
@@ -62,33 +63,33 @@
 }
 
 TEST(DenseNullOverlay, RangeFilterSearch) {
-  auto fake = FakeStorage::SearchSubset(5, Range(1, 3));
+  auto fake = FakeStorageChain::SearchSubset(5, Range(1, 3));
 
   BitVector bv{0, 1, 0, 1, 0};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1));
 }
 
 TEST(DenseNullOverlay, BitvectorFilterSearch) {
-  auto fake = FakeStorage::SearchSubset(5, BitVector({0, 1, 1, 0, 0}));
+  auto fake = FakeStorageChain::SearchSubset(5, BitVector({0, 1, 1, 0, 0}));
 
   BitVector bv{0, 1, 0, 1, 0};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0), Range(0, 5));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1));
 }
 
 TEST(DenseNullOverlay, IsNullSearch) {
-  auto fake = FakeStorage::SearchSubset(5, BitVector({1, 1, 0, 0, 1}));
+  auto fake = FakeStorageChain::SearchSubset(5, BitVector({1, 1, 0, 0, 1}));
 
   BitVector bv{1, 0, 0, 1, 1};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kIsNull, SqlValue(), Range(0, 5));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 1, 2, 4));
@@ -112,11 +113,11 @@
 }
 
 TEST(DenseNullOverlay, IsNullIndexSearch) {
-  auto fake = FakeStorage::SearchSubset(6, BitVector({0, 0, 0, 1, 1, 1}));
+  auto fake = FakeStorageChain::SearchSubset(6, BitVector({0, 0, 0, 1, 1, 1}));
 
   BitVector bv{0, 1, 0, 1, 1, 1};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> index({5, 2, 3, 4, 1});
   auto res = chain->IndexSearch(
@@ -127,11 +128,11 @@
 }
 
 TEST(DenseNullOverlay, OrderedIndexSearch) {
-  auto fake = FakeStorage::SearchSubset(6, BitVector({0, 1, 0, 1, 0, 1}));
+  auto fake = FakeStorageChain::SearchSubset(6, BitVector({0, 1, 0, 1, 0, 1}));
 
   BitVector bv{0, 1, 0, 1, 0, 1};
   DenseNullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> indices_vec({0, 2, 4, 1, 3, 5});
   Indices indices{indices_vec.data(), 6, Indices::State::kNonmonotonic};
@@ -168,8 +169,8 @@
 TEST(DenseNullOverlay, SingleSearch) {
   BitVector bv{0, 1, 0, 1, 1, 1};
   DenseNullOverlay storage(&bv);
-  auto fake = FakeStorage::SearchSubset(5, std::vector<uint32_t>{1, 2});
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto fake = FakeStorageChain::SearchSubset(5, std::vector<uint32_t>{1, 2});
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0u), 1),
             SingleSearchResult::kMatch);
@@ -180,8 +181,8 @@
 TEST(DenseNullOverlay, SingleSearchIsNull) {
   BitVector bv{0, 1, 0, 1, 1, 1};
   DenseNullOverlay storage(&bv);
-  auto fake = FakeStorage::SearchNone(5);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto fake = FakeStorageChain::SearchNone(5);
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kIsNull, SqlValue(), 0),
             SingleSearchResult::kMatch);
@@ -192,8 +193,8 @@
 TEST(DenseNullOverlay, SingleSearchIsNotNull) {
   BitVector bv{0, 1, 0, 1, 1, 1};
   DenseNullOverlay storage(&bv);
-  auto fake = FakeStorage::SearchAll(5);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto fake = FakeStorageChain::SearchAll(5);
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kIsNotNull, SqlValue(), 0),
             SingleSearchResult::kNoMatch);
@@ -201,6 +202,40 @@
             SingleSearchResult::kMatch);
 }
 
+TEST(DenseNullOverlay, UniqueSearchNonNull) {
+  BitVector bv{0, 1, 0, 1, 1};
+  DenseNullOverlay storage(&bv);
+  auto fake = FakeStorageChain::SearchSubset(5, Range(1, 2));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kMatch);
+  ASSERT_EQ(row, 1u);
+}
+
+TEST(DenseNullOverlay, UniqueSearchNull) {
+  BitVector bv{0, 0, 0, 1, 1};
+  DenseNullOverlay storage(&bv);
+  auto fake = FakeStorageChain::SearchSubset(5, Range(1, 2));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kNeedsFullSearch);
+}
+
+TEST(DenseNullOverlay, UniqueSearchOutOfBounds) {
+  BitVector bv{0, 0, 0, 1, 1};
+  DenseNullOverlay storage(&bv);
+  auto fake = FakeStorageChain::SearchSubset(6, Range(5, 6));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kNoMatch);
+}
+
 TEST(DenseNullOverlay, StableSort) {
   std::vector<uint32_t> numeric_data{0, 3, 0, 1, 0, 2, 4};
   NumericStorage<uint32_t> numeric(&numeric_data, ColumnType::kUint32, false);
diff --git a/src/trace_processor/db/column/dummy_storage.cc b/src/trace_processor/db/column/dummy_storage.cc
index 71a3a5c..0f182f8 100644
--- a/src/trace_processor/db/column/dummy_storage.cc
+++ b/src/trace_processor/db/column/dummy_storage.cc
@@ -17,7 +17,6 @@
 #include "src/trace_processor/db/column/dummy_storage.h"
 
 #include <cstdint>
-#include <memory>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/trace_processor/basic_types.h"
@@ -32,6 +31,12 @@
   PERFETTO_FATAL("Shouldn't be called");
 }
 
+UniqueSearchResult DummyStorage::ChainImpl::UniqueSearch(FilterOp,
+                                                         SqlValue,
+                                                         uint32_t*) const {
+  PERFETTO_FATAL("Shouldn't be called");
+}
+
 SearchValidationResult DummyStorage::ChainImpl::ValidateSearchConstraints(
     FilterOp,
     SqlValue) const {
diff --git a/src/trace_processor/db/column/dummy_storage.h b/src/trace_processor/db/column/dummy_storage.h
index b723c2e..286c1ea 100644
--- a/src/trace_processor/db/column/dummy_storage.h
+++ b/src/trace_processor/db/column/dummy_storage.h
@@ -38,6 +38,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
@@ -61,7 +65,7 @@
 
     std::string DebugString() const override { return "DummyStorage"; }
   };
-  std::unique_ptr<DataLayerChain> MakeChain() override;
+  std::unique_ptr<DataLayerChain> MakeChain();
 };
 
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/fake_storage.cc b/src/trace_processor/db/column/fake_storage.cc
index efe7bee..1bab025 100644
--- a/src/trace_processor/db/column/fake_storage.cc
+++ b/src/trace_processor/db/column/fake_storage.cc
@@ -19,7 +19,6 @@
 #include <algorithm>
 #include <cstdint>
 #include <iterator>
-#include <memory>
 #include <utility>
 
 #include "perfetto/base/logging.h"
@@ -30,26 +29,18 @@
 
 namespace perfetto::trace_processor::column {
 
-FakeStorage::FakeStorage(uint32_t size, SearchStrategy strategy)
-    : size_(size), strategy_(strategy) {}
-
-std::unique_ptr<DataLayerChain> FakeStorage::MakeChain() {
-  return std::make_unique<ChainImpl>(size_, strategy_, range_,
-                                     bit_vector_.Copy());
-}
-
-FakeStorage::ChainImpl::ChainImpl(uint32_t size,
-                                  SearchStrategy strategy,
-                                  Range range,
-                                  BitVector bv)
+FakeStorageChain::FakeStorageChain(uint32_t size,
+                                   SearchStrategy strategy,
+                                   Range range,
+                                   BitVector bv)
     : size_(size),
       strategy_(strategy),
       range_(range),
       bit_vector_(std::move(bv)) {}
 
-SingleSearchResult FakeStorage::ChainImpl::SingleSearch(FilterOp,
-                                                        SqlValue,
-                                                        uint32_t i) const {
+SingleSearchResult FakeStorageChain::SingleSearch(FilterOp,
+                                                  SqlValue,
+                                                  uint32_t i) const {
   switch (strategy_) {
     case kAll:
       return SingleSearchResult::kMatch;
@@ -65,15 +56,43 @@
   PERFETTO_FATAL("For GCC");
 }
 
-SearchValidationResult FakeStorage::ChainImpl::ValidateSearchConstraints(
+UniqueSearchResult FakeStorageChain::UniqueSearch(FilterOp,
+                                                  SqlValue,
+                                                  uint32_t* i) const {
+  switch (strategy_) {
+    case kAll:
+      if (size_ != 1) {
+        return UniqueSearchResult::kNeedsFullSearch;
+      }
+      *i = 0;
+      return UniqueSearchResult::kMatch;
+    case kNone:
+      return UniqueSearchResult::kNoMatch;
+    case kBitVector:
+      if (bit_vector_.CountSetBits() != 1) {
+        return UniqueSearchResult::kNeedsFullSearch;
+      }
+      *i = bit_vector_.IndexOfNthSet(0);
+      return UniqueSearchResult::kMatch;
+    case kRange:
+      if (range_.size() != 1) {
+        return UniqueSearchResult::kNeedsFullSearch;
+      }
+      *i = range_.start;
+      return UniqueSearchResult::kMatch;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+SearchValidationResult FakeStorageChain::ValidateSearchConstraints(
     FilterOp,
     SqlValue) const {
   return SearchValidationResult::kOk;
 }
 
-RangeOrBitVector FakeStorage::ChainImpl::SearchValidated(FilterOp,
-                                                         SqlValue,
-                                                         Range in) const {
+RangeOrBitVector FakeStorageChain::SearchValidated(FilterOp,
+                                                   SqlValue,
+                                                   Range in) const {
   switch (strategy_) {
     case kAll:
       return RangeOrBitVector(in);
@@ -91,10 +110,9 @@
   PERFETTO_FATAL("For GCC");
 }
 
-RangeOrBitVector FakeStorage::ChainImpl::IndexSearchValidated(
-    FilterOp,
-    SqlValue,
-    Indices indices) const {
+RangeOrBitVector FakeStorageChain::IndexSearchValidated(FilterOp,
+                                                        SqlValue,
+                                                        Indices indices) const {
   switch (strategy_) {
     case kAll:
       return RangeOrBitVector(Range(0, indices.size));
@@ -115,10 +133,9 @@
   PERFETTO_FATAL("For GCC");
 }
 
-Range FakeStorage::ChainImpl::OrderedIndexSearchValidated(
-    FilterOp,
-    SqlValue,
-    Indices indices) const {
+Range FakeStorageChain::OrderedIndexSearchValidated(FilterOp,
+                                                    SqlValue,
+                                                    Indices indices) const {
   if (strategy_ == kAll) {
     return {0, indices.size};
   }
@@ -152,13 +169,11 @@
           static_cast<uint32_t>(std::distance(indices.data, first_non_set))};
 }
 
-void FakeStorage::ChainImpl::StableSort(SortToken*,
-                                        SortToken*,
-                                        SortDirection) const {
+void FakeStorageChain::StableSort(SortToken*, SortToken*, SortDirection) const {
   PERFETTO_FATAL("Not implemented");
 }
 
-void FakeStorage::ChainImpl::Serialize(StorageProto*) const {
+void FakeStorageChain::Serialize(StorageProto*) const {
   // FakeStorage doesn't really make sense to serialize.
   PERFETTO_FATAL("Not implemented");
 }
diff --git a/src/trace_processor/db/column/fake_storage.h b/src/trace_processor/db/column/fake_storage.h
index 07323fa..8d4d6b5 100644
--- a/src/trace_processor/db/column/fake_storage.h
+++ b/src/trace_processor/db/column/fake_storage.h
@@ -30,90 +30,80 @@
 
 namespace perfetto::trace_processor::column {
 
-// Fake implementation of Storage for use in tests.
-class FakeStorage final : public DataLayer {
+// Fake implementation of DataLayerChain which can be used in unittests.
+class FakeStorageChain : public DataLayerChain {
  public:
-  std::unique_ptr<DataLayerChain> MakeChain() override;
-
-  static std::unique_ptr<DataLayer> SearchAll(uint32_t size) {
-    return std::unique_ptr<DataLayer>(
-        new FakeStorage(size, SearchStrategy::kAll));
+  // Factory function for creating a DataLayerChain which matches all rows from
+  // [0, size).
+  static std::unique_ptr<DataLayerChain> SearchAll(uint32_t size) {
+    return std::unique_ptr<DataLayerChain>(
+        new FakeStorageChain(size, SearchStrategy::kAll, Range(), BitVector()));
   }
 
-  static std::unique_ptr<DataLayer> SearchNone(uint32_t size) {
-    return std::unique_ptr<DataLayer>(
-        new FakeStorage(size, SearchStrategy::kNone));
+  // Factory function for creating a DataLayerChain which matches zero rows.
+  static std::unique_ptr<DataLayerChain> SearchNone(uint32_t size) {
+    return std::unique_ptr<DataLayerChain>(new FakeStorageChain(
+        size, SearchStrategy::kNone, Range(), BitVector()));
   }
 
-  static std::unique_ptr<DataLayer> SearchSubset(uint32_t size, Range r) {
-    std::unique_ptr<FakeStorage> storage(
-        new FakeStorage(size, SearchStrategy::kRange));
-    storage->range_ = r;
-    return std::move(storage);
+  // Factory function for creating a DataLayerChain which matches rows [r.start,
+  // r.end).
+  static std::unique_ptr<DataLayerChain> SearchSubset(uint32_t size, Range r) {
+    return std::unique_ptr<DataLayerChain>(
+        new FakeStorageChain(size, SearchStrategy::kRange, r, BitVector()));
   }
 
-  static std::unique_ptr<DataLayer> SearchSubset(uint32_t size, BitVector bv) {
-    std::unique_ptr<FakeStorage> storage(
-        new FakeStorage(size, SearchStrategy::kBitVector));
-    storage->bit_vector_ = std::move(bv);
-    return std::move(storage);
+  // Factory function for creating a DataLayerChain which matches rows of the
+  // set bit positions of |bv|.
+  static std::unique_ptr<DataLayerChain> SearchSubset(uint32_t size,
+                                                      BitVector bv) {
+    return std::unique_ptr<DataLayerChain>(new FakeStorageChain(
+        size, SearchStrategy::kBitVector, Range(), std::move(bv)));
   }
 
-  static std::unique_ptr<DataLayer> SearchSubset(
+  // Factory function for creating a DataLayerChain which matches rows specified
+  // by |index_vec|.
+  static std::unique_ptr<DataLayerChain> SearchSubset(
       uint32_t size,
       const std::vector<uint32_t>& index_vec) {
-    std::unique_ptr<FakeStorage> storage(
-        new FakeStorage(size, SearchStrategy::kBitVector));
     BitVector bv(size);
     for (uint32_t i : index_vec) {
       bv.Set(i);
     }
-    storage->bit_vector_ = std::move(bv);
-    return std::move(storage);
+    return std::unique_ptr<DataLayerChain>(new FakeStorageChain(
+        size, SearchStrategy::kBitVector, Range(), std::move(bv)));
   }
 
+  // Implementation of DataLayerChain.
+  SingleSearchResult SingleSearch(FilterOp, SqlValue, uint32_t) const override;
+
+  UniqueSearchResult UniqueSearch(FilterOp, SqlValue, uint32_t*) const override;
+
+  SearchValidationResult ValidateSearchConstraints(FilterOp,
+                                                   SqlValue) const override;
+
+  RangeOrBitVector SearchValidated(FilterOp, SqlValue, Range) const override;
+
+  RangeOrBitVector IndexSearchValidated(FilterOp,
+                                        SqlValue,
+                                        Indices) const override;
+
+  Range OrderedIndexSearchValidated(FilterOp, SqlValue, Indices) const override;
+
+  void StableSort(SortToken* start,
+                  SortToken* end,
+                  SortDirection) const override;
+
+  void Serialize(StorageProto*) const override;
+
+  uint32_t size() const override { return size_; }
+
+  std::string DebugString() const override { return "FakeStorage"; }
+
  private:
   enum SearchStrategy { kNone, kAll, kRange, kBitVector };
 
-  class ChainImpl : public DataLayerChain {
-   public:
-    ChainImpl(uint32_t, SearchStrategy, Range, BitVector);
-
-    SingleSearchResult SingleSearch(FilterOp,
-                                    SqlValue,
-                                    uint32_t) const override;
-
-    SearchValidationResult ValidateSearchConstraints(FilterOp,
-                                                     SqlValue) const override;
-
-    RangeOrBitVector SearchValidated(FilterOp, SqlValue, Range) const override;
-
-    RangeOrBitVector IndexSearchValidated(FilterOp,
-                                          SqlValue,
-                                          Indices) const override;
-
-    Range OrderedIndexSearchValidated(FilterOp,
-                                      SqlValue,
-                                      Indices) const override;
-
-    void StableSort(SortToken* start,
-                    SortToken* end,
-                    SortDirection) const override;
-
-    void Serialize(StorageProto*) const override;
-
-    uint32_t size() const override { return size_; }
-
-    std::string DebugString() const override { return "FakeStorage"; }
-
-   private:
-    uint32_t size_ = 0;
-    SearchStrategy strategy_ = SearchStrategy::kNone;
-    Range range_;
-    BitVector bit_vector_;
-  };
-
-  FakeStorage(uint32_t size, SearchStrategy strategy);
+  FakeStorageChain(uint32_t, SearchStrategy, Range, BitVector);
 
   uint32_t size_ = 0;
   SearchStrategy strategy_ = SearchStrategy::kNone;
diff --git a/src/trace_processor/db/column/id_storage.cc b/src/trace_processor/db/column/id_storage.cc
index 6fdb800..1c32194 100644
--- a/src/trace_processor/db/column/id_storage.cc
+++ b/src/trace_processor/db/column/id_storage.cc
@@ -191,6 +191,19 @@
   PERFETTO_FATAL("For GCC");
 }
 
+UniqueSearchResult IdStorage::ChainImpl::UniqueSearch(FilterOp op,
+                                                      SqlValue sql_val,
+                                                      uint32_t* index) const {
+  if (sql_val.type != SqlValue::kLong ||
+      sql_val.long_value >= std::numeric_limits<uint32_t>::max() ||
+      sql_val.long_value <= std::numeric_limits<uint32_t>::min() ||
+      op != FilterOp::kEq) {
+    return UniqueSearchResult::kNeedsFullSearch;
+  }
+  *index = static_cast<uint32_t>(sql_val.long_value);
+  return UniqueSearchResult::kMatch;
+}
+
 RangeOrBitVector IdStorage::ChainImpl::SearchValidated(
     FilterOp op,
     SqlValue sql_val,
diff --git a/src/trace_processor/db/column/id_storage.h b/src/trace_processor/db/column/id_storage.h
index fd8ba95..bb0d7d5 100644
--- a/src/trace_processor/db/column/id_storage.h
+++ b/src/trace_processor/db/column/id_storage.h
@@ -36,7 +36,10 @@
 // included in the column.
 class IdStorage final : public DataLayer {
  public:
-  std::unique_ptr<DataLayerChain> MakeChain() override;
+  IdStorage();
+  ~IdStorage() override;
+
+  std::unique_ptr<DataLayerChain> MakeChain();
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -45,6 +48,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/id_storage_unittest.cc b/src/trace_processor/db/column/id_storage_unittest.cc
index 48ce18e..32b0583 100644
--- a/src/trace_processor/db/column/id_storage_unittest.cc
+++ b/src/trace_processor/db/column/id_storage_unittest.cc
@@ -145,6 +145,25 @@
             SingleSearchResult::kNoMatch);
 }
 
+TEST(IdStorage, UniqueSearch) {
+  IdStorage storage;
+  auto chain = storage.MakeChain();
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kEq, SqlValue::Long(5), &row),
+            UniqueSearchResult::kMatch);
+  ASSERT_EQ(row, 5u);
+
+  row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(
+      chain->UniqueSearch(
+          FilterOp::kEq,
+          SqlValue::Long(std::numeric_limits<uint32_t>::max() + 1ll), &row),
+      UniqueSearchResult::kNeedsFullSearch);
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kEq, SqlValue::Double(0), &row),
+            UniqueSearchResult::kNeedsFullSearch);
+}
+
 TEST(IdStorage, SearchEqSimple) {
   IdStorage storage;
   auto chain = storage.MakeChain();
diff --git a/src/trace_processor/db/column/make_chain.cc b/src/trace_processor/db/column/make_chain.cc
deleted file mode 100644
index 15ee387..0000000
--- a/src/trace_processor/db/column/make_chain.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-#include <memory>
-#include <utility>
-
-#include "src/trace_processor/db/column/arrangement_overlay.h"
-#include "src/trace_processor/db/column/data_layer.h"
-#include "src/trace_processor/db/column/dense_null_overlay.h"
-#include "src/trace_processor/db/column/dummy_storage.h"
-#include "src/trace_processor/db/column/id_storage.h"
-#include "src/trace_processor/db/column/null_overlay.h"
-#include "src/trace_processor/db/column/numeric_storage.h"
-#include "src/trace_processor/db/column/range_overlay.h"
-#include "src/trace_processor/db/column/selector_overlay.h"
-#include "src/trace_processor/db/column/set_id_storage.h"
-#include "src/trace_processor/db/column/string_storage.h"
-
-// This file contains the implementation of MakeChain for all the
-// DataLayer implementations. They are all centralised here because
-// there is an alternative set of implementations (see make_chain_minimal.cc)
-// the "minimal" target used by export_json in Chrome.
-
-namespace perfetto::trace_processor::column {
-
-std::unique_ptr<DataLayerChain> ArrangementOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain> inner,
-    ChainCreationArgs args) {
-  return std::make_unique<ChainImpl>(std::move(inner), arrangement_,
-                                     arrangement_state_,
-                                     args.does_layer_order_chain_contents);
-}
-
-std::unique_ptr<DataLayerChain> DenseNullOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain> inner,
-    ChainCreationArgs) {
-  return std::make_unique<ChainImpl>(std::move(inner), non_null_);
-}
-
-std::unique_ptr<DataLayerChain> DummyStorage::MakeChain() {
-  return std::make_unique<ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> IdStorage::MakeChain() {
-  return std::make_unique<ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> NullOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain> inner,
-    ChainCreationArgs) {
-  return std::make_unique<ChainImpl>(std::move(inner), non_null_);
-}
-
-template <typename T>
-std::unique_ptr<DataLayerChain> NumericStorage<T>::MakeChain() {
-  return std::make_unique<ChainImpl>(vector_, storage_type_, is_sorted_);
-}
-template std::unique_ptr<DataLayerChain> NumericStorage<double>::MakeChain();
-template std::unique_ptr<DataLayerChain> NumericStorage<uint32_t>::MakeChain();
-template std::unique_ptr<DataLayerChain> NumericStorage<int32_t>::MakeChain();
-template std::unique_ptr<DataLayerChain> NumericStorage<int64_t>::MakeChain();
-
-std::unique_ptr<DataLayerChain> RangeOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain> inner,
-    ChainCreationArgs) {
-  return std::make_unique<ChainImpl>(std::move(inner), range_);
-}
-
-std::unique_ptr<DataLayerChain> SelectorOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain> inner,
-    ChainCreationArgs) {
-  return std::make_unique<ChainImpl>(std::move(inner), selector_);
-}
-
-std::unique_ptr<DataLayerChain> SetIdStorage::MakeChain() {
-  return std::make_unique<ChainImpl>(values_);
-}
-
-std::unique_ptr<DataLayerChain> StringStorage::MakeChain() {
-  return std::make_unique<ChainImpl>(string_pool_, data_, is_sorted_);
-}
-
-}  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/make_chain_minimal.cc b/src/trace_processor/db/column/make_chain_minimal.cc
deleted file mode 100644
index 7a23549..0000000
--- a/src/trace_processor/db/column/make_chain_minimal.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-#include <memory>
-
-#include "src/trace_processor/db/column/arrangement_overlay.h"
-#include "src/trace_processor/db/column/data_layer.h"
-#include "src/trace_processor/db/column/dense_null_overlay.h"
-#include "src/trace_processor/db/column/dummy_storage.h"
-#include "src/trace_processor/db/column/id_storage.h"
-#include "src/trace_processor/db/column/null_overlay.h"
-#include "src/trace_processor/db/column/numeric_storage.h"
-#include "src/trace_processor/db/column/range_overlay.h"
-#include "src/trace_processor/db/column/selector_overlay.h"
-#include "src/trace_processor/db/column/set_id_storage.h"
-#include "src/trace_processor/db/column/string_storage.h"
-
-// This file contains the implementation of MakeChain for all the
-// DataLayer implementations the "minimal" target used by export_json in Chrome.
-// See make_chain.cc for the real implementations for these functions.
-
-namespace perfetto::trace_processor::column {
-
-std::unique_ptr<DataLayerChain> ArrangementOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain>,
-    ChainCreationArgs) {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> DenseNullOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain>,
-    ChainCreationArgs) {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> DummyStorage::MakeChain() {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> IdStorage::MakeChain() {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> NullOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain>,
-    ChainCreationArgs) {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-template <typename T>
-std::unique_ptr<DataLayerChain> NumericStorage<T>::MakeChain() {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-template std::unique_ptr<DataLayerChain> NumericStorage<double>::MakeChain();
-template std::unique_ptr<DataLayerChain> NumericStorage<uint32_t>::MakeChain();
-template std::unique_ptr<DataLayerChain> NumericStorage<int32_t>::MakeChain();
-template std::unique_ptr<DataLayerChain> NumericStorage<int64_t>::MakeChain();
-
-std::unique_ptr<DataLayerChain> RangeOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain>,
-    ChainCreationArgs) {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> SelectorOverlay::MakeChain(
-    std::unique_ptr<DataLayerChain>,
-    ChainCreationArgs) {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> SetIdStorage::MakeChain() {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-std::unique_ptr<DataLayerChain> StringStorage::MakeChain() {
-  return std::make_unique<DummyStorage::ChainImpl>();
-}
-
-}  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/null_overlay.cc b/src/trace_processor/db/column/null_overlay.cc
index 2ddc004..fe489e4 100644
--- a/src/trace_processor/db/column/null_overlay.cc
+++ b/src/trace_processor/db/column/null_overlay.cc
@@ -78,8 +78,6 @@
 
 }  // namespace
 
-NullOverlay::NullOverlay(const BitVector* non_null) : non_null_(non_null) {}
-
 SingleSearchResult NullOverlay::ChainImpl::SingleSearch(FilterOp op,
                                                         SqlValue sql_val,
                                                         uint32_t index) const {
@@ -106,6 +104,24 @@
   PERFETTO_FATAL("For GCC");
 }
 
+UniqueSearchResult NullOverlay::ChainImpl::UniqueSearch(FilterOp op,
+                                                        SqlValue sql_val,
+                                                        uint32_t* index) const {
+  switch (inner_->UniqueSearch(op, sql_val, index)) {
+    case UniqueSearchResult::kMatch:
+      if (*index >= non_null_->CountSetBits()) {
+        return UniqueSearchResult::kNoMatch;
+      }
+      *index = non_null_->IndexOfNthSet(*index);
+      return UniqueSearchResult::kMatch;
+    case UniqueSearchResult::kNoMatch:
+      return UniqueSearchResult::kNoMatch;
+    case UniqueSearchResult::kNeedsFullSearch:
+      return UniqueSearchResult::kNeedsFullSearch;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 NullOverlay::ChainImpl::ChainImpl(std::unique_ptr<DataLayerChain> innner,
                                   const BitVector* non_null)
     : inner_(std::move(innner)), non_null_(non_null) {
diff --git a/src/trace_processor/db/column/null_overlay.h b/src/trace_processor/db/column/null_overlay.h
index 8609251..35c5e52 100644
--- a/src/trace_processor/db/column/null_overlay.h
+++ b/src/trace_processor/db/column/null_overlay.h
@@ -33,10 +33,11 @@
 class NullOverlay final : public DataLayer {
  public:
   explicit NullOverlay(const BitVector* non_null);
+  ~NullOverlay() override;
 
   std::unique_ptr<DataLayerChain> MakeChain(
       std::unique_ptr<DataLayerChain>,
-      ChainCreationArgs = ChainCreationArgs()) override;
+      ChainCreationArgs = ChainCreationArgs());
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -47,6 +48,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/null_overlay_unittest.cc b/src/trace_processor/db/column/null_overlay_unittest.cc
index eb7c2bf..73934a6 100644
--- a/src/trace_processor/db/column/null_overlay_unittest.cc
+++ b/src/trace_processor/db/column/null_overlay_unittest.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/db/column/null_overlay.h"
 
 #include <cstdint>
+#include <limits>
 #include <memory>
 #include <vector>
 
@@ -37,9 +38,9 @@
 
 TEST(NullOverlay, SingleSearch) {
   BitVector bv{0, 1, 0, 1, 1, 1};
-  auto fake = FakeStorage::SearchSubset(4, std::vector<uint32_t>{1, 2});
+  auto fake = FakeStorageChain::SearchSubset(4, std::vector<uint32_t>{1, 2});
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0u), 3),
             SingleSearchResult::kMatch);
@@ -51,9 +52,9 @@
 
 TEST(NullOverlay, SingleSearchIsNull) {
   BitVector bv{0, 1, 0, 1, 1, 1};
-  auto fake = FakeStorage::SearchNone(4);
+  auto fake = FakeStorageChain::SearchNone(4);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kIsNull, SqlValue(), 0),
             SingleSearchResult::kMatch);
@@ -63,9 +64,9 @@
 
 TEST(NullOverlay, SingleSearchIsNotNull) {
   BitVector bv{0, 1, 0, 1, 1, 1};
-  auto fake = FakeStorage::SearchAll(4);
+  auto fake = FakeStorageChain::SearchAll(4);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kIsNotNull, SqlValue(), 1),
             SingleSearchResult::kMatch);
@@ -73,11 +74,34 @@
             SingleSearchResult::kNoMatch);
 }
 
+TEST(NullOverlay, UniqueSearch) {
+  BitVector bv{0, 0, 0, 1, 1};
+  NullOverlay storage(&bv);
+  auto fake = FakeStorageChain::SearchSubset(5, Range(1, 2));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kMatch);
+  ASSERT_EQ(row, 4u);
+}
+
+TEST(NullOverlay, UniqueSearchOutOfBounds) {
+  BitVector bv{0, 0, 0, 1, 1};
+  NullOverlay storage(&bv);
+  auto fake = FakeStorageChain::SearchSubset(5, Range(4, 5));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kNoMatch);
+}
+
 TEST(NullOverlay, SearchInputInsideBoundary) {
   BitVector bv{0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  auto fake = FakeStorage::SearchAll(4u);
+  auto fake = FakeStorageChain::SearchAll(4u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGt, SqlValue::Long(0), Range(1, 6));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4));
@@ -85,9 +109,9 @@
 
 TEST(NullOverlay, SearchInputOutsideBoundary) {
   BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  auto fake = FakeStorage::SearchAll(5u);
+  auto fake = FakeStorageChain::SearchAll(5u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGt, SqlValue::Long(0), Range(3, 8));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4, 7));
@@ -95,9 +119,9 @@
 
 TEST(NullOverlay, SubsetResultOutsideBoundary) {
   BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  auto fake = FakeStorage::SearchSubset(5u, Range(1, 3));
+  auto fake = FakeStorageChain::SearchSubset(5u, Range(1, 3));
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGt, SqlValue::Long(0), Range(0, 11));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(3, 4));
@@ -105,9 +129,9 @@
 
 TEST(NullOverlay, SubsetResultOnBoundary) {
   BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
-  auto fake = FakeStorage::SearchAll(5u);
+  auto fake = FakeStorageChain::SearchAll(5u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGt, SqlValue::Long(0), Range(0, 11));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1, 3, 4, 7, 8));
@@ -115,9 +139,9 @@
 
 TEST(NullOverlay, BitVectorSubset) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchSubset(4u, BitVector{0, 1, 0, 1});
+  auto fake = FakeStorageChain::SearchSubset(4u, BitVector{0, 1, 0, 1});
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGt, SqlValue::Long(0), Range(0, 8));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(2, 6));
@@ -125,9 +149,9 @@
 
 TEST(NullOverlay, BitVectorSubsetIsNull) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchSubset(4u, BitVector{0, 1, 0, 1});
+  auto fake = FakeStorageChain::SearchSubset(4u, BitVector{0, 1, 0, 1});
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kIsNull, SqlValue(), Range(0, 8));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 2, 3, 4, 6, 7));
@@ -135,9 +159,9 @@
 
 TEST(NullOverlay, IndexSearchAllElements) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchAll(4u);
+  auto fake = FakeStorageChain::SearchAll(4u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{1, 5, 2};
   auto res =
@@ -149,9 +173,9 @@
 
 TEST(NullOverlay, IndexSearchPartialElements) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchAll(4u);
+  auto fake = FakeStorageChain::SearchAll(4u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{1, 4, 2};
   auto res =
@@ -163,9 +187,9 @@
 
 TEST(NullOverlay, IndexSearchIsNullOpEmptyRes) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchNone(4u);
+  auto fake = FakeStorageChain::SearchNone(4u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{0, 3, 5, 4, 2};
   auto res =
@@ -177,9 +201,9 @@
 
 TEST(NullOverlay, IndexSearchIsNullOp) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchSubset(4u, Range(2, 3));
+  auto fake = FakeStorageChain::SearchSubset(4u, Range(2, 3));
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{0, 3, 2, 4, 5};
   auto res =
@@ -191,9 +215,9 @@
 
 TEST(NullOverlay, IndexSearchIsNotNullOp) {
   BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchAll(4u);
+  auto fake = FakeStorageChain::SearchAll(4u);
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{0, 3, 4};
   auto res =
@@ -207,9 +231,9 @@
   BitVector bv{0, 1, 1, 1, 0, 1};
   // Passing values in final storage (on normal operations)
   // 0, 1, 0, 1, 0, 0
-  auto fake = FakeStorage::SearchSubset(4, BitVector{1, 0, 1, 0});
+  auto fake = FakeStorageChain::SearchSubset(4, BitVector{1, 0, 1, 0});
   NullOverlay storage(&bv);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   // Passing values on final data
   // NULL, NULL, 0, 1, 1
diff --git a/src/trace_processor/db/column/numeric_storage.cc b/src/trace_processor/db/column/numeric_storage.cc
index 2c88b38..27f6204 100644
--- a/src/trace_processor/db/column/numeric_storage.cc
+++ b/src/trace_processor/db/column/numeric_storage.cc
@@ -27,6 +27,7 @@
 #include <string>
 #include <utility>
 #include <variant>
+#include <vector>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/public/compiler.h"
@@ -258,7 +259,7 @@
   auto i_as_d = static_cast<double>(i);
 
   // Case when |sql_val| can be interpreted as a SqlValue::Long.
-  if (std::equal_to<int64_t>()(i, static_cast<int64_t>(i_as_d))) {
+  if (std::equal_to<>()(i, static_cast<int64_t>(i_as_d))) {
     *sql_val = SqlValue::Double(i_as_d);
     return SearchValidationResult::kOk;
   }
@@ -293,16 +294,18 @@
 
 }  // namespace
 
-NumericStorageBase::NumericStorageBase(ColumnType type, bool is_sorted)
-    : storage_type_(type), is_sorted_(is_sorted) {}
-
-NumericStorageBase::~NumericStorageBase() = default;
-
 NumericStorageBase::ChainImpl::ChainImpl(const void* vector_ptr,
                                          ColumnType type,
                                          bool is_sorted)
     : vector_ptr_(vector_ptr), storage_type_(type), is_sorted_(is_sorted) {}
 
+UniqueSearchResult NumericStorageBase::ChainImpl::UniqueSearch(
+    FilterOp,
+    SqlValue,
+    uint32_t*) const {
+  return UniqueSearchResult::kNeedsFullSearch;
+}
+
 SearchValidationResult NumericStorageBase::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue val) const {
@@ -701,11 +704,4 @@
   }
 }
 
-// Define explicit instantiation of the necessary templates here to reduce
-// binary size bloat.
-template class NumericStorage<double>;
-template class NumericStorage<uint32_t>;
-template class NumericStorage<int32_t>;
-template class NumericStorage<int64_t>;
-
 }  // namespace perfetto::trace_processor::column
diff --git a/src/trace_processor/db/column/numeric_storage.h b/src/trace_processor/db/column/numeric_storage.h
index f6f450a..af69e28 100644
--- a/src/trace_processor/db/column/numeric_storage.h
+++ b/src/trace_processor/db/column/numeric_storage.h
@@ -37,6 +37,10 @@
  protected:
   class ChainImpl : public DataLayerChain {
    public:
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
@@ -77,7 +81,7 @@
     const bool is_sorted_ = false;
   };
 
-  NumericStorageBase(ColumnType type, bool is_sorted);
+  NumericStorageBase(ColumnType type, bool is_sorted, Impl impl);
   ~NumericStorageBase() override;
 
   const ColumnType storage_type_ = ColumnType::kDummy;
@@ -90,13 +94,12 @@
  public:
   PERFETTO_NO_INLINE NumericStorage(const std::vector<T>* vec,
                                     ColumnType type,
-                                    bool is_sorted)
-      : NumericStorageBase(type, is_sorted), vector_(vec) {}
+                                    bool is_sorted);
 
   // The implementation of this function is given by
   // make_chain.cc/make_chain_minimal.cc depending on whether this is a minimal
   // or full build of trace processor.
-  std::unique_ptr<DataLayerChain> MakeChain() override;
+  std::unique_ptr<DataLayerChain> MakeChain();
 
  private:
   class ChainImpl : public NumericStorageBase::ChainImpl {
@@ -152,6 +155,21 @@
    private:
     const std::vector<T>* vector_;
   };
+  Impl GetImpl() {
+    if constexpr (std::is_same_v<T, double>) {
+      return Impl::kNumericDouble;
+    } else if constexpr (std::is_same_v<T, uint32_t>) {
+      return Impl::kNumericUint32;
+    } else if constexpr (std::is_same_v<T, int32_t>) {
+      return Impl::kNumericInt32;
+    } else if constexpr (std::is_same_v<T, int64_t>) {
+      return Impl::kNumericInt64;
+    } else {
+      // false doesn't work as expression has to depend on the template
+      // parameter
+      static_assert(sizeof(T*) == 0, "T is not supported");
+    }
+  }
 
   const std::vector<T>* vector_;
 };
diff --git a/src/trace_processor/db/column/range_overlay.cc b/src/trace_processor/db/column/range_overlay.cc
index 5a92138..308bf1c 100644
--- a/src/trace_processor/db/column/range_overlay.cc
+++ b/src/trace_processor/db/column/range_overlay.cc
@@ -34,8 +34,6 @@
 
 using Range = Range;
 
-RangeOverlay::RangeOverlay(const Range* range) : range_(range) {}
-
 RangeOverlay::ChainImpl::ChainImpl(std::unique_ptr<DataLayerChain> inner,
                                    const Range* range)
     : inner_(std::move(inner)), range_(range) {
@@ -49,6 +47,25 @@
   return inner_->SingleSearch(op, sql_val, i + range_->start);
 }
 
+UniqueSearchResult RangeOverlay::ChainImpl::UniqueSearch(
+    FilterOp op,
+    SqlValue sql_val,
+    uint32_t* index) const {
+  switch (inner_->UniqueSearch(op, sql_val, index)) {
+    case UniqueSearchResult::kMatch:
+      if (!range_->Contains(*index)) {
+        return UniqueSearchResult::kNoMatch;
+      }
+      *index -= range_->start;
+      return UniqueSearchResult::kMatch;
+    case UniqueSearchResult::kNoMatch:
+      return UniqueSearchResult::kNoMatch;
+    case UniqueSearchResult::kNeedsFullSearch:
+      return UniqueSearchResult::kNeedsFullSearch;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 SearchValidationResult RangeOverlay::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue sql_val) const {
diff --git a/src/trace_processor/db/column/range_overlay.h b/src/trace_processor/db/column/range_overlay.h
index 1f53acb..366b2d3 100644
--- a/src/trace_processor/db/column/range_overlay.h
+++ b/src/trace_processor/db/column/range_overlay.h
@@ -30,10 +30,11 @@
 class RangeOverlay final : public DataLayer {
  public:
   explicit RangeOverlay(const Range*);
+  ~RangeOverlay() override;
 
   std::unique_ptr<DataLayerChain> MakeChain(
       std::unique_ptr<DataLayerChain>,
-      ChainCreationArgs = ChainCreationArgs()) override;
+      ChainCreationArgs = ChainCreationArgs());
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -44,6 +45,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/range_overlay_unittest.cc b/src/trace_processor/db/column/range_overlay_unittest.cc
index ee493dd..bf13cbb 100644
--- a/src/trace_processor/db/column/range_overlay_unittest.cc
+++ b/src/trace_processor/db/column/range_overlay_unittest.cc
@@ -17,6 +17,8 @@
 #include "src/trace_processor/db/column/range_overlay.h"
 
 #include <cstdint>
+#include <limits>
+#include <utility>
 #include <vector>
 
 #include "perfetto/trace_processor/basic_types.h"
@@ -35,12 +37,12 @@
 using testing::IsEmpty;
 using Range = Range;
 
-TEST(SelectorOverlay, SearchSingle) {
+TEST(SelectorOverlay, SingleSearch) {
   Range range(3, 8);
   RangeOverlay storage(&range);
-  auto fake = FakeStorage::SearchSubset(
+  auto fake = FakeStorageChain::SearchSubset(
       8, BitVector{false, false, false, true, false, false, false, false});
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kEq, SqlValue::Long(0u), 0),
             SingleSearchResult::kMatch);
@@ -48,11 +50,45 @@
             SingleSearchResult::kNoMatch);
 }
 
+TEST(SelectorOverlay, UniqueSearch) {
+  Range range(1, 3);
+  RangeOverlay storage(&range);
+  auto fake = FakeStorageChain::SearchSubset(5, Range(2, 3));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kMatch);
+  ASSERT_EQ(row, 1u);
+}
+
+TEST(SelectorOverlay, UniqueSearchLowOutOfBounds) {
+  Range range(3, 8);
+  RangeOverlay storage(&range);
+  auto fake = FakeStorageChain::SearchSubset(8, Range(1, 2));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kNoMatch);
+}
+
+TEST(SelectorOverlay, UniqueSearchHighOutOfBounds) {
+  Range range(3, 8);
+  RangeOverlay storage(&range);
+  auto fake = FakeStorageChain::SearchSubset(9, Range(8, 9));
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kIsNotNull, SqlValue(), &row),
+            UniqueSearchResult::kNoMatch);
+}
+
 TEST(RangeOverlay, SearchAll) {
   Range range(3, 8);
   RangeOverlay storage(&range);
-  auto fake = FakeStorage::SearchAll(10);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto fake = FakeStorageChain::SearchAll(10);
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(1, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1u, 2u, 3u));
@@ -61,38 +97,40 @@
 TEST(RangeOverlay, SearchNone) {
   Range range(3, 8);
   RangeOverlay storage(&range);
-  auto fake = FakeStorage::SearchNone(10);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto fake = FakeStorageChain::SearchNone(10);
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(1, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), IsEmpty());
 }
 
 TEST(RangeOverlay, SearchLimited) {
-  auto fake = FakeStorage::SearchSubset(10, std::vector<uint32_t>{4});
+  auto fake = FakeStorageChain::SearchSubset(10, std::vector<uint32_t>{4});
   Range range(3, 5);
   RangeOverlay storage(&range);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 2));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1u));
 }
 
 TEST(RangeOverlay, SearchBitVector) {
-  auto fake = FakeStorage::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
+  auto fake =
+      FakeStorageChain::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
   Range range(3, 6);
   RangeOverlay storage(&range);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 3));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 2));
 }
 
 TEST(RangeOverlay, IndexSearch) {
-  auto fake = FakeStorage::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
+  auto fake =
+      FakeStorageChain::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
   Range range(3, 5);
   RangeOverlay storage(&range);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{1u, 0u, 3u};
   RangeOrBitVector res = chain->IndexSearch(
diff --git a/src/trace_processor/db/column/selector_overlay.cc b/src/trace_processor/db/column/selector_overlay.cc
index 47df692..935c4e2 100644
--- a/src/trace_processor/db/column/selector_overlay.cc
+++ b/src/trace_processor/db/column/selector_overlay.cc
@@ -34,9 +34,6 @@
 
 namespace perfetto::trace_processor::column {
 
-SelectorOverlay::SelectorOverlay(const BitVector* selector)
-    : selector_(selector) {}
-
 SelectorOverlay::ChainImpl::ChainImpl(std::unique_ptr<DataLayerChain> inner,
                                       const BitVector* selector)
     : inner_(std::move(inner)), selector_(selector) {}
@@ -47,6 +44,25 @@
   return inner_->SingleSearch(op, sql_val, selector_->IndexOfNthSet(i));
 }
 
+UniqueSearchResult SelectorOverlay::ChainImpl::UniqueSearch(
+    FilterOp op,
+    SqlValue sql_val,
+    uint32_t* index) const {
+  switch (inner_->UniqueSearch(op, sql_val, index)) {
+    case UniqueSearchResult::kMatch:
+      if (*index >= selector_->size() || !selector_->IsSet(*index)) {
+        return UniqueSearchResult::kNoMatch;
+      }
+      *index = selector_->CountSetBits(*index);
+      return UniqueSearchResult::kMatch;
+    case UniqueSearchResult::kNoMatch:
+      return UniqueSearchResult::kNoMatch;
+    case UniqueSearchResult::kNeedsFullSearch:
+      return UniqueSearchResult::kNeedsFullSearch;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
 SearchValidationResult SelectorOverlay::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue sql_val) const {
diff --git a/src/trace_processor/db/column/selector_overlay.h b/src/trace_processor/db/column/selector_overlay.h
index b550da3..04bc2e7 100644
--- a/src/trace_processor/db/column/selector_overlay.h
+++ b/src/trace_processor/db/column/selector_overlay.h
@@ -34,10 +34,11 @@
 class SelectorOverlay final : public DataLayer {
  public:
   explicit SelectorOverlay(const BitVector*);
+  ~SelectorOverlay() override;
 
   std::unique_ptr<DataLayerChain> MakeChain(
       std::unique_ptr<DataLayerChain>,
-      ChainCreationArgs = ChainCreationArgs()) override;
+      ChainCreationArgs = ChainCreationArgs());
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -48,6 +49,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/selector_overlay_unittest.cc b/src/trace_processor/db/column/selector_overlay_unittest.cc
index cd6f0c5..b9151b8 100644
--- a/src/trace_processor/db/column/selector_overlay_unittest.cc
+++ b/src/trace_processor/db/column/selector_overlay_unittest.cc
@@ -17,11 +17,12 @@
 #include "src/trace_processor/db/column/selector_overlay.h"
 
 #include <cstdint>
+#include <limits>
 #include <vector>
 
-#include "data_layer.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/db/column/data_layer.h"
 #include "src/trace_processor/db/column/fake_storage.h"
 #include "src/trace_processor/db/column/numeric_storage.h"
 #include "src/trace_processor/db/column/types.h"
@@ -36,9 +37,9 @@
 
 TEST(SelectorOverlay, SingleSearch) {
   BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchSubset(8, Range(2, 5));
+  auto fake = FakeStorageChain::SearchSubset(8, Range(2, 5));
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0u), 1),
             SingleSearchResult::kMatch);
@@ -46,11 +47,43 @@
             SingleSearchResult::kNoMatch);
 }
 
+TEST(SelectorOverlay, UniqueSearch) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  auto fake = FakeStorageChain::SearchSubset(8, Range(2, 3));
+  SelectorOverlay storage(&selector);
+  auto chain = storage.MakeChain(std::move(fake));
+
+  uint32_t row = std::numeric_limits<uint32_t>::max();
+  ASSERT_EQ(chain->UniqueSearch(FilterOp::kGe, SqlValue::Long(0u), &row),
+            UniqueSearchResult::kMatch);
+  ASSERT_EQ(row, 1u);
+}
+
+TEST(SelectorOverlay, UniqueSearchNotSet) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  auto fake = FakeStorageChain::SearchSubset(8, Range(4, 5));
+  SelectorOverlay storage(&selector);
+  auto chain = storage.MakeChain(std::move(fake));
+
+  ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0u), 1),
+            SingleSearchResult::kNoMatch);
+}
+
+TEST(SelectorOverlay, UniqueSearchOutOfBounds) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  auto fake = FakeStorageChain::SearchSubset(9, Range(8, 9));
+  SelectorOverlay storage(&selector);
+  auto chain = storage.MakeChain(std::move(fake));
+
+  ASSERT_EQ(chain->SingleSearch(FilterOp::kGe, SqlValue::Long(0u), 1),
+            SingleSearchResult::kNoMatch);
+}
+
 TEST(SelectorOverlay, SearchAll) {
   BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
-  auto fake = FakeStorage::SearchAll(10);
+  auto fake = FakeStorageChain::SearchAll(10);
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(1, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(1u, 2u, 3u));
@@ -58,9 +91,9 @@
 
 TEST(SelectorOverlay, SearchNone) {
   BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
-  auto fake = FakeStorage::SearchNone(10);
+  auto fake = FakeStorageChain::SearchNone(10);
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(1, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), IsEmpty());
@@ -68,9 +101,9 @@
 
 TEST(SelectorOverlay, SearchLimited) {
   BitVector selector{0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1};
-  auto fake = FakeStorage::SearchSubset(10, Range(4, 5));
+  auto fake = FakeStorageChain::SearchSubset(10, Range(4, 5));
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(1, 5));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(2u));
@@ -78,9 +111,10 @@
 
 TEST(SelectorOverlay, SearchBitVector) {
   BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
+  auto fake =
+      FakeStorageChain::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   auto res = chain->Search(FilterOp::kGe, SqlValue::Long(0u), Range(0, 4));
   ASSERT_THAT(utils::ToIndexVectorForTests(res), ElementsAre(0, 2));
@@ -88,9 +122,10 @@
 
 TEST(SelectorOverlay, IndexSearch) {
   BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
-  auto fake = FakeStorage::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
+  auto fake =
+      FakeStorageChain::SearchSubset(8, BitVector({0, 1, 0, 1, 0, 1, 0, 0}));
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{1u, 0u, 3u};
   RangeOrBitVector res = chain->IndexSearch(
@@ -102,9 +137,9 @@
 
 TEST(SelectorOverlay, OrderedIndexSearchTrivial) {
   BitVector selector{1, 0, 1, 0, 1};
-  auto fake = FakeStorage::SearchAll(5);
+  auto fake = FakeStorageChain::SearchAll(5);
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{1u, 0u, 2u};
   Range res = chain->OrderedIndexSearch(
@@ -117,9 +152,9 @@
 
 TEST(SelectorOverlay, OrderedIndexSearchNone) {
   BitVector selector{1, 0, 1, 0, 1};
-  auto fake = FakeStorage::SearchNone(5);
+  auto fake = FakeStorageChain::SearchNone(5);
   SelectorOverlay storage(&selector);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   std::vector<uint32_t> table_idx{1u, 0u, 2u};
   Range res = chain->OrderedIndexSearch(
diff --git a/src/trace_processor/db/column/set_id_storage.cc b/src/trace_processor/db/column/set_id_storage.cc
index dd22def..3e9d497 100644
--- a/src/trace_processor/db/column/set_id_storage.cc
+++ b/src/trace_processor/db/column/set_id_storage.cc
@@ -66,9 +66,6 @@
 
 }  // namespace
 
-SetIdStorage::SetIdStorage(const std::vector<uint32_t>* values)
-    : values_(values) {}
-
 SetIdStorage::ChainImpl::ChainImpl(const std::vector<uint32_t>* values)
     : values_(values) {}
 
@@ -86,6 +83,12 @@
                                     static_cast<uint32_t>(sql_val.long_value));
 }
 
+UniqueSearchResult SetIdStorage::ChainImpl::UniqueSearch(FilterOp,
+                                                         SqlValue,
+                                                         uint32_t*) const {
+  return UniqueSearchResult::kNeedsFullSearch;
+}
+
 SearchValidationResult SetIdStorage::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue val) const {
diff --git a/src/trace_processor/db/column/set_id_storage.h b/src/trace_processor/db/column/set_id_storage.h
index b20ba9c..86d2508 100644
--- a/src/trace_processor/db/column/set_id_storage.h
+++ b/src/trace_processor/db/column/set_id_storage.h
@@ -34,8 +34,9 @@
   using SetId = uint32_t;
 
   explicit SetIdStorage(const std::vector<uint32_t>*);
+  ~SetIdStorage() override;
 
-  std::unique_ptr<DataLayerChain> MakeChain() override;
+  std::unique_ptr<DataLayerChain> MakeChain();
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -46,6 +47,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index 5eaf950..ae6a797 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -197,11 +197,6 @@
 
 }  // namespace
 
-StringStorage::StringStorage(StringPool* string_pool,
-                             const std::vector<StringPool::Id>* data,
-                             bool is_sorted)
-    : data_(data), string_pool_(string_pool), is_sorted_(is_sorted) {}
-
 StringStorage::ChainImpl::ChainImpl(StringPool* string_pool,
                                     const std::vector<StringPool::Id>* data,
                                     bool is_sorted)
@@ -239,8 +234,8 @@
     case FilterOp::kNe: {
       std::optional<StringPool::Id> id =
           string_pool_->GetId(base::StringView(sql_val.string_value));
-      return id && NotEqual()((*data_)[i], *id) ? SingleSearchResult::kMatch
-                                                : SingleSearchResult::kNoMatch;
+      return !id || NotEqual()((*data_)[i], *id) ? SingleSearchResult::kMatch
+                                                 : SingleSearchResult::kNoMatch;
     }
     case FilterOp::kGe:
       return GreaterEqual{string_pool_}(
@@ -285,6 +280,12 @@
   PERFETTO_FATAL("For GCC");
 }
 
+UniqueSearchResult StringStorage::ChainImpl::UniqueSearch(FilterOp,
+                                                          SqlValue,
+                                                          uint32_t*) const {
+  return UniqueSearchResult::kNeedsFullSearch;
+}
+
 SearchValidationResult StringStorage::ChainImpl::ValidateSearchConstraints(
     FilterOp op,
     SqlValue val) const {
diff --git a/src/trace_processor/db/column/string_storage.h b/src/trace_processor/db/column/string_storage.h
index dafa43c..03541aa 100644
--- a/src/trace_processor/db/column/string_storage.h
+++ b/src/trace_processor/db/column/string_storage.h
@@ -35,8 +35,9 @@
   StringStorage(StringPool* string_pool,
                 const std::vector<StringPool::Id>* data,
                 bool is_sorted = false);
+  ~StringStorage() override;
 
-  std::unique_ptr<DataLayerChain> MakeChain() override;
+  std::unique_ptr<DataLayerChain> MakeChain();
 
  private:
   class ChainImpl : public DataLayerChain {
@@ -49,6 +50,10 @@
                                     SqlValue,
                                     uint32_t) const override;
 
+    UniqueSearchResult UniqueSearch(FilterOp,
+                                    SqlValue,
+                                    uint32_t*) const override;
+
     SearchValidationResult ValidateSearchConstraints(FilterOp,
                                                      SqlValue) const override;
 
diff --git a/src/trace_processor/db/column/string_storage_unittest.cc b/src/trace_processor/db/column/string_storage_unittest.cc
index 7d42d14..303ab66 100644
--- a/src/trace_processor/db/column/string_storage_unittest.cc
+++ b/src/trace_processor/db/column/string_storage_unittest.cc
@@ -53,6 +53,8 @@
   ASSERT_EQ(chain->SingleSearch(FilterOp::kEq, SqlValue::String("pierogi"), 3),
             SingleSearchResult::kNoMatch);
 
+  ASSERT_EQ(chain->SingleSearch(FilterOp::kNe, SqlValue::String("foo"), 0),
+            SingleSearchResult::kMatch);
   ASSERT_EQ(chain->SingleSearch(FilterOp::kNe, SqlValue::String("pierogi"), 0),
             SingleSearchResult::kMatch);
   ASSERT_EQ(chain->SingleSearch(FilterOp::kNe, SqlValue::String("pierogi"), 4),
diff --git a/src/trace_processor/db/column/types.h b/src/trace_processor/db/column/types.h
index 803c6ce..f27daa7 100644
--- a/src/trace_processor/db/column/types.h
+++ b/src/trace_processor/db/column/types.h
@@ -37,6 +37,17 @@
                      // the crtiteria, a call to *Search is required.
 };
 
+// Result of calling Storage::UniqueSearch function.
+enum class UniqueSearchResult {
+  kMatch,            // The returned row matches the constraint.
+  kNoMatch,          // The returned row does not matches the constraint.
+  kNeedsFullSearch,  // UniqueSearch was unable to determine if a row meets
+                     // the crtiteria, a call to *Search is required. This
+                     // does not mean there >1 row necessarily, just that
+                     // UniqueSearch was unable to quickly identify a single
+                     // row.
+};
+
 // Result of calling Storage::ValidateSearchResult function.
 enum class SearchValidationResult {
   kOk,       // It makes sense to run search
diff --git a/src/trace_processor/db/column_storage_overlay.h b/src/trace_processor/db/column_storage_overlay.h
index 6ff16ca..c13c095 100644
--- a/src/trace_processor/db/column_storage_overlay.h
+++ b/src/trace_processor/db/column_storage_overlay.h
@@ -19,17 +19,13 @@
 
 #include <stdint.h>
 
-#include <memory>
 #include <optional>
 #include <vector>
 
-#include "perfetto/base/logging.h"
 #include "src/trace_processor/containers/bit_vector.h"
-#include "src/trace_processor/containers/bit_vector_iterators.h"
 #include "src/trace_processor/containers/row_map.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // Contains indices which can be used to lookup data in one or more
 // ColumnStorages.
@@ -136,66 +132,6 @@
   // state.
   void Clear() { *this = ColumnStorageOverlay(); }
 
-  // Filters the current ColumnStorageOverlay into the RowMap given by |out|
-  // based on the return value of |p(idx)|.
-  //
-  // Precondition: |out| should be sorted by the indices inside it (this is
-  // required to keep this method efficient). This is automatically true if the
-  // mode of |out| is Range or BitVector but needs to be enforced if the mode is
-  // IndexVector.
-  //
-  // Specifically, the setup for each of the variables is as follows:
-  //  this: contains the indices passed to p to filter.
-  //  out : contains indicies into |this| and will be filtered down to only
-  //        contain indicies where p returns true.
-  //  p   : takes an index given by |this| and returns whether the index should
-  //        be retained in |out|.
-  //
-  // Concretely, the algorithm being invoked looks like (but more efficient
-  // based on the mode of |this| and |out|):
-  // for (idx : out)
-  //   this_idx = (*this)[idx]
-  //   if (!p(this_idx))
-  //     out->Remove(idx)
-  template <typename Predicate>
-  void FilterInto(RowMap* out, Predicate p) const {
-    PERFETTO_DCHECK(size() >= out->size());
-
-    if (out->empty()) {
-      // If the output ColumnStorageOverlay is empty, we don't need to do
-      // anything.
-      return;
-    }
-
-    if (out->size() == 1) {
-      // If the output ColumnStorageOverlay has a single entry, just lookup
-      // that entry and see if we should keep it.
-      if (!p(Get(out->Get(0))))
-        out->Clear();
-      return;
-    }
-
-    // TODO(lalitm): investigate whether we should have another fast path for
-    // cases where |out| has only a few entries so we can scan |out| instead of
-    // scanning |this|.
-
-    // Ideally, we'd always just scan |out| and keep the indices in |this| which
-    // meet |p|. However, if |this| is a BitVector, we end up needing expensive
-    // |IndexOfNthSet| calls (as we need to convert the row to an index before
-    // passing it to |p|).
-    if (row_map_.IsBitVector()) {
-      FilterIntoScanSelfBv(out, p);
-      return;
-    }
-    auto ip = [this, p](uint32_t row) { return p(row_map_.Get(row)); };
-    out->Filter(ip);
-  }
-
-  template <typename Comparator = bool(uint32_t, uint32_t)>
-  void StableSort(std::vector<uint32_t>* out, Comparator c) const {
-    return row_map_.StableSort(out, c);
-  }
-
   // Returns the iterator over the rows in this ColumnStorageOverlay.
   Iterator IterateRows() const { return Iterator(row_map_.IterateRows()); }
 
@@ -204,66 +140,9 @@
  private:
   explicit ColumnStorageOverlay(RowMap rm) : row_map_(std::move(rm)) {}
 
-  // Filters the current ColumnStorageOverlay into |out| by performing a full
-  // scan on |row_map.bit_vector_|. See |FilterInto| for a full breakdown of the
-  // semantics of this function.
-
-  template <typename Predicate>
-  struct FilterIntoScanSelfBvVisitor {
-    void operator()(RowMap::Range out_r) {
-      BitVector bv(out_r.end, false);
-      for (auto out_it = bv.IterateAllBits(); bv_iter;
-           bv_iter.Next(), out_it.Next()) {
-        uint32_t ordinal = bv_iter.ordinal();
-        if (ordinal < out_r.start)
-          continue;
-        if (ordinal >= out_r.end)
-          break;
-
-        if (p(bv_iter.index())) {
-          out_it.Set();
-        }
-      }
-      *out = RowMap(std::move(bv));
-    }
-    void operator()(const BitVector& out_bv) {
-      auto out_it = out_bv.IterateAllBits();
-      for (; out_it; bv_iter.Next(), out_it.Next()) {
-        PERFETTO_DCHECK(bv_iter);
-        if (out_it.IsSet() && !p(bv_iter.index()))
-          out_it.Clear();
-      }
-    }
-    void operator()(std::vector<OutputIndex>& out_vec) {
-      PERFETTO_DCHECK(std::is_sorted(out_vec.begin(), out_vec.end()));
-      auto fn = [this](uint32_t i) {
-        while (bv_iter.ordinal() < i) {
-          bv_iter.Next();
-          PERFETTO_DCHECK(bv_iter);
-        }
-        PERFETTO_DCHECK(bv_iter.ordinal() == i);
-        return !p(bv_iter.index());
-      };
-      auto iv_it = std::remove_if(out_vec.begin(), out_vec.end(), fn);
-      out_vec.erase(iv_it, out_vec.end());
-    }
-    RowMap* out;
-    Predicate p;
-    internal::SetBitsIterator bv_iter;
-  };
-
-  template <typename Predicate>
-  void FilterIntoScanSelfBv(RowMap* out, Predicate p) const {
-    const BitVector* bv = std::get_if<BitVector>(&row_map_.data_);
-    auto it = bv->IterateSetBits();
-    std::visit(FilterIntoScanSelfBvVisitor<Predicate>{out, p, std::move(it)},
-               out->data_);
-  }
-
   RowMap row_map_;
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_DB_COLUMN_STORAGE_OVERLAY_H_
diff --git a/src/trace_processor/db/column_storage_overlay_benchmark.cc b/src/trace_processor/db/column_storage_overlay_benchmark.cc
deleted file mode 100644
index 49be7c7..0000000
--- a/src/trace_processor/db/column_storage_overlay_benchmark.cc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (C) 2022 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.
-
-#include <random>
-
-#include <benchmark/benchmark.h>
-
-#include "src/trace_processor/db/column_storage_overlay.h"
-
-using perfetto::trace_processor::BitVector;
-using perfetto::trace_processor::ColumnStorageOverlay;
-using perfetto::trace_processor::RowMap;
-
-namespace {
-
-static constexpr uint32_t kPoolSize = 100000;
-static constexpr uint32_t kSize = 123456;
-
-template <typename Container>
-Container CreateRange(uint32_t end) {
-  static constexpr uint32_t kRandomSeed = 32;
-  std::minstd_rand0 rnd_engine(kRandomSeed);
-
-  uint32_t start = rnd_engine() % end;
-  uint32_t size = rnd_engine() % (end - start);
-  return Container(start, start + size);
-}
-
-std::vector<uint32_t> CreateIndexVector(uint32_t size, uint32_t mod) {
-  static constexpr uint32_t kRandomSeed = 476;
-  std::minstd_rand0 rnd_engine(kRandomSeed);
-  std::vector<uint32_t> rows(size);
-  for (uint32_t i = 0; i < size; ++i) {
-    rows[i] = rnd_engine() % mod;
-  }
-  return rows;
-}
-
-BitVector CreateBitVector(uint32_t size) {
-  static constexpr uint32_t kRandomSeed = 42;
-  std::minstd_rand0 rnd_engine(kRandomSeed);
-  BitVector bv;
-  for (uint32_t i = 0; i < size; ++i) {
-    if (rnd_engine() % 2) {
-      bv.AppendTrue();
-    } else {
-      bv.AppendFalse();
-    }
-  }
-  return bv;
-}
-
-template <typename Factory>
-void BenchFilterInto(benchmark::State& state,
-                     ColumnStorageOverlay rm,
-                     Factory factory) {
-  auto pool_vec = CreateIndexVector(kPoolSize, kSize);
-
-  uint32_t pool_idx = 0;
-  for (auto _ : state) {
-    state.PauseTiming();
-    RowMap out = factory();
-    state.ResumeTiming();
-
-    auto fn = [&pool_vec, pool_idx](uint32_t row) {
-      return pool_vec[pool_idx] != 0 && (row % pool_vec[pool_idx]) != 0;
-    };
-    rm.FilterInto(&out, fn);
-    pool_idx = (pool_idx + 1) % kPoolSize;
-
-    benchmark::ClobberMemory();
-  }
-}
-
-}  // namespace
-
-static void BM_CSOFilterIntoRangeWithRange(benchmark::State& state) {
-  ColumnStorageOverlay overlay(CreateRange<ColumnStorageOverlay>(kSize));
-  uint32_t overlay_size = overlay.size();
-  BenchFilterInto(state, std::move(overlay), [overlay_size]() {
-    return CreateRange<RowMap>(overlay_size);
-  });
-}
-BENCHMARK(BM_CSOFilterIntoRangeWithRange);
-
-static void BM_CSOFilterIntoRangeWithBv(benchmark::State& state) {
-  ColumnStorageOverlay overlay(CreateRange<ColumnStorageOverlay>(kSize));
-  uint32_t overlay_size = overlay.size();
-  BenchFilterInto(state, std::move(overlay), [overlay_size]() {
-    return RowMap(CreateBitVector(overlay_size));
-  });
-}
-BENCHMARK(BM_CSOFilterIntoRangeWithBv);
-
-static void BM_CSOFilterIntoBvWithRange(benchmark::State& state) {
-  ColumnStorageOverlay overlay(CreateBitVector(kSize));
-  uint32_t overlay_size = overlay.size();
-  BenchFilterInto(state, std::move(overlay), [overlay_size]() {
-    return CreateRange<RowMap>(overlay_size);
-  });
-}
-BENCHMARK(BM_CSOFilterIntoBvWithRange);
-
-static void BM_CSOFilterIntoBvWithBv(benchmark::State& state) {
-  ColumnStorageOverlay overlay(CreateBitVector(kSize));
-  uint32_t overlay_size = overlay.size();
-  BenchFilterInto(state, std::move(overlay), [overlay_size]() {
-    return RowMap(CreateBitVector(overlay_size));
-  });
-}
-BENCHMARK(BM_CSOFilterIntoBvWithBv);
-
-static void BM_CSOFilterIntoIvWithRange(benchmark::State& state) {
-  ColumnStorageOverlay overlay(CreateIndexVector(kSize, kSize));
-  uint32_t overlay_size = overlay.size();
-  BenchFilterInto(state, std::move(overlay), [overlay_size]() {
-    return CreateRange<RowMap>(overlay_size);
-  });
-}
-BENCHMARK(BM_CSOFilterIntoIvWithRange);
-
-static void BM_CSOFilterIntoIvWithBv(benchmark::State& state) {
-  ColumnStorageOverlay overlay(CreateIndexVector(kSize, kSize));
-  uint32_t overlay_size = overlay.size();
-  BenchFilterInto(state, std::move(overlay), [overlay_size]() {
-    return RowMap(CreateBitVector(overlay_size));
-  });
-}
-BENCHMARK(BM_CSOFilterIntoIvWithBv);
diff --git a/src/trace_processor/db/column_storage_overlay_unittest.cc b/src/trace_processor/db/column_storage_overlay_unittest.cc
deleted file mode 100644
index 827153f..0000000
--- a/src/trace_processor/db/column_storage_overlay_unittest.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-#include "src/trace_processor/db/column_storage_overlay.h"
-
-#include <memory>
-
-#include "src/base/test/gtest_test_suite.h"
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-
-TEST(ColumnStorageOverlay, FilterIntoEmptyOutput) {
-  ColumnStorageOverlay rm(0, 10000);
-  RowMap filter(4, 4);
-  rm.FilterInto(&filter, [](uint32_t) -> bool {
-    ADD_FAILURE() << "Should not have called lambda";
-    return true;
-  });
-
-  ASSERT_EQ(filter.size(), 0u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoSingleRowTrue) {
-  ColumnStorageOverlay rm(100, 10000);
-  RowMap filter(6, 7);
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 106u; });
-
-  ASSERT_EQ(filter.size(), 1u);
-  ASSERT_EQ(filter.Get(0u), 6u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoSingleRowFalse) {
-  ColumnStorageOverlay rm(100, 10000);
-  RowMap filter(6, 7);
-  rm.FilterInto(&filter, [](uint32_t row) {
-    EXPECT_EQ(row, 106u);
-    return row != 106u;
-  });
-
-  ASSERT_EQ(filter.size(), 0u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoRangeWithRange) {
-  ColumnStorageOverlay rm(93, 157);
-  RowMap filter(4, 7);
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 97u || row == 98u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 4u);
-  ASSERT_EQ(filter.Get(1u), 5u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoOffsetRangeWithRange) {
-  ColumnStorageOverlay rm(100000, 100010);
-  RowMap filter(4, 7);
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 100004u; });
-
-  ASSERT_EQ(filter.size(), 1u);
-  ASSERT_EQ(filter.Get(0u), 4u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoLargeRangeWithRange) {
-  ColumnStorageOverlay rm(0, 100000);
-  RowMap filter(0, 100000);
-  rm.FilterInto(&filter, [](uint32_t row) { return row % 2 == 0; });
-
-  ASSERT_EQ(filter.size(), 100000u / 2);
-  for (uint32_t i = 0; i < 100000 / 2; ++i) {
-    ASSERT_EQ(filter.Get(i), i * 2);
-  }
-}
-
-TEST(ColumnStorageOverlay, FilterIntoBitVectorWithRange) {
-  ColumnStorageOverlay rm(
-      BitVector{true, false, false, true, false, true, false, true, true});
-  RowMap filter(1u, 5u);
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 3u || row == 7u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 1u);
-  ASSERT_EQ(filter.Get(1u), 3u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoIndexVectorWithRange) {
-  ColumnStorageOverlay rm(std::vector<uint32_t>{33, 2u, 45u, 7u, 8u, 9u});
-  RowMap filter(2, 5);
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 45u || row == 8u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 2u);
-  ASSERT_EQ(filter.Get(1u), 4u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoRangeWithBitVector) {
-  ColumnStorageOverlay rm(27, 31);
-  RowMap filter(BitVector{true, false, true, true});
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 29u || row == 30u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 2u);
-  ASSERT_EQ(filter.Get(1u), 3u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoBitVectorWithBitVector) {
-  ColumnStorageOverlay rm(BitVector{true, false, true, true, false, true});
-  RowMap filter(BitVector{true, true, false, true});
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 2u || row == 5u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 1u);
-  ASSERT_EQ(filter.Get(1u), 3u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoIndexVectorWithBitVector) {
-  ColumnStorageOverlay rm(std::vector<uint32_t>{0u, 2u, 3u, 5u});
-  RowMap filter(BitVector{true, true, false, true});
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 2u || row == 5u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 1u);
-  ASSERT_EQ(filter.Get(1u), 3u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoRangeWithIndexVector) {
-  ColumnStorageOverlay rm(27, 41);
-  RowMap filter(std::vector<uint32_t>{3u, 5u, 9u, 10u, 12u});
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 32u || row == 39u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 5u);
-  ASSERT_EQ(filter.Get(1u), 12u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoBitVectorWithIndexVector) {
-  ColumnStorageOverlay rm(
-      BitVector{false, true, false, true, true, false, true});
-  RowMap filter(std::vector<uint32_t>{1u, 2u, 3u});
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 3u || row == 4u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 1u);
-  ASSERT_EQ(filter.Get(1u), 2u);
-}
-
-TEST(ColumnStorageOverlay, FilterIntoIndexVectorWithIndexVector) {
-  ColumnStorageOverlay rm(std::vector<uint32_t>{33u, 2u, 45u, 7u, 8u, 9u});
-  RowMap filter(std::vector<uint32_t>{1u, 2u, 3u});
-  rm.FilterInto(&filter, [](uint32_t row) { return row == 2u || row == 7u; });
-
-  ASSERT_EQ(filter.size(), 2u);
-  ASSERT_EQ(filter.Get(0u), 1u);
-  ASSERT_EQ(filter.Get(1u), 3u);
-}
-
-}  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/query_executor.cc b/src/trace_processor/db/query_executor.cc
index bd15245..30214e4 100644
--- a/src/trace_processor/db/query_executor.cc
+++ b/src/trace_processor/db/query_executor.cc
@@ -57,6 +57,21 @@
     }
   }
 
+  // Given the chain a chance to return a single row which uniquely matches
+  // this constraint. This is a relatively frequent occurence e.g. id equality
+  // constraints so it is valuable to have an explicit fastpath.
+  uint32_t unique_row;
+  switch (chain.UniqueSearch(c.op, c.value, &unique_row)) {
+    case UniqueSearchResult::kMatch:
+      rm->IntersectExact(unique_row);
+      return;
+    case UniqueSearchResult::kNoMatch:
+      rm->Clear();
+      return;
+    case UniqueSearchResult::kNeedsFullSearch:
+      break;
+  }
+
   // Comparison of NULL with any operation apart from |IS_NULL| and
   // |IS_NOT_NULL| should return no rows.
   if (c.value.is_null() && c.op != FilterOp::kIsNull &&
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index fa79206..dd1e8a9 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -67,8 +67,6 @@
 constexpr std::string_view kHeapGraphObjectTable =
     "test/data/heap_pgraph_object_for_benchmarks_query.csv";
 
-enum DB { V1, V2 };
-
 std::vector<std::string> SplitCSVLine(const std::string& line) {
   std::vector<std::string> output;
   uint32_t start = 0;
@@ -231,7 +229,6 @@
 void BenchmarkSliceTableFilter(benchmark::State& state,
                                SliceTableForBenchmark& table,
                                std::initializer_list<Constraint> c) {
-  Table::kUseFilterV2 = state.range(0) == 1;
   for (auto _ : state) {
     benchmark::DoNotOptimize(table.table_.QueryToRowMap(c, {}));
   }
@@ -244,7 +241,6 @@
 void BenchmarkSliceTableSort(benchmark::State& state,
                              SliceTableForBenchmark& table,
                              std::initializer_list<Order> ob) {
-  Table::kUseSortV2 = state.range(0) == 1;
   for (auto _ : state) {
     benchmark::DoNotOptimize(table.table_.Sort(ob));
   }
@@ -258,7 +254,6 @@
     benchmark::State& state,
     ExpectedFrameTimelineTableForBenchmark& table,
     Constraint c) {
-  Table::kUseFilterV2 = state.range(0) == 1;
   for (auto _ : state) {
     benchmark::DoNotOptimize(table.table_.QueryToRowMap({c}, {}));
   }
@@ -271,7 +266,6 @@
 void BenchmarkFtraceEventTableFilter(benchmark::State& state,
                                      FtraceEventTableForBenchmark& table,
                                      std::initializer_list<Constraint> c) {
-  Table::kUseFilterV2 = state.range(0) == 1;
   for (auto _ : state) {
     benchmark::DoNotOptimize(table.table_.QueryToRowMap(c, {}));
   }
@@ -284,7 +278,6 @@
 void BenchmarkFtraceEventTableSort(benchmark::State& state,
                                    FtraceEventTableForBenchmark& table,
                                    std::initializer_list<Order> ob) {
-  Table::kUseSortV2 = state.range(0) == 1;
   for (auto _ : state) {
     benchmark::DoNotOptimize(table.table_.Sort(ob));
   }
@@ -299,7 +292,7 @@
   BenchmarkSliceTableFilter(state, table, {table.table_.track_id().eq(100)});
 }
 
-BENCHMARK(BM_QESliceTableTrackIdEq)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableTrackIdEq);
 
 void BM_QESliceTableParentIdIsNotNull(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -307,28 +300,28 @@
                             {table.table_.parent_id().is_not_null()});
 }
 
-BENCHMARK(BM_QESliceTableParentIdIsNotNull)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableParentIdIsNotNull);
 
 void BM_QESliceTableParentIdEq(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(state, table, {table.table_.parent_id().eq(88)});
 }
 
-BENCHMARK(BM_QESliceTableParentIdEq)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableParentIdEq);
 
 void BM_QESliceTableNameEq(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(state, table, {table.table_.name().eq("cheese")});
 }
 
-BENCHMARK(BM_QESliceTableNameEq)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableNameEq);
 
 void BM_QESliceTableNameGlobNoStars(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(state, table, {table.table_.name().glob("cheese")});
 }
 
-BENCHMARK(BM_QESliceTableNameGlobNoStars)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableNameGlobNoStars);
 
 void BM_QESliceTableNameGlob(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -336,7 +329,7 @@
                             {table.table_.name().glob("chee*se")});
 }
 
-BENCHMARK(BM_QESliceTableNameGlob)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableNameGlob);
 
 void BM_QESliceTableNameRegex(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -344,14 +337,14 @@
                             {table.table_.name().regex(".*Pool.*")});
 }
 
-BENCHMARK(BM_QESliceTableNameRegex)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableNameRegex);
 
 void BM_QESliceTableSorted(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(state, table, {table.table_.ts().gt(1000)});
 }
 
-BENCHMARK(BM_QESliceTableSorted)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableSorted);
 
 void BM_QEFilterWithSparseSelector(benchmark::State& state) {
   ExpectedFrameTimelineTableForBenchmark table(state);
@@ -359,28 +352,28 @@
                                     table.table_.track_id().eq(88));
 }
 
-BENCHMARK(BM_QEFilterWithSparseSelector)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFilterWithSparseSelector);
 
 void BM_QEFilterWithDenseSelector(benchmark::State& state) {
   FtraceEventTableForBenchmark table(state);
   BenchmarkFtraceEventTableFilter(state, table, {table.table_.cpu().eq(4)});
 }
 
-BENCHMARK(BM_QEFilterWithDenseSelector)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFilterWithDenseSelector);
 
 void BM_QESliceEventFilterId(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableFilter(state, table, {table.table_.id().eq(500)});
 }
 
-BENCHMARK(BM_QESliceEventFilterId)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceEventFilterId);
 
 void BM_QEFtraceEventFilterId(benchmark::State& state) {
   FtraceEventTableForBenchmark table(state);
   BenchmarkFtraceEventTableFilter(state, table, {table.table_.id().eq(500)});
 }
 
-BENCHMARK(BM_QEFtraceEventFilterId)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFtraceEventFilterId);
 
 void BM_QESliceTableTsAndTrackId(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -390,7 +383,7 @@
        table.table_.track_id().eq(100)});
 }
 
-BENCHMARK(BM_QESliceTableTsAndTrackId)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceTableTsAndTrackId);
 
 void BM_QEFilterOneElement(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -398,11 +391,9 @@
       state, table, {table.table_.id().eq(10), table.table_.dur().eq(100)});
 }
 
-BENCHMARK(BM_QEFilterOneElement)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFilterOneElement);
 
 void BM_QEFilterWithArrangement(benchmark::State& state) {
-  Table::kUseFilterV2 = state.range(0) == 1;
-
   SliceTableForBenchmark table(state);
   Order order{table.table_.dur().index_in_table(), false};
   Table slice_sorted_with_duration = table.table_.Sort({order});
@@ -418,11 +409,9 @@
           benchmark::Counter::kInvert);
 }
 
-BENCHMARK(BM_QEFilterWithArrangement)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFilterWithArrangement);
 
 void BM_QEDenseNullFilter(benchmark::State& state) {
-  Table::kUseFilterV2 = state.range(0) == 1;
-
   HeapGraphObjectTableForBenchmark table(state);
   Constraint c{table.table_.reference_set_id().index_in_table(), FilterOp::kGt,
                SqlValue::Long(1000)};
@@ -434,11 +423,9 @@
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
 }
-BENCHMARK(BM_QEDenseNullFilter)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEDenseNullFilter);
 
 void BM_QEDenseNullFilterIsNull(benchmark::State& state) {
-  Table::kUseFilterV2 = state.range(0) == 1;
-
   HeapGraphObjectTableForBenchmark table(state);
   Constraint c{table.table_.reference_set_id().index_in_table(),
                FilterOp::kIsNull, SqlValue()};
@@ -450,7 +437,7 @@
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
 }
-BENCHMARK(BM_QEDenseNullFilterIsNull)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEDenseNullFilterIsNull);
 
 void BM_QEIdColumnWithIntAsDouble(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -459,7 +446,7 @@
   BenchmarkSliceTableFilter(state, table, {c});
 }
 
-BENCHMARK(BM_QEIdColumnWithIntAsDouble)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEIdColumnWithIntAsDouble);
 
 void BM_QEIdColumnWithDouble(benchmark::State& state) {
   SliceTableForBenchmark table(state);
@@ -468,11 +455,9 @@
   BenchmarkSliceTableFilter(state, table, {c});
 }
 
-BENCHMARK(BM_QEIdColumnWithDouble)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEIdColumnWithDouble);
 
 void BM_QEFilterOrderedArrangement(benchmark::State& state) {
-  Table::kUseFilterV2 = state.range(0) == 1;
-
   SliceTableForBenchmark table(state);
   Order order{table.table_.dur().index_in_table(), false};
   Table slice_sorted_with_duration = table.table_.Sort({order});
@@ -488,29 +473,28 @@
           benchmark::Counter::kInvert);
 }
 
-BENCHMARK(BM_QEFilterOrderedArrangement)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFilterOrderedArrangement);
 
 void BM_QESliceSortNumericAsc(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableSort(state, table, {table.table_.track_id().ascending()});
 }
 
-BENCHMARK(BM_QESliceSortNumericAsc)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceSortNumericAsc);
 
 void BM_QESliceSortNullNumericAsc(benchmark::State& state) {
   SliceTableForBenchmark table(state);
   BenchmarkSliceTableSort(state, table, {table.table_.parent_id().ascending()});
 }
 
-BENCHMARK(BM_QESliceSortNullNumericAsc)->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QESliceSortNullNumericAsc);
 
 void BM_QEFtraceEventSortSelectorNumericAsc(benchmark::State& state) {
   FtraceEventTableForBenchmark table(state);
   BenchmarkFtraceEventTableSort(state, table, {table.table_.cpu().ascending()});
 }
 
-BENCHMARK(BM_QEFtraceEventSortSelectorNumericAsc)
-    ->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFtraceEventSortSelectorNumericAsc);
 
 void BM_QEFtraceEventSortSelectorNumericDesc(benchmark::State& state) {
   FtraceEventTableForBenchmark table(state);
@@ -518,8 +502,7 @@
                                 {table.table_.cpu().descending()});
 }
 
-BENCHMARK(BM_QEFtraceEventSortSelectorNumericDesc)
-    ->ArgsProduct({{DB::V1, DB::V2}});
+BENCHMARK(BM_QEFtraceEventSortSelectorNumericDesc);
 
 }  // namespace
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/query_executor_unittest.cc b/src/trace_processor/db/query_executor_unittest.cc
index a1a5ab3..f0d8f03 100644
--- a/src/trace_processor/db/query_executor_unittest.cc
+++ b/src/trace_processor/db/query_executor_unittest.cc
@@ -252,11 +252,11 @@
 }
 
 TEST(QueryExecutor, ArrangementOverlaySubsetInputRange) {
-  auto fake = column::FakeStorage::SearchSubset(5u, RowMap::Range(2u, 4u));
+  auto fake = column::FakeStorageChain::SearchSubset(5u, RowMap::Range(2u, 4u));
 
   std::vector<uint32_t> arrangement{4, 1, 2, 2, 3};
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(0u)};
   RowMap rm(1, 3);
@@ -266,11 +266,12 @@
 }
 
 TEST(QueryExecutor, ArrangementOverlaySubsetInputBitvector) {
-  auto fake = column::FakeStorage::SearchSubset(5u, BitVector({0, 0, 1, 1, 0}));
+  auto fake =
+      column::FakeStorageChain::SearchSubset(5u, BitVector({0, 0, 1, 1, 0}));
 
   std::vector<uint32_t> arrangement{4, 1, 2, 2, 3};
   ArrangementOverlay storage(&arrangement, Indices::State::kNonmonotonic);
-  auto chain = storage.MakeChain(fake->MakeChain());
+  auto chain = storage.MakeChain(std::move(fake));
 
   Constraint c{0, FilterOp::kGe, SqlValue::Long(0u)};
   RowMap rm(1, 3);
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index b5c7e54..7bedbb7 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/ref_counted.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
@@ -37,9 +38,6 @@
 
 namespace perfetto::trace_processor {
 
-bool Table::kUseFilterV2 = true;
-bool Table::kUseSortV2 = true;
-
 Table::Table(StringPool* pool,
              uint32_t row_count,
              std::vector<ColumnLegacy> columns,
@@ -90,9 +88,20 @@
 }
 
 RowMap Table::QueryToRowMap(const std::vector<Constraint>& cs,
-                            const std::vector<Order>& ob,
-                            RowMap::OptimizeFor optimize_for) const {
-  RowMap rm = FilterToRowMap(cs, optimize_for);
+                            const std::vector<Order>& ob) const {
+  // We need to delay creation of the chains to this point because of Chrome
+  // does not want the binary size overhead of including the chain
+  // implementations. As they also don't query tables (instead just iterating)
+  // over them), using a combination of dead code elimination and linker
+  // stripping all chain related code be removed.
+  //
+  // From rough benchmarking, this has a negligible impact on peformance as this
+  // branch is almost never taken.
+  if (PERFETTO_UNLIKELY(chains_.size() != columns_.size())) {
+    CreateChains();
+  }
+
+  RowMap rm = QueryExecutor::FilterLegacy(this, cs);
   if (ob.empty())
     return rm;
 
@@ -112,43 +121,7 @@
     PERFETTO_DCHECK(ob.front().desc);
     std::reverse(idx.begin(), idx.end());
   } else {
-    // As our data is columnar, it's always more efficient to sort one column
-    // at a time rather than try and sort lexiographically all at once.
-    // To preserve correctness, we need to stably sort the index vector once
-    // for each order by in *reverse* order. Reverse order is important as it
-    // preserves the lexiographical property.
-    //
-    // For example, suppose we have the following:
-    // Table {
-    //   Column x;
-    //   Column y
-    //   Column z;
-    // }
-    //
-    // Then, to sort "y asc, x desc", we could do one of two things:
-    //  1) sort the index vector all at once and on each index, we compare
-    //     y then z. This is slow as the data is columnar and we need to
-    //     repeatedly branch inside each column.
-    //  2) we can stably sort first on x desc and then sort on y asc. This will
-    //     first put all the x in the correct order such that when we sort on
-    //     y asc, we will have the correct order of x where y is the same (since
-    //     the sort is stable).
-    //
-    // TODO(lalitm): it is possible that we could sort the last constraint (i.e.
-    // the first constraint in the below loop) in a non-stable way. However,
-    // this is more subtle than it appears as we would then need special
-    // handling where there are order bys on a column which is already sorted
-    // (e.g. ts, id). Investigate whether the performance gains from this are
-    // worthwhile. This also needs changes to the constraint modification logic
-    // in DbSqliteTable which currently eliminates constraints on sorted
-    // columns.
-    if (Table::kUseSortV2) {
-      QueryExecutor::SortLegacy(this, ob, idx);
-    } else {
-      for (auto it = ob.rbegin(); it != ob.rend(); ++it) {
-        columns_[it->col_idx].StableSort(it->desc, &idx);
-      }
-    }
+    QueryExecutor::SortLegacy(this, ob, idx);
   }
   return RowMap(std::move(idx));
 }
@@ -211,19 +184,21 @@
   storage_layers_ = std::move(storage_layers);
   null_layers_ = std::move(null_layers);
   overlay_layers_ = std::move(overlay_layers);
+}
 
+void Table::CreateChains() const {
+  chains_.resize(columns_.size());
   for (uint32_t i = 0; i < columns_.size(); ++i) {
-    auto chain = storage_layers_[i]->MakeChain();
+    chains_[i] = storage_layers_[i]->MakeChain();
     if (const auto& null_overlay = null_layers_[i]; null_overlay.get()) {
-      chain = null_overlay->MakeChain(std::move(chain));
+      chains_[i] = null_overlay->MakeChain(std::move(chains_[i]));
     }
     const auto& oly_idx = columns_[i].overlay_index();
     if (const auto& overlay = overlay_layers_[oly_idx]; overlay.get()) {
-      chain = overlay->MakeChain(
-          std::move(chain),
+      chains_[i] = overlay->MakeChain(
+          std::move(chains_[i]),
           column::DataLayer::ChainCreationArgs{columns_[i].IsSorted()});
     }
-    chains_.emplace_back(std::move(chain));
   }
 }
 
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index 4ed56c8..eab9f92 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -118,9 +118,6 @@
     std::vector<Column> columns;
   };
 
-  static bool kUseFilterV2;
-  static bool kUseSortV2;
-
   virtual ~Table();
 
   // We explicitly define the move constructor here because we need to update
@@ -135,10 +132,8 @@
 
   // Filters and sorts the tables with the arguments specified, returning the
   // result as a RowMap.
-  RowMap QueryToRowMap(
-      const std::vector<Constraint>&,
-      const std::vector<Order>&,
-      RowMap::OptimizeFor = RowMap::OptimizeFor::kMemory) const;
+  RowMap QueryToRowMap(const std::vector<Constraint>&,
+                       const std::vector<Order>&) const;
 
   // Applies the RowMap |rm| onto this table and returns an iterator over the
   // resulting rows.
@@ -202,25 +197,7 @@
  private:
   friend class ColumnLegacy;
 
-  PERFETTO_ALWAYS_INLINE RowMap FilterToRowMap(
-      const std::vector<Constraint>& cs,
-      RowMap::OptimizeFor optimize_for = RowMap::OptimizeFor::kMemory) const {
-    if (cs.empty()) {
-      return {0, row_count_, optimize_for};
-    }
-
-    if (kUseFilterV2) {
-      if (optimize_for == RowMap::OptimizeFor::kMemory) {
-        return QueryExecutor::FilterLegacy(this, cs);
-      }
-      return RowMap(QueryExecutor::FilterLegacy(this, cs).TakeAsIndexVector());
-    }
-    RowMap rm(0, row_count_, optimize_for);
-    for (const Constraint& c : cs) {
-      columns_[c.col_idx].FilterInto(c.op, c.value, &rm);
-    }
-    return rm;
-  }
+  void CreateChains() const;
 
   Table CopyExceptOverlays() const;
 
@@ -232,7 +209,7 @@
   std::vector<RefPtr<column::DataLayer>> storage_layers_;
   std::vector<RefPtr<column::DataLayer>> null_layers_;
   std::vector<RefPtr<column::DataLayer>> overlay_layers_;
-  std::vector<std::unique_ptr<column::DataLayerChain>> chains_;
+  mutable std::vector<std::unique_ptr<column::DataLayerChain>> chains_;
 };
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 823edd8..dac6e41 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -44,8 +44,6 @@
     "slice_tracker.h",
     "slice_translation_table.cc",
     "slice_translation_table.h",
-    "stack_profile_tracker.cc",
-    "stack_profile_tracker.h",
     "system_info_tracker.cc",
     "system_info_tracker.h",
     "trace_parser.cc",
@@ -68,10 +66,7 @@
     "../../../base",
     "../../db:minimal",
     "../../storage",
-    "../../tables:tables",
     "../../types",
-    "../../util:profiler_util",
-    "../../util:stack_traces_util",
     "../fuchsia:fuchsia_record",
     "../systrace:systrace_line",
   ]
diff --git a/src/trace_processor/importers/common/stack_profile_tracker.cc b/src/trace_processor/importers/common/stack_profile_tracker.cc
deleted file mode 100644
index ad57523..0000000
--- a/src/trace_processor/importers/common/stack_profile_tracker.cc
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
-
-#include "perfetto/ext/base/string_utils.h"
-#include "perfetto/ext/base/string_view.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables_py.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-#include "src/trace_processor/util/profiler_util.h"
-#include "src/trace_processor/util/stack_traces_util.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-namespace {
-std::string CleanBuildId(base::StringView build_id) {
-  if (build_id.empty()) {
-    return build_id.ToStdString();
-  }
-  // If the build_id is 33 characters long, we assume it's a Breakpad debug
-  // identifier which is already in Hex and doesn't need conversion.
-  // TODO(b/148109467): Remove workaround once all active Chrome versions
-  // write raw bytes instead of a string as build_id.
-  if (util::IsHexModuleId(build_id)) {
-    return build_id.ToStdString();
-  }
-
-  return base::ToHex(build_id.data(), build_id.size());
-}
-
-}  // namespace
-
-std::vector<FrameId> StackProfileTracker::JavaFramesForName(
-    NameInPackage name) const {
-  if (const auto* frames = java_frames_for_name_.Find(name); frames) {
-    return *frames;
-  }
-  return {};
-}
-
-std::vector<MappingId> StackProfileTracker::FindMappingRow(
-    StringId name,
-    StringId build_id) const {
-  if (const auto* mappings =
-          mappings_by_name_and_build_id_.Find(std::make_pair(name, build_id));
-      mappings) {
-    return *mappings;
-  }
-  return {};
-}
-
-std::vector<FrameId> StackProfileTracker::FindFrameIds(MappingId mapping_id,
-                                                       uint64_t rel_pc) const {
-  if (const auto* frames =
-          frame_by_mapping_and_rel_pc_.Find(std::make_pair(mapping_id, rel_pc));
-      frames) {
-    return *frames;
-  }
-  return {};
-}
-
-MappingId StackProfileTracker::InternMapping(
-    const CreateMappingParams& params) {
-  tables::StackProfileMappingTable::Row row;
-  row.build_id = InternBuildId(params.build_id);
-  row.exact_offset = static_cast<int64_t>(params.exact_offset);
-  row.start_offset = static_cast<int64_t>(params.start_offset);
-  row.start = static_cast<int64_t>(params.start);
-  row.end = static_cast<int64_t>(params.end);
-  row.load_bias = static_cast<int64_t>(params.load_bias);
-  row.name = context_->storage->InternString(params.name);
-
-  if (MappingId* id = mapping_unique_row_index_.Find(row); id) {
-    return *id;
-  }
-
-  MappingId mapping_id =
-      context_->storage->mutable_stack_profile_mapping_table()->Insert(row).id;
-  mapping_unique_row_index_.Insert(row, mapping_id);
-  mappings_by_name_and_build_id_[{row.name, row.build_id}].push_back(
-      mapping_id);
-  return mapping_id;
-}
-
-CallsiteId StackProfileTracker::InternCallsite(
-    std::optional<CallsiteId> parent_callsite_id,
-    FrameId frame_id,
-    uint32_t depth) {
-  tables::StackProfileCallsiteTable::Row row{depth, parent_callsite_id,
-                                             frame_id};
-  if (CallsiteId* id = callsite_unique_row_index_.Find(row); id) {
-    return *id;
-  }
-
-  CallsiteId callsite_id =
-      context_->storage->mutable_stack_profile_callsite_table()->Insert(row).id;
-  callsite_unique_row_index_.Insert(row, callsite_id);
-  return callsite_id;
-}
-
-FrameId StackProfileTracker::InternFrame(MappingId mapping_id,
-                                         uint64_t rel_pc,
-                                         base::StringView function_name) {
-  tables::StackProfileFrameTable::Row row;
-  row.mapping = mapping_id;
-  row.rel_pc = static_cast<int64_t>(rel_pc);
-  row.name = context_->storage->InternString(function_name);
-
-  if (FrameId* id = frame_unique_row_index_.Find(row); id) {
-    return *id;
-  }
-
-  FrameId frame_id =
-      context_->storage->mutable_stack_profile_frame_table()->Insert(row).id;
-  frame_unique_row_index_.Insert(row, frame_id);
-  frame_by_mapping_and_rel_pc_[{mapping_id, rel_pc}].push_back(frame_id);
-
-  if (function_name.find('.') != base::StringView::npos) {
-    // Java frames always contain a '.'
-    base::StringView mapping_name = context_->storage->GetString(
-        context_->storage->stack_profile_mapping_table()
-            .FindById(mapping_id)
-            ->name());
-    std::optional<std::string> package =
-        PackageFromLocation(context_->storage.get(), mapping_name);
-    if (package) {
-      NameInPackage nip{row.name, context_->storage->InternString(
-                                      base::StringView(*package))};
-      java_frames_for_name_[nip].push_back(frame_id);
-    } else if (mapping_name.find("/memfd:") == 0) {
-      NameInPackage nip{row.name, context_->storage->InternString("memfd")};
-      java_frames_for_name_[nip].push_back(frame_id);
-    }
-  }
-
-  return frame_id;
-}
-
-StringId StackProfileTracker::InternBuildId(base::StringView build_id) {
-  return context_->storage->InternString(
-      base::StringView(CleanBuildId(build_id)));
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/importers/common/stack_profile_tracker.h b/src/trace_processor/importers/common/stack_profile_tracker.h
deleted file mode 100644
index a1067b8..0000000
--- a/src/trace_processor/importers/common/stack_profile_tracker.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2019 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 SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_STACK_PROFILE_TRACKER_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_STACK_PROFILE_TRACKER_H_
-
-#include <cstdint>
-#include <optional>
-#include <tuple>
-#include <utility>
-#include <vector>
-
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "perfetto/ext/base/hash.h"
-#include "perfetto/ext/base/string_view.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-struct NameInPackage {
-  StringId name;
-  StringId package;
-
-  bool operator==(const NameInPackage& b) const {
-    return std::tie(name, package) == std::tie(b.name, b.package);
-  }
-
-  struct Hasher {
-    size_t operator()(const NameInPackage& o) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(o.name.raw_id(), o.package.raw_id()));
-    }
-  };
-};
-
-class TraceProcessorContext;
-
-class StackProfileTracker {
- public:
-  struct CreateMappingParams {
-    base::StringView build_id;
-    uint64_t exact_offset;
-    uint64_t start_offset;
-    uint64_t start;
-    uint64_t end;
-    uint64_t load_bias;
-    base::StringView name;
-  };
-
-  explicit StackProfileTracker(TraceProcessorContext* context)
-      : context_(context) {}
-
-  std::vector<FrameId> JavaFramesForName(NameInPackage name) const;
-  std::vector<MappingId> FindMappingRow(StringId name, StringId build_id) const;
-  std::vector<FrameId> FindFrameIds(MappingId mapping_id,
-                                    uint64_t rel_pc) const;
-
-  MappingId InternMapping(const CreateMappingParams& params);
-  CallsiteId InternCallsite(std::optional<CallsiteId> parent_callsite_id,
-                            FrameId frame_id,
-                            uint32_t depth);
-  FrameId InternFrame(MappingId mapping_id,
-                      uint64_t rel_pc,
-                      base::StringView function_name);
-
- private:
-  StringId InternBuildId(base::StringView build_id);
-
-  TraceProcessorContext* const context_;
-  base::FlatHashMap<tables::StackProfileMappingTable::Row, MappingId>
-      mapping_unique_row_index_;
-  base::FlatHashMap<tables::StackProfileCallsiteTable::Row, CallsiteId>
-      callsite_unique_row_index_;
-  base::FlatHashMap<tables::StackProfileFrameTable::Row, FrameId>
-      frame_unique_row_index_;
-
-  struct MappingHasher {
-    size_t operator()(const std::pair<StringId, StringId>& o) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(o.first.raw_id(), o.second.raw_id()));
-    }
-  };
-  base::FlatHashMap<std::pair<StringId, StringId>,
-                    std::vector<MappingId>,
-                    MappingHasher>
-      mappings_by_name_and_build_id_;
-
-  struct FrameHasher {
-    size_t operator()(const std::pair<MappingId, uint64_t>& o) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(o.first.value, o.second));
-    }
-  };
-  base::FlatHashMap<std::pair<MappingId, uint64_t>,
-                    std::vector<FrameId>,
-                    FrameHasher>
-      frame_by_mapping_and_rel_pc_;
-
-  base::FlatHashMap<NameInPackage, std::vector<FrameId>, NameInPackage::Hasher>
-      java_frames_for_name_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_STACK_PROFILE_TRACKER_H_
diff --git a/src/trace_processor/importers/ftrace/BUILD.gn b/src/trace_processor/importers/ftrace/BUILD.gn
index c618978..8065a41 100644
--- a/src/trace_processor/importers/ftrace/BUILD.gn
+++ b/src/trace_processor/importers/ftrace/BUILD.gn
@@ -68,7 +68,6 @@
     "../../../../protos/perfetto/trace:zero",
     "../../../../protos/perfetto/trace/ftrace:zero",
     "../../../../protos/perfetto/trace/interned_data:zero",
-    "../../../../protos/perfetto/trace/profiling:zero",
     "../../../protozero",
     "../../sorter",
     "../../storage",
@@ -77,7 +76,6 @@
     "../common:parser_types",
     "../i2c:full",
     "../proto:minimal",
-    "../proto:packet_sequence_state_generation_hdr",
     "../syscalls:full",
     "../systrace:systrace_parser",
   ]
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 951c03d..cfed1d1 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -32,7 +32,7 @@
 #include "src/trace_processor/importers/ftrace/v4l2_tracker.h"
 #include "src/trace_processor/importers/ftrace/virtio_video_tracker.h"
 #include "src/trace_processor/importers/i2c/i2c_tracker.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/syscalls/syscall_tracker.h"
 #include "src/trace_processor/importers/systrace/systrace_parser.h"
 #include "src/trace_processor/storage/stats.h"
@@ -85,7 +85,6 @@
 #include "protos/perfetto/trace/ftrace/vmscan.pbzero.h"
 #include "protos/perfetto/trace/ftrace/workqueue.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index 8e72751..429cfce 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -120,6 +120,13 @@
         cpu, kMaxCpuCount);
   }
 
+  if (PERFETTO_UNLIKELY(decoder.lost_events())) {
+    // If set, it means that the kernel overwrote an unspecified number of
+    // events since our last read from the per-cpu buffer.
+    context_->storage->SetIndexedStats(stats::ftrace_cpu_has_data_loss,
+                                       static_cast<int>(cpu), 1);
+  }
+
   ClockTracker::ClockId clock_id;
   switch (decoder.ftrace_clock()) {
     case FtraceClock::FTRACE_CLOCK_UNSPECIFIED:
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
index d644742..e61a137 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
@@ -29,12 +29,12 @@
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/importers/proto/additional_modules.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/storage/metadata.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -236,7 +236,8 @@
     context_.track_tracker.reset(new TrackTracker(&context_));
     context_.global_args_tracker.reset(
         new GlobalArgsTracker(context_.storage.get()));
-    context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
+    context_.global_stack_profile_tracker.reset(
+        new GlobalStackProfileTracker());
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
     context_.metadata_tracker.reset(
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 2eaa5fe..a901ae1 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -26,6 +26,8 @@
     "chrome_system_probes_parser.h",
     "default_modules.cc",
     "default_modules.h",
+    "heap_profile_tracker.cc",
+    "heap_profile_tracker.h",
     "memory_tracker_snapshot_module.cc",
     "memory_tracker_snapshot_module.h",
     "memory_tracker_snapshot_parser.cc",
@@ -42,10 +44,10 @@
     "perf_sample_tracker.h",
     "profile_module.cc",
     "profile_module.h",
-    "profile_packet_sequence_state.cc",
-    "profile_packet_sequence_state.h",
     "profile_packet_utils.cc",
     "profile_packet_utils.h",
+    "profiler_util.cc",
+    "profiler_util.h",
     "proto_incremental_state.h",
     "proto_trace_parser.cc",
     "proto_trace_parser.h",
@@ -53,8 +55,8 @@
     "proto_trace_reader.h",
     "proto_trace_tokenizer.cc",
     "proto_trace_tokenizer.h",
-    "stack_profile_sequence_state.cc",
-    "stack_profile_sequence_state.h",
+    "stack_profile_tracker.cc",
+    "stack_profile_tracker.h",
     "track_event_module.cc",
     "track_event_module.h",
     "track_event_parser.cc",
@@ -92,7 +94,6 @@
     "../../tables",
     "../../types",
     "../../util:gzip",
-    "../../util:profiler_util",
     "../../util:stack_traces_util",
     "../common",
     "../common:parser_types",
@@ -154,7 +155,6 @@
     ":gen_cc_statsd_atoms_descriptor",
     ":gen_cc_trace_descriptor",
     ":minimal",
-    ":packet_sequence_state_generation_hdr",
     "../../../../gn:default_deps",
     "../../../../include/perfetto/ext/traced:sys_stats_counters",
     "../../../../protos/perfetto/common:zero",
@@ -178,10 +178,8 @@
     "../../tables",
     "../../types",
     "../../util:descriptors",
-    "../../util:profiler_util",
     "../../util:proto_profiler",
     "../../util:proto_to_args_parser",
-    "../../util:stack_traces_util",
     "../common",
     "../common:parser_types",
     "../etw:full",
@@ -245,9 +243,9 @@
   sources = [
     "active_chrome_processes_tracker_unittest.cc",
     "heap_graph_tracker_unittest.cc",
+    "heap_profile_tracker_unittest.cc",
     "network_trace_module_unittest.cc",
     "perf_sample_tracker_unittest.cc",
-    "profile_packet_sequence_state_unittest.cc",
     "proto_trace_parser_unittest.cc",
     "string_encoding_utils_unittests.cc",
   ]
@@ -275,8 +273,6 @@
     "../../storage",
     "../../types",
     "../../util:descriptors",
-    "../../util:profiler_util",
-    "../../util:stack_traces_util",
     "../common",
     "../ftrace:full",
   ]
diff --git a/src/trace_processor/importers/proto/heap_graph_module.cc b/src/trace_processor/importers/proto/heap_graph_module.cc
index 93fd468..cc1d7ad 100644
--- a/src/trace_processor/importers/proto/heap_graph_module.cc
+++ b/src/trace_processor/importers/proto/heap_graph_module.cc
@@ -19,9 +19,9 @@
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
-#include "src/trace_processor/util/profiler_util.h"
 
 #include "protos/perfetto/trace/profiling/deobfuscation.pbzero.h"
 #include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 2e95889..fcbd054 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -22,8 +22,8 @@
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
-#include "src/trace_processor/util/profiler_util.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
index ebc2a5e..70c7398 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
@@ -18,7 +18,7 @@
 
 #include "perfetto/base/logging.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/util/profiler_util.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/importers/proto/heap_profile_tracker.cc b/src/trace_processor/importers/proto/heap_profile_tracker.cc
new file mode 100644
index 0000000..ea20108
--- /dev/null
+++ b/src/trace_processor/importers/proto/heap_profile_tracker.cc
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "src/trace_processor/importers/proto/heap_profile_tracker.h"
+
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+
+#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+HeapProfileTracker::HeapProfileTracker(TraceProcessorContext* context)
+    : context_(context),
+      empty_(context_->storage->InternString({"", 0})),
+      art_heap_(context_->storage->InternString("com.android.art")) {}
+
+HeapProfileTracker::~HeapProfileTracker() = default;
+
+void HeapProfileTracker::SetProfilePacketIndex(uint32_t seq_id,
+                                               uint64_t index) {
+  SequenceState& sequence_state = sequence_state_[seq_id];
+  bool dropped_packet = false;
+  // heapprofd starts counting at index = 0.
+  if (!sequence_state.prev_index && index != 0) {
+    dropped_packet = true;
+  }
+
+  if (sequence_state.prev_index && *sequence_state.prev_index + 1 != index) {
+    dropped_packet = true;
+  }
+
+  if (dropped_packet) {
+    if (sequence_state.prev_index) {
+      PERFETTO_ELOG("Missing packets between %" PRIu64 " and %" PRIu64,
+                    *sequence_state.prev_index, index);
+    } else {
+      PERFETTO_ELOG("Invalid first packet index %" PRIu64 " (!= 0)", index);
+    }
+
+    context_->storage->IncrementStats(stats::heapprofd_missing_packet);
+  }
+  sequence_state.prev_index = index;
+}
+
+void HeapProfileTracker::AddAllocation(
+    uint32_t seq_id,
+    SequenceStackProfileTracker* sequence_stack_profile_tracker,
+    const SourceAllocation& alloc,
+    const SequenceStackProfileTracker::InternLookup* intern_lookup) {
+  SequenceState& sequence_state = sequence_state_[seq_id];
+
+  auto opt_callstack_id = sequence_stack_profile_tracker->FindOrInsertCallstack(
+      alloc.callstack_id, intern_lookup);
+  if (!opt_callstack_id)
+    return;
+
+  CallsiteId callstack_id = *opt_callstack_id;
+
+  UniquePid upid = context_->process_tracker->GetOrCreateProcess(
+      static_cast<uint32_t>(alloc.pid));
+
+  tables::HeapProfileAllocationTable::Row alloc_row{
+      alloc.timestamp,
+      upid,
+      alloc.heap_name,
+      callstack_id,
+      static_cast<int64_t>(alloc.alloc_count),
+      static_cast<int64_t>(alloc.self_allocated)};
+
+  tables::HeapProfileAllocationTable::Row free_row{
+      alloc.timestamp,
+      upid,
+      alloc.heap_name,
+      callstack_id,
+      -static_cast<int64_t>(alloc.free_count),
+      -static_cast<int64_t>(alloc.self_freed)};
+
+  auto prev_alloc_it = sequence_state.prev_alloc.find({upid, callstack_id});
+  if (prev_alloc_it == sequence_state.prev_alloc.end()) {
+    std::tie(prev_alloc_it, std::ignore) = sequence_state.prev_alloc.emplace(
+        std::make_pair(upid, callstack_id),
+        tables::HeapProfileAllocationTable::Row{});
+  }
+
+  tables::HeapProfileAllocationTable::Row& prev_alloc = prev_alloc_it->second;
+
+  auto prev_free_it = sequence_state.prev_free.find({upid, callstack_id});
+  if (prev_free_it == sequence_state.prev_free.end()) {
+    std::tie(prev_free_it, std::ignore) = sequence_state.prev_free.emplace(
+        std::make_pair(upid, callstack_id),
+        tables::HeapProfileAllocationTable::Row{});
+  }
+
+  tables::HeapProfileAllocationTable::Row& prev_free = prev_free_it->second;
+
+  std::set<CallsiteId>& callstacks_for_source_callstack_id =
+      sequence_state.seen_callstacks[SourceAllocationIndex{
+          upid, alloc.callstack_id, alloc.heap_name}];
+  bool new_callstack;
+  std::tie(std::ignore, new_callstack) =
+      callstacks_for_source_callstack_id.emplace(callstack_id);
+
+  if (new_callstack) {
+    sequence_state.alloc_correction[alloc.callstack_id] = prev_alloc;
+    sequence_state.free_correction[alloc.callstack_id] = prev_free;
+  }
+
+  auto alloc_correction_it =
+      sequence_state.alloc_correction.find(alloc.callstack_id);
+  if (alloc_correction_it != sequence_state.alloc_correction.end()) {
+    const auto& alloc_correction = alloc_correction_it->second;
+    alloc_row.count += alloc_correction.count;
+    alloc_row.size += alloc_correction.size;
+  }
+
+  auto free_correction_it =
+      sequence_state.free_correction.find(alloc.callstack_id);
+  if (free_correction_it != sequence_state.free_correction.end()) {
+    const auto& free_correction = free_correction_it->second;
+    free_row.count += free_correction.count;
+    free_row.size += free_correction.size;
+  }
+
+  tables::HeapProfileAllocationTable::Row alloc_delta = alloc_row;
+  tables::HeapProfileAllocationTable::Row free_delta = free_row;
+
+  alloc_delta.count -= prev_alloc.count;
+  alloc_delta.size -= prev_alloc.size;
+
+  free_delta.count -= prev_free.count;
+  free_delta.size -= prev_free.size;
+
+  if (alloc_delta.count < 0 || alloc_delta.size < 0 || free_delta.count > 0 ||
+      free_delta.size > 0) {
+    PERFETTO_DLOG("Non-monotonous allocation.");
+    context_->storage->IncrementIndexedStats(stats::heapprofd_malformed_packet,
+                                             static_cast<int>(upid));
+    return;
+  }
+
+  // Dump at max profiles do not have .count set.
+  if (alloc_delta.count || alloc_delta.size) {
+    context_->storage->mutable_heap_profile_allocation_table()->Insert(
+        alloc_delta);
+  }
+
+  // ART only reports allocations, and not frees. This throws off our logic
+  // that assumes that if a new object was allocated with the same address,
+  // the old one has to have been freed in the meantime.
+  // See HeapTracker::RecordMalloc in bookkeeping.cc.
+  if (alloc.heap_name != art_heap_ && (free_delta.count || free_delta.size)) {
+    context_->storage->mutable_heap_profile_allocation_table()->Insert(
+        free_delta);
+  }
+
+  prev_alloc = alloc_row;
+  prev_free = free_row;
+}
+
+void HeapProfileTracker::StoreAllocation(uint32_t seq_id,
+                                         SourceAllocation alloc) {
+  SequenceState& sequence_state = sequence_state_[seq_id];
+  sequence_state.pending_allocs.emplace_back(std::move(alloc));
+}
+
+void HeapProfileTracker::CommitAllocations(
+    uint32_t seq_id,
+    SequenceStackProfileTracker* sequence_stack_profile_tracker,
+    const SequenceStackProfileTracker::InternLookup* intern_lookup) {
+  SequenceState& sequence_state = sequence_state_[seq_id];
+  for (const auto& p : sequence_state.pending_allocs)
+    AddAllocation(seq_id, sequence_stack_profile_tracker, p, intern_lookup);
+  sequence_state.pending_allocs.clear();
+}
+
+void HeapProfileTracker::FinalizeProfile(
+    uint32_t seq_id,
+    SequenceStackProfileTracker* sequence_stack_profile_tracker,
+    const SequenceStackProfileTracker::InternLookup* intern_lookup) {
+  CommitAllocations(seq_id, sequence_stack_profile_tracker, intern_lookup);
+  sequence_stack_profile_tracker->ClearIndices();
+}
+
+void HeapProfileTracker::NotifyEndOfFile() {
+  for (const auto& key_and_sequence_state : sequence_state_) {
+    const SequenceState& sequence_state = key_and_sequence_state.second;
+    if (!sequence_state.pending_allocs.empty()) {
+      context_->storage->IncrementStats(stats::heapprofd_non_finalized_profile);
+    }
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/heap_profile_tracker.h b/src/trace_processor/importers/proto/heap_profile_tracker.h
new file mode 100644
index 0000000..5ed5394
--- /dev/null
+++ b/src/trace_processor/importers/proto/heap_profile_tracker.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 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 SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_HEAP_PROFILE_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_HEAP_PROFILE_TRACKER_H_
+
+#include <optional>
+#include <set>
+#include <unordered_map>
+
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+class TraceProcessorContext;
+
+class HeapProfileTracker {
+ public:
+  struct SourceAllocation {
+    uint64_t pid = 0;
+    // This is int64_t, because we get this from the TraceSorter which also
+    // converts this for us.
+    int64_t timestamp = 0;
+    StringPool::Id heap_name;
+    SequenceStackProfileTracker::SourceCallstackId callstack_id = 0;
+    uint64_t self_allocated = 0;
+    uint64_t self_freed = 0;
+    uint64_t alloc_count = 0;
+    uint64_t free_count = 0;
+  };
+
+  void SetProfilePacketIndex(uint32_t seq_id, uint64_t id);
+
+  explicit HeapProfileTracker(TraceProcessorContext* context);
+
+  void StoreAllocation(uint32_t seq_id, SourceAllocation);
+
+  // Call after the last profile packet of a dump to commit the allocations
+  // that had been stored using StoreAllocation and clear internal indices
+  // for that dump.
+  void FinalizeProfile(
+      uint32_t seq_id,
+      SequenceStackProfileTracker* sequence_stack_profile_tracker,
+      const SequenceStackProfileTracker::InternLookup* lookup);
+
+  // Only commit the allocations that had been stored using StoreAllocations.
+  // This is only needed in tests, use FinalizeProfile instead.
+  void CommitAllocations(
+      uint32_t seq_id,
+      SequenceStackProfileTracker* sequence_stack_profile_tracker,
+      const SequenceStackProfileTracker::InternLookup* lookup);
+
+  void NotifyEndOfFile();
+
+  ~HeapProfileTracker();
+
+ private:
+  void AddAllocation(
+      uint32_t seq_id,
+      SequenceStackProfileTracker* sequence_stack_profile_tracker,
+      const SourceAllocation&,
+      const SequenceStackProfileTracker::InternLookup* intern_lookup = nullptr);
+  struct SourceAllocationIndex {
+    UniquePid upid;
+    SequenceStackProfileTracker::SourceCallstackId src_callstack_id;
+    StringPool::Id heap_name;
+    bool operator<(const SourceAllocationIndex& o) const {
+      return std::tie(upid, src_callstack_id, heap_name) <
+             std::tie(o.upid, o.src_callstack_id, o.heap_name);
+    }
+  };
+  struct SequenceState {
+    std::vector<SourceAllocation> pending_allocs;
+
+    std::unordered_map<std::pair<UniquePid, CallsiteId>,
+                       tables::HeapProfileAllocationTable::Row>
+        prev_alloc;
+    std::unordered_map<std::pair<UniquePid, CallsiteId>,
+                       tables::HeapProfileAllocationTable::Row>
+        prev_free;
+
+    // For continuous dumps, we only store the delta in the data-base. To do
+    // this, we subtract the previous dump's value. Sometimes, we should not
+    // do that subtraction, because heapprofd garbage collects stacks that
+    // have no unfreed allocations. If the application then allocations again
+    // at that stack, it gets recreated and initialized to zero.
+    //
+    // To correct for this, we add the previous' stacks value to the current
+    // one, and then handle it as normal. If it is the first time we see a
+    // SourceCallstackId for a CallsiteId, we put the previous value into
+    // the correction maps below.
+    std::map<SourceAllocationIndex, std::set<CallsiteId>> seen_callstacks;
+    std::map<SequenceStackProfileTracker::SourceCallstackId,
+             tables::HeapProfileAllocationTable::Row>
+        alloc_correction;
+    std::map<SequenceStackProfileTracker::SourceCallstackId,
+             tables::HeapProfileAllocationTable::Row>
+        free_correction;
+
+    std::optional<uint64_t> prev_index;
+  };
+  std::map<uint32_t, SequenceState> sequence_state_;
+  TraceProcessorContext* const context_;
+  const StringId empty_;
+  const StringId art_heap_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_HEAP_PROFILE_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc b/src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc
similarity index 74%
rename from src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
rename to src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc
index ab947fa..558ad91 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_profile_tracker_unittest.cc
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/importers/proto/profile_packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 
-#include <memory>
-
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "test/gtest_and_gmock.h"
 
@@ -43,6 +40,7 @@
 constexpr auto kMappingStart = 234;
 constexpr auto kMappingEnd = 345;
 constexpr auto kMappingLoadBias = 456;
+constexpr auto kDefaultSequence = 1;
 
 // heapprofd on Android Q has large callstack ideas, explicitly test large
 // values.
@@ -58,8 +56,10 @@
  public:
   HeapProfileTrackerDupTest() {
     context.storage.reset(new TraceStorage());
-    context.stack_profile_tracker.reset(new StackProfileTracker(&context));
-    packet_sequence_state.reset(new PacketSequenceState(&context));
+    context.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
+    sequence_stack_profile_tracker.reset(
+        new SequenceStackProfileTracker(&context));
+    context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
 
     mapping_name = context.storage->InternString("[mapping]");
     fully_qualified_mapping_name = context.storage->InternString("/[mapping]");
@@ -68,17 +68,13 @@
   }
 
  protected:
-  ProfilePacketSequenceState& profile_packet_sequence_state() {
-    return *packet_sequence_state->current_generation()
-                ->GetOrCreate<ProfilePacketSequenceState>();
-  }
   void InsertMapping(const Packet& packet) {
-    profile_packet_sequence_state().AddString(packet.mapping_name_id,
+    sequence_stack_profile_tracker->AddString(packet.mapping_name_id,
                                               "[mapping]");
 
-    profile_packet_sequence_state().AddString(packet.build_id, kBuildIDName);
+    sequence_stack_profile_tracker->AddString(packet.build_id, kBuildIDName);
 
-    ProfilePacketSequenceState::SourceMapping first_frame;
+    SequenceStackProfileTracker::SourceMapping first_frame;
     first_frame.build_id = packet.build_id;
     first_frame.exact_offset = kMappingExactOffset;
     first_frame.start_offset = kMappingStartOffset;
@@ -87,27 +83,27 @@
     first_frame.load_bias = kMappingLoadBias;
     first_frame.name_ids = {packet.mapping_name_id};
 
-    profile_packet_sequence_state().AddMapping(packet.mapping_id, first_frame);
+    sequence_stack_profile_tracker->AddMapping(packet.mapping_id, first_frame);
   }
 
   void InsertFrame(const Packet& packet) {
     InsertMapping(packet);
-    profile_packet_sequence_state().AddString(packet.frame_name_id, "[frame]");
+    sequence_stack_profile_tracker->AddString(packet.frame_name_id, "[frame]");
 
-    ProfilePacketSequenceState::SourceFrame first_frame;
+    SequenceStackProfileTracker::SourceFrame first_frame;
     first_frame.name_id = packet.frame_name_id;
     first_frame.mapping_id = packet.mapping_id;
     first_frame.rel_pc = kFrameRelPc;
 
-    profile_packet_sequence_state().AddFrame(packet.frame_id, first_frame);
+    sequence_stack_profile_tracker->AddFrame(packet.frame_id, first_frame);
   }
 
   void InsertCallsite(const Packet& packet) {
     InsertFrame(packet);
 
-    ProfilePacketSequenceState::SourceCallstack first_callsite = {
+    SequenceStackProfileTracker::SourceCallstack first_callsite = {
         packet.frame_id, packet.frame_id};
-    profile_packet_sequence_state().AddCallstack(kCallstackId, first_callsite);
+    sequence_stack_profile_tracker->AddCallstack(kCallstackId, first_callsite);
   }
 
   StringId mapping_name;
@@ -115,16 +111,18 @@
   StringId build;
   StringId frame_name;
   TraceProcessorContext context;
-  std::unique_ptr<PacketSequenceState> packet_sequence_state;
+  std::unique_ptr<SequenceStackProfileTracker> sequence_stack_profile_tracker;
 };
 
 // Insert the same mapping from two different packets, with different strings
 // interned, and assert we only store one.
 TEST_F(HeapProfileTrackerDupTest, Mapping) {
   InsertMapping(kFirstPacket);
-  profile_packet_sequence_state().FinalizeProfile();
+  context.heap_profile_tracker->FinalizeProfile(
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
   InsertMapping(kSecondPacket);
-  profile_packet_sequence_state().FinalizeProfile();
+  context.heap_profile_tracker->FinalizeProfile(
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
 
   EXPECT_THAT(context.storage->stack_profile_mapping_table().build_id()[0],
               context.storage->InternString({kBuildIDHexName}));
@@ -146,9 +144,11 @@
 // interned, and assert we only store one.
 TEST_F(HeapProfileTrackerDupTest, Frame) {
   InsertFrame(kFirstPacket);
-  profile_packet_sequence_state().FinalizeProfile();
+  context.heap_profile_tracker->FinalizeProfile(
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
   InsertFrame(kSecondPacket);
-  profile_packet_sequence_state().FinalizeProfile();
+  context.heap_profile_tracker->FinalizeProfile(
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
 
   const auto& frames = context.storage->stack_profile_frame_table();
   EXPECT_THAT(frames.name()[0], frame_name);
@@ -160,9 +160,11 @@
 // stored once.
 TEST_F(HeapProfileTrackerDupTest, Callstack) {
   InsertCallsite(kFirstPacket);
-  profile_packet_sequence_state().FinalizeProfile();
+  context.heap_profile_tracker->FinalizeProfile(
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
   InsertCallsite(kSecondPacket);
-  profile_packet_sequence_state().FinalizeProfile();
+  context.heap_profile_tracker->FinalizeProfile(
+      kDefaultSequence, sequence_stack_profile_tracker.get(), nullptr);
 
   const auto& callsite_table = context.storage->stack_profile_callsite_table();
   const auto& depth = callsite_table.depth();
@@ -196,20 +198,22 @@
 TEST(HeapProfileTrackerTest, SourceMappingPath) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
-  context.stack_profile_tracker.reset(new StackProfileTracker(&context));
-  PacketSequenceState pss(&context);
-  ProfilePacketSequenceState& ppss =
-      *pss.current_generation()->GetOrCreate<ProfilePacketSequenceState>();
+  context.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
+  context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
+
+  HeapProfileTracker* hpt = context.heap_profile_tracker.get();
+  std::unique_ptr<SequenceStackProfileTracker> spt(
+      new SequenceStackProfileTracker(&context));
 
   constexpr auto kBuildId = 1u;
   constexpr auto kMappingNameId1 = 2u;
   constexpr auto kMappingNameId2 = 3u;
 
-  ppss.AddString(kBuildId, "buildid");
-  ppss.AddString(kMappingNameId1, "foo");
-  ppss.AddString(kMappingNameId2, "bar");
+  spt->AddString(kBuildId, "buildid");
+  spt->AddString(kMappingNameId1, "foo");
+  spt->AddString(kMappingNameId2, "bar");
 
-  ProfilePacketSequenceState::SourceMapping mapping;
+  SequenceStackProfileTracker::SourceMapping mapping;
   mapping.build_id = kBuildId;
   mapping.exact_offset = 1;
   mapping.start_offset = 1;
@@ -217,8 +221,8 @@
   mapping.end = 3;
   mapping.load_bias = 0;
   mapping.name_ids = {kMappingNameId1, kMappingNameId2};
-  ppss.AddMapping(0, mapping);
-  ppss.CommitAllocations();
+  spt->AddMapping(0, mapping);
+  hpt->CommitAllocations(kDefaultSequence, spt.get(), nullptr);
   auto foo_bar_id = context.storage->string_pool().GetId("/foo/bar");
   ASSERT_NE(foo_bar_id, std::nullopt);
   EXPECT_THAT(context.storage->stack_profile_mapping_table().name()[0],
@@ -229,11 +233,12 @@
 TEST(HeapProfileTrackerTest, Functional) {
   TraceProcessorContext context;
   context.storage.reset(new TraceStorage());
-  context.stack_profile_tracker.reset(new StackProfileTracker(&context));
+  context.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
+  context.heap_profile_tracker.reset(new HeapProfileTracker(&context));
 
-  PacketSequenceState pss(&context);
-  ProfilePacketSequenceState& ppss =
-      *pss.current_generation()->GetOrCreate<ProfilePacketSequenceState>();
+  HeapProfileTracker* hpt = context.heap_profile_tracker.get();
+  std::unique_ptr<SequenceStackProfileTracker> spt(
+      new SequenceStackProfileTracker(&context));
 
   uint32_t next_string_intern_id = 1;
 
@@ -247,7 +252,7 @@
   for (size_t i = 0; i < base::ArraySize(mapping_names); ++i)
     mapping_name_ids[i] = next_string_intern_id++;
 
-  ProfilePacketSequenceState::SourceMapping
+  SequenceStackProfileTracker::SourceMapping
       mappings[base::ArraySize(mapping_names)] = {};
   mappings[0].build_id = build_id_ids[0];
   mappings[0].exact_offset = 1;
@@ -278,7 +283,7 @@
   for (size_t i = 0; i < base::ArraySize(function_names); ++i)
     function_name_ids[i] = next_string_intern_id++;
 
-  ProfilePacketSequenceState::SourceFrame
+  SequenceStackProfileTracker::SourceFrame
       frames[base::ArraySize(function_names)];
   frames[0].name_id = function_name_ids[0];
   frames[0].mapping_id = 0;
@@ -296,41 +301,41 @@
   frames[3].mapping_id = 2;
   frames[3].rel_pc = 123;
 
-  ProfilePacketSequenceState::SourceCallstack callstacks[3];
+  SequenceStackProfileTracker::SourceCallstack callstacks[3];
   callstacks[0] = {2, 1, 0};
   callstacks[1] = {2, 1, 0, 1, 0};
   callstacks[2] = {0, 2, 0, 1, 2};
 
   for (size_t i = 0; i < base::ArraySize(build_ids); ++i) {
     auto interned = base::StringView(build_ids[i].data(), build_ids[i].size());
-    ppss.AddString(build_id_ids[i], interned);
+    spt->AddString(build_id_ids[i], interned);
   }
   for (size_t i = 0; i < base::ArraySize(mapping_names); ++i) {
     auto interned =
         base::StringView(mapping_names[i].data(), mapping_names[i].size());
-    ppss.AddString(mapping_name_ids[i], interned);
+    spt->AddString(mapping_name_ids[i], interned);
   }
   for (size_t i = 0; i < base::ArraySize(function_names); ++i) {
     auto interned =
         base::StringView(function_names[i].data(), function_names[i].size());
-    ppss.AddString(function_name_ids[i], interned);
+    spt->AddString(function_name_ids[i], interned);
   }
 
   for (uint32_t i = 0; i < base::ArraySize(mappings); ++i)
-    ppss.AddMapping(i, mappings[i]);
+    spt->AddMapping(i, mappings[i]);
   for (uint32_t i = 0; i < base::ArraySize(frames); ++i)
-    ppss.AddFrame(i, frames[i]);
+    spt->AddFrame(i, frames[i]);
   for (uint32_t i = 0; i < base::ArraySize(callstacks); ++i)
-    ppss.AddCallstack(i, callstacks[i]);
+    spt->AddCallstack(i, callstacks[i]);
 
-  ppss.CommitAllocations();
+  hpt->CommitAllocations(kDefaultSequence, spt.get(), nullptr);
 
   for (size_t i = 0; i < base::ArraySize(callstacks); ++i) {
     std::optional<CallsiteId> parent;
-    const ProfilePacketSequenceState::SourceCallstack& callstack =
+    const SequenceStackProfileTracker::SourceCallstack& callstack =
         callstacks[i];
     for (size_t depth = 0; depth < callstack.size(); ++depth) {
-      auto frame_id = ppss.GetDatabaseFrameIdForTesting(callstack[depth]);
+      auto frame_id = spt->GetDatabaseFrameIdForTesting(callstack[depth]);
       std::optional<CallsiteId> self = FindCallstack(
           *context.storage, static_cast<int64_t>(depth), parent, frame_id);
       ASSERT_TRUE(self.has_value());
@@ -338,7 +343,7 @@
     }
   }
 
-  ppss.FinalizeProfile();
+  hpt->FinalizeProfile(kDefaultSequence, spt.get(), nullptr);
 }
 
 }  // namespace
diff --git a/src/trace_processor/importers/proto/packet_sequence_state.h b/src/trace_processor/importers/proto/packet_sequence_state.h
index 2ad0c7b..8c29222 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state.h
@@ -19,12 +19,13 @@
 
 #include <stdint.h>
 
-#include <memory>
-#include <type_traits>
+#include <unordered_map>
 #include <vector>
 
 #include "perfetto/base/compiler.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/interned_message_view.h"
 
@@ -33,8 +34,19 @@
 
 class PacketSequenceState {
  public:
+  // Helper to keep per sequence state. These are not reset when the generation
+  // changes.
+  // Trackers or parsers can add their custom per sequence state here instead of
+  // keeping a map from seq_id to some internal state.
+  // TODO(carlscab): We should come up with a nicer API that allows extensions
+  // to be notified of generation changes.
+  // TODO(carlscab): There is some existing code that could use this. Migrate.
+  struct ExtensibleSequenceState {
+    std::unique_ptr<Destructible> v8_sequence_state;
+  };
+
   explicit PacketSequenceState(TraceProcessorContext* context)
-      : context_(context) {
+      : context_(context), sequence_stack_profile_tracker_(context) {
     current_generation_.reset(
         new PacketSequenceStateGeneration(this, generation_index_++));
   }
@@ -75,7 +87,7 @@
     // sequence. Add a new generation with the updated defaults but the
     // current generation's interned data state.
     current_generation_.reset(new PacketSequenceStateGeneration(
-        this, generation_index_++, current_generation_.get(),
+        this, generation_index_++, current_generation_->interned_data_,
         std::move(defaults)));
   }
 
@@ -107,6 +119,14 @@
 
   bool IsIncrementalStateValid() const { return !packet_loss_; }
 
+  SequenceStackProfileTracker& sequence_stack_profile_tracker() {
+    return sequence_stack_profile_tracker_;
+  }
+
+  ExtensibleSequenceState& extensible_sequence_state() {
+    return extensible_sequence_state_;
+  }
+
   // Returns a ref-counted ptr to the current generation.
   RefPtr<PacketSequenceStateGeneration> current_generation() const {
     return current_generation_;
@@ -154,8 +174,20 @@
   int64_t track_event_thread_instruction_count_ = 0;
 
   RefPtr<PacketSequenceStateGeneration> current_generation_;
+  SequenceStackProfileTracker sequence_stack_profile_tracker_;
+  ExtensibleSequenceState extensible_sequence_state_;
 };
 
+template <uint32_t FieldId, typename MessageType>
+typename MessageType::Decoder*
+PacketSequenceStateGeneration::LookupInternedMessage(uint64_t iid) {
+  auto* interned_message_view = GetInternedMessageView(FieldId, iid);
+  if (!interned_message_view)
+    return nullptr;
+
+  return interned_message_view->template GetOrCreateDecoder<MessageType>();
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/importers/proto/packet_sequence_state_generation.cc b/src/trace_processor/importers/proto/packet_sequence_state_generation.cc
index 1e89096..6e8e98d 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state_generation.cc
+++ b/src/trace_processor/importers/proto/packet_sequence_state_generation.cc
@@ -15,38 +15,12 @@
  */
 
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
-#include <cstddef>
 
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-PacketSequenceStateGeneration::PacketSequenceStateGeneration(
-    PacketSequenceState* state,
-    size_t generation_index,
-    PacketSequenceStateGeneration* prev_gen,
-    TraceBlobView defaults)
-    : state_(state),
-      generation_index_(generation_index),
-      interned_data_(prev_gen->interned_data_),
-      trace_packet_defaults_(InternedMessageView(std::move(defaults))),
-      trackers_(prev_gen->trackers_) {
-  for (auto& t : trackers_) {
-    if (t.get() != nullptr) {
-      t->set_generation(this);
-    }
-  }
-}
-
-PacketSequenceStateGeneration::InternedDataTracker::~InternedDataTracker() =
-    default;
-
-TraceProcessorContext* PacketSequenceStateGeneration::GetContext() const {
-  return state_->context();
-}
-
 void PacketSequenceStateGeneration::InternMessage(uint32_t field_id,
                                                   TraceBlobView message) {
   constexpr auto kIidFieldNumber = 1;
diff --git a/src/trace_processor/importers/proto/packet_sequence_state_generation.h b/src/trace_processor/importers/proto/packet_sequence_state_generation.h
index 1f05d5c..9c7aa5a 100644
--- a/src/trace_processor/importers/proto/packet_sequence_state_generation.h
+++ b/src/trace_processor/importers/proto/packet_sequence_state_generation.h
@@ -17,17 +17,10 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PACKET_SEQUENCE_STATE_GENERATION_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PACKET_SEQUENCE_STATE_GENERATION_H_
 
-#include <array>
-#include <cstddef>
-#include <memory>
 #include <optional>
-#include <tuple>
-#include <type_traits>
 #include <unordered_map>
 
-#include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/ref_counted.h"
-#include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/util/interned_message_view.h"
 
 #include "protos/perfetto/trace/trace_packet_defaults.pbzero.h"
@@ -42,65 +35,9 @@
     std::unordered_map<uint32_t /*field_id*/, InternedMessageMap>;
 
 class PacketSequenceState;
-class TraceProcessorContext;
-
-class StackProfileSequenceState;
-class ProfilePacketSequenceState;
-class V8SequenceState;
-
-using InternedDataTrackers = std::tuple<StackProfileSequenceState,
-                                        ProfilePacketSequenceState,
-                                        V8SequenceState>;
 
 class PacketSequenceStateGeneration : public RefCounted {
  public:
-  // Base class to add custom sequence state. This state is keep per sequence
-  // and per incremental state interval, that is, each time incremental state is
-  // reset a new instance is created but not each time `TracePacketDefaults` are
-  // updated. Note that this means that different
-  // `PacketSequenceStateGeneration` instances might point to the same
-  // `InternedDataTracker` (because they only differ in their
-  // `TracePacketDefaults`).
-  //
-  // ATTENTION: You should not create instances of these classes yourself but
-  // use the `PacketSequenceStateGeneration::GetOrCreate<>' method instead.
-  class InternedDataTracker : public RefCounted {
-   public:
-    virtual ~InternedDataTracker();
-
-   protected:
-    template <uint32_t FieldId, typename MessageType>
-    typename MessageType::Decoder* LookupInternedMessage(uint64_t iid) {
-      return generation_->LookupInternedMessage<FieldId, MessageType>(iid);
-    }
-
-    InternedMessageView* GetInternedMessageView(uint32_t field_id,
-                                                uint64_t iid) {
-      return generation_->GetInternedMessageView(field_id, iid);
-    }
-
-    template <typename T>
-    std::remove_cv_t<T>* GetOrCreate() {
-      return generation_->GetOrCreate<T>();
-    }
-
-   private:
-    friend PacketSequenceStateGeneration;
-    // Called when the a new generation is created as a result of
-    // `TracePacketDefaults` being updated.
-    void set_generation(PacketSequenceStateGeneration* generation) {
-      generation_ = generation;
-    }
-
-    // Note: A `InternedDataTracker` instance can be linked to multiple
-    // `PacketSequenceStateGeneration` instances (when there are multiple
-    // `TracePacketDefaults` in the same interning context). `generation_` will
-    // point to the latest one. We keep this member private to prevent misuse /
-    // confusion around this fact. Instead subclasses should access the public
-    // methods of this class to get any interned data.
-    PacketSequenceStateGeneration* generation_ = nullptr;
-  };
-
   // Returns |nullptr| if the message with the given |iid| was not found (also
   // records a stat in this case).
   template <uint32_t FieldId, typename MessageType>
@@ -142,47 +79,21 @@
   PacketSequenceState* state() const { return state_; }
   size_t generation_index() const { return generation_index_; }
 
-  // Extension point for custom sequence state. To add new per sequence state
-  // just subclass ´PacketSequenceStateGeneration´ and get your sequence bound
-  // instance by calling this method.
-  template <typename T>
-  std::remove_cv_t<T>* GetOrCreate();
-
  private:
   friend class PacketSequenceState;
 
-  // Helper to find the index in a tuple of a given type. Lookups are done
-  // ignoring cv qualifiers. If no index is found size of the tuple is returned.
-  //
-  // ATTENTION: Duplicate types in the tuple will trigger a compiler error.
-  template <typename Tuple, typename Type, size_t index = 0>
-  static constexpr size_t FindUniqueType() {
-    constexpr size_t kSize = std::tuple_size_v<Tuple>;
-    if constexpr (index < kSize) {
-      using TypeAtIndex = typename std::tuple_element<index, Tuple>::type;
-      if constexpr (std::is_same_v<std::remove_cv_t<Type>,
-                                   std::remove_cv_t<TypeAtIndex>>) {
-        static_assert(FindUniqueType<Tuple, Type, index + 1>() == kSize,
-                      "Duplicate types.");
-        return index;
-      } else {
-        return FindUniqueType<Tuple, Type, index + 1>();
-      }
-    } else {
-      return kSize;
-    }
-  }
-
   PacketSequenceStateGeneration(PacketSequenceState* state,
                                 size_t generation_index)
       : state_(state), generation_index_(generation_index) {}
 
   PacketSequenceStateGeneration(PacketSequenceState* state,
                                 size_t generation_index,
-                                PacketSequenceStateGeneration* prev_gen,
-                                TraceBlobView defaults);
-
-  TraceProcessorContext* GetContext() const;
+                                InternedFieldMap interned_data,
+                                TraceBlobView defaults)
+      : state_(state),
+        generation_index_(generation_index),
+        interned_data_(interned_data),
+        trace_packet_defaults_(InternedMessageView(std::move(defaults))) {}
 
   void InternMessage(uint32_t field_id, TraceBlobView message);
 
@@ -196,34 +107,8 @@
   size_t generation_index_;
   InternedFieldMap interned_data_;
   std::optional<InternedMessageView> trace_packet_defaults_;
-  std::array<RefPtr<InternedDataTracker>,
-             std::tuple_size_v<InternedDataTrackers>>
-      trackers_;
 };
 
-template <typename T>
-std::remove_cv_t<T>* PacketSequenceStateGeneration::GetOrCreate() {
-  constexpr size_t index = FindUniqueType<InternedDataTrackers, T>();
-  static_assert(index < std::tuple_size_v<InternedDataTrackers>, "Not found");
-  auto& ptr = trackers_[index];
-  if (PERFETTO_UNLIKELY(ptr.get() == nullptr)) {
-    ptr.reset(new T(GetContext()));
-    ptr->set_generation(this);
-  }
-
-  return static_cast<std::remove_cv_t<T>*>(ptr.get());
-}
-
-template <uint32_t FieldId, typename MessageType>
-typename MessageType::Decoder*
-PacketSequenceStateGeneration::LookupInternedMessage(uint64_t iid) {
-  auto* interned_message_view = GetInternedMessageView(FieldId, iid);
-  if (!interned_message_view)
-    return nullptr;
-
-  return interned_message_view->template GetOrCreateDecoder<MessageType>();
-}
-
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index 61596d3..c337869 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -25,18 +25,17 @@
 #include "src/trace_processor/importers/common/deobfuscation_mapping_table.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
+#include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
-#include "src/trace_processor/importers/proto/profile_packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
-#include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
-#include "src/trace_processor/util/profiler_util.h"
 #include "src/trace_processor/util/stack_traces_util.h"
 
 #include "protos/perfetto/common/builtin_clock.pbzero.h"
@@ -92,7 +91,8 @@
       ParsePerfSample(ts, data.sequence_state.get(), decoder);
       return;
     case TracePacket::kProfilePacketFieldNumber:
-      ParseProfilePacket(ts, data.sequence_state->state(),
+      ParseProfilePacket(ts, data.sequence_state.get(),
+                         decoder.trusted_packet_sequence_id(),
                          decoder.profile_packet());
       return;
     case TracePacket::kModuleSymbolsFieldNumber:
@@ -152,8 +152,9 @@
 
   ProcessTracker* procs = context_->process_tracker.get();
   TraceStorage* storage = context_->storage.get();
-  StackProfileSequenceState& stack_profile_sequence_state =
-      *sequence_state->GetOrCreate<StackProfileSequenceState>();
+  SequenceStackProfileTracker& sequence_stack_profile_tracker =
+      sequence_state->state()->sequence_stack_profile_tracker();
+  ProfilePacketInternLookup intern_lookup(sequence_state);
 
   uint32_t pid = static_cast<uint32_t>(sequence_state->state()->pid());
   uint32_t tid = static_cast<uint32_t>(sequence_state->state()->tid());
@@ -170,8 +171,8 @@
       break;
     }
 
-    auto opt_cs_id =
-        stack_profile_sequence_state.FindOrInsertCallstack(*callstack_it);
+    auto opt_cs_id = sequence_stack_profile_tracker.FindOrInsertCallstack(
+        *callstack_it, &intern_lookup);
     if (!opt_cs_id) {
       context_->storage->IncrementStats(stats::stackprofile_parser_error);
       continue;
@@ -246,11 +247,12 @@
       ts, static_cast<double>(sample.timebase_count()),
       sampling_stream.timebase_track_id);
 
-  StackProfileSequenceState& stack_profile_sequence_state =
-      *sequence_state->GetOrCreate<StackProfileSequenceState>();
+  SequenceStackProfileTracker& stack_tracker =
+      sequence_state->state()->sequence_stack_profile_tracker();
+  ProfilePacketInternLookup intern_lookup(sequence_state);
   uint64_t callstack_iid = sample.callstack_iid();
   std::optional<CallsiteId> cs_id =
-      stack_profile_sequence_state.FindOrInsertCallstack(callstack_iid);
+      stack_tracker.FindOrInsertCallstack(callstack_iid, &intern_lookup);
 
   // A failed lookup of the interned callstack can mean either:
   // (a) This is a counter-only profile without callstacks. Due to an
@@ -296,38 +298,45 @@
   context_->storage->mutable_perf_sample_table()->Insert(sample_row);
 }
 
-void ProfileModule::ParseProfilePacket(int64_t ts,
-                                       PacketSequenceState* sequence_state,
-                                       ConstBytes blob) {
-  ProfilePacketSequenceState& profile_packet_sequence_state =
-      *sequence_state->current_generation()
-           ->GetOrCreate<ProfilePacketSequenceState>();
+void ProfileModule::ParseProfilePacket(
+    int64_t ts,
+    PacketSequenceStateGeneration* sequence_state,
+    uint32_t seq_id,
+    ConstBytes blob) {
   protos::pbzero::ProfilePacket::Decoder packet(blob.data, blob.size);
-  profile_packet_sequence_state.SetProfilePacketIndex(packet.index());
+  context_->heap_profile_tracker->SetProfilePacketIndex(seq_id, packet.index());
 
   for (auto it = packet.strings(); it; ++it) {
     protos::pbzero::InternedString::Decoder entry(*it);
+
     const char* str = reinterpret_cast<const char*>(entry.str().data);
     auto str_view = base::StringView(str, entry.str().size);
-    profile_packet_sequence_state.AddString(entry.iid(), str_view);
+    sequence_state->state()->sequence_stack_profile_tracker().AddString(
+        entry.iid(), str_view);
   }
 
   for (auto it = packet.mappings(); it; ++it) {
     protos::pbzero::Mapping::Decoder entry(*it);
-    profile_packet_sequence_state.AddMapping(
-        entry.iid(), ProfilePacketUtils::MakeSourceMapping(entry));
+    SequenceStackProfileTracker::SourceMapping src_mapping =
+        ProfilePacketUtils::MakeSourceMapping(entry);
+    sequence_state->state()->sequence_stack_profile_tracker().AddMapping(
+        entry.iid(), src_mapping);
   }
 
   for (auto it = packet.frames(); it; ++it) {
     protos::pbzero::Frame::Decoder entry(*it);
-    profile_packet_sequence_state.AddFrame(
-        entry.iid(), ProfilePacketUtils::MakeSourceFrame(entry));
+    SequenceStackProfileTracker::SourceFrame src_frame =
+        ProfilePacketUtils::MakeSourceFrame(entry);
+    sequence_state->state()->sequence_stack_profile_tracker().AddFrame(
+        entry.iid(), src_frame);
   }
 
   for (auto it = packet.callstacks(); it; ++it) {
     protos::pbzero::Callstack::Decoder entry(*it);
-    profile_packet_sequence_state.AddCallstack(
-        entry.iid(), ProfilePacketUtils::MakeSourceCallstack(entry));
+    SequenceStackProfileTracker::SourceCallstack src_callstack =
+        ProfilePacketUtils::MakeSourceCallstack(entry);
+    sequence_state->state()->sequence_stack_profile_tracker().AddCallstack(
+        entry.iid(), src_callstack);
   }
 
   for (auto it = packet.process_dumps(); it; ++it) {
@@ -397,7 +406,7 @@
     for (auto sample_it = entry.samples(); sample_it; ++sample_it) {
       protos::pbzero::ProfilePacket::HeapSample::Decoder sample(*sample_it);
 
-      ProfilePacketSequenceState::SourceAllocation src_allocation;
+      HeapProfileTracker::SourceAllocation src_allocation;
       src_allocation.pid = entry.pid();
       if (entry.heap_name().size != 0) {
         src_allocation.heap_name =
@@ -418,11 +427,15 @@
         src_allocation.free_count = sample.free_count();
       }
 
-      profile_packet_sequence_state.StoreAllocation(src_allocation);
+      context_->heap_profile_tracker->StoreAllocation(seq_id, src_allocation);
     }
   }
   if (!packet.continued()) {
-    profile_packet_sequence_state.FinalizeProfile();
+    PERFETTO_CHECK(sequence_state);
+    ProfilePacketInternLookup intern_lookup(sequence_state);
+    context_->heap_profile_tracker->FinalizeProfile(
+        seq_id, &sequence_state->state()->sequence_stack_profile_tracker(),
+        &intern_lookup);
   }
 }
 
@@ -438,7 +451,7 @@
         module_symbols.build_id().data, module_symbols.build_id().size)));
   }
 
-  auto mapping_ids = context_->stack_profile_tracker->FindMappingRow(
+  auto mapping_ids = context_->global_stack_profile_tracker->FindMappingRow(
       context_->storage->InternString(module_symbols.path()), build_id);
   if (mapping_ids.empty()) {
     context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
@@ -471,7 +484,7 @@
       context_->args_translation_table->AddNativeSymbolTranslationRule(
           mapping_id, address_symbols.address(), last_location);
       std::vector<FrameId> frame_ids =
-          context_->stack_profile_tracker->FindFrameIds(
+          context_->global_stack_profile_tracker->FindFrameIds(
               mapping_id, address_symbols.address());
 
       for (const FrameId frame_id : frame_ids) {
@@ -523,16 +536,21 @@
 
       std::vector<tables::StackProfileFrameTable::Id> frames;
       if (opt_package_name_id) {
-        const std::vector<tables::StackProfileFrameTable::Id> pkg_frames =
-            context_->stack_profile_tracker->JavaFramesForName(
+        const std::vector<tables::StackProfileFrameTable::Id>* pkg_frames =
+            context_->global_stack_profile_tracker->JavaFramesForName(
                 {*merged_obfuscated_id, *opt_package_name_id});
-        frames.insert(frames.end(), pkg_frames.begin(), pkg_frames.end());
+        if (pkg_frames) {
+          frames.insert(frames.end(), pkg_frames->begin(), pkg_frames->end());
+        }
       }
       if (opt_memfd_id) {
-        const std::vector<tables::StackProfileFrameTable::Id> memfd_frames =
-            context_->stack_profile_tracker->JavaFramesForName(
+        const std::vector<tables::StackProfileFrameTable::Id>* memfd_frames =
+            context_->global_stack_profile_tracker->JavaFramesForName(
                 {*merged_obfuscated_id, *opt_memfd_id});
-        frames.insert(frames.end(), memfd_frames.begin(), memfd_frames.end());
+        if (memfd_frames) {
+          frames.insert(frames.end(), memfd_frames->begin(),
+                        memfd_frames->end());
+        }
       }
 
       for (tables::StackProfileFrameTable::Id frame_id : frames) {
diff --git a/src/trace_processor/importers/proto/profile_module.h b/src/trace_processor/importers/proto/profile_module.h
index 883fb7a..e6674d7 100644
--- a/src/trace_processor/importers/proto/profile_module.h
+++ b/src/trace_processor/importers/proto/profile_module.h
@@ -65,7 +65,8 @@
 
   // heap profiling:
   void ParseProfilePacket(int64_t ts,
-                          PacketSequenceState*,
+                          PacketSequenceStateGeneration*,
+                          uint32_t seq_id,
                           protozero::ConstBytes);
   void ParseDeobfuscationMapping(int64_t ts,
                                  PacketSequenceStateGeneration*,
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
deleted file mode 100644
index 71a4b13..0000000
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.cc
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include "src/trace_processor/importers/proto/profile_packet_sequence_state.h"
-
-#include "perfetto/base/flat_set.h"
-#include "perfetto/ext/base/string_view.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
-#include "src/trace_processor/importers/proto/profile_packet_utils.h"
-#include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
-#include "src/trace_processor/storage/stats.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-const char kArtHeapName[] = "com.android.art";
-}
-
-ProfilePacketSequenceState::ProfilePacketSequenceState(
-    TraceProcessorContext* context)
-    : context_(context) {}
-
-ProfilePacketSequenceState::~ProfilePacketSequenceState() = default;
-
-void ProfilePacketSequenceState::SetProfilePacketIndex(uint64_t index) {
-  bool dropped_packet = false;
-  // heapprofd starts counting at index = 0.
-  if (!prev_index.has_value() && index != 0) {
-    dropped_packet = true;
-  }
-
-  if (prev_index.has_value() && *prev_index + 1 != index) {
-    dropped_packet = true;
-  }
-
-  if (dropped_packet) {
-    context_->storage->IncrementStats(stats::heapprofd_missing_packet);
-  }
-  prev_index = index;
-}
-
-void ProfilePacketSequenceState::AddString(SourceStringId id,
-                                           base::StringView str) {
-  strings_.Insert(id, str.ToStdString());
-}
-
-void ProfilePacketSequenceState::AddMapping(SourceMappingId id,
-                                            const SourceMapping& mapping) {
-  StackProfileTracker::CreateMappingParams params;
-  if (std::string* str = strings_.Find(mapping.build_id); str) {
-    params.build_id = base::StringView(*str);
-  } else {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
-    return;
-  }
-  params.exact_offset = mapping.exact_offset;
-  params.start_offset = mapping.start_offset;
-  params.start = mapping.start;
-  params.end = mapping.end;
-  params.load_bias = mapping.load_bias;
-
-  std::vector<base::StringView> path_components;
-  path_components.reserve(mapping.name_ids.size());
-  for (SourceStringId string_id : mapping.name_ids) {
-    if (std::string* str = strings_.Find(string_id); str) {
-      path_components.push_back(base::StringView(*str));
-    } else {
-      context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
-      return;
-    }
-  }
-  std::string path = ProfilePacketUtils::MakeMappingName(path_components);
-  params.name = base::StringView(path);
-  MappingId mapping_id = context_->stack_profile_tracker->InternMapping(params);
-  mappings_.Insert(id, mapping_id);
-}
-
-void ProfilePacketSequenceState::AddFrame(SourceFrameId id,
-                                          const SourceFrame& frame) {
-  MappingId* mapping_id = mappings_.Find(frame.mapping_id);
-  if (!mapping_id) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
-    return;
-  }
-
-  std::string* function_name = strings_.Find(frame.name_id);
-  if (!function_name) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
-    return;
-  }
-
-  FrameId frame_id = context_->stack_profile_tracker->InternFrame(
-      *mapping_id, frame.rel_pc, base::StringView(*function_name));
-
-  frames_.Insert(id, frame_id);
-}
-
-void ProfilePacketSequenceState::AddCallstack(
-    SourceCallstackId id,
-    const SourceCallstack& callstack) {
-  std::optional<CallsiteId> parent_callsite_id;
-  uint32_t depth = 0;
-  for (SourceFrameId source_frame_id : callstack) {
-    FrameId* frame_id = frames_.Find(source_frame_id);
-    if (!frame_id) {
-      context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
-      return;
-    }
-    parent_callsite_id = context_->stack_profile_tracker->InternCallsite(
-        parent_callsite_id, *frame_id, depth);
-    ++depth;
-  }
-
-  if (!parent_callsite_id) {
-    context_->storage->IncrementStats(stats::stackprofile_empty_callstack);
-    return;
-  }
-
-  callstacks_.Insert(id, *parent_callsite_id);
-}
-
-void ProfilePacketSequenceState::StoreAllocation(
-    const SourceAllocation& alloc) {
-  pending_allocs_.push_back(std::move(alloc));
-}
-
-void ProfilePacketSequenceState::CommitAllocations() {
-  for (const SourceAllocation& alloc : pending_allocs_)
-    AddAllocation(alloc);
-  pending_allocs_.clear();
-}
-
-void ProfilePacketSequenceState::FinalizeProfile() {
-  CommitAllocations();
-  strings_.Clear();
-  mappings_.Clear();
-  frames_.Clear();
-  callstacks_.Clear();
-}
-
-FrameId ProfilePacketSequenceState::GetDatabaseFrameIdForTesting(
-    SourceFrameId source_frame_id) {
-  FrameId* frame_id = frames_.Find(source_frame_id);
-  if (!frame_id) {
-    PERFETTO_DLOG("Invalid frame.");
-    return {};
-  }
-  return *frame_id;
-}
-
-void ProfilePacketSequenceState::AddAllocation(const SourceAllocation& alloc) {
-  auto opt_callstack_id = FindOrInsertCallstack(alloc.callstack_id);
-  if (!opt_callstack_id)
-    return;
-
-  CallsiteId callstack_id = *opt_callstack_id;
-
-  UniquePid upid = context_->process_tracker->GetOrCreateProcess(
-      static_cast<uint32_t>(alloc.pid));
-
-  tables::HeapProfileAllocationTable::Row alloc_row{
-      alloc.timestamp,
-      upid,
-      alloc.heap_name,
-      callstack_id,
-      static_cast<int64_t>(alloc.alloc_count),
-      static_cast<int64_t>(alloc.self_allocated)};
-
-  tables::HeapProfileAllocationTable::Row free_row{
-      alloc.timestamp,
-      upid,
-      alloc.heap_name,
-      callstack_id,
-      -static_cast<int64_t>(alloc.free_count),
-      -static_cast<int64_t>(alloc.self_freed)};
-
-  auto* prev_alloc = prev_alloc_.Find({upid, callstack_id});
-  if (!prev_alloc) {
-    prev_alloc = prev_alloc_
-                     .Insert(std::make_pair(upid, callstack_id),
-                             tables::HeapProfileAllocationTable::Row{})
-                     .first;
-  }
-
-  auto* prev_free = prev_free_.Find({upid, callstack_id});
-  if (!prev_free) {
-    prev_free = prev_free_
-                    .Insert(std::make_pair(upid, callstack_id),
-                            tables::HeapProfileAllocationTable::Row{})
-                    .first;
-  }
-
-  base::FlatSet<CallsiteId>& callstacks_for_source_callstack_id =
-      seen_callstacks_[SourceAllocationIndex{upid, alloc.callstack_id,
-                                             alloc.heap_name}];
-  bool new_callstack;
-  std::tie(std::ignore, new_callstack) =
-      callstacks_for_source_callstack_id.insert(callstack_id);
-
-  if (new_callstack) {
-    alloc_correction_[alloc.callstack_id] = *prev_alloc;
-    free_correction_[alloc.callstack_id] = *prev_free;
-  }
-
-  const auto* alloc_correction = alloc_correction_.Find(alloc.callstack_id);
-  if (alloc_correction) {
-    alloc_row.count += alloc_correction->count;
-    alloc_row.size += alloc_correction->size;
-  }
-
-  const auto* free_correction = free_correction_.Find(alloc.callstack_id);
-  if (free_correction) {
-    free_row.count += free_correction->count;
-    free_row.size += free_correction->size;
-  }
-
-  tables::HeapProfileAllocationTable::Row alloc_delta = alloc_row;
-  tables::HeapProfileAllocationTable::Row free_delta = free_row;
-
-  alloc_delta.count -= prev_alloc->count;
-  alloc_delta.size -= prev_alloc->size;
-
-  free_delta.count -= prev_free->count;
-  free_delta.size -= prev_free->size;
-
-  if (alloc_delta.count < 0 || alloc_delta.size < 0 || free_delta.count > 0 ||
-      free_delta.size > 0) {
-    PERFETTO_DLOG("Non-monotonous allocation.");
-    context_->storage->IncrementIndexedStats(stats::heapprofd_malformed_packet,
-                                             static_cast<int>(upid));
-    return;
-  }
-
-  // Dump at max profiles do not have .count set.
-  if (alloc_delta.count || alloc_delta.size) {
-    context_->storage->mutable_heap_profile_allocation_table()->Insert(
-        alloc_delta);
-  }
-
-  // ART only reports allocations, and not frees. This throws off our logic
-  // that assumes that if a new object was allocated with the same address,
-  // the old one has to have been freed in the meantime.
-  // See HeapTracker::RecordMalloc in bookkeeping.cc.
-  if (context_->storage->GetString(alloc.heap_name) != kArtHeapName &&
-      (free_delta.count || free_delta.size)) {
-    context_->storage->mutable_heap_profile_allocation_table()->Insert(
-        free_delta);
-  }
-
-  *prev_alloc = alloc_row;
-  *prev_free = free_row;
-}
-
-std::optional<CallsiteId> ProfilePacketSequenceState::FindOrInsertCallstack(
-    uint64_t iid) {
-  if (CallsiteId* id = callstacks_.Find(iid); id) {
-    return *id;
-  }
-  return GetOrCreate<StackProfileSequenceState>()->FindOrInsertCallstack(iid);
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state.h b/src/trace_processor/importers/proto/profile_packet_sequence_state.h
deleted file mode 100644
index 678aab2..0000000
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2019 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 SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILE_PACKET_SEQUENCE_STATE_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILE_PACKET_SEQUENCE_STATE_H_
-
-#include <cstdint>
-#include "perfetto/base/flat_set.h"
-#include "perfetto/ext/base/flat_hash_map.h"
-
-#include "perfetto/ext/base/hash.h"
-#include "perfetto/ext/base/string_view.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
-#include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
-#include "src/trace_processor/storage/trace_storage.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// Keeps sequence specific state for profile packets.
-class ProfilePacketSequenceState final
-    : public PacketSequenceStateGeneration::InternedDataTracker {
- public:
-  using SourceStringId = uint64_t;
-
-  struct SourceMapping {
-    SourceStringId build_id = 0;
-    uint64_t exact_offset = 0;
-    uint64_t start_offset = 0;
-    uint64_t start = 0;
-    uint64_t end = 0;
-    uint64_t load_bias = 0;
-    std::vector<SourceStringId> name_ids;
-  };
-  using SourceMappingId = uint64_t;
-
-  struct SourceFrame {
-    SourceStringId name_id = 0;
-    SourceMappingId mapping_id = 0;
-    uint64_t rel_pc = 0;
-  };
-  using SourceFrameId = uint64_t;
-
-  using SourceCallstack = std::vector<SourceFrameId>;
-  using SourceCallstackId = uint64_t;
-  struct SourceAllocation {
-    uint64_t pid = 0;
-    // This is int64_t, because we get this from the TraceSorter which also
-    // converts this for us.
-    int64_t timestamp = 0;
-    StringId heap_name;
-    uint64_t callstack_id = 0;
-    uint64_t self_allocated = 0;
-    uint64_t self_freed = 0;
-    uint64_t alloc_count = 0;
-    uint64_t free_count = 0;
-  };
-
-  explicit ProfilePacketSequenceState(TraceProcessorContext* context);
-  virtual ~ProfilePacketSequenceState() override;
-
-  // Profile packets keep track of a index to detect packet loss. Call this
-  // method to update this index with the latest seen value.
-  void SetProfilePacketIndex(uint64_t index);
-
-  // In Android version Q we did not intern Mappings, Frames nor Callstacks,
-  // instead the profile packed "interned these". The following methods are used
-  // to support this old use case. They add the given object to a sequence local
-  // index for them to be retrieved later (see Find* Lookup* methods).
-  void AddString(SourceStringId id, base::StringView str);
-  void AddMapping(SourceMappingId id, const SourceMapping& mapping);
-  void AddFrame(SourceFrameId id, const SourceFrame& frame);
-  void AddCallstack(SourceCallstackId id, const SourceCallstack& callstack);
-
-  void StoreAllocation(const SourceAllocation& allocation);
-  void FinalizeProfile();
-  void CommitAllocations();
-
-  FrameId GetDatabaseFrameIdForTesting(SourceFrameId);
-
- private:
-  struct SourceAllocationIndex {
-    UniquePid upid;
-    SourceCallstackId src_callstack_id;
-    StringPool::Id heap_name;
-    bool operator==(const SourceAllocationIndex& o) const {
-      return std::tie(upid, src_callstack_id, heap_name) ==
-             std::tie(o.upid, o.src_callstack_id, o.heap_name);
-    }
-    struct Hasher {
-      size_t operator()(const SourceAllocationIndex& o) const {
-        return static_cast<size_t>(base::Hasher::Combine(
-            o.upid, o.src_callstack_id, o.heap_name.raw_id()));
-      }
-    };
-  };
-
-  void AddAllocation(const SourceAllocation& alloc);
-
-  // The following methods deal with interned data. In Android version Q we did
-  // not intern Mappings, Frames nor Callstacks, instead the profile packed
-  // "interned these" and this class keeps those ina  sequence local index. In
-  // newer versions, these objects are in InternedData (see
-  // protos/perfetto/trace/interned_data) and are shared across multiple
-  // ProfilePackets. For backwards compatibility, the following methods first
-  // look up interned data in the private sequence local index (for values added
-  // via the Add* methods), and then, if this lookup fails, in the InternedData
-  // instead.
-  std::optional<MappingId> FindOrInsertMapping(uint64_t iid);
-  std::optional<CallsiteId> FindOrInsertCallstack(uint64_t iid);
-
-  TraceProcessorContext* const context_;
-
-  base::FlatHashMap<SourceStringId, std::string> strings_;
-  base::FlatHashMap<SourceMappingId, MappingId> mappings_;
-  base::FlatHashMap<SourceFrameId, FrameId> frames_;
-  base::FlatHashMap<SourceCallstackId, CallsiteId> callstacks_;
-
-  std::vector<SourceAllocation> pending_allocs_;
-
-  struct Hasher {
-    size_t operator()(const std::pair<UniquePid, CallsiteId>& p) const {
-      return static_cast<size_t>(
-          base::Hasher::Combine(p.first, p.second.value));
-    }
-  };
-  base::FlatHashMap<std::pair<UniquePid, CallsiteId>,
-                    tables::HeapProfileAllocationTable::Row,
-                    Hasher>
-      prev_alloc_;
-  base::FlatHashMap<std::pair<UniquePid, CallsiteId>,
-                    tables::HeapProfileAllocationTable::Row,
-                    Hasher>
-      prev_free_;
-
-  // For continuous dumps, we only store the delta in the data-base. To do
-  // this, we subtract the previous dump's value. Sometimes, we should not
-  // do that subtraction, because heapprofd garbage collects stacks that
-  // have no unfreed allocations. If the application then allocations again
-  // at that stack, it gets recreated and initialized to zero.
-  //
-  // To correct for this, we add the previous' stacks value to the current
-  // one, and then handle it as normal. If it is the first time we see a
-  // SourceCallstackId for a CallsiteId, we put the previous value into
-  // the correction maps below.
-  base::FlatHashMap<SourceAllocationIndex,
-                    base::FlatSet<CallsiteId>,
-                    SourceAllocationIndex::Hasher>
-      seen_callstacks_;
-  base::FlatHashMap<SourceCallstackId, tables::HeapProfileAllocationTable::Row>
-      alloc_correction_;
-  base::FlatHashMap<SourceCallstackId, tables::HeapProfileAllocationTable::Row>
-      free_correction_;
-
-  std::optional<uint64_t> prev_index;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILE_PACKET_SEQUENCE_STATE_H_
diff --git a/src/trace_processor/importers/proto/profile_packet_utils.cc b/src/trace_processor/importers/proto/profile_packet_utils.cc
index 3cc5dc1..9478db9 100644
--- a/src/trace_processor/importers/proto/profile_packet_utils.cc
+++ b/src/trace_processor/importers/proto/profile_packet_utils.cc
@@ -15,27 +15,11 @@
  */
 
 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
-#include "perfetto/ext/base/string_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-// static
-std::string ProfilePacketUtils::MakeMappingName(
-    const std::vector<base::StringView>& path_components) {
-  std::string name;
-  for (base::StringView p : path_components) {
-    name.push_back('/');
-    name.append(p.data(), p.size());
-  }
-
-  // When path strings just have single full path(like Chrome does), the mapping
-  // path gets an extra '/' prepended, strip the extra '/'.
-  if (base::StartsWith(name, "//")) {
-    name = name.substr(1);
-  }
-  return name;
-}
+ProfilePacketInternLookup::~ProfilePacketInternLookup() = default;
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/profile_packet_utils.h b/src/trace_processor/importers/proto/profile_packet_utils.h
index a9ea0e6..64b2c50 100644
--- a/src/trace_processor/importers/proto/profile_packet_utils.h
+++ b/src/trace_processor/importers/proto/profile_packet_utils.h
@@ -20,7 +20,7 @@
 
 #include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/profile_packet_sequence_state.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
@@ -31,12 +31,9 @@
 
 class ProfilePacketUtils {
  public:
-  static std::string MakeMappingName(
-      const std::vector<base::StringView>& path_components);
-
-  static ProfilePacketSequenceState::SourceMapping MakeSourceMapping(
+  static SequenceStackProfileTracker::SourceMapping MakeSourceMapping(
       const protos::pbzero::Mapping::Decoder& entry) {
-    ProfilePacketSequenceState::SourceMapping src_mapping{};
+    SequenceStackProfileTracker::SourceMapping src_mapping{};
     src_mapping.build_id = entry.build_id();
     src_mapping.exact_offset = entry.exact_offset();
     src_mapping.start_offset = entry.start_offset();
@@ -50,18 +47,18 @@
     return src_mapping;
   }
 
-  static ProfilePacketSequenceState::SourceFrame MakeSourceFrame(
+  static SequenceStackProfileTracker::SourceFrame MakeSourceFrame(
       const protos::pbzero::Frame::Decoder& entry) {
-    ProfilePacketSequenceState::SourceFrame src_frame;
+    SequenceStackProfileTracker::SourceFrame src_frame;
     src_frame.name_id = entry.function_name_id();
     src_frame.mapping_id = entry.mapping_id();
     src_frame.rel_pc = entry.rel_pc();
     return src_frame;
   }
 
-  static ProfilePacketSequenceState::SourceCallstack MakeSourceCallstack(
+  static SequenceStackProfileTracker::SourceCallstack MakeSourceCallstack(
       const protos::pbzero::Callstack::Decoder& entry) {
-    ProfilePacketSequenceState::SourceCallstack src_callstack;
+    SequenceStackProfileTracker::SourceCallstack src_callstack;
     for (auto frame_it = entry.frame_ids(); frame_it; ++frame_it)
       src_callstack.emplace_back(*frame_it);
     return src_callstack;
@@ -129,6 +126,76 @@
   }
 };
 
+class ProfilePacketInternLookup
+    : public SequenceStackProfileTracker::InternLookup {
+ public:
+  explicit ProfilePacketInternLookup(PacketSequenceStateGeneration* seq_state)
+      : seq_state_(seq_state) {}
+  ~ProfilePacketInternLookup() override;
+
+  std::optional<base::StringView> GetString(
+      SequenceStackProfileTracker::SourceStringId iid,
+      SequenceStackProfileTracker::InternedStringType type) const override {
+    protos::pbzero::InternedString::Decoder* decoder = nullptr;
+    switch (type) {
+      case SequenceStackProfileTracker::InternedStringType::kBuildId:
+        decoder = seq_state_->LookupInternedMessage<
+            protos::pbzero::InternedData::kBuildIdsFieldNumber,
+            protos::pbzero::InternedString>(iid);
+        break;
+      case SequenceStackProfileTracker::InternedStringType::kFunctionName:
+        decoder = seq_state_->LookupInternedMessage<
+            protos::pbzero::InternedData::kFunctionNamesFieldNumber,
+            protos::pbzero::InternedString>(iid);
+        break;
+      case SequenceStackProfileTracker::InternedStringType::kMappingPath:
+        decoder = seq_state_->LookupInternedMessage<
+            protos::pbzero::InternedData::kMappingPathsFieldNumber,
+            protos::pbzero::InternedString>(iid);
+        break;
+    }
+    if (!decoder)
+      return std::nullopt;
+    return base::StringView(reinterpret_cast<const char*>(decoder->str().data),
+                            decoder->str().size);
+  }
+
+  std::optional<SequenceStackProfileTracker::SourceMapping> GetMapping(
+      SequenceStackProfileTracker::SourceMappingId iid) const override {
+    auto* decoder = seq_state_->LookupInternedMessage<
+        protos::pbzero::InternedData::kMappingsFieldNumber,
+        protos::pbzero::Mapping>(iid);
+    if (!decoder)
+      return std::nullopt;
+    return ProfilePacketUtils::MakeSourceMapping(*decoder);
+  }
+
+  std::optional<SequenceStackProfileTracker::SourceFrame> GetFrame(
+      SequenceStackProfileTracker::SourceFrameId iid) const override {
+    auto* decoder = seq_state_->LookupInternedMessage<
+        protos::pbzero::InternedData::kFramesFieldNumber,
+        protos::pbzero::Frame>(iid);
+    if (!decoder)
+      return std::nullopt;
+    return ProfilePacketUtils::MakeSourceFrame(*decoder);
+  }
+
+  std::optional<SequenceStackProfileTracker::SourceCallstack> GetCallstack(
+      SequenceStackProfileTracker::SourceCallstackId iid) const override {
+    auto* interned_message_view = seq_state_->GetInternedMessageView(
+        protos::pbzero::InternedData::kCallstacksFieldNumber, iid);
+    if (!interned_message_view)
+      return std::nullopt;
+    protos::pbzero::Callstack::Decoder decoder(
+        interned_message_view->message().data(),
+        interned_message_view->message().length());
+    return ProfilePacketUtils::MakeSourceCallstack(std::move(decoder));
+  }
+
+ private:
+  PacketSequenceStateGeneration* seq_state_;
+};
+
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/util/profiler_util.cc b/src/trace_processor/importers/proto/profiler_util.cc
similarity index 98%
rename from src/trace_processor/util/profiler_util.cc
rename to src/trace_processor/importers/proto/profiler_util.cc
index 71c3c25..79a5448 100644
--- a/src/trace_processor/util/profiler_util.cc
+++ b/src/trace_processor/importers/proto/profiler_util.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/util/profiler_util.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
 #include <optional>
 
 #include "perfetto/ext/base/string_utils.h"
diff --git a/src/trace_processor/util/profiler_util.h b/src/trace_processor/importers/proto/profiler_util.h
similarity index 86%
rename from src/trace_processor/util/profiler_util.h
rename to src/trace_processor/importers/proto/profiler_util.h
index 3af3606..cbf8ad1 100644
--- a/src/trace_processor/util/profiler_util.h
+++ b/src/trace_processor/importers/proto/profiler_util.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_UTIL_PROFILER_UTIL_H_
-#define SRC_TRACE_PROCESSOR_UTIL_PROFILER_UTIL_H_
+#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILER_UTIL_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILER_UTIL_H_
 
 #include <optional>
 #include <string>
@@ -38,4 +38,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_UTIL_PROFILER_UTIL_H_
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROFILER_UTIL_H_
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 148bf60..af96d34 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -28,12 +28,12 @@
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/sched_event_tracker.h"
 #include "src/trace_processor/importers/proto/additional_modules.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/storage/metadata.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -55,7 +55,6 @@
 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
 #include "protos/perfetto/trace/ftrace/task.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
 #include "protos/perfetto/trace/ps/process_tree.pbzero.h"
 #include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
@@ -252,7 +251,8 @@
     context_.track_tracker.reset(new TrackTracker(&context_));
     context_.global_args_tracker.reset(
         new GlobalArgsTracker(context_.storage.get()));
-    context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
+    context_.global_stack_profile_tracker.reset(
+        new GlobalStackProfileTracker());
     context_.args_tracker.reset(new ArgsTracker(&context_));
     context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
     context_.metadata_tracker.reset(
diff --git a/src/trace_processor/importers/proto/proto_trace_reader.h b/src/trace_processor/importers/proto/proto_trace_reader.h
index 8afc40c..1c554a6 100644
--- a/src/trace_processor/importers/proto/proto_trace_reader.h
+++ b/src/trace_processor/importers/proto/proto_trace_reader.h
@@ -24,7 +24,6 @@
 #include "src/trace_processor/importers/common/chunked_trace_reader.h"
 #include "src/trace_processor/importers/proto/proto_incremental_state.h"
 #include "src/trace_processor/importers/proto/proto_trace_tokenizer.h"
-#include "src/trace_processor/storage/trace_storage.h"
 
 namespace protozero {
 struct ConstBytes;
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc b/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
deleted file mode 100644
index 693a1ed..0000000
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.cc
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
-
-#include <optional>
-#include <vector>
-
-#include "perfetto/base/logging.h"
-#include "perfetto/ext/base/string_view.h"
-#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
-#include "src/trace_processor/importers/proto/profile_packet_utils.h"
-#include "src/trace_processor/storage/stats.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-base::StringView ToStringView(protozero::ConstBytes bytes) {
-  return base::StringView(reinterpret_cast<const char*>(bytes.data),
-                          bytes.size);
-}
-}  // namespace
-
-StackProfileSequenceState::StackProfileSequenceState(
-    TraceProcessorContext* context)
-    : context_(context) {}
-
-StackProfileSequenceState::~StackProfileSequenceState() = default;
-
-std::optional<MappingId> StackProfileSequenceState::FindOrInsertMapping(
-    uint64_t iid) {
-  if (MappingId* id = cached_mappings_.Find(iid); id) {
-    return *id;
-  }
-  auto* decoder =
-      LookupInternedMessage<protos::pbzero::InternedData::kMappingsFieldNumber,
-                            protos::pbzero::Mapping>(iid);
-  if (!decoder) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
-    return std::nullopt;
-  }
-
-  base::StringView build_id;
-  if (decoder->has_build_id()) {
-    std::optional<base::StringView> maybe_build_id =
-        LookupInternedBuildId(decoder->build_id());
-    if (!maybe_build_id) {
-      return std::nullopt;
-    }
-    build_id = *maybe_build_id;
-  }
-
-  std::vector<base::StringView> path_components;
-  for (auto it = decoder->path_string_ids(); it; ++it) {
-    std::optional<base::StringView> str = LookupInternedMappingPath(*it);
-    if (!str) {
-      return std::nullopt;
-    }
-    path_components.push_back(*str);
-  }
-  std::string path = ProfilePacketUtils::MakeMappingName(path_components);
-
-  StackProfileTracker::CreateMappingParams params;
-  params.build_id = build_id;
-  params.exact_offset = decoder->exact_offset();
-  params.start_offset = decoder->start_offset();
-  params.start = decoder->start();
-  params.end = decoder->end();
-  params.load_bias = decoder->load_bias();
-  params.name = base::StringView(path);
-  MappingId mapping_id = context_->stack_profile_tracker->InternMapping(params);
-  cached_mappings_.Insert(iid, mapping_id);
-  return mapping_id;
-}
-
-std::optional<base::StringView>
-StackProfileSequenceState::LookupInternedBuildId(uint64_t iid) {
-  auto* decoder =
-      LookupInternedMessage<protos::pbzero::InternedData::kBuildIdsFieldNumber,
-                            protos::pbzero::InternedString>(iid);
-  if (!decoder) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
-    return std::nullopt;
-  }
-
-  return ToStringView(decoder->str());
-}
-
-std::optional<base::StringView>
-StackProfileSequenceState::LookupInternedMappingPath(uint64_t iid) {
-  auto* decoder =
-
-      LookupInternedMessage<
-          protos::pbzero::InternedData::kMappingPathsFieldNumber,
-          protos::pbzero::InternedString>(iid);
-  if (!decoder) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
-    return std::nullopt;
-  }
-
-  return ToStringView(decoder->str());
-}
-
-std::optional<CallsiteId> StackProfileSequenceState::FindOrInsertCallstack(
-    uint64_t iid) {
-  if (CallsiteId* id = cached_callstacks_.Find(iid); id) {
-    return *id;
-  }
-  auto* decoder = LookupInternedMessage<
-      protos::pbzero::InternedData::kCallstacksFieldNumber,
-      protos::pbzero::Callstack>(iid);
-  if (!decoder) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_callstack_id);
-    return std::nullopt;
-  }
-
-  std::optional<CallsiteId> parent_callsite_id;
-  uint32_t depth = 0;
-  for (auto it = decoder->frame_ids(); it; ++it) {
-    std::optional<FrameId> frame_id = FindOrInsertFrame(*it);
-    if (!frame_id) {
-      return std::nullopt;
-    }
-    parent_callsite_id = context_->stack_profile_tracker->InternCallsite(
-        parent_callsite_id, *frame_id, depth);
-    ++depth;
-  }
-
-  if (!parent_callsite_id) {
-    context_->storage->IncrementStats(stats::stackprofile_empty_callstack);
-    return std::nullopt;
-  }
-
-  cached_callstacks_.Insert(iid, *parent_callsite_id);
-
-  return *parent_callsite_id;
-}
-
-std::optional<FrameId> StackProfileSequenceState::FindOrInsertFrame(
-    uint64_t iid) {
-  if (FrameId* id = cached_frames_.Find(iid); id) {
-    return *id;
-  }
-  auto* decoder =
-      LookupInternedMessage<protos::pbzero::InternedData::kFramesFieldNumber,
-                            protos::pbzero::Frame>(iid);
-  if (!decoder) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
-    return std::nullopt;
-  }
-
-  std::optional<MappingId> mapping_id =
-      FindOrInsertMapping(decoder->mapping_id());
-  if (!mapping_id) {
-    return std::nullopt;
-  }
-
-  base::StringView function_name;
-  if (decoder->function_name_id() != 0) {
-    std::optional<base::StringView> func =
-        LookupInternedFunctionName(decoder->function_name_id());
-    if (!func) {
-      return std::nullopt;
-    }
-    function_name = *func;
-  }
-
-  FrameId frame_id = context_->stack_profile_tracker->InternFrame(
-      *mapping_id, decoder->rel_pc(), function_name);
-
-  cached_frames_.Insert(iid, frame_id);
-
-  return frame_id;
-}
-
-std::optional<base::StringView>
-StackProfileSequenceState::LookupInternedFunctionName(uint64_t iid) {
-  auto* decoder = LookupInternedMessage<
-      protos::pbzero::InternedData::kFunctionNamesFieldNumber,
-      protos::pbzero::InternedString>(iid);
-  if (!decoder) {
-    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
-    return std::nullopt;
-  }
-
-  return ToStringView(decoder->str());
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/stack_profile_sequence_state.h b/src/trace_processor/importers/proto/stack_profile_sequence_state.h
deleted file mode 100644
index 7a7879a..0000000
--- a/src/trace_processor/importers/proto/stack_profile_sequence_state.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2019 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 SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_STACK_PROFILE_SEQUENCE_STATE_H_
-#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_STACK_PROFILE_SEQUENCE_STATE_H_
-
-#include <cstdint>
-#include <optional>
-
-#include "perfetto/ext/base/flat_hash_map.h"
-#include "perfetto/ext/base/string_view.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
-#include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/types/trace_processor_context.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-class TraceProcessorContext;
-
-class StackProfileSequenceState final
-    : public PacketSequenceStateGeneration::InternedDataTracker {
- public:
-  explicit StackProfileSequenceState(TraceProcessorContext* context);
-
-  StackProfileSequenceState(const StackProfileSequenceState&);
-
-  virtual ~StackProfileSequenceState() override;
-
-  std::optional<MappingId> FindOrInsertMapping(uint64_t iid);
-  std::optional<CallsiteId> FindOrInsertCallstack(uint64_t iid);
-
- private:
-  std::optional<base::StringView> LookupInternedBuildId(uint64_t iid);
-  std::optional<base::StringView> LookupInternedMappingPath(uint64_t iid);
-  std::optional<base::StringView> LookupInternedFunctionName(uint64_t iid);
-  std::optional<FrameId> FindOrInsertFrame(uint64_t iid);
-
-  TraceProcessorContext* const context_;
-  base::FlatHashMap<uint64_t, MappingId> cached_mappings_;
-  base::FlatHashMap<uint64_t, CallsiteId> cached_callstacks_;
-  base::FlatHashMap<uint64_t, FrameId> cached_frames_;
-};
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_STACK_PROFILE_SEQUENCE_STATE_H_
diff --git a/src/trace_processor/importers/proto/stack_profile_tracker.cc b/src/trace_processor/importers/proto/stack_profile_tracker.cc
new file mode 100644
index 0000000..d00f287
--- /dev/null
+++ b/src/trace_processor/importers/proto/stack_profile_tracker.cc
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/importers/proto/profiler_util.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/util/stack_traces_util.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+SequenceStackProfileTracker::InternLookup::~InternLookup() = default;
+
+SequenceStackProfileTracker::SequenceStackProfileTracker(
+    TraceProcessorContext* context)
+    : context_(context), empty_(kNullStringId) {}
+
+SequenceStackProfileTracker::~SequenceStackProfileTracker() = default;
+
+StringId SequenceStackProfileTracker::GetEmptyStringId() {
+  if (empty_ == kNullStringId) {
+    empty_ = context_->storage->InternString({"", 0});
+  }
+
+  return empty_;
+}
+
+void SequenceStackProfileTracker::AddString(SourceStringId id,
+                                            base::StringView str) {
+  string_map_.emplace(id, str.ToStdString());
+}
+
+std::optional<MappingId> SequenceStackProfileTracker::AddMapping(
+    SourceMappingId id,
+    const SourceMapping& mapping,
+    const InternLookup* intern_lookup) {
+  std::string path;
+  for (SourceStringId str_id : mapping.name_ids) {
+    auto opt_str = FindOrInsertString(str_id, intern_lookup,
+                                      InternedStringType::kMappingPath);
+    if (!opt_str)
+      break;
+    path += "/" + *opt_str;
+  }
+  // When path strings just have single full path(like Chrome does), the mapping
+  // path gets an extra '/' prepended, strip the extra '/'.
+  if (base::StartsWith(path, "//")) {
+    path = path.substr(1);
+  }
+
+  auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup,
+                                          InternedStringType::kBuildId);
+  if (!opt_build_id) {
+    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
+    PERFETTO_DLOG("Invalid string.");
+    return std::nullopt;
+  }
+  const StringId raw_build_id = opt_build_id.value();
+  NullTermStringView raw_build_id_str =
+      context_->storage->GetString(raw_build_id);
+  StringId build_id = GetEmptyStringId();
+  if (!raw_build_id_str.empty()) {
+    // If the build_id is 33 characters long, we assume it's a Breakpad debug
+    // identifier which is already in Hex and doesn't need conversion.
+    // TODO(b/148109467): Remove workaround once all active Chrome versions
+    // write raw bytes instead of a string as build_id.
+    if (util::IsHexModuleId(raw_build_id_str)) {
+      build_id = raw_build_id;
+    } else {
+      std::string hex_build_id =
+          base::ToHex(raw_build_id_str.c_str(), raw_build_id_str.size());
+      build_id =
+          context_->storage->InternString(base::StringView(hex_build_id));
+    }
+  }
+
+  tables::StackProfileMappingTable::Row row{
+      build_id,
+      static_cast<int64_t>(mapping.exact_offset),
+      static_cast<int64_t>(mapping.start_offset),
+      static_cast<int64_t>(mapping.start),
+      static_cast<int64_t>(mapping.end),
+      static_cast<int64_t>(mapping.load_bias),
+      context_->storage->InternString(base::StringView(path))};
+
+  tables::StackProfileMappingTable* mappings =
+      context_->storage->mutable_stack_profile_mapping_table();
+  std::optional<MappingId> cur_id;
+  auto it = mapping_idx_.find(row);
+  if (it != mapping_idx_.end()) {
+    cur_id = it->second;
+  } else {
+    std::vector<MappingId> db_mappings =
+        context_->global_stack_profile_tracker->FindMappingRow(row.name,
+                                                               row.build_id);
+    for (const MappingId preexisting_mapping : db_mappings) {
+      uint32_t preexisting_row = *mappings->id().IndexOf(preexisting_mapping);
+      tables::StackProfileMappingTable::Row preexisting_data{
+          mappings->build_id()[preexisting_row],
+          mappings->exact_offset()[preexisting_row],
+          mappings->start_offset()[preexisting_row],
+          mappings->start()[preexisting_row],
+          mappings->end()[preexisting_row],
+          mappings->load_bias()[preexisting_row],
+          mappings->name()[preexisting_row]};
+
+      if (row == preexisting_data) {
+        cur_id = preexisting_mapping;
+      }
+    }
+    if (!cur_id) {
+      MappingId mapping_id = mappings->Insert(row).id;
+      context_->global_stack_profile_tracker->InsertMappingId(
+          row.name, row.build_id, mapping_id);
+      cur_id = mapping_id;
+    }
+    mapping_idx_.emplace(row, *cur_id);
+  }
+  mapping_ids_.emplace(id, *cur_id);
+  return cur_id;
+}
+
+std::optional<FrameId> SequenceStackProfileTracker::AddFrame(
+    SourceFrameId id,
+    const SourceFrame& frame,
+    const InternLookup* intern_lookup) {
+  std::optional<std::string> opt_name = FindOrInsertString(
+      frame.name_id, intern_lookup, InternedStringType::kFunctionName);
+  if (!opt_name) {
+    context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
+    PERFETTO_DLOG("Invalid string.");
+    return std::nullopt;
+  }
+  const std::string& name = *opt_name;
+  const StringId str_id =
+      context_->storage->InternString(base::StringView(name));
+
+  auto opt_mapping = FindOrInsertMapping(frame.mapping_id, intern_lookup);
+  if (!opt_mapping) {
+    context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
+    return std::nullopt;
+  }
+  MappingId mapping_id = *opt_mapping;
+  const auto& mappings = context_->storage->stack_profile_mapping_table();
+  StringId mapping_name_id =
+      mappings.name()[*mappings.id().IndexOf(mapping_id)];
+  auto mapping_name = context_->storage->GetString(mapping_name_id);
+
+  tables::StackProfileFrameTable::Row row{str_id, mapping_id,
+                                          static_cast<int64_t>(frame.rel_pc)};
+
+  auto* frames = context_->storage->mutable_stack_profile_frame_table();
+
+  std::optional<FrameId> cur_id;
+  auto it = frame_idx_.find(row);
+  if (it != frame_idx_.end()) {
+    cur_id = it->second;
+  } else {
+    std::vector<FrameId> db_frames =
+        context_->global_stack_profile_tracker->FindFrameIds(mapping_id,
+                                                             frame.rel_pc);
+    for (const FrameId preexisting_frame : db_frames) {
+      uint32_t preexisting_row_id = *frames->id().IndexOf(preexisting_frame);
+      tables::StackProfileFrameTable::Row preexisting_row{
+          frames->name()[preexisting_row_id],
+          frames->mapping()[preexisting_row_id],
+          frames->rel_pc()[preexisting_row_id]};
+
+      if (row == preexisting_row) {
+        cur_id = preexisting_frame;
+      }
+    }
+    if (!cur_id) {
+      cur_id = frames->Insert(row).id;
+      context_->global_stack_profile_tracker->InsertFrameRow(
+          mapping_id, static_cast<uint64_t>(row.rel_pc), *cur_id);
+      if (base::Contains(name, '.')) {
+        // Java frames always contain a '.'
+        std::optional<std::string> package =
+            PackageFromLocation(context_->storage.get(), mapping_name);
+        if (package) {
+          NameInPackage nip{str_id, context_->storage->InternString(
+                                        base::StringView(*package))};
+          context_->global_stack_profile_tracker->InsertJavaFrameForName(
+              nip, *cur_id);
+        } else if (mapping_name.find("/memfd:") == 0) {
+          NameInPackage nip{str_id, context_->storage->InternString("memfd")};
+          context_->global_stack_profile_tracker->InsertJavaFrameForName(
+              nip, *cur_id);
+        }
+      }
+    }
+    frame_idx_.emplace(row, *cur_id);
+  }
+  frame_ids_.emplace(id, *cur_id);
+  return cur_id;
+}
+
+std::optional<CallsiteId> SequenceStackProfileTracker::AddCallstack(
+    SourceCallstackId id,
+    const SourceCallstack& frame_ids,
+    const InternLookup* intern_lookup) {
+  if (frame_ids.empty())
+    return std::nullopt;
+
+  std::optional<CallsiteId> parent_id;
+  for (uint32_t depth = 0; depth < frame_ids.size(); ++depth) {
+    auto opt_frame_id = FindOrInsertFrame(frame_ids[depth], intern_lookup);
+    if (!opt_frame_id) {
+      context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
+      return std::nullopt;
+    }
+    FrameId frame_id = *opt_frame_id;
+
+    tables::StackProfileCallsiteTable::Row row{depth, parent_id, frame_id};
+    CallsiteId self_id;
+    auto callsite_it = callsite_idx_.find(row);
+    if (callsite_it != callsite_idx_.end()) {
+      self_id = callsite_it->second;
+    } else {
+      auto* callsite =
+          context_->storage->mutable_stack_profile_callsite_table();
+      self_id = callsite->Insert(row).id;
+      callsite_idx_.emplace(row, self_id);
+    }
+    parent_id = self_id;
+  }
+  PERFETTO_DCHECK(parent_id);  // The loop ran at least once.
+  callstack_ids_.emplace(id, *parent_id);
+  return parent_id;
+}
+
+FrameId SequenceStackProfileTracker::GetDatabaseFrameIdForTesting(
+    SourceFrameId frame_id) {
+  auto it = frame_ids_.find(frame_id);
+  if (it == frame_ids_.end()) {
+    PERFETTO_DLOG("Invalid frame.");
+    return {};
+  }
+  return it->second;
+}
+
+std::optional<StringId> SequenceStackProfileTracker::FindAndInternString(
+    SourceStringId id,
+    const InternLookup* intern_lookup,
+    SequenceStackProfileTracker::InternedStringType type) {
+  if (id == 0)
+    return GetEmptyStringId();
+
+  auto opt_str = FindOrInsertString(id, intern_lookup, type);
+  if (!opt_str)
+    return GetEmptyStringId();
+
+  return context_->storage->InternString(base::StringView(*opt_str));
+}
+
+std::optional<std::string> SequenceStackProfileTracker::FindOrInsertString(
+    SourceStringId id,
+    const InternLookup* intern_lookup,
+    SequenceStackProfileTracker::InternedStringType type) {
+  if (id == 0)
+    return "";
+
+  auto it = string_map_.find(id);
+  if (it == string_map_.end()) {
+    if (intern_lookup) {
+      auto str = intern_lookup->GetString(id, type);
+      if (!str) {
+        context_->storage->IncrementStats(
+            stats::stackprofile_invalid_string_id);
+        PERFETTO_DLOG("Invalid string.");
+        return std::nullopt;
+      }
+      return str->ToStdString();
+    }
+    return std::nullopt;
+  }
+
+  return it->second;
+}
+
+std::optional<MappingId> SequenceStackProfileTracker::FindOrInsertMapping(
+    SourceMappingId mapping_id,
+    const InternLookup* intern_lookup) {
+  std::optional<MappingId> res;
+  auto it = mapping_ids_.find(mapping_id);
+  if (it == mapping_ids_.end()) {
+    if (intern_lookup) {
+      auto interned_mapping = intern_lookup->GetMapping(mapping_id);
+      if (interned_mapping) {
+        res = AddMapping(mapping_id, *interned_mapping, intern_lookup);
+        return res;
+      }
+    }
+    context_->storage->IncrementStats(stats::stackprofile_invalid_mapping_id);
+    return res;
+  }
+  res = it->second;
+  return res;
+}
+
+std::optional<FrameId> SequenceStackProfileTracker::FindOrInsertFrame(
+    SourceFrameId frame_id,
+    const InternLookup* intern_lookup) {
+  std::optional<FrameId> res;
+  auto it = frame_ids_.find(frame_id);
+  if (it == frame_ids_.end()) {
+    if (intern_lookup) {
+      auto interned_frame = intern_lookup->GetFrame(frame_id);
+      if (interned_frame) {
+        res = AddFrame(frame_id, *interned_frame, intern_lookup);
+        return res;
+      }
+    }
+    context_->storage->IncrementStats(stats::stackprofile_invalid_frame_id);
+    PERFETTO_DLOG("Unknown frame %" PRIu64 " : %zu", frame_id,
+                  frame_ids_.size());
+    return res;
+  }
+  res = it->second;
+  return res;
+}
+
+std::optional<CallsiteId> SequenceStackProfileTracker::FindOrInsertCallstack(
+    SourceCallstackId callstack_id,
+    const InternLookup* intern_lookup) {
+  std::optional<CallsiteId> res;
+  auto it = callstack_ids_.find(callstack_id);
+  if (it == callstack_ids_.end()) {
+    auto interned_callstack = intern_lookup->GetCallstack(callstack_id);
+    if (interned_callstack) {
+      res = AddCallstack(callstack_id, *interned_callstack, intern_lookup);
+      return res;
+    }
+    context_->storage->IncrementStats(stats::stackprofile_invalid_callstack_id);
+    PERFETTO_DLOG("Unknown callstack %" PRIu64 " : %zu", callstack_id,
+                  callstack_ids_.size());
+    return res;
+  }
+  res = it->second;
+  return res;
+}
+
+void SequenceStackProfileTracker::ClearIndices() {
+  string_map_.clear();
+  mapping_ids_.clear();
+  callstack_ids_.clear();
+  frame_ids_.clear();
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/stack_profile_tracker.h b/src/trace_processor/importers/proto/stack_profile_tracker.h
new file mode 100644
index 0000000..a29d3c1
--- /dev/null
+++ b/src/trace_processor/importers/proto/stack_profile_tracker.h
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 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 SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_STACK_PROFILE_TRACKER_H_
+#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_STACK_PROFILE_TRACKER_H_
+
+#include <deque>
+#include <optional>
+#include <unordered_map>
+
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+
+#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
+#include "protos/perfetto/trace/profiling/profile_packet.pbzero.h"
+
+template <>
+struct std::hash<std::pair<uint32_t, int64_t>> {
+  using argument_type = std::pair<uint32_t, int64_t>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^ std::hash<int64_t>{}(p.second);
+  }
+};
+
+template <>
+struct std::hash<std::pair<uint32_t, perfetto::trace_processor::CallsiteId>> {
+  using argument_type =
+      std::pair<uint32_t, perfetto::trace_processor::CallsiteId>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^
+           std::hash<uint32_t>{}(p.second.value);
+  }
+};
+
+template <>
+struct std::hash<std::pair<uint32_t, perfetto::trace_processor::MappingId>> {
+  using argument_type =
+      std::pair<uint32_t, perfetto::trace_processor::MappingId>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^
+           std::hash<uint32_t>{}(p.second.value);
+  }
+};
+
+template <>
+struct std::hash<std::pair<uint32_t, perfetto::trace_processor::FrameId>> {
+  using argument_type = std::pair<uint32_t, perfetto::trace_processor::FrameId>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    return std::hash<uint32_t>{}(p.first) ^
+           std::hash<uint32_t>{}(p.second.value);
+  }
+};
+
+template <>
+struct std::hash<std::vector<uint64_t>> {
+  using argument_type = std::vector<uint64_t>;
+  using result_type = size_t;
+
+  result_type operator()(const argument_type& p) const {
+    size_t h = 0u;
+    for (auto v : p)
+      h = h ^ std::hash<uint64_t>{}(v);
+    return h;
+  }
+};
+
+namespace perfetto {
+namespace trace_processor {
+
+struct NameInPackage {
+  StringId name;
+  StringId package;
+
+  bool operator<(const NameInPackage& b) const {
+    return std::tie(name, package) < std::tie(b.name, b.package);
+  }
+};
+
+class TraceProcessorContext;
+
+class GlobalStackProfileTracker {
+ public:
+  std::vector<MappingId> FindMappingRow(StringId name,
+                                        StringId build_id) const {
+    auto it = stack_profile_mapping_index_.find(std::make_pair(name, build_id));
+    if (it == stack_profile_mapping_index_.end())
+      return {};
+    return it->second;
+  }
+
+  void InsertMappingId(StringId name, StringId build_id, MappingId row) {
+    auto pair = std::make_pair(name, build_id);
+    stack_profile_mapping_index_[pair].emplace_back(row);
+  }
+
+  std::vector<FrameId> FindFrameIds(MappingId mapping_row,
+                                    uint64_t rel_pc) const {
+    auto it =
+        stack_profile_frame_index_.find(std::make_pair(mapping_row, rel_pc));
+    if (it == stack_profile_frame_index_.end())
+      return {};
+    return it->second;
+  }
+
+  void InsertFrameRow(MappingId mapping_row, uint64_t rel_pc, FrameId row) {
+    auto pair = std::make_pair(mapping_row, rel_pc);
+    stack_profile_frame_index_[pair].emplace_back(row);
+  }
+
+  const std::vector<tables::StackProfileFrameTable::Id>* JavaFramesForName(
+      NameInPackage name) {
+    auto it = java_frames_for_name_.find(name);
+    if (it == java_frames_for_name_.end())
+      return nullptr;
+    return &it->second;
+  }
+
+  void InsertJavaFrameForName(NameInPackage name,
+                              tables::StackProfileFrameTable::Id id) {
+    java_frames_for_name_[name].push_back(id);
+  }
+
+ private:
+  using MappingKey = std::pair<StringId /* name */, StringId /* build id */>;
+  std::map<MappingKey, std::vector<MappingId>> stack_profile_mapping_index_;
+
+  using FrameKey = std::pair<MappingId, uint64_t /* rel_pc */>;
+  std::map<FrameKey, std::vector<FrameId>> stack_profile_frame_index_;
+
+  std::map<NameInPackage, std::vector<tables::StackProfileFrameTable::Id>>
+      java_frames_for_name_;
+};
+
+// TODO(lalitm): Overhaul this class to make row vs id consistent and use
+// base::Optional instead of int64_t.
+class SequenceStackProfileTracker {
+ public:
+  using SourceStringId = uint64_t;
+
+  enum class InternedStringType {
+    kMappingPath,
+    kBuildId,
+    kFunctionName,
+  };
+
+  struct SourceMapping {
+    SourceStringId build_id = 0;
+    uint64_t exact_offset = 0;
+    uint64_t start_offset = 0;
+    uint64_t start = 0;
+    uint64_t end = 0;
+    uint64_t load_bias = 0;
+    std::vector<SourceStringId> name_ids;
+  };
+  using SourceMappingId = uint64_t;
+
+  struct SourceFrame {
+    SourceStringId name_id = 0;
+    SourceMappingId mapping_id = 0;
+    uint64_t rel_pc = 0;
+  };
+  using SourceFrameId = uint64_t;
+
+  using SourceCallstack = std::vector<SourceFrameId>;
+  using SourceCallstackId = uint64_t;
+
+  struct SourceAllocation {
+    uint64_t pid = 0;
+    // This is int64_t, because we get this from the TraceSorter which also
+    // converts this for us.
+    int64_t timestamp = 0;
+    SourceCallstackId callstack_id = 0;
+    uint64_t self_allocated = 0;
+    uint64_t self_freed = 0;
+    uint64_t alloc_count = 0;
+    uint64_t free_count = 0;
+  };
+
+  class InternLookup {
+   public:
+    virtual ~InternLookup();
+
+    virtual std::optional<base::StringView> GetString(
+        SourceStringId,
+        InternedStringType) const = 0;
+    virtual std::optional<SourceMapping> GetMapping(SourceMappingId) const = 0;
+    virtual std::optional<SourceFrame> GetFrame(SourceFrameId) const = 0;
+    virtual std::optional<SourceCallstack> GetCallstack(
+        SourceCallstackId) const = 0;
+  };
+
+  explicit SequenceStackProfileTracker(TraceProcessorContext* context);
+  ~SequenceStackProfileTracker();
+
+  void AddString(SourceStringId, base::StringView);
+  std::optional<MappingId> AddMapping(
+      SourceMappingId,
+      const SourceMapping&,
+      const InternLookup* intern_lookup = nullptr);
+  std::optional<FrameId> AddFrame(SourceFrameId,
+                                  const SourceFrame&,
+                                  const InternLookup* intern_lookup = nullptr);
+  std::optional<CallsiteId> AddCallstack(
+      SourceCallstackId,
+      const SourceCallstack&,
+      const InternLookup* intern_lookup = nullptr);
+
+  FrameId GetDatabaseFrameIdForTesting(SourceFrameId);
+
+  // Gets the row number of string / mapping / frame / callstack previously
+  // added through AddString / AddMapping/ AddFrame / AddCallstack.
+  //
+  // If it is not found, look up the string / mapping / frame / callstack in
+  // the global InternedData state, and if found, add to the database, if not
+  // already added before.
+  //
+  // This is to support both ProfilePackets that contain the interned data
+  // (for Android Q) and where the interned data is kept globally in
+  // InternedData (for versions newer than Q).
+  std::optional<StringId> FindAndInternString(SourceStringId,
+                                              const InternLookup* intern_lookup,
+                                              InternedStringType type);
+  std::optional<std::string> FindOrInsertString(
+      SourceStringId,
+      const InternLookup* intern_lookup,
+      InternedStringType type);
+  std::optional<MappingId> FindOrInsertMapping(
+      SourceMappingId,
+      const InternLookup* intern_lookup);
+  std::optional<FrameId> FindOrInsertFrame(SourceFrameId,
+                                           const InternLookup* intern_lookup);
+
+  std::optional<CallsiteId> FindOrInsertCallstack(
+      SourceCallstackId,
+      const InternLookup* intern_lookup);
+
+  // Clear indices when they're no longer needed.
+  void ClearIndices();
+
+ private:
+  StringId GetEmptyStringId();
+
+  std::unordered_map<SourceStringId, std::string> string_map_;
+
+  // Mapping from ID of mapping / frame / callstack in original trace and the
+  // index in the respective table it was inserted into.
+  std::unordered_map<SourceMappingId, MappingId> mapping_ids_;
+  std::unordered_map<SourceFrameId, FrameId> frame_ids_;
+  std::unordered_map<SourceCallstackId, CallsiteId> callstack_ids_;
+
+  // TODO(oysteine): Share these indices between the StackProfileTrackers,
+  // since they're not sequence-specific.
+  //
+  // Mapping from content of database row to the index of the raw.
+  std::unordered_map<tables::StackProfileMappingTable::Row, MappingId>
+      mapping_idx_;
+  std::unordered_map<tables::StackProfileFrameTable::Row, FrameId> frame_idx_;
+  std::unordered_map<tables::StackProfileCallsiteTable::Row, CallsiteId>
+      callsite_idx_;
+
+  TraceProcessorContext* const context_;
+  StringId empty_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_STACK_PROFILE_TRACKER_H_
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index 00bf52e..06e5c39 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -34,7 +34,6 @@
 #include "src/trace_processor/importers/proto/packet_analyzer.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/importers/proto/profile_packet_utils.h"
-#include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
 #include "src/trace_processor/importers/proto/track_event_tracker.h"
 #include "src/trace_processor/util/debug_annotation_parser.h"
 #include "src/trace_processor/util/proto_to_args_parser.h"
@@ -196,9 +195,12 @@
   }
   // Interned mapping_id loses it's meaning when the sequence ends. So we need
   // to get an id from stack_profile_mapping table.
-  auto mapping_id = delegate.seq_state()
-                        ->GetOrCreate<StackProfileSequenceState>()
-                        ->FindOrInsertMapping(decoder->mapping_id());
+  ProfilePacketInternLookup intern_lookup(delegate.seq_state());
+  auto mapping_id =
+      delegate.seq_state()
+          ->state()
+          ->sequence_stack_profile_tracker()
+          .FindOrInsertMapping(decoder->mapping_id(), &intern_lookup);
   if (!mapping_id) {
     return std::nullopt;
   }
diff --git a/src/trace_processor/importers/proto/v8_module.cc b/src/trace_processor/importers/proto/v8_module.cc
index 94684e3..ac2086a 100644
--- a/src/trace_processor/importers/proto/v8_module.cc
+++ b/src/trace_processor/importers/proto/v8_module.cc
@@ -88,7 +88,8 @@
 void V8Module::ParseV8JsCode(protozero::ConstBytes bytes,
                              int64_t ts,
                              const TracePacketData& data) {
-  V8SequenceState& state = *data.sequence_state->GetOrCreate<V8SequenceState>();
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
 
   V8JsCode::Decoder code(bytes);
 
@@ -109,7 +110,8 @@
 void V8Module::ParseV8InternalCode(protozero::ConstBytes bytes,
                                    int64_t ts,
                                    const TracePacketData& data) {
-  V8SequenceState& state = *data.sequence_state->GetOrCreate<V8SequenceState>();
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
 
   V8InternalCode::Decoder code(bytes);
 
@@ -124,7 +126,8 @@
 void V8Module::ParseV8WasmCode(protozero::ConstBytes bytes,
                                int64_t ts,
                                const TracePacketData& data) {
-  V8SequenceState& state = *data.sequence_state->GetOrCreate<V8SequenceState>();
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
 
   V8WasmCode::Decoder code(bytes);
 
@@ -145,7 +148,8 @@
 void V8Module::ParseV8RegExpCode(protozero::ConstBytes bytes,
                                  int64_t ts,
                                  const TracePacketData& data) {
-  V8SequenceState& state = *data.sequence_state->GetOrCreate<V8SequenceState>();
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
 
   V8RegExpCode::Decoder code(bytes);
 
@@ -160,7 +164,8 @@
 void V8Module::ParseV8CodeMove(protozero::ConstBytes bytes,
                                int64_t,
                                const TracePacketData& data) {
-  V8SequenceState& state = *data.sequence_state->GetOrCreate<V8SequenceState>();
+  V8SequenceState& state =
+      *V8SequenceState::GetOrCreate(data.sequence_state->state());
   protos::pbzero::V8CodeMove::Decoder v8_code_move(bytes);
 
   std::optional<tables::V8IsolateTable::Id> isolate_id =
diff --git a/src/trace_processor/importers/proto/v8_sequence_state.cc b/src/trace_processor/importers/proto/v8_sequence_state.cc
index 33abb24..946ab22 100644
--- a/src/trace_processor/importers/proto/v8_sequence_state.cc
+++ b/src/trace_processor/importers/proto/v8_sequence_state.cc
@@ -17,10 +17,10 @@
 #include "src/trace_processor/importers/proto/v8_sequence_state.h"
 #include <optional>
 
+#include "perfetto/ext/base/string_utils.h"
 #include "protos/perfetto/trace/chrome/v8.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
 #include "src/trace_processor/importers/proto/string_encoding_utils.h"
 #include "src/trace_processor/importers/proto/v8_tracker.h"
 #include "src/trace_processor/storage/stats.h"
@@ -41,8 +41,9 @@
 
 }  // namespace
 
-V8SequenceState::V8SequenceState(TraceProcessorContext* context)
-    : context_(context), v8_tracker_(V8Tracker::GetOrCreate(context_)) {}
+V8SequenceState::V8SequenceState(PacketSequenceState* sequence_state)
+    : sequence_state_(sequence_state),
+      v8_tracker_(V8Tracker::GetOrCreate(sequence_state->context())) {}
 
 V8SequenceState::~V8SequenceState() = default;
 
@@ -52,9 +53,11 @@
     return *id;
   }
 
-  auto* view = GetInternedMessageView(InternedData::kV8IsolateFieldNumber, iid);
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8IsolateFieldNumber, iid);
   if (!view) {
-    context_->storage->IncrementStats(stats::v8_intern_errors);
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
     return std::nullopt;
   }
 
@@ -70,10 +73,11 @@
     return *id;
   }
 
-  auto* view =
-      GetInternedMessageView(InternedData::kV8JsFunctionFieldNumber, iid);
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8JsFunctionFieldNumber, iid);
   if (!view) {
-    context_->storage->IncrementStats(stats::v8_intern_errors);
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
     return std::nullopt;
   }
 
@@ -103,10 +107,11 @@
   if (auto* id = wasm_scripts_.Find(iid); id != nullptr) {
     return *id;
   }
-  auto* view =
-      GetInternedMessageView(InternedData::kV8WasmScriptFieldNumber, iid);
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8WasmScriptFieldNumber, iid);
   if (!view) {
-    context_->storage->IncrementStats(stats::v8_intern_errors);
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
     return std::nullopt;
   }
 
@@ -122,10 +127,11 @@
   if (auto* id = js_scripts_.Find(iid); id != nullptr) {
     return *id;
   }
-  auto* view =
-      GetInternedMessageView(InternedData::kV8JsScriptFieldNumber, iid);
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8JsScriptFieldNumber, iid);
   if (!view) {
-    context_->storage->IncrementStats(stats::v8_intern_errors);
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
     return std::nullopt;
   }
 
@@ -141,16 +147,17 @@
     return *id;
   }
 
-  auto* view =
-      GetInternedMessageView(InternedData::kV8JsFunctionNameFieldNumber, iid);
+  auto* view = sequence_state_->current_generation()->GetInternedMessageView(
+      InternedData::kV8JsFunctionNameFieldNumber, iid);
 
   if (!view) {
-    context_->storage->IncrementStats(stats::v8_intern_errors);
+    sequence_state_->context()->storage->IncrementStats(
+        stats::v8_intern_errors);
     return std::nullopt;
   }
 
   InternedV8String::Decoder function_name(ToConstBytes(view->message()));
-  auto& storage = *context_->storage;
+  auto& storage = *sequence_state_->context()->storage;
   StringId id;
   if (function_name.has_latin1()) {
     id = storage.InternString(
diff --git a/src/trace_processor/importers/proto/v8_sequence_state.h b/src/trace_processor/importers/proto/v8_sequence_state.h
index d2e008a..781960d 100644
--- a/src/trace_processor/importers/proto/v8_sequence_state.h
+++ b/src/trace_processor/importers/proto/v8_sequence_state.h
@@ -21,7 +21,7 @@
 #include <optional>
 
 #include "perfetto/ext/base/flat_hash_map.h"
-#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/v8_tables_py.h"
 #include "src/trace_processor/types/destructible.h"
@@ -29,14 +29,19 @@
 namespace perfetto {
 namespace trace_processor {
 
-class TraceProcessorContext;
 class V8Tracker;
 
 // Helper class to deal with V8 related interned data.
-class V8SequenceState final
-    : public PacketSequenceStateGeneration::InternedDataTracker {
+class V8SequenceState : public Destructible {
  public:
-  explicit V8SequenceState(TraceProcessorContext* context);
+  static V8SequenceState* GetOrCreate(PacketSequenceState* sequence_state) {
+    auto& v8_sequence_state =
+        sequence_state->extensible_sequence_state().v8_sequence_state;
+    if (!v8_sequence_state) {
+      v8_sequence_state.reset(new V8SequenceState(sequence_state));
+    }
+    return static_cast<V8SequenceState*>(v8_sequence_state.get());
+  }
 
   ~V8SequenceState() override;
 
@@ -49,12 +54,13 @@
       tables::V8IsolateTable::Id isolate_id);
 
  private:
+  explicit V8SequenceState(PacketSequenceState* sequence_state);
   std::optional<tables::V8JsScriptTable::Id> GetOrInsertJsScript(
       uint64_t iid,
       tables::V8IsolateTable::Id isolate_id);
   std::optional<StringId> GetOrInsertJsFunctionName(uint64_t iid);
 
-  TraceProcessorContext* const context_;
+  PacketSequenceState* const sequence_state_;
   V8Tracker* const v8_tracker_;
 
   using InterningId = uint64_t;
diff --git a/src/trace_processor/importers/proto/vulkan_memory_tracker.h b/src/trace_processor/importers/proto/vulkan_memory_tracker.h
index 9d1afc5..1457923 100644
--- a/src/trace_processor/importers/proto/vulkan_memory_tracker.h
+++ b/src/trace_processor/importers/proto/vulkan_memory_tracker.h
@@ -17,7 +17,6 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_VULKAN_MEMORY_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_VULKAN_MEMORY_TRACKER_H_
 
-#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 #include "src/trace_processor/importers/proto/proto_incremental_state.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
diff --git a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
index e61c5f5..0f47c32 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_messages_tracker.h
@@ -35,6 +35,7 @@
     std::vector<double> double_params;
     std::vector<bool> boolean_params;
     std::vector<std::string> string_params;
+    std::optional<StringId> stacktrace;
     tables::ProtoLogTable::Id table_row_id;
     int64_t timestamp;
   };
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.cc b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
index 0543222..15c19ac 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.cc
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.cc
@@ -30,7 +30,6 @@
 #include "src/trace_processor/types/trace_processor_context.h"
 
 #include <sstream>
-
 namespace perfetto {
 namespace trace_processor {
 
@@ -79,9 +78,9 @@
   }
 
   std::vector<std::string> string_params;
-  if (protolog_message.has_interned_str_params()) {
+  if (protolog_message.has_str_param_iids()) {
     if (sequence_state->state()->IsIncrementalStateValid()) {
-      for (auto it = protolog_message.interned_str_params(); it; ++it) {
+      for (auto it = protolog_message.str_param_iids(); it; ++it) {
         auto decoder =
             sequence_state->state()
                 ->current_generation()
@@ -109,6 +108,28 @@
     }
   }
 
+  std::optional<StringId> stacktrace = std::nullopt;
+  if (protolog_message.has_stacktrace_iid()) {
+    auto stacktrace_decoder =
+        sequence_state->state()
+            ->current_generation()
+            ->LookupInternedMessage<
+                protos::pbzero::InternedData::kProtologStacktraceFieldNumber,
+                protos::pbzero::InternedString>(
+                protolog_message.stacktrace_iid());
+
+    if (!stacktrace_decoder) {
+      // This shouldn't happen since we already checked the incremental
+      // state is valid.
+      string_params.emplace_back("<ERROR>");
+      context_->storage->IncrementStats(
+          stats::winscope_protolog_missing_interned_stacktrace_parse_errors);
+    } else {
+      stacktrace = context_->storage->InternString(
+          base::StringView(stacktrace_decoder->str().ToStdString()));
+    }
+  }
+
   auto* protolog_table = context_->storage->mutable_protolog_table();
 
   tables::ProtoLogTable::Row row;
@@ -122,6 +143,7 @@
       std::move(double_params),
       std::move(boolean_params),
       std::move(string_params),
+      stacktrace,
       row_id,
       timestamp};
   protolog_message_tracker->TrackMessage(std::move(tracked_message));
@@ -195,6 +217,10 @@
         auto message = context_->storage->InternString(
             base::StringView(formatted_message));
         row.set_message(message);
+
+        if (tracked_message.stacktrace.has_value()) {
+          row.set_stacktrace(tracked_message.stacktrace.value());
+        }
       }
     }
   }
diff --git a/src/trace_processor/importers/proto/winscope/protolog_parser.h b/src/trace_processor/importers/proto/winscope/protolog_parser.h
index 97d9183..fbcf9d9 100644
--- a/src/trace_processor/importers/proto/winscope/protolog_parser.h
+++ b/src/trace_processor/importers/proto/winscope/protolog_parser.h
@@ -19,7 +19,6 @@
 
 #include "protos/perfetto/trace/android/protolog.pbzero.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state.h"
-#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/util/descriptors.h"
 #include "src/trace_processor/util/proto_to_args_parser.h"
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
index ea8b3ae..e34973c 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
@@ -42,7 +42,7 @@
   ]
   deps = [
     "../../..:demangle",
-    "../../..:export_json_sources",
+    "../../..:export_json",
     "../../..:metatrace",
     "../../../../../gn:default_deps",
     "../../../../../gn:sqlite",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
index b4d76cf..fde9046 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
@@ -31,8 +31,8 @@
 #include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
+#include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql b/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql
index 3afe0bf..6467ea7 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/broadcasts.sql
@@ -14,37 +14,82 @@
 -- limitations under the License.
 --
 
+INCLUDE PERFETTO MODULE android.freezer;
+
+CREATE PERFETTO FUNCTION _extract_broadcast_process_name(name STRING)
+RETURNS INT
+AS
+WITH
+  pid_and_name AS (
+    SELECT STR_SPLIT(STR_SPLIT($name, '/', 0), ' ', 1) AS value
+  ),
+  start AS (
+    SELECT CAST(INSTR(value, ':') AS INT) + 1 AS value FROM pid_and_name
+  )
+SELECT SUBSTR(pid_and_name.value, start.value) FROM pid_and_name, start;
+
 -- Provides a list of broadcast names and processes they were sent to by the
 -- system_server process on U+ devices.
-CREATE PERFETTO VIEW _android_broadcasts_minsdk_u(
-  -- The name of the broadcast type which was sent.
-  type STRING,
-  -- The process name the broadcast was sent to.
+CREATE PERFETTO TABLE _android_broadcasts_minsdk_u(
+  -- Intent action of the broadcast.
+  intent_action STRING,
+  -- Name of the process the broadcast was sent to.
   process_name STRING,
-  -- The name of the broacast queue the broadcast was dispatched from.
-  queue_name STRING
+  -- Pid of the process the broadcast was sent to.
+  pid STRING,
+  -- Upid of the process the broadcast was sent to.
+  upid STRING,
+  -- Id of the broacast queue the broadcast was dispatched from.
+  queue_id INT,
+  -- Slice id of the broadcast dispatch.
+  id INT,
+  -- Timestamp the broadcast was dispatched.
+  ts INT,
+  -- Duration to dispatch the broadcast.
+  dur INT,
+  -- Track id the broadcast was dispatched from.
+  track_id INT
 ) AS
 WITH
-broadcast_queues AS (
-  SELECT process_track.id, process_track.name AS queue_name
-  FROM process_track
-  JOIN process USING (upid)
-  WHERE
-    process_track.name GLOB 'BroadcastQueue.mRunning*'
-    AND process.name = 'system_server'
-),
-broadcast_process_running AS (
-  SELECT ts, dur, str_split(slice.name, '/', 0) AS process_name, queue_name
-  FROM slice
-  JOIN broadcast_queues ON broadcast_queues.id = slice.track_id
-  WHERE slice.name GLOB '* running'
-)
-SELECT str_split(slice.name, '/', 0) AS type, process_name, queue_name
-FROM broadcast_process_running
-JOIN broadcast_queues USING (queue_name)
-JOIN slice ON (
-  broadcast_process_running.ts < slice.ts
-  AND slice.ts < broadcast_process_running.ts + broadcast_process_running.dur
-  AND slice.track_id = broadcast_queues.id
+  broadcast_queues AS (
+    SELECT
+      process_track.id,
+      CAST(replace(str_split(process_track.name, '[', 1), ']', '') AS INT) AS queue_id
+    FROM process_track
+    JOIN process
+      USING (upid)
+    WHERE
+      process_track.name GLOB 'BroadcastQueue.mRunning*'
+      AND process.name = 'system_server'
+  ),
+  broadcast_process_running AS (
+    SELECT
+      slice.ts,
+      slice.dur,
+      _extract_broadcast_process_name(slice.name) AS process_name,
+      CAST(str_split(str_split(str_split(slice.name, '/', 0), ' ', 1), ':', 0) AS INT) AS pid,
+      queue_id
+    FROM slice
+    JOIN broadcast_queues
+      ON broadcast_queues.id = slice.track_id
+    WHERE slice.name GLOB '* running'
   )
+SELECT
+  str_split(str_split(slice.name, '/', 0), ' ', 1) AS intent_action,
+  process_name,
+  pid,
+  _pid_to_upid(pid, slice.ts) AS upid,
+  queue_id,
+  slice.id,
+  slice.ts,
+  slice.dur,
+  slice.track_id
+FROM broadcast_process_running
+JOIN broadcast_queues
+  USING (queue_id)
+JOIN slice
+  ON (
+    broadcast_process_running.ts < slice.ts
+    AND slice.ts < broadcast_process_running.ts + broadcast_process_running.dur
+    AND slice.track_id = broadcast_queues.id)
 WHERE slice.name GLOB '* scheduled';
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
index 040d79f..314e612 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql
@@ -93,7 +93,7 @@
       slice.name AS oom_adj_trigger,
       LEAD(thread_slice.ts) OVER (ORDER BY thread_slice.ts) AS oom_adj_next_ts
     FROM thread_slice
-    JOIN slice ON slice.id = thread_slice.parent_id
+    LEFT JOIN slice ON slice.id = thread_slice.parent_id AND slice.dur != -1
     WHERE thread_slice.name GLOB 'updateOomAdj_*' AND process_name = 'system_server'
   )
 SELECT
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index 8131d36..4d38bd6 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -646,15 +646,7 @@
         }
       });
 
-  // Attempt to filter into a RowMap first - weall figure out whether to apply
-  // this to the table or we should use the RowMap directly. Also, if we are
-  // going to sort on the RowMap, it makes sense that we optimize for lookup
-  // speed so our sorting is not super slow.
-  RowMap::OptimizeFor optimize_for = orders_.empty()
-                                         ? RowMap::OptimizeFor::kMemory
-                                         : RowMap::OptimizeFor::kLookupSpeed;
-  RowMap filter_map =
-      SourceTable()->QueryToRowMap(constraints_, orders_, optimize_for);
+  RowMap filter_map = SourceTable()->QueryToRowMap(constraints_, orders_);
   if (filter_map.IsRange() && filter_map.size() <= 1) {
     // Currently, our criteria where we have a special fast path is if it's
     // a single ranged row. We have this fast path for joins on id columns
diff --git a/src/trace_processor/storage/stats.h b/src/trace_processor/storage/stats.h
index 74cba79..66a524e 100644
--- a/src/trace_processor/storage/stats.h
+++ b/src/trace_processor/storage/stats.h
@@ -60,12 +60,15 @@
   F(ftrace_cpu_oldest_event_ts_end,       kIndexed, kInfo,     kTrace,    ""), \
   F(ftrace_cpu_overrun_begin,             kIndexed, kInfo,     kTrace,    ""), \
   F(ftrace_cpu_overrun_end,               kIndexed, kInfo,     kTrace,    ""), \
-  F(ftrace_cpu_overrun_delta,             kIndexed, kDataLoss, kTrace,         \
-       "The kernel ftrace buffer cannot keep up with the rate of events "      \
-       "produced. Indexed by CPU. This is likely a misconfiguration."),        \
+  F(ftrace_cpu_overrun_delta,             kIndexed, kInfo,     kTrace,    ""), \
   F(ftrace_cpu_read_events_begin,         kIndexed, kInfo,     kTrace,    ""), \
   F(ftrace_cpu_read_events_end,           kIndexed, kInfo,     kTrace,    ""), \
   F(ftrace_cpu_read_events_delta,         kIndexed, kInfo,     kTrace,    ""), \
+  F(ftrace_cpu_has_data_loss,             kIndexed, kDataLoss, kTrace,         \
+       "Ftrace data for the given cpu has data losses and is therefore "       \
+       "unreliable. The kernel buffer overwrote events between our reads "     \
+       "in userspace. Try re-recording the trace with a bigger buffer "        \
+       "(ftrace_config.buffer_size_kb), or with fewer enabled ftrace events."),\
   F(ftrace_setup_errors,                  kSingle,  kInfo,     kTrace,         \
        "One or more atrace/ftrace categories were not found or failed to "     \
        "enable. See ftrace_setup_errors in the metadata table for details."),  \
@@ -112,8 +115,6 @@
   F(flow_end_without_start,               kSingle,  kInfo,     kTrace,    ""), \
   F(flow_invalid_id,                      kSingle,  kError,    kTrace,    ""), \
   F(flow_without_direction,               kSingle,  kError,    kTrace,    ""), \
-  F(stackprofile_empty_callstack,         kSingle,  kError,    kTrace,         \
-      "Callstack had no frames. Ignored"),                                     \
   F(stackprofile_invalid_string_id,       kSingle,  kError,    kTrace,    ""), \
   F(stackprofile_invalid_mapping_id,      kSingle,  kError,    kTrace,    ""), \
   F(stackprofile_invalid_frame_id,        kSingle,  kError,    kTrace,    ""), \
@@ -140,7 +141,11 @@
                                           kIndexed, kInfo,     kTrace,    ""), \
   F(traced_buf_padding_bytes_cleared,     kIndexed, kInfo,     kTrace,    ""), \
   F(traced_buf_padding_bytes_written,     kIndexed, kInfo,     kTrace,    ""), \
-  F(traced_buf_patches_failed,            kIndexed, kDataLoss, kTrace,    ""), \
+  F(traced_buf_patches_failed,            kIndexed, kDataLoss, kTrace,         \
+      "The tracing service potentially lost data from one of the data sources "\
+      "writing into the given target_buffer. This entry can be ignored"        \
+      "if you're using DISCARD buffers and traced_buf_chunks_discarded is      \
+      nonzero, meaning that the buffer was filled."),                          \
   F(traced_buf_patches_succeeded,         kIndexed, kInfo,     kTrace,    ""), \
   F(traced_buf_readaheads_failed,         kIndexed, kInfo,     kTrace,    ""), \
   F(traced_buf_readaheads_succeeded,      kIndexed, kInfo,     kTrace,    ""), \
@@ -299,7 +304,10 @@
       "ProtoLog message string has invalid interplation parameter."),          \
   F(winscope_protolog_missing_interned_arg_parse_errors,                       \
                                           kSingle,  kInfo,     kAnalysis,      \
-      "Failed to find interned ProtoLog argument."),          \
+      "Failed to find interned ProtoLog argument."),                           \
+  F(winscope_protolog_missing_interned_stacktrace_parse_errors,                \
+                                          kSingle,  kInfo,     kAnalysis,      \
+      "Failed to find interned ProtoLog stacktrace."),                         \
   F(ftrace_missing_event_id,              kSingle,  kInfo,    kAnalysis,       \
       "Indicates that the ftrace event was dropped because the event id was "  \
       "missing. This is an 'info' stat rather than an error stat because "     \
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index a0ec6fa..b2fcffb 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -44,9 +44,6 @@
     "../../../include/perfetto/trace_processor",
     "../containers",
     "../db:minimal",
-
-    # TODO(lalitm): change this to minimal to reduce binary size
-    # impact on Chrome.
     "../db/column",
   ]
   public_deps = [ ":tables_python" ]
diff --git a/src/trace_processor/tables/py_tables_benchmark.cc b/src/trace_processor/tables/py_tables_benchmark.cc
index 37bb9f7..6aea3d5 100644
--- a/src/trace_processor/tables/py_tables_benchmark.cc
+++ b/src/trace_processor/tables/py_tables_benchmark.cc
@@ -95,29 +95,6 @@
 }
 BENCHMARK(BM_TableIteratorChild)->Apply(TableFilterArgs);
 
-static void BM_TableFilterAndSortRoot(benchmark::State& state) {
-  StringPool pool;
-  RootTestTable root(&pool);
-
-  auto size = static_cast<uint32_t>(state.range(0));
-  uint32_t partitions = 8;
-
-  std::minstd_rand0 rnd_engine(45);
-  for (uint32_t i = 0; i < size; ++i) {
-    RootTestTable::Row row;
-    row.root_non_null = rnd_engine() % partitions;
-    row.root_non_null_2 = static_cast<uint32_t>(rnd_engine());
-    root.Insert(row);
-  }
-
-  for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(
-        {root.root_non_null().eq(5)}, {root.root_non_null_2().ascending()},
-        RowMap::OptimizeFor::kLookupSpeed)));
-  }
-}
-BENCHMARK(BM_TableFilterAndSortRoot)->Apply(TableFilterArgs);
-
 static void BM_TableFilterRootId(benchmark::State& state) {
   StringPool pool;
   RootTestTable root(&pool);
diff --git a/src/trace_processor/tables/winscope_tables.py b/src/trace_processor/tables/winscope_tables.py
index 830f88e..8a60e43 100644
--- a/src/trace_processor/tables/winscope_tables.py
+++ b/src/trace_processor/tables/winscope_tables.py
@@ -112,6 +112,7 @@
         C('level', CppString()),
         C('tag', CppString()),
         C('message', CppString()),
+        C('stacktrace', CppString()),
     ],
     tabledoc=TableDoc(
         doc='Protolog',
@@ -121,6 +122,7 @@
             'level': 'The log level of the protolog message',
             'tag': 'The log tag of the protolog message',
             'message': 'The protolog message',
+            'stacktrace': 'Stacktrace captured at the message\'s logpoint',
         }))
 
 # Keep this list sorted.
diff --git a/src/trace_processor/trace_processor_context.cc b/src/trace_processor/trace_processor_context.cc
index c7cea38..34da487 100644
--- a/src/trace_processor/trace_processor_context.cc
+++ b/src/trace_processor/trace_processor_context.cc
@@ -31,12 +31,13 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/slice_translation_table.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_module.h"
+#include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/proto_trace_parser.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/importers/proto/track_event_module.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/types/destructible.h"
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 41fdab3..8894f47 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -339,18 +339,6 @@
     context_.content_analyzer.reset(new ProtoContentAnalyzer(&context_));
   }
 
-  auto v2 = context_.config.dev_flags.find("enable_db2_filtering");
-  if (v2 != context_.config.dev_flags.end()) {
-    if (v2->second == "true") {
-      Table::kUseFilterV2 = true;
-    } else if (v2->second == "false") {
-      Table::kUseFilterV2 = false;
-    } else {
-      PERFETTO_ELOG("Unknown value for enable_db2_filtering %s",
-                    v2->second.c_str());
-    }
-  }
-
   // Add metrics to descriptor pool
   const std::vector<std::string> sanitized_extension_paths =
       SanitizeMetricMountPaths(config_.skip_builtin_metric_paths);
diff --git a/src/trace_processor/trace_processor_storage_impl.cc b/src/trace_processor/trace_processor_storage_impl.cc
index 05b88c0..f24a1fc 100644
--- a/src/trace_processor/trace_processor_storage_impl.cc
+++ b/src/trace_processor/trace_processor_storage_impl.cc
@@ -30,14 +30,15 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/slice_translation_table.h"
-#include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/chrome_track_event.descriptor.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
+#include "src/trace_processor/importers/proto/heap_profile_tracker.h"
 #include "src/trace_processor/importers/proto/packet_analyzer.h"
 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/importers/proto/proto_trace_reader.h"
+#include "src/trace_processor/importers/proto/stack_profile_tracker.h"
 #include "src/trace_processor/importers/proto/track_event.descriptor.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/util/descriptors.h"
@@ -62,8 +63,9 @@
   context_.process_tracker.reset(new ProcessTracker(&context_));
   context_.clock_tracker.reset(new ClockTracker(&context_));
   context_.clock_converter.reset(new ClockConverter(&context_));
+  context_.heap_profile_tracker.reset(new HeapProfileTracker(&context_));
   context_.perf_sample_tracker.reset(new PerfSampleTracker(&context_));
-  context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
+  context_.global_stack_profile_tracker.reset(new GlobalStackProfileTracker());
   context_.metadata_tracker.reset(new MetadataTracker(context_.storage.get()));
   context_.global_args_tracker.reset(
       new GlobalArgsTracker(context_.storage.get()));
@@ -141,6 +143,7 @@
   }
   context_.event_tracker->FlushPendingEvents();
   context_.slice_tracker->FlushPendingSlices();
+  context_.heap_profile_tracker->NotifyEndOfFile();
   context_.args_tracker->Flush();
   context_.process_tracker->NotifyEndOfFile();
 }
diff --git a/src/trace_processor/types/trace_processor_context.h b/src/trace_processor/types/trace_processor_context.h
index ad4e148..25f129b 100644
--- a/src/trace_processor/types/trace_processor_context.h
+++ b/src/trace_processor/types/trace_processor_context.h
@@ -52,8 +52,9 @@
 class ForwardingTraceParser;
 class FtraceModule;
 class GlobalArgsTracker;
-class StackProfileTracker;
+class GlobalStackProfileTracker;
 class HeapGraphTracker;
+class HeapProfileTracker;
 class PerfSampleTracker;
 class MetadataTracker;
 class PacketAnalyzer;
@@ -100,8 +101,9 @@
   std::unique_ptr<EventTracker> event_tracker;
   std::unique_ptr<ClockTracker> clock_tracker;
   std::unique_ptr<ClockConverter> clock_converter;
+  std::unique_ptr<HeapProfileTracker> heap_profile_tracker;
   std::unique_ptr<PerfSampleTracker> perf_sample_tracker;
-  std::unique_ptr<StackProfileTracker> stack_profile_tracker;
+  std::unique_ptr<GlobalStackProfileTracker> global_stack_profile_tracker;
   std::unique_ptr<MetadataTracker> metadata_tracker;
 
   // These fields are stored as pointers to Destructible objects rather than
diff --git a/src/trace_processor/util/BUILD.gn b/src/trace_processor/util/BUILD.gn
index 8420a06..c717d3e 100644
--- a/src/trace_processor/util/BUILD.gn
+++ b/src/trace_processor/util/BUILD.gn
@@ -61,19 +61,6 @@
   }
 }
 
-source_set("profiler_util") {
-  sources = [
-    "profiler_util.cc",
-    "profiler_util.h",
-  ]
-  deps = [
-    "../../../gn:default_deps",
-    "../../../include/perfetto/ext/base:base",
-    "../../../protos/perfetto/trace/profiling:zero",
-    "../storage:storage",
-  ]
-}
-
 source_set("stack_traces_util") {
   sources = [
     "stack_traces_util.cc",
@@ -82,7 +69,6 @@
   deps = [
     "../../../gn:default_deps",
     "../../../include/perfetto/ext/base:base",
-    "../../../protos/perfetto/trace/profiling:zero",
   ]
 }
 
diff --git a/src/tracing/service/tracing_service_impl.cc b/src/tracing/service/tracing_service_impl.cc
index 3b94cbf..aba6620 100644
--- a/src/tracing/service/tracing_service_impl.cc
+++ b/src/tracing/service/tracing_service_impl.cc
@@ -1797,7 +1797,7 @@
   }
 
   if (tracing_session->state != TracingSession::STARTED) {
-    PERFETTO_ELOG("Flush() called, but tracing has not been started");
+    PERFETTO_LOG("Flush() called, but tracing has not been started");
     callback(false);
     return;
   }
diff --git a/src/tracing/service/tracing_service_impl_unittest.cc b/src/tracing/service/tracing_service_impl_unittest.cc
index 76590b4..17936e5 100644
--- a/src/tracing/service/tracing_service_impl_unittest.cc
+++ b/src/tracing/service/tracing_service_impl_unittest.cc
@@ -5080,7 +5080,7 @@
 }
 
 TEST_F(TracingServiceImplTest, ClearBeforeClone) {
-  // The consumer the creates the initial tracing session.
+  // The consumer that creates the initial tracing session.
   std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
   consumer->Connect(svc.get());
 
@@ -5150,6 +5150,53 @@
                                                   HasSubstr("after_clone")))));
 }
 
+TEST_F(TracingServiceImplTest, CloneMainSessionStopped) {
+  // The consumer that creates the initial tracing session.
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer1");
+  producer->RegisterDataSource("ds_1");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);  // Buf 0.
+  auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+  ds_cfg->set_name("ds_1");
+  ds_cfg->set_target_buffer(0);
+
+  consumer->EnableTracing(trace_config);
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("ds_1");
+  producer->WaitForDataSourceStart("ds_1");
+
+  std::unique_ptr<TraceWriter> writer = producer->CreateTraceWriter("ds_1");
+  {
+    auto packet = writer->NewTracePacket();
+    packet->set_for_testing()->set_str("before_clone");
+  }
+  writer->Flush();
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("ds_1");
+  consumer->WaitForTracingDisabled();
+
+  // The tracing session is disabled, but it's still there. We can still clone
+  // it.
+  std::unique_ptr<MockConsumer> clone_consumer = CreateMockConsumer();
+  clone_consumer->Connect(svc.get());
+
+  auto clone_done = task_runner.CreateCheckpoint("clone_done");
+  EXPECT_CALL(*clone_consumer, OnSessionCloned(_))
+      .WillOnce(InvokeWithoutArgs(clone_done));
+  clone_consumer->CloneSession(1);
+
+  auto packets = clone_consumer->ReadBuffers();
+  EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+                                         Property(&protos::gen::TestEvent::str,
+                                                  HasSubstr("before_clone")))));
+}
+
 TEST_F(TracingServiceImplTest, CloneTransferFlush) {
   // The consumer the creates the initial tracing session.
   std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index 25fb3c5..590d1c0 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-9f4d57409034b11957935bbd94c8fa925d24db453b617503b6c908366b1f11cc
\ No newline at end of file
+71f0799ce780f36dbdeb601ba535ee012b7323afc57254a5b383704ec384da62
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index f99f37b..fd6b0fe 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-f358a8b059b98a0445674d5e720230047f85829a6366439559ffc5908508ba11
\ No newline at end of file
+725ff7289949bda82fc4c2d454e1ee0b4218b3d56f20c1098d6fa7914ef19bfc
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
index d8f53e7..06a3e7b 100644
--- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
@@ -1 +1 @@
-a4b1867255b838c3ab73f6d00c84aa5bd7fc31bb53da5b83f52ceb84d99e96b2
\ No newline at end of file
+22ca974ba2f262c4e421e95dba686746eb89564a8ff213e50f78c9424277587c
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
index 6cbb409..a917bab 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
@@ -1 +1 @@
-ec9873453c3834735d55eb21138bf510069cf863342509a799076d821a8f01dd
\ No newline at end of file
+90505c5620c73940b88d163a73c79005fa8e81d097d0fcabf2585e56f7bc6a99
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index 6667e5f..89a9c57 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-351edcf1ade9e6b1b7ced9d4f204e9a939370b7a56066f66793e56c9d93ec972
\ No newline at end of file
+d1a9ace55344d8a2850a8238b698892aca1ea2eacf80886420108043de8d3c56
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index 90d04b9..470075f 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-3699ab1be1ccf7f41559fad373027c884b3f1539183f4b405e29852f01985937
\ No newline at end of file
+a97c6027c4bef120c4227a7ed32e6a6fb6b290938ef8d66652e6d369b7e5d157
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index 92f9c01..a0ddb77 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-1bcbdbf6d1c2c1c2e77faa90be1c060b350f21472f67aed9477e6ba5e5219e81
\ No newline at end of file
+47cda8e8a9ed007945bff13d7c10fa4d33fc6e264c1198b2038a216559bec962
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index bd02978..98f2571 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-0b44f0292e51fcf599f17622193d67b17bbaca02440cf2f0e994dbbbac65306a
\ No newline at end of file
+bd495271f97ffe17851f6605acaf24463c2363cb6a4bcf8e1b3fd78b9e18353f
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
index ff33850..5b79980 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
@@ -1 +1 @@
-7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
+ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index 92f9c01..a0ddb77 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-1bcbdbf6d1c2c1c2e77faa90be1c060b350f21472f67aed9477e6ba5e5219e81
\ No newline at end of file
+47cda8e8a9ed007945bff13d7c10fa4d33fc6e264c1198b2038a216559bec962
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
index ff33850..5b79980 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
@@ -1 +1 @@
-7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
+ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
index ff33850..5b79980 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
@@ -1 +1 @@
-7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
+ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index 2fe4afb..d806076 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-b3f9edabe8db232b481a7484c5230fa08d01f37336f9e60eaf6fbe8e8d0ded16
\ No newline at end of file
+2dc0e3ade667e7578fa6b5b8911177ab272300bfddb015c4979c3b4361d93047
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index 92f9c01..a0ddb77 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-1bcbdbf6d1c2c1c2e77faa90be1c060b350f21472f67aed9477e6ba5e5219e81
\ No newline at end of file
+47cda8e8a9ed007945bff13d7c10fa4d33fc6e264c1198b2038a216559bec962
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
index ff33850..5b79980 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
@@ -1 +1 @@
-7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
+ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
index ff33850..5b79980 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
@@ -1 +1 @@
-7d8429af94229b4cb96f3e7691e987b4eb0ade9683acce9a2c14c41777415d73
\ No newline at end of file
+ab93fa5646ab5e2bc63922ee8f6c6d7657f128fe7b5426df97d02f19c770a1bc
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/parser/android/protolog.textproto b/test/trace_processor/diff_tests/parser/android/protolog.textproto
index 4ab660d..6e65ffd 100644
--- a/test/trace_processor/diff_tests/parser/android/protolog.textproto
+++ b/test/trace_processor/diff_tests/parser/android/protolog.textproto
@@ -20,15 +20,27 @@
 packet {
   trusted_uid: 1000
   trusted_packet_sequence_id: 2
+  interned_data {
+    protolog_stacktrace {
+      iid: 1
+      str: "A STACK TRACE"
+    }
+  }
+  trusted_pid: 1716
+}
+packet {
+  trusted_uid: 1000
+  trusted_packet_sequence_id: 2
   sequence_flags: 2
   trusted_pid: 1716
   timestamp: 857384100
   protolog_message {
     message_id: 6924537961316301726
-    interned_str_params: 1
+    str_param_iids: 1
     sint64_params: 888
     double_params: 8.88
     boolean_params: 1
+    stacktrace_iid: 1
   }
 }
 packet {
@@ -83,10 +95,10 @@
   timestamp: 857384130
   protolog_message {
     message_id: 9274895847396301003
-    interned_str_params: 1
-    interned_str_params: 1
-    interned_str_params: 2
-    interned_str_params: 1
+    str_param_iids: 1
+    str_param_iids: 1
+    str_param_iids: 2
+    str_param_iids: 1
   }
 }
 packet {
diff --git a/test/trace_processor/diff_tests/parser/android/tests_protolog.py b/test/trace_processor/diff_tests/parser/android/tests_protolog.py
index 6db8cfe..404f34e 100644
--- a/test/trace_processor/diff_tests/parser/android/tests_protolog.py
+++ b/test/trace_processor/diff_tests/parser/android/tests_protolog.py
@@ -24,10 +24,10 @@
   def test_has_expected_protolog_rows(self):
     return DiffTestBlueprint(
         trace=Path('protolog.textproto'),
-        query="SELECT id, ts, level, tag, message FROM protolog;",
+        query="SELECT id, ts, level, tag, message, stacktrace FROM protolog;",
         out=Csv("""
-        "id","ts","level","tag","message"
-        0,857384100,"DEBUG","MyFirstGroup","Test message with a string (MyTestString), an int (1776), a double 8.88, and a boolean true."
-        1,857384110,"WARN","MySecondGroup","Test message with different int formats: 1776, 0o3360, 0x6f0, 888.000000, 8.880000e+02."
-        2,857384130,"ERROR","MyThirdGroup","Message re-using interned string 'MyOtherTestString' == 'MyOtherTestString', but 'SomeOtherTestString' != 'MyOtherTestString'"
+        "id","ts","level","tag","message","stacktrace"
+        0,857384100,"DEBUG","MyFirstGroup","Test message with a string (MyTestString), an int (1776), a double 8.88, and a boolean true.","A STACK TRACE"
+        1,857384110,"WARN","MySecondGroup","Test message with different int formats: 1776, 0o3360, 0x6f0, 888.000000, 8.880000e+02.","[NULL]"
+        2,857384130,"ERROR","MyThirdGroup","Message re-using interned string 'MyOtherTestString' == 'MyOtherTestString', but 'SomeOtherTestString' != 'MyOtherTestString'","[NULL]"
         """))
diff --git a/test/trace_processor/diff_tests/parser/parsing/android_sched_and_ps_stats.out b/test/trace_processor/diff_tests/parser/parsing/android_sched_and_ps_stats.out
index 17268dc..e1542c5 100644
--- a/test/trace_processor/diff_tests/parser/parsing/android_sched_and_ps_stats.out
+++ b/test/trace_processor/diff_tests/parser/parsing/android_sched_and_ps_stats.out
@@ -143,14 +143,14 @@
 "ftrace_cpu_overrun_end",5,"info","trace",0
 "ftrace_cpu_overrun_end",6,"info","trace",0
 "ftrace_cpu_overrun_end",7,"info","trace",0
-"ftrace_cpu_overrun_delta",0,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",1,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",2,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",3,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",4,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",5,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",6,"data_loss","trace",0
-"ftrace_cpu_overrun_delta",7,"data_loss","trace",0
+"ftrace_cpu_overrun_delta",0,"info","trace",0
+"ftrace_cpu_overrun_delta",1,"info","trace",0
+"ftrace_cpu_overrun_delta",2,"info","trace",0
+"ftrace_cpu_overrun_delta",3,"info","trace",0
+"ftrace_cpu_overrun_delta",4,"info","trace",0
+"ftrace_cpu_overrun_delta",5,"info","trace",0
+"ftrace_cpu_overrun_delta",6,"info","trace",0
+"ftrace_cpu_overrun_delta",7,"info","trace",0
 "ftrace_cpu_read_events_begin",0,"info","trace",0
 "ftrace_cpu_read_events_begin",1,"info","trace",0
 "ftrace_cpu_read_events_begin",2,"info","trace",0
diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
index 23ae137..897fe4f 100644
--- a/test/trace_processor/diff_tests/stdlib/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -1255,3 +1255,25 @@
         1737069091010,3467799559,975,"cached_app_lmk_first","com.android.packageinstaller",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
         1737069240534,3467650035,985,"cached_app_lmk_first","com.android.managedprovisioning",1737064421516,29484835,"binder:642_1","processEnd","IActivityManager#1598246212"
       """))
+
+  def test_broadcast_minsdk_u(self):
+    return DiffTestBlueprint(
+        trace=DataPath('freezer_trace.atr'),
+        query="""
+        INCLUDE PERFETTO MODULE android.broadcasts;
+        SELECT intent_action, process_name, pid, queue_id, ts, dur FROM _android_broadcasts_minsdk_u
+        ORDER BY ts LIMIT 10
+      """,
+        out=Csv("""
+        "intent_action","process_name","pid","queue_id","ts","dur"
+        "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED","system",2519,0,91286297271477,221619
+        "android.intent.action.TIME_TICK","com.android.systemui",2762,0,91295942589896,469216
+        "android.intent.action.TIME_TICK","com.android.systemui",2762,0,91295943366025,313104
+        "android.intent.action.TIME_TICK","com.android.systemui",2762,0,91295943943713,356194
+        "android.intent.action.TIME_TICK","com.android.systemui",2762,0,91355941417856,444189
+        "android.intent.action.TIME_TICK","com.android.systemui",2762,0,91355942543001,405369
+        "android.intent.action.TIME_TICK","com.android.systemui",2762,0,91355943262781,339640
+        "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION","system",2519,0,91359865607938,862534
+        "android.content.pm.action.SESSION_COMMITTED","com.android.launcher3",3219,0,91360380556725,15221753
+        "android.intent.action.PACKAGE_ADDED","system",2519,0,91360396877398,107502
+        """))
diff --git a/tools/install-build-deps b/tools/install-build-deps
index d1efa23..b27e8ea 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -221,8 +221,8 @@
     # From https://github.com/ianlancetaylor/libbacktrace/archive/177940370e4a6b2509e92a0aaa9749184e64af43.zip
     Dependency(
         'buildtools/libbacktrace.zip',
-        'https://storage.googleapis.com/perfetto/libbacktrace-177940370e4a6b2509e92a0aaa9749184e64af43.zip',
-        '21ac9a4209f7aeef766c482be53a7fa365063c031c7077e2070b491202983b31',
+        'https://storage.googleapis.com/perfetto/libbacktrace-14818b7783eeb9a56c3f0fca78cefd3143f8c5f6.zip',
+        '0d09295938155aa84d9a6049f63df8cd2def3a28302b3550ea3ead9100b3d086',
         'all', 'all'),
 
     # Sqlite for the trace processing library.
diff --git a/ui/release/channels.json b/ui/release/channels.json
index 8e7a8cf..778c4d8 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -6,7 +6,7 @@
     },
     {
       "name": "canary",
-      "rev": "ed568855bbbfffe3fb099cd5f3d0096f1e4aa268"
+      "rev": "f62b11e6b5d34d0b0f1af3d60eb4d40a57b652b6"
     },
     {
       "name": "autopush",