1use std::collections::HashMap;
6
7use ddc_dex::insn::InsnKind;
8use jdc_core::convert::Converter;
9use jdc_core::ir::build::BlockResult;
10use jdc_core::ir::expr::{Expr, TypeRef};
11use jdc_core::ir::stmt::Stmt;
12use jdc_core::structure::{reverse_postorder, Structurer};
13use jdc_core::types::{JavaType, MethodDescriptor};
14use jdc_core::var::VarTable;
15
16use crate::cfg::DexCfg;
17use crate::lift::{entry_regs, Lifter, MethodEnv, OutState, Reg};
18use crate::passes;
19use crate::{DexPool, PoolClass, PoolMethod};
20
21pub struct MethodBody {
22 pub body: Stmt,
23 pub vt: VarTable,
24 pub desc: MethodDescriptor,
25}
26
27fn handler_types_for(cfg: &DexCfg, bid: usize) -> Vec<Option<String>> {
29 cfg.blocks[bid]
30 .handlers
31 .iter()
32 .filter_map(|&ri| cfg.exc_ranges.get(ri).map(|r| r.catch_type.clone()))
33 .collect()
34}
35
36pub fn decompile_method(
39 pool: &DexPool,
40 class: &PoolClass,
41 m: &PoolMethod,
42) -> Result<Option<MethodBody>, String> {
43 let t0 = std::time::Instant::now();
44 if m.is_abstract_or_native() {
45 return Ok(None);
46 }
47 let desc = m
48 .parsed_desc()
49 .ok_or_else(|| format!("bad descriptor {}", m.desc))?;
50 let dex = pool.dex(m.dex_idx).ok_or("missing dex image")?;
51 let dex = &*dex;
52 let Some(mut code) = dex.code_at(m.code_off) else {
53 return Ok(None);
54 };
55 let insn_count = code.insns.len();
56 let param_names = dex.parameter_names(m.debug_info_off);
57 let cfg = DexCfg::build(&mut code, &|ty| dex.class_name(ty));
58 let n = cfg.blocks.len();
59 let env = MethodEnv {
60 pool,
61 dex,
62 code: &code,
63 class_name: class.name.clone(),
64 method_name: m.name.clone(),
65 desc: desc.clone(),
66 is_static: m.is_static(),
67 code_units: cfg.code_units,
68 };
69 let mut vt = VarTable::default();
70 let entry = entry_regs(&mut vt, &env, ¶m_names);
71 if n == 0 {
72 return Ok(None);
73 }
74 if insn_count > 30_000 || n > 8_000 {
77 return Ok(Some(stub_body(
78 format!(
79 "$DDC: method too large to decompile ({} insns, {} blocks)",
80 insn_count, n
81 ),
82 desc,
83 )));
84 }
85
86 let core_for_order = cfg.to_core();
89 let universe: std::collections::HashSet<usize> = (0..n).collect();
90 let mut order = reverse_postorder(&core_for_order, core_for_order.entry, &universe);
91 {
92 let mut seen: std::collections::HashSet<usize> = order.iter().copied().collect();
93 for b in &cfg.blocks {
94 if seen.insert(b.id) {
95 order.push(b.id);
96 }
97 }
98 }
99
100 let mut handler_entry_block: HashMap<usize, usize> = HashMap::new();
103 for b in &cfg.blocks {
104 if b.handlers.is_empty() {
105 continue;
106 }
107 let mut best: Option<(u32, usize)> = None;
108 for &ri in &b.handlers {
109 if let Some(r) = cfg.exc_ranges.get(ri) {
110 if best.map(|(s, _)| r.start < s).unwrap_or(true) {
111 if let Some(eb) = cfg.block_at(r.start) {
112 best = Some((r.start, eb));
113 }
114 }
115 }
116 }
117 if let Some((_, eb)) = best {
118 handler_entry_block.insert(b.id, eb);
119 }
120 }
121
122 let mut results: Vec<BlockResult> = Vec::with_capacity(n);
124 results.resize_with(n, || BlockResult {
125 stmts: vec![],
126 out_stack: vec![],
127 term: jdc_core::ir::build::Term::Return(None),
128 });
129 let mut out_states: Vec<Option<OutState>> = vec![None; n];
130 let mut stable_vars: HashMap<(usize, u16), u32> = HashMap::new();
134 let mut mflags = crate::lift::MethodFlags::none();
138 let mut try_entry_snapshots: HashMap<usize, Vec<Reg>> = HashMap::new();
142 let mut built_once = vec![false; n];
143 let mut errors: HashMap<usize, String> = HashMap::new();
144 let mut phis: HashMap<(usize, u16), u32> = HashMap::new();
146 let entry_is_handler = handler_entry_block.contains_key(&0);
149
150 let try_entry_flags: Vec<bool> = (0..n)
155 .map(|b| {
156 !cfg.blocks[b].handlers.is_empty()
157 || cfg
158 .exc_ranges
159 .iter()
160 .any(|r| cfg.block_at(r.start) == Some(b))
161 })
162 .collect();
163 let handler_types: Vec<Vec<Option<String>>> =
164 (0..n).map(|b| handler_types_for(&cfg, b)).collect();
165
166 let mut ver: Vec<u64> = vec![0; n]; let mut built_ver: Vec<u64> = vec![u64::MAX; n]; fn input_sig(cfg: &DexCfg, bid: usize, ver: &[u64]) -> u64 {
175 let mut h: u64 = 1;
176 for &p in &cfg.blocks[bid].pred {
177 h = h
178 .wrapping_mul(31)
179 .wrapping_add(p as u64)
180 .wrapping_mul(31)
181 .wrapping_add(ver[p]);
182 }
183 h
185 }
186 let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
187 let mut queued: Vec<bool> = vec![false; n];
188 for &bid in &order {
189 queue.push_back(bid);
190 queued[bid] = true;
191 }
192 let mut appends: HashMap<usize, Vec<(usize, u32, u32, Expr)>> = HashMap::new();
195 let mut total_visits: usize = 0;
196 let visit_cap: usize = 64 * n + 256;
197
198 while let Some(bid) = queue.pop_front() {
199 queued[bid] = false;
200 total_visits += 1;
201 if total_visits > visit_cap {
202 CAP_HITS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
203 CAP_INSNS.fetch_add(
204 insn_count as u64 * 1000 + n as u64,
205 std::sync::atomic::Ordering::Relaxed,
206 );
207 break; }
209 let is_handler = handler_entry_block.contains_key(&bid);
210 let sig = input_sig(&cfg, bid, &ver)
214 ^ (if is_handler {
215 let eb = handler_entry_block[&bid];
216 (eb as u64) << 32
217 } else {
218 0
219 });
220 let need_build = !built_once[bid] || built_ver[bid] != sig;
221 if !need_build {
222 continue;
226 }
227 let ins: Vec<Reg> = if bid == cfg.entry {
229 if cfg.blocks[0].pred.is_empty() || entry_is_handler {
230 entry.clone()
231 } else {
232 let mut sides: Vec<&[Reg]> = vec![entry.as_slice()];
236 for p in cfg.blocks[bid].pred.clone() {
237 if let Some(o) = &out_states[p] {
238 sides.push(o.regs.as_slice());
239 }
240 }
241 let merged = merge_states(&mut vt, &mut phis, bid, &sides);
242 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
247 for (r, st) in entry.iter().enumerate() {
248 if let Some(&phi) = phis.get(&(bid, r as u16)) {
249 let value = match st {
250 Reg::Live(v) if *v == phi => continue,
251 Reg::Live(v) => Expr::Local {
252 var: *v,
253 ty: vt.var(*v).ty.clone(),
254 },
255 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
256 Reg::Undef | Reg::WideHi => continue,
257 };
258 recs.push((usize::MAX, 0, phi, value));
259 }
260 }
261 appends.insert(bid, recs);
262 merged
263 }
264 } else if is_handler {
265 let eb = handler_entry_block[&bid];
266 try_entry_snapshots
267 .get(&eb)
268 .cloned()
269 .unwrap_or_else(|| vec![Reg::Undef; env.code.registers_size as usize])
270 } else {
271 let mut sides: Vec<&[Reg]> = Vec::new();
274 for p in cfg.blocks[bid].pred.clone() {
275 if let Some(o) = &out_states[p] {
276 sides.push(o.regs.as_slice());
277 }
278 }
279 if sides.is_empty() {
280 vec![Reg::Undef; env.code.registers_size as usize]
281 } else if sides.len() == 1 {
282 sides[0].to_vec()
283 } else {
284 merge_states(&mut vt, &mut phis, bid, &sides)
285 }
286 };
287
288 if phis_for_merge(bid, &phis) {
292 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
293 record_appends(&cfg, bid, &out_states, &phis, &vt, &mut recs);
294 if bid == cfg.entry {
297 if let Some(prev) = appends.get(&bid) {
298 for (p, pc, v, e) in prev.iter() {
299 if *p == usize::MAX {
300 recs.push((*p, *pc, *v, e.clone()));
301 }
302 }
303 }
304 }
305 appends.insert(bid, recs);
306 }
307
308 built_once[bid] = true;
312 built_ver[bid] = sig;
313 if try_entry_flags[bid] {
314 try_entry_snapshots.insert(bid, ins.clone());
315 }
316 BUILD_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
317 INSN_LIFTED.fetch_add(
318 cfg.block_ins(&cfg.blocks[bid]).len() as u64,
319 std::sync::atomic::Ordering::Relaxed,
320 );
321 let htypes = &handler_types[bid];
322 let block_ins = cfg.block_ins(&cfg.blocks[bid]);
323 let lifter = Lifter::new(&env, &mut vt, ins, bid, &mut stable_vars, &mut mflags);
324 let rebuilt = match lifter.build_block(block_ins, htypes) {
325 Ok((r, out)) => {
326 results[bid] = r;
327 let changed = match &out_states[bid] {
328 Some(prev) => prev.regs != out.regs,
329 None => true,
330 };
331 out_states[bid] = Some(out);
332 changed
333 }
334 Err(msg) => {
335 results[bid] = BlockResult {
336 stmts: vec![Stmt::Comment(format!("$DDC-BLOCK-ERROR: {msg}"))],
337 out_stack: vec![],
338 term: jdc_core::ir::build::Term::Return(None),
339 };
340 out_states[bid] = Some(OutState {
341 regs: vec![Reg::Undef; env.code.registers_size as usize],
342 write_pc: vec![0; env.code.registers_size as usize],
343 });
344 errors.insert(bid, msg);
345 false
346 }
347 };
348 if rebuilt {
349 ver[bid] += 1;
350 for &succ in &cfg.blocks[bid].succ {
352 if !queued[succ] && succ < n {
353 queue.push_back(succ);
354 queued[succ] = true;
355 }
356 }
357 if is_handler {
361 let eb = handler_entry_block[&bid];
362 if !queued[eb] && eb < n {
363 queue.push_back(eb);
364 queued[eb] = true;
365 }
366 }
367 for &ri in &cfg.blocks[bid].handlers {
370 if let Some(r) = cfg.exc_ranges.get(ri) {
371 if let Some(hb) = cfg.block_at(r.handler) {
372 if !queued[hb] && hb < n {
373 queue.push_back(hb);
374 queued[hb] = true;
375 }
376 }
377 }
378 }
379 }
380 }
381
382 let mut entry_phi_inits: Vec<(u32, Expr)> = Vec::new();
391 if let Some(recs) = appends.get_mut(&cfg.entry) {
392 let mut entry_side: Vec<(u32, Expr)> = Vec::new();
393 recs.retain(|(p, _, v, e)| {
394 if *p == usize::MAX {
395 entry_side.push((*v, e.clone()));
396 false
397 } else {
398 true
399 }
400 });
401 entry_side.sort_by_key(|(v, _)| *v);
402 entry_phi_inits = entry_side;
403 }
404
405 let mut per_pred: HashMap<usize, Vec<(u32, u32, Expr)>> = HashMap::new();
412 for recs in appends.values() {
413 for (p, pc, v, e) in recs {
414 per_pred.entry(*p).or_default().push((*pc, *v, e.clone()));
415 }
416 }
417 for (p, mut adds) in per_pred {
418 if p == usize::MAX {
419 continue;
420 }
421 adds.sort_by_key(|(pc, _, _)| *pc);
422 let term_is_exit = matches!(
423 results[p].term,
424 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
425 );
426 if term_is_exit {
427 continue;
428 }
429 let phi_set: std::collections::HashSet<u32> = adds.iter().map(|(_, v, _)| *v).collect();
430 let mut snapshots: Vec<Stmt> = Vec::new();
431 let mut emitted: Vec<(u32, Expr)> = Vec::new();
433 for (_, v, e) in &adds {
434 let mut e = e.clone();
435 let mut refs_phi = false;
436 visit_phi_refs(&e, &phi_set, &mut refs_phi);
437 if refs_phi {
438 let ty = e.type_ref();
440 let id = vt.vars.len() as u32;
441 let name = format!("v{}", id);
442 vt.vars.push(jdc_core::var::VarInfo {
443 id,
444 slot: u16::MAX,
445 name,
446 ty: ty.clone(),
447 is_param: false,
448 range_start: 0,
449 range_end: u16::MAX,
450 synthetic_name: true,
451 });
452 snapshots.push(Stmt::LocalDef {
453 var: id,
454 init: Some(e),
455 is_final: false,
456 force_type: true,
457 });
458 e = Expr::Local { var: id, ty };
459 }
460 emitted.push((*v, e));
461 }
462 for st in snapshots {
463 results[p].stmts.push(st);
464 }
465 for (v, e) in emitted {
466 results[p].stmts.push(Stmt::ExprStmt(Expr::Assign {
467 target: Box::new(Expr::Local {
468 var: v,
469 ty: vt.var(v).ty.clone(),
470 }),
471 op: jdc_core::ir::expr::AssignOp::Plain,
472 value: Box::new(e),
473 }));
474 }
475 }
476
477 let t_fix = t0;
478 phase_hit_n(0, n, t_fix);
479 if n == 1 && !entry_is_handler && cfg.blocks[0].pred.is_empty() {
485 let r = std::mem::replace(
486 &mut results[0],
487 BlockResult {
488 stmts: vec![],
489 out_stack: vec![],
490 term: jdc_core::ir::build::Term::Goto,
491 },
492 );
493 let mut body = r.stmts;
494 match r.term {
495 jdc_core::ir::build::Term::Return(Some(e)) => {
496 body.push(Stmt::Return(Some(e)));
497 }
498 jdc_core::ir::build::Term::Return(None) => {}
499 jdc_core::ir::build::Term::Throw(e) => body.push(Stmt::Throw(e)),
500 _ => {}
501 }
502 let mut body = Stmt::Block(body);
503 passes::fused_expr_rewrites(&mut body, &vt);
504 if mflags.has_sb() {
505 passes::fold_string_builders(&mut body, &vt);
506 }
507 passes::forward_single_use(&mut body, &vt);
508 passes::cleanup(&mut body);
509 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
510 passes::ensure_declared(&mut body, &vt);
511 passes::strip_trailing_void_return(&mut body);
512 passes::cleanup(&mut body);
513 if !errors.is_empty() {
514 passes::prepend_comment(
515 &mut body,
516 format!("$DDC: {} block(s) failed to decompile", errors.len()),
517 );
518 }
519 phase_hit_n(2, n, t_fix);
520 record_bucket(n, t0);
521 return Ok(Some(MethodBody { body, vt, desc }));
522 }
523
524 const TREE_GUARD: usize = 20_000;
529 let core_cfg = cfg.to_core();
530 #[allow(unused_assignments)]
531 let mut body: Option<Stmt> = None;
532 let mut budget: u32 = if n > 100 {
537 64
538 } else if n > 20 {
539 128
540 } else {
541 512
542 };
543 let groups = jdc_core::structure::group_exceptions_with(&core_cfg, Some(&results));
549 let dom_universe: std::collections::HashSet<usize> = (0..core_cfg.blocks.len()).collect();
550 let dom = jdc_core::structure::compute_dominators(&core_cfg, &dom_universe, core_cfg.entry);
551 if std::env::var("DDC_TRACE").is_ok() {
552 eprintln!("[mshape] {}.{} blocks={}", class.name, m.name, n);
553 }
554 #[cfg(feature = "visit-stats")]
555 {
556 jdc_core::structure::reset_visit_stats();
557 }
558 let walk_budget: u64 = std::env::var("DDC_WALKBUDGET")
568 .ok()
569 .and_then(|v| v.parse().ok())
570 .unwrap_or(8 * n as u64 + 128);
571 let walk_deadline = std::time::Instant::now()
577 + std::time::Duration::from_millis(
578 std::env::var("DDC_WALKMS")
579 .ok()
580 .and_then(|v| v.parse().ok())
581 .unwrap_or(1500),
582 );
583 loop {
584 jdc_core::structure::set_budget_override(Some(budget));
585 jdc_core::structure::set_walk_visit_budget(Some(walk_budget));
586 jdc_core::structure::set_walk_deadline(Some(walk_deadline));
587 let mut st = Structurer::with_precomputed_groups(
588 &core_cfg,
589 &results,
590 groups.clone(),
591 Default::default(),
592 Default::default(),
593 );
594 let region = st.structure_method();
595 let mut converter = Converter::with_precomputed(
596 &core_cfg,
597 &results,
598 groups.clone(),
599 jdc_core::structure::DomInfo {
600 idom: dom.idom.clone(),
601 },
602 );
603 let candidate = converter.convert(region);
604 jdc_core::structure::set_budget_override(None);
605 jdc_core::structure::set_walk_visit_budget(None);
606 jdc_core::structure::set_walk_deadline(None);
607 let size = {
608 let mut c = 0usize;
609 passes::count_stmts_deep(&candidate, &mut c);
610 c
611 };
612 if std::env::var("DDC_TRACE").is_ok() {
613 static TRACE_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
614 if *TRACE_ON.get_or_init(|| std::env::var("DDC_TRACE").is_ok()) {
615 eprintln!(
616 "[guard] {}.{} size={} budget={}",
617 class.name, m.name, size, budget
618 );
619 }
620 }
621 if size <= TREE_GUARD {
622 body = Some(candidate);
623 break;
624 }
625 if budget <= 16 {
626 return Ok(Some(stub_body(
627 format!(
628 "$DDC: statement tree exploded ({} nodes) even at copy budget 16",
629 size
630 ),
631 desc,
632 )));
633 }
634 budget /= 2;
635 }
636 #[cfg(feature = "visit-stats")]
637 if std::env::var("DDC_TRACE").is_ok() && n > 3 {
638 let used = jdc_core::structure::walk_visits_consumed();
639 eprintln!(
640 "[visits] {}.{} blocks={} consumed={} ratio={:.1}",
641 class.name,
642 m.name,
643 n,
644 used,
645 used as f64 / n as f64
646 );
647 }
648 phase_hit_n(1, n, t_fix);
649 let mut body = body.unwrap();
650 if !entry_phi_inits.is_empty() {
651 let head: Vec<Stmt> = entry_phi_inits
652 .into_iter()
653 .map(|(v, e)| Stmt::LocalDef {
654 var: v,
655 init: Some(e),
656 is_final: false,
657 force_type: true,
658 })
659 .collect();
660 body = match body {
661 Stmt::Block(v) => {
662 let mut nv = head;
663 nv.extend(v);
664 Stmt::Block(nv)
665 }
666 other => {
667 let mut nv = head;
668 nv.push(other);
669 Stmt::Block(nv)
670 }
671 };
672 }
673
674 passes::bind_catches(&mut body, &mut vt);
675 passes::prune_unreachable(&mut body);
676 passes::fused_expr_rewrites(&mut body, &vt);
679 if mflags.has_sb() {
680 passes::fold_string_builders(&mut body, &vt);
681 }
682 passes::ternary_fold(&mut body);
683 if mflags.has_monitor() {
684 passes::fold_synchronized(&mut body);
685 }
686 passes::forward_single_use(&mut body, &vt);
687 passes::cleanup(&mut body);
688 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
689 passes::booleanize(&mut vt, &mut body);
690 passes::ensure_declared(&mut body, &vt);
691 passes::strip_trailing_void_return(&mut body);
692 passes::cleanup(&mut body);
693
694 if !errors.is_empty() {
695 passes::prepend_comment(
696 &mut body,
697 format!("$DDC: {} block(s) failed to decompile", errors.len()),
698 );
699 }
700
701 phase_hit_n(2, n, t_fix);
702 record_bucket(n, t0);
703 Ok(Some(MethodBody { body, vt, desc }))
704}
705
706static PHASE_MICROS: [std::sync::atomic::AtomicU64; 5] = [
707 std::sync::atomic::AtomicU64::new(0),
708 std::sync::atomic::AtomicU64::new(0),
709 std::sync::atomic::AtomicU64::new(0),
710 std::sync::atomic::AtomicU64::new(0),
711 std::sync::atomic::AtomicU64::new(0),
712];
713static PHASE_BUCKET_MICROS: [std::sync::atomic::AtomicU64; 25] = [
715 std::sync::atomic::AtomicU64::new(0),
716 std::sync::atomic::AtomicU64::new(0),
717 std::sync::atomic::AtomicU64::new(0),
718 std::sync::atomic::AtomicU64::new(0),
719 std::sync::atomic::AtomicU64::new(0),
720 std::sync::atomic::AtomicU64::new(0),
721 std::sync::atomic::AtomicU64::new(0),
722 std::sync::atomic::AtomicU64::new(0),
723 std::sync::atomic::AtomicU64::new(0),
724 std::sync::atomic::AtomicU64::new(0),
725 std::sync::atomic::AtomicU64::new(0),
726 std::sync::atomic::AtomicU64::new(0),
727 std::sync::atomic::AtomicU64::new(0),
728 std::sync::atomic::AtomicU64::new(0),
729 std::sync::atomic::AtomicU64::new(0),
730 std::sync::atomic::AtomicU64::new(0),
731 std::sync::atomic::AtomicU64::new(0),
732 std::sync::atomic::AtomicU64::new(0),
733 std::sync::atomic::AtomicU64::new(0),
734 std::sync::atomic::AtomicU64::new(0),
735 std::sync::atomic::AtomicU64::new(0),
736 std::sync::atomic::AtomicU64::new(0),
737 std::sync::atomic::AtomicU64::new(0),
738 std::sync::atomic::AtomicU64::new(0),
739 std::sync::atomic::AtomicU64::new(0),
740];
741static PHASE_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
742
743fn bucket_of(n: usize) -> usize {
744 if n <= 1 {
745 0
746 } else if n <= 5 {
747 1
748 } else if n <= 20 {
749 2
750 } else if n <= 100 {
751 3
752 } else {
753 4
754 }
755}
756
757pub(crate) fn phase_hit_n(i: usize, n: usize, t: std::time::Instant) {
758 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
759 return;
760 }
761 let us = t.elapsed().as_micros() as u64;
762 PHASE_MICROS[i].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
763 PHASE_BUCKET_MICROS[i * 5 + bucket_of(n)].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
764}
765
766pub(crate) fn phase_hit(i: usize, t: std::time::Instant) {
767 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
768 return;
769 }
770 PHASE_MICROS[i].fetch_add(
771 t.elapsed().as_micros() as u64,
772 std::sync::atomic::Ordering::Relaxed,
773 );
774}
775
776pub fn phases_enable() {
778 PHASE_ON.store(true, std::sync::atomic::Ordering::Relaxed);
779}
780
781pub fn phases_dump() -> [u64; 5] {
783 let mut out = [0u64; 5];
784 for i in 0..5 {
785 out[i] = PHASE_MICROS[i].load(std::sync::atomic::Ordering::Relaxed);
786 }
787 out
788}
789
790pub fn phases_bucket_dump() -> [[u64; 5]; 5] {
792 let mut out = [[0u64; 5]; 5];
793 for i in 0..5 {
794 for b in 0..5 {
795 out[i][b] = PHASE_BUCKET_MICROS[i * 5 + b].load(std::sync::atomic::Ordering::Relaxed);
796 }
797 }
798 out
799}
800
801static CAP_HITS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
802static CAP_INSNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
803
804static BUILD_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
805static INSN_LIFTED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
806
807static BUCKET_COUNT: [std::sync::atomic::AtomicU64; 5] = [
808 std::sync::atomic::AtomicU64::new(0),
809 std::sync::atomic::AtomicU64::new(0),
810 std::sync::atomic::AtomicU64::new(0),
811 std::sync::atomic::AtomicU64::new(0),
812 std::sync::atomic::AtomicU64::new(0),
813];
814static BUCKET_MICROS: [std::sync::atomic::AtomicU64; 5] = [
815 std::sync::atomic::AtomicU64::new(0),
816 std::sync::atomic::AtomicU64::new(0),
817 std::sync::atomic::AtomicU64::new(0),
818 std::sync::atomic::AtomicU64::new(0),
819 std::sync::atomic::AtomicU64::new(0),
820];
821
822pub fn dump_builds() -> (u64, u64) {
824 (
825 BUILD_COUNT.load(std::sync::atomic::Ordering::Relaxed),
826 INSN_LIFTED.load(std::sync::atomic::Ordering::Relaxed),
827 )
828}
829
830pub fn dump_caps() -> (u64, u64) {
832 (
833 CAP_HITS.load(std::sync::atomic::Ordering::Relaxed),
834 CAP_INSNS.load(std::sync::atomic::Ordering::Relaxed),
835 )
836}
837
838pub fn dump_buckets() -> [(u64, u64); 5] {
840 let mut out = [(0u64, 0u64); 5];
841 for i in 0..5 {
842 out[i] = (
843 BUCKET_COUNT[i].load(std::sync::atomic::Ordering::Relaxed),
844 BUCKET_MICROS[i].load(std::sync::atomic::Ordering::Relaxed),
845 );
846 }
847 out
848}
849
850pub fn dom_counters() -> (u64, u64) {
852 (
853 jdc_core::structure::DOM_CALLS.load(std::sync::atomic::Ordering::Relaxed),
854 jdc_core::structure::DOM_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
855 )
856}
857
858pub fn dom_counters_enable() {
860 jdc_core::structure::set_dom_counters(true);
861}
862
863fn stub_body(msg: String, desc: MethodDescriptor) -> MethodBody {
865 MethodBody {
866 body: Stmt::Block(vec![Stmt::Comment(msg)]),
867 vt: VarTable::default(),
868 desc,
869 }
870}
871
872fn phis_for_merge(bid: usize, phis: &HashMap<(usize, u16), u32>) -> bool {
874 phis.keys().any(|(b, _)| *b == bid)
875}
876
877fn record_appends(
879 cfg: &DexCfg,
880 bid: usize,
881 out_states: &[Option<OutState>],
882 phis: &HashMap<(usize, u16), u32>,
883 vt: &jdc_core::var::VarTable,
884 out: &mut Vec<(usize, u32, u32, Expr)>,
885) {
886 for p in cfg.blocks[bid].pred.clone() {
887 let Some(o) = &out_states[p] else { continue };
888 for (r, st) in o.regs.iter().enumerate() {
889 if let Some(&phi) = phis.get(&(bid, r as u16)) {
890 let value = match st {
891 Reg::Live(v) if *v == phi => continue,
892 Reg::Live(v) => Expr::Local {
893 var: *v,
894 ty: vt.var(*v).ty.clone(),
895 },
896 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
897 Reg::Undef | Reg::WideHi => continue,
898 };
899 out.push((p, o.write_pc.get(r).copied().unwrap_or(0), phi, value));
900 }
901 }
902 }
903}
904
905#[allow(unused)]
906fn record_bucket(n: usize, t0: std::time::Instant) {
907 static BUCKETS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
910 if !*BUCKETS_ON.get_or_init(|| std::env::var("DDC_BUCKETS").is_ok()) {
911 return;
912 }
913 let bucket = if n <= 1 {
914 0
915 } else if n <= 5 {
916 1
917 } else if n <= 20 {
918 2
919 } else if n <= 100 {
920 3
921 } else {
922 4
923 };
924 BUCKET_COUNT[bucket].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
925 BUCKET_MICROS[bucket].fetch_add(
926 t0.elapsed().as_micros() as u64,
927 std::sync::atomic::Ordering::Relaxed,
928 );
929}
930
931fn visit_phi_refs(e: &Expr, phis: &std::collections::HashSet<u32>, hit: &mut bool) {
933 if *hit {
934 return;
935 }
936 if let Expr::Local { var, .. } = e {
937 if phis.contains(var) {
938 *hit = true;
939 }
940 return;
941 }
942 let mut kids: Vec<&Expr> = Vec::new();
943 match e {
944 Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => kids.push(e),
945 Expr::Bin { l, r, .. } => {
946 kids.push(l);
947 kids.push(r);
948 }
949 Expr::Cond { c, t, f } => {
950 kids.push(c);
951 kids.push(t);
952 kids.push(f);
953 }
954 Expr::Assign { target, value, .. } => {
955 kids.push(target);
956 kids.push(value);
957 }
958 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => kids.push(e),
959 Expr::Field { owner: Some(o), .. } => kids.push(o),
960 Expr::Method {
961 owner: Some(o),
962 args,
963 ..
964 } => {
965 kids.push(o);
966 kids.extend(args.iter());
967 }
968 Expr::ArrayIndex { array, index } => {
969 kids.push(array);
970 kids.push(index);
971 }
972 Expr::New { args, .. } => kids.extend(args.iter()),
973 Expr::NewArray { dims, init, .. } => {
974 kids.extend(dims.iter());
975 if let Some(v) = init {
976 kids.extend(v.iter());
977 }
978 }
979 Expr::NewMultiArray { dims, .. } => kids.extend(dims.iter()),
980 _ => {}
981 }
982 for k in kids {
983 visit_phi_refs(k, phis, hit);
984 if *hit {
985 return;
986 }
987 }
988}
989
990fn merge_states(
993 vt: &mut VarTable,
994 phis: &mut HashMap<(usize, u16), u32>,
995 bid: usize,
996 sides: &[&[Reg]],
997) -> Vec<Reg> {
998 let len = sides.iter().map(|s| s.len()).max().unwrap_or(0);
999 let mut out = vec![Reg::Undef; len];
1000 for (r, slot) in out.iter_mut().enumerate() {
1001 let mut all_eq = true;
1002 let mut first: Option<&Reg> = None;
1003 for s in sides {
1004 let v = s.get(r);
1005 match (first, v) {
1006 (None, v) => first = v,
1007 (Some(f), Some(v)) if f == v => {}
1008 _ => all_eq = false,
1009 }
1010 }
1011 if all_eq {
1012 *slot = first.cloned().unwrap_or(Reg::Undef);
1013 continue;
1014 }
1015 let key = (bid, r as u16);
1017 let phi = match phis.get(&key) {
1018 Some(&v) => v,
1019 None => {
1020 let ty = join_side_types(vt, sides, r);
1021 let id = vt.vars.len() as u32;
1022 let name = format!("v{}", id);
1023 vt.vars.push(jdc_core::var::VarInfo {
1024 id,
1025 slot: r as u16,
1026 name,
1027 ty,
1028 is_param: false,
1029 range_start: 0,
1030 range_end: u16::MAX,
1031 synthetic_name: true,
1032 });
1033 while vt.by_slot.len() <= r {
1034 vt.by_slot.push(Vec::new());
1035 }
1036 vt.by_slot[r].push((0, u16::MAX, id));
1037 phis.insert(key, id);
1038 id
1039 }
1040 };
1041 *slot = Reg::Live(phi);
1042 }
1043 out
1044}
1045
1046fn join_side_types(vt: &VarTable, sides: &[&[Reg]], r: usize) -> TypeRef {
1048 let mut ty: Option<JavaType> = None;
1049 for s in sides {
1050 let st = s.get(r).cloned().unwrap_or(Reg::Undef);
1051 let e = match st {
1052 Reg::Pending(e) | Reg::PendingCall(e) => e,
1053 Reg::Live(v) => Expr::Local {
1054 var: v,
1055 ty: vt.var(v).ty.clone(),
1056 },
1057 _ => continue,
1058 };
1059 let t = e.type_ref().erased();
1060 ty = Some(match ty {
1061 None => t,
1062 Some(prev) => join_types(&prev, &t),
1063 });
1064 }
1065 TypeRef::J(ty.unwrap_or(JavaType::Int))
1066}
1067
1068fn join_types(a: &JavaType, b: &JavaType) -> JavaType {
1069 if a == b {
1070 return a.clone();
1071 }
1072 let num = |t: &JavaType| {
1073 matches!(
1074 t,
1075 JavaType::Boolean
1076 | JavaType::Byte
1077 | JavaType::Char
1078 | JavaType::Short
1079 | JavaType::Int
1080 | JavaType::Long
1081 | JavaType::Float
1082 | JavaType::Double
1083 )
1084 };
1085 if num(a) && num(b) {
1086 return match (a, b) {
1088 (JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
1089 (JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
1090 (JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
1091 _ => JavaType::Int,
1092 };
1093 }
1094 if let (JavaType::Object(x), JavaType::Object(y)) = (a, b) {
1095 if x == y {
1096 return a.clone();
1097 }
1098 return JavaType::Object("java/lang/Object".into());
1100 }
1101 JavaType::Object("java/lang/Object".into())
1102}
1103
1104#[allow(dead_code)]
1106fn is_exit_term(term: &jdc_core::ir::build::Term) -> bool {
1107 matches!(
1108 term,
1109 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
1110 )
1111}
1112
1113#[allow(dead_code)]
1114fn unused(_: &InsnKind) {}