Mid rewrite
This commit is contained in:
+21
-354
@@ -1,25 +1,34 @@
|
||||
mod basic_block;
|
||||
mod walk;
|
||||
|
||||
use crate::{
|
||||
flatten::FlatFn,
|
||||
libqjs::parse::{ImmOperand, Instr, JsValue, opcodes::OpCode},
|
||||
libqjs::parse::{ImmOperand, JsValue, opcodes::OpCode},
|
||||
};
|
||||
|
||||
pub use basic_block::BasicBlock;
|
||||
use basic_block::find_basic_blocks;
|
||||
use walk::walk_block;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Operand {
|
||||
Local(usize),
|
||||
Arg(usize),
|
||||
Reg(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RegInstr {
|
||||
pub dsts: Vec<usize>,
|
||||
pub dsts: Vec<Operand>,
|
||||
pub op: OpCode,
|
||||
pub imm: ImmOperand,
|
||||
pub args: Vec<usize>,
|
||||
pub args: Vec<Operand>,
|
||||
}
|
||||
|
||||
impl RegInstr {
|
||||
fn nop() -> Self {
|
||||
Self {
|
||||
dsts: vec![], // TODO: do we ever have more than one dst?
|
||||
dsts: vec![],
|
||||
op: OpCode::Nop,
|
||||
imm: ImmOperand::None,
|
||||
args: vec![],
|
||||
@@ -30,10 +39,8 @@ impl RegInstr {
|
||||
#[derive(Debug)]
|
||||
pub struct RegFn {
|
||||
pub arg_count: usize,
|
||||
pub var_count: usize,
|
||||
/// Locals and args are not renamed: local `k` is value id `k`, and arg `k`
|
||||
/// is value id `var_count + k`.
|
||||
pub val_count: usize,
|
||||
pub local_count: usize,
|
||||
pub reg_count: usize,
|
||||
pub cpool: Vec<JsValue>,
|
||||
pub code: Vec<RegInstr>,
|
||||
pub blocks: Vec<BasicBlock>,
|
||||
@@ -41,18 +48,12 @@ pub struct RegFn {
|
||||
|
||||
impl RegFn {
|
||||
fn from_flat(f: FlatFn) -> Self {
|
||||
let mut blocks = find_basic_blocks(&f.code);
|
||||
let blocks = find_basic_blocks(&f.code);
|
||||
let mut out = vec![RegInstr::nop(); f.code.len()];
|
||||
|
||||
// Reserve values for locals, then args
|
||||
let mut next_val = f.var_count + f.arg_count;
|
||||
|
||||
// The stack each block recieves at entry from its predecessors
|
||||
let mut entry_stacks: Vec<Option<Vec<usize>>> = vec![None; blocks.len()];
|
||||
|
||||
// How many blocks lead to each block
|
||||
let mut n_preds = vec![0; blocks.len()];
|
||||
blocks.iter().flat_map(|b| &b.succ).for_each(|&s| n_preds[s] += 1);
|
||||
let mut entry_stacks: Vec<Option<Vec<Operand>>> = vec![None; blocks.len()];
|
||||
let mut next_reg = 0;
|
||||
|
||||
// Walk blocks in CFG order, starting from block 0
|
||||
let mut worklist = vec![];
|
||||
@@ -65,348 +66,14 @@ impl RegFn {
|
||||
walk_block(
|
||||
&blocks[block_idx],
|
||||
&f.code,
|
||||
f.var_count,
|
||||
&mut stack,
|
||||
&mut next_val,
|
||||
&mut next_reg,
|
||||
&mut out,
|
||||
);
|
||||
|
||||
for i in 0..blocks[block_idx].succ.len() {
|
||||
let succ_idx = blocks[block_idx].succ[i];
|
||||
|
||||
// A join's stack slots are new values, which every block that
|
||||
// leads to it copies its own values into. Naming a slot after
|
||||
// one predecessor's value would give that value a second
|
||||
// meaning (this does the job of phi nodes in SSA)
|
||||
let succ_stack = match &entry_stacks[succ_idx] {
|
||||
Some(succ_stack) => succ_stack.clone(),
|
||||
None => {
|
||||
let succ_stack: Vec<usize> = match n_preds[succ_idx] {
|
||||
1 => stack.clone(),
|
||||
_ => stack.iter().map(|_| fresh_val(&mut next_val)).collect(),
|
||||
};
|
||||
|
||||
entry_stacks[succ_idx] = Some(succ_stack.clone());
|
||||
worklist.push(succ_idx);
|
||||
succ_stack
|
||||
}
|
||||
};
|
||||
|
||||
let copies = edge_copies(&stack, &succ_stack);
|
||||
|
||||
if copies.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The copies belong to this one edge. A block that can go
|
||||
// elsewhere too gets them on a block of their own on this
|
||||
// edge, so the other edge does not run them
|
||||
if blocks[block_idx].succ.len() == 1 {
|
||||
blocks[block_idx].postfix.extend(copies);
|
||||
} else {
|
||||
let at = blocks[succ_idx].start;
|
||||
|
||||
blocks.push(BasicBlock {
|
||||
start: at,
|
||||
end: at,
|
||||
succ: vec![succ_idx],
|
||||
postfix: copies,
|
||||
});
|
||||
|
||||
blocks[block_idx].succ[i] = blocks.len() - 1;
|
||||
}
|
||||
}
|
||||
// TODO: handle sucessors
|
||||
}
|
||||
|
||||
// A define_field into an object that no op of this fn made (a class
|
||||
// field on `this`, a static field on the class) is a write like any
|
||||
// other: a put_field. So a define_field is always a write into an
|
||||
// object before it goes anywhere, and its field is never absent (see
|
||||
// `Cell::write()` in infer)
|
||||
let mut created = vec![false; next_val];
|
||||
|
||||
for instr in &out {
|
||||
if matches!(instr.op, OpCode::Object | OpCode::Fclosure | OpCode::Fclosure8) {
|
||||
created[instr.dsts[0]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for instr in &mut out {
|
||||
if instr.op == OpCode::DefineField && !created[instr.args[1]] {
|
||||
instr.op = OpCode::PutField;
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
arg_count: f.arg_count,
|
||||
var_count: f.var_count,
|
||||
val_count: next_val,
|
||||
cpool: f.cpool,
|
||||
code: out,
|
||||
blocks,
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk a basic block's instructions, converting each instr to register form
|
||||
fn walk_block(
|
||||
block: &BasicBlock,
|
||||
code: &[Instr],
|
||||
var_count: usize,
|
||||
stack: &mut Vec<usize>,
|
||||
next_val: &mut usize,
|
||||
out: &mut [RegInstr],
|
||||
) {
|
||||
let mut fresh_val = || fresh_val(next_val);
|
||||
|
||||
for instr_idx in block.start..block.end {
|
||||
let instr = &code[instr_idx];
|
||||
|
||||
let mut out_instr = RegInstr {
|
||||
dsts: vec![],
|
||||
op: instr.op,
|
||||
imm: instr.imm,
|
||||
args: vec![],
|
||||
};
|
||||
|
||||
let info = instr.op.info();
|
||||
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);
|
||||
}
|
||||
|
||||
if matches!(
|
||||
instr.op,
|
||||
OpCode::Call | OpCode::CallMethod | OpCode::CallConstructor
|
||||
) {
|
||||
out_instr.args = call_operands(instr.op, out_instr.args);
|
||||
}
|
||||
|
||||
// Locals and args are not renamed, so their read/writes need special handling
|
||||
// Handle read of a local or arg
|
||||
if let Some(var) = is_var_read(instr, var_count) {
|
||||
let v = fresh_val();
|
||||
stack.push(v);
|
||||
out_instr.op = OpCode::SetLoc;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out_instr.dsts.push(v);
|
||||
out_instr.args.push(var);
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle write of a local or arg value
|
||||
if let Some(var) = is_var_write(instr, var_count) {
|
||||
out_instr.op = OpCode::SetLoc;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out_instr.dsts.push(var);
|
||||
|
||||
// set_loc also leaves the value it stored on the stack.
|
||||
if n_push == 1 {
|
||||
stack.push(out_instr.args[0]); // args set by previos pops loop
|
||||
}
|
||||
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some more special cases:
|
||||
// set_home_object binds [[HomeObject]], which only `super` reads,
|
||||
// `super` is unsupported currently, It returns both its operands unchanged
|
||||
if instr.op == OpCode::SetHomeObject {
|
||||
stack.push(out_instr.args[1]);
|
||||
stack.push(out_instr.args[0]);
|
||||
out_instr.op = OpCode::Nop;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle `Dup`, `Dup2`, `Swap` and `ToPropkey2`, which just push values
|
||||
if instr.op == OpCode::Dup {
|
||||
stack.push(out_instr.args[0]);
|
||||
stack.push(out_instr.args[0]);
|
||||
out_instr.op = OpCode::Nop;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
if instr.op == OpCode::Dup2 {
|
||||
let (a, b) = (out_instr.args[1], out_instr.args[0]);
|
||||
stack.extend([a, b, a, b]);
|
||||
out_instr.op = OpCode::Nop;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// to_propkey2 makes the key on top a property key, and keeps the
|
||||
// object under it. A key is a number, which it keeps as it is (see
|
||||
// infer), so it only gives both back
|
||||
if instr.op == OpCode::ToPropkey2 {
|
||||
stack.push(out_instr.args[1]);
|
||||
stack.push(out_instr.args[0]);
|
||||
out_instr.op = OpCode::Nop;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
if instr.op == OpCode::Swap {
|
||||
stack.push(out_instr.args[0]);
|
||||
stack.push(out_instr.args[1]);
|
||||
out_instr.op = OpCode::Nop;
|
||||
out_instr.imm = ImmOperand::None;
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// define_field leaves the object it wrote on the stack: the same value,
|
||||
// not a new one
|
||||
if instr.op == OpCode::DefineField {
|
||||
stack.push(out_instr.args[1]);
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normal case: 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Some instrs have extra pops not reflected by instr.op.info().n_pop
|
||||
fn extra_pops(instr: &Instr) -> usize {
|
||||
match instr.imm {
|
||||
ImmOperand::Npop(npop) => npop as usize,
|
||||
ImmOperand::NpopU16 { npop, .. } => npop as usize,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Put a call's popped operands in the order of the callee's params: the
|
||||
/// callee (which is its env), `this`, then the args in source order
|
||||
///
|
||||
/// `call_method` has `this` at the bottom of its frame, below the callee.
|
||||
/// `call_constructor` has the callee at the bottom and `this` above it (see
|
||||
/// transform). `call` has `this` on top, where transform pushed it
|
||||
fn call_operands(op: OpCode, mut popped: Vec<usize>) -> Vec<usize> {
|
||||
let (callee, this) = match op {
|
||||
OpCode::CallMethod => {
|
||||
let this = popped.pop().unwrap();
|
||||
(popped.pop().unwrap(), this)
|
||||
}
|
||||
OpCode::CallConstructor => {
|
||||
let callee = popped.pop().unwrap();
|
||||
(callee, popped.pop().unwrap())
|
||||
}
|
||||
_ => {
|
||||
let callee = popped.pop().unwrap();
|
||||
(callee, popped.remove(0))
|
||||
}
|
||||
};
|
||||
|
||||
// What remains are the args, last arg first
|
||||
popped.reverse();
|
||||
|
||||
[callee, this].into_iter().chain(popped).collect()
|
||||
}
|
||||
|
||||
/// The value id of the local or arg that `instr` reads, if any.
|
||||
pub fn is_var_read(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
let arg = |k: usize| Some(var_count + k);
|
||||
|
||||
match instr.op {
|
||||
OpCode::GetLoc0 => Some(0),
|
||||
OpCode::GetLoc1 => Some(1),
|
||||
OpCode::GetLoc2 => Some(2),
|
||||
OpCode::GetLoc3 => Some(3),
|
||||
|
||||
OpCode::GetLoc | OpCode::GetLoc8 | OpCode::GetLocCheck => match instr.imm {
|
||||
ImmOperand::Loc(k) => Some(k as usize),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
||||
OpCode::GetArg0 => arg(0),
|
||||
OpCode::GetArg1 => arg(1),
|
||||
OpCode::GetArg2 => arg(2),
|
||||
OpCode::GetArg3 => arg(3),
|
||||
|
||||
OpCode::GetArg => match instr.imm {
|
||||
ImmOperand::Arg(k) => arg(k as usize),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The value id of the local or arg that `instr` writes, if any.
|
||||
pub fn is_var_write(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
let arg = |k: usize| Some(var_count + k);
|
||||
|
||||
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::PutLocCheck
|
||||
| OpCode::PutLocCheckInit
|
||||
| OpCode::SetLoc
|
||||
| OpCode::SetLoc8 => match instr.imm {
|
||||
ImmOperand::Loc(k) => Some(k as usize),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
||||
OpCode::PutArg0 | OpCode::SetArg0 => arg(0),
|
||||
OpCode::PutArg1 | OpCode::SetArg1 => arg(1),
|
||||
OpCode::PutArg2 | OpCode::SetArg2 => arg(2),
|
||||
OpCode::PutArg3 | OpCode::SetArg3 => arg(3),
|
||||
|
||||
OpCode::PutArg | OpCode::SetArg => match instr.imm {
|
||||
ImmOperand::Arg(k) => arg(k as usize),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The copies that carry the values on a block's stack into its successor's
|
||||
/// entry stack
|
||||
fn edge_copies(stack: &[usize], succ_stack: &[usize]) -> Vec<RegInstr> {
|
||||
let overlap = stack.iter().rev().zip(succ_stack.iter().rev());
|
||||
|
||||
overlap
|
||||
.filter(|(val, succ_val)| val != succ_val)
|
||||
.map(|(&val, &succ_val)| RegInstr {
|
||||
dsts: vec![succ_val],
|
||||
op: OpCode::SetLoc,
|
||||
imm: ImmOperand::None,
|
||||
args: vec![val],
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn fresh_val(next_val: &mut usize) -> usize {
|
||||
let v = *next_val;
|
||||
*next_val += 1;
|
||||
v
|
||||
}
|
||||
|
||||
pub fn regify(flat_fns: Vec<FlatFn>) -> Vec<RegFn> {
|
||||
flat_fns.into_iter().map(RegFn::from_flat).collect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user