Skip to main content

libperl_macrogen/
inline_fn.rs

1//! inline 関数辞書
2//!
3//! is_target なヘッダーファイルに含まれる inline 関数を収集し、
4//! 型推論と Rust コード生成に活用する。
5
6use std::collections::{HashMap, HashSet};
7
8use crate::apidoc_patches::ApidocPatchSet;
9use crate::ast::FunctionDef;
10use crate::intern::{InternedStr, StringInterner};
11use crate::macro_infer::{convert_assert_calls_in_compound_stmt, MacroInferContext};
12
13/// inline 関数辞書
14///
15/// FunctionDef をそのまま保持し、型情報は AST から直接取得する。
16/// 各 inline 関数の呼び出し先(called_functions)と利用可能性も追跡する。
17#[derive(Debug, Default)]
18pub struct InlineFnDict {
19    fns: HashMap<InternedStr, FunctionDef>,
20    /// 各 inline 関数の呼び出し先
21    called_functions: HashMap<InternedStr, HashSet<InternedStr>>,
22    /// 利用不可関数の呼び出しを含む inline 関数の集合
23    calls_unavailable: HashSet<InternedStr>,
24    /// apidoc patches / skip-list で skip_codegen 指定された inline 関数の集合
25    ///
26    /// 直接の skip 対象にしか入れない。伝播では `is_unavailable_for_codegen()`
27    /// で `calls_unavailable` と OR を取って参照する。
28    apidoc_suppressed: HashSet<InternedStr>,
29    /// Phase 2 の名前使用解析 (mut 必要性 / 未使用 / 代入前 AddrOf)。
30    /// `analyze_local_usage()` で全関数分を計算し、Phase 3 は読むだけ
31    /// (GH #16/#22/#23)
32    local_usage: HashMap<InternedStr, crate::local_usage::LocalUsageAnalysis>,
33}
34
35impl InlineFnDict {
36    /// 新しい辞書を作成
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// inline 関数を登録
42    pub fn insert(&mut self, name: InternedStr, func_def: FunctionDef) {
43        self.fns.insert(name, func_def);
44    }
45
46    /// inline 関数を取得
47    pub fn get(&self, name: InternedStr) -> Option<&FunctionDef> {
48        self.fns.get(&name)
49    }
50
51    /// 全ての inline 関数を走査
52    pub fn iter(&self) -> impl Iterator<Item = (&InternedStr, &FunctionDef)> {
53        self.fns.iter()
54    }
55
56    /// inline 関数の数
57    pub fn len(&self) -> usize {
58        self.fns.len()
59    }
60
61    /// 辞書が空かどうか
62    pub fn is_empty(&self) -> bool {
63        self.fns.is_empty()
64    }
65
66    /// inline 関数の呼び出し先を取得
67    pub fn get_called_functions(&self, name: InternedStr) -> Option<&HashSet<InternedStr>> {
68        self.called_functions.get(&name)
69    }
70
71    /// 利用不可関数を呼び出すかどうか
72    pub fn is_calls_unavailable(&self, name: InternedStr) -> bool {
73        self.calls_unavailable.contains(&name)
74    }
75
76    /// 利用不可フラグを設定
77    pub fn set_calls_unavailable(&mut self, name: InternedStr) {
78        self.calls_unavailable.insert(name);
79    }
80
81    /// apidoc skip_codegen 対象かどうか
82    pub fn is_apidoc_suppressed(&self, name: InternedStr) -> bool {
83        self.apidoc_suppressed.contains(&name)
84    }
85
86    /// apidoc skip_codegen フラグを設定
87    pub fn set_apidoc_suppressed(&mut self, name: InternedStr) {
88        self.apidoc_suppressed.insert(name);
89    }
90
91    /// 出力可否の総合判定
92    ///
93    /// `calls_unavailable`(不在関数を呼ぶ/推移的)または
94    /// `apidoc_suppressed`(自分が skip_codegen 対象)のいずれかが立っていれば
95    /// codegen 対象外。
96    pub fn is_unavailable_for_codegen(&self, name: InternedStr) -> bool {
97        self.is_calls_unavailable(name) || self.is_apidoc_suppressed(name)
98    }
99
100    /// apidoc skip_codegen を `apidoc_suppressed` 集合に反映
101    ///
102    /// `patches.skip_codegen` の各エントリ名を interner で解決し、
103    /// 該当する inline 関数が辞書に登録されていれば `apidoc_suppressed`
104    /// に追加する。マッチした関数数を返す(マクロ側のマッチは
105    /// `MacroInferContext::apply_apidoc_suppressions` が別途扱う)。
106    pub fn apply_apidoc_suppressions(
107        &mut self,
108        patches: &ApidocPatchSet,
109        interner: &StringInterner,
110    ) -> usize {
111        let mut count = 0usize;
112        for name_str in patches.skip_codegen.keys() {
113            if let Some(interned) = interner.lookup(name_str) {
114                if self.fns.contains_key(&interned) {
115                    self.apidoc_suppressed.insert(interned);
116                    count += 1;
117                }
118            }
119        }
120        count
121    }
122
123    /// called_functions の全エントリを走査
124    pub fn called_functions_iter(&self) -> impl Iterator<Item = (&InternedStr, &HashSet<InternedStr>)> {
125        self.called_functions.iter()
126    }
127
128    /// 全 inline 関数の名前使用解析を実行して保持する (Phase 2)
129    pub fn analyze_local_usage(&mut self) {
130        let names: Vec<InternedStr> = self.fns.keys().copied().collect();
131        for name in names {
132            if let Some(func_def) = self.fns.get(&name) {
133                let analysis = crate::local_usage::analyze_function(func_def);
134                self.local_usage.insert(name, analysis);
135            }
136        }
137    }
138
139    /// 名前使用解析の結果を取得 (Phase 3 から参照)
140    pub fn local_usage(&self, name: InternedStr) -> Option<&crate::local_usage::LocalUsageAnalysis> {
141        self.local_usage.get(&name)
142    }
143
144    /// FunctionDef から inline 関数を収集
145    ///
146    /// `inline` または `static`(内部リンケージ)の関数を対象とする。
147    /// `STATIC` (= `static`) のみで `inline` でない関数も翻訳単位ローカルなため
148    /// Rust 側に独自に持つ意味論的問題はない(`bodies_by_type` 配列と同じ理屈)。
149    /// 例: `perlstatic.h` の `Perl_croak_memory_wrap` (STATIC void) を取り込む
150    /// ことで、これを呼ぶ inline 関数 (`Perl_newSV_type` 等) のカスケード解消に
151    /// 寄与する。
152    ///
153    /// assert/assert_ 呼び出しを Assert 式に変換してから保存する。
154    /// 関数呼び出し先(called_functions)も同時に収集する。
155    pub fn collect_from_function_def(&mut self, func_def: &FunctionDef, interner: &StringInterner) {
156        let is_static = func_def.specs.storage == Some(crate::ast::StorageClass::Static);
157        if !func_def.specs.is_inline && !is_static {
158            return;
159        }
160
161        let name = match func_def.declarator.name {
162            Some(n) => n,
163            None => return,
164        };
165
166        // クローンして assert 呼び出しを変換
167        let mut func_def = func_def.clone();
168        convert_assert_calls_in_compound_stmt(&mut func_def.body, interner);
169
170        // 関数呼び出し先を収集
171        let mut calls = HashSet::new();
172        MacroInferContext::collect_function_calls_from_block_items(
173            &func_def.body.items,
174            &mut calls,
175        );
176        self.called_functions.insert(name, calls);
177
178        self.insert(name, func_def);
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_inline_fn_dict_new() {
188        let dict = InlineFnDict::new();
189        assert!(dict.is_empty());
190        assert_eq!(dict.len(), 0);
191    }
192}