New model
This commit is contained in:
+115
-42
@@ -51,33 +51,70 @@ impl RegFn {
|
||||
// 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);
|
||||
|
||||
// Walk blocks in CFG order, starting from block 0
|
||||
let mut worklist = vec![];
|
||||
entry_stacks[0] = Some(vec![]);
|
||||
worklist.push(0);
|
||||
|
||||
while let Some(block_idx) = worklist.pop() {
|
||||
let block = &mut blocks[block_idx];
|
||||
let mut entry_stack = entry_stacks[block_idx].clone().unwrap_or_default();
|
||||
let mut stack = entry_stacks[block_idx].clone().unwrap_or_default();
|
||||
|
||||
walk_block(
|
||||
block,
|
||||
&blocks[block_idx],
|
||||
&f.code,
|
||||
f.var_count,
|
||||
&mut entry_stack,
|
||||
&mut stack,
|
||||
&mut next_val,
|
||||
&mut out,
|
||||
);
|
||||
|
||||
for succ_idx in block.succ.clone() {
|
||||
let succ_stack = &mut entry_stacks[succ_idx];
|
||||
for i in 0..blocks[block_idx].succ.len() {
|
||||
let succ_idx = blocks[block_idx].succ[i];
|
||||
|
||||
match succ_stack {
|
||||
Some(succ_stack) => add_block_postfix(block, &entry_stack, succ_stack),
|
||||
// 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 => {
|
||||
*succ_stack = Some(entry_stack.clone());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,11 +140,7 @@ fn walk_block(
|
||||
next_val: &mut usize,
|
||||
out: &mut [RegInstr],
|
||||
) {
|
||||
let mut fresh_val = || {
|
||||
let v = *next_val;
|
||||
*next_val += 1;
|
||||
v
|
||||
};
|
||||
let mut fresh_val = || fresh_val(next_val);
|
||||
|
||||
for instr_idx in block.start..block.end {
|
||||
let instr = &code[instr_idx];
|
||||
@@ -129,6 +162,13 @@ fn walk_block(
|
||||
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) {
|
||||
@@ -188,10 +228,17 @@ fn walk_block(
|
||||
continue;
|
||||
}
|
||||
|
||||
// A field write is a define whose result lands back in the object's own value
|
||||
if instr.op == OpCode::PutField {
|
||||
out_instr.op = OpCode::DefineField;
|
||||
out_instr.dsts.push(out_instr.args[1]);
|
||||
// A field write lands back in the object's own value: the object with
|
||||
// the field is the same object. define_field also leaves it on the
|
||||
// stack, put_field does not
|
||||
if matches!(instr.op, OpCode::PutField | OpCode::DefineField) {
|
||||
let obj = out_instr.args[1];
|
||||
|
||||
if n_push == 1 {
|
||||
stack.push(obj);
|
||||
}
|
||||
|
||||
out_instr.dsts.push(obj);
|
||||
out[instr_idx] = out_instr;
|
||||
continue;
|
||||
}
|
||||
@@ -209,21 +256,43 @@ fn walk_block(
|
||||
|
||||
/// Some instrs have extra pops not reflected by instr.op.info().n_pop
|
||||
fn extra_pops(instr: &Instr) -> usize {
|
||||
match instr.op {
|
||||
OpCode::Call0 => 0,
|
||||
OpCode::Call1 => 1,
|
||||
OpCode::Call2 => 2,
|
||||
OpCode::Call3 => 3,
|
||||
_ => match instr.imm {
|
||||
ImmOperand::Npop(npop) => npop as usize,
|
||||
ImmOperand::NpopU16 { npop, .. } => npop as usize,
|
||||
_ => 0,
|
||||
},
|
||||
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.
|
||||
fn is_var_read(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
pub fn is_var_read(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
let arg = |k: usize| Some(var_count + k);
|
||||
|
||||
match instr.op {
|
||||
@@ -252,7 +321,7 @@ fn is_var_read(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
}
|
||||
|
||||
/// The value id of the local or arg that `instr` writes, if any.
|
||||
fn is_var_write(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
pub fn is_var_write(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
let arg = |k: usize| Some(var_count + k);
|
||||
|
||||
match instr.op {
|
||||
@@ -285,22 +354,26 @@ fn is_var_write(instr: &Instr, var_count: usize) -> Option<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a postfix to a block to join values with a successor's entry stack
|
||||
///
|
||||
/// This does the job of phi nodes in typical SSA, Currently only relevant to
|
||||
/// ternary operators because ternaries are the only place where JS becomes
|
||||
/// "expression oriented"
|
||||
fn add_block_postfix(block: &mut BasicBlock, stack: &[usize], succ_stack: &[usize]) {
|
||||
/// 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());
|
||||
|
||||
for (val, succ_val) in overlap {
|
||||
block.postfix.push(RegInstr {
|
||||
dsts: vec![*succ_val],
|
||||
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],
|
||||
});
|
||||
}
|
||||
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> {
|
||||
|
||||
Reference in New Issue
Block a user