b2c2_compiler/
optimize.rs

1// b2c2-compiler crate::optimize
2// author: Leonardone @ NEETSDKASU
3
4use super::*;
5
6// コメントの除去
7pub fn remove_comment(statements: &[casl2::Statement]) -> Vec<casl2::Statement> {
8    let mut ret = vec![];
9
10    for stmt in statements.iter() {
11        if let casl2::Statement::Code { label, command, .. } = stmt {
12            ret.labeled(label.clone(), command.clone());
13        }
14    }
15
16    ret
17}
18
19// NOPの除去
20pub fn remove_nop(statements: &[casl2::Statement]) -> Vec<casl2::Statement> {
21    let mut ret: Vec<casl2::Statement> = vec![];
22    let mut marge_label: Option<&str> = None;
23    let mut mapped_label = HashMap::<&str, &str>::new();
24
25    for stmt in statements.iter() {
26        match stmt {
27            casl2::Statement::Code {
28                label: None,
29                command: casl2::Command::Nop,
30                comment,
31            } => {
32                if let Some(comment) = comment {
33                    ret.comment(comment.clone());
34                }
35            }
36            casl2::Statement::Code {
37                label: Some(label),
38                command: casl2::Command::Nop,
39                comment,
40            } => {
41                if let Some(comment) = comment {
42                    ret.comment(comment.clone());
43                }
44                if let Some(key) = marge_label {
45                    mapped_label.insert(label.as_str(), key);
46                } else {
47                    marge_label = Some(label.as_str());
48                }
49            }
50            casl2::Statement::Code {
51                label,
52                command,
53                comment,
54            } => {
55                if let Some(key) = marge_label.take() {
56                    if let Some(label) = label {
57                        mapped_label.insert(label.as_str(), key);
58                    }
59                    ret.push(casl2::Statement::Code {
60                        label: Some(key.into()),
61                        command: command.clone(),
62                        comment: comment.clone(),
63                    });
64                } else {
65                    ret.push(stmt.clone());
66                }
67            }
68            casl2::Statement::Comment { .. } => ret.push(stmt.clone()),
69        }
70    }
71
72    assert!(marge_label.is_none());
73
74    for command in ret.iter_mut().filter_map(|stmt| {
75        if let casl2::Statement::Code { command, .. } = stmt {
76            Some(command)
77        } else {
78            None
79        }
80    }) {
81        match command {
82            casl2::Command::Start { entry_point } => {
83                let new_label = entry_point
84                    .as_ref()
85                    .and_then(|label| mapped_label.get(label.as_str()));
86                if let Some(&label) = new_label {
87                    *entry_point = Some(label.into());
88                }
89            }
90            casl2::Command::Dc { constants } => {
91                for label in constants.iter_mut().filter_map(|cnst| {
92                    if let casl2::Constant::Label(label) = cnst {
93                        Some(label)
94                    } else {
95                        None
96                    }
97                }) {
98                    if let Some(&new_label) = mapped_label.get(label.as_str()) {
99                        *label = new_label.into();
100                    }
101                }
102            }
103            casl2::Command::In { pos, len } | casl2::Command::Out { pos, len } => {
104                if let Some(&label) = mapped_label.get(pos.as_str()) {
105                    *pos = label.into();
106                }
107                if let Some(&label) = mapped_label.get(len.as_str()) {
108                    *len = label.into();
109                }
110            }
111            casl2::Command::A { adr, .. } | casl2::Command::P { adr, .. } => {
112                if let casl2::Adr::Label(label) = adr {
113                    if let Some(&new_label) = mapped_label.get(label.as_str()) {
114                        *label = new_label.into();
115                    }
116                }
117            }
118            casl2::Command::End
119            | casl2::Command::Ds { .. }
120            | casl2::Command::Rpush
121            | casl2::Command::Rpop
122            | casl2::Command::R { .. }
123            | casl2::Command::Pop { .. }
124            | casl2::Command::Ret => {}
125            casl2::Command::Nop => unreachable!("BUG"),
126            casl2::Command::DebugBasicStep { .. } => {}
127        }
128    }
129
130    ret
131}
132
133// 未参照ラベルの除去
134pub fn remove_unreferenced_label(statements: &[casl2::Statement]) -> Vec<casl2::Statement> {
135    let mut ret: Vec<casl2::Statement> = vec![];
136    let mut set = std::collections::HashSet::<&str>::new();
137
138    for command in statements.iter().filter_map(|stmt| {
139        if let casl2::Statement::Code { command, .. } = stmt {
140            Some(command)
141        } else {
142            None
143        }
144    }) {
145        match command {
146            casl2::Command::Start { entry_point } => {
147                if let Some(label) = entry_point {
148                    set.insert(label.as_str());
149                }
150            }
151            casl2::Command::Dc { constants } => {
152                for cnst in constants.iter() {
153                    if let casl2::Constant::Label(label) = cnst {
154                        set.insert(label.as_str());
155                    }
156                }
157            }
158            casl2::Command::In { pos, len } | casl2::Command::Out { pos, len } => {
159                set.insert(pos.as_str());
160                set.insert(len.as_str());
161            }
162            casl2::Command::A { adr, .. } | casl2::Command::P { adr, .. } => {
163                if let casl2::Adr::Label(label) = adr {
164                    set.insert(label.as_str());
165                }
166            }
167            casl2::Command::End
168            | casl2::Command::Ds { .. }
169            | casl2::Command::Rpush
170            | casl2::Command::Rpop
171            | casl2::Command::R { .. }
172            | casl2::Command::Pop { .. }
173            | casl2::Command::Ret
174            | casl2::Command::Nop
175            | casl2::Command::DebugBasicStep { .. } => {}
176        }
177    }
178
179    for stmt in statements.iter() {
180        match stmt {
181            casl2::Statement::Code {
182                label: Some(label),
183                command,
184                comment,
185            } if !matches!(command, casl2::Command::Start { .. })
186                && !set.contains(label.as_str()) =>
187            {
188                ret.push(casl2::Statement::Code {
189                    label: None,
190                    command: command.clone(),
191                    comment: comment.clone(),
192                });
193            }
194            _ => ret.push(stmt.clone()),
195        }
196    }
197
198    ret
199}
200
201// スニペット化できるステートメントの判定用
202trait Casl2StatementExtension {
203    fn is_jump_code(&self) -> bool;
204    fn is_push_code(&self) -> bool;
205    fn is_pop_code(&self) -> bool;
206}
207
208impl Casl2StatementExtension for casl2::Statement {
209    fn is_jump_code(&self) -> bool {
210        if let casl2::Statement::Code {
211            command: casl2::Command::P { code, .. },
212            ..
213        } = self
214        {
215            use casl2::P::*;
216            match code {
217                Jpl | Jmi | Jnz | Jze | Jov | Jump => true,
218                Push | Call | Svc => false,
219            }
220        } else {
221            false
222        }
223    }
224
225    fn is_push_code(&self) -> bool {
226        matches!(
227            self,
228            casl2::Statement::Code {
229                command: casl2::Command::P {
230                    code: casl2::P::Push,
231                    ..
232                },
233                ..
234            }
235        )
236    }
237
238    fn is_pop_code(&self) -> bool {
239        matches!(
240            self,
241            casl2::Statement::Code {
242                command: casl2::Command::Pop { .. },
243                ..
244            }
245        )
246    }
247}
248
249fn can_include_snippet(stmt: &casl2::Statement) -> bool {
250    stmt.is_code() && !stmt.is_jump_code()
251}
252
253// 共通コードのスニペットをサブルーチンとしてまとめる
254pub fn collect_duplicates(mut statements: Vec<casl2::Statement>) -> Vec<casl2::Statement> {
255    let mut snippets = vec![];
256    let mut id = 0;
257
258    loop {
259        let end = {
260            let mut end = statements.len();
261            for (i, stmt) in statements.iter().enumerate() {
262                if let casl2::Statement::Code {
263                    command: casl2::Command::Ret,
264                    ..
265                } = stmt
266                {
267                    end = i;
268                    break;
269                }
270            }
271            end
272        };
273
274        let mut hset = std::collections::HashSet::new();
275
276        let mut best: Option<(usize, usize, Vec<usize>)> = None;
277
278        for i in 0..end {
279            if !can_include_snippet(&statements[i]) {
280                continue;
281            }
282            let mut stack: i32 = 0;
283            if statements[i].is_pop_code() {
284                continue;
285            } else if statements[i].is_push_code() {
286                stack += 1;
287            }
288            for r in i + 1..end {
289                if !can_include_snippet(&statements[r]) {
290                    break;
291                }
292                if statements[r].is_pop_code() {
293                    stack -= 1;
294                    if stack < 0 {
295                        break;
296                    }
297                } else if statements[r].is_push_code() {
298                    stack += 1;
299                }
300                if stack != 0 {
301                    continue;
302                }
303                let len = r - i + 1;
304                if hset.contains(&(i, len)) {
305                    continue;
306                }
307                let mut dups = vec![i];
308                for k in r + 1..end - len {
309                    if statements[i..=r] != statements[k..k + len] {
310                        continue;
311                    }
312                    if hset.insert((k, len)) {
313                        dups.push(k);
314                    }
315                }
316                if dups.len() < 2 {
317                    continue;
318                }
319                let before_cost = len * dups.len();
320                let after_cost = len + 1 + dups.len();
321                if after_cost >= before_cost {
322                    continue;
323                }
324                let score = before_cost - after_cost;
325                if let Some((best_score, best_len, _)) = best.as_ref() {
326                    if *best_score > score {
327                        continue;
328                    }
329                    if *best_score == score && *best_len <= len {
330                        continue;
331                    }
332                }
333                best = Some((score, len, dups));
334            }
335        }
336
337        if let Some((_, len, mut dups)) = best {
338            id += 1;
339            let name = format!("F{:03}", id);
340            let mut snippet = None;
341            while let Some(pos) = dups.pop() {
342                let rest = statements.split_off(pos + len);
343                if snippet.is_none() {
344                    let code = statements.split_off(pos);
345                    snippet = Some(code);
346                } else {
347                    statements.truncate(pos);
348                }
349                statements.code(format!(" CALL {}", name));
350                statements.extend(rest);
351            }
352            if let Some(mut snippet) = snippet {
353                if let Some(casl2::Statement::Code { label, .. }) = snippet.first_mut() {
354                    *label = Some(name.into());
355                }
356                snippet.code(casl2::Command::Ret);
357                snippets.push(snippet);
358            }
359        } else {
360            break;
361        }
362    }
363
364    let rest = {
365        let mut end = statements.len();
366        for (i, stmt) in statements.iter().enumerate() {
367            if let casl2::Statement::Code {
368                command: casl2::Command::Ret,
369                ..
370            } = stmt
371            {
372                end = i + 1;
373                break;
374            }
375        }
376        statements.split_off(end)
377    };
378
379    for snippet in snippets {
380        statements.extend(snippet);
381    }
382
383    statements.extend(rest);
384
385    statements
386}