use super::BytecodeBuilder;
use super::BytecodeStringRef;
use crate::dump::{append_closure_name, append_string_constant};
use crate::model::{
BytecodeClass, BytecodeFeedbackSlot, BytecodeImportId, BytecodeTypedLocal, BytecodeVector,
BytecodeVectorDouble, ClosureIndex, ConstantIndex, Instruction, InstructionWord, Register,
TableShape,
};
use crate::wire::{BytecodeFunctionWire, ClosureNameLookup};
use bitfields::bitfield;
use luau_common::{BStr, BString, ByteSlice, DenseHashHasher, DenseHashMap, flags};
use std::hash::{Hash, Hasher};
use std::io::Write;
#[cfg(debug_assertions)]
use crate::model::InstructionAux;
#[cfg(debug_assertions)]
use crate::opcodes::{CaptureType, Opcode};
pub trait BytecodeEncoder: std::fmt::Debug {
fn encode(&self, data: &mut [InstructionWord]);
}
#[derive(Debug, Clone, Copy)]
pub struct BytecodeStringHasher;
pub(super) fn builder_string_hash(bytes: &[u8]) -> u64 {
let mut hash = 2166136261u32;
for byte in bytes {
hash ^= u32::from(*byte);
hash = hash.wrapping_mul(16777619);
}
u64::from(hash)
}
impl DenseHashHasher<BytecodeStringRef<'_>> for BytecodeStringHasher {
fn hash(key: &BytecodeStringRef<'_>) -> u64 {
builder_string_hash(key.as_bytes())
}
}
pub(super) fn constant_cache_hash(key: &ConstantCacheKey) -> u64 {
match key.kind {
ConstantCacheKeyKind::Vector => {
let mut values = [
key.value as u32,
(key.value >> 32) as u32,
key.extra as u32,
(key.extra >> 32) as u32,
];
for value in &mut values {
*value ^= *value >> 17;
}
let hash = values[0].wrapping_mul(73856093)
^ values[1].wrapping_mul(19349663)
^ values[2].wrapping_mul(83492791)
^ values[3].wrapping_mul(39916801);
u64::from(hash)
}
ConstantCacheKeyKind::VectorDouble => {
let mut values = [key.value, key.extra, key.extra2, key.extra3];
for value in &mut values {
*value ^= *value >> 32;
}
let hash = (values[0] as u32).wrapping_mul(73856093)
^ (values[1] as u32).wrapping_mul(19349663)
^ (values[2] as u32).wrapping_mul(83492791)
^ (values[3] as u32).wrapping_mul(39916801);
u64::from(hash)
}
_ => {
let ty = match key.kind {
ConstantCacheKeyKind::Nil => 0u32,
ConstantCacheKeyKind::Boolean => 1u32,
ConstantCacheKeyKind::Number => 2u32,
ConstantCacheKeyKind::Integer64 => 3u32,
ConstantCacheKeyKind::String => 5u32,
ConstantCacheKeyKind::Import => 6u32,
ConstantCacheKeyKind::Closure => 8u32,
ConstantCacheKeyKind::Vector => unreachable!(),
ConstantCacheKeyKind::VectorDouble => unreachable!(),
};
let value = key.value;
let m = 0x5bd1e995u32;
let mut h1 = value as u32;
let mut h2 = ((value >> 32) as u32) ^ ty.wrapping_mul(m);
h1 ^= h2 >> 18;
h1 = h1.wrapping_mul(m);
h2 ^= h1 >> 22;
h2 = h2.wrapping_mul(m);
h1 ^= h2 >> 17;
h1 = h1.wrapping_mul(m);
h2 ^= h1 >> 19;
h2 = h2.wrapping_mul(m);
u64::from(h2)
}
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct ConstantCacheHasher;
impl DenseHashHasher<ConstantCacheKey> for ConstantCacheHasher {
fn hash(key: &ConstantCacheKey) -> u64 {
constant_cache_hash(key)
}
}
pub(super) fn table_shape_hash(shape: &TableShape) -> u64 {
let mut hash = 2166136261u32;
for entry in shape.entries() {
hash ^= entry.key as u32;
hash = hash.wrapping_mul(16777619);
if let Some(value) = entry.value {
hash ^= value as u32;
hash = hash.wrapping_mul(16777619);
}
}
u64::from(hash)
}
#[derive(Debug, Clone, Copy)]
pub(super) struct TableShapeCacheHasher;
impl DenseHashHasher<TableShapeCacheKey> for TableShapeCacheHasher {
fn hash(key: &TableShapeCacheKey) -> u64 {
table_shape_hash(&key.shape)
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct U32IdentityHasher;
impl DenseHashHasher<u32> for U32IdentityHasher {
fn hash(key: &u32) -> u64 {
u64::from(*key)
}
}
#[derive(Debug, Default)]
pub(super) struct BytecodeBuilderFunction {
pub(super) data: Vec<u8>,
pub(super) max_stack_size: u8,
pub(super) num_params: u8,
pub(super) upvalue_count: u8,
pub(super) is_vararg: bool,
pub(super) flags: u8,
pub(super) cost: u64,
pub(super) type_info: Vec<u8>,
pub(super) debug_name: Option<u32>,
pub(super) line_defined: i32,
pub(super) dump: BString,
pub(super) dump_name: BString,
pub(super) dump_instruction_offsets: Vec<i32>,
}
impl BytecodeBuilderFunction {
pub(super) fn new(num_params: u8, is_vararg: bool) -> Self {
Self {
num_params,
is_vararg,
max_stack_size: num_params,
..Self::default()
}
}
}
pub(super) struct BytecodeBuilderScratch<'src> {
pub(super) upvalue_types: Vec<u8>,
pub(super) local_types: Vec<BytecodeTypedLocal>,
pub(super) code: Vec<Instruction>,
pub(super) constants: Vec<BytecodeBuilderConstant>,
pub(super) constant_index: DenseHashMap<ConstantCacheKey, ConstantIndex, ConstantCacheHasher>,
pub(super) table_shapes: Vec<TableShape>,
pub(super) table_shape_index:
DenseHashMap<TableShapeCacheKey, ConstantIndex, TableShapeCacheHasher>,
pub(super) child_functions: Vec<u32>,
pub(super) child_function_map: DenseHashMap<u32, i16, U32IdentityHasher>,
pub(super) jumps: Vec<Jump>,
pub(super) has_long_jumps: bool,
pub(super) lines: Vec<i32>,
pub(super) local_vars: Vec<BytecodeBuilderLocal>,
pub(super) upvalues: Vec<u32>,
pub(super) feedback_slots: Vec<BytecodeFeedbackSlot>,
pub(super) debug_remarks: Vec<StoredDebugRemark>,
_src: std::marker::PhantomData<&'src ()>,
}
impl<'src> Default for BytecodeBuilderScratch<'src> {
fn default() -> Self {
Self {
upvalue_types: Vec::new(),
local_types: Vec::new(),
code: Vec::new(),
constants: Vec::new(),
constant_index: DenseHashMap::new(ConstantCacheKey::empty()),
table_shapes: Vec::new(),
table_shape_index: DenseHashMap::new(TableShapeCacheKey::empty()),
child_functions: Vec::new(),
child_function_map: DenseHashMap::new(u32::MAX),
jumps: Vec::new(),
has_long_jumps: false,
lines: Vec::new(),
local_vars: Vec::new(),
upvalues: Vec::new(),
feedback_slots: Vec::new(),
debug_remarks: Vec::new(),
_src: std::marker::PhantomData,
}
}
}
impl<'src> BytecodeBuilderScratch<'src> {
pub(super) fn borrowed_wire<'a>(
&'a self,
strings: &'a [BytecodeStringRef<'a>],
class_shapes: &'a [BytecodeClass],
) -> BorrowedBytecodeBuilderFunction<'a, 'a> {
BorrowedBytecodeBuilderFunction {
code: &self.code,
constants: &self.constants,
table_shapes: &self.table_shapes,
class_shapes,
lines: &self.lines,
strings,
_src: std::marker::PhantomData,
}
}
pub(super) fn clear(&mut self) {
self.upvalue_types.clear();
self.local_types.clear();
self.code.clear();
self.constants.clear();
self.table_shapes.clear();
self.constant_index.clear_with_threshold(32);
self.table_shape_index.clear_with_threshold(32);
self.child_functions.clear();
self.child_function_map.clear_with_threshold(32);
self.jumps.clear();
self.has_long_jumps = false;
self.lines.clear();
self.local_vars.clear();
self.upvalues.clear();
self.feedback_slots.clear();
self.debug_remarks.clear();
}
#[cfg(debug_assertions)]
pub(super) fn validate(
&self,
function: &BytecodeBuilderFunction,
functions: &[BytecodeBuilderFunction],
) {
self.validate_instructions(function, functions);
self.validate_variadic();
}
#[cfg(debug_assertions)]
fn validate_instructions(
&self,
function: &BytecodeBuilderFunction,
functions: &[BytecodeBuilderFunction],
) {
let mut instruction_valid = vec![false; self.code.len()];
let mut pc = 0usize;
while pc < self.code.len() {
let opcode = unsafe { self.code[pc].opcode_unchecked() };
instruction_valid[pc] = true;
pc += opcode.length();
debug_assert!(pc <= self.code.len());
}
let mut open_captures = Vec::new();
let mut pc = 0usize;
while pc < self.code.len() {
let instruction = self.code[pc];
let opcode = unsafe { instruction.opcode_unchecked() };
let aux = || {
self.code
.get(pc + 1)
.copied()
.map(Instruction::word)
.map(InstructionAux::new)
.expect("instruction requires aux word")
};
let aux_word = || aux().word();
let reg = |register: u8| debug_assert!(register < function.max_stack_size);
let reg_range = |register: u8, count: i32| {
let end = i32::from(register) + count.max(0);
debug_assert!(end <= i32::from(function.max_stack_size));
};
let upvalue = |index: u8| debug_assert!(index < function.upvalue_count);
let any_constant = |index: u32| {
debug_assert!((index as usize) < self.constants.len());
};
let constant = |index: u32, kind: ConstantKind| {
any_constant(index);
debug_assert_eq!(self.constants[index as usize].kind(), kind);
};
let jump = |offset: i32| {
let target = pc as i32 + 1 + offset;
debug_assert!(target >= 0);
let target = target as usize;
debug_assert!(target < self.code.len());
debug_assert!(instruction_valid[target]);
};
match opcode {
Opcode::Nop | Opcode::Break | Opcode::Coverage | Opcode::NativeCall => {}
Opcode::LoadNil | Opcode::LoadN | Opcode::NewTable => reg(instruction.a()),
Opcode::LoadB => {
reg(instruction.a());
debug_assert!(instruction.b() == 0 || instruction.b() == 1);
jump(i32::from(instruction.c()));
}
Opcode::LoadK => {
reg(instruction.a());
any_constant(instruction.d() as u16 as u32);
}
Opcode::Move => {
reg(instruction.a());
reg(instruction.b());
}
Opcode::GetGlobal | Opcode::SetGlobal => {
reg(instruction.a());
constant(aux_word(), ConstantKind::String);
}
Opcode::GetUpval | Opcode::SetUpval => {
reg(instruction.a());
upvalue(instruction.b());
}
Opcode::CloseUpvals => {
reg(instruction.a());
while open_captures
.last()
.is_some_and(|capture| *capture >= instruction.a())
{
open_captures.pop();
}
}
Opcode::GetImport => {
reg(instruction.a());
constant(instruction.d() as u16 as u32, ConstantKind::Import);
let import_id = aux_word();
debug_assert!((import_id >> 30) != 0);
for index in 0..(import_id >> 30) {
constant(
(import_id >> (20 - 10 * index)) & 1023,
ConstantKind::String,
);
}
}
Opcode::GetTable | Opcode::SetTable => {
reg(instruction.a());
reg(instruction.b());
reg(instruction.c());
}
Opcode::GetTableKs | Opcode::SetTableKs => {
reg(instruction.a());
reg(instruction.b());
constant(aux_word(), ConstantKind::String);
}
Opcode::GetTableN | Opcode::SetTableN => {
reg(instruction.a());
reg(instruction.b());
}
Opcode::NewClosure => {
reg(instruction.a());
let child = instruction.d() as u16 as usize;
debug_assert!(child < self.child_functions.len());
let child_id = self.child_functions[child] as usize;
debug_assert!(child_id < functions.len());
let upvalues = functions[child_id].upvalue_count;
for capture in 0..upvalues as usize {
debug_assert!(pc + 1 + capture < self.code.len());
debug_assert_eq!(
unsafe { self.code[pc + 1 + capture].opcode_unchecked() },
Opcode::Capture
);
}
}
Opcode::NameCall => {
reg(instruction.a());
reg(instruction.b());
constant(aux_word(), ConstantKind::String);
debug_assert!(self.code.get(pc + 2).is_some_and(|next| matches!(
unsafe { next.opcode_unchecked() },
Opcode::Call | Opcode::CallFb
)));
}
Opcode::Call | Opcode::CallFb => {
let params = i32::from(instruction.b()) - 1;
let results = i32::from(instruction.c()) - 1;
reg(instruction.a());
reg_range(instruction.a().saturating_add(1), params);
reg_range(instruction.a(), results);
}
Opcode::Return => {
reg_range(instruction.a(), i32::from(instruction.b()) - 1);
}
Opcode::Jump | Opcode::JumpBack => jump(i32::from(instruction.d())),
Opcode::CmpProto => {
reg(instruction.a());
jump(i32::from(instruction.d()));
}
Opcode::JumpIf | Opcode::JumpIfNot => {
reg(instruction.a());
jump(i32::from(instruction.d()));
}
Opcode::JumpIfEq
| Opcode::JumpIfLe
| Opcode::JumpIfLt
| Opcode::JumpIfNotEq
| Opcode::JumpIfNotLe
| Opcode::JumpIfNotLt => {
reg(instruction.a());
reg(aux_word() as u8);
jump(i32::from(instruction.d()));
}
Opcode::JumpXEqKNil | Opcode::JumpXEqKB => {
reg(instruction.a());
jump(i32::from(instruction.d()));
}
Opcode::JumpXEqKN => {
reg(instruction.a());
constant(aux_word() & 0x00ff_ffff, ConstantKind::Number);
jump(i32::from(instruction.d()));
}
Opcode::JumpXEqKS => {
reg(instruction.a());
constant(aux_word() & 0x00ff_ffff, ConstantKind::String);
jump(i32::from(instruction.d()));
}
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Div
| Opcode::IDiv
| Opcode::Mod
| Opcode::Pow
| Opcode::And
| Opcode::Or
| Opcode::Concat => {
reg(instruction.a());
reg(instruction.b());
reg(instruction.c());
if opcode == Opcode::Concat {
debug_assert!(instruction.b() <= instruction.c());
}
}
Opcode::AddK
| Opcode::SubK
| Opcode::MulK
| Opcode::DivK
| Opcode::IDivK
| Opcode::ModK
| Opcode::PowK => {
reg(instruction.a());
reg(instruction.b());
constant(u32::from(instruction.c()), ConstantKind::Number);
}
Opcode::SubRK | Opcode::DivRK => {
reg(instruction.a());
constant(u32::from(instruction.b()), ConstantKind::Number);
reg(instruction.c());
}
Opcode::AndK | Opcode::OrK => {
reg(instruction.a());
reg(instruction.b());
any_constant(u32::from(instruction.c()));
}
Opcode::Not | Opcode::Minus | Opcode::Length => {
reg(instruction.a());
reg(instruction.b());
}
Opcode::DupTable => {
reg(instruction.a());
constant(instruction.d() as u16 as u32, ConstantKind::Table);
}
Opcode::SetList => {
reg(instruction.a());
reg_range(instruction.b(), i32::from(instruction.c()) - 1);
}
Opcode::ForNPrep | Opcode::ForNLoop => {
reg(instruction.a().saturating_add(2));
jump(i32::from(instruction.d()));
}
Opcode::ForGPrep => {
reg(instruction.a().saturating_add(3));
jump(i32::from(instruction.d()));
}
Opcode::ForGLoop => {
reg(instruction.a().saturating_add(2 + aux_word() as u8));
jump(i32::from(instruction.d()));
debug_assert!(aux_word() as u8 >= 1);
}
Opcode::ForGPrepInext | Opcode::ForGPrepNext => {
reg(instruction.a().saturating_add(4));
jump(i32::from(instruction.d()));
}
Opcode::GetVarargs => {
reg_range(instruction.a(), i32::from(instruction.b()) - 1);
}
Opcode::DupClosure => {
reg(instruction.a());
constant(instruction.d() as u16 as u32, ConstantKind::Closure);
let child = match &self.constants[instruction.d() as u16 as usize] {
BytecodeBuilderConstant::Closure(index) => index.as_usize(),
_ => unreachable!(),
};
debug_assert!(child < functions.len());
let upvalues = functions[child].upvalue_count;
for capture in 0..upvalues as usize {
debug_assert!(pc + 1 + capture < self.code.len());
let capture_instruction = self.code[pc + 1 + capture];
debug_assert_eq!(
unsafe { capture_instruction.opcode_unchecked() },
Opcode::Capture
);
debug_assert!(matches!(
CaptureType::try_from(capture_instruction.a()),
Ok(CaptureType::Val | CaptureType::Upval)
));
}
}
Opcode::PrepVarargs => {
debug_assert_eq!(instruction.a(), function.num_params);
debug_assert!(function.is_vararg);
}
Opcode::LoadKx => {
reg(instruction.a());
any_constant(aux_word());
}
Opcode::JumpX => jump(instruction.e()),
Opcode::FastCall => {
jump(i32::from(instruction.c()));
debug_assert_eq!(
unsafe {
self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
},
Opcode::Call
);
}
Opcode::FastCall1 => {
reg(instruction.b());
jump(i32::from(instruction.c()));
debug_assert_eq!(
unsafe {
self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
},
Opcode::Call
);
}
Opcode::FastCall2 => {
reg(instruction.b());
jump(i32::from(instruction.c()));
debug_assert_eq!(
unsafe {
self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
},
Opcode::Call
);
reg(aux_word() as u8);
}
Opcode::FastCall2K => {
reg(instruction.b());
jump(i32::from(instruction.c()));
debug_assert_eq!(
unsafe {
self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
},
Opcode::Call
);
any_constant(aux_word());
}
Opcode::FastCall3 => {
reg(instruction.b());
jump(i32::from(instruction.c()));
debug_assert_eq!(
unsafe {
self.code[pc + 1 + usize::from(instruction.c())].opcode_unchecked()
},
Opcode::Call
);
reg((aux_word() & 0xff) as u8);
reg(((aux_word() >> 8) & 0xff) as u8);
}
Opcode::Capture => match CaptureType::try_from(instruction.a()) {
Ok(CaptureType::Val) => reg(instruction.b()),
Ok(CaptureType::Ref) => {
reg(instruction.b());
open_captures.push(instruction.b());
}
Ok(CaptureType::Upval) => upvalue(instruction.b()),
Err(_) => debug_assert!(false, "unsupported capture type"),
},
Opcode::NewClassMember => {
reg(instruction.a());
debug_assert_eq!(instruction.b(), 0);
reg(instruction.c());
constant(aux_word(), ConstantKind::String);
}
Opcode::NewClass => {
reg(instruction.a());
debug_assert!(
instruction.b() == u8::MAX || instruction.b() < function.max_stack_size
);
debug_assert_eq!(instruction.c(), 0);
constant(aux_word(), ConstantKind::Class);
}
Opcode::GetUDataKs | Opcode::SetUDataKs => {
reg(instruction.a());
reg(instruction.b());
constant(aux().kv16().into(), ConstantKind::String);
}
Opcode::NameCallUData => {
reg(instruction.a());
reg(instruction.b());
constant(aux().kv16().into(), ConstantKind::String);
debug_assert!(
self.code
.get(pc + 2)
.is_some_and(|next| unsafe { next.opcode_unchecked() } == Opcode::Call)
);
}
}
pc += opcode.length();
debug_assert!(pc <= self.code.len());
}
debug_assert!(open_captures.is_empty());
}
#[cfg(debug_assertions)]
fn validate_variadic(&self) {
let mut variadic_sequence = false;
let mut instruction_targets = vec![false; self.code.len()];
let mut pc = 0usize;
while pc < self.code.len() {
let instruction = self.code[pc];
let opcode = unsafe { instruction.opcode_unchecked() };
if let Some(target) = unsafe { instruction.jump_target_unchecked(pc as u32) }
.filter(|_| !opcode.is_fast_call())
.filter(|target| *target >= 0)
{
debug_assert!((target as usize) < self.code.len());
instruction_targets[target as usize] = true;
}
pc += opcode.length();
debug_assert!(pc <= self.code.len());
}
let mut pc = 0usize;
while pc < self.code.len() {
let instruction = self.code[pc];
let opcode = unsafe { instruction.opcode_unchecked() };
if variadic_sequence {
debug_assert!(!instruction_targets[pc]);
}
if matches!(opcode, Opcode::Call | Opcode::CallFb) {
if instruction.b() == 0 {
debug_assert!(variadic_sequence);
variadic_sequence = false;
} else {
debug_assert!(!variadic_sequence);
}
if instruction.c() == 0 {
debug_assert!(!variadic_sequence);
variadic_sequence = true;
}
} else if opcode == Opcode::GetVarargs && instruction.b() == 0 {
debug_assert!(!variadic_sequence);
variadic_sequence = true;
} else if (opcode == Opcode::Return && instruction.b() == 0)
|| (opcode == Opcode::SetList && instruction.c() == 0)
{
debug_assert!(variadic_sequence);
variadic_sequence = false;
} else if opcode == Opcode::FastCall {
let call_pc = pc + usize::from(instruction.c()) + 1;
debug_assert!(call_pc < self.code.len());
debug_assert_eq!(
unsafe { self.code[call_pc].opcode_unchecked() },
Opcode::Call
);
if self.code[call_pc].b() == 0 {
debug_assert!(variadic_sequence);
} else {
debug_assert!(!variadic_sequence);
}
} else if matches!(
opcode,
Opcode::CloseUpvals
| Opcode::NameCall
| Opcode::GetImport
| Opcode::Move
| Opcode::GetUpval
| Opcode::GetGlobal
| Opcode::GetTableKs
| Opcode::Coverage
) {
} else {
debug_assert!(!variadic_sequence);
}
pc += opcode.length();
debug_assert!(pc <= self.code.len());
}
debug_assert!(!variadic_sequence);
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum BytecodeBuilderConstant {
Boolean(bool),
Class(u32),
Closure(ClosureIndex),
Import(BytecodeImportId),
Integer64(i64),
Nil,
Number(f64),
String(u32),
Table(u32),
Vector(BytecodeVector),
VectorDouble(BytecodeVectorDouble),
}
impl BytecodeBuilderConstant {
pub(super) fn cache_key(&self) -> ConstantCacheKey {
match self {
Self::Boolean(value) => ConstantCacheKey {
kind: ConstantCacheKeyKind::Boolean,
value: u64::from(*value),
extra: 0,
extra2: 0,
extra3: 0,
},
Self::Class(_) => unreachable!("class constants use dedicated class-shape storage"),
Self::Closure(value) => ConstantCacheKey {
kind: ConstantCacheKeyKind::Closure,
value: u64::from(value.get()),
extra: 0,
extra2: 0,
extra3: 0,
},
Self::Import(value) => ConstantCacheKey {
kind: ConstantCacheKeyKind::Import,
value: u64::from(value.raw()),
extra: 0,
extra2: 0,
extra3: 0,
},
Self::Integer64(value) => ConstantCacheKey {
kind: ConstantCacheKeyKind::Integer64,
value: *value as u64,
extra: 0,
extra2: 0,
extra3: 0,
},
Self::Nil => ConstantCacheKey::nil(),
Self::Number(value) => ConstantCacheKey {
kind: ConstantCacheKeyKind::Number,
value: value.to_bits(),
extra: 0,
extra2: 0,
extra3: 0,
},
Self::String(value) => ConstantCacheKey {
kind: ConstantCacheKeyKind::String,
value: u64::from(*value),
extra: 0,
extra2: 0,
extra3: 0,
},
Self::Table(_) => unreachable!("table constants use dedicated table-shape storage"),
Self::Vector(value) => {
let bits = value.to_bits();
ConstantCacheKey {
kind: ConstantCacheKeyKind::Vector,
value: u64::from(bits[0]) | (u64::from(bits[1]) << 32),
extra: u64::from(bits[2]) | (u64::from(bits[3]) << 32),
extra2: 0,
extra3: 0,
}
}
Self::VectorDouble(value) => {
let bits = value.to_bits();
ConstantCacheKey {
kind: ConstantCacheKeyKind::VectorDouble,
value: bits[0],
extra: bits[1],
extra2: bits[2],
extra3: bits[3],
}
}
}
}
#[cfg(debug_assertions)]
fn kind(&self) -> ConstantKind {
match self {
Self::Boolean(_) => ConstantKind::Boolean,
Self::Class(_) => ConstantKind::Class,
Self::Closure(_) => ConstantKind::Closure,
Self::Import(_) => ConstantKind::Import,
Self::Integer64(_) => ConstantKind::Integer64,
Self::Nil => ConstantKind::Nil,
Self::Number(_) => ConstantKind::Number,
Self::String(_) => ConstantKind::String,
Self::Table(_) => ConstantKind::Table,
Self::Vector(_) => ConstantKind::Vector,
Self::VectorDouble(_) => ConstantKind::VectorDouble,
}
}
}
#[derive(Debug, Clone)]
pub(super) struct BytecodeBuilderLocal {
pub(super) name: u32,
pub(super) start_pc: u32,
pub(super) end_pc: u32,
pub(super) register: Register,
}
#[derive(Debug)]
pub(super) struct BorrowedBytecodeBuilderFunction<'a, 'src> {
code: &'a [Instruction],
constants: &'a [BytecodeBuilderConstant],
table_shapes: &'a [TableShape],
class_shapes: &'a [BytecodeClass],
lines: &'a [i32],
strings: &'a [BytecodeStringRef<'src>],
_src: std::marker::PhantomData<&'src ()>,
}
pub(super) struct BuilderClosureNames<'a> {
pub(super) functions: &'a [BytecodeBuilderFunction],
}
impl ClosureNameLookup for BuilderClosureNames<'_> {
fn closure_name(&self, id: ClosureIndex) -> Option<&BStr> {
self.functions
.get(id.as_usize())
.map(|function| function.dump_name.as_bstr())
.filter(|name| !name.is_empty())
}
}
impl BorrowedBytecodeBuilderFunction<'_, '_> {
fn string_bytes(&self, index: u32) -> Option<&BStr> {
index
.checked_sub(1)
.and_then(|index| self.strings.get(index as usize))
.map(|value| value.as_bytes().as_bstr())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(debug_assertions)]
enum ConstantKind {
Boolean,
Class,
Closure,
Import,
Integer64,
Nil,
Number,
String,
Table,
Vector,
VectorDouble,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ConstantCacheKeyKind {
Nil,
Boolean,
Number,
Integer64,
String,
Import,
Closure,
Vector,
VectorDouble,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ConstantCacheKey {
kind: ConstantCacheKeyKind,
value: u64,
extra: u64,
extra2: u64,
extra3: u64,
}
impl ConstantCacheKey {
pub(super) const fn empty() -> Self {
Self {
kind: ConstantCacheKeyKind::Nil,
value: u64::MAX,
extra: 0,
extra2: 0,
extra3: 0,
}
}
pub(super) const fn nil() -> Self {
Self {
kind: ConstantCacheKeyKind::Nil,
value: 0,
extra: 0,
extra2: 0,
extra3: 0,
}
}
}
impl Hash for ConstantCacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(constant_cache_hash(self));
}
}
#[derive(Debug, Clone)]
pub(super) struct TableShapeCacheKey {
shape: TableShape,
}
impl TableShapeCacheKey {
pub(super) fn empty() -> Self {
Self {
shape: TableShape::new(Vec::new()),
}
}
pub(super) fn new(shape: TableShape) -> Self {
Self { shape }
}
}
impl PartialEq for TableShapeCacheKey {
fn eq(&self, other: &Self) -> bool {
self.shape == other.shape
}
}
impl Eq for TableShapeCacheKey {}
impl Hash for TableShapeCacheKey {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(table_shape_hash(&self.shape));
}
}
impl BytecodeFunctionWire for BorrowedBytecodeBuilderFunction<'_, '_> {
fn code(&self) -> &[Instruction] {
self.code
}
fn constant_count(&self) -> usize {
self.constants.len()
}
fn append_constant(
&self,
result: &mut Vec<u8>,
index: usize,
closure_names: &dyn ClosureNameLookup,
detailed: bool,
) {
match self.constants.get(index) {
Some(BytecodeBuilderConstant::Nil) => result.extend_from_slice(b"nil"),
Some(BytecodeBuilderConstant::Boolean(true)) => result.extend_from_slice(b"true"),
Some(BytecodeBuilderConstant::Boolean(false)) => result.extend_from_slice(b"false"),
Some(BytecodeBuilderConstant::Number(n)) => {
luau_printf::sprintf!(=> result, "%.17g", *n);
}
Some(BytecodeBuilderConstant::Integer64(n)) => {
write!(result, "{n}").unwrap();
}
Some(BytecodeBuilderConstant::String(value)) => {
if let Some(bytes) = self.string_bytes(*value) {
append_string_constant(result, bytes);
} else {
write!(result, "K{index}").unwrap();
}
}
Some(BytecodeBuilderConstant::Import(import_id)) => {
let count = import_id.raw() >> 30;
for component in 0..count {
let constant = (import_id.raw() >> (20 - 10 * component)) & 1023;
let Some(BytecodeBuilderConstant::String(value)) =
self.constants.get(constant as usize)
else {
continue;
};
if component > 0 {
result.push(b'.');
}
if let Some(bytes) = self.string_bytes(*value) {
result.extend_from_slice(bytes);
}
}
}
Some(BytecodeBuilderConstant::Table(shape_index)) => {
let Some(shape) = self.table_shapes.get(*shape_index as usize) else {
result.extend_from_slice(b"{...}");
return;
};
if detailed {
let entries = shape.entries();
let sizenode = if entries.is_empty() {
0
} else {
1u32 << (i32::BITS - (entries.len() as i32 - 1).leading_zeros())
};
let mask = sizenode.saturating_sub(1);
let mut slots = vec![0u32; entries.len()];
let mut slot_owner = vec![usize::MAX; sizenode as usize];
for (shape_index, entry) in entries.iter().enumerate() {
let Some(BytecodeBuilderConstant::String(key)) =
self.constants.get(entry.key as usize)
else {
result.extend_from_slice(b"{...}");
return;
};
let Some(key_bytes) = self.string_bytes(*key) else {
result.extend_from_slice(b"{...}");
return;
};
slots[shape_index] = BytecodeBuilder::get_string_hash(key_bytes) & mask;
if slot_owner[slots[shape_index] as usize] == usize::MAX {
slot_owner[slots[shape_index] as usize] = shape_index;
}
}
result.push(b'{');
for (shape_index, entry) in entries.iter().enumerate() {
if shape_index > 0 {
result.extend_from_slice(b", ");
}
result.push(b'[');
self.append_constant(result, entry.key as usize, closure_names, false);
result.extend_from_slice(b"]");
if let Some(value) = entry.value {
result.extend_from_slice(b" = ");
self.append_constant(result, value as usize, closure_names, false);
}
write!(result, " #{}", slots[shape_index]).unwrap();
if slot_owner[slots[shape_index] as usize] != shape_index {
result.extend_from_slice(b" (conflict)");
}
}
write!(result, "}} sizenode={sizenode}").unwrap();
} else {
result.extend_from_slice(b"{...}");
}
}
Some(BytecodeBuilderConstant::Closure(id)) => {
append_closure_name(result, *id, closure_names)
}
Some(BytecodeBuilderConstant::Vector(value)) => {
if value.w() == 0.0 {
luau_printf::sprintf!(
=> result,
"%.9g, %.9g, %.9g",
f64::from(value.x()),
f64::from(value.y()),
f64::from(value.z())
);
} else {
luau_printf::sprintf!(
=> result,
"%.9g, %.9g, %.9g, %.9g",
f64::from(value.x()),
f64::from(value.y()),
f64::from(value.z()),
f64::from(value.w())
);
}
}
Some(BytecodeBuilderConstant::VectorDouble(value)) => {
if flags::LuauCompileEmitVectorDouble.get() {
if value.w() == 0.0 {
luau_printf::sprintf!(
=> result,
"%.17g, %.17g, %.17g",
value.x(),
value.y(),
value.z()
);
} else {
luau_printf::sprintf!(
=> result,
"%.17g, %.17g, %.17g, %.17g",
value.x(),
value.y(),
value.z(),
value.w()
);
}
} else if value.w() == 0.0 {
luau_printf::sprintf!(
=> result,
"%.9g, %.9g, %.9g",
value.x() as f32 as f64,
value.y() as f32 as f64,
value.z() as f32 as f64
);
} else {
luau_printf::sprintf!(
=> result,
"%.9g, %.9g, %.9g, %.9g",
value.x() as f32 as f64,
value.y() as f32 as f64,
value.z() as f32 as f64,
value.w() as f32 as f64
);
}
}
Some(BytecodeBuilderConstant::Class(class_index)) => {
let Some(class) = self.class_shapes.get(*class_index as usize) else {
result.extend_from_slice(b"class ?");
return;
};
result.extend_from_slice(b"class ");
match self.constants.get(class.class_name as usize) {
Some(BytecodeBuilderConstant::String(value)) => {
if let Some(bytes) = self.string_bytes(*value) {
result.extend_from_slice(bytes);
} else {
write!(result, "K{}", class.class_name).unwrap();
}
}
_ => write!(result, "K{}", class.class_name).unwrap(),
}
write!(
result,
" (props: {}, methods: {})",
class.property_names.len(),
class.method_names.len()
)
.unwrap();
}
None => result.extend_from_slice(b"?"),
}
}
fn line_for_pc(&self, pc: usize) -> Option<i32> {
self.lines.get(pc).copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Jump {
pub(super) source: usize,
pub(super) target: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct StoredDebugRemark {
pub(super) pc: usize,
pub(super) line: i32,
pub(super) text: BString,
}
#[bitfield(u32)]
#[derive(PartialEq, Eq)]
pub struct BytecodeDumpFlags {
#[bits(default = false)]
code: bool,
#[bits(default = false)]
lines: bool,
#[bits(default = false)]
source: bool,
#[bits(default = false)]
locals: bool,
#[bits(default = false)]
remarks: bool,
#[bits(default = false)]
types: bool,
#[bits(default = false)]
constants: bool,
#[bits(25, default = 0)]
_reserved: u32,
}