1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
use super::*;
/// True for `struct { ... } field;` records that are emitted inline, not hoisted.
///
/// Array/pointer-wrapped inline records still hoist because RDL cannot nest them there.
pub fn is_named_instance_record(record: &Cursor) -> bool {
let kind = record.kind();
if kind != CXCursor_StructDecl && kind != CXCursor_UnionDecl {
return false;
}
// Field-less anonymous aggregates and named nested types use other paths.
if !record.is_definition() || record.is_anonymous_record() || !is_anonymous_name(&record.name())
{
return false;
}
let parent = record.semantic_parent();
if parent.kind() != CXCursor_StructDecl && parent.kind() != CXCursor_UnionDecl {
return false;
}
// Only a direct field type is emitted inline; arrays/pointers stay hoisted.
let loc = record.location_id();
parent
.children()
.into_iter()
.any(|c| c.kind() == CXCursor_FieldDecl && c.ty().ty().location_id() == loc)
}
#[derive(Debug)]
pub struct Struct {
pub name: String,
pub fields: Vec<Field>,
pub is_union: bool,
/// Non-zero packing size in bytes, or `None` for natural alignment.
pub packing: Option<u16>,
/// Forced over-alignment in bytes; mutually exclusive with `packing`.
pub alignment: Option<u16>,
}
impl Struct {
/// Build an opaque struct for a forward declaration referenced through pointers.
pub fn opaque(name: &str) -> Self {
Self {
name: name.to_string(),
fields: vec![],
is_union: false,
packing: None,
alignment: None,
}
}
pub fn parse(cursor: Cursor, parser: &mut Parser<'_>, is_union: bool) -> Result<Self, Error> {
let tag_name = cursor.name();
// Use the public typedef alias; anonymous types are keyed by source location.
let name = if is_anonymous_name(&tag_name) {
parser
.tag_rename
.get(&cursor.location_id())
.cloned()
.unwrap_or(tag_name)
} else {
parser
.tag_rename
.get(&tag_name)
.cloned()
.unwrap_or(tag_name)
};
let mut fields = vec![];
// Packing lowers the struct alignment below its largest field alignment.
let struct_align_bytes = cursor.ty().align_of();
let mut max_field_align_bytes: i64 = 0;
// Coalesce consecutive bit-fields into backing integer fields; winmd has no
// bit-field concept.
let mut bitfield_indices: Vec<usize> = vec![];
let mut unit_size: i64 = 0;
let mut remaining_bits: i64 = 0;
// Names anonymous aggregate fields in declaration order.
let mut anonymous_count: usize = 0;
// Names C++ base subobjects in declaration order.
let mut base_count: usize = 0;
for child in cursor.children() {
// C++ base subobjects sit at the front of the layout; emit leading fields.
if child.kind() == CXCursor_CXXBaseSpecifier {
unit_size = 0;
remaining_bits = 0;
base_count += 1;
let name = if base_count == 1 {
"Base".to_string()
} else {
format!("Base{base_count}")
};
let field_align = child.ty().align_of();
if field_align > max_field_align_bytes {
max_field_align_bytes = field_align;
}
let ty = child.ty().to_type(parser);
fields.push(Field {
name,
ty,
nested: None,
bitfields: vec![],
});
continue;
}
// Reconstruct field-less anonymous aggregates inline as nested records.
if matches!(child.kind(), CXCursor_StructDecl | CXCursor_UnionDecl)
&& child.is_anonymous_record()
{
unit_size = 0;
remaining_bits = 0;
anonymous_count += 1;
let name = if anonymous_count == 1 {
"Anonymous".to_string()
} else {
format!("Anonymous{anonymous_count}")
};
// Anonymous aggregate members contribute to the parent's natural alignment.
let field_align = child.ty().align_of();
if field_align > max_field_align_bytes {
max_field_align_bytes = field_align;
}
let child_is_union = child.kind() == CXCursor_UnionDecl;
let nested = Self::parse(child, parser, child_is_union)?;
fields.push(Field {
name,
ty: metadata::Type::Void,
nested: Some(Box::new(nested)),
bitfields: vec![],
});
continue;
}
if child.kind() != CXCursor_FieldDecl {
continue;
}
let field_align = child.ty().align_of();
if field_align > max_field_align_bytes {
max_field_align_bytes = field_align;
}
// Emit `struct { ... } field;` inline so the reader rebuilds a nested type.
let decl = child.ty().ty();
if is_named_instance_record(&decl) {
unit_size = 0;
remaining_bits = 0;
let child_is_union = decl.kind() == CXCursor_UnionDecl;
let nested = Self::parse(decl, parser, child_is_union)?;
fields.push(Field {
name: demacro_member_name(child.name(), parser.macro_defs),
ty: metadata::Type::Void,
nested: Some(Box::new(nested)),
bitfields: vec![],
});
continue;
}
if child.is_bit_field() {
let width = child.bit_field_width() as i64;
if width <= 0 {
// A zero-width bit-field only forces a fresh storage unit.
unit_size = 0;
remaining_bits = 0;
continue;
}
let size = child.ty().size_of();
let member = demacro_member_name(child.name(), parser.macro_defs);
if size != unit_size || width > remaining_bits {
// New storage units use the bit-field's declared signedness.
let ty = child.ty().to_type(parser);
bitfield_indices.push(fields.len());
// Anonymous padding consumes bits but gets no accessor.
let members = if member.is_empty() {
vec![]
} else {
vec![(member, 0, width as u32)]
};
fields.push(Field {
name: String::new(),
ty,
nested: None,
bitfields: members,
});
unit_size = size;
remaining_bits = size * 8 - width;
} else {
// Continue filling the open unit; padding advances the offset only.
let offset = (unit_size * 8 - remaining_bits) as u32;
if !member.is_empty()
&& let Some(&index) = bitfield_indices.last()
{
fields[index].bitfields.push((member, offset, width as u32));
}
remaining_bits -= width;
}
continue;
}
unit_size = 0;
remaining_bits = 0;
let name = demacro_member_name(child.name(), parser.macro_defs);
let ty = child.ty().to_type(parser);
fields.push(Field {
name,
ty,
nested: None,
bitfields: vec![],
});
}
// Name backing fields after the total count is known.
if bitfield_indices.len() == 1 {
fields[bitfield_indices[0]].name = "_bitfield".to_string();
} else {
for (n, &index) in bitfield_indices.iter().enumerate() {
fields[index].name = format!("_bitfield{}", n + 1);
}
}
// Emit packing only when it lowers natural alignment.
let packing = if struct_align_bytes > 0 && max_field_align_bytes > struct_align_bytes {
Some(struct_align_bytes as u16)
} else {
None
};
// Record forced over-alignment separately because `ClassLayout` can only lower it.
let alignment = if struct_align_bytes > 0 && struct_align_bytes > max_field_align_bytes {
Some(struct_align_bytes as u16)
} else {
None
};
Ok(Self {
name,
fields,
is_union,
packing,
alignment,
})
}
pub fn write(&self, namespace: &str) -> Result<TokenStream, Error> {
let name = write_ident(&self.name);
let attrs = self.write_attrs();
let keyword = self.write_keyword();
let fields = self.write_fields(namespace);
Ok(quote! {
#attrs
#keyword #name {
#(#fields)*
}
})
}
/// Emit this record inline as the type of an anonymous nested field.
fn write_inline(&self, namespace: &str) -> TokenStream {
let attrs = self.write_attrs();
let keyword = self.write_keyword();
let fields = self.write_fields(namespace);
quote! {
#attrs #keyword {
#(#fields)*
}
}
}
fn write_keyword(&self) -> TokenStream {
if self.is_union {
quote! { union }
} else {
quote! { struct }
}
}
/// The record's layout attributes.
fn write_attrs(&self) -> TokenStream {
let packed_attr = if let Some(packing) = self.packing {
let size = Literal::u16_unsuffixed(packing);
quote! { #[packed(#size)] }
} else {
quote! {}
};
let align_attr = if let Some(alignment) = self.alignment {
let size = Literal::u16_unsuffixed(alignment);
quote! { #[align(#size)] }
} else {
quote! {}
};
quote! { #packed_attr #align_attr }
}
fn write_fields(&self, namespace: &str) -> Vec<TokenStream> {
self.fields
.iter()
.map(|field| {
let name = write_ident(&field.name);
// RDL bit-field syntax uses implicit offsets; gaps become padding.
if !field.bitfields.is_empty() {
let ty = write_type(namespace, &field.ty);
let mut members = vec![];
let mut cursor = 0u32;
for (member, offset, width) in &field.bitfields {
if *offset > cursor {
let pad = Literal::u32_unsuffixed(offset - cursor);
members.push(quote! { _: #pad, });
}
let member = write_ident(member);
let width_lit = Literal::u32_unsuffixed(*width);
members.push(quote! { #member: #width_lit, });
cursor = offset + width;
}
return quote! { #name: #ty { #(#members)* }, };
}
if let Some(nested) = &field.nested {
let inner = nested.write_inline(namespace);
quote! { #name: #inner, }
} else {
let ty = write_type(namespace, &field.ty);
quote! { #name: #ty, }
}
})
.collect()
}
}