Skip to main content

ghostscope_dwarf/semantics/
c_types.rs

1//! C type semantics derived from DWARF type information.
2//!
3//! This module keeps language-level type classification close to the DWARF
4//! semantic layer so compiler backends do not need to duplicate C rules.
5
6use crate::TypeInfo;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct CIntegerComparisonType {
10    pub size: u64,
11    pub is_unsigned: bool,
12}
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct CIntegerComparisonPlan {
16    pub size: u64,
17    pub is_unsigned: bool,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct MemberLayout {
22    pub offset: u64,
23    pub member_type: TypeInfo,
24}
25
26#[derive(Clone, Debug, PartialEq)]
27pub struct IndexableElementLayout {
28    pub element_type: TypeInfo,
29    pub stride: u64,
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum TypeLayoutError {
34    #[error("Unknown member '{field}' in {kind} '{type_name}' (known members: {members})")]
35    UnknownMember {
36        kind: &'static str,
37        type_name: String,
38        field: String,
39        members: String,
40    },
41
42    #[error("member access requires struct or union type, got '{type_name}'")]
43    InvalidMemberBase { type_name: String },
44}
45
46impl CIntegerComparisonType {
47    pub fn promoted(self) -> Self {
48        if self.size < 4 {
49            Self {
50                size: 4,
51                is_unsigned: false,
52            }
53        } else {
54            self
55        }
56    }
57
58    pub fn signed_i64() -> Self {
59        Self {
60            size: 8,
61            is_unsigned: false,
62        }
63    }
64}
65
66pub fn strip_type_aliases(mut ty: &TypeInfo) -> &TypeInfo {
67    while let TypeInfo::TypedefType {
68        underlying_type, ..
69    }
70    | TypeInfo::QualifiedType {
71        underlying_type, ..
72    } = ty
73    {
74        ty = underlying_type.as_ref();
75    }
76    ty
77}
78
79pub fn is_c_aggregate_type(ty: &TypeInfo) -> bool {
80    matches!(
81        strip_type_aliases(ty),
82        TypeInfo::StructType { .. } | TypeInfo::UnionType { .. } | TypeInfo::ArrayType { .. }
83    )
84}
85
86pub fn is_c_pointer_or_array_type(ty: &TypeInfo) -> bool {
87    matches!(
88        strip_type_aliases(ty),
89        TypeInfo::PointerType { .. } | TypeInfo::ArrayType { .. }
90    )
91}
92
93pub fn member_layout(ty: &TypeInfo, field: &str) -> Result<MemberLayout, TypeLayoutError> {
94    match strip_type_aliases(ty) {
95        TypeInfo::StructType { name, members, .. } => members
96            .iter()
97            .find(|member| member.name == field)
98            .map(|member| MemberLayout {
99                offset: member.offset,
100                member_type: member.member_type.clone(),
101            })
102            .ok_or_else(|| unknown_member_error("struct", name, field, members)),
103        TypeInfo::UnionType { name, members, .. } => members
104            .iter()
105            .find(|member| member.name == field)
106            .map(|member| MemberLayout {
107                offset: member.offset,
108                member_type: member.member_type.clone(),
109            })
110            .ok_or_else(|| unknown_member_error("union", name, field, members)),
111        other => Err(TypeLayoutError::InvalidMemberBase {
112            type_name: other.type_name(),
113        }),
114    }
115}
116
117pub fn indexable_element_layout(ty: &TypeInfo) -> Option<IndexableElementLayout> {
118    match strip_type_aliases(ty) {
119        TypeInfo::ArrayType { element_type, .. } => Some(IndexableElementLayout {
120            element_type: element_type.as_ref().clone(),
121            stride: element_type.size().max(1),
122        }),
123        TypeInfo::PointerType { target_type, .. } => Some(IndexableElementLayout {
124            element_type: target_type.as_ref().clone(),
125            stride: target_type.size().max(1),
126        }),
127        _ => None,
128    }
129}
130
131pub fn c_integer_comparison_type(ty: &TypeInfo) -> Option<CIntegerComparisonType> {
132    match ty {
133        TypeInfo::BaseType { encoding, size, .. } => {
134            let is_unsigned = *encoding == crate::constants::DW_ATE_unsigned.0 as u16
135                || *encoding == crate::constants::DW_ATE_unsigned_char.0 as u16;
136            let is_signed = *encoding == crate::constants::DW_ATE_signed.0 as u16
137                || *encoding == crate::constants::DW_ATE_signed_char.0 as u16
138                || *encoding == crate::constants::DW_ATE_boolean.0 as u16;
139            if is_unsigned || is_signed {
140                Some(CIntegerComparisonType {
141                    size: *size,
142                    is_unsigned,
143                })
144            } else {
145                None
146            }
147        }
148        TypeInfo::EnumType {
149            base_type, size, ..
150        } => c_integer_comparison_type(base_type).map(|mut ty| {
151            if ty.size == 0 {
152                ty.size = *size;
153            }
154            ty
155        }),
156        TypeInfo::BitfieldType {
157            underlying_type,
158            bit_size,
159            ..
160        } => c_integer_comparison_type(underlying_type).map(|mut ty| {
161            ty.size = (*bit_size as u64).max(1).div_ceil(8);
162            ty
163        }),
164        TypeInfo::TypedefType {
165            underlying_type, ..
166        }
167        | TypeInfo::QualifiedType {
168            underlying_type, ..
169        } => c_integer_comparison_type(underlying_type),
170        _ => None,
171    }
172}
173
174pub fn is_c_signed_integer_type(ty: &TypeInfo) -> bool {
175    match ty {
176        TypeInfo::BaseType { encoding, .. } => {
177            *encoding == crate::constants::DW_ATE_signed.0 as u16
178                || *encoding == crate::constants::DW_ATE_signed_char.0 as u16
179        }
180        TypeInfo::EnumType { base_type, .. } => is_c_signed_integer_type(base_type),
181        TypeInfo::BitfieldType {
182            underlying_type, ..
183        } => is_c_signed_integer_type(underlying_type),
184        TypeInfo::TypedefType {
185            underlying_type, ..
186        }
187        | TypeInfo::QualifiedType {
188            underlying_type, ..
189        } => is_c_signed_integer_type(underlying_type),
190        _ => false,
191    }
192}
193
194pub fn usual_c_arithmetic_comparison_plan(
195    left: CIntegerComparisonType,
196    right: CIntegerComparisonType,
197) -> CIntegerComparisonPlan {
198    let left = left.promoted();
199    let right = right.promoted();
200    if left.is_unsigned == right.is_unsigned {
201        return CIntegerComparisonPlan {
202            size: left.size.max(right.size),
203            is_unsigned: left.is_unsigned,
204        };
205    }
206
207    let unsigned = if left.is_unsigned { left } else { right };
208    let signed = if left.is_unsigned { right } else { left };
209    if unsigned.size >= signed.size {
210        CIntegerComparisonPlan {
211            size: unsigned.size,
212            is_unsigned: true,
213        }
214    } else {
215        CIntegerComparisonPlan {
216            size: signed.size,
217            is_unsigned: false,
218        }
219    }
220}
221
222fn unknown_member_error(
223    kind: &'static str,
224    type_name: &str,
225    field: &str,
226    members: &[crate::StructMember],
227) -> TypeLayoutError {
228    let mut member_names = members
229        .iter()
230        .map(|member| member.name.clone())
231        .collect::<Vec<_>>();
232    member_names.sort();
233    member_names.dedup();
234    let list = if member_names.is_empty() {
235        "<none>".to_string()
236    } else {
237        member_names.join(", ")
238    };
239
240    TypeLayoutError::UnknownMember {
241        kind,
242        type_name: type_name.to_string(),
243        field: field.to_string(),
244        members: list,
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use crate::StructMember;
252
253    fn int_type(name: &str, size: u64, encoding: u16) -> TypeInfo {
254        TypeInfo::BaseType {
255            name: name.to_string(),
256            size,
257            encoding,
258        }
259    }
260
261    fn signed_int() -> TypeInfo {
262        int_type("int", 4, crate::constants::DW_ATE_signed.0 as u16)
263    }
264
265    #[test]
266    fn aggregate_classification_strips_aliases() {
267        let struct_type = TypeInfo::StructType {
268            name: "Request".to_string(),
269            size: 4,
270            members: vec![StructMember {
271                name: "fd".to_string(),
272                member_type: signed_int(),
273                offset: 0,
274                bit_offset: None,
275                bit_size: None,
276            }],
277        };
278        let typedef = TypeInfo::TypedefType {
279            name: "request_t".to_string(),
280            underlying_type: Box::new(TypeInfo::QualifiedType {
281                qualifier: crate::TypeQualifier::Const,
282                underlying_type: Box::new(struct_type),
283            }),
284        };
285
286        assert!(is_c_aggregate_type(&typedef));
287    }
288
289    #[test]
290    fn pointer_or_array_classification_strips_aliases() {
291        let array_type = TypeInfo::ArrayType {
292            element_type: Box::new(signed_int()),
293            element_count: Some(4),
294            total_size: Some(16),
295        };
296        let typedef = TypeInfo::TypedefType {
297            name: "int_array_t".to_string(),
298            underlying_type: Box::new(array_type),
299        };
300
301        assert!(is_c_pointer_or_array_type(&typedef));
302    }
303
304    #[test]
305    fn member_and_index_layout_strip_aliases() {
306        let signed_int = signed_int();
307        let struct_type = TypeInfo::StructType {
308            name: "Request".to_string(),
309            size: 16,
310            members: vec![StructMember {
311                name: "fd".to_string(),
312                member_type: signed_int.clone(),
313                offset: 8,
314                bit_offset: None,
315                bit_size: None,
316            }],
317        };
318        let qualified_struct = TypeInfo::QualifiedType {
319            qualifier: crate::TypeQualifier::Const,
320            underlying_type: Box::new(struct_type),
321        };
322
323        let layout = member_layout(&qualified_struct, "fd").expect("member layout");
324        assert_eq!(layout.offset, 8);
325        assert_eq!(layout.member_type, signed_int.clone());
326
327        let pointer_type = TypeInfo::PointerType {
328            target_type: Box::new(signed_int),
329            size: 8,
330        };
331        let element = indexable_element_layout(&pointer_type).expect("pointer element layout");
332        assert_eq!(element.stride, 4);
333    }
334
335    #[test]
336    fn integer_comparison_type_handles_enums_and_bitfields() {
337        let enum_type = TypeInfo::EnumType {
338            name: "Mode".to_string(),
339            size: 4,
340            variants: vec![],
341            base_type: Box::new(int_type(
342                "unsigned int",
343                0,
344                crate::constants::DW_ATE_unsigned.0 as u16,
345            )),
346        };
347        assert_eq!(
348            c_integer_comparison_type(&enum_type),
349            Some(CIntegerComparisonType {
350                size: 4,
351                is_unsigned: true,
352            })
353        );
354
355        let bitfield_type = TypeInfo::BitfieldType {
356            underlying_type: Box::new(signed_int()),
357            bit_offset: 0,
358            bit_size: 9,
359        };
360        assert_eq!(
361            c_integer_comparison_type(&bitfield_type),
362            Some(CIntegerComparisonType {
363                size: 2,
364                is_unsigned: false,
365            })
366        );
367    }
368
369    #[test]
370    fn signed_integer_classification_excludes_boolean() {
371        let bool_type = int_type("bool", 1, crate::constants::DW_ATE_boolean.0 as u16);
372        assert_eq!(
373            c_integer_comparison_type(&bool_type),
374            Some(CIntegerComparisonType {
375                size: 1,
376                is_unsigned: false,
377            })
378        );
379        assert!(!is_c_signed_integer_type(&bool_type));
380        assert!(is_c_signed_integer_type(&signed_int()));
381    }
382
383    #[test]
384    fn usual_comparison_plan_applies_integer_promotions() {
385        let u8_type = CIntegerComparisonType {
386            size: 1,
387            is_unsigned: true,
388        };
389        let i8_type = CIntegerComparisonType {
390            size: 1,
391            is_unsigned: false,
392        };
393
394        assert_eq!(
395            usual_c_arithmetic_comparison_plan(u8_type, i8_type),
396            CIntegerComparisonPlan {
397                size: 4,
398                is_unsigned: false,
399            }
400        );
401    }
402
403    #[test]
404    fn usual_comparison_plan_respects_unsigned_rank() {
405        let u64_type = CIntegerComparisonType {
406            size: 8,
407            is_unsigned: true,
408        };
409        let i32_type = CIntegerComparisonType {
410            size: 4,
411            is_unsigned: false,
412        };
413
414        assert_eq!(
415            usual_c_arithmetic_comparison_plan(u64_type, i32_type),
416            CIntegerComparisonPlan {
417                size: 8,
418                is_unsigned: true,
419            }
420        );
421    }
422}