467 lines
16 KiB
Rust
467 lines
16 KiB
Rust
use std::fmt::Write;
|
|
|
|
use colored::Colorize;
|
|
use num_enum::TryFromPrimitive;
|
|
|
|
use crate::{InstructionPtr, Op, OperandType, PrimOpPhase};
|
|
|
|
pub trait DisassemblerContext {
|
|
fn resolve_string(&self, id: u32) -> &str;
|
|
fn get_code(&self) -> &[u8];
|
|
}
|
|
|
|
pub struct Disassembler<'a, Ctx> {
|
|
code: &'a [u8],
|
|
ctx: &'a Ctx,
|
|
pc: usize,
|
|
}
|
|
|
|
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
|
|
Self {
|
|
code: ctx.get_code(),
|
|
ctx,
|
|
pc: ip.0,
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_u8(&mut self) -> u8 {
|
|
let b = self.code[self.pc];
|
|
self.pc += 1;
|
|
b
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_u16(&mut self) -> u16 {
|
|
let bytes = self.code[self.pc..self.pc + 2]
|
|
.try_into()
|
|
.expect("no enough bytes");
|
|
self.pc += 2;
|
|
u16::from_le_bytes(bytes)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_u32(&mut self) -> u32 {
|
|
let bytes = self.code[self.pc..self.pc + 4]
|
|
.try_into()
|
|
.expect("no enough bytes");
|
|
self.pc += 4;
|
|
u32::from_le_bytes(bytes)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_i32(&mut self) -> i32 {
|
|
let bytes = self.code[self.pc..self.pc + 4]
|
|
.try_into()
|
|
.expect("no enough bytes");
|
|
self.pc += 4;
|
|
i32::from_le_bytes(bytes)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_i64(&mut self) -> i64 {
|
|
let bytes = self.code[self.pc..self.pc + 8]
|
|
.try_into()
|
|
.expect("no enough bytes");
|
|
self.pc += 8;
|
|
i64::from_le_bytes(bytes)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_f64(&mut self) -> f64 {
|
|
let bytes = self.code[self.pc..self.pc + 8]
|
|
.try_into()
|
|
.expect("no enough bytes");
|
|
self.pc += 8;
|
|
f64::from_le_bytes(bytes)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn read_operand_data(&mut self) {
|
|
use OperandType::*;
|
|
let tag = self.read_u8();
|
|
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
|
|
match ty {
|
|
Const => {
|
|
self.read_u32();
|
|
}
|
|
BigInt => {
|
|
self.read_i64();
|
|
}
|
|
Local => {
|
|
self.read_u8();
|
|
self.read_u32();
|
|
}
|
|
BuiltinConst => {
|
|
self.read_u32();
|
|
}
|
|
Builtins => {}
|
|
ReplBinding => {
|
|
self.read_u32();
|
|
}
|
|
ScopedImportBinding => {
|
|
self.read_u32();
|
|
self.read_u32();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn disassemble(&mut self) -> String {
|
|
self.disassemble_impl(false)
|
|
}
|
|
|
|
pub fn disassemble_colored(&mut self) -> String {
|
|
self.disassemble_impl(true)
|
|
}
|
|
|
|
fn disassemble_impl(&mut self, color: bool) -> String {
|
|
let mut out = String::new();
|
|
if color {
|
|
let _ = writeln!(out, "{}", "=== Bytecode Disassembly ===".bold().white());
|
|
let _ = writeln!(
|
|
out,
|
|
"{} {}",
|
|
"Length:".white(),
|
|
format!("{} bytes", self.code.len()).cyan()
|
|
);
|
|
} else {
|
|
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
|
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
|
}
|
|
|
|
while self.pc < self.code.len() {
|
|
let start_pos = self.pc;
|
|
let op_byte = self.read_u8();
|
|
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
|
|
|
|
let bytes_slice = &self.code[start_pos + 1..self.pc];
|
|
let mut chunks = bytes_slice.chunks(4);
|
|
|
|
let first_chunk = chunks.next().unwrap_or(&[]);
|
|
let bytes_str = {
|
|
let mut temp = format!("{:02x}", self.code[start_pos]);
|
|
for b in first_chunk {
|
|
let _ = write!(&mut temp, " {:02x}", b);
|
|
}
|
|
temp
|
|
};
|
|
|
|
if color {
|
|
let sep = if args.is_empty() { "" } else { " " };
|
|
let _ = writeln!(
|
|
out,
|
|
"{} {:<14} | {}{}{}",
|
|
format!("{:04x}", start_pos).dimmed(),
|
|
bytes_str.green(),
|
|
mnemonic.yellow().bold(),
|
|
sep,
|
|
args.cyan()
|
|
);
|
|
} else {
|
|
let op_str = if args.is_empty() {
|
|
mnemonic.to_string()
|
|
} else {
|
|
format!("{} {}", mnemonic, args)
|
|
};
|
|
let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str);
|
|
}
|
|
|
|
for chunk in chunks {
|
|
let bytes_str = {
|
|
let mut temp = String::from(" ");
|
|
for b in chunk {
|
|
let _ = write!(&mut temp, " {:02x}", b);
|
|
}
|
|
temp
|
|
};
|
|
|
|
let extra_width = if start_pos > 0 {
|
|
start_pos.ilog2() >> 4
|
|
} else {
|
|
0
|
|
};
|
|
|
|
if color {
|
|
let _ = write!(out, " ");
|
|
for _ in 0..extra_width {
|
|
let _ = write!(out, " ");
|
|
}
|
|
let _ = writeln!(out, " {:<14} |", bytes_str.green());
|
|
} else {
|
|
let _ = write!(out, " ");
|
|
for _ in 0..extra_width {
|
|
let _ = write!(out, " ");
|
|
}
|
|
let _ = writeln!(out, " {:<14} |", bytes_str);
|
|
}
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
|
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
|
|
|
|
match op {
|
|
Op::PushSmi => {
|
|
let val = self.read_i32();
|
|
("PushSmi", format!("{}", val))
|
|
}
|
|
Op::PushBigInt => {
|
|
let val = self.read_i64();
|
|
("PushBigInt", format!("{}", val))
|
|
}
|
|
Op::PushFloat => {
|
|
let val = self.read_f64();
|
|
("PushFloat", format!("{}", val))
|
|
}
|
|
Op::PushString => {
|
|
let idx = self.read_u32();
|
|
let s = self.ctx.resolve_string(idx);
|
|
let len = s.len();
|
|
let mut s_fmt = format!("{:?}", s);
|
|
if s_fmt.len() > 60 {
|
|
s_fmt.truncate(57);
|
|
#[allow(clippy::unwrap_used)]
|
|
write!(s_fmt, "...\" (total {len} bytes)").unwrap();
|
|
}
|
|
("PushString", format!("@{} {}", idx, s_fmt))
|
|
}
|
|
Op::PushNull => ("PushNull", String::new()),
|
|
Op::PushTrue => ("PushTrue", String::new()),
|
|
Op::PushFalse => ("PushFalse", String::new()),
|
|
|
|
Op::LoadLocal => {
|
|
let idx = self.read_u32();
|
|
("LoadLocal", format!("[{}]", idx))
|
|
}
|
|
Op::LoadOuter => {
|
|
let depth = self.read_u8();
|
|
let idx = self.read_u32();
|
|
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
|
}
|
|
Op::StoreLocal => {
|
|
let idx = self.read_u32();
|
|
("StoreLocal", format!("[{}]", idx))
|
|
}
|
|
Op::AllocLocals => {
|
|
let count = self.read_u32();
|
|
("AllocLocals", format!("count={}", count))
|
|
}
|
|
|
|
Op::MakeThunk => {
|
|
let offset = self.read_u32();
|
|
("MakeThunk", format!("-> {:04x}", offset))
|
|
}
|
|
Op::MakeClosure => {
|
|
let offset = self.read_u32();
|
|
let slots = self.read_u32();
|
|
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
|
|
}
|
|
Op::MakePatternClosure => {
|
|
let offset = self.read_u32();
|
|
let slots = self.read_u32();
|
|
let req_count = self.read_u16();
|
|
let opt_count = self.read_u16();
|
|
let ellipsis = self.read_u8() != 0;
|
|
|
|
let mut arg_str = format!(
|
|
"-> {:04x} slots={} req={} opt={} ...={})",
|
|
offset, slots, req_count, opt_count, ellipsis
|
|
);
|
|
|
|
arg_str.push_str(" Args=[");
|
|
for _ in 0..req_count {
|
|
let idx = self.read_u32();
|
|
arg_str.push_str(&format!("Req({}) ", self.ctx.resolve_string(idx)));
|
|
}
|
|
for _ in 0..opt_count {
|
|
let idx = self.read_u32();
|
|
arg_str.push_str(&format!("Opt({}) ", self.ctx.resolve_string(idx)));
|
|
}
|
|
|
|
let total_args = req_count + opt_count;
|
|
for _ in 0..total_args {
|
|
let _name_idx = self.read_u32();
|
|
let _span_id = self.read_u32();
|
|
}
|
|
arg_str.push(']');
|
|
|
|
("MakePatternClosure", arg_str)
|
|
}
|
|
|
|
Op::Call => {
|
|
self.read_operand_data();
|
|
("Call", "arg=?".into())
|
|
}
|
|
Op::DispatchPrimOp => {
|
|
let phase = PrimOpPhase::try_from(self.read_u8()).expect("invalid primop phase");
|
|
("DispatchPrimOp", format!("phase={phase:?}"))
|
|
}
|
|
|
|
Op::MakeAttrs => {
|
|
let static_count = self.read_u32();
|
|
let dynamic_count = self.read_u32();
|
|
let mut args = format!("static={} dynamic={}", static_count, dynamic_count);
|
|
|
|
for _ in 0..static_count {
|
|
let key_id = self.read_u32();
|
|
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
|
self.read_operand_data();
|
|
let _span_id = self.read_u32();
|
|
args.push(']');
|
|
}
|
|
|
|
for _ in 0..dynamic_count {
|
|
let _ = write!(args, " [dyn");
|
|
self.read_operand_data();
|
|
let _span_id = self.read_u32();
|
|
args.push(']');
|
|
}
|
|
|
|
("MakeAttrs", args)
|
|
}
|
|
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
|
|
|
Op::SelectStatic => {
|
|
let span_id = self.read_u32();
|
|
let key_id = self.read_u32();
|
|
(
|
|
"SelectStatic",
|
|
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
|
)
|
|
}
|
|
Op::SelectDynamic => {
|
|
let span_id = self.read_u32();
|
|
("SelectDynamic", format!("span={}", span_id))
|
|
}
|
|
Op::HasAttrPathStatic => {
|
|
let span_id = self.read_u32();
|
|
let key_id = self.read_u32();
|
|
(
|
|
"HasAttrPathStatic",
|
|
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
|
)
|
|
}
|
|
Op::HasAttrPathDynamic => {
|
|
let span_id = self.read_u32();
|
|
("HasAttrPathDynamic", format!("span={}", span_id))
|
|
}
|
|
Op::HasAttrStatic => {
|
|
let key_id = self.read_u32();
|
|
(
|
|
"HasAttrStatic",
|
|
format!("key={}", self.ctx.resolve_string(key_id)),
|
|
)
|
|
}
|
|
Op::HasAttrDynamic => ("HasAttrDynamic", String::new()),
|
|
Op::HasAttrResolve => ("HasAttrResolve", String::new()),
|
|
Op::JumpIfSelectSucceeded => {
|
|
let offset = self.read_i32();
|
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
(
|
|
"JumpIfSelectSucceeded",
|
|
format!("-> {:04x} offset={}", target, offset),
|
|
)
|
|
}
|
|
Op::JumpIfSelectFailed => {
|
|
let offset = self.read_i32();
|
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
(
|
|
"JumpIfSelectFailed",
|
|
format!("-> {:04x} offset={}", target, offset),
|
|
)
|
|
}
|
|
|
|
Op::MakeList => {
|
|
let count = self.read_u32();
|
|
for _ in 0..count {
|
|
self.read_operand_data();
|
|
}
|
|
("MakeList", format!("size={}", count))
|
|
}
|
|
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
|
|
|
|
Op::OpAdd => ("OpAdd", String::new()),
|
|
Op::OpSub => ("OpSub", String::new()),
|
|
Op::OpMul => ("OpMul", String::new()),
|
|
Op::OpDiv => ("OpDiv", String::new()),
|
|
Op::OpEq => ("OpEq", String::new()),
|
|
Op::OpNeq => ("OpNeq", String::new()),
|
|
Op::OpLt => ("OpLt", String::new()),
|
|
Op::OpGt => ("OpGt", String::new()),
|
|
Op::OpLeq => ("OpLeq", String::new()),
|
|
Op::OpGeq => ("OpGeq", String::new()),
|
|
Op::OpConcat => ("OpConcat", String::new()),
|
|
Op::OpUpdate => ("OpUpdate", String::new()),
|
|
Op::OpNeg => ("OpNeg", String::new()),
|
|
Op::OpNot => ("OpNot", String::new()),
|
|
|
|
Op::JumpIfFalse => {
|
|
let offset = self.read_i32();
|
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
(
|
|
"JumpIfFalse",
|
|
format!("-> {:04x} offset={}", target, offset),
|
|
)
|
|
}
|
|
Op::JumpIfTrue => {
|
|
let offset = self.read_i32();
|
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
|
|
}
|
|
Op::Jump => {
|
|
let offset = self.read_i32();
|
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
|
("Jump", format!("-> {:04x} offset={}", target, offset))
|
|
}
|
|
|
|
Op::ConcatStrings => {
|
|
let count = self.read_u16();
|
|
let force = self.read_u8();
|
|
("ConcatStrings", format!("count={} force={}", count, force))
|
|
}
|
|
Op::CoerceToString => ("CoerceToString", String::new()),
|
|
Op::ResolvePath => {
|
|
let dir_id = self.read_u32();
|
|
let dir = self.ctx.resolve_string(dir_id);
|
|
("ResolvePath", format!("dir={:?}", dir))
|
|
}
|
|
Op::Assert => {
|
|
let raw_idx = self.read_u32();
|
|
let span_id = self.read_u32();
|
|
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
|
}
|
|
Op::LookupWith => {
|
|
let idx = self.read_u32();
|
|
let name = self.ctx.resolve_string(idx);
|
|
let n = self.read_u8();
|
|
for _ in 0..n {
|
|
self.read_operand_data();
|
|
}
|
|
("LookupWith", format!("sym={:?} n={}", name, n))
|
|
}
|
|
|
|
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
|
Op::LoadBuiltin => {
|
|
let id = self.read_u8();
|
|
("LoadBuiltin", format!("id={}", id))
|
|
}
|
|
Op::LoadReplBinding => {
|
|
let idx = self.read_u32();
|
|
let name = self.ctx.resolve_string(idx);
|
|
("LoadReplBinding", format!("{:?}", name))
|
|
}
|
|
Op::LoadScopedBinding => {
|
|
let slot = self.read_u32();
|
|
let idx = self.read_u32();
|
|
let name = self.ctx.resolve_string(idx);
|
|
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
|
}
|
|
Op::Return => ("Return", String::new()),
|
|
Op::Illegal => ("Illegal", String::new()),
|
|
}
|
|
}
|
|
}
|