Initial SSA impl + docs
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//! This module takes the nested QuickJS binary structure and separates the functions into a flat indexed namespace
|
||||
|
||||
use crate::parse::{
|
||||
Atom, AtomRef, ClosureKind, Function, Instruction, JsString, JsValue, QuickJSBinary, atom_names,
|
||||
Atom, AtomRef, ClosureKind, Function, Instr, JsString, JsValue, QuickJSBinary, atom_names,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -34,7 +34,7 @@ pub struct FlatFn {
|
||||
pub var_count: usize, // num locals
|
||||
pub captures: Vec<Capture>,
|
||||
pub cpool: Vec<JsValue>,
|
||||
pub code: Vec<Instruction>,
|
||||
pub code: Vec<Instr>,
|
||||
}
|
||||
|
||||
pub fn flatten(bin: QuickJSBinary) -> (Vec<FlatFn>, Vec<Atom>) {
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::process::Command;
|
||||
|
||||
use flatten::flatten;
|
||||
use parse::QuickJSBinary;
|
||||
use ssa::convert_ssa;
|
||||
|
||||
const QJSC: &str = "../third_party/quickjs/build/qjsc";
|
||||
const BIN_OUT: &str = "/tmp/qjsrt.bin";
|
||||
@@ -16,7 +17,7 @@ fn main() {
|
||||
|
||||
// Compile source file
|
||||
let output = Command::new(QJSC)
|
||||
.args(["-b", "-m", "-o", BIN_OUT])
|
||||
.args(["-b", "-o", BIN_OUT])
|
||||
.arg(filename)
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -27,4 +28,7 @@ fn main() {
|
||||
let bin = std::fs::read(BIN_OUT).unwrap();
|
||||
let (_, parsed) = QuickJSBinary::parse(&bin).unwrap();
|
||||
let (flat_fns, atoms) = flatten(parsed);
|
||||
|
||||
println!("{atoms:#?}");
|
||||
convert_ssa(flat_fns);
|
||||
}
|
||||
|
||||
@@ -516,13 +516,13 @@ pub enum ImmOperand {
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Instruction {
|
||||
pub struct Instr {
|
||||
pub pc: usize,
|
||||
pub op: opcodes::OpCode,
|
||||
pub imm_operands: ImmOperand,
|
||||
}
|
||||
|
||||
impl Debug for Instruction {
|
||||
impl Debug for Instr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:04} {}", self.pc, self.op.name())?;
|
||||
match &self.imm_operands {
|
||||
@@ -544,7 +544,7 @@ impl Debug for Instruction {
|
||||
// - Jump operands are offsets from the operand field's own position,
|
||||
// not from the start or end of the instruction. For the
|
||||
// `atom_label_*` formats that base sits 4 bytes into the operands.
|
||||
fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instruction>> {
|
||||
fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instr>> {
|
||||
let jump = |base: usize, offset: i64| (base as i64 + offset) as usize;
|
||||
|
||||
let mut out = Vec::new();
|
||||
@@ -635,7 +635,7 @@ fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instruction>> {
|
||||
},
|
||||
};
|
||||
|
||||
out.push(Instruction {
|
||||
out.push(Instr {
|
||||
pc,
|
||||
op: info.op,
|
||||
imm_operands: operands,
|
||||
@@ -665,7 +665,7 @@ pub struct Function {
|
||||
pub closure_vars: Vec<ClosureVar>,
|
||||
pub cpool: Vec<JsValue>,
|
||||
/// LLM GENERATED, TENTATIVE
|
||||
pub byte_code: Vec<Instruction>,
|
||||
pub byte_code: Vec<Instr>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
|
||||
+115
-25
@@ -1,45 +1,68 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::parse::{ImmOperand, Instruction};
|
||||
use crate::{
|
||||
flatten::FlatFn,
|
||||
parse::{ImmOperand, opcodes::OpCode},
|
||||
};
|
||||
|
||||
fn is_jump_op(op: OpCode) -> bool {
|
||||
use OpCode::*;
|
||||
matches!(
|
||||
op,
|
||||
Goto | Goto8 | Goto16 | IfFalse | IfTrue | IfFalse8 | IfTrue8
|
||||
)
|
||||
}
|
||||
|
||||
fn is_terminal_op(op: OpCode) -> bool {
|
||||
use OpCode::*;
|
||||
matches!(op, Return | ReturnUndef | ReturnAsync | Throw)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Terminator {
|
||||
UncondJump(usize),
|
||||
CondJump {
|
||||
truthy: bool,
|
||||
value: usize,
|
||||
true_block: usize,
|
||||
false_block: usize,
|
||||
},
|
||||
Terminal,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlockSpan {
|
||||
start: usize,
|
||||
end: usize,
|
||||
term: Terminator,
|
||||
}
|
||||
|
||||
fn find_basic_blocks(code: &[Instruction]) -> Vec<BlockSpan> {
|
||||
fn find_basic_blocks(flat_fn: &FlatFn) -> Vec<BlockSpan> {
|
||||
// Find block starts: the first instruction, every jump target,
|
||||
// and whatever follows a jump or a return
|
||||
let mut starts = vec![0usize];
|
||||
let code = &flat_fn.code;
|
||||
let mut starts = vec![0];
|
||||
|
||||
for (i, instr) in code.iter().enumerate() {
|
||||
use crate::parse::opcodes::OpCode::*;
|
||||
// If cond/uncond jump, jump target is a start, next instr (if exists) is another start
|
||||
if is_jump_op(instr.op) {
|
||||
let jump_target = match instr.imm_operands {
|
||||
ImmOperand::Label { target } => code.iter().position(|x| x.pc == target).unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match instr.op {
|
||||
// If cond/uncond jump, jump target is a start, next instr (if exists) is another start
|
||||
Goto | Goto8 | Goto16 | IfFalse | IfTrue | IfFalse8 | IfTrue8 => {
|
||||
let jump_target = match instr.imm_operands {
|
||||
ImmOperand::Label { target } => {
|
||||
code.iter().position(|x| x.pc == target).unwrap()
|
||||
}
|
||||
_ => panic!("branch opcode without a label operand"),
|
||||
};
|
||||
starts.push(jump_target);
|
||||
|
||||
starts.push(jump_target);
|
||||
|
||||
if i + 1 < code.len() {
|
||||
starts.push(i + 1);
|
||||
}
|
||||
if i + 1 < code.len() {
|
||||
starts.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If terminal, next instr if exists is a start
|
||||
Return | ReturnUndef | ReturnAsync | Throw => {
|
||||
if i + 1 < code.len() {
|
||||
starts.push(i + 1);
|
||||
}
|
||||
// If terminal, next instr (if exists) is a start
|
||||
if is_terminal_op(instr.op) {
|
||||
if i + 1 < code.len() {
|
||||
starts.push(i + 1);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +76,75 @@ fn find_basic_blocks(code: &[Instruction]) -> Vec<BlockSpan> {
|
||||
.map(|(i, &start)| BlockSpan {
|
||||
start,
|
||||
end: starts.get(i + 1).copied().unwrap_or(code.len()),
|
||||
term: Terminator::Terminal,
|
||||
})
|
||||
.collect::<Vec<BlockSpan>>();
|
||||
|
||||
blocks
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SsaInstr {
|
||||
dsts: Vec<usize>,
|
||||
op: OpCode,
|
||||
imm: ImmOperand,
|
||||
args: Vec<usize>,
|
||||
}
|
||||
|
||||
fn ssa_walk(flat_fn: &FlatFn) -> Vec<SsaInstr> {
|
||||
//let mut locals = vec![];
|
||||
let mut stack = vec![];
|
||||
let mut output = vec![];
|
||||
let mut next_val = 0;
|
||||
|
||||
let mut fresh_val = || {
|
||||
let v = next_val;
|
||||
next_val += 1;
|
||||
v
|
||||
};
|
||||
|
||||
for instr in &flat_fn.code {
|
||||
let mut ssa_instr = SsaInstr {
|
||||
dsts: vec![],
|
||||
op: instr.op,
|
||||
imm: instr.imm_operands,
|
||||
args: vec![],
|
||||
};
|
||||
|
||||
let info = instr.op.info();
|
||||
let n_pop = info.n_pop;
|
||||
let n_push = info.n_push;
|
||||
|
||||
for _ in 0..n_pop {
|
||||
let v = stack.pop().unwrap();
|
||||
ssa_instr.args.push(v);
|
||||
}
|
||||
|
||||
for _ in 0..n_push {
|
||||
let v = fresh_val();
|
||||
stack.push(v);
|
||||
ssa_instr.dsts.push(v);
|
||||
}
|
||||
|
||||
output.push(ssa_instr);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn compute_terminators(ssa: &[SsaInstr], blocks: &mut [BlockSpan]) {
|
||||
for instr in ssa {
|
||||
if is_jump_op(instr.op) {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_ssa(flat_fns: Vec<FlatFn>) {
|
||||
for flat_fn in flat_fns {
|
||||
let blocks = find_basic_blocks(&flat_fn);
|
||||
let ssa = ssa_walk(&flat_fn);
|
||||
|
||||
println!("{flat_fn:#?}");
|
||||
println!("{blocks:#?}");
|
||||
println!("{ssa:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user