//! Step 2 of declass: splice the field initialiser into the constructor. //! //! A class constructor does not set its own fields; quickjs hoisted them into a //! separate initialiser function that the constructor reaches through the //! captured and runs with a call (declass_algo.txt, step 2): //! //! get_var_ref_check ; dup ; if_false //! ; get_loc ; swap ; call_method //! //! This step takes the initialiser's field writes -- its body minus the //! this/home-object prologue and the trailing return -- and splices them into //! the constructor in place of that call, renumbered onto fresh value ids. The //! initialiser is left with an empty body. It cannot be dropped from the //! function list, because a function id is an index into that list. //! //! Deficiency, left for step 3: a class with NO instance fields compiles //! `` to `Undefined` rather than a closure, so there is no //! initialiser to splice and `field_init_links` records no link. Its //! constructor keeps the call block above, which the `if_false` skips at run //! time -- a harmless no-op. Step 3 must strip that block when it drops the //! `` capture, or the `get_var_ref_check` would dangle. use super::step_1::ClassSequence; use crate::{ parse::{ImmOperand, opcodes::OpCode}, regify::{RegFn, RegInstr}, }; /// The name quickjs gives the hoisted field initialiser. const FIELDS_INIT: &str = ""; pub fn splice_ctors(fns: &mut [RegFn], class_seqs: &[Vec]) { // Only a class with instance fields has an initialiser to splice. for seq in class_seqs.iter().flatten() { if let Some(field_init_fn) = seq.field_init_fn { splice_one(fns, seq.ctor_fn, field_init_fn); } } } fn splice_one(fns: &mut [RegFn], ctor: usize, fields: usize) { // Find the call block first. If the constructor does not have one there is // nothing to splice into, and the initialiser has to keep working. let Some((start, end)) = call_span(&fns[ctor]) else { return; }; // Take the field writes out of the initialiser (a separate function), then // hand them to the constructor. let writes = take_field_writes(&mut fns[fields]); let ctor_fn = &mut fns[ctor]; let writes = renumber(writes, ctor_fn); ctor_fn.code.splice(start..=end, writes); } /// The initialiser's field writes: its body minus the this/home-object prologue /// and the trailing return. They reach `this` through get_loc0, which is `this` /// in the constructor too, so they carry over unchanged. /// /// This empties the initialiser, which is dead once its writes move into the /// constructor. The function itself has to stay: a function id is an index into /// the function list, so removing one would renumber every function after it. fn take_field_writes(init: &mut RegFn) -> Vec { init.code.retain(|i| !is_prologue(i.op)); std::mem::take(&mut init.code) } /// Renumber the writes' value ids onto fresh ids in the constructor. Each write /// only uses values it or an earlier write produced, so a flat map suffices. fn renumber(mut writes: Vec, ctor_fn: &mut RegFn) -> Vec { let hi = writes .iter() .flat_map(|i| i.dsts.iter().chain(&i.args)) .copied() .max() .unwrap_or(0); let mut map = vec![usize::MAX; hi + 1]; for instr in &mut writes { for arg in &mut instr.args { *arg = map[*arg]; } for dst in &mut instr.dsts { let fresh = ctor_fn.fresh_val(); map[*dst] = fresh; *dst = fresh; } } writes } /// The call block `get_var_ref_check .. call_method`, as an /// inclusive `start..=end` into the constructor's code. fn call_span(ctor: &RegFn) -> Option<(usize, usize)> { let start = ctor.code.iter().position(|i| reads_fields_init(i, ctor))?; let end = ctor.code[start..] .iter() .position(|i| matches!(i.op, OpCode::CallMethod | OpCode::TailCallMethod))?; Some((start, start + end)) } /// True for `get_var_ref_check` on the `` capture. fn reads_fields_init(i: &RegInstr, ctor: &RegFn) -> bool { let ImmOperand::VarRef(k) = i.imm else { return false; }; i.op == OpCode::GetVarRefCheck && ctor.captures[k as usize].name == FIELDS_INIT } /// The initialiser prologue and terminator, dropped when splicing. fn is_prologue(op: OpCode) -> bool { matches!( op, OpCode::SpecialObject | OpCode::PushThis | OpCode::PutLoc0 | OpCode::PutLoc1 | OpCode::PutLoc2 | OpCode::PutLoc3 | OpCode::Goto8 | OpCode::Goto | OpCode::ReturnUndef ) }