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::inline_accessors(&mut body, pool);
510 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
511 passes::apply_local_names(&mut vt, &body);
512 passes::remove_kotlin_checks(&mut body);
513 passes::platform_constants(&mut body);
514 passes::ensure_declared(&mut body, &vt);
515 passes::strip_trailing_void_return(&mut body);
516 passes::cleanup(&mut body);
517 if !errors.is_empty() {
518 passes::prepend_comment(
519 &mut body,
520 format!("$DDC: {} block(s) failed to decompile", errors.len()),
521 );
522 }
523 phase_hit_n(2, n, t_fix);
524 record_bucket(n, t0);
525 return Ok(Some(MethodBody { body, vt, desc }));
526 }
527
528 const TREE_GUARD: usize = 20_000;
533 let core_cfg = cfg.to_core();
534 #[allow(unused_assignments)]
535 let mut body: Option<Stmt> = None;
536 let mut budget: u32 = if n > 100 {
541 64
542 } else if n > 20 {
543 128
544 } else {
545 512
546 };
547 let groups = jdc_core::structure::group_exceptions_with(&core_cfg, Some(&results));
553 let dom_universe: std::collections::HashSet<usize> = (0..core_cfg.blocks.len()).collect();
554 let dom = jdc_core::structure::compute_dominators(&core_cfg, &dom_universe, core_cfg.entry);
555 if std::env::var("DDC_TRACE").is_ok() {
556 eprintln!("[mshape] {}.{} blocks={}", class.name, m.name, n);
557 }
558 #[cfg(feature = "visit-stats")]
559 {
560 jdc_core::structure::reset_visit_stats();
561 }
562 let walk_budget: u64 = std::env::var("DDC_WALKBUDGET")
572 .ok()
573 .and_then(|v| v.parse().ok())
574 .unwrap_or(8 * n as u64 + 128);
575 let walk_deadline = std::time::Instant::now()
581 + std::time::Duration::from_millis(
582 std::env::var("DDC_WALKMS")
583 .ok()
584 .and_then(|v| v.parse().ok())
585 .unwrap_or(1500),
586 );
587 loop {
588 jdc_core::structure::set_budget_override(Some(budget));
589 jdc_core::structure::set_walk_visit_budget(Some(walk_budget));
590 jdc_core::structure::set_walk_deadline(Some(walk_deadline));
591 let mut st = Structurer::with_precomputed_groups(
592 &core_cfg,
593 &results,
594 groups.clone(),
595 Default::default(),
596 Default::default(),
597 );
598 let region = st.structure_method();
599 let mut converter = Converter::with_precomputed(
600 &core_cfg,
601 &results,
602 groups.clone(),
603 jdc_core::structure::DomInfo {
604 idom: dom.idom.clone(),
605 },
606 );
607 let candidate = converter.convert(region);
608 jdc_core::structure::set_budget_override(None);
609 jdc_core::structure::set_walk_visit_budget(None);
610 jdc_core::structure::set_walk_deadline(None);
611 let size = {
612 let mut c = 0usize;
613 passes::count_stmts_deep(&candidate, &mut c);
614 c
615 };
616 if std::env::var("DDC_TRACE").is_ok() {
617 static TRACE_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
618 if *TRACE_ON.get_or_init(|| std::env::var("DDC_TRACE").is_ok()) {
619 eprintln!(
620 "[guard] {}.{} size={} budget={}",
621 class.name, m.name, size, budget
622 );
623 }
624 }
625 if size <= TREE_GUARD {
626 body = Some(candidate);
627 break;
628 }
629 if budget <= 16 {
630 return Ok(Some(stub_body(
631 format!(
632 "$DDC: statement tree exploded ({} nodes) even at copy budget 16",
633 size
634 ),
635 desc,
636 )));
637 }
638 budget /= 2;
639 }
640 #[cfg(feature = "visit-stats")]
641 if std::env::var("DDC_TRACE").is_ok() && n > 3 {
642 let used = jdc_core::structure::walk_visits_consumed();
643 eprintln!(
644 "[visits] {}.{} blocks={} consumed={} ratio={:.1}",
645 class.name,
646 m.name,
647 n,
648 used,
649 used as f64 / n as f64
650 );
651 }
652 phase_hit_n(1, n, t_fix);
653 let mut body = body.unwrap();
654 if !entry_phi_inits.is_empty() {
655 let head: Vec<Stmt> = entry_phi_inits
656 .into_iter()
657 .map(|(v, e)| Stmt::LocalDef {
658 var: v,
659 init: Some(e),
660 is_final: false,
661 force_type: true,
662 })
663 .collect();
664 body = match body {
665 Stmt::Block(v) => {
666 let mut nv = head;
667 nv.extend(v);
668 Stmt::Block(nv)
669 }
670 other => {
671 let mut nv = head;
672 nv.push(other);
673 Stmt::Block(nv)
674 }
675 };
676 }
677
678 passes::bind_catches(&mut body, &mut vt);
679 passes::prune_unreachable(&mut body);
680 passes::fused_expr_rewrites(&mut body, &vt);
683 if mflags.has_sb() {
684 passes::fold_string_builders(&mut body, &vt);
685 }
686 passes::ternary_fold(&mut body);
687 if mflags.has_monitor() {
688 passes::fold_synchronized(&mut body);
689 }
690 passes::forward_single_use(&mut body, &vt);
691 passes::cleanup(&mut body);
692 passes::inline_accessors(&mut body, pool);
693 passes::infer_types(&mut vt, &mut body, &desc.ret, &env);
694 passes::booleanize(&mut vt, &mut body);
695 passes::apply_local_names(&mut vt, &body);
696 passes::remove_kotlin_checks(&mut body);
697 passes::platform_constants(&mut body);
698 passes::ensure_declared(&mut body, &vt);
699 passes::strip_trailing_void_return(&mut body);
700 passes::cleanup(&mut body);
701
702 if !errors.is_empty() {
703 passes::prepend_comment(
704 &mut body,
705 format!("$DDC: {} block(s) failed to decompile", errors.len()),
706 );
707 }
708
709 phase_hit_n(2, n, t_fix);
710 record_bucket(n, t0);
711 Ok(Some(MethodBody { body, vt, desc }))
712}
713
714static PHASE_MICROS: [std::sync::atomic::AtomicU64; 5] = [
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];
721static PHASE_BUCKET_MICROS: [std::sync::atomic::AtomicU64; 25] = [
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 std::sync::atomic::AtomicU64::new(0),
741 std::sync::atomic::AtomicU64::new(0),
742 std::sync::atomic::AtomicU64::new(0),
743 std::sync::atomic::AtomicU64::new(0),
744 std::sync::atomic::AtomicU64::new(0),
745 std::sync::atomic::AtomicU64::new(0),
746 std::sync::atomic::AtomicU64::new(0),
747 std::sync::atomic::AtomicU64::new(0),
748];
749static PHASE_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
750
751fn bucket_of(n: usize) -> usize {
752 if n <= 1 {
753 0
754 } else if n <= 5 {
755 1
756 } else if n <= 20 {
757 2
758 } else if n <= 100 {
759 3
760 } else {
761 4
762 }
763}
764
765pub(crate) fn phase_hit_n(i: usize, n: usize, t: std::time::Instant) {
766 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
767 return;
768 }
769 let us = t.elapsed().as_micros() as u64;
770 PHASE_MICROS[i].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
771 PHASE_BUCKET_MICROS[i * 5 + bucket_of(n)].fetch_add(us, std::sync::atomic::Ordering::Relaxed);
772}
773
774pub(crate) fn phase_hit(i: usize, t: std::time::Instant) {
775 if !PHASE_ON.load(std::sync::atomic::Ordering::Relaxed) {
776 return;
777 }
778 PHASE_MICROS[i].fetch_add(
779 t.elapsed().as_micros() as u64,
780 std::sync::atomic::Ordering::Relaxed,
781 );
782}
783
784pub fn phases_enable() {
786 PHASE_ON.store(true, std::sync::atomic::Ordering::Relaxed);
787}
788
789pub fn phases_dump() -> [u64; 5] {
791 let mut out = [0u64; 5];
792 for i in 0..5 {
793 out[i] = PHASE_MICROS[i].load(std::sync::atomic::Ordering::Relaxed);
794 }
795 out
796}
797
798pub fn phases_bucket_dump() -> [[u64; 5]; 5] {
800 let mut out = [[0u64; 5]; 5];
801 for i in 0..5 {
802 for b in 0..5 {
803 out[i][b] = PHASE_BUCKET_MICROS[i * 5 + b].load(std::sync::atomic::Ordering::Relaxed);
804 }
805 }
806 out
807}
808
809static CAP_HITS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
810static CAP_INSNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
811
812static BUILD_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
813static INSN_LIFTED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
814
815static BUCKET_COUNT: [std::sync::atomic::AtomicU64; 5] = [
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 std::sync::atomic::AtomicU64::new(0),
821];
822static BUCKET_MICROS: [std::sync::atomic::AtomicU64; 5] = [
823 std::sync::atomic::AtomicU64::new(0),
824 std::sync::atomic::AtomicU64::new(0),
825 std::sync::atomic::AtomicU64::new(0),
826 std::sync::atomic::AtomicU64::new(0),
827 std::sync::atomic::AtomicU64::new(0),
828];
829
830pub fn dump_builds() -> (u64, u64) {
832 (
833 BUILD_COUNT.load(std::sync::atomic::Ordering::Relaxed),
834 INSN_LIFTED.load(std::sync::atomic::Ordering::Relaxed),
835 )
836}
837
838pub fn dump_caps() -> (u64, u64) {
840 (
841 CAP_HITS.load(std::sync::atomic::Ordering::Relaxed),
842 CAP_INSNS.load(std::sync::atomic::Ordering::Relaxed),
843 )
844}
845
846pub fn dump_buckets() -> [(u64, u64); 5] {
848 let mut out = [(0u64, 0u64); 5];
849 for i in 0..5 {
850 out[i] = (
851 BUCKET_COUNT[i].load(std::sync::atomic::Ordering::Relaxed),
852 BUCKET_MICROS[i].load(std::sync::atomic::Ordering::Relaxed),
853 );
854 }
855 out
856}
857
858pub fn dom_counters() -> (u64, u64) {
860 (
861 jdc_core::structure::DOM_CALLS.load(std::sync::atomic::Ordering::Relaxed),
862 jdc_core::structure::DOM_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
863 )
864}
865
866pub fn dom_counters_enable() {
868 jdc_core::structure::set_dom_counters(true);
869}
870
871fn stub_body(msg: String, desc: MethodDescriptor) -> MethodBody {
873 MethodBody {
874 body: Stmt::Block(vec![Stmt::Comment(msg)]),
875 vt: VarTable::default(),
876 desc,
877 }
878}
879
880fn phis_for_merge(bid: usize, phis: &HashMap<(usize, u16), u32>) -> bool {
882 phis.keys().any(|(b, _)| *b == bid)
883}
884
885fn record_appends(
887 cfg: &DexCfg,
888 bid: usize,
889 out_states: &[Option<OutState>],
890 phis: &HashMap<(usize, u16), u32>,
891 vt: &jdc_core::var::VarTable,
892 out: &mut Vec<(usize, u32, u32, Expr)>,
893) {
894 for p in cfg.blocks[bid].pred.clone() {
895 let Some(o) = &out_states[p] else { continue };
896 for (r, st) in o.regs.iter().enumerate() {
897 if let Some(&phi) = phis.get(&(bid, r as u16)) {
898 let value = match st {
899 Reg::Live(v) if *v == phi => continue,
900 Reg::Live(v) => Expr::Local {
901 var: *v,
902 ty: vt.var(*v).ty.clone(),
903 },
904 Reg::Pending(e) | Reg::PendingCall(e) => e.clone(),
905 Reg::Undef | Reg::WideHi => continue,
906 };
907 out.push((p, o.write_pc.get(r).copied().unwrap_or(0), phi, value));
908 }
909 }
910 }
911}
912
913#[allow(unused)]
914fn record_bucket(n: usize, t0: std::time::Instant) {
915 static BUCKETS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
918 if !*BUCKETS_ON.get_or_init(|| std::env::var("DDC_BUCKETS").is_ok()) {
919 return;
920 }
921 let bucket = if n <= 1 {
922 0
923 } else if n <= 5 {
924 1
925 } else if n <= 20 {
926 2
927 } else if n <= 100 {
928 3
929 } else {
930 4
931 };
932 BUCKET_COUNT[bucket].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
933 BUCKET_MICROS[bucket].fetch_add(
934 t0.elapsed().as_micros() as u64,
935 std::sync::atomic::Ordering::Relaxed,
936 );
937}
938
939fn visit_phi_refs(e: &Expr, phis: &std::collections::HashSet<u32>, hit: &mut bool) {
941 if *hit {
942 return;
943 }
944 if let Expr::Local { var, .. } = e {
945 if phis.contains(var) {
946 *hit = true;
947 }
948 return;
949 }
950 let mut kids: Vec<&Expr> = Vec::new();
951 match e {
952 Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => kids.push(e),
953 Expr::Bin { l, r, .. } => {
954 kids.push(l);
955 kids.push(r);
956 }
957 Expr::Cond { c, t, f } => {
958 kids.push(c);
959 kids.push(t);
960 kids.push(f);
961 }
962 Expr::Assign { target, value, .. } => {
963 kids.push(target);
964 kids.push(value);
965 }
966 Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => kids.push(e),
967 Expr::Field { owner: Some(o), .. } => kids.push(o),
968 Expr::Method {
969 owner: Some(o),
970 args,
971 ..
972 } => {
973 kids.push(o);
974 kids.extend(args.iter());
975 }
976 Expr::ArrayIndex { array, index } => {
977 kids.push(array);
978 kids.push(index);
979 }
980 Expr::New { args, .. } => kids.extend(args.iter()),
981 Expr::NewArray { dims, init, .. } => {
982 kids.extend(dims.iter());
983 if let Some(v) = init {
984 kids.extend(v.iter());
985 }
986 }
987 Expr::NewMultiArray { dims, .. } => kids.extend(dims.iter()),
988 _ => {}
989 }
990 for k in kids {
991 visit_phi_refs(k, phis, hit);
992 if *hit {
993 return;
994 }
995 }
996}
997
998fn merge_states(
1001 vt: &mut VarTable,
1002 phis: &mut HashMap<(usize, u16), u32>,
1003 bid: usize,
1004 sides: &[&[Reg]],
1005) -> Vec<Reg> {
1006 let len = sides.iter().map(|s| s.len()).max().unwrap_or(0);
1007 let mut out = vec![Reg::Undef; len];
1008 for (r, slot) in out.iter_mut().enumerate() {
1009 let mut all_eq = true;
1010 let mut first: Option<&Reg> = None;
1011 for s in sides {
1012 let v = s.get(r);
1013 match (first, v) {
1014 (None, v) => first = v,
1015 (Some(f), Some(v)) if f == v => {}
1016 _ => all_eq = false,
1017 }
1018 }
1019 if all_eq {
1020 *slot = first.cloned().unwrap_or(Reg::Undef);
1021 continue;
1022 }
1023 let key = (bid, r as u16);
1025 let phi = match phis.get(&key) {
1026 Some(&v) => v,
1027 None => {
1028 let ty = join_side_types(vt, sides, r);
1029 let id = vt.vars.len() as u32;
1030 let name = format!("v{}", id);
1031 vt.vars.push(jdc_core::var::VarInfo {
1032 id,
1033 slot: r as u16,
1034 name,
1035 ty,
1036 is_param: false,
1037 range_start: 0,
1038 range_end: u16::MAX,
1039 synthetic_name: true,
1040 });
1041 while vt.by_slot.len() <= r {
1042 vt.by_slot.push(Vec::new());
1043 }
1044 vt.by_slot[r].push((0, u16::MAX, id));
1045 phis.insert(key, id);
1046 id
1047 }
1048 };
1049 *slot = Reg::Live(phi);
1050 }
1051 out
1052}
1053
1054fn join_side_types(vt: &VarTable, sides: &[&[Reg]], r: usize) -> TypeRef {
1056 let mut ty: Option<JavaType> = None;
1057 for s in sides {
1058 let st = s.get(r).cloned().unwrap_or(Reg::Undef);
1059 let e = match st {
1060 Reg::Pending(e) | Reg::PendingCall(e) => e,
1061 Reg::Live(v) => Expr::Local {
1062 var: v,
1063 ty: vt.var(v).ty.clone(),
1064 },
1065 _ => continue,
1066 };
1067 let t = e.type_ref().erased();
1068 ty = Some(match ty {
1069 None => t,
1070 Some(prev) => join_types(&prev, &t),
1071 });
1072 }
1073 TypeRef::J(ty.unwrap_or(JavaType::Int))
1074}
1075
1076fn join_types(a: &JavaType, b: &JavaType) -> JavaType {
1077 if a == b {
1078 return a.clone();
1079 }
1080 let num = |t: &JavaType| {
1081 matches!(
1082 t,
1083 JavaType::Boolean
1084 | JavaType::Byte
1085 | JavaType::Char
1086 | JavaType::Short
1087 | JavaType::Int
1088 | JavaType::Long
1089 | JavaType::Float
1090 | JavaType::Double
1091 )
1092 };
1093 if num(a) && num(b) {
1094 return match (a, b) {
1096 (JavaType::Double, _) | (_, JavaType::Double) => JavaType::Double,
1097 (JavaType::Float, _) | (_, JavaType::Float) => JavaType::Float,
1098 (JavaType::Long, _) | (_, JavaType::Long) => JavaType::Long,
1099 _ => JavaType::Int,
1100 };
1101 }
1102 if let (JavaType::Object(x), JavaType::Object(y)) = (a, b) {
1103 if x == y {
1104 return a.clone();
1105 }
1106 return JavaType::Object("java/lang/Object".into());
1108 }
1109 JavaType::Object("java/lang/Object".into())
1110}
1111
1112#[allow(dead_code)]
1114fn is_exit_term(term: &jdc_core::ir::build::Term) -> bool {
1115 matches!(
1116 term,
1117 jdc_core::ir::build::Term::Return(_) | jdc_core::ir::build::Term::Throw(_)
1118 )
1119}
1120
1121#[allow(dead_code)]
1122fn unused(_: &InsnKind) {}