Merge "tp: migrate all table functions to use Python based tables"
diff --git a/Android.bp b/Android.bp
index 9734c15..2077923 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2246,6 +2246,7 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
         "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_tables_python",
@@ -10069,6 +10070,43 @@
     ],
 }
 
+// GN: //src/trace_processor/prelude/table_functions:tables
+genrule {
+    name: "perfetto_src_trace_processor_prelude_table_functions_tables",
+    srcs: [
+        "src/trace_processor/prelude/table_functions/tables.py",
+    ],
+    tools: [
+        "perfetto_src_trace_processor_prelude_table_functions_tables_binary",
+    ],
+    cmd: "$(location perfetto_src_trace_processor_prelude_table_functions_tables_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
+    out: [
+        "src/trace_processor/prelude/table_functions/tables_py.h",
+    ],
+}
+
+// GN: //src/trace_processor/prelude/table_functions:tables
+python_binary_host {
+    name: "perfetto_src_trace_processor_prelude_table_functions_tables_binary",
+    srcs: [
+        "python/generators/trace_processor_table/public.py",
+        "python/generators/trace_processor_table/serialize.py",
+        "python/generators/trace_processor_table/util.py",
+        "src/trace_processor/prelude/table_functions/tables.py",
+        "src/trace_processor/tables/android_tables.py",
+        "src/trace_processor/tables/counter_tables.py",
+        "src/trace_processor/tables/flow_tables.py",
+        "src/trace_processor/tables/memory_tables.py",
+        "src/trace_processor/tables/metadata_tables.py",
+        "src/trace_processor/tables/profiler_tables.py",
+        "src/trace_processor/tables/slice_tables.py",
+        "src/trace_processor/tables/trace_proto_tables.py",
+        "src/trace_processor/tables/track_tables.py",
+        "tools/gen_tp_table_headers.py",
+    ],
+    main: "tools/gen_tp_table_headers.py",
+}
+
 // GN: //src/trace_processor/prelude/table_functions:unittests
 filegroup {
     name: "perfetto_src_trace_processor_prelude_table_functions_unittests",
@@ -10234,7 +10272,7 @@
     tools: [
         "perfetto_src_trace_processor_tables_py_tables_unittest_binary",
     ],
-    cmd: "$(location perfetto_src_trace_processor_tables_py_tables_unittest_binary) --gen-dir=$(genDir) --inputs $(in) --outputs $(out)",
+    cmd: "$(location perfetto_src_trace_processor_tables_py_tables_unittest_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
     out: [
         "src/trace_processor/tables/py_tables_unittest_py.h",
     ],
@@ -10278,7 +10316,7 @@
     tools: [
         "perfetto_src_trace_processor_tables_tables_python_binary",
     ],
-    cmd: "$(location perfetto_src_trace_processor_tables_tables_python_binary) --gen-dir=$(genDir) --inputs $(in) --outputs $(out)",
+    cmd: "$(location perfetto_src_trace_processor_tables_tables_python_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
     out: [
         "src/trace_processor/tables/android_tables_py.h",
         "src/trace_processor/tables/counter_tables_py.h",
@@ -12048,6 +12086,7 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
         "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_py_tables_unittest",
@@ -12565,6 +12604,7 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
         "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_tables_python",
@@ -12789,6 +12829,7 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
         "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_tables_python",
diff --git a/BUILD b/BUILD
index c30c684..6481aa5 100644
--- a/BUILD
+++ b/BUILD
@@ -1849,6 +1849,20 @@
     ],
 )
 
+# GN target: //src/trace_processor/prelude/table_functions:tables
+perfetto_cc_tp_tables(
+    name = "src_trace_processor_prelude_table_functions_tables",
+    srcs = [
+        "src/trace_processor/prelude/table_functions/tables.py",
+    ],
+    deps = [
+        ":src_trace_processor_tables_tables_python",
+    ],
+    outs = [
+        "src/trace_processor/prelude/table_functions/tables_py.h",
+    ],
+)
+
 # GN target: //src/trace_processor/prelude/tables_views:sources
 perfetto_filegroup(
     name = "src_trace_processor_prelude_tables_views_sources",
@@ -2023,11 +2037,8 @@
 perfetto_filegroup(
     name = "src_trace_processor_tables_tables",
     srcs = [
-        "src/trace_processor/tables/counter_tables.h",
-        "src/trace_processor/tables/flow_tables.h",
         "src/trace_processor/tables/macros.h",
         "src/trace_processor/tables/macros_internal.h",
-        "src/trace_processor/tables/profiler_tables.h",
         "src/trace_processor/tables/slice_tables.h",
         "src/trace_processor/tables/table_destructors.cc",
     ],
@@ -4736,6 +4747,7 @@
         ":src_trace_processor_prelude_functions_functions",
         ":src_trace_processor_prelude_operators_operators",
         ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
         ":src_trace_processor_sorter_sorter",
         ":src_trace_processor_sqlite_sqlite",
         ":src_trace_processor_sqlite_sqlite_minimal",
@@ -4889,6 +4901,7 @@
         ":src_trace_processor_prelude_functions_functions",
         ":src_trace_processor_prelude_operators_operators",
         ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
         ":src_trace_processor_rpc_httpd",
         ":src_trace_processor_rpc_rpc",
         ":src_trace_processor_sorter_sorter",
@@ -5101,6 +5114,7 @@
         ":src_trace_processor_prelude_functions_functions",
         ":src_trace_processor_prelude_operators_operators",
         ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
         ":src_trace_processor_sorter_sorter",
         ":src_trace_processor_sqlite_sqlite",
         ":src_trace_processor_sqlite_sqlite_minimal",
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 4021c58..7a626c1 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -308,7 +308,7 @@
         **kwargs,
     )
 
-def perfetto_cc_tp_tables(name, srcs, outs, **kwargs):
+def perfetto_cc_tp_tables(name, srcs, outs, deps = [], **kwargs):
     if PERFETTO_CONFIG.root[:2] != "//":
         fail("Expected PERFETTO_CONFIG.root to start with //")
 
@@ -317,12 +317,21 @@
     else:
         python_path = PERFETTO_CONFIG.root + "/python"
 
-    perfetto_py_binary(
-        name = name + "_tool",
+    perfetto_py_library(
+        name = name + "_lib",
         deps = [
             python_path + ":trace_processor_table_generator",
         ],
-        srcs = srcs + [
+        srcs = srcs,
+    )
+
+    perfetto_py_binary(
+        name = name + "_tool",
+        deps = [
+            ":" + name + "_lib",
+            python_path + ":trace_processor_table_generator",
+        ] + [d + "_lib" for d in deps],
+        srcs = [
             "tools/gen_tp_table_headers.py",
         ],
         main = "tools/gen_tp_table_headers.py",
@@ -332,7 +341,6 @@
     cmd = ["$(location " + name + "_tool)"]
     cmd += ["--gen-dir", "$(RULEDIR)"]
     cmd += ["--inputs", "$(SRCS)"]
-    cmd += ["--outputs", "$(OUTS)"]
     if PERFETTO_CONFIG.root != "//":
         cmd += ["--header-prefix", PERFETTO_CONFIG.root[2:]]
 
diff --git a/gn/perfetto_tp_tables.gni b/gn/perfetto_tp_tables.gni
index da8cfae..0a9d902 100644
--- a/gn/perfetto_tp_tables.gni
+++ b/gn/perfetto_tp_tables.gni
@@ -17,6 +17,10 @@
 template("perfetto_tp_tables") {
   config_name = target_name + "_config"
   action_name = target_name
+  relative_args = [
+    "--relative-input-dir",
+    rebase_path(perfetto_root_path, root_build_dir),
+  ]
 
   config(config_name) {
     include_dirs = [ root_gen_dir ]
@@ -32,6 +36,9 @@
     }
 
     deps = [ "$perfetto_root_path/python:trace_processor_table_generator" ]
+    if (defined(invoker.deps)) {
+      deps += invoker.deps
+    }
     public_configs = [ ":$config_name" ]
 
     gen_args = [
@@ -39,8 +46,7 @@
       rebase_path("$root_gen_dir", root_build_dir),
     ]
     input_args = [ "--inputs" ] + rebase_path(invoker.sources, root_build_dir)
-    output_args = [ "--outputs" ] + rebase_path(outputs, root_build_dir)
-    args = gen_args + input_args + output_args
+    args = gen_args + input_args + relative_args
 
     metadata = {
       perfetto_action_type_for_generator = [ "tp_tables" ]
@@ -56,7 +62,7 @@
       deps = [ "$perfetto_root_path/python:trace_processor_table_generator" ]
       outputs = [ "$target_gen_dir/$docs_name.json" ]
       args = [ "--out" ] + rebase_path(outputs, root_build_dir) +
-             rebase_path(invoker.sources, root_build_dir)
+             rebase_path(invoker.sources, root_build_dir) + relative_args
     }
   }
 }
diff --git a/python/generators/trace_processor_table/public.py b/python/generators/trace_processor_table/public.py
index abd0aec..c8bdb0b 100644
--- a/python/generators/trace_processor_table/public.py
+++ b/python/generators/trace_processor_table/public.py
@@ -119,6 +119,8 @@
   Representation of of a C++ table.
 
   Attributes:
+    python_module: Path to the Python module this table is defined in. Always
+    pass __file__.
     class_name: Name of the C++ table class.
     sql_name: Name of the table in SQL.
     columns: The columns in this table.
@@ -129,6 +131,7 @@
     specified table.
     wrapping_sql_view: See |WrappingSqlView|.
   """
+  python_module: str
   class_name: str
   sql_name: str
   columns: List[Column]
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index 2636733..875b3de 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -19,6 +19,8 @@
 from python.generators.trace_processor_table.public import ColumnFlag
 from python.generators.trace_processor_table.util import ParsedTable
 from python.generators.trace_processor_table.util import ParsedColumn
+from python.generators.trace_processor_table.util import parse_type
+from python.generators.trace_processor_table.util import typed_column_type
 
 
 class ColumnSerializer:
@@ -30,8 +32,9 @@
     self.col = self.parsed_col.column
     self.name = self.col.name
     self.flags = self.col.flags
-    self.typed_column_type = table.typed_column_type(self.parsed_col)
-    self.cpp_type = table.parse_type(self.col.type).cpp_type_with_optionality()
+    self.typed_column_type = typed_column_type(table.table, self.parsed_col)
+    self.cpp_type = parse_type(table.table,
+                               self.col.type).cpp_type_with_optionality()
 
     self.is_implicit_id = self.parsed_col.is_implicit_id
     self.is_implicit_type = self.parsed_col.is_implicit_type
@@ -180,11 +183,11 @@
     if self.is_implicit_id or self.is_implicit_type:
       return None
     return f'''
-      schema.columns.emplace_back(Table::Schema::Column{{
-          "{self.name}", ColumnType::{self.name}::SqlValueType(), false,
-          {str(ColumnFlag.SORTED in self.flags).lower()},
-          {str(ColumnFlag.HIDDEN in self.flags).lower()},
-          {str(ColumnFlag.SET_ID in self.flags).lower()}}});
+    schema.columns.emplace_back(Table::Schema::Column{{
+        "{self.name}", ColumnType::{self.name}::SqlValueType(), false,
+        {str(ColumnFlag.SORTED in self.flags).lower()},
+        {str(ColumnFlag.HIDDEN in self.flags).lower()},
+        {str(ColumnFlag.SET_ID in self.flags).lower()}}});
     '''
 
   def row_eq(self) -> Optional[str]:
@@ -192,6 +195,42 @@
       return None
     return f'ColumnType::{self.name}::Equals({self.name}, other.{self.name})'
 
+  def extend_parent_param(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'ColumnStorage<ColumnType::{self.name}::stored_type> {self.name}'
+
+  def extend_parent_param_arg(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'std::move({self.name})'
+
+  def static_assert_flags(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'''
+      static_assert(
+        Column::IsFlagsAndTypeValid<ColumnType::{self.name}::stored_type>(
+          ColumnFlag::{self.name}),
+        "Column type and flag combination is not valid");
+    '''
+
+  def extend_nullable_vector(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'''
+    PERFETTO_DCHECK({self.name}.size() == parent_overlay.size());
+    {self.name}_ = std::move({self.name});
+    '''
+
 
 class TableSerializer(object):
   """Functions for seralizing a single Table into C++."""
@@ -317,6 +356,7 @@
   explicit {self.table_name}(StringPool* pool{parent_param})
       : macros_internal::MacroTable(pool, {parent_arg}),
         {parent_init}{storage_init} {{
+    {self.foreach_col(ColumnSerializer.static_assert_flags)}
     {olay}
     {col_init}
   }}
@@ -389,6 +429,60 @@
   }};
       '''
 
+  def extend(self) -> str:
+    if not self.table.parent:
+      return ''
+    params = self.foreach_col(
+        ColumnSerializer.extend_parent_param, delimiter='\n, ')
+    args = self.foreach_col(
+        ColumnSerializer.extend_parent_param_arg, delimiter=', ')
+    delim = ',' if params else ''
+    return f'''
+  static std::unique_ptr<Table> ExtendParent(
+      const {self.parent_class_name}& parent{delim}
+      {params}) {{
+    return std::unique_ptr<Table>(new {self.table_name}(
+        parent.string_pool(), parent, RowMap(0, parent.row_count()){delim}
+        {args}));
+  }}
+
+  static std::unique_ptr<Table> SelectAndExtendParent(
+      const {self.parent_class_name}& parent,
+      std::vector<{self.parent_class_name}::RowNumber> parent_overlay{delim}
+      {params}) {{
+    std::vector<uint32_t> prs_untyped(parent_overlay.size());
+    for (uint32_t i = 0; i < parent_overlay.size(); ++i) {{
+      prs_untyped[i] = parent_overlay[i].row_number();
+    }}
+    return std::unique_ptr<Table>(new {self.table_name}(
+        parent.string_pool(), parent, RowMap(std::move(prs_untyped)){delim}
+        {args}));
+  }}
+    '''
+
+  def extend_constructor(self) -> str:
+    if not self.table.parent:
+      return ''
+    params = self.foreach_col(
+        ColumnSerializer.extend_parent_param, delimiter='\n, ')
+    if params:
+      olay = 'uint32_t olay_idx = static_cast<uint32_t>(overlays_.size()) - 1;'
+    else:
+      olay = ''
+    return f'''
+  {self.table_name}(StringPool* pool,
+            const {self.parent_class_name}& parent,
+            const RowMap& parent_overlay{',' if params else ''}
+            {params})
+      : macros_internal::MacroTable(pool, parent, parent_overlay) {{
+    {self.foreach_col(ColumnSerializer.static_assert_flags)}
+    {self.foreach_col(ColumnSerializer.extend_nullable_vector)}
+
+    {olay}
+    {self.foreach_col(ColumnSerializer.column_init)}
+  }}
+    '''
+
   def serialize(self) -> str:
     return f'''
 class {self.table_name} : public macros_internal::MacroTable {{
@@ -488,11 +582,14 @@
                      RowNumber(row_number)}};
   }}
 
+  {self.extend().strip()}
+
   {self.foreach_col(ColumnSerializer.accessor)}
 
   {self.foreach_col(ColumnSerializer.mutable_accessor)}
 
  private:
+  {self.extend_constructor().strip()}
   {self.parent_field().strip()}
   {self.foreach_col(ColumnSerializer.storage)}
 }};
diff --git a/python/generators/trace_processor_table/util.py b/python/generators/trace_processor_table/util.py
index 015f02c..1f0dc0c 100644
--- a/python/generators/trace_processor_table/util.py
+++ b/python/generators/trace_processor_table/util.py
@@ -14,7 +14,8 @@
 
 import dataclasses
 from dataclasses import dataclass
-import runpy
+import importlib
+import sys
 from typing import Dict
 from typing import List
 from typing import Set
@@ -92,71 +93,78 @@
 
   table: Table
   columns: List[ParsedColumn]
-  input_path: str
 
-  def parse_type(self, col_type: CppColumnType) -> ParsedType:
-    """Parses a CppColumnType into its constiuent parts."""
 
-    if isinstance(col_type, CppInt64):
-      return ParsedType('int64_t')
-    if isinstance(col_type, CppInt32):
-      return ParsedType('int32_t')
-    if isinstance(col_type, CppUint32):
-      return ParsedType('uint32_t')
-    if isinstance(col_type, CppDouble):
-      return ParsedType('double')
-    if isinstance(col_type, CppString):
-      return ParsedType('StringPool::Id')
+def parse_type_with_cols(table: Table, cols: List[Column],
+                         col_type: CppColumnType) -> ParsedType:
+  """Parses a CppColumnType into its constiuent parts."""
 
-    if isinstance(col_type, Alias):
-      col = next(c for c in self.columns
-                 if c.column.name == col_type.underlying_column)
-      return ParsedType(
-          self.parse_type(col.column.type).cpp_type,
-          is_alias=True,
-          alias_underlying_name=col.column.name)
+  if isinstance(col_type, CppInt64):
+    return ParsedType('int64_t')
+  if isinstance(col_type, CppInt32):
+    return ParsedType('int32_t')
+  if isinstance(col_type, CppUint32):
+    return ParsedType('uint32_t')
+  if isinstance(col_type, CppDouble):
+    return ParsedType('double')
+  if isinstance(col_type, CppString):
+    return ParsedType('StringPool::Id')
 
-    if isinstance(col_type, CppTableId):
-      return ParsedType(
-          f'{col_type.table.class_name}::Id', id_table=col_type.table)
+  if isinstance(col_type, Alias):
+    col = next(c for c in cols if c.name == col_type.underlying_column)
+    return ParsedType(
+        parse_type(table, col.type).cpp_type,
+        is_alias=True,
+        alias_underlying_name=col.name)
 
-    if isinstance(col_type, CppSelfTableId):
-      return ParsedType(
-          f'{self.table.class_name}::Id', is_self_id=True, id_table=self.table)
+  if isinstance(col_type, CppTableId):
+    return ParsedType(
+        f'{col_type.table.class_name}::Id', id_table=col_type.table)
 
-    if isinstance(col_type, CppOptional):
-      inner = self.parse_type(col_type.inner)
-      assert not inner.is_optional, 'Nested optional not allowed'
-      return dataclasses.replace(inner, is_optional=True)
+  if isinstance(col_type, CppSelfTableId):
+    return ParsedType(
+        f'{table.class_name}::Id', is_self_id=True, id_table=table)
 
-    raise Exception(f'Unknown type {col_type}')
+  if isinstance(col_type, CppOptional):
+    inner = parse_type(table, col_type.inner)
+    assert not inner.is_optional, 'Nested optional not allowed'
+    return dataclasses.replace(inner, is_optional=True)
 
-  def typed_column_type(self, col: ParsedColumn) -> str:
-    """Returns the TypedColumn/IdColumn C++ type for a given column."""
+  raise Exception(f'Unknown type {col_type}')
 
-    parsed = self.parse_type(col.column.type)
-    if col.is_implicit_id:
-      return f'IdColumn<{parsed.cpp_type}>'
-    return f'TypedColumn<{parsed.cpp_type_with_optionality()}>'
 
-  def find_table_deps(self) -> Set[str]:
-    """Finds all the other table class names this table depends on.
+def parse_type(table: Table, col_type: CppColumnType) -> ParsedType:
+  """Parses a CppColumnType into its constiuent parts."""
+  return parse_type_with_cols(table, table.columns, col_type)
 
-    By "depends", we mean this table in C++ would need the dependency to be
-    defined (or included) before this table is defined."""
 
-    deps: Set[str] = set()
-    if self.table.parent:
-      deps.add(self.table.parent.class_name)
-    for c in self.table.columns:
-      # Aliases cannot have dependencies so simply ignore them: trying to parse
-      # them before adding implicit columns can cause issues.
-      if isinstance(c.type, Alias):
-        continue
-      id_table = self.parse_type(c.type).id_table
-      if id_table:
-        deps.add(id_table.class_name)
-    return deps
+def typed_column_type(table: Table, col: ParsedColumn) -> str:
+  """Returns the TypedColumn/IdColumn C++ type for a given column."""
+
+  parsed = parse_type(table, col.column.type)
+  if col.is_implicit_id:
+    return f'IdColumn<{parsed.cpp_type}>'
+  return f'TypedColumn<{parsed.cpp_type_with_optionality()}>'
+
+
+def find_table_deps(table: Table) -> List[Table]:
+  """Finds all the other table class names this table depends on.
+
+  By "depends", we mean this table in C++ would need the dependency to be
+  defined (or included) before this table is defined."""
+
+  deps: Dict[str, Table] = {}
+  if table.parent:
+    deps[table.parent.class_name] = table.parent
+  for c in table.columns:
+    # Aliases cannot have dependencies so simply ignore them: trying to parse
+    # them before adding implicit columns can cause issues.
+    if isinstance(c.type, Alias):
+      continue
+    id_table = parse_type(table, c.type).id_table
+    if id_table:
+      deps[id_table.class_name] = id_table
+  return list(deps.values())
 
 
 def public_sql_name(table: Table) -> str:
@@ -165,10 +173,9 @@
   wrapping_view = table.wrapping_sql_view
   return wrapping_view.view_name if wrapping_view else table.sql_name
 
-def _create_implicit_columns_for_root(parsed: ParsedTable
-                                     ) -> List[ParsedColumn]:
+
+def _create_implicit_columns_for_root(table: Table) -> List[ParsedColumn]:
   """Given a root table, returns the implicit id and type columns."""
-  table = parsed.table
   assert table.parent is None
 
   sql_name = public_sql_name(table)
@@ -191,26 +198,25 @@
   ]
 
 
-def _topological_sort_tables(parsed: List[ParsedTable]) -> List[ParsedTable]:
+def _topological_sort_table_and_deps(parsed: List[Table]) -> List[Table]:
   """Topologically sorts a list of tables (i.e. dependenices appear earlier).
 
   See [1] for information on a topological sort. We do this to allow
   dependencies to be processed and appear ealier than their dependents.
 
   [1] https://en.wikipedia.org/wiki/Topological_sorting"""
-  table_to_parsed_table = {p.table.class_name: p for p in parsed}
   visited: Set[str] = set()
-  result: List[ParsedTable] = []
+  result: List[Table] = []
 
   # Topological sorting is really just a DFS where we put the nodes in the list
   # after any dependencies.
-  def dfs(t: ParsedTable):
-    if t.table.class_name in visited:
+  def dfs(t: Table):
+    if t.class_name in visited:
       return
-    visited.add(t.table.class_name)
+    visited.add(t.class_name)
 
-    for dep in t.find_table_deps():
-      dfs(table_to_parsed_table[dep])
+    for dep in find_table_deps(t):
+      dfs(dep)
     result.append(t)
 
   for p in parsed:
@@ -226,26 +232,27 @@
   return ColumnDoc(doc=doc)
 
 
-def parse_tables_from_files(input_paths: List[str]) -> List[ParsedTable]:
+def parse_tables_from_modules(modules: List[str]) -> List[ParsedTable]:
   """Creates a list of tables with the associated paths."""
 
   # Create a mapping from the table to a "parsed" version of the table.
+  tables: Dict[str, Table] = {}
+  for module in modules:
+    imported = importlib.import_module(module)
+    run_tables: List[Table] = imported.__dict__['ALL_TABLES']
+    for table in run_tables:
+      existing_table = tables.get(table.class_name)
+      assert not existing_table or existing_table == table
+      tables[table.class_name] = table
+
+  # Sort all the tables: note that this list may include tables which are not
+  # in |tables| dictionary due to dependencies on tables which live in a file
+  # not covered by |input_paths|.
+  sorted_tables = _topological_sort_table_and_deps(list(tables.values()))
+
   parsed_tables: Dict[str, ParsedTable] = {}
-  for in_path in input_paths:
-    tables: List[Table] = runpy.run_path(in_path)['ALL_TABLES']
-    for table in tables:
-      existing_table = parsed_tables.get(table.class_name)
-      assert not existing_table or existing_table.table == table
-      parsed_tables[table.class_name] = ParsedTable(table, [], in_path)
-
-  # Sort all the tables to be in order.
-  sorted_tables = _topological_sort_tables(list(parsed_tables.values()))
-
-  # Create the list of parsed columns
-  for i, parsed in enumerate(sorted_tables):
+  for table in sorted_tables:
     parsed_columns: List[ParsedColumn]
-    table = parsed.table
-
     if table.parent:
       parsed_parent = parsed_tables[table.parent.class_name]
       parsed_columns = [
@@ -253,13 +260,17 @@
           for c in parsed_parent.columns
       ]
     else:
-      parsed_columns = _create_implicit_columns_for_root(parsed)
+      parsed_columns = _create_implicit_columns_for_root(table)
 
     for c in table.columns:
       doc = table.tabledoc.columns.get(c.name) if table.tabledoc else None
       parsed_columns.append(ParsedColumn(c, _to_column_doc(doc)))
+    parsed_tables[table.class_name] = ParsedTable(table, parsed_columns)
 
-    sorted_tables[i] = dataclasses.replace(parsed, columns=parsed_columns)
-    parsed_tables[parsed.table.class_name] = sorted_tables[i]
-
-  return sorted_tables
+  # Only return tables which come directly from |input_paths|. This stops us
+  # generating tables which were not requested.
+  return [
+      parsed_tables[p.class_name]
+      for p in sorted_tables
+      if p.class_name in tables
+  ]
diff --git a/src/trace_processor/prelude/table_functions/BUILD.gn b/src/trace_processor/prelude/table_functions/BUILD.gn
index 4f5ced3..1125c22 100644
--- a/src/trace_processor/prelude/table_functions/BUILD.gn
+++ b/src/trace_processor/prelude/table_functions/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../../gn/perfetto.gni")
+import("../../../../gn/perfetto_tp_tables.gni")
 
 assert(enable_perfetto_trace_processor_sqlite)
 
@@ -44,6 +45,7 @@
     "view.h",
   ]
   deps = [
+    ":tables",
     "../../../../gn:default_deps",
     "../../../../gn:sqlite",
     "../../../base",
@@ -59,6 +61,11 @@
   ]
 }
 
+perfetto_tp_tables("tables") {
+  sources = [ "tables.py" ]
+  deps = [ "../../tables:tables_python" ]
+}
+
 source_set("unittests") {
   testonly = true
   sources = [
@@ -71,6 +78,7 @@
   ]
   deps = [
     ":table_functions",
+    ":tables",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
     "../../containers",
diff --git a/src/trace_processor/prelude/table_functions/ancestor.cc b/src/trace_processor/prelude/table_functions/ancestor.cc
index c1656d9..9089b82 100644
--- a/src/trace_processor/prelude/table_functions/ancestor.cc
+++ b/src/trace_processor/prelude/table_functions/ancestor.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <set>
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/status_macros.h"
diff --git a/src/trace_processor/prelude/table_functions/ancestor.h b/src/trace_processor/prelude/table_functions/ancestor.h
index c625f16..755a482 100644
--- a/src/trace_processor/prelude/table_functions/ancestor.h
+++ b/src/trace_processor/prelude/table_functions/ancestor.h
@@ -16,40 +16,14 @@
 
 #ifndef SRC_TRACE_PROCESSOR_PRELUDE_TABLE_FUNCTIONS_ANCESTOR_H_
 #define SRC_TRACE_PROCESSOR_PRELUDE_TABLE_FUNCTIONS_ANCESTOR_H_
+
 #include <optional>
 
 #include "src/trace_processor/prelude/table_functions/table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables.h"
-#include "src/trace_processor/tables/slice_tables.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_ANCESTOR_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(AncestorSliceTable, "ancestor_slice")                  \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                      \
-  C(tables::SliceTable::Id, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANCESTOR_SLICE_TABLE_DEF);
-
-#define PERFETTO_TP_ANCESTOR_STACK_PROFILE_CALLSITE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(AncestorStackProfileCallsiteTable,                                      \
-       "experimental_ancestor_stack_profile_callsite")                         \
-  PARENT(PERFETTO_TP_STACK_PROFILE_CALLSITE_DEF, C)                            \
-  C(tables::StackProfileCallsiteTable::Id, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANCESTOR_STACK_PROFILE_CALLSITE_TABLE_DEF);
-
-#define PERFETTO_TP_ANCESTOR_SLICE_BY_STACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(AncestorSliceByStackTable, "ancestor_slice_by_stack")           \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                               \
-  C(int64_t, start_stack_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANCESTOR_SLICE_BY_STACK_TABLE_DEF);
-
-}  // namespace tables
 
 class TraceProcessorContext;
 
diff --git a/src/trace_processor/prelude/table_functions/ancestor_unittest.cc b/src/trace_processor/prelude/table_functions/ancestor_unittest.cc
index 08fdad4..afc2d33 100644
--- a/src/trace_processor/prelude/table_functions/ancestor_unittest.cc
+++ b/src/trace_processor/prelude/table_functions/ancestor_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/prelude/table_functions/ancestor.h"
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/table_functions/connected_flow.h b/src/trace_processor/prelude/table_functions/connected_flow.h
index c05e25e..3ef3fc3 100644
--- a/src/trace_processor/prelude/table_functions/connected_flow.h
+++ b/src/trace_processor/prelude/table_functions/connected_flow.h
@@ -18,24 +18,14 @@
 #define SRC_TRACE_PROCESSOR_PRELUDE_TABLE_FUNCTIONS_CONNECTED_FLOW_H_
 
 #include "src/trace_processor/prelude/table_functions/table_function.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/flow_tables.h"
 
 #include <queue>
 #include <set>
 
 namespace perfetto {
 namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_CONNECTED_FLOW_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ConnectedFlowTable, "not_exposed_to_sql")              \
-  PARENT(PERFETTO_TP_FLOW_TABLE_DEF, C)                       \
-  C(uint32_t, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_CONNECTED_FLOW_TABLE_DEF);
-
-}  // namespace tables
 
 class TraceProcessorContext;
 
diff --git a/src/trace_processor/prelude/table_functions/descendant.cc b/src/trace_processor/prelude/table_functions/descendant.cc
index 75cb8c9..382edf2 100644
--- a/src/trace_processor/prelude/table_functions/descendant.cc
+++ b/src/trace_processor/prelude/table_functions/descendant.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <set>
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/status_macros.h"
diff --git a/src/trace_processor/prelude/table_functions/descendant.h b/src/trace_processor/prelude/table_functions/descendant.h
index 8e93811..bffd9b5 100644
--- a/src/trace_processor/prelude/table_functions/descendant.h
+++ b/src/trace_processor/prelude/table_functions/descendant.h
@@ -24,23 +24,6 @@
 
 namespace perfetto {
 namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_DESCENDANT_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(DescendantSliceTable, "descendant_slice")                \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                        \
-  C(uint32_t, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_DESCENDANT_SLICE_TABLE_DEF);
-
-#define PERFETTO_TP_DESCENDANT_SLICE_BY_STACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(DescendantSliceByStackTable, "descendant_slice_by_stack")         \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                                 \
-  C(int64_t, start_stack_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_DESCENDANT_SLICE_BY_STACK_TABLE_DEF);
-
-}  // namespace tables
 
 class TraceProcessorContext;
 
diff --git a/src/trace_processor/prelude/table_functions/descendant_unittest.cc b/src/trace_processor/prelude/table_functions/descendant_unittest.cc
index f1ef1ca..67399d1 100644
--- a/src/trace_processor/prelude/table_functions/descendant_unittest.cc
+++ b/src/trace_processor/prelude/table_functions/descendant_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/prelude/table_functions/descendant.h"
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc b/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc
index 8198376..09363f2 100644
--- a/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc
@@ -19,24 +19,15 @@
 #include <optional>
 
 #include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
 
-#define PERFETTO_TP_ANNOTATED_CALLSTACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ExperimentalAnnotatedCallstackTable,                        \
-       "experimental_annotated_callstack")                         \
-  PARENT(PERFETTO_TP_STACK_PROFILE_CALLSITE_DEF, C)                \
-  C(StringId, annotation)                                          \
-  C(tables::StackProfileCallsiteTable::Id, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANNOTATED_CALLSTACK_TABLE_DEF);
-
 ExperimentalAnnotatedCallstackTable::~ExperimentalAnnotatedCallstackTable() =
     default;
 
diff --git a/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc b/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc
index 3513c89..a7d9814 100644
--- a/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc
@@ -16,20 +16,12 @@
 
 #include "src/trace_processor/prelude/table_functions/experimental_counter_dur.h"
 
-#include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
 
-#define PERFETTO_TP_COUNTER_DUR_TABLE_DEF(NAME, PARENT, C)      \
-  NAME(ExperimentalCounterDurTable, "experimental_counter_dur") \
-  PARENT(PERFETTO_TP_COUNTER_TABLE_DEF, C)                      \
-  C(int64_t, dur)                                               \
-  C(double, delta)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_COUNTER_DUR_TABLE_DEF);
-
 ExperimentalCounterDurTable::~ExperimentalCounterDurTable() = default;
 
 }  // namespace tables
diff --git a/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc b/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc
index 45a76ea..ba3a0c6 100644
--- a/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc
@@ -16,17 +16,14 @@
 
 #include "src/trace_processor/prelude/table_functions/experimental_sched_upid.h"
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
+
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
-#define PERFETTO_TP_SCHED_UPID_TABLE_DEF(NAME, PARENT, C)     \
-  NAME(ExperimentalSchedUpidTable, "experimental_sched_upid") \
-  PARENT(PERFETTO_TP_SCHED_SLICE_TABLE_DEF, C)                \
-  C(std::optional<UniquePid>, upid)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_SCHED_UPID_TABLE_DEF);
 
 ExperimentalSchedUpidTable::~ExperimentalSchedUpidTable() = default;
+
 }  // namespace tables
 
 ExperimentalSchedUpid::ExperimentalSchedUpid(
diff --git a/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc b/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc
index e544dbb..d6276dc 100644
--- a/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc
@@ -20,11 +20,13 @@
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
+
 ExperimentalSliceLayoutTable::~ExperimentalSliceLayoutTable() = default;
 }
 
diff --git a/src/trace_processor/prelude/table_functions/experimental_slice_layout.h b/src/trace_processor/prelude/table_functions/experimental_slice_layout.h
index 19af803..3aa61bb 100644
--- a/src/trace_processor/prelude/table_functions/experimental_slice_layout.h
+++ b/src/trace_processor/prelude/table_functions/experimental_slice_layout.h
@@ -25,18 +25,6 @@
 namespace perfetto {
 namespace trace_processor {
 
-namespace tables {
-
-#define PERFETTO_TP_SLICE_LAYOUT_TABLE_DEF(NAME, PARENT, C)       \
-  NAME(ExperimentalSliceLayoutTable, "experimental_slice_layout") \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                          \
-  C(uint32_t, layout_depth)                                       \
-  C(StringPool::Id, filter_track_ids, Column::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_SLICE_LAYOUT_TABLE_DEF);
-
-}  // namespace tables
-
 class ExperimentalSliceLayout : public TableFunction {
  public:
   ExperimentalSliceLayout(StringPool* string_pool,
diff --git a/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc b/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc
index 43c986d..164dfbb 100644
--- a/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 
 #include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/table_functions/tables.py b/src/trace_processor/prelude/table_functions/tables.py
new file mode 100644
index 0000000..4ffc526
--- /dev/null
+++ b/src/trace_processor/prelude/table_functions/tables.py
@@ -0,0 +1,166 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains tables for finding ancestor events."""
+
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import CppDouble
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import Table
+
+from src.trace_processor.tables.counter_tables import COUNTER_TABLE
+from src.trace_processor.tables.flow_tables import FLOW_TABLE
+from src.trace_processor.tables.metadata_tables import PROCESS_TABLE
+from src.trace_processor.tables.profiler_tables import STACK_PROFILE_CALLSITE_TABLE
+from src.trace_processor.tables.slice_tables import SLICE_TABLE
+from src.trace_processor.tables.slice_tables import SCHED_SLICE_TABLE
+
+ANCESTOR_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name="AncestorSliceTable",
+    sql_name="ancestor_slice",
+    columns=[
+        C("start_id", CppTableId(SLICE_TABLE), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+ANCESTOR_SLICE_BY_STACK_TABLE = Table(
+    python_module=__file__,
+    class_name="AncestorSliceByStackTable",
+    sql_name="ancestor_slice_by_stack",
+    columns=[
+        C("start_stack_id", CppInt64(), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+ANCESTOR_STACK_PROFILE_CALLSITE_TABLE = Table(
+    python_module=__file__,
+    class_name="AncestorStackProfileCallsiteTable",
+    sql_name="experimental_ancestor_stack_profile_callsite",
+    columns=[
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+CONNECTED_FLOW_TABLE = Table(
+    python_module=__file__,
+    class_name="ConnectedFlowTable",
+    sql_name="not_exposed_to_sql",
+    columns=[
+        C("start_id", CppTableId(SLICE_TABLE), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=FLOW_TABLE)
+
+DESCENDANT_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name="DescendantSliceTable",
+    sql_name="descendant_slice",
+    columns=[
+        C("start_id", CppTableId(SLICE_TABLE), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+DESCENDANT_SLICE_BY_STACK_TABLE = Table(
+    python_module=__file__,
+    class_name="DescendantSliceByStackTable",
+    sql_name="descendant_slice_by_stack",
+    columns=[
+        C("start_stack_id", CppInt64(), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalAnnotatedCallstackTable",
+    sql_name="experimental_annotated_callstack",
+    columns=[
+        C("annotation", CppString()),
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalAnnotatedCallstackTable",
+    sql_name="experimental_annotated_callstack",
+    columns=[
+        C("annotation", CppString()),
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalAnnotatedCallstackTable",
+    sql_name="experimental_annotated_callstack",
+    columns=[
+        C("annotation", CppString()),
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+EXPERIMENTAL_COUNTER_DUR_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalCounterDurTable",
+    sql_name="experimental_counter_dur",
+    columns=[
+        C("dur", CppInt64()),
+        C("delta", CppDouble()),
+    ],
+    parent=COUNTER_TABLE)
+
+EXPERIMENTAL_SCHED_UPID_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalSchedUpidTable",
+    sql_name="experimental_sched_upid",
+    columns=[
+        C("upid", CppOptional(CppTableId(PROCESS_TABLE))),
+    ],
+    parent=SCHED_SLICE_TABLE)
+
+EXPERIMENTAL_SLICE_LAYOUT_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalSliceLayoutTable",
+    sql_name="experimental_slice_layout",
+    columns=[
+        C("layout_depth", CppUint32()),
+        C("filter_track_ids", CppString(), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+# Keep this list sorted.
+ALL_TABLES = [
+    ANCESTOR_SLICE_BY_STACK_TABLE,
+    ANCESTOR_SLICE_TABLE,
+    ANCESTOR_STACK_PROFILE_CALLSITE_TABLE,
+    CONNECTED_FLOW_TABLE,
+    DESCENDANT_SLICE_BY_STACK_TABLE,
+    DESCENDANT_SLICE_TABLE,
+    EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE,
+    EXPERIMENTAL_COUNTER_DUR_TABLE,
+    EXPERIMENTAL_SCHED_UPID_TABLE,
+    EXPERIMENTAL_SLICE_LAYOUT_TABLE,
+]
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 83145a4..ef39e08 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -32,11 +32,8 @@
 
 source_set("tables") {
   sources = [
-    "counter_tables.h",
-    "flow_tables.h",
     "macros.h",
     "macros_internal.h",
-    "profiler_tables.h",
     "slice_tables.h",
     "table_destructors.cc",
   ]
diff --git a/src/trace_processor/tables/android_tables.py b/src/trace_processor/tables/android_tables.py
index b77afcb..03dff07 100644
--- a/src/trace_processor/tables/android_tables.py
+++ b/src/trace_processor/tables/android_tables.py
@@ -28,6 +28,7 @@
 from src.trace_processor.tables.metadata_tables import THREAD_TABLE
 
 ANDROID_LOG_TABLE = Table(
+    python_module=__file__,
     class_name="AndroidLogTable",
     sql_name="android_logs",
     columns=[
@@ -54,6 +55,7 @@
         }))
 
 ANDROID_GAME_INTERVENTION_LIST_TABLE = Table(
+    python_module=__file__,
     class_name='AndroidGameInterventionListTable',
     sql_name='android_game_intervention_list',
     columns=[
@@ -126,6 +128,7 @@
         }))
 
 ANDROID_DUMPSTATE_TABLE = Table(
+    python_module=__file__,
     class_name='AndroidDumpstateTable',
     sql_name='android_dumpstate',
     columns=[
diff --git a/src/trace_processor/tables/counter_tables.h b/src/trace_processor/tables/counter_tables.h
deleted file mode 100644
index 69668b2..0000000
--- a/src/trace_processor/tables/counter_tables.h
+++ /dev/null
@@ -1,41 +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_TABLES_COUNTER_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_COUNTER_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/track_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// @tablegroup Events
-// @param arg_set_id {@joinable args.arg_set_id}
-#define PERFETTO_TP_COUNTER_TABLE_DEF(NAME, PARENT, C) \
-  NAME(CounterTable, "counter")                        \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                    \
-  C(int64_t, ts, Column::Flag::kSorted)                \
-  C(CounterTrackTable::Id, track_id)                   \
-  C(double, value)                                     \
-  C(std::optional<uint32_t>, arg_set_id)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_COUNTER_TABLES_H_
diff --git a/src/trace_processor/tables/counter_tables.py b/src/trace_processor/tables/counter_tables.py
index 1dd6495..e882452 100644
--- a/src/trace_processor/tables/counter_tables.py
+++ b/src/trace_processor/tables/counter_tables.py
@@ -26,6 +26,7 @@
 from src.trace_processor.tables.track_tables import COUNTER_TRACK_TABLE
 
 COUNTER_TABLE = Table(
+    python_module=__file__,
     class_name='CounterTable',
     sql_name='counter',
     columns=[
diff --git a/src/trace_processor/tables/flow_tables.h b/src/trace_processor/tables/flow_tables.h
deleted file mode 100644
index a00ee89..0000000
--- a/src/trace_processor/tables/flow_tables.h
+++ /dev/null
@@ -1,39 +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_TABLES_FLOW_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/slice_tables.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// @param arg_set_id {@joinable args.arg_set_id}
-#define PERFETTO_TP_FLOW_TABLE_DEF(NAME, PARENT, C) \
-  NAME(FlowTable, "flow")                           \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                 \
-  C(SliceTable::Id, slice_out)                      \
-  C(SliceTable::Id, slice_in)                       \
-  C(uint32_t, arg_set_id)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
diff --git a/src/trace_processor/tables/flow_tables.py b/src/trace_processor/tables/flow_tables.py
index 0491a31..df0ae4c 100644
--- a/src/trace_processor/tables/flow_tables.py
+++ b/src/trace_processor/tables/flow_tables.py
@@ -22,6 +22,7 @@
 from src.trace_processor.tables.slice_tables import SLICE_TABLE
 
 FLOW_TABLE = Table(
+    python_module=__file__,
     class_name='FlowTable',
     sql_name='flow',
     columns=[
diff --git a/src/trace_processor/tables/memory_tables.py b/src/trace_processor/tables/memory_tables.py
index 65263f8..f270aa7 100644
--- a/src/trace_processor/tables/memory_tables.py
+++ b/src/trace_processor/tables/memory_tables.py
@@ -26,6 +26,7 @@
 from src.trace_processor.tables.track_tables import TRACK_TABLE
 
 MEMORY_SNAPSHOT_TABLE = Table(
+    python_module=__file__,
     class_name='MemorySnapshotTable',
     sql_name='memory_snapshot',
     columns=[
@@ -43,6 +44,7 @@
         }))
 
 PROCESS_MEMORY_SNAPSHOT_TABLE = Table(
+    python_module=__file__,
     class_name='ProcessMemorySnapshotTable',
     sql_name='process_memory_snapshot',
     columns=[
@@ -58,6 +60,7 @@
         }))
 
 MEMORY_SNAPSHOT_NODE_TABLE = Table(
+    python_module=__file__,
     class_name='MemorySnapshotNodeTable',
     sql_name='memory_snapshot_node',
     columns=[
@@ -81,6 +84,7 @@
         }))
 
 MEMORY_SNAPSHOT_EDGE_TABLE = Table(
+    python_module=__file__,
     class_name='MemorySnapshotEdgeTable',
     sql_name='memory_snapshot_edge',
     columns=[
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index ee9acca..0219aa9 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -29,6 +29,7 @@
 from python.generators.trace_processor_table.public import WrappingSqlView
 
 PROCESS_TABLE = Table(
+    python_module=__file__,
     class_name='ProcessTable',
     sql_name='internal_process',
     columns=[
@@ -100,6 +101,7 @@
         }))
 
 THREAD_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadTable',
     sql_name='internal_thread',
     columns=[
@@ -159,6 +161,7 @@
         }))
 
 RAW_TABLE = Table(
+    python_module=__file__,
     class_name='RawTable',
     sql_name='raw',
     columns=[
@@ -194,6 +197,7 @@
         }))
 
 FTRACE_EVENT_TABLE = Table(
+    python_module=__file__,
     class_name='FtraceEventTable',
     sql_name='ftrace_event',
     parent=RAW_TABLE,
@@ -209,6 +213,7 @@
         columns={}))
 
 ARG_TABLE = Table(
+    python_module=__file__,
     class_name='ArgTable',
     sql_name='internal_args',
     columns=[
@@ -235,6 +240,7 @@
         }))
 
 METADATA_TABLE = Table(
+    python_module=__file__,
     class_name='MetadataTable',
     sql_name='metadata',
     columns=[
@@ -254,6 +260,7 @@
         }))
 
 FILEDESCRIPTOR_TABLE = Table(
+    python_module=__file__,
     class_name='FiledescriptorTable',
     sql_name='filedescriptor',
     columns=[
@@ -290,6 +297,7 @@
         }))
 
 EXP_MISSING_CHROME_PROC_TABLE = Table(
+    python_module=__file__,
     class_name='ExpMissingChromeProcTable',
     sql_name='experimental_missing_chrome_processes',
     columns=[
@@ -307,6 +315,7 @@
         }))
 
 CPU_TABLE = Table(
+    python_module=__file__,
     class_name='CpuTable',
     sql_name='cpu',
     columns=[
@@ -327,6 +336,7 @@
         }))
 
 CPU_FREQ_TABLE = Table(
+    python_module=__file__,
     class_name='CpuFreqTable',
     sql_name='cpu_freq',
     columns=[
@@ -340,6 +350,7 @@
         }))
 
 CLOCK_SNAPSHOT_TABLE = Table(
+    python_module=__file__,
     class_name='ClockSnapshotTable',
     sql_name='clock_snapshot',
     columns=[
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
deleted file mode 100644
index f0e84b2..0000000
--- a/src/trace_processor/tables/profiler_tables.h
+++ /dev/null
@@ -1,44 +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_TABLES_PROFILER_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_PROFILER_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/track_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// A callsite. This is a list of frames that were on the stack.
-// This is generated by the stack profilers: heapprofd and traced_perf.
-// @param depth distance from the bottom-most frame of the callstack.
-// @param parent_id parent frame on the callstack. NULL for the bottom-most.
-// @param frame_id frame at this position in the callstack.
-// @tablegroup Callstack profilers
-#define PERFETTO_TP_STACK_PROFILE_CALLSITE_DEF(NAME, PARENT, C) \
-  NAME(StackProfileCallsiteTable, "stack_profile_callsite")     \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                             \
-  C(uint32_t, depth)                                            \
-  C(std::optional<StackProfileCallsiteTable::Id>, parent_id)    \
-  C(StackProfileFrameTable::Id, frame_id)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_PROFILER_TABLES_H_
diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py
index d428009..c712adf 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -28,6 +28,7 @@
 from src.trace_processor.tables.track_tables import TRACK_TABLE
 
 PROFILER_SMAPS_TABLE = Table(
+    python_module=__file__,
     class_name='ProfilerSmapsTable',
     sql_name='profiler_smaps',
     columns=[
@@ -94,6 +95,7 @@
         }))
 
 PACKAGE_LIST_TABLE = Table(
+    python_module=__file__,
     class_name='PackageListTable',
     sql_name='package_list',
     columns=[
@@ -123,6 +125,7 @@
         }))
 
 STACK_PROFILE_MAPPING_TABLE = Table(
+    python_module=__file__,
     class_name='StackProfileMappingTable',
     sql_name='stack_profile_mapping',
     columns=[
@@ -151,6 +154,7 @@
         }))
 
 STACK_PROFILE_FRAME_TABLE = Table(
+    python_module=__file__,
     class_name='StackProfileFrameTable',
     sql_name='stack_profile_frame',
     columns=[
@@ -181,6 +185,7 @@
         }))
 
 STACK_PROFILE_CALLSITE_TABLE = Table(
+    python_module=__file__,
     class_name='StackProfileCallsiteTable',
     sql_name='stack_profile_callsite',
     columns=[
@@ -204,6 +209,7 @@
         }))
 
 STACK_SAMPLE_TABLE = Table(
+    python_module=__file__,
     class_name='StackSampleTable',
     sql_name='stack_sample',
     columns=[
@@ -221,6 +227,7 @@
         }))
 
 CPU_PROFILE_STACK_SAMPLE_TABLE = Table(
+    python_module=__file__,
     class_name='CpuProfileStackSampleTable',
     sql_name='cpu_profile_stack_sample',
     columns=[
@@ -239,6 +246,7 @@
         }))
 
 PERF_SAMPLE_TABLE = Table(
+    python_module=__file__,
     class_name='PerfSampleTable',
     sql_name='perf_sample',
     columns=[
@@ -278,6 +286,7 @@
         }))
 
 SYMBOL_TABLE = Table(
+    python_module=__file__,
     class_name='SymbolTable',
     sql_name='stack_profile_symbol',
     columns=[
@@ -323,6 +332,7 @@
         }))
 
 HEAP_PROFILE_ALLOCATION_TABLE = Table(
+    python_module=__file__,
     class_name='HeapProfileAllocationTable',
     sql_name='heap_profile_allocation',
     columns=[
@@ -367,6 +377,7 @@
         }))
 
 EXPERIMENTAL_FLAMEGRAPH_NODES_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalFlamegraphNodesTable',
     sql_name='experimental_flamegraph_nodes',
     columns=[
@@ -421,6 +432,7 @@
         }))
 
 HEAP_GRAPH_CLASS_TABLE = Table(
+    python_module=__file__,
     class_name='HeapGraphClassTable',
     sql_name='heap_graph_class',
     columns=[
@@ -455,6 +467,7 @@
         }))
 
 HEAP_GRAPH_OBJECT_TABLE = Table(
+    python_module=__file__,
     class_name='HeapGraphObjectTable',
     sql_name='heap_graph_object',
     columns=[
@@ -500,6 +513,7 @@
         }))
 
 HEAP_GRAPH_REFERENCE_TABLE = Table(
+    python_module=__file__,
     class_name='HeapGraphReferenceTable',
     sql_name='heap_graph_reference',
     columns=[
@@ -537,6 +551,7 @@
         }))
 
 VULKAN_MEMORY_ALLOCATIONS_TABLE = Table(
+    python_module=__file__,
     class_name='VulkanMemoryAllocationsTable',
     sql_name='vulkan_memory_allocations',
     columns=[
@@ -576,6 +591,7 @@
         }))
 
 GPU_COUNTER_GROUP_TABLE = Table(
+    python_module=__file__,
     class_name='GpuCounterGroupTable',
     sql_name='gpu_counter_group',
     columns=[
diff --git a/src/trace_processor/tables/py_tables_unittest.cc b/src/trace_processor/tables/py_tables_unittest.cc
index 9c0b72b..c563a40 100644
--- a/src/trace_processor/tables/py_tables_unittest.cc
+++ b/src/trace_processor/tables/py_tables_unittest.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/tables/py_tables_unittest_py.h"
 
 #include "test/gtest_and_gmock.h"
@@ -165,6 +166,59 @@
   ASSERT_EQ(slice_.dur()[1], 20);
 }
 
+TEST_F(PyTablesUnittest, Extend) {
+  event_.Insert(TestEventTable::Row(50, 0));
+  event_.Insert(TestEventTable::Row(100, 1));
+  event_.Insert(TestEventTable::Row(150, 2));
+
+  ColumnStorage<int64_t> dur;
+  dur.Append(512);
+  dur.Append(1024);
+  dur.Append(2048);
+
+  auto slice_ext = TestSliceTable::ExtendParent(event_, std::move(dur));
+  ASSERT_EQ(slice_ext->row_count(), 3u);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(0).AsLong(),
+      50);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(0).AsLong(),
+      512);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(1).AsLong(),
+      100);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(1).AsLong(),
+      1024);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(2).AsLong(),
+      150);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(2).AsLong(),
+      2048);
+}
+
+TEST_F(PyTablesUnittest, SelectAndExtend) {
+  event_.Insert(TestEventTable::Row(50, 0));
+  event_.Insert(TestEventTable::Row(100, 1));
+  event_.Insert(TestEventTable::Row(150, 2));
+
+  std::vector<TestEventTable::RowNumber> rows;
+  rows.emplace_back(TestEventTable::RowNumber(1));
+  ColumnStorage<int64_t> dur;
+  dur.Append(1024);
+
+  auto slice_ext = TestSliceTable::SelectAndExtendParent(
+      event_, std::move(rows), std::move(dur));
+  ASSERT_EQ(slice_ext->row_count(), 1u);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(0).AsLong(),
+      100);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(0).AsLong(),
+      1024);
+}
+
 }  // namespace
 }  // namespace tables
 }  // namespace trace_processor
diff --git a/src/trace_processor/tables/py_tables_unittest.py b/src/trace_processor/tables/py_tables_unittest.py
index ba7b824..99f69dd 100644
--- a/src/trace_processor/tables/py_tables_unittest.py
+++ b/src/trace_processor/tables/py_tables_unittest.py
@@ -21,6 +21,7 @@
 from python.generators.trace_processor_table.public import CppUint32
 
 EVENT_TABLE = Table(
+    python_module=__file__,
     class_name="TestEventTable",
     sql_name="event",
     columns=[
@@ -29,12 +30,14 @@
     ])
 
 EVENT_CHILD_TABLE = Table(
+    python_module=__file__,
     class_name="TestEventChildTable",
     sql_name="event",
     parent=EVENT_TABLE,
     columns=[])
 
 SLICE_TABLE = Table(
+    python_module=__file__,
     class_name="TestSliceTable",
     sql_name="slice",
     parent=EVENT_TABLE,
@@ -43,6 +46,7 @@
     ])
 
 ARGS_TABLE = Table(
+    python_module=__file__,
     class_name="TestArgsTable",
     sql_name="args",
     columns=[
diff --git a/src/trace_processor/tables/slice_tables.h b/src/trace_processor/tables/slice_tables.h
index e33ae19..44f5ba9 100644
--- a/src/trace_processor/tables/slice_tables.h
+++ b/src/trace_processor/tables/slice_tables.h
@@ -42,16 +42,6 @@
   C(std::optional<int64_t>, thread_instruction_count) \
   C(std::optional<int64_t>, thread_instruction_delta)
 
-#define PERFETTO_TP_SCHED_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(SchedSliceTable, "sched_slice")                     \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                        \
-  C(int64_t, ts, Column::Flag::kSorted)                    \
-  C(int64_t, dur)                                          \
-  C(uint32_t, cpu)                                         \
-  C(uint32_t, utid)                                        \
-  C(StringPool::Id, end_state)                             \
-  C(int32_t, priority)
-
 }  // namespace tables
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 50b8c3e..22bd9b3 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -29,6 +29,7 @@
 from src.trace_processor.tables.track_tables import TRACK_TABLE
 
 SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='SliceTable',
     sql_name='internal_slice',
     columns=[
@@ -69,6 +70,7 @@
         }))
 
 SCHED_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='SchedSliceTable',
     sql_name='sched_slice',
     columns=[
@@ -108,6 +110,7 @@
         }))
 
 THREAD_STATE_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadStateTable',
     sql_name='thread_state',
     columns=[
@@ -135,6 +138,7 @@
         }))
 
 GPU_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='GpuSliceTable',
     sql_name='gpu_slice',
     columns=[
@@ -169,6 +173,7 @@
         }))
 
 GRAPHICS_FRAME_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='GraphicsFrameSliceTable',
     sql_name='frame_slice',
     columns=[
@@ -191,6 +196,7 @@
         }))
 
 EXPECTED_FRAME_TIMELINE_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='ExpectedFrameTimelineSliceTable',
     sql_name='expected_frame_timeline_slice',
     columns=[
@@ -211,6 +217,7 @@
         }))
 
 ACTUAL_FRAME_TIMELINE_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='ActualFrameTimelineSliceTable',
     sql_name='actual_frame_timeline_slice',
     columns=[
@@ -243,6 +250,7 @@
         }))
 
 EXPERIMENTAL_FLAT_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalFlatSliceTable',
     sql_name='experimental_flat_slice',
     columns=[
diff --git a/src/trace_processor/tables/trace_proto_tables.h b/src/trace_processor/tables/trace_proto_tables.h
deleted file mode 100644
index aa57b59..0000000
--- a/src/trace_processor/tables/trace_proto_tables.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2020 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_TABLES_TRACE_PROTO_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_TRACE_PROTO_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// Experimental table, subject to arbitrary breaking changes.
-#define PERFETTO_TP_EXPERIMENTAL_PROTO_PATH_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ExperimentalProtoPathTable, "experimental_proto_path")          \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                                    \
-  C(std::optional<ExperimentalProtoPathTable::Id>, parent_id)          \
-  C(StringPool::Id, field_type)                                        \
-  C(std::optional<StringPool::Id>, field_name)                         \
-  C(std::optional<uint32_t>, arg_set_id)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_EXPERIMENTAL_PROTO_PATH_TABLE_DEF);
-
-#define PERFETTO_TP_EXPERIMENTAL_PROTO_CONTENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ExperimentalProtoContentTable, "experimental_proto_content")       \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                                       \
-  C(StringPool::Id, path)                                                 \
-  C(ExperimentalProtoPathTable::Id, path_id)                              \
-  C(int64_t, total_size)                                                  \
-  C(int64_t, size)                                                        \
-  C(int64_t, count)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_EXPERIMENTAL_PROTO_CONTENT_TABLE_DEF);
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_TRACE_PROTO_TABLES_H_
diff --git a/src/trace_processor/tables/trace_proto_tables.py b/src/trace_processor/tables/trace_proto_tables.py
index f6f5f9d..e9c9754 100644
--- a/src/trace_processor/tables/trace_proto_tables.py
+++ b/src/trace_processor/tables/trace_proto_tables.py
@@ -24,6 +24,7 @@
 from python.generators.trace_processor_table.public import CppUint32
 
 EXPERIMENTAL_PROTO_PATH_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalProtoPathTable',
     sql_name='experimental_proto_path',
     columns=[
@@ -45,6 +46,7 @@
         }))
 
 EXPERIMENTAL_PROTO_CONTENT_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalProtoContentTable',
     sql_name='experimental_proto_content',
     columns=[
diff --git a/src/trace_processor/tables/track_tables.py b/src/trace_processor/tables/track_tables.py
index bb5a0df..3cbce76 100644
--- a/src/trace_processor/tables/track_tables.py
+++ b/src/trace_processor/tables/track_tables.py
@@ -25,6 +25,7 @@
 from python.generators.trace_processor_table.public import CppUint32
 
 TRACK_TABLE = Table(
+    python_module=__file__,
     class_name="TrackTable",
     sql_name="track",
     columns=[
@@ -62,6 +63,7 @@
         }))
 
 PROCESS_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name="ProcessTrackTable",
     sql_name="process_track",
     columns=[
@@ -81,6 +83,7 @@
         }))
 
 THREAD_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadTrackTable',
     sql_name='thread_track',
     columns=[
@@ -101,6 +104,7 @@
         }))
 
 CPU_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='CpuTrackTable',
     sql_name='cpu_track',
     columns=[
@@ -113,6 +117,7 @@
         columns={'cpu': 'The CPU associated with this track'}))
 
 GPU_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='GpuTrackTable',
     sql_name='gpu_track',
     columns=[
@@ -134,6 +139,7 @@
         }))
 
 COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='CounterTrackTable',
     sql_name='counter_track',
     columns=[
@@ -156,6 +162,7 @@
         }))
 
 THREAD_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadCounterTrackTable',
     sql_name='thread_counter_track',
     columns=[
@@ -174,6 +181,7 @@
         }))
 
 PROCESS_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='ProcessCounterTrackTable',
     sql_name='process_counter_track',
     columns=[
@@ -193,6 +201,7 @@
         }))
 
 CPU_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='CpuCounterTrackTable',
     sql_name='cpu_counter_track',
     columns=[
@@ -205,6 +214,7 @@
         columns={'cpu': 'The CPU this track is associated with'}))
 
 IRQ_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='IrqCounterTrackTable',
     sql_name='irq_counter_track',
     columns=[
@@ -217,6 +227,7 @@
         columns={'irq': 'The identifier for the hardirq.'}))
 
 SOFTIRQ_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='SoftirqCounterTrackTable',
     sql_name='softirq_counter_track',
     columns=[
@@ -229,6 +240,7 @@
         columns={'softirq': 'The identifier for the softirq.'}))
 
 GPU_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='GpuCounterTrackTable',
     sql_name='gpu_counter_track',
     columns=[
@@ -241,6 +253,7 @@
         columns={'gpu_id': 'The identifier for the GPU.'}))
 
 PERF_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='PerfCounterTrackTable',
     sql_name='perf_counter_track',
     columns=[
@@ -265,6 +278,7 @@
         }))
 
 ENERGY_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='EnergyCounterTrackTable',
     sql_name='energy_counter_track',
     columns=[
@@ -286,6 +300,7 @@
         }))
 
 UID_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='UidCounterTrackTable',
     sql_name='uid_counter_track',
     columns=[
@@ -298,6 +313,7 @@
         columns={'uid': 'uid of process for which breakdowns are emitted'}))
 
 ENERGY_PER_UID_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='EnergyPerUidCounterTrackTable',
     sql_name='energy_per_uid_counter_track',
     columns=[
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index d0eb99f..5317914 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -819,10 +819,9 @@
   module.cmd = ' '.join([
       f'$(location {bp_binary_module_name})',
       '--gen-dir=$(genDir)',
+      '--relative-input-dir=external/perfetto',
       '--inputs',
       '$(in)',
-      '--outputs',
-      '$(out)',
   ])
   module.out.update(target.outputs)
   module.genrule_headers.add(module.name)
diff --git a/tools/gen_bazel b/tools/gen_bazel
index a514fe3..3121aa8 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -582,6 +582,9 @@
   label = BazelLabel(get_bazel_label_name(target.name), 'perfetto_cc_tp_tables')
   label.comment = target.name
   label.srcs += (gn_utils.label_to_path(x) for x in target.sources)
+  label.deps += sorted(':' + get_bazel_label_name(x.name)
+                       for x in target.transitive_deps
+                       if x.name not in default_python_targets)
   label.outs += target.outputs
   return [label]
 
diff --git a/tools/gen_tp_table_docs.py b/tools/gen_tp_table_docs.py
index 8b8565a..97e183e 100755
--- a/tools/gen_tp_table_docs.py
+++ b/tools/gen_tp_table_docs.py
@@ -59,7 +59,9 @@
     raise Exception('Unknown column documentation type '
                     f'{table.table.class_name}::{col.column.name}')
 
-  parsed_type = table.parse_type(col.column.type)
+  parsed_type = util.parse_type_with_cols(table.table,
+                                          [c.column for c in table.columns],
+                                          col.column.type)
   docs_type = parsed_type.cpp_type
   if docs_type == 'StringPool::Id':
     docs_type = 'string'
@@ -88,11 +90,20 @@
   parser = argparse.ArgumentParser()
   parser.add_argument('--out', required=True)
   parser.add_argument('inputs', nargs='*')
+  parser.add_argument('--relative-input-dir')
   args = parser.parse_args()
 
-  tables = util.parse_tables_from_files(args.inputs)
+  def get_relin_path(in_path: str):
+    if not args.relative_input_dir:
+      return in_path
+    return os.path.relpath(in_path, args.relative_input_dir)
+
+  modules = [
+      os.path.splitext(get_relin_path(i).replace('/', '.'))[0]
+      for i in args.inputs
+  ]
   table_docs = []
-  for parsed in tables:
+  for parsed in util.parse_tables_from_modules(modules):
     table = parsed.table
     doc = table.tabledoc
     assert doc
diff --git a/tools/gen_tp_table_headers.py b/tools/gen_tp_table_headers.py
index 1ce0963..d06c637 100755
--- a/tools/gen_tp_table_headers.py
+++ b/tools/gen_tp_table_headers.py
@@ -28,63 +28,71 @@
 
 #pylint: disable=wrong-import-position
 from python.generators.trace_processor_table.serialize import serialize_header
+from python.generators.trace_processor_table.util import find_table_deps
 from python.generators.trace_processor_table.util import ParsedTable
-from python.generators.trace_processor_table.util import parse_tables_from_files
+from python.generators.trace_processor_table.util import parse_tables_from_modules
 #pylint: enable=wrong-import-position
 
+# Suffix which replaces the .py extension for all input modules.
+OUT_HEADER_SUFFIX = '_py.h'
+
 
 @dataclass
 class Header:
   """Represents a Python module which will be converted to a header."""
-  out_path: str
-  relout_path: str
   tables: List[ParsedTable]
 
 
 def main():
   """Main function."""
   parser = argparse.ArgumentParser()
-  parser.add_argument('--gen-dir', required=True)
   parser.add_argument('--inputs', required=True, nargs='*')
-  parser.add_argument('--outputs', required=True, nargs='*')
+  parser.add_argument('--gen-dir', required=True)
   parser.add_argument('--header-prefix')
+  parser.add_argument('--relative-input-dir')
   args = parser.parse_args()
 
-  if len(args.inputs) != len(args.outputs):
-    raise Exception('Number of inputs must match number of outputs')
-
   header_prefix = args.header_prefix if args.header_prefix else ''
-  in_to_out = dict(zip(args.inputs, args.outputs))
+
+  def get_relin_path(in_path: str):
+    if not args.relative_input_dir:
+      return in_path
+    return os.path.relpath(in_path, args.relative_input_dir)
+
+  def get_relout_path(in_path: str):
+    path = os.path.splitext(in_path)[0]
+    return os.path.join(header_prefix, path + OUT_HEADER_SUFFIX)
+
+  def get_out_path(in_path: str):
+    return os.path.join(args.gen_dir, get_relout_path(in_path))
+
+  modules = [
+      os.path.splitext(get_relin_path(i).replace('/', '.'))[0]
+      for i in args.inputs
+  ]
   headers: Dict[str, Header] = {}
-  for table in parse_tables_from_files(args.inputs):
-    out_path = in_to_out[table.input_path]
-    relout_path = os.path.join(header_prefix,
-                               os.path.relpath(out_path, args.gen_dir))
-
-    header = headers.get(table.input_path, Header(out_path, relout_path, []))
+  for table in parse_tables_from_modules(modules):
+    input_path = os.path.relpath(table.table.python_module, ROOT_DIR)
+    header = headers.get(input_path, Header([]))
     header.tables.append(table)
-    headers[table.input_path] = header
+    headers[input_path] = header
 
-  # Build a mapping from table class name to the output path of the header
-  # which will be generated for it. This is used to include one header into
-  # another for Id dependencies.
-  table_class_name_to_relout: Dict[str, str] = {}
-  for header in headers.values():
-    for table in header.tables:
-      table_class_name_to_relout[table.table.class_name] = header.relout_path
+  for in_path, header in headers.items():
+    out_path = get_out_path(in_path)
+    relout_path = get_relout_path(in_path)
 
-  for header in headers.values():
     # Find all headers depended on by this table. These will be #include-ed when
     # generating the header file below so ensure we remove ourself.
     header_relout_deps: Set[str] = set()
     for table in header.tables:
-      header_relout_deps = header_relout_deps.union(
-          [table_class_name_to_relout[c] for c in table.find_table_deps()])
-    header_relout_deps.discard(header.relout_path)
+      header_relout_deps = header_relout_deps.union([
+          get_relout_path(os.path.relpath(c.python_module, ROOT_DIR))
+          for c in find_table_deps(table.table)
+      ])
+    header_relout_deps.discard(relout_path)
 
-    with open(header.out_path, 'w', encoding='utf8') as out:
-      ifdef_guard = re.sub(r'[^a-zA-Z0-9_-]', '_',
-                           header.relout_path).upper() + '_'
+    with open(out_path, 'w', encoding='utf8') as out:
+      ifdef_guard = re.sub(r'[^a-zA-Z0-9_-]', '_', relout_path).upper() + '_'
       out.write(
           serialize_header(ifdef_guard, header.tables,
                            sorted(header_relout_deps)))