tp: reduce Trace Processor binary size

Share general interpreter execution code between filter-value providers while
keeping the latency-sensitive single-row path specialized. This removes
large native and WebAssembly template instantiations without affecting
point-query performance.
diff --git a/CHANGELOG b/CHANGELOG
index bec02cc..d944b75 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,7 @@
    *
   Trace Processor:
     * Improved performance of SQLite joins using ID columns.
+    * Reduced native and WebAssembly Trace Processor binary size.
   UI:
     * Double-clicking a timeline slice now selects and zooms into it.
   SDK:
diff --git a/src/trace_processor/core/interpreter/bytecode_interpreter_impl.cc b/src/trace_processor/core/interpreter/bytecode_interpreter_impl.cc
index 9276bc6..9b2b5b9 100644
--- a/src/trace_processor/core/interpreter/bytecode_interpreter_impl.cc
+++ b/src/trace_processor/core/interpreter/bytecode_interpreter_impl.cc
@@ -338,3 +338,38 @@
 }
 
 }  // namespace perfetto::trace_processor::core::interpreter::ops
+
+namespace perfetto::trace_processor::core::interpreter {
+
+#define PERFETTO_OP_CASE_FVF(...)                                \
+  case base::variant_index<BytecodeVariant, __VA_ARGS__>(): {    \
+    ops::__VA_ARGS__(state, fetcher,                             \
+                     static_cast<const __VA_ARGS__&>(bytecode)); \
+    break;                                                       \
+  }
+
+#define PERFETTO_OP_CASE_STATE(...)                                     \
+  case base::variant_index<BytecodeVariant, __VA_ARGS__>(): {           \
+    ops::__VA_ARGS__(state, static_cast<const __VA_ARGS__&>(bytecode)); \
+    break;                                                              \
+  }
+
+void ExecuteGeneralBytecode(InterpreterState& state,
+                            ErasedFilterValueFetcher& fetcher,
+                            const Bytecode* pc,
+                            const Bytecode* end) {
+  for (; pc != end; ++pc) {
+    const Bytecode& bytecode = *pc;
+    switch (bytecode.option) {
+      PERFETTO_DATAFRAME_BYTECODE_GENERAL_FVF_LIST(PERFETTO_OP_CASE_FVF)
+      PERFETTO_DATAFRAME_BYTECODE_STATE_ONLY_LIST(PERFETTO_OP_CASE_STATE)
+      default:
+        PERFETTO_ASSUME(false);
+    }
+  }
+}
+
+#undef PERFETTO_OP_CASE_FVF
+#undef PERFETTO_OP_CASE_STATE
+
+}  // namespace perfetto::trace_processor::core::interpreter
diff --git a/src/trace_processor/core/interpreter/bytecode_interpreter_impl.h b/src/trace_processor/core/interpreter/bytecode_interpreter_impl.h
index e375146..b45abb9 100644
--- a/src/trace_processor/core/interpreter/bytecode_interpreter_impl.h
+++ b/src/trace_processor/core/interpreter/bytecode_interpreter_impl.h
@@ -1804,18 +1804,85 @@
 
 }  // namespace ops
 
-#define PERFETTO_OP_CASE_FVF(...)                                \
-  case base::variant_index<BytecodeVariant, __VA_ARGS__>(): {    \
-    ops::__VA_ARGS__(state_, fetcher,                            \
-                     static_cast<const __VA_ARGS__&>(bytecode)); \
-    break;                                                       \
+// Type-erased fetcher used by the shared general interpreter. Fetching a value
+// also caches its payload so each cast requires only one indirect call.
+struct ErasedFilterValueFetcher : ValueFetcher {
+  using Type = uint8_t;
+  static constexpr Type kInt64 = 0;
+  static constexpr Type kDouble = 1;
+  static constexpr Type kString = 2;
+  static constexpr Type kNull = 3;
+
+  template <typename F>
+  static ErasedFilterValueFetcher Create(F& fetcher) {
+    return ErasedFilterValueFetcher{&fetcher, &FetchValue<F>,
+                                    &IteratorInitImpl<F>, &IteratorNextImpl<F>};
   }
 
-#define PERFETTO_OP_CASE_STATE(...)                                      \
-  case base::variant_index<BytecodeVariant, __VA_ARGS__>(): {            \
-    ops::__VA_ARGS__(state_, static_cast<const __VA_ARGS__&>(bytecode)); \
-    break;                                                               \
+  Type GetValueType(uint32_t index) {
+    fetch_value(fetcher, index, this);
+    return value_type;
   }
+  int64_t GetInt64Value(uint32_t) const { return int64_value; }
+  double GetDoubleValue(uint32_t) const { return double_value; }
+  const char* GetStringValue(uint32_t) const { return string_value; }
+  bool IteratorInit(uint32_t index) { return iterator_init(fetcher, index); }
+  bool IteratorNext(uint32_t index) { return iterator_next(fetcher, index); }
+
+ private:
+  using FetchValueFn = void (*)(void*, uint32_t, ErasedFilterValueFetcher*);
+  using IteratorFn = bool (*)(void*, uint32_t);
+
+  ErasedFilterValueFetcher(void* f,
+                           FetchValueFn fv,
+                           IteratorFn init,
+                           IteratorFn next)
+      : fetcher(f), fetch_value(fv), iterator_init(init), iterator_next(next) {}
+
+  template <typename F>
+  static void FetchValue(void* raw_fetcher,
+                         uint32_t index,
+                         ErasedFilterValueFetcher* self) {
+    auto& f = *static_cast<F*>(raw_fetcher);
+    auto type = f.GetValueType(index);
+    if (type == F::kInt64) {
+      self->value_type = kInt64;
+      self->int64_value = f.GetInt64Value(index);
+    } else if (type == F::kDouble) {
+      self->value_type = kDouble;
+      self->double_value = f.GetDoubleValue(index);
+    } else if (type == F::kString) {
+      self->value_type = kString;
+      self->string_value = f.GetStringValue(index);
+    } else {
+      PERFETTO_DCHECK(type == F::kNull);
+      self->value_type = kNull;
+    }
+  }
+
+  template <typename F>
+  static bool IteratorInitImpl(void* raw_fetcher, uint32_t index) {
+    return static_cast<F*>(raw_fetcher)->IteratorInit(index);
+  }
+  template <typename F>
+  static bool IteratorNextImpl(void* raw_fetcher, uint32_t index) {
+    return static_cast<F*>(raw_fetcher)->IteratorNext(index);
+  }
+
+  void* fetcher;
+  FetchValueFn fetch_value;
+  IteratorFn iterator_init;
+  IteratorFn iterator_next;
+  Type value_type = kNull;
+  int64_t int64_value = 0;
+  double double_value = 0;
+  const char* string_value = nullptr;
+};
+
+void ExecuteGeneralBytecode(InterpreterState& state,
+                            ErasedFilterValueFetcher& fetcher,
+                            const Bytecode* pc,
+                            const Bytecode* end);
 
 template <typename FilterValueFetcherImpl>
 PERFETTO_ALWAYS_INLINE Span<uint32_t>
@@ -1831,24 +1898,14 @@
     return ops::ExecuteSingleRowQuery(state_, fetcher, query);
   }
 
-  for (; pc != end; ++pc) {
-    const Bytecode& bytecode = *pc;
-    switch (bytecode.option) {
-      PERFETTO_DATAFRAME_BYTECODE_GENERAL_FVF_LIST(PERFETTO_OP_CASE_FVF)
-      PERFETTO_DATAFRAME_BYTECODE_STATE_ONLY_LIST(PERFETTO_OP_CASE_STATE)
-      default:
-        PERFETTO_ASSUME(false);
-    }
-  }
+  auto erased = ErasedFilterValueFetcher::Create(fetcher);
+  ExecuteGeneralBytecode(state_, erased, pc, end);
   if (output_register.index == std::numeric_limits<uint32_t>::max()) {
     return {};
   }
   return state_.ReadFromRegister(output_register);
 }
 
-#undef PERFETTO_OP_CASE_FVF
-#undef PERFETTO_OP_CASE_STATE
-
 }  // namespace perfetto::trace_processor::core::interpreter
 
 #endif  // SRC_TRACE_PROCESSOR_CORE_INTERPRETER_BYTECODE_INTERPRETER_IMPL_H_