use luau_common::{BString, ByteSlice};
use std::borrow::Borrow;
use crate::opcodes::{InvalidOpcode, Opcode};
pub type Register = u8;
pub type ConstantIndex = i32;
pub type PackedTableValue = i32;
pub type InstructionWord = u32;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BytecodeClass {
pub class_name: ConstantIndex,
pub property_names: Vec<ConstantIndex>,
pub method_names: Vec<ConstantIndex>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BytecodeImportId(u32);
impl BytecodeImportId {
pub fn new(id0: ConstantIndex) -> Self {
debug_assert!((0..1024).contains(&id0));
Self((1 << 30) | ((id0 as u32) << 20))
}
pub fn from_two(id0: ConstantIndex, id1: ConstantIndex) -> Self {
debug_assert!((0..1024).contains(&id0));
debug_assert!((0..1024).contains(&id1));
Self((2 << 30) | ((id0 as u32) << 20) | ((id1 as u32) << 10))
}
pub fn from_three(id0: ConstantIndex, id1: ConstantIndex, id2: ConstantIndex) -> Self {
debug_assert!((0..1024).contains(&id0));
debug_assert!((0..1024).contains(&id1));
debug_assert!((0..1024).contains(&id2));
Self((3 << 30) | ((id0 as u32) << 20) | ((id1 as u32) << 10) | id2 as u32)
}
pub fn from_raw(value: u32) -> Self {
Self(value)
}
pub fn raw(self) -> u32 {
self.0
}
pub fn components(self) -> Vec<ConstantIndex> {
let count = self.0 >> 30;
let mut result = Vec::with_capacity(count as usize);
if count > 0 {
result.push(((self.0 >> 20) & 1023) as ConstantIndex);
}
if count > 1 {
result.push(((self.0 >> 10) & 1023) as ConstantIndex);
}
if count > 2 {
result.push((self.0 & 1023) as ConstantIndex);
}
result
}
}
#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BytecodeString(BString);
impl BytecodeString {
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Borrow<[u8]> for BytecodeString {
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
impl From<&[u8]> for BytecodeString {
fn from(value: &[u8]) -> Self {
Self(BString::from(value))
}
}
impl<const N: usize> From<&[u8; N]> for BytecodeString {
fn from(value: &[u8; N]) -> Self {
Self::from(value.as_slice())
}
}
impl From<Vec<u8>> for BytecodeString {
fn from(value: Vec<u8>) -> Self {
Self(BString::new(value))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClosureIndex(u32);
impl ClosureIndex {
pub fn new(index: u32) -> Self {
Self(index)
}
pub fn get(self) -> u32 {
self.0
}
pub fn as_usize(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableShape {
entries: [TableShapeEntry; Self::MAX_LENGTH],
length: u8,
has_constants: bool,
}
impl TableShape {
pub const MAX_LENGTH: usize = 32;
pub fn new(entries: Vec<TableShapeEntry>) -> Self {
debug_assert!(entries.len() <= Self::MAX_LENGTH);
let has_constants = entries.iter().any(|entry| entry.value.is_some());
let mut storage = [TableShapeEntry::default(); Self::MAX_LENGTH];
let length = entries.len();
storage[..length].copy_from_slice(&entries);
Self {
entries: storage,
length: length as u8,
has_constants,
}
}
pub fn entries(&self) -> &[TableShapeEntry] {
&self.entries[..usize::from(self.length)]
}
pub fn len(&self) -> usize {
usize::from(self.length)
}
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn has_constants(&self) -> bool {
self.has_constants
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct TableShapeEntry {
pub key: ConstantIndex,
pub value: Option<PackedTableValue>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BytecodeVector {
values: [f32; 4],
}
impl BytecodeVector {
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self {
values: [x, y, z, w],
}
}
pub fn x(self) -> f32 {
self.values[0]
}
pub fn y(self) -> f32 {
self.values[1]
}
pub fn z(self) -> f32 {
self.values[2]
}
pub fn w(self) -> f32 {
self.values[3]
}
pub fn to_bits(self) -> [u32; 4] {
self.values.map(f32::to_bits)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BytecodeVectorDouble {
values: [f64; 4],
}
impl BytecodeVectorDouble {
pub fn new(x: f64, y: f64, z: f64, w: f64) -> Self {
Self {
values: [x, y, z, w],
}
}
pub fn x(self) -> f64 {
self.values[0]
}
pub fn y(self) -> f64 {
self.values[1]
}
pub fn z(self) -> f64 {
self.values[2]
}
pub fn w(self) -> f64 {
self.values[3]
}
pub fn to_bits(self) -> [u64; 4] {
self.values.map(f64::to_bits)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BytecodeTypedLocal {
pub ty: u8,
pub register: Register,
pub start_pc: u32,
pub end_pc: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BytecodeFeedbackSlot {
pub pc: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BytecodeFeedbackType {
CallTarget,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BytecodeUserdataType {
pub name: BytecodeString,
pub name_ref: u32,
pub used: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Instruction(InstructionWord);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct InstructionAux(InstructionWord);
impl Instruction {
pub fn new(word: InstructionWord) -> Self {
Self(word)
}
pub fn abc(opcode: Opcode, a: u8, b: u8, c: u8) -> Self {
Self(opcode.word() | ((a as u32) << 8) | ((b as u32) << 16) | ((c as u32) << 24))
}
pub fn ad(opcode: Opcode, a: u8, d: i16) -> Self {
Self(opcode.word() | ((a as u32) << 8) | (((d as u16) as u32) << 16))
}
pub fn ae(opcode: Opcode, e: i32) -> Self {
Self(opcode.word() | (((e as u32) & 0x00ff_ffff) << 8))
}
pub fn word(self) -> InstructionWord {
self.0
}
pub fn with_d(self, d: i16) -> Self {
Self((self.0 & 0x0000_ffff) | (((d as u16) as u32) << 16))
}
pub fn with_c(self, c: u8) -> Self {
Self((self.0 & 0x00ff_ffff) | ((c as u32) << 24))
}
pub fn try_opcode(self) -> Result<Opcode, InvalidOpcode> {
let opcode = (self.0 & 0xff) as u8;
Opcode::from_byte(opcode).ok_or(InvalidOpcode::new(opcode))
}
pub unsafe fn opcode_unchecked(self) -> Opcode {
unsafe { self.try_opcode().unwrap_unchecked() }
}
pub fn a(self) -> u8 {
((self.0 >> 8) & 0xff) as u8
}
pub fn b(self) -> u8 {
((self.0 >> 16) & 0xff) as u8
}
pub fn c(self) -> u8 {
((self.0 >> 24) & 0xff) as u8
}
pub fn d(self) -> i16 {
(self.0 >> 16) as u16 as i16
}
pub fn e(self) -> i32 {
(self.0 as i32) >> 8
}
pub fn try_jump_target(self, pc: u32) -> Result<Option<i32>, InvalidOpcode> {
Ok(self.try_opcode()?.jump_target(self, pc))
}
pub unsafe fn jump_target_unchecked(self, pc: u32) -> Option<i32> {
unsafe { self.opcode_unchecked().jump_target(self, pc) }
}
}
impl InstructionAux {
pub fn new(word: InstructionWord) -> Self {
Self(word)
}
pub fn word(self) -> InstructionWord {
self.0
}
pub fn a(self) -> u8 {
(self.0 & 0xff) as u8
}
pub fn b(self) -> u8 {
((self.0 >> 8) & 0xff) as u8
}
pub fn kv(self) -> u32 {
self.0 & 0x00ff_ffff
}
pub fn kb(self) -> bool {
(self.0 & 1) != 0
}
pub fn is_negated(self) -> bool {
(self.0 >> 31) != 0
}
pub fn kv16(self) -> u16 {
(self.0 & 0xffff) as u16
}
pub fn slot(self) -> u16 {
(self.0 >> 16) as u16
}
}