4"""Code generation from ESI manifests to source code.
6Uses a two-pass approach for C++: first collect and name all reachable types,
7then emit structs/aliases in a dependency-ordered sequence so headers are
8standalone and deterministic.
14from typing
import Dict, List, Set, TextIO, Tuple, Type, Optional
15from .accelerator
import AcceleratorConnection, Context
16from .esiCppAccel
import ModuleInfo
23from pathlib
import Path
25_thisdir = Path(__file__).absolute().
resolve().parent
29 """Base class for all generators."""
31 language: Optional[str] =
None
33 def __init__(self, conn: AcceleratorConnection):
36 def generate(self, output_dir: Path, system_name: str):
37 raise NotImplementedError(
"Generator.generate() must be overridden")
41 """Generate C++ headers from an ESI manifest."""
45 def __init__(self, conn: AcceleratorConnection):
51 """Get the C++ code for a constant in a module."""
52 const_strs: List[str] = [
53 f
"static constexpr {self.type_emitter.type_identifier(const.type)} "
54 f
"{name} = 0x{const.value:x};"
55 for name, const
in module_info.constants.items()
57 return "\n".join(const_strs)
60 """Write the C++ header. One for each module in the manifest."""
62 for module_info
in self.
manifest.module_infos:
64 /// Generated header for {system_name} module {module_info.name}.
68 namespace {system_name} {{
69 class {module_info.name} {{
71 {self.get_consts_str(module_info)}
73 }} // namespace {system_name}
76 hdr_file = output_dir / f
"{module_info.name}.h"
77 with open(hdr_file,
"w")
as hdr:
78 hdr.write(textwrap.dedent(s))
80 def generate(self, output_dir: Path, system_name: str):
86 """Plan C++ type naming and ordering from an ESI manifest."""
89 """Initialize the generator with the manifest and target namespace."""
93 self.used_names: Dict[str, bool] = {}
95 self.alias_base_names: Set[str] = set()
101 """Name the types and prepare for emission by registering all reachable
102 types and assigning."""
103 visited: Set[str] = set()
118 """Create a C++-safe identifier from the manifest-provided name."""
119 name = name.replace(
"::",
"_")
120 if name.startswith(
"@"):
124 if ch.isalnum()
or ch ==
"_":
127 sanitized.append(
"_")
131 sanitized.insert(0,
"_")
132 return "".join(sanitized)
135 """Reserve a globally unique identifier using the sanitized base name."""
137 if is_alias
and base
in self.alias_base_names:
139 f
"Warning: duplicate alias name '{base}' detected; disambiguating.\n")
141 self.alias_base_names.add(base)
144 while name
in self.used_names:
145 name = f
"{base}_{idx}"
147 self.used_names[name] = is_alias
151 """Derive a deterministic name for anonymous structs from their fields."""
153 for field_name, field_type
in struct_type.fields:
154 parts.append(field_name)
159 """Derive a deterministic name for anonymous unions from their fields."""
161 for field_name, field_type
in union_type.fields:
162 parts.append(field_name)
167 """Derive a deterministic name for generated window helpers."""
170 return self.
_reserve_name(f
"_window_{self._sanitize_name(window_type.id)}",
175 wrapped = wrapped.inner_type
188 for field_name, field_type
in into_type.fields:
190 list_fields.append(field_name)
191 if len(list_fields) != 1:
194 list_field_name = list_fields[0]
199 for frame
in current_type.frames:
200 for field
in frame.fields:
201 if field.name != list_field_name:
203 if field.bulk_count_width > 0:
204 if header_field
is not None:
207 elif field.num_items > 0:
208 if data_field
is not None:
211 return (header_field
is not None and data_field
is not None and
212 data_field.num_items == 1)
215 """Return child types in a stable order for traversal."""
217 return [t.inner_type]
if t.inner_type
is not None else []
219 return [channel.type
for channel
in t.channels]
223 return [field_type
for _, field_type
in t.fields]
225 return [field_type
for _, field_type
in t.fields]
227 return [t.element_type]
231 return [t.element_type]
234 def _visit_types(self, t: types.ESIType, visited: Set[str], visit_fn) ->
None:
235 """Traverse types with alphabetical child ordering in post-order."""
237 raise TypeError(f
"Expected ESIType, got {type(t)}")
243 for child
in children:
248 """Scan for aliases and reserve their names (recursive)."""
251 def visit(alias_type: types.ESIType) ->
None:
254 if alias_type
not in self.type_id_map:
255 alias_name = self.
_reserve_name(alias_type.name, is_alias=
True)
256 self.type_id_map[alias_type] = alias_name
261 """Scan for structs/unions needing auto-names and reserve them."""
264 def visit(current_type: types.ESIType) ->
None:
265 if current_type
in self.type_id_map:
275 """Scan for supported window types and reserve helper names."""
277 def visit(current_type: types.ESIType) ->
None:
281 if current_type
in self.type_id_map:
289 """Collect types that require top-level declarations for a given type."""
294 def visit(current: types.ESIType) ->
None:
296 inner = current.inner_type
297 if inner
is not None and (isinstance(
313 """Collect only the declarations referenced by a generated window helper."""
319 for _, field_type
in into_type.fields:
328 """Collect and order types for deterministic emission."""
330 for esi_type
in self.type_id_map.keys():
336 for esi_type
in self.type_id_map.keys():
337 if isinstance(esi_type,
341 inner = esi_type.inner_type
343 inner)
in window_into_types:
345 if (isinstance(esi_type,
348 emit_types.append(esi_type)
351 name_to_type = {self.type_id_map[t]: t
for t
in emit_types}
352 sorted_names = sorted(name_to_type.keys(),
354 (0
if self.used_names.get(name,
False)
else 1, name))
362 def visit(current: types.ESIType) ->
None:
364 if current
in visited:
366 if current
in visiting:
369 visiting.add(current)
373 inner = current.inner_type
374 if inner
is not None:
377 for _, field_type
in current.fields:
382 for dep
in sorted(deps, key=
lambda dep: self.type_id_map[dep]):
385 visiting.remove(current)
387 ordered.append(current)
389 for name
in sorted_names:
390 visit(name_to_type[name])
392 return ordered, has_cycle
396 """Emit C++ headers from precomputed type ordering."""
398 def __init__(self, planner: CppTypePlanner) ->
None:
404 """Get the C++ type string for an ESI type."""
408 """Escape a Python string for use as a C++ string literal."""
409 escaped = value.replace(
"\\",
"\\\\").replace(
'"',
'\\"')
410 return f
'"{escaped}"'
413 """Get the textual code for the storage class of an integer type."""
420 """Get the textual code for a byte-addressable integer storage type."""
426 elif bit_width <= 16:
428 elif bit_width <= 32:
430 elif bit_width <= 64:
433 raise ValueError(f
"Unsupported integer width: {bit_width}")
436 return f
"uint{storage_width}_t"
437 return f
"int{storage_width}_t"
440 """Return the size of a fixed-width type in bytes."""
441 if wrapped.bit_width < 0:
442 raise ValueError(f
"Unsupported unbounded type width for '{wrapped}'")
443 return (wrapped.bit_width + 7) // 8
446 array_type: types.ArrayType) -> Tuple[str, str]:
447 """Return the base C++ type and bracket suffix for a nested array."""
451 dims.append(inner.size)
452 inner = inner.element_type
454 suffix =
"".join([f
"[{d}]" for d
in dims])
455 return base_cpp, suffix
458 """Return the C++ type string for a nested array alias."""
460 return f
"{base_cpp}{suffix}"
463 """Resolve an ESI type to its C++ identifier."""
466 if isinstance(wrapped,
474 raise ValueError(
"List types require a generated window wrapper")
485 raise NotImplementedError(
486 f
"Type '{wrapped}' not supported for C++ generation")
489 """Strip alias wrappers to reach the underlying type."""
491 wrapped = wrapped.inner_type
495 """Emit a field declaration for a multi-dimensional array member.
497 The declaration flattens nested arrays into repeated bracketed sizes for C++.
500 return f
"{base_cpp} {name}{suffix};"
504 """Emit a packed field declaration for generated window helpers."""
510 wrapped.bit_width % 8 != 0:
511 return f
"{field_cpp} {field_name} : {wrapped.bit_width};"
512 return f
"{field_cpp} {field_name};"
516 """Emit a constructor parameter for generated window helpers.
518 Small scalar header fields are cheaper to pass by value than by reference.
519 Larger aggregates stay as const references.
523 return f
"const {base_cpp} (&{field_name}){suffix}"
527 return f
"{field_cpp} {field_name}"
528 return f
"const {field_cpp} &{field_name}"
532 """Copy a generated window field, preserving array semantics."""
535 f
" std::memcpy(&{dest_expr}, &{src_expr}, sizeof({dest_expr}));\n")
537 hdr.write(f
" {dest_expr} = {src_expr};\n")
540 """Compute the byte width of a field type, rounding up to full bytes."""
541 return (field_type.bit_width + 7) // 8
544 """Extract the metadata needed to emit a bulk list window wrapper."""
547 raise ValueError(
"window codegen currently requires a struct into-type")
549 field_map = {name: field_type
for name, field_type
in into_type.fields}
552 for name, field_type
in into_type.fields
555 if len(list_fields) != 1:
556 raise ValueError(
"window codegen currently supports exactly one list")
558 list_field_name, list_type = list_fields[0]
565 for frame
in window_type.frames:
566 for field
in frame.fields:
567 if field.name != list_field_name:
569 if field.bulk_count_width > 0:
572 elif field.num_items > 0:
576 if header_frame
is None or header_field
is None:
577 raise ValueError(
"window codegen requires a bulk-count header frame")
578 if data_frame
is None or data_field
is None:
579 raise ValueError(
"window codegen requires a data frame for the list")
580 if data_field.num_items != 1:
581 raise ValueError(
"window codegen currently supports numItems == 1")
583 ctor_params = [(name, field_type)
584 for name, field_type
in into_type.fields
585 if name != list_field_name]
589 count_field_name = f
"{list_field_name}_count"
590 count_width = header_field.bulk_count_width
592 count_bytes = (count_width + 7) // 8
593 for field
in reversed(header_frame.fields):
594 if field.name == list_field_name:
595 header_fields.append((count_field_name,
None))
596 header_bytes += count_bytes
598 field_type = field_map[field.name]
599 header_fields.append((field.name, field_type))
604 for field
in reversed(data_frame.fields):
605 if field.name == list_field_name:
606 data_fields.append((list_field_name, list_type.element_type))
609 field_type = field_map[field.name]
610 data_fields.append((field.name, field_type))
613 frame_bytes = max(header_bytes, data_bytes)
616 "ctor_params": ctor_params,
617 "count_cpp": count_cpp,
618 "count_field_name": count_field_name,
619 "count_width": count_width,
620 "data_fields": data_fields,
621 "data_pad_bytes": frame_bytes - data_bytes,
622 "element_cpp": self.
_cpp_type(list_type.element_type),
623 "header_fields": header_fields,
624 "header_pad_bytes": frame_bytes - header_bytes,
625 "list_field_name": list_field_name,
630 """Emit a packed struct declaration plus its type id string."""
631 fields = list(struct_type.fields)
632 if struct_type.cpp_type.reverse:
633 fields = list(reversed(fields))
634 field_decls: List[str] = []
635 for field_name, field_type
in fields:
644 bitfield_width = wrapped.bit_width
645 if bitfield_width >= 0:
646 field_decls.append(f
"{field_cpp} {field_name} : {bitfield_width};")
648 field_decls.append(f
"{field_cpp} {field_name};")
650 field_decls.append(f
"{field_cpp} {field_name};")
651 hdr.write(f
"struct {self.type_id_map[struct_type]} {{\n")
652 for decl
in field_decls:
653 hdr.write(f
" {decl}\n")
656 f
" static constexpr std::string_view _ESI_ID = {self._cpp_string_literal(struct_type.id)};\n"
661 """Emit a packed union declaration plus its type id string.
663 Fields narrower than the union width get wrapper structs with a `_pad`
664 byte array so the data sits at the MSB end, matching SV packed union
665 layout where padding occupies the LSBs / lower addresses.
669 fields = list(union_type.fields)
672 wrapper_names: Dict[str, str] = {}
673 for field_name, field_type
in fields:
675 pad_bytes = union_bytes - field_bytes
677 wrapper = f
"{union_name}_{field_name}"
678 wrapper_names[field_name] = wrapper
679 hdr.write(f
"struct {wrapper} {{\n")
680 hdr.write(f
" uint8_t _pad[{pad_bytes}];\n")
682 hdr.write(f
" {field_cpp} {field_name};\n")
686 union_field_decls: List[str] = []
687 for field_name, field_type
in fields:
688 if field_name
in wrapper_names:
689 union_field_decls.append(f
"{wrapper_names[field_name]} {field_name};")
692 union_field_decls.append(f
"{field_cpp} {field_name};")
693 hdr.write(f
"union {union_name} {{\n")
694 for decl
in union_field_decls:
695 hdr.write(f
" {decl}\n")
698 f
" static constexpr std::string_view _ESI_ID = {self._cpp_string_literal(union_type.id)};\n"
703 """Emit a SegmentedMessageData helper for a serial list window."""
707 for name, field_type
in info[
"ctor_params"]
709 value_ctor_params = list(ctor_params)
710 value_ctor_params.append(
711 f
"const std::vector<value_type> &{info['list_field_name']}")
712 value_ctor_signature =
", ".join(value_ctor_params)
713 frame_ctor_params = list(ctor_params)
714 frame_ctor_params.append(
"std::vector<data_frame> frames")
715 frame_ctor_signature =
", ".join(frame_ctor_params)
716 helper_args =
", ".join(name
for name, _
in info[
"ctor_params"])
717 helper_call = f
"{helper_args}, std::move(frames)" if helper_args
else "std::move(frames)"
720 f
"struct {info['window_name']} : public esi::SegmentedMessageData {{\n")
721 hdr.write(
"public:\n")
722 hdr.write(f
" using value_type = {info['element_cpp']};\n")
723 hdr.write(f
" using count_type = {info['count_cpp']};\n\n")
724 hdr.write(
" struct data_frame {\n")
725 if info[
"data_pad_bytes"] > 0:
726 hdr.write(f
" uint8_t _pad[{info['data_pad_bytes']}];\n")
727 for field_name, field_type
in info[
"data_fields"]:
729 hdr.write(f
" {decl}\n")
731 hdr.write(
"private:\n")
732 hdr.write(
" struct header_frame {\n")
733 if info[
"header_pad_bytes"] > 0:
734 hdr.write(f
" uint8_t _pad[{info['header_pad_bytes']}];\n")
735 for field_name, field_type
in info[
"header_fields"]:
736 if field_type
is None:
737 if info[
"count_width"] % 8 == 0:
738 decl = f
"count_type {field_name};"
740 decl = f
"count_type {field_name} : {info['count_width']};"
743 hdr.write(f
" {decl}\n")
745 hdr.write(
" header_frame header{};\n")
746 hdr.write(
" std::vector<data_frame> data_frames;\n")
747 hdr.write(
" header_frame footer{};\n\n")
748 hdr.write(f
" void construct({frame_ctor_signature}) {{\n")
749 hdr.write(
" if (frames.empty())\n")
751 f
" throw std::invalid_argument(\"{info['window_name']}: bulk windowed lists cannot be empty\");\n"
754 " if (frames.size() > std::numeric_limits<count_type>::max())\n")
756 f
" throw std::invalid_argument(\"{info['window_name']}: list too large for encoded count\");\n"
759 f
" header.{info['count_field_name']} = static_cast<count_type>(frames.size());\n"
761 for name, _
in info[
"ctor_params"]:
763 field_type
for field_name, field_type
in info[
"ctor_params"]
764 if field_name == name)
766 hdr.write(f
" footer.{info['count_field_name']} = 0;\n")
767 hdr.write(
" data_frames = std::move(frames);\n")
769 hdr.write(
"public:\n")
770 hdr.write(f
" {info['window_name']}({frame_ctor_signature}) {{\n")
771 hdr.write(f
" construct({helper_call});\n")
773 hdr.write(f
" {info['window_name']}({value_ctor_signature}) {{\n")
774 hdr.write(
" std::vector<data_frame> frames;\n")
775 hdr.write(f
" frames.reserve({info['list_field_name']}.size());\n")
776 hdr.write(f
" for (const auto &element : {info['list_field_name']}) {{\n")
777 hdr.write(
" auto &frame = frames.emplace_back();\n")
778 list_field_type = next(
779 field_type
for field_name, field_type
in info[
"data_fields"]
780 if field_name == info[
"list_field_name"])
783 f
" std::memcpy(&frame.{info['list_field_name']}, &element, sizeof(frame.{info['list_field_name']}));\n"
786 hdr.write(f
" frame.{info['list_field_name']} = element;\n")
788 hdr.write(f
" construct({helper_call});\n")
790 hdr.write(
" size_t numSegments() const override { return 3; }\n")
791 hdr.write(
" esi::Segment segment(size_t idx) const override {\n")
792 hdr.write(
" if (idx == 0)\n")
794 " return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};\n"
796 hdr.write(
" if (idx == 1)\n")
798 " return {reinterpret_cast<const uint8_t *>(data_frames.data()),\n"
800 hdr.write(
" data_frames.size() * sizeof(data_frame)};\n")
801 hdr.write(
" if (idx == 2)\n")
803 " return {reinterpret_cast<const uint8_t *>(&footer), sizeof(footer)};\n"
806 f
" throw std::out_of_range(\"{info['window_name']}: invalid segment index\");\n"
810 f
" static constexpr std::string_view _ESI_ID = {self._cpp_string_literal(window_type.id)};\n"
815 """Emit a using alias when the alias targets a different C++ type."""
816 inner_wrapped = alias_type.inner_type
819 if inner_wrapped
is not None:
820 inner_cpp = self.
_cpp_type(inner_wrapped)
821 if inner_cpp
is None:
823 if inner_cpp != alias_name:
824 hdr.write(f
"using {alias_name} = {inner_cpp};\n\n")
827 """Emit the fully ordered types.h header into the output directory."""
828 hdr_file = output_dir /
"types.h"
829 with open(hdr_file,
"w")
as hdr:
832 // Generated header for {system_name} types.
841 #include <string_view>
845 #include "esi/Common.h"
847 namespace {system_name} {{
848 #pragma pack(push, 1)
852 sys.stderr.write(
"Warning: cyclic type dependencies detected.\n")
853 sys.stderr.write(
" Logically this should not be possible.\n")
855 " Emitted code may fail to compile due to ordering issues.\n")
867 except ValueError
as e:
868 sys.stderr.write(f
"Error emitting type '{emit_type}': {e}\n")
869 hdr.write(f
"// Unsupported type '{emit_type}': {e}\n\n")
874 }} // namespace {system_name}
878def run(generator: Type[Generator] = CppGenerator,
879 cmdline_args=sys.argv) -> int:
880 """Create and run a generator reading options from the command line."""
882 argparser = argparse.ArgumentParser(
883 description=f
"Generate {generator.language} headers from an ESI manifest",
884 formatter_class=argparse.RawDescriptionHelpFormatter,
885 epilog=textwrap.dedent(
"""
886 Can read the manifest from either a file OR a running accelerator.
889 # To read the manifest from a file:
890 esi-cppgen --file /path/to/manifest.json
892 # To read the manifest from a running accelerator:
893 esi-cppgen --platform cosim --connection localhost:1234
896 argparser.add_argument(
"--file",
899 help=
"Path to the manifest file.")
900 argparser.add_argument(
903 help=
"Name of platform for live accelerator connection.")
904 argparser.add_argument(
907 help=
"Connection string for live accelerator connection.")
908 argparser.add_argument(
912 help=
"Output directory for generated files. Recommend adding either `esi`"
913 " or the system name to the end of the path so as to avoid header name"
914 "conflicts. Defaults to `esi`")
915 argparser.add_argument(
918 default=
"esi_system",
919 help=
"Name of the ESI system. For C++, this will be the namespace.")
921 if (len(cmdline_args) <= 1):
922 argparser.print_help()
924 args = argparser.parse_args(cmdline_args[1:])
926 if args.file
is not None and args.platform
is not None:
927 print(
"Cannot specify both --file and --platform")
930 conn: AcceleratorConnection
931 if args.file
is not None:
934 conn = Context.default().
connect(
"trace", f
"-{os.pathsep}{args.file}")
935 elif args.platform
is not None:
936 if args.connection
is None:
937 print(
"Must specify --connection with --platform")
939 conn = Context.default().
connect(args.platform, args.connection)
941 print(
"Must specify either --file or --platform")
944 output_dir = Path(args.output_dir)
945 if output_dir.exists()
and not output_dir.is_dir():
946 print(f
"Output directory {output_dir} is not a directory")
948 if not output_dir.exists():
949 output_dir.mkdir(parents=
True)
951 gen = generator(conn)
952 gen.generate(output_dir, args.system_name)
956if __name__ ==
'__main__':
static void print(TypedAttr val, llvm::raw_ostream &os)
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
str get_consts_str(self, ModuleInfo module_info)
__init__(self, AcceleratorConnection conn)
write_modules(self, Path output_dir, str system_name)
generate(self, Path output_dir, str system_name)
str _format_array_type(self, types.ArrayType array_type)
None _emit_struct(self, TextIO hdr, types.StructType struct_type)
str _format_window_field_decl(self, str field_name, types.ESIType field_type)
_analyze_window(self, types.WindowType window_type)
str _storage_type(self, int bit_width, bool signed)
str _get_bitvector_str(self, types.ESIType type)
None write_header(self, Path output_dir, str system_name)
str _format_window_ctor_param(self, str field_name, types.ESIType field_type)
str _format_array_decl(self, types.ArrayType array_type, str name)
None _emit_alias(self, TextIO hdr, types.TypeAlias alias_type)
Tuple[str, str] _array_base_and_suffix(self, types.ArrayType array_type)
None __init__(self, CppTypePlanner planner)
int _field_byte_width(self, types.ESIType field_type)
int _type_byte_width(self, types.ESIType wrapped)
types.ESIType _unwrap_aliases(self, types.ESIType wrapped)
str type_identifier(self, types.ESIType type)
None _emit_union(self, TextIO hdr, types.UnionType union_type)
str _cpp_string_literal(self, str value)
None _emit_window(self, TextIO hdr, types.WindowType window_type)
None _emit_window_field_copy(self, TextIO hdr, str dest_expr, str src_expr, types.ESIType field_type)
str _cpp_type(self, types.ESIType wrapped)
str _auto_window_name(self, types.WindowType window_type)
Set[types.ESIType] _collect_decls_from_window(self, types.WindowType window_type)
types.ESIType _unwrap_aliases(self, types.ESIType wrapped)
str _sanitize_name(self, str name)
bool _is_supported_window(self, types.ESIType current_type)
None _visit_types(self, types.ESIType t, Set[str] visited, visit_fn)
str _reserve_name(self, str base, bool is_alias)
Tuple[List[types.ESIType], bool] _ordered_emit_types(self)
List[types.ESIType] _iter_type_children(self, types.ESIType t)
None __init__(self, type_table)
Set[types.ESIType] _collect_decls_from_type(self, types.ESIType wrapped)
str _auto_struct_name(self, types.StructType struct_type)
str _auto_union_name(self, types.UnionType union_type)
None _collect_windows(self, types.ESIType t, Set[str] visited)
None _collect_structs(self, types.ESIType t, Set[str] visited)
None _prepare_types(self, type_table)
None _collect_aliases(self, types.ESIType t, Set[str] visited)
generate(self, Path output_dir, str system_name)
__init__(self, AcceleratorConnection conn)
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
"AcceleratorConnection" connect(str platform, str connection_str)