CIRCT 23.0.0git
Loading...
Searching...
No Matches
test_codegen.py
Go to the documentation of this file.
1"""Tests for UnionType support in codegen (CppTypePlanner + CppTypeEmitter)."""
2
3import re
4import tempfile
5from pathlib import Path
6
7import esiaccel.types as types
8from esiaccel.codegen import CppTypePlanner, CppTypeEmitter
9
10
11def _generate_header(type_table, system_name="test_ns"):
12 """Helper: run the planner + emitter on a type table and return the header."""
13 planner = CppTypePlanner(type_table)
14 emitter = CppTypeEmitter(planner)
15 with tempfile.TemporaryDirectory() as tmpdir:
16 emitter.write_header(Path(tmpdir), system_name)
17 return (Path(tmpdir) / "types.h").read_text()
18
19
20def _window_struct_name(hdr, window_name_suffix):
21 """Find the generated SegmentedMessageData subclass name for a window.
22
23 Auto-names compose `{into_name}_{window_name}`, so locate the helper by its
24 trailing window-name suffix instead of hard-coding the full identifier.
25 """
26 match = re.search(
27 rf"struct (\S*{re.escape(window_name_suffix)}) "
28 r": public esi::SegmentedMessageData", hdr)
29 assert match, f"Window helper for '*{window_name_suffix}' not found in:\n{hdr}"
30 return match.group(1)
31
32
34 """A simple union with two scalar fields produces a C++ union."""
35 uint8 = types.UIntType("ui8", 8)
36 uint16 = types.UIntType("ui16", 16)
37 union_t = types.UnionType("!hw.union<a: ui8, b: ui16>", [("a", uint8),
38 ("b", uint16)])
39
40 hdr = _generate_header([union_t])
41 assert "union " in hdr
42 assert '_ESI_ID = "!hw.union<a: ui8, b: ui16>"' in hdr
43 # Field "a" (8 bits) is narrower than the 16-bit union → wrapper struct
44 # with _pad before the data field.
45 assert "struct _union_a_ui8_b_ui16_a" in hdr
46 assert "uint8_t _pad[1]" in hdr
47 assert "uint8_t a;" in hdr
48 # In the wrapper struct, _pad appears before the field.
49 pad_pos = hdr.index("uint8_t _pad[1]")
50 field_a_pos = hdr.index("uint8_t a;")
51 assert pad_pos < field_a_pos
52 # Field "b" (16 bits) is full width → no wrapper, appears directly in union.
53 assert "uint16_t b;" in hdr
54 # Wrapper struct for "a" is emitted before the union keyword.
55 wrapper_pos = hdr.index("struct _union_a_ui8_b_ui16_a")
56 union_pos = hdr.index("union ")
57 assert wrapper_pos < union_pos
58 # Inside the union, "a" comes before "b" (declaration order preserved).
59 union_body = hdr[union_pos:]
60 assert union_body.index("a;") < union_body.index("b;")
61 # The union member for "a" uses the wrapper struct type, not a raw int.
62 assert "_union_a_ui8_b_ui16_a a;" in union_body
63
64
66 """A union containing a struct field emits the struct before the union."""
67 uint8 = types.UIntType("ui8", 8)
68 uint16 = types.UIntType("ui16", 16)
69 inner = types.StructType("!hw.struct<x: ui8, y: ui8>", [("x", uint8),
70 ("y", uint8)])
71 union_t = types.UnionType("!hw.union<header: ui16, data: !s>",
72 [("header", uint16), ("data", inner)])
73
74 hdr = _generate_header([union_t])
75 # The inner struct must appear before the wrapper structs and union.
76 struct_pos = hdr.index("struct _struct")
77 union_pos = hdr.index("union ")
78 assert struct_pos < union_pos
79 assert "data;" in hdr
80 # "header" is 16 bits in a 16-bit union (both fields are 16 bits),
81 # so no padding wrapper is needed for either field.
82 assert "_pad" not in hdr
83
84
86 """Unions are properly ordered with respect to struct dependencies."""
87 uint8 = types.UIntType("ui8", 8)
88 s1 = types.StructType("!hw.struct<p: ui8>", [("p", uint8)])
89 s2 = types.StructType("!hw.struct<q: ui8>", [("q", uint8)])
90 union_t = types.UnionType("!hw.union<a: !s1, b: !s2>", [("a", s1), ("b", s2)])
91
92 hdr = _generate_header([union_t])
93 union_pos = hdr.index("union ")
94 # Both structs should appear before the union.
95 for keyword in ["struct _struct_p_ui8", "struct _struct_q_ui8"]:
96 assert keyword in hdr
97 assert hdr.index(keyword) < union_pos
98
99
101 """A struct with a union field emits the union before the struct."""
102 uint8 = types.UIntType("ui8", 8)
103 uint16 = types.UIntType("ui16", 16)
104 union_t = types.UnionType("!hw.union<a: ui8, b: ui16>", [("a", uint8),
105 ("b", uint16)])
106 outer = types.StructType("!hw.struct<tag: ui8, data: !u>",
107 [("tag", uint8), ("data", union_t)])
108
109 hdr = _generate_header([outer])
110 assert "union " in hdr
111 # Wrapper struct for padded field "a" and the union both precede the
112 # outer struct that references the union.
113 wrapper_pos = hdr.index("struct _union")
114 union_pos = hdr.index("union ")
115 struct_pos = hdr.index("struct _struct")
116 assert wrapper_pos < union_pos < struct_pos
117
118
120 """The planner auto-generates deterministic names for unions."""
121 uint8 = types.UIntType("ui8", 8)
122 union_t = types.UnionType("!hw.union<x: ui8>", [("x", uint8)])
123
124 planner = CppTypePlanner([union_t])
125 assert union_t in planner.type_id_map
126 name = planner.type_id_map[union_t]
127 assert name.startswith("_union")
128
129
131 """A TypeAlias wrapping a union emits the union then a using alias."""
132 uint8 = types.UIntType("ui8", 8)
133 uint16 = types.UIntType("ui16", 16)
134 union_t = types.UnionType("!hw.union<a: ui8, b: ui16>", [("a", uint8),
135 ("b", uint16)])
136 alias = types.TypeAlias("!hw.typealias<MyUnion>", "MyUnion", union_t)
137
138 hdr = _generate_header([alias])
139 assert "union " in hdr
140 assert "using MyUnion" in hdr
141
142
144 """Integrals of the same width don't need padding wrappers."""
145 uint16 = types.UIntType("ui16", 16)
146 sint16 = types.SIntType("si16", 16)
147 union_t = types.UnionType("!hw.union<a: ui16, b: si16>", [("a", uint16),
148 ("b", sint16)])
149
150 hdr = _generate_header([union_t])
151 assert "union " in hdr
152 assert "_pad" not in hdr
153 # Both fields appear directly in the union as raw types.
154 union_body = hdr[hdr.index("union "):]
155 assert "uint16_t a;" in union_body
156 assert "int16_t b;" in union_body
157
158
160 """Union fields are emitted in declaration order, not reversed."""
161 uint8 = types.UIntType("ui8", 8)
162 sint16 = types.SIntType("si16", 16)
163 uint32 = types.UIntType("ui32", 32)
164 union_t = types.UnionType("!hw.union<z: ui8, m: si16, a: ui32>",
165 [("z", uint8), ("m", sint16), ("a", uint32)])
166
167 hdr = _generate_header([union_t])
168 # Fields z (8 bit) and m (16 bit) need padding wrappers; a (32 bit) doesn't.
169 assert "_pad[3]" in hdr # z wrapper: 4 - 1 = 3 bytes padding
170 assert "_pad[2]" in hdr # m wrapper: 4 - 2 = 2 bytes padding
171 # In each wrapper struct, _pad appears before the data field.
172 z_pad = hdr.index("_pad[3]")
173 z_field = hdr.index("uint8_t z;")
174 assert z_pad < z_field
175 m_pad = hdr.index("_pad[2]")
176 m_field = hdr.index("int16_t m;")
177 assert m_pad < m_field
178 # Inside the union body, field order is preserved (z, m, a).
179 union_body = hdr[hdr.index("union "):]
180 z_pos = union_body.index(" z;")
181 m_pos = union_body.index(" m;")
182 a_pos = union_body.index(" a;")
183 assert z_pos < m_pos < a_pos
184 # Field a (full width) has no wrapper.
185 assert "uint32_t a;" in union_body
186 # Wrapped fields use wrapper struct types as union members.
187 assert "_union_z_ui8_m_si16_a_ui32_z z;" in union_body
188 assert "_union_z_ui8_m_si16_a_ui32_m m;" in union_body
189
190
192 """Bulk-encoded list windows emit a SegmentedMessageData helper."""
193 uint16 = types.UIntType("ui16", 16)
194 uint32 = types.UIntType("ui32", 32)
195 coord_struct_id = "!hw.struct<x: ui32, y: ui32>"
196 coord_alias_id = (
197 f"!hw.typealias<@esi_runtime_codegen::@Coord, {coord_struct_id}>")
198 coord_list_id = f"!esi.list<{coord_alias_id}>"
199 arg_struct_id = (
200 f"!hw.struct<x_translation: ui32, y_translation: ui32, coords: "
201 f"{coord_list_id}>")
202 header_struct_id = (
203 "!hw.struct<x_translation: ui32, y_translation: ui32, coords_count: "
204 "ui16>")
205 data_struct_id = f"!hw.struct<coords: !hw.array<1x{coord_alias_id}>>"
206 lowered_id = f"!hw.union<header: {header_struct_id}, data: {data_struct_id}>"
207 serial_args_id = (f'!esi.window<"serial_coord_args", {arg_struct_id}, '
208 '[<"header", [<"x_translation">, <"y_translation">, '
209 '<"coords" countWidth 16>]>, <"data", [<"coords", 1>]>]>')
210
211 coord_inner = types.StructType(coord_struct_id, [("x", uint32),
212 ("y", uint32)])
213 coord = types.TypeAlias(coord_alias_id, "Coord", coord_inner)
214 coord_list = types.ListType(coord_list_id, coord)
215 arg_struct = types.StructType(arg_struct_id, [("x_translation", uint32),
216 ("y_translation", uint32),
217 ("coords", coord_list)])
218 header_struct = types.StructType(header_struct_id, [("x_translation", uint32),
219 ("y_translation", uint32),
220 ("coords_count", uint16)])
221 data_struct = types.StructType(
222 data_struct_id,
223 [("coords", types.ArrayType(f"!hw.array<1x{coord_alias_id}>", coord, 1))],
224 )
225 lowered = types.UnionType(lowered_id, [("header", header_struct),
226 ("data", data_struct)])
227 serial_args = types.WindowType(
228 serial_args_id, "serial_coord_args", arg_struct, lowered, [
229 types.WindowType.Frame(
230 "header",
231 [
232 types.WindowType.Field("x_translation", 0, 0),
233 types.WindowType.Field("y_translation", 0, 0),
234 types.WindowType.Field("coords", 0, 16),
235 ],
236 ),
237 types.WindowType.Frame(
238 "data",
239 [types.WindowType.Field("coords", 1, 0)],
240 ),
241 ])
242
243 hdr = _generate_header([coord, serial_args])
244 assert "Unsupported type" not in hdr
245 win_name = _window_struct_name(hdr, "_serial_coord_args")
246 assert f"struct {win_name} : public esi::SegmentedMessageData" in hdr
247 assert "using value_type = Coord;" in hdr
248 assert "using count_type = uint16_t;" in hdr
249 assert "count_type coords_count;" in hdr
250 assert "uint8_t _pad[2];" in hdr
251 assert "Coord coords;" in hdr
252 assert hdr.index("struct data_frame {") < hdr.index(
253 "private:\n#pragma pack(push, 1)\n struct header_frame {")
254 assert "std::vector<data_frame> data_frames;" in hdr
255 assert "esi::Segment segment(size_t idx) const override" in hdr
256 assert "footer.coords_count = 0;" in hdr
257 assert "const std::vector<value_type> &coords" in hdr
258 assert "void construct(uint32_t x_translation, uint32_t y_translation, std::vector<data_frame> frames)" in hdr
259 assert "construct(x_translation, y_translation, std::move(frames));" in hdr
260 assert "auto &frame = frames.emplace_back();" in hdr
261 assert "for (const auto &element : coords) {" in hdr
262 assert "frame.coords = element;" in hdr
263 # Inner struct id is _ESI_ID; window id (with escaped quotes) is
264 # _ESI_WINDOW_ID so the runtime can verify the wire format too.
265 assert f'_ESI_ID = "{arg_struct_id}"' in hdr
266 escaped_serial_args_id = serial_args_id.replace('"', '\\"')
267 assert f'_ESI_WINDOW_ID = "{escaped_serial_args_id}"' in hdr
268 assert f'throw std::out_of_range("{win_name}: invalid segment index")' in hdr
269 # Accessor methods for header fields and data fields.
270 assert "uint32_t x_translation() const { return header.x_translation; }" in hdr
271 assert "uint32_t y_translation() const { return header.y_translation; }" in hdr
272 assert "size_t coords_count() const { return data_frames.size(); }" in hdr
273 # Byte-aligned data field: pointer-to-member projection (zero-copy view).
274 assert "return std::views::transform(data_frames, &data_frame::coords);" in hdr
275 assert "std::vector<value_type> coords_vector() const" in hdr
276 assert "out.push_back(frame.coords);" in hdr
277
278
280 """Headers pad out to the data frame width for count-only windows."""
281 uint16 = types.UIntType("ui16", 16)
282 uint32 = types.UIntType("ui32", 32)
283 element_id = "!hw.struct<x: ui32, y: ui32>"
284 list_id = f"!esi.list<{element_id}>"
285 arg_struct_id = f"!hw.struct<coords: {list_id}>"
286 header_struct_id = "!hw.struct<coords_count: ui16>"
287 data_struct_id = f"!hw.struct<coords: !hw.array<1x{element_id}>>"
288 lowered_id = f"!hw.union<header: {header_struct_id}, data: {data_struct_id}>"
289 window_id = (f'!esi.window<"coords_only", {arg_struct_id}, '
290 '[<"header", [<"coords" countWidth 16>]>, '
291 '<"data", [<"coords", 1>]>]>')
292
293 element = types.StructType(element_id, [("x", uint32), ("y", uint32)])
294 coord_list = types.ListType(list_id, element)
295 arg_struct = types.StructType(arg_struct_id, [("coords", coord_list)])
296 header_struct = types.StructType(header_struct_id, [("coords_count", uint16)])
297 data_struct = types.StructType(
298 data_struct_id,
299 [("coords", types.ArrayType(f"!hw.array<1x{element_id}>", element, 1))],
300 )
301 lowered = types.UnionType(lowered_id, [("header", header_struct),
302 ("data", data_struct)])
303 window = types.WindowType(window_id, "coords_only", arg_struct, lowered, [
304 types.WindowType.Frame("header",
305 [types.WindowType.Field("coords", 0, 16)]),
306 types.WindowType.Frame("data", [types.WindowType.Field("coords", 1, 0)]),
307 ])
308
309 hdr = _generate_header([window])
310 win_name = _window_struct_name(hdr, "_coords_only")
311 assert f"struct {win_name} : public esi::SegmentedMessageData" in hdr
312 assert "struct header_frame {\n uint8_t _pad[6];\n count_type coords_count;\n };" in hdr
313 assert "header_frame footer{};" in hdr
314 assert "void construct(std::vector<data_frame> frames)" in hdr
315
316
318 """Window helpers copy array header fields and array-valued elements."""
319 uint8 = types.UIntType("ui8", 8)
320 uint16 = types.UIntType("ui16", 16)
321 header_array_id = "!hw.array<2xui16>"
322 value_array_id = "!hw.array<4xui8>"
323 list_id = f"!esi.list<{value_array_id}>"
324 arg_struct_id = (
325 f"!hw.struct<header_words: {header_array_id}, payloads: {list_id}>")
326 header_struct_id = (
327 f"!hw.struct<header_words: {header_array_id}, payloads_count: ui16>")
328 data_struct_id = f"!hw.struct<payloads: !hw.array<1x{value_array_id}>>"
329 lowered_id = f"!hw.union<header: {header_struct_id}, data: {data_struct_id}>"
330 window_id = (f'!esi.window<"array_payloads", {arg_struct_id}, '
331 '[<"header", [<"header_words">, <"payloads" countWidth 16>]>, '
332 '<"data", [<"payloads", 1>]>]>')
333
334 header_array = types.ArrayType(header_array_id, uint16, 2)
335 value_array = types.ArrayType(value_array_id, uint8, 4)
336 payload_list = types.ListType(list_id, value_array)
337 arg_struct = types.StructType(arg_struct_id, [("header_words", header_array),
338 ("payloads", payload_list)])
339 header_struct = types.StructType(header_struct_id,
340 [("header_words", header_array),
341 ("payloads_count", uint16)])
342 data_struct = types.StructType(
343 data_struct_id,
344 [("payloads",
345 types.ArrayType(f"!hw.array<1x{value_array_id}>", value_array, 1))],
346 )
347 lowered = types.UnionType(lowered_id, [("header", header_struct),
348 ("data", data_struct)])
349 window = types.WindowType(window_id, "array_payloads", arg_struct, lowered, [
350 types.WindowType.Frame(
351 "header",
352 [
353 types.WindowType.Field("header_words", 0, 0),
354 types.WindowType.Field("payloads", 0, 16),
355 ],
356 ),
357 types.WindowType.Frame(
358 "data",
359 [types.WindowType.Field("payloads", 1, 0)],
360 ),
361 ])
362
363 hdr = _generate_header([window])
364 assert "#include <array>" in hdr
365 win_name = _window_struct_name(hdr, "_array_payloads")
366 assert f"struct {win_name} : public esi::SegmentedMessageData" in hdr
367 # `value_type` is exposed as `std::array` so it is storable in `std::vector`.
368 assert "using value_type = std::array<uint8_t, 4>;" in hdr
369 assert "using count_type = uint16_t;" in hdr
370 # All array-typed fields (header and data) are emitted as `std::array`.
371 assert "std::array<uint16_t, 2> header_words;" in hdr
372 assert "std::array<uint8_t, 4> payloads;" in hdr
373 # Constructor params for arrays are simple const-refs to `std::array`.
374 assert f"{win_name}(const std::array<uint16_t, 2> &header_words, const std::vector<value_type> &payloads)" in hdr
375 assert "void construct(const std::array<uint16_t, 2> &header_words, std::vector<data_frame> frames)" in hdr
376 # Plain `=` assignment for both header and data array fields.
377 assert "header.header_words = header_words;" in hdr
378 assert "frame.payloads = element;" in hdr
379 assert f'throw std::out_of_range("{win_name}: invalid segment index")' in hdr
380 # Array-typed header field: const-ref accessor with std::array return type.
381 assert "const std::array<uint16_t, 2> &header_words() const { return header.header_words; }" in hdr
382 # Array-typed data field: pointer-to-member projection (zero-copy view) and
383 # std::array-valued vector helper. The list field uses the `value_type`
384 # alias (which equals `std::array<uint8_t, 4>`).
385 assert "return std::views::transform(data_frames, &data_frame::payloads);" in hdr
386 assert "std::vector<value_type> payloads_vector() const" in hdr
387 assert "out.push_back(frame.payloads);" in hdr
388
389
391 """Struct-typed data fields use a pointer-to-member projection, not a lambda.
392
393 Even if the struct contains a non-byte-aligned (bit-field) member, the data
394 field itself is a struct type, which is byte-aligned and supports
395 pointer-to-member projection.
396 """
397 sint3 = types.SIntType("si3", 3)
398 uint16 = types.UIntType("ui16", 16)
399 list_id = f"!esi.list<!hw.struct<v: si3>>"
400 elem_id = "!hw.struct<v: si3>"
401 elem_struct = types.StructType(elem_id, [("v", sint3)])
402 elem_list = types.ListType(list_id, elem_struct)
403 arg_struct_id = f"!hw.struct<items: {list_id}>"
404 arg_struct = types.StructType(arg_struct_id, [("items", elem_list)])
405 header_struct_id = "!hw.struct<items_count: ui16>"
406 header_struct = types.StructType(header_struct_id, [("items_count", uint16)])
407 data_struct_id = f"!hw.struct<items: !hw.array<1x{elem_id}>>"
408 data_struct = types.StructType(
409 data_struct_id,
410 [("items", types.ArrayType(f"!hw.array<1x{elem_id}>", elem_struct, 1))],
411 )
412 lowered_id = (
413 f"!hw.union<header: {header_struct_id}, data: {data_struct_id}>")
414 lowered = types.UnionType(lowered_id, [("header", header_struct),
415 ("data", data_struct)])
416 window_id = (f'!esi.window<"bitfield_items", {arg_struct_id}, '
417 '[<"header", [<"items" countWidth 16>]>, '
418 '<"data", [<"items", 1>]>]>')
419 window = types.WindowType(window_id, "bitfield_items", arg_struct, lowered, [
420 types.WindowType.Frame("header",
421 [types.WindowType.Field("items", 0, 16)]),
422 types.WindowType.Frame("data", [types.WindowType.Field("items", 1, 0)]),
423 ])
424
425 hdr = _generate_header([elem_struct, window])
426 win_name = _window_struct_name(hdr, "_bitfield_items")
427 assert f"struct {win_name} : public esi::SegmentedMessageData" in hdr
428 # The data field "items" is a struct type (byte-aligned), so pointer-to-member
429 # IS valid. The generated accessor must use &data_frame::items, not a lambda.
430 assert "return std::views::transform(data_frames, &data_frame::items);" in hdr
431 assert "[](const data_frame &f) { return f.items; }" not in hdr
432 assert "std::vector<value_type> items_vector() const" in hdr
433
434
436 """A window data field that is itself a non-byte-aligned int uses a lambda projection."""
437 uint3 = types.UIntType("ui3", 3)
438 uint16 = types.UIntType("ui16", 16)
439 # Build a window where the data field is directly a 3-bit uint.
440 list_id = "!esi.list<ui3>"
441 elem_list = types.ListType(list_id, uint3)
442 arg_struct_id = f"!hw.struct<vals: {list_id}>"
443 arg_struct = types.StructType(arg_struct_id, [("vals", elem_list)])
444 header_struct_id = "!hw.struct<vals_count: ui16>"
445 header_struct = types.StructType(header_struct_id, [("vals_count", uint16)])
446 data_struct_id = "!hw.struct<vals: !hw.array<1xui3>>"
447 data_struct = types.StructType(
448 data_struct_id,
449 [("vals", types.ArrayType("!hw.array<1xui3>", uint3, 1))],
450 )
451 lowered_id = (
452 f"!hw.union<header: {header_struct_id}, data: {data_struct_id}>")
453 lowered = types.UnionType(lowered_id, [("header", header_struct),
454 ("data", data_struct)])
455 window_id = (f'!esi.window<"bitval_window", {arg_struct_id}, '
456 '[<"header", [<"vals" countWidth 16>]>, '
457 '<"data", [<"vals", 1>]>]>')
458 window = types.WindowType(window_id, "bitval_window", arg_struct, lowered, [
459 types.WindowType.Frame("header", [types.WindowType.Field("vals", 0, 16)]),
460 types.WindowType.Frame("data", [types.WindowType.Field("vals", 1, 0)]),
461 ])
462
463 hdr = _generate_header([window])
464 win_name = _window_struct_name(hdr, "_bitval_window")
465 assert f"struct {win_name} : public esi::SegmentedMessageData" in hdr
466 # The data field "vals" stores a 3-bit uint, which becomes a bit-field.
467 # The range accessor must use a lambda, not &data_frame::vals.
468 assert "[](const data_frame &f) { return f.vals; }" in hdr
469 assert "&data_frame::vals" not in hdr
470 assert "std::vector<value_type> vals_vector() const" in hdr
471
472
474 """Each packed struct gets a `static_assert` pinning its `sizeof`."""
475 uint16 = types.UIntType("ui16", 16)
476 sint8 = types.SIntType("si8", 8)
477 s = types.StructType("!hw.struct<a: ui16, b: si8>", [("a", uint16),
478 ("b", sint8)])
479
480 hdr = _generate_header([s])
481 # Total: 16 + 8 = 24 bits = 3 bytes.
482 assert "static_assert(sizeof(_struct_a_ui16_b_si8) == 3," in hdr
483 assert "packed layout does not match manifest size" in hdr
484
485
487 """Unions and their padding wrapper structs each get a size assert."""
488 uint8 = types.UIntType("ui8", 8)
489 uint16 = types.UIntType("ui16", 16)
490 union_t = types.UnionType("!hw.union<a: ui8, b: ui16>", [("a", uint8),
491 ("b", uint16)])
492
493 hdr = _generate_header([union_t])
494 # The union itself is 2 bytes (max(8, 16) = 16 bits).
495 assert "static_assert(sizeof(_union_a_ui8_b_ui16) == 2," in hdr
496 # The wrapper around the narrow `a` field is also 2 bytes.
497 assert "static_assert(sizeof(_union_a_ui8_b_ui16_a) == 2," in hdr
498
499
501 """Both `data_frame` and `header_frame` get size asserts inside the window."""
502 uint16 = types.UIntType("ui16", 16)
503 uint32 = types.UIntType("ui32", 32)
504 element_id = "!hw.struct<x: ui32, y: ui32>"
505 list_id = f"!esi.list<{element_id}>"
506 arg_struct_id = f"!hw.struct<coords: {list_id}>"
507 header_struct_id = "!hw.struct<coords_count: ui16>"
508 data_struct_id = f"!hw.struct<coords: !hw.array<1x{element_id}>>"
509 lowered_id = f"!hw.union<header: {header_struct_id}, data: {data_struct_id}>"
510 window_id = (f'!esi.window<"coords_only", {arg_struct_id}, '
511 '[<"header", [<"coords" countWidth 16>]>, '
512 '<"data", [<"coords", 1>]>]>')
513
514 element = types.StructType(element_id, [("x", uint32), ("y", uint32)])
515 coord_list = types.ListType(list_id, element)
516 arg_struct = types.StructType(arg_struct_id, [("coords", coord_list)])
517 header_struct = types.StructType(header_struct_id, [("coords_count", uint16)])
518 data_struct = types.StructType(
519 data_struct_id,
520 [("coords", types.ArrayType(f"!hw.array<1x{element_id}>", element, 1))],
521 )
522 lowered = types.UnionType(lowered_id, [("header", header_struct),
523 ("data", data_struct)])
524 window = types.WindowType(window_id, "coords_only", arg_struct, lowered, [
525 types.WindowType.Frame("header",
526 [types.WindowType.Field("coords", 0, 16)]),
527 types.WindowType.Frame("data", [types.WindowType.Field("coords", 1, 0)]),
528 ])
529
530 hdr = _generate_header([window])
531 # Both inner frames are sized to the wider data frame: 8 bytes per coord.
532 assert "static_assert(sizeof(data_frame) == 8," in hdr
533 assert "static_assert(sizeof(header_frame) == 8," in hdr
534
535
537 """Structs containing an `!esi.any` field have no static size, so no assert."""
538 uint8 = types.UIntType("ui8", 8)
539 any_t = types.AnyType("!esi.any")
540 s = types.StructType("!hw.struct<tag: ui8, data: !esi.any>",
541 [("tag", uint8), ("data", any_t)])
542
543 hdr = _generate_header([s])
544 # The struct is still emitted, but no size assert (its size is unbounded).
545 assert "struct _struct_tag_ui8_data__esi_any" in hdr
546 assert "static_assert(sizeof(_struct_tag_ui8_data__esi_any)" not in hdr
547
548
550 """Void-typed struct fields are commented out so the header stays valid C++.
551
552 `void x;` is not a valid C++ field declaration. The codegen must instead
553 emit `// void x;` and exclude the field from the generated constructor.
554 """
555 uint8 = types.UIntType("ui8", 8)
556 void_t = types.VoidType("!esi.void")
557 s = types.StructType(
558 "!hw.struct<valid: ui8, client_data: void>",
559 [("valid", uint8), ("client_data", void_t)],
560 )
561
562 hdr = _generate_header([s])
563 # No uncommented `void <name>;` declaration may appear in the header.
564 assert re.search(r"^\s*void\s+\w+;", hdr, re.M) is None, hdr
565 # The void field must instead show up as a commented-out placeholder.
566 assert "// void client_data;" in hdr
567 # The non-void field is still emitted normally.
568 assert "uint8_t valid" in hdr
569 # The constructor must not take a `void` parameter (which would be
570 # invalid C++); only the non-void field is in the parameter list.
571 ctor_match = re.search(r"_struct_[^\s(]*\‍(([^)]*)\‍) :", hdr)
572 assert ctor_match, f"Constructor not found in:\n{hdr}"
573 ctor_params = ctor_match.group(1)
574 assert "client_data" not in ctor_params
575 assert "valid" in ctor_params
576 # The generated struct only contains the non-void field, so the size
577 # assertion must not include the void field's wire-format byte. With one
578 # ui8 field the struct must be exactly 1 byte.
579 assert "static_assert(sizeof(_struct_valid_ui8_client_data__esi_void) == 1," \
580 in hdr
581
582
584 """Void-typed union members are commented out and skip wrapper generation."""
585 uint16 = types.UIntType("ui16", 16)
586 void_t = types.VoidType("!esi.void")
587 u = types.UnionType(
588 "!hw.union<a: ui16, b: void>",
589 [("a", uint16), ("b", void_t)],
590 )
591
592 hdr = _generate_header([u])
593 # No uncommented `void <name>;` declaration may appear in the header;
594 # the void member must show up as a commented-out placeholder instead.
595 assert re.search(r"^\s*void\s+\w+;", hdr, re.M) is None, hdr
596 assert "// void b;" in hdr
597 # No padding wrapper struct is generated for the void field.
598 assert "_union_a_ui16_b__esi_void_b" not in hdr
599 # The non-void member is still present in the union body.
600 union_body = hdr[hdr.index("union "):]
601 assert "uint16_t a;" in union_body
602
603
605 """An outer struct containing an inner struct with a void field must use
606 the inner struct's effective (post-void-elimination) size in its size
607 assertion, not the manifest's wire-format size.
608 """
609 uint8 = types.UIntType("ui8", 8)
610 uint64 = types.UIntType("ui64", 64)
611 void_t = types.VoidType("!esi.void")
612 inner = types.StructType(
613 "!hw.struct<valid: ui8, client_data: void>",
614 [("valid", uint8), ("client_data", void_t)],
615 )
616 outer = types.StructType(
617 "!hw.struct<address: ui64, tag: ui8, "
618 "data: !hw.struct<valid: ui8, client_data: void>>",
619 [("address", uint64), ("tag", uint8), ("data", inner)],
620 )
621
622 hdr = _generate_header([outer])
623 # Inner struct: 1 byte (only the ui8 field).
624 assert "static_assert(sizeof(_struct_valid_ui8_client_data__esi_void) == 1," \
625 in hdr
626 # Outer struct: 8 (address) + 1 (tag) + 1 (inner) == 10 bytes; the manifest
627 # wire-format width would have been 11 if the inner's void byte leaked
628 # through, so the regression fix must pull the inner's effective width.
629 outer_assert = re.search(
630 r"static_assert\‍(sizeof\‍(_struct_address_ui64_tag_ui8_data_[^)]+\‍) == (\d+),",
631 hdr,
632 )
633 assert outer_assert, f"Outer size assertion not found in:\n{hdr}"
634 assert outer_assert.group(1) == "10", \
635 f"Expected outer struct size 10, got {outer_assert.group(1)}"
636
637
639 """A struct whose fields are all void collapses to `void` everywhere.
640
641 The all-void inner struct must not be emitted at all, and an outer struct
642 that references it must comment out that field and exclude it from the
643 generated constructor.
644 """
645 uint8 = types.UIntType("ui8", 8)
646 uint64 = types.UIntType("ui64", 64)
647 void_t = types.VoidType("!esi.void")
648 inner = types.StructType(
649 "!hw.struct<a: void, b: void>",
650 [("a", void_t), ("b", void_t)],
651 )
652 outer = types.StructType(
653 "!hw.struct<address: ui64, tag: ui8, "
654 "data: !hw.struct<a: void, b: void>>",
655 [("address", uint64), ("tag", uint8), ("data", inner)],
656 )
657
658 hdr = _generate_header([outer])
659 # The inner all-void struct must not appear as a struct declaration.
660 assert "struct _struct_a__esi_void_b__esi_void" not in hdr
661 # The outer struct's `data` field collapses to a commented-out void.
662 assert "// void data;" in hdr
663 # No uncommented `void <name>;` declaration may appear anywhere.
664 assert re.search(r"^\s*void\s+\w+;", hdr, re.M) is None, hdr
665 # The outer constructor must not take the void-collapsed `data` parameter.
666 outer_ctor = re.search(
667 r"_struct_address_ui64_tag_ui8_data_[^\s(]*\‍(([^)]*)\‍) :", hdr)
668 assert outer_ctor, f"Outer constructor not found in:\n{hdr}"
669 assert "data" not in outer_ctor.group(1)
670 # The outer struct's size assert reflects the actual emitted layout
671 # (8 address + 1 tag + 0 data == 9 bytes).
672 outer_assert = re.search(
673 r"static_assert\‍(sizeof\‍(_struct_address_ui64_tag_ui8_data_[^)]+\‍) "
674 r"== (\d+),",
675 hdr,
676 )
677 assert outer_assert, f"Outer size assertion not found in:\n{hdr}"
678 assert outer_assert.group(1) == "9", \
679 f"Expected outer struct size 9, got {outer_assert.group(1)}"
680
681
683 """Structs that embed a WindowType field (e.g. DMA transport wrappers like
684 {valid: i8, client_data: <window>}) should not be emitted because the C++
685 window helper is a variable-size multi-frame container whose sizeof cannot
686 match the manifest's compact frame bit-width."""
687 uint8 = types.UIntType("ui8", 8)
688 uint16 = types.UIntType("ui16", 16)
689 uint32 = types.UIntType("ui32", 32)
690
691 # Build a simple window type over a struct with a list field.
692 list_t = types.ListType("!esi.list<ui32>", uint32)
693 into_struct = types.StructType("!hw.struct<data: !esi.list<ui32>>",
694 [("data", list_t)])
695 header_struct = types.StructType("!hw.struct<data_count: ui16>",
696 [("data_count", uint16)])
697 data_struct = types.StructType("!hw.struct<data: ui32>", [("data", uint32)])
698 lowered = types.UnionType(
699 "!hw.union<header: !hw.struct<data_count: ui16>, "
700 "data: !hw.struct<data: ui32>>",
701 [("header", header_struct), ("data", data_struct)],
702 )
703 window = types.WindowType(
704 '!esi.window<"test_win", !hw.struct<data: !esi.list<ui32>>, '
705 '[<"header", [<"data" countWidth 16>]>, <"data", [<"data", 1>]>]>',
706 "test_win",
707 into_struct,
708 lowered,
709 [
710 types.WindowType.Frame("header",
711 [types.WindowType.Field("data", 0, 16)]),
712 types.WindowType.Frame("data",
713 [types.WindowType.Field("data", 1, 0)]),
714 ],
715 )
716
717 # DMA-style wrapper: {valid: i8, client_data: <window>}
718 dma_wrapper = types.StructType(
719 "!hw.struct<valid: i8, client_data: !the_window>",
720 [("valid", uint8), ("client_data", window)],
721 )
722 # Nested DMA wrapper: {address: ui64, tag: ui8, data: <dma_wrapper>}
723 uint64 = types.UIntType("ui64", 64)
724 outer_wrapper = types.StructType(
725 "!hw.struct<address: ui64, tag: ui8, data: !dma_wrapper>",
726 [("address", uint64), ("tag", uint8), ("data", dma_wrapper)],
727 )
728
729 hdr = _generate_header([window, dma_wrapper, outer_wrapper])
730
731 # The window helper itself should still be emitted.
732 assert "esi::SegmentedMessageData" in hdr
733
734 # But the DMA wrapper structs containing the window should NOT be emitted.
735 assert "struct _struct_valid" not in hdr, \
736 "DMA wrapper struct with window field should not be emitted"
737 assert "struct _struct_address" not in hdr, \
738 "Outer struct nesting a window-containing struct should not be emitted"
739
740 # A TypeAlias targeting a window-containing struct should also be excluded
741 # (otherwise codegen emits `using Foo = <missing>;`).
742 alias = types.TypeAlias("!hw.typealias<@dma_wrap>", "DmaWrap", dma_wrapper)
743 hdr2 = _generate_header([window, dma_wrapper, alias])
744 assert "using DmaWrap" not in hdr2, \
745 "Alias of a window-containing struct should not be emitted"
test_windowed_list_arrays_in_header_and_value_type()
test_size_assert_skipped_for_unbounded_struct()
test_windowed_list_struct_element_data_uses_pointer_to_member()
test_union_field_order_preserved()
test_union_with_struct_field()
_window_struct_name(hdr, window_name_suffix)
_generate_header(type_table, system_name="test_ns")
test_size_assert_emitted_for_window_frames()
test_windowed_list_bulk_message_wrapper()
test_union_ordering_among_structs()
test_size_assert_emitted_for_union_and_wrappers()
test_union_planner_naming()
test_nested_struct_with_void_field_size_assert()
test_windowed_list_bitfield_scalar_data_uses_lambda()
test_struct_containing_window_is_skipped()
test_union_with_void_field_commented_out()
test_struct_with_void_field_commented_out()
test_all_void_struct_collapses_to_void()
test_union_same_width_integrals()
test_windowed_list_header_padding_matches_frame_width()
test_size_assert_emitted_for_struct()