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
20from pathlib
import Path
24_thisdir = Path(__file__).absolute().
resolve().parent
28 """Base class for all generators."""
30 language: Optional[str] =
None
32 def __init__(self, conn: AcceleratorConnection):
35 def generate(self, output_dir: Path, system_name: str):
36 raise NotImplementedError(
"Generator.generate() must be overridden")
40 """Generate C++ headers from an ESI manifest."""
44 def __init__(self, conn: AcceleratorConnection):
50 """Get the C++ code for a constant in a module."""
51 const_strs: List[str] = [
52 f
"static constexpr {self.type_emitter.type_identifier(const.type)} "
53 f
"{name} = 0x{const.value:x};"
54 for name, const
in module_info.constants.items()
56 return "\n".join(const_strs)
59 """Write the C++ header. One for each module in the manifest."""
61 for module_info
in self.
manifest.module_infos:
63 /// Generated header for {system_name} module {module_info.name}.
67 namespace {system_name} {{
68 class {module_info.name} {{
70 {self.get_consts_str(module_info)}
72 }} // namespace {system_name}
75 hdr_file = output_dir / f
"{module_info.name}.h"
76 with open(hdr_file,
"w")
as hdr:
77 hdr.write(textwrap.dedent(s))
79 def generate(self, output_dir: Path, system_name: str):
85 """Plan C++ type naming and ordering from an ESI manifest."""
88 """Initialize the generator with the manifest and target namespace."""
92 self.used_names: Dict[str, bool] = {}
94 self.alias_base_names: Set[str] = set()
100 """Name the types and prepare for emission by registering all reachable
101 types and assigning."""
102 visited: Set[str] = set()
113 """Create a C++-safe identifier from the manifest-provided name."""
114 name = name.replace(
"::",
"_")
115 if name.startswith(
"@"):
119 if ch.isalnum()
or ch ==
"_":
122 sanitized.append(
"_")
126 sanitized.insert(0,
"_")
127 return "".join(sanitized)
130 """Reserve a globally unique identifier using the sanitized base name."""
132 if is_alias
and base
in self.alias_base_names:
134 f
"Warning: duplicate alias name '{base}' detected; disambiguating.\n")
136 self.alias_base_names.add(base)
139 while name
in self.used_names:
140 name = f
"{base}_{idx}"
142 self.used_names[name] = is_alias
146 """Derive a deterministic name for anonymous structs from their fields."""
148 for field_name, field_type
in struct_type.fields:
149 parts.append(field_name)
154 """Return child types in a stable order for traversal."""
156 return [t.inner_type]
if t.inner_type
is not None else []
158 return [channel.type
for channel
in t.channels]
162 return [field_type
for _, field_type
in t.fields]
164 return [t.element_type]
167 def _visit_types(self, t: types.ESIType, visited: Set[str], visit_fn) ->
None:
168 """Traverse types with alphabetical child ordering in post-order."""
170 raise TypeError(f
"Expected ESIType, got {type(t)}")
176 for child
in children:
181 """Scan for aliases and reserve their names (recursive)."""
184 def visit(alias_type: types.ESIType) ->
None:
187 if alias_type
not in self.type_id_map:
188 alias_name = self.
_reserve_name(alias_type.name, is_alias=
True)
189 self.type_id_map[alias_type] = alias_name
194 """Scan for structs needing auto-names and reserve them (recursive)."""
197 def visit(struct_type: types.ESIType) ->
None:
200 if struct_type
in self.type_id_map:
203 self.type_id_map[struct_type] = struct_name
209 """Collect types that require top-level declarations for a given type."""
213 def visit(current: types.ESIType) ->
None:
215 inner = current.inner_type
227 """Collect and order types for deterministic emission."""
229 for esi_type
in self.type_id_map.keys():
231 emit_types.append(esi_type)
234 name_to_type = {self.type_id_map[t]: t
for t
in emit_types}
235 sorted_names = sorted(name_to_type.keys(),
237 (0
if self.used_names.get(name,
False)
else 1, name))
245 def visit(current: types.ESIType) ->
None:
247 if current
in visited:
249 if current
in visiting:
252 visiting.add(current)
256 inner = current.inner_type
257 if inner
is not None:
260 for _, field_type
in current.fields:
262 for dep
in sorted(deps, key=
lambda dep: self.type_id_map[dep]):
265 visiting.remove(current)
267 ordered.append(current)
269 for name
in sorted_names:
270 visit(name_to_type[name])
272 return ordered, has_cycle
276 """Emit C++ headers from precomputed type ordering."""
278 def __init__(self, planner: CppTypePlanner) ->
None:
284 """Get the C++ type string for an ESI type."""
288 """Get the textual code for the storage class of an integer type."""
291 if type.bit_width == 1:
293 elif type.bit_width <= 8:
295 elif type.bit_width <= 16:
297 elif type.bit_width <= 32:
299 elif type.bit_width <= 64:
302 raise ValueError(f
"Unsupported integer width: {type.bit_width}")
305 return f
"uint{storage_width}_t"
306 return f
"int{storage_width}_t"
309 array_type: types.ArrayType) -> Tuple[str, str]:
310 """Return the base C++ type and bracket suffix for a nested array."""
314 dims.append(inner.size)
315 inner = inner.element_type
317 suffix =
"".join([f
"[{d}]" for d
in dims])
318 return base_cpp, suffix
321 """Return the C++ type string for a nested array alias."""
323 return f
"{base_cpp}{suffix}"
326 """Resolve an ESI type to its C++ identifier."""
343 raise NotImplementedError(
344 f
"Type '{wrapped}' not supported for C++ generation")
347 """Strip alias wrappers to reach the underlying type."""
349 wrapped = wrapped.inner_type
353 """Emit a field declaration for a multi-dimensional array member.
355 The declaration flattens nested arrays into repeated bracketed sizes for C++.
358 return f
"{base_cpp} {name}{suffix};"
361 """Emit a packed struct declaration plus its type id string."""
362 fields = list(struct_type.fields)
363 if struct_type.cpp_type.reverse:
364 fields = list(reversed(fields))
365 field_decls: List[str] = []
366 for field_name, field_type
in fields:
375 bitfield_width = wrapped.bit_width
376 if bitfield_width >= 0:
377 field_decls.append(f
"{field_cpp} {field_name} : {bitfield_width};")
379 field_decls.append(f
"{field_cpp} {field_name};")
381 field_decls.append(f
"{field_cpp} {field_name};")
382 hdr.write(f
"struct {self.type_id_map[struct_type]} {{\n")
383 for decl
in field_decls:
384 hdr.write(f
" {decl}\n")
387 f
" static constexpr std::string_view _ESI_ID = \"{struct_type.id}\";\n"
392 """Emit a using alias when the alias targets a different C++ type."""
393 inner_wrapped = alias_type.inner_type
396 if inner_wrapped
is not None:
397 inner_cpp = self.
_cpp_type(inner_wrapped)
398 if inner_cpp
is None:
400 if inner_cpp != alias_name:
401 hdr.write(f
"using {alias_name} = {inner_cpp};\n\n")
404 """Emit the fully ordered types.h header into the output directory."""
405 hdr_file = output_dir /
"types.h"
406 with open(hdr_file,
"w")
as hdr:
409 // Generated header for {system_name} types.
414 #include <string_view>
416 namespace {system_name} {{
417 #pragma pack(push, 1)
421 sys.stderr.write(
"Warning: cyclic type dependencies detected.\n")
422 sys.stderr.write(
" Logically this should not be possible.\n")
424 " Emitted code may fail to compile due to ordering issues.\n")
435 }} // namespace {system_name}
439def run(generator: Type[Generator] = CppGenerator,
440 cmdline_args=sys.argv) -> int:
441 """Create and run a generator reading options from the command line."""
443 argparser = argparse.ArgumentParser(
444 description=f
"Generate {generator.language} headers from an ESI manifest",
445 formatter_class=argparse.RawDescriptionHelpFormatter,
446 epilog=textwrap.dedent(
"""
447 Can read the manifest from either a file OR a running accelerator.
450 # To read the manifest from a file:
451 esi-cppgen --file /path/to/manifest.json
453 # To read the manifest from a running accelerator:
454 esi-cppgen --platform cosim --connection localhost:1234
457 argparser.add_argument(
"--file",
460 help=
"Path to the manifest file.")
461 argparser.add_argument(
464 help=
"Name of platform for live accelerator connection.")
465 argparser.add_argument(
468 help=
"Connection string for live accelerator connection.")
469 argparser.add_argument(
473 help=
"Output directory for generated files. Recommend adding either `esi`"
474 " or the system name to the end of the path so as to avoid header name"
475 "conflicts. Defaults to `esi`")
476 argparser.add_argument(
479 default=
"esi_system",
480 help=
"Name of the ESI system. For C++, this will be the namespace.")
482 if (len(cmdline_args) <= 1):
483 argparser.print_help()
485 args = argparser.parse_args(cmdline_args[1:])
487 if args.file
is not None and args.platform
is not None:
488 print(
"Cannot specify both --file and --platform")
491 conn: AcceleratorConnection
492 if args.file
is not None:
493 conn = Context.default().
connect(
"trace", f
"-:{args.file}")
494 elif args.platform
is not None:
495 if args.connection
is None:
496 print(
"Must specify --connection with --platform")
498 conn = Context.default().
connect(args.platform, args.connection)
500 print(
"Must specify either --file or --platform")
503 output_dir = Path(args.output_dir)
504 if output_dir.exists()
and not output_dir.is_dir():
505 print(f
"Output directory {output_dir} is not a directory")
507 if not output_dir.exists():
508 output_dir.mkdir(parents=
True)
510 gen = generator(conn)
511 gen.generate(output_dir, args.system_name)
515if __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 _get_bitvector_str(self, types.ESIType type)
None write_header(self, Path output_dir, str system_name)
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)
types.ESIType _unwrap_aliases(self, types.ESIType wrapped)
str type_identifier(self, types.ESIType type)
str _cpp_type(self, types.ESIType wrapped)
str _sanitize_name(self, str name)
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)
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)