blob: 643891380e26af21325171d67f2daf9fb65cc5ab [file] [log] [blame]
# 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.
from typing import List
from typing import Optional
from python.generators.trace_processor_table.public import Alias
from python.generators.trace_processor_table.public import ColumnFlag
from python.generators.trace_processor_table.public import CppAccess
from python.generators.trace_processor_table.public import CppAccessDuration
from python.generators.trace_processor_table.public import SqlAccess
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
class ColumnSerializer:
def __init__(self, table: ParsedTable, column: ParsedColumn, col_index: int):
self.col_index = col_index
self.parsed_col = column
self.col = self.parsed_col.column
self.name = self.col.name
self.flags = self.col.flags
parsed_type = parse_type(table.table, self.col.type)
self.cpp_type_non_optional = parsed_type.cpp_type()
self.cpp_type_with_optionality = parsed_type.cpp_type_with_optionality()
self.is_optional = parsed_type.is_optional
self.is_string = self.cpp_type_non_optional == 'StringPool::Id'
self.is_id_type = parsed_type.id_table
self.is_no_transform_id = parsed_type.is_no_transform_id()
self.is_implicit_id = self.parsed_col.is_implicit_id
def row_field(self) -> Optional[str]:
if self.is_implicit_id:
return None
return f' {self.cpp_type_with_optionality} {self.name};'
def row_insert_arg_value(self) -> Optional[str]:
"""Generates the C++ code to access this column's value from a Row object,
for use in the Dataframe::InsertUnchecked call."""
if self.is_implicit_id:
return f'std::monostate()'
if self.is_optional and self.is_id_type and not self.is_no_transform_id:
return f'row.{self.name} ? std::make_optional(row.{self.name}->value) : std::nullopt'
if self.is_optional and self.is_string:
return f'row.{self.name} && row.{self.name} != StringPool::Id::Null() ? std::make_optional(*row.{self.name}) : std::nullopt'
if self.is_string:
return f'row.{self.name} != StringPool::Id::Null() ? std::make_optional(row.{self.name}) : std::nullopt'
if self.is_id_type and not self.is_no_transform_id:
return f'row.{self.name}.value'
return f'row.{self.name}'
def cursor_getter(self) -> Optional[str]:
if self.col.cpp_access == CppAccess.NONE:
return ''
if self.col.cpp_access_duration == CppAccessDuration.PRE_FINALIZATION_ONLY:
dcheck = 'PERFETTO_DCHECK(!dataframe_->finalized());'
else:
dcheck = ''
if self.is_optional and self.is_id_type:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
auto res = cursor_.GetCellUnchecked<ColumnIndex::{self.name}>(kSpec);
return res ? std::make_optional({self.cpp_type_non_optional}{{*res}}) : std::nullopt;
}}'''
if self.is_optional and self.is_string:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
auto res = cursor_.GetCellUnchecked<ColumnIndex::{self.name}>(kSpec);
return res && res != StringPool::Id::Null() ? std::make_optional({self.cpp_type_non_optional}{{*res}}) : std::nullopt;
}}'''
if self.is_string:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
auto res = cursor_.GetCellUnchecked<ColumnIndex::{self.name}>(kSpec);
return res && res != StringPool::Id::Null() ? *res : StringPool::Id::Null();
}}'''
if self.is_id_type:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
return {self.cpp_type_non_optional}{{cursor_.GetCellUnchecked<ColumnIndex::{self.name}>(kSpec)}};
}}'''
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
return cursor_.GetCellUnchecked<ColumnIndex::{self.name}>(kSpec);
}}'''
def cursor_setter(self) -> Optional[str]:
if self.col.cpp_access == CppAccess.NONE or self.col.cpp_access == CppAccess.READ:
return ''
if self.col.cpp_access_duration == CppAccessDuration.PRE_FINALIZATION_ONLY:
dcheck = 'PERFETTO_DCHECK(!dataframe_->finalized());'
else:
dcheck = ''
if self.is_optional and self.is_id_type and not self.is_no_transform_id:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
auto res_value = res ? std::make_optional(res->value) : std::nullopt;
cursor_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, res_value);
}}'''
if self.is_optional and self.is_string:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
auto res_value = res && res != StringPool::Id::Null() ? std::make_optional(*res) : std::nullopt;
cursor_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, res_value);
}}'''
if self.is_string:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
auto res_value = res != StringPool::Id::Null() ? std::make_optional(res) : std::nullopt;
cursor_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, res_value);
}}'''
if self.is_id_type and not self.is_no_transform_id:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
cursor_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, res.value);
}}'''
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
cursor_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, res);
}}'''
def row_reference_getter(self) -> str:
if self.col.cpp_access == CppAccess.NONE:
return ''
if self.col.cpp_access_duration == CppAccessDuration.PRE_FINALIZATION_ONLY:
dcheck = 'PERFETTO_DCHECK(!table_->dataframe_.finalized());'
else:
dcheck = ''
if self.is_optional and self.is_id_type:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
auto res = table_->dataframe_.template GetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_);
return res ? std::make_optional({self.cpp_type_non_optional}{{*res}}) : std::nullopt;
}}'''
if self.is_optional and self.is_string:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
auto res = table_->dataframe_.template GetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_);
return res && res != StringPool::Id::Null() ? std::make_optional({self.cpp_type_non_optional}{{*res}}) : std::nullopt;
}}'''
if self.is_string:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
auto res = table_->dataframe_.template GetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_);
return res && res != StringPool::Id::Null() ? {self.cpp_type_non_optional}{{*res}} : StringPool::Id::Null();
}}'''
if self.is_id_type:
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
return {self.cpp_type_non_optional}{{table_->dataframe_.template GetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_)}};
}}'''
return f'''
{self.cpp_type_with_optionality} {self.name}() const {{
{dcheck}
return table_->dataframe_.template GetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_);
}}'''
def row_reference_setter(self) -> Optional[str]:
if self.col.cpp_access == CppAccess.NONE or self.col.cpp_access == CppAccess.READ:
return ''
if self.col.cpp_access_duration == CppAccessDuration.PRE_FINALIZATION_ONLY:
dcheck = 'PERFETTO_DCHECK(!table_->dataframe_.finalized());'
else:
dcheck = ''
if self.is_optional and self.is_id_type and not self.is_no_transform_id:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
auto res_value = res ? std::make_optional(res->value) : std::nullopt;
table_->dataframe_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_, res_value);
}}'''
if self.is_optional and self.is_string:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
auto res_value = res && res != StringPool::Id::Null() ? std::make_optional(*res) : std::nullopt;
table_->dataframe_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_, res_value);
}}'''
if self.is_string:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
auto res_value = res != StringPool::Id::Null() ? std::make_optional(res) : std::nullopt;
table_->dataframe_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_, res_value);
}}'''
if self.is_id_type and not self.is_no_transform_id:
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
table_->dataframe_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_, res.value);
}}'''
return f'''
void set_{self.name}({self.cpp_type_with_optionality} res) {{
{dcheck}
table_->dataframe_.SetCellUnchecked<ColumnIndex::{self.name}>(kSpec, row_, res);
}}'''
def colindex_member(self) -> str:
return f'static constexpr uint32_t {self.name} = {self.col_index}'
def typespec_column_name_literal(self) -> str:
return f'"{self.name}"'
def _get_storage_type_tag(self) -> str:
if self.is_implicit_id:
return 'dataframe::Id'
if self.is_string:
return 'dataframe::String'
if self.cpp_type_non_optional == 'uint32_t' or self.is_id_type:
return 'dataframe::Uint32'
if self.cpp_type_non_optional == 'int32_t':
return 'dataframe::Int32'
if self.cpp_type_non_optional == 'int64_t':
return 'dataframe::Int64'
if self.cpp_type_non_optional == 'double':
return 'dataframe::Double'
raise ValueError(
f"Unknown cpp_type_non_optional '{self.cpp_type_non_optional}' for Dataframe StorageType for column '{self.name}'"
)
def _get_nullability_tag(self) -> str:
if self.is_optional or self.is_string:
if self.col.sql_access == SqlAccess.HIGH_PERF:
return 'dataframe::DenseNull'
if self.col.cpp_access == CppAccess.NONE:
return 'dataframe::SparseNull'
if self.col.cpp_access == CppAccess.READ_AND_HIGH_PERF_WRITE:
return 'dataframe::DenseNull'
assert self.col.cpp_access in (
CppAccess.READ,
CppAccess.READ_AND_LOW_PERF_WRITE,
)
# TODO(lalitm): we force this to dense for strings because in the
# pre-dataframe days, string nullability was just represented by
# StringPool::Id::Null(). There are hidden dependencies everywhere in the
# codebase that expect O(1) writes to string columns. Fixing this is
# non-trivial, so we just keep it dense for now.
if self.is_string and self.col.cpp_access == CppAccess.READ_AND_LOW_PERF_WRITE:
return 'dataframe::DenseNull'
if self.col.cpp_access_duration == CppAccessDuration.PRE_FINALIZATION_ONLY:
return 'dataframe::SparseNullWithPopcountUntilFinalization'
assert self.col.cpp_access_duration == CppAccessDuration.POST_FINALIZATION
return 'dataframe::SparseNullWithPopcountAlways'
return 'dataframe::NonNull'
def _get_sort_state_tag(self) -> str:
if ColumnFlag.SORTED in self.flags:
if self.is_implicit_id:
return 'dataframe::IdSorted'
if ColumnFlag.SET_ID in self.flags:
return 'dataframe::SetIdSorted'
return 'dataframe::Sorted'
return 'dataframe::Unsorted'
def _get_duplicate_state_tag(self) -> str:
if self.is_implicit_id:
return 'dataframe::NoDuplicates'
return 'dataframe::HasDuplicates'
def typespec_typed_column_spec_expr(self) -> str:
storage_tag = self._get_storage_type_tag()
null_tag = self._get_nullability_tag()
sort_tag = self._get_sort_state_tag()
dupe_tag = self._get_duplicate_state_tag()
return (
f"dataframe::CreateTypedColumnSpec("
f"{storage_tag}{{}}, {null_tag}{{}}, {sort_tag}{{}}, {dupe_tag}{{}})")
def column_spec_initializer(self) -> str:
"""Generate ColumnSpec initializer for non-templated array."""
storage_tag = self._get_storage_type_tag()
null_tag = self._get_nullability_tag()
sort_tag = self._get_sort_state_tag()
dupe_tag = self._get_duplicate_state_tag()
return f"dataframe::ColumnSpec{{{storage_tag}{{}}, {null_tag}{{}}, {sort_tag}{{}}, {dupe_tag}{{}}}}"
class TableSerializer(object):
def __init__(self, parsed: ParsedTable):
self.table = parsed.table
self.table_name = parsed.table.class_name
self.column_serializers: List[ColumnSerializer] = []
# Filter out aliases first, then assign col_index
temp_serializers = []
for c_parsed in parsed.columns:
if isinstance(c_parsed.column.type, Alias):
continue
# col_index will be assigned when rebuilding self.column_serializers
temp_serializers.append(ColumnSerializer(parsed, c_parsed, 0))
self.column_serializers = [
ColumnSerializer(parsed, cs.parsed_col, i)
for i, cs in enumerate(temp_serializers)
]
def foreach_col(self, serialize_fn, delimiter='\n') -> str:
lines = []
for c_ser in self.column_serializers:
serialized = serialize_fn(c_ser)
if serialized:
lines.append(serialized.lstrip('\n').rstrip())
return delimiter.join(lines).strip()
def serialize_id_type(self) -> str:
"""Generate the Id type at namespace scope."""
return f'''struct {self.table_name}_Id : BaseId {{
{self.table_name}_Id() = default;
explicit constexpr {self.table_name}_Id(uint32_t _value) : BaseId(_value) {{}}
bool operator==(const {self.table_name}_Id& other) const {{
return value == other.value;
}}
}};'''
def serialize_static_specs(self) -> str:
"""Generate non-templated column names and specs arrays."""
col_names = self.foreach_col(
ColumnSerializer.typespec_column_name_literal, delimiter=', ')
col_specs = self.foreach_col(
ColumnSerializer.column_spec_initializer, delimiter=',\n ')
col_count = len(self.column_serializers)
return f'''
inline constexpr const char* k{self.table_name}ColumnNames[] = {{{col_names}}};
inline constexpr dataframe::ColumnSpec k{self.table_name}ColumnSpecs[] = {{
{col_specs}}};
inline constexpr uint32_t k{self.table_name}ColumnCount = {col_count};
'''
def row_struct(self) -> str:
fields = []
constructor_params_list = []
constructor_inits_list = []
for c_ser in self.column_serializers:
if not c_ser.is_implicit_id:
fields.append(c_ser.row_field())
constructor_params_list.append(
f'{c_ser.cpp_type_with_optionality} _{c_ser.name} = {{}}')
constructor_inits_list.append(f'{c_ser.name}(std::move(_{c_ser.name}))')
fields_str = "\n".join(filter(None, fields))
params_str = ', '.join(constructor_params_list)
inits_str = (
': ' +
', '.join(constructor_inits_list)) if constructor_inits_list else ''
return f'''
struct Row {{
Row({params_str}) {inits_str} {{}}
bool operator==(const Row& other) const {{
return {' && '.join([f'std::equal_to<>()({c_ser.name}, other.{c_ser.name})' for c_ser in self.column_serializers if not c_ser.is_implicit_id])};
}}
{fields_str}
}};'''
def serialize(self) -> str:
col_names = self.foreach_col(
ColumnSerializer.typespec_column_name_literal, delimiter=',')
col_specs = self.foreach_col(
ColumnSerializer.typespec_typed_column_spec_expr, delimiter=',\n ')
col_index = self.foreach_col(
ColumnSerializer.colindex_member, delimiter=';\n ')
row_ref_getter = self.foreach_col(
ColumnSerializer.row_reference_getter, delimiter='\n ')
row_ref_setter = self.foreach_col(
ColumnSerializer.row_reference_setter, delimiter='\n ')
cursor_getter = self.foreach_col(
ColumnSerializer.cursor_getter, delimiter='\n')
cursor_setter = self.foreach_col(
ColumnSerializer.cursor_setter, delimiter='\n')
insert_argvalue = self.foreach_col(
ColumnSerializer.row_insert_arg_value, delimiter=', ')
return f'''
class {self.table_name} {{
public:
static constexpr auto kSpec = dataframe::CreateTypedDataframeSpec(
{{{col_names}}},
{col_specs});
using Id = {self.table_name}_Id;
struct RowReference;
struct ConstRowReference;
struct RowNumber {{
public:
explicit constexpr RowNumber(uint32_t value) : value_(value) {{}}
uint32_t row_number() const {{ return value_; }}
RowReference ToRowReference({self.table_name}* table) const {{
return RowReference(table, value_);
}}
ConstRowReference ToRowReference(const {self.table_name}& table) const {{
return ConstRowReference(&table, value_);
}}
bool operator==(const RowNumber& other) const {{
return value_ == other.value_;
}}
bool operator<(const RowNumber& other) const {{
return value_ < other.value_;
}}
private:
uint32_t value_;
}};
struct ColumnIndex {{
{col_index};
}};
struct RowReference {{
public:
explicit RowReference({self.table_name}* table, uint32_t row)
: table_(table), row_(row) {{
base::ignore_result(table_);
}}
{row_ref_getter}
{row_ref_setter}
RowNumber ToRowNumber() const {{
return RowNumber{{row_}};
}}
private:
friend struct ConstRowReference;
{self.table_name}* table_;
uint32_t row_;
}};
struct ConstRowReference {{
public:
explicit ConstRowReference(const {self.table_name}* table, uint32_t row)
: table_(table), row_(row) {{
base::ignore_result(table_);
}}
ConstRowReference(const RowReference& other)
: table_(other.table_), row_(other.row_) {{}}
{row_ref_getter}
RowNumber ToRowNumber() const {{
return RowNumber{{row_}};
}}
private:
const {self.table_name}* table_;
uint32_t row_;
}};
class ConstCursor {{
public:
explicit ConstCursor(const dataframe::Dataframe& df,
std::vector<dataframe::FilterSpec> filters,
std::vector<dataframe::SortSpec> sorts)
: dataframe_(&df), cursor_(&df, std::move(filters), std::move(sorts)) {{
base::ignore_result(dataframe_);
}}
PERFETTO_ALWAYS_INLINE void Execute() {{ cursor_.ExecuteUnchecked(); }}
PERFETTO_ALWAYS_INLINE bool Eof() const {{ return cursor_.Eof(); }}
PERFETTO_ALWAYS_INLINE void Next() {{ cursor_.Next(); }}
template <typename C>
PERFETTO_ALWAYS_INLINE void SetFilterValueUnchecked(uint32_t index, C value) {{
cursor_.SetFilterValueUnchecked(index, std::move(value));
}}
RowNumber ToRowNumber() const {{
return RowNumber{{cursor_.RowIndex()}};
}}
void Reset() {{ cursor_.Reset(); }}
{cursor_getter}
private:
const dataframe::Dataframe* dataframe_;
dataframe::TypedCursor cursor_;
}};
class Cursor {{
public:
explicit Cursor(dataframe::Dataframe& df,
std::vector<dataframe::FilterSpec> filters,
std::vector<dataframe::SortSpec> sorts)
: dataframe_(&df), cursor_(&df, std::move(filters), std::move(sorts)) {{
base::ignore_result(dataframe_);
}}
PERFETTO_ALWAYS_INLINE void Execute() {{ cursor_.ExecuteUnchecked(); }}
PERFETTO_ALWAYS_INLINE bool Eof() const {{ return cursor_.Eof(); }}
PERFETTO_ALWAYS_INLINE void Next() {{ cursor_.Next(); }}
template <typename C>
PERFETTO_ALWAYS_INLINE void SetFilterValueUnchecked(uint32_t index, C value) {{
cursor_.SetFilterValueUnchecked(index, std::move(value));
}}
RowNumber ToRowNumber() const {{
return RowNumber{{cursor_.RowIndex()}};
}}
void Reset() {{ cursor_.Reset(); }}
{cursor_getter}
{cursor_setter}
private:
dataframe::Dataframe* dataframe_;
dataframe::TypedCursor cursor_;
}};
class Iterator {{
public:
explicit Iterator({self.table_name}* table) : table_(table) {{
base::ignore_result(table_);
}}
explicit operator bool() const {{ return row_ < table_->row_count(); }}
Iterator& operator++() {{
++row_;
return *this;
}}
RowNumber row_number() const {{
return RowNumber{{row_}};
}}
RowReference ToRowReference() const {{
return RowReference(table_, row_);
}}
{row_ref_getter}
{row_ref_setter}
private:
{self.table_name}* table_;
uint32_t row_ = 0;
}};
class ConstIterator {{
public:
explicit ConstIterator(const {self.table_name}* table) : table_(table) {{
base::ignore_result(table_);
}}
explicit operator bool() const {{ return row_ < table_->row_count(); }}
ConstIterator& operator++() {{
++row_;
return *this;
}}
RowNumber row_number() const {{
return RowNumber{{row_}};
}}
ConstRowReference ToRowReference() const {{
return ConstRowReference(table_, row_);
}}
{row_ref_getter}
private:
const {self.table_name}* table_;
uint32_t row_ = 0;
}};
struct IdAndRow {{
Id id;
RowNumber row_number;
uint32_t row;
RowReference row_reference;
}};
{self.row_struct()}
explicit {self.table_name}(StringPool* pool)
: dataframe_(dataframe::Dataframe::CreateFromTypedSpec(kSpec, pool)) {{}}
template <typename = void>
IdAndRow Insert(const Row& row) {{
uint32_t row_count = dataframe_.row_count();
dataframe_.InsertUnchecked(kSpec, {insert_argvalue});
return IdAndRow{{Id{{row_count}}, RowNumber{{row_count}}, row_count, RowReference(this, row_count)}};
}}
uint32_t row_count() const {{
return dataframe_.row_count();
}}
std::optional<ConstRowReference> FindById(Id id) const {{
return ConstRowReference(this, id.value);
}}
ConstRowReference operator[](uint32_t row) const {{
return ConstRowReference(this, row);
}}
std::optional<RowReference> FindById(Id id) {{
return RowReference(this, id.value);
}}
RowReference operator[](uint32_t row) {{
return RowReference(this, row);
}}
ConstCursor CreateCursor(
std::vector<dataframe::FilterSpec> filters = {{}},
std::vector<dataframe::SortSpec> sorts = {{}}) const {{
return ConstCursor(dataframe_, std::move(filters), std::move(sorts));
}}
Cursor CreateCursor(
std::vector<dataframe::FilterSpec> filters = {{}},
std::vector<dataframe::SortSpec> sorts = {{}}) {{
return Cursor(dataframe_, std::move(filters), std::move(sorts));
}}
Iterator IterateRows() {{ return Iterator(this); }}
ConstIterator IterateRows() const {{ return ConstIterator(this); }}
void Finalize() {{ dataframe_.Finalize(); }}
void Clear() {{ dataframe_.Clear(); }}
static const char* Name() {{
return "{self.table.sql_name}";
}}
dataframe::Dataframe& dataframe() {{
return dataframe_;
}}
const dataframe::Dataframe& dataframe() const {{
return dataframe_;
}}
private:
dataframe::Dataframe dataframe_;
}};
static_assert(sizeof({self.table_name}) == sizeof(dataframe::Dataframe));
'''
def serialize_header(ifdef_guard: str, tables: List[ParsedTable],
include_paths: List[str], fwd_header_path: str) -> str:
"""Serializes a table header file containing the given set of tables."""
# Replace the backslash with forward slash when building on Windows.
# Caused b/327985369 without the replace.
include_paths_str = '\n'.join([f'#include "{i}"' for i in include_paths
]).replace("\\", "/")
tables_str = '\n\n'.join([TableSerializer(t).serialize() for t in tables])
return f'''\
#ifndef {ifdef_guard}
#define {ifdef_guard}
#include <cstdint>
#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "perfetto/base/compiler.h"
#include "perfetto/public/compiler.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/tables/macros_internal.h"
#include "src/trace_processor/dataframe/dataframe.h"
#include "src/trace_processor/dataframe/specs.h"
#include "src/trace_processor/dataframe/typed_cursor.h"
#include "{fwd_header_path}"
{include_paths_str}
namespace perfetto::trace_processor::tables {{
{tables_str}
}} // namespace perfetto::trace_processor::tables
#endif // {ifdef_guard}
'''
def serialize_fwd_header(ifdef_guard: str, tables: List[ParsedTable],
include_paths: List[str]) -> str:
"""Serializes a forward declaration header with Id types only."""
# Replace the backslash with forward slash when building on Windows.
include_paths_str = '\n'.join([
f'#include "{i}" // IWYU pragma: export' for i in include_paths
]).replace("\\", "/")
id_types = '\n\n'.join(
[TableSerializer(t).serialize_id_type() for t in tables])
fwd_decls = '\n'.join([f'class {t.table.class_name};' for t in tables])
# Generate tuple of table classes for this header
table_names = ', '.join([t.table.class_name for t in tables])
tuple_name = ifdef_guard.replace('_H_', '').replace(
'SRC_TRACE_PROCESSOR_TABLES_', '') + '_TABLES'
return f'''\
#ifndef {ifdef_guard}
#define {ifdef_guard}
#include <cstdint>
#include <tuple>
#include "src/trace_processor/tables/macros_internal.h" // IWYU pragma: export
{include_paths_str}
namespace perfetto::trace_processor::tables {{
{id_types}
{fwd_decls}
// Tuple of all table types in this header
using {tuple_name} = std::tuple<{table_names}>;
}} // namespace perfetto::trace_processor::tables
#endif // {ifdef_guard}
'''