mod entry;
mod execute;
pub use entry::{PreCallResult, VmCallFrame};
pub use execute::VmExecution;
use core::cmp::Ordering;
use core::ptr;
use luau_common::{ByteSlice, flags};
use crate::Table;
use crate::call::{CallRuntime, ThreadStack};
use crate::debug::DebugRuntime;
use crate::gc::GcBarrier;
use crate::gc::GcObject;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::metamethod::{MetamethodRuntime, TmEvent};
use crate::native::NativeCallContext;
use crate::number::str_to_num;
use crate::state::ThreadState;
use crate::string::{MAX_STRING_SIZE, StringRuntime, TString};
use crate::table::TableRuntime;
use crate::thread::Thread;
use crate::types::{
LUA_TBOOLEAN, LUA_TCLASS, LUA_TINTEGER, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNUMBER, LUA_TOBJECT,
LUA_TSTRING, LUA_TTABLE, LUA_TUSERDATA, LUA_TVECTOR, LUA_VECTOR_SIZE,
};
use crate::value::{RAW_TVALUE_NIL, TValue, TValueCursor, nil_object};
use crate::{VmErrorResult, VmResult};
const MAX_TAG_LOOP: i32 = 100;
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait VmConversions: Sealed {
unsafe fn to_number_internal(&self, value: TValue, result: TValue) -> Option<TValue>;
unsafe fn to_vector_internal(
&self,
value: TValue,
) -> Option<[f32; crate::types::LUA_VECTOR_SIZE]>;
unsafe fn to_string_internal(&self, value: TValue) -> VmErrorResult<i32>;
}
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait VmOperations: Sealed {
unsafe fn prepare_forn(
&self,
limit: TValueCursor,
step: TValueCursor,
index: TValueCursor,
) -> VmResult;
unsafe fn call_tm(&self, n_params: i32, result: i32) -> VmResult;
unsafe fn less_than_internal(&self, left: TValue, right: TValue) -> VmResult<i32>;
unsafe fn less_equal(&self, left: TValue, right: TValue) -> VmResult<i32>;
unsafe fn equal_value(&self, left: TValue, right: TValue) -> VmResult<i32>;
unsafe fn do_arith_impl(
&self,
result: TValueCursor,
left: TValue,
right: TValue,
op: TmEvent,
) -> VmResult;
unsafe fn do_len(&self, result: TValueCursor, value: TValue) -> VmResult;
unsafe fn get_table_internal(
&self,
table: TValue,
key: TValue,
value: TValueCursor,
) -> VmResult;
unsafe fn set_table_internal(&self, table: TValue, key: TValue, value: TValue) -> VmResult;
unsafe fn concat_internal(&self, total: i32, last: i32) -> VmResult;
unsafe fn get_import(
&self,
env: Table,
constants: TValueCursor,
target: TValueCursor,
id: u32,
propagate_nil: bool,
) -> VmResult;
}
pub fn string_compare(left: TString, right: TString) -> i32 {
if left == right {
return 0;
}
let left_bytes = unsafe { left.as_bytes() };
let right_bytes = unsafe { right.as_bytes() };
let left_first = left_bytes.first().copied().unwrap_or(0);
let right_first = right_bytes.first().copied().unwrap_or(0);
if left_first != right_first {
return i32::from(left_first) - i32::from(right_first);
}
match left_bytes.cmp(right_bytes) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
pub unsafe fn get_comp_tm(
thread: &Thread,
left: Option<Table>,
right: Option<Table>,
event: TmEvent,
) -> Option<TValue> {
let left = left?;
let tm_left = unsafe { thread.fast_tm(Some(left), event) }?;
if right == Some(left) {
return Some(tm_left);
}
let tm_right = unsafe { thread.fast_tm(right, event) }?;
tm_left.raw_equal(tm_right).then_some(tm_left)
}
impl Thread {
unsafe fn call_tm_result(
&self,
result: TValueCursor,
function: TValue,
p1: TValue,
p2: TValue,
) -> VmResult<TValueCursor> {
unsafe {
let result_offset = self.save_stack(result);
let top = self.stack_top();
let top_offset = self.save_stack(top);
top.value_unchecked().set_obj(function);
top.add(1).value_unchecked().set_obj(p1);
top.add(2).value_unchecked().set_obj(p2);
self.check_stack_internal(3)?;
let top = self.restore_stack(top_offset);
self.set_stack_top(top.add(3));
self.call_internal(top, 1)?;
let result = self.restore_stack(result_offset);
let top = self.stack_top().sub(1);
self.set_stack_top(top);
result.value_unchecked().set_obj(top.value_unchecked());
Ok(result)
}
}
unsafe fn call_tm3(&self, function: TValue, p1: TValue, p2: TValue, p3: TValue) -> VmResult {
unsafe {
let top = self.stack_top();
let top_offset = self.save_stack(top);
top.value_unchecked().set_obj(function);
top.add(1).value_unchecked().set_obj(p1);
top.add(2).value_unchecked().set_obj(p2);
top.add(3).value_unchecked().set_obj(p3);
self.check_stack_internal(4)?;
let top = self.restore_stack(top_offset);
self.set_stack_top(top.add(4));
self.call_internal(top, 0)
}
}
unsafe fn call_order_tm(
&self,
left: TValue,
right: TValue,
event: TmEvent,
error: bool,
) -> VmResult<i32> {
unsafe {
let Some(tm_left) = self.get_tm_by_obj(left, event) else {
if error {
return self.order_error(left, right, event).map_err(Into::into);
}
return Ok(-1);
};
let Some(tm_right) = self.get_tm_by_obj(right, event) else {
if error {
return self.order_error(left, right, event).map_err(Into::into);
}
return Ok(-1);
};
if !tm_left.raw_equal(tm_right) {
if error {
return self.order_error(left, right, event).map_err(Into::into);
}
return Ok(-1);
}
let result = self.call_tm_result(self.stack_top(), tm_left, left, right)?;
Ok(i32::from(!result.value_unchecked().is_false()))
}
}
unsafe fn call_bin_tm(
&self,
left: TValue,
right: TValue,
result: TValueCursor,
event: TmEvent,
) -> VmResult<bool> {
unsafe {
let tm = self
.get_tm_by_obj(left, event)
.or_else(|| self.get_tm_by_obj(right, event));
let Some(tm) = tm else {
return Ok(false);
};
self.call_tm_result(result, tm, left, right)?;
Ok(true)
}
}
unsafe fn coerce_string_slot(&self, object: TValue) -> VmResult<bool> {
Ok(object.is_string() || unsafe { self.to_string_internal(object)? != 0 })
}
}
unsafe fn set_vector_result(result: TValueCursor, value: [f32; LUA_VECTOR_SIZE]) {
#[cfg(not(feature = "vector4"))]
unsafe {
result
.value_unchecked()
.set_vector([value[0], value[1], value[2]]);
}
#[cfg(feature = "vector4")]
unsafe {
result
.value_unchecked()
.set_vector([value[0], value[1], value[2], value[3]]);
}
}
impl Thread {
unsafe fn call_tm_operation(&self, n_params: i32, result: i32) -> VmResult {
unsafe {
self.increment_native_call_depth();
if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
>= crate::thread::LUAI_MAX_NATIVE_CALLS
{
self.check_c_stack()?;
}
self.check_stack_internal(crate::thread::LUA_MIN_STACK as i32)?;
let top = self.stack_top();
let function = top.sub(n_params as usize + 1);
let closure = function.value_unchecked().closure_value();
let call_info = self.incr_ci()?.call_info_unchecked();
call_info.init_call(
function,
top.add(crate::thread::LUA_MIN_STACK),
i32::from(result >= 0),
closure.proto(),
);
debug_assert!(call_info.top() <= self.stack_last());
debug_assert!(function.value_unchecked().is_function());
debug_assert!(closure.is_native());
self.set_stack_base(function.add(1));
debug_assert!(self.stack_top() == self.stack_base().add(n_params as usize));
let function = closure.native_data().function.unwrap_unchecked();
let produced = function(NativeCallContext::new(self))?;
let parent_cursor = self.current_call_info_cursor().sub(1);
let parent = parent_cursor.call_info_unchecked();
if result >= 0 {
let destination = parent.base().add(result as usize);
if produced > 0 {
let produced_value = self.stack_top().sub(produced);
destination
.value_unchecked()
.set_obj(produced_value.value_unchecked());
} else {
destination.value_unchecked().set_nil();
}
}
self.decrement_native_call_depth();
self.restore_call_frame(parent_cursor, parent.top());
}
Ok(())
}
unsafe fn prepare_forn_operation(
&self,
limit: TValueCursor,
step: TValueCursor,
index: TValueCursor,
) -> VmResult {
unsafe {
if !index.value_unchecked().is_number()
&& self
.to_number_internal(index.value_unchecked(), index.value_unchecked())
.is_none()
{
return self
.for_error(index.value_unchecked(), "initial value")
.map_err(Into::into);
}
if !limit.value_unchecked().is_number()
&& self
.to_number_internal(limit.value_unchecked(), limit.value_unchecked())
.is_none()
{
return self
.for_error(limit.value_unchecked(), "limit")
.map_err(Into::into);
}
if !step.value_unchecked().is_number()
&& self
.to_number_internal(step.value_unchecked(), step.value_unchecked())
.is_none()
{
return self
.for_error(step.value_unchecked(), "step")
.map_err(Into::into);
}
}
Ok(())
}
}
impl VmConversions for Thread {
unsafe fn to_number_internal(&self, value: TValue, result: TValue) -> Option<TValue> {
if value.is_number() {
return Some(value);
}
if value.is_string() {
let string = value.string_value();
if let Some(number) = str_to_num(unsafe { string.as_bytes() }) {
result.set_number(number);
return Some(result);
}
}
None
}
unsafe fn to_vector_internal(&self, value: TValue) -> Option<[f32; LUA_VECTOR_SIZE]> {
if value.is_vector() {
Some(value.vector_value())
} else {
None
}
}
unsafe fn to_string_internal(&self, value: TValue) -> VmErrorResult<i32> {
if !value.is_number() {
return Ok(0);
}
let mut digits = [0u8; crate::number::LUAI_MAXNUM2STR];
let len = crate::number::num_to_str(&mut digits, value.number_value());
let string = unsafe { self.intern_string(digits[..len].as_bstr())? };
value.set_string_value(string);
Ok(1)
}
}
impl VmOperations for Thread {
unsafe fn prepare_forn(
&self,
limit: TValueCursor,
step: TValueCursor,
index: TValueCursor,
) -> VmResult {
unsafe { self.prepare_forn_operation(limit, step, index) }
}
unsafe fn call_tm(&self, n_params: i32, result: i32) -> VmResult {
unsafe { self.call_tm_operation(n_params, result) }
}
unsafe fn less_than_internal(&self, left: TValue, right: TValue) -> VmResult<i32> {
if left.tt() != right.tt() {
unsafe {
self.order_error(left, right, TmEvent::Lt)
.map_err(Into::into)
}
} else if left.is_number() {
Ok(i32::from(crate::number::num_lt(
left.number_value(),
right.number_value(),
)))
} else if left.is_string() {
Ok(i32::from(
string_compare(left.string_value(), right.string_value()) < 0,
))
} else {
unsafe { self.call_order_tm(left, right, TmEvent::Lt, true) }
}
}
unsafe fn less_equal(&self, left: TValue, right: TValue) -> VmResult<i32> {
unsafe {
if left.tt() != right.tt() {
self.order_error(left, right, TmEvent::Le)
.map_err(Into::into)
} else if left.is_number() {
Ok(i32::from(crate::number::num_le(
left.number_value(),
right.number_value(),
)))
} else if left.is_string() {
Ok(i32::from(
string_compare(left.string_value(), right.string_value()) <= 0,
))
} else {
let result = self.call_order_tm(left, right, TmEvent::Le, false)?;
if result != -1 {
Ok(result)
} else {
let fallback = self.call_order_tm(right, left, TmEvent::Lt, false)?;
if fallback == -1 {
return self
.order_error(left, right, TmEvent::Le)
.map_err(Into::into);
}
Ok(i32::from(fallback == 0))
}
}
}
}
unsafe fn equal_value(&self, left: TValue, right: TValue) -> VmResult<i32> {
debug_assert_eq!(left.tt(), right.tt());
unsafe {
let tm = match left.tt() {
x if x == LUA_TNIL => return Ok(1),
x if x == LUA_TNUMBER => {
return Ok(i32::from(crate::number::num_eq(
left.number_value(),
right.number_value(),
)));
}
x if x == LUA_TINTEGER => {
return Ok(i32::from(crate::number::int_eq(
left.integer_value(),
right.integer_value(),
)));
}
x if x == LUA_TVECTOR => {
return Ok(i32::from(crate::number::vec_eq(
&left.vector_value(),
&right.vector_value(),
)));
}
x if x == LUA_TBOOLEAN => {
return Ok(i32::from(left.boolean_value() == right.boolean_value()));
}
x if x == LUA_TLIGHTUSERDATA => {
return Ok(i32::from(
left.pointer_value() == right.pointer_value()
&& left.light_userdata_tag() == right.light_userdata_tag(),
));
}
x if x == LUA_TUSERDATA => {
let left_userdata = left.userdata_value();
let right_userdata = right.userdata_value();
let tm = get_comp_tm(
self,
left_userdata.metatable(),
right_userdata.metatable(),
TmEvent::Eq,
);
if tm.is_none() {
return Ok(i32::from(left_userdata == right_userdata));
}
tm.unwrap_unchecked()
}
x if x == LUA_TCLASS => {
return Ok(i32::from(left.class_value() == right.class_value()));
}
x if x == LUA_TOBJECT => {
let left_object = left.object_value();
let right_object = right.object_value();
if left_object.class() != right_object.class() {
return Ok(0);
}
let Some(tm) = self.get_tm_by_obj(left, TmEvent::Eq) else {
return Ok(i32::from(left_object == right_object));
};
tm
}
x if x == LUA_TTABLE => {
let left_table = left.table_value();
let right_table = right.table_value();
let tm = get_comp_tm(
self,
left_table.metatable(),
right_table.metatable(),
TmEvent::Eq,
);
if tm.is_none() {
return Ok(i32::from(left_table == right_table));
}
tm.unwrap_unchecked()
}
_ => return Ok(i32::from(left.gc_value() == right.gc_value())),
};
let result = self.call_tm_result(self.stack_top(), tm, left, right)?;
Ok(i32::from(!result.value_unchecked().is_false()))
}
}
unsafe fn do_arith_impl(
&self,
result: TValueCursor,
left: TValue,
right: TValue,
op: TmEvent,
) -> VmResult {
let mut left_number_storage = RAW_TVALUE_NIL;
let mut right_number_storage = RAW_TVALUE_NIL;
unsafe {
let (left_number, right_number) = (
TValue::from_mut(&mut left_number_storage),
TValue::from_mut(&mut right_number_storage),
);
let left_vector = left.is_vector().then(|| left.vector_value());
let right_vector = right.is_vector().then(|| right.vector_value());
if let (Some(left_vector), Some(right_vector)) = (left_vector, right_vector) {
match op {
TmEvent::Add | TmEvent::Sub | TmEvent::Mul | TmEvent::Div | TmEvent::IDiv => {
let mut value = [0.0; LUA_VECTOR_SIZE];
for index in 0..LUA_VECTOR_SIZE {
value[index] = match op {
TmEvent::Add => left_vector[index] + right_vector[index],
TmEvent::Sub => left_vector[index] - right_vector[index],
TmEvent::Mul => left_vector[index] * right_vector[index],
TmEvent::Div => left_vector[index] / right_vector[index],
TmEvent::IDiv => crate::number::num_idiv(
left_vector[index] as f64,
right_vector[index] as f64,
) as f32,
_ => unreachable!(),
};
}
set_vector_result(result, value);
return Ok(());
}
TmEvent::Unm => {
let mut value = [0.0; LUA_VECTOR_SIZE];
for index in 0..LUA_VECTOR_SIZE {
value[index] = -left_vector[index];
}
set_vector_result(result, value);
return Ok(());
}
_ => {}
}
} else if let Some(left_vector) = left_vector {
if let Some(right_number) = self
.to_number_internal(right, right_number)
.map(|n| n.number_value() as f32)
{
match op {
TmEvent::Mul | TmEvent::Div | TmEvent::IDiv => {
let mut value = [0.0; LUA_VECTOR_SIZE];
for index in 0..LUA_VECTOR_SIZE {
value[index] = match op {
TmEvent::Mul => left_vector[index] * right_number,
TmEvent::Div => left_vector[index] / right_number,
TmEvent::IDiv => crate::number::num_idiv(
left_vector[index] as f64,
right_number as f64,
) as f32,
_ => unreachable!(),
};
}
set_vector_result(result, value);
return Ok(());
}
_ => {}
}
}
} else if let Some(right_vector) = right_vector
&& let Some(left_number) = self
.to_number_internal(left, left_number)
.map(|n| n.number_value() as f32)
{
match op {
TmEvent::Mul | TmEvent::Div | TmEvent::IDiv => {
let mut value = [0.0; LUA_VECTOR_SIZE];
for index in 0..LUA_VECTOR_SIZE {
value[index] = match op {
TmEvent::Mul => left_number * right_vector[index],
TmEvent::Div => left_number / right_vector[index],
TmEvent::IDiv => crate::number::num_idiv(
left_number as f64,
right_vector[index] as f64,
) as f32,
_ => unreachable!(),
};
}
set_vector_result(result, value);
return Ok(());
}
_ => {}
}
}
if let (Some(left_number), Some(right_number)) = (
self.to_number_internal(left, left_number)
.map(|n| n.number_value()),
self.to_number_internal(right, right_number)
.map(|n| n.number_value()),
) {
result.value_unchecked().set_number(match op {
TmEvent::Add => crate::number::num_add(left_number, right_number),
TmEvent::Sub => crate::number::num_sub(left_number, right_number),
TmEvent::Mul => crate::number::num_mul(left_number, right_number),
TmEvent::Div => crate::number::num_div(left_number, right_number),
TmEvent::IDiv => crate::number::num_idiv(left_number, right_number),
TmEvent::Mod => crate::number::num_mod(left_number, right_number),
TmEvent::Pow => crate::number::num_pow(left_number, right_number),
TmEvent::Unm => crate::number::num_unm(left_number),
_ => unreachable!("invalid arithmetic metamethod {:?}", op),
});
return Ok(());
}
if !self.call_bin_tm(left, right, result, op)? {
return self.arith_error(left, right, op).map_err(Into::into);
}
}
Ok(())
}
unsafe fn do_len(&self, result: TValueCursor, value: TValue) -> VmResult {
unsafe {
let tm = match value.tt() {
x if x == LUA_TTABLE => {
let table = value.table_value();
match self.fast_tm(table.metatable(), TmEvent::Len) {
Some(tm) => tm,
None => {
result.value_unchecked().set_number(table.getn() as f64);
return Ok(());
}
}
}
x if x == LUA_TSTRING => {
result.value_unchecked().set_number(
value
.string_value()
.as_ptr()
.as_ref()
.unwrap_unchecked()
.len as f64,
);
return Ok(());
}
_ => match self.get_tm_by_obj(value, TmEvent::Len) {
Some(tm) => tm,
None => return self.type_error(value, "get length of").map_err(Into::into),
},
};
let result = self.call_tm_result(result, tm, value, nil_object())?;
if !result.value_unchecked().is_number() {
return crate::run_error!(self, "'__len' must return a number").map_err(Into::into);
}
}
Ok(())
}
unsafe fn get_table_internal(
&self,
table: TValue,
key: TValue,
value: TValueCursor,
) -> VmResult {
let mut table = table;
unsafe {
for _ in 0..MAX_TAG_LOOP {
let tm;
if table.is_table() {
let hash = table.table_value();
let result = hash.get(key);
if result != nil_object() {
self.set_cached_slot(hash.value_slot_unchecked(result));
}
if !result.is_nil() || {
tm = self.fast_tm(hash.metatable(), TmEvent::Index);
tm.is_none()
} {
value.value_unchecked().set_obj(result);
return Ok(());
}
} else if flags::DebugLuauUserDefinedClassesRuntime.get() && table.is_object() {
let instance = table.object_value();
let Some(member) = instance.lookup_member(key) else {
return self.missing_member_error(table, key).map_err(Into::into);
};
value.value_unchecked().set_obj(member);
return Ok(());
} else if flags::DebugLuauUserDefinedClassesRuntime.get() && table.is_class() {
let class = table.class_value();
let Some(static_member) = class.lookup_static_member(key) else {
return self.missing_member_error(table, key).map_err(Into::into);
};
value.value_unchecked().set_obj(static_member);
return Ok(());
} else {
let Some(candidate) = self.get_tm_by_obj(table, TmEvent::Index) else {
return self.index_error(table, key).map_err(Into::into);
};
tm = Some(candidate);
}
let tm = tm.unwrap_unchecked();
if tm.is_function() {
self.call_tm_result(value, tm, table, key)?;
return Ok(());
}
table = tm;
}
crate::run_error!(self, "'__index' chain too long; possible loop").map_err(Into::into)
}
}
unsafe fn set_table_internal(&self, table: TValue, key: TValue, value: TValue) -> VmResult {
let mut temp_storage = RAW_TVALUE_NIL;
let temp = unsafe { TValue::from_mut(&mut temp_storage) };
let mut table = table;
unsafe {
for _ in 0..MAX_TAG_LOOP {
let tm;
if table.is_table() {
let hash = table.table_value();
let old_value = hash.get(key);
if !old_value.is_nil() || {
tm = self.fast_tm(hash.metatable(), TmEvent::NewIndex);
tm.is_none()
} {
if hash.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
return self.readonly_error().map_err(Into::into);
}
let new_value = self.set_slot(hash, old_value, key)?;
self.set_cached_slot(hash.value_slot_unchecked(new_value));
new_value.set_obj(value);
if value.is_collectable() {
let table_object: GcObject = hash.into();
let child = value.gc_value();
if table_object.is_black() && child.is_white() {
self.barrier_table(hash, child);
}
}
return Ok(());
}
} else if flags::DebugLuauUserDefinedClassesRuntime.get() && table.is_object() {
let instance = table.object_value();
let class = instance.class();
let Some(offset) = class.member_offset(key) else {
return self.missing_member_error(table, key).map_err(Into::into);
};
debug_assert!(instance.offset_in_bounds(offset));
if offset
>= class
.as_ptr()
.as_ref()
.unwrap_unchecked()
.number_of_instance_members
{
return self.index_error(table, key).map_err(Into::into);
}
let member = instance.lookup_member_at_offset(offset);
member.set_obj(value);
if value.is_collectable() {
let object: GcObject = instance.into();
let child = value.gc_value();
if object.is_black() && child.is_white() {
self.barrier_forward(object, child);
}
}
return Ok(());
} else {
let Some(candidate) = self.get_tm_by_obj(table, TmEvent::NewIndex) else {
return self.index_error(table, key).map_err(Into::into);
};
tm = Some(candidate);
}
let tm = tm.unwrap_unchecked();
if tm.is_function() {
self.call_tm3(tm, table, key, value)?;
return Ok(());
}
temp.set_obj(tm);
table = temp;
}
crate::run_error!(self, "'__newindex' chain too long; possible loop")
.map_err(Into::into)
}
}
unsafe fn concat_internal(&self, mut total: i32, mut last: i32) -> VmResult {
unsafe {
while total > 1 {
let top = self.stack_base().add(last as usize + 1);
let mut count = 2;
if !(top.sub(2).value_unchecked().is_string()
|| top.sub(2).value_unchecked().is_number())
|| !self.coerce_string_slot(top.sub(1).value_unchecked())?
{
if !self.call_bin_tm(
top.sub(2).value_unchecked(),
top.sub(1).value_unchecked(),
top.sub(2),
TmEvent::Concat,
)? {
return self
.concat_error(
top.sub(2).value_unchecked(),
top.sub(1).value_unchecked(),
)
.map_err(Into::into);
}
} else if top
.sub(1)
.value_unchecked()
.string_value()
.as_ptr()
.as_ref()
.unwrap_unchecked()
.len
== 0
{
let _ = self.coerce_string_slot(top.sub(2).value_unchecked())?;
} else {
let mut total_len = top
.sub(1)
.value_unchecked()
.string_value()
.as_ptr()
.as_ref()
.unwrap_unchecked()
.len as usize;
count = 1;
while count < total {
let slot = top.sub(count as usize + 1);
if !self.coerce_string_slot(slot.value_unchecked())? {
break;
}
let len = slot
.value_unchecked()
.string_value()
.as_ptr()
.as_ref()
.unwrap_unchecked()
.len as usize;
if len > MAX_STRING_SIZE - total_len {
return crate::run_error!(self, "string length overflow")
.map_err(Into::into);
}
total_len += len;
count += 1;
}
let mut local_buf = [0u8; crate::thread::LUA_BUFFER_SIZE];
let (buffer, heap_string) = if total_len < crate::thread::LUA_BUFFER_SIZE {
(local_buf.as_mut_ptr(), None)
} else {
let string = self.buffer_start(total_len)?;
(string.data_mut_ptr(), Some(string))
};
let mut write = 0usize;
for index in (1..=count).rev() {
let string = top.sub(index as usize).value_unchecked().string_value();
let bytes = string.as_bytes();
ptr::copy_nonoverlapping(bytes.as_ptr(), buffer.add(write), bytes.len());
write += bytes.len();
}
let dest = top.sub(count as usize);
let result = if let Some(string) = heap_string {
self.buffer_finish(string)?
} else {
self.intern_string(
core::slice::from_raw_parts(buffer, total_len).as_bstr(),
)?
};
dest.value_unchecked().set_string_value(result);
}
total -= count - 1;
last -= count - 1;
}
}
Ok(())
}
unsafe fn get_import(
&self,
env: Table,
constants: TValueCursor,
target: TValueCursor,
id: u32,
propagate_nil: bool,
) -> VmResult {
let count = (id >> 30) as i32;
debug_assert!(count > 0);
let id0 = ((id >> 20) & 1023) as usize;
let id1 = ((id >> 10) & 1023) as usize;
let id2 = (id & 1023) as usize;
let mut global_storage = RAW_TVALUE_NIL;
unsafe {
let result_offset = self.save_stack(target);
let global = TValue::from_mut(&mut global_storage);
global.set_table_value(env);
self.get_table_internal(global, constants.add(id0).value_unchecked(), target)?;
if count < 2 {
return Ok(());
}
let target = self.restore_stack(result_offset);
if !propagate_nil || !target.value_unchecked().is_nil() {
self.get_table_internal(
target.value_unchecked(),
constants.add(id1).value_unchecked(),
target,
)?;
}
if count < 3 {
return Ok(());
}
let target = self.restore_stack(result_offset);
if !propagate_nil || !target.value_unchecked().is_nil() {
self.get_table_internal(
target.value_unchecked(),
constants.add(id2).value_unchecked(),
target,
)?;
}
}
Ok(())
}
}