Skip to main content

ddc_dec/
passes.rs

1//! Post-convert refinement passes for DEX-lifted statement trees.
2//!
3//! The converter hands back a correct-but-raw tree; these passes restore the
4//! source shapes a register machine loses: catch parameter binding,
5//! copy-forwarding (single-use temporaries), if/else→ternary folding,
6//! `StringBuilder` chain → `+` concatenation, `synchronized` recovery from
7//! the d8 monitor pattern, erased-type inference, boolean and null
8//! comparisons, and declaration hygiene.
9
10// The tree-walker match arms intentionally mirror the statement grammar
11// one level at a time; collapsing the nested `if let`s into outer match
12// arms would trade per-arm clarity for lint silence.
13#![allow(clippy::collapsible_match)]
14
15use std::collections::HashSet;
16
17// The tree-walker match arms intentionally mirror the statement grammar
18// one level at a time; collapsing the nested `if let`s into outer match
19// arms would trade per-arm clarity for lint silence.
20use jdc_core::ir::build::has_side_effects;
21use jdc_core::ir::expr::{AssignOp, BinOp, ConcatPart, ConstVal, Expr, TypeRef, UnOp};
22use jdc_core::ir::stmt::{CaseGroup, Catch, Stmt};
23use jdc_core::types::{JavaType, MethodDescriptor};
24use jdc_core::var::VarTable;
25
26use crate::lift::MethodEnv;
27
28// ---------------------------------------------------------------------------
29// Generic tree walking
30// ---------------------------------------------------------------------------
31
32/// Deep expression rewrite over a statement tree.
33pub fn rewrite_exprs<F: FnMut(&mut Expr)>(s: &mut Stmt, f: &mut F) {
34    walk_stmt_exprs(s, f);
35}
36
37fn walk_stmt_exprs<F: FnMut(&mut Expr)>(s: &mut Stmt, f: &mut F) {
38    match s {
39        Stmt::Block(v) => {
40            for x in v.iter_mut() {
41                walk_stmt_exprs(x, f);
42            }
43        }
44        Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
45            f(e);
46        }
47        Stmt::Return(Some(e)) => f(e),
48        Stmt::LocalDef { init, .. } => {
49            if let Some(e) = init {
50                f(e);
51            }
52        }
53        Stmt::If {
54            cond,
55            then_stmt,
56            else_stmt,
57        } => {
58            f(cond);
59            walk_stmt_exprs(then_stmt, f);
60            if let Some(e) = else_stmt {
61                walk_stmt_exprs(e, f);
62            }
63        }
64        Stmt::While { cond, body } => {
65            f(cond);
66            walk_stmt_exprs(body, f);
67        }
68        Stmt::DoWhile { body, cond } => {
69            walk_stmt_exprs(body, f);
70            f(cond);
71        }
72        Stmt::For {
73            init,
74            cond,
75            update,
76            body,
77        } => {
78            for x in init.iter_mut() {
79                walk_stmt_exprs(x, f);
80            }
81            if let Some(c) = cond {
82                f(c);
83            }
84            for u in update.iter_mut() {
85                f(u);
86            }
87            walk_stmt_exprs(body, f);
88        }
89        Stmt::ForEach { iterable, body, .. } => {
90            f(iterable);
91            walk_stmt_exprs(body, f);
92        }
93        Stmt::Switch {
94            selector,
95            cases,
96            default,
97            ..
98        } => {
99            f(selector);
100            for c in cases {
101                for x in c.body.iter_mut() {
102                    walk_stmt_exprs(x, f);
103                }
104                if let Some(g) = &mut c.guard {
105                    f(g);
106                }
107            }
108            if let Some(d) = default {
109                walk_stmt_exprs(d, f);
110            }
111        }
112        Stmt::Try {
113            body,
114            catches,
115            finally,
116        } => {
117            walk_stmt_exprs(body, f);
118            for c in catches {
119                walk_stmt_exprs(&mut c.body, f);
120            }
121            if let Some(fl) = finally {
122                walk_stmt_exprs(fl, f);
123            }
124        }
125        Stmt::TryWithResources {
126            resources,
127            body,
128            catches,
129            finally,
130        } => {
131            for x in resources.iter_mut() {
132                walk_stmt_exprs(x, f);
133            }
134            walk_stmt_exprs(body, f);
135            for c in catches {
136                walk_stmt_exprs(&mut c.body, f);
137            }
138            if let Some(fl) = finally {
139                walk_stmt_exprs(fl, f);
140            }
141        }
142        Stmt::Assert { cond, msg } => {
143            f(cond);
144            if let Some(m) = msg {
145                f(m);
146            }
147        }
148        Stmt::Synchronized { lock, body } => {
149            f(lock);
150            walk_stmt_exprs(body, f);
151        }
152        Stmt::TernaryValue { e } => f(e),
153        Stmt::Labeled { body, .. } => walk_stmt_exprs(body, f),
154        Stmt::Goto(_) | Stmt::Label(_) | Stmt::Break(_) | Stmt::Continue(_) => {}
155        _ => {}
156    }
157}
158
159/// Visit every expression (immutable).
160fn visit_exprs<F: FnMut(&Expr)>(e: &Expr, f: &mut F) {
161    f(e);
162    for_each_child(e, &mut |c| visit_exprs(c, f));
163}
164
165fn for_each_child<F: FnMut(&Expr)>(e: &Expr, f: &mut F) {
166    match e {
167        Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => f(e),
168        Expr::Bin { l, r, .. } => {
169            f(l);
170            f(r);
171        }
172        Expr::Cond { c, t, f: fe } => {
173            f(c);
174            f(t);
175            f(fe);
176        }
177        Expr::Assign { target, value, .. } => {
178            f(target);
179            f(value);
180        }
181        Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => f(e),
182        Expr::Field { owner, .. } => {
183            if let Some(o) = owner {
184                f(o);
185            }
186        }
187        Expr::Method { owner, args, .. } => {
188            if let Some(o) = owner {
189                f(o);
190            }
191            for a in args {
192                f(a);
193            }
194        }
195        Expr::ArrayIndex { array, index } => {
196            f(array);
197            f(index);
198        }
199        Expr::New { args, .. } => {
200            for a in args {
201                f(a);
202            }
203        }
204        Expr::NewArray { dims, init, .. } => {
205            for d in dims {
206                f(d);
207            }
208            if let Some(v) = init {
209                for x in v {
210                    f(x);
211                }
212            }
213        }
214        Expr::NewMultiArray { dims, .. } => {
215            for d in dims {
216                f(d);
217            }
218        }
219        Expr::StringConcat(parts) => {
220            for p in parts {
221                if let ConcatPart::Str(x) = p {
222                    f(x);
223                }
224            }
225        }
226        Expr::Invokedynamic { args, .. } => {
227            for a in args {
228                f(a);
229            }
230        }
231        Expr::AnonNew { args, .. } => {
232            for a in args {
233                f(a);
234            }
235        }
236        _ => {}
237    }
238}
239
240/// Mutable child walk (one level).
241fn for_each_child_mut<F: FnMut(&mut Expr)>(e: &mut Expr, f: &mut F) {
242    match e {
243        Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => f(e),
244        Expr::Bin { l, r, .. } => {
245            f(l);
246            f(r);
247        }
248        Expr::Cond { c, t, f: fe } => {
249            f(c);
250            f(t);
251            f(fe);
252        }
253        Expr::Assign { target, value, .. } => {
254            f(target);
255            f(value);
256        }
257        Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => f(e),
258        Expr::Field { owner, .. } => {
259            if let Some(o) = owner {
260                f(o);
261            }
262        }
263        Expr::Method { owner, args, .. } => {
264            if let Some(o) = owner {
265                f(o);
266            }
267            for a in args.iter_mut() {
268                f(a);
269            }
270        }
271        Expr::ArrayIndex { array, index } => {
272            f(array);
273            f(index);
274        }
275        Expr::New { args, .. } => {
276            for a in args.iter_mut() {
277                f(a);
278            }
279        }
280        Expr::NewArray { dims, init, .. } => {
281            for d in dims.iter_mut() {
282                f(d);
283            }
284            if let Some(v) = init {
285                for x in v.iter_mut() {
286                    f(x);
287                }
288            }
289        }
290        Expr::NewMultiArray { dims, .. } => {
291            for d in dims.iter_mut() {
292                f(d);
293            }
294        }
295        Expr::StringConcat(parts) => {
296            for p in parts {
297                if let ConcatPart::Str(x) = p {
298                    f(x);
299                }
300            }
301        }
302        Expr::Invokedynamic { args, .. } => {
303            for a in args.iter_mut() {
304                f(a);
305            }
306        }
307        Expr::AnonNew { args, .. } => {
308            for a in args.iter_mut() {
309                f(a);
310            }
311        }
312        _ => {}
313    }
314}
315
316/// Deep mutable expression rewrite.
317fn deep_rewrite<F: FnMut(&mut Expr)>(e: &mut Expr, f: &mut F) {
318    f(e);
319    for_each_child_mut(e, &mut |c| deep_rewrite(c, f));
320}
321
322fn collect_vars(e: &Expr, out: &mut HashSet<u32>) {
323    visit_exprs(e, &mut |x| {
324        if let Expr::Local { var, .. } = x {
325            out.insert(*var);
326        }
327    });
328}
329
330/// Collect variable references (reads; `assignments` adds assignment
331/// targets). Single-pass recursion — the previous shape wrapped a
332/// self-recursive closure in `walk_all`, visiting every subtree twice over
333/// (exponential in nesting depth).
334fn stmt_collect_vars(s: &Stmt, out: &mut HashSet<u32>, assignments: bool) {
335    match s {
336        Stmt::Block(v) => {
337            for x in v {
338                stmt_collect_vars(x, out, assignments);
339            }
340        }
341        Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
342            if let Expr::Local { var, .. } = &**target {
343                if assignments {
344                    out.insert(*var);
345                }
346            } else {
347                collect_vars(target, out);
348            }
349            collect_vars(value, out);
350        }
351        Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
352            collect_vars(e, out)
353        }
354        Stmt::Return(Some(e)) => collect_vars(e, out),
355        Stmt::LocalDef { var, init, .. } => {
356            if assignments {
357                out.insert(*var);
358            }
359            if let Some(e) = init {
360                collect_vars(e, out);
361            }
362        }
363        Stmt::If {
364            cond,
365            then_stmt,
366            else_stmt,
367        } => {
368            collect_vars(cond, out);
369            stmt_collect_vars(then_stmt, out, assignments);
370            if let Some(e) = else_stmt {
371                stmt_collect_vars(e, out, assignments);
372            }
373        }
374        Stmt::While { cond, body } => {
375            collect_vars(cond, out);
376            stmt_collect_vars(body, out, assignments);
377        }
378        Stmt::DoWhile { body, cond } => {
379            stmt_collect_vars(body, out, assignments);
380            collect_vars(cond, out);
381        }
382        Stmt::For {
383            init,
384            cond,
385            update,
386            body,
387        } => {
388            for x in init {
389                stmt_collect_vars(x, out, assignments);
390            }
391            if let Some(c) = cond {
392                collect_vars(c, out);
393            }
394            for u in update {
395                collect_vars(u, out);
396            }
397            stmt_collect_vars(body, out, assignments);
398        }
399        Stmt::ForEach {
400            var,
401            iterable,
402            body,
403            ..
404        } => {
405            if assignments {
406                out.insert(*var);
407            }
408            collect_vars(iterable, out);
409            stmt_collect_vars(body, out, assignments);
410        }
411        Stmt::Switch {
412            selector,
413            cases,
414            default,
415            ..
416        } => {
417            collect_vars(selector, out);
418            for c in cases {
419                for x in &c.body {
420                    stmt_collect_vars(x, out, assignments);
421                }
422            }
423            if let Some(d) = default {
424                stmt_collect_vars(d, out, assignments);
425            }
426        }
427        Stmt::Try {
428            body,
429            catches,
430            finally,
431        } => {
432            stmt_collect_vars(body, out, assignments);
433            for c in catches {
434                stmt_collect_vars(&c.body, out, assignments);
435            }
436            if let Some(f) = finally {
437                stmt_collect_vars(f, out, assignments);
438            }
439        }
440        Stmt::TryWithResources {
441            resources,
442            body,
443            catches,
444            finally,
445        } => {
446            for x in resources {
447                stmt_collect_vars(x, out, assignments);
448            }
449            stmt_collect_vars(body, out, assignments);
450            for c in catches {
451                stmt_collect_vars(&c.body, out, assignments);
452            }
453            if let Some(f) = finally {
454                stmt_collect_vars(f, out, assignments);
455            }
456        }
457        Stmt::Synchronized { lock, body } => {
458            collect_vars(lock, out);
459            stmt_collect_vars(body, out, assignments);
460        }
461        Stmt::Assert { cond, msg } => {
462            collect_vars(cond, out);
463            if let Some(m) = msg {
464                collect_vars(m, out);
465            }
466        }
467        Stmt::Labeled { body, .. } => stmt_collect_vars(body, out, assignments),
468        _ => {}
469    }
470}
471
472fn walk_all<F: FnMut(&Stmt)>(s: &Stmt, f: &mut F) {
473    f(s);
474    match s {
475        Stmt::Block(v) => {
476            for x in v {
477                walk_all(x, f);
478            }
479        }
480        Stmt::If {
481            then_stmt,
482            else_stmt,
483            ..
484        } => {
485            walk_all(then_stmt, f);
486            if let Some(e) = else_stmt {
487                walk_all(e, f);
488            }
489        }
490        Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => walk_all(body, f),
491        Stmt::For { init, body, .. } => {
492            for x in init {
493                walk_all(x, f);
494            }
495            walk_all(body, f);
496        }
497        Stmt::ForEach { body, .. } => walk_all(body, f),
498        Stmt::Switch { cases, default, .. } => {
499            for c in cases {
500                for x in &c.body {
501                    walk_all(x, f);
502                }
503            }
504            if let Some(d) = default {
505                walk_all(d, f);
506            }
507        }
508        Stmt::Try {
509            body,
510            catches,
511            finally,
512        } => {
513            walk_all(body, f);
514            for c in catches {
515                walk_all(&c.body, f);
516            }
517            if let Some(fl) = finally {
518                walk_all(fl, f);
519            }
520        }
521        Stmt::TryWithResources {
522            resources,
523            body,
524            catches,
525            finally,
526        } => {
527            for x in resources {
528                walk_all(x, f);
529            }
530            walk_all(body, f);
531            for c in catches {
532                walk_all(&c.body, f);
533            }
534            if let Some(fl) = finally {
535                walk_all(fl, f);
536            }
537        }
538        Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => walk_all(body, f),
539        _ => {}
540    }
541}
542
543// ---------------------------------------------------------------------------
544// Passes
545// ---------------------------------------------------------------------------
546
547/// Bind catch parameters: the handler's first statement (a bare LocalDef
548/// from move-exception) becomes the catch variable.
549pub fn bind_catches(s: &mut Stmt, vt: &mut VarTable) {
550    match s {
551        Stmt::Try {
552            body,
553            catches,
554            finally,
555        } => {
556            bind_catches(body, vt);
557            for c in catches.iter_mut() {
558                if c.var == u32::MAX {
559                    let stored = match c.body.as_ref() {
560                        Stmt::Block(v) => match v.first() {
561                            Some(Stmt::LocalDef { var, .. }) => Some(*var),
562                            Some(Stmt::ExprStmt(Expr::Assign { target, .. })) => match &**target {
563                                Expr::Local { var, .. } => Some(*var),
564                                _ => None,
565                            },
566                            _ => None,
567                        },
568                        _ => None,
569                    };
570                    if let Some(v) = stored {
571                        let exc = c
572                            .exc
573                            .first()
574                            .cloned()
575                            .unwrap_or_else(|| "java/lang/Throwable".into());
576                        let slot = vt.var(v).slot;
577                        let name = "e".to_string();
578                        let new_var =
579                            vt.add_catch_var(slot, name, TypeRef::J(JavaType::Object(exc)));
580                        if let Stmt::Block(vs) = c.body.as_mut() {
581                            vs.remove(0);
582                        }
583                        rewrite_local_refs(c.body.as_mut(), v, new_var);
584                        c.var = new_var;
585                    }
586                }
587                bind_catches(&mut c.body, vt);
588            }
589            if let Some(f) = finally {
590                bind_catches(f.as_mut(), vt);
591            }
592        }
593        Stmt::Block(v) => {
594            for x in v.iter_mut() {
595                bind_catches(x, vt);
596            }
597        }
598        Stmt::If {
599            then_stmt,
600            else_stmt,
601            ..
602        } => {
603            bind_catches(then_stmt, vt);
604            if let Some(e) = else_stmt {
605                bind_catches(e, vt);
606            }
607        }
608        Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => bind_catches(body, vt),
609        Stmt::For { init, body, .. } => {
610            for x in init.iter_mut() {
611                bind_catches(x, vt);
612            }
613            bind_catches(body, vt);
614        }
615        Stmt::ForEach { body, .. }
616        | Stmt::Labeled { body, .. }
617        | Stmt::Synchronized { body, .. } => bind_catches(body, vt),
618        Stmt::Switch { cases, default, .. } => {
619            for c in cases.iter_mut() {
620                for x in c.body.iter_mut() {
621                    bind_catches(x, vt);
622                }
623            }
624            if let Some(d) = default {
625                bind_catches(d, vt);
626            }
627        }
628        _ => {}
629    }
630}
631
632fn rewrite_local_refs(s: &mut Stmt, from: u32, to: u32) {
633    rewrite_exprs(s, &mut |e| {
634        deep_rewrite(e, &mut |x| {
635            if let Expr::Local { var, .. } = x {
636                if *var == from {
637                    *var = to;
638                }
639            }
640        });
641    });
642}
643
644/// Flatten blocks, drop empty ones and trailing no-op statements.
645pub fn cleanup(s: &mut Stmt) {
646    jdc_core::ir::stmt::flatten(s);
647    strip_empty(s);
648    merge_singleton_stmts(s);
649}
650
651fn strip_empty(s: &mut Stmt) {
652    match s {
653        Stmt::Block(v) => {
654            v.retain(|x| !x.is_empty_block());
655            for x in v.iter_mut() {
656                strip_empty(x);
657            }
658        }
659        Stmt::If {
660            then_stmt,
661            else_stmt,
662            ..
663        } => {
664            strip_empty(then_stmt);
665            if let Some(e) = else_stmt {
666                strip_empty(e);
667                if e.is_empty_block() {
668                    *else_stmt = None;
669                }
670            }
671        }
672        Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => strip_empty(body),
673        Stmt::For { init, body, .. } => {
674            init.retain(|x| !x.is_empty_block());
675            strip_empty(body);
676        }
677        Stmt::ForEach { body, .. } => strip_empty(body),
678        Stmt::Switch { cases, default, .. } => {
679            for c in cases {
680                c.body.retain(|x| !x.is_empty_block());
681            }
682            if let Some(d) = default {
683                strip_empty(d);
684            }
685        }
686        Stmt::Try {
687            body,
688            catches,
689            finally,
690        } => {
691            strip_empty(body);
692            for c in catches {
693                strip_empty(&mut c.body);
694            }
695            if let Some(f) = finally {
696                strip_empty(f);
697            }
698        }
699        Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => strip_empty(body),
700        _ => {}
701    }
702}
703
704fn merge_singleton_stmts(s: &mut Stmt) {
705    if let Stmt::Block(v) = s {
706        for x in v.iter_mut() {
707            merge_singleton_stmts(x);
708        }
709    }
710}
711
712/// Drop statements after a definite terminator within a block.
713pub fn prune_unreachable(s: &mut Stmt) {
714    if let Stmt::Block(v) = s {
715        let mut cut: Option<usize> = None;
716        for (i, x) in v.iter().enumerate() {
717            if matches!(x, Stmt::Return(_) | Stmt::Throw(_)) {
718                cut = Some(i + 1);
719                break;
720            }
721        }
722        if let Some(c) = cut {
723            if c < v.len() {
724                v.truncate(c);
725            }
726        }
727        for x in v.iter_mut() {
728            prune_unreachable(x);
729        }
730    } else {
731        walk_mut(s, &mut prune_unreachable);
732    }
733}
734
735fn walk_mut<F: FnMut(&mut Stmt)>(s: &mut Stmt, f: &mut F) {
736    match s {
737        Stmt::Block(v) => {
738            for x in v.iter_mut() {
739                f(x);
740            }
741        }
742        Stmt::If {
743            then_stmt,
744            else_stmt,
745            ..
746        } => {
747            f(then_stmt);
748            if let Some(e) = else_stmt {
749                f(e);
750            }
751        }
752        Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => f(body),
753        Stmt::For { init, body, .. } => {
754            for x in init.iter_mut() {
755                f(x);
756            }
757            f(body);
758        }
759        Stmt::ForEach { body, .. } => f(body),
760        Stmt::Switch { cases, default, .. } => {
761            for c in cases {
762                for x in c.body.iter_mut() {
763                    f(x);
764                }
765            }
766            if let Some(d) = default {
767                f(d);
768            }
769        }
770        Stmt::Try {
771            body,
772            catches,
773            finally,
774        } => {
775            f(body);
776            for c in catches {
777                f(&mut c.body);
778            }
779            if let Some(fl) = finally {
780                f(fl);
781            }
782        }
783        Stmt::Synchronized { body, .. } | Stmt::Labeled { body, .. } => f(body),
784        _ => {}
785    }
786}
787
788/// Count LocalDef statements (pipeline diagnostics).
789pub fn count_localdefs(s: &Stmt, out: &mut usize) {
790    if matches!(s, Stmt::LocalDef { .. }) {
791        *out += 1;
792    }
793    walk_all(s, &mut |x| {
794        if matches!(x, Stmt::LocalDef { .. }) {
795            *out += 1;
796        }
797    });
798}
799
800/// Drop a trailing `return;` (d8's explicit return-void at method end).
801pub fn strip_trailing_void_return(s: &mut Stmt) {
802    if let Stmt::Block(v) = s {
803        while let Some(Stmt::Return(None)) = v.last() {
804            v.pop();
805        }
806    }
807}
808
809pub fn prepend_comment(s: &mut Stmt, text: String) {
810    let stmt = Stmt::Comment(text);
811    match s {
812        Stmt::Block(v) => v.insert(0, stmt),
813        other => {
814            let inner = std::mem::replace(other, Stmt::Block(vec![]));
815            *other = Stmt::Block(vec![stmt, inner]);
816        }
817    }
818}
819
820/// Fused expression rewrites that each used to walk the whole tree:
821/// residual cmp sentinels, object-null comparisons, const-first compares.
822/// One traversal, three rules — the separate walks were a top profile cost.
823pub fn fused_expr_rewrites(s: &mut Stmt, vt: &VarTable) {
824    // var ids are dense: a byte table beats a HashSet lookup.
825    let mut obj_vars: Vec<bool> = Vec::with_capacity(vt.vars.len());
826    let mut any_obj = false;
827    for v in &vt.vars {
828        let is_obj = v.ty.erased().is_reference();
829        if is_obj {
830            any_obj = true;
831        }
832        obj_vars.push(is_obj);
833    }
834    rewrite_exprs(s, &mut |e| {
835        deep_rewrite(e, &mut |x| {
836            // 1. cmp sentinels.
837            if let Expr::Invokedynamic { name, args, .. } = x {
838                if name.starts_with('\0') && args.len() == 2 {
839                    let (cls, ty) = match name.as_str() {
840                        "\0cmp-long" => ("java/lang/Long", JavaType::Long),
841                        "\0cmpl-float" | "\0cmpg-float" => ("java/lang/Float", JavaType::Float),
842                        _ => ("java/lang/Double", JavaType::Double),
843                    };
844                    let desc = MethodDescriptor {
845                        args: vec![ty.clone(), ty],
846                        ret: JavaType::Int,
847                    };
848                    *x = Expr::Method {
849                        owner: None,
850                        cls: cls.into(),
851                        name: "compare".into(),
852                        desc,
853                        args: args.clone(),
854                        is_static: true,
855                        is_interface: false,
856                        is_special: false,
857                        is_super: false,
858                        is_dynamic: false,
859                        type_args: vec![],
860                    };
861                    return;
862                }
863            }
864            // 2. null compares.
865            if any_obj {
866                if let Expr::Bin { op, l, r, .. } = x {
867                    if matches!(op, BinOp::Eq | BinOp::Ne) {
868                        let rewrite = |side: &mut Expr, other: &Expr| {
869                            if let Expr::Const(ConstVal::Int(0)) = other {
870                                if let Expr::Local { var, .. } = &*side {
871                                    if obj_vars.get(*var as usize).copied().unwrap_or(false) {
872                                        *side = Expr::Const(ConstVal::Null);
873                                    }
874                                }
875                            }
876                        };
877                        let lo = l.clone();
878                        let ro = r.clone();
879                        rewrite(l, &ro);
880                        rewrite(r, &lo);
881                    }
882                }
883            }
884            // 3. const-first compares.
885            if let Expr::Bin { op, l, r, .. } = x {
886                if matches!(
887                    op,
888                    BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
889                ) {
890                    let l_const = matches!(&**l, Expr::Const(_));
891                    let r_var = matches!(&**r, Expr::Local { .. });
892                    if l_const && r_var {
893                        std::mem::swap(l, r);
894                    }
895                }
896            }
897        });
898    });
899}
900
901/// Replace residual cmp sentinels (`\0cmp*` Invokedynamic markers that never
902/// reached a condition) with library compare calls.
903#[allow(dead_code)]
904pub fn desugar_cmp_residuals(s: &mut Stmt) {
905    rewrite_exprs(s, &mut |e| {
906        deep_rewrite(e, &mut |x| {
907            if let Expr::Invokedynamic { name, args, .. } = x {
908                if name.starts_with('\0') && args.len() == 2 {
909                    let (cls, ty) = match name.as_str() {
910                        "\0cmp-long" => ("java/lang/Long", JavaType::Long),
911                        "\0cmpl-float" | "\0cmpg-float" => ("java/lang/Float", JavaType::Float),
912                        _ => ("java/lang/Double", JavaType::Double),
913                    };
914                    let desc = jdc_core::types::MethodDescriptor {
915                        args: vec![ty.clone(), ty],
916                        ret: JavaType::Int,
917                    };
918                    *x = Expr::Method {
919                        owner: None,
920                        cls: cls.into(),
921                        name: "compare".into(),
922                        desc,
923                        args: args.clone(),
924                        is_static: true,
925                        is_interface: false,
926                        is_special: false,
927                        is_super: false,
928                        is_dynamic: false,
929                        type_args: vec![],
930                    };
931                }
932            }
933        });
934    });
935}
936
937/// `if (c) { x = A; } else { x = B; }` → `x = c ? A : B;`
938pub fn ternary_fold(s: &mut Stmt) {
939    let mut changed = true;
940    let mut guard = 0;
941    while changed && guard < 8 {
942        changed = false;
943        guard += 1;
944        ternary_fold_walk(s, &mut changed);
945        strip_empty(s);
946    }
947}
948
949fn ternary_fold_walk(s: &mut Stmt, changed: &mut bool) {
950    if let Stmt::Block(v) = s {
951        for x in v.iter_mut() {
952            ternary_fold_walk(x, changed);
953        }
954        return;
955    }
956    let Stmt::If {
957        cond,
958        then_stmt,
959        else_stmt,
960    } = s
961    else {
962        return;
963    };
964    ternary_fold_walk(then_stmt, changed);
965    if let Some(e) = else_stmt {
966        ternary_fold_walk(e, changed);
967    }
968    let Some(else_b) = else_stmt else { return };
969    let target_of = |st: &Stmt| -> Option<u32> {
970        let Stmt::ExprStmt(Expr::Assign { target, .. }) = st else {
971            return None;
972        };
973        if let Expr::Local { var, .. } = &**target {
974            Some(*var)
975        } else {
976            None
977        }
978    };
979    let (Stmt::Block(tv), Stmt::Block(ev)) = (then_stmt.as_mut(), else_b.as_mut()) else {
980        return;
981    };
982    if tv.len() != 1 || ev.len() != 1 {
983        return;
984    }
985    let (Some(vt_), Some(ve)) = (target_of(&tv[0]), target_of(&ev[0])) else {
986        return;
987    };
988    if vt_ != ve {
989        return;
990    }
991    // The condition and both values must not re-assign the target between
992    // the fold — they are single statements, so a mention is a re-read at
993    // worst; re-assignment only happens via assignments inside them.
994    let mut touched = HashSet::new();
995    let (Stmt::ExprStmt(a_then), Stmt::ExprStmt(a_else)) = (&tv[0], &ev[0]) else {
996        return;
997    };
998    if let Expr::Assign { value, .. } = a_then {
999        collect_vars(value, &mut touched);
1000    }
1001    if let Expr::Assign { value, .. } = a_else {
1002        collect_vars(value, &mut touched);
1003    }
1004    if touched.contains(&vt_) {
1005        return;
1006    }
1007    let value_then = match a_then {
1008        Expr::Assign { value, .. } => value.clone(),
1009        _ => return,
1010    };
1011    let value_else = match a_else {
1012        Expr::Assign { value, .. } => value.clone(),
1013        _ => return,
1014    };
1015    let cond_e = cond.clone();
1016    let folded = Stmt::ExprStmt(Expr::Assign {
1017        target: Box::new(Expr::Local {
1018            var: vt_,
1019            ty: vt_join_ty(&value_then, &value_else),
1020        }),
1021        op: AssignOp::Plain,
1022        value: Box::new(Expr::Cond {
1023            c: Box::new(cond_e),
1024            t: value_then,
1025            f: value_else,
1026        }),
1027    });
1028    *s = Stmt::Block(vec![folded]);
1029    *changed = true;
1030}
1031
1032fn vt_join_ty(a: &Expr, b: &Expr) -> TypeRef {
1033    let ta = a.type_ref();
1034    let tb = b.type_ref();
1035    if ta.erased() == tb.erased() {
1036        return ta;
1037    }
1038    TypeRef::J(JavaType::Object("java/lang/Object".into()))
1039}
1040
1041/// Fold `new StringBuilder(...).append(x)...toString()` chains into
1042/// `StringConcat`. Single-assignment temporaries carry the chain.
1043///
1044/// d8 drops append results (statement-form calls), so a builder whose chain
1045/// has STATEMENT appends attached is left alone: folding its `toString`
1046/// with only the ctor arguments would misstate the contents.
1047pub fn fold_string_builders(s: &mut Stmt, vt: &VarTable) {
1048    // var → assigned value, for vars assigned exactly once.
1049    let n_vars = vt.vars.len().max(1);
1050    let mut counts = vec![0usize; n_vars];
1051    count_assignments(s, &mut counts);
1052    let mut values: Vec<Option<Expr>> = vec![None; n_vars];
1053    record_assignments(s, &counts, &mut values);
1054
1055    // Builders with statement-form appends (their chains are incomplete).
1056    let mut appended_stmts: HashSet<u32> = HashSet::new();
1057    walk_all(s, &mut |st| {
1058        if let Stmt::ExprStmt(Expr::Method {
1059            cls,
1060            name,
1061            owner,
1062            args,
1063            ..
1064        }) = st
1065        {
1066            if name == "append" && is_string_builder(cls) && args.len() == 1 {
1067                if let Some(Expr::Local { var, .. }) = owner.as_deref() {
1068                    appended_stmts.insert(*var);
1069                }
1070            }
1071        }
1072    });
1073
1074    rewrite_exprs(s, &mut |e| {
1075        fold_concat_in_expr(e, &values, &appended_stmts);
1076    });
1077
1078    // Drop now-unused builder temporaries (their only consumers were
1079    // folded); the drop COUNT drives iteration — no statement counting
1080    // walks at all.
1081    for _ in 0..8 {
1082        let mut reads: HashSet<u32> = HashSet::new();
1083        stmt_collect_vars(s, &mut reads, false);
1084        if drop_unused_assigns(s, &reads) == 0 {
1085            break;
1086        }
1087    }
1088}
1089
1090/// Deep statement count (pipeline guard input).
1091pub fn count_stmts_deep(s: &Stmt, out: &mut usize) {
1092    *out += 1;
1093    walk_all(s, &mut |_| {
1094        *out += 1;
1095    });
1096}
1097
1098/// Fused per-variable analysis gathered in one statement-tree walk.
1099struct VarAnalysis {
1100    assigns: Vec<usize>,
1101    reads: Vec<usize>,
1102    /// Value of the (single) assignment, when recorded.
1103    values: Vec<Option<Expr>>,
1104}
1105
1106fn analyze_vars(s: &Stmt, a: &mut VarAnalysis) {
1107    walk_all(s, &mut |st| {
1108        let exprs: Vec<&Expr> = match st {
1109            Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
1110                if let Expr::Local { var, .. } = &**target {
1111                    grow_to(&mut a.assigns, *var);
1112                    let idx = *var as usize;
1113                    a.assigns[idx] += 1;
1114                    if a.assigns[idx] == 1 {
1115                        if idx >= a.values.len() {
1116                            a.values.resize(idx + 1, None);
1117                        }
1118                        a.values[idx] = Some((**value).clone());
1119                    } else if idx < a.values.len() {
1120                        a.values[idx] = None;
1121                    }
1122                }
1123                let mut v: Vec<&Expr> = vec![value];
1124                if !matches!(&**target, Expr::Local { .. }) {
1125                    v.push(target);
1126                }
1127                v
1128            }
1129            Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
1130                vec![e]
1131            }
1132            Stmt::Return(Some(e)) => vec![e],
1133            Stmt::LocalDef {
1134                var, init: Some(e), ..
1135            } => {
1136                grow_to(&mut a.assigns, *var);
1137                let idx = *var as usize;
1138                a.assigns[idx] += 1;
1139                if a.assigns[idx] == 1 {
1140                    if idx >= a.values.len() {
1141                        a.values.resize(idx + 1, None);
1142                    }
1143                    a.values[idx] = Some(e.clone());
1144                } else if idx < a.values.len() {
1145                    a.values[idx] = None;
1146                }
1147                vec![e]
1148            }
1149            Stmt::If { cond, .. } => vec![cond],
1150            Stmt::While { cond, .. } => vec![cond],
1151            Stmt::DoWhile { cond, .. } => vec![cond],
1152            _ => Vec::new(),
1153        };
1154        for e in exprs {
1155            visit_exprs(e, &mut |x| {
1156                if let Expr::Local { var, .. } = x {
1157                    grow_to(&mut a.reads, *var);
1158                    a.reads[*var as usize] += 1;
1159                }
1160            });
1161        }
1162    });
1163    // Keep the three views length-aligned.
1164    let n = a.assigns.len().max(a.reads.len()).max(a.values.len());
1165    a.assigns.resize(n, 0);
1166    a.reads.resize(n, 0);
1167    a.values.resize(n, None);
1168}
1169
1170fn grow_to(v: &mut Vec<usize>, var: u32) {
1171    if var as usize >= v.len() {
1172        v.resize(var as usize + 1, 0);
1173    }
1174}
1175
1176fn count_assignments(s: &Stmt, counts: &mut Vec<usize>) {
1177    walk_all(s, &mut |st| match st {
1178        Stmt::ExprStmt(Expr::Assign { target, .. }) => {
1179            if let Expr::Local { var, .. } = &**target {
1180                grow_to(counts, *var);
1181                counts[*var as usize] += 1;
1182            }
1183        }
1184        Stmt::LocalDef {
1185            var, init: Some(_), ..
1186        } => {
1187            grow_to(counts, *var);
1188            counts[*var as usize] += 1;
1189        }
1190        _ => {}
1191    });
1192}
1193
1194fn record_assignments(s: &Stmt, counts: &[usize], out: &mut Vec<Option<Expr>>) {
1195    walk_all(s, &mut |st| match st {
1196        Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
1197            if let Expr::Local { var, .. } = &**target {
1198                if counts.get(*var as usize).copied().unwrap_or(0) == 1 {
1199                    if *var as usize >= out.len() {
1200                        out.resize(*var as usize + 1, None);
1201                    }
1202                    out[*var as usize] = Some((**value).clone());
1203                }
1204            }
1205        }
1206        Stmt::LocalDef {
1207            var, init: Some(e), ..
1208        } if counts.get(*var as usize).copied().unwrap_or(0) == 1 => {
1209            if *var as usize >= out.len() {
1210                out.resize(*var as usize + 1, None);
1211            }
1212            out[*var as usize] = Some(e.clone());
1213        }
1214        _ => {}
1215    });
1216}
1217
1218fn fold_concat_in_expr(e: &mut Expr, values: &[Option<Expr>], appended: &HashSet<u32>) {
1219    deep_rewrite(e, &mut |x| {
1220        if let Expr::Method {
1221            cls,
1222            name,
1223            args,
1224            owner,
1225            ..
1226        } = x
1227        {
1228            if name == "toString" && args.is_empty() && is_string_builder(cls) {
1229                if let Some(o) = owner {
1230                    // Statement-form appends attached to any chain var mean
1231                    // the parts are incomplete — keep the call.
1232                    let mut has_stmt_appends = false;
1233                    visit_exprs(o, &mut |y| {
1234                        if let Expr::Local { var, .. } = y {
1235                            if appended.contains(var) {
1236                                has_stmt_appends = true;
1237                            }
1238                        }
1239                    });
1240                    if has_stmt_appends {
1241                        return;
1242                    }
1243                    let resolved = resolve_local(o, values);
1244                    if let Some(parts) = collect_sb_parts(&resolved, values, 0) {
1245                        *x = Expr::StringConcat(parts);
1246                    }
1247                }
1248            }
1249        }
1250    });
1251}
1252
1253fn is_string_builder(cls: &str) -> bool {
1254    cls == "java/lang/StringBuilder" || cls == "java/lang/StringBuffer"
1255}
1256
1257fn resolve_local(e: &Expr, values: &[Option<Expr>]) -> Expr {
1258    match e {
1259        Expr::Local { var, ty } => values
1260            .get(*var as usize)
1261            .and_then(|o| o.clone())
1262            .unwrap_or_else(|| Expr::Local {
1263                var: *var,
1264                ty: ty.clone(),
1265            }),
1266        other => other.clone(),
1267    }
1268}
1269
1270/// Collect concat parts from a StringBuilder chain; depth caps cycles.
1271fn collect_sb_parts(e: &Expr, values: &[Option<Expr>], depth: u32) -> Option<Vec<ConcatPart>> {
1272    if depth > 16 {
1273        return None;
1274    }
1275    match e {
1276        Expr::Method {
1277            cls,
1278            name,
1279            args,
1280            owner,
1281            ..
1282        } if name == "append" && is_string_builder(cls) && args.len() == 1 => {
1283            let mut parts =
1284                collect_sb_parts(&resolve_local(owner.as_deref()?, values), values, depth + 1)?;
1285            parts.push(ConcatPart::Str(args[0].clone()));
1286            Some(parts)
1287        }
1288        Expr::New {
1289            cls,
1290            args,
1291            raw: false,
1292            ..
1293        } if is_string_builder(cls) => {
1294            let mut parts = Vec::new();
1295            for a in args {
1296                if let Expr::Const(ConstVal::Str(sv)) = a {
1297                    parts.push(ConcatPart::Const(sv.clone()));
1298                } else {
1299                    parts.push(ConcatPart::Str(a.clone()));
1300                }
1301            }
1302            Some(parts)
1303        }
1304        _ => None,
1305    }
1306}
1307
1308fn drop_unused_assigns(s: &mut Stmt, reads: &HashSet<u32>) -> usize {
1309    let mut dropped = 0usize;
1310    walk_mut_deep(s, &mut |st| {
1311        if let Stmt::Block(v) = st {
1312            let before = v.len();
1313            v.retain(|x| match x {
1314                // Drop only when unread AND a builder chain (folded
1315                // consumer; `new StringBuilder` chains cannot NPE).
1316                Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &**target {
1317                    Expr::Local { var, .. } => reads.contains(var) || !is_sbish(value),
1318                    _ => true,
1319                },
1320                Stmt::LocalDef { var, init, .. } => {
1321                    reads.contains(var) || !init.as_ref().map(is_sbish).unwrap_or(false)
1322                }
1323                _ => true,
1324            });
1325            dropped += before - v.len();
1326        }
1327    });
1328    dropped
1329}
1330
1331fn is_sbish(e: &Expr) -> bool {
1332    match e {
1333        Expr::New {
1334            cls, raw: false, ..
1335        } => is_string_builder(cls),
1336        Expr::Method { name, cls, .. } => name == "append" && is_string_builder(cls),
1337        _ => false,
1338    }
1339}
1340
1341fn walk_mut_deep<F: FnMut(&mut Stmt)>(s: &mut Stmt, f: &mut F) {
1342    f(s);
1343    walk_mut(s, &mut |x| walk_mut_deep(x, f));
1344}
1345
1346/// Recover `synchronized` from the d8 monitor pattern:
1347/// `monitorenter(e); try { body } catch (Throwable) { monitorexit(e); throw t; }`
1348/// (optionally followed by `monitorexit(e)`).
1349pub fn fold_synchronized(s: &mut Stmt) {
1350    fold_sync_walk(s);
1351}
1352
1353fn fold_sync_walk(s: &mut Stmt) {
1354    walk_mut_deep(s, &mut |st| {
1355        let Stmt::Block(v) = st else { return };
1356        let mut i = 0;
1357        while i < v.len() {
1358            if i + 1 < v.len() {
1359                if let Some(sync) = try_sync_at(v, i) {
1360                    let _ = v.drain(i..i + 2);
1361                    v.insert(i, sync);
1362                    continue;
1363                }
1364            }
1365            i += 1;
1366        }
1367    });
1368}
1369
1370fn try_sync_at(v: &[Stmt], i: usize) -> Option<Stmt> {
1371    let Stmt::MonitorEnter(lock) = &v[i] else {
1372        return None;
1373    };
1374    let Stmt::Try {
1375        body,
1376        catches,
1377        finally,
1378    } = &v[i + 1]
1379    else {
1380        return None;
1381    };
1382    if finally.is_some() || catches.len() != 1 {
1383        return None;
1384    }
1385    let c = &catches[0];
1386    if !c.exc.is_empty() {
1387        return None;
1388    }
1389    // The catch-all body: monitorexit(lock) then rethrow.
1390    let Stmt::Block(cv) = c.body.as_ref() else {
1391        return None;
1392    };
1393    let exit_matches = cv.len() >= 2
1394        && matches!(&cv[0], Stmt::MonitorExit(e) if expr_local_var(e) == expr_local_var(lock));
1395    let throws = cv.len() >= 2 && matches!(&cv[1], Stmt::Throw(_));
1396    if !(exit_matches && throws) {
1397        return None;
1398    }
1399    Some(Stmt::Synchronized {
1400        lock: lock.clone(),
1401        body: body.clone(),
1402    })
1403}
1404
1405fn expr_local_var(e: &Expr) -> Option<u32> {
1406    if let Expr::Local { var, .. } = e {
1407        Some(*var)
1408    } else {
1409        None
1410    }
1411}
1412
1413/// Copy-forward single-use temporaries: `v = e; ... v ...` inlines `e` at
1414/// the (single) use and drops the assignment when safe. Pure values inline
1415/// anywhere; impure values (calls, array reads) only when the use is the
1416/// statement immediately after the definition.
1417pub fn forward_single_use(s: &mut Stmt, _vt: &VarTable) {
1418    // Fused single traversal: assignment counts, read counts and the
1419    // single-assignment values all come out of ONE walk (three separate
1420    // full-tree walks dominated the pass cost on large methods).
1421    let mut analysis = VarAnalysis {
1422        assigns: Vec::new(),
1423        reads: Vec::new(),
1424        values: Vec::new(),
1425    };
1426    analyze_vars(s, &mut analysis);
1427    let mut assigns = analysis.assigns;
1428    let mut reads = analysis.reads;
1429    // Var ids beyond the table (synthetic test shapes) are covered by the
1430    // counters' on-demand growth.
1431    let n_vars = assigns.len().max(_vt.vars.len()).max(1);
1432    assigns.resize(n_vars, 0);
1433    reads.resize(n_vars, 0);
1434    let mut values = analysis.values;
1435    values.resize(n_vars, None);
1436
1437    // Candidates: assigned exactly once, read exactly once. Additionally
1438    // the inlined VALUE must not reference a multi-assigned var (phi vars):
1439    // moving the read across the phi's reassignment would be a stale
1440    // capture (register rotations snapshot through temps for this reason).
1441    let mut cand = vec![false; n_vars];
1442    let mut edges: Vec<Vec<u32>> = vec![Vec::new(); n_vars];
1443    for (v, cand_v) in cand.iter_mut().enumerate() {
1444        if assigns.get(v).copied().unwrap_or(0) != 1 || reads.get(v).copied().unwrap_or(0) != 1 {
1445            continue;
1446        }
1447        if let Some(val) = values.get(v).and_then(|o| o.as_ref()) {
1448            let mut refs = HashSet::new();
1449            collect_vars(val, &mut refs);
1450            if refs.iter().any(|r| assigns[*r as usize] > 1) {
1451                continue;
1452            }
1453            // Growth-graph edge set, built in the same walk: inlining v
1454            // inserts a clone of its value, and every Local inside the
1455            // clone is replaced in turn.
1456            edges[v] = refs.into_iter().collect();
1457        }
1458        *cand_v = true;
1459    }
1460
1461    // Cycle rejection on the growth graph. A def-reference cycle
1462    // (v = f(w), w = g(v) — loop-carried register rotation reaching the
1463    // pass as two mutually-referencing single-assign/single-read locals)
1464    // re-introduces the other cycle Local at every replacement level, so
1465    // deep_rewrite grows the tree one layer per level and never
1466    // terminates: weixin's com/tencent/mm/plugin/appbrand/widget/input/b4
1467    // exhausted a 64MB worker stack at ~300k recursion frames. Edges into
1468    // non-candidates are harmless (they are never replaced, so growth
1469    // dies there — and they carry no outgoing edges).
1470    //
1471    // Three-color DFS, iterative: a legit 60k-long single-use chain must
1472    // not trade one stack overflow for another. A GRAY child closes a
1473    // cycle; `bad[v]` (v on a cycle, or v's inlined closure grows into
1474    // one) then taints the whole current path — everything on it reaches
1475    // the cycle. Black verdicts memoize: a node whose subtree was proven
1476    // clean cannot grow a cycle later.
1477    let mut color = vec![0u8; n_vars]; // 0 white, 1 gray, 2 black
1478    let mut bad = vec![false; n_vars];
1479    for root in 0..n_vars {
1480        if color[root] != 0 || edges[root].is_empty() {
1481            continue;
1482        }
1483        color[root] = 1;
1484        let mut stack: Vec<(usize, usize)> = vec![(root, 0)];
1485        while let Some(top) = stack.last_mut() {
1486            let v = top.0;
1487            let i = top.1;
1488            top.1 += 1;
1489            if i < edges[v].len() {
1490                let r = edges[v][i] as usize;
1491                if color[r] == 1 {
1492                    for &(pn, _) in &stack {
1493                        bad[pn] = true;
1494                    }
1495                } else if color[r] == 0 {
1496                    color[r] = 1;
1497                    stack.push((r, 0));
1498                } else if bad[r] {
1499                    for &(pn, _) in &stack {
1500                        bad[pn] = true;
1501                    }
1502                }
1503            } else {
1504                color[v] = 2;
1505                stack.pop();
1506            }
1507        }
1508    }
1509    let single: Vec<bool> = (0..n_vars).map(|v| cand[v] && !bad[v]).collect();
1510    if !single.iter().any(|&b| b) {
1511        return;
1512    }
1513    let pure: Vec<bool> = (0..n_vars)
1514        .map(|v| {
1515            single[v]
1516                && values
1517                    .get(v)
1518                    .and_then(|o| o.as_ref())
1519                    .map(|e| !has_side_effects(e))
1520                    .unwrap_or(false)
1521        })
1522        .collect();
1523    let impure: Vec<bool> = (0..n_vars).map(|v| single[v] && !pure[v]).collect();
1524
1525    // 1. Pure values: inline anywhere.
1526    if pure.iter().any(|&b| b) {
1527        let vals: Vec<Option<Expr>> = (0..n_vars)
1528            .map(|v| {
1529                if pure[v] {
1530                    values.get(v).cloned().flatten()
1531                } else {
1532                    None
1533                }
1534            })
1535            .collect();
1536        rewrite_exprs(s, &mut |e| {
1537            deep_rewrite(e, &mut |x| {
1538                if let Expr::Local { var, .. } = x {
1539                    if let Some(Some(v)) = vals.get(*var as usize) {
1540                        *x = v.clone();
1541                    }
1542                }
1543            });
1544        });
1545        drop_defs(s, &pure);
1546    }
1547
1548    // 2. Impure values: inline only into the immediately following
1549    //    statement.
1550    if impure.iter().any(|&b| b) {
1551        forward_adjacent_impure(s, &values, &impure);
1552    }
1553}
1554
1555/// Count READS of variables (assignment targets excluded).
1556fn count_reads(s: &Stmt, out: &mut Vec<usize>) {
1557    walk_all(s, &mut |st| {
1558        let exprs: Vec<&Expr> = match st {
1559            // Assign must match BEFORE the generic ExprStmt arm (the
1560            // assignment target is a write, not a read).
1561            Stmt::ExprStmt(Expr::Assign { target, value, .. }) => {
1562                let mut v: Vec<&Expr> = vec![value];
1563                if !matches!(&**target, Expr::Local { .. }) {
1564                    v.push(target);
1565                }
1566                v
1567            }
1568            Stmt::ExprStmt(e) | Stmt::Throw(e) | Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
1569                vec![e]
1570            }
1571            Stmt::Return(Some(e)) => vec![e],
1572            Stmt::LocalDef { init: Some(e), .. } => vec![e],
1573            Stmt::If { cond, .. } => vec![cond],
1574            Stmt::While { cond, .. } => vec![cond],
1575            Stmt::DoWhile { cond, .. } => vec![cond],
1576            _ => Vec::new(),
1577        };
1578        for e in exprs {
1579            visit_exprs(e, &mut |x| {
1580                if let Expr::Local { var, .. } = x {
1581                    grow_to(out, *var);
1582                    out[*var as usize] += 1;
1583                }
1584            });
1585        }
1586    });
1587}
1588
1589fn drop_defs(s: &mut Stmt, vars: &[bool]) {
1590    walk_mut_deep(s, &mut |st| {
1591        if let Stmt::Block(v) = st {
1592            v.retain(|x| match x {
1593                Stmt::ExprStmt(Expr::Assign { target, .. }) => match &**target {
1594                    Expr::Local { var, .. } => !vars.get(*var as usize).copied().unwrap_or(false),
1595                    _ => true,
1596                },
1597                Stmt::LocalDef { var, .. } => !vars.get(*var as usize).copied().unwrap_or(false),
1598                _ => true,
1599            });
1600        }
1601    });
1602}
1603
1604/// For each statement list: `v = IMPURE; NEXT(v)` folds NEXT's reference.
1605fn forward_adjacent_impure(s: &mut Stmt, values: &[Option<Expr>], impure: &[bool]) {
1606    if let Stmt::Block(v) = s {
1607        let mut i = 0;
1608        while i + 1 < v.len() {
1609            let def_var = match &v[i] {
1610                Stmt::ExprStmt(Expr::Assign { target, .. }) => match &**target {
1611                    Expr::Local { var, .. } => Some(*var),
1612                    _ => None,
1613                },
1614                Stmt::LocalDef { var, .. } => Some(*var),
1615                _ => None,
1616            };
1617            if let Some(var) = def_var {
1618                if impure.get(var as usize).copied().unwrap_or(false) {
1619                    // Does the NEXT statement reference var?
1620                    let mut reads_here = vec![0usize; values.len().max(1)];
1621                    count_reads(&v[i + 1], &mut reads_here);
1622                    if reads_here.get(var as usize).copied().unwrap_or(0) == 1 {
1623                        // The def statement's CURRENT value (earlier inlines
1624                        // in this walk may have rewritten it — the pre-built
1625                        // values map would be stale).
1626                        let val = match &v[i] {
1627                            Stmt::ExprStmt(Expr::Assign { value, .. }) => (**value).clone(),
1628                            Stmt::LocalDef { init: Some(e), .. } => (*e).clone(),
1629                            _ => {
1630                                i += 1;
1631                                continue;
1632                            }
1633                        };
1634                        rewrite_exprs(&mut v[i + 1], &mut |e| {
1635                            deep_rewrite(e, &mut |x| {
1636                                if let Expr::Local { var: v2, .. } = x {
1637                                    if *v2 == var {
1638                                        *x = val.clone();
1639                                    }
1640                                }
1641                            });
1642                        });
1643                        v.remove(i);
1644                        // The PREVIOUS definition may now be adjacent to its
1645                        // (shifted) use — re-examine it.
1646                        i = i.saturating_sub(1);
1647                        continue;
1648                    }
1649                }
1650            }
1651            i += 1;
1652        }
1653    }
1654    walk_mut(s, &mut |x| forward_adjacent_impure(x, values, impure));
1655}
1656
1657// ---------------------------------------------------------------------------
1658// Type inference, booleanization, null comparisons, declarations
1659// ---------------------------------------------------------------------------
1660
1661/// Evidence-based type inference for synthetic (non-parameter) vars.
1662pub fn infer_types(vt: &mut VarTable, body: &mut Stmt, ret: &JavaType, env: &MethodEnv) {
1663    let n_vars = vt.vars.len();
1664    let mut evidence: Vec<Vec<JavaType>> = vec![Vec::new(); n_vars];
1665    let ev = |evidence: &mut Vec<Vec<JavaType>>, var: u32, t: JavaType| {
1666        if (var as usize) < evidence.len() {
1667            evidence[var as usize].push(t);
1668        }
1669    };
1670
1671    walk_all(body, &mut |st| match st {
1672        Stmt::LocalDef { var, init, .. } => {
1673            if let Some(e) = init {
1674                expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t));
1675                if let Expr::Local { var: src, .. } = e {
1676                    let _ = src;
1677                }
1678                if let Expr::Local { var: src, .. } = e {
1679                    if evidence.get(*var as usize).is_some() {
1680                        let _ = src;
1681                    }
1682                }
1683            }
1684        }
1685        Stmt::ExprStmt(e) => expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t)),
1686        Stmt::Throw(e) => {
1687            expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t));
1688            if let Expr::Local { var, .. } = e {
1689                ev(
1690                    &mut evidence,
1691                    *var,
1692                    JavaType::Object("java/lang/Throwable".into()),
1693                );
1694            }
1695        }
1696        Stmt::Return(Some(e)) => {
1697            expr_evidence(e, &mut |v, t| ev(&mut evidence, v, t));
1698            if let Expr::Local { var, .. } = e {
1699                ev(&mut evidence, *var, ret.clone());
1700            }
1701        }
1702        Stmt::MonitorEnter(e) | Stmt::MonitorExit(e) => {
1703            if let Expr::Local { var, .. } = e {
1704                ev(
1705                    &mut evidence,
1706                    *var,
1707                    JavaType::Object("java/lang/Object".into()),
1708                );
1709            }
1710        }
1711        Stmt::ForEach {
1712            var,
1713            iterable,
1714            is_array,
1715            ..
1716        } => {
1717            if let Expr::Local { var: v0, .. } = iterable {
1718                if *is_array {
1719                    ev(&mut evidence, *v0, JavaType::Array(Box::new(JavaType::Int)));
1720                } else {
1721                    ev(
1722                        &mut evidence,
1723                        *v0,
1724                        JavaType::Object("java/lang/Object".into()),
1725                    );
1726                }
1727            }
1728            let _ = var;
1729        }
1730        _ => {}
1731    });
1732    let _ = env;
1733
1734    // Return-position locals take the declared return type (also inside
1735    // nested returns — handled by the walk above).
1736
1737    for (i, evs) in evidence.iter_mut().enumerate() {
1738        let info = &vt.vars[i];
1739        if info.is_param {
1740            continue;
1741        }
1742        if info.ty.erased().is_reference() {
1743            // Only refine reference-typed synthetics with object evidence.
1744            if let Some(t) = pick_object(evs) {
1745                vt.vars[i].ty = TypeRef::J(t);
1746            }
1747        } else if evs.iter().all(|t| t.is_numeric()) && !evs.is_empty() {
1748            if let Some(t) = pick_numeric(evs) {
1749                if !info.ty.erased().is_reference() {
1750                    vt.vars[i].ty = TypeRef::J(t);
1751                }
1752            }
1753        } else if let Some(t) = pick_object(evs) {
1754            vt.vars[i].ty = TypeRef::J(t);
1755        }
1756    }
1757
1758    // Rewrite embedded Local types.
1759    let types: Vec<TypeRef> = vt.vars.iter().map(|v| v.ty.clone()).collect();
1760    rewrite_exprs(body, &mut |e| {
1761        deep_rewrite(e, &mut |x| {
1762            if let Expr::Local { var, ty } = x {
1763                if (*var as usize) < types.len() {
1764                    *ty = types[*var as usize].clone();
1765                }
1766            }
1767        });
1768    });
1769
1770    // Numeric literals take their variable's declared width (`double v = 0L`
1771    // prints as `0.0`; `int x = 5L` as `5`).
1772    coerce_num_consts(body, &types);
1773}
1774
1775fn coerce_num_consts(body: &mut Stmt, types: &[TypeRef]) {
1776    walk_mut_deep(body, &mut |st| {
1777        let (var, val): (u32, &mut Expr) = match st {
1778            Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &mut **target {
1779                Expr::Local { var, .. } => (*var, value),
1780                _ => return,
1781            },
1782            Stmt::LocalDef {
1783                var, init: Some(e), ..
1784            } => (*var, e),
1785            _ => return,
1786        };
1787        let want = match types.get(var as usize) {
1788            Some(TypeRef::J(t)) => t.clone(),
1789            _ => return,
1790        };
1791        let rewritten = match &*val {
1792            Expr::Const(ConstVal::Long(l)) => match &want {
1793                JavaType::Double => Some(Expr::Const(ConstVal::Double(*l as f64))),
1794                JavaType::Float => Some(Expr::Const(ConstVal::Float(*l as f32))),
1795                _ => None,
1796            },
1797            Expr::Const(ConstVal::Int(i)) => match &want {
1798                JavaType::Double => Some(Expr::Const(ConstVal::Double(*i as f64))),
1799                JavaType::Float => Some(Expr::Const(ConstVal::Float(*i as f32))),
1800                _ => None,
1801            },
1802            _ => None,
1803        };
1804        if let Some(e) = rewritten {
1805            *val = e;
1806        }
1807    });
1808}
1809
1810fn pick_object(evs: &[JavaType]) -> Option<JavaType> {
1811    evs.iter()
1812        .find(|t| t.is_reference() && !matches!(t, JavaType::Object(n) if n == "java/lang/Object"))
1813        .cloned()
1814}
1815
1816fn pick_numeric(evs: &[JavaType]) -> Option<JavaType> {
1817    if evs.is_empty() {
1818        return None;
1819    }
1820    let mut t = evs[0].clone();
1821    for x in &evs[1..] {
1822        t = join_numeric(&t, x);
1823    }
1824    Some(t)
1825}
1826
1827fn join_numeric(a: &JavaType, b: &JavaType) -> JavaType {
1828    if a == b {
1829        return a.clone();
1830    }
1831    match (a, b) {
1832        (JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
1833        (JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
1834        (JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
1835        (JavaType::Int, x) if x.is_integral() => JavaType::Int,
1836        (x, JavaType::Int) if x.is_integral() => JavaType::Int,
1837        (x, y) if x.is_integral() && y.is_integral() => JavaType::Int,
1838        _ => JavaType::Int,
1839    }
1840}
1841
1842/// Gather per-var type evidence from one expression.
1843fn expr_evidence<F: FnMut(u32, JavaType)>(e: &Expr, f: &mut F) {
1844    visit_exprs(e, &mut |x| match x {
1845        Expr::Method {
1846            owner,
1847            args,
1848            desc,
1849            is_static,
1850            cls,
1851            ..
1852        } => {
1853            if !*is_static {
1854                if let Some(o) = owner {
1855                    if let Expr::Local { var, .. } = &**o {
1856                        f(*var, JavaType::Object(cls.clone()));
1857                    }
1858                }
1859            }
1860            for (i, a) in args.iter().enumerate() {
1861                if let Expr::Local { var, .. } = a {
1862                    if let Some(t) = desc.args.get(i) {
1863                        f(*var, t.clone());
1864                    }
1865                }
1866            }
1867        }
1868        Expr::Field {
1869            owner,
1870            ty,
1871            is_static,
1872            ..
1873        } => {
1874            if !*is_static {
1875                if let Some(o) = owner {
1876                    if let Expr::Local { var, .. } = &**o {
1877                        f(*var, JavaType::Object("java/lang/Object".into()));
1878                    }
1879                }
1880            }
1881            let _ = ty;
1882        }
1883        Expr::ArrayIndex { array, .. } => {
1884            if let Expr::Local { var, ty, .. } = &**array {
1885                // Reinforce the local's KNOWN array type: the hardcoded
1886                // int[] fallback contradicted a typed array local (a
1887                // register reused for byte[], int[] then byte[] across
1888                // one clinit labeled the byte[] defs `int[]`).
1889                let t = ty.erased();
1890                if matches!(t, JavaType::Array(_)) {
1891                    f(*var, t);
1892                } else {
1893                    f(*var, JavaType::Array(Box::new(JavaType::Int)));
1894                }
1895            }
1896        }
1897        Expr::Assign { target, value, .. } => {
1898            if let Expr::Local { var, .. } = &**target {
1899                f(*var, value.type_ref().erased());
1900            }
1901        }
1902        Expr::Cast { ty, e: inner } => {
1903            if let Expr::Local { var, .. } = &**inner {
1904                f(*var, ty.erased());
1905            }
1906        }
1907        Expr::InstanceOf { e: inner, .. } => {
1908            let _ = inner;
1909        }
1910        _ => {}
1911    });
1912}
1913
1914/// Boolean inference: vars only ever assigned 0/1/comparisons/booleans and
1915/// read in conditions become `boolean`, with `v != 0` → `v` in conditions.
1916pub fn booleanize(vt: &mut VarTable, body: &mut Stmt) {
1917    let n = vt.vars.len();
1918    let mut in_cond = vec![false; n];
1919
1920    // Condition uses: `v == 0` / `v != 0` if-conditions and comparison
1921    // operands inside assigned values.
1922    walk_all(body, &mut |st| {
1923        let cond: Option<&Expr> = match st {
1924            Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => {
1925                Some(cond)
1926            }
1927            _ => None,
1928        };
1929        if let Some(c) = cond {
1930            if let Expr::Bin {
1931                op: BinOp::Eq | BinOp::Ne,
1932                l,
1933                r,
1934                ..
1935            } = c
1936            {
1937                for (x, other) in [(l, r), (r, l)] {
1938                    if let (Expr::Local { var, .. }, Expr::Const(ConstVal::Int(0))) =
1939                        (&**x, &**other)
1940                    {
1941                        if (*var as usize) < n {
1942                            in_cond[*var as usize] = true;
1943                        }
1944                    }
1945                }
1946            }
1947        }
1948        let val: Option<&Expr> = match st {
1949            Stmt::ExprStmt(Expr::Assign { value, .. }) => Some(value),
1950            Stmt::LocalDef { init: Some(e), .. } => Some(e),
1951            Stmt::Return(Some(e)) => Some(e),
1952            _ => None,
1953        };
1954        if let Some(v) = val {
1955            visit_exprs(v, &mut |x| {
1956                if let Expr::Bin { op, l, r, .. } = x {
1957                    if matches!(
1958                        op,
1959                        BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
1960                    ) {
1961                        for side in [l, r] {
1962                            if let Expr::Local { var, .. } = &**side {
1963                                if (*var as usize) < n {
1964                                    in_cond[*var as usize] = true;
1965                                }
1966                            }
1967                        }
1968                    }
1969                }
1970            });
1971        }
1972    });
1973
1974    // A var whose every assignment value is boolean-shaped AND which is
1975    // referenced in conditions becomes boolean.
1976    // (Perf: this used to re-walk the whole statement tree ONCE PER VAR —
1977    // O(vars × stmts); on R8-merged monsters (2000+ vars, 15k statements)
1978    // that was tens of millions of visits. One pass collects the same
1979    // (any, all_bool) facts for every var.)
1980    let mut assigned_any = vec![false; n];
1981    let mut all_bool = vec![true; n];
1982    walk_all(body, &mut |st| {
1983        let (var, value) = match st {
1984            Stmt::ExprStmt(Expr::Assign { target, value, .. }) => match &**target {
1985                Expr::Local { var, .. } => (var, &**value),
1986                _ => return,
1987            },
1988            Stmt::LocalDef {
1989                var, init: Some(e), ..
1990            } => (var, e),
1991            _ => return,
1992        };
1993        let i = *var as usize;
1994        if i < n {
1995            assigned_any[i] = true;
1996            if !is_boolean_valued(value) {
1997                all_bool[i] = false;
1998            }
1999        }
2000    });
2001    let mut boolean_vars: HashSet<u32> = HashSet::new();
2002    for i in 0..n {
2003        if vt.vars[i].is_param {
2004            continue;
2005        }
2006        if !in_cond[i] {
2007            continue;
2008        }
2009        if assigned_any[i] && all_bool[i] {
2010            boolean_vars.insert(i as u32);
2011        }
2012    }
2013    if boolean_vars.is_empty() {
2014        return;
2015    }
2016    for v in &boolean_vars {
2017        vt.vars[*v as usize].ty = TypeRef::J(JavaType::Boolean);
2018    }
2019    let types: Vec<TypeRef> = vt.vars.iter().map(|v| v.ty.clone()).collect();
2020    rewrite_exprs(body, &mut |e| {
2021        deep_rewrite(e, &mut |x| {
2022            if let Expr::Local { var, ty } = x {
2023                if (*var as usize) < types.len() {
2024                    *ty = types[*var as usize].clone();
2025                }
2026            }
2027        });
2028    });
2029
2030    // Condition folding: `b != 0` → `b`, `b == 0` → `!b` (boolean vars).
2031    fold_bool_conditions(body, &boolean_vars);
2032}
2033
2034fn is_boolean_valued(e: &Expr) -> bool {
2035    match e {
2036        Expr::Const(ConstVal::Int(0)) | Expr::Const(ConstVal::Int(1)) => true,
2037        Expr::Bin { op, .. } => matches!(
2038            op,
2039            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
2040        ),
2041        Expr::InstanceOf { .. } => true,
2042        Expr::Local { ty, .. } => ty.erased() == JavaType::Boolean,
2043        Expr::Method { desc, .. } => desc.ret == JavaType::Boolean,
2044        Expr::Cond { t, f, .. } => is_boolean_valued(t) && is_boolean_valued(f),
2045        Expr::Un { op: UnOp::Not, .. } => true,
2046        _ => false,
2047    }
2048}
2049
2050fn fold_bool_conditions(s: &mut Stmt, bv: &HashSet<u32>) {
2051    let fold = |e: &mut Expr| {
2052        deep_rewrite(e, &mut |x| {
2053            if let Expr::Bin { op, l, r, .. } = x {
2054                let (var_side, const_side) = match (&**l, &**r) {
2055                    (Expr::Local { .. }, Expr::Const(ConstVal::Int(_))) => (l, 0),
2056                    (Expr::Const(ConstVal::Int(_)), Expr::Local { .. }) => (r, 1),
2057                    _ => return,
2058                };
2059                let var = match &**var_side {
2060                    Expr::Local { var, .. } => *var,
2061                    _ => return,
2062                };
2063                if !bv.contains(&var) {
2064                    return;
2065                }
2066                let _ = const_side;
2067                match op {
2068                    BinOp::Ne => {
2069                        let inner = var_side.clone();
2070                        *x = *inner;
2071                    }
2072                    BinOp::Eq => {
2073                        let inner = var_side.clone();
2074                        *x = Expr::Un {
2075                            op: UnOp::Not,
2076                            e: Box::new(*inner),
2077                        };
2078                    }
2079                    _ => {}
2080                }
2081            }
2082        });
2083    };
2084    match s {
2085        Stmt::If { cond, .. } | Stmt::While { cond, .. } | Stmt::DoWhile { cond, .. } => fold(cond),
2086        _ => {}
2087    }
2088    walk_mut_deep(s, &mut |st| {
2089        let cond = match st {
2090            Stmt::If { cond, .. } => Some(cond),
2091            Stmt::While { cond, .. } => Some(cond),
2092            Stmt::DoWhile { cond, .. } => Some(cond),
2093            _ => None,
2094        };
2095        if let Some(c) = cond {
2096            fold(c);
2097        }
2098    });
2099}
2100
2101/// Normalize `1 == v` comparisons to `v == 1` (d8 emits const-first if-eq).
2102pub fn flip_const_compares(s: &mut Stmt) {
2103    rewrite_exprs(s, &mut |e| {
2104        deep_rewrite(e, &mut |x| {
2105            if let Expr::Bin { op, l, r, .. } = x {
2106                if matches!(
2107                    op,
2108                    BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Ge | BinOp::Gt | BinOp::Le
2109                ) {
2110                    let l_const = matches!(&**l, Expr::Const(_));
2111                    let r_var = matches!(&**r, Expr::Local { .. });
2112                    if l_const && r_var {
2113                        std::mem::swap(l, r);
2114                    }
2115                }
2116            }
2117        });
2118    });
2119}
2120
2121/// Object-typed locals compared against 0 compare against `null`.
2122pub fn null_compares(vt: &VarTable, body: &mut Stmt) {
2123    let obj_vars: HashSet<u32> = vt
2124        .vars
2125        .iter()
2126        .filter(|v| v.ty.erased().is_reference())
2127        .map(|v| v.id)
2128        .collect();
2129    if obj_vars.is_empty() {
2130        return;
2131    }
2132    rewrite_exprs(body, &mut |e| {
2133        deep_rewrite(e, &mut |x| {
2134            if let Expr::Bin { op, l, r, .. } = x {
2135                if !matches!(op, BinOp::Eq | BinOp::Ne) {
2136                    return;
2137                }
2138                let rewrite = |side: &mut Expr, other: &Expr| {
2139                    if let Expr::Const(ConstVal::Int(0)) = other {
2140                        if let Expr::Local { var, .. } = &*side {
2141                            if obj_vars.contains(var) {
2142                                *side = Expr::Const(ConstVal::Null);
2143                            }
2144                        }
2145                    }
2146                };
2147                let lo = l.clone();
2148                let ro = r.clone();
2149                rewrite(l, &ro);
2150                rewrite(r, &lo);
2151            }
2152        });
2153    });
2154}
2155
2156/// Declaration hygiene: every used var that has no LocalDef gets a bare
2157/// declaration at the top; duplicate LocalDefs demote to assignments.
2158pub fn ensure_declared(body: &mut Stmt, vt: &VarTable) {
2159    // (Perf: dense Vec<bool> tables instead of HashSets — var ids are
2160    // dense; the `assigned` set collected here was never read and cost a
2161    // full extra tree walk per method.)
2162    let n = vt.vars.len().max(1);
2163    let mut declared: Vec<bool> = vec![false; n];
2164    let mut is_param: Vec<bool> = vec![false; n];
2165    for v in &vt.vars {
2166        if v.is_param && (v.id as usize) < n {
2167            is_param[v.id as usize] = true;
2168        }
2169    }
2170    walk_all(body, &mut |st| {
2171        if let Stmt::LocalDef { var, .. } = st {
2172            if (*var as usize) < n {
2173                declared[*var as usize] = true;
2174            }
2175        }
2176    });
2177    let mut used: HashSet<u32> = HashSet::new();
2178    stmt_collect_vars(body, &mut used, false);
2179
2180    // Params are declared by the signature.
2181    let mut needs_decl: Vec<u32> = used
2182        .into_iter()
2183        .filter(|v| {
2184            let i = *v as usize;
2185            i >= n || (!declared[i] && !is_param[i])
2186        })
2187        .collect();
2188    needs_decl.sort_unstable();
2189
2190    // Demote extra LocalDefs (same var declared more than once). A fresh
2191    // set: the pre-collected `declared` would demote every def.
2192    let mut seen: HashSet<u32> = HashSet::new();
2193    demote_dup_decls(body, &mut seen, vt);
2194
2195    if !needs_decl.is_empty() {
2196        let mut decls: Vec<Stmt> = needs_decl
2197            .into_iter()
2198            .map(|v| Stmt::LocalDef {
2199                var: v,
2200                init: None,
2201                is_final: false,
2202                force_type: true,
2203            })
2204            .collect();
2205        decls.reverse();
2206        match body {
2207            Stmt::Block(v) => {
2208                for d in decls {
2209                    v.insert(0, d);
2210                }
2211            }
2212            other => {
2213                let inner = std::mem::replace(other, Stmt::Block(vec![]));
2214                *other = Stmt::Block({
2215                    let mut vs = decls;
2216                    vs.push(inner);
2217                    vs
2218                });
2219            }
2220        }
2221    }
2222}
2223
2224fn demote_dup_decls(s: &mut Stmt, seen: &mut HashSet<u32>, vt: &VarTable) {
2225    match s {
2226        // Exactly one visit per node: the Block arm consumes its children,
2227        // the fallthrough walk would double-visit them.
2228        Stmt::Block(v) => {
2229            for x in v.iter_mut() {
2230                demote_dup_decls(x, seen, vt);
2231            }
2232        }
2233        Stmt::LocalDef { var, init, .. } => {
2234            if seen.contains(var) {
2235                let ty = vt.var(*var).ty.clone();
2236                if let Some(e) = init.take() {
2237                    *s = Stmt::ExprStmt(Expr::Assign {
2238                        target: Box::new(Expr::Local { var: *var, ty }),
2239                        op: AssignOp::Plain,
2240                        value: Box::new(e),
2241                    });
2242                } else {
2243                    *s = Stmt::Block(vec![]);
2244                }
2245            } else {
2246                seen.insert(*var);
2247            }
2248        }
2249        other => {
2250            walk_mut(other, &mut |x| demote_dup_decls(x, seen, vt));
2251        }
2252    }
2253}
2254
2255#[allow(dead_code)]
2256fn unused(_: &Vec<CaseGroup>, _: &Catch) {}