use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
enum Line {
Decl(Vec<usize>),
Store { reg: usize, rhs: String },
Other(String),
}
fn parse(line: &str) -> Line {
let t = line.trim();
if let Some(rest) = t.strip_prefix("let ") {
if let Some(list) = rest.strip_suffix(';') {
let mut regs = Vec::new();
let mut ok = true;
for part in list.split(',') {
match reg_name(part.trim()) {
Some(r) => regs.push(r),
None => {
ok = false;
break;
}
}
}
if ok && !regs.is_empty() {
return Line::Decl(regs);
}
}
}
if t.ends_with(';') {
if let Some((lhs, rhs)) = t.strip_suffix(';').unwrap_or(t).split_once('=') {
let lhs = lhs.trim();
if !lhs.is_empty() && !rhs.starts_with('=') && !lhs.ends_with(['!', '<', '>', '=']) {
if let Some(reg) = reg_name(lhs) {
return Line::Store {
reg,
rhs: rhs.trim().to_string(),
};
}
}
}
}
Line::Other(line.to_string())
}
impl Line {
fn text(&self) -> &str {
match self {
Line::Decl(_) => "",
Line::Store { rhs, .. } => rhs,
Line::Other(t) => t,
}
}
}
fn reg_name(s: &str) -> Option<usize> {
let n = s.strip_prefix('r')?;
if n.is_empty() || !n.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
n.parse().ok()
}
fn reads_in(line: &Line) -> HashMap<usize, usize> {
let mut out = HashMap::new();
let text = match line {
Line::Decl(_) => return out,
Line::Store { rhs, .. } => rhs.as_str(),
Line::Other(t) => t.as_str(),
};
let mut cur = String::new();
let flush = |cur: &mut String, out: &mut HashMap<usize, usize>| {
if !cur.is_empty() {
if let Some(r) = reg_name(cur) {
*out.entry(r).or_insert(0) += 1;
}
cur.clear();
}
};
for ch in text.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' || ch == '$' {
cur.push(ch);
} else {
flush(&mut cur, &mut out);
}
}
flush(&mut cur, &mut out);
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Purity {
Stable,
Ident,
Effectful,
}
fn purity(rhs: &str) -> Purity {
let t = strip_comments(rhs).trim().to_string();
let t = t.as_str();
if t.is_empty() {
return Purity::Effectful;
}
if reg_name(t).is_some() {
return Purity::Stable;
}
if matches!(t, "undefined" | "null" | "true" | "false" | "NaN" | "Infinity") {
return Purity::Stable;
}
let b = t.as_bytes();
if b[0].is_ascii_digit() || (b[0] == b'-' && b.len() > 1 && b[1].is_ascii_digit()) {
return if t.bytes().all(|c| {
c.is_ascii_digit() || c == b'.' || c == b'-' || c == b'+' || c == b'e' || c == b'x' || c == b'_'
}) {
Purity::Stable
} else {
Purity::Effectful
};
}
if (b[0] == b'"' || b[0] == b'\'') && t.len() >= 2 && t.ends_with(b[0] as char) {
return Purity::Stable; }
if t.starts_with('[') && t.ends_with(']') {
let inner = &t[1..t.len() - 1];
let mut depth = 0i32;
let mut cur = String::new();
let mut elems = Vec::new();
for ch in inner.chars() {
match ch {
'[' | '(' | '{' => depth += 1,
']' | ')' | '}' => depth -= 1,
',' if depth == 0 => {
elems.push(std::mem::take(&mut cur));
continue;
}
_ => {}
}
cur.push(ch);
}
if !cur.trim().is_empty() {
elems.push(cur);
}
return if elems
.iter()
.all(|e| matches!(purity(e), Purity::Stable | Purity::Ident))
{
Purity::Stable
} else {
Purity::Effectful
};
}
let mut it = t.chars();
match it.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' || c == '$' => {}
_ => return Purity::Effectful,
}
if it.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$') {
Purity::Ident
} else {
Purity::Effectful
}
}
fn strip_comments(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
match s[i + 2..].find("*/") {
Some(p) => {
i += 2 + p + 2;
out.push(' ');
continue;
}
None => break,
}
}
if bytes[i] == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'/' {
break;
}
out.push(bytes[i] as char);
i += 1;
}
out
}
fn pure_expr(rhs: &str) -> bool {
purity(rhs) != Purity::Effectful
}
fn replace_reg(line: &str, reg: usize, replacement: &str) -> Option<String> {
let needle = format!("r{reg}");
let mut hits = Vec::new();
let bytes = line.as_bytes();
let mut i = 0;
while let Some(pos) = line[i..].find(&needle) {
let at = i + pos;
let before_ok = at == 0 || !is_ident_byte(bytes[at - 1]);
let after = at + needle.len();
let after_ok = after >= bytes.len() || !is_ident_byte(bytes[after]);
if before_ok && after_ok {
hits.push(at);
}
i = at + needle.len();
}
if hits.len() != 1 {
return None;
}
let at = hits[0];
let before = line[..at].chars().last();
let after = line[at + needle.len()..].chars().next();
let first = replacement.chars().next();
let last = replacement.chars().last();
let glue_left = matches!((before, first), (Some('-'), Some('-')) | (Some('+'), Some('+')));
let glue_right = matches!((last, after), (Some(c), Some('.')) if c.is_ascii_digit());
let text = if glue_left || glue_right {
format!("({replacement})")
} else {
replacement.to_string()
};
Some(format!("{}{}{}", &line[..at], text, &line[at + needle.len()..]))
}
fn is_ident_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_' || b == b'$'
}
fn is_simple_ident(s: &str) -> bool {
let mut b = s.bytes();
match b.next() {
Some(c) if c.is_ascii_alphabetic() || c == b'_' || c == b'$' => {}
_ => return false,
}
b.all(is_ident_byte)
}
fn mentions_ident(text: &str, name: &str) -> bool {
let bytes = text.as_bytes();
let mut i = 0;
while let Some(pos) = text[i..].find(name) {
let at = i + pos;
let before_ok = at == 0 || !is_ident_byte(bytes[at - 1]);
let after = at + name.len();
let after_ok = after >= bytes.len() || !is_ident_byte(bytes[after]);
if before_ok && after_ok {
return true;
}
i = at + name.len();
}
false
}
fn call_scratch_lines(
parsed: &[Line],
lines: &[String],
region: &[usize],
) -> HashSet<usize> {
let mut drop_idx = HashSet::new();
for (i, l) in parsed.iter().enumerate() {
let Line::Store { reg, rhs } = l else { continue };
let expr = rhs.trim();
let Some((obj, prop)) = expr.split_once('.') else { continue };
if !is_simple_ident(obj) || !is_simple_ident(prop) {
continue;
}
let mut live = false;
for k in i + 1..parsed.len() {
if region[k] != region[i] {
break;
}
if matches!(&parsed[k], Line::Store { reg: r2, .. } if r2 == reg) {
break; }
if reads_in(&parsed[k]).contains_key(reg) {
live = true;
break;
}
}
if live {
continue;
}
let call = format!("{expr}(");
let obj_reg = reg_name(obj);
let Some(j) = (i + 1..lines.len()).find(|j| {
region[*j] == region[i] && lines[*j].contains(&call)
}) else {
continue;
};
let interfered = (i + 1..j).any(|k| {
match obj_reg {
Some(r) => matches!(&parsed[k], Line::Store { reg, .. } if *reg == r),
None => mentions_ident(parsed[k].text(), obj),
}
});
if interfered {
continue;
}
drop_idx.insert(i);
}
drop_idx
}
pub fn optimize(text: &str) -> String {
if std::env::var_os("JSCD_NO_OPT").is_some() {
return text.to_string();
}
let mut lines: Vec<String> = text.lines().map(str::to_string).collect();
let keep_ending = text.ends_with('\n');
lines.retain(|l| {
let t = l.trim();
!(t.starts_with("__runtime.DeclareGlobals(")
|| t.starts_with("__runtime.DeclareGlobalsForInterpreter("))
});
let dbg = std::env::var("JSCD_DBG_OPT").is_ok();
let dump = |tag: &str, ls: &[String]| {
if dbg {
eprintln!("[opt] ── {tag} ──");
for (i, l) in ls.iter().enumerate() {
eprintln!("[opt] {i:4} {}", l);
}
}
};
dump("in", &lines);
prune_dead_temps(&mut lines);
optimize_once(&mut lines);
dump("after optimize_once", &lines);
prune_dead_phis(&mut lines);
dump("after prune_dead_phis", &lines);
rebuild_calls(&mut lines);
dump("after rebuild_calls", &lines);
prune_decls(&mut lines);
dump("after prune_decls", &lines);
bind_declarators(&mut lines);
dump("after bind_declarators", &lines);
if dbg {
eprintln!("[opt] input was:\n{}", text);
}
let mut out = lines.join("\n");
if keep_ending && !out.ends_with('\n') {
out.push('\n');
}
out
}
fn inside_loop(lines: &[String]) -> Vec<bool> {
let mut out = vec![false; lines.len()];
let mut stack: Vec<bool> = Vec::new(); let mut depth = 0usize;
let mut in_loop = 0usize;
for (i, l) in lines.iter().enumerate() {
out[i] = in_loop > 0;
let t = l.trim();
let opens_loop = t.starts_with("while (")
|| t.starts_with("while(")
|| t.starts_with("for (")
|| t.starts_with("for(")
|| t == "do {"
|| t.ends_with(" do {")
|| t.contains(": while (")
|| t.contains(": for (");
let opens = t.ends_with('{');
if opens {
depth += 1;
stack.push(opens_loop);
if opens_loop {
in_loop += 1;
}
}
if t.contains('}') {
let closes = t.matches('}').count();
for _ in 0..closes {
if depth == 0 {
break;
}
depth -= 1;
if stack.pop().unwrap_or(false) {
in_loop -= 1;
}
}
}
}
out
}
fn block_paths(lines: &[String]) -> Vec<Vec<u32>> {
let mut out: Vec<Vec<u32>> = Vec::with_capacity(lines.len());
let mut stack: Vec<u32> = Vec::new();
let mut next_id = 0u32;
for l in lines.iter() {
let t = l.trim();
let mut opens_after_close = false;
if t.starts_with('}') {
let closes = t.matches('}').count();
for _ in 0..closes.saturating_sub(if t.ends_with('{') { 1 } else { 0 }) {
stack.pop();
}
opens_after_close = t.ends_with('{');
}
out.push(stack.clone());
if t.ends_with('{') && (!t.starts_with('}') || opens_after_close) {
stack.push(next_id);
next_id += 1;
} else if t.ends_with('{') {
stack.push(next_id);
next_id += 1;
}
}
out
}
fn regions(lines: &[String]) -> Vec<usize> {
let mut out = vec![0usize; lines.len()];
let mut cur = 0usize;
for (i, l) in lines.iter().enumerate() {
let t = l.trim_start();
let head = l.len() - t.len() == 0;
if head
&& (t.starts_with("function ")
|| t.starts_with("var ")
|| t.starts_with("let ")
|| t.starts_with("const ")
|| t.starts_with("class "))
&& !t.starts_with("let phi")
{
cur += 1;
}
out[i] = cur;
}
out
}
fn block_ids(lines: &[String]) -> Vec<usize> {
let mut out = vec![0usize; lines.len()];
let mut stack: Vec<usize> = vec![0];
let mut next = 1usize;
for (i, l) in lines.iter().enumerate() {
out[i] = *stack.last().unwrap_or(&0);
let t = strip_lexical(l);
for ch in t.chars() {
match ch {
'{' => {
stack.push(next);
next += 1;
}
'}' if stack.len() > 1 => {
stack.pop();
}
_ => {}
}
}
}
out
}
fn strip_lexical(s: &str) -> String {
let b: Vec<char> = s.chars().collect();
let mut out = String::new();
let mut i = 0;
let regex_prev = |c: char| "=(,:[!&|?{};+-*%^~<>".contains(c);
while i < b.len() {
let c = b[i];
if c == '/' && b.get(i + 1) == Some(&'/') {
break; }
if c == '/' && b.get(i + 1) == Some(&'*') {
i += 2;
while i + 1 < b.len() && !(b[i] == '*' && b[i + 1] == '/') {
i += 1;
}
i += 2;
continue;
}
if c == '"' || c == '\'' || c == '`' {
i += 1;
while i < b.len() {
if b[i] == '\\' {
i += 2;
continue;
}
if b[i] == c {
i += 1;
break;
}
i += 1;
}
out.push('S');
continue;
}
if c == '/' && out.trim_end().chars().last().map(regex_prev).unwrap_or(true) {
i += 1;
let mut in_class = false;
while i < b.len() {
match b[i] {
'\\' => i += 2,
'[' => {
in_class = true;
i += 1;
}
']' => {
in_class = false;
i += 1;
}
'/' if !in_class => {
i += 1;
break;
}
_ => i += 1,
}
continue;
}
out.push('S');
continue;
}
out.push(c);
i += 1;
}
out
}
fn phi_rhs_pure(rhs: &str) -> bool {
let t = strip_lexical(rhs);
if t.contains('(') || t.contains('[') || t.contains('.') {
return false;
}
let b: Vec<char> = t.chars().collect();
for (i, c) in b.iter().enumerate() {
if *c != '=' {
continue;
}
let prev = if i > 0 { b[i - 1] } else { ' ' };
let next = b.get(i + 1).copied().unwrap_or(' ');
if prev != '=' && prev != '!' && prev != '<' && prev != '>' && next != '=' {
return false;
}
}
true
}
fn count_ident(text: &str, name: &str) -> usize {
let bytes = text.as_bytes();
let mut n = 0;
let mut i = 0;
while let Some(pos) = text[i..].find(name) {
let at = i + pos;
let before_ok = at == 0 || !is_ident_byte(bytes[at - 1]);
let after = at + name.len();
let after_ok = after >= bytes.len() || !is_ident_byte(bytes[after]);
if before_ok && after_ok {
n += 1;
}
i = at + name.len();
}
n
}
fn phi_name(s: &str) -> Option<usize> {
let n = s.strip_prefix("phi")?;
if n.is_empty() || !n.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
n.parse().ok()
}
fn prune_dead_phis(lines: &mut Vec<String>) {
for _ in 0..3 {
let mut stores: HashMap<usize, Vec<usize>> = HashMap::new(); let mut decl_lines: Vec<(usize, Vec<usize>)> = Vec::new();
for (i, l) in lines.iter().enumerate() {
let t = l.trim_start();
if let Some(rest) = t.strip_prefix("let ") {
if let Some(list) = rest.strip_suffix(';') {
let names: Vec<usize> = list
.split(',')
.filter_map(|p| phi_name(p.trim()))
.collect();
if !names.is_empty()
&& names.len() == list.split(',').count()
{
decl_lines.push((i, names));
}
continue;
}
}
if let Some((lhs, _)) = t.strip_suffix(';').and_then(|x| x.split_once('=')) {
if let Some(p) = phi_name(lhs.trim()) {
stores.entry(p).or_default().push(i);
}
}
}
if stores.is_empty() && decl_lines.is_empty() {
return;
}
let mut dead: HashSet<usize> = HashSet::new();
for (p, rows) in &stores {
let name = format!("phi{p}");
let mut total = 0usize;
for l in lines.iter() {
total += count_ident(l, &name);
}
let own: usize = rows
.iter()
.map(|i| count_ident(&lines[*i], &name))
.sum();
let in_decl: usize = decl_lines
.iter()
.filter(|(_, ns)| ns.contains(p))
.map(|(i, _)| count_ident(&lines[*i], &name))
.sum();
if total <= own + in_decl {
dead.insert(*p);
}
}
if dead.is_empty() {
return;
}
let mut drop: HashSet<usize> = HashSet::new();
let mut rewrite: HashMap<usize, String> = HashMap::new();
for (p, rows) in &stores {
if !dead.contains(p) {
continue;
}
for i in rows {
if let Some(rhs) = lines[*i]
.trim_start()
.strip_suffix(';')
.and_then(|x| x.split_once('='))
.map(|(_, r)| r.trim().to_string())
{
if pure_expr(&rhs) || phi_rhs_pure(&rhs) {
drop.insert(*i);
} else {
let indent = &lines[*i][..lines[*i].len() - lines[*i].trim_start().len()];
rewrite.insert(*i, format!("{indent}{rhs};"));
}
}
}
}
let mut out = Vec::with_capacity(lines.len());
for (i, l) in lines.iter().enumerate() {
if drop.contains(&i) {
continue;
}
if let Some(r) = rewrite.get(&i) {
out.push(r.clone());
continue;
}
if let Some((_, names)) = decl_lines.iter().find(|(j, _)| *j == i) {
let keep: Vec<String> = names
.iter()
.filter(|p| !dead.contains(p))
.map(|p| format!("phi{p}"))
.collect();
if keep.is_empty() {
continue;
}
let indent = &l[..l.len() - l.trim_start().len()];
out.push(format!("{indent}let {};", keep.join(", ")));
continue;
}
out.push(l.clone());
}
*lines = out;
}
}
fn rebuild_calls(lines: &mut Vec<String>) {
let region = regions(lines);
let block = block_ids(lines);
let mut drop: HashSet<usize> = HashSet::new();
let mut rewritten: HashMap<usize, String> = HashMap::new();
let mut drop_owner: HashMap<usize, usize> = HashMap::new();
for li in 0..lines.len() {
if drop.contains(&li) || !lines[li].contains('(') {
continue; }
if !starts_statement(lines, li) {
continue; }
if is_control_header(lines[li].trim_start()) {
continue; }
let mut cur = lines[li].clone();
let mut k = li;
let mut inlined: Vec<usize> = Vec::new();
let mut steps = 0usize;
while k > 0 && steps < 64 {
steps += 1;
let prev = k - 1;
if drop.contains(&prev) {
let ours = drop_owner.get(&prev).is_some_and(|o| *o == li || inlined.contains(o));
if ours {
k = prev; continue;
}
break; }
let prev_text = rewritten
.get(&prev)
.cloned()
.unwrap_or_else(|| lines[prev].clone());
let Line::Store { reg, rhs } = parse(&prev_text) else { break };
if region[prev] != region[li] || block[prev] != block[li] {
if std::env::var("JSCD_DBG7").is_ok() { eprintln!("[7] L={li} prev={prev} reg={reg} stop: region/block"); } break; }
if !starts_statement(lines, prev) {
if std::env::var("JSCD_DBG7").is_ok() {
eprintln!("[7] L={li} prev={prev} stop: braceless");
}
break; }
if reads_in(&parse(&cur)).get(®).copied().unwrap_or(0) != 1 {
if std::env::var("JSCD_DBG7").is_ok() { eprintln!("[7] L={li} prev={prev} reg={reg} stop: read not in L"); } break; }
let mut later = 0usize;
for idx in li + 1..lines.len() {
if drop.contains(&idx) {
continue; }
if region[idx] != region[li] {
break; }
if matches!(parse(&lines[idx]), Line::Store { reg: r2, .. } if r2 == reg) {
later += reads_in(&parse(&lines[idx])).get(®).copied().unwrap_or(0);
break;
}
later += reads_in(&parse(&lines[idx])).get(®).copied().unwrap_or(0);
}
if later > 0 {
if std::env::var("JSCD_DBG7").is_ok() { eprintln!("[7] L={li} prev={prev} reg={reg} stop: later reads"); } break;
}
if breaks_dependency(&rhs, &inlined, lines) {
break;
}
let Some(ctx) = occurrence_context(&cur, reg) else { break };
if ctx.as_target {
break;
}
let text = paren_if_needed(&rhs, ctx.after_new);
let Some(next) = replace_reg(&cur, reg, &text) else { break };
cur = next;
if rewritten.contains_key(&prev) {
rewritten.remove(&prev);
drop.insert(prev); drop_owner.insert(prev, li);
}
inlined.push(prev);
k = prev;
}
if !inlined.is_empty() {
for i in &inlined {
drop.insert(*i);
drop_owner.insert(*i, li);
}
rewritten.insert(li, cur);
}
}
let mut out = Vec::with_capacity(lines.len());
for (i, l) in lines.iter().enumerate() {
if drop.contains(&i) {
continue;
}
out.push(rewritten.remove(&i).unwrap_or_else(|| l.clone()));
}
*lines = out;
}
fn breaks_dependency(rhs: &str, inlined: &[usize], lines: &[String]) -> bool {
if inlined.is_empty() {
return false;
}
let deps: HashSet<usize> = reads_in(&parse(&format!("x = {rhs};"))).into_keys().collect();
for i in inlined {
let Line::Store { reg, .. } = parse(&lines[*i]) else { continue };
if deps.contains(®) {
return true;
}
for name in ident_words(rhs) {
let pat = format!("{name} =");
if lines[*i].contains(&pat) || lines[*i].contains(&format!("{name}++"))
|| lines[*i].contains(&format!("++{name}"))
{
return true;
}
}
}
false
}
fn ident_words(s: &str) -> Vec<String> {
let clean = strip_lexical(s);
let mut out = Vec::new();
let mut cur = String::new();
for ch in clean.chars().chain(std::iter::once(' ')) {
if is_ident_byte(ch as u8) && !ch.is_ascii_digit() || !cur.is_empty() && is_ident_byte(ch as u8) {
cur.push(ch);
} else {
if !cur.is_empty() {
if !cur.starts_with('r') || !cur[1..].bytes().all(|b| b.is_ascii_digit()) {
out.push(cur.clone());
}
cur.clear();
}
}
}
out
}
struct OccurCtx {
after_new: bool,
as_target: bool,
}
fn occurrence_context(line: &str, reg: usize) -> Option<OccurCtx> {
let needle = format!("r{reg}");
let bytes = line.as_bytes();
let mut hits = Vec::new();
let mut i = 0;
while let Some(pos) = line[i..].find(&needle) {
let at = i + pos;
let before_ok = at == 0 || !is_ident_byte(bytes[at - 1]);
let after = at + needle.len();
let after_ok = after >= bytes.len() || !is_ident_byte(bytes[after]);
if before_ok && after_ok {
hits.push(at);
}
i = at + needle.len();
}
if hits.len() != 1 {
return None;
}
let at = hits[0];
let before = line[..at].trim_end();
let after = line[at + needle.len()..].trim_start();
let as_target = after.starts_with("++")
|| after.starts_with("--")
|| (after.starts_with('=') && !after.starts_with("=="));
Some(OccurCtx {
after_new: before.ends_with("new"),
as_target,
})
}
fn paren_if_needed(rhs: &str, after_new: bool) -> String {
if after_new {
return if is_plain_ref(rhs) {
rhs.to_string()
} else {
format!("({rhs})")
};
}
if is_primary_expr(rhs) {
rhs.to_string()
} else {
format!("({rhs})")
}
}
fn is_plain_ref(s: &str) -> bool {
let t = s.trim();
!t.is_empty()
&& t.split('.').all(|part| {
let mut b = part.bytes();
matches!(b.next(), Some(c) if c.is_ascii_alphabetic() || c == b'_' || c == b'$')
&& b.all(is_ident_byte)
})
}
fn is_primary_expr(s: &str) -> bool {
let t = s.trim();
let Some(first) = t.chars().next() else { return false };
if matches!(first, '-' | '+' | '!' | '~' | '?' | '{') {
return false;
}
for kw in ["typeof ", "void ", "delete ", "await ", "yield", "function", "class", "async "] {
if t.starts_with(kw) {
return false;
}
}
let b: Vec<char> = t.chars().collect();
let mut depth = 0i32;
let mut i = 0usize;
while i < b.len() {
let c = b[i];
match c {
'"' | '\'' | '`' => {
i += 1;
while i < b.len() {
if b[i] == '\\' {
i += 2;
continue;
}
if b[i] == c {
i += 1;
break;
}
i += 1;
}
continue;
}
'(' | '[' | '{' => depth += 1,
')' | ']' | '}' => depth -= 1,
'+' | '-' | '*' | '/' | '%' | '<' | '>' | '=' | '&' | '|' | '^' | '~' | '!' | '?'
| ',' if depth == 0 => return false,
_ => {}
}
i += 1;
}
true
}
fn bind_declarators(lines: &mut Vec<String>) {
let region = regions(lines);
let block = block_ids(lines);
let parsed: Vec<Line> = lines.iter().map(|l| parse(l)).collect();
type Key = (usize, usize); let mut decls: HashMap<Key, usize> = HashMap::new(); let mut decl_at: HashMap<Key, usize> = HashMap::new(); let mut stores: HashMap<Key, usize> = HashMap::new(); let mut first_use: HashMap<Key, usize> = HashMap::new(); for (i, l) in parsed.iter().enumerate() {
if let Line::Decl(regs) = l {
for r in regs {
*decls.entry((region[i], *r)).or_insert(0) += 1;
decl_at.entry((region[i], *r)).or_insert(i);
}
}
if let Line::Store { reg, .. } = l {
let k = (region[i], *reg);
*stores.entry(k).or_insert(0) += 1;
first_use.entry(k).or_insert(i);
}
for r in reads_in(l).keys() {
first_use.entry((region[i], *r)).or_insert(i);
}
}
let mut drop_names: HashMap<usize, HashSet<usize>> = HashMap::new(); let mut promote: HashMap<usize, usize> = HashMap::new(); for (i, l) in parsed.iter().enumerate() {
let Line::Decl(regs) = l else { continue };
for r in regs {
let k = (region[i], *r);
if decls.get(&k).copied() != Some(1) || stores.get(&k).copied() != Some(1) {
continue; }
if decl_at.get(&k).copied() != Some(i) {
continue;
}
let Some(&j) = first_use.get(&k) else { continue };
if j <= i
|| region[j] != region[i]
|| block[j] != block[i]
|| !matches!(&parsed[j], Line::Store { reg, .. } if reg == r)
|| !starts_statement(lines, j)
{
continue;
}
drop_names.entry(i).or_default().insert(*r);
promote.insert(j, *r);
}
}
if drop_names.is_empty() {
return;
}
let mut out = Vec::with_capacity(lines.len());
for (i, l) in lines.iter().enumerate() {
if let Some(drop) = drop_names.get(&i) {
let Line::Decl(regs) = &parsed[i] else { unreachable!() };
let keep: Vec<String> = regs
.iter()
.filter(|r| !drop.contains(r))
.map(|r| format!("r{r}"))
.collect();
if keep.is_empty() {
continue; }
let indent = &l[..l.len() - l.trim_start().len()];
out.push(format!("{indent}let {};", keep.join(", ")));
continue;
}
if promote.contains_key(&i) {
let indent = &l[..l.len() - l.trim_start().len()];
out.push(format!("{indent}let {}", l.trim_start()));
continue;
}
out.push(l.clone());
}
*lines = out;
}
fn starts_statement(lines: &[String], j: usize) -> bool {
for k in (0..j).rev() {
let t = strip_lexical(&lines[k]);
let t = t.trim();
if t.is_empty() {
continue;
}
return t.ends_with(';') || t.ends_with('{') || t.ends_with('}') || t.ends_with(':');
}
true }
fn prune_dead_temps(lines: &mut Vec<String>) {
let declared: std::collections::HashSet<String> = {
let mut out = std::collections::HashSet::new();
for l in lines.iter() {
let t = l.trim();
let Some(rest) = t.strip_prefix("let ") else { continue };
let Some(list) = rest.strip_suffix(';') else { continue };
for part in list.split(',') {
let n = part.trim();
if is_temp_name(n) {
out.insert(n.to_string());
}
}
}
out
};
if declared.is_empty() {
return;
}
let mut reads: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
for l in lines.iter() {
let t = l.trim();
if t.starts_with("let ") || t.starts_with("var ") || t.starts_with("const ") {
continue;
}
for name in &declared {
let mut count = 0usize;
let bytes = t.as_bytes();
let mut i = 0usize;
while let Some(pos) = t[i..].find(name.as_str()) {
let at = i + pos;
let before_ok = at == 0 || !is_ident_byte(bytes[at - 1]);
let after = at + name.len();
let after_ok = after >= bytes.len() || !is_ident_byte(bytes[after]);
if before_ok && after_ok {
count += 1;
}
i = at + name.len();
}
let is_store = t.strip_suffix(';').and_then(|x| x.split_once('=')).map(|(a, _)| a.trim() == name).unwrap_or(false);
if is_store {
count = count.saturating_sub(1);
}
if count > 0 {
*reads.entry(name.clone()).or_insert(0) += count;
}
}
}
lines.retain(|l| {
let t = l.trim();
let Some(name) = t.strip_suffix(';').and_then(|x| x.split_once('=')).map(|(a, _)| a.trim()) else {
return true;
};
if !is_temp_name(name) || !declared.contains(name) || reads.get(name).copied().unwrap_or(0) > 0 {
return true;
}
let rhs = t.strip_suffix(';').and_then(|x| x.split_once('=')).map(|(_, b)| b.trim()).unwrap_or("");
!pure_expr(rhs)
});
}
fn is_temp_name(s: &str) -> bool {
s.strip_prefix('t')
.map(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit()))
.unwrap_or(false)
}
fn optimize_once(lines: &mut Vec<String>) {
let loop_body = inside_loop(lines);
let region = regions(lines);
let parsed: Vec<Line> = lines.iter().map(|l| parse(l)).collect();
let mut reads: HashMap<(usize, usize), usize> = HashMap::new();
for (i, l) in parsed.iter().enumerate() {
for (r, n) in reads_in(l) {
*reads.entry((region[i], r)).or_insert(0) += n;
}
}
#[derive(PartialEq)]
enum Next {
Read,
Write,
}
let mut next: HashMap<(usize, usize), (Next, usize)> = HashMap::new();
let paths = block_paths(lines);
let mut drop_idx: HashSet<usize> = call_scratch_lines(&parsed, lines, ®ion);
for i in (0..parsed.len()).rev() {
let reads_here = reads_in(&parsed[i]);
if let Line::Store { reg, rhs } = &parsed[i] {
let key = (region[i], *reg);
let dead = match next.get(&key) {
Some((Next::Write, j)) => paths[i] == paths[*j],
None => true,
Some((Next::Read, _)) => false,
};
if dead && pure_expr(rhs) && !loop_body[i] && !reads_here.contains_key(reg) {
drop_idx.insert(i);
}
}
for r in reads_here.keys() {
next.insert((region[i], *r), (Next::Read, i));
}
if let Line::Store { reg, .. } = &parsed[i] {
if !reads_here.contains_key(reg) {
next.insert((region[i], *reg), (Next::Write, i));
}
}
}
let mut forward: HashMap<usize, (usize, usize, String)> = HashMap::new(); let mut forwarded_store: HashSet<usize> = HashSet::new();
for i in 0..parsed.len() {
if drop_idx.contains(&i) {
continue;
}
let Line::Store { reg, rhs } = &parsed[i] else {
continue;
};
if loop_body[i] {
continue; }
let Some(j) = (i + 1 < parsed.len()).then_some(i + 1) else {
continue;
};
if drop_idx.contains(&j) {
continue; }
if matches!(&parsed[j], Line::Store { reg: r2, .. } if r2 == reg) {
continue;
}
let n = reads_in(&parsed[j]).get(reg).copied().unwrap_or(0);
if n != 1 {
continue;
}
let mut later_reads = 0usize;
for l in &parsed[j + 1..] {
if matches!(l, Line::Store { reg: r2, .. } if r2 == reg) {
break;
}
later_reads += reads_in(l).get(reg).copied().unwrap_or(0);
}
if later_reads > 0 {
continue;
}
if !matches!(purity(rhs), Purity::Stable | Purity::Ident) {
continue;
}
if reads.get(&(region[i], *reg)).copied().unwrap_or(0) != 1 {
continue;
}
if replace_reg(&lines[j], *reg, rhs).is_none() {
continue;
}
forward.insert(j, (i, *reg, rhs.clone()));
forwarded_store.insert(i);
}
let conflicted: Vec<usize> = forward
.keys()
.filter(|d| forwarded_store.contains(d))
.cloned()
.collect();
for d in conflicted {
if let Some((src, _, _)) = forward.remove(&d) {
forwarded_store.remove(&src);
}
}
let mut new_lines = Vec::with_capacity(lines.len());
for (i, l) in lines.iter().enumerate() {
if forwarded_store.contains(&i) || drop_idx.contains(&i) {
continue;
}
let mut cur = l.clone();
if let Some((_, reg, rhs)) = forward.get(&i) {
if let Some(replaced) = replace_reg(&cur, *reg, rhs) {
cur = replaced;
}
}
new_lines.push(cur);
}
*lines = new_lines;
prune_decls(lines);
}
fn is_control_header(t: &str) -> bool {
t.starts_with("while (")
|| t.starts_with("while(")
|| t.starts_with("for (")
|| t.starts_with("for(")
|| t.starts_with("if (")
|| t.starts_with("if(")
|| t.starts_with("switch (")
|| t.starts_with("switch(")
|| t.starts_with("do {")
|| t.contains(": while (")
|| t.contains(": for (")
|| t.contains("} else if (")
}
fn prune_decls(lines: &mut Vec<String>) {
let region = regions(lines);
let mut used: HashSet<(usize, usize)> = HashSet::new();
for (i, l) in lines.iter().enumerate() {
for r in reads_in(&parse(l)).keys() {
used.insert((region[i], *r));
}
if let Line::Store { reg, .. } = parse(l) {
used.insert((region[i], reg));
}
}
let mut with_decls = Vec::with_capacity(lines.len());
for (i, l) in lines.drain(..).enumerate() {
match parse(&l) {
Line::Decl(regs) => {
let keep: Vec<String> = regs
.iter()
.filter(|r| used.contains(&(region[i], **r)))
.map(|r| format!("r{r}"))
.collect();
if keep.is_empty() {
continue;
}
let indent = &l[..l.len() - l.trim_start().len()];
with_decls.push(format!("{indent}let {};", keep.join(", ")));
}
_ => with_decls.push(l),
}
}
*lines = with_decls;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forwards_literals_and_identifiers() {
let src = "let r0, r1;\nr1 = greet;\nr0 = r1(1);\n";
let out = optimize(src);
assert!(out.contains("r0 = greet(1);"), "{out}");
assert!(!out.contains("r1 = greet;"), "{out}");
assert!(!out.contains("let r0, r1;"), "{out}");
}
#[test]
fn does_not_reorder_effectful_calls() {
let src = "let r0, r1, r2, r3;\nr1 = f();\nr2 = g();\nr3 = h(r1);\nr0 = r2 + r3;\n";
let out = optimize(src);
assert!(out.contains("r1 = f();"), "f() 必须留在原地:{out}");
assert!(!out.contains("h(f())"), "不许把 f() 越过 h() 前送:{out}");
let (pf, pg) = (
out.find("f()").unwrap_or(usize::MAX),
out.find("g()").unwrap_or(usize::MAX),
);
assert!(pf < pg, "f 必须仍在 g 之前:{out}");
}
#[test]
fn merges_adjacent_stable_values() {
let src = "let r0, r1, r2;\nr1 = 1;\nr0 = r1 + r2;\n";
let out = optimize(src);
assert!(out.contains("r0 = 1 + r2;"), "{out}");
assert!(!out.contains("r1 = 1;"), "{out}");
}
#[test]
fn does_not_touch_loop_bodies() {
let src = "let r0;\nwhile (true) {\n use(r0);\n r0 = 1;\n}\n";
let out = optimize(src);
assert!(out.contains("r0 = 1;"), "循环体里的写不许删:{out}");
}
#[test]
fn does_not_move_calls() {
let src = "let r0, r1;\nr1 = f();\nr0 = r1 + 1;\n";
let out = optimize(src);
assert!(out.contains("r1 = f();"), "{out}");
assert!(!out.contains("f() + 1"), "{out}");
}
#[test]
fn keeps_property_reads() {
let src = "let r0, r1;\nr1 = r0.log;\n";
let out = optimize(src);
assert!(out.contains("r1 = r0.log;"), "属性读不许删:{out}");
}
#[test]
fn drops_unused_declared_registers() {
let src = "let r0, r1, r2;\nr0 = 1;\nreturn r0;\n";
let out = optimize(src);
assert!(out.contains("return 1;"), "{out}");
assert!(!out.contains("r1"), "{out}");
assert!(!out.contains("let "), "没有寄存器再用时不该留声明:{out}");
}
#[test]
fn keeps_token_boundaries_on_forward() {
let src = "r7 = -1;\nr2 = r2-r7;\n";
let out = optimize(src);
assert!(!out.contains("--"), "不许拼出自减:{out}");
let src2 = "r0 = 1;\nr2 = r0.toString();\n";
let out2 = optimize(src2);
assert!(!out2.contains("1.toString"), "数字后跟点要括号:{out2}");
}
#[test]
fn does_not_cancel_chained_forwards() {
let src = "let r0, r1, r3;\nr4 = C;\nr0 = r4;\nr1 = r0;\nr3 = new r1(a0);\n";
let out = optimize(src);
assert!(!out.contains("r1 = r0;"), "链会被并掉:{out}");
assert!(out.contains("new C(a0)"), "整条链要重建到 C:{out}");
}
#[test]
fn does_not_inline_into_a_braceless_if_body() {
let src = "let r0;\nr0 = f();\nif (c)\n use(r0);\n";
let out = optimize(src);
assert!(!out.contains("use(f())"), "不能把无条件求值挪进条件体:{out}");
}
#[test]
fn does_not_inline_across_blocks() {
let src = "let r0;\nr0 = f();\nif (c) {\n use(r0);\n}\n";
let out = optimize(src);
assert!(!out.contains("use(f())"), "跨块不许并:{out}");
let src2 = "let r0;\nif (c) {\n r0 = f();\n}\nuse(r0);\n";
let out2 = optimize(src2);
assert!(!out2.contains("use(f())"), "跨块不许并:{out2}");
}
#[test]
fn does_not_inline_into_loop_header() {
let src = "let r0;\nr0 = f();\nwhile (r0 < 10) {\n r0 = r0 + 1;\n}\n";
let out = optimize(src);
assert!(!out.contains("while (f()"), "循环头不许并:{out}");
}
#[test]
fn rebuilds_call_arguments() {
let src = "let r2, r3;\nr2 = console;\nr3 = greet(\"world\");\nr2.log(r3);\n";
let out = optimize(src);
assert!(out.contains("console.log(greet(\"world\"))"), "{out}");
}
#[test]
fn dead_pure_store_removed() {
let src = "let r0, r1;\nr1 = \"x\";\nreturn 1;\n";
let out = optimize(src);
assert!(!out.contains("r1 = \"x\";"), "{out}");
}
#[test]
fn binds_declaration_to_single_assignment() {
let src = "let r0, r2, r3;\nr0 = greet;\nr2 = console;\nr3 = r0(\"world\");\nr2.log(r3);\n";
let out = optimize(src);
assert!(out.contains("console.log(greet(\"world\"))"), "{out}");
assert!(!out.contains("let r0, r2, r3;"), "旧声明行该没了:{out}");
}
#[test]
fn does_not_bind_when_read_before_store() {
let src = "let r0;\nif (c) {\n use(r0);\n}\nr0 = f();\n";
let out = optimize(src);
assert!(out.contains("r0 = f();"), "不许提升:{out}");
assert!(!out.contains("let r0 = f();"), "{out}");
}
#[test]
fn does_not_bind_into_braceless_if() {
let src = "let r0;\nif (c)\n r0 = f();\nuse(r0);\n";
let out = optimize(src);
assert!(out.contains(" r0 = f();"), "无括号 if 体里不许放 let:{out}");
assert!(!out.contains("let r0 = f();"), "{out}");
}
#[test]
fn binds_inside_same_block_only() {
let src = "let r0, r1;\nr1 = f();\nif (c) {\n r0 = g();\n}\nuse(r0, r1);\n";
let out = optimize(src);
assert!(out.contains("r0 = g();"), "跨块不许提升:{out}");
assert!(!out.contains("let r0 = g();"), "{out}");
}
}