Compare commits
2
Commits
465d9095bb
...
61c4971fa6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c4971fa6 | ||
|
|
d026165e7a |
@@ -0,0 +1,204 @@
|
|||||||
|
use crate::{
|
||||||
|
flatten::FlatFn,
|
||||||
|
libqjs::parse::{AtomRef, ClosureVar, ImmOperand, Instr, JsValue, opcodes::OpCode},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn declosure(flat_fns: Vec<FlatFn>, captures: &[Vec<ClosureVar>]) -> Vec<FlatFn> {
|
||||||
|
flat_fns
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, f)| declosure_one(i, f, captures))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declosure_one(fn_id: usize, f: FlatFn, captures: &[Vec<ClosureVar>]) -> FlatFn {
|
||||||
|
let mut out_len = 0;
|
||||||
|
|
||||||
|
for instr in &f.code {
|
||||||
|
if let Some(info) = is_fclosure(instr, &f, captures) {
|
||||||
|
out_len += 1 + (2 * info.child_captures.len()) + 1 + 1; // object; (push; define_field)*; push_fn_const; define_field
|
||||||
|
} else if is_capture_acc(instr).is_some() {
|
||||||
|
out_len += 2;
|
||||||
|
} else {
|
||||||
|
out_len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = Vec::with_capacity(out_len);
|
||||||
|
|
||||||
|
for instr in &f.code {
|
||||||
|
if replace_fclosure(instr, &f, captures, &mut out) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if replace_capture_accs(instr, &captures[fn_id], &mut out) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(instr.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatFn {
|
||||||
|
name: f.name,
|
||||||
|
arg_count: f.arg_count,
|
||||||
|
var_count: f.var_count,
|
||||||
|
cpool: f.cpool,
|
||||||
|
code: out,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FClosureInfo<'a> {
|
||||||
|
cpool_idx: u32,
|
||||||
|
child_captures: &'a [ClosureVar],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fclosure<'a>(
|
||||||
|
instr: &Instr,
|
||||||
|
f: &FlatFn,
|
||||||
|
captures: &'a [Vec<ClosureVar>],
|
||||||
|
) -> Option<FClosureInfo<'a>> {
|
||||||
|
if instr.op != OpCode::Fclosure8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cpool_idx = match instr.imm_operands {
|
||||||
|
ImmOperand::Const(idx) => idx,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_fn_id = match f.cpool[cpool_idx as usize] {
|
||||||
|
JsValue::FnById(id) => id,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(FClosureInfo {
|
||||||
|
cpool_idx,
|
||||||
|
child_captures: &captures[child_fn_id],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_fclosure(
|
||||||
|
instr: &Instr,
|
||||||
|
f: &FlatFn,
|
||||||
|
captures: &[Vec<ClosureVar>],
|
||||||
|
out: &mut Vec<Instr>,
|
||||||
|
) -> bool {
|
||||||
|
let info = match is_fclosure(instr, f, captures) {
|
||||||
|
Some(info) => info,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pc = instr.pc;
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::Object,
|
||||||
|
imm_operands: ImmOperand::None,
|
||||||
|
});
|
||||||
|
|
||||||
|
for c in info.child_captures {
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::GetLoc,
|
||||||
|
imm_operands: ImmOperand::Loc(c.var_idx as u16),
|
||||||
|
});
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::DefineField,
|
||||||
|
imm_operands: ImmOperand::Atom(c.name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::PushConst,
|
||||||
|
imm_operands: ImmOperand::Const(info.cpool_idx),
|
||||||
|
});
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::DefineField,
|
||||||
|
imm_operands: ImmOperand::Atom(AtomRef::Predefined(0)),
|
||||||
|
});
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CaptureAccess {
|
||||||
|
Read(u16),
|
||||||
|
Write(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_capture_acc(instr: &Instr) -> Option<CaptureAccess> {
|
||||||
|
let acc = match instr.op {
|
||||||
|
OpCode::GetVarRef0 => CaptureAccess::Read(0),
|
||||||
|
OpCode::GetVarRef1 => CaptureAccess::Read(1),
|
||||||
|
OpCode::GetVarRef2 => CaptureAccess::Read(2),
|
||||||
|
OpCode::GetVarRef3 => CaptureAccess::Read(3),
|
||||||
|
OpCode::GetVarRef | OpCode::GetVarRefCheck => match instr.imm_operands {
|
||||||
|
ImmOperand::VarRef(var) => CaptureAccess::Read(var),
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
|
||||||
|
OpCode::PutVarRef0 => CaptureAccess::Write(0),
|
||||||
|
OpCode::PutVarRef1 => CaptureAccess::Write(1),
|
||||||
|
OpCode::PutVarRef2 => CaptureAccess::Write(2),
|
||||||
|
OpCode::PutVarRef3 => CaptureAccess::Write(3),
|
||||||
|
OpCode::PutVarRef | OpCode::PutVarRefCheck => match instr.imm_operands {
|
||||||
|
ImmOperand::VarRef(var) => CaptureAccess::Write(var),
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_capture_accs(instr: &Instr, my_captures: &[ClosureVar], out: &mut Vec<Instr>) -> bool {
|
||||||
|
let acc = is_capture_acc(instr);
|
||||||
|
|
||||||
|
let acc = match acc {
|
||||||
|
Some(acc) => acc,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pc = instr.pc;
|
||||||
|
|
||||||
|
match acc {
|
||||||
|
CaptureAccess::Read(read_var) => {
|
||||||
|
let atom = my_captures[read_var as usize].name;
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::GetArg,
|
||||||
|
imm_operands: ImmOperand::Arg(u16::MAX),
|
||||||
|
});
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::GetField,
|
||||||
|
imm_operands: ImmOperand::Atom(atom),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureAccess::Write(write_var) => {
|
||||||
|
let atom = my_captures[write_var as usize].name;
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::GetArg,
|
||||||
|
imm_operands: ImmOperand::Arg(u16::MAX),
|
||||||
|
});
|
||||||
|
|
||||||
|
out.push(Instr {
|
||||||
|
pc,
|
||||||
|
op: OpCode::PutField,
|
||||||
|
imm_operands: ImmOperand::Atom(atom),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
@@ -6,17 +6,17 @@ pub struct FlatFn {
|
|||||||
pub arg_count: usize,
|
pub arg_count: usize,
|
||||||
pub var_count: usize,
|
pub var_count: usize,
|
||||||
pub cpool: Vec<JsValue>,
|
pub cpool: Vec<JsValue>,
|
||||||
pub captures: Vec<ClosureVar>,
|
|
||||||
pub code: Vec<Instr>,
|
pub code: Vec<Instr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flatten(bin: QuickJSBinary) -> (Vec<FlatFn>, Vec<Atom>) {
|
pub fn flatten(bin: QuickJSBinary) -> (Vec<FlatFn>, Vec<Vec<ClosureVar>>, Vec<Atom>) {
|
||||||
let mut fns = vec![];
|
let mut fns = vec![];
|
||||||
walk(&mut fns, bin.root_mod.root_fn);
|
let mut captures = vec![];
|
||||||
(fns, bin.atoms)
|
walk(bin.root_mod.root_fn, &mut fns, &mut captures);
|
||||||
|
(fns, captures, bin.atoms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk(flat_fns: &mut Vec<FlatFn>, func: Function) -> usize {
|
fn walk(func: Function, flat_fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<ClosureVar>>) -> usize {
|
||||||
let id = flat_fns.len();
|
let id = flat_fns.len();
|
||||||
|
|
||||||
flat_fns.push(FlatFn {
|
flat_fns.push(FlatFn {
|
||||||
@@ -24,15 +24,16 @@ fn walk(flat_fns: &mut Vec<FlatFn>, func: Function) -> usize {
|
|||||||
arg_count: func.arg_count,
|
arg_count: func.arg_count,
|
||||||
var_count: func.var_count,
|
var_count: func.var_count,
|
||||||
cpool: vec![],
|
cpool: vec![],
|
||||||
captures: func.closure_vars,
|
|
||||||
code: func.byte_code,
|
code: func.byte_code,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
captures.push(func.closure_vars);
|
||||||
|
|
||||||
let cpool = func
|
let cpool = func
|
||||||
.cpool
|
.cpool
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| match c {
|
.map(|c| match c {
|
||||||
JsValue::Function(child) => JsValue::FnById(walk(flat_fns, child)),
|
JsValue::Function(child) => JsValue::FnById(walk(child, flat_fns, captures)),
|
||||||
other => other,
|
other => other,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
+53
-52
@@ -3,10 +3,9 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libqjs::parse::{
|
infer::Type::ReturnOf,
|
||||||
Atom, AtomRef, ClosureVar, ImmOperand, JsString, JsValue, atom_names, opcodes::OpCode,
|
libqjs::parse::{Atom, AtomRef, ImmOperand, JsString, JsValue, atom_names, opcodes::OpCode},
|
||||||
},
|
regify::RegFn,
|
||||||
regify::{BasicBlock, RegFn, RegInstr},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
@@ -19,10 +18,13 @@ pub enum FnKind {
|
|||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
Undefined,
|
||||||
Bool,
|
Bool,
|
||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
|
Empty,
|
||||||
Fn(FnKind),
|
Fn(FnKind),
|
||||||
|
ReturnOf((FnKind, Vec<Type>)),
|
||||||
Any,
|
Any,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,35 +91,19 @@ fn walk_fn(
|
|||||||
reg_fns: &[RegFn],
|
reg_fns: &[RegFn],
|
||||||
cache: &mut TypeCache,
|
cache: &mut TypeCache,
|
||||||
) -> Result<InferResult, InferRequest> {
|
) -> Result<InferResult, InferRequest> {
|
||||||
|
use OpCode::*;
|
||||||
|
|
||||||
let f = ®_fns[req.fn_id];
|
let f = ®_fns[req.fn_id];
|
||||||
let mut val_types = vec![Type::Unknown; f.val_count];
|
let mut val_types = vec![Type::Unknown; f.val_count];
|
||||||
let mut ret_type = Type::Unknown;
|
let mut ret_type = Type::Unknown;
|
||||||
|
|
||||||
let mut worklist = vec![0];
|
for instr in &f.code {
|
||||||
|
let is_ret = matches!(instr.op, Return | ReturnAsync | ReturnUndef);
|
||||||
|
|
||||||
while let Some(block_id) = worklist.pop() {
|
if instr.dsts.is_empty() && !is_ret {
|
||||||
let block = &f.blocks[block_id];
|
continue;
|
||||||
walk_block(&block, &req.arg_types, f, &mut val_types, &mut ret_type)?;
|
|
||||||
|
|
||||||
worklist.extend(block.succ.iter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(InferResult {
|
|
||||||
val_types,
|
|
||||||
ret_type,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_block(
|
|
||||||
block: &BasicBlock,
|
|
||||||
arg_types: &[Type],
|
|
||||||
f: &RegFn,
|
|
||||||
val_types: &mut [Type],
|
|
||||||
ret_type: &mut Type,
|
|
||||||
) -> Result<(), InferRequest> {
|
|
||||||
use OpCode::*;
|
|
||||||
|
|
||||||
for instr in &f.code[block.start..block.end] {
|
|
||||||
let ty = match instr.op {
|
let ty = match instr.op {
|
||||||
// ──────────────────────────────────── Literals ────────────────────────────────────
|
// ──────────────────────────────────── Literals ────────────────────────────────────
|
||||||
PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7 | PushI8
|
PushMinus1 | Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7 | PushI8
|
||||||
@@ -125,15 +111,16 @@ fn walk_block(
|
|||||||
|
|
||||||
PushTrue | PushFalse => Type::Bool,
|
PushTrue | PushFalse => Type::Bool,
|
||||||
|
|
||||||
|
// TODO: Fclosure will disappear soon
|
||||||
PushConst | PushConst8 | Fclosure | Fclosure8 => match instr.imm {
|
PushConst | PushConst8 | Fclosure | Fclosure8 => match instr.imm {
|
||||||
ImmOperand::Const(k) => match f.cpool.get(k as usize) {
|
ImmOperand::Const(k) => match f.cpool.get(k as usize) {
|
||||||
Some(JsValue::Int(_)) => Type::Int,
|
Some(JsValue::Int(_)) => Type::Int,
|
||||||
Some(JsValue::Float(_)) => Type::Float,
|
Some(JsValue::Float(_)) => Type::Float,
|
||||||
Some(JsValue::BoolTrue) | Some(JsValue::BoolFalse) => Type::Bool,
|
Some(JsValue::BoolTrue) | Some(JsValue::BoolFalse) => Type::Bool,
|
||||||
Some(&JsValue::FnById(id)) => Type::Fn(FnKind::Id(id)),
|
Some(&JsValue::FnById(id)) => Type::Fn(FnKind::Id(id)),
|
||||||
_ => panic!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
_ => panic!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
|
|
||||||
// ─────────────────────────────────── Arithmetic ───────────────────────────────────
|
// ─────────────────────────────────── Arithmetic ───────────────────────────────────
|
||||||
@@ -156,43 +143,57 @@ fn walk_block(
|
|||||||
Lt | Lte | Gt | Gte | Eq | Neq | StrictEq | StrictNeq | Not => Type::Bool,
|
Lt | Lte | Gt | Gte | Eq | Neq | StrictEq | StrictNeq | Not => Type::Bool,
|
||||||
|
|
||||||
// ────────────────────────────────── Value Movers ──────────────────────────────────
|
// ────────────────────────────────── Value Movers ──────────────────────────────────
|
||||||
Dup | PutLoc0 | PutLoc1 | PutLoc2 | PutLoc3 | PutLoc | PutLoc8 | PutLocCheck
|
Dup | SetLoc => val_types[instr.args[0]].clone(),
|
||||||
| PutLocCheckInit | SetLoc0 | SetLoc1 | SetLoc2 | SetLoc3 | SetLoc | SetLoc8 => {
|
|
||||||
val_types[instr.args[0]].clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
SetLocUninitialized => Type::Unknown,
|
// ──────────────────────────────────── Arguments ───────────────────────────────────
|
||||||
|
OpCode::GetArg0 => req.arg_types.first().cloned().unwrap_or(Type::Undefined),
|
||||||
|
OpCode::GetArg1 => req.arg_types.get(1).cloned().unwrap_or(Type::Undefined),
|
||||||
|
OpCode::GetArg2 => req.arg_types.get(2).cloned().unwrap_or(Type::Undefined),
|
||||||
|
OpCode::GetArg3 => req.arg_types.get(3).cloned().unwrap_or(Type::Undefined),
|
||||||
|
|
||||||
// ────────────────────────────────────── Returns ───────────────────────────────────
|
OpCode::GetArg => match instr.imm {
|
||||||
Return | ReturnAsync | ReturnUndef => val_types[instr.args[0]].clone(),
|
ImmOperand::Arg(k) => req
|
||||||
|
.arg_types
|
||||||
|
.get(k as usize)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Type::Undefined),
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
|
||||||
_ => Type::Unknown,
|
// ──────────────────────────────────── Objects ─────────────────────────────────────
|
||||||
|
OpCode::Object => Type::Empty,
|
||||||
|
OpCode::DefineField => {
|
||||||
|
let atom = match instr.imm {
|
||||||
|
ImmOperand::Atom(atom) => atom,
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_ret = matches!(instr.op, Return | ReturnAsync | ReturnUndef);
|
let arg0_ty = val_types[instr.args[0]].clone();
|
||||||
let is_loc_read = matches!(
|
let arg1_ty = val_types[instr.args[1]].clone();
|
||||||
instr.op,
|
|
||||||
OpCode::GetLoc0
|
ReturnOf((FnKind::DefineField(atom), vec![arg0_ty, arg1_ty]))
|
||||||
| OpCode::GetLoc1
|
}
|
||||||
| OpCode::GetLoc2
|
|
||||||
| OpCode::GetLoc3
|
// ────────────────────────────────────── Returns ───────────────────────────────────
|
||||||
| OpCode::GetLoc0Loc1
|
Return | ReturnAsync => val_types[instr.args[0]].clone(),
|
||||||
| OpCode::GetLoc
|
ReturnUndef => Type::Empty,
|
||||||
| OpCode::GetLoc8
|
|
||||||
| OpCode::GetLocCheck
|
_ => unreachable!(),
|
||||||
);
|
};
|
||||||
|
|
||||||
// TODO: is_loc_read logic is ungraceful
|
|
||||||
if is_ret {
|
if is_ret {
|
||||||
*ret_type = join_ty(ret_type, &ty);
|
ret_type = join_ty(&ret_type, &ty);
|
||||||
} else if !is_loc_read {
|
} else {
|
||||||
for &dst in &instr.dsts {
|
for &dst in &instr.dsts {
|
||||||
val_types[dst] = join_ty(&val_types[dst], &ty);
|
val_types[dst] = join_ty(&val_types[dst], &ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(InferResult {
|
||||||
|
val_types,
|
||||||
|
ret_type,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn atom_to_str(a: &AtomRef, atoms: &[Atom]) -> String {
|
pub fn atom_to_str(a: &AtomRef, atoms: &[Atom]) -> String {
|
||||||
|
|||||||
@@ -514,6 +514,7 @@ pub enum ImmOperand {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Instr {
|
pub struct Instr {
|
||||||
pub pc: usize,
|
pub pc: usize,
|
||||||
pub op: opcodes::OpCode,
|
pub op: opcodes::OpCode,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod declosure;
|
||||||
mod flatten;
|
mod flatten;
|
||||||
mod infer;
|
mod infer;
|
||||||
mod libqjs;
|
mod libqjs;
|
||||||
@@ -6,6 +7,7 @@ mod regify;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use declosure::declosure;
|
||||||
use flatten::flatten;
|
use flatten::flatten;
|
||||||
use infer::infer_types;
|
use infer::infer_types;
|
||||||
use regify::regify;
|
use regify::regify;
|
||||||
@@ -26,12 +28,8 @@ fn main() {
|
|||||||
let binary = libqjs::compile(&src, filename);
|
let binary = libqjs::compile(&src, filename);
|
||||||
|
|
||||||
// Process it
|
// Process it
|
||||||
let (flat_fns, atoms) = flatten(binary);
|
let (flat_fns, captures, atoms) = flatten(binary);
|
||||||
|
let flat_fns = declosure(flat_fns, &captures);
|
||||||
let reg_fns = regify(flat_fns);
|
let reg_fns = regify(flat_fns);
|
||||||
println!("{reg_fns:#?}");
|
|
||||||
println!("{atoms:#?}");
|
|
||||||
|
|
||||||
let type_cache = infer_types(®_fns, &atoms);
|
let type_cache = infer_types(®_fns, &atoms);
|
||||||
|
|
||||||
println!("{type_cache:#?}");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use super::RegInstr;
|
||||||
use crate::libqjs::parse::{ImmOperand, Instr, opcodes::OpCode};
|
use crate::libqjs::parse::{ImmOperand, Instr, opcodes::OpCode};
|
||||||
|
|
||||||
type BlockId = usize;
|
type BlockId = usize;
|
||||||
@@ -7,6 +8,7 @@ pub struct BasicBlock {
|
|||||||
pub start: usize,
|
pub start: usize,
|
||||||
pub end: usize,
|
pub end: usize,
|
||||||
pub succ: Vec<BlockId>,
|
pub succ: Vec<BlockId>,
|
||||||
|
pub postfix: Vec<RegInstr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicBlock {
|
impl BasicBlock {
|
||||||
@@ -15,6 +17,7 @@ impl BasicBlock {
|
|||||||
start,
|
start,
|
||||||
end: 0,
|
end: 0,
|
||||||
succ: vec![],
|
succ: vec![],
|
||||||
|
postfix: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,12 +38,10 @@ pub fn find_basic_blocks(code: &[Instr]) -> Vec<BasicBlock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If terminal, next instr (if exists) is a start
|
// If terminal, next instr (if exists) is a start
|
||||||
if is_terminal_instr(instr) {
|
if is_terminal_instr(instr) && i + 1 < code.len() {
|
||||||
if i + 1 < code.len() {
|
|
||||||
blocks.push(BasicBlock::new(i + 1));
|
blocks.push(BasicBlock::new(i + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
blocks.sort_unstable_by_key(|b| b.start);
|
blocks.sort_unstable_by_key(|b| b.start);
|
||||||
blocks.dedup_by_key(|b| b.start);
|
blocks.dedup_by_key(|b| b.start);
|
||||||
|
|||||||
+113
-64
@@ -43,14 +43,13 @@ pub struct RegFn {
|
|||||||
pub arg_count: usize,
|
pub arg_count: usize,
|
||||||
pub val_count: usize,
|
pub val_count: usize,
|
||||||
pub cpool: Vec<JsValue>,
|
pub cpool: Vec<JsValue>,
|
||||||
pub captures: Vec<ClosureVar>,
|
|
||||||
pub code: Vec<RegInstr>,
|
pub code: Vec<RegInstr>,
|
||||||
pub blocks: Vec<BasicBlock>,
|
pub blocks: Vec<BasicBlock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegFn {
|
impl RegFn {
|
||||||
fn from_flat(flat_fn: FlatFn) -> Self {
|
fn from_flat(flat_fn: FlatFn) -> Self {
|
||||||
let blocks = find_basic_blocks(&flat_fn.code);
|
let mut blocks = find_basic_blocks(&flat_fn.code);
|
||||||
let mut out = vec![RegInstr::nop(); flat_fn.code.len()];
|
let mut out = vec![RegInstr::nop(); flat_fn.code.len()];
|
||||||
|
|
||||||
let mut next_val = flat_fn.var_count; // Reserve values for locals
|
let mut next_val = flat_fn.var_count; // Reserve values for locals
|
||||||
@@ -62,30 +61,29 @@ impl RegFn {
|
|||||||
worklist.push(0);
|
worklist.push(0);
|
||||||
|
|
||||||
while let Some(block_idx) = worklist.pop() {
|
while let Some(block_idx) = worklist.pop() {
|
||||||
|
let block = &mut blocks[block_idx];
|
||||||
let mut stack: Vec<usize> = entry_stacks[block_idx].clone().unwrap_or_default();
|
let mut stack: Vec<usize> = entry_stacks[block_idx].clone().unwrap_or_default();
|
||||||
|
|
||||||
walk_block(
|
walk_block(block, &flat_fn.code, &mut stack, &mut next_val, &mut out);
|
||||||
&blocks[block_idx],
|
|
||||||
&flat_fn.code,
|
|
||||||
&mut stack,
|
|
||||||
&mut next_val,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
|
|
||||||
for &succ_idx in &blocks[block_idx].succ {
|
for succ_idx in block.succ.clone() {
|
||||||
if entry_stacks[succ_idx].is_none() {
|
let succ_stack = &mut entry_stacks[succ_idx];
|
||||||
entry_stacks[succ_idx] = Some(stack.clone());
|
|
||||||
|
match succ_stack {
|
||||||
|
Some(succ_stack) => add_block_postfix(block, &stack, succ_stack),
|
||||||
|
None => {
|
||||||
|
*succ_stack = Some(stack.clone());
|
||||||
worklist.push(succ_idx);
|
worklist.push(succ_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name: flat_fn.name,
|
name: flat_fn.name,
|
||||||
arg_count: flat_fn.arg_count,
|
arg_count: flat_fn.arg_count,
|
||||||
val_count: next_val,
|
val_count: next_val,
|
||||||
cpool: flat_fn.cpool,
|
cpool: flat_fn.cpool,
|
||||||
captures: flat_fn.captures,
|
|
||||||
code: out,
|
code: out,
|
||||||
blocks,
|
blocks,
|
||||||
}
|
}
|
||||||
@@ -115,8 +113,73 @@ fn walk_block(
|
|||||||
args: vec![],
|
args: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Some instrs have extra pops not reflected in info.n_pop
|
let info = instr.op.info();
|
||||||
let extra_pop = match instr.op {
|
let n_pop = info.n_pop as usize + extra_pops(instr);
|
||||||
|
let n_push = info.n_push as usize;
|
||||||
|
|
||||||
|
// Handle this instr's pops
|
||||||
|
for _ in 0..n_pop {
|
||||||
|
let v = stack.pop().unwrap();
|
||||||
|
out_instr.args.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locals are not renamed, local read/writes need special handling
|
||||||
|
// Handle read of a local value
|
||||||
|
match is_local_read(instr) {
|
||||||
|
Some(loc) => {
|
||||||
|
stack.push(loc);
|
||||||
|
out_instr.op = OpCode::Nop;
|
||||||
|
out_instr.imm = ImmOperand::None;
|
||||||
|
out[instr_idx] = out_instr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
None if instr.op == OpCode::GetLoc0Loc1 => {
|
||||||
|
stack.push(0);
|
||||||
|
stack.push(1);
|
||||||
|
out_instr.op = OpCode::Nop;
|
||||||
|
out_instr.imm = ImmOperand::None;
|
||||||
|
out[instr_idx] = out_instr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle write of a local value
|
||||||
|
match is_local_write(instr) {
|
||||||
|
Some(loc) => {
|
||||||
|
out_instr.op = OpCode::SetLoc;
|
||||||
|
out_instr.imm = ImmOperand::None;
|
||||||
|
out_instr.dsts.push(loc);
|
||||||
|
out[instr_idx] = out_instr;
|
||||||
|
|
||||||
|
if n_push == 1 {
|
||||||
|
stack.push(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
None if instr.op == OpCode::SetLocUninitialized => {
|
||||||
|
out_instr.op = OpCode::Nop;
|
||||||
|
out_instr.imm = ImmOperand::None;
|
||||||
|
out[instr_idx] = out_instr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle this instr's pushes
|
||||||
|
for _ in 0..n_push {
|
||||||
|
let v = fresh_val();
|
||||||
|
stack.push(v);
|
||||||
|
out_instr.dsts.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
out[instr_idx] = out_instr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_pops(instr: &Instr) -> usize {
|
||||||
|
match instr.op {
|
||||||
OpCode::Call0 => 0,
|
OpCode::Call0 => 0,
|
||||||
OpCode::Call1 => 1,
|
OpCode::Call1 => 1,
|
||||||
OpCode::Call2 => 2,
|
OpCode::Call2 => 2,
|
||||||
@@ -126,70 +189,56 @@ fn walk_block(
|
|||||||
ImmOperand::NpopU16 { npop, .. } => npop as usize,
|
ImmOperand::NpopU16 { npop, .. } => npop as usize,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
let info = instr.op.info();
|
|
||||||
let n_pop = info.n_pop as usize + extra_pop;
|
|
||||||
let n_push = info.n_push as usize;
|
|
||||||
|
|
||||||
// Handle this instr's pops
|
|
||||||
for _ in 0..n_pop {
|
|
||||||
let v = stack.pop().unwrap();
|
|
||||||
out_instr.args.push(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locals are not renamed, needs special handling
|
fn is_local_read(instr: &Instr) -> Option<usize> {
|
||||||
let local_access = match instr.op {
|
match instr.op {
|
||||||
OpCode::GetLoc0 | OpCode::PutLoc0 | OpCode::SetLoc0 => Some(0),
|
OpCode::GetLoc0 => Some(0),
|
||||||
OpCode::GetLoc1 | OpCode::PutLoc1 | OpCode::SetLoc1 => Some(1),
|
OpCode::GetLoc1 => Some(1),
|
||||||
OpCode::GetLoc2 | OpCode::PutLoc2 | OpCode::SetLoc2 => Some(2),
|
OpCode::GetLoc2 => Some(2),
|
||||||
OpCode::GetLoc3 | OpCode::PutLoc3 | OpCode::SetLoc3 => Some(3),
|
OpCode::GetLoc3 => Some(3),
|
||||||
|
|
||||||
OpCode::GetLoc
|
OpCode::GetLoc | OpCode::GetLoc8 | OpCode::GetLocCheck => match instr.imm_operands {
|
||||||
| OpCode::GetLoc8
|
ImmOperand::Loc(k) => Some(k as usize),
|
||||||
| OpCode::GetLocCheck
|
_ => unreachable!(),
|
||||||
| OpCode::PutLoc
|
},
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_local_write(instr: &Instr) -> Option<usize> {
|
||||||
|
match instr.op {
|
||||||
|
OpCode::PutLoc0 | OpCode::SetLoc0 => Some(0),
|
||||||
|
OpCode::PutLoc1 | OpCode::SetLoc1 => Some(1),
|
||||||
|
OpCode::PutLoc2 | OpCode::SetLoc2 => Some(2),
|
||||||
|
OpCode::PutLoc3 | OpCode::SetLoc3 => Some(3),
|
||||||
|
|
||||||
|
OpCode::PutLoc
|
||||||
| OpCode::PutLoc8
|
| OpCode::PutLoc8
|
||||||
| OpCode::PutLocCheck
|
| OpCode::PutLocCheck
|
||||||
| OpCode::PutLocCheckInit
|
| OpCode::PutLocCheckInit
|
||||||
| OpCode::SetLoc
|
| OpCode::SetLoc
|
||||||
| OpCode::SetLoc8
|
| OpCode::SetLoc8 => match instr.imm_operands {
|
||||||
| OpCode::SetLocUninitialized => match instr.imm_operands {
|
|
||||||
ImmOperand::Loc(k) => Some(k as usize),
|
ImmOperand::Loc(k) => Some(k as usize),
|
||||||
_ => None,
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
|
||||||
|
|
||||||
// Handle this instr's pushes, handling local accesses separately
|
|
||||||
match local_access {
|
|
||||||
Some(loc) => {
|
|
||||||
out_instr.dsts.push(loc);
|
|
||||||
|
|
||||||
if n_push == 1 {
|
|
||||||
stack.push(loc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The one op that reads two locals at once
|
fn add_block_postfix(block: &mut BasicBlock, stack: &[usize], succ_stack: &[usize]) {
|
||||||
None if instr.op == OpCode::GetLoc0Loc1 => {
|
let overlap = stack.iter().rev().zip(succ_stack.iter().rev());
|
||||||
for k in [0, 1] {
|
|
||||||
stack.push(k);
|
|
||||||
out_instr.dsts.push(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
for (val, succ_val) in overlap {
|
||||||
for _ in 0..n_push {
|
block.postfix.push(RegInstr {
|
||||||
let v = fresh_val();
|
dsts: vec![*succ_val],
|
||||||
stack.push(v);
|
op: OpCode::SetLoc,
|
||||||
out_instr.dsts.push(v);
|
imm: ImmOperand::None,
|
||||||
}
|
args: vec![*val],
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
out[instr_idx] = out_instr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user