Skip to main content

libperl_macrogen/
struct_emitter.rs

1//! 構造体・union 定義の Rust ソース生成
2//!
3//! C ヘッダで宣言されているが bindings.rs に存在しない struct/union を
4//! `macro_bindings.rs` 側で `#[repr(C)]` 付き Rust 定義として生成する。
5//! 例: `body_details`、`ALIGNED_TYPE_NAME(*)` 系 typedef union。
6//!
7//! ビットフィールドは連続する bitfield グループを 1 つの packed `u8`/`u16`/`u32`
8//! フィールドにまとめる(簡易版)。bindgen 風の getter/setter は付けず、
9//! 初期化時の値計算は呼び出し側で行う想定。
10//!
11//! `flexible array member`(最終メンバーが `T[1]`/`[0]`/`[]`)は
12//! `[T; 0]` として出力する(Rust では size 0 配列が flex array 相当)。
13
14use std::collections::HashSet;
15
16use crate::fields_dict::{FieldsDict, StructDef, StructMemberInfo};
17use crate::intern::{InternedStr, StringInterner};
18use crate::rust_decl::RustDeclDict;
19use crate::type_repr::{CDerivedType, TypeRepr};
20
21/// 名前が Rust の予約語で、struct 名として利用すると複雑な escape が必要な場合に
22/// 出力をスキップするための集合。`r#name` は struct 自体は valid だが、その struct
23/// を参照する全箇所も `r#name` にしないとならず、対応が大きい。初版では諦める。
24const SKIP_NAMES_KEYWORDS: &[&str] = &[
25    "as", "async", "await", "break", "const", "continue", "crate", "dyn",
26    "else", "enum", "extern", "fn", "for", "if", "impl", "in",
27    "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return",
28    "self", "Self", "static", "struct", "super", "trait", "type",
29    "unsafe", "use", "where", "while",
30    "abstract", "become", "box", "do", "final", "gen", "macro", "override",
31    "priv", "try", "typeof", "unsized", "virtual", "yield",
32];
33
34/// `bindings.rs` に無い struct/union を順序保持して列挙する。
35///
36/// 戻り値は (name, def) のペア。bindings.rs の `structs` HashMap に存在する
37/// 名前は除外する。
38pub fn missing_struct_defs<'a>(
39    fields_dict: &'a FieldsDict,
40    rust_decl_dict: Option<&RustDeclDict>,
41    interner: &StringInterner,
42) -> Vec<(InternedStr, &'a StructDef)> {
43    let bindings_struct_names: HashSet<String> = rust_decl_dict
44        .map(|d| d.structs.keys().cloned().collect())
45        .unwrap_or_default();
46
47    let mut result: Vec<(InternedStr, &StructDef)> = fields_dict
48        .iter_struct_defs()
49        .filter(|(name, _)| {
50            let n = interner.get(**name);
51            !bindings_struct_names.contains(n) && !SKIP_NAMES_KEYWORDS.contains(&n)
52        })
53        .map(|(n, d)| (*n, d))
54        .collect();
55    // 名前順で安定ソート(決定論的出力)
56    result.sort_by(|a, b| interner.get(a.0).cmp(interner.get(b.0)));
57    result
58}
59
60/// 1 つの struct/union を Rust ソース形式で整形する。
61///
62/// ビットフィールド群は連続グループを `_bitfield_<n>: u8` に統合する。
63/// 1 byte (8 bit) を超えるグループは現状非対応で warning コメントを出力。
64/// 第 2 戻り値は当該 struct に生成した bit-field getter メソッド名の集合。
65/// 呼出側でこれを `bitfield_methods` 集合にマージすると、codegen 時に
66/// フィールド参照を `.name()`、代入を `.set_name(val)` に変換できる。
67pub fn format_struct(def: &StructDef, interner: &StringInterner) -> (String, HashSet<String>) {
68    let mut buf = String::new();
69    let mut bitfield_accessors: HashSet<String> = HashSet::new();
70    buf.push_str("#[repr(C)]\n");
71    buf.push_str("#[derive(Copy, Clone)]\n");
72    buf.push_str(&format!(
73        "pub {} {} {{\n",
74        if def.is_union { "union" } else { "struct" },
75        interner.get(def.name)
76    ));
77
78    // bitfield 群ごとの情報(impl 生成用): (group_idx, pack_ty, [(name, shift, width)])
79    let mut bitfield_groups: Vec<(usize, &'static str, Vec<(String, u32, u32)>)> = Vec::new();
80
81    // bitfield 連続グループを検出
82    let mut i = 0;
83    let mut bitfield_group_idx = 0;
84    while i < def.members.len() {
85        let m = &def.members[i];
86        if m.bitfield_width.is_some() {
87            // bitfield グループの終端を探す
88            let group_start = i;
89            let mut total_width = 0u32;
90            while i < def.members.len() && def.members[i].bitfield_width.is_some() {
91                total_width += def.members[i].bitfield_width.unwrap();
92                i += 1;
93            }
94            // packed 型を選択
95            let pack_ty: &'static str = if total_width <= 8 { "u8" }
96                else if total_width <= 16 { "u16" }
97                else if total_width <= 32 { "u32" }
98                else { "u64" };
99            // 含まれる field 名のコメント
100            let names: Vec<&str> = def.members[group_start..i]
101                .iter().map(|m| interner.get(m.name)).collect();
102            buf.push_str(&format!(
103                "    /// packed bitfields ({} bit total): {}\n",
104                total_width, names.join(", ")
105            ));
106            buf.push_str(&format!(
107                "    pub _bitfield_{}: {},\n",
108                bitfield_group_idx, pack_ty
109            ));
110            // impl 生成用情報を収集
111            let mut shift = 0u32;
112            let mut entries: Vec<(String, u32, u32)> = Vec::new();
113            for bm in &def.members[group_start..i] {
114                let w = bm.bitfield_width.unwrap();
115                entries.push((interner.get(bm.name).to_string(), shift, w));
116                shift += w;
117            }
118            bitfield_groups.push((bitfield_group_idx, pack_ty, entries));
119            bitfield_group_idx += 1;
120        } else {
121            buf.push_str(&format_member_line(m, interner));
122            i += 1;
123        }
124    }
125
126    buf.push_str("}\n");
127
128    // Bitfield の getter / setter を impl ブロックで emit する。
129    // bindgen と同名(`fn <name>(&self)` / `fn set_<name>(&mut self, val)`)にして
130    // codegen 側の bitfield-method 判定にそのまま乗せる。
131    if !bitfield_groups.is_empty() {
132        buf.push_str(&format!("impl {} {{\n", interner.get(def.name)));
133        for (gidx, pack_ty, entries) in &bitfield_groups {
134            for (name, shift, width) in entries {
135                // Rust キーワードは getter 名として使えないため skip
136                if SKIP_NAMES_KEYWORDS.contains(&name.as_str()) {
137                    continue;
138                }
139                let mask = if *width >= 64 { u64::MAX } else { (1u64 << width) - 1 };
140                // getter: ((self._bitfield_N >> shift) & mask) as pack_ty
141                buf.push_str(&format!(
142                    "    #[inline]\n    pub const fn {name}(&self) -> {pack_ty} {{\n\
143                     \x20       ((self._bitfield_{gidx} >> {shift}) & {mask}) as {pack_ty}\n\
144                     \x20   }}\n",
145                    name = name, pack_ty = pack_ty, gidx = gidx,
146                    shift = shift, mask = mask
147                ));
148                // setter: clear bits then OR new value
149                buf.push_str(&format!(
150                    "    #[inline]\n    pub fn set_{name}(&mut self, val: {pack_ty}) {{\n\
151                     \x20       self._bitfield_{gidx} = (self._bitfield_{gidx} & !(({mask} as {pack_ty}) << {shift}))\n\
152                     \x20           | ((val & {mask} as {pack_ty}) << {shift});\n\
153                     \x20   }}\n",
154                    name = name, pack_ty = pack_ty, gidx = gidx,
155                    shift = shift, mask = mask
156                ));
157                bitfield_accessors.insert(name.clone());
158            }
159        }
160        buf.push_str("}\n");
161    }
162
163    (buf, bitfield_accessors)
164}
165
166/// 単一メンバー行を整形(非 bitfield)。flex array は `[T; 0]` に置換。
167fn format_member_line(m: &StructMemberInfo, interner: &StringInterner) -> String {
168    let ty_str = type_repr_to_rust_struct_field(&m.type_repr, interner);
169    format!("    pub {}: {},\n", interner.get(m.name), ty_str)
170}
171
172/// `TypeRepr` を struct フィールド型の Rust 文字列にする。
173/// 末尾 size 1/0 配列は `[T; 0]` に変換(flex array 慣用句)。
174pub fn type_repr_to_rust_struct_field(ty: &TypeRepr, interner: &StringInterner) -> String {
175    // flex array 検出: 最後の derived が Array { Some(0|1) | None }
176    if let TypeRepr::CType { derived, .. } = ty {
177        if let Some(CDerivedType::Array { size }) = derived.last() {
178            if matches!(size, None | Some(0) | Some(1)) {
179                // flex array → [elem; 0]
180                let mut without_last = (*ty).clone();
181                if let TypeRepr::CType { derived: d, .. } = &mut without_last {
182                    d.pop();
183                }
184                let elem = without_last.to_rust_string(interner);
185                return format!("[{}; 0]", elem);
186            }
187        }
188    }
189    ty.to_rust_string(interner)
190}
191
192/// 出力結果と実際に出力した名前集合のペア
193pub struct EmittedStructs {
194    pub source: String,
195    /// syn::parse_str を通った struct/union 名
196    pub emitted_struct_names: HashSet<String>,
197    /// 出力した typedef alias 名(左辺)
198    pub emitted_typedef_names: HashSet<String>,
199    /// 自動生成した struct ごとの bit-field getter 名集合。codegen 側で
200    /// `bitfield_methods` にマージすると、フィールド参照が自動的に
201    /// `.name()` / `.set_name(val)` に書き換えられる。
202    pub bitfield_methods: std::collections::HashMap<String, HashSet<String>>,
203}
204
205/// `missing_struct_defs` 全件を 1 つの Rust ソース文字列にする。
206/// セクションヘッダコメントを冒頭に付与。
207pub fn emit_missing_structs(
208    fields_dict: &FieldsDict,
209    rust_decl_dict: Option<&RustDeclDict>,
210    interner: &StringInterner,
211) -> EmittedStructs {
212    let defs = missing_struct_defs(fields_dict, rust_decl_dict, interner);
213    let mut emitted_struct_names: HashSet<String> = HashSet::new();
214    let mut emitted_typedef_names: HashSet<String> = HashSet::new();
215    let mut bitfield_methods: std::collections::HashMap<String, HashSet<String>> =
216        std::collections::HashMap::new();
217    if defs.is_empty() {
218        return EmittedStructs {
219            source: String::new(),
220            emitted_struct_names,
221            emitted_typedef_names,
222            bitfield_methods,
223        };
224    }
225    let mut buf = String::new();
226    buf.push_str("// === Auto-generated struct definitions ===\n");
227    buf.push_str("// Structs/unions declared in C headers but absent from bindings.rs\n");
228    buf.push_str("// (typically static-inline-only headers like sv_inline.h).\n\n");
229    for (name, def) in defs {
230        let (formatted, accessors) = format_struct(def, interner);
231        // `struct ... impl ...` 連結は syn::Item 単体では parse できないので
232        // File としてパース検証する。
233        if syn::parse_str::<syn::File>(&formatted).is_ok() {
234            buf.push_str(&formatted);
235            buf.push('\n');
236            let struct_name = interner.get(name).to_string();
237            emitted_struct_names.insert(struct_name.clone());
238            if !accessors.is_empty() {
239                bitfield_methods.insert(struct_name, accessors);
240            }
241        } else {
242            buf.push_str(&format!(
243                "// [SKIPPED] struct/union {} — failed to format as valid Rust\n\n",
244                interner.get(name)
245            ));
246        }
247    }
248
249    // typedef alias: bindings.rs に既存の struct を別名で typedef している
250    // (例: `typedef struct xpvhv_with_aux XPVHV_WITH_AUX;`)が、
251    // typedef 名 (XPVHV_WITH_AUX) が bindings.rs に無い場合に補完する。
252    let bindings_struct_names: HashSet<String> = rust_decl_dict
253        .map(|d| d.structs.keys().cloned().collect())
254        .unwrap_or_default();
255    let bindings_type_names: HashSet<String> = rust_decl_dict
256        .map(|d| d.types.keys().cloned().collect())
257        .unwrap_or_default();
258    let mut typedef_aliases: Vec<(String, String)> = fields_dict
259        .iter_typedefs()
260        .map(|(td, st)| (interner.get(*td).to_string(), interner.get(*st).to_string()))
261        .filter(|(td, st)| {
262            !bindings_struct_names.contains(td)
263                && !bindings_type_names.contains(td)
264                && bindings_struct_names.contains(st)
265        })
266        .collect();
267    typedef_aliases.sort();
268    if !typedef_aliases.is_empty() {
269        buf.push_str("// === Auto-generated typedef aliases ===\n");
270        buf.push_str("// `typedef struct foo NAME;` where struct foo is in bindings.rs\n");
271        buf.push_str("// but the typedef name NAME is not (e.g. XPVHV_WITH_AUX).\n\n");
272        for (td, st) in typedef_aliases {
273            buf.push_str(&format!("#[allow(non_camel_case_types)] pub type {} = {};\n", td, st));
274            emitted_typedef_names.insert(td);
275        }
276        buf.push('\n');
277    }
278    EmittedStructs { source: buf, emitted_struct_names, emitted_typedef_names, bitfield_methods }
279}