1use jdc_core::FxHashMap as 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 trace_on() -> bool {
32 static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
33 *T.get_or_init(|| std::env::var("DDC_TRACE").is_ok())
34}
35
36fn walk_budget_override() -> Option<u64> {
37 static T: std::sync::OnceLock<Option<u64>> = std::sync::OnceLock::new();
38 *T.get_or_init(|| {
39 std::env::var("DDC_WALKBUDGET")
40 .ok()
41 .and_then(|v| v.parse().ok())
42 })
43}
44
45fn walk_ms_override() -> Option<u64> {
46 static T: std::sync::OnceLock<Option<u64>> = std::sync::OnceLock::new();
47 *T.get_or_init(|| std::env::var("DDC_WALKMS").ok().and_then(|v| v.parse().ok()))
48}
49
50fn handler_types_for(cfg: &DexCfg, bid: usize) -> Vec<Option<std::sync::Arc<str>>> {
51 cfg.blocks[bid]
52 .handlers
53 .iter()
54 .filter_map(|&ri| cfg.exc_ranges.get(ri).map(|r| r.catch_type.clone()))
55 .collect()
56}
57
58pub fn decompile_method(
61 pool: &DexPool,
62 class: &PoolClass,
63 m: &PoolMethod,
64) -> Result<Option<MethodBody>, String> {
65 let t0 = std::time::Instant::now();
66 if m.is_abstract_or_native() {
67 return Ok(None);
68 }
69 let desc = m
70 .parsed_desc()
71 .ok_or_else(|| format!("bad descriptor {}", m.desc))?;
72 let dex = pool.dex(m.dex_idx).ok_or("missing dex image")?;
73 let dex = &*dex;
74 let Some(mut code) = dex.code_at(m.code_off) else {
75 return Ok(None);
76 };
77 let insn_count = code.insns.len();
78 let param_names = dex.parameter_names(m.debug_info_off);
79 let cfg = DexCfg::build(&mut code, &|ty| pool.type_name_arc(m.dex_idx, ty));
82 let n = cfg.blocks.len();
83 let env = MethodEnv {
84 pool,
85 di: m.dex_idx,
86 dex,
87 code: &code,
88 class_name: class.name.clone(),
89 method_name: m.name.clone(),
90 desc: desc.clone(),
91 debug_locals: dex.debug_locals(m.debug_info_off),
92 is_static: m.is_static(),
93 code_units: cfg.code_units,
94 };
95 let mut vt = VarTable::default();
96 let entry = entry_regs(&mut vt, &env, ¶m_names);
97 if n == 0 {
98 return Ok(None);
99 }
100 if insn_count > 30_000 || n > 8_000 {
103 return Ok(Some(stub_body(
104 format!(
105 "$DDC: method too large to decompile ({} insns, {} blocks)",
106 insn_count, n
107 ),
108 desc,
109 )));
110 }
111
112 let core_for_order = cfg.to_core();
115 let universe: jdc_core::FxHashSet<usize> = (0..n).collect();
116 let mut order = reverse_postorder(&core_for_order, core_for_order.entry, &universe);
117 {
118 let mut seen: jdc_core::FxHashSet<usize> = order.iter().copied().collect();
119 for b in &cfg.blocks {
120 if seen.insert(b.id) {
121 order.push(b.id);
122 }
123 }
124 }
125
126 let mut handler_entry_block: HashMap<usize, usize> = HashMap::default();
129 for b in &cfg.blocks {
130 if b.handlers.is_empty() {
131 continue;
132 }
133 let mut best: Option<(u32, usize)> = None;
134 for &ri in &b.handlers {
135 if let Some(r) = cfg.exc_ranges.get(ri) {
136 if best.map(|(s, _)| r.start < s).unwrap_or(true) {
137 if let Some(eb) = cfg.block_at(r.start) {
138 best = Some((r.start, eb));
139 }
140 }
141 }
142 }
143 if let Some((_, eb)) = best {
144 handler_entry_block.insert(b.id, eb);
145 }
146 }
147
148 let mut results: Vec<BlockResult> = Vec::with_capacity(n);
150 results.resize_with(n, || BlockResult {
151 stmts: vec![],
152 out_stack: vec![],
153 term: jdc_core::ir::build::Term::Return(None),
154 });
155 let mut out_states: Vec<Option<OutState>> = vec![None; n];
156 let mut stable_vars: HashMap<(usize, u16, u64), u32> = HashMap::default();
160 let mut mflags = crate::lift::MethodFlags::none();
164 let mut try_entry_snapshots: HashMap<usize, Vec<Reg>> = HashMap::default();
168 let mut built_once = vec![false; n];
169 let mut errors: HashMap<usize, String> = HashMap::default();
170 let mut phis: HashMap<(usize, u16), u32> = HashMap::default();
172 let entry_is_handler = handler_entry_block.contains_key(&0);
175
176 let try_entry_flags: Vec<bool> = (0..n)
181 .map(|b| {
182 !cfg.blocks[b].handlers.is_empty()
183 || cfg
184 .exc_ranges
185 .iter()
186 .any(|r| cfg.block_at(r.start) == Some(b))
187 })
188 .collect();
189 let handler_types: Vec<Vec<Option<std::sync::Arc<str>>>> =
190 (0..n).map(|b| handler_types_for(&cfg, b)).collect();
191
192 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 {
201 let mut h: u64 = 1;
202 for &p in &cfg.blocks[bid].pred {
203 h = h
204 .wrapping_mul(31)
205 .wrapping_add(p as u64)
206 .wrapping_mul(31)
207 .wrapping_add(ver[p]);
208 }
209 h
211 }
212 let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
213 let mut queued: Vec<bool> = vec![false; n];
214 for &bid in &order {
215 queue.push_back(bid);
216 queued[bid] = true;
217 }
218 let mut appends: HashMap<usize, Vec<(usize, u32, u32, Expr)>> = HashMap::default();
221 let mut total_visits: usize = 0;
222 let visit_cap: usize = 64 * n + 256;
223
224 while let Some(bid) = queue.pop_front() {
225 queued[bid] = false;
226 total_visits += 1;
227 if total_visits > visit_cap {
228 CAP_HITS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
229 CAP_INSNS.fetch_add(
230 insn_count as u64 * 1000 + n as u64,
231 std::sync::atomic::Ordering::Relaxed,
232 );
233 break; }
235 let is_handler = handler_entry_block.contains_key(&bid);
236 let sig = input_sig(&cfg, bid, &ver)
240 ^ (if is_handler {
241 let eb = handler_entry_block[&bid];
242 (eb as u64) << 32
243 } else {
244 0
245 });
246 let need_build = !built_once[bid] || built_ver[bid] != sig;
247 if !need_build {
248 continue;
252 }
253 let ins: Vec<Reg> = if bid == cfg.entry {
255 if cfg.blocks[0].pred.is_empty() || entry_is_handler {
256 entry.clone()
257 } else {
258 let mut sides: Vec<&[Reg]> = vec![entry.as_slice()];
262 for p in cfg.blocks[bid].pred.clone() {
263 if let Some(o) = &out_states[p] {
264 sides.push(o.regs.as_slice());
265 }
266 }
267 let merged =
268 merge_states(&mut vt, &mut phis, bid, &sides, &env.debug_locals, cfg.blocks[bid].start);
269 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
274 for (r, st) in entry.iter().enumerate() {
275 if let Some(&phi) = phis.get(&(bid, r as u16)) {
276 let value = match st {
277 Reg::Live(v) if *v == phi => continue,
278 Reg::Live(v) => Expr::Local {
279 var: *v,
280 ty: vt.var(*v).ty.clone(),
281 },
282 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
283 Reg::Undef | Reg::WideHi => continue,
284 };
285 recs.push((usize::MAX, 0, phi, value));
286 }
287 }
288 appends.insert(bid, recs);
289 merged
290 }
291 } else if is_handler {
292 let eb = handler_entry_block[&bid];
293 try_entry_snapshots
294 .get(&eb)
295 .cloned()
296 .unwrap_or_else(|| vec![Reg::Undef; env.code.registers_size as usize])
297 } else {
298 let mut sides: Vec<&[Reg]> = Vec::new();
301 for p in cfg.blocks[bid].pred.clone() {
302 if let Some(o) = &out_states[p] {
303 sides.push(o.regs.as_slice());
304 }
305 }
306 if sides.is_empty() {
307 vec![Reg::Undef; env.code.registers_size as usize]
308 } else if sides.len() == 1 {
309 sides[0].to_vec()
310 } else {
311 merge_states(
312 &mut vt,
313 &mut phis,
314 bid,
315 &sides,
316 &env.debug_locals,
317 cfg.blocks[bid].start,
318 )
319 }
320 };
321
322 if phis_for_merge(bid, &phis) {
326 let mut recs: Vec<(usize, u32, u32, Expr)> = Vec::new();
327 record_appends(&cfg, bid, &out_states, &phis, &vt, &mut recs);
328 if bid == cfg.entry {
331 if let Some(prev) = appends.get(&bid) {
332 for (p, pc, v, e) in prev.iter() {
333 if *p == usize::MAX {
334 recs.push((*p, *pc, *v, e.clone()));
335 }
336 }
337 }
338 }
339 appends.insert(bid, recs);
340 }
341
342 built_once[bid] = true;
346 built_ver[bid] = sig;
347 if try_entry_flags[bid] {
348 try_entry_snapshots.insert(bid, ins.clone());
349 }
350 BUILD_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
351 INSN_LIFTED.fetch_add(
352 cfg.block_ins(&cfg.blocks[bid]).len() as u64,
353 std::sync::atomic::Ordering::Relaxed,
354 );
355 let htypes = &handler_types[bid];
356 let block_ins = cfg.block_ins(&cfg.blocks[bid]);
357 let lifter = Lifter::new(&env, &mut vt, ins, bid, &mut stable_vars, &mut mflags);
358 let rebuilt = match lifter.build_block(block_ins, htypes) {
359 Ok((r, out)) => {
360 results[bid] = r;
361 let changed = match &out_states[bid] {
362 Some(prev) => prev.regs != out.regs,
363 None => true,
364 };
365 out_states[bid] = Some(out);
366 changed
367 }
368 Err(msg) => {
369 results[bid] = BlockResult {
370 stmts: vec![Stmt::Comment(format!("$DDC-BLOCK-ERROR: {msg}"))],
371 out_stack: vec![],
372 term: jdc_core::ir::build::Term::Return(None),
373 };
374 out_states[bid] = Some(OutState {
375 regs: vec![Reg::Undef; env.code.registers_size as usize],
376 write_pc: vec![0; env.code.registers_size as usize],
377 });
378 errors.insert(bid, msg);
379 false
380 }
381 };
382 if rebuilt {
383 ver[bid] += 1;
384 for &succ in &cfg.blocks[bid].succ {
386 if !queued[succ] && succ < n {
387 queue.push_back(succ);
388 queued[succ] = true;
389 }
390 }
391 if is_handler {
395 let eb = handler_entry_block[&bid];
396 if !queued[eb] && eb < n {
397 queue.push_back(eb);
398 queued[eb] = true;
399 }
400 }
401 for &ri in &cfg.blocks[bid].handlers {
404 if let Some(r) = cfg.exc_ranges.get(ri) {
405 if let Some(hb) = cfg.block_at(r.handler) {
406 if !queued[hb] && hb < n {
407 queue.push_back(hb);
408 queued[hb] = true;
409 }
410 }
411 }
412 }
413 }
414 }
415
416 let mut entry_phi_inits: Vec<(u32, Expr)> = Vec::new();
425 if let Some(recs) = appends.get_mut(&cfg.entry) {
426 let mut entry_side: Vec<(u32, Expr)> = Vec::new();
427 recs.retain(|(p, _, v, e)| {
428 if *p == usize::MAX {
429 entry_side.push((*v, e.clone()));
430 false
431 } else {
432 true
433 }
434 });
435 entry_side.sort_by_key(|(v, _)| *v);
436 entry_phi_inits = entry_side;
437 }
438
439 let mut merge_order: Vec<usize> = appends.keys().copied().collect();
453 merge_order.sort_unstable();
454 let mut per_pred: HashMap<usize, Vec<(u32, u32, Expr)>> = HashMap::default();
455 for bid in merge_order {
456 for (p, pc, v, e) in &appends[&bid] {
457 per_pred.entry(*p).or_default().push((*pc, *v, e.clone()));
458 }
459 }
460 let mut pred_order: Vec<usize> = per_pred.keys().copied().collect();
461 pred_order.sort_unstable();
462 for p in pred_order {
463 let mut adds = per_pred.remove(&p).unwrap();
464 if p == usize::MAX {
465 continue;
466 }
467 adds.sort_by_key(|(pc, _, _)| *pc);
468 let term_is_exit = matches!(
469 results[p].term,
470 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
471 );
472 if term_is_exit {
473 continue;
474 }
475 let phi_set: std::collections::HashSet<u32> = adds.iter().map(|(_, v, _)| *v).collect();
476 let mut snapshots: Vec<Stmt> = Vec::new();
477 let mut emitted: Vec<(u32, Expr)> = Vec::new();
479 for (_, v, e) in &adds {
480 let mut e = e.clone();
481 let mut refs_phi = false;
482 visit_phi_refs(&e, &phi_set, &mut refs_phi);
483 if refs_phi {
484 let ty = e.type_ref();
486 let id = vt.vars.len() as u32;
487 let name = format!("v{}", id);
488 vt.vars.push(jdc_core::var::VarInfo {
489 id,
490 slot: u16::MAX,
491 name,
492 ty: ty.clone(),
493 is_param: false,
494 range_start: 0,
495 range_end: u16::MAX,
496 synthetic_name: true,
497 });
498 snapshots.push(Stmt::LocalDef {
499 var: id,
500 init: Some(e),
501 is_final: false,
502 force_type: true,
503 });
504 e = Expr::Local { var: id, ty };
505 }
506 emitted.push((*v, e));
507 }
508 for st in snapshots {
509 results[p].stmts.push(st);
510 }
511 for (v, e) in emitted {
512 results[p].stmts.push(Stmt::ExprStmt(Expr::Assign {
513 target: Box::new(Expr::Local {
514 var: v,
515 ty: vt.var(v).ty.clone(),
516 }),
517 op: jdc_core::ir::expr::AssignOp::Plain,
518 value: Box::new(e),
519 }));
520 }
521 }
522
523 let t_fix = t0;
524 phase_hit_n(0, n, t_fix);
525 if n == 1 && !entry_is_handler && cfg.blocks[0].pred.is_empty() {
531 let r = std::mem::replace(
532 &mut results[0],
533 BlockResult {
534 stmts: vec![],
535 out_stack: vec![],
536 term: jdc_core::ir::build::Term::Goto,
537 },
538 );
539 let mut body = r.stmts;
540 match r.term {
541 jdc_core::ir::build::Term::Return(Some(e)) => {
542 body.push(Stmt::Return(Some(e)));
543 }
544 jdc_core::ir::build::Term::Return(None) => {}
545 jdc_core::ir::build::Term::Throw(e) => body.push(Stmt::Throw(e)),
546 _ => {}
547 }
548 let mut body = Stmt::Block(body);
549 passes::fused_expr_rewrites(&mut body, &vt);
550 if mflags.has_sb() {
551 passes::fold_string_builders(&mut body, &vt);
552 }
553 passes::forward_single_use(&mut body, &vt);
554 passes::cleanup(&mut body);
555 passes::inline_accessors(&mut body, pool);
556 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
557 passes::apply_local_names(&mut vt, &body);
558 passes::remove_kotlin_checks(&mut body);
559 passes::platform_constants(&mut body);
560 passes::drop_dead_locals(&mut body);
561 passes::ensure_declared(&mut body, &vt);
562 if &*m.name == "<init>" {
566 passes::fix_ctor_super_first(&mut body);
567 }
568 passes::strip_trailing_void_return(&mut body);
569 passes::cleanup(&mut body);
570 if !errors.is_empty() {
571 passes::prepend_comment(
572 &mut body,
573 format!("$DDC: {} block(s) failed to decompile", errors.len()),
574 );
575 }
576 phase_hit_n(2, n, t_fix);
577 record_bucket(n, t0);
578 return Ok(Some(MethodBody { body, vt, desc }));
579 }
580
581 const TREE_GUARD: usize = 20_000;
586 let core_cfg = cfg.to_core();
587 #[allow(unused_assignments)]
588 let mut body: Option<Stmt> = None;
589 let mut budget: u32 = if n > 100 {
594 64
595 } else if n > 20 {
596 128
597 } else {
598 512
599 };
600 let groups = jdc_core::structure::group_exceptions_with(&core_cfg, Some(&results));
606 let dom_universe: jdc_core::FxHashSet<usize> = (0..core_cfg.blocks.len()).collect();
607 let dom = jdc_core::structure::compute_dominators(&core_cfg, &dom_universe, core_cfg.entry);
608 if trace_on() {
609 eprintln!("[mshape] {}.{} blocks={}", class.name, m.name, n);
610 }
611 #[cfg(feature = "visit-stats")]
612 {
613 jdc_core::structure::reset_visit_stats();
614 }
615 let walk_budget: u64 = walk_budget_override().unwrap_or(8 * n as u64 + 128);
625 let walk_deadline = std::time::Instant::now()
631 + std::time::Duration::from_millis(
632 walk_ms_override().unwrap_or(1500),
633 );
634 loop {
635 jdc_core::structure::set_budget_override(Some(budget));
636 jdc_core::structure::set_walk_visit_budget(Some(walk_budget));
637 jdc_core::structure::set_walk_deadline(Some(walk_deadline));
638 let mut st = Structurer::with_shared_groups(
639 &core_cfg,
640 &results,
641 &groups,
642 Default::default(),
643 Default::default(),
644 );
645 let region = st.structure_method();
646 let mut converter = Converter::with_precomputed_ref(&core_cfg, &results, &groups, &dom);
647 let candidate = converter.convert(region);
648 jdc_core::structure::set_budget_override(None);
649 jdc_core::structure::set_walk_visit_budget(None);
650 jdc_core::structure::set_walk_deadline(None);
651 let size = {
652 let mut c = 0usize;
653 passes::count_stmts_deep(&candidate, &mut c);
654 c
655 };
656 if trace_on() {
657 eprintln!("[guard] {}.{} size={} budget={}", class.name, m.name, size, budget);
658 }
659 if size <= TREE_GUARD {
660 body = Some(candidate);
661 break;
662 }
663 if budget <= 16 {
664 return Ok(Some(stub_body(
665 format!(
666 "$DDC: statement tree exploded ({} nodes) even at copy budget 16",
667 size
668 ),
669 desc,
670 )));
671 }
672 budget /= 2;
673 }
674 #[cfg(feature = "visit-stats")]
675 if trace_on() && n > 3 {
676 let used = jdc_core::structure::walk_visits_consumed();
677 eprintln!(
678 "[visits] {}.{} blocks={} consumed={} ratio={:.1}",
679 class.name,
680 m.name,
681 n,
682 used,
683 used as f64 / n as f64
684 );
685 }
686 phase_hit_n(1, n, t_fix);
687 let mut body = body.unwrap();
688 if !entry_phi_inits.is_empty() {
689 let head: Vec<Stmt> = entry_phi_inits
690 .into_iter()
691 .map(|(v, e)| Stmt::LocalDef {
692 var: v,
693 init: Some(e),
694 is_final: false,
695 force_type: true,
696 })
697 .collect();
698 body = match body {
699 Stmt::Block(v) => {
700 let mut nv = head;
701 nv.extend(v);
702 Stmt::Block(nv)
703 }
704 other => {
705 let mut nv = head;
706 nv.push(other);
707 Stmt::Block(nv)
708 }
709 };
710 }
711
712 passes::bind_catches(&mut body, &mut vt);
713 passes::prune_unreachable(&mut body);
714 passes::fused_expr_rewrites(&mut body, &vt);
717 if mflags.has_sb() {
718 passes::fold_string_builders(&mut body, &vt);
719 }
720 passes::ternary_fold(&mut body);
721 if mflags.has_monitor() {
722 passes::fold_synchronized(&mut body);
723 }
724 passes::forward_single_use(&mut body, &vt);
725 passes::cleanup(&mut body);
726 passes::inline_accessors(&mut body, pool);
727 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
728 passes::booleanize(&mut vt, &mut body, matches!(desc.ret, JavaType::Boolean));
729 passes::apply_local_names(&mut vt, &body);
730 passes::remove_kotlin_checks(&mut body);
731 passes::platform_constants(&mut body);
732 passes::drop_dead_locals(&mut body);
733 passes::ensure_declared(&mut body, &vt);
734 if &*m.name == "<init>" {
738 passes::fix_ctor_super_first(&mut body);
739 }
740 if &*m.name == "<clinit>" {
741 passes::strip_clinit_returns(&mut body);
742 }
743 passes::strip_trailing_void_return(&mut body);
744 passes::cleanup(&mut body);
745
746 if !errors.is_empty() {
747 passes::prepend_comment(
748 &mut body,
749 format!("$DDC: {} block(s) failed to decompile", errors.len()),
750 );
751 }
752
753 phase_hit_n(2, n, t_fix);
754 record_bucket(n, t0);
755 Ok(Some(MethodBody { body, vt, desc }))
756}
757
758static PHASE_MICROS: [std::sync::atomic::AtomicU64; 5] = [
759 std::sync::atomic::AtomicU64::new(0),
760 std::sync::atomic::AtomicU64::new(0),
761 std::sync::atomic::AtomicU64::new(0),
762 std::sync::atomic::AtomicU64::new(0),
763 std::sync::atomic::AtomicU64::new(0),
764];
765static PHASE_BUCKET_MICROS: [std::sync::atomic::AtomicU64; 25] = [
767 std::sync::atomic::AtomicU64::new(0),
768 std::sync::atomic::AtomicU64::new(0),
769 std::sync::atomic::AtomicU64::new(0),
770 std::sync::atomic::AtomicU64::new(0),
771 std::sync::atomic::AtomicU64::new(0),
772 std::sync::atomic::AtomicU64::new(0),
773 std::sync::atomic::AtomicU64::new(0),
774 std::sync::atomic::AtomicU64::new(0),
775 std::sync::atomic::AtomicU64::new(0),
776 std::sync::atomic::AtomicU64::new(0),
777 std::sync::atomic::AtomicU64::new(0),
778 std::sync::atomic::AtomicU64::new(0),
779 std::sync::atomic::AtomicU64::new(0),
780 std::sync::atomic::AtomicU64::new(0),
781 std::sync::atomic::AtomicU64::new(0),
782 std::sync::atomic::AtomicU64::new(0),
783 std::sync::atomic::AtomicU64::new(0),
784 std::sync::atomic::AtomicU64::new(0),
785 std::sync::atomic::AtomicU64::new(0),
786 std::sync::atomic::AtomicU64::new(0),
787 std::sync::atomic::AtomicU64::new(0),
788 std::sync::atomic::AtomicU64::new(0),
789 std::sync::atomic::AtomicU64::new(0),
790 std::sync::atomic::AtomicU64::new(0),
791 std::sync::atomic::AtomicU64::new(0),
792];
793static PHASE_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
794
795fn bucket_of(n: usize) -> usize {
796 if n <= 1 {
797 0
798 } else if n <= 5 {
799 1
800 } else if n <= 20 {
801 2
802 } else if n <= 100 {
803 3
804 } else {
805 4
806 }
807}
808
809pub(crate) fn phase_hit_n(i: usize, n: usize, t: std::time::Instant) {
810 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
811 return;
812 }
813 let us = t.elapsed().as_micros() as u64;
814 PHASE_MICROS[i].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
815 PHASE_BUCKET_MICROS[i * 5 + bucket_of(n)].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
816}
817
818pub(crate) fn phase_hit(i: usize, t: std::time::Instant) {
819 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
820 return;
821 }
822 PHASE_MICROS[i].fetch_add(
823 t.elapsed().as_micros() as u64,
824 std::sync::atomic::Ordering::Relaxed,
825 );
826}
827
828pub fn phases_enable() {
830 PHASE_ON.store(true, std::sync::atomic::Ordering::Relaxed);
831}
832
833pub fn phases_dump() -> [u64; 5] {
835 let mut out = [0u64; 5];
836 for i in 0..5 {
837 out[i] = PHASE_MICROS[i].load(std::sync::atomic::Ordering::Relaxed);
838 }
839 out
840}
841
842pub fn phases_bucket_dump() -> [[u64; 5]; 5] {
844 let mut out = [[0u64; 5]; 5];
845 for i in 0..5 {
846 for b in 0..5 {
847 out[i][b] = PHASE_BUCKET_MICROS[i * 5 + b].load(std::sync::atomic::Ordering::Relaxed);
848 }
849 }
850 out
851}
852
853static CAP_HITS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
854static CAP_INSNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
855
856static BUILD_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
857static INSN_LIFTED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
858
859static BUCKET_COUNT: [std::sync::atomic::AtomicU64; 5] = [
860 std::sync::atomic::AtomicU64::new(0),
861 std::sync::atomic::AtomicU64::new(0),
862 std::sync::atomic::AtomicU64::new(0),
863 std::sync::atomic::AtomicU64::new(0),
864 std::sync::atomic::AtomicU64::new(0),
865];
866static BUCKET_MICROS: [std::sync::atomic::AtomicU64; 5] = [
867 std::sync::atomic::AtomicU64::new(0),
868 std::sync::atomic::AtomicU64::new(0),
869 std::sync::atomic::AtomicU64::new(0),
870 std::sync::atomic::AtomicU64::new(0),
871 std::sync::atomic::AtomicU64::new(0),
872];
873
874pub fn dump_builds() -> (u64, u64) {
876 (
877 BUILD_COUNT.load(std::sync::atomic::Ordering::Relaxed),
878 INSN_LIFTED.load(std::sync::atomic::Ordering::Relaxed),
879 )
880}
881
882pub fn dump_caps() -> (u64, u64) {
884 (
885 CAP_HITS.load(std::sync::atomic::Ordering::Relaxed),
886 CAP_INSNS.load(std::sync::atomic::Ordering::Relaxed),
887 )
888}
889
890pub fn dump_buckets() -> [(u64, u64); 5] {
892 let mut out = [(0u64, 0u64); 5];
893 for i in 0..5 {
894 out[i] = (
895 BUCKET_COUNT[i].load(std::sync::atomic::Ordering::Relaxed),
896 BUCKET_MICROS[i].load(std::sync::atomic::Ordering::Relaxed),
897 );
898 }
899 out
900}
901
902pub fn dom_counters() -> (u64, u64) {
904 (
905 jdc_core::structure::DOM_CALLS.load(std::sync::atomic::Ordering::Relaxed),
906 jdc_core::structure::DOM_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
907 )
908}
909
910pub fn dom_counters_enable() {
912 jdc_core::structure::set_dom_counters(true);
913}
914
915fn stub_body(msg: String, desc: MethodDescriptor) -> MethodBody {
917 MethodBody {
918 body: Stmt::Block(vec![Stmt::Comment(msg)]),
919 vt: VarTable::default(),
920 desc,
921 }
922}
923
924fn phis_for_merge(bid: usize, phis: &HashMap<(usize, u16), u32>) -> bool {
926 phis.keys().any(|(b, _)| *b == bid)
927}
928
929fn record_appends(
931 cfg: &DexCfg,
932 bid: usize,
933 out_states: &[Option<OutState>],
934 phis: &HashMap<(usize, u16), u32>,
935 vt: &jdc_core::var::VarTable,
936 out: &mut Vec<(usize, u32, u32, Expr)>,
937) {
938 for p in cfg.blocks[bid].pred.clone() {
939 let Some(o) = &out_states[p] else { continue };
940 for (r, st) in o.regs.iter().enumerate() {
941 if let Some(&phi) = phis.get(&(bid, r as u16)) {
942 let value = match st {
943 Reg::Live(v) if *v == phi => continue,
944 Reg::Live(v) => Expr::Local {
945 var: *v,
946 ty: vt.var(*v).ty.clone(),
947 },
948 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
949 Reg::Undef | Reg::WideHi => continue,
950 };
951 out.push((p, o.write_pc.get(r).copied().unwrap_or(0), phi, value));
952 }
953 }
954 }
955}
956
957#[allow(unused)]
958fn record_bucket(n: usize, t0: std::time::Instant) {
959 static BUCKETS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
962 if !*BUCKETS_ON.get_or_init(|| std::env::var("DDC_BUCKETS").is_ok()) {
963 return;
964 }
965 let bucket = if n <= 1 {
966 0
967 } else if n <= 5 {
968 1
969 } else if n <= 20 {
970 2
971 } else if n <= 100 {
972 3
973 } else {
974 4
975 };
976 BUCKET_COUNT[bucket].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
977 BUCKET_MICROS[bucket].fetch_add(
978 t0.elapsed().as_micros() as u64,
979 std::sync::atomic::Ordering::Relaxed,
980 );
981}
982
983fn visit_phi_refs(e: &Expr, phis: &std::collections::HashSet<u32>, hit: &mut bool) {
985 if *hit {
986 return;
987 }
988 if let Expr::Local { var, .. } = e {
989 if phis.contains(var) {
990 *hit = true;
991 }
992 return;
993 }
994 let mut kids: Vec<&Expr> = Vec::new();
995 match e {
996 Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => kids.push(e),
997 Expr::Bin { l, r, .. } => {
998 kids.push(l);
999 kids.push(r);
1000 }
1001 Expr::Cond { c, t, f } => {
1002 kids.push(c);
1003 kids.push(t);
1004 kids.push(f);
1005 }
1006 Expr::Assign { target, value, .. } => {
1007 kids.push(target);
1008 kids.push(value);
1009 }
1010 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => kids.push(e),
1011 Expr::Field { owner: Some(o), .. } => kids.push(o),
1012 Expr::Method {
1013 owner: Some(o),
1014 args,
1015 ..
1016 } => {
1017 kids.push(o);
1018 kids.extend(args.iter());
1019 }
1020 Expr::ArrayIndex { array, index } => {
1021 kids.push(array);
1022 kids.push(index);
1023 }
1024 Expr::New { args, .. } => kids.extend(args.iter()),
1025 Expr::NewArray { dims, init, .. } => {
1026 kids.extend(dims.iter());
1027 if let Some(v) = init {
1028 kids.extend(v.iter());
1029 }
1030 }
1031 Expr::NewMultiArray { dims, .. } => kids.extend(dims.iter()),
1032 _ => {}
1033 }
1034 for k in kids {
1035 visit_phi_refs(k, phis, hit);
1036 if *hit {
1037 return;
1038 }
1039 }
1040}
1041
1042fn debug_name_at(
1047 locals: &[ddc_dex::DebugLocal],
1048 slot: u16,
1049 pc: u32,
1050 ty: &jdc_core::types::JavaType,
1051) -> Option<String> {
1052 if locals.is_empty() {
1053 return None;
1054 }
1055 let hit = locals.iter().find(|l| {
1056 l.reg == slot
1057 && pc >= l.start
1058 && pc < l.end
1059 && l.ty.as_deref().map(|d| &crate::desc_type(d) == ty).unwrap_or(true)
1060 })?;
1061 let n = crate::classdec::java_ident(&hit.name);
1062 if n.is_empty() {
1063 None
1064 } else {
1065 Some(n.into_owned())
1066 }
1067}
1068
1069fn merge_states(
1070 vt: &mut VarTable,
1071 phis: &mut HashMap<(usize, u16), u32>,
1072 bid: usize,
1073 sides: &[&[Reg]],
1074 debug_locals: &[ddc_dex::DebugLocal],
1075 merge_pc: u32,
1076) -> Vec<Reg> {
1077 let len = sides.iter().map(|s| s.len()).max().unwrap_or(0);
1078 let mut out = vec![Reg::Undef; len];
1079 for (r, slot) in out.iter_mut().enumerate() {
1080 let mut all_eq = true;
1081 let mut first: Option<&Reg> = None;
1082 for s in sides {
1083 let v = s.get(r);
1084 match (first, v) {
1085 (None, v) => first = v,
1086 (Some(f), Some(v)) if f == v => {}
1087 _ => all_eq = false,
1088 }
1089 }
1090 if all_eq {
1091 *slot = first.cloned().unwrap_or(Reg::Undef);
1092 continue;
1093 }
1094 let key = (bid, r as u16);
1096 let phi = match phis.get(&key) {
1097 Some(&v) => v,
1098 None => {
1099 let ty = join_side_types(vt, sides, r);
1100 if let Some(n) = debug_name_at(debug_locals, r as u16, merge_pc, &ty.erased()) {
1108 let unified = vt
1109 .vars
1110 .iter()
1111 .find(|v| {
1112 v.slot == r as u16
1113 && !v.synthetic_name
1114 && v.name == n
1115 && v.ty.erased() == ty.erased()
1116 })
1117 .map(|v| v.id);
1118 let id = match unified {
1119 Some(id) => id,
1120 None => {
1121 let id = vt.vars.len() as u32;
1122 vt.vars.push(jdc_core::var::VarInfo {
1123 id,
1124 slot: r as u16,
1125 name: n,
1126 ty,
1127 is_param: false,
1128 range_start: 0,
1129 range_end: u16::MAX,
1130 synthetic_name: false,
1131 });
1132 while vt.by_slot.len() <= r {
1133 vt.by_slot.push(Vec::new());
1134 }
1135 vt.by_slot[r].push((0, u16::MAX, id));
1136 id
1137 }
1138 };
1139 phis.insert(key, id);
1140 id
1141 } else {
1142 let id = vt.vars.len() as u32;
1143 vt.vars.push(jdc_core::var::VarInfo {
1144 id,
1145 slot: r as u16,
1146 name: format!("v{}", id),
1147 ty,
1148 is_param: false,
1149 range_start: 0,
1150 range_end: u16::MAX,
1151 synthetic_name: true,
1152 });
1153 while vt.by_slot.len() <= r {
1154 vt.by_slot.push(Vec::new());
1155 }
1156 vt.by_slot[r].push((0, u16::MAX, id));
1157 phis.insert(key, id);
1158 id
1159 }
1160 }
1161 };
1162 *slot = Reg::Live(phi);
1163 }
1164 out
1165}
1166
1167fn join_side_types(vt: &VarTable, sides: &[&[Reg]], r: usize) -> TypeRef {
1169 let mut ty: Option<JavaType> = None;
1170 for s in sides {
1171 let st = s.get(r).cloned().unwrap_or(Reg::Undef);
1172 let e = match st {
1173 Reg::Pending(e) | Reg::PendingCall(e) => e,
1174 Reg::Live(v) => Expr::Local {
1175 var: v,
1176 ty: vt.var(v).ty.clone(),
1177 },
1178 _ => continue,
1179 };
1180 let t = e.type_ref().erased();
1181 ty = Some(match ty {
1182 None => t,
1183 Some(prev) => join_types(&prev, &t),
1184 });
1185 }
1186 TypeRef::J(ty.unwrap_or(JavaType::Int))
1187}
1188
1189fn join_types(a: &JavaType, b: &JavaType) -> JavaType {
1190 if a == b {
1191 return a.clone();
1192 }
1193 let num = |t: &JavaType| {
1194 matches!(
1195 t,
1196 JavaType::Boolean
1197 | JavaType::Byte
1198 | JavaType::Char
1199 | JavaType::Short
1200 | JavaType::Int
1201 | JavaType::Long
1202 | JavaType::Float
1203 | JavaType::Double
1204 )
1205 };
1206 if num(a) && num(b) {
1207 return match (a, b) {
1209 (JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
1210 (JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
1211 (JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
1212 _ => JavaType::Int,
1213 };
1214 }
1215 if let (JavaType::Object(x), JavaType::Object(y)) = (a, b) {
1216 if x == y {
1217 return a.clone();
1218 }
1219 return JavaType::Object("java/lang/Object".into());
1221 }
1222 JavaType::Object("java/lang/Object".into())
1223}
1224
1225#[allow(dead_code)]
1227fn is_exit_term(term: &jdc_core::ir::build::Term) -> bool {
1228 matches!(
1229 term,
1230 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
1231 )
1232}
1233
1234#[allow(dead_code)]
1235fn unused(_: &InsnKind) {}