diff --git a/mlua-sys/src/lua52/lua.rs b/mlua-sys/src/lua52/lua.rs index e5239cee..8de7cdee 100644 --- a/mlua-sys/src/lua52/lua.rs +++ b/mlua-sys/src/lua52/lua.rs @@ -272,6 +272,11 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int { lua_yieldk(L, n, 0, None) } +#[inline(always)] +pub unsafe fn lua_yieldc(L: *mut lua_State, n: c_int, k: lua_CFunction) -> c_int { + lua_yieldk(L, n, 0, Some(k)) +} + // // Garbage-collection function and options // diff --git a/mlua-sys/src/lua53/lua.rs b/mlua-sys/src/lua53/lua.rs index 2729fdcd..c3d82e63 100644 --- a/mlua-sys/src/lua53/lua.rs +++ b/mlua-sys/src/lua53/lua.rs @@ -286,6 +286,11 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int { lua_yieldk(L, n, 0, None) } +#[inline(always)] +pub unsafe fn lua_yieldc(L: *mut lua_State, n: c_int, k: lua_KFunction) -> c_int { + lua_yieldk(L, n, 0, Some(k)) +} + // // Garbage-collection function and options // diff --git a/mlua-sys/src/lua54/lua.rs b/mlua-sys/src/lua54/lua.rs index 15a30444..c74e1576 100644 --- a/mlua-sys/src/lua54/lua.rs +++ b/mlua-sys/src/lua54/lua.rs @@ -299,6 +299,11 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int { lua_yieldk(L, n, 0, None) } +#[inline(always)] +pub unsafe fn lua_yieldc(L: *mut lua_State, n: c_int, k: lua_KFunction) -> c_int { + lua_yieldk(L, n, 0, Some(k)) +} + // // Warning-related functions // diff --git a/mlua-sys/src/luau/lua.rs b/mlua-sys/src/luau/lua.rs index 8a55eef1..d898534c 100644 --- a/mlua-sys/src/luau/lua.rs +++ b/mlua-sys/src/luau/lua.rs @@ -426,6 +426,11 @@ pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) lua_pushcclosurek(L, f, ptr::null(), nup, None) } +#[inline(always)] +pub unsafe fn lua_pushcclosurec(L: *mut lua_State, f: lua_CFunction, cont: lua_Continuation, nup: c_int) { + lua_pushcclosurek(L, f, ptr::null(), nup, Some(cont)) +} + #[inline(always)] pub unsafe fn lua_pushcclosured(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char, nup: c_int) { lua_pushcclosurek(L, f, debugname, nup, None) diff --git a/src/buffer.rs b/src/buffer.rs index 181e3696..4989acef 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -61,7 +61,7 @@ impl Buffer { unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { let lua = self.0.lua.lock(); let mut size = 0usize; - let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); + let buf = ffi::lua_tobuffer(lua.ref_thread(self.0.aux_thread), self.0.index, &mut size); mlua_assert!(!buf.is_null(), "invalid Luau buffer"); (buf as *mut u8, size) } diff --git a/src/chunk.rs b/src/chunk.rs index 3de61648..6f1e37f3 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -629,7 +629,7 @@ impl Chunk<'_> { /// Fetches compiled bytecode of this chunk from the cache. /// /// If not found, compiles the source code and stores it on the cache. - pub(crate) fn try_cache(mut self) -> Self { + pub fn try_cache(mut self) -> Self { struct ChunksCache(HashMap, Vec>); // Try to fetch compiled chunk from cache diff --git a/src/conversion.rs b/src/conversion.rs index b7dfa305..f50be658 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -12,6 +12,7 @@ use num_traits::cast; use crate::error::{Error, Result}; use crate::function::Function; +use crate::state::util::get_next_spot; use crate::state::{Lua, RawLua}; use crate::string::{BorrowedBytes, BorrowedStr, String}; use crate::table::Table; @@ -35,8 +36,8 @@ impl IntoLua for &Value { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_value(self) + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_value_at(self, state) } } @@ -61,8 +62,8 @@ impl IntoLua for &String { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.0, state); Ok(()) } } @@ -79,15 +80,18 @@ impl FromLua for String { }) } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let state = lua.state(); + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { let type_id = ffi::lua_type(state, idx); if type_id == ffi::LUA_TSTRING { - ffi::lua_xpush(state, lua.ref_thread(), idx); - return Ok(String(lua.pop_ref_thread())); + let (aux_thread, idxs, replace) = get_next_spot(lua.extra()); + ffi::lua_xpush(state, lua.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(lua.ref_thread(aux_thread), idxs); + } + return Ok(String(lua.new_value_ref(aux_thread, idxs))); } // Fallback to default - Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + Self::from_lua(lua.stack_value_at(idx, Some(type_id), state), lua.lua()) } } @@ -98,8 +102,8 @@ impl IntoLua for BorrowedStr<'_> { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.borrow.0, state); Ok(()) } } @@ -111,8 +115,8 @@ impl IntoLua for &BorrowedStr<'_> { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.borrow.0, state); Ok(()) } } @@ -126,8 +130,8 @@ impl FromLua for BorrowedStr<'_> { Ok(Self { buf, borrow, _lua }) } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let s = String::from_stack(idx, lua)?; + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + let s = String::from_specified_stack(idx, lua, state)?; let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; let borrow = Cow::Owned(s); @@ -142,8 +146,8 @@ impl IntoLua for BorrowedBytes<'_> { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.borrow.0, state); Ok(()) } } @@ -155,8 +159,8 @@ impl IntoLua for &BorrowedBytes<'_> { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.borrow.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.borrow.0, state); Ok(()) } } @@ -170,8 +174,8 @@ impl FromLua for BorrowedBytes<'_> { Ok(Self { buf, borrow, _lua }) } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let s = String::from_stack(idx, lua)?; + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + let s = String::from_specified_stack(idx, lua, state)?; let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; let borrow = Cow::Owned(s); @@ -193,8 +197,8 @@ impl IntoLua for &Table { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.0, state); Ok(()) } } @@ -227,8 +231,8 @@ impl IntoLua for &Function { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.0, state); Ok(()) } } @@ -261,8 +265,8 @@ impl IntoLua for &Thread { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.0, state); Ok(()) } } @@ -295,8 +299,8 @@ impl IntoLua for &AnyUserData { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.0, state); Ok(()) } } @@ -354,8 +358,8 @@ impl IntoLua for RegistryKey { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - <&RegistryKey>::push_into_stack(&self, lua) + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + <&RegistryKey>::push_into_specified_stack(&self, lua, state) } } @@ -365,15 +369,15 @@ impl IntoLua for &RegistryKey { lua.registry_value(self) } - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { if !lua.owns_registry_value(self) { return Err(Error::MismatchedRegistryKey); } match self.id() { - ffi::LUA_REFNIL => ffi::lua_pushnil(lua.state()), + ffi::LUA_REFNIL => ffi::lua_pushnil(state), id => { - ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, id as _); + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, id as _); } } Ok(()) @@ -394,8 +398,8 @@ impl IntoLua for bool { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - ffi::lua_pushboolean(lua.state(), self as c_int); + unsafe fn push_into_specified_stack(self, _lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + ffi::lua_pushboolean(state, self as c_int); Ok(()) } } @@ -411,8 +415,8 @@ impl FromLua for bool { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - Ok(ffi::lua_toboolean(lua.state(), idx) != 0) + unsafe fn from_specified_stack(idx: c_int, _lua: &RawLua, state: *mut ffi::lua_State) -> Result { + Ok(ffi::lua_toboolean(state, idx) != 0) } } @@ -476,8 +480,8 @@ impl IntoLua for &crate::Buffer { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_ref(&self.0); + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_ref_at(&self.0, state); Ok(()) } } @@ -504,8 +508,8 @@ impl IntoLua for StdString { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - push_bytes_into_stack(self, lua) + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + push_bytes_into_stack(self, lua, state) } } @@ -525,8 +529,7 @@ impl FromLua for StdString { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let state = lua.state(); + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { let type_id = ffi::lua_type(state, idx); if type_id == ffi::LUA_TSTRING { let mut size = 0; @@ -541,7 +544,7 @@ impl FromLua for StdString { }); } // Fallback to default - Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + Self::from_lua(lua.stack_value_at(idx, Some(type_id), state), lua.lua()) } } @@ -552,8 +555,8 @@ impl IntoLua for &str { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - push_bytes_into_stack(self, lua) + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + push_bytes_into_stack(self, lua, state) } } @@ -658,8 +661,7 @@ impl FromLua for BString { } } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let state = lua.state(); + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { match ffi::lua_type(state, idx) { ffi::LUA_TSTRING => { let mut size = 0; @@ -675,7 +677,7 @@ impl FromLua for BString { } type_id => { // Fallback to default - Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + Self::from_lua(lua.stack_value_at(idx, Some(type_id), state), lua.lua()) } } } @@ -789,18 +791,18 @@ impl FromLua for char { } #[inline] -unsafe fn push_bytes_into_stack(this: T, lua: &RawLua) -> Result<()> +unsafe fn push_bytes_into_stack(this: T, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> where T: IntoLua + AsRef<[u8]>, { let bytes = this.as_ref(); if lua.unlikely_memory_error() && bytes.len() < (1 << 30) { // Fast path: push directly into the Lua stack. - ffi::lua_pushlstring(lua.state(), bytes.as_ptr() as *const _, bytes.len()); + ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len()); return Ok(()); } // Fallback to default - lua.push_value(&T::into_lua(this, lua.lua())?) + lua.push_value_at(&T::into_lua(this, lua.lua())?, state) } macro_rules! lua_convert_int { @@ -814,10 +816,14 @@ macro_rules! lua_convert_int { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + unsafe fn push_into_specified_stack( + self, + _lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result<()> { match cast(self) { - Some(i) => ffi::lua_pushinteger(lua.state(), i), - None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number), + Some(i) => ffi::lua_pushinteger(state, i), + None => ffi::lua_pushnumber(state, self as ffi::lua_Number), } Ok(()) } @@ -854,8 +860,11 @@ macro_rules! lua_convert_int { }) } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let state = lua.state(); + unsafe fn from_specified_stack( + idx: c_int, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { let type_id = ffi::lua_type(state, idx); if type_id == ffi::LUA_TNUMBER { let mut ok = 0; @@ -869,7 +878,7 @@ macro_rules! lua_convert_int { } } // Fallback to default - Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + Self::from_lua(lua.stack_value_at(idx, Some(type_id), state), lua.lua()) } } }; @@ -910,14 +919,17 @@ macro_rules! lua_convert_float { }) } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - let state = lua.state(); + unsafe fn from_specified_stack( + idx: c_int, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { let type_id = ffi::lua_type(state, idx); if type_id == ffi::LUA_TNUMBER { return Ok(ffi::lua_tonumber(state, idx) as _); } // Fallback to default - Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) + Self::from_lua(lua.stack_value_at(idx, Some(type_id), state), lua.lua()) } } }; @@ -1120,10 +1132,10 @@ impl IntoLua for Option { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { match self { - Some(val) => val.push_into_stack(lua)?, - None => ffi::lua_pushnil(lua.state()), + Some(val) => val.push_into_specified_stack(lua, state)?, + None => ffi::lua_pushnil(state), } Ok(()) } @@ -1139,10 +1151,10 @@ impl FromLua for Option { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - match ffi::lua_type(lua.state(), idx) { + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + match ffi::lua_type(state, idx) { ffi::LUA_TNIL => Ok(None), - _ => Ok(Some(T::from_stack(idx, lua)?)), + _ => Ok(Some(T::from_specified_stack(idx, lua, state)?)), } } } @@ -1157,10 +1169,10 @@ impl IntoLua for Either { } #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { match self { - Either::Left(l) => l.push_into_stack(lua), - Either::Right(r) => r.push_into_stack(lua), + Either::Left(l) => l.push_into_specified_stack(lua, state), + Either::Right(r) => r.push_into_specified_stack(lua, state), } } } @@ -1185,13 +1197,13 @@ impl FromLua for Either { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - match L::from_stack(idx, lua) { + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + match L::from_specified_stack(idx, lua, state) { Ok(l) => Ok(Either::Left(l)), - Err(_) => match R::from_stack(idx, lua).map(Either::Right) { + Err(_) => match R::from_specified_stack(idx, lua, state).map(Either::Right) { Ok(r) => Ok(r), Err(_) => { - let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx)); + let value_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx)); Err(Error::FromLuaConversionError { from: value_type_name.to_str().unwrap(), to: Self::type_name(), diff --git a/src/error.rs b/src/error.rs index c20a39ef..2d369109 100644 --- a/src/error.rs +++ b/src/error.rs @@ -321,7 +321,7 @@ impl fmt::Display for Error { Error::WithContext { context, cause } => { writeln!(fmt, "{context}")?; write!(fmt, "{cause}") - } + }, } } } diff --git a/src/function.rs b/src/function.rs index 9a5b291b..50b49ec2 100644 --- a/src/function.rs +++ b/src/function.rs @@ -3,6 +3,8 @@ use std::os::raw::{c_int, c_void}; use std::{mem, ptr, slice}; use crate::error::{Error, Result}; +#[cfg(feature = "luau")] +use crate::state::util::get_next_spot; use crate::state::Lua; use crate::table::Table; use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut}; @@ -112,11 +114,11 @@ impl Function { check_stack(state, 2)?; // Push error handler - lua.push_error_traceback(); + lua.push_error_traceback_at(state); let stack_start = ffi::lua_gettop(state); // Push function and the arguments - lua.push_ref(&self.0); - let nargs = args.push_into_stack_multi(&lua)?; + lua.push_ref_at(&self.0, state); + let nargs = args.push_into_specified_stack_multi(&lua, state)?; // Call the function let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start); if ret != ffi::LUA_OK { @@ -124,7 +126,7 @@ impl Function { } // Get the results let nresults = ffi::lua_gettop(state) - stack_start; - R::from_stack_multi(nresults, &lua) + R::from_specified_stack_multi(nresults, &lua, state) } } @@ -234,7 +236,7 @@ impl Function { ffi::lua_pushinteger(state, nargs as ffi::lua_Integer); for arg in &args { - lua.push_value(arg)?; + lua.push_value_at(arg, state)?; } protect_lua!(state, nargs + 1, 1, fn(state) { ffi::lua_pushcclosure(state, args_wrapper_impl, ffi::lua_gettop(state)); @@ -269,7 +271,7 @@ impl Function { let _sg = StackGuard::new(state); assert_stack(state, 1); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); if ffi::lua_iscfunction(state, -1) != 0 { return None; } @@ -306,14 +308,14 @@ impl Function { let _sg = StackGuard::new(state); check_stack(state, 2)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); if ffi::lua_iscfunction(state, -1) != 0 { return Ok(false); } #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] { - lua.push_ref(&env.0); + lua.push_ref_at(&env.0, state); ffi::lua_setfenv(state, -2); } #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] @@ -329,7 +331,7 @@ impl Function { .set_environment(env) .try_cache() .into_function()?; - lua.push_ref(&f_with_env.0); + lua.push_ref_at(&f_with_env.0, state); ffi::lua_upvaluejoin(state, -2, i, -1, 1); break; } @@ -354,7 +356,7 @@ impl Function { assert_stack(state, 1); let mut ar: ffi::lua_Debug = mem::zeroed(); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); #[cfg(not(feature = "luau"))] let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar); #[cfg(feature = "luau")] @@ -415,7 +417,7 @@ impl Function { let _sg = StackGuard::new(state); assert_stack(state, 1); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); let data_ptr = &mut data as *mut Vec as *mut c_void; ffi::lua_dump(state, writer, data_ptr, strip as i32); ffi::lua_pop(state, 1); @@ -469,7 +471,7 @@ impl Function { let _sg = StackGuard::new(state); assert_stack(state, 1); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); let func_ptr = &mut func as *mut F as *mut c_void; ffi::lua_getcoverage(state, -1, func_ptr, callback::); } @@ -494,14 +496,22 @@ impl Function { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn deep_clone(&self) -> Self { let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); + let ref_thread = lua.ref_thread(self.0.aux_thread); unsafe { if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 { return self.clone(); } ffi::lua_clonefunction(ref_thread, self.0.index); - Function(lua.pop_ref_thread()) + + // Get the real next spot + let (aux_thread, index, replace) = get_next_spot(lua.extra()); + ffi::lua_xpush(lua.ref_thread(self.0.aux_thread), lua.ref_thread(aux_thread), -1); + if replace { + ffi::lua_replace(lua.ref_thread(aux_thread), index); + } + + Function(lua.new_value_ref(aux_thread, index)) } } } @@ -522,8 +532,9 @@ impl Function { R: IntoLuaMulti, { WrappedFunction(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, None, lua)?; - func.call(args)?.push_into_stack_multi(lua) + let state = lua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, lua, state)?; + func.call(args)?.push_into_specified_stack_multi(lua, state) })) } @@ -537,8 +548,9 @@ impl Function { let func = RefCell::new(func); WrappedFunction(Box::new(move |lua, nargs| unsafe { let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; - let args = A::from_stack_args(nargs, 1, None, lua)?; - func.call(args)?.push_into_stack_multi(lua) + let state = lua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, lua, state)?; + func.call(args)?.push_into_specified_stack_multi(lua, state) })) } @@ -554,8 +566,9 @@ impl Function { A: FromLuaMulti, { WrappedFunction(Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, None, lua)?; - func.call(args).push_into_stack_multi(lua) + let state = lua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, lua, state)?; + func.call(args).push_into_specified_stack_multi(lua, state) })) } @@ -572,8 +585,9 @@ impl Function { let func = RefCell::new(func); WrappedFunction(Box::new(move |lua, nargs| unsafe { let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; - let args = A::from_stack_args(nargs, 1, None, lua)?; - func.call(args).push_into_stack_multi(lua) + let state = lua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, lua, state)?; + func.call(args).push_into_specified_stack_multi(lua, state) })) } @@ -588,13 +602,17 @@ impl Function { R: IntoLuaMulti, { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { - let args = match A::from_stack_args(nargs, 1, None, rawlua) { + let state = rawlua.state(); + let args = match A::from_specified_stack_args(nargs, 1, None, rawlua, state) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let lua = rawlua.lua(); let fut = func.call(args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { + fut.await? + .push_into_specified_stack_multi(lua.raw_lua(), lua.raw_lua().state()) + }) })) } @@ -611,13 +629,16 @@ impl Function { A: FromLuaMulti, { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { - let args = match A::from_stack_args(nargs, 1, None, rawlua) { + let args = match A::from_specified_stack_args(nargs, 1, None, rawlua, rawlua.state()) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let lua = rawlua.lua(); let fut = func.call(args); - Box::pin(async move { fut.await.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { + fut.await + .push_into_specified_stack_multi(lua.raw_lua(), lua.raw_lua().state()) + }) })) } } diff --git a/src/lib.rs b/src/lib.rs index e1589ce6..8c7668ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::table::{Table, TablePairs, TableSequence}; -pub use crate::thread::{Thread, ThreadStatus}; +pub use crate::thread::{ContinuationStatus, Thread, ThreadStatus}; pub use crate::traits::{ FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, }; diff --git a/src/luau/mod.rs b/src/luau/mod.rs index 19eb8fa5..a65da39c 100644 --- a/src/luau/mod.rs +++ b/src/luau/mod.rs @@ -81,15 +81,20 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_ unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int { callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| { let rawlua = (*extra).raw_lua(); - let (chunk, chunk_name) = - <(String, Option)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?; + let (chunk, chunk_name) = <(String, Option)>::from_specified_stack_args( + nargs, + 1, + Some("loadstring"), + rawlua, + state, + )?; let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)"); (rawlua.lua()) .load(chunk) .set_name(chunk_name) .set_mode(ChunkMode::Text) .into_function()? - .push_into_stack(rawlua)?; + .push_into_specified_stack(rawlua, state)?; Ok(1) }) } diff --git a/src/luau/require.rs b/src/luau/require.rs index ebb7468f..d091149d 100644 --- a/src/luau/require.rs +++ b/src/luau/require.rs @@ -452,7 +452,7 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_ callback_error_ext(state, ptr::null_mut(), true, move |extra, _| { let rawlua = (*extra).raw_lua(); let loader = this.loader(rawlua.lua())?; - rawlua.push(loader)?; + rawlua.push_at(state, loader)?; Ok(1) }) } diff --git a/src/multi.rs b/src/multi.rs index c171962c..9b4aff13 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -23,10 +23,14 @@ impl IntoLuaMulti for StdResult { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + unsafe fn push_into_specified_stack_multi( + self, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { match self { - Ok(val) => (val,).push_into_stack_multi(lua), - Err(err) => (Nil, err).push_into_stack_multi(lua), + Ok(val) => (val,).push_into_specified_stack_multi(lua, state), + Err(err) => (Nil, err).push_into_specified_stack_multi(lua, state), } } } @@ -41,10 +45,14 @@ impl IntoLuaMulti for StdResult<(), E> { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + unsafe fn push_into_specified_stack_multi( + self, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { match self { Ok(_) => Ok(0), - Err(err) => (Nil, err).push_into_stack_multi(lua), + Err(err) => (Nil, err).push_into_specified_stack_multi(lua, state), } } } @@ -58,8 +66,12 @@ impl IntoLuaMulti for T { } #[inline] - unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { - self.push_into_stack(lua)?; + unsafe fn push_into_specified_stack_multi( + self, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { + self.push_into_specified_stack(lua, state)?; Ok(1) } } @@ -76,19 +88,29 @@ impl FromLuaMulti for T { } #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { + unsafe fn from_specified_stack_multi( + nvals: c_int, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { if nvals == 0 { return T::from_lua(Nil, lua.lua()); } - T::from_stack(-nvals, lua) + T::from_specified_stack(-nvals, lua, state) } #[inline] - unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { + unsafe fn from_specified_stack_args( + nargs: c_int, + i: usize, + to: Option<&str>, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { if nargs == 0 { return T::from_lua_arg(Nil, i, to, lua.lua()); } - T::from_stack_arg(-nargs, i, to, lua) + T::from_specified_stack_arg(-nargs, i, to, lua, state) } } @@ -319,7 +341,11 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn push_into_stack_multi(self, _lua: &RawLua) -> Result { + unsafe fn push_into_specified_stack_multi( + self, + _lua: &RawLua, + _state: *mut ffi::lua_State, + ) -> Result { Ok(0) } } @@ -331,7 +357,7 @@ macro_rules! impl_tuple { } #[inline] - unsafe fn from_stack_multi(_nvals: c_int, _lua: &RawLua) -> Result { + unsafe fn from_specified_stack_multi(_nvals: c_int, _lua: &RawLua, _state: *mut ffi::lua_State) -> Result { Ok(()) } } @@ -354,7 +380,7 @@ macro_rules! impl_tuple { #[allow(non_snake_case)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + unsafe fn push_into_specified_stack_multi(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result { let ($($name,)* $last,) = self; let mut nresults = 0; $( @@ -363,9 +389,9 @@ macro_rules! impl_tuple { )* check_stack(lua.state(), nresults + 1)?; $( - $name.push_into_stack(lua)?; + $name.push_into_specified_stack(lua, state)?; )* - nresults += $last.push_into_stack_multi(lua)?; + nresults += $last.push_into_specified_stack_multi(lua, state)?; Ok(nresults) } } @@ -395,32 +421,32 @@ macro_rules! impl_tuple { #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_multi(mut nvals: c_int, lua: &RawLua) -> Result { + unsafe fn from_specified_stack_multi(mut nvals: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { $( let $name = if nvals > 0 { nvals -= 1; - FromLua::from_stack(-(nvals + 1), lua) + FromLua::from_specified_stack(-(nvals + 1), lua, state) } else { FromLua::from_lua(Nil, lua.lua()) }?; )* - let $last = FromLuaMulti::from_stack_multi(nvals, lua)?; + let $last = FromLuaMulti::from_specified_stack_multi(nvals, lua, state)?; Ok(($($name,)* $last,)) } #[allow(unused_mut, non_snake_case)] #[inline] - unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &RawLua) -> Result { + unsafe fn from_specified_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &RawLua, state: *mut ffi::lua_State) -> Result { $( let $name = if nargs > 0 { nargs -= 1; - FromLua::from_stack_arg(-(nargs + 1), i, to, lua) + FromLua::from_specified_stack_arg(-(nargs + 1), i, to, lua, state) } else { FromLua::from_lua_arg(Nil, i, to, lua.lua()) }?; i += 1; )* - let $last = FromLuaMulti::from_stack_args(nargs, i, to, lua)?; + let $last = FromLuaMulti::from_specified_stack_args(nargs, i, to, lua, state)?; Ok(($($name,)* $last,)) } } diff --git a/src/prelude.rs b/src/prelude.rs index a3a03201..c3b5d443 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,15 +3,15 @@ #[doc(no_inline)] pub use crate::{ AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, - Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, - ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, - Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, - IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, - MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, - ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, - String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, - Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, - UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, + Chunk as LuaChunk, ContinuationStatus as LuaContinuationStatus, Either as LuaEither, Error as LuaError, + ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, + FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, + Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, + LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, + Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, + StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, + TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, + UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, diff --git a/src/scope.rs b/src/scope.rs index c56647a4..e837393f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -53,8 +53,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { { unsafe { self.create_callback(Box::new(move |rawlua, nargs| { - let args = A::from_stack_args(nargs, 1, None, rawlua)?; - func(rawlua.lua(), args)?.push_into_stack_multi(rawlua) + let state = rawlua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, rawlua, state)?; + func(rawlua.lua(), args)?.push_into_specified_stack_multi(rawlua, state) })) } } @@ -174,7 +175,8 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { // Push the metatable and register it with no TypeId let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _); T::register(&mut registry); - self.lua.push_userdata_metatable(registry.into_raw())?; + self.lua + .push_userdata_metatable_at(registry.into_raw(), self.lua.state())?; let mt_ptr = ffi::lua_topointer(state, -1); self.lua.register_userdata_metatable(mt_ptr, None); @@ -222,7 +224,8 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { // Push the metatable and register it with no TypeId let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _); register(&mut registry); - self.lua.push_userdata_metatable(registry.into_raw())?; + self.lua + .push_userdata_metatable_at(registry.into_raw(), self.lua.state())?; let mt_ptr = ffi::lua_topointer(state, -1); self.lua.register_userdata_metatable(mt_ptr, None); @@ -267,7 +270,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { let f = self.lua.create_callback(f)?; let destructor: DestructorCallback = Box::new(|rawlua, vref| { - let ref_thread = rawlua.ref_thread(); + let ref_thread = rawlua.ref_thread(vref.aux_thread); ffi::lua_getupvalue(ref_thread, vref.index, 1); let upvalue = get_userdata::(ref_thread, -1); let data = (*upvalue).data.take(); @@ -287,13 +290,13 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> { Ok(Some(_)) => {} Ok(None) => { // Deregister metatable - let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index); + let mt_ptr = get_metatable_ptr(rawlua.ref_thread(vref.aux_thread), vref.index); rawlua.deregister_userdata_metatable(mt_ptr); } Err(_) => return vec![], } - let data = take_userdata::>(rawlua.ref_thread(), vref.index); + let data = take_userdata::>(rawlua.ref_thread(vref.aux_thread), vref.index); vec![Box::new(move || drop(data))] }); self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 1b85a763..87563ae8 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -7,6 +7,7 @@ use serde::ser::Serialize; use crate::error::Result; use crate::private::Sealed; +use crate::state::util::get_next_spot; use crate::state::Lua; use crate::table::Table; use crate::util::check_stack; @@ -183,8 +184,14 @@ impl LuaSerdeExt for Lua { fn array_metatable(&self) -> Table { let lua = self.lock(); unsafe { - push_array_metatable(lua.ref_thread()); - Table(lua.pop_ref_thread()) + let (aux_thread, index, replace) = get_next_spot(lua.extra()); + push_array_metatable(lua.state()); + ffi::lua_xmove(lua.state(), lua.ref_thread(aux_thread), 1); + if replace { + ffi::lua_replace(lua.ref_thread(aux_thread), index); + } + + Table(lua.new_value_ref(aux_thread, index)) } } diff --git a/src/state.rs b/src/state.rs index dbe4c2b2..d882b182 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,10 +14,15 @@ use crate::hook::Debug; use crate::memory::MemoryState; use crate::multi::MultiValue; use crate::scope::Scope; +use crate::state::util::get_next_spot; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; use crate::thread::Thread; + +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +use crate::thread::ContinuationStatus; + use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{ AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex, @@ -329,11 +334,11 @@ impl Lua { let state = lua.state(); let _sg = StackGuard::new(state); let stack_start = ffi::lua_gettop(state); - let nargs = args.push_into_stack_multi(&lua)?; + let nargs = args.push_into_specified_stack_multi(&lua, state)?; check_stack(state, 3)?; protect_lua_closure::<_, ()>(state, nargs, ffi::LUA_MULTRET, f)?; let nresults = ffi::lua_gettop(state) - stack_start; - R::from_stack_multi(nresults, &lua) + R::from_specified_stack_multi(nresults, &lua, state) } /// Loads the specified subset of the standard libraries into an existing Lua state. @@ -453,8 +458,8 @@ impl Lua { callback_error_ext(state, ptr::null_mut(), true, move |extra, nargs| { let rawlua = (*extra).raw_lua(); - let args = A::from_stack_args(nargs, 1, None, rawlua)?; - func(rawlua.lua(), args)?.push_into_stack(rawlua)?; + let args = A::from_specified_stack_args(nargs, 1, None, rawlua, state)?; + func(rawlua.lua(), args)?.push_into_specified_stack(rawlua, state)?; Ok(1) }) } @@ -522,7 +527,7 @@ impl Lua { ffi::luaL_sandboxthread(state); } else { // Restore original `LUA_GLOBALSINDEX` - ffi::lua_xpush(lua.ref_thread(), state, ffi::LUA_GLOBALSINDEX); + ffi::lua_xpush(lua.ref_thread_internal(), state, ffi::LUA_GLOBALSINDEX); ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX); ffi::luaL_sandbox(state, 0); } @@ -762,8 +767,12 @@ impl Lua { return; // Don't allow recursion } ffi::lua_pushthread(child); - ffi::lua_xmove(child, (*extra).ref_thread, 1); - let value = Thread((*extra).raw_lua().pop_ref_thread(), child); + let (aux_thread, index, replace) = get_next_spot(extra); + ffi::lua_xmove(child, (*extra).raw_lua().ref_thread(aux_thread), 1); + if replace { + ffi::lua_replace((*extra).raw_lua().ref_thread(aux_thread), index); + } + let value = Thread((*extra).raw_lua().new_value_ref(aux_thread, index), child); callback_error_ext(parent, extra, false, move |extra, _| { callback((*extra).lua(), value) }) @@ -1260,11 +1269,52 @@ impl Lua { R: IntoLuaMulti, { (self.lock()).create_callback(Box::new(move |rawlua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, None, rawlua)?; - func(rawlua.lua(), args)?.push_into_stack_multi(rawlua) + let state = rawlua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, rawlua, state)?; + func(rawlua.lua(), args)?.push_into_specified_stack_multi(rawlua, state) })) } + /// Same as ``create_function`` but with an added continuation function. + /// + /// The values passed to the continuation will be the yielded arguments + /// from the function for the initial continuation call. If yielding from a + /// continuation, the yielded results will be returned to the ``Thread::resume`` caller. The + /// arguments passed in the next ``Thread::resume`` call will then be the arguments passed + /// to the yielding continuation upon resumption. + /// + /// Returning a value from a continuation without setting yield + /// arguments will then be returned as the final return value of the Lua function call. + /// Values returned in a function in which there is also yielding will be ignored + #[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] + pub fn create_function_with_continuation( + &self, + func: F, + cont: FC, + ) -> Result + where + F: Fn(&Lua, A) -> Result + MaybeSend + 'static, + FC: Fn(&Lua, ContinuationStatus, AC) -> Result + MaybeSend + 'static, + A: FromLuaMulti, + AC: FromLuaMulti, + R: IntoLuaMulti, + RC: IntoLuaMulti, + { + (self.lock()).create_callback_with_continuation( + Box::new(move |rawlua, nargs| unsafe { + let state = rawlua.state(); + let args = A::from_specified_stack_args(nargs, 1, None, rawlua, state)?; + func(rawlua.lua(), args)?.push_into_specified_stack_multi(rawlua, state) + }), + Box::new(move |rawlua, nargs, status| unsafe { + let state = rawlua.state(); + let args = AC::from_specified_stack_args(nargs, 1, None, rawlua, state)?; + let status = ContinuationStatus::from_status(status); + cont(rawlua.lua(), status, args)?.push_into_specified_stack_multi(rawlua, state) + }), + ) + } + /// Wraps a Rust mutable closure, creating a callable Lua function handle to it. /// /// This is a version of [`Lua::create_function`] that accepts a `FnMut` argument. @@ -1286,8 +1336,13 @@ impl Lua { /// This function is unsafe because provides a way to execute unsafe C function. pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result { let lua = self.lock(); - ffi::lua_pushcfunction(lua.ref_thread(), func); - Ok(Function(lua.pop_ref_thread())) + let (aux_thread, idx, replace) = get_next_spot(lua.extra()); + ffi::lua_pushcfunction(lua.ref_thread(aux_thread), func); + if replace { + ffi::lua_replace(lua.ref_thread(aux_thread), idx); + } + + Ok(Function(lua.new_value_ref(aux_thread, idx))) } /// Wraps a Rust async function or closure, creating a callable Lua function handle to it. @@ -1338,13 +1393,16 @@ impl Lua { // In future we should switch to async closures when they are stable to capture `&Lua` // See https://rust-lang.github.io/rfcs/3668-async-closures.html (self.lock()).create_async_callback(Box::new(move |rawlua, nargs| unsafe { - let args = match A::from_stack_args(nargs, 1, None, rawlua) { + let args = match A::from_specified_stack_args(nargs, 1, None, rawlua, rawlua.state()) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let lua = rawlua.lua(); let fut = func(lua.clone(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { + fut.await? + .push_into_specified_stack_multi(lua.raw_lua(), lua.raw_lua().state()) + }) })) } @@ -1515,7 +1573,7 @@ impl Lua { ffi::lua_pushstring(state, b"\0" as *const u8 as *const _); } ffi::LUA_TFUNCTION => match self.load("function() end").eval::() { - Ok(func) => lua.push_ref(&func.0), + Ok(func) => lua.push_ref_at(&func.0, state), Err(_) => return, }, ffi::LUA_TTHREAD => { @@ -1528,7 +1586,7 @@ impl Lua { _ => return, } match metatable { - Some(metatable) => lua.push_ref(&metatable.0), + Some(metatable) => lua.push_ref_at(&metatable.0, state), None => ffi::lua_pushnil(state), } ffi::lua_setmetatable(state, -2); @@ -1596,7 +1654,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_value(&v)?; + lua.push_value_at(&v, state)?; let res = if lua.unlikely_memory_error() { ffi::lua_tolstring(state, -1, ptr::null_mut()) } else { @@ -1628,7 +1686,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 2)?; - lua.push_value(&v)?; + lua.push_value_at(&v, state)?; let mut isint = 0; let i = ffi::lua_tointegerx(state, -1, &mut isint); if isint == 0 { @@ -1654,7 +1712,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 2)?; - lua.push_value(&v)?; + lua.push_value_at(&v, state)?; let mut isnum = 0; let n = ffi::lua_tonumberx(state, -1, &mut isnum); if isnum == 0 { @@ -1707,7 +1765,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push(t)?; + lua.push_at(state, t)?; rawset_field(state, ffi::LUA_REGISTRYINDEX, key) } } @@ -1730,7 +1788,7 @@ impl Lua { push_string(state, key.as_bytes(), protect)?; ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX); - T::from_stack(-1, &lua) + T::from_specified_stack(-1, &lua, state) } } @@ -1757,7 +1815,7 @@ impl Lua { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push(t)?; + lua.push_at(state, t)?; let unref_list = (*lua.extra.get()).registry_unref_list.clone(); @@ -1804,7 +1862,7 @@ impl Lua { check_stack(state, 1)?; ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); - T::from_stack(-1, &lua) + T::from_specified_stack(-1, &lua, state) }, } } @@ -1859,7 +1917,7 @@ impl Lua { } (value, registry_id) => { // It must be safe to replace the value without triggering memory error - lua.push_value(&value)?; + lua.push_value_at(&value, state)?; ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer); } } @@ -2080,6 +2138,42 @@ impl Lua { pub(crate) unsafe fn raw_lua(&self) -> &RawLua { &*self.raw.data_ptr() } + + /// Set the yield arguments. Note that Lua will not yield until you return from the function + /// + /// This method is mostly useful with continuations and Rust-Rust yields + /// due to the Rust/Lua boundary + /// + /// Example: + /// + /// ```rust + /// fn test() -> mlua::Result<()> { + /// let lua = mlua::Lua::new(); + /// let always_yield = lua.create_function(|lua, ()| lua.yield_with((42, "69420".to_string(), 45.6)))?; + /// + /// let thread = lua.create_thread(always_yield)?; + /// assert_eq!( + /// thread.resume::<(i32, String, f32)>(())?, + /// (42, String::from("69420"), 45.6) + /// ); + /// + /// Ok(()) + /// } + /// ``` + pub fn yield_with(&self, args: impl IntoLuaMulti) -> Result<()> { + let raw = self.lock(); + unsafe { + raw.extra.get().as_mut().unwrap_unchecked().yielded_values = Some(args.into_lua_multi(self)?); + } + Ok(()) + } + + /// Checks if Lua is be allowed to yield. + #[cfg(not(any(feature = "lua51", feature = "lua52", feature = "luajit")))] + #[inline] + pub fn is_yieldable(&self) -> bool { + self.lock().is_yieldable() + } } impl WeakLua { diff --git a/src/state/extra.rs b/src/state/extra.rs index 5ff74a33..fa6d2d3a 100644 --- a/src/state/extra.rs +++ b/src/state/extra.rs @@ -13,11 +13,13 @@ use crate::error::Result; use crate::state::RawLua; use crate::stdlib::StdLib; use crate::types::{AppData, ReentrantMutex, XRc}; + use crate::userdata::RawUserDataRegistry; use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; #[cfg(any(feature = "luau", doc))] use crate::chunk::Compiler; +use crate::MultiValue; #[cfg(feature = "async")] use {futures_util::task::noop_waker_ref, std::ptr::NonNull, std::task::Waker}; @@ -30,6 +32,44 @@ static EXTRA_REGISTRY_KEY: u8 = 0; const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64; const REF_STACK_RESERVE: c_int = 2; +pub(crate) struct RefThread { + pub(super) ref_thread: *mut ffi::lua_State, + pub(super) stack_size: c_int, + pub(super) stack_top: c_int, + pub(super) free: Vec, +} + +impl RefThread { + #[inline(always)] + pub(crate) unsafe fn new(state: *mut ffi::lua_State) -> Self { + // Create ref stack thread and place it in the registry to prevent it + // from being garbage collected. + let ref_thread = mlua_expect!( + protect_lua!(state, 0, 0, |state| { + let thread = ffi::lua_newthread(state); + ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); + thread + }), + "Error while creating ref thread", + ); + + // Store `error_traceback` function on the ref stack + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + { + ffi::lua_pushcfunction(ref_thread, crate::util::error_traceback); + assert_eq!(ffi::lua_gettop(ref_thread), ExtraData::ERROR_TRACEBACK_IDX); + } + + RefThread { + ref_thread, + // We need some reserved stack space to move values in and out of the ref stack. + stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, + stack_top: ffi::lua_gettop(ref_thread), + free: Vec::new(), + } + } +} + /// Data associated with the Lua state. pub(crate) struct ExtraData { pub(super) lua: MaybeUninit, @@ -53,18 +93,17 @@ pub(crate) struct ExtraData { // Used in module mode pub(super) skip_memory_check: bool, - // Auxiliary thread to store references - pub(super) ref_thread: *mut ffi::lua_State, - pub(super) ref_stack_size: c_int, - pub(super) ref_stack_top: c_int, - pub(super) ref_free: Vec, + // Auxiliary threads to store references + pub(super) ref_thread: Vec, + // Special auxillary thread for mlua internal use + pub(super) ref_thread_internal: RefThread, // Pool of `WrappedFailure` enums in the ref thread (as userdata) pub(super) wrapped_failure_pool: Vec, pub(super) wrapped_failure_top: usize, // Pool of `Thread`s (coroutines) for async execution #[cfg(feature = "async")] - pub(super) thread_pool: Vec, + pub(super) thread_pool: Vec<(usize, c_int)>, // Address of `WrappedFailure` metatable pub(super) wrapped_failure_mt_ptr: *const c_void, @@ -94,6 +133,9 @@ pub(crate) struct ExtraData { pub(super) compiler: Option, #[cfg(feature = "luau-jit")] pub(super) enable_jit: bool, + + // Values currently being yielded from Lua.yield() + pub(super) yielded_values: Option, } impl Drop for ExtraData { @@ -124,17 +166,6 @@ impl ExtraData { pub(super) const ERROR_TRACEBACK_IDX: c_int = 1; pub(super) unsafe fn init(state: *mut ffi::lua_State, owned: bool) -> XRc> { - // Create ref stack thread and place it in the registry to prevent it - // from being garbage collected. - let ref_thread = mlua_expect!( - protect_lua!(state, 0, 0, |state| { - let thread = ffi::lua_newthread(state); - ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); - thread - }), - "Error while creating ref thread", - ); - let wrapped_failure_mt_ptr = { get_internal_metatable::(state); let ptr = ffi::lua_topointer(state, -1); @@ -142,13 +173,6 @@ impl ExtraData { ptr }; - // Store `error_traceback` function on the ref stack - #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - { - ffi::lua_pushcfunction(ref_thread, crate::util::error_traceback); - assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX); - } - #[allow(clippy::arc_with_non_send_sync)] let extra = XRc::new(UnsafeCell::new(ExtraData { lua: MaybeUninit::uninit(), @@ -164,11 +188,8 @@ impl ExtraData { safe: false, libs: StdLib::NONE, skip_memory_check: false, - ref_thread, - // We need some reserved stack space to move values in and out of the ref stack. - ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE, - ref_stack_top: ffi::lua_gettop(ref_thread), - ref_free: Vec::new(), + ref_thread: vec![RefThread::new(state)], + ref_thread_internal: RefThread::new(state), wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY), wrapped_failure_top: 0, #[cfg(feature = "async")] @@ -196,6 +217,7 @@ impl ExtraData { enable_jit: true, #[cfg(feature = "luau")] running_gc: false, + yielded_values: None, })); // Store it in the registry diff --git a/src/state/raw.rs b/src/state/raw.rs index b7de97f2..6292dab0 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -11,7 +11,9 @@ use crate::chunk::ChunkMode; use crate::error::{Error, Result}; use crate::function::Function; use crate::memory::{MemoryState, ALLOCATOR}; -use crate::state::util::{callback_error_ext, ref_stack_pop}; +#[allow(unused_imports)] +use crate::state::util::callback_error_ext; +use crate::state::util::{callback_error_ext_yieldable, get_next_spot}; use crate::stdlib::StdLib; use crate::string::String; use crate::table::Table; @@ -21,6 +23,12 @@ use crate::types::{ AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; + +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +use crate::types::Continuation; +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +use crate::types::ContinuationUpvalue; + use crate::userdata::{ init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, @@ -116,8 +124,24 @@ impl RawLua { } #[inline(always)] - pub(crate) fn ref_thread(&self) -> *mut ffi::lua_State { - unsafe { (*self.extra.get()).ref_thread } + pub(crate) fn ref_thread(&self, aux_thread: usize) -> *mut ffi::lua_State { + unsafe { + (&(*self.extra()).ref_thread) + .get(aux_thread) + .unwrap_unchecked() + .ref_thread + } + } + + #[inline(always)] + #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] + pub(crate) fn ref_thread_internal(&self) -> *mut ffi::lua_State { + unsafe { (*self.extra.get()).ref_thread_internal.ref_thread } + } + + #[inline(always)] + pub(crate) fn extra(&self) -> *mut ExtraData { + self.extra.get() } pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc> { @@ -197,6 +221,8 @@ impl RawLua { init_internal_metatable::>>(state, None)?; init_internal_metatable::(state, None)?; init_internal_metatable::(state, None)?; + #[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] + init_internal_metatable::(state, None)?; #[cfg(not(feature = "luau"))] init_internal_metatable::(state, None)?; #[cfg(feature = "async")] @@ -385,7 +411,7 @@ impl RawLua { mode, match env { Some(env) => { - self.push_ref(&env.0); + self.push_ref_at(&env.0, self.state()); -1 } _ => 0, @@ -553,8 +579,8 @@ impl RawLua { let protect = !self.unlikely_memory_error(); push_table(state, 0, lower_bound, protect)?; for (k, v) in iter { - self.push(k)?; - self.push(v)?; + self.push_at(state, k)?; + self.push_at(state, v)?; if protect { protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?; } else { @@ -580,7 +606,7 @@ impl RawLua { let protect = !self.unlikely_memory_error(); push_table(state, lower_bound, 0, protect)?; for (i, v) in iter.enumerate() { - self.push(v)?; + self.push_at(state, v)?; if protect { protect_lua!(state, 2, 1, |state| { ffi::lua_rawseti(state, -2, (i + 1) as Integer); @@ -616,16 +642,16 @@ impl RawLua { self.set_thread_hook(thread_state, HookKind::Global)?; let thread = Thread(self.pop_ref(), thread_state); - ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); + ffi::lua_xpush(self.ref_thread(func.0.aux_thread), thread_state, func.0.index); Ok(thread) } /// Wraps a Lua function into a new or recycled thread (coroutine). #[cfg(feature = "async")] pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result { - if let Some(index) = (*self.extra.get()).thread_pool.pop() { - let thread_state = ffi::lua_tothread(self.ref_thread(), index); - ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); + if let Some((aux_thread, index)) = (*self.extra.get()).thread_pool.pop() { + let thread_state = ffi::lua_tothread(self.ref_thread(aux_thread), index); + ffi::lua_xpush(self.ref_thread(func.0.aux_thread), thread_state, func.0.index); #[cfg(feature = "luau")] { @@ -634,7 +660,7 @@ impl RawLua { ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); } - return Ok(Thread(ValueRef::new(self, index), thread_state)); + return Ok(Thread(ValueRef::new(self, aux_thread, index), thread_state)); } self.create_thread(func) @@ -645,7 +671,7 @@ impl RawLua { pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { let extra = &mut *self.extra.get(); if extra.thread_pool.len() < extra.thread_pool.capacity() { - extra.thread_pool.push(thread.0.index); + extra.thread_pool.push((thread.0.aux_thread, thread.0.index)); thread.0.drop = false; // Prevent thread from being garbage collected } } @@ -654,15 +680,14 @@ impl RawLua { /// /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`. #[inline(always)] - pub(crate) unsafe fn push(&self, value: impl IntoLua) -> Result<()> { - value.push_into_stack(self) + pub(crate) unsafe fn push_at(&self, state: *mut ffi::lua_State, value: impl IntoLua) -> Result<()> { + value.push_into_specified_stack(self, state) } - /// Pushes a `Value` (by reference) onto the Lua stack. + /// Pushes a `Value` (by reference) onto the specified Lua stack. /// /// Uses 2 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { - let state = self.state(); + pub(crate) unsafe fn push_value_at(&self, value: &Value, state: *mut ffi::lua_State) -> Result<()> { match value { Value::Nil => ffi::lua_pushnil(state), Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int), @@ -676,18 +701,18 @@ impl RawLua { #[cfg(feature = "luau-vector4")] ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w()); } - Value::String(s) => self.push_ref(&s.0), - Value::Table(t) => self.push_ref(&t.0), - Value::Function(f) => self.push_ref(&f.0), - Value::Thread(t) => self.push_ref(&t.0), - Value::UserData(ud) => self.push_ref(&ud.0), + Value::String(s) => self.push_ref_at(&s.0, state), + Value::Table(t) => self.push_ref_at(&t.0, state), + Value::Function(f) => self.push_ref_at(&f.0, state), + Value::Thread(t) => self.push_ref_at(&t.0, state), + Value::UserData(ud) => self.push_ref_at(&ud.0, state), #[cfg(feature = "luau")] - Value::Buffer(buf) => self.push_ref(&buf.0), + Value::Buffer(buf) => self.push_ref_at(&buf.0, state), Value::Error(err) => { let protect = !self.unlikely_memory_error(); push_internal_userdata(state, WrappedFailure::Error(*err.clone()), protect)?; } - Value::Other(vref) => self.push_ref(vref), + Value::Other(vref) => self.push_ref_at(vref, state), } Ok(()) } @@ -695,17 +720,21 @@ impl RawLua { /// Pops a value from the Lua stack. /// /// Uses 2 stack spaces, does not call `checkstack`. - pub(crate) unsafe fn pop_value(&self) -> Value { - let value = self.stack_value(-1, None); - ffi::lua_pop(self.state(), 1); + pub(crate) unsafe fn pop_value_at(&self, state: *mut ffi::lua_State) -> Value { + let value = self.stack_value_at(-1, None, state); + ffi::lua_pop(state, 1); value } /// Returns value at given stack index without popping it. /// /// Uses 2 stack spaces, does not call checkstack. - pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option) -> Value { - let state = self.state(); + pub(crate) unsafe fn stack_value_at( + &self, + idx: c_int, + type_hint: Option, + state: *mut ffi::lua_State, + ) -> Value { match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) { ffi::LUA_TNIL => Nil, @@ -744,18 +773,30 @@ impl RawLua { } ffi::LUA_TSTRING => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::String(String(self.pop_ref_thread())) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + Value::String(String(self.new_value_ref(aux_thread, idxs))) } ffi::LUA_TTABLE => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Table(Table(self.pop_ref_thread())) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + Value::Table(Table(self.new_value_ref(aux_thread, idxs))) } ffi::LUA_TFUNCTION => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Function(Function(self.pop_ref_thread())) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + Value::Function(Function(self.new_value_ref(aux_thread, idxs))) } ffi::LUA_TUSERDATA => { @@ -771,39 +812,57 @@ impl RawLua { Value::Nil } _ => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::UserData(AnyUserData(self.pop_ref_thread())) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + + Value::UserData(AnyUserData(self.new_value_ref(aux_thread, idxs))) } } } ffi::LUA_TTHREAD => { - ffi::lua_xpush(state, self.ref_thread(), idx); - let thread_state = ffi::lua_tothread(self.ref_thread(), -1); - Value::Thread(Thread(self.pop_ref_thread(), thread_state)) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + let thread_state = ffi::lua_tothread(self.ref_thread(aux_thread), -1); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + Value::Thread(Thread(self.new_value_ref(aux_thread, idxs), thread_state)) } #[cfg(feature = "luau")] ffi::LUA_TBUFFER => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Buffer(crate::Buffer(self.pop_ref_thread())) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + Value::Buffer(crate::Buffer(self.new_value_ref(aux_thread, idxs))) } _ => { - ffi::lua_xpush(state, self.ref_thread(), idx); - Value::Other(self.pop_ref_thread()) + let (aux_thread, idxs, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush(state, self.ref_thread(aux_thread), idx); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idxs); + } + Value::Other(self.new_value_ref(aux_thread, idxs)) } } } - // Pushes a ValueRef value onto the stack, uses 1 stack space, does not call checkstack + // Pushes a ValueRef value onto the specified Lua stack, uses 1 stack space, does not call + // checkstack #[inline] - pub(crate) fn push_ref(&self, vref: &ValueRef) { + pub(crate) unsafe fn push_ref_at(&self, vref: &ValueRef, state: *mut ffi::lua_State) { assert!( self.weak() == &vref.lua, "Lua instance passed Value created from a different main Lua state" ); - unsafe { ffi::lua_xpush(self.ref_thread(), self.state(), vref.index) }; + ffi::lua_xpush(self.ref_thread(vref.aux_thread), state, vref.index); } // Pops the topmost element of the stack and stores a reference to it. This pins the object, @@ -815,41 +874,58 @@ impl RawLua { // used stack. #[inline] pub(crate) unsafe fn pop_ref(&self) -> ValueRef { - ffi::lua_xmove(self.state(), self.ref_thread(), 1); - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) + self.pop_ref_at(self.state()) } - // Same as `pop_ref` but assumes the value is already on the reference thread + /// Same as pop_ref but allows specifying state #[inline] - pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) + pub(crate) unsafe fn pop_ref_at(&self, state: *mut ffi::lua_State) -> ValueRef { + let (aux_thread, idx, replace) = get_next_spot(self.extra.get()); + ffi::lua_xmove(state, self.ref_thread(aux_thread), 1); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), idx); + } + + ValueRef::new(self, aux_thread, idx) + } + + // Given a known aux_thread and index, creates a ValueRef. + #[inline] + pub(crate) unsafe fn new_value_ref(&self, aux_thread: usize, index: c_int) -> ValueRef { + ValueRef::new(self, aux_thread, index) } #[inline] pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef { - ffi::lua_pushvalue(self.ref_thread(), vref.index); - let index = ref_stack_pop(self.extra.get()); - ValueRef::new(self, index) + let (aux_thread, index, replace) = get_next_spot(self.extra.get()); + ffi::lua_xpush( + self.ref_thread(vref.aux_thread), + self.ref_thread(aux_thread), + vref.index, + ); + if replace { + ffi::lua_replace(self.ref_thread(aux_thread), index); + } + ValueRef::new(self, aux_thread, index) } pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) { - let ref_thread = self.ref_thread(); + let ref_thread = self.ref_thread(vref.aux_thread); mlua_debug_assert!( ffi::lua_gettop(ref_thread) >= vref.index, "GC finalizer is not allowed in ref_thread" ); ffi::lua_pushnil(ref_thread); ffi::lua_replace(ref_thread, vref.index); - (*self.extra.get()).ref_free.push(vref.index); + (&mut (*self.extra.get()).ref_thread)[vref.aux_thread] + .free + .push(vref.index); } #[inline] - pub(crate) unsafe fn push_error_traceback(&self) { - let state = self.state(); + pub(crate) unsafe fn push_error_traceback_at(&self, state: *mut ffi::lua_State) { #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] - ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); + ffi::lua_xpush(self.ref_thread_internal(), state, ExtraData::ERROR_TRACEBACK_IDX); // Lua 5.2+ support light C functions that does not require extra allocations #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] ffi::lua_pushcfunction(state, crate::util::error_traceback); @@ -884,7 +960,7 @@ impl RawLua { let mut registry = UserDataRegistry::new(self.lua()); T::register(&mut registry); - self.create_userdata_metatable(registry.into_raw()) + self.create_userdata_metatable_at(registry.into_raw(), self.state()) }) } @@ -904,7 +980,7 @@ impl RawLua { Some(registry) => registry, None => UserDataRegistry::::new(self.lua()).into_raw(), }; - self.create_userdata_metatable(registry) + self.create_userdata_metatable_at(registry, self.state()) }) } @@ -939,11 +1015,14 @@ impl RawLua { Ok(AnyUserData(self.pop_ref())) } - pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result { - let state = self.state(); + pub(crate) unsafe fn create_userdata_metatable_at( + &self, + registry: RawUserDataRegistry, + state: *mut ffi::lua_State, + ) -> Result { let type_id = registry.type_id; - self.push_userdata_metatable(registry)?; + self.push_userdata_metatable_at(registry, state)?; let mt_ptr = ffi::lua_topointer(state, -1); let id = protect_lua!(state, 1, 0, |state| { @@ -958,8 +1037,11 @@ impl RawLua { Ok(id as Integer) } - pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> { - let state = self.state(); + pub(crate) unsafe fn push_userdata_metatable_at( + &self, + mut registry: RawUserDataRegistry, + state: *mut ffi::lua_State, + ) -> Result<()> { let mut stack_guard = StackGuard::new(state); check_stack(state, 13)?; @@ -969,18 +1051,18 @@ impl RawLua { let metatable_nrec = metatable_nrec + registry.async_meta_methods.len(); push_table(state, 0, metatable_nrec, true)?; for (k, m) in registry.meta_methods { - self.push(self.create_callback(m)?)?; + self.push_at(state, self.create_callback(m)?)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } #[cfg(feature = "async")] for (k, m) in registry.async_meta_methods { - self.push(self.create_async_callback(m)?)?; + self.push_at(state, self.create_async_callback(m)?)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } let mut has_name = false; for (k, v) in registry.meta_fields { has_name = has_name || k == MetaMethod::Type; - v?.push_into_stack(self)?; + v?.push_into_specified_stack(self, state)?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } // Set `__name/__type` if not provided @@ -1003,7 +1085,7 @@ impl RawLua { push_table(state, 0, fields_nrec, true)?; } for (k, v) in mem::take(&mut registry.fields) { - v?.push_into_stack(self)?; + v?.push_into_specified_stack(self, state)?; rawset_field(state, -2, &k)?; } rawset_field(state, metatable_index, "__index")?; @@ -1020,7 +1102,7 @@ impl RawLua { if field_getters_nrec > 0 { push_table(state, 0, field_getters_nrec, true)?; for (k, m) in registry.field_getters { - self.push(self.create_callback(m)?)?; + self.push_at(state, self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } for (k, v) in registry.fields { @@ -1028,7 +1110,7 @@ impl RawLua { ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); 1 } - v?.push_into_stack(self)?; + v?.push_into_specified_stack(self, state)?; protect_lua!(state, 1, 1, fn(state) { ffi::lua_pushcclosure(state, return_field, 1); })?; @@ -1042,7 +1124,7 @@ impl RawLua { if field_setters_nrec > 0 { push_table(state, 0, field_setters_nrec, true)?; for (k, m) in registry.field_setters { - self.push(self.create_callback(m)?)?; + self.push_at(state, self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } field_setters_index = Some(ffi::lua_absindex(state, -1)); @@ -1064,12 +1146,12 @@ impl RawLua { } } for (k, m) in registry.methods { - self.push(self.create_callback(m)?)?; + self.push_at(state, self.create_callback(m)?)?; rawset_field(state, -2, &k)?; } #[cfg(feature = "async")] for (k, m) in registry.async_methods { - self.push(self.create_async_callback(m)?)?; + self.push_at(state, self.create_async_callback(m)?)?; rawset_field(state, -2, &k)?; } match index_type { @@ -1121,7 +1203,7 @@ impl RawLua { // Returns `None` if the userdata is registered but non-static. #[inline(always)] pub(crate) fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result> { - unsafe { self.get_userdata_type_id_inner(self.ref_thread(), vref.index) } + unsafe { self.get_userdata_type_id_inner(self.ref_thread(vref.aux_thread), vref.index) } } // Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack. @@ -1173,9 +1255,13 @@ impl RawLua { // Pushes a ValueRef (userdata) value onto the stack, returning their `TypeId`. // Uses 1 stack space, does not call checkstack. - pub(crate) unsafe fn push_userdata_ref(&self, vref: &ValueRef) -> Result> { - let type_id = self.get_userdata_type_id_inner(self.ref_thread(), vref.index)?; - self.push_ref(vref); + pub(crate) unsafe fn push_userdata_ref_at( + &self, + vref: &ValueRef, + state: *mut ffi::lua_State, + ) -> Result> { + let type_id = self.get_userdata_type_id_inner(self.ref_thread(vref.aux_thread), vref.index)?; + self.push_ref_at(vref, state); Ok(type_id) } @@ -1183,15 +1269,21 @@ impl RawLua { pub(crate) fn create_callback(&self, func: Callback) -> Result { unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); - callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| { - // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) - // The lock must be already held as the callback is executed - let rawlua = (*extra).raw_lua(); - match (*upvalue).data { - Some(ref func) => func(rawlua, nargs), - None => Err(Error::CallbackDestructed), - } - }) + callback_error_ext_yieldable( + state, + (*upvalue).extra.get(), + true, + |extra, nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + match (*upvalue).data { + Some(ref func) => func(rawlua, nargs), + None => Err(Error::CallbackDestructed), + } + }, + false, + ) } let state = self.state(); @@ -1215,6 +1307,124 @@ impl RawLua { } } + // Creates a Function out of a Callback and a continuation containing a 'static Fn. + // + // In Luau, uses pushcclosurek + // + // In Lua 5.2/5.3/5.4/JIT, makes a normal function that then yields to the continuation via yieldk + #[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] + pub(crate) fn create_callback_with_continuation( + &self, + func: Callback, + cont: Continuation, + ) -> Result { + #[cfg(feature = "luau")] + { + unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + callback_error_ext_yieldable( + state, + (*upvalue).extra.get(), + true, + |extra, nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing + // arguments) The lock must be already held as the callback is + // executed + let rawlua = (*extra).raw_lua(); + match (*upvalue).data { + Some(ref func) => (func.0)(rawlua, nargs), + None => Err(Error::CallbackDestructed), + } + }, + true, + ) + } + + unsafe extern "C-unwind" fn cont_callback(state: *mut ffi::lua_State, status: c_int) -> c_int { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + callback_error_ext_yieldable( + state, + (*upvalue).extra.get(), + true, + |extra, nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing + // arguments) The lock must be already held as the callback is + // executed + let rawlua = (*extra).raw_lua(); + match (*upvalue).data { + Some(ref func) => (func.1)(rawlua, nargs, status), + None => Err(Error::CallbackDestructed), + } + }, + true, + ) + } + + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + let func = Some((func, cont)); + let extra = XRc::clone(&self.extra); + let protect = !self.unlikely_memory_error(); + push_internal_userdata(state, ContinuationUpvalue { data: func, extra }, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosurec(state, call_callback, cont_callback, 1); + })?; + } else { + ffi::lua_pushcclosurec(state, call_callback, cont_callback, 1); + } + + Ok(Function(self.pop_ref())) + } + } + + #[cfg(not(feature = "luau"))] + { + unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + callback_error_ext_yieldable( + state, + (*upvalue).extra.get(), + true, + |extra, nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing + // arguments) The lock must be already held as the callback is + // executed + let rawlua = (*extra).raw_lua(); + match (*upvalue).data { + Some((ref func, _)) => func(rawlua, nargs), + None => Err(Error::CallbackDestructed), + } + }, + true, + ) + } + + let state = self.state(); + unsafe { + let _sg = StackGuard::new(state); + check_stack(state, 4)?; + + let func = Some((func, cont)); + let extra = XRc::clone(&self.extra); + let protect = !self.unlikely_memory_error(); + push_internal_userdata(state, ContinuationUpvalue { data: func, extra }, protect)?; + if protect { + protect_lua!(state, 1, 1, fn(state) { + ffi::lua_pushcclosure(state, call_callback, 1); + })?; + } else { + ffi::lua_pushcclosure(state, call_callback, 1); + } + + Ok(Function(self.pop_ref())) + } + } + } + #[cfg(feature = "async")] pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result { // Ensure that the coroutine library is loaded @@ -1285,9 +1495,10 @@ impl RawLua { Ok(nresults + 1) } nresults => { - let results = MultiValue::from_stack_multi(nresults, rawlua)?; + let results = + MultiValue::from_specified_stack_multi(nresults, rawlua, state)?; ffi::lua_pushinteger(state, nresults as _); - rawlua.push(rawlua.create_sequence_from(results)?)?; + rawlua.push_at(state, rawlua.create_sequence_from(results)?)?; Ok(2) } } @@ -1375,6 +1586,12 @@ impl RawLua { pub(crate) unsafe fn set_waker(&self, waker: NonNull) -> NonNull { mem::replace(&mut (*self.extra.get()).waker, waker) } + + #[cfg(not(any(feature = "lua51", feature = "lua52", feature = "luajit")))] + #[inline] + pub(crate) fn is_yieldable(&self) -> bool { + unsafe { ffi::lua_isyieldable(self.state()) != 0 } + } } // Uses 3 stack spaces diff --git a/src/state/util.rs b/src/state/util.rs index c3c79302..2749471d 100644 --- a/src/state/util.rs +++ b/src/state/util.rs @@ -1,11 +1,17 @@ +use crate::IntoLuaMulti; +use std::mem::take; use std::os::raw::c_int; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::ptr; use std::sync::Arc; use crate::error::{Error, Result}; +use crate::state::extra::RefThread; use crate::state::{ExtraData, RawLua}; -use crate::util::{self, get_internal_metatable, WrappedFailure}; +use crate::util::{self, check_stack, get_internal_metatable, WrappedFailure}; + +#[cfg(all(not(feature = "lua51"), not(feature = "luajit"), not(feature = "luau")))] +use crate::{types::ContinuationUpvalue, util::get_userdata}; struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); @@ -22,6 +28,65 @@ impl Drop for StateGuard<'_> { } } +pub(crate) enum PreallocatedFailure { + New(*mut WrappedFailure), + Reserved, +} + +impl PreallocatedFailure { + unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { + if (*extra).wrapped_failure_top > 0 { + (*extra).wrapped_failure_top -= 1; + return PreallocatedFailure::Reserved; + } + + // We need to check stack for Luau in case when callback is called from interrupt + // See https://github.com/luau-lang/luau/issues/446 and mlua #142 and #153 + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + // Place it to the beginning of the stack + let ud = WrappedFailure::new_userdata(state); + ffi::lua_insert(state, 1); + PreallocatedFailure::New(ud) + } + + #[cold] + unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure { + let ref_thread = &(*extra).ref_thread_internal; + match *self { + PreallocatedFailure::New(ud) => { + ffi::lua_settop(state, 1); + ud + } + PreallocatedFailure::Reserved => { + let index = (*extra).wrapped_failure_pool.pop().unwrap(); + ffi::lua_settop(state, 0); + #[cfg(feature = "luau")] + ffi::lua_rawcheckstack(state, 2); + ffi::lua_xpush(ref_thread.ref_thread, state, index); + ffi::lua_pushnil(ref_thread.ref_thread); + ffi::lua_replace(ref_thread.ref_thread, index); + (*extra).ref_thread_internal.free.push(index); + ffi::lua_touserdata(state, -1) as *mut WrappedFailure + } + } + } + + unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) { + let ref_thread = &(*extra).ref_thread_internal; + match self { + PreallocatedFailure::New(_) => { + ffi::lua_rotate(state, 1, -1); + ffi::lua_xmove(state, ref_thread.ref_thread, 1); + let index = ref_stack_pop_internal(extra); + (*extra).wrapped_failure_pool.push(index); + (*extra).wrapped_failure_top += 1; + } + PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1, + } + } +} + // An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata // and instead reuses unused values from previous calls (or allocates new). pub(crate) unsafe fn callback_error_ext( @@ -39,64 +104,78 @@ where let nargs = ffi::lua_gettop(state); - enum PreallocatedFailure { - New(*mut WrappedFailure), - Reserved, - } - - impl PreallocatedFailure { - unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self { - if (*extra).wrapped_failure_top > 0 { - (*extra).wrapped_failure_top -= 1; - return PreallocatedFailure::Reserved; - } + // We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory + // to store a wrapped failure (error or panic) *before* we proceed. + let prealloc_failure = PreallocatedFailure::reserve(state, extra); - // We need to check stack for Luau in case when callback is called from interrupt - // See https://github.com/luau-lang/luau/issues/446 and mlua #142 and #153 - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - // Place it to the beginning of the stack - let ud = WrappedFailure::new_userdata(state); - ffi::lua_insert(state, 1); - PreallocatedFailure::New(ud) + match catch_unwind(AssertUnwindSafe(|| { + let rawlua = (*extra).raw_lua(); + let _guard = StateGuard::new(rawlua, state); + f(extra, nargs) + })) { + Ok(Ok(r)) => { + // Return unused `WrappedFailure` to the pool + prealloc_failure.release(state, extra); + r } + Ok(Err(err)) => { + let wrapped_error = prealloc_failure.r#use(state, extra); - #[cold] - unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure { - let ref_thread = (*extra).ref_thread; - match *self { - PreallocatedFailure::New(ud) => { - ffi::lua_settop(state, 1); - ud - } - PreallocatedFailure::Reserved => { - let index = (*extra).wrapped_failure_pool.pop().unwrap(); - ffi::lua_settop(state, 0); - #[cfg(feature = "luau")] - ffi::lua_rawcheckstack(state, 2); - ffi::lua_xpush(ref_thread, state, index); - ffi::lua_pushnil(ref_thread); - ffi::lua_replace(ref_thread, index); - (*extra).ref_free.push(index); - ffi::lua_touserdata(state, -1) as *mut WrappedFailure - } + if !wrap_error { + ptr::write(wrapped_error, WrappedFailure::Error(err)); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state) } - } - unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) { - let ref_thread = (*extra).ref_thread; - match self { - PreallocatedFailure::New(_) => { - ffi::lua_rotate(state, 1, -1); - ffi::lua_xmove(state, ref_thread, 1); - let index = ref_stack_pop(extra); - (*extra).wrapped_failure_pool.push(index); - (*extra).wrapped_failure_top += 1; - } - PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1, - } + // Build `CallbackError` with traceback + let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { + ffi::luaL_traceback(state, state, ptr::null(), 0); + let traceback = util::to_string(state, -1); + ffi::lua_pop(state, 1); + traceback + } else { + "".to_string() + }; + let cause = Arc::new(err); + ptr::write( + wrapped_error, + WrappedFailure::Error(Error::CallbackError { traceback, cause }), + ); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + + ffi::lua_error(state) + } + Err(p) => { + let wrapped_panic = prealloc_failure.r#use(state, extra); + ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p))); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state) } } +} + +/// An yieldable version of `callback_error_ext` +/// +/// Unlike ``callback_error_ext``, this method requires a c_int return +/// and not a generic R +pub(crate) unsafe fn callback_error_ext_yieldable( + state: *mut ffi::lua_State, + mut extra: *mut ExtraData, + wrap_error: bool, + f: F, + #[allow(unused_variables)] in_callback_with_continuation: bool, +) -> c_int +where + F: FnOnce(*mut ExtraData, c_int) -> Result, +{ + if extra.is_null() { + extra = ExtraData::get(state); + } + + let nargs = ffi::lua_gettop(state); // We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory // to store a wrapped failure (error or panic) *before* we proceed. @@ -109,7 +188,106 @@ where })) { Ok(Ok(r)) => { // Return unused `WrappedFailure` to the pool + // + // In either case, we cannot use it in the yield case anyways due to the lua_pop call + // so drop it properly now while we can. prealloc_failure.release(state, extra); + + let raw = extra.as_ref().unwrap_unchecked().raw_lua(); + let values = take(&mut extra.as_mut().unwrap_unchecked().yielded_values); + + if let Some(values) = values { + // A note on Luau + // + // When using the yieldable continuations fflag (and in future when the fflag gets removed and + // yieldable continuations) becomes default, we must either pop the top of the + // stack on the state we are resuming or somehow store the number of + // args on top of stack pre-yield and then subtract in the resume in order to get predictable + // behaviour here. See https://github.com/luau-lang/luau/issues/1867 for more information + // + // In this case, popping is easier and leads to less bugs/more ergonomic API. + + // We need to pop/clear stack early, then push args + ffi::lua_pop(state, -1); + + match values.push_into_specified_stack_multi(raw, state) { + Ok(nargs) => { + #[cfg(all(not(feature = "luau"), not(feature = "lua51"), not(feature = "luajit")))] + { + // Yield to a continuation. Unlike luau, we need to do this manually and on the + // fly using a yieldk call + if in_callback_with_continuation { + // On Lua 5.2, status and ctx are not present, so use 0 as status for + // compatibility + #[cfg(feature = "lua52")] + unsafe extern "C-unwind" fn cont_callback( + state: *mut ffi::lua_State, + ) -> c_int { + let upvalue = + get_userdata::(state, ffi::lua_upvalueindex(1)); + callback_error_ext_yieldable( + state, + (*upvalue).extra.get(), + true, + |extra, nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available + // (after pushing arguments) + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + match (*upvalue).data { + Some(ref func) => (func.1)(rawlua, nargs, 0), + None => Err(Error::CallbackDestructed), + } + }, + true, + ) + } + + // Lua 5.3/5.4 case + #[cfg(not(feature = "lua52"))] + unsafe extern "C-unwind" fn cont_callback( + state: *mut ffi::lua_State, + status: c_int, + _ctx: ffi::lua_KContext, + ) -> c_int { + let upvalue = + get_userdata::(state, ffi::lua_upvalueindex(1)); + callback_error_ext_yieldable( + state, + (*upvalue).extra.get(), + true, + |extra, nargs| { + // Lua ensures that `LUA_MINSTACK` stack spaces are available + // (after pushing arguments) + // The lock must be already held as the callback is executed + let rawlua = (*extra).raw_lua(); + match (*upvalue).data { + Some(ref func) => (func.1)(rawlua, nargs, status), + None => Err(Error::CallbackDestructed), + } + }, + true, + ) + } + + return ffi::lua_yieldc(state, nargs, cont_callback); + } + } + + return ffi::lua_yield(state, nargs); + } + Err(err) => { + // Make a *new* preallocated failure, and then do normal wrap_error + let prealloc_failure = PreallocatedFailure::reserve(state, extra); + let wrapped_panic = prealloc_failure.r#use(state, extra); + ptr::write(wrapped_panic, WrappedFailure::Error(err)); + get_internal_metatable::(state); + ffi::lua_setmetatable(state, -2); + ffi::lua_error(state); + } + } + } + r } Ok(Err(err)) => { @@ -151,30 +329,104 @@ where } } -pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int { +pub(super) unsafe fn ref_stack_pop_internal(extra: *mut ExtraData) -> c_int { let extra = &mut *extra; - if let Some(free) = extra.ref_free.pop() { - ffi::lua_replace(extra.ref_thread, free); + let ref_th = &mut extra.ref_thread_internal; + + if let Some(free) = ref_th.free.pop() { + ffi::lua_replace(ref_th.ref_thread, free); return free; } // Try to grow max stack size - if extra.ref_stack_top >= extra.ref_stack_size { - let mut inc = extra.ref_stack_size; // Try to double stack size - while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 { + if ref_th.stack_top >= ref_th.stack_size { + let mut inc = ref_th.stack_size; // Try to double stack size + while inc > 0 && ffi::lua_checkstack(ref_th.ref_thread, inc) == 0 { inc /= 2; } if inc == 0 { // Pop item on top of the stack to avoid stack leaking and successfully run destructors // during unwinding. - ffi::lua_pop(extra.ref_thread, 1); - let top = extra.ref_stack_top; + ffi::lua_pop(ref_th.ref_thread, 1); + let top = ref_th.stack_top; // It is a user error to create enough references to exhaust the Lua max stack size for - // the ref thread. - panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)"); + // the ref thread. This should never happen for the internal aux thread but still + panic!("internal error: cannot create a Lua reference, out of internal auxiliary stack space (used {top} slots)"); + } + ref_th.stack_size += inc; + } + ref_th.stack_top += 1; + return ref_th.stack_top; +} + +// Run a comparison function on two Lua references from different auxiliary threads. +pub(crate) unsafe fn compare_refs( + extra: *mut ExtraData, + aux_thread_a: usize, + aux_thread_a_index: c_int, + aux_thread_b: usize, + aux_thread_b_index: c_int, + f: impl FnOnce(*mut ffi::lua_State, c_int, c_int) -> R, +) -> R { + let extra = &mut *extra; + + if aux_thread_a == aux_thread_b { + // If both threads are the same, just return the value at the index + let th = &mut extra.ref_thread[aux_thread_a]; + return f(th.ref_thread, aux_thread_a_index, aux_thread_b_index); + } + + let th_a = &extra.ref_thread[aux_thread_a]; + let th_b = &extra.ref_thread[aux_thread_b]; + let internal_thread = &mut extra.ref_thread_internal; + + // 4 spaces needed: idx element on A, idx element on B + check_stack(internal_thread.ref_thread, 2) + .expect("internal error: cannot merge references, out of internal auxiliary stack space"); + + // Push the index element from thread A to top + ffi::lua_xpush(th_a.ref_thread, internal_thread.ref_thread, aux_thread_a_index); + // Push the index element from thread B to top + ffi::lua_xpush(th_b.ref_thread, internal_thread.ref_thread, aux_thread_b_index); + // Now we have the following stack: + // - index element from thread A (1) [copy from pushvalue] + // - index element from thread B (2) [copy from pushvalue] + // We want to compare the index elements from both threads, so use 3 and 4 as indices + let result = f(internal_thread.ref_thread, -1, -2); + + // Pop the top 2 elements to clean the copies + ffi::lua_pop(internal_thread.ref_thread, 2); + + result +} + +pub(crate) unsafe fn get_next_spot(extra: *mut ExtraData) -> (usize, c_int, bool) { + let extra = &mut *extra; + + // Find the first thread with a free slot + for (i, ref_th) in extra.ref_thread.iter_mut().enumerate() { + if let Some(free) = ref_th.free.pop() { + return (i, free, true); } - extra.ref_stack_size += inc; + + // Try to grow max stack size + if ref_th.stack_top >= ref_th.stack_size { + let mut inc = ref_th.stack_size; // Try to double stack size + while inc > 0 && ffi::lua_checkstack(ref_th.ref_thread, inc + 1) == 0 { + inc /= 2; + } + if inc == 0 { + continue; // No stack space available, try next thread + } + ref_th.stack_size += inc; + } + + ref_th.stack_top += 1; + return (i, ref_th.stack_top, false); } - extra.ref_stack_top += 1; - extra.ref_stack_top + + // No free slots found, create a new one + let new_ref_thread = RefThread::new(extra.raw_lua().state()); + extra.ref_thread.push(new_ref_thread); + return get_next_spot(extra); } diff --git a/src/string.rs b/src/string.rs index 6304d484..d2567ca0 100644 --- a/src/string.rs +++ b/src/string.rs @@ -119,7 +119,7 @@ impl String { let lua = self.0.lua.upgrade(); let slice = { let rawlua = lua.lock(); - let ref_thread = rawlua.ref_thread(); + let ref_thread = rawlua.ref_thread(self.0.aux_thread); mlua_debug_assert!( ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING, diff --git a/src/table.rs b/src/table.rs index 4b62705d..492766d5 100644 --- a/src/table.rs +++ b/src/table.rs @@ -76,9 +76,9 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.0); - key.push_into_stack(&lua)?; - value.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + key.push_into_specified_stack(&lua, state)?; + value.push_into_specified_stack(&lua, state)?; protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3)) } } @@ -123,11 +123,11 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); - key.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + key.push_into_specified_stack(&lua, state)?; protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?; - V::from_stack(-1, &lua) + V::from_specified_stack(-1, &lua, state) } } @@ -153,8 +153,8 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); - value.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + value.push_into_specified_stack(&lua, state)?; protect_lua!(state, 2, 0, fn(state) { let len = ffi::luaL_len(state, -2) as Integer; ffi::lua_seti(state, -2, len + 1); @@ -178,14 +178,14 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); protect_lua!(state, 1, 1, fn(state) { let len = ffi::luaL_len(state, -1) as Integer; ffi::lua_geti(state, -1, len); ffi::lua_pushnil(state); ffi::lua_seti(state, -3, len); })?; - V::from_stack(-1, &lua) + V::from_specified_stack(-1, &lua, state) } } @@ -251,9 +251,9 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.0); - key.push_into_stack(&lua)?; - value.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + key.push_into_specified_stack(&lua, state)?; + value.push_into_specified_stack(&lua, state)?; if lua.unlikely_memory_error() { ffi::lua_rawset(state, -3); @@ -273,11 +273,11 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 3)?; - lua.push_ref(&self.0); - key.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + key.push_into_specified_stack(&lua, state)?; ffi::lua_rawget(state, -2); - V::from_stack(-1, &lua) + V::from_specified_stack(-1, &lua, state) } } @@ -297,8 +297,8 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.0); - value.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + value.push_into_specified_stack(&lua, state)?; protect_lua!(state, 2, 0, |state| { for i in (idx..=size).rev() { // table[i+1] = table[i] @@ -321,8 +321,8 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); - value.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + value.push_into_specified_stack(&lua, state)?; unsafe fn callback(state: *mut ffi::lua_State) { let len = ffi::lua_rawlen(state, -2) as Integer; @@ -349,14 +349,14 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 3)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); let len = ffi::lua_rawlen(state, -1) as Integer; ffi::lua_rawgeti(state, -1, len); // Set slot to nil (it must be safe to do) ffi::lua_pushnil(state); ffi::lua_rawseti(state, -3, len); - V::from_stack(-1, &lua) + V::from_specified_stack(-1, &lua, state) } } @@ -381,7 +381,7 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); protect_lua!(state, 1, 0, |state| { for i in idx..size { ffi::lua_rawgeti(state, -1, i + 1); @@ -406,7 +406,7 @@ impl Table { #[cfg(feature = "luau")] { self.check_readonly_write(&lua)?; - ffi::lua_cleartable(lua.ref_thread(), self.0.index); + ffi::lua_cleartable(lua.ref_thread(self.0.aux_thread), self.0.index); } #[cfg(not(feature = "luau"))] @@ -414,7 +414,7 @@ impl Table { let state = lua.state(); check_stack(state, 4)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); // Clear array part for i in 1..=ffi::lua_rawlen(state, -1) { @@ -453,7 +453,7 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); protect_lua!(state, 1, 0, |state| ffi::luaL_len(state, -1)) } } @@ -461,7 +461,7 @@ impl Table { /// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod. pub fn raw_len(&self) -> usize { let lua = self.0.lua.lock(); - unsafe { ffi::lua_rawlen(lua.ref_thread(), self.0.index) } + unsafe { ffi::lua_rawlen(lua.ref_thread(self.0.aux_thread), self.0.index) } } /// Returns `true` if the table is empty, without invoking metamethods. @@ -469,7 +469,7 @@ impl Table { /// It checks both the array part and the hash part. pub fn is_empty(&self) -> bool { let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); + let ref_thread = lua.ref_thread(self.0.aux_thread); unsafe { ffi::lua_pushnil(ref_thread); if ffi::lua_next(ref_thread, self.0.index) == 0 { @@ -492,7 +492,7 @@ impl Table { let _sg = StackGuard::new(state); assert_stack(state, 2); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); if ffi::lua_getmetatable(state, -1) == 0 { None } else { @@ -518,9 +518,9 @@ impl Table { let _sg = StackGuard::new(state); assert_stack(state, 2); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); if let Some(metatable) = metatable { - lua.push_ref(&metatable.0); + lua.push_ref_at(&metatable.0, state); } else { ffi::lua_pushnil(state); } @@ -533,7 +533,7 @@ impl Table { #[inline] pub fn has_metatable(&self) -> bool { let lua = self.0.lua.lock(); - unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() } + unsafe { !get_metatable_ptr(lua.ref_thread(self.0.aux_thread), self.0.index).is_null() } } /// Sets `readonly` attribute on the table. @@ -541,7 +541,7 @@ impl Table { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_readonly(&self, enabled: bool) { let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); + let ref_thread = lua.ref_thread(self.0.aux_thread); unsafe { ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _); if !enabled { @@ -556,7 +556,7 @@ impl Table { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn is_readonly(&self) -> bool { let lua = self.0.lua.lock(); - let ref_thread = lua.ref_thread(); + let ref_thread = lua.ref_thread(self.0.aux_thread); unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 } } @@ -573,7 +573,7 @@ impl Table { #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_safeenv(&self, enabled: bool) { let lua = self.0.lua.lock(); - unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) }; + unsafe { ffi::lua_setsafeenv(lua.ref_thread(self.0.aux_thread), self.0.index, enabled as _) }; } /// Converts this table to a generic C pointer. @@ -637,11 +637,11 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); ffi::lua_pushnil(state); while ffi::lua_next(state, -2) != 0 { - let k = K::from_stack(-2, &lua)?; - let v = V::from_stack(-1, &lua)?; + let k = K::from_specified_stack(-2, &lua, state)?; + let v = V::from_specified_stack(-1, &lua, state)?; f(k, v)?; // Keep key for next iteration ffi::lua_pop(state, 1); @@ -699,11 +699,11 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); let len = ffi::lua_rawlen(state, -1); for i in 1..=len { ffi::lua_rawgeti(state, -1, i as _); - f(V::from_stack(-1, &lua)?)?; + f(V::from_specified_stack(-1, &lua, state)?)?; ffi::lua_pop(state, 1); } } @@ -722,8 +722,8 @@ impl Table { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.0); - value.push_into_stack(&lua)?; + lua.push_ref_at(&self.0, state); + value.push_into_specified_stack(&lua, state)?; let idx = idx.try_into().unwrap(); if lua.unlikely_memory_error() { @@ -743,7 +743,7 @@ impl Table { let _sg = StackGuard::new(state); assert_stack(state, 3); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); if ffi::lua_getmetatable(state, -1) == 0 { return false; } @@ -755,7 +755,7 @@ impl Table { #[cfg(feature = "luau")] #[inline(always)] fn check_readonly_write(&self, lua: &RawLua) -> Result<()> { - if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } { + if unsafe { ffi::lua_getreadonly(lua.ref_thread(self.0.aux_thread), self.0.index) != 0 } { return Err(Error::runtime("attempt to modify a readonly table")); } Ok(()) @@ -832,12 +832,12 @@ where let _sg = StackGuard::new(state); assert_stack(state, 4); - lua.push_ref(&self.0); + lua.push_ref_at(&self.0, state); let len = ffi::lua_rawlen(state, -1); for i in 0..len { ffi::lua_rawgeti(state, -1, (i + 1) as _); - let val = lua.pop_value(); + let val = lua.pop_value_at(state); if val == Nil { return i == other.len(); } @@ -1096,18 +1096,18 @@ where let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_ref(&self.table.0); - lua.push_value(&prev_key)?; + lua.push_ref_at(&self.table.0, state); + lua.push_value_at(&prev_key, state)?; // It must be safe to call `lua_next` unprotected as deleting a key from a table is // a permitted operation. // It fails only if the key is not found (never existed) which seems impossible scenario. if ffi::lua_next(state, -2) != 0 { - let key = lua.stack_value(-2, None); + let key = lua.stack_value_at(-2, None, state); Ok(Some(( key.clone(), K::from_lua(key, lua.lua())?, - V::from_stack(-1, lua)?, + V::from_specified_stack(-1, lua, state)?, ))) } else { Ok(None) @@ -1155,12 +1155,12 @@ where return Some(Err(err)); } - lua.push_ref(&self.table.0); + lua.push_ref_at(&self.table.0, state); match ffi::lua_rawgeti(state, -1, self.index) { ffi::LUA_TNIL => None, _ => { self.index += 1; - Some(V::from_stack(-1, lua)) + Some(V::from_specified_stack(-1, lua, state)) } } } diff --git a/src/thread.rs b/src/thread.rs index 13d95532..6b4b51c1 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -26,6 +26,25 @@ use { }, }; +/// Continuation thread status. Can either be Ok, Yielded (rare, but can happen) or Error +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ContinuationStatus { + Ok, + Yielded, + Error, +} + +impl ContinuationStatus { + #[allow(dead_code)] + pub(crate) fn from_status(status: c_int) -> Self { + match status { + ffi::LUA_YIELD => Self::Yielded, + ffi::LUA_OK => Self::Ok, + _ => Self::Error, + } + } +} + /// Status of a Lua thread (coroutine). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ThreadStatus { @@ -156,18 +175,12 @@ impl Thread { let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); - let nargs = args.push_into_stack_multi(&lua)?; - if nargs > 0 { - check_stack(thread_state, nargs)?; - ffi::lua_xmove(state, thread_state, nargs); - pushed_nargs += nargs; - } + let nargs = args.push_into_specified_stack_multi(&lua, thread_state)?; + pushed_nargs += nargs; let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?; - check_stack(state, nresults + 1)?; - ffi::lua_xmove(thread_state, state, nresults); - R::from_stack_multi(nresults, &lua) + R::from_specified_stack_multi(nresults, &lua, thread_state) } } @@ -192,15 +205,12 @@ impl Thread { let _sg = StackGuard::new(state); let _thread_sg = StackGuard::with_top(thread_state, 0); - check_stack(state, 1)?; - error.push_into_stack(&lua)?; - ffi::lua_xmove(state, thread_state, 1); + check_stack(thread_state, 1)?; + error.push_into_specified_stack(&lua, thread_state)?; let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?; - check_stack(state, nresults + 1)?; - ffi::lua_xmove(thread_state, state, nresults); - R::from_stack_multi(nresults, &lua) + R::from_specified_stack_multi(nresults, &lua, thread_state) } } @@ -215,6 +225,7 @@ impl Thread { let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int); #[cfg(feature = "luau")] let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int); + match ret { ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)), ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)), @@ -248,11 +259,19 @@ impl Thread { return ThreadStatusInner::Running; } let status = unsafe { ffi::lua_status(thread_state) }; - let top = unsafe { ffi::lua_gettop(thread_state) }; match status { - ffi::LUA_YIELD => ThreadStatusInner::Yielded(top), - ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1), - ffi::LUA_OK => ThreadStatusInner::Finished, + ffi::LUA_YIELD => { + let top = unsafe { ffi::lua_gettop(thread_state) }; + ThreadStatusInner::Yielded(top) + } + ffi::LUA_OK => { + let top = unsafe { ffi::lua_gettop(thread_state) }; + if top > 0 { + ThreadStatusInner::New(top - 1) + } else { + ThreadStatusInner::Finished + } + } _ => ThreadStatusInner::Error, } } @@ -312,7 +331,7 @@ impl Thread { self.reset_inner(status)?; // Push function to the top of the thread stack - ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index); + ffi::lua_xpush(lua.ref_thread(func.0.aux_thread), thread_state, func.0.index); #[cfg(feature = "luau")] { @@ -423,11 +442,7 @@ impl Thread { unsafe { let _sg = StackGuard::new(state); - let nargs = args.push_into_stack_multi(&lua)?; - if nargs > 0 { - check_stack(thread_state, nargs)?; - ffi::lua_xmove(state, thread_state, nargs); - } + args.push_into_specified_stack_multi(&lua, thread_state)?; Ok(AsyncThread { thread: self, @@ -571,10 +586,7 @@ impl Stream for AsyncThread { cx.waker().wake_by_ref(); } - check_stack(state, nresults + 1)?; - ffi::lua_xmove(thread_state, state, nresults); - - Poll::Ready(Some(R::from_stack_multi(nresults, &lua))) + Poll::Ready(Some(R::from_specified_stack_multi(nresults, &lua, thread_state))) } } } @@ -607,10 +619,7 @@ impl Future for AsyncThread { return Poll::Pending; } - check_stack(state, nresults + 1)?; - ffi::lua_xmove(thread_state, state, nresults); - - Poll::Ready(R::from_stack_multi(nresults, &lua)) + Poll::Ready(R::from_specified_stack_multi(nresults, &lua, thread_state)) } } } diff --git a/src/traits.rs b/src/traits.rs index 02373e2f..97813cc3 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -18,14 +18,14 @@ pub trait IntoLua: Sized { /// Performs the conversion. fn into_lua(self, lua: &Lua) -> Result; - /// Pushes the value into the Lua stack. + /// Pushes the value directly into a Lua stack /// /// # Safety /// This method does not check Lua stack space. #[doc(hidden)] #[inline] - unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { - lua.push_value(&self.into_lua(lua.lua())?) + unsafe fn push_into_specified_stack(self, lua: &RawLua, state: *mut ffi::lua_State) -> Result<()> { + lua.push_value_at(&self.into_lua(lua.lua())?, state) } } @@ -52,15 +52,21 @@ pub trait FromLua: Sized { /// Performs the conversion for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - Self::from_lua(lua.stack_value(idx, None), lua.lua()) + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + Self::from_lua(lua.stack_value_at(idx, None, state), lua.lua()) } /// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`. #[doc(hidden)] #[inline] - unsafe fn from_stack_arg(idx: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { - Self::from_stack(idx, lua).map_err(|err| Error::BadArgument { + unsafe fn from_specified_stack_arg( + idx: c_int, + i: usize, + to: Option<&str>, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { + Self::from_specified_stack(idx, lua, state).map_err(|err| Error::BadArgument { to: to.map(|s| s.to_string()), pos: i, name: None, @@ -77,18 +83,22 @@ pub trait IntoLuaMulti: Sized { /// Performs the conversion. fn into_lua_multi(self, lua: &Lua) -> Result; - /// Pushes the values into the Lua stack. + /// Pushes the values directly into a Lua stack /// /// Returns number of pushed values. #[doc(hidden)] #[inline] - unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result { + unsafe fn push_into_specified_stack_multi( + self, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { let values = self.into_lua_multi(lua.lua())?; let len: c_int = values.len().try_into().unwrap(); unsafe { - check_stack(lua.state(), len + 1)?; + check_stack(state, len + 1)?; for val in &values { - lua.push_value(val)?; + lua.push_value_at(val, state)?; } } Ok(len) @@ -120,23 +130,38 @@ pub trait FromLuaMulti: Sized { Self::from_lua_multi(args, lua) } - /// Performs the conversion for a number of values in the Lua stack. + /// Performs the conversion for a number of values in the specified Lua stack. #[doc(hidden)] #[inline] - unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result { + unsafe fn from_specified_stack_multi( + nvals: c_int, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { let mut values = MultiValue::with_capacity(nvals as usize); for idx in 0..nvals { - values.push_back(lua.stack_value(-nvals + idx, None)); + values.push_back(lua.stack_value_at(-nvals + idx, None, state)); } Self::from_lua_multi(values, lua.lua()) } - /// Same as `from_lua_args` but for a number of values in the Lua stack. + /// Same as `from_lua_args` but for a number of values in the specified Lua stack. #[doc(hidden)] #[inline] - unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result { + unsafe fn from_specified_stack_args( + nvals: c_int, + i: usize, + to: Option<&str>, + lua: &RawLua, + state: *mut ffi::lua_State, + ) -> Result { let _ = (i, to); - Self::from_stack_multi(nargs, lua) + Self::from_specified_stack_multi(nvals, lua, state).map_err(|err| Error::BadArgument { + to: to.map(|s| s.to_string()), + pos: i, + name: None, + cause: Arc::new(err), + }) } } diff --git a/src/types.rs b/src/types.rs index 2589ea6e..45806247 100644 --- a/src/types.rs +++ b/src/types.rs @@ -39,6 +39,11 @@ pub(crate) type Callback = Box Result + Send + #[cfg(not(feature = "send"))] pub(crate) type Callback = Box Result + 'static>; +#[cfg(all(feature = "send", not(feature = "lua51"), not(feature = "luajit")))] +pub(crate) type Continuation = Box Result + Send + 'static>; + +#[cfg(all(not(feature = "send"), not(feature = "lua51"), not(feature = "luajit")))] +pub(crate) type Continuation = Box Result + 'static>; pub(crate) type ScopedCallback<'s> = Box Result + 's>; @@ -48,6 +53,8 @@ pub(crate) struct Upvalue { } pub(crate) type CallbackUpvalue = Upvalue>; +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +pub(crate) type ContinuationUpvalue = Upvalue>; #[cfg(all(feature = "async", feature = "send"))] pub(crate) type AsyncCallback = diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs index 89bac543..fd9027e4 100644 --- a/src/types/value_ref.rs +++ b/src/types/value_ref.rs @@ -1,20 +1,23 @@ use std::fmt; use std::os::raw::{c_int, c_void}; +use crate::state::util::compare_refs; use crate::state::{RawLua, WeakLua}; /// A reference to a Lua (complex) value stored in the Lua auxiliary thread. pub struct ValueRef { pub(crate) lua: WeakLua, + pub(crate) aux_thread: usize, pub(crate) index: c_int, pub(crate) drop: bool, } impl ValueRef { #[inline] - pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { + pub(crate) fn new(lua: &RawLua, aux_thread: usize, index: c_int) -> Self { ValueRef { lua: lua.weak().clone(), + aux_thread, index, drop: true, } @@ -23,7 +26,7 @@ impl ValueRef { #[inline] pub(crate) fn to_pointer(&self) -> *const c_void { let lua = self.lua.lock(); - unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } + unsafe { ffi::lua_topointer(lua.ref_thread(self.aux_thread), self.index) } } /// Returns a copy of the value, which is valid as long as the original value is held. @@ -31,6 +34,7 @@ impl ValueRef { pub(crate) fn copy(&self) -> Self { ValueRef { lua: self.lua.clone(), + aux_thread: self.aux_thread, index: self.index, drop: false, } @@ -66,6 +70,16 @@ impl PartialEq for ValueRef { "Lua instance passed Value created from a different main Lua state" ); let lua = self.lua.lock(); - unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 } + + unsafe { + compare_refs( + lua.extra(), + self.aux_thread, + self.index, + other.aux_thread, + other.index, + |state, a, b| ffi::lua_rawequal(state, a, b) == 1, + ) + } } } diff --git a/src/userdata.rs b/src/userdata.rs index a568485d..f475d176 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -634,7 +634,7 @@ impl AnyUserData { #[inline] pub fn borrow(&self) -> Result> { let lua = self.0.lua.lock(); - unsafe { UserDataRef::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) } + unsafe { UserDataRef::borrow_from_stack(&lua, lua.ref_thread(self.0.aux_thread), self.0.index) } } /// Borrow this userdata immutably if it is of type `T`, passing the borrowed value @@ -645,7 +645,15 @@ impl AnyUserData { let lua = self.0.lua.lock(); let type_id = lua.get_userdata_ref_type_id(&self.0)?; let type_hints = TypeIdHints::new::(); - unsafe { borrow_userdata_scoped(lua.ref_thread(), self.0.index, type_id, type_hints, f) } + unsafe { + borrow_userdata_scoped( + lua.ref_thread(self.0.aux_thread), + self.0.index, + type_id, + type_hints, + f, + ) + } } /// Borrow this userdata mutably if it is of type `T`. @@ -661,7 +669,7 @@ impl AnyUserData { #[inline] pub fn borrow_mut(&self) -> Result> { let lua = self.0.lua.lock(); - unsafe { UserDataRefMut::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) } + unsafe { UserDataRefMut::borrow_from_stack(&lua, lua.ref_thread(self.0.aux_thread), self.0.index) } } /// Borrow this userdata mutably if it is of type `T`, passing the borrowed value @@ -672,7 +680,15 @@ impl AnyUserData { let lua = self.0.lua.lock(); let type_id = lua.get_userdata_ref_type_id(&self.0)?; let type_hints = TypeIdHints::new::(); - unsafe { borrow_userdata_scoped_mut(lua.ref_thread(), self.0.index, type_id, type_hints, f) } + unsafe { + borrow_userdata_scoped_mut( + lua.ref_thread(self.0.aux_thread), + self.0.index, + type_id, + type_hints, + f, + ) + } } /// Takes the value out of this userdata. @@ -685,7 +701,7 @@ impl AnyUserData { let lua = self.0.lua.lock(); match lua.get_userdata_ref_type_id(&self.0)? { Some(type_id) if type_id == TypeId::of::() => unsafe { - let ref_thread = lua.ref_thread(); + let ref_thread = lua.ref_thread(self.0.aux_thread); if (*get_userdata::>(ref_thread, self.0.index)).has_exclusive_access() { take_userdata::>(ref_thread, self.0.index).into_inner() } else { @@ -708,7 +724,7 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 3)?; - lua.push_userdata_ref(&self.0)?; + lua.push_userdata_ref_at(&self.0, state)?; protect_lua!(state, 1, 1, fn(state) { if ffi::luaL_callmeta(state, -1, cstr!("__gc")) == 0 { ffi::lua_pushboolean(state, 0); @@ -764,8 +780,8 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_userdata_ref(&self.0)?; - lua.push(v)?; + lua.push_userdata_ref_at(&self.0, state)?; + lua.push_at(state, v)?; // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 2, 0, |state| { @@ -802,7 +818,7 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_userdata_ref(&self.0)?; + lua.push_userdata_ref_at(&self.0, state)?; // Multiple (extra) user values are emulated by storing them in a table if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { @@ -810,7 +826,7 @@ impl AnyUserData { } ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer); - V::from_lua(lua.pop_value(), lua.lua()) + V::from_lua(lua.pop_value_at(state), lua.lua()) } } @@ -826,8 +842,8 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 5)?; - lua.push_userdata_ref(&self.0)?; - lua.push(v)?; + lua.push_userdata_ref_at(&self.0, state)?; + lua.push_at(state, v)?; // Multiple (extra) user values are emulated by storing them in a table protect_lua!(state, 2, 0, |state| { @@ -857,7 +873,7 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 4)?; - lua.push_userdata_ref(&self.0)?; + lua.push_userdata_ref_at(&self.0, state)?; // Multiple (extra) user values are emulated by storing them in a table if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE { @@ -866,7 +882,7 @@ impl AnyUserData { push_string(state, name.as_bytes(), !lua.unlikely_memory_error())?; ffi::lua_rawget(state, -2); - V::from_stack(-1, &lua) + V::from_specified_stack(-1, &lua, state) } } @@ -888,7 +904,7 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 3)?; - lua.push_userdata_ref(&self.0)?; + lua.push_userdata_ref_at(&self.0, state)?; ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call Ok(Table(lua.pop_ref())) } @@ -921,7 +937,7 @@ impl AnyUserData { let _sg = StackGuard::new(state); check_stack(state, 3)?; - lua.push_userdata_ref(&self.0)?; + lua.push_userdata_ref_at(&self.0, state)?; let protect = !lua.unlikely_memory_error(); let name_type = if protect { protect_lua!(state, 1, 1, |state| { @@ -963,7 +979,7 @@ impl AnyUserData { let is_serializable = || unsafe { // Userdata must be registered and not destructed let _ = lua.get_userdata_ref_type_id(&self.0)?; - let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); + let ud = &*get_userdata::>(lua.ref_thread(self.0.aux_thread), self.0.index); Ok::<_, Error>((*ud).is_serializable()) }; is_serializable().unwrap_or(false) @@ -1052,7 +1068,7 @@ impl Serialize for AnyUserData { let _ = lua .get_userdata_ref_type_id(&self.0) .map_err(ser::Error::custom)?; - let ud = &*get_userdata::>(lua.ref_thread(), self.0.index); + let ud = &*get_userdata::>(lua.ref_thread(self.0.aux_thread), self.0.index); ud.serialize(serializer) } } diff --git a/src/userdata/ref.rs b/src/userdata/ref.rs index 750443a9..a7475c7a 100644 --- a/src/userdata/ref.rs +++ b/src/userdata/ref.rs @@ -80,8 +80,8 @@ impl FromLua for UserDataRef { } #[inline] - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - Self::borrow_from_stack(lua, lua.state(), idx) + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + Self::borrow_from_stack(lua, state, idx) } } @@ -295,8 +295,8 @@ impl FromLua for UserDataRefMut { try_value_to_userdata::(value)?.borrow_mut() } - unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result { - Self::borrow_from_stack(lua, lua.state(), idx) + unsafe fn from_specified_stack(idx: c_int, lua: &RawLua, state: *mut ffi::lua_State) -> Result { + Self::borrow_from_stack(lua, state, idx) } } diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index 74e36e7d..5fda6121 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -133,20 +133,20 @@ impl UserDataRegistry { // Find absolute "self" index before processing args let self_index = ffi::lua_absindex(state, -nargs); // Self was at position 1, so we pass 2 here - let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); + let args = A::from_specified_stack_args(nargs - 1, 2, Some(&name), rawlua, state); match target_type { #[rustfmt::skip] UserDataType::Shared(type_hints) => { let type_id = try_self_arg!(rawlua.get_userdata_type_id::(state, self_index)); try_self_arg!(borrow_userdata_scoped(state, self_index, type_id, type_hints, |ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + method(rawlua.lua(), ud, args?)?.push_into_specified_stack_multi(rawlua, state) })) } UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => { let ud = target_ptr as *mut UserDataStorage; try_self_arg!((*ud).try_borrow_scoped(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + method(rawlua.lua(), ud, args?)?.push_into_specified_stack_multi(rawlua, state) })) } UserDataType::Unique(_) => { @@ -182,20 +182,20 @@ impl UserDataRegistry { // Find absolute "self" index before processing args let self_index = ffi::lua_absindex(state, -nargs); // Self was at position 1, so we pass 2 here - let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); + let args = A::from_specified_stack_args(nargs - 1, 2, Some(&name), rawlua, state); match target_type { #[rustfmt::skip] UserDataType::Shared(type_hints) => { let type_id = try_self_arg!(rawlua.get_userdata_type_id::(state, self_index)); try_self_arg!(borrow_userdata_scoped_mut(state, self_index, type_id, type_hints, |ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + method(rawlua.lua(), ud, args?)?.push_into_specified_stack_multi(rawlua, state) })) } UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => { let ud = target_ptr as *mut UserDataStorage; try_self_arg!((*ud).try_borrow_scoped_mut(|ud| { - method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua) + method(rawlua.lua(), ud, args?)?.push_into_specified_stack_multi(rawlua, state) })) } UserDataType::Unique(_) => { @@ -231,8 +231,9 @@ impl UserDataRegistry { try_self_arg!(Err(err)); } // Stack will be empty when polling the future, keep `self` on the ref thread - let self_ud = try_self_arg!(AnyUserData::from_stack(-nargs, rawlua)); - let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); + let state = rawlua.state(); + let self_ud = try_self_arg!(AnyUserData::from_specified_stack(-nargs, rawlua, state)); + let args = A::from_specified_stack_args(nargs - 1, 2, Some(&name), rawlua, state); let self_ud = try_self_arg!(self_ud.borrow()); let args = match args { @@ -242,7 +243,10 @@ impl UserDataRegistry { let lua = rawlua.lua(); let fut = method(lua.clone(), self_ud, args); // Lua is locked when the future is polled - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { + fut.await? + .push_into_specified_stack_multi(lua.raw_lua(), lua.raw_lua().state()) + }) }) } @@ -271,8 +275,9 @@ impl UserDataRegistry { try_self_arg!(Err(err)); } // Stack will be empty when polling the future, keep `self` on the ref thread - let self_ud = try_self_arg!(AnyUserData::from_stack(-nargs, rawlua)); - let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua); + let state = rawlua.state(); + let self_ud = try_self_arg!(AnyUserData::from_specified_stack(-nargs, rawlua, state)); + let args = A::from_specified_stack_args(nargs - 1, 2, Some(&name), rawlua, state); let self_ud = try_self_arg!(self_ud.borrow_mut()); let args = match args { @@ -282,7 +287,10 @@ impl UserDataRegistry { let lua = rawlua.lua(); let fut = method(lua.clone(), self_ud, args); // Lua is locked when the future is polled - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { + fut.await? + .push_into_specified_stack_multi(lua.raw_lua(), lua.raw_lua().state()) + }) }) } @@ -294,8 +302,9 @@ impl UserDataRegistry { { let name = get_function_name::(name); Box::new(move |lua, nargs| unsafe { - let args = A::from_stack_args(nargs, 1, Some(&name), lua)?; - function(lua.lua(), args)?.push_into_stack_multi(lua) + let state = lua.state(); + let args = A::from_specified_stack_args(nargs, 1, Some(&name), lua, state)?; + function(lua.lua(), args)?.push_into_specified_stack_multi(lua, state) }) } @@ -311,8 +320,9 @@ impl UserDataRegistry { let function = &mut *function .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?; - let args = A::from_stack_args(nargs, 1, Some(&name), lua)?; - function(lua.lua(), args)?.push_into_stack_multi(lua) + let state = lua.state(); + let args = A::from_specified_stack_args(nargs, 1, Some(&name), lua, state)?; + function(lua.lua(), args)?.push_into_specified_stack_multi(lua, state) }) } @@ -326,13 +336,16 @@ impl UserDataRegistry { { let name = get_function_name::(name); Box::new(move |rawlua, nargs| unsafe { - let args = match A::from_stack_args(nargs, 1, Some(&name), rawlua) { + let args = match A::from_specified_stack_args(nargs, 1, Some(&name), rawlua, rawlua.state()) { Ok(args) => args, Err(e) => return Box::pin(future::ready(Err(e))), }; let lua = rawlua.lua(); let fut = function(lua.clone(), args); - Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) }) + Box::pin(async move { + fut.await? + .push_into_specified_stack_multi(lua.raw_lua(), lua.raw_lua().state()) + }) }) } diff --git a/src/util/types.rs b/src/util/types.rs index 8bc9d8b2..8627042f 100644 --- a/src/util/types.rs +++ b/src/util/types.rs @@ -3,6 +3,9 @@ use std::os::raw::c_void; use crate::types::{Callback, CallbackUpvalue}; +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +use crate::types::ContinuationUpvalue; + #[cfg(feature = "async")] use crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}; @@ -34,6 +37,15 @@ impl TypeKey for CallbackUpvalue { } } +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +impl TypeKey for ContinuationUpvalue { + #[inline(always)] + fn type_key() -> *const c_void { + static CONTINUATION_UPVALUE_TYPE_KEY: u8 = 0; + &CONTINUATION_UPVALUE_TYPE_KEY as *const u8 as *const c_void + } +} + #[cfg(not(feature = "luau"))] impl TypeKey for crate::types::HookCallback { #[inline(always)] diff --git a/src/value.rs b/src/value.rs index cfc13251..5c0a81f5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -132,7 +132,7 @@ impl Value { // In Lua < 5.4 (excluding Luau), string pointers are NULL // Use alternative approach let lua = vref.lua.lock(); - unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void } + unsafe { ffi::lua_tostring(lua.ref_thread(vref.aux_thread), vref.index) as *const c_void } } Value::LightUserData(ud) => ud.0, Value::Table(Table(vref)) @@ -157,7 +157,7 @@ impl Value { let _guard = StackGuard::new(state); check_stack(state, 3)?; - lua.push_ref(vref); + lua.push_ref_at(vref, state); protect_lua!(state, 1, 1, fn(state) { ffi::luaL_tolstring(state, -1, ptr::null_mut()); })?; diff --git a/tests/byte_string.rs b/tests/byte_string.rs index 76e43e14..b66c58b4 100644 --- a/tests/byte_string.rs +++ b/tests/byte_string.rs @@ -1,6 +1,13 @@ use bstr::{BStr, BString}; use mlua::{Lua, Result}; +#[test] +fn create_lua() { + let lua = Lua::new(); + let th = lua.create_table().unwrap(); + println!("{th:#?}"); +} + #[test] fn test_byte_string_round_trip() -> Result<()> { let lua = Lua::new(); diff --git a/tests/tests.rs b/tests/tests.rs index 7bf40af5..4d6cc805 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1016,16 +1016,14 @@ fn test_ref_stack_exhaustion() { match catch_unwind(AssertUnwindSafe(|| -> Result<()> { let lua = Lua::new(); let mut vals = Vec::new(); - for _ in 0..10000000 { + for _ in 0..200000 { + println!("Creating table {}", vals.len()); vals.push(lua.create_table()?); } Ok(()) })) { - Ok(_) => panic!("no panic was detected"), - Err(p) => assert!(p - .downcast::() - .unwrap() - .starts_with("cannot create a Lua reference, out of auxiliary stack space")), + Ok(_) => {} + Err(p) => panic!("got panic: {:?}", p), } } diff --git a/tests/thread.rs b/tests/thread.rs index 4cb6ab10..0dd9c6e1 100644 --- a/tests/thread.rs +++ b/tests/thread.rs @@ -252,3 +252,497 @@ fn test_thread_resume_error() -> Result<()> { Ok(()) } + +#[test] +fn test_thread_yield_args() -> Result<()> { + let lua = Lua::new(); + let always_yield = lua.create_function(|lua, ()| lua.yield_with((42, "69420".to_string(), 45.6)))?; + + let thread = lua.create_thread(always_yield)?; + assert_eq!( + thread.resume::<(i32, String, f32)>(())?, + (42, String::from("69420"), 45.6) + ); + + Ok(()) +} + +#[test] +#[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] +fn test_continuation() { + let lua = Lua::new(); + // No yielding continuation fflag test + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with(a), + |_lua, _status, a: u64| { + println!("Reached cont"); + Ok(a + 39) + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local res = cont_func(1) + return res + 1 + ", + ) + .into_function() + .expect("Failed to create function"); + + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + let v = th.resume::(v).expect("Failed to load continuation"); + + assert_eq!(v, 41); + + // empty yield args test + let cont_func = lua + .create_function_with_continuation( + |lua, _: ()| lua.yield_with(()), + |_lua, _status, mv: mlua::MultiValue| Ok(mv.len()), + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local res = cont_func(1) + return res - 1 + ", + ) + .into_function() + .expect("Failed to create function"); + + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + assert!(v.is_empty()); + let v = th.resume::(v).expect("Failed to load continuation"); + assert_eq!(v, -1); + + // Yielding continuation test (only supported on luau) + #[cfg(feature = "luau")] + { + mlua::Lua::set_fflag("LuauYieldableContinuations", true).unwrap(); + } + + let cont_func = lua + .create_function_with_continuation( + |_lua, a: u64| Ok(a + 1), + |_lua, _status, a: u64| { + println!("Reached cont"); + Ok(a + 2) + }, + ) + .expect("Failed to create cont_func"); + + // Ensure normal calls work still + assert_eq!( + lua.load("local cont_func = ...\nreturn cont_func(1)") + .call::(cont_func) + .expect("Failed to call cont_func"), + 2 + ); + + // basic yield test before we go any further + let always_yield = lua + .create_function(|lua, ()| lua.yield_with((42, "69420".to_string(), 45.6))) + .unwrap(); + + let thread = lua.create_thread(always_yield).unwrap(); + assert_eq!( + thread.resume::<(i32, String, f32)>(()).unwrap(), + (42, String::from("69420"), 45.6) + ); + + // Trigger the continuation + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with(a), + |_lua, _status, a: u64| { + println!("Reached cont"); + Ok(a + 39) + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local res = cont_func(1) + return res + 1 + ", + ) + .into_function() + .expect("Failed to create function"); + + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + let v = th.resume::(v).expect("Failed to load continuation"); + + assert_eq!(v, 41); + + let always_yield = lua + .create_function_with_continuation( + |lua, ()| lua.yield_with((42, "69420".to_string(), 45.6)), + |_lua, _, mv: mlua::MultiValue| { + println!("Reached second continuation"); + if mv.is_empty() { + return Ok(mv); + } + Err(mlua::Error::external(format!("a{}", mv.len()))) + }, + ) + .unwrap(); + + let thread = lua.create_thread(always_yield).unwrap(); + let mv = thread.resume::(()).unwrap(); + assert!(thread + .resume::(mv) + .unwrap_err() + .to_string() + .starts_with("a3")); + + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with((a + 1, 1)), + |lua, status, args: mlua::MultiValue| { + println!("Reached cont recursive/multiple: {:?}", args); + + if args.len() == 5 { + if cfg!(any(feature = "luau", feature = "lua52")) { + assert_eq!(status, mlua::ContinuationStatus::Ok); + } else { + assert_eq!(status, mlua::ContinuationStatus::Yielded); + } + return Ok(6_i32); + } + + lua.yield_with((args.len() + 1, args))?; // thread state becomes LEN, LEN-1... 1 + Ok(1_i32) // this will be ignored + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local res = cont_func(1) + return res + 1 + ", + ) + .into_function() + .expect("Failed to create function"); + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + println!("v={:?}", v); + + let v = th + .resume::(v) + .expect("Failed to load continuation"); + println!("v={:?}", v); + let v = th + .resume::(v) + .expect("Failed to load continuation"); + println!("v={:?}", v); + let v = th + .resume::(v) + .expect("Failed to load continuation"); + + // (2, 1) followed by () + assert_eq!(v.len(), 2 + 3); + + let v = th.resume::(v).expect("Failed to load continuation"); + + assert_eq!(v, 7); + + // test panics + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with(a), + |_lua, _status, _a: u64| { + panic!("Reached continuation which should panic!"); + #[allow(unreachable_code)] + Ok(()) + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local ok, res = pcall(cont_func, 1) + assert(not ok) + return tostring(res) + ", + ) + .into_function() + .expect("Failed to create function"); + + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + + let v = th.resume::(v).expect("Failed to load continuation"); + assert!(v.contains("Reached continuation which should panic!")); +} + +#[test] +fn test_large_thread_creation() { + let lua = Lua::new(); + lua.set_memory_limit(100_000_000_000).unwrap(); + let th1 = lua + .create_thread(lua.create_function(|lua, _: ()| Ok(())).unwrap()) + .unwrap(); + + let mut ths = Vec::new(); + for i in 1..2000000 { + let th = lua + .create_thread(lua.create_function(|_, ()| Ok(())).unwrap()) + .expect("Failed to create thread"); + ths.push(th); + } + let th2 = lua + .create_thread(lua.create_function(|lua, _: ()| Ok(())).unwrap()) + .unwrap(); + + for rth in ths { + let dbg_a = format!("{:?}", rth); + let th_a = format!("{:?}", th1); + let th_b = format!("{:?}", th2); + assert!( + th1 != rth && th2 != rth, + "Thread {:?} is equal to th1 ({:?}) or th2 ({:?})", + rth, + th1, + th2 + ); + let dbg_b = format!("{:?}", rth); + let dbg_th1 = format!("{:?}", th1); + let dbg_th2 = format!("{:?}", th2); + + // Ensure that the PartialEq across auxillary threads does not affect the values on stack + // themselves. + assert_eq!(dbg_a, dbg_b, "Thread {:?} debug format changed", rth); + assert_eq!(th_a, dbg_th1, "Thread {:?} debug format changed for th1", rth); + assert_eq!(th_b, dbg_th2, "Thread {:?} debug format changed for th2", rth); + } + + #[cfg(all(not(feature = "lua51"), not(feature = "luajit")))] + { + // Repeat yielded continuation test now with a new aux thread + // Yielding continuation test (only supported on luau) + #[cfg(feature = "luau")] + { + mlua::Lua::set_fflag("LuauYieldableContinuations", true).unwrap(); + } + + let cont_func = lua + .create_function_with_continuation( + |_lua, a: u64| Ok(a + 1), + |_lua, _status, a: u64| { + println!("Reached cont"); + Ok(a + 2) + }, + ) + .expect("Failed to create cont_func"); + + // Ensure normal calls work still + assert_eq!( + lua.load("local cont_func = ...\nreturn cont_func(1)") + .call::(cont_func) + .expect("Failed to call cont_func"), + 2 + ); + + // basic yield test before we go any further + let always_yield = lua + .create_function(|lua, ()| lua.yield_with((42, "69420".to_string(), 45.6))) + .unwrap(); + + let thread = lua.create_thread(always_yield).unwrap(); + assert_eq!( + thread.resume::<(i32, String, f32)>(()).unwrap(), + (42, String::from("69420"), 45.6) + ); + + // Trigger the continuation + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with(a), + |_lua, _status, a: u64| { + println!("Reached cont"); + Ok(a + 39) + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local res = cont_func(1) + return res + 1 + ", + ) + .into_function() + .expect("Failed to create function"); + + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + let v = th.resume::(v).expect("Failed to load continuation"); + + assert_eq!(v, 41); + + let always_yield = lua + .create_function_with_continuation( + |lua, ()| lua.yield_with((42, "69420".to_string(), 45.6)), + |_lua, _, mv: mlua::MultiValue| { + println!("Reached second continuation"); + if mv.is_empty() { + return Ok(mv); + } + Err(mlua::Error::external(format!("a{}", mv.len()))) + }, + ) + .unwrap(); + + let thread = lua.create_thread(always_yield).unwrap(); + let mv = thread.resume::(()).unwrap(); + assert!(thread + .resume::(mv) + .unwrap_err() + .to_string() + .starts_with("a3")); + + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with((a + 1, 1)), + |lua, status, args: mlua::MultiValue| { + println!("Reached cont recursive/multiple: {:?}", args); + + if args.len() == 5 { + if cfg!(any(feature = "luau", feature = "lua52")) { + assert_eq!(status, mlua::ContinuationStatus::Ok); + } else { + assert_eq!(status, mlua::ContinuationStatus::Yielded); + } + return Ok(6_i32); + } + + lua.yield_with((args.len() + 1, args))?; // thread state becomes LEN, LEN-1... 1 + Ok(1_i32) // this will be ignored + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local res = cont_func(1) + return res + 1 + ", + ) + .into_function() + .expect("Failed to create function"); + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + println!("v={:?}", v); + + let v = th + .resume::(v) + .expect("Failed to load continuation"); + println!("v={:?}", v); + let v = th + .resume::(v) + .expect("Failed to load continuation"); + println!("v={:?}", v); + let v = th + .resume::(v) + .expect("Failed to load continuation"); + + // (2, 1) followed by () + assert_eq!(v.len(), 2 + 3); + + let v = th.resume::(v).expect("Failed to load continuation"); + + assert_eq!(v, 7); + + // test panics + let cont_func = lua + .create_function_with_continuation( + |lua, a: u64| lua.yield_with(a), + |_lua, _status, _a: u64| { + panic!("Reached continuation which should panic!"); + #[allow(unreachable_code)] + Ok(()) + }, + ) + .expect("Failed to create cont_func"); + + let luau_func = lua + .load( + " + local cont_func = ... + local ok, res = pcall(cont_func, 1) + assert(not ok) + return tostring(res) + ", + ) + .into_function() + .expect("Failed to create function"); + + let th = lua + .create_thread(luau_func) + .expect("Failed to create luau thread"); + + let v = th + .resume::(cont_func) + .expect("Failed to resume"); + + let v = th.resume::(v).expect("Failed to load continuation"); + assert!(v.contains("Reached continuation which should panic!")); + } +}