Big progress by Claude

This commit is contained in:
2026-09-25 19:31:15 +00:00
parent 183c4e35c9
commit e93ce94ac7
15 changed files with 1577 additions and 470 deletions
+169 -47
View File
@@ -1,22 +1,34 @@
mod declass;
use crate::{
builtins,
flatten::FlatFn,
libqjs::parse::{ClosureVar, ImmOperand, Instr, JsValue, opcodes::OpCode},
model::{EXTRA_ARGS, FIELD_FN},
libqjs::parse::{
Atom, ClosureKind, ClosureVar, ImmOperand, Instr, JsValue, atom_to_str, opcodes::OpCode,
},
model::{EXTRA_ARGS, FIELD_PROTO},
};
use declass::declass;
pub fn transform(flat_fns: Vec<FlatFn>, captures: &[Vec<ClosureVar>]) -> Vec<FlatFn> {
pub fn transform(
flat_fns: Vec<FlatFn>,
captures: &[Vec<ClosureVar>],
atoms: &[Atom],
) -> Vec<FlatFn> {
flat_fns
.into_iter()
.enumerate()
.map(|(i, f)| transform_one(i, f, captures))
.map(|(i, f)| transform_one(i, f, captures, atoms))
.collect()
}
fn transform_one(fn_id: usize, f: FlatFn, captures: &[Vec<ClosureVar>]) -> FlatFn {
fn transform_one(
fn_id: usize,
mut f: FlatFn,
captures: &[Vec<ClosureVar>],
atoms: &[Atom],
) -> FlatFn {
let declassed = declass(&f.code);
// Every function gains two parameters after its own, in this order. The
@@ -28,22 +40,34 @@ fn transform_one(fn_id: usize, f: FlatFn, captures: &[Vec<ClosureVar>]) -> FlatF
for instr in &declassed {
if let Some(info) = is_fclosure(instr, &f, captures) {
out_len += 1 + (2 * info.child_captures.len()) + 2; // object; (push; define_field)*; push_fn_const; define_field
// fclosure; (load; define_field)*
out_len += 1 + info.child_captures.iter().map(capture_len).sum::<usize>();
continue;
}
if is_capture_acc(instr).is_some() || is_tail_call(instr) {
// get_arg env; swap; put_field
if let Some(CaptureAccess::Write(_)) = is_capture_acc(instr) {
out_len += 3;
continue;
}
if is_capture_acc(instr).is_some() {
out_len += 2;
continue;
}
if let Some(seq) = expansion(instr.op) {
out_len += seq.len();
continue;
}
out_len += 1;
}
let mut out = Vec::with_capacity(out_len);
for instr in &declassed {
if replace_fclosure(instr, &f, captures, &mut out) {
if replace_fclosure(instr, &f, captures, &captures[fn_id], env_idx, &mut out) {
continue;
}
@@ -55,11 +79,15 @@ fn transform_one(fn_id: usize, f: FlatFn, captures: &[Vec<ClosureVar>]) -> FlatF
continue;
}
if replace_tail_call(instr, &mut out) {
if replace_expansion(instr, &mut out) {
continue;
}
out.push(instr.clone());
if replace_get_var(instr, atoms, &mut f.cpool, &mut out) {
continue;
}
out.push(renamed(instr).unwrap_or_else(|| instr.clone()));
}
debug_assert!(out.len() == out_len);
@@ -89,7 +117,6 @@ fn replace_push_this(instr: &Instr, this_idx: u16, out: &mut Vec<Instr>) -> bool
}
struct FClosureInfo<'a> {
cpool_idx: u32,
child_captures: &'a [ClosureVar],
}
@@ -98,7 +125,7 @@ fn is_fclosure<'a>(
f: &FlatFn,
captures: &'a [Vec<ClosureVar>],
) -> Option<FClosureInfo<'a>> {
if instr.op != OpCode::Fclosure8 {
if !matches!(instr.op, OpCode::Fclosure8 | OpCode::Fclosure) {
return None;
}
@@ -113,7 +140,6 @@ fn is_fclosure<'a>(
};
Some(FClosureInfo {
cpool_idx,
child_captures: &captures[child_fn_id],
})
}
@@ -122,6 +148,8 @@ fn replace_fclosure(
instr: &Instr,
f: &FlatFn,
captures: &[Vec<ClosureVar>],
my_captures: &[ClosureVar],
env_idx: u16,
out: &mut Vec<Instr>,
) -> bool {
let info = match is_fclosure(instr, f, captures) {
@@ -129,43 +157,66 @@ fn replace_fclosure(
None => return false,
};
let pc = instr.pc;
out.push(Instr {
pc,
op: OpCode::Object,
imm: ImmOperand::None,
});
// The closure keeps the op that makes it: `fclosure` creates a closure of the
// child with an empty environment, and each capture is written into that
// environment as an ordinary field.
out.push(instr.clone());
for c in info.child_captures {
out.push(Instr {
pc,
op: OpCode::GetLoc,
imm: ImmOperand::Loc(c.var_idx as u16),
});
if !load_capture(c, my_captures, env_idx, instr.pc, out) {
continue;
}
out.push(Instr {
pc,
pc: instr.pc,
op: OpCode::DefineField,
imm: ImmOperand::Atom(c.name),
});
}
out.push(Instr {
pc,
op: OpCode::PushConst,
imm: ImmOperand::Const(info.cpool_idx),
});
true
}
out.push(Instr {
pc,
op: OpCode::DefineField,
imm: ImmOperand::Atom(FIELD_FN),
});
/// Load the value a child closure captures, from where it lives in this frame.
/// `var_idx` means something different per kind: a local slot, an argument, or
/// one of this function's own captures — which, after lowering, is a field of
/// its env. A module binding has no frame at all, so nothing is loaded and the
/// capture is not written: its field first appears when `<eval>` writes it,
/// and a read before that finds no field and gives undefined.
fn load_capture(
c: &ClosureVar,
my_captures: &[ClosureVar],
env_idx: u16,
pc: usize,
out: &mut Vec<Instr>,
) -> bool {
let mut push = |op, imm| out.push(Instr { pc, op, imm });
match c.flags.closure_kind {
ClosureKind::Local => push(OpCode::GetLoc, ImmOperand::Loc(c.var_idx as u16)),
ClosureKind::Arg => push(OpCode::GetArg, ImmOperand::Arg(c.var_idx as u16)),
ClosureKind::Ref => {
push(OpCode::GetArg, ImmOperand::Arg(env_idx));
push(
OpCode::GetField,
ImmOperand::Atom(my_captures[c.var_idx].name),
);
}
_ => return false,
}
true
}
/// How many instructions one capture takes: its load, then its define_field.
fn capture_len(c: &ClosureVar) -> usize {
match c.flags.closure_kind {
ClosureKind::Local | ClosureKind::Arg => 2,
ClosureKind::Ref => 3,
_ => 0,
}
}
enum CaptureAccess {
Read(u16),
Write(u16),
@@ -232,12 +283,20 @@ fn replace_capture_accs(
CaptureAccess::Write(write_var) => {
let atom = my_captures[write_var as usize].name;
// The value is already on the stack, so env lands above it, and
// put_field wants the object below the value.
out.push(Instr {
pc,
op: OpCode::GetArg,
imm: ImmOperand::Arg(env_idx),
});
out.push(Instr {
pc,
op: OpCode::Swap,
imm: ImmOperand::None,
});
out.push(Instr {
pc,
op: OpCode::PutField,
@@ -249,26 +308,89 @@ fn replace_capture_accs(
true
}
fn is_tail_call(instr: &Instr) -> bool {
instr.op == OpCode::TailCall
/// Ops that are a fixed sequence of simpler ones. `true` passes the original
/// immediate on to that op.
fn expansion(op: OpCode) -> Option<&'static [(OpCode, bool)]> {
use OpCode::*;
let seq: &'static [(OpCode, bool)] = match op {
TailCall => &[(Call, true), (Return, false)],
TailCallMethod => &[(CallMethod, true), (Return, false)],
GetField2 => &[(Dup, false), (GetField, true)],
GetLoc0Loc1 => &[(GetLoc0, false), (GetLoc1, false)],
IncLoc => &[(GetLoc, true), (Inc, false), (PutLoc, true)],
DecLoc => &[(GetLoc, true), (Dec, false), (PutLoc, true)],
// The added value is on the stack; the local is the left operand.
AddLoc => &[(GetLoc, true), (Swap, false), (Add, false), (PutLoc, true)],
_ => return None,
};
Some(seq)
}
fn replace_tail_call(instr: &Instr, out: &mut Vec<Instr>) -> bool {
if !is_tail_call(instr) {
fn replace_expansion(instr: &Instr, out: &mut Vec<Instr>) -> bool {
let Some(seq) = expansion(instr.op) else {
return false;
};
for &(op, keep_imm) in seq {
let imm = if keep_imm { instr.imm } else { ImmOperand::None };
out.push(Instr { pc: instr.pc, op, imm });
}
true
}
/// The only globals a program reads are the C functions builtins call. Each
/// becomes a constant naming it.
fn replace_get_var(
instr: &Instr,
atoms: &[Atom],
cpool: &mut Vec<JsValue>,
out: &mut Vec<Instr>,
) -> bool {
let ImmOperand::Atom(atom) = instr.imm else {
return false;
};
if instr.op != OpCode::GetVar {
return false;
}
out.push(Instr {
pc: instr.pc,
op: OpCode::Call,
imm: instr.imm,
});
let name = atom_to_str(&atom, atoms);
let native = builtins::native(&name).unwrap_or_else(|| panic!("unknown global `{name}`"));
cpool.push(JsValue::Native(native));
out.push(Instr {
pc: instr.pc,
op: OpCode::Return,
imm: ImmOperand::None,
op: OpCode::PushConst,
imm: ImmOperand::Const(cpool.len() as u32 - 1),
});
true
}
/// Ops that are another op with a different immediate.
fn renamed(instr: &Instr) -> Option<Instr> {
let define_field = |atom| Instr {
pc: instr.pc,
op: OpCode::DefineField,
imm: ImmOperand::Atom(atom),
};
match (instr.op, instr.imm) {
// `{ __proto__: p }`: the prototype is an ordinary field.
(OpCode::SetProto, _) => Some(define_field(FIELD_PROTO)),
// A method in an object literal is a field holding a closure. The low
// two bits of `arg` say method, getter or setter; only methods are
// supported.
(OpCode::DefineMethod, ImmOperand::AtomU8 { atom, arg }) => {
assert!(arg & 3 == 0, "getters and setters are not supported");
Some(define_field(atom))
}
_ => None,
}
}