1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use super::super::super::HighLevelEmitter;
impl HighLevelEmitter {
pub(in super::super::super) fn inline_for_increment_temps(statements: &mut [String]) {
let mut index = 0;
while index < statements.len() {
let trimmed = statements[index].trim();
if !trimmed.starts_with("for (") || !trimmed.ends_with('{') {
index += 1;
continue;
}
if let Some((init, condition, increment)) = Self::parse_for_parts(trimmed) {
let mut depth = 1isize;
let mut cursor = index + 1;
while cursor < statements.len() && depth > 0 {
depth += Self::brace_delta(&statements[cursor]);
let line = statements[cursor].trim();
if depth <= 0 {
break;
}
if line.starts_with("let ") {
if let Some(assign) = Self::parse_assignment(line) {
let target = assign.lhs.clone();
let rhs = assign.rhs.clone();
if Self::contains_identifier(&increment, &target) {
// Only inline+delete the definition when the temp
// is used nowhere else (besides its own def and
// the for-header increment) AND its value is pure
// (no call). Otherwise clearing the definition
// would dangle a still-live reference, and moving
// a side-effecting RHS into the increment would
// change evaluation order.
let used_elsewhere =
statements.iter().enumerate().any(|(i, stmt)| {
i != cursor
&& i != index
&& Self::contains_identifier(stmt, &target)
});
if used_elsewhere || rhs.contains('(') {
cursor += 1;
continue;
}
let replaced = Self::replace_identifier(&increment, &target, &rhs);
statements[index] =
format!("for ({init}; {condition}; {replaced}) {{");
statements[cursor].clear();
break;
}
}
}
cursor += 1;
}
}
index += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::super::super::super::HighLevelEmitter;
#[test]
fn does_not_inline_for_increment_temp_used_elsewhere() {
// Regression (adversarial): a temp used both in the for-increment and
// by another body statement must NOT be inlined+deleted, or the other
// use dangles and the side effect moves.
let mut statements = vec![
"for (let loc0 = 0; loc0 < 10; loc0 = loc0 + t5) {".to_string(),
" let t5 = step();".to_string(),
" log(t5);".to_string(),
"}".to_string(),
];
HighLevelEmitter::inline_for_increment_temps(&mut statements);
assert!(
statements.iter().any(|s| s.trim() == "let t5 = step();"),
"still-used temp definition must survive: {statements:?}"
);
assert!(
statements[0].contains("loc0 + t5"),
"increment must not inline a non-single-use temp: {}",
statements[0]
);
}
#[test]
fn inlines_pure_single_use_for_increment_temp() {
// A pure, single-use increment temp is still inlined (the intended case).
let mut statements = vec![
"for (let loc0 = 0; loc0 < 10; loc0 = loc0 + t5) {".to_string(),
" let t5 = 1 + 1;".to_string(),
" body();".to_string(),
"}".to_string(),
];
HighLevelEmitter::inline_for_increment_temps(&mut statements);
assert!(
statements[0].contains("loc0 + 1 + 1") || statements[0].contains("loc0 + (1 + 1)"),
"pure single-use temp should inline into the increment: {}",
statements[0]
);
}
}