diff options
author | kyren <kerriganw@gmail.com> | 2018-03-11 23:20:10 -0400 |
---|---|---|
committer | kyren <kerriganw@gmail.com> | 2018-03-11 23:20:10 -0400 |
commit | 601e9f4cac93a8e87f070b724a3841df4aa91a6a (patch) | |
tree | 5bcf3d9ad5af3bbabb0f7cf72ea225ef2b9f0f05 | |
parent | 84ee394b1d36e947ce35a8d9a40e7e7381e72189 (diff) | |
download | mlua-601e9f4cac93a8e87f070b724a3841df4aa91a6a.zip |
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.
-rw-r--r-- | src/conversion.rs | 2 | ||||
-rw-r--r-- | src/ffi.rs | 1 | ||||
-rw-r--r-- | src/function.rs | 25 | ||||
-rw-r--r-- | src/lua.rs | 614 | ||||
-rw-r--r-- | src/multi.rs | 14 | ||||
-rw-r--r-- | src/string.rs | 3 | ||||
-rw-r--r-- | src/table.rs | 139 | ||||
-rw-r--r-- | src/tests/mod.rs | 35 | ||||
-rw-r--r-- | src/thread.rs | 20 | ||||
-rw-r--r-- | src/types.rs | 58 | ||||
-rw-r--r-- | src/userdata.rs | 24 | ||||
-rw-r--r-- | src/util.rs | 199 | ||||
-rw-r--r-- | src/value.rs | 68 |
13 files changed, 717 insertions, 485 deletions
diff --git a/src/conversion.rs b/src/conversion.rs index a6749f5..d7db574 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap}; use std::hash::{BuildHasher, Hash}; use std::string::String as StdString; -use error::*; +use error::{Error, Result}; use types::{Integer, LightUserData, Number}; use string::String; use table::Table; @@ -123,6 +123,7 @@ extern "C" { 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_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_isnumber(state: *mut lua_State, index: c_int) -> c_int; diff --git a/src/function.rs b/src/function.rs index 246e852..0f97dac 100644 --- a/src/function.rs +++ b/src/function.rs @@ -2,8 +2,9 @@ use std::ptr; use std::os::raw::c_int; use ffi; -use error::*; -use util::*; +use error::{Error, Result}; +use util::{check_stack, check_stack_err, error_traceback, pop_error, protect_lua_closure, + stack_guard}; use types::LuaRef; 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> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { let args = args.to_lua_multi(lua)?; let nargs = args.len() as c_int; check_stack_err(lua.state, nargs + 3)?; ffi::lua_pushcfunction(lua.state, error_traceback); let stack_start = ffi::lua_gettop(lua.state); - lua.push_ref(lua.state, &self.0); + lua.push_ref(&self.0); 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); if ret != ffi::LUA_OK { @@ -82,7 +83,7 @@ impl<'lua> Function<'lua> { let mut results = MultiValue::new(); check_stack(lua.state, 2); for _ in 0..nresults { - results.push_front(lua.pop_value(lua.state)); + results.push_front(lua.pop_value()); } ffi::lua_pop(lua.state, 1); R::from_lua_multi(results, lua) @@ -144,7 +145,7 @@ impl<'lua> Function<'lua> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { let args = args.to_lua_multi(lua)?; let nargs = args.len() as c_int; @@ -152,18 +153,18 @@ impl<'lua> Function<'lua> { return Err(Error::BindError); } - check_stack_err(lua.state, nargs + 3)?; - lua.push_ref(lua.state, &self.0); + check_stack_err(lua.state, nargs + 5)?; + lua.push_ref(&self.0); ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer); 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); })?; - Ok(Function(lua.pop_ref(lua.state))) + Ok(Function(lua.pop_ref())) }) } } @@ -1,19 +1,23 @@ -use std::{mem, ptr, str}; +use std::{cmp, mem, ptr, str}; use std::sync::{Arc, Mutex}; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::ffi::CString; use std::any::{Any, TypeId}; use std::marker::PhantomData; use std::collections::HashMap; use std::os::raw::{c_char, c_int, c_void}; +use std::panic::{RefUnwindSafe, UnwindSafe}; use libc; use ffi; -use error::*; -use util::*; +use error::{Error, Result}; +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 types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey}; +use types::{Callback, Integer, LightUserData, LuaRef, Number, RefType, RegistryKey}; use string::String; use table::Table; use function::Function; @@ -23,8 +27,8 @@ use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods}; /// Top level Lua struct which holds the Lua state itself. pub struct Lua { pub(crate) state: *mut ffi::lua_State, - main_state: *mut ffi::lua_State, 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 @@ -35,7 +39,7 @@ pub struct Lua { /// [`Lua::scope`]: struct.Lua.html#method.scope pub struct Scope<'scope> { 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: PhantomData<&'scope mut &'scope ()>, } @@ -46,14 +50,25 @@ struct ExtraData { 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 {} +impl UnwindSafe for Lua {} +impl RefUnwindSafe for Lua {} + impl Drop for Lua { fn drop(&mut self) { unsafe { if !self.ephemeral { 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); *(*extra_data).registry_unref_list.lock().unwrap() = None; @@ -87,7 +102,7 @@ impl Lua { /// Equivalent to Lua's `load` function. pub fn load(&self, source: &str, name: Option<&str>) -> Result<Function> { unsafe { - stack_err_guard(self.state, || { + stack_guard(self.state, || { check_stack(self.state, 1); match if let Some(name) = name { @@ -111,7 +126,7 @@ impl Lua { 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)), } }) @@ -152,10 +167,10 @@ impl Lua { /// Pass a `&str` slice to Lua, creating and returning an interned Lua string. pub fn create_string(&self, s: &str) -> Result<String> { unsafe { - stack_err_guard(self.state, || { + stack_guard(self.state, || { check_stack(self.state, 4); 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. pub fn create_table(&self) -> Result<Table> { unsafe { - stack_err_guard(self.state, || { - check_stack(self.state, 4); - protect_lua_call(self.state, 0, 1, |state| { + stack_guard(self.state, || { + check_stack(self.state, 3); + unsafe extern "C" fn new_table(state: *mut ffi::lua_State) -> c_int { ffi::lua_newtable(state); - })?; - Ok(Table(self.pop_ref(self.state))) + 1 + } + protect_lua(self.state, 0, new_table)?; + Ok(Table(self.pop_ref())) }) } } @@ -181,20 +198,24 @@ impl Lua { I: IntoIterator<Item = (K, V)>, { unsafe { - stack_err_guard(self.state, || { - check_stack(self.state, 6); - protect_lua_call(self.state, 0, 1, |state| { + stack_guard(self.state, || { + check_stack(self.state, 5); + unsafe extern "C" fn new_table(state: *mut ffi::lua_State) -> c_int { ffi::lua_newtable(state); - })?; + 1 + } + protect_lua(self.state, 0, new_table)?; for (k, v) in cont { - self.push_value(self.state, k.to_lua(self)?); - self.push_value(self.state, v.to_lua(self)?); - protect_lua_call(self.state, 3, 1, |state| { + self.push_value(k.to_lua(self)?); + self.push_value(v.to_lua(self)?); + unsafe extern "C" fn raw_set(state: *mut ffi::lua_State) -> c_int { ffi::lua_rawset(state, -3); - })?; + 1 + } + protect_lua(self.state, 3, raw_set)?; } - Ok(Table(self.pop_ref(self.state))) + Ok(Table(self.pop_ref())) }) } } @@ -301,14 +322,15 @@ impl Lua { /// Equivalent to `coroutine.create`. pub fn create_thread<'lua>(&'lua self, func: Function<'lua>) -> Result<Thread<'lua>> { unsafe { - stack_err_guard(self.state, move || { + stack_guard(self.state, move || { check_stack(self.state, 2); let thread_state = - protect_lua_call(self.state, 0, 1, |state| ffi::lua_newthread(state))?; - self.push_ref(thread_state, &func.0); + protect_lua_closure(self.state, 0, 1, |state| ffi::lua_newthread(state))?; + 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 || { check_stack(self.state, 2); 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 { Value::String(s) => Ok(s), v => unsafe { - stack_err_guard(self.state, || { + stack_guard(self.state, || { check_stack(self.state, 4); let ty = v.type_name(); - self.push_value(self.state, v); - let s = - protect_lua_call(self.state, 1, 1, |state| ffi::lua_tostring(state, -1))?; + self.push_value(v); + let s = protect_lua_closure(self.state, 1, 1, |state| { + ffi::lua_tostring(state, -1) + })?; if s.is_null() { - ffi::lua_pop(self.state, 1); Err(Error::FromLuaConversionError { from: ty, to: "String", message: Some("expected string or number".to_string()), }) } else { - Ok(String(self.pop_ref(self.state))) + Ok(String(self.pop_ref())) } }) }, @@ -402,10 +424,9 @@ impl Lua { stack_guard(self.state, || { check_stack(self.state, 2); let ty = v.type_name(); - self.push_value(self.state, v); + self.push_value(v); let mut isint = 0; let i = ffi::lua_tointegerx(self.state, -1, &mut isint); - ffi::lua_pop(self.state, 1); if isint == 0 { Err(Error::FromLuaConversionError { from: ty, @@ -431,10 +452,9 @@ impl Lua { stack_guard(self.state, || { check_stack(self.state, 2); let ty = v.type_name(); - self.push_value(self.state, v); + self.push_value(v); let mut isnum = 0; let n = ffi::lua_tonumberx(self.state, -1, &mut isnum); - ffi::lua_pop(self.state, 1); if isnum == 0 { Err(Error::FromLuaConversionError { from: ty, @@ -482,15 +502,17 @@ impl Lua { t: T, ) -> Result<()> { unsafe { - stack_err_guard(self.state, || { + stack_guard(self.state, || { check_stack(self.state, 5); 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); - }) + 0 + } + protect_lua(self.state, 2, set_registry) }) } } @@ -503,15 +525,17 @@ impl Lua { /// [`set_named_registry_value`]: #method.set_named_registry_value pub fn named_registry_value<'lua, T: FromLua<'lua>>(&'lua self, name: &str) -> Result<T> { unsafe { - stack_err_guard(self.state, || { + stack_guard(self.state, || { check_stack(self.state, 4); push_string(self.state, name)?; - protect_lua_call(self.state, 1, 1, |state| { - ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX) - })?; + unsafe extern "C" fn get_registry(state: *mut ffi::lua_State) -> c_int { + 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, || { 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, || { ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX) }); @@ -542,7 +566,6 @@ impl Lua { Ok(RegistryKey { registry_id, unref_list: (*self.extra()).registry_unref_list.clone(), - drop_unref: true, }) }) } @@ -560,14 +583,14 @@ impl Lua { return Err(Error::MismatchedRegistryKey); } - stack_err_guard(self.state, || { + stack_guard(self.state, || { check_stack(self.state, 2); ffi::lua_rawgeti( self.state, ffi::LUA_REGISTRYINDEX, 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 /// [`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 { if !Arc::ptr_eq(&key.unref_list, &(*self.extra()).registry_unref_list) { return Err(Error::MismatchedRegistryKey); } - ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.registry_id); - key.drop_unref = false; + ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, key.take()); Ok(()) } } @@ -620,119 +642,129 @@ impl Lua { } // 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 { Value::Nil => { - ffi::lua_pushnil(state); + ffi::lua_pushnil(self.state); } 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) => { - ffi::lua_pushlightuserdata(state, ud.0); + ffi::lua_pushlightuserdata(self.state, ud.0); } Value::Integer(i) => { - ffi::lua_pushinteger(state, i); + ffi::lua_pushinteger(self.state, i); } Value::Number(n) => { - ffi::lua_pushnumber(state, n); + ffi::lua_pushnumber(self.state, n); } Value::String(s) => { - self.push_ref(state, &s.0); + self.push_ref(&s.0); } Value::Table(t) => { - self.push_ref(state, &t.0); + self.push_ref(&t.0); } Value::Function(f) => { - self.push_ref(state, &f.0); + self.push_ref(&f.0); } Value::Thread(t) => { - self.push_ref(state, &t.0); + self.push_ref(&t.0); } Value::UserData(ud) => { - self.push_ref(state, &ud.0); + self.push_ref(&ud.0); } Value::Error(e) => { - push_wrapped_error(state, e); + push_wrapped_error(self.state, e); } } } // Uses 2 stack spaces, does not call checkstack - pub(crate) unsafe fn pop_value(&self, state: *mut ffi::lua_State) -> Value { - match ffi::lua_type(state, -1) { + pub(crate) unsafe fn pop_value(&self) -> Value { + match ffi::lua_type(self.state, -1) { ffi::LUA_TNIL => { - ffi::lua_pop(state, 1); + ffi::lua_pop(self.state, 1); Nil } ffi::LUA_TBOOLEAN => { - let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0); - ffi::lua_pop(state, 1); + let b = Value::Boolean(ffi::lua_toboolean(self.state, -1) != 0); + ffi::lua_pop(self.state, 1); b } ffi::LUA_TLIGHTUSERDATA => { - let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(state, -1))); - ffi::lua_pop(state, 1); + let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(self.state, -1))); + ffi::lua_pop(self.state, 1); ud } - ffi::LUA_TNUMBER => if ffi::lua_isinteger(state, -1) != 0 { - let i = Value::Integer(ffi::lua_tointeger(state, -1)); - ffi::lua_pop(state, 1); + ffi::LUA_TNUMBER => if ffi::lua_isinteger(self.state, -1) != 0 { + let i = Value::Integer(ffi::lua_tointeger(self.state, -1)); + ffi::lua_pop(self.state, 1); i } else { - let n = Value::Number(ffi::lua_tonumber(state, -1)); - ffi::lua_pop(state, 1); + let n = Value::Number(ffi::lua_tonumber(self.state, -1)); + ffi::lua_pop(self.state, 1); 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 => { // 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 // 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) } 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"), } } // 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!( - 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" ); - ffi::lua_rawgeti( - state, - ffi::LUA_REGISTRYINDEX, - lref.registry_id as ffi::lua_Integer, - ); + 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( + self.state, + ffi::LUA_REGISTRYINDEX, + registry_id as ffi::lua_Integer, + ); + } + } } // Pops the topmost element of the stack and stores a reference to it in the @@ -742,12 +774,91 @@ impl Lua { // `LuaRef` is dropped. // // 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 { - let registry_id = gc_guard(state, || ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)); - LuaRef { - lua: self, - registry_id: registry_id, - drop_unref: true, + pub(crate) unsafe fn pop_ref(&self) -> LuaRef { + 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 { + lua: self, + ref_type: RefType::Nil, + } + } else { + LuaRef { + lua: self, + ref_type: RefType::Registry { + registry_id: registry_id, + }, + } + } + } + + 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 || { - check_stack(self.state, 5); - + stack_guard(self.state, move || { if let Some(table_id) = (*self.extra()).registered_userdata.get(&TypeId::of::<T>()) { return Ok(*table_id); } + check_stack(self.state, 6); + let mut methods = UserDataMethods { methods: HashMap::new(), meta_methods: HashMap::new(), @@ -786,7 +897,7 @@ impl Lua { }; 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); })?; @@ -794,22 +905,19 @@ impl Lua { if has_methods { 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); })?; for (k, m) in methods.methods { push_string(self.state, &k)?; - self.push_value( - self.state, - Value::Function(self.create_callback_function(m)?), - ); - protect_lua_call(self.state, 3, 1, |state| { + self.push_value(Value::Function(self.create_callback_function(m)?)); + protect_lua_closure(self.state, 3, 1, |state| { 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); })?; } @@ -819,15 +927,12 @@ impl Lua { push_string(self.state, "__index")?; ffi::lua_pushvalue(self.state, -1); ffi::lua_gettable(self.state, -3); - self.push_value( - self.state, - Value::Function(self.create_callback_function(m)?), - ); - protect_lua_call(self.state, 2, 1, |state| { + self.push_value(Value::Function(self.create_callback_function(m)?)); + protect_lua_closure(self.state, 2, 1, |state| { 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); })?; } else { @@ -857,11 +962,8 @@ impl Lua { MetaMethod::ToString => "__tostring", }; push_string(self.state, name)?; - self.push_value( - self.state, - Value::Function(self.create_callback_function(m)?), - ); - protect_lua_call(self.state, 3, 1, |state| { + self.push_value(Value::Function(self.create_callback_function(m)?)); + protect_lua_closure(self.state, 3, 1, |state| { ffi::lua_rawset(state, -3); })?; } @@ -869,13 +971,13 @@ impl Lua { push_string(self.state, "__gc")?; 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); })?; push_string(self.state, "__metatable")?; 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); })?; @@ -918,72 +1020,75 @@ impl Lua { // Ignores or `unwrap()`s 'm' errors, because this is assuming that nothing in the lua // standard library will have a `__gc` metamethod error. - stack_guard(state, || { - // 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!("coroutine"), ffi::luaopen_coroutine, 1); - ffi::luaL_requiref(state, cstr!("table"), ffi::luaopen_table, 1); - ffi::luaL_requiref(state, cstr!("io"), ffi::luaopen_io, 1); - ffi::luaL_requiref(state, cstr!("os"), ffi::luaopen_os, 1); - ffi::luaL_requiref(state, cstr!("string"), ffi::luaopen_string, 1); - ffi::luaL_requiref(state, cstr!("utf8"), ffi::luaopen_utf8, 1); - ffi::luaL_requiref(state, cstr!("math"), ffi::luaopen_math, 1); - ffi::luaL_requiref(state, cstr!("package"), ffi::luaopen_package, 1); - ffi::lua_pop(state, 9); - - init_error_metatables(state); - - if load_debug { - ffi::luaL_requiref(state, cstr!("debug"), ffi::luaopen_debug, 1); - ffi::lua_pop(state, 1); - } - // Create the function metatable + // 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!("coroutine"), ffi::luaopen_coroutine, 1); + ffi::luaL_requiref(state, cstr!("table"), ffi::luaopen_table, 1); + ffi::luaL_requiref(state, cstr!("io"), ffi::luaopen_io, 1); + ffi::luaL_requiref(state, cstr!("os"), ffi::luaopen_os, 1); + ffi::luaL_requiref(state, cstr!("string"), ffi::luaopen_string, 1); + ffi::luaL_requiref(state, cstr!("utf8"), ffi::luaopen_utf8, 1); + ffi::luaL_requiref(state, cstr!("math"), ffi::luaopen_math, 1); + ffi::luaL_requiref(state, cstr!("package"), ffi::luaopen_package, 1); + ffi::lua_pop(state, 9); + + init_error_metatables(state); + + if load_debug { + ffi::luaL_requiref(state, cstr!("debug"), ffi::luaopen_debug, 1); + ffi::lua_pop(state, 1); + } - ffi::lua_pushlightuserdata( - state, - &FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void, - ); + // Create the function metatable + + ffi::lua_pushlightuserdata( + state, + &FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void, + ); - ffi::lua_newtable(state); + ffi::lua_newtable(state); - push_string(state, "__gc").unwrap(); - ffi::lua_pushcfunction(state, userdata_destructor::<Callback>); - ffi::lua_rawset(state, -3); + push_string(state, "__gc").unwrap(); + ffi::lua_pushcfunction(state, userdata_destructor::<Callback>); + ffi::lua_rawset(state, -3); - push_string(state, "__metatable").unwrap(); - ffi::lua_pushboolean(state, 0); - ffi::lua_rawset(state, -3); + push_string(state, "__metatable").unwrap(); + ffi::lua_pushboolean(state, 0); + ffi::lua_rawset(state, -3); - ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX); + ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX); - // Override pcall and xpcall with versions that cannot be used to catch rust panics. + // Override pcall and xpcall with versions that cannot be used to catch rust panics. - ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); + ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); - push_string(state, "pcall").unwrap(); - ffi::lua_pushcfunction(state, safe_pcall); - ffi::lua_rawset(state, -3); + push_string(state, "pcall").unwrap(); + ffi::lua_pushcfunction(state, safe_pcall); + ffi::lua_rawset(state, -3); - push_string(state, "xpcall").unwrap(); - ffi::lua_pushcfunction(state, safe_xpcall); - ffi::lua_rawset(state, -3); + push_string(state, "xpcall").unwrap(); + ffi::lua_pushcfunction(state, safe_xpcall); + ffi::lua_rawset(state, -3); - ffi::lua_pop(state, 1); + ffi::lua_pop(state, 1); - // Create ExtraData, and place it in the lua_State "extra space" + // Create ExtraData, and place it in the lua_State "extra space" - let extra_data = Box::into_raw(Box::new(ExtraData { - registered_userdata: HashMap::new(), - registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), - })); - *(ffi::lua_getextraspace(state) as *mut *mut ExtraData) = extra_data; - }); + let extra_data = Box::into_raw(Box::new(ExtraData { + registered_userdata: HashMap::new(), + registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), + })); + *(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 { state, - main_state: state, ephemeral: false, + ref_stack_slots: Default::default(), } } @@ -993,32 +1098,26 @@ impl Lua { ) -> Result<Function<'lua>> { unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int { callback_error(state, || { + if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL { + return Err(Error::CallbackDestructed); + } + let lua = Lua { state: state, - main_state: main_state(state), ephemeral: true, + ref_stack_slots: Default::default(), }; - - if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL { - return Err(Error::CallbackDestructed); - } + let args = lua.setup_callback_stack_slots(); let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1)); - let nargs = ffi::lua_gettop(state); - 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 nresults = results.len() as c_int; check_stack_err(state, nresults)?; for r in results { - lua.push_value(state, r); + lua.push_value(r); } Ok(nresults) @@ -1026,8 +1125,8 @@ impl Lua { } unsafe { - stack_err_guard(self.state, move || { - check_stack(self.state, 2); + stack_guard(self.state, move || { + check_stack(self.state, 4); push_userdata::<Callback>(self.state, func)?; @@ -1038,11 +1137,11 @@ impl Lua { ffi::lua_rawget(self.state, ffi::LUA_REGISTRYINDEX); 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); })?; - Ok(Function(self.pop_ref(self.state))) + Ok(Function(self.pop_ref())) }) } } @@ -1052,8 +1151,8 @@ impl Lua { T: UserData, { unsafe { - stack_err_guard(self.state, move || { - check_stack(self.state, 3); + stack_guard(self.state, move || { + check_stack(self.state, 4); push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?; @@ -1065,13 +1164,104 @@ impl Lua { 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 { - *(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) }); 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 registry_id = f.0.registry_id; - destructors.push(Box::new(move |state| { + let f_destruct = f.0.clone(); + destructors.push(Box::new(move || { + let state = f_destruct.lua.state; stack_guard(state, || { check_stack(state, 2); - - ffi::lua_rawgeti( - state, - ffi::LUA_REGISTRYINDEX, - registry_id as ffi::lua_Integer, - ); - ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); + f_destruct.lua.push_ref(&f_destruct); ffi::lua_getupvalue(state, -1, 1); let ud = take_userdata::<Callback>(state); @@ -1159,19 +1343,14 @@ impl<'scope> Scope<'scope> { T: UserData, { unsafe { - let mut u = self.lua.do_create_userdata(data)?; - u.0.drop_unref = false; + let u = self.lua.do_create_userdata(data)?; let mut destructors = self.destructors.borrow_mut(); - let registry_id = u.0.registry_id; - destructors.push(Box::new(move |state| { + let u_destruct = u.0.clone(); + destructors.push(Box::new(move || { + let state = u_destruct.lua.state; stack_guard(state, || { check_stack(state, 1); - ffi::lua_rawgeti( - state, - ffi::LUA_REGISTRYINDEX, - registry_id as ffi::lua_Integer, - ); - ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id); + u_destruct.lua.push_ref(&u_destruct); 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 // can be sure that all of the userdata in Lua is actually invalidated. - let state = self.lua.state; let to_drop = self.destructors .get_mut() .drain(..) - .map(|destructor| destructor(state)) + .map(|destructor| destructor()) .collect::<Vec<_>>(); drop(to_drop); } } - -static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0; diff --git a/src/multi.rs b/src/multi.rs index e0c10e0..3b22c5a 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -2,9 +2,9 @@ use std::ops::{Deref, DerefMut}; use std::iter::FromIterator; use std::result::Result as StdResult; -use error::*; -use value::*; -use lua::*; +use error::Result; +use value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti}; +use lua::Lua; /// 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. @@ -13,10 +13,10 @@ impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E> let mut result = MultiValue::new(); match self { - Ok(v) => result.push_back(v.to_lua(lua)?), + Ok(v) => result.push_front(v.to_lua(lua)?), Err(e) => { - result.push_back(Nil); - result.push_back(e.to_lua(lua)?); + result.push_front(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 { fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { let mut v = MultiValue::new(); - v.push_back(self.to_lua(lua)?); + v.push_front(self.to_lua(lua)?); Ok(v) } } diff --git a/src/string.rs b/src/string.rs index ba127ef..d854b16 100644 --- a/src/string.rs +++ b/src/string.rs @@ -71,7 +71,7 @@ impl<'lua> String<'lua> { unsafe { stack_guard(lua.state, || { check_stack(lua.state, 1); - lua.push_ref(lua.state, &self.0); + lua.push_ref(&self.0); rlua_assert!( ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING, "string ref is not string type" @@ -82,7 +82,6 @@ impl<'lua> String<'lua> { // string type 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) }) } diff --git a/src/table.rs b/src/table.rs index 0b7875c..693e017 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,9 +1,10 @@ use std::marker::PhantomData; +use std::os::raw::c_int; use ffi; use error::Result; -use util::*; -use types::{Integer, LuaRef}; +use util::{check_stack, protect_lua, protect_lua_closure, stack_guard}; +use types::{Integer, LuaRef, RefType}; use value::{FromLua, ToLua}; /// 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<()> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 6); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - lua.push_value(lua.state, value.to_lua(lua)?); - protect_lua_call(lua.state, 3, 0, |state| { + lua.push_ref(&self.0); + lua.push_value(key.to_lua(lua)?); + lua.push_value(value.to_lua(lua)?); + + unsafe extern "C" fn set_table(state: *mut ffi::lua_State) -> c_int { 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> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 5); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, 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) + lua.push_ref(&self.0); + lua.push_value(key.to_lua(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> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 5); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - protect_lua_call(lua.state, 2, 1, |state| ffi::lua_gettable(state, -2))?; + lua.push_ref(&self.0); + lua.push_value(key.to_lua(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)?; + let has = ffi::lua_isnil(lua.state, -1) == 0; - ffi::lua_pop(lua.state, 1); 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<()> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 6); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); - lua.push_value(lua.state, value.to_lua(lua)?); - protect_lua_call(lua.state, 3, 0, |state| { + lua.push_ref(&self.0); + lua.push_value(key.to_lua(lua)?); + lua.push_value(value.to_lua(lua)?); + + unsafe extern "C" fn raw_set(state: *mut ffi::lua_State) -> c_int { ffi::lua_rawset(state, -3); - })?; + 0 + } + protect_lua(lua.state, 3, raw_set)?; + Ok(()) }) } @@ -141,13 +160,12 @@ impl<'lua> Table<'lua> { pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 3); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, key.to_lua(lua)?); + lua.push_ref(&self.0); + lua.push_value(key.to_lua(lua)?); ffi::lua_rawget(lua.state, -2); - let res = V::from_lua(lua.pop_value(lua.state), lua)?; - ffi::lua_pop(lua.state, 1); + let res = V::from_lua(lua.pop_value(), lua)?; Ok(res) }) } @@ -161,10 +179,10 @@ impl<'lua> Table<'lua> { pub fn len(&self) -> Result<Integer> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 4); - lua.push_ref(lua.state, &self.0); - protect_lua_call(lua.state, 1, 0, |state| ffi::luaL_len(state, -1)) + lua.push_ref(&self.0); + protect_lua_closure(lua.state, 1, 0, |state| ffi::luaL_len(state, -1)) }) } } @@ -175,9 +193,8 @@ impl<'lua> Table<'lua> { unsafe { stack_guard(lua.state, || { 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); - ffi::lua_pop(lua.state, 1); len as Integer }) } @@ -191,13 +208,11 @@ impl<'lua> Table<'lua> { unsafe { stack_guard(lua.state, || { 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 { - ffi::lua_pop(lua.state, 1); None } else { - let table = Table(lua.pop_ref(lua.state)); - ffi::lua_pop(lua.state, 1); + let table = Table(lua.pop_ref()); Some(table) } }) @@ -213,14 +228,13 @@ impl<'lua> Table<'lua> { unsafe { stack_guard(lua.state, move || { check_stack(lua.state, 1); - lua.push_ref(lua.state, &self.0); + lua.push_ref(&self.0); if let Some(metatable) = metatable { - lua.push_ref(lua.state, &metatable.0); + lua.push_ref(&metatable.0); } else { ffi::lua_pushnil(lua.state); } 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> { let next_key = Some(LuaRef { lua: self.0.lua, - registry_id: ffi::LUA_REFNIL, - drop_unref: true, + ref_type: RefType::Nil, }); TablePairs { @@ -349,26 +362,18 @@ where stack_guard(lua.state, || { check_stack(lua.state, 6); - lua.push_ref(lua.state, &self.table); - lua.push_ref(lua.state, &next_key); + lua.push_ref(&self.table); + lua.push_ref(&next_key); - match protect_lua_call(lua.state, 2, ffi::LUA_MULTRET, |state| { - if ffi::lua_next(state, -2) == 0 { - 0 - } else { - 1 - } + match protect_lua_closure(lua.state, 2, ffi::LUA_MULTRET, |state| { + ffi::lua_next(state, -2) != 0 }) { - Ok(0) => { - ffi::lua_pop(lua.state, 1); - None - } - Ok(_) => { + Ok(false) => None, + Ok(true) => { ffi::lua_pushvalue(lua.state, -2); - let key = lua.pop_value(lua.state); - let value = lua.pop_value(lua.state); - self.next_key = Some(lua.pop_ref(lua.state)); - ffi::lua_pop(lua.state, 1); + let key = lua.pop_value(); + let value = lua.pop_value(); + self.next_key = Some(lua.pop_ref()); Some((|| { let key = K::from_lua(key, lua)?; @@ -411,15 +416,13 @@ where stack_guard(lua.state, || { check_stack(lua.state, 5); - lua.push_ref(lua.state, &self.table); - match protect_lua_call(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index)) - { - Ok(ffi::LUA_TNIL) => { - ffi::lua_pop(lua.state, 1); - None - } + lua.push_ref(&self.table); + match protect_lua_closure(lua.state, 1, 1, |state| { + ffi::lua_geti(state, -1, index) + }) { + Ok(ffi::LUA_TNIL) => None, Ok(_) => { - let value = lua.pop_value(lua.state); + let value = lua.pop_value(); self.index = Some(index + 1); Some(V::from_lua(value, lua)) } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0490c7d..7491e8d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -744,3 +744,38 @@ fn too_many_binds() { .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 + ); +} diff --git a/src/thread.rs b/src/thread.rs index 1c8d753..7174240 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -1,8 +1,8 @@ use std::os::raw::c_int; use ffi; -use error::*; -use util::*; +use error::{Error, Result}; +use util::{check_stack, check_stack_err, error_traceback, pop_error, stack_guard}; use types::LuaRef; use value::{FromLuaMulti, MultiValue, ToLuaMulti}; @@ -78,10 +78,10 @@ impl<'lua> Thread<'lua> { { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { 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 status = ffi::lua_status(thread_state); @@ -93,11 +93,13 @@ impl<'lua> Thread<'lua> { let args = args.to_lua_multi(lua)?; let nargs = args.len() as c_int; + check_stack_err(lua.state, nargs)?; check_stack_err(thread_state, nargs + 1)?; 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); 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 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 { - results.push_front(lua.pop_value(thread_state)); + results.push_front(lua.pop_value()); } R::from_lua_multi(results, lua) }) @@ -123,7 +127,7 @@ impl<'lua> Thread<'lua> { stack_guard(lua.state, || { 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); ffi::lua_pop(lua.state, 1); diff --git a/src/types.rs b/src/types.rs index 4b09f24..48fe6c0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, mem, ptr}; use std::os::raw::{c_int, c_void}; use std::sync::{Arc, Mutex}; @@ -16,6 +16,9 @@ pub type Number = ffi::lua_Number; #[derive(Debug, Copy, Clone, Eq, PartialEq)] 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. /// /// 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(crate) registry_id: c_int, pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>, - pub(crate) drop_unref: bool, } impl Drop for RegistryKey { fn drop(&mut self) { - if self.drop_unref { - if let Some(list) = self.unref_list.lock().unwrap().as_mut() { - list.push(self.registry_id); - } + if let Some(list) = self.unref_list.lock().unwrap().as_mut() { + list.push(self.registry_id); } } } -pub(crate) type Callback<'lua, 'a> = - Box<Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>; +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 + } +} + +#[derive(Debug)] +pub(crate) enum RefType { + Nil, + Stack { stack_slot: c_int }, + Registry { registry_id: c_int }, +} pub(crate) struct LuaRef<'lua> { - pub lua: &'lua Lua, - pub registry_id: c_int, - pub drop_unref: bool, + pub(crate) lua: &'lua Lua, + pub(crate) ref_type: RefType, } impl<'lua> fmt::Debug for LuaRef<'lua> { 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> { fn clone(&self) -> Self { - if self.drop_unref { - 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, - } - } + self.lua.clone_ref(self) } } impl<'lua> Drop for LuaRef<'lua> { fn drop(&mut self) { - if self.drop_unref { - unsafe { - ffi::luaL_unref(self.lua.state, ffi::LUA_REGISTRYINDEX, self.registry_id); - } - } + self.lua.drop_ref(self) } } diff --git a/src/userdata.rs b/src/userdata.rs index d40eb1f..6ff6c07 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::string::String as StdString; use ffi; -use error::*; -use util::*; +use error::{Error, Result}; +use util::{check_stack, get_userdata, stack_guard}; use types::{Callback, LuaRef}; use value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti}; use lua::Lua; @@ -415,10 +415,10 @@ impl<'lua> AnyUserData<'lua> { { unsafe { let lua = self.0.lua; - stack_err_guard(lua.state, move || { + stack_guard(lua.state, move || { check_stack(lua.state, 3); - lua.push_ref(lua.state, &self.0); + lua.push_ref(&self.0); rlua_assert!( ffi::lua_getmetatable(lua.state, -1) != 0, @@ -432,11 +432,9 @@ impl<'lua> AnyUserData<'lua> { ); if ffi::lua_rawequal(lua.state, -1, -2) == 0 { - ffi::lua_pop(lua.state, 3); Err(Error::UserDataTypeMismatch) } else { let res = func(&*get_userdata::<RefCell<T>>(lua.state, -3)); - ffi::lua_pop(lua.state, 3); res } }) @@ -451,12 +449,11 @@ impl<'lua> AnyUserData<'lua> { pub fn set_user_value<V: ToLua<'lua>>(&self, v: V) -> Result<()> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 2); - lua.push_ref(lua.state, &self.0); - lua.push_value(lua.state, v.to_lua(lua)?); + lua.push_ref(&self.0); + lua.push_value(v.to_lua(lua)?); ffi::lua_setuservalue(lua.state, -2); - ffi::lua_pop(lua.state, 1); Ok(()) }) } @@ -468,12 +465,11 @@ impl<'lua> AnyUserData<'lua> { pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> { let lua = self.0.lua; unsafe { - stack_err_guard(lua.state, || { + stack_guard(lua.state, || { check_stack(lua.state, 3); - lua.push_ref(lua.state, &self.0); + lua.push_ref(&self.0); ffi::lua_getuservalue(lua.state, -1); - let res = V::from_lua(lua.pop_value(lua.state), lua)?; - ffi::lua_pop(lua.state, 1); + let res = V::from_lua(lua.pop_value(), lua)?; Ok(res) }) } diff --git a/src/util.rs b/src/util.rs index ba4dda8..724fde8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -27,74 +27,71 @@ 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 -// restored on panic. -pub unsafe fn stack_guard<F, R>(state: *mut ffi::lua_State, op: F) -> R -where - F: FnOnce() -> R, -{ - let begin = ffi::lua_gettop(state); +pub struct StackGuard { + state: *mut ffi::lua_State, + top: c_int, +} - 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); +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), } - }; - - 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 +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 automatically clean up the stack on error. Takes the -// lua_State and an operation to run. If the operation results in success, then the stack is -// 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> +// 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 where - F: FnOnce() -> Result<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); - } - }; +// 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; - 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); - } + ffi::lua_pushcfunction(state, error_traceback); + ffi::lua_pushcfunction(state, f); + if nargs > 0 { + ffi::lua_rotate(state, stack_start + 1, 2); + } + + let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1); + ffi::lua_remove(state, stack_start + 1); + + if ret == ffi::LUA_OK { + Ok(()) } else { - if top > begin { - ffi::lua_settop(state, begin); - } else if top < begin { - rlua_abort!("{} too many stack values popped", begin - top); - } + Err(pop_error(state, ret)) } - res } // Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way. @@ -104,7 +101,7 @@ where // 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 // 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, nargs: c_int, nresults: c_int, @@ -115,7 +112,7 @@ where R: Copy, { struct Params<F, R> { - function: *const F, + function: F, result: R, nresults: c_int, } @@ -127,7 +124,7 @@ where let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>; ffi::lua_pop(state, 1); - (*params).result = (*(*params).function)(state); + (*params).result = ((*params).function)(state); if (*params).nresults == ffi::LUA_MULTRET { ffi::lua_gettop(state) @@ -140,10 +137,12 @@ where ffi::lua_pushcfunction(state, error_traceback); ffi::lua_pushcfunction(state, do_call::<F, R>); - ffi::lua_rotate(state, stack_start + 1, 2); + if nargs > 0 { + ffi::lua_rotate(state, stack_start + 1, 2); + } let mut params = Params { - function: &f, + function: f, result: mem::uninitialized(), 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" ); - if let Some(err) = pop_wrapped_error(state) { - err + if let Some(err) = get_wrapped_error(state, -1).as_ref() { + ffi::lua_pop(state, 1); + err.clone() } else if is_wrapped_panic(state, -1) { let panic = get_userdata::<WrappedPanic>(state, -1); 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 => { // This should be impossible, as we set the lua allocator to one that aborts // instead of failing. - rlua_abort!("impossible Lua allocation error, aborting!") + rlua_abort!("impossible Lua allocation error") } ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string), _ => 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 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()); }) } // Internally uses 4 stack spaces, does not call checkstack 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 })?; 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 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 { gc_guard(state, || { 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() }; - let error = pop_wrapped_error(state).unwrap(); + let error = error.clone(); + ffi::lua_pop(state, 1); + push_wrapped_error( state, 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 // lua_checkstack. 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); } -// Pops a WrappedError off of the top of the stack, if it is a WrappedError. If it is not a -// WrappedError, returns None and does not pop anything. Uses 2 stack spaces and does not call -// lua_checkstack. -pub unsafe fn pop_wrapped_error(state: *mut ffi::lua_State) -> Option<Error> { - if !is_wrapped_error(state, -1) { - None +// Checks if the value at the given index is a WrappedError, and if it is returns a pointer to it, +// otherwise returns null. Uses 2 stack spaces and does not call lua_checkstack. +pub unsafe fn get_wrapped_error(state: *mut ffi::lua_State, index: c_int) -> *const Error { + let userdata = ffi::lua_touserdata(state, index); + if userdata.is_null() { + 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 { - let err = &*get_userdata::<WrappedError>(state, -1); - // We are assuming here that Error::clone() cannot panic. - let err = err.0.clone(); - ffi::lua_pop(state, 1); - Some(err) + ptr::null() } } @@ -466,9 +468,8 @@ pub unsafe fn init_error_metatables(state: *mut ffi::lua_State) { ffi::luaL_checkstack(state, 2, ptr::null()); callback_error(state, || { - if is_wrapped_error(state, -1) { - let error = get_userdata::<WrappedError>(state, -1); - let error_str = (*error).0.to_string(); + if let Some(error) = get_wrapped_error(state, -1).as_ref() { + let error_str = error.to_string(); gc_guard(state, || { ffi::lua_pushlstring( state, @@ -587,24 +588,6 @@ unsafe fn push_wrapped_panic(state: *mut ffi::lua_State, panic: Box<Any + Send>) 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 // lua_checkstack. unsafe fn is_wrapped_panic(state: *mut ffi::lua_State, index: c_int) -> bool { diff --git a/src/value.rs b/src/value.rs index b3b4f2f..18c8810 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,9 +1,7 @@ -use std::str; -use std::ops::{Deref, DerefMut}; -use std::iter::FromIterator; -use std::collections::VecDeque; +use std::{slice, str, vec}; +use std::iter::{self, FromIterator}; -use error::*; +use error::{Error, Result}; use types::{Integer, LightUserData, Number}; use string::String; 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. #[derive(Debug, Clone)] -pub struct MultiValue<'lua>(VecDeque<Value<'lua>>); +pub struct MultiValue<'lua>(Vec<Value<'lua>>); impl<'lua> MultiValue<'lua> { /// Creates an empty `MultiValue` containing no values. pub fn new() -> MultiValue<'lua> { - MultiValue(VecDeque::new()) + MultiValue(Vec::new()) } } impl<'lua> FromIterator<Value<'lua>> for MultiValue<'lua> { 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> { 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 { - self.0.into_iter() + self.0.into_iter().rev() } } -impl<'lua> Deref for MultiValue<'lua> { - type Target = VecDeque<Value<'lua>>; +impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> { + type Item = &'a Value<'lua>; + type IntoIter = iter::Rev<slice::Iter<'a, Value<'lua>>>; - fn deref(&self) -> &Self::Target { - &self.0 + fn into_iter(self) -> Self::IntoIter { + (&self.0).into_iter().rev() } } -impl<'lua> DerefMut for MultiValue<'lua> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 +impl<'lua> MultiValue<'lua> { + pub fn from_vec(mut v: Vec<Value<'lua>>) -> MultiValue<'lua> { + 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() } } |