New direction
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
//! 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 <class_fields_init> and runs with a call (declass_algo.txt, step 2):
|
||||
//!
|
||||
//! get_var_ref_check <class_fields_init> ; 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 function is left unreferenced.
|
||||
//!
|
||||
//! Deficiency, left for step 3: a class with NO instance fields compiles
|
||||
//! `<class_fields_init>` 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
|
||||
//! `<class_fields_init>` capture, or the `get_var_ref_check` would dangle.
|
||||
|
||||
use super::FieldInit;
|
||||
use crate::{
|
||||
parse::{ImmOperand, opcodes::OpCode},
|
||||
regify::{RegFn, RegInstr},
|
||||
};
|
||||
|
||||
/// The name quickjs gives the hoisted field initialiser.
|
||||
const FIELDS_INIT: &str = "<class_fields_init>";
|
||||
|
||||
pub(super) fn splice(fns: &mut [RegFn], field_inits: &[FieldInit]) {
|
||||
for &FieldInit { ctor, fields } in field_inits {
|
||||
splice_one(fns, ctor, fields);
|
||||
}
|
||||
}
|
||||
|
||||
fn splice_one(fns: &mut [RegFn], ctor: usize, fields: usize) {
|
||||
// Pull the field writes out of the initialiser (a separate function), then
|
||||
// hand them to the constructor.
|
||||
let writes = field_writes(&fns[fields]);
|
||||
|
||||
let ctor_fn = &mut fns[ctor];
|
||||
let Some((start, end)) = call_span(ctor_fn) else {
|
||||
return;
|
||||
};
|
||||
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.
|
||||
fn field_writes(init: &RegFn) -> Vec<RegInstr> {
|
||||
init.code
|
||||
.iter()
|
||||
.filter(|i| !is_prologue(i.op))
|
||||
.map(|i| RegInstr::new(i.op, i.imm, i.dsts.clone(), i.args.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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<RegInstr>, ctor_fn: &mut RegFn) -> Vec<RegInstr> {
|
||||
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 <class_fields_init> .. 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 `<class_fields_init>` 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
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user