Big progress by Claude
This commit is contained in:
+169
-47
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user