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::string::String as StdString;
use error::*;
use error::{Error, Result};
use types::{Integer, LightUserData, Number};
use string::String;
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_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;

View file

@ -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()))
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -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)
}
}

View file

@ -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)
})
}

View file

@ -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))
}

View file

@ -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
);
}

View file

@ -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);

View file

@ -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)
}
}

View file

@ -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)
})
}

View file

@ -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 struct StackGuard {
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
where
F: FnOnce() -> R,
{
let begin = ffi::lua_gettop(state);
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
let _stack_guard = StackGuard::new(state);
op()
}
// 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>
where
F: FnOnce() -> Result<R>,
{
let begin = ffi::lua_gettop(state);
// 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 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 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 {
if top > begin {
ffi::lua_settop(state, begin);
} 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 {
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 {

View file

@ -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()
}
}