use super::support::{
BytecodeBuilderConstant, BytecodeBuilderFunction, BytecodeBuilderScratch, TableShapeCacheKey,
};
use super::*;
use crate::function::BytecodeStringTable;
use crate::model::{
BytecodeClass, BytecodeFeedbackSlot, BytecodeFeedbackType, BytecodeImportId, BytecodeString,
BytecodeTypedLocal, BytecodeUserdataType, BytecodeVector, BytecodeVectorDouble, ClosureIndex,
ConstantIndex, Register, TableShape,
};
use crate::opcodes::{
BYTECODE_TYPE_VERSION_TARGET, BYTECODE_VERSION_CLASSES, BYTECODE_VERSION_TARGET,
BytecodeConstantTag, FEEDBACK_TYPE_CALLTARGET, PROTO_FLAG_INLINABLE,
};
use crate::wire::BytecodeWriter;
use luau_common::flags;
use std::borrow::Cow;
impl<'src> BytecodeBuilder<'src> {
fn add_bytecode_constant(&mut self, value: BytecodeBuilderConstant) -> ConstantIndex {
let key = value.cache_key();
let proto = self.current_function();
if let Some(index) = proto.constant_index.get(&key) {
return *index;
}
if proto.constants.len() >= MAX_CONSTANT_COUNT {
return -1;
}
let index = proto.constants.len() as ConstantIndex;
proto.constants.push(value);
let (_, fresh) = proto.constant_index.insert(key, index);
debug_assert!(fresh);
index
}
pub fn add_constant_nil(&mut self) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Nil)
}
pub fn add_constant_boolean(&mut self, value: bool) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Boolean(value))
}
pub fn add_constant_number(&mut self, value: f64) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Number(value))
}
pub fn add_constant_integer(&mut self, value: i64) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Integer64(value))
}
pub fn add_constant_string(
&mut self,
value: impl Into<BytecodeStringRef<'src>>,
) -> ConstantIndex {
let value = value.into();
let index = self.add_string_table_entry(&value);
self.add_bytecode_constant(BytecodeBuilderConstant::String(index))
}
pub fn add_import(&mut self, import_id: BytecodeImportId) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Import(import_id))
}
pub fn add_constant_closure(&mut self, function_id: u32) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Closure(ClosureIndex::new(
function_id,
)))
}
pub fn add_constant_table(&mut self, shape: &TableShape) -> ConstantIndex {
let proto = self.current_function();
let key = TableShapeCacheKey::new(shape.clone());
if let Some(index) = proto.table_shape_index.get(&key) {
return *index;
}
if proto.constants.len() >= MAX_CONSTANT_COUNT {
return -1;
}
let index = proto.constants.len() as ConstantIndex;
let table_shape_index = proto.table_shapes.len() as u32;
proto.table_shapes.push(shape.clone());
let (_, fresh) = proto.table_shape_index.insert(key, index);
debug_assert!(fresh);
proto
.constants
.push(BytecodeBuilderConstant::Table(table_shape_index));
index
}
pub fn add_constant_vector(&mut self, x: f32, y: f32, z: f32, w: f32) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::Vector(BytecodeVector::new(
x, y, z, w,
)))
}
pub fn add_constant_vector_double(&mut self, x: f64, y: f64, z: f64, w: f64) -> ConstantIndex {
self.add_bytecode_constant(BytecodeBuilderConstant::VectorDouble(
BytecodeVectorDouble::new(x, y, z, w),
))
}
pub fn add_class_shape(&mut self, shape: BytecodeClass) -> ConstantIndex {
if self.scratch.constants.len() >= MAX_CONSTANT_COUNT {
return -1;
}
let index = self.scratch.constants.len() as ConstantIndex;
let class_shape_index = self.class_shapes.len() as u32;
self.class_shapes.push(shape);
self.scratch
.constants
.push(BytecodeBuilderConstant::Class(class_shape_index));
index
}
pub fn add_fb_slot(&mut self, ty: BytecodeFeedbackType) -> u32 {
debug_assert_eq!(ty, BytecodeFeedbackType::CallTarget);
let pc = self.current_function().code.len() as u32;
let proto = self.current_function();
proto.feedback_slots.push(BytecodeFeedbackSlot { pc });
proto.feedback_slots.len() as u32 - 1
}
pub fn set_function_type_info(&mut self, value: Vec<u8>) {
self.current_function_meta().type_info = value;
}
pub fn push_local_type_info(&mut self, ty: u8, register: Register, start_pc: u32, end_pc: u32) {
self.current_function()
.local_types
.push(BytecodeTypedLocal {
ty,
register,
start_pc,
end_pc,
});
}
pub fn push_upvalue_type_info(&mut self, ty: u8) {
self.current_function().upvalue_types.push(ty);
}
pub fn add_userdata_type(&mut self, name: impl Into<BytecodeString>) -> u32 {
let index = self.userdata_types.len();
self.userdata_types.push(BytecodeUserdataType {
name: name.into(),
name_ref: 0,
used: false,
});
u32::try_from(index).expect("userdata type index must fit bytecode varint")
}
pub fn use_userdata_type(&mut self, index: u32) {
self.userdata_types[index as usize].used = true;
}
pub fn finalize(&mut self) {
debug_assert!(
self.bytecode.is_empty(),
"BytecodeBuilder::finalize requires bytecode to be empty"
);
let main = u32::try_from(
self.main
.expect("main function must be set before finalize"),
)
.expect("main function id must fit varint");
self.assign_userdata_type_name_refs();
self.bytecode = self.finish_bytecode(main);
}
pub fn get_bytecode(&self) -> &[u8] {
debug_assert!(
!self.bytecode.is_empty(),
"BytecodeBuilder::get_bytecode requires finalize first"
);
&self.bytecode
}
pub fn get_error(message: impl AsRef<[u8]>) -> Vec<u8> {
let message = message.as_ref();
let mut result = Vec::with_capacity(message.len() + 1);
result.push(0);
result.extend_from_slice(message);
result
}
pub fn get_string_table(&self) -> BytecodeStringTable<'_> {
let mut strings = vec![Cow::Borrowed(&[][..]); self.string_index.len()];
for (value, index) in &self.string_index {
debug_assert!(*index > 0 && (*index as usize) <= strings.len());
strings[*index as usize - 1] = Cow::Borrowed(value.as_bytes());
}
BytecodeStringTable::new(strings)
}
pub fn get_function_data(&self, id: usize) -> Vec<u8> {
self.functions[id].data.clone()
}
pub(super) fn function_data(&self, id: usize) -> Vec<u8> {
let mut writer = BytecodeWriter::new();
self.write_function(&mut writer, &self.functions[id], &self.scratch);
writer.into_bytes()
}
fn finish_bytecode(&self, main: u32) -> Vec<u8> {
let version = self.version();
let mut writer = BytecodeWriter::new();
writer.write_u8(version);
writer.write_u8(BYTECODE_TYPE_VERSION_TARGET);
self.write_finalized_string_table(&mut writer);
self.write_userdata_remapping(&mut writer);
writer.write_varint(self.functions.len() as u32);
for function in &self.functions {
if version >= 12 {
writer.write_varint(function.data.len() as u32);
}
writer.write_bytes(&function.data);
}
writer.write_varint(main);
writer.into_bytes()
}
fn version(&self) -> u8 {
if flags::DebugLuauUserDefinedClasses.get() {
return BYTECODE_VERSION_CLASSES;
}
if flags::LuauCompileEmitVectorDouble.get() {
return 13;
}
if flags::LuauBytecodeCostModel.get() {
return 12;
}
if flags::LuauEmitCallFeedback.get() {
return 11;
}
BYTECODE_VERSION_TARGET
}
fn write_function(
&self,
writer: &mut BytecodeWriter,
function: &BytecodeBuilderFunction,
scratch: &BytecodeBuilderScratch<'src>,
) {
writer.write_u8(function.max_stack_size);
writer.write_u8(function.num_params);
writer.write_u8(function.upvalue_count);
writer.write_u8(u8::from(function.is_vararg));
writer.write_u8(function.flags);
if function.type_info.is_empty()
&& scratch.upvalue_types.is_empty()
&& scratch.local_types.is_empty()
{
writer.write_varint(0);
} else {
let mut types = BytecodeWriter::new();
types.write_varint(function.type_info.len() as u32);
types.write_varint(scratch.upvalue_types.len() as u32);
types.write_varint(scratch.local_types.len() as u32);
types.write_bytes(&function.type_info);
for ty in &scratch.upvalue_types {
types.write_u8(*ty);
}
for local in &scratch.local_types {
types.write_u8(local.ty);
types.write_u8(local.register);
types.write_varint(local.start_pc);
debug_assert!(local.end_pc >= local.start_pc);
types.write_varint(local.end_pc - local.start_pc);
}
let types = types.into_bytes();
writer.write_varint(types.len() as u32);
writer.write_bytes(&types);
}
writer.write_varint(scratch.code.len() as u32);
for instruction in &scratch.code {
writer.write_u32(instruction.word());
}
writer.write_varint(scratch.constants.len() as u32);
for constant in &scratch.constants {
self.write_function_constant(writer, scratch, constant);
}
writer.write_varint(scratch.child_functions.len() as u32);
for child in &scratch.child_functions {
writer.write_varint(*child);
}
writer.write_varint(function.line_defined as u32);
writer.write_varint(function.debug_name.as_ref().copied().unwrap_or(0));
if scratch.lines.is_empty() || scratch.lines.contains(&0) {
writer.write_u8(0);
} else {
writer.write_u8(1);
Self::write_line_info(writer, &scratch.lines);
}
if scratch.local_vars.is_empty() && scratch.upvalues.is_empty() {
writer.write_u8(0);
} else {
writer.write_u8(1);
writer.write_varint(scratch.local_vars.len() as u32);
for local in &scratch.local_vars {
writer.write_varint(local.name);
writer.write_varint(local.start_pc);
writer.write_varint(local.end_pc);
writer.write_u8(local.register);
}
writer.write_varint(scratch.upvalues.len() as u32);
for upvalue in &scratch.upvalues {
writer.write_varint(*upvalue);
}
}
if flags::LuauEmitCallFeedback.get() {
writer.write_varint(scratch.feedback_slots.len() as u32);
for slot in &scratch.feedback_slots {
writer.write_u8(FEEDBACK_TYPE_CALLTARGET);
writer.write_varint(slot.pc);
}
} else if self.version() >= 12 {
writer.write_varint(0);
}
if self.version() >= 12 && function.flags & PROTO_FLAG_INLINABLE != 0 {
writer.write_varint64(function.cost);
}
}
fn write_function_constant(
&self,
writer: &mut BytecodeWriter,
scratch: &BytecodeBuilderScratch<'src>,
constant: &BytecodeBuilderConstant,
) {
match constant {
BytecodeBuilderConstant::Nil => writer.write_u8(BytecodeConstantTag::Nil as u8),
BytecodeBuilderConstant::Boolean(value) => {
writer.write_u8(BytecodeConstantTag::Boolean as u8);
writer.write_u8(u8::from(*value));
}
BytecodeBuilderConstant::Number(value) => {
writer.write_u8(BytecodeConstantTag::Number as u8);
writer.write_f64(*value);
}
BytecodeBuilderConstant::Integer64(value) => {
writer.write_u8(BytecodeConstantTag::Integer as u8);
writer.write_integer_constant(*value);
}
BytecodeBuilderConstant::Vector(value) => {
writer.write_u8(BytecodeConstantTag::Vector as u8);
writer.write_f32(value.x());
writer.write_f32(value.y());
writer.write_f32(value.z());
writer.write_f32(value.w());
}
BytecodeBuilderConstant::VectorDouble(value) => {
if flags::LuauCompileEmitVectorDouble.get() {
writer.write_u8(BytecodeConstantTag::VectorDouble as u8);
writer.write_f64(value.x());
writer.write_f64(value.y());
writer.write_f64(value.z());
writer.write_f64(value.w());
} else {
writer.write_u8(BytecodeConstantTag::Vector as u8);
writer.write_f32(value.x() as f32);
writer.write_f32(value.y() as f32);
writer.write_f32(value.z() as f32);
writer.write_f32(value.w() as f32);
}
}
BytecodeBuilderConstant::String(value) => {
writer.write_u8(BytecodeConstantTag::String as u8);
writer.write_varint(*value);
}
BytecodeBuilderConstant::Import(value) => {
writer.write_u8(BytecodeConstantTag::Import as u8);
writer.write_u32(value.raw());
}
BytecodeBuilderConstant::Table(shape_index) => {
let shape = &scratch.table_shapes[*shape_index as usize];
let write_constants = shape.has_constants();
writer.write_u8(if write_constants {
BytecodeConstantTag::TableWithConstants as u8
} else {
BytecodeConstantTag::Table as u8
});
writer.write_varint(shape.len() as u32);
for entry in shape.entries() {
writer
.write_varint(u32::try_from(entry.key).expect("table key must fit varint"));
if write_constants {
writer.write_i32(entry.value.unwrap_or(-1));
}
}
}
BytecodeBuilderConstant::Closure(id) => {
writer.write_u8(BytecodeConstantTag::Closure as u8);
writer.write_varint(id.get());
}
BytecodeBuilderConstant::Class(class_index) => {
let class = &self.class_shapes[*class_index as usize];
writer.write_u8(BytecodeConstantTag::ClassShape as u8);
writer.write_varint(
u32::try_from(class.class_name).expect("class name must fit varint"),
);
writer.write_varint(class.property_names.len() as u32);
writer.write_varint(class.method_names.len() as u32);
for prop in &class.property_names {
writer
.write_varint(u32::try_from(*prop).expect("property name must fit varint"));
}
for method in &class.method_names {
writer
.write_varint(u32::try_from(*method).expect("method name must fit varint"));
}
}
}
}
fn write_line_info(writer: &mut BytecodeWriter, lines: &[i32]) {
debug_assert!(!lines.is_empty());
let mut span = 1usize << 24;
let mut offset = 0usize;
while offset < lines.len() {
let mut next = offset;
let mut min = lines[offset];
let mut max = lines[offset];
while next < lines.len() && next < offset + span {
min = min.min(lines[next]);
max = max.max(lines[next]);
if max - min > 255 {
break;
}
next += 1;
}
if next < lines.len() && next - offset < span {
span = 1usize << (next - offset).ilog2();
} else {
offset += span;
}
}
let baseline_size = (lines.len() - 1) / span + 1;
let mut baseline = vec![0i32; baseline_size];
for offset in (0..lines.len()).step_by(span) {
let end = (offset + span).min(lines.len());
baseline[offset / span] = *lines[offset..end]
.iter()
.min()
.expect("line range must be non-empty");
}
let log_span = span.ilog2() as u8;
writer.write_u8(log_span);
let mut last_offset = 0u8;
for (index, line) in lines.iter().copied().enumerate() {
let delta = line - baseline[index >> usize::from(log_span)];
debug_assert!((0..=255).contains(&delta));
let delta = delta as u8;
writer.write_u8(delta.wrapping_sub(last_offset));
last_offset = delta;
}
let mut last_line = 0i32;
for line in baseline {
writer.write_i32(line.wrapping_sub(last_line));
last_line = line;
}
}
pub(super) fn add_string_table_entry(&mut self, value: &BytecodeStringRef<'src>) -> u32 {
let next_index = self.string_index.len() as u32 + 1;
let index = self.string_index.get_or_insert_default(*value);
if *index == 0 {
*index = next_index;
if self.dump_flags.code() {
self.debug_strings.push(*value);
}
}
*index
}
fn base_string_table(&self) -> Vec<&[u8]> {
let mut strings = vec![None; self.string_index.len()];
for (value, index) in &self.string_index {
debug_assert!(*index > 0 && (*index as usize) <= strings.len());
strings[*index as usize - 1] = Some(value.as_bytes());
}
strings
.into_iter()
.map(|string| string.expect("base string table entry must exist"))
.collect()
}
fn assign_userdata_type_name_refs(&mut self) {
let base_string_refs = self
.string_index
.iter()
.map(|(value, index)| (value.as_bytes(), *index))
.collect::<Vec<_>>();
let mut next_index = self.string_index.len() as u32 + 1;
for index in 0..self.userdata_types.len() {
let (previous, current_and_rest) = self.userdata_types.split_at_mut(index);
let current = &mut current_and_rest[0];
if !current.used {
current.name_ref = 0;
continue;
}
if let Some((_, name_ref)) = base_string_refs
.iter()
.find(|(name, _)| *name == current.name.as_bytes())
{
current.name_ref = *name_ref;
continue;
}
if let Some(name_ref) = previous
.iter()
.find(|userdata_type| {
userdata_type.used && userdata_type.name.as_bytes() == current.name.as_bytes()
})
.map(|userdata_type| userdata_type.name_ref)
{
current.name_ref = name_ref;
continue;
}
current.name_ref = next_index;
next_index += 1;
}
}
fn finalized_string_table(&self) -> Vec<&[u8]> {
let base_count = self.string_index.len();
let mut strings = self
.base_string_table()
.into_iter()
.map(Some)
.collect::<Vec<_>>();
let final_len = self
.userdata_types
.iter()
.map(|userdata_type| userdata_type.name_ref as usize)
.max()
.unwrap_or(strings.len())
.max(strings.len());
strings.resize(final_len, None);
for userdata_type in &self.userdata_types {
if userdata_type.used {
let slot = &mut strings[userdata_type.name_ref as usize - 1];
if slot.is_none() && userdata_type.name_ref as usize > base_count {
*slot = Some(userdata_type.name.as_bytes());
} else {
debug_assert_eq!(
slot.expect("userdata string table entry must exist"),
userdata_type.name.as_bytes()
);
}
}
}
strings
.into_iter()
.map(|string| string.expect("finalized string table entry must exist"))
.collect()
}
fn write_finalized_string_table(&self, writer: &mut BytecodeWriter) {
let strings = self.finalized_string_table();
writer.write_varint(strings.len() as u32);
for string in strings {
writer.write_varint(string.len() as u32);
writer.write_bytes(string);
}
}
fn write_userdata_remapping(&self, writer: &mut BytecodeWriter) {
for (index, userdata_type) in self.userdata_types.iter().enumerate() {
if userdata_type.used {
let bytecode_index = u8::try_from(index + 1)
.expect("userdata type remapping index must fit bytecode byte");
writer.write_u8(bytecode_index);
writer.write_varint(userdata_type.name_ref);
}
}
writer.write_u8(0);
}
pub fn get_string_hash(key: impl AsRef<[u8]>) -> u32 {
let bytes = key.as_ref();
let mut hash = bytes.len() as u32;
for byte in bytes.iter().rev() {
hash ^= (hash << 5)
.wrapping_add(hash >> 2)
.wrapping_add(u32::from(*byte));
}
hash
}
}