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}
30
31impl InlineFnDict {
32    /// 新しい辞書を作成
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// inline 関数を登録
38    pub fn insert(&mut self, name: InternedStr, func_def: FunctionDef) {
39        self.fns.insert(name, func_def);
40    }
41
42    /// inline 関数を取得
43    pub fn get(&self, name: InternedStr) -> Option<&FunctionDef> {
44        self.fns.get(&name)
45    }
46
47    /// 全ての inline 関数を走査
48    pub fn iter(&self) -> impl Iterator<Item = (&InternedStr, &FunctionDef)> {
49        self.fns.iter()
50    }
51
52    /// inline 関数の数
53    pub fn len(&self) -> usize {
54        self.fns.len()
55    }
56
57    /// 辞書が空かどうか
58    pub fn is_empty(&self) -> bool {
59        self.fns.is_empty()
60    }
61
62    /// inline 関数の呼び出し先を取得
63    pub fn get_called_functions(&self, name: InternedStr) -> Option<&HashSet<InternedStr>> {
64        self.called_functions.get(&name)
65    }
66
67    /// 利用不可関数を呼び出すかどうか
68    pub fn is_calls_unavailable(&self, name: InternedStr) -> bool {
69        self.calls_unavailable.contains(&name)
70    }
71
72    /// 利用不可フラグを設定
73    pub fn set_calls_unavailable(&mut self, name: InternedStr) {
74        self.calls_unavailable.insert(name);
75    }
76
77    /// apidoc skip_codegen 対象かどうか
78    pub fn is_apidoc_suppressed(&self, name: InternedStr) -> bool {
79        self.apidoc_suppressed.contains(&name)
80    }
81
82    /// apidoc skip_codegen フラグを設定
83    pub fn set_apidoc_suppressed(&mut self, name: InternedStr) {
84        self.apidoc_suppressed.insert(name);
85    }
86
87    /// 出力可否の総合判定
88    ///
89    /// `calls_unavailable`(不在関数を呼ぶ/推移的)または
90    /// `apidoc_suppressed`(自分が skip_codegen 対象)のいずれかが立っていれば
91    /// codegen 対象外。
92    pub fn is_unavailable_for_codegen(&self, name: InternedStr) -> bool {
93        self.is_calls_unavailable(name) || self.is_apidoc_suppressed(name)
94    }
95
96    /// apidoc skip_codegen を `apidoc_suppressed` 集合に反映
97    ///
98    /// `patches.skip_codegen` の各エントリ名を interner で解決し、
99    /// 該当する inline 関数が辞書に登録されていれば `apidoc_suppressed`
100    /// に追加する。マッチした関数数を返す(マクロ側のマッチは
101    /// `MacroInferContext::apply_apidoc_suppressions` が別途扱う)。
102    pub fn apply_apidoc_suppressions(
103        &mut self,
104        patches: &ApidocPatchSet,
105        interner: &StringInterner,
106    ) -> usize {
107        let mut count = 0usize;
108        for name_str in patches.skip_codegen.keys() {
109            if let Some(interned) = interner.lookup(name_str) {
110                if self.fns.contains_key(&interned) {
111                    self.apidoc_suppressed.insert(interned);
112                    count += 1;
113                }
114            }
115        }
116        count
117    }
118
119    /// called_functions の全エントリを走査
120    pub fn called_functions_iter(&self) -> impl Iterator<Item = (&InternedStr, &HashSet<InternedStr>)> {
121        self.called_functions.iter()
122    }
123
124    /// FunctionDef から inline 関数を収集
125    ///
126    /// `inline` または `static`(内部リンケージ)の関数を対象とする。
127    /// `STATIC` (= `static`) のみで `inline` でない関数も翻訳単位ローカルなため
128    /// Rust 側に独自に持つ意味論的問題はない(`bodies_by_type` 配列と同じ理屈)。
129    /// 例: `perlstatic.h` の `Perl_croak_memory_wrap` (STATIC void) を取り込む
130    /// ことで、これを呼ぶ inline 関数 (`Perl_newSV_type` 等) のカスケード解消に
131    /// 寄与する。
132    ///
133    /// assert/assert_ 呼び出しを Assert 式に変換してから保存する。
134    /// 関数呼び出し先(called_functions)も同時に収集する。
135    pub fn collect_from_function_def(&mut self, func_def: &FunctionDef, interner: &StringInterner) {
136        let is_static = func_def.specs.storage == Some(crate::ast::StorageClass::Static);
137        if !func_def.specs.is_inline && !is_static {
138            return;
139        }
140
141        let name = match func_def.declarator.name {
142            Some(n) => n,
143            None => return,
144        };
145
146        // クローンして assert 呼び出しを変換
147        let mut func_def = func_def.clone();
148        convert_assert_calls_in_compound_stmt(&mut func_def.body, interner);
149
150        // 関数呼び出し先を収集
151        let mut calls = HashSet::new();
152        MacroInferContext::collect_function_calls_from_block_items(
153            &func_def.body.items,
154            &mut calls,
155        );
156        self.called_functions.insert(name, calls);
157
158        self.insert(name, func_def);
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_inline_fn_dict_new() {
168        let dict = InlineFnDict::new();
169        assert!(dict.is_empty());
170        assert_eq!(dict.len(), 0);
171    }
172}