Skip to main content

ddc_dec/
lift.rs

1//! Register-machine expression lifting: Dalvik instructions → jdc-core IR.
2//!
3//! The register file is modeled as per-register *views* of values:
4//! * `Live(v)` — the register equals local `v`, whose defining statement was
5//!   already emitted (SSA-ish: every materialization allocates a fresh var,
6//!   so a `Local` reference can never go stale);
7//! * `Pending(e)` — the register holds the expression `e`, whose store is
8//!   deferred so consumers can nest it (`return a + b`);
9//! * `PendingCall(e)` — the value of a just-executed invoke /
10//!   filled-new-array whose statement has not been emitted yet: consumed
11//!   exactly once (a materialization, a term, or a discarded-value
12//!   statement), never duplicated;
13//! * `WideHi` — the high half of a J/D pair.
14//!
15//! At block exit the terminator is built FIRST (terms may carry pending
16//! call expressions: `return foo();`), then unconsumed calls become
17//! statements and impure pendings materialize, so cross-block states only
18//! carry pure values, locals, and open `new`/`new-array` views.
19
20use ddc_dex::insn::{ArithOp, CmpKind, CmpOp, Insn, InsnKind, InvokeKind, Payload};
21use ddc_dex::{CodeItem, DexFile};
22use jdc_core::ir::build::{has_side_effects, BlockResult, SwitchTargets, Term};
23use jdc_core::ir::expr::{AssignOp, BinOp, ConcatPart, ConstVal, Expr, TypeRef, UnOp};
24use jdc_core::ir::stmt::Stmt;
25use jdc_core::types::{parse_method_descriptor, JavaType, MethodDescriptor};
26use jdc_core::var::{VarInfo, VarTable};
27
28use crate::DexPool;
29
30pub type BResult<T> = Result<T, String>;
31
32/// Cheap per-method feature flags accumulated by the lifter: which
33/// post-lift passes could possibly match anything. Most methods use none of
34/// these — gating their passes skips full-tree analysis walks.
35#[derive(Clone, Copy, Default, PartialEq)]
36pub struct MethodFlags(u8);
37
38impl MethodFlags {
39    const SB: u8 = 1;
40    const MONITOR: u8 = 2;
41    const CMP: u8 = 4;
42    pub fn none() -> MethodFlags {
43        MethodFlags(0)
44    }
45    pub fn with_sb(self) -> MethodFlags {
46        MethodFlags(self.0 | Self::SB)
47    }
48    pub fn with_monitor(self) -> MethodFlags {
49        MethodFlags(self.0 | Self::MONITOR)
50    }
51    pub fn with_cmp(self) -> MethodFlags {
52        MethodFlags(self.0 | Self::CMP)
53    }
54    pub fn has_sb(self) -> bool {
55        self.0 & Self::SB != 0
56    }
57    pub fn has_monitor(self) -> bool {
58        self.0 & Self::MONITOR != 0
59    }
60    pub fn has_cmp(self) -> bool {
61        self.0 & Self::CMP != 0
62    }
63    pub fn merge(self, o: MethodFlags) -> MethodFlags {
64        MethodFlags(self.0 | o.0)
65    }
66}
67
68/// Everything one method body needs from its DEX image.
69pub struct MethodEnv<'a> {
70    pub pool: &'a DexPool,
71    pub dex: &'a DexFile,
72    pub code: &'a CodeItem,
73    pub class_name: String,
74    pub method_name: String,
75    pub desc: MethodDescriptor,
76    pub is_static: bool,
77    /// Total code units — the CFG owns the insns stream, so the lifter reads
78    /// the extent from here instead of `code.insns` (hollowed after the
79    /// CFG build moved it).
80    pub code_units: u32,
81}
82
83impl<'a> MethodEnv<'a> {
84    pub fn type_name(&self, idx: u32) -> String {
85        self.dex.type_name(idx).to_string()
86    }
87    pub fn java_type(&self, idx: u32) -> JavaType {
88        crate::desc_type(self.dex.type_name(idx))
89    }
90    pub fn field_ref(&self, idx: u32) -> (String, String, JavaType) {
91        let f = self.dex.field(idx);
92        (
93            self.dex.class_name(f.class_idx),
94            self.dex.string(f.name_idx).to_string(),
95            self.java_type(f.type_idx),
96        )
97    }
98    pub fn method_ref(&self, idx: u32) -> (String, String, MethodDescriptor) {
99        let m = self.dex.method(idx);
100        let proto = self.dex.proto(m.proto_idx);
101        let params: Vec<String> = self
102            .dex
103            .proto_params(m.proto_idx)
104            .into_iter()
105            .map(|t| self.dex.type_name(t).to_string())
106            .collect();
107        let ret = self.dex.type_name(proto.return_type_idx);
108        let desc = format!("({}){}", params.join(""), ret);
109        let md = parse_method_descriptor(&desc).unwrap_or(MethodDescriptor {
110            args: vec![],
111            ret: JavaType::Void,
112        });
113        (
114            self.dex.class_name(m.class_idx),
115            self.dex.string(m.name_idx).to_string(),
116            md,
117        )
118    }
119}
120
121/// Per-register value view (see module docs).
122#[derive(Debug, Clone, PartialEq)]
123pub enum Reg {
124    Undef,
125    Live(u32),
126    Pending(Expr),
127    PendingCall(Expr),
128    WideHi,
129}
130
131/// Block-exit register state (machine-neutral view plus origin tracking).
132#[derive(Debug, Clone)]
133pub struct OutState {
134    pub regs: Vec<Reg>,
135    /// Per register: the pc of the instruction that produced its value
136    /// (orders merge materializations by original instruction order).
137    pub write_pc: Vec<u32>,
138}
139
140pub struct Lifter<'a> {
141    pub env: &'a MethodEnv<'a>,
142    pub vt: &'a mut VarTable,
143    /// Owning block id (stable-var registry key).
144    block_id: usize,
145    /// (block, register) → var id, shared across REBUILDS: a re-lift of
146    /// the same block with the same input reuses the same materialized
147    /// variables, so the output state is id-stable. Without this, every
148    /// rebuild allocated fresh ids, the out-state compared unequal, and
149    /// the worklist cascaded re-queuing until the visit cap (~60
150    /// rebuilds per block, 30M lifts on weibo).
151    stable: &'a mut std::collections::HashMap<(usize, u16), u32>,
152    regs: Vec<Reg>,
153    write_pc: Vec<u32>,
154    stmts: Vec<Stmt>,
155    pending_call: Option<Expr>,
156    code_units: u32,
157    /// Method-level feature flags, merged in place as features are seen
158    /// (build_block consumes the lifter, so the flags must escape via a
159    /// shared reference rather than a field read afterwards).
160    mflags: &'a mut MethodFlags,
161}
162
163impl<'a> Lifter<'a> {
164    pub fn new(
165        env: &'a MethodEnv<'a>,
166        vt: &'a mut VarTable,
167        in_regs: Vec<Reg>,
168        block_id: usize,
169        stable: &'a mut std::collections::HashMap<(usize, u16), u32>,
170        mflags: &'a mut MethodFlags,
171    ) -> Lifter<'a> {
172        let n = env.code.registers_size as usize;
173        let n = n.max(in_regs.len()).max(1);
174        let mut regs = in_regs;
175        regs.resize(n, Reg::Undef);
176        Lifter {
177            env,
178            vt,
179            regs,
180            write_pc: vec![0; n],
181            stmts: Vec::new(),
182            block_id,
183            stable,
184            pending_call: None,
185            code_units: env.code_units,
186            mflags,
187        }
188    }
189
190    // -- variable creation ---------------------------------------------------
191
192    fn fresh_var(&mut self, slot: u16, ty: TypeRef) -> u32 {
193        if let Some(&id) = self.stable.get(&(self.block_id, slot)) {
194            return id;
195        }
196        let id = self.vt.vars.len() as u32;
197        let name = format!("v{}", id);
198        self.stable.insert((self.block_id, slot), id);
199        self.vt.vars.push(VarInfo {
200            id,
201            slot,
202            name,
203            ty,
204            is_param: false,
205            range_start: 0,
206            range_end: u16::MAX,
207            synthetic_name: true,
208        });
209        while self.vt.by_slot.len() <= slot as usize {
210            self.vt.by_slot.push(Vec::new());
211        }
212        self.vt.by_slot[slot as usize].push((0, u16::MAX, id));
213        id
214    }
215
216    fn local_expr(&self, v: u32) -> Expr {
217        Expr::Local {
218            var: v,
219            ty: self.vt.var(v).ty.clone(),
220        }
221    }
222
223    // -- register access -----------------------------------------------------
224
225    /// Read for nesting into another expression (clones the value).
226    #[track_caller]
227    fn read_nest(&mut self, r: u16) -> Expr {
228        match self.regs.get(r as usize).cloned().unwrap_or(Reg::Undef) {
229            Reg::Undef | Reg::WideHi => {
230                // Reading a register never written on this path (or beyond
231                // the frame): a fresh (never-assigned) local keeps the
232                // output compilable.
233                let ty = TypeRef::J(JavaType::Int);
234                let v = self.fresh_var(r, ty);
235                if (r as usize) < self.regs.len() {
236                    self.regs[r as usize] = Reg::Live(v);
237                }
238                self.local_expr(v)
239            }
240            Reg::Live(v) => self.local_expr(v),
241            Reg::Pending(e) => e,
242            Reg::PendingCall(_) => {
243                let v = self.materialize(r);
244                self.local_expr(v)
245            }
246        }
247    }
248
249    /// Read for a context that evaluates exactly once (term payloads): the
250    /// value is consumed, not cloned — otherwise the block-exit
251    /// materialization would emit it a second time.
252    fn read_term(&mut self, r: u16) -> Expr {
253        match self.regs.get(r as usize).cloned().unwrap_or(Reg::Undef) {
254            Reg::PendingCall(e) | Reg::Pending(e) => {
255                if (r as usize) < self.regs.len() {
256                    self.regs[r as usize] = Reg::Undef;
257                }
258                self.pending_call = None;
259                e
260            }
261            _ => self.read_nest(r),
262        }
263    }
264
265    fn write(&mut self, r: u16, e: Expr, pc: u32, wide: bool) {
266        if r as usize >= self.regs.len() {
267            return;
268        }
269        self.write_pc[r as usize] = pc;
270        // Loop-carried Pending trees grow multiplicatively across fixpoint
271        // rounds (merge clones the whole tree per side per visit); past
272        // this size the register materializes, which caps every state at
273        // a bounded expression over stable locals.
274        if expr_size(&e) > 32 {
275            self.write_pc[r as usize] = pc;
276            let v = self.materialize_value(r, e);
277            if (r as usize) < self.regs.len() {
278                self.regs[r as usize] = Reg::Live(v);
279            }
280            if wide {
281                self.mark_wide_hi(r + 1, pc);
282            }
283            return;
284        }
285        self.regs[r as usize] = Reg::Pending(e);
286        if wide {
287            self.mark_wide_hi(r + 1, pc);
288        }
289    }
290
291    /// Materialize WITHOUT consuming the register slot's caller contract:
292    /// emit `v = e` and return the var id (used by the size cap).
293    fn materialize_value(&mut self, r: u16, e: Expr) -> u32 {
294        let e = value_of_cmp(&e);
295        let ty = e.type_ref();
296        let v = self.fresh_var(r, ty);
297        self.stmts.push(Stmt::LocalDef {
298            var: v,
299            init: Some(e),
300            is_final: false,
301            force_type: true,
302        });
303        v
304    }
305
306    /// Write a value whose evaluation already happened (invoke /
307    /// filled-new-array results): consumed exactly once.
308    fn write_call_result(&mut self, r: u16, e: Expr, pc: u32) {
309        if r as usize >= self.regs.len() {
310            return;
311        }
312        self.write_pc[r as usize] = pc;
313        self.regs[r as usize] = Reg::PendingCall(e);
314    }
315
316    fn mark_wide_hi(&mut self, r: u16, pc: u32) {
317        if (r as usize) < self.regs.len() {
318            self.regs[r as usize] = Reg::WideHi;
319            self.write_pc[r as usize] = pc;
320        }
321    }
322
323    /// Emit `v = expr` and mark the register `Live(v)`.
324    fn materialize(&mut self, r: u16) -> u32 {
325        let cur = self.regs.get(r as usize).cloned().unwrap_or(Reg::Undef);
326        let e = match cur {
327            Reg::Pending(e) | Reg::PendingCall(e) => e,
328            Reg::Live(v) => return v,
329            _ => Expr::Const(ConstVal::Int(0)),
330        };
331        // cmp sentinels stored as values become library compare calls.
332        let e = value_of_cmp(&e);
333        let ty = e.type_ref();
334        let v = self.fresh_var(r, ty);
335        self.stmts.push(Stmt::LocalDef {
336            var: v,
337            init: Some(e),
338            is_final: false,
339            force_type: true,
340        });
341        if (r as usize) < self.regs.len() {
342            self.regs[r as usize] = Reg::Live(v);
343        }
344        v
345    }
346
347    /// Discard an unconsumed call result (invoke without move-result).
348    fn drop_pending_call(&mut self) {
349        if let Some(e) = self.pending_call.take() {
350            let e = value_of_cmp(&e);
351            if has_side_effects(&e) {
352                self.stmts.push(Stmt::ExprStmt(e));
353            }
354        }
355    }
356
357    // -- helpers -------------------------------------------------------------
358
359    fn owner_expr(&self, obj: Expr, cls: &str) -> Option<Box<Expr>> {
360        if cls == self.env.class_name && matches!(obj, Expr::This) {
361            return None;
362        }
363        Some(Box::new(obj))
364    }
365
366    fn arith_op(op: ArithOp) -> BinOp {
367        match op {
368            ArithOp::Add => BinOp::Add,
369            ArithOp::Sub => BinOp::Sub,
370            ArithOp::Mul => BinOp::Mul,
371            ArithOp::Div => BinOp::Div,
372            ArithOp::Rem => BinOp::Rem,
373            ArithOp::And => BinOp::And,
374            ArithOp::Or => BinOp::Or,
375            ArithOp::Xor => BinOp::Xor,
376            ArithOp::Shl => BinOp::Shl,
377            ArithOp::Shr => BinOp::Shr,
378            ArithOp::Ushr => BinOp::Ushr,
379        }
380    }
381
382    fn cmp_op(op: CmpOp) -> BinOp {
383        match op {
384            CmpOp::Eq => BinOp::Eq,
385            CmpOp::Ne => BinOp::Ne,
386            CmpOp::Lt => BinOp::Lt,
387            CmpOp::Ge => BinOp::Ge,
388            CmpOp::Gt => BinOp::Gt,
389            CmpOp::Le => BinOp::Le,
390        }
391    }
392
393    fn cmp_sentinel(kind: CmpKind, l: Expr, r: Expr) -> Expr {
394        let name = match kind {
395            CmpKind::CmplF => "\0cmpl-float",
396            CmpKind::CmpgF => "\0cmpg-float",
397            CmpKind::CmplD => "\0cmpl-double",
398            CmpKind::CmpgD => "\0cmpg-double",
399            CmpKind::CmpJ => "\0cmp-long",
400        };
401        Expr::Invokedynamic {
402            name: name.into(),
403            desc: MethodDescriptor {
404                args: vec![],
405                ret: JavaType::Int,
406            },
407            args: vec![l, r],
408            bsm_text: String::new(),
409            bsm_static_args: vec![],
410        }
411    }
412
413    // -- the main loop ---------------------------------------------------------
414
415    /// Lift one block. `handler_types` gives catch types for ranges this
416    /// block handles (for move-exception typing).
417    pub fn build_block(
418        mut self,
419        ins: &[Insn],
420        handler_types: &[Option<String>],
421    ) -> BResult<(BlockResult, OutState)> {
422        let payloads = &self.env.code.payloads;
423        for ins in ins {
424            match &ins.kind {
425                InsnKind::Nop => {}
426                InsnKind::Unknown => {
427                    return Err(format!(
428                        "unsupported opcode {:#04x} ({}) at pc {}",
429                        ins.op,
430                        ddc_dex::insn::op_name(ins.op),
431                        ins.pc
432                    ));
433                }
434                InsnKind::Move { dst, src } => {
435                    // Impure / call values are consumed once: materialize the
436                    // source so both registers share one evaluation.
437                    let src_state = self.regs.get(*src as usize).cloned().unwrap_or(Reg::Undef);
438                    if matches!(src_state, Reg::PendingCall(_))
439                        || matches!(&src_state, Reg::Pending(e) if has_side_effects(e))
440                    {
441                        self.materialize(*src);
442                    }
443                    let st = self.regs.get(*src as usize).cloned().unwrap_or(Reg::Undef);
444                    let wide = reg_is_wide(&st, self.vt);
445                    if (*dst as usize) < self.regs.len() {
446                        self.regs[*dst as usize] = st;
447                        self.write_pc[*dst as usize] =
448                            self.write_pc.get(*src as usize).copied().unwrap_or(0);
449                    }
450                    if wide {
451                        self.mark_wide_hi(*dst + 1, ins.pc);
452                    }
453                }
454                InsnKind::MoveResult { dst } => {
455                    if let Some(e) = self.pending_call.take() {
456                        let wide =
457                            matches!(e.type_ref().erased(), JavaType::Long | JavaType::Double);
458                        self.write_call_result(*dst, e, ins.pc);
459                        if wide {
460                            self.mark_wide_hi(*dst + 1, ins.pc);
461                        }
462                    } else {
463                        // move-result without a pending call: fresh var.
464                        let v = self.fresh_var(*dst, TypeRef::J(JavaType::Int));
465                        if (*dst as usize) < self.regs.len() {
466                            self.regs[*dst as usize] = Reg::Live(v);
467                        }
468                    }
469                }
470                InsnKind::MoveException { dst } => {
471                    let ty = handler_types
472                        .iter()
473                        .flatten()
474                        .next()
475                        .map(|t| TypeRef::J(JavaType::Object(t.clone())))
476                        .unwrap_or_else(|| {
477                            TypeRef::J(JavaType::Object("java/lang/Throwable".into()))
478                        });
479                    let v = self.fresh_var(*dst, ty);
480                    self.stmts.push(Stmt::LocalDef {
481                        var: v,
482                        init: None,
483                        is_final: false,
484                        force_type: true,
485                    });
486                    if (*dst as usize) < self.regs.len() {
487                        self.regs[*dst as usize] = Reg::Live(v);
488                    }
489                }
490                InsnKind::ReturnVoid
491                | InsnKind::Return { .. }
492                | InsnKind::Throw { .. }
493                | InsnKind::Goto { .. }
494                | InsnKind::PackedSwitch { .. }
495                | InsnKind::SparseSwitch { .. }
496                | InsnKind::If { .. } => {}
497                InsnKind::Const { dst, val, wide } => {
498                    let e = if *wide {
499                        Expr::Const(ConstVal::Long(*val))
500                    } else {
501                        Expr::Const(ConstVal::Int(*val as i32))
502                    };
503                    self.write(*dst, e, ins.pc, *wide);
504                }
505                InsnKind::ConstString { dst, str_idx } => {
506                    let s = self.env.dex.string(*str_idx).to_string();
507                    self.write(*dst, Expr::Const(ConstVal::Str(s)), ins.pc, false);
508                }
509                InsnKind::ConstClass { dst, type_idx } => {
510                    let ty = TypeRef::J(self.env.java_type(*type_idx));
511                    self.write(*dst, Expr::Const(ConstVal::ClassLit(ty)), ins.pc, false);
512                }
513                InsnKind::MonitorEnter { reg } => {
514                    let e = self.read_nest(*reg);
515                    *self.mflags = self.mflags.with_monitor();
516                    self.stmts.push(Stmt::MonitorEnter(e));
517                }
518                InsnKind::MonitorExit { reg } => {
519                    let e = self.read_nest(*reg);
520                    *self.mflags = self.mflags.with_monitor();
521                    self.stmts.push(Stmt::MonitorExit(e));
522                }
523                InsnKind::CheckCast { reg, type_idx } => {
524                    let e = self.read_nest(*reg);
525                    let ty = TypeRef::J(self.env.java_type(*type_idx));
526                    self.write(*reg, Expr::Cast { ty, e: Box::new(e) }, ins.pc, false);
527                }
528                InsnKind::InstanceOf { dst, src, type_idx } => {
529                    let e = self.read_nest(*src);
530                    let ty = TypeRef::J(self.env.java_type(*type_idx));
531                    self.write(*dst, Expr::InstanceOf { e: Box::new(e), ty }, ins.pc, false);
532                }
533                InsnKind::ArrayLength { dst, src } => {
534                    let e = self.read_nest(*src);
535                    let length = Expr::Field {
536                        owner: Some(Box::new(e)),
537                        cls: String::new(),
538                        name: "length".into(),
539                        ty: TypeRef::J(JavaType::Int),
540                        is_static: false,
541                    };
542                    self.write(*dst, length, ins.pc, false);
543                }
544                InsnKind::NewInstance { dst, type_idx } => {
545                    let cls = self.env.dex.class_name(*type_idx);
546                    let ty = TypeRef::J(JavaType::Object(cls.clone()));
547                    self.write(
548                        *dst,
549                        Expr::New {
550                            cls,
551                            ty,
552                            args: vec![],
553                            raw: true,
554                        },
555                        ins.pc,
556                        false,
557                    );
558                }
559                InsnKind::NewArray {
560                    dst,
561                    size,
562                    type_idx,
563                } => {
564                    let elem_desc = self.env.type_name(*type_idx);
565                    // The type id names the ARRAY type: strip one `[`.
566                    let elem = crate::desc_type(elem_desc.trim_start_matches('['));
567                    let dims = self.read_nest(*size);
568                    self.write(
569                        *dst,
570                        Expr::NewArray {
571                            elem: TypeRef::J(elem),
572                            dims: vec![dims],
573                            trailing_dims: 0,
574                            init: None,
575                        },
576                        ins.pc,
577                        false,
578                    );
579                }
580                InsnKind::FilledNewArray { regs, type_idx } => {
581                    self.drop_pending_call();
582                    let elem_desc = self.env.type_name(*type_idx);
583                    let elem = crate::desc_type(elem_desc.trim_start_matches('['));
584                    let args: Vec<Expr> = regs.iter().map(|&r| self.read_nest(r)).collect();
585                    self.pending_call = Some(Expr::NewArray {
586                        elem: TypeRef::J(elem),
587                        dims: vec![],
588                        trailing_dims: 0,
589                        init: Some(args),
590                    });
591                }
592                InsnKind::FillArrayData { reg, payload_pc } => {
593                    if let Some(Payload::ArrayData {
594                        elem_width,
595                        size,
596                        data,
597                    }) = payloads.get(payload_pc)
598                    {
599                        let elems = payload_consts(*elem_width, *size, data);
600                        let cur = self.regs.get(*reg as usize).cloned().unwrap_or(Reg::Undef);
601                        if let Reg::Pending(Expr::NewArray { elem, init, .. }) = cur {
602                            if init.is_none() {
603                                self.write(
604                                    *reg,
605                                    Expr::NewArray {
606                                        elem,
607                                        dims: vec![Expr::Const(ConstVal::Int(*size as i32))],
608                                        trailing_dims: 0,
609                                        init: Some(elems),
610                                    },
611                                    ins.pc,
612                                    false,
613                                );
614                                continue;
615                            }
616                        }
617                        // Not the new-array+fill pattern: element stores.
618                        let arr = self.read_nest(*reg);
619                        for (i, e) in elems.into_iter().enumerate() {
620                            let idx = Expr::Const(ConstVal::Int(i as i32));
621                            let target = Expr::ArrayIndex {
622                                array: Box::new(arr.clone()),
623                                index: Box::new(idx),
624                            };
625                            self.stmts.push(Stmt::ExprStmt(Expr::Assign {
626                                target: Box::new(target),
627                                op: AssignOp::Plain,
628                                value: Box::new(e),
629                            }));
630                        }
631                    }
632                }
633                InsnKind::Cmp { dst, a, b, kind } => {
634                    let l = self.read_nest(*a);
635                    let r = self.read_nest(*b);
636                    *self.mflags = self.mflags.with_cmp();
637                    self.write(*dst, Self::cmp_sentinel(*kind, l, r), ins.pc, false);
638                }
639                InsnKind::AGet {
640                    dst, array, index, ..
641                } => {
642                    let a = self.read_nest(*array);
643                    let i = self.read_nest(*index);
644                    self.write(
645                        *dst,
646                        Expr::ArrayIndex {
647                            array: Box::new(a),
648                            index: Box::new(i),
649                        },
650                        ins.pc,
651                        false,
652                    );
653                }
654                InsnKind::APut {
655                    value,
656                    array,
657                    index,
658                    ..
659                } => {
660                    self.drop_pending_call();
661                    let v = self.read_nest(*value);
662                    let a = self.read_nest(*array);
663                    let i = self.read_nest(*index);
664                    let target = Expr::ArrayIndex {
665                        array: Box::new(a),
666                        index: Box::new(i),
667                    };
668                    self.stmts.push(Stmt::ExprStmt(Expr::Assign {
669                        target: Box::new(target),
670                        op: AssignOp::Plain,
671                        value: Box::new(v),
672                    }));
673                }
674                InsnKind::IGet {
675                    dst,
676                    obj,
677                    field_idx,
678                } => {
679                    self.drop_pending_call();
680                    let (cls, name, ty) = self.env.field_ref(*field_idx);
681                    let owner = self.read_nest(*obj);
682                    let owner_opt = self.owner_expr(owner, &cls);
683                    self.write(
684                        *dst,
685                        Expr::Field {
686                            owner: owner_opt,
687                            cls,
688                            name,
689                            ty: TypeRef::J(ty),
690                            is_static: false,
691                        },
692                        ins.pc,
693                        false,
694                    );
695                }
696                InsnKind::IPut {
697                    value,
698                    obj,
699                    field_idx,
700                } => {
701                    self.drop_pending_call();
702                    let (cls, name, ty) = self.env.field_ref(*field_idx);
703                    let v = self.read_nest(*value);
704                    let owner = self.read_nest(*obj);
705                    let owner_opt = self.owner_expr(owner, &cls);
706                    let target = Expr::Field {
707                        owner: owner_opt,
708                        cls,
709                        name,
710                        ty: TypeRef::J(ty),
711                        is_static: false,
712                    };
713                    self.stmts.push(Stmt::ExprStmt(Expr::Assign {
714                        target: Box::new(target),
715                        op: AssignOp::Plain,
716                        value: Box::new(v),
717                    }));
718                }
719                InsnKind::SGet { dst, field_idx } => {
720                    self.drop_pending_call();
721                    let (cls, name, ty) = self.env.field_ref(*field_idx);
722                    self.write(
723                        *dst,
724                        Expr::Field {
725                            owner: None,
726                            cls,
727                            name,
728                            ty: TypeRef::J(ty),
729                            is_static: true,
730                        },
731                        ins.pc,
732                        false,
733                    );
734                }
735                InsnKind::SPut { value, field_idx } => {
736                    self.drop_pending_call();
737                    let (cls, name, ty) = self.env.field_ref(*field_idx);
738                    let v = self.read_nest(*value);
739                    let target = Expr::Field {
740                        owner: None,
741                        cls,
742                        name,
743                        ty: TypeRef::J(ty),
744                        is_static: true,
745                    };
746                    self.stmts.push(Stmt::ExprStmt(Expr::Assign {
747                        target: Box::new(target),
748                        op: AssignOp::Plain,
749                        value: Box::new(v),
750                    }));
751                }
752                InsnKind::Invoke {
753                    kind,
754                    regs,
755                    method_idx,
756                } => {
757                    self.drop_pending_call();
758                    self.do_invoke(*kind, regs, *method_idx, ins.pc)?;
759                }
760                InsnKind::InvokeCustom {
761                    call_site_idx,
762                    regs,
763                } => {
764                    self.drop_pending_call();
765                    let e = self.build_invoke_custom(*call_site_idx, regs, ins.pc);
766                    self.pending_call = Some(e);
767                }
768                InsnKind::Un {
769                    dst,
770                    src,
771                    op,
772                    from: _,
773                    to,
774                } => {
775                    let e = self.read_nest(*src);
776                    let ne = match op {
777                        ddc_dex::insn::UnArith::Neg => Expr::Un {
778                            op: UnOp::Neg,
779                            e: Box::new(e),
780                        },
781                        ddc_dex::insn::UnArith::Not => Expr::Un {
782                            op: UnOp::BitNot,
783                            e: Box::new(e),
784                        },
785                        ddc_dex::insn::UnArith::Conv => Expr::Cast {
786                            ty: TypeRef::J(primitive_type(*to)),
787                            e: Box::new(e),
788                        },
789                    };
790                    let wide = matches!(*to, 'J' | 'D');
791                    self.write(*dst, ne, ins.pc, wide);
792                }
793                InsnKind::Bin { op, dst, a, b, ty } => {
794                    let l = self.read_nest(*a);
795                    let r = self.read_nest(*b);
796                    let jt = primitive_type(*ty);
797                    let e = Expr::Bin {
798                        op: Self::arith_op(*op),
799                        l: Box::new(l),
800                        r: Box::new(r),
801                        ty: Some(TypeRef::J(jt)),
802                    };
803                    let wide = matches!(*ty, 'J' | 'D');
804                    self.write(*dst, e, ins.pc, wide);
805                }
806                InsnKind::BinLit {
807                    op,
808                    dst,
809                    a,
810                    lit,
811                    rsub,
812                } => {
813                    let av = self.read_nest(*a);
814                    let e = if *rsub {
815                        Expr::Bin {
816                            op: BinOp::Sub,
817                            l: Box::new(Expr::Const(ConstVal::Int(*lit))),
818                            r: Box::new(av),
819                            ty: Some(TypeRef::J(JavaType::Int)),
820                        }
821                    } else {
822                        Expr::Bin {
823                            op: Self::arith_op(*op),
824                            l: Box::new(av),
825                            r: Box::new(Expr::Const(ConstVal::Int(*lit))),
826                            ty: Some(TypeRef::J(JavaType::Int)),
827                        }
828                    };
829                    self.write(*dst, e, ins.pc, false);
830                }
831                InsnKind::ConstMethodHandle { dst, handle_idx } => {
832                    self.drop_pending_call();
833                    let e = self.method_handle_expr(*handle_idx);
834                    self.write(*dst, e, ins.pc, false);
835                }
836                InsnKind::ConstMethodType { dst, proto_idx } => {
837                    self.drop_pending_call();
838                    let proto = self.env.dex.proto(*proto_idx);
839                    let desc = format!(
840                        "({}){}",
841                        self.env
842                            .dex
843                            .proto_params(*proto_idx)
844                            .into_iter()
845                            .map(|t| self.env.dex.type_name(t))
846                            .collect::<Vec<_>>()
847                            .join(""),
848                        self.env.dex.type_name(proto.return_type_idx)
849                    );
850                    self.write(*dst, Expr::Raw(desc), ins.pc, false);
851                }
852            }
853        }
854
855        // Terminator first: terms may consume PendingCall values directly.
856        let term = self.block_term(ins);
857
858        // Exit cleanup: unconsumed call → statement; impure pendings → vars.
859        // Open `new`/`new-array` views stay pending (their consumer may live
860        // in a later block).
861        self.drop_pending_call();
862        self.materialize_impure_at_exit();
863
864        // Move (not clone): the lifter is consumed after build_block, and
865        // these two clones — regs deep-copies boxed Exprs for any Pending
866        // states — ran 3.7M times on weibo.
867        let out = OutState {
868            regs: std::mem::take(&mut self.regs),
869            write_pc: std::mem::take(&mut self.write_pc),
870        };
871        Ok((
872            BlockResult {
873                stmts: std::mem::take(&mut self.stmts),
874                out_stack: Vec::new(),
875                term,
876            },
877            out,
878        ))
879    }
880
881    fn do_invoke(
882        &mut self,
883        kind: InvokeKind,
884        regs: &[u16],
885        method_idx: u32,
886        pc: u32,
887    ) -> BResult<()> {
888        let (cls, name, md) = self.env.method_ref(method_idx);
889        if cls == "java/lang/StringBuilder" || cls == "java/lang/StringBuffer" {
890            *self.mflags = self.mflags.with_sb();
891        }
892        let is_static = matches!(kind, InvokeKind::Static);
893        let receiver_reg = if is_static {
894            None
895        } else {
896            regs.first().copied()
897        };
898        let arg_regs: Vec<u16> = if is_static {
899            regs.to_vec()
900        } else {
901            regs.iter().skip(1).copied().collect()
902        };
903        let mut args: Vec<Expr> = Vec::with_capacity(md.args.len());
904        for (i, _) in md.args.iter().enumerate() {
905            let r = arg_regs.get(i).copied().unwrap_or(0);
906            args.push(self.read_nest(r));
907        }
908        let recv_expr = match receiver_reg {
909            Some(r) => Some(self.read_nest(r)),
910            None => None,
911        };
912
913        // Constructor call: fold `new C` receivers; this/super otherwise.
914        if name == "<init>" && matches!(kind, InvokeKind::Direct) {
915            let recv_reg = receiver_reg.unwrap_or(0);
916            let recv_state = self
917                .regs
918                .get(recv_reg as usize)
919                .cloned()
920                .unwrap_or(Reg::Undef);
921            if let Reg::Pending(Expr::New {
922                raw: true, cls: nc, ..
923            }) = &recv_state
924            {
925                if nc == &cls {
926                    let ty = TypeRef::J(JavaType::Object(cls.clone()));
927                    self.write(
928                        recv_reg,
929                        Expr::New {
930                            cls: cls.clone(),
931                            ty,
932                            args,
933                            raw: false,
934                        },
935                        pc,
936                        false,
937                    );
938                    return Ok(());
939                }
940            }
941            let owner_expr_v = recv_expr.unwrap_or(Expr::This);
942            let is_super = cls != self.env.class_name;
943            let owner = if matches!(owner_expr_v, Expr::This) && is_super {
944                None
945            } else {
946                Some(Box::new(owner_expr_v))
947            };
948            let call = Expr::Method {
949                owner,
950                cls,
951                name: "<init>".into(),
952                desc: md,
953                args,
954                is_static: false,
955                is_interface: false,
956                is_special: true,
957                is_super,
958                is_dynamic: false,
959                type_args: vec![],
960            };
961            self.stmts.push(Stmt::ExprStmt(call));
962            return Ok(());
963        }
964
965        let is_interface = matches!(kind, InvokeKind::Interface);
966        let is_super = match kind {
967            InvokeKind::Super => true,
968            InvokeKind::Direct if !is_static => cls != self.env.class_name,
969            _ => false,
970        };
971        let owner: Option<Box<Expr>> = match recv_expr {
972            None => None,
973            Some(o) => {
974                if is_super && matches!(o, Expr::This) {
975                    None
976                } else {
977                    self.owner_expr(o, &cls)
978                }
979            }
980        };
981        let is_dynamic = matches!(kind, InvokeKind::Polymorphic);
982        let call = Expr::Method {
983            owner,
984            cls,
985            name,
986            desc: md,
987            args,
988            is_static,
989            is_interface,
990            is_special: matches!(kind, InvokeKind::Direct),
991            is_super,
992            is_dynamic,
993            type_args: vec![],
994        };
995        self.pending_call = Some(call);
996        Ok(())
997    }
998
999    /// Resolve a method-handle constant into a readable expression
1000    /// (`Cls::name` for method kinds).
1001    fn method_handle_expr(&mut self, handle_idx: u32) -> Expr {
1002        let Some(h) = self.env.dex.method_handle(handle_idx) else {
1003            return Expr::Raw(format!("methodHandle@{}", handle_idx));
1004        };
1005        if h.is_field {
1006            let f = self.env.dex.field(h.target_id);
1007            let cls = self.env.dex.class_name(f.class_idx);
1008            let name = self.env.dex.string(f.name_idx).to_string();
1009            return Expr::Raw(format!("{}::{}", cls.replace('/', "."), name));
1010        }
1011        let m = self.env.dex.method(h.target_id);
1012        let cls = self.env.dex.class_name(m.class_idx);
1013        let name = self.env.dex.string(m.name_idx).to_string();
1014        Expr::Raw(format!("{}::{}", cls.replace('/', "."), name))
1015    }
1016
1017    /// Build the expression for an invoke-custom site.
1018    fn build_invoke_custom(&mut self, cs_idx: u32, regs: &[u16], pc: u32) -> Expr {
1019        let Some(cs) = self.env.dex.call_site(cs_idx).cloned() else {
1020            return Expr::Invokedynamic {
1021                name: format!("call-site#{}", cs_idx),
1022                desc: jdc_core::types::MethodDescriptor {
1023                    args: vec![],
1024                    ret: JavaType::Object("java/lang/Object".into()),
1025                },
1026                args: vec![],
1027                bsm_text: "unresolved".into(),
1028                bsm_static_args: vec![],
1029            };
1030        };
1031        let site_name = self.env.dex.string(cs.name_idx).to_string();
1032        let params: Vec<JavaType> = self
1033            .env
1034            .dex
1035            .proto_params(cs.proto_idx)
1036            .into_iter()
1037            .map(|t| self.env.java_type(t))
1038            .collect();
1039        let ret = self
1040            .env
1041            .java_type(self.env.dex.proto(cs.proto_idx).return_type_idx);
1042
1043        let (bs_cls, bs_name) = match self.env.dex.method_handle(cs.bootstrap_handle) {
1044            Some(h) if !h.is_field => {
1045                let m = self.env.dex.method(h.target_id);
1046                (
1047                    self.env.dex.class_name(m.class_idx),
1048                    self.env.dex.string(m.name_idx).to_string(),
1049                )
1050            }
1051            _ => (String::new(), String::new()),
1052        };
1053
1054        // Dynamic arguments (the SAM parameters for lambdas).
1055        let mut args: Vec<Expr> = Vec::with_capacity(params.len().min(regs.len()));
1056        for (i, _) in params.iter().enumerate() {
1057            if let Some(&r) = regs.get(i) {
1058                args.push(self.read_nest(r));
1059            }
1060        }
1061
1062        // StringConcatFactory: fold the recipe into `+`.
1063        if bs_name == "makeConcatWithConstants" {
1064            let recipe = cs
1065                .linker_args
1066                .iter()
1067                .find_map(|v| match v {
1068                    ddc_dex::annotations::EncodedValue::String(s) => {
1069                        Some(self.env.dex.string(*s).to_string())
1070                    }
1071                    _ => None,
1072                })
1073                .unwrap_or_default();
1074            return concat_from_recipe(&recipe, args);
1075        }
1076
1077        // LambdaMetafactory: linker args are [samType (erased),
1078        // implMethod (handle), instantiatedMethodType]. The instantiated
1079        // type carries the source-level SAM parameters.
1080        let mut impl_m = None;
1081        let mut instantiated_proto: Option<u32> = None;
1082        if bs_name == "metafactory" || bs_name == "altMetafactory" {
1083            let mut seen_handle = false;
1084            for lv in &cs.linker_args {
1085                match lv {
1086                    ddc_dex::annotations::EncodedValue::MethodHandle(hi) => {
1087                        if !seen_handle {
1088                            seen_handle = true;
1089                            if let Some(h) = self.env.dex.method_handle(*hi) {
1090                                if !h.is_field {
1091                                    let m = self.env.dex.method(h.target_id);
1092                                    impl_m = Some((
1093                                        self.env.dex.class_name(m.class_idx).replace('/', "."),
1094                                        self.env.dex.string(m.name_idx).to_string(),
1095                                    ));
1096                                }
1097                            }
1098                        }
1099                    }
1100                    ddc_dex::annotations::EncodedValue::MethodType(p) => {
1101                        if seen_handle {
1102                            instantiated_proto = Some(*p);
1103                        }
1104                    }
1105                    _ => {}
1106                }
1107            }
1108        }
1109        let _ = pc;
1110
1111        // Remaining registers are the captured values.
1112        let mut captures = Vec::new();
1113        for &r in regs.iter().skip(params.len().min(regs.len())) {
1114            captures.push(self.read_nest(r));
1115        }
1116
1117        if let Some((impl_cls, impl_name)) = impl_m {
1118            if captures.is_empty() {
1119                // Non-capturing: render real Java syntax. A synthetic
1120                // `lambda$` impl is a lambda; anything else is a method
1121                // reference (`Cls::name`).
1122                if impl_name.starts_with("lambda$") {
1123                    // Source-level parameter count comes from the
1124                    // instantiated SAM type, not the site proto (which is
1125                    // the functional-interface factory view: `()Function`).
1126                    let n_params = match instantiated_proto {
1127                        Some(p) => self.env.dex.proto_params(p).len(),
1128                        None => params.len(),
1129                    };
1130                    let pnames: Vec<String> = (0..n_params).map(|i| format!("a{}", i)).collect();
1131                    return Expr::Raw(format!(
1132                        "({}) -> {}.{}({})",
1133                        pnames.join(", "),
1134                        impl_cls,
1135                        impl_name,
1136                        pnames.join(", ")
1137                    ));
1138                }
1139                return Expr::Raw(format!("{}::{}", impl_cls, impl_name));
1140            }
1141            // Capturing: the captures are real expressions; emit the
1142            // desugared call shape (impl applies SAM params then captures).
1143            let mut call_args = args;
1144            call_args.extend(captures);
1145            return Expr::Invokedynamic {
1146                name: format!("{} -> {}.{}", site_name, impl_cls, impl_name),
1147                desc: jdc_core::types::MethodDescriptor { args: params, ret },
1148                args: call_args,
1149                bsm_text: format!("{}::{}", bs_cls, bs_name),
1150                bsm_static_args: vec![],
1151            };
1152        }
1153
1154        Expr::Invokedynamic {
1155            name: site_name,
1156            desc: jdc_core::types::MethodDescriptor { args: params, ret },
1157            args: captures,
1158            bsm_text: format!("{}::{}", bs_cls, bs_name),
1159            bsm_static_args: vec![],
1160        }
1161    }
1162
1163    fn materialize_impure_at_exit(&mut self) {
1164        let n = self.regs.len();
1165        for r in 0..n {
1166            let st = self.regs[r].clone();
1167            let impure = match &st {
1168                Reg::PendingCall(_) => true,
1169                Reg::Pending(e) => {
1170                    has_side_effects(e)
1171                        && !matches!(e, Expr::New { raw: true, .. })
1172                        && !matches!(e, Expr::NewArray { init: None, .. })
1173                }
1174                _ => false,
1175            };
1176            if impure {
1177                self.materialize(r as u16);
1178            }
1179        }
1180    }
1181
1182    /// Terminator for the block, from its last instruction.
1183    fn block_term(&mut self, ins: &[Insn]) -> Term {
1184        let Some(last) = ins.last() else {
1185            return Term::Goto;
1186        };
1187        match &last.kind {
1188            InsnKind::Goto { .. } => Term::Goto,
1189            InsnKind::ReturnVoid => Term::Return(None),
1190            InsnKind::Return { src } => Term::Return(Some(self.read_term(*src))),
1191            InsnKind::Throw { reg } => Term::Throw(self.read_term(*reg)),
1192            InsnKind::If { op, a, b, z, .. } => {
1193                let cond = if *z {
1194                    let v = self.read_nest(*a);
1195                    let (l, r, realop) = jdc_core::ir::build::unfold_cmp(v, Self::cmp_op(*op));
1196                    Expr::Bin {
1197                        op: realop,
1198                        l: Box::new(l),
1199                        r: Box::new(r),
1200                        ty: Some(TypeRef::J(JavaType::Boolean)),
1201                    }
1202                } else {
1203                    let l = self.read_nest(*a);
1204                    let r = self.read_nest(*b);
1205                    Expr::Bin {
1206                        op: Self::cmp_op(*op),
1207                        l: Box::new(l),
1208                        r: Box::new(r),
1209                        ty: Some(TypeRef::J(JavaType::Boolean)),
1210                    }
1211                };
1212                Term::Cond { cond }
1213            }
1214            InsnKind::PackedSwitch { reg, payload_pc }
1215            | InsnKind::SparseSwitch { reg, payload_pc } => {
1216                let selector = self.read_term(*reg);
1217                let default = Some(last.pc + last.size);
1218                let targets = match self.env.code.payloads.get(payload_pc) {
1219                    Some(Payload::Packed { first_key, targets }) => SwitchTargets::Table {
1220                        low: *first_key,
1221                        targets: targets
1222                            .iter()
1223                            .map(|t| (last.pc as i64 + *t as i64) as u32)
1224                            .collect(),
1225                    },
1226                    Some(Payload::Sparse { pairs }) => SwitchTargets::Lookup {
1227                        pairs: pairs
1228                            .iter()
1229                            .map(|(k, t)| (*k, (last.pc as i64 + *t as i64) as u32))
1230                            .collect(),
1231                    },
1232                    _ => SwitchTargets::Lookup { pairs: vec![] },
1233                };
1234                Term::Switch {
1235                    selector,
1236                    targets,
1237                    default,
1238                }
1239            }
1240            // Fell off the analysis end (payload region after): goto.
1241            _ => {
1242                let next = last.pc + last.size;
1243                if next < self.code_units {
1244                    Term::Fallthrough
1245                } else {
1246                    Term::Goto
1247                }
1248            }
1249        }
1250    }
1251}
1252
1253/// StringConcatFactory recipe → `+` parts: `\u{1}` slots consume the next
1254/// argument, `\u{2}` <len> <chars> emits literal text (rare), other bytes
1255/// are literal.
1256fn concat_from_recipe(recipe: &str, args: Vec<Expr>) -> Expr {
1257    use jdc_core::ir::expr::ConcatPart;
1258    let mut parts: Vec<ConcatPart> = Vec::new();
1259    let mut arg_i = 0usize;
1260    let mut const_buf = String::new();
1261    for c in recipe.chars() {
1262        match c {
1263            '\u{1}' => {
1264                if !const_buf.is_empty() {
1265                    parts.push(ConcatPart::Const(std::mem::take(&mut const_buf)));
1266                }
1267                if arg_i < args.len() {
1268                    parts.push(ConcatPart::Str(args[arg_i].clone()));
1269                    arg_i += 1;
1270                }
1271            }
1272            '\u{2}' => {
1273                // Length-prefixed literal: skip the length char and copy the
1274                // following chars until the next marker.
1275            }
1276            c => const_buf.push(c),
1277        }
1278    }
1279    if !const_buf.is_empty() {
1280        parts.push(ConcatPart::Const(const_buf));
1281    }
1282    Expr::StringConcat(parts)
1283}
1284
1285/// Node count of an expression tree (caps loop-carried growth: a Pending
1286/// tree that grows across fixpoint rounds would otherwise reach GBs — each
1287/// merge clones the whole tree per side per visit).
1288fn expr_size(e: &Expr) -> usize {
1289    let mut n = 0usize;
1290    count_nodes(e, &mut n);
1291    n
1292}
1293
1294fn count_nodes(e: &Expr, n: &mut usize) {
1295    *n += 1;
1296    if *n > 512 {
1297        return; // cap the walk itself
1298    }
1299    match e {
1300        Expr::Un { e, .. } | Expr::Cast { e, .. } | Expr::InstanceOf { e, .. } => count_nodes(e, n),
1301        Expr::Bin { l, r, .. } => {
1302            count_nodes(l, n);
1303            count_nodes(r, n);
1304        }
1305        Expr::Cond { c, t, f } => {
1306            count_nodes(c, n);
1307            count_nodes(t, n);
1308            count_nodes(f, n);
1309        }
1310        Expr::Assign { target, value, .. } => {
1311            count_nodes(target, n);
1312            count_nodes(value, n);
1313        }
1314        Expr::PreIncDec { e, .. } | Expr::PostIncDec { e, .. } => count_nodes(e, n),
1315        Expr::Field { owner: Some(o), .. } => count_nodes(o, n),
1316        Expr::Method {
1317            owner: Some(o),
1318            args,
1319            ..
1320        } => {
1321            count_nodes(o, n);
1322            for a in args {
1323                count_nodes(a, n);
1324            }
1325        }
1326        Expr::ArrayIndex { array, index } => {
1327            count_nodes(array, n);
1328            count_nodes(index, n);
1329        }
1330        Expr::New { args, .. } => {
1331            for a in args {
1332                count_nodes(a, n);
1333            }
1334        }
1335        Expr::NewArray { dims, init, .. } => {
1336            for d in dims {
1337                count_nodes(d, n);
1338            }
1339            if let Some(v) = init {
1340                for x in v {
1341                    count_nodes(x, n);
1342                }
1343            }
1344        }
1345        Expr::NewMultiArray { dims, .. } => {
1346            for d in dims {
1347                count_nodes(d, n);
1348            }
1349        }
1350        Expr::StringConcat(parts) => {
1351            for p in parts {
1352                if let ConcatPart::Str(x) = p {
1353                    count_nodes(x, n);
1354                }
1355            }
1356        }
1357        _ => {}
1358    }
1359}
1360
1361fn reg_is_wide(st: &Reg, vt: &VarTable) -> bool {
1362    match st {
1363        Reg::Pending(e) | Reg::PendingCall(e) => e.type_ref().erased().is_wide(),
1364        Reg::Live(v) => vt.var(*v).ty.erased().is_wide(),
1365        Reg::WideHi => true,
1366        Reg::Undef => false,
1367    }
1368}
1369
1370/// Turn a stored cmp sentinel into a `Long.compare`-style call.
1371fn value_of_cmp(e: &Expr) -> Expr {
1372    if let Expr::Invokedynamic { name, args, .. } = e {
1373        if name.starts_with('\0') && args.len() == 2 {
1374            let (cls, ty) = match name.as_str() {
1375                "\0cmp-long" => ("java/lang/Long", JavaType::Long),
1376                "\0cmpl-float" | "\0cmpg-float" => ("java/lang/Float", JavaType::Float),
1377                _ => ("java/lang/Double", JavaType::Double),
1378            };
1379            let desc = MethodDescriptor {
1380                args: vec![ty.clone(), ty],
1381                ret: JavaType::Int,
1382            };
1383            return Expr::Method {
1384                owner: None,
1385                cls: cls.into(),
1386                name: "compare".into(),
1387                desc,
1388                args: args.clone(),
1389                is_static: true,
1390                is_interface: false,
1391                is_special: false,
1392                is_super: false,
1393                is_dynamic: false,
1394                type_args: vec![],
1395            };
1396        }
1397    }
1398    e.clone()
1399}
1400
1401pub fn primitive_type(c: char) -> JavaType {
1402    match c {
1403        'Z' => JavaType::Boolean,
1404        'B' => JavaType::Byte,
1405        'C' => JavaType::Char,
1406        'S' => JavaType::Short,
1407        'F' => JavaType::Float,
1408        'J' => JavaType::Long,
1409        'D' => JavaType::Double,
1410        _ => JavaType::Int,
1411    }
1412}
1413
1414/// Constant elements from a fill-array-data payload, typed by width (the
1415/// consuming new-array's element type refines them later).
1416fn payload_consts(elem_width: u16, size: u32, data: &[u8]) -> Vec<Expr> {
1417    let n = (size as usize).min(data.len() / elem_width.max(1) as usize);
1418    let mut out = Vec::with_capacity(n);
1419    for i in 0..n {
1420        let off = i * elem_width as usize;
1421        let e = match elem_width {
1422            1 => Expr::Const(ConstVal::Int(data[off] as i8 as i32)),
1423            2 => {
1424                let v = i16::from_le_bytes([data[off], data[off + 1]]);
1425                Expr::Const(ConstVal::Int(v as i32))
1426            }
1427            4 => {
1428                let mut b = [0u8; 4];
1429                b.copy_from_slice(&data[off..off + 4]);
1430                Expr::Const(ConstVal::Int(i32::from_le_bytes(b)))
1431            }
1432            8 => {
1433                let mut b = [0u8; 8];
1434                b.copy_from_slice(&data[off..off + 8]);
1435                Expr::Const(ConstVal::Long(i64::from_le_bytes(b)))
1436            }
1437            _ => Expr::Const(ConstVal::Int(0)),
1438        };
1439        out.push(e);
1440    }
1441    out
1442}
1443
1444/// Entry register state from parameters. Returns the register state.
1445pub fn entry_regs(vt: &mut VarTable, env: &MethodEnv, param_names: &[Option<String>]) -> Vec<Reg> {
1446    let code = env.code;
1447    let n = code.registers_size as usize;
1448    let mut regs = vec![Reg::Undef; n.max(1)];
1449    let ins = code.ins_size.min(code.registers_size) as usize;
1450    let mut reg = n.saturating_sub(ins);
1451    let mut pidx = 0;
1452    if !env.is_static && reg < n {
1453        // `this` occupies the first incoming register.
1454        let id = vt.vars.len() as u32;
1455        vt.vars.push(VarInfo {
1456            id,
1457            slot: reg as u16,
1458            name: "this".into(),
1459            ty: TypeRef::J(JavaType::Object(env.class_name.clone())),
1460            is_param: true,
1461            range_start: 0,
1462            range_end: u16::MAX,
1463            synthetic_name: false,
1464        });
1465        while vt.by_slot.len() <= reg {
1466            vt.by_slot.push(Vec::new());
1467        }
1468        vt.by_slot[reg].push((0, u16::MAX, id));
1469        regs[reg] = Reg::Live(id);
1470        reg += 1;
1471        pidx += 1;
1472    }
1473    for (i, arg) in env.desc.args.iter().enumerate() {
1474        if reg >= n {
1475            break;
1476        }
1477        let dbg = param_names.get(i).and_then(|o| o.clone());
1478        let named = dbg.is_some();
1479        let name = dbg.unwrap_or_else(|| format!("p{}", pidx));
1480        let id = vt.vars.len() as u32;
1481        vt.vars.push(VarInfo {
1482            id,
1483            slot: reg as u16,
1484            name,
1485            ty: TypeRef::J(arg.clone()),
1486            is_param: true,
1487            range_start: 0,
1488            range_end: u16::MAX,
1489            synthetic_name: !named,
1490        });
1491        while vt.by_slot.len() <= reg {
1492            vt.by_slot.push(Vec::new());
1493        }
1494        vt.by_slot[reg].push((0, u16::MAX, id));
1495        regs[reg] = Reg::Live(id);
1496        reg += if arg.is_wide() { 2 } else { 1 };
1497        pidx += 1;
1498    }
1499    regs
1500}