Misc cleanups

This commit is contained in:
UditDey
2026-09-28 07:47:01 +00:00
parent e93ce94ac7
commit a4d295fbd9
18 changed files with 563 additions and 1388 deletions
+59 -72
View File
@@ -8,7 +8,7 @@ use crate::{
pub use basic_block::BasicBlock;
use basic_block::find_basic_blocks;
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct RegInstr {
pub dsts: Vec<usize>,
pub op: OpCode,
@@ -19,7 +19,7 @@ pub struct RegInstr {
impl RegInstr {
fn nop() -> Self {
Self {
dsts: vec![],
dsts: vec![], // TODO: do we ever have more than one dst?
op: OpCode::Nop,
imm: ImmOperand::None,
args: vec![],
@@ -27,23 +27,13 @@ impl RegInstr {
}
}
impl std::fmt::Debug for RegInstr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?} = {:?} {:?} {:?}",
self.dsts, self.op, self.imm, self.args
)
}
}
#[derive(Debug)]
pub struct RegFn {
pub name: AtomRef,
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 var_count: usize,
pub val_count: usize,
pub cpool: Vec<JsValue>,
pub code: Vec<RegInstr>,
@@ -51,32 +41,41 @@ pub struct RegFn {
}
impl RegFn {
fn from_flat(flat_fn: FlatFn) -> Self {
let mut blocks = find_basic_blocks(&flat_fn.code);
let mut out = vec![RegInstr::nop(); flat_fn.code.len()];
fn from_flat(f: FlatFn) -> Self {
let mut 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 = flat_fn.var_count + flat_fn.arg_count;
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()];
let mut worklist = vec![];
// 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 stack: Vec<usize> = entry_stacks[block_idx].clone().unwrap_or_default();
let mut entry_stack = entry_stacks[block_idx].clone().unwrap_or_default();
walk_block(block, &flat_fn.code, flat_fn.var_count, &mut stack, &mut next_val, &mut out);
walk_block(
block,
&f.code,
f.var_count,
&mut entry_stack,
&mut next_val,
&mut out,
);
for succ_idx in block.succ.clone() {
let succ_stack = &mut entry_stacks[succ_idx];
match succ_stack {
Some(succ_stack) => add_block_postfix(block, &stack, succ_stack),
Some(succ_stack) => add_block_postfix(block, &entry_stack, succ_stack),
None => {
*succ_stack = Some(stack.clone());
*succ_stack = Some(entry_stack.clone());
worklist.push(succ_idx);
}
}
@@ -84,17 +83,18 @@ impl RegFn {
}
Self {
name: flat_fn.name,
arg_count: flat_fn.arg_count,
var_count: flat_fn.var_count,
name: f.name,
arg_count: f.arg_count,
var_count: f.var_count,
val_count: next_val,
cpool: flat_fn.cpool,
cpool: f.cpool,
code: out,
blocks,
}
}
}
/// Walk a basic block's instructions, converting each instr to register form
fn walk_block(
block: &BasicBlock,
code: &[Instr],
@@ -129,51 +129,37 @@ fn walk_block(
out_instr.args.push(v);
}
// Locals and args are not renamed, their read/writes need special handling
// Handle read of a local or arg value
match is_var_read(instr, var_count) {
// A read takes the var's value at this point. The stack holds a
// copy, never the var itself.
Some(var) => {
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;
}
None => (),
// 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
match is_var_write(instr, var_count) {
Some(loc) => {
out_instr.op = OpCode::SetLoc;
out_instr.imm = ImmOperand::None;
out_instr.dsts.push(loc);
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]);
}
// 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;
}
None if instr.op == OpCode::SetLocUninitialized => {
out_instr.op = OpCode::Nop;
out_instr.imm = ImmOperand::None;
out[instr_idx] = out_instr;
continue;
}
None => (),
out[instr_idx] = out_instr;
continue;
}
// set_home_object binds [[HomeObject]], which only `super` reads, and
// super is not supported. It returns both its operands unchanged, so on
// values it is nothing at all: hand them straight back.
// 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]);
@@ -183,8 +169,7 @@ fn walk_block(
continue;
}
// dup only copies a stack slot; it computes nothing. The copy is the
// same value, so whatever is done through it is done to the original.
// Handle `Dup` and `Swap`, which just push values
if instr.op == OpCode::Dup {
stack.push(out_instr.args[0]);
stack.push(out_instr.args[0]);
@@ -194,7 +179,6 @@ fn walk_block(
continue;
}
// swap only reorders the stack; it computes nothing.
if instr.op == OpCode::Swap {
stack.push(out_instr.args[0]);
stack.push(out_instr.args[1]);
@@ -204,10 +188,7 @@ fn walk_block(
continue;
}
// A field write is a define whose result lands back in the object's own
// value, so later reads of that object see the field. This is what lets
// a chain of writes to one local build a single object type, and it is
// why no later stage needs to know PutField at all.
// 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]);
@@ -215,7 +196,7 @@ fn walk_block(
continue;
}
// Handle this instr's pushes
// Normal case: handle this instr's pushes
for _ in 0..n_push {
let v = fresh_val();
stack.push(v);
@@ -226,6 +207,7 @@ 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,
@@ -303,6 +285,11 @@ 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]) {
let overlap = stack.iter().rev().zip(succ_stack.iter().rev());