New model

This commit is contained in:
2026-10-01 13:58:50 +00:00
parent a4d295fbd9
commit 23a2c3bc46
15 changed files with 3431 additions and 689 deletions
+115 -42
View File
@@ -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> {