A lot of performance changes.

Okay, so this is kind of a mega-commit of a lot of performance related changes
to rlua, some of which are pretty complicated.

There are some small improvements here and there, but most of the benefits of
this change are from a few big changes.  The simplest big change is that there
is now `protect_lua` as well as `protect_lua_call`, which allows skipping a
lightuserdata parameter and some stack manipulation in some cases.  Second
simplest is the change to use Vec instead of VecDeque for MultiValue, and to
have MultiValue be used as a sort of "backwards-only" Vec so that ToLuaMulti /
FromLuaMulti still work correctly.

The most complex change, though, is a change to the way LuaRef works, so that
LuaRef can optionally point into the Lua stack instead of only registry values.
At state creation a set number of stack slots is reserved for the first N LuaRef
types (currently 16), and space for these are also allocated separately
allocated at callback time.  There is a huge breaking change here, which is that
now any LuaRef types MUST only be used with the Lua on which they were created,
and CANNOT be used with any other Lua callback instance.  This mostly will
affect people using LuaRef types from inside a scope callback, but hopefully in
those cases `Function::bind` will be a suitable replacement.  On the plus side,
the rules for LuaRef types are easier to state now.

There is probably more easy-ish perf on the table here, but here's the
preliminary results, based on my very limited benchmarks:

create table            time:   [314.13 ns 315.71 ns 317.44 ns]
                        change: [-36.154% -35.670% -35.205%] (p = 0.00 < 0.05)
create array 10         time:   [2.9731 us 2.9816 us 2.9901 us]
                        change: [-16.996% -16.600% -16.196%] (p = 0.00 < 0.05)
                        Performance has improved.
create string table 10  time:   [5.6904 us 5.7164 us 5.7411 us]
                        change: [-53.536% -53.309% -53.079%] (p = 0.00 < 0.05)
                        Performance has improved.
call add function 3 10  time:   [5.1134 us 5.1222 us 5.1320 us]
                        change: [-4.1095% -3.6910% -3.1781%] (p = 0.00 < 0.05)
                        Performance has improved.
call callback add 2 10  time:   [5.4408 us 5.4480 us 5.4560 us]
                        change: [-6.4203% -5.7780% -5.0013%] (p = 0.00 < 0.05)
                        Performance has improved.
call callback append 10 time:   [9.8243 us 9.8410 us 9.8586 us]
                        change: [-26.937% -26.702% -26.469%] (p = 0.00 < 0.05)
                        Performance has improved.
create registry 10      time:   [3.7005 us 3.7089 us 3.7174 us]
                        change: [-8.4965% -8.1042% -7.6926%] (p = 0.00 < 0.05)
                        Performance has improved.

I think that a lot of these benchmarks are too "easy", and most API usage is
going to be more like the 'create string table 10' benchmark, where there are a
lot of handles and tables and strings, so I think that 25%-50% improvement is a
good guess for most use cases.
This commit is contained in:
kyren 2018-03-11 23:20:10 -04:00
parent 84ee394b1d
commit 601e9f4cac
13 changed files with 729 additions and 497 deletions

View File

@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap};
use std::hash::{BuildHasher, Hash}; use std::hash::{BuildHasher, Hash};
use std::string::String as StdString; use std::string::String as StdString;
use error::*; use error::{Error, Result};
use types::{Integer, LightUserData, Number}; use types::{Integer, LightUserData, Number};
use string::String; use string::String;
use table::Table; use table::Table;

View File

@ -123,6 +123,7 @@ extern "C" {
pub fn lua_rotate(state: *mut lua_State, index: c_int, n: c_int); pub fn lua_rotate(state: *mut lua_State, index: c_int, n: c_int);
pub fn lua_copy(state: *mut lua_State, from: c_int, to: c_int); pub fn lua_copy(state: *mut lua_State, from: c_int, to: c_int);
pub fn lua_absindex(state: *mut lua_State, index: c_int) -> c_int; pub fn lua_absindex(state: *mut lua_State, index: c_int) -> c_int;
pub fn lua_xmove(from: *mut lua_State, to: *mut lua_State, n: c_int);
pub fn lua_isinteger(state: *mut lua_State, index: c_int) -> c_int; pub fn lua_isinteger(state: *mut lua_State, index: c_int) -> c_int;
pub fn lua_isnumber(state: *mut lua_State, index: c_int) -> c_int; pub fn lua_isnumber(state: *mut lua_State, index: c_int) -> c_int;

View File

@ -2,8 +2,9 @@ use std::ptr;
use std::os::raw::c_int; use std::os::raw::c_int;
use ffi; use ffi;
use error::*; use error::{Error, Result};
use util::*; use util::{check_stack, check_stack_err, error_traceback, pop_error, protect_lua_closure,
stack_guard};
use types::LuaRef; use types::LuaRef;
use value::{FromLuaMulti, MultiValue, ToLuaMulti}; use value::{FromLuaMulti, MultiValue, ToLuaMulti};
@ -63,16 +64,16 @@ impl<'lua> Function<'lua> {
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> { pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
let args = args.to_lua_multi(lua)?; let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
check_stack_err(lua.state, nargs + 3)?; check_stack_err(lua.state, nargs + 3)?;
ffi::lua_pushcfunction(lua.state, error_traceback); ffi::lua_pushcfunction(lua.state, error_traceback);
let stack_start = ffi::lua_gettop(lua.state); let stack_start = ffi::lua_gettop(lua.state);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
for arg in args { for arg in args {
lua.push_value(lua.state, arg); lua.push_value(arg);
} }
let ret = ffi::lua_pcall(lua.state, nargs, ffi::LUA_MULTRET, stack_start); let ret = ffi::lua_pcall(lua.state, nargs, ffi::LUA_MULTRET, stack_start);
if ret != ffi::LUA_OK { if ret != ffi::LUA_OK {
@ -82,7 +83,7 @@ impl<'lua> Function<'lua> {
let mut results = MultiValue::new(); let mut results = MultiValue::new();
check_stack(lua.state, 2); check_stack(lua.state, 2);
for _ in 0..nresults { for _ in 0..nresults {
results.push_front(lua.pop_value(lua.state)); results.push_front(lua.pop_value());
} }
ffi::lua_pop(lua.state, 1); ffi::lua_pop(lua.state, 1);
R::from_lua_multi(results, lua) R::from_lua_multi(results, lua)
@ -144,7 +145,7 @@ impl<'lua> Function<'lua> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
let args = args.to_lua_multi(lua)?; let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
@ -152,18 +153,18 @@ impl<'lua> Function<'lua> {
return Err(Error::BindError); return Err(Error::BindError);
} }
check_stack_err(lua.state, nargs + 3)?; check_stack_err(lua.state, nargs + 5)?;
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer); ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer);
for arg in args { for arg in args {
lua.push_value(lua.state, arg); lua.push_value(arg);
} }
protect_lua_call(lua.state, nargs + 2, 1, |state| { protect_lua_closure(lua.state, nargs + 2, 1, |state| {
ffi::lua_pushcclosure(state, bind_call_impl, nargs + 2); ffi::lua_pushcclosure(state, bind_call_impl, nargs + 2);
})?; })?;
Ok(Function(lua.pop_ref(lua.state))) Ok(Function(lua.pop_ref()))
}) })
} }
} }

View File

@ -1,19 +1,23 @@
use std::{mem, ptr, str}; use std::{cmp, mem, ptr, str};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use std::ffi::CString; use std::ffi::CString;
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::collections::HashMap; use std::collections::HashMap;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::{RefUnwindSafe, UnwindSafe};
use libc; use libc;
use ffi; use ffi;
use error::*; use error::{Error, Result};
use util::*; use util::{callback_error, check_stack, check_stack_err, gc_guard, get_userdata,
get_wrapped_error, init_error_metatables, pop_error, protect_lua, protect_lua_closure,
push_string, push_userdata, push_wrapped_error, safe_pcall, safe_xpcall, stack_guard,
take_userdata, userdata_destructor};
use value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; use value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
use types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey}; use types::{Callback, Integer, LightUserData, LuaRef, Number, RefType, RegistryKey};
use string::String; use string::String;
use table::Table; use table::Table;
use function::Function; use function::Function;
@ -23,8 +27,8 @@ use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
/// Top level Lua struct which holds the Lua state itself. /// Top level Lua struct which holds the Lua state itself.
pub struct Lua { pub struct Lua {
pub(crate) state: *mut ffi::lua_State, pub(crate) state: *mut ffi::lua_State,
main_state: *mut ffi::lua_State,
ephemeral: bool, ephemeral: bool,
ref_stack_slots: [Cell<usize>; REF_STACK_SIZE as usize],
} }
/// Constructed by the [`Lua::scope`] method, allows temporarily passing to Lua userdata that is /// Constructed by the [`Lua::scope`] method, allows temporarily passing to Lua userdata that is
@ -35,7 +39,7 @@ pub struct Lua {
/// [`Lua::scope`]: struct.Lua.html#method.scope /// [`Lua::scope`]: struct.Lua.html#method.scope
pub struct Scope<'scope> { pub struct Scope<'scope> {
lua: &'scope Lua, lua: &'scope Lua,
destructors: RefCell<Vec<Box<Fn(*mut ffi::lua_State) -> Box<Any>>>>, destructors: RefCell<Vec<Box<Fn() -> Box<Any> + 'scope>>>,
// 'scope lifetime must be invariant // 'scope lifetime must be invariant
_scope: PhantomData<&'scope mut &'scope ()>, _scope: PhantomData<&'scope mut &'scope ()>,
} }
@ -46,14 +50,25 @@ struct ExtraData {
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>, registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
} }
const REF_STACK_SIZE: c_int = 16;
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;
unsafe impl Send for Lua {} unsafe impl Send for Lua {}
impl UnwindSafe for Lua {}
impl RefUnwindSafe for Lua {}
impl Drop for Lua { impl Drop for Lua {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
if !self.ephemeral { if !self.ephemeral {
let top = ffi::lua_gettop(self.state); let top = ffi::lua_gettop(self.state);
rlua_assert!(top == 0, "stack leak detected, stack top is {}", top); rlua_assert!(
top == REF_STACK_SIZE,
"stack problem detected, stack top is {}",
top - REF_STACK_SIZE
);
let extra_data = *(ffi::lua_getextraspace(self.state) as *mut *mut ExtraData); let extra_data = *(ffi::lua_getextraspace(self.state) as *mut *mut ExtraData);
*(*extra_data).registry_unref_list.lock().unwrap() = None; *(*extra_data).registry_unref_list.lock().unwrap() = None;
@ -87,7 +102,7 @@ impl Lua {
/// Equivalent to Lua's `load` function. /// Equivalent to Lua's `load` function.
pub fn load(&self, source: &str, name: Option<&str>) -> Result<Function> { pub fn load(&self, source: &str, name: Option<&str>) -> Result<Function> {
unsafe { unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 1); check_stack(self.state, 1);
match if let Some(name) = name { match if let Some(name) = name {
@ -111,7 +126,7 @@ impl Lua {
ptr::null(), ptr::null(),
) )
} { } {
ffi::LUA_OK => Ok(Function(self.pop_ref(self.state))), ffi::LUA_OK => Ok(Function(self.pop_ref())),
err => Err(pop_error(self.state, err)), err => Err(pop_error(self.state, err)),
} }
}) })
@ -152,10 +167,10 @@ impl Lua {
/// Pass a `&str` slice to Lua, creating and returning an interned Lua string. /// Pass a `&str` slice to Lua, creating and returning an interned Lua string.
pub fn create_string(&self, s: &str) -> Result<String> { pub fn create_string(&self, s: &str) -> Result<String> {
unsafe { unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 4); check_stack(self.state, 4);
push_string(self.state, s)?; push_string(self.state, s)?;
Ok(String(self.pop_ref(self.state))) Ok(String(self.pop_ref()))
}) })
} }
} }
@ -163,12 +178,14 @@ impl Lua {
/// Creates and returns a new table. /// Creates and returns a new table.
pub fn create_table(&self) -> Result<Table> { pub fn create_table(&self) -> Result<Table> {
unsafe { unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 4); check_stack(self.state, 3);
protect_lua_call(self.state, 0, 1, |state| { unsafe extern "C" fn new_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_newtable(state); ffi::lua_newtable(state);
})?; 1
Ok(Table(self.pop_ref(self.state))) }
protect_lua(self.state, 0, new_table)?;
Ok(Table(self.pop_ref()))
}) })
} }
} }
@ -181,20 +198,24 @@ impl Lua {
I: IntoIterator<Item = (K, V)>, I: IntoIterator<Item = (K, V)>,
{ {
unsafe { unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 6); check_stack(self.state, 5);
protect_lua_call(self.state, 0, 1, |state| { unsafe extern "C" fn new_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_newtable(state); ffi::lua_newtable(state);
})?; 1
}
protect_lua(self.state, 0, new_table)?;
for (k, v) in cont { for (k, v) in cont {
self.push_value(self.state, k.to_lua(self)?); self.push_value(k.to_lua(self)?);
self.push_value(self.state, v.to_lua(self)?); self.push_value(v.to_lua(self)?);
protect_lua_call(self.state, 3, 1, |state| { unsafe extern "C" fn raw_set(state: *mut ffi::lua_State) -> c_int {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; 1
} }
Ok(Table(self.pop_ref(self.state))) protect_lua(self.state, 3, raw_set)?;
}
Ok(Table(self.pop_ref()))
}) })
} }
} }
@ -301,14 +322,15 @@ impl Lua {
/// Equivalent to `coroutine.create`. /// Equivalent to `coroutine.create`.
pub fn create_thread<'lua>(&'lua self, func: Function<'lua>) -> Result<Thread<'lua>> { pub fn create_thread<'lua>(&'lua self, func: Function<'lua>) -> Result<Thread<'lua>> {
unsafe { unsafe {
stack_err_guard(self.state, move || { stack_guard(self.state, move || {
check_stack(self.state, 2); check_stack(self.state, 2);
let thread_state = let thread_state =
protect_lua_call(self.state, 0, 1, |state| ffi::lua_newthread(state))?; protect_lua_closure(self.state, 0, 1, |state| ffi::lua_newthread(state))?;
self.push_ref(thread_state, &func.0); self.push_ref(&func.0);
ffi::lua_xmove(self.state, thread_state, 1);
Ok(Thread(self.pop_ref(self.state))) Ok(Thread(self.pop_ref()))
}) })
} }
} }
@ -327,7 +349,7 @@ impl Lua {
stack_guard(self.state, move || { stack_guard(self.state, move || {
check_stack(self.state, 2); check_stack(self.state, 2);
ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
Table(self.pop_ref(self.state)) Table(self.pop_ref())
}) })
} }
} }
@ -370,21 +392,21 @@ impl Lua {
match v { match v {
Value::String(s) => Ok(s), Value::String(s) => Ok(s),
v => unsafe { v => unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 4); check_stack(self.state, 4);
let ty = v.type_name(); let ty = v.type_name();
self.push_value(self.state, v); self.push_value(v);
let s = let s = protect_lua_closure(self.state, 1, 1, |state| {
protect_lua_call(self.state, 1, 1, |state| ffi::lua_tostring(state, -1))?; ffi::lua_tostring(state, -1)
})?;
if s.is_null() { if s.is_null() {
ffi::lua_pop(self.state, 1);
Err(Error::FromLuaConversionError { Err(Error::FromLuaConversionError {
from: ty, from: ty,
to: "String", to: "String",
message: Some("expected string or number".to_string()), message: Some("expected string or number".to_string()),
}) })
} else { } else {
Ok(String(self.pop_ref(self.state))) Ok(String(self.pop_ref()))
} }
}) })
}, },
@ -402,10 +424,9 @@ impl Lua {
stack_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 2); check_stack(self.state, 2);
let ty = v.type_name(); let ty = v.type_name();
self.push_value(self.state, v); self.push_value(v);
let mut isint = 0; let mut isint = 0;
let i = ffi::lua_tointegerx(self.state, -1, &mut isint); let i = ffi::lua_tointegerx(self.state, -1, &mut isint);
ffi::lua_pop(self.state, 1);
if isint == 0 { if isint == 0 {
Err(Error::FromLuaConversionError { Err(Error::FromLuaConversionError {
from: ty, from: ty,
@ -431,10 +452,9 @@ impl Lua {
stack_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 2); check_stack(self.state, 2);
let ty = v.type_name(); let ty = v.type_name();
self.push_value(self.state, v); self.push_value(v);
let mut isnum = 0; let mut isnum = 0;
let n = ffi::lua_tonumberx(self.state, -1, &mut isnum); let n = ffi::lua_tonumberx(self.state, -1, &mut isnum);
ffi::lua_pop(self.state, 1);
if isnum == 0 { if isnum == 0 {
Err(Error::FromLuaConversionError { Err(Error::FromLuaConversionError {
from: ty, from: ty,
@ -482,15 +502,17 @@ impl Lua {
t: T, t: T,
) -> Result<()> { ) -> Result<()> {
unsafe { unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 5); check_stack(self.state, 5);
push_string(self.state, name)?; push_string(self.state, name)?;
self.push_value(self.state, t.to_lua(self)?); self.push_value(t.to_lua(self)?);
protect_lua_call(self.state, 2, 0, |state| { unsafe extern "C" fn set_registry(state: *mut ffi::lua_State) -> c_int {
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX); ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}) 0
}
protect_lua(self.state, 2, set_registry)
}) })
} }
} }
@ -503,15 +525,17 @@ impl Lua {
/// [`set_named_registry_value`]: #method.set_named_registry_value /// [`set_named_registry_value`]: #method.set_named_registry_value
pub fn named_registry_value<'lua, T: FromLua<'lua>>(&'lua self, name: &str) -> Result<T> { pub fn named_registry_value<'lua, T: FromLua<'lua>>(&'lua self, name: &str) -> Result<T> {
unsafe { unsafe {
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 4); check_stack(self.state, 4);
push_string(self.state, name)?; push_string(self.state, name)?;
protect_lua_call(self.state, 1, 1, |state| { unsafe extern "C" fn get_registry(state: *mut ffi::lua_State) -> c_int {
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX) ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
})?; 1
}
protect_lua(self.state, 1, get_registry)?;
T::from_lua(self.pop_value(self.state), self) T::from_lua(self.pop_value(), self)
}) })
} }
} }
@ -534,7 +558,7 @@ impl Lua {
stack_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 2); check_stack(self.state, 2);
self.push_value(self.state, t.to_lua(self)?); self.push_value(t.to_lua(self)?);
let registry_id = gc_guard(self.state, || { let registry_id = gc_guard(self.state, || {
ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX) ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX)
}); });
@ -542,7 +566,6 @@ impl Lua {
Ok(RegistryKey { Ok(RegistryKey {
registry_id, registry_id,
unref_list: (*self.extra()).registry_unref_list.clone(), unref_list: (*self.extra()).registry_unref_list.clone(),
drop_unref: true,
}) })
}) })
} }
@ -560,14 +583,14 @@ impl Lua {
return Err(Error::MismatchedRegistryKey); return Err(Error::MismatchedRegistryKey);
} }
stack_err_guard(self.state, || { stack_guard(self.state, || {
check_stack(self.state, 2); check_stack(self.state, 2);
ffi::lua_rawgeti( ffi::lua_rawgeti(
self.state, self.state,
ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTRYINDEX,
key.registry_id as ffi::lua_Integer, key.registry_id as ffi::lua_Integer,
); );
T::from_lua(self.pop_value(self.state), self) T::from_lua(self.pop_value(), self)
}) })
} }
} }
@ -581,14 +604,13 @@ impl Lua {
/// ///
/// [`create_registry_value`]: #method.create_registry_value /// [`create_registry_value`]: #method.create_registry_value
/// [`expire_registry_values`]: #method.expire_registry_values /// [`expire_registry_values`]: #method.expire_registry_values
pub fn remove_registry_value(&self, mut key: RegistryKey) -> Result<()> { pub fn remove_registry_value(&self, key: RegistryKey) -> Result<()> {
unsafe { unsafe {
if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) { if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) {
return Err(Error::MismatchedRegistryKey); return Err(Error::MismatchedRegistryKey);
} }
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.registry_id); ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.take());
key.drop_unref = false;
Ok(()) Ok(())
} }
} }
@ -620,120 +642,130 @@ impl Lua {
} }
// Uses 2 stack spaces, does not call checkstack // Uses 2 stack spaces, does not call checkstack
pub(crate) unsafe fn push_value(&self, state: *mut ffi::lua_State, value: Value) { pub(crate) unsafe fn push_value(&self, value: Value) {
match value { match value {
Value::Nil => { Value::Nil => {
ffi::lua_pushnil(state); ffi::lua_pushnil(self.state);
} }
Value::Boolean(b) => { Value::Boolean(b) => {
ffi::lua_pushboolean(state, if b { 1 } else { 0 }); ffi::lua_pushboolean(self.state, if b { 1 } else { 0 });
} }
Value::LightUserData(ud) => { Value::LightUserData(ud) => {
ffi::lua_pushlightuserdata(state, ud.0); ffi::lua_pushlightuserdata(self.state, ud.0);
} }
Value::Integer(i) => { Value::Integer(i) => {
ffi::lua_pushinteger(state, i); ffi::lua_pushinteger(self.state, i);
} }
Value::Number(n) => { Value::Number(n) => {
ffi::lua_pushnumber(state, n); ffi::lua_pushnumber(self.state, n);
} }
Value::String(s) => { Value::String(s) => {
self.push_ref(state, &s.0); self.push_ref(&s.0);
} }
Value::Table(t) => { Value::Table(t) => {
self.push_ref(state, &t.0); self.push_ref(&t.0);
} }
Value::Function(f) => { Value::Function(f) => {
self.push_ref(state, &f.0); self.push_ref(&f.0);
} }
Value::Thread(t) => { Value::Thread(t) => {
self.push_ref(state, &t.0); self.push_ref(&t.0);
} }
Value::UserData(ud) => { Value::UserData(ud) => {
self.push_ref(state, &ud.0); self.push_ref(&ud.0);
} }
Value::Error(e) => { Value::Error(e) => {
push_wrapped_error(state, e); push_wrapped_error(self.state, e);
} }
} }
} }
// Uses 2 stack spaces, does not call checkstack // Uses 2 stack spaces, does not call checkstack
pub(crate) unsafe fn pop_value(&self, state: *mut ffi::lua_State) -> Value { pub(crate) unsafe fn pop_value(&self) -> Value {
match ffi::lua_type(state, -1) { match ffi::lua_type(self.state, -1) {
ffi::LUA_TNIL => { ffi::LUA_TNIL => {
ffi::lua_pop(state, 1); ffi::lua_pop(self.state, 1);
Nil Nil
} }
ffi::LUA_TBOOLEAN => { ffi::LUA_TBOOLEAN => {
let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0); let b = Value::Boolean(ffi::lua_toboolean(self.state, -1) != 0);
ffi::lua_pop(state, 1); ffi::lua_pop(self.state, 1);
b b
} }
ffi::LUA_TLIGHTUSERDATA => { ffi::LUA_TLIGHTUSERDATA => {
let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(state, -1))); let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(self.state, -1)));
ffi::lua_pop(state, 1); ffi::lua_pop(self.state, 1);
ud ud
} }
ffi::LUA_TNUMBER => if ffi::lua_isinteger(state, -1) != 0 { ffi::LUA_TNUMBER => if ffi::lua_isinteger(self.state, -1) != 0 {
let i = Value::Integer(ffi::lua_tointeger(state, -1)); let i = Value::Integer(ffi::lua_tointeger(self.state, -1));
ffi::lua_pop(state, 1); ffi::lua_pop(self.state, 1);
i i
} else { } else {
let n = Value::Number(ffi::lua_tonumber(state, -1)); let n = Value::Number(ffi::lua_tonumber(self.state, -1));
ffi::lua_pop(state, 1); ffi::lua_pop(self.state, 1);
n n
}, },
ffi::LUA_TSTRING => Value::String(String(self.pop_ref(state))), ffi::LUA_TSTRING => Value::String(String(self.pop_ref())),
ffi::LUA_TTABLE => Value::Table(Table(self.pop_ref(state))), ffi::LUA_TTABLE => Value::Table(Table(self.pop_ref())),
ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref(state))), ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref())),
ffi::LUA_TUSERDATA => { ffi::LUA_TUSERDATA => {
// It should not be possible to interact with userdata types other than custom // It should not be possible to interact with userdata types other than custom
// UserData types OR a WrappedError. WrappedPanic should never be able to be caught // UserData types OR a WrappedError. WrappedPanic should never be able to be caught
// in lua, so it should never be here. // in lua, so it should never be here.
if let Some(err) = pop_wrapped_error(state) { if let Some(err) = get_wrapped_error(self.state, -1).as_ref() {
let err = err.clone();
ffi::lua_pop(self.state, 1);
Value::Error(err) Value::Error(err)
} else { } else {
Value::UserData(AnyUserData(self.pop_ref(state))) Value::UserData(AnyUserData(self.pop_ref()))
} }
} }
ffi::LUA_TTHREAD => Value::Thread(Thread(self.pop_ref(state))), ffi::LUA_TTHREAD => Value::Thread(Thread(self.pop_ref())),
_ => unreachable!("internal error: LUA_TNONE in pop_value"), _ => unreachable!("internal error: LUA_TNONE in pop_value"),
} }
} }
// Used 1 stack space, does not call checkstack // Used 1 stack space, does not call checkstack
pub(crate) unsafe fn push_ref(&self, state: *mut ffi::lua_State, lref: &LuaRef) { pub(crate) unsafe fn push_ref(&self, lref: &LuaRef) {
assert!( assert!(
lref.lua.main_state == self.main_state, lref.lua as *const Lua == self as *const Lua,
"Lua instance passed Value created from a different Lua" "Lua instance passed Value created from a different Lua"
); );
match lref.ref_type {
RefType::Nil => ffi::lua_pushnil(self.state),
RefType::Stack { stack_slot } => {
ffi::lua_pushvalue(self.state, stack_slot);
}
RefType::Registry { registry_id } => {
ffi::lua_rawgeti( ffi::lua_rawgeti(
state, self.state,
ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTRYINDEX,
lref.registry_id as ffi::lua_Integer, registry_id as ffi::lua_Integer,
); );
} }
}
}
// Pops the topmost element of the stack and stores a reference to it in the // Pops the topmost element of the stack and stores a reference to it in the
// registry. // registry.
@ -742,12 +774,91 @@ impl Lua {
// `LuaRef` is dropped. // `LuaRef` is dropped.
// //
// pop_ref uses 1 extra stack space and does not call checkstack // pop_ref uses 1 extra stack space and does not call checkstack
pub(crate) unsafe fn pop_ref(&self, state: *mut ffi::lua_State) -> LuaRef { pub(crate) unsafe fn pop_ref(&self) -> LuaRef {
let registry_id = gc_guard(state, || ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)); for i in 0..REF_STACK_SIZE {
let ref_slot = &self.ref_stack_slots[i as usize];
if ref_slot.get() == 0 {
ref_slot.set(1);
ffi::lua_replace(self.state, i + 1);
return LuaRef {
lua: self,
ref_type: RefType::Stack { stack_slot: i + 1 },
};
}
}
let registry_id = gc_guard(self.state, || {
ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX)
});
if registry_id == ffi::LUA_REFNIL {
LuaRef { LuaRef {
lua: self, lua: self,
ref_type: RefType::Nil,
}
} else {
LuaRef {
lua: self,
ref_type: RefType::Registry {
registry_id: registry_id, registry_id: registry_id,
drop_unref: true, },
}
}
}
pub(crate) fn clone_ref(&self, lref: &LuaRef) -> LuaRef {
unsafe {
match lref.ref_type {
RefType::Nil => LuaRef {
lua: self,
ref_type: RefType::Nil,
},
RefType::Stack { stack_slot } => {
let ref_slot = &self.ref_stack_slots[(stack_slot - 1) as usize];
ref_slot.set(ref_slot.get() + 1);
LuaRef {
lua: self,
ref_type: RefType::Stack { stack_slot },
}
}
RefType::Registry { registry_id } => {
check_stack(self.state, 2);
ffi::lua_rawgeti(
self.state,
ffi::LUA_REGISTRYINDEX,
registry_id as ffi::lua_Integer,
);
let registry_id = gc_guard(self.state, || {
ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX)
});
LuaRef {
lua: self,
ref_type: RefType::Registry {
registry_id: registry_id,
},
}
}
}
}
}
pub(crate) fn drop_ref(&self, lref: &mut LuaRef) {
unsafe {
match lref.ref_type {
RefType::Nil => {}
RefType::Stack { stack_slot } => {
let ref_slot = &self.ref_stack_slots[(stack_slot - 1) as usize];
let ref_count = ref_slot.get();
rlua_assert!(ref_count > 0, "ref slot use count has gone below zero");
ref_slot.set(ref_count - 1);
if ref_count == 1 {
ffi::lua_pushnil(self.state);
ffi::lua_replace(self.state, stack_slot);
}
}
RefType::Registry { registry_id } => {
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, registry_id);
}
}
} }
} }
@ -772,13 +883,13 @@ impl Lua {
} }
} }
stack_err_guard(self.state, move || { stack_guard(self.state, move || {
check_stack(self.state, 5);
if let Some(table_id) = (*self.extra()).registered_userdata.get(&TypeId::of::<T>()) { if let Some(table_id) = (*self.extra()).registered_userdata.get(&TypeId::of::<T>()) {
return Ok(*table_id); return Ok(*table_id);
} }
check_stack(self.state, 6);
let mut methods = UserDataMethods { let mut methods = UserDataMethods {
methods: HashMap::new(), methods: HashMap::new(),
meta_methods: HashMap::new(), meta_methods: HashMap::new(),
@ -786,7 +897,7 @@ impl Lua {
}; };
T::add_methods(&mut methods); T::add_methods(&mut methods);
protect_lua_call(self.state, 0, 1, |state| { protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_newtable(state); ffi::lua_newtable(state);
})?; })?;
@ -794,22 +905,19 @@ impl Lua {
if has_methods { if has_methods {
push_string(self.state, "__index")?; push_string(self.state, "__index")?;
protect_lua_call(self.state, 0, 1, |state| { protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_newtable(state); ffi::lua_newtable(state);
})?; })?;
for (k, m) in methods.methods { for (k, m) in methods.methods {
push_string(self.state, &k)?; push_string(self.state, &k)?;
self.push_value( self.push_value(Value::Function(self.create_callback_function(m)?));
self.state, protect_lua_closure(self.state, 3, 1, |state| {
Value::Function(self.create_callback_function(m)?),
);
protect_lua_call(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; })?;
} }
protect_lua_call(self.state, 3, 1, |state| { protect_lua_closure(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; })?;
} }
@ -819,15 +927,12 @@ impl Lua {
push_string(self.state, "__index")?; push_string(self.state, "__index")?;
ffi::lua_pushvalue(self.state, -1); ffi::lua_pushvalue(self.state, -1);
ffi::lua_gettable(self.state, -3); ffi::lua_gettable(self.state, -3);
self.push_value( self.push_value(Value::Function(self.create_callback_function(m)?));
self.state, protect_lua_closure(self.state, 2, 1, |state| {
Value::Function(self.create_callback_function(m)?),
);
protect_lua_call(self.state, 2, 1, |state| {
ffi::lua_pushcclosure(state, meta_index_impl, 2); ffi::lua_pushcclosure(state, meta_index_impl, 2);
})?; })?;
protect_lua_call(self.state, 3, 1, |state| { protect_lua_closure(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; })?;
} else { } else {
@ -857,11 +962,8 @@ impl Lua {
MetaMethod::ToString => "__tostring", MetaMethod::ToString => "__tostring",
}; };
push_string(self.state, name)?; push_string(self.state, name)?;
self.push_value( self.push_value(Value::Function(self.create_callback_function(m)?));
self.state, protect_lua_closure(self.state, 3, 1, |state| {
Value::Function(self.create_callback_function(m)?),
);
protect_lua_call(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; })?;
} }
@ -869,13 +971,13 @@ impl Lua {
push_string(self.state, "__gc")?; push_string(self.state, "__gc")?;
ffi::lua_pushcfunction(self.state, userdata_destructor::<RefCell<T>>); ffi::lua_pushcfunction(self.state, userdata_destructor::<RefCell<T>>);
protect_lua_call(self.state, 3, 1, |state| { protect_lua_closure(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; })?;
push_string(self.state, "__metatable")?; push_string(self.state, "__metatable")?;
ffi::lua_pushboolean(self.state, 0); ffi::lua_pushboolean(self.state, 0);
protect_lua_call(self.state, 3, 1, |state| { protect_lua_closure(self.state, 3, 1, |state| {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; })?;
@ -918,7 +1020,7 @@ impl Lua {
// Ignores or `unwrap()`s 'm' errors, because this is assuming that nothing in the lua // Ignores or `unwrap()`s 'm' errors, because this is assuming that nothing in the lua
// standard library will have a `__gc` metamethod error. // standard library will have a `__gc` metamethod error.
stack_guard(state, || {
// Do not open the debug library, it can be used to cause unsafety. // Do not open the debug library, it can be used to cause unsafety.
ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1); ffi::luaL_requiref(state, cstr!("_G"), ffi::luaopen_base, 1);
ffi::luaL_requiref(state, cstr!("coroutine"), ffi::luaopen_coroutine, 1); ffi::luaL_requiref(state, cstr!("coroutine"), ffi::luaopen_coroutine, 1);
@ -978,12 +1080,15 @@ impl Lua {
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
})); }));
*(ffi::lua_getextraspace(state) as *mut *mut ExtraData) = extra_data; *(ffi::lua_getextraspace(state) as *mut *mut ExtraData) = extra_data;
});
rlua_assert!(ffi::lua_gettop(state) == 0, "stack leak during creation");
check_stack(state, REF_STACK_SIZE);
ffi::lua_settop(state, REF_STACK_SIZE);
Lua { Lua {
state, state,
main_state: state,
ephemeral: false, ephemeral: false,
ref_stack_slots: Default::default(),
} }
} }
@ -993,24 +1098,18 @@ impl Lua {
) -> Result<Function<'lua>> { ) -> Result<Function<'lua>> {
unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int { unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || { callback_error(state, || {
let lua = Lua {
state: state,
main_state: main_state(state),
ephemeral: true,
};
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL { if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL {
return Err(Error::CallbackDestructed); return Err(Error::CallbackDestructed);
} }
let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1)); let lua = Lua {
state: state,
ephemeral: true,
ref_stack_slots: Default::default(),
};
let args = lua.setup_callback_stack_slots();
let nargs = ffi::lua_gettop(state); let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1));
let mut args = MultiValue::new();
check_stack(state, 2);
for _ in 0..nargs {
args.push_front(lua.pop_value(state));
}
let results = (*func)(&lua, args)?; let results = (*func)(&lua, args)?;
let nresults = results.len() as c_int; let nresults = results.len() as c_int;
@ -1018,7 +1117,7 @@ impl Lua {
check_stack_err(state, nresults)?; check_stack_err(state, nresults)?;
for r in results { for r in results {
lua.push_value(state, r); lua.push_value(r);
} }
Ok(nresults) Ok(nresults)
@ -1026,8 +1125,8 @@ impl Lua {
} }
unsafe { unsafe {
stack_err_guard(self.state, move || { stack_guard(self.state, move || {
check_stack(self.state, 2); check_stack(self.state, 4);
push_userdata::<Callback>(self.state, func)?; push_userdata::<Callback>(self.state, func)?;
@ -1038,11 +1137,11 @@ impl Lua {
ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX); ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX);
ffi::lua_setmetatable(self.state, -2); ffi::lua_setmetatable(self.state, -2);
protect_lua_call(self.state, 1, 1, |state| { protect_lua_closure(self.state, 1, 1, |state| {
ffi::lua_pushcclosure(state, callback_call_impl, 1); ffi::lua_pushcclosure(state, callback_call_impl, 1);
})?; })?;
Ok(Function(self.pop_ref(self.state))) Ok(Function(self.pop_ref()))
}) })
} }
} }
@ -1052,8 +1151,8 @@ impl Lua {
T: UserData, T: UserData,
{ {
unsafe { unsafe {
stack_err_guard(self.state, move || { stack_guard(self.state, move || {
check_stack(self.state, 3); check_stack(self.state, 4);
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?; push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
@ -1065,13 +1164,104 @@ impl Lua {
ffi::lua_setmetatable(self.state, -2); ffi::lua_setmetatable(self.state, -2);
Ok(AnyUserData(self.pop_ref(self.state))) Ok(AnyUserData(self.pop_ref()))
}) })
} }
} }
// Set up the stack slot area in a callback, returning all arguments on the stack as a
// MultiValue
fn setup_callback_stack_slots<'lua>(&'lua self) -> MultiValue<'lua> {
unsafe {
check_stack(self.state, 2);
let nargs = ffi::lua_gettop(self.state);
let stack_nargs = cmp::min(REF_STACK_SIZE, nargs);
let mut args = MultiValue::new();
args.reserve(stack_nargs as usize);
for i in 0..stack_nargs {
let n = stack_nargs - i;
let make_ref = || {
self.ref_stack_slots[(n - 1) as usize].set(1);
LuaRef {
lua: self,
ref_type: RefType::Stack { stack_slot: n },
}
};
match ffi::lua_type(self.state, n) {
ffi::LUA_TNIL => {
args.push_front(Value::Nil);
}
ffi::LUA_TBOOLEAN => {
args.push_front(Value::Boolean(ffi::lua_toboolean(self.state, n) != 0));
}
ffi::LUA_TLIGHTUSERDATA => {
args.push_front(Value::LightUserData(LightUserData(
ffi::lua_touserdata(self.state, n),
)));
}
ffi::LUA_TNUMBER => if ffi::lua_isinteger(self.state, n) != 0 {
args.push_front(Value::Integer(ffi::lua_tointeger(self.state, n)));
} else {
args.push_front(Value::Number(ffi::lua_tonumber(self.state, n)));
},
ffi::LUA_TSTRING => {
args.push_front(Value::String(String(make_ref())));
}
ffi::LUA_TTABLE => {
args.push_front(Value::Table(Table(make_ref())));
}
ffi::LUA_TFUNCTION => {
args.push_front(Value::Function(Function(make_ref())));
}
ffi::LUA_TUSERDATA => {
if let Some(err) = get_wrapped_error(self.state, n).as_ref() {
args.push_front(Value::Error(err.clone()));
} else {
args.push_front(Value::UserData(AnyUserData(make_ref())));
}
}
ffi::LUA_TTHREAD => {
args.push_front(Value::Thread(Thread(make_ref())));
}
_ => unreachable!("internal error: LUA_TNONE in pop_value"),
}
}
if nargs < REF_STACK_SIZE {
check_stack(self.state, REF_STACK_SIZE - nargs);
ffi::lua_settop(self.state, REF_STACK_SIZE);
args
} else if nargs > REF_STACK_SIZE {
let mut extra_args = Vec::new();
extra_args.reserve((nargs - REF_STACK_SIZE) as usize);
for _ in REF_STACK_SIZE..nargs {
extra_args.push(self.pop_value());
}
extra_args.extend(args.into_vec_rev());
MultiValue::from_vec_rev(extra_args)
} else {
args
}
}
}
unsafe fn extra(&self) -> *mut ExtraData { unsafe fn extra(&self) -> *mut ExtraData {
*(ffi::lua_getextraspace(self.main_state) as *mut *mut ExtraData) *(ffi::lua_getextraspace(self.state) as *mut *mut ExtraData)
} }
} }
@ -1094,21 +1284,15 @@ impl<'scope> Scope<'scope> {
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
}); });
let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'callback, 'static>>(f); let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'callback, 'static>>(f);
let mut f = self.lua.create_callback_function(f)?; let f = self.lua.create_callback_function(f)?;
f.0.drop_unref = false;
let mut destructors = self.destructors.borrow_mut(); let mut destructors = self.destructors.borrow_mut();
let registry_id = f.0.registry_id; let f_destruct = f.0.clone();
destructors.push(Box::new(move |state| { destructors.push(Box::new(move || {
let state = f_destruct.lua.state;
stack_guard(state, || { stack_guard(state, || {
check_stack(state, 2); check_stack(state, 2);
f_destruct.lua.push_ref(&f_destruct);
ffi::lua_rawgeti(
state,
ffi::LUA_REGISTRYINDEX,
registry_id as ffi::lua_Integer,
);
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
ffi::lua_getupvalue(state, -1, 1); ffi::lua_getupvalue(state, -1, 1);
let ud = take_userdata::<Callback>(state); let ud = take_userdata::<Callback>(state);
@ -1159,19 +1343,14 @@ impl<'scope> Scope<'scope> {
T: UserData, T: UserData,
{ {
unsafe { unsafe {
let mut u = self.lua.do_create_userdata(data)?; let u = self.lua.do_create_userdata(data)?;
u.0.drop_unref = false;
let mut destructors = self.destructors.borrow_mut(); let mut destructors = self.destructors.borrow_mut();
let registry_id = u.0.registry_id; let u_destruct = u.0.clone();
destructors.push(Box::new(move |state| { destructors.push(Box::new(move || {
let state = u_destruct.lua.state;
stack_guard(state, || { stack_guard(state, || {
check_stack(state, 1); check_stack(state, 1);
ffi::lua_rawgeti( u_destruct.lua.push_ref(&u_destruct);
state,
ffi::LUA_REGISTRYINDEX,
registry_id as ffi::lua_Integer,
);
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
Box::new(take_userdata::<RefCell<T>>(state)) Box::new(take_userdata::<RefCell<T>>(state))
}) })
})); }));
@ -1186,14 +1365,11 @@ impl<'scope> Drop for Scope<'scope> {
// userdata type into two phases. This is so that, in the event a userdata drop panics, we // userdata type into two phases. This is so that, in the event a userdata drop panics, we
// can be sure that all of the userdata in Lua is actually invalidated. // can be sure that all of the userdata in Lua is actually invalidated.
let state = self.lua.state;
let to_drop = self.destructors let to_drop = self.destructors
.get_mut() .get_mut()
.drain(..) .drain(..)
.map(|destructor| destructor(state)) .map(|destructor| destructor())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
drop(to_drop); drop(to_drop);
} }
} }
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;

View File

@ -2,9 +2,9 @@ use std::ops::{Deref, DerefMut};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::result::Result as StdResult; use std::result::Result as StdResult;
use error::*; use error::Result;
use value::*; use value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti};
use lua::*; use lua::Lua;
/// Result is convertible to `MultiValue` following the common Lua idiom of returning the result /// Result is convertible to `MultiValue` following the common Lua idiom of returning the result
/// on success, or in the case of an error, returning `nil` and an error message. /// on success, or in the case of an error, returning `nil` and an error message.
@ -13,10 +13,10 @@ impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E>
let mut result = MultiValue::new(); let mut result = MultiValue::new();
match self { match self {
Ok(v) => result.push_back(v.to_lua(lua)?), Ok(v) => result.push_front(v.to_lua(lua)?),
Err(e) => { Err(e) => {
result.push_back(Nil); result.push_front(e.to_lua(lua)?);
result.push_back(e.to_lua(lua)?); result.push_front(Nil);
} }
} }
@ -27,7 +27,7 @@ impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E>
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T { impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T {
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::new(); let mut v = MultiValue::new();
v.push_back(self.to_lua(lua)?); v.push_front(self.to_lua(lua)?);
Ok(v) Ok(v)
} }
} }

View File

@ -71,7 +71,7 @@ impl<'lua> String<'lua> {
unsafe { unsafe {
stack_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 1); check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
rlua_assert!( rlua_assert!(
ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING, ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING,
"string ref is not string type" "string ref is not string type"
@ -82,7 +82,6 @@ impl<'lua> String<'lua> {
// string type // string type
let data = ffi::lua_tolstring(lua.state, -1, &mut size); let data = ffi::lua_tolstring(lua.state, -1, &mut size);
ffi::lua_pop(lua.state, 1);
slice::from_raw_parts(data as *const u8, size + 1) slice::from_raw_parts(data as *const u8, size + 1)
}) })
} }

View File

@ -1,9 +1,10 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::raw::c_int;
use ffi; use ffi;
use error::Result; use error::Result;
use util::*; use util::{check_stack, protect_lua, protect_lua_closure, stack_guard};
use types::{Integer, LuaRef}; use types::{Integer, LuaRef, RefType};
use value::{FromLua, ToLua}; use value::{FromLua, ToLua};
/// Handle to an internal Lua table. /// Handle to an internal Lua table.
@ -51,14 +52,17 @@ impl<'lua> Table<'lua> {
pub fn set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { pub fn set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 6); check_stack(lua.state, 6);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(key.to_lua(lua)?);
lua.push_value(lua.state, value.to_lua(lua)?); lua.push_value(value.to_lua(lua)?);
protect_lua_call(lua.state, 3, 0, |state| {
unsafe extern "C" fn set_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settable(state, -3); ffi::lua_settable(state, -3);
}) 1
}
protect_lua(lua.state, 3, set_table)
}) })
} }
} }
@ -94,12 +98,18 @@ impl<'lua> Table<'lua> {
pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> { pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 5); check_stack(lua.state, 5);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(key.to_lua(lua)?);
protect_lua_call(lua.state, 2, 1, |state| ffi::lua_gettable(state, -2))?;
V::from_lua(lua.pop_value(lua.state), lua) unsafe extern "C" fn get_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettable(state, -2);
1
}
protect_lua(lua.state, 2, get_table)?;
V::from_lua(lua.pop_value(), lua)
}) })
} }
} }
@ -108,13 +118,18 @@ impl<'lua> Table<'lua> {
pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> { pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 5); check_stack(lua.state, 5);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(key.to_lua(lua)?);
protect_lua_call(lua.state, 2, 1, |state| ffi::lua_gettable(state, -2))?;
unsafe extern "C" fn get_table(state: *mut ffi::lua_State) -> c_int {
ffi::lua_gettable(state, -2);
1
}
protect_lua(lua.state, 2, get_table)?;
let has = ffi::lua_isnil(lua.state, -1) == 0; let has = ffi::lua_isnil(lua.state, -1) == 0;
ffi::lua_pop(lua.state, 1);
Ok(has) Ok(has)
}) })
} }
@ -124,14 +139,18 @@ impl<'lua> Table<'lua> {
pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> { pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 6); check_stack(lua.state, 6);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(key.to_lua(lua)?);
lua.push_value(lua.state, value.to_lua(lua)?); lua.push_value(value.to_lua(lua)?);
protect_lua_call(lua.state, 3, 0, |state| {
unsafe extern "C" fn raw_set(state: *mut ffi::lua_State) -> c_int {
ffi::lua_rawset(state, -3); ffi::lua_rawset(state, -3);
})?; 0
}
protect_lua(lua.state, 3, raw_set)?;
Ok(()) Ok(())
}) })
} }
@ -141,13 +160,12 @@ impl<'lua> Table<'lua> {
pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> { pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 3); check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
lua.push_value(lua.state, key.to_lua(lua)?); lua.push_value(key.to_lua(lua)?);
ffi::lua_rawget(lua.state, -2); ffi::lua_rawget(lua.state, -2);
let res = V::from_lua(lua.pop_value(lua.state), lua)?; let res = V::from_lua(lua.pop_value(), lua)?;
ffi::lua_pop(lua.state, 1);
Ok(res) Ok(res)
}) })
} }
@ -161,10 +179,10 @@ impl<'lua> Table<'lua> {
pub fn len(&self) -> Result<Integer> { pub fn len(&self) -> Result<Integer> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 4); check_stack(lua.state, 4);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
protect_lua_call(lua.state, 1, 0, |state| ffi::luaL_len(state, -1)) protect_lua_closure(lua.state, 1, 0, |state| ffi::luaL_len(state, -1))
}) })
} }
} }
@ -175,9 +193,8 @@ impl<'lua> Table<'lua> {
unsafe { unsafe {
stack_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 1); check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
let len = ffi::lua_rawlen(lua.state, -1); let len = ffi::lua_rawlen(lua.state, -1);
ffi::lua_pop(lua.state, 1);
len as Integer len as Integer
}) })
} }
@ -191,13 +208,11 @@ impl<'lua> Table<'lua> {
unsafe { unsafe {
stack_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 1); check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 { if ffi::lua_getmetatable(lua.state, -1) == 0 {
ffi::lua_pop(lua.state, 1);
None None
} else { } else {
let table = Table(lua.pop_ref(lua.state)); let table = Table(lua.pop_ref());
ffi::lua_pop(lua.state, 1);
Some(table) Some(table)
} }
}) })
@ -213,14 +228,13 @@ impl<'lua> Table<'lua> {
unsafe { unsafe {
stack_guard(lua.state, move || { stack_guard(lua.state, move || {
check_stack(lua.state, 1); check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
if let Some(metatable) = metatable { if let Some(metatable) = metatable {
lua.push_ref(lua.state, &metatable.0); lua.push_ref(&metatable.0);
} else { } else {
ffi::lua_pushnil(lua.state); ffi::lua_pushnil(lua.state);
} }
ffi::lua_setmetatable(lua.state, -2); ffi::lua_setmetatable(lua.state, -2);
ffi::lua_pop(lua.state, 1);
}) })
} }
} }
@ -265,8 +279,7 @@ impl<'lua> Table<'lua> {
pub fn pairs<K: FromLua<'lua>, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> { pub fn pairs<K: FromLua<'lua>, V: FromLua<'lua>>(self) -> TablePairs<'lua, K, V> {
let next_key = Some(LuaRef { let next_key = Some(LuaRef {
lua: self.0.lua, lua: self.0.lua,
registry_id: ffi::LUA_REFNIL, ref_type: RefType::Nil,
drop_unref: true,
}); });
TablePairs { TablePairs {
@ -349,26 +362,18 @@ where
stack_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 6); check_stack(lua.state, 6);
lua.push_ref(lua.state, &self.table); lua.push_ref(&self.table);
lua.push_ref(lua.state, &next_key); lua.push_ref(&next_key);
match protect_lua_call(lua.state, 2, ffi::LUA_MULTRET, |state| { match protect_lua_closure(lua.state, 2, ffi::LUA_MULTRET, |state| {
if ffi::lua_next(state, -2) == 0 { ffi::lua_next(state, -2) != 0
0
} else {
1
}
}) { }) {
Ok(0) => { Ok(false) => None,
ffi::lua_pop(lua.state, 1); Ok(true) => {
None
}
Ok(_) => {
ffi::lua_pushvalue(lua.state, -2); ffi::lua_pushvalue(lua.state, -2);
let key = lua.pop_value(lua.state); let key = lua.pop_value();
let value = lua.pop_value(lua.state); let value = lua.pop_value();
self.next_key = Some(lua.pop_ref(lua.state)); self.next_key = Some(lua.pop_ref());
ffi::lua_pop(lua.state, 1);
Some((|| { Some((|| {
let key = K::from_lua(key, lua)?; let key = K::from_lua(key, lua)?;
@ -411,15 +416,13 @@ where
stack_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 5); check_stack(lua.state, 5);
lua.push_ref(lua.state, &self.table); lua.push_ref(&self.table);
match protect_lua_call(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index)) match protect_lua_closure(lua.state, 1, 1, |state| {
{ ffi::lua_geti(state, -1, index)
Ok(ffi::LUA_TNIL) => { }) {
ffi::lua_pop(lua.state, 1); Ok(ffi::LUA_TNIL) => None,
None
}
Ok(_) => { Ok(_) => {
let value = lua.pop_value(lua.state); let value = lua.pop_value();
self.index = Some(index + 1); self.index = Some(index + 1);
Some(V::from_lua(value, lua)) Some(V::from_lua(value, lua))
} }

View File

@ -744,3 +744,38 @@ fn too_many_binds() {
.is_err() .is_err()
); );
} }
#[test]
fn large_args() {
let lua = Lua::new();
let globals = lua.globals();
globals
.set(
"c",
lua.create_function(|_, args: Variadic<usize>| {
let mut s = 0;
for i in 0..args.len() {
s += i;
assert_eq!(i, args[i]);
}
Ok(s)
}).unwrap(),
)
.unwrap();
let f: Function = lua.eval(
r#"
return function(...)
return c(...)
end
"#,
None,
).unwrap();
assert_eq!(
f.call::<_, usize>((0..100).collect::<Variadic<usize>>())
.unwrap(),
4950
);
}

View File

@ -1,8 +1,8 @@
use std::os::raw::c_int; use std::os::raw::c_int;
use ffi; use ffi;
use error::*; use error::{Error, Result};
use util::*; use util::{check_stack, check_stack_err, error_traceback, pop_error, stack_guard};
use types::LuaRef; use types::LuaRef;
use value::{FromLuaMulti, MultiValue, ToLuaMulti}; use value::{FromLuaMulti, MultiValue, ToLuaMulti};
@ -78,10 +78,10 @@ impl<'lua> Thread<'lua> {
{ {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 1); check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1); let thread_state = ffi::lua_tothread(lua.state, -1);
let status = ffi::lua_status(thread_state); let status = ffi::lua_status(thread_state);
@ -93,11 +93,13 @@ impl<'lua> Thread<'lua> {
let args = args.to_lua_multi(lua)?; let args = args.to_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
check_stack_err(lua.state, nargs)?;
check_stack_err(thread_state, nargs + 1)?; check_stack_err(thread_state, nargs + 1)?;
for arg in args { for arg in args {
lua.push_value(thread_state, arg); lua.push_value(arg);
} }
ffi::lua_xmove(lua.state, thread_state, nargs);
let ret = ffi::lua_resume(thread_state, lua.state, nargs); let ret = ffi::lua_resume(thread_state, lua.state, nargs);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD { if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
@ -107,9 +109,11 @@ impl<'lua> Thread<'lua> {
let nresults = ffi::lua_gettop(thread_state); let nresults = ffi::lua_gettop(thread_state);
let mut results = MultiValue::new(); let mut results = MultiValue::new();
check_stack(thread_state, 2); ffi::lua_xmove(thread_state, lua.state, nresults);
check_stack(lua.state, 2);
for _ in 0..nresults { for _ in 0..nresults {
results.push_front(lua.pop_value(thread_state)); results.push_front(lua.pop_value());
} }
R::from_lua_multi(results, lua) R::from_lua_multi(results, lua)
}) })
@ -123,7 +127,7 @@ impl<'lua> Thread<'lua> {
stack_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 1); check_stack(lua.state, 1);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1); let thread_state = ffi::lua_tothread(lua.state, -1);
ffi::lua_pop(lua.state, 1); ffi::lua_pop(lua.state, 1);

View File

@ -1,4 +1,4 @@
use std::fmt; use std::{fmt, mem, ptr};
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -16,6 +16,9 @@ pub type Number = ffi::lua_Number;
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LightUserData(pub *mut c_void); pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> =
Box<Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
/// An auto generated key into the Lua registry. /// An auto generated key into the Lua registry.
/// ///
/// This is a handle into a value stored inside the Lua registry, similar to the normal handle types /// This is a handle into a value stored inside the Lua registry, similar to the normal handle types
@ -32,57 +35,54 @@ pub struct LightUserData(pub *mut c_void);
pub struct RegistryKey { pub struct RegistryKey {
pub(crate) registry_id: c_int, pub(crate) registry_id: c_int,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>, pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
pub(crate) drop_unref: bool,
} }
impl Drop for RegistryKey { impl Drop for RegistryKey {
fn drop(&mut self) { fn drop(&mut self) {
if self.drop_unref {
if let Some(list) = self.unref_list.lock().unwrap().as_mut() { if let Some(list) = self.unref_list.lock().unwrap().as_mut() {
list.push(self.registry_id); list.push(self.registry_id);
} }
} }
} }
impl RegistryKey {
// Destroys the RegistryKey without adding to the drop list
pub(crate) fn take(self) -> c_int {
let registry_id = self.registry_id;
unsafe {
ptr::read(&self.unref_list);
mem::forget(self);
}
registry_id
}
} }
pub(crate) type Callback<'lua, 'a> = #[derive(Debug)]
Box<Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>; pub(crate) enum RefType {
Nil,
Stack { stack_slot: c_int },
Registry { registry_id: c_int },
}
pub(crate) struct LuaRef<'lua> { pub(crate) struct LuaRef<'lua> {
pub lua: &'lua Lua, pub(crate) lua: &'lua Lua,
pub registry_id: c_int, pub(crate) ref_type: RefType,
pub drop_unref: bool,
} }
impl<'lua> fmt::Debug for LuaRef<'lua> { impl<'lua> fmt::Debug for LuaRef<'lua> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "LuaRef({})", self.registry_id) write!(f, "{:?}", self.ref_type)
} }
} }
impl<'lua> Clone for LuaRef<'lua> { impl<'lua> Clone for LuaRef<'lua> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
if self.drop_unref { self.lua.clone_ref(self)
unsafe {
self.lua.push_ref(self.lua.state, self);
self.lua.pop_ref(self.lua.state)
}
} else {
LuaRef {
lua: self.lua,
registry_id: self.registry_id,
drop_unref: self.drop_unref,
}
}
} }
} }
impl<'lua> Drop for LuaRef<'lua> { impl<'lua> Drop for LuaRef<'lua> {
fn drop(&mut self) { fn drop(&mut self) {
if self.drop_unref { self.lua.drop_ref(self)
unsafe {
ffi::luaL_unref(self.lua.state, ffi::LUA_REGISTRYINDEX, self.registry_id);
}
}
} }
} }

View File

@ -4,8 +4,8 @@ use std::collections::HashMap;
use std::string::String as StdString; use std::string::String as StdString;
use ffi; use ffi;
use error::*; use error::{Error, Result};
use util::*; use util::{check_stack, get_userdata, stack_guard};
use types::{Callback, LuaRef}; use types::{Callback, LuaRef};
use value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti}; use value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
use lua::Lua; use lua::Lua;
@ -415,10 +415,10 @@ impl<'lua> AnyUserData<'lua> {
{ {
unsafe { unsafe {
let lua = self.0.lua; let lua = self.0.lua;
stack_err_guard(lua.state, move || { stack_guard(lua.state, move || {
check_stack(lua.state, 3); check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
rlua_assert!( rlua_assert!(
ffi::lua_getmetatable(lua.state, -1) != 0, ffi::lua_getmetatable(lua.state, -1) != 0,
@ -432,11 +432,9 @@ impl<'lua> AnyUserData<'lua> {
); );
if ffi::lua_rawequal(lua.state, -1, -2) == 0 { if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
ffi::lua_pop(lua.state, 3);
Err(Error::UserDataTypeMismatch) Err(Error::UserDataTypeMismatch)
} else { } else {
let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3)); let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3));
ffi::lua_pop(lua.state, 3);
res res
} }
}) })
@ -451,12 +449,11 @@ impl<'lua> AnyUserData<'lua> {
pub fn set_user_value<V: ToLua<'lua>>(&self, v: V) -> Result<()> { pub fn set_user_value<V: ToLua<'lua>>(&self, v: V) -> Result<()> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 2); check_stack(lua.state, 2);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
lua.push_value(lua.state, v.to_lua(lua)?); lua.push_value(v.to_lua(lua)?);
ffi::lua_setuservalue(lua.state, -2); ffi::lua_setuservalue(lua.state, -2);
ffi::lua_pop(lua.state, 1);
Ok(()) Ok(())
}) })
} }
@ -468,12 +465,11 @@ impl<'lua> AnyUserData<'lua> {
pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> { pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> {
let lua = self.0.lua; let lua = self.0.lua;
unsafe { unsafe {
stack_err_guard(lua.state, || { stack_guard(lua.state, || {
check_stack(lua.state, 3); check_stack(lua.state, 3);
lua.push_ref(lua.state, &self.0); lua.push_ref(&self.0);
ffi::lua_getuservalue(lua.state, -1); ffi::lua_getuservalue(lua.state, -1);
let res = V::from_lua(lua.pop_value(lua.state), lua)?; let res = V::from_lua(lua.pop_value(), lua)?;
ffi::lua_pop(lua.state, 1);
Ok(res) Ok(res)
}) })
} }

View File

@ -27,75 +27,72 @@ pub unsafe fn check_stack_err(state: *mut ffi::lua_State, amount: c_int) -> Resu
} }
} }
// Run an operation on a lua_State and ensure that there are no stack leaks and the stack is pub struct StackGuard {
// restored on panic. state: *mut ffi::lua_State,
top: c_int,
}
impl StackGuard {
// Creates a StackGuard instance with wa record of the stack size, and on Drop will check the
// stack size and drop any extra elements. If the stack size at the end is *smaller* than at
// the beginning, this is considered a fatal logic error and will result in an abort.
pub unsafe fn new(state: *mut ffi::lua_State) -> StackGuard {
StackGuard {
state,
top: ffi::lua_gettop(state),
}
}
}
impl Drop for StackGuard {
fn drop(&mut self) {
unsafe {
let top = ffi::lua_gettop(self.state);
if top > self.top {
ffi::lua_settop(self.state, self.top);
} else if top < self.top {
rlua_panic!("{} too many stack values popped", self.top - top);
}
}
}
}
// Run an operation on a lua_State and restores the stack state at the end, using `StackGuard`.
pub unsafe fn stack_guard<F, R>(state: *mut ffi::lua_State, op: F) -> R pub unsafe fn stack_guard<F, R>(state: *mut ffi::lua_State, op: F) -> R
where where
F: FnOnce() -> R, F: FnOnce() -> R,
{ {
let begin = ffi::lua_gettop(state); let _stack_guard = StackGuard::new(state);
op()
let res = match catch_unwind(AssertUnwindSafe(op)) {
Ok(r) => r,
Err(p) => {
let top = ffi::lua_gettop(state);
if top > begin {
ffi::lua_settop(state, begin);
}
resume_unwind(p);
}
};
let top = ffi::lua_gettop(state);
if top > begin {
ffi::lua_settop(state, begin);
rlua_panic!("expected stack to be {}, got {}", begin, top);
} else if top < begin {
rlua_abort!("{} too many stack values popped", begin - top);
} }
res // Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is
// always LUA_MULTRET. Internally uses 2 extra stack spaces, and does not call checkstack.
// Provided function must *never* panic.
pub unsafe fn protect_lua(
state: *mut ffi::lua_State,
nargs: c_int,
f: unsafe extern "C" fn(*mut ffi::lua_State) -> c_int,
) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
} }
// Run an operation on a lua_State and automatically clean up the stack on error. Takes the let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1);
// lua_State and an operation to run. If the operation results in success, then the stack is ffi::lua_remove(state, stack_start + 1);
// inspected to make sure there is not a stack leak, and otherwise this is a logic error and will
// panic. If the operation results in an error, or if the operation panics, the stack is shrunk to
// the value before the call.
pub unsafe fn stack_err_guard<F, R>(state: *mut ffi::lua_State, op: F) -> Result<R>
where
F: FnOnce() -> Result<R>,
{
let begin = ffi::lua_gettop(state);
let res = match catch_unwind(AssertUnwindSafe(op)) { if ret == ffi::LUA_OK {
Ok(r) => r, Ok(())
Err(p) => {
let top = ffi::lua_gettop(state);
if top > begin {
ffi::lua_settop(state, begin);
}
resume_unwind(p);
}
};
let top = ffi::lua_gettop(state);
if res.is_ok() {
if top > begin {
ffi::lua_settop(state, begin);
rlua_panic!("expected stack to be {}, got {}", begin, top);
} else if top < begin {
rlua_abort!("{} too many stack values popped", begin - top);
}
} else { } else {
if top > begin { Err(pop_error(state, ret))
ffi::lua_settop(state, begin);
} else if top < begin {
rlua_abort!("{} too many stack values popped", begin - top);
} }
} }
res
}
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. // Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a // Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
@ -104,7 +101,7 @@ where
// values are assumed to match the `nresults` param. Internally uses 3 extra stack spaces, and does // values are assumed to match the `nresults` param. Internally uses 3 extra stack spaces, and does
// not call checkstack. Provided function must *not* panic, and since it will generally be // not call checkstack. Provided function must *not* panic, and since it will generally be
// lonjmping, should not contain any values that implement Drop. // lonjmping, should not contain any values that implement Drop.
pub unsafe fn protect_lua_call<F, R>( pub unsafe fn protect_lua_closure<F, R>(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
nargs: c_int, nargs: c_int,
nresults: c_int, nresults: c_int,
@ -115,7 +112,7 @@ where
R: Copy, R: Copy,
{ {
struct Params<F, R> { struct Params<F, R> {
function: *const F, function: F,
result: R, result: R,
nresults: c_int, nresults: c_int,
} }
@ -127,7 +124,7 @@ where
let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>; let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>;
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
(*params).result = (*(*params).function)(state); (*params).result = ((*params).function)(state);
if (*params).nresults == ffi::LUA_MULTRET { if (*params).nresults == ffi::LUA_MULTRET {
ffi::lua_gettop(state) ffi::lua_gettop(state)
@ -140,10 +137,12 @@ where
ffi::lua_pushcfunction(state, error_traceback); ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, do_call::<F, R>); ffi::lua_pushcfunction(state, do_call::<F, R>);
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2); ffi::lua_rotate(state, stack_start + 1, 2);
}
let mut params = Params { let mut params = Params {
function: &f, function: f,
result: mem::uninitialized(), result: mem::uninitialized(),
nresults, nresults,
}; };
@ -173,8 +172,9 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
"pop_error called with non-error return code" "pop_error called with non-error return code"
); );
if let Some(err) = pop_wrapped_error(state) { if let Some(err) = get_wrapped_error(state, -1).as_ref() {
err ffi::lua_pop(state, 1);
err.clone()
} else if is_wrapped_panic(state, -1) { } else if is_wrapped_panic(state, -1) {
let panic = get_userdata::<WrappedPanic>(state, -1); let panic = get_userdata::<WrappedPanic>(state, -1);
if let Some(p) = (*panic).0.take() { if let Some(p) = (*panic).0.take() {
@ -212,7 +212,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
ffi::LUA_ERRMEM => { ffi::LUA_ERRMEM => {
// This should be impossible, as we set the lua allocator to one that aborts // This should be impossible, as we set the lua allocator to one that aborts
// instead of failing. // instead of failing.
rlua_abort!("impossible Lua allocation error, aborting!") rlua_abort!("impossible Lua allocation error")
} }
ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
_ => rlua_panic!("unrecognized lua error code"), _ => rlua_panic!("unrecognized lua error code"),
@ -222,14 +222,14 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
// Internally uses 4 stack spaces, does not call checkstack // Internally uses 4 stack spaces, does not call checkstack
pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) -> Result<()> { pub unsafe fn push_string(state: *mut ffi::lua_State, s: &str) -> Result<()> {
protect_lua_call(state, 0, 1, |state| { protect_lua_closure(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
}) })
} }
// Internally uses 4 stack spaces, does not call checkstack // Internally uses 4 stack spaces, does not call checkstack
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> { pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T) -> Result<()> {
let ud = protect_lua_call(state, 0, 1, move |state| { let ud = protect_lua_closure(state, 0, 1, move |state| {
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
})?; })?;
ptr::write(ud, t); ptr::write(ud, t);
@ -300,7 +300,7 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
if ffi::lua_checkstack(state, 2) == 0 { if ffi::lua_checkstack(state, 2) == 0 {
// If we don't have enough stack space to even check the error type, do nothing // If we don't have enough stack space to even check the error type, do nothing
} else if is_wrapped_error(state, 1) { } else if let Some(error) = get_wrapped_error(state, 1).as_ref() {
let traceback = if ffi::lua_checkstack(state, LUA_TRACEBACK_STACK) != 0 { let traceback = if ffi::lua_checkstack(state, LUA_TRACEBACK_STACK) != 0 {
gc_guard(state, || { gc_guard(state, || {
ffi::luaL_traceback(state, state, ptr::null(), 0); ffi::luaL_traceback(state, state, ptr::null(), 0);
@ -314,7 +314,9 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
"not enough stack space for traceback".to_owned() "not enough stack space for traceback".to_owned()
}; };
let error = pop_wrapped_error(state).unwrap(); let error = error.clone();
ffi::lua_pop(state, 1);
push_wrapped_error( push_wrapped_error(
state, state,
Error::CallbackError { Error::CallbackError {
@ -404,14 +406,6 @@ pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
} }
} }
// Does not call lua_checkstack, uses 1 stack space.
pub unsafe fn main_state(state: *mut ffi::lua_State) -> *mut ffi::lua_State {
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
let main_state = ffi::lua_tothread(state, -1);
ffi::lua_pop(state, 1);
main_state
}
// Pushes a WrappedError::Error to the top of the stack. Uses two stack spaces and does not call // Pushes a WrappedError::Error to the top of the stack. Uses two stack spaces and does not call
// lua_checkstack. // lua_checkstack.
pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) { pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) {
@ -424,18 +418,26 @@ pub unsafe fn push_wrapped_error(state: *mut ffi::lua_State, err: Error) {
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
} }
// Pops a WrappedError off of the top of the stack, if it is a WrappedError. If it is not a // Checks if the value at the given index is a WrappedError, and if it is returns a pointer to it,
// WrappedError, returns None and does not pop anything. Uses 2 stack spaces and does not call // otherwise returns null. Uses 2 stack spaces and does not call lua_checkstack.
// lua_checkstack. pub unsafe fn get_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> *const Error {
pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<Error> { let userdata = ffi::lua_touserdata(state, index);
if !is_wrapped_error(state, -1) { if userdata.is_null() {
None return ptr::null();
}
if ffi::lua_getmetatable(state, index) == 0 {
return ptr::null();
}
get_error_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
if res {
&(*get_userdata::<WrappedError>(state, -1)).0
} else { } else {
let err = &*get_userdata::<WrappedError>(state, -1); ptr::null()
// We are assuming here that Error::clone() cannot panic.
let err = err.0.clone();
ffi::lua_pop(state, 1);
Some(err)
} }
} }
@ -466,9 +468,8 @@ pub unsafe fn init_error_metatables(state: *mut ffi::lua_State) {
ffi::luaL_checkstack(state, 2, ptr::null()); ffi::luaL_checkstack(state, 2, ptr::null());
callback_error(state, || { callback_error(state, || {
if is_wrapped_error(state, -1) { if let Some(error) = get_wrapped_error(state, -1).as_ref() {
let error = get_userdata::<WrappedError>(state, -1); let error_str = error.to_string();
let error_str = (*error).0.to_string();
gc_guard(state, || { gc_guard(state, || {
ffi::lua_pushlstring( ffi::lua_pushlstring(
state, state,
@ -587,24 +588,6 @@ unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>)
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
} }
// Checks if the value at the given index is a WrappedError, uses 2 stack spaces and does not call
// lua_checkstack.
unsafe fn is_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> bool {
let userdata = ffi::lua_touserdata(state, index);
if userdata.is_null() {
return false;
}
if ffi::lua_getmetatable(state, index) == 0 {
return false;
}
get_error_metatable(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
res
}
// Checks if the value at the given index is a WrappedPanic. Uses 2 stack spaces and does not call // Checks if the value at the given index is a WrappedPanic. Uses 2 stack spaces and does not call
// lua_checkstack. // lua_checkstack.
unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool { unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool {

View File

@ -1,9 +1,7 @@
use std::str; use std::{slice, str, vec};
use std::ops::{Deref, DerefMut}; use std::iter::{self, FromIterator};
use std::iter::FromIterator;
use std::collections::VecDeque;
use error::*; use error::{Error, Result};
use types::{Integer, LightUserData, Number}; use types::{Integer, LightUserData, Number};
use string::String; use string::String;
use table::Table; use table::Table;
@ -76,41 +74,77 @@ pub trait FromLua<'lua>: Sized {
/// Multiple Lua values used for both argument passing and also for multiple return values. /// Multiple Lua values used for both argument passing and also for multiple return values.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MultiValue<'lua>(VecDeque<Value<'lua>>); pub struct MultiValue<'lua>(Vec<Value<'lua>>);
impl<'lua> MultiValue<'lua> { impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values. /// Creates an empty `MultiValue` containing no values.
pub fn new() -> MultiValue<'lua> { pub fn new() -> MultiValue<'lua> {
MultiValue(VecDeque::new()) MultiValue(Vec::new())
} }
} }
impl<'lua> FromIterator<Value<'lua>> for MultiValue<'lua> { impl<'lua> FromIterator<Value<'lua>> for MultiValue<'lua> {
fn from_iter<I: IntoIterator<Item = Value<'lua>>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = Value<'lua>>>(iter: I) -> Self {
MultiValue(VecDeque::from_iter(iter)) MultiValue::from_vec(Vec::from_iter(iter))
} }
} }
impl<'lua> IntoIterator for MultiValue<'lua> { impl<'lua> IntoIterator for MultiValue<'lua> {
type Item = Value<'lua>; type Item = Value<'lua>;
type IntoIter = <VecDeque<Value<'lua>> as IntoIterator>::IntoIter; type IntoIter = iter::Rev<vec::IntoIter<Value<'lua>>>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.0.into_iter() self.0.into_iter().rev()
} }
} }
impl<'lua> Deref for MultiValue<'lua> { impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
type Target = VecDeque<Value<'lua>>; type Item = &'a Value<'lua>;
type IntoIter = iter::Rev<slice::Iter<'a, Value<'lua>>>;
fn deref(&self) -> &Self::Target { fn into_iter(self) -> Self::IntoIter {
&self.0 (&self.0).into_iter().rev()
} }
} }
impl<'lua> DerefMut for MultiValue<'lua> { impl<'lua> MultiValue<'lua> {
fn deref_mut(&mut self) -> &mut Self::Target { pub fn from_vec(mut v: Vec<Value<'lua>>) -> MultiValue<'lua> {
&mut self.0 v.reverse();
MultiValue(v)
}
pub fn into_vec(self) -> Vec<Value<'lua>> {
let mut v = self.0;
v.reverse();
v
}
pub fn from_vec_rev(v: Vec<Value<'lua>>) -> MultiValue<'lua> {
MultiValue(v)
}
pub fn into_vec_rev(self) -> Vec<Value<'lua>> {
self.0
}
pub fn reserve(&mut self, size: usize) {
self.0.reserve(size);
}
pub fn push_front(&mut self, value: Value<'lua>) {
self.0.push(value);
}
pub fn pop_front(&mut self) -> Option<Value<'lua>> {
self.0.pop()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> {
self.0.iter().rev()
} }
} }