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

View File

@ -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
}
Ok(Table(self.pop_ref(self.state)))
protect_lua(self.state, 3, raw_set)?;
}
Ok(Table(self.pop_ref()))
})
}
}
@ -301,14 +322,15 @@ impl Lua {
/// Equivalent to `coroutine.create`.
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,120 +642,130 @@ 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"
);
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(
state,
self.state,
ffi::LUA_REGISTRYINDEX,
lref.registry_id as ffi::lua_Integer,
registry_id as ffi::lua_Integer,
);
}
}
}
// Pops the topmost element of the stack and stores a reference to it in the
// registry.
@ -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));
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,
drop_unref: true,
},
}
}
}
pub(crate) fn clone_ref(&self, lref: &LuaRef) -> LuaRef {
unsafe {
match lref.ref_type {
RefType::Nil => LuaRef {
lua: self,
ref_type: RefType::Nil,
},
RefType::Stack { stack_slot } => {
let ref_slot = &self.ref_stack_slots[(stack_slot - 1) as usize];
ref_slot.set(ref_slot.get() + 1);
LuaRef {
lua: self,
ref_type: RefType::Stack { stack_slot },
}
}
RefType::Registry { registry_id } => {
check_stack(self.state, 2);
ffi::lua_rawgeti(
self.state,
ffi::LUA_REGISTRYINDEX,
registry_id as ffi::lua_Integer,
);
let registry_id = gc_guard(self.state, || {
ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX)
});
LuaRef {
lua: self,
ref_type: RefType::Registry {
registry_id: registry_id,
},
}
}
}
}
}
pub(crate) fn drop_ref(&self, lref: &mut LuaRef) {
unsafe {
match lref.ref_type {
RefType::Nil => {}
RefType::Stack { stack_slot } => {
let ref_slot = &self.ref_stack_slots[(stack_slot - 1) as usize];
let ref_count = ref_slot.get();
rlua_assert!(ref_count > 0, "ref slot use count has gone below zero");
ref_slot.set(ref_count - 1);
if ref_count == 1 {
ffi::lua_pushnil(self.state);
ffi::lua_replace(self.state, stack_slot);
}
}
RefType::Registry { registry_id } => {
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, registry_id);
}
}
}
}
@ -772,13 +883,13 @@ impl Lua {
}
}
stack_err_guard(self.state, move || {
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,7 +1020,7 @@ 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);
@ -978,12 +1080,15 @@ impl Lua {
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,24 +1098,18 @@ impl Lua {
) -> Result<Function<'lua>> {
unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || {
let lua = Lua {
state: state,
main_state: main_state(state),
ephemeral: true,
};
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL {
return Err(Error::CallbackDestructed);
}
let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1));
let lua = Lua {
state: state,
ephemeral: true,
ref_stack_slots: Default::default(),
};
let args = lua.setup_callback_stack_slots();
let nargs = ffi::lua_gettop(state);
let mut args = MultiValue::new();
check_stack(state, 2);
for _ in 0..nargs {
args.push_front(lua.pop_value(state));
}
let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1));
let results = (*func)(&lua, args)?;
let nresults = results.len() as c_int;
@ -1018,7 +1117,7 @@ impl Lua {
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;

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);
}
}
}
impl RegistryKey {
// Destroys the RegistryKey without adding to the drop list
pub(crate) fn take(self) -> c_int {
let registry_id = self.registry_id;
unsafe {
ptr::read(&self.unref_list);
mem::forget(self);
}
registry_id
}
}
pub(crate) type Callback<'lua, 'a> =
Box<Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
#[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,75 +27,72 @@ pub unsafe fn check_stack_err(state: *mut ffi::lua_State, amount: c_int) -> Resu
}
}
// Run an operation on a lua_State and ensure that there are no stack leaks and the stack is
// 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);
let _stack_guard = StackGuard::new(state);
op()
}
res
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is
// always LUA_MULTRET. Internally uses 2 extra stack spaces, and does not call checkstack.
// Provided function must *never* panic.
pub unsafe fn protect_lua(
state: *mut ffi::lua_State,
nargs: c_int,
f: unsafe extern "C" fn(*mut ffi::lua_State) -> c_int,
) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
}
// Run an operation on a lua_State and automatically clean up the stack on error. Takes the
// 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);
let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1);
ffi::lua_remove(state, stack_start + 1);
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);
}
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.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
@ -104,7 +101,7 @@ where
// values are assumed to match the `nresults` param. Internally uses 3 extra stack spaces, and does
// 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>);
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()
}
}