CIRCT 23.0.0git
Loading...
Searching...
No Matches
codegen.py
Go to the documentation of this file.
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4"""Code generation from ESI manifests to source code.
5
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.
9"""
10
11# C++ header support included with the runtime, though it is intended to be
12# extensible for other languages.
13
14from typing import Dict, List, Set, TextIO, Tuple, Type, Optional
15from .accelerator import AcceleratorConnection, Context
16from .esiCppAccel import ModuleInfo
17from . import types
18
19import sys
20import os
21import textwrap
22import argparse
23from pathlib import Path
24
25_thisdir = Path(__file__).absolute().resolve().parent
26
27
29 """Base class for all generators."""
30
31 language: Optional[str] = None
32
33 def __init__(self, conn: AcceleratorConnection):
34 self.manifest = conn.manifest()
35
36 def generate(self, output_dir: Path, system_name: str):
37 raise NotImplementedError("Generator.generate() must be overridden")
38
39
41 """Generate C++ headers from an ESI manifest."""
42
43 language = "C++"
44
45 def __init__(self, conn: AcceleratorConnection):
46 super().__init__(conn)
47 self.type_planner = CppTypePlanner(self.manifest.type_table)
49
50 def get_consts_str(self, module_info: ModuleInfo) -> str:
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()
56 ]
57 return "\n".join(const_strs)
58
59 def write_modules(self, output_dir: Path, system_name: str):
60 """Write the C++ header. One for each module in the manifest."""
61
62 for module_info in self.manifest.module_infos:
63 s = f"""
64 /// Generated header for {system_name} module {module_info.name}.
65 #pragma once
66 #include "types.h"
67
68 namespace {system_name} {{
69 class {module_info.name} {{
70 public:
71 {self.get_consts_str(module_info)}
72 }};
73 }} // namespace {system_name}
74 """
75
76 hdr_file = output_dir / f"{module_info.name}.h"
77 with open(hdr_file, "w") as hdr:
78 hdr.write(textwrap.dedent(s))
79
80 def generate(self, output_dir: Path, system_name: str):
81 self.type_emitter.write_header(output_dir, system_name)
82 self.write_modules(output_dir, system_name)
83
84
86 """Plan C++ type naming and ordering from an ESI manifest."""
87
88 def __init__(self, type_table) -> None:
89 """Initialize the generator with the manifest and target namespace."""
90 # Map manifest type ids to their preferred C++ names.
91 self.type_id_map: Dict[types.ESIType, str] = {}
92 # Track all names already taken to avoid collisions. True => alias-based.
93 self.used_names: Dict[str, bool] = {}
94 # Track alias base names to warn on collisions.
95 self.alias_base_names: Set[str] = set()
96 self.ordered_types: List[types.ESIType] = []
97 self.has_cycle = False
98 self._prepare_types(type_table)
99
100 def _prepare_types(self, type_table) -> None:
101 """Name the types and prepare for emission by registering all reachable
102 types and assigning."""
103 visited: Set[str] = set()
104 for t in type_table:
105 self._collect_aliases(t, visited)
106
107 visited = set()
108 for t in type_table:
109 self._collect_structs(t, visited)
110
111 visited = set()
112 for t in type_table:
113 self._collect_windows(t, visited)
114
116
117 def _sanitize_name(self, name: str) -> str:
118 """Create a C++-safe identifier from the manifest-provided name."""
119 name = name.replace("::", "_")
120 if name.startswith("@"):
121 name = name[1:]
122 sanitized = []
123 for ch in name:
124 if ch.isalnum() or ch == "_":
125 sanitized.append(ch)
126 else:
127 sanitized.append("_")
128 if not sanitized:
129 return "Type"
130 if sanitized[0].isdigit():
131 sanitized.insert(0, "_")
132 return "".join(sanitized)
133
134 def _reserve_name(self, base: str, is_alias: bool) -> str:
135 """Reserve a globally unique identifier using the sanitized base name."""
136 base = self._sanitize_name(base)
137 if is_alias and base in self.alias_base_names:
138 sys.stderr.write(
139 f"Warning: duplicate alias name '{base}' detected; disambiguating.\n")
140 if is_alias:
141 self.alias_base_names.add(base)
142 name = base
143 idx = 1
144 while name in self.used_names:
145 name = f"{base}_{idx}"
146 idx += 1
147 self.used_names[name] = is_alias
148 return name
149
150 def _auto_struct_name(self, struct_type: types.StructType) -> str:
151 """Derive a deterministic name for anonymous structs from their fields."""
152 parts = ["_struct"]
153 for field_name, field_type in struct_type.fields:
154 parts.append(field_name)
155 parts.append(self._sanitize_name(field_type.id))
156 return self._reserve_name("_".join(parts), is_alias=False)
157
158 def _auto_union_name(self, union_type: types.UnionType) -> str:
159 """Derive a deterministic name for anonymous unions from their fields."""
160 parts = ["_union"]
161 for field_name, field_type in union_type.fields:
162 parts.append(field_name)
163 parts.append(self._sanitize_name(field_type.id))
164 return self._reserve_name("_".join(parts), is_alias=False)
165
166 def _auto_window_name(self, window_type: types.WindowType) -> str:
167 """Derive a deterministic name for generated window helpers."""
168 if window_type.name:
169 return self._reserve_name(window_type.name, is_alias=False)
170 return self._reserve_name(f"_window_{self._sanitize_name(window_type.id)}",
171 is_alias=False)
172
173 def _unwrap_aliases(self, wrapped: types.ESIType) -> types.ESIType:
174 while isinstance(wrapped, types.TypeAlias):
175 wrapped = wrapped.inner_type
176 return wrapped
177
178 def _is_supported_window(self, current_type: types.ESIType) -> bool:
179 if not isinstance(current_type, types.WindowType):
180 return False
181 into_type = self._unwrap_aliases(current_type.into_type)
182 if not isinstance(into_type, types.StructType):
183 return False
184
185 # The generated window helper only supports struct-shaped payloads with a
186 # single logical list field to stream across multiple frames.
187 list_fields = []
188 for field_name, field_type in into_type.fields:
189 if isinstance(self._unwrap_aliases(field_type), types.ListType):
190 list_fields.append(field_name)
191 if len(list_fields) != 1:
192 return False
193
194 list_field_name = list_fields[0]
195 header_field = None
196 data_field = None
197 # That list must appear exactly once as a bulk-count field and exactly once
198 # as a single-item data field so the helper can synthesize header/data/footer.
199 for frame in current_type.frames:
200 for field in frame.fields:
201 if field.name != list_field_name:
202 continue
203 if field.bulk_count_width > 0:
204 if header_field is not None:
205 return False
206 header_field = field
207 elif field.num_items > 0:
208 if data_field is not None:
209 return False
210 data_field = field
211 return (header_field is not None and data_field is not None and
212 data_field.num_items == 1)
213
214 def _iter_type_children(self, t: types.ESIType) -> List[types.ESIType]:
215 """Return child types in a stable order for traversal."""
216 if isinstance(t, types.TypeAlias):
217 return [t.inner_type] if t.inner_type is not None else []
218 if isinstance(t, types.BundleType):
219 return [channel.type for channel in t.channels]
220 if isinstance(t, types.ChannelType):
221 return [t.inner]
222 if isinstance(t, types.StructType):
223 return [field_type for _, field_type in t.fields]
224 if isinstance(t, types.UnionType):
225 return [field_type for _, field_type in t.fields]
226 if isinstance(t, types.ListType):
227 return [t.element_type]
228 if isinstance(t, types.WindowType):
229 return [t.into_type]
230 if isinstance(t, types.ArrayType):
231 return [t.element_type]
232 return []
233
234 def _visit_types(self, t: types.ESIType, visited: Set[str], visit_fn) -> None:
235 """Traverse types with alphabetical child ordering in post-order."""
236 if not isinstance(t, types.ESIType):
237 raise TypeError(f"Expected ESIType, got {type(t)}")
238 tid = t.id
239 if tid in visited:
240 return
241 visited.add(tid)
242 children = sorted(self._iter_type_children(t), key=lambda child: child.id)
243 for child in children:
244 self._visit_types(child, visited, visit_fn)
245 visit_fn(t)
246
247 def _collect_aliases(self, t: types.ESIType, visited: Set[str]) -> None:
248 """Scan for aliases and reserve their names (recursive)."""
249
250 # Visit callback: reserve alias names and map aliases to identifiers.
251 def visit(alias_type: types.ESIType) -> None:
252 if not isinstance(alias_type, types.TypeAlias):
253 return
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
257
258 self._visit_types(t, visited, visit)
259
260 def _collect_structs(self, t: types.ESIType, visited: Set[str]) -> None:
261 """Scan for structs/unions needing auto-names and reserve them."""
262
263 # Visit callback: assign auto-names to unnamed structs and unions.
264 def visit(current_type: types.ESIType) -> None:
265 if current_type in self.type_id_map:
266 return
267 if isinstance(current_type, types.StructType):
268 self.type_id_map[current_type] = self._auto_struct_name(current_type)
269 elif isinstance(current_type, types.UnionType):
270 self.type_id_map[current_type] = self._auto_union_name(current_type)
271
272 self._visit_types(t, visited, visit)
273
274 def _collect_windows(self, t: types.ESIType, visited: Set[str]) -> None:
275 """Scan for supported window types and reserve helper names."""
276
277 def visit(current_type: types.ESIType) -> None:
278 if not self._is_supported_window(current_type):
279 return
280 assert isinstance(current_type, types.WindowType)
281 if current_type in self.type_id_map:
282 return
283 self.type_id_map[current_type] = self._auto_window_name(current_type)
284
285 self._visit_types(t, visited, visit)
286
288 wrapped: types.ESIType) -> Set[types.ESIType]:
289 """Collect types that require top-level declarations for a given type."""
290 deps: Set[types.ESIType] = set()
291
292 # Visit callback: collect structs, unions, and non-struct aliases used by a
293 # type.
294 def visit(current: types.ESIType) -> None:
295 if isinstance(current, types.TypeAlias):
296 inner = current.inner_type
297 if inner is not None and (isinstance(
299 self._is_supported_window(inner)):
300 deps.add(inner)
301 else:
302 deps.add(current)
303 elif isinstance(current, (types.StructType, types.UnionType)):
304 deps.add(current)
305 elif self._is_supported_window(current):
306 deps.add(current)
307
308 self._visit_types(wrapped, set(), visit)
309 return deps
310
312 self, window_type: types.WindowType) -> Set[types.ESIType]:
313 """Collect only the declarations referenced by a generated window helper."""
314 deps: Set[types.ESIType] = set()
315 into_type = self._unwrap_aliases(window_type.into_type)
316 if not isinstance(into_type, types.StructType):
317 return deps
318
319 for _, field_type in into_type.fields:
320 unwrapped = self._unwrap_aliases(field_type)
321 if isinstance(unwrapped, types.ListType):
322 deps.update(self._collect_decls_from_type(unwrapped.element_type))
323 else:
324 deps.update(self._collect_decls_from_type(field_type))
325 return deps
326
327 def _ordered_emit_types(self) -> Tuple[List[types.ESIType], bool]:
328 """Collect and order types for deterministic emission."""
329 window_into_types: Set[types.ESIType] = set()
330 for esi_type in self.type_id_map.keys():
331 if not self._is_supported_window(esi_type):
332 continue
333 assert isinstance(esi_type, types.WindowType)
334 window_into_types.add(self._unwrap_aliases(esi_type.into_type))
335 emit_types: List[types.ESIType] = []
336 for esi_type in self.type_id_map.keys():
337 if isinstance(esi_type,
338 types.StructType) and esi_type in window_into_types:
339 continue
340 if isinstance(esi_type, types.TypeAlias):
341 inner = esi_type.inner_type
342 if inner is not None and self._unwrap_aliases(
343 inner) in window_into_types:
344 continue
345 if (isinstance(esi_type,
347 self._is_supported_window(esi_type)):
348 emit_types.append(esi_type)
349
350 # Prefer alias-reserved names first, then lexicographic for determinism.
351 name_to_type = {self.type_id_map[t]: t for t in emit_types}
352 sorted_names = sorted(name_to_type.keys(),
353 key=lambda name:
354 (0 if self.used_names.get(name, False) else 1, name))
355
356 ordered: List[types.ESIType] = []
357 visited: Set[types.ESIType] = set()
358 visiting: Set[types.ESIType] = set()
359 has_cycle = False
360
361 # Visit callback: DFS to emit dependencies before their users.
362 def visit(current: types.ESIType) -> None:
363 nonlocal has_cycle
364 if current in visited:
365 return
366 if current in visiting:
367 has_cycle = True
368 return
369 visiting.add(current)
370
371 deps: Set[types.ESIType] = set()
372 if isinstance(current, types.TypeAlias):
373 inner = current.inner_type
374 if inner is not None:
375 deps.update(self._collect_decls_from_type(inner))
376 elif isinstance(current, (types.StructType, types.UnionType)):
377 for _, field_type in current.fields:
378 deps.update(self._collect_decls_from_type(field_type))
379 elif self._is_supported_window(current):
380 assert isinstance(current, types.WindowType)
381 deps.update(self._collect_decls_from_window(current))
382 for dep in sorted(deps, key=lambda dep: self.type_id_map[dep]):
383 visit(dep)
384
385 visiting.remove(current)
386 visited.add(current)
387 ordered.append(current)
388
389 for name in sorted_names:
390 visit(name_to_type[name])
391
392 return ordered, has_cycle
393
394
396 """Emit C++ headers from precomputed type ordering."""
397
398 def __init__(self, planner: CppTypePlanner) -> None:
399 self.type_id_map = planner.type_id_map
400 self.ordered_types = planner.ordered_types
401 self.has_cycle = planner.has_cycle
402
403 def type_identifier(self, type: types.ESIType) -> str:
404 """Get the C++ type string for an ESI type."""
405 return self._cpp_type(type)
406
407 def _cpp_string_literal(self, value: str) -> str:
408 """Escape a Python string for use as a C++ string literal."""
409 escaped = value.replace("\\", "\\\\").replace('"', '\\"')
410 return f'"{escaped}"'
411
412 def _get_bitvector_str(self, type: types.ESIType) -> str:
413 """Get the textual code for the storage class of an integer type."""
414 assert isinstance(type, (types.BitsType, types.IntType))
415
416 return self._storage_type(
417 type.bit_width, not isinstance(type, (types.BitsType, types.UIntType)))
418
419 def _storage_type(self, bit_width: int, signed: bool) -> str:
420 """Get the textual code for a byte-addressable integer storage type."""
421
422 if bit_width == 1:
423 return "bool"
424 elif bit_width <= 8:
425 storage_width = 8
426 elif bit_width <= 16:
427 storage_width = 16
428 elif bit_width <= 32:
429 storage_width = 32
430 elif bit_width <= 64:
431 storage_width = 64
432 else:
433 raise ValueError(f"Unsupported integer width: {bit_width}")
434
435 if not signed:
436 return f"uint{storage_width}_t"
437 return f"int{storage_width}_t"
438
439 def _type_byte_width(self, wrapped: types.ESIType) -> int:
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
444
446 array_type: types.ArrayType) -> Tuple[str, str]:
447 """Return the base C++ type and bracket suffix for a nested array."""
448 dims: List[int] = []
449 inner: types.ESIType = array_type
450 while isinstance(inner, types.ArrayType):
451 dims.append(inner.size)
452 inner = inner.element_type
453 base_cpp = self._cpp_type(inner)
454 suffix = "".join([f"[{d}]" for d in dims])
455 return base_cpp, suffix
456
457 def _format_array_type(self, array_type: types.ArrayType) -> str:
458 """Return the C++ type string for a nested array alias."""
459 base_cpp, suffix = self._array_base_and_suffix(array_type)
460 return f"{base_cpp}{suffix}"
461
462 def _cpp_type(self, wrapped: types.ESIType) -> str:
463 """Resolve an ESI type to its C++ identifier."""
464 if isinstance(wrapped, types.WindowType) and wrapped in self.type_id_map:
465 return self.type_id_map[wrapped]
466 if isinstance(wrapped,
468 return self.type_id_map[wrapped]
469 if isinstance(wrapped, types.BundleType):
470 return "void"
471 if isinstance(wrapped, types.ChannelType):
472 return self._cpp_type(wrapped.inner)
473 if isinstance(wrapped, types.ListType):
474 raise ValueError("List types require a generated window wrapper")
475 if isinstance(wrapped, types.VoidType):
476 return "void"
477 if isinstance(wrapped, types.AnyType):
478 return "std::any"
479 if isinstance(wrapped, (types.BitsType, types.IntType)):
480 return self._get_bitvector_str(wrapped)
481 if isinstance(wrapped, types.ArrayType):
482 return self._format_array_type(wrapped)
483 if type(wrapped) is types.ESIType:
484 return "std::any"
485 raise NotImplementedError(
486 f"Type '{wrapped}' not supported for C++ generation")
487
488 def _unwrap_aliases(self, wrapped: types.ESIType) -> types.ESIType:
489 """Strip alias wrappers to reach the underlying type."""
490 while isinstance(wrapped, types.TypeAlias):
491 wrapped = wrapped.inner_type
492 return wrapped
493
494 def _format_array_decl(self, array_type: types.ArrayType, name: str) -> str:
495 """Emit a field declaration for a multi-dimensional array member.
496
497 The declaration flattens nested arrays into repeated bracketed sizes for C++.
498 """
499 base_cpp, suffix = self._array_base_and_suffix(array_type)
500 return f"{base_cpp} {name}{suffix};"
501
502 def _format_window_field_decl(self, field_name: str,
503 field_type: types.ESIType) -> str:
504 """Emit a packed field declaration for generated window helpers."""
505 if isinstance(field_type, types.ArrayType):
506 return self._format_array_decl(field_type, field_name)
507 field_cpp = self._cpp_type(field_type)
508 wrapped = self._unwrap_aliases(field_type)
509 if isinstance(wrapped, (types.BitsType, types.IntType)) and \
510 wrapped.bit_width % 8 != 0:
511 return f"{field_cpp} {field_name} : {wrapped.bit_width};"
512 return f"{field_cpp} {field_name};"
513
514 def _format_window_ctor_param(self, field_name: str,
515 field_type: types.ESIType) -> str:
516 """Emit a constructor parameter for generated window helpers.
517
518 Small scalar header fields are cheaper to pass by value than by reference.
519 Larger aggregates stay as const references.
520 """
521 if isinstance(field_type, types.ArrayType):
522 base_cpp, suffix = self._array_base_and_suffix(field_type)
523 return f"const {base_cpp} (&{field_name}){suffix}"
524 field_cpp = self._cpp_type(field_type)
525 wrapped = self._unwrap_aliases(field_type)
526 if isinstance(wrapped, (types.BitsType, types.IntType)):
527 return f"{field_cpp} {field_name}"
528 return f"const {field_cpp} &{field_name}"
529
530 def _emit_window_field_copy(self, hdr: TextIO, dest_expr: str, src_expr: str,
531 field_type: types.ESIType) -> None:
532 """Copy a generated window field, preserving array semantics."""
533 if isinstance(field_type, types.ArrayType):
534 hdr.write(
535 f" std::memcpy(&{dest_expr}, &{src_expr}, sizeof({dest_expr}));\n")
536 return
537 hdr.write(f" {dest_expr} = {src_expr};\n")
538
539 def _field_byte_width(self, field_type: types.ESIType) -> int:
540 """Compute the byte width of a field type, rounding up to full bytes."""
541 return (field_type.bit_width + 7) // 8
542
543 def _analyze_window(self, window_type: types.WindowType):
544 """Extract the metadata needed to emit a bulk list window wrapper."""
545 into_type = self._unwrap_aliases(window_type.into_type)
546 if not isinstance(into_type, types.StructType):
547 raise ValueError("window codegen currently requires a struct into-type")
548
549 field_map = {name: field_type for name, field_type in into_type.fields}
550 list_fields = [
551 (name, self._unwrap_aliases(field_type))
552 for name, field_type in into_type.fields
553 if isinstance(self._unwrap_aliases(field_type), types.ListType)
554 ]
555 if len(list_fields) != 1:
556 raise ValueError("window codegen currently supports exactly one list")
557
558 list_field_name, list_type = list_fields[0]
559 assert isinstance(list_type, types.ListType)
560
561 header_frame = None
562 header_field = None
563 data_frame = None
564 data_field = None
565 for frame in window_type.frames:
566 for field in frame.fields:
567 if field.name != list_field_name:
568 continue
569 if field.bulk_count_width > 0:
570 header_frame = frame
571 header_field = field
572 elif field.num_items > 0:
573 data_frame = frame
574 data_field = field
575
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")
582
583 ctor_params = [(name, field_type)
584 for name, field_type in into_type.fields
585 if name != list_field_name]
586
587 header_fields = []
588 header_bytes = 0
589 count_field_name = f"{list_field_name}_count"
590 count_width = header_field.bulk_count_width
591 count_cpp = self._storage_type(count_width, signed=False)
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
597 else:
598 field_type = field_map[field.name]
599 header_fields.append((field.name, field_type))
600 header_bytes += self._type_byte_width(field_type)
601
602 data_fields = []
603 data_bytes = 0
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))
607 data_bytes += self._type_byte_width(list_type.element_type)
608 else:
609 field_type = field_map[field.name]
610 data_fields.append((field.name, field_type))
611 data_bytes += self._type_byte_width(field_type)
612
613 frame_bytes = max(header_bytes, data_bytes)
614
615 return {
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,
626 "window_name": self.type_id_map[window_type],
627 }
628
629 def _emit_struct(self, hdr: TextIO, struct_type: types.StructType) -> None:
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:
636 if isinstance(field_type, types.ArrayType):
637 field_decls.append(self._format_array_decl(field_type, field_name))
638 else:
639 field_cpp = self._cpp_type(field_type)
640 wrapped = self._unwrap_aliases(field_type)
641 if isinstance(wrapped, (types.BitsType, types.IntType)):
642 # TODO: Bitfield layout is implementation-defined; consider
643 # byte-aligned storage with explicit pack/unpack helpers.
644 bitfield_width = wrapped.bit_width
645 if bitfield_width >= 0:
646 field_decls.append(f"{field_cpp} {field_name} : {bitfield_width};")
647 else:
648 field_decls.append(f"{field_cpp} {field_name};")
649 else:
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")
654 hdr.write("\n")
655 hdr.write(
656 f" static constexpr std::string_view _ESI_ID = {self._cpp_string_literal(struct_type.id)};\n"
657 )
658 hdr.write("};\n\n")
659
660 def _emit_union(self, hdr: TextIO, union_type: types.UnionType) -> None:
661 """Emit a packed union declaration plus its type id string.
662
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.
666 """
667 union_name = self.type_id_map[union_type]
668 union_bytes = self._field_byte_width(union_type)
669 fields = list(union_type.fields)
670
671 # First pass: emit wrapper structs for fields that need padding.
672 wrapper_names: Dict[str, str] = {}
673 for field_name, field_type in fields:
674 field_bytes = self._field_byte_width(field_type)
675 pad_bytes = union_bytes - field_bytes
676 if pad_bytes > 0:
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")
681 field_cpp = self._cpp_type(field_type)
682 hdr.write(f" {field_cpp} {field_name};\n")
683 hdr.write("};\n")
684
685 # Second pass: emit the union itself.
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};")
690 else:
691 field_cpp = self._cpp_type(field_type)
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")
696 hdr.write("\n")
697 hdr.write(
698 f" static constexpr std::string_view _ESI_ID = {self._cpp_string_literal(union_type.id)};\n"
699 )
700 hdr.write("};\n\n")
701
702 def _emit_window(self, hdr: TextIO, window_type: types.WindowType) -> None:
703 """Emit a SegmentedMessageData helper for a serial list window."""
704 info = self._analyze_window(window_type)
705 ctor_params = [
706 self._format_window_ctor_param(name, field_type)
707 for name, field_type in info["ctor_params"]
708 ]
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)"
718
719 hdr.write(
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"]:
728 decl = self._format_window_field_decl(field_name, field_type)
729 hdr.write(f" {decl}\n")
730 hdr.write(" };\n\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};"
739 else:
740 decl = f"count_type {field_name} : {info['count_width']};"
741 else:
742 decl = self._format_window_field_decl(field_name, field_type)
743 hdr.write(f" {decl}\n")
744 hdr.write(" };\n\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")
750 hdr.write(
751 f" throw std::invalid_argument(\"{info['window_name']}: bulk windowed lists cannot be empty\");\n"
752 )
753 hdr.write(
754 " if (frames.size() > std::numeric_limits<count_type>::max())\n")
755 hdr.write(
756 f" throw std::invalid_argument(\"{info['window_name']}: list too large for encoded count\");\n"
757 )
758 hdr.write(
759 f" header.{info['count_field_name']} = static_cast<count_type>(frames.size());\n"
760 )
761 for name, _ in info["ctor_params"]:
762 field_type = next(
763 field_type for field_name, field_type in info["ctor_params"]
764 if field_name == name)
765 self._emit_window_field_copy(hdr, f"header.{name}", name, field_type)
766 hdr.write(f" footer.{info['count_field_name']} = 0;\n")
767 hdr.write(" data_frames = std::move(frames);\n")
768 hdr.write(" }\n\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")
772 hdr.write(" }\n\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"])
781 if isinstance(list_field_type, types.ArrayType):
782 hdr.write(
783 f" std::memcpy(&frame.{info['list_field_name']}, &element, sizeof(frame.{info['list_field_name']}));\n"
784 )
785 else:
786 hdr.write(f" frame.{info['list_field_name']} = element;\n")
787 hdr.write(" }\n")
788 hdr.write(f" construct({helper_call});\n")
789 hdr.write(" }\n\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")
793 hdr.write(
794 " return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};\n"
795 )
796 hdr.write(" if (idx == 1)\n")
797 hdr.write(
798 " return {reinterpret_cast<const uint8_t *>(data_frames.data()),\n"
799 )
800 hdr.write(" data_frames.size() * sizeof(data_frame)};\n")
801 hdr.write(" if (idx == 2)\n")
802 hdr.write(
803 " return {reinterpret_cast<const uint8_t *>(&footer), sizeof(footer)};\n"
804 )
805 hdr.write(
806 f" throw std::out_of_range(\"{info['window_name']}: invalid segment index\");\n"
807 )
808 hdr.write(" }\n\n")
809 hdr.write(
810 f" static constexpr std::string_view _ESI_ID = {self._cpp_string_literal(window_type.id)};\n"
811 )
812 hdr.write("};\n\n")
813
814 def _emit_alias(self, hdr: TextIO, alias_type: types.TypeAlias) -> None:
815 """Emit a using alias when the alias targets a different C++ type."""
816 inner_wrapped = alias_type.inner_type
817 alias_name = self.type_id_map[alias_type]
818 inner_cpp = None
819 if inner_wrapped is not None:
820 inner_cpp = self._cpp_type(inner_wrapped)
821 if inner_cpp is None:
822 inner_cpp = self.type_id_map[alias_type]
823 if inner_cpp != alias_name:
824 hdr.write(f"using {alias_name} = {inner_cpp};\n\n")
825
826 def write_header(self, output_dir: Path, system_name: str) -> None:
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:
830 hdr.write(
831 textwrap.dedent(f"""
832 // Generated header for {system_name} types.
833 #pragma once
834
835 #include <cstdint>
836 #include <cstddef>
837 #include <any>
838 #include <cstring>
839 #include <limits>
840 #include <stdexcept>
841 #include <string_view>
842 #include <utility>
843 #include <vector>
844
845 #include "esi/Common.h"
846
847 namespace {system_name} {{
848 #pragma pack(push, 1)
849
850 """))
851 if self.has_cycle:
852 sys.stderr.write("Warning: cyclic type dependencies detected.\n")
853 sys.stderr.write(" Logically this should not be possible.\n")
854 sys.stderr.write(
855 " Emitted code may fail to compile due to ordering issues.\n")
856
857 for emit_type in self.ordered_types:
858 try:
859 if isinstance(emit_type, types.StructType):
860 self._emit_struct(hdr, emit_type)
861 elif isinstance(emit_type, types.UnionType):
862 self._emit_union(hdr, emit_type)
863 elif isinstance(emit_type, types.WindowType):
864 self._emit_window(hdr, emit_type)
865 elif isinstance(emit_type, types.TypeAlias):
866 self._emit_alias(hdr, emit_type)
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")
870
871 hdr.write(
872 textwrap.dedent(f"""
873 #pragma pack(pop)
874 }} // namespace {system_name}
875 """))
876
877
878def run(generator: Type[Generator] = CppGenerator,
879 cmdline_args=sys.argv) -> int:
880 """Create and run a generator reading options from the command line."""
881
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.
887
888 Usage examples:
889 # To read the manifest from a file:
890 esi-cppgen --file /path/to/manifest.json
891
892 # To read the manifest from a running accelerator:
893 esi-cppgen --platform cosim --connection localhost:1234
894 """))
895
896 argparser.add_argument("--file",
897 type=str,
898 default=None,
899 help="Path to the manifest file.")
900 argparser.add_argument(
901 "--platform",
902 type=str,
903 help="Name of platform for live accelerator connection.")
904 argparser.add_argument(
905 "--connection",
906 type=str,
907 help="Connection string for live accelerator connection.")
908 argparser.add_argument(
909 "--output-dir",
910 type=str,
911 default="esi",
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(
916 "--system-name",
917 type=str,
918 default="esi_system",
919 help="Name of the ESI system. For C++, this will be the namespace.")
920
921 if (len(cmdline_args) <= 1):
922 argparser.print_help()
923 return 1
924 args = argparser.parse_args(cmdline_args[1:])
925
926 if args.file is not None and args.platform is not None:
927 print("Cannot specify both --file and --platform")
928 return 1
929
930 conn: AcceleratorConnection
931 if args.file is not None:
932 # Use os.pathsep (';' on Windows, ':' on Unix) to avoid conflicts with
933 # drive letters.
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")
938 return 1
939 conn = Context.default().connect(args.platform, args.connection)
940 else:
941 print("Must specify either --file or --platform")
942 return 1
943
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")
947 return 1
948 if not output_dir.exists():
949 output_dir.mkdir(parents=True)
950
951 gen = generator(conn)
952 gen.generate(output_dir, args.system_name)
953 return 0
954
955
956if __name__ == '__main__':
957 sys.exit(run())
static void print(TypedAttr val, llvm::raw_ostream &os)
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
#define isdigit(x)
Definition FIRLexer.cpp:26
str get_consts_str(self, ModuleInfo module_info)
Definition codegen.py:50
__init__(self, AcceleratorConnection conn)
Definition codegen.py:45
write_modules(self, Path output_dir, str system_name)
Definition codegen.py:59
generate(self, Path output_dir, str system_name)
Definition codegen.py:80
str _format_array_type(self, types.ArrayType array_type)
Definition codegen.py:457
None _emit_struct(self, TextIO hdr, types.StructType struct_type)
Definition codegen.py:629
str _format_window_field_decl(self, str field_name, types.ESIType field_type)
Definition codegen.py:503
_analyze_window(self, types.WindowType window_type)
Definition codegen.py:543
str _storage_type(self, int bit_width, bool signed)
Definition codegen.py:419
str _get_bitvector_str(self, types.ESIType type)
Definition codegen.py:412
None write_header(self, Path output_dir, str system_name)
Definition codegen.py:826
str _format_window_ctor_param(self, str field_name, types.ESIType field_type)
Definition codegen.py:515
str _format_array_decl(self, types.ArrayType array_type, str name)
Definition codegen.py:494
None _emit_alias(self, TextIO hdr, types.TypeAlias alias_type)
Definition codegen.py:814
Tuple[str, str] _array_base_and_suffix(self, types.ArrayType array_type)
Definition codegen.py:446
None __init__(self, CppTypePlanner planner)
Definition codegen.py:398
int _field_byte_width(self, types.ESIType field_type)
Definition codegen.py:539
int _type_byte_width(self, types.ESIType wrapped)
Definition codegen.py:439
types.ESIType _unwrap_aliases(self, types.ESIType wrapped)
Definition codegen.py:488
str type_identifier(self, types.ESIType type)
Definition codegen.py:403
None _emit_union(self, TextIO hdr, types.UnionType union_type)
Definition codegen.py:660
str _cpp_string_literal(self, str value)
Definition codegen.py:407
None _emit_window(self, TextIO hdr, types.WindowType window_type)
Definition codegen.py:702
None _emit_window_field_copy(self, TextIO hdr, str dest_expr, str src_expr, types.ESIType field_type)
Definition codegen.py:531
str _cpp_type(self, types.ESIType wrapped)
Definition codegen.py:462
str _auto_window_name(self, types.WindowType window_type)
Definition codegen.py:166
Set[types.ESIType] _collect_decls_from_window(self, types.WindowType window_type)
Definition codegen.py:312
types.ESIType _unwrap_aliases(self, types.ESIType wrapped)
Definition codegen.py:173
str _sanitize_name(self, str name)
Definition codegen.py:117
bool _is_supported_window(self, types.ESIType current_type)
Definition codegen.py:178
None _visit_types(self, types.ESIType t, Set[str] visited, visit_fn)
Definition codegen.py:234
str _reserve_name(self, str base, bool is_alias)
Definition codegen.py:134
Tuple[List[types.ESIType], bool] _ordered_emit_types(self)
Definition codegen.py:327
List[types.ESIType] _iter_type_children(self, types.ESIType t)
Definition codegen.py:214
None __init__(self, type_table)
Definition codegen.py:88
Set[types.ESIType] _collect_decls_from_type(self, types.ESIType wrapped)
Definition codegen.py:288
str _auto_struct_name(self, types.StructType struct_type)
Definition codegen.py:150
str _auto_union_name(self, types.UnionType union_type)
Definition codegen.py:158
None _collect_windows(self, types.ESIType t, Set[str] visited)
Definition codegen.py:274
None _collect_structs(self, types.ESIType t, Set[str] visited)
Definition codegen.py:260
None _prepare_types(self, type_table)
Definition codegen.py:100
None _collect_aliases(self, types.ESIType t, Set[str] visited)
Definition codegen.py:247
generate(self, Path output_dir, str system_name)
Definition codegen.py:36
__init__(self, AcceleratorConnection conn)
Definition codegen.py:33
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:879
"AcceleratorConnection" connect(str platform, str connection_str)
Definition __init__.py:27