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