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()
114 """Create a C++-safe identifier from the manifest-provided name."""
115 name = name.replace(
"::",
"_")
116 if name.startswith(
"@"):
120 if ch.isalnum()
or ch ==
"_":
123 sanitized.append(
"_")
127 sanitized.insert(0,
"_")
128 return "".join(sanitized)
131 """Reserve a globally unique identifier using the sanitized base name."""
133 if is_alias
and base
in self.alias_base_names:
135 f
"Warning: duplicate alias name '{base}' detected; disambiguating.\n")
137 self.alias_base_names.add(base)
140 while name
in self.used_names:
141 name = f
"{base}_{idx}"
143 self.used_names[name] = is_alias
147 """Derive a deterministic name for anonymous structs from their fields."""
149 for field_name, field_type
in struct_type.fields:
150 parts.append(field_name)
155 """Return child types in a stable order for traversal."""
157 return [t.inner_type]
if t.inner_type
is not None else []
159 return [channel.type
for channel
in t.channels]
163 return [field_type
for _, field_type
in t.fields]
165 return [t.element_type]
168 def _visit_types(self, t: types.ESIType, visited: Set[str], visit_fn) ->
None:
169 """Traverse types with alphabetical child ordering in post-order."""
171 raise TypeError(f
"Expected ESIType, got {type(t)}")
177 for child
in children:
182 """Scan for aliases and reserve their names (recursive)."""
185 def visit(alias_type: types.ESIType) ->
None:
188 if alias_type
not in self.type_id_map:
189 alias_name = self.
_reserve_name(alias_type.name, is_alias=
True)
190 self.type_id_map[alias_type] = alias_name
195 """Scan for structs needing auto-names and reserve them (recursive)."""
198 def visit(struct_type: types.ESIType) ->
None:
201 if struct_type
in self.type_id_map:
204 self.type_id_map[struct_type] = struct_name
210 """Collect types that require top-level declarations for a given type."""
214 def visit(current: types.ESIType) ->
None:
216 inner = current.inner_type
228 """Collect and order types for deterministic emission."""
230 for esi_type
in self.type_id_map.keys():
232 emit_types.append(esi_type)
235 name_to_type = {self.type_id_map[t]: t
for t
in emit_types}
236 sorted_names = sorted(name_to_type.keys(),
238 (0
if self.used_names.get(name,
False)
else 1, name))
246 def visit(current: types.ESIType) ->
None:
248 if current
in visited:
250 if current
in visiting:
253 visiting.add(current)
257 inner = current.inner_type
258 if inner
is not None:
261 for _, field_type
in current.fields:
263 for dep
in sorted(deps, key=
lambda dep: self.type_id_map[dep]):
266 visiting.remove(current)
268 ordered.append(current)
270 for name
in sorted_names:
271 visit(name_to_type[name])
273 return ordered, has_cycle
277 """Emit C++ headers from precomputed type ordering."""
279 def __init__(self, planner: CppTypePlanner) ->
None:
285 """Get the C++ type string for an ESI type."""
289 """Get the textual code for the storage class of an integer type."""
292 if type.bit_width == 1:
294 elif type.bit_width <= 8:
296 elif type.bit_width <= 16:
298 elif type.bit_width <= 32:
300 elif type.bit_width <= 64:
303 raise ValueError(f
"Unsupported integer width: {type.bit_width}")
306 return f
"uint{storage_width}_t"
307 return f
"int{storage_width}_t"
310 array_type: types.ArrayType) -> Tuple[str, str]:
311 """Return the base C++ type and bracket suffix for a nested array."""
315 dims.append(inner.size)
316 inner = inner.element_type
318 suffix =
"".join([f
"[{d}]" for d
in dims])
319 return base_cpp, suffix
322 """Return the C++ type string for a nested array alias."""
324 return f
"{base_cpp}{suffix}"
327 """Resolve an ESI type to its C++ identifier."""
344 raise NotImplementedError(
345 f
"Type '{wrapped}' not supported for C++ generation")
348 """Strip alias wrappers to reach the underlying type."""
350 wrapped = wrapped.inner_type
354 """Emit a field declaration for a multi-dimensional array member.
356 The declaration flattens nested arrays into repeated bracketed sizes for C++.
359 return f
"{base_cpp} {name}{suffix};"
362 """Emit a packed struct declaration plus its type id string."""
363 fields = list(struct_type.fields)
364 if struct_type.cpp_type.reverse:
365 fields = list(reversed(fields))
366 field_decls: List[str] = []
367 for field_name, field_type
in fields:
376 bitfield_width = wrapped.bit_width
377 if bitfield_width >= 0:
378 field_decls.append(f
"{field_cpp} {field_name} : {bitfield_width};")
380 field_decls.append(f
"{field_cpp} {field_name};")
382 field_decls.append(f
"{field_cpp} {field_name};")
383 hdr.write(f
"struct {self.type_id_map[struct_type]} {{\n")
384 for decl
in field_decls:
385 hdr.write(f
" {decl}\n")
388 f
" static constexpr std::string_view _ESI_ID = \"{struct_type.id}\";\n"
393 """Emit a using alias when the alias targets a different C++ type."""
394 inner_wrapped = alias_type.inner_type
397 if inner_wrapped
is not None:
398 inner_cpp = self.
_cpp_type(inner_wrapped)
399 if inner_cpp
is None:
401 if inner_cpp != alias_name:
402 hdr.write(f
"using {alias_name} = {inner_cpp};\n\n")
405 """Emit the fully ordered types.h header into the output directory."""
406 hdr_file = output_dir /
"types.h"
407 with open(hdr_file,
"w")
as hdr:
410 // Generated header for {system_name} types.
415 #include <string_view>
417 namespace {system_name} {{
418 #pragma pack(push, 1)
422 sys.stderr.write(
"Warning: cyclic type dependencies detected.\n")
423 sys.stderr.write(
" Logically this should not be possible.\n")
425 " Emitted code may fail to compile due to ordering issues.\n")
433 except ValueError
as e:
434 sys.stderr.write(f
"Error emitting type '{emit_type}': {e}\n")
435 hdr.write(f
"// Unsupported type '{emit_type}': {e}\n\n")
440 }} // namespace {system_name}
444def run(generator: Type[Generator] = CppGenerator,
445 cmdline_args=sys.argv) -> int:
446 """Create and run a generator reading options from the command line."""
448 argparser = argparse.ArgumentParser(
449 description=f
"Generate {generator.language} headers from an ESI manifest",
450 formatter_class=argparse.RawDescriptionHelpFormatter,
451 epilog=textwrap.dedent(
"""
452 Can read the manifest from either a file OR a running accelerator.
455 # To read the manifest from a file:
456 esi-cppgen --file /path/to/manifest.json
458 # To read the manifest from a running accelerator:
459 esi-cppgen --platform cosim --connection localhost:1234
462 argparser.add_argument(
"--file",
465 help=
"Path to the manifest file.")
466 argparser.add_argument(
469 help=
"Name of platform for live accelerator connection.")
470 argparser.add_argument(
473 help=
"Connection string for live accelerator connection.")
474 argparser.add_argument(
478 help=
"Output directory for generated files. Recommend adding either `esi`"
479 " or the system name to the end of the path so as to avoid header name"
480 "conflicts. Defaults to `esi`")
481 argparser.add_argument(
484 default=
"esi_system",
485 help=
"Name of the ESI system. For C++, this will be the namespace.")
487 if (len(cmdline_args) <= 1):
488 argparser.print_help()
490 args = argparser.parse_args(cmdline_args[1:])
492 if args.file
is not None and args.platform
is not None:
493 print(
"Cannot specify both --file and --platform")
496 conn: AcceleratorConnection
497 if args.file
is not None:
500 conn = Context.default().
connect(
"trace", f
"-{os.pathsep}{args.file}")
501 elif args.platform
is not None:
502 if args.connection
is None:
503 print(
"Must specify --connection with --platform")
505 conn = Context.default().
connect(args.platform, args.connection)
507 print(
"Must specify either --file or --platform")
510 output_dir = Path(args.output_dir)
511 if output_dir.exists()
and not output_dir.is_dir():
512 print(f
"Output directory {output_dir} is not a directory")
514 if not output_dir.exists():
515 output_dir.mkdir(parents=
True)
517 gen = generator(conn)
518 gen.generate(output_dir, args.system_name)
522if __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)