//! Step 1 of declass: rewrite how a class is built, in prototype form. //! //! A class-build cluster (declass_algo.txt, step 1): //! //! push_const8 #ctor ; define_class @Name //! ( fclosure8 #m ; define_method @meth )* //! ( fclosure8 #fields ; set_home_object ; put_loc ; drop )? //! //! becomes `fclosure8 #ctor` followed, per method, by //! `get_field "prototype" ; put_field @meth`; the field-initialiser tail is //! dropped. Detection (`find_class_seqs`) records the ranges; replacement //! (`ClassSequence::emit_prototype`) reads operands out of each matched range. //! //! Derived classes (`extends`) are unsupported and panic: prototype form would //! have to flatten the parent's prototype in and lower `super`. use std::range::Range; use crate::{ parse::{AtomRef, ImmOperand, JsValue, atom_names::PREDEFINED_ATOMS, opcodes::OpCode}, regify::{RegFn, RegInstr}, }; use super::FieldInit; /// Read each constructor <-> field-initialiser link off the class builds, as /// fn ids. Run before `rewrite`, which drops the instructions carrying them. pub(super) fn field_init_links(fns: &[RegFn]) -> Vec { let mut links = vec![]; for f in fns { for seq in find_class_seqs(&f.code) { if let Some(link) = seq.field_init(&f.code, &f.cpool) { links.push(link); } } } links } /// Rewrite every class build in one function to prototype form. pub(super) fn rewrite(mut reg_fn: RegFn) -> RegFn { let seqs = find_class_seqs(®_fn.code); // Move the code out of reg_fn so we can consume it while still calling // reg_fn.fresh_val(); the empty Vec left behind costs nothing. let code = std::mem::take(&mut reg_fn.code); let total = code.len(); let mut out = Vec::with_capacity(total); let mut instrs = code.into_iter(); let mut seqs = seqs.iter(); let mut next_seq = seqs.next(); let mut pos = 0; while pos < total { match next_seq { Some(seq) if seq.range.start == pos => { let adv = seq.range.end - seq.range.start; let matched: Vec = instrs.by_ref().take(adv).collect(); seq.emit_prototype(&mut reg_fn, &mut out, &matched); pos += adv; next_seq = seqs.next(); } _ => { out.push(instrs.next().unwrap()); pos += 1; } } } reg_fn.code = out; reg_fn } struct ClassSequence { range: Range, num_methods: usize, } impl ClassSequence { /// Emit the prototype form, reading operands straight out of `matched` -- /// the class sequence's own instructions. fn emit_prototype(&self, reg_fn: &mut RegFn, out: &mut Vec, matched: &[RegInstr]) { // push_const8 #ctor ; define_class -> fclosure8 #ctor (constructor kept) let (ctor_const, ctor_val) = self.ctor(matched); out.push(fclosure8(ctor_const, ctor_val)); // each ( fclosure8 #m ; define_method @meth ) -> get_field + put_field for k in 0..self.num_methods { let (method_const, method_val, meth) = self.method(matched, k); out.push(fclosure8(method_const, method_val)); let proto = reg_fn.fresh_val(); out.push(get_field(proto_atom(), proto, ctor_val)); out.push(put_field(ImmOperand::Atom(meth), method_val, proto)); } // the fclosure8 #fields ; set_home_object ; put_loc ; drop tail is dropped. } /// The constructor's constant and value: push_const8 #ctor ; define_class. fn ctor(&self, matched: &[RegInstr]) -> (u32, usize) { (as_const(matched[0].imm), matched[1].dsts[0]) } /// The k-th method's constant, closure value, and name; each method pair /// sits after the two constructor instructions. fn method(&self, matched: &[RegInstr], k: usize) -> (u32, usize, AtomRef) { let fclosure = &matched[2 + 2 * k]; let define_method = &matched[3 + 2 * k]; ( as_const(fclosure.imm), fclosure.dsts[0], atom_of(define_method.imm), ) } /// The constructor and field-initialiser fn ids, if the class has a /// field-initialiser tail. `code` is the whole enclosing function, so the /// range indexes it directly. fn field_init(&self, code: &[RegInstr], cpool: &[JsValue]) -> Option { let base = 2 + 2 * self.num_methods; // constructor + methods, before the tail if self.range.end - self.range.start == base { return None; // no field-initialiser tail } let ctor_const = as_const(code[self.range.start].imm); let fields_const = as_const(code[self.range.start + base].imm); Some(FieldInit { ctor: fn_id(cpool, ctor_const), fields: fn_id(cpool, fields_const), }) } } fn find_class_seqs(mut ins: &[RegInstr]) -> Vec { let mut seqs = vec![]; let mut offset = 0; while !ins.is_empty() { match match_class_seq(ins) { Ok((rest, num_methods)) => { let adv = ins.len() - rest.len(); seqs.push(ClassSequence { range: Range { start: offset, end: offset + adv, }, num_methods, }); offset += adv; ins = rest; } Err(_) => { offset += 1; ins = &ins[1..]; } } } seqs } // ── the grammar, as matchers over the instruction stream ─────────────────── fn match_class_seq(ins: &[RegInstr]) -> Result<(&[RegInstr], usize), &[RegInstr]> { let ins = match_one(ins, |i| i.op == OpCode::PushConst8)?; // A class build is `push_const8 ; define_class`. Derived classes (`extends`) // are unsupported: prototype form would have to flatten the parent's // prototype in and lower `super`, neither of which is implemented. if let Some(i) = ins.first() { if i.op == OpCode::DefineClass && is_derived(i) { panic!("declass: derived classes (extends) are not supported"); } } let ins = match_one(ins, |i| i.op == OpCode::DefineClass)?; let (ins, num_methods) = match_many(ins, match_method_seq); let ins = match_opt(ins, match_field_init_seq); Ok((ins, num_methods)) } /// A derived class: `define_class` with the derived flag set. fn is_derived(i: &RegInstr) -> bool { matches!(i.imm, ImmOperand::AtomU8 { arg, .. } if arg & 1 == 1) } // TODO: accessors are not handled. A getter/setter is a `define_method` with a // non-zero kind in `arg`; this matcher takes it for an ordinary method, so it // is wrongly lowered to a `put_field` (a data property) instead of an accessor // property. It should instead make the whole class pass through untouched. fn match_method_seq(ins: &[RegInstr]) -> Result<&[RegInstr], &[RegInstr]> { let ins = match_one(ins, |i| i.op == OpCode::Fclosure8)?; let ins = match_one(ins, |i| i.op == OpCode::DefineMethod)?; Ok(ins) } fn match_field_init_seq(ins: &[RegInstr]) -> Result<&[RegInstr], &[RegInstr]> { let is_put_loc = |op| { matches!( op, OpCode::PutLoc | OpCode::PutLoc8 | OpCode::PutLoc0 | OpCode::PutLoc1 | OpCode::PutLoc2 | OpCode::PutLoc3 ) }; let ins = match_one(ins, |i| i.op == OpCode::Fclosure8)?; let ins = match_one(ins, |i| i.op == OpCode::SetHomeObject)?; let ins = match_one(ins, |i| is_put_loc(i.op))?; let ins = match_one(ins, |i| i.op == OpCode::Drop)?; Ok(ins) } // ── matcher primitives ───────────────────────────────────────────────────── fn match_one(i: &[RegInstr], f: impl Fn(&RegInstr) -> bool) -> Result<&[RegInstr], &[RegInstr]> { match i.first() { Some(instr) if f(instr) => Ok(&i[1..]), _ => Err(i), } } fn match_many( mut i: &[RegInstr], f: impl Fn(&[RegInstr]) -> Result<&[RegInstr], &[RegInstr]>, ) -> (&[RegInstr], usize) { let mut n = 0; while let Ok(rest) = f(i) { n += 1; i = rest; } (i, n) } fn match_opt<'a>( i: &'a [RegInstr], f: impl Fn(&'a [RegInstr]) -> Result<&'a [RegInstr], &'a [RegInstr]>, ) -> &'a [RegInstr] { match f(i) { Ok(rest) => rest, Err(_) => i, } } // ── emitted instructions ─────────────────────────────────────────────────── fn fclosure8(const_index: u32, dst: usize) -> RegInstr { RegInstr::new( OpCode::Fclosure8, ImmOperand::Const(const_index), vec![dst], vec![], ) } fn get_field(field: ImmOperand, dst: usize, obj: usize) -> RegInstr { RegInstr::new(OpCode::GetField, field, vec![dst], vec![obj]) } fn put_field(field: ImmOperand, value: usize, obj: usize) -> RegInstr { RegInstr::new(OpCode::PutField, field, vec![], vec![value, obj]) } // ── operand helpers ──────────────────────────────────────────────────────── fn atom_of(imm: ImmOperand) -> AtomRef { match imm { ImmOperand::AtomU8 { atom, .. } => atom, _ => unreachable!("expected an atom operand"), } } fn as_const(imm: ImmOperand) -> u32 { match imm { ImmOperand::Const(c) => c, _ => unreachable!("expected a constant operand"), } } /// The function id a constant-pool entry points at. fn fn_id(cpool: &[JsValue], const_index: u32) -> usize { match cpool[const_index as usize] { JsValue::FnById(id) => id, _ => unreachable!("class constant is not a function"), } } /// The `"prototype"` field name as a predefined-atom operand. fn proto_atom() -> ImmOperand { let i = PREDEFINED_ATOMS .iter() .position(|&n| n == "prototype") .unwrap(); ImmOperand::Atom(AtomRef::Predefined(i as u32)) }