Modifications

This commit is contained in:
2026-09-07 11:44:42 +00:00
parent 617dbeb988
commit 457e63ad8e
7 changed files with 259 additions and 225 deletions
+26 -17
View File
@@ -10,7 +10,8 @@
//! 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.
//! 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
//! `<class_fields_init>` to `Undefined` rather than a closure, so there is no
@@ -19,7 +20,8 @@
//! 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 super::step_1::ClassSequence;
use crate::{
parse::{ImmOperand, opcodes::OpCode},
regify::{RegFn, RegInstr},
@@ -28,21 +30,27 @@ use crate::{
/// 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);
pub fn splice_ctor(fns: &mut [RegFn], class_seqs: &[Vec<ClassSequence>]) {
// 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) {
// 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 {
// 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);
}
@@ -50,12 +58,13 @@ fn splice_one(fns: &mut [RegFn], ctor: usize, fields: usize) {
/// 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()
///
/// 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<RegInstr> {
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