ladybird/Libraries/LibJS/Rust/src/bytecode/operand.rs
Andreas Kling 7a6af95db3 LibJS: Move bytecode instruction dumping to Rust
Generate Rust bytecode dump helpers from Bytecode.def and route
Executable::dump() through them for instruction stream formatting.

Add a small Rust runtime::value helper for decoding encoded LibJS
Values so immediate Value operands are formatted on the Rust side. C++
callbacks remain only for local names and GC-backed Value payloads that
still need LibJS object access.

Remove the generated C++ to_byte_string_impl() methods and the old
Instruction::to_byte_string() dispatch. The bytecode dump tests cover
output compatibility.
2026-06-15 02:41:57 +02:00

188 lines
4.8 KiB
Rust

/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
/// A bytecode register index.
///
/// Reserved registers:
/// - 0: accumulator
/// - 1: exception
/// - 2: this_value
/// - 3: return_value
/// - 4: saved_lexical_environment
/// - 5+: user registers
#[derive(Debug, Clone, Copy)]
pub struct Register(pub u32);
impl Register {
pub const ACCUMULATOR: Register = Register(0);
pub const EXCEPTION: Register = Register(1);
pub const THIS_VALUE: Register = Register(2);
pub const RETURN_VALUE: Register = Register(3);
pub const SAVED_LEXICAL_ENVIRONMENT: Register = Register(4);
pub const RESERVED_COUNT: u32 = 5;
}
/// A bytecode operand.
///
/// Encoded as a single `u32` with a 3-bit type tag in the top 3 bits
/// and a 29-bit index in the lower 29 bits:
///
/// `raw = (type << 29) | index`
///
/// This encoding is ABI-compatible with the VM's operand format.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Operand(u32);
impl Operand {
const TYPE_SHIFT: u32 = 29;
const INDEX_MASK: u32 = 0x1FFF_FFFF;
pub const INVALID: u32 = 0xFFFF_FFFF;
pub fn register(reg: Register) -> Self {
Self((0 << Self::TYPE_SHIFT) | reg.0)
}
pub fn local(index: u32) -> Self {
Self((1 << Self::TYPE_SHIFT) | index)
}
pub fn constant(index: u32) -> Self {
Self((2 << Self::TYPE_SHIFT) | index)
}
pub fn argument(index: u32) -> Self {
Self((3 << Self::TYPE_SHIFT) | index)
}
pub fn invalid() -> Self {
Self(Self::INVALID)
}
pub fn is_invalid(self) -> bool {
self.0 == Self::INVALID
}
pub fn is_register(self) -> bool {
!self.is_invalid() && self.operand_type() == OperandType::Register
}
pub fn is_local(self) -> bool {
!self.is_invalid() && self.operand_type() == OperandType::Local
}
pub fn is_constant(self) -> bool {
!self.is_invalid() && self.operand_type() == OperandType::Constant
}
pub fn operand_type(self) -> OperandType {
assert!(!self.is_invalid(), "operand_type() called on INVALID operand");
match (self.0 >> Self::TYPE_SHIFT) & 0x7 {
0 => OperandType::Register,
1 => OperandType::Local,
2 => OperandType::Constant,
3 => OperandType::Argument,
_ => unreachable!("operand type bits can only be 0-3"),
}
}
pub fn index(self) -> u32 {
self.0 & Self::INDEX_MASK
}
pub fn raw(self) -> u32 {
self.0
}
pub fn from_raw(raw: u32) -> Self {
Self(raw)
}
pub fn optional_from_raw(raw: u32) -> Option<Self> {
if raw == Self::INVALID { None } else { Some(Self(raw)) }
}
/// Offset the index by the given amount, stripping the type tag and
/// leaving a flat index into the combined
/// [registers | locals | constants | arguments] array.
/// Used during operand rewriting in the assembler.
pub fn offset_index_by(&mut self, offset: u32) {
self.0 &= Self::INDEX_MASK;
self.0 = self.0.checked_add(offset).expect("operand index overflow");
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperandType {
Register,
Local,
Constant,
Argument,
}
/// A bytecode label.
///
/// During compilation, holds a basic block index. After linking,
/// holds the final byte offset in the flat bytecode stream.
/// Stored as a single `u32`.
#[derive(Debug, Clone, Copy)]
pub struct Label(pub u32);
impl Label {
pub fn basic_block_index(self) -> usize {
self.0 as usize
}
}
/// Index into the string table.
#[derive(Debug, Clone, Copy)]
pub struct StringTableIndex(pub u32);
impl StringTableIndex {
pub const INVALID: u32 = 0xFFFF_FFFF;
pub fn optional_from_raw(raw: u32) -> Option<Self> {
if raw == Self::INVALID { None } else { Some(Self(raw)) }
}
}
/// Index into the identifier table.
#[derive(Debug, Clone, Copy)]
pub struct IdentifierTableIndex(pub u32);
impl IdentifierTableIndex {
pub const INVALID: u32 = 0xFFFF_FFFF;
pub fn optional_from_raw(raw: u32) -> Option<Self> {
if raw == Self::INVALID { None } else { Some(Self(raw)) }
}
}
/// Index into the property key table.
#[derive(Debug, Clone, Copy)]
pub struct PropertyKeyTableIndex(pub u32);
/// Index into the regex table.
#[derive(Debug, Clone, Copy)]
pub struct RegexTableIndex(pub u32);
/// Environment coordinate used by environment lookup instructions.
/// Layout: two `u32` fields (hops + index).
#[derive(Debug, Clone, Copy)]
pub struct EnvironmentCoordinate {
pub hops: u32,
pub index: u32,
}
impl EnvironmentCoordinate {
pub const INVALID: u32 = 0xFFFF_FFFE;
pub fn empty() -> Self {
Self {
hops: Self::INVALID,
index: Self::INVALID,
}
}
}