treewide: enforce strict clippy check
This commit is contained in:
@@ -2,7 +2,7 @@ use std::fmt::Write;
|
||||
|
||||
use colored::Colorize as _;
|
||||
|
||||
use crate::{Continuation, InstructionPtr, Op, OperandType};
|
||||
use crate::{BytecodeReader, Continuation, InstructionPtr, Op};
|
||||
|
||||
pub trait DisassemblerContext {
|
||||
fn resolve_string(&self, id: u32) -> &str;
|
||||
@@ -10,99 +10,15 @@ pub trait DisassemblerContext {
|
||||
}
|
||||
|
||||
pub struct Disassembler<'a, Ctx> {
|
||||
code: &'a [u8],
|
||||
reader: BytecodeReader<'a>,
|
||||
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(),
|
||||
reader: BytecodeReader::new(ctx.get_code(), ip.0),
|
||||
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(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +30,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
self.disassemble_impl(true)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::let_underscore_must_use,
|
||||
clippy::indexing_slicing,
|
||||
reason = "disassembler operates on well-formed bytecode; writes target an in-memory String via fmt::Write, which is infallible"
|
||||
)]
|
||||
fn disassemble_impl(&mut self, color: bool) -> String {
|
||||
let mut out = String::new();
|
||||
if color {
|
||||
@@ -122,24 +43,24 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
out,
|
||||
"{} {}",
|
||||
"Length:".white(),
|
||||
format!("{} bytes", self.code.len()).cyan()
|
||||
format!("{} bytes", self.reader.len()).cyan()
|
||||
);
|
||||
} else {
|
||||
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
||||
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
||||
let _ = writeln!(out, "Length: {} bytes", self.reader.len());
|
||||
}
|
||||
|
||||
while self.pc < self.code.len() {
|
||||
let start_pos = self.pc;
|
||||
let op_byte = self.read_u8();
|
||||
while self.reader.pc() < self.reader.len() {
|
||||
let start_pos = self.reader.pc();
|
||||
let op_byte = self.reader.read_u8();
|
||||
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
|
||||
|
||||
let bytes_slice = &self.code[start_pos + 1..self.pc];
|
||||
let bytes_slice = &self.reader[start_pos + 1..self.reader.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]);
|
||||
let mut temp = format!("{:02x}", self.reader[start_pos]);
|
||||
for b in first_chunk {
|
||||
let _ = write!(&mut temp, " {:02x}", b);
|
||||
}
|
||||
@@ -199,31 +120,36 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
out
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::let_underscore_must_use,
|
||||
clippy::cast_sign_loss,
|
||||
reason = "disassembler operates on well-formed bytecode; jump targets are valid non-negative PCs and writes to String are infallible"
|
||||
)]
|
||||
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
||||
let op = Op::try_from(op_byte).expect("invalid op code");
|
||||
|
||||
match op {
|
||||
Op::PushSmi => {
|
||||
let val = self.read_i32();
|
||||
let val = self.reader.read_i32();
|
||||
("PushSmi", format!("{}", val))
|
||||
}
|
||||
Op::PushBigInt => {
|
||||
let val = self.read_i64();
|
||||
let val = self.reader.read_i64();
|
||||
("PushBigInt", format!("{}", val))
|
||||
}
|
||||
Op::PushFloat => {
|
||||
let val = self.read_f64();
|
||||
let val = self.reader.read_f64();
|
||||
("PushFloat", format!("{}", val))
|
||||
}
|
||||
Op::PushString => {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.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();
|
||||
write!(s_fmt, "...\" (total {len} bytes)")
|
||||
.expect("writing to String is infallible");
|
||||
}
|
||||
("PushString", format!("@{} {}", idx, s_fmt))
|
||||
}
|
||||
@@ -232,38 +158,38 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
Op::PushFalse => ("PushFalse", String::new()),
|
||||
|
||||
Op::LoadLocal => {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.read_u32();
|
||||
("LoadLocal", format!("[{}]", idx))
|
||||
}
|
||||
Op::LoadOuter => {
|
||||
let depth = self.read_u8();
|
||||
let idx = self.read_u32();
|
||||
let depth = self.reader.read_u8();
|
||||
let idx = self.reader.read_u32();
|
||||
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
||||
}
|
||||
Op::StoreLocal => {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.read_u32();
|
||||
("StoreLocal", format!("[{}]", idx))
|
||||
}
|
||||
Op::AllocLocals => {
|
||||
let count = self.read_u32();
|
||||
let count = self.reader.read_u32();
|
||||
("AllocLocals", format!("count={}", count))
|
||||
}
|
||||
|
||||
Op::MakeThunk => {
|
||||
let offset = self.read_u32();
|
||||
let offset = self.reader.read_u32();
|
||||
("MakeThunk", format!("-> {:04x}", offset))
|
||||
}
|
||||
Op::MakeClosure => {
|
||||
let offset = self.read_u32();
|
||||
let slots = self.read_u32();
|
||||
let offset = self.reader.read_u32();
|
||||
let slots = self.reader.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 offset = self.reader.read_u32();
|
||||
let slots = self.reader.read_u32();
|
||||
let req_count = self.reader.read_u16();
|
||||
let opt_count = self.reader.read_u16();
|
||||
let ellipsis = self.reader.read_u8() != 0;
|
||||
|
||||
let mut arg_str = format!(
|
||||
"-> {:04x} slots={} req={} opt={} ...={})",
|
||||
@@ -272,18 +198,18 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
|
||||
arg_str.push_str(" Args=[");
|
||||
for _ in 0..req_count {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.read_u32();
|
||||
arg_str.push_str(&format!("Req({}) ", self.ctx.resolve_string(idx)));
|
||||
}
|
||||
for _ in 0..opt_count {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.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();
|
||||
let _name_idx = self.reader.read_u32();
|
||||
let _span_id = self.reader.read_u32();
|
||||
}
|
||||
arg_str.push(']');
|
||||
|
||||
@@ -291,31 +217,32 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
}
|
||||
|
||||
Op::Call => {
|
||||
self.read_operand_data();
|
||||
let _ = self.reader.read_operand_data();
|
||||
("Call", "arg=?".into())
|
||||
}
|
||||
Op::DispatchCont => {
|
||||
let phase = Continuation::try_from(self.read_u8()).expect("invalid primop phase");
|
||||
let phase =
|
||||
Continuation::try_from(self.reader.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 static_count = self.reader.read_u32();
|
||||
let dynamic_count = self.reader.read_u32();
|
||||
let mut args = format!("static={} dynamic={}", static_count, dynamic_count);
|
||||
|
||||
for _ in 0..static_count {
|
||||
let key_id = self.read_u32();
|
||||
let key_id = self.reader.read_u32();
|
||||
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||
self.read_operand_data();
|
||||
let _span_id = self.read_u32();
|
||||
let _ = self.reader.read_operand_data();
|
||||
let _span_id = self.reader.read_u32();
|
||||
args.push(']');
|
||||
}
|
||||
|
||||
for _ in 0..dynamic_count {
|
||||
let _ = write!(args, " [dyn");
|
||||
self.read_operand_data();
|
||||
let _span_id = self.read_u32();
|
||||
let _ = self.reader.read_operand_data();
|
||||
let _span_id = self.reader.read_u32();
|
||||
args.push(']');
|
||||
}
|
||||
|
||||
@@ -324,31 +251,31 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||
|
||||
Op::SelectStatic => {
|
||||
let span_id = self.read_u32();
|
||||
let key_id = self.read_u32();
|
||||
let span_id = self.reader.read_u32();
|
||||
let key_id = self.reader.read_u32();
|
||||
(
|
||||
"SelectStatic",
|
||||
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
||||
)
|
||||
}
|
||||
Op::SelectDynamic => {
|
||||
let span_id = self.read_u32();
|
||||
let span_id = self.reader.read_u32();
|
||||
("SelectDynamic", format!("span={}", span_id))
|
||||
}
|
||||
Op::HasAttrPathStatic => {
|
||||
let span_id = self.read_u32();
|
||||
let key_id = self.read_u32();
|
||||
let span_id = self.reader.read_u32();
|
||||
let key_id = self.reader.read_u32();
|
||||
(
|
||||
"HasAttrPathStatic",
|
||||
format!("key={} span={}", self.ctx.resolve_string(key_id), span_id),
|
||||
)
|
||||
}
|
||||
Op::HasAttrPathDynamic => {
|
||||
let span_id = self.read_u32();
|
||||
let span_id = self.reader.read_u32();
|
||||
("HasAttrPathDynamic", format!("span={}", span_id))
|
||||
}
|
||||
Op::HasAttrStatic => {
|
||||
let key_id = self.read_u32();
|
||||
let key_id = self.reader.read_u32();
|
||||
(
|
||||
"HasAttrStatic",
|
||||
format!("key={}", self.ctx.resolve_string(key_id)),
|
||||
@@ -357,7 +284,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
Op::HasAttrDynamic => ("HasAttrDynamic", String::new()),
|
||||
Op::HasAttrResolve => ("HasAttrResolve", String::new()),
|
||||
Op::JumpIfSelectSucceeded => {
|
||||
let offset = self.read_i32();
|
||||
let offset = self.reader.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
(
|
||||
"JumpIfSelectSucceeded",
|
||||
@@ -365,7 +292,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
)
|
||||
}
|
||||
Op::JumpIfSelectFailed => {
|
||||
let offset = self.read_i32();
|
||||
let offset = self.reader.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
(
|
||||
"JumpIfSelectFailed",
|
||||
@@ -374,9 +301,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
}
|
||||
|
||||
Op::MakeList => {
|
||||
let count = self.read_u32();
|
||||
let count = self.reader.read_u32();
|
||||
for _ in 0..count {
|
||||
self.read_operand_data();
|
||||
let _ = self.reader.read_operand_data();
|
||||
}
|
||||
("MakeList", format!("size={}", count))
|
||||
}
|
||||
@@ -398,7 +325,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
Op::OpNot => ("OpNot", String::new()),
|
||||
|
||||
Op::JumpIfFalse => {
|
||||
let offset = self.read_i32();
|
||||
let offset = self.reader.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
(
|
||||
"JumpIfFalse",
|
||||
@@ -406,55 +333,55 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
)
|
||||
}
|
||||
Op::JumpIfTrue => {
|
||||
let offset = self.read_i32();
|
||||
let offset = self.reader.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 offset = self.reader.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();
|
||||
let count = self.reader.read_u16();
|
||||
let force = self.reader.read_u8();
|
||||
("ConcatStrings", format!("count={} force={}", count, force))
|
||||
}
|
||||
Op::CoerceToString => ("CoerceToString", String::new()),
|
||||
Op::ResolvePath => {
|
||||
let dir_id = self.read_u32();
|
||||
let dir_id = self.reader.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();
|
||||
let raw_idx = self.reader.read_u32();
|
||||
let span_id = self.reader.read_u32();
|
||||
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
||||
}
|
||||
Op::LookupWith => {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.read_u32();
|
||||
let name = self.ctx.resolve_string(idx);
|
||||
let n = self.read_u8();
|
||||
let n = self.reader.read_u8();
|
||||
for _ in 0..n {
|
||||
self.read_operand_data();
|
||||
let _ = self.reader.read_operand_data();
|
||||
}
|
||||
("LookupWith", format!("sym={:?} n={}", name, n))
|
||||
}
|
||||
|
||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||
Op::LoadBuiltin => {
|
||||
let id = self.read_u8();
|
||||
let id = self.reader.read_u8();
|
||||
("LoadBuiltin", format!("id={}", id))
|
||||
}
|
||||
Op::LoadReplBinding => {
|
||||
let idx = self.read_u32();
|
||||
let idx = self.reader.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 slot = self.reader.read_u32();
|
||||
let idx = self.reader.read_u32();
|
||||
let name = self.ctx.resolve_string(idx);
|
||||
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
||||
}
|
||||
|
||||
+162
-90
@@ -1,4 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(
|
||||
dead_code,
|
||||
reason = "crate is under active development; some opcodes and helpers are not yet wired up"
|
||||
)]
|
||||
|
||||
use fix_lang::{BuiltinId, StringId};
|
||||
use num_enum::TryFromPrimitive;
|
||||
@@ -10,8 +13,7 @@ pub mod disassembler;
|
||||
pub struct InstructionPtr(pub usize);
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Op {
|
||||
PushSmi,
|
||||
PushBigInt,
|
||||
@@ -88,6 +90,21 @@ pub enum Op {
|
||||
Illegal,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Op {
|
||||
type Error = u8;
|
||||
|
||||
#[inline(always)]
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if (0..Self::Illegal as u8).contains(&value) {
|
||||
// SAFETY: `value` is in `0..Illegal`, which are exactly the valid
|
||||
// discriminants of this `#[repr(u8)]` enum, so the transmute is sound.
|
||||
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
|
||||
} else {
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
pub enum OperandType {
|
||||
@@ -106,11 +123,7 @@ pub enum Const {
|
||||
Bool(bool),
|
||||
String(StringId),
|
||||
Path(StringId),
|
||||
PrimOp {
|
||||
id: BuiltinId,
|
||||
arity: u8,
|
||||
dispatch_ip: u32,
|
||||
},
|
||||
PrimOp(BuiltinId),
|
||||
Null,
|
||||
}
|
||||
|
||||
@@ -286,8 +299,11 @@ pub enum Continuation {
|
||||
impl TryFrom<u8> for Continuation {
|
||||
type Error = u8;
|
||||
|
||||
#[inline(always)]
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
if (0..Self::Illegal as u8).contains(&value) {
|
||||
// SAFETY: `value` is in `0..Illegal`, which are exactly the valid
|
||||
// discriminants of this `#[repr(u8)]` enum, so the transmute is sound.
|
||||
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
|
||||
} else {
|
||||
Err(value)
|
||||
@@ -296,7 +312,7 @@ impl TryFrom<u8> for Continuation {
|
||||
}
|
||||
|
||||
impl Continuation {
|
||||
pub fn entry_for_builtin(id: BuiltinId) -> Self {
|
||||
pub const fn entry_for_builtin(id: BuiltinId) -> Self {
|
||||
use BuiltinId::*;
|
||||
match id {
|
||||
Abort => Self::PAbort,
|
||||
@@ -405,7 +421,7 @@ impl Continuation {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ip(self) -> u32 {
|
||||
pub const fn ip(self) -> u32 {
|
||||
self as u32 * 2
|
||||
}
|
||||
}
|
||||
@@ -416,7 +432,129 @@ pub struct BytecodeReader<'a> {
|
||||
inst_start_pc: usize,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for BytecodeReader<'_> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.bytecode
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromBytecode {
|
||||
#[must_use]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self;
|
||||
}
|
||||
|
||||
impl<const N: usize> FromBytecode for [u8; N] {
|
||||
#[inline(always)]
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "reads well-formed bytecode; an out-of-range PC indicates a codegen bug and should panic loudly"
|
||||
)]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||
let ret = reader.bytecode[reader.pc..reader.pc + N]
|
||||
.try_into()
|
||||
.expect("read_array failed");
|
||||
reader.pc += N;
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl FromBytecode for u8 {
|
||||
#[inline(always)]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||
let [byte] = reader.read();
|
||||
byte
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! numbers_impl {
|
||||
( $($ty:ty),* ) => {
|
||||
$(
|
||||
impl FromBytecode for $ty {
|
||||
#[inline(always)]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||
<$ty>::from_le_bytes(FromBytecode::read(reader))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
numbers_impl!(u16, u32, i32, i64, f64);
|
||||
|
||||
impl FromBytecode for Op {
|
||||
#[inline(always)]
|
||||
#[expect(
|
||||
clippy::panic,
|
||||
reason = "reads well-formed bytecode; an unknown opcode indicates a codegen bug and should panic loudly"
|
||||
)]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||
reader.inst_start_pc = reader.pc;
|
||||
let byte: u8 = reader.read();
|
||||
byte.try_into().unwrap_or_else(|byte| {
|
||||
std::hint::cold_path();
|
||||
panic!("unknown opcode: {byte:#04x}")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromBytecode for StringId {
|
||||
#[inline(always)]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||
let raw: u32 = reader.read();
|
||||
StringId(
|
||||
string_interner::symbol::SymbolU32::try_from_usize(raw as usize)
|
||||
.expect("raw is a valid interner symbol index"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromBytecode for OperandData {
|
||||
#[inline(always)]
|
||||
#[expect(
|
||||
clippy::panic,
|
||||
reason = "an unknown operand tag in well-formed bytecode indicates a codegen bug and should panic loudly"
|
||||
)]
|
||||
fn read(reader: &mut BytecodeReader<'_>) -> Self {
|
||||
let tag = reader.read();
|
||||
let ty = OperandType::try_from_primitive(tag).unwrap_or_else(|err| {
|
||||
std::hint::cold_path();
|
||||
panic!("unknown operand tag: {:#04x}", err.number)
|
||||
});
|
||||
match ty {
|
||||
OperandType::Const => OperandData::Const(reader.read()),
|
||||
OperandType::BigInt => OperandData::BigInt(reader.read()),
|
||||
OperandType::Local => {
|
||||
let layer = reader.read();
|
||||
let idx = reader.read();
|
||||
OperandData::Local { layer, idx }
|
||||
}
|
||||
OperandType::BuiltinConst => OperandData::BuiltinConst(reader.read()),
|
||||
OperandType::Builtins => OperandData::Builtins,
|
||||
OperandType::ReplBinding => OperandData::ReplBinding(reader.read()),
|
||||
OperandType::ScopedImportBinding => {
|
||||
let slot_id = reader.read();
|
||||
let name = reader.read();
|
||||
OperandData::ScopedImportBinding { slot_id, name }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! read_aliases {
|
||||
{ $($alias:ident -> $ty:ty),*$(,)? } => {
|
||||
$(
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn $alias(&mut self) -> $ty {
|
||||
<$ty>::read(self)
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a> BytecodeReader<'a> {
|
||||
#[must_use]
|
||||
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
||||
Self {
|
||||
bytecode,
|
||||
@@ -426,6 +564,7 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
||||
Self {
|
||||
bytecode,
|
||||
@@ -435,88 +574,21 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(debug_assertions, track_caller)]
|
||||
fn read_array<const N: usize>(&mut self) -> [u8; N] {
|
||||
let ret = self.bytecode[self.pc..self.pc + N]
|
||||
.try_into()
|
||||
.expect("read_array failed");
|
||||
self.pc += N;
|
||||
ret
|
||||
#[must_use]
|
||||
pub fn read<T: FromBytecode>(&mut self) -> T {
|
||||
T::read(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_op(&mut self) -> Op {
|
||||
self.inst_start_pc = self.pc;
|
||||
let byte = self.bytecode[self.pc];
|
||||
if !(0..Op::Illegal as u8).contains(&byte) {
|
||||
std::hint::cold_path();
|
||||
panic!("unknown opcode: {byte:#04x}")
|
||||
}
|
||||
self.pc += 1;
|
||||
unsafe { std::mem::transmute::<u8, Op>(byte) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_u8(&mut self) -> u8 {
|
||||
let val = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
val
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_u16(&mut self) -> u16 {
|
||||
u16::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_u32(&mut self) -> u32 {
|
||||
u32::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_i32(&mut self) -> i32 {
|
||||
i32::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_i64(&mut self) -> i64 {
|
||||
i64::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_f64(&mut self) -> f64 {
|
||||
f64::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_string_id(&mut self) -> StringId {
|
||||
let raw = self.read_u32();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_operand_data(&mut self) -> OperandData {
|
||||
let tag = self.read_u8();
|
||||
let Ok(ty) = OperandType::try_from_primitive(tag)
|
||||
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
||||
match ty {
|
||||
OperandType::Const => OperandData::Const(self.read_u32()),
|
||||
OperandType::BigInt => OperandData::BigInt(self.read_i64()),
|
||||
OperandType::Local => {
|
||||
let layer = self.read_u8();
|
||||
let idx = self.read_u32();
|
||||
OperandData::Local { layer, idx }
|
||||
}
|
||||
OperandType::BuiltinConst => OperandData::BuiltinConst(self.read_string_id()),
|
||||
OperandType::Builtins => OperandData::Builtins,
|
||||
OperandType::ReplBinding => OperandData::ReplBinding(self.read_string_id()),
|
||||
OperandType::ScopedImportBinding => {
|
||||
let slot_id = self.read_u32();
|
||||
let name = self.read_string_id();
|
||||
OperandData::ScopedImportBinding { slot_id, name }
|
||||
}
|
||||
}
|
||||
read_aliases! {
|
||||
read_op -> Op,
|
||||
read_u8 -> u8,
|
||||
read_u16 -> u16,
|
||||
read_u32 -> u32,
|
||||
read_i32 -> i32,
|
||||
read_i64 -> i64,
|
||||
read_f64 -> f64,
|
||||
read_string_id -> StringId,
|
||||
read_operand_data -> OperandData,
|
||||
}
|
||||
|
||||
pub fn pc(&self) -> usize {
|
||||
|
||||
Reference in New Issue
Block a user