First declosure impl
This commit is contained in:
@@ -10,6 +10,9 @@
|
|||||||
//! `get_field "prototype" ; put_field @meth`; the field-initialiser tail is
|
//! `get_field "prototype" ; put_field @meth`; the field-initialiser tail is
|
||||||
//! dropped. Detection (`find_class_seqs`) records the ranges; replacement
|
//! dropped. Detection (`find_class_seqs`) records the ranges; replacement
|
||||||
//! (`ClassSequence::emit_prototype`) reads operands out of each matched range.
|
//! (`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 std::range::Range;
|
||||||
|
|
||||||
@@ -160,6 +163,16 @@ fn find_class_seqs(mut ins: &[RegInstr]) -> Vec<ClassSequence> {
|
|||||||
|
|
||||||
fn match_class_seq(ins: &[RegInstr]) -> Result<(&[RegInstr], usize), &[RegInstr]> {
|
fn match_class_seq(ins: &[RegInstr]) -> Result<(&[RegInstr], usize), &[RegInstr]> {
|
||||||
let ins = match_one(ins, |i| i.op == OpCode::PushConst8)?;
|
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 = match_one(ins, |i| i.op == OpCode::DefineClass)?;
|
||||||
let (ins, num_methods) = match_many(ins, match_method_seq);
|
let (ins, num_methods) = match_many(ins, match_method_seq);
|
||||||
let ins = match_opt(ins, match_field_init_seq);
|
let ins = match_opt(ins, match_field_init_seq);
|
||||||
@@ -167,6 +180,15 @@ fn match_class_seq(ins: &[RegInstr]) -> Result<(&[RegInstr], usize), &[RegInstr]
|
|||||||
Ok((ins, num_methods))
|
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]> {
|
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::Fclosure8)?;
|
||||||
let ins = match_one(ins, |i| i.op == OpCode::DefineMethod)?;
|
let ins = match_one(ins, |i| i.op == OpCode::DefineMethod)?;
|
||||||
@@ -177,7 +199,12 @@ fn match_field_init_seq(ins: &[RegInstr]) -> Result<&[RegInstr], &[RegInstr]> {
|
|||||||
let is_put_loc = |op| {
|
let is_put_loc = |op| {
|
||||||
matches!(
|
matches!(
|
||||||
op,
|
op,
|
||||||
OpCode::PutLoc | OpCode::PutLoc0 | OpCode::PutLoc1 | OpCode::PutLoc2 | OpCode::PutLoc3
|
OpCode::PutLoc
|
||||||
|
| OpCode::PutLoc8
|
||||||
|
| OpCode::PutLoc0
|
||||||
|
| OpCode::PutLoc1
|
||||||
|
| OpCode::PutLoc2
|
||||||
|
| OpCode::PutLoc3
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,317 @@
|
|||||||
|
//! declosure: closures become plain objects.
|
||||||
|
//!
|
||||||
|
//! A closure becomes an object holding its function plus its captured
|
||||||
|
//! variables (docs/declosure_algo.txt). Field 0 is the function; fields 1..
|
||||||
|
//! are the captures, in capture order. The object doubles as the environment,
|
||||||
|
//! so there is no second env value to track. Field names are plain integers,
|
||||||
|
//! so a field operand reads `Atom(int(k))`.
|
||||||
|
//!
|
||||||
|
//! Three steps, one property each:
|
||||||
|
//!
|
||||||
|
//! 1. `take_env_param` -- every function takes the env as its
|
||||||
|
//! LAST parameter and reads its captures out of it: `get_var_ref` /
|
||||||
|
//! `put_var_ref` become field access on the env.
|
||||||
|
//! 2. `build_closure_objects` -- every `fclosure` builds the object: the
|
||||||
|
//! function in field 0, then one field per capture, loaded from the
|
||||||
|
//! parent frame.
|
||||||
|
//! 3. `route_calls_through_object` -- every plain call reads field 0 and
|
||||||
|
//! passes the object itself as the trailing env argument.
|
||||||
|
//!
|
||||||
|
//! Every function takes an env whether it captures anything or not, so step 3
|
||||||
|
//! can append the object at a uniform call site.
|
||||||
|
//!
|
||||||
|
//! Two facts about the register form this leans on:
|
||||||
|
//!
|
||||||
|
//! * A function is a plain constant -- an index into the cpool. `fclosure`
|
||||||
|
//! becomes `push_const`, so it loads like any other const, and storing it
|
||||||
|
//! in a field is no different from storing a number.
|
||||||
|
//! * The callee of a call is its LAST argument (the deepest stack slot,
|
||||||
|
//! popped last), so step 3 reads `args.last()`.
|
||||||
|
//!
|
||||||
|
//! Known gaps, in rough order of how likely they are to bite:
|
||||||
|
//!
|
||||||
|
//! * A captured variable the parent later writes. The write produces a new
|
||||||
|
//! value instead of storing through the alias, so the closure never sees
|
||||||
|
//! it. A capture aliases the value a slot held at capture time.
|
||||||
|
//! * Non-local capture sources. Step 2 can materialise a capture that comes
|
||||||
|
//! from a parent local or arg. A capture that itself comes from the
|
||||||
|
//! parent's captures, a module binding, or a global is left as an
|
||||||
|
//! `undefined` placeholder -- it needs the parent to forward its own env.
|
||||||
|
//! * Foreign callees. Step 3 rewrites every plain call into a field-0 read.
|
||||||
|
//! That is only valid for functions we produced; a host builtin such as
|
||||||
|
//! `Math.sqrt` has no such field.
|
||||||
|
//! * Entry points. Step 1 gives every function an env parameter, but the
|
||||||
|
//! module root and any host-invoked function have no caller of ours to
|
||||||
|
//! supply one.
|
||||||
|
//! * `call_method` and `call_constructor` carry a receiver, so they belong
|
||||||
|
//! to the class passes; step 3 leaves them untouched. Same for `apply`.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
flatten::CaptureFrom,
|
||||||
|
parse::{AtomRef, ImmOperand, JsValue, opcodes::OpCode},
|
||||||
|
regify::{RegFn, RegInstr},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn declosure(reg_fns: Vec<RegFn>) -> Vec<RegFn> {
|
||||||
|
// Where each function's captures come from, per fn id. Snapshotted first,
|
||||||
|
// because step 1 clears the capture lists that step 2 reads.
|
||||||
|
let capture_sources: Vec<Vec<CaptureFrom>> = reg_fns
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.captures.iter().map(|c| c.from).collect())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
reg_fns
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| declosure_one(f, &capture_sources))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declosure_one(reg_fn: RegFn, capture_sources: &[Vec<CaptureFrom>]) -> RegFn {
|
||||||
|
let reg_fn = take_env_param(reg_fn);
|
||||||
|
let reg_fn = build_closure_objects(reg_fn, capture_sources);
|
||||||
|
route_calls_through_object(reg_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 1. every function takes an env ─────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Append `env` as the last parameter -- existing arguments keep their numbers
|
||||||
|
/// -- and turn every capture access into a field access on it.
|
||||||
|
fn take_env_param(mut reg_fn: RegFn) -> RegFn {
|
||||||
|
let env_arg = reg_fn.arg_count;
|
||||||
|
reg_fn.arg_count += 1;
|
||||||
|
|
||||||
|
let code = std::mem::take(&mut reg_fn.code);
|
||||||
|
let touches_captures = code.iter().any(|i| capture_access(i).is_some());
|
||||||
|
|
||||||
|
let mut out = Vec::with_capacity(code.len() + 1);
|
||||||
|
let env = reg_fn.fresh_val();
|
||||||
|
|
||||||
|
// Load the env once at entry -- only if a capture is actually accessed.
|
||||||
|
if touches_captures {
|
||||||
|
out.push(get_arg(env, env_arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
for instr in code {
|
||||||
|
match capture_access(&instr) {
|
||||||
|
// A read keeps its destination, so downstream uses do not change.
|
||||||
|
Some(CaptureAccess::Read(k)) => {
|
||||||
|
out.push(get_field(int_field(k + 1), instr.dsts[0], env))
|
||||||
|
}
|
||||||
|
Some(CaptureAccess::Write(k)) => {
|
||||||
|
out.push(put_field(int_field(k + 1), instr.args[0], env))
|
||||||
|
}
|
||||||
|
None => out.push(instr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reg_fn.code = out;
|
||||||
|
reg_fn.captures.clear();
|
||||||
|
reg_fn
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which capture an instruction reads or writes, if any.
|
||||||
|
enum CaptureAccess {
|
||||||
|
Read(u32),
|
||||||
|
Write(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_access(i: &RegInstr) -> Option<CaptureAccess> {
|
||||||
|
// The numbered forms fold the slot into the opcode; the rest carry it.
|
||||||
|
let slot = || match i.imm {
|
||||||
|
ImmOperand::VarRef(k) => k as u32,
|
||||||
|
_ => unreachable!("a var_ref opcode without a var_ref operand"),
|
||||||
|
};
|
||||||
|
Some(match i.op {
|
||||||
|
OpCode::GetVarRef | OpCode::GetVarRefCheck => CaptureAccess::Read(slot()),
|
||||||
|
OpCode::GetVarRef0 => CaptureAccess::Read(0),
|
||||||
|
OpCode::GetVarRef1 => CaptureAccess::Read(1),
|
||||||
|
OpCode::GetVarRef2 => CaptureAccess::Read(2),
|
||||||
|
OpCode::GetVarRef3 => CaptureAccess::Read(3),
|
||||||
|
OpCode::PutVarRef | OpCode::PutVarRefCheck | OpCode::PutVarRefCheckInit => {
|
||||||
|
CaptureAccess::Write(slot())
|
||||||
|
}
|
||||||
|
OpCode::PutVarRef0 => CaptureAccess::Write(0),
|
||||||
|
OpCode::PutVarRef1 => CaptureAccess::Write(1),
|
||||||
|
OpCode::PutVarRef2 => CaptureAccess::Write(2),
|
||||||
|
OpCode::PutVarRef3 => CaptureAccess::Write(3),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. every fclosure builds the object ────────────────────────────────────
|
||||||
|
|
||||||
|
/// Replace each `fclosure` with the object that stands for it: the function in
|
||||||
|
/// field 0, then one field per capture, each loaded from the parent frame.
|
||||||
|
fn build_closure_objects(mut reg_fn: RegFn, capture_sources: &[Vec<CaptureFrom>]) -> RegFn {
|
||||||
|
let code = std::mem::take(&mut reg_fn.code);
|
||||||
|
let mut out = Vec::with_capacity(code.len());
|
||||||
|
|
||||||
|
for instr in code {
|
||||||
|
let Some(child) = closure_child(&instr, ®_fn.cpool) else {
|
||||||
|
out.push(instr);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// The value the rest of the code already uses for this closure.
|
||||||
|
let closure = instr.dsts[0];
|
||||||
|
let captures: &[CaptureFrom] = capture_sources
|
||||||
|
.get(child)
|
||||||
|
.map(Vec::as_slice)
|
||||||
|
.unwrap_or(&[]);
|
||||||
|
|
||||||
|
// The function is a plain constant, so it loads with `push_const`.
|
||||||
|
let load = match instr.op {
|
||||||
|
OpCode::Fclosure8 => OpCode::PushConst8,
|
||||||
|
_ => OpCode::PushConst,
|
||||||
|
};
|
||||||
|
let func = reg_fn.fresh_val();
|
||||||
|
out.push(RegInstr::new(load, instr.imm, vec![func], vec![]));
|
||||||
|
|
||||||
|
let mut obj = reg_fn.fresh_val();
|
||||||
|
out.push(object(obj));
|
||||||
|
|
||||||
|
// Each define_field returns the object, so the stores chain. The LAST
|
||||||
|
// store must land in `closure`.
|
||||||
|
let dst = if captures.is_empty() {
|
||||||
|
closure
|
||||||
|
} else {
|
||||||
|
reg_fn.fresh_val()
|
||||||
|
};
|
||||||
|
out.push(define_field(int_field(0), dst, func, obj));
|
||||||
|
obj = dst;
|
||||||
|
|
||||||
|
for (k, source) in captures.iter().enumerate() {
|
||||||
|
let value = reg_fn.fresh_val();
|
||||||
|
out.push(load_capture(*source, value));
|
||||||
|
|
||||||
|
let dst = if k + 1 == captures.len() {
|
||||||
|
closure
|
||||||
|
} else {
|
||||||
|
reg_fn.fresh_val()
|
||||||
|
};
|
||||||
|
out.push(define_field(int_field(k as u32 + 1), dst, value, obj));
|
||||||
|
obj = dst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reg_fn.code = out;
|
||||||
|
reg_fn
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The nested function an `fclosure` instantiates, as a fn id.
|
||||||
|
fn closure_child(i: &RegInstr, cpool: &[JsValue]) -> Option<usize> {
|
||||||
|
if !matches!(i.op, OpCode::Fclosure | OpCode::Fclosure8) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let ImmOperand::Const(index) = i.imm else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
match cpool.get(index as usize) {
|
||||||
|
Some(JsValue::FnById(id)) => Some(*id),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load one captured value out of the parent frame into `into`. Only parent
|
||||||
|
/// locals and args are supported; any other source is a preserved gap and
|
||||||
|
/// loads `undefined`.
|
||||||
|
fn load_capture(source: CaptureFrom, into: usize) -> RegInstr {
|
||||||
|
match source {
|
||||||
|
CaptureFrom::ParentLocal(slot) => get_loc(into, slot),
|
||||||
|
CaptureFrom::ParentArg(slot) => get_arg(into, slot),
|
||||||
|
_ => RegInstr::new(OpCode::Undefined, ImmOperand::None, vec![into], vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. every call goes through the object ──────────────────────────────────
|
||||||
|
|
||||||
|
/// A call names a closure object now, not a function. Read the function out of
|
||||||
|
/// field 0, and pass the object itself as the trailing env argument.
|
||||||
|
fn route_calls_through_object(mut reg_fn: RegFn) -> RegFn {
|
||||||
|
let code = std::mem::take(&mut reg_fn.code);
|
||||||
|
let mut out = Vec::with_capacity(code.len());
|
||||||
|
|
||||||
|
for instr in code {
|
||||||
|
let Some((op, argc)) = plain_call(&instr) else {
|
||||||
|
out.push(instr);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The callee is the deepest stack slot, so it is popped last.
|
||||||
|
let closure = *instr.args.last().unwrap();
|
||||||
|
let func = reg_fn.fresh_val();
|
||||||
|
out.push(get_field(int_field(0), func, closure));
|
||||||
|
|
||||||
|
// Args become [ env, <original args>, func ]: the env is pushed last so
|
||||||
|
// it pops first, and the callee stays last.
|
||||||
|
let mut args = Vec::with_capacity(instr.args.len() + 1);
|
||||||
|
args.push(closure);
|
||||||
|
args.extend_from_slice(&instr.args[..instr.args.len() - 1]);
|
||||||
|
args.push(func);
|
||||||
|
|
||||||
|
out.push(RegInstr::new(
|
||||||
|
op,
|
||||||
|
ImmOperand::Npop(argc + 1),
|
||||||
|
instr.dsts,
|
||||||
|
args,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
reg_fn.code = out;
|
||||||
|
reg_fn
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The opcode to emit and the argument count, for a plain call. `None` if this
|
||||||
|
/// is not one: the receiver-carrying calls (`call_method`, `call_constructor`,
|
||||||
|
/// `apply`) are deliberately excluded.
|
||||||
|
///
|
||||||
|
/// A tail call stays a tail call -- it pushes nothing and returns, so turning
|
||||||
|
/// it into an ordinary `call` would leave a value behind and drop the return.
|
||||||
|
fn plain_call(i: &RegInstr) -> Option<(OpCode, u16)> {
|
||||||
|
if i.args.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let npop = || match i.imm {
|
||||||
|
ImmOperand::Npop(n) => n,
|
||||||
|
_ => unreachable!("a call opcode without an npop operand"),
|
||||||
|
};
|
||||||
|
Some(match i.op {
|
||||||
|
OpCode::Call0 => (OpCode::Call, 0),
|
||||||
|
OpCode::Call1 => (OpCode::Call, 1),
|
||||||
|
OpCode::Call2 => (OpCode::Call, 2),
|
||||||
|
OpCode::Call3 => (OpCode::Call, 3),
|
||||||
|
OpCode::Call => (OpCode::Call, npop()),
|
||||||
|
OpCode::TailCall => (OpCode::TailCall, npop()),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── emitted instructions ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn get_arg(dst: usize, arg: usize) -> RegInstr {
|
||||||
|
RegInstr::new(OpCode::GetArg, ImmOperand::Arg(arg as u16), vec![dst], vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_loc(dst: usize, loc: usize) -> RegInstr {
|
||||||
|
RegInstr::new(OpCode::GetLoc, ImmOperand::Loc(loc as u16), vec![dst], vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn object(dst: usize) -> RegInstr {
|
||||||
|
RegInstr::new(OpCode::Object, ImmOperand::None, 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define_field(field: ImmOperand, dst: usize, value: usize, obj: usize) -> RegInstr {
|
||||||
|
RegInstr::new(OpCode::DefineField, field, vec![dst], vec![value, obj])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A plain integer field name, as an operand.
|
||||||
|
fn int_field(n: u32) -> ImmOperand {
|
||||||
|
ImmOperand::Atom(AtomRef::TaggedInt(n))
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ use crate::parse::{
|
|||||||
Atom, AtomRef, ClosureKind, Function, Instr, JsString, JsValue, QuickJSBinary, atom_names,
|
Atom, AtomRef, ClosureKind, Function, Instr, JsString, JsValue, QuickJSBinary, atom_names,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum CaptureFrom {
|
pub enum CaptureFrom {
|
||||||
/// `parent.var_defs[i]`
|
/// `parent.var_defs[i]`
|
||||||
ParentArg(usize),
|
ParentArg(usize),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod declass;
|
mod declass;
|
||||||
|
mod declosure;
|
||||||
mod flatten;
|
mod flatten;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod regify;
|
mod regify;
|
||||||
@@ -6,6 +7,7 @@ mod regify;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use declass::declass;
|
use declass::declass;
|
||||||
|
use declosure::declosure;
|
||||||
use flatten::flatten;
|
use flatten::flatten;
|
||||||
use parse::QuickJSBinary;
|
use parse::QuickJSBinary;
|
||||||
use regify::regify;
|
use regify::regify;
|
||||||
@@ -41,6 +43,10 @@ fn main() {
|
|||||||
let declassed = declass(reg_fns, &atoms);
|
let declassed = declass(reg_fns, &atoms);
|
||||||
println!("{declassed:#?}");
|
println!("{declassed:#?}");
|
||||||
|
|
||||||
|
println!("=== after declosure ===");
|
||||||
|
let declosured = declosure(declassed);
|
||||||
|
println!("{declosured:#?}");
|
||||||
|
|
||||||
println!("{atoms:#?}");
|
println!("{atoms:#?}");
|
||||||
|
|
||||||
/*println!("=== after deprototype ===");
|
/*println!("=== after deprototype ===");
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
flatten::{Capture, FlatFn},
|
flatten::{Capture, FlatFn},
|
||||||
parse::{AtomRef, ImmOperand, Instr, JsValue, opcodes::OpCode},
|
parse::{AtomRef, ImmOperand, JsValue, opcodes::OpCode},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RegInstr {
|
pub struct RegInstr {
|
||||||
@@ -51,7 +49,7 @@ impl RegFn {
|
|||||||
fn from_flat(flat_fn: FlatFn) -> Self {
|
fn from_flat(flat_fn: FlatFn) -> Self {
|
||||||
let mut stack = vec![];
|
let mut stack = vec![];
|
||||||
let mut output = Vec::with_capacity(flat_fn.code.len());
|
let mut output = Vec::with_capacity(flat_fn.code.len());
|
||||||
let mut next_val = flat_fn.var_count;
|
let mut next_val = flat_fn.var_count; // Reserve values for locals
|
||||||
|
|
||||||
let mut fresh_val = || {
|
let mut fresh_val = || {
|
||||||
let v = next_val;
|
let v = next_val;
|
||||||
|
|||||||
+16
-3
@@ -175,16 +175,29 @@ Swap, CallMethod, PutLoc1, Drop) still occur elsewhere for ordinary reasons.
|
|||||||
Only the specific class-pattern instances listed above are removed.
|
Only the specific class-pattern instances listed above are removed.
|
||||||
|
|
||||||
|
|
||||||
|
====================================================================
|
||||||
|
UNSUPPORTED (the pass panics)
|
||||||
|
====================================================================
|
||||||
|
|
||||||
|
DefineClass AtomU8 { arg: 1 } derived class (extends)
|
||||||
|
|
||||||
|
Prototype form would have to flatten the parent's prototype into the
|
||||||
|
child's and lower `super`. Neither is implemented, so declass panics
|
||||||
|
rather than emit a class that has silently lost its parent.
|
||||||
|
|
||||||
|
|
||||||
====================================================================
|
====================================================================
|
||||||
NOT TOUCHED (passed through unchanged)
|
NOT TOUCHED (passed through unchanged)
|
||||||
====================================================================
|
====================================================================
|
||||||
|
|
||||||
DefineClass AtomU8 { arg: 1 } derived class
|
|
||||||
DefineMethod AtomU8 { arg: 1 } getter
|
|
||||||
DefineMethod AtomU8 { arg: 2 } setter
|
|
||||||
DefineMethodComputed ... computed method name
|
DefineMethodComputed ... computed method name
|
||||||
DefineClassComputed ... computed class name
|
DefineClassComputed ... computed class name
|
||||||
|
|
||||||
|
Known gap: a getter or setter (DefineMethod with a non-zero kind) is
|
||||||
|
NOT yet passed through. It is wrongly lowered as an ordinary method,
|
||||||
|
to a put_field, giving a data property instead of an accessor. It
|
||||||
|
should instead make the whole class pass through untouched.
|
||||||
|
|
||||||
|
|
||||||
====================================================================
|
====================================================================
|
||||||
LEFT BEHIND (removed by a later pass)
|
LEFT BEHIND (removed by a later pass)
|
||||||
|
|||||||
@@ -120,8 +120,12 @@ last argument, so it is args.last().
|
|||||||
|
|
||||||
DETECT, abstractly:
|
DETECT, abstractly:
|
||||||
|
|
||||||
plain call call | call0..3 (callee = last arg; not call_method,
|
plain call call | call0..3 | tail_call (callee = last arg; not
|
||||||
call_constructor, or apply)
|
call_method, call_constructor,
|
||||||
|
or apply)
|
||||||
|
|
||||||
|
A tail call stays a tail call. It pushes nothing and returns, so turning it
|
||||||
|
into an ordinary call would leave a value behind and drop the return.
|
||||||
|
|
||||||
outer, which in the example is:
|
outer, which in the example is:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user