Skip to content

Commit

Permalink
Fastpath for LuaString/integer/float conversion from Lua
Browse files Browse the repository at this point in the history
  • Loading branch information
khvzak committed Sep 7, 2024
1 parent 9c86eef commit 7957c68
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 14 deletions.
63 changes: 55 additions & 8 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ impl FromLua for String {
message: Some("expected string or number".to_string()),
})
}

unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TSTRING {
ffi::lua_xpush(state, lua.ref_thread(), idx);
return Ok(String(lua.pop_ref_thread()));
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}

impl IntoLua for Table {
Expand Down Expand Up @@ -385,7 +396,8 @@ impl FromLua for StdString {
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
if ffi::lua_type(state, idx) == ffi::LUA_TSTRING {
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TSTRING {
let mut size = 0;
let data = ffi::lua_tolstring(state, idx, &mut size);
let bytes = slice::from_raw_parts(data as *const u8, size);
Expand All @@ -398,7 +410,7 @@ impl FromLua for StdString {
});
}
// Fallback to default
Self::from_lua(lua.stack_value(idx), lua.lua())
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}

Expand Down Expand Up @@ -536,9 +548,9 @@ impl FromLua for BString {
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
Ok(slice::from_raw_parts(buf as *const u8, size).into())
}
_ => {
type_id => {
// Fallback to default
Self::from_lua(lua.stack_value(idx), lua.lua())
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}
}
Expand Down Expand Up @@ -622,6 +634,24 @@ macro_rules! lua_convert_int {
message: Some("out of range".to_owned()),
})
}

unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TNUMBER {
let mut ok = 0;
let i = ffi::lua_tointegerx(state, idx, &mut ok);
if ok != 0 {
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
from: "integer",
to: stringify!($x),
message: Some("out of range".to_owned()),
});
}
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}
};
}
Expand Down Expand Up @@ -672,6 +702,24 @@ macro_rules! lua_convert_float {
})
})
}

unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TNUMBER {
let mut ok = 0;
let i = ffi::lua_tonumberx(state, idx, &mut ok);
if ok != 0 {
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
from: "number",
to: stringify!($x),
message: Some("out of range".to_owned()),
});
}
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}
};
}
Expand Down Expand Up @@ -893,10 +941,9 @@ impl<T: FromLua> FromLua for Option<T> {

#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
if ffi::lua_isnil(lua.state(), idx) != 0 {
Ok(None)
} else {
Ok(Some(T::from_stack(idx, lua)?))
match ffi::lua_type(lua.state(), idx) {
ffi::LUA_TNIL => Ok(None),
_ => Ok(Some(T::from_stack(idx, lua)?)),
}
}
}
6 changes: 3 additions & 3 deletions src/state/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,17 +542,17 @@ impl RawLua {
///
/// Uses 2 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1);
let value = self.stack_value(-1, None);
ffi::lua_pop(self.state(), 1);
value
}

/// Returns value at given stack index without popping it.
///
/// Uses 2 stack spaces, does not call checkstack.
pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value {
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
let state = self.state();
match ffi::lua_type(state, idx) {
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
ffi::LUA_TNIL => Nil,

ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0),
Expand Down
2 changes: 1 addition & 1 deletion src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ where
// a permitted operation.
// It fails only if the key is not found (never existed) which seems impossible scenario.
if ffi::lua_next(state, -2) != 0 {
let key = lua.stack_value(-2);
let key = lua.stack_value(-2, None);
Ok(Some((
key.clone(),
K::from_lua(key, lua.lua())?,
Expand Down
4 changes: 2 additions & 2 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ pub trait FromLua: Sized {
#[doc(hidden)]
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::from_lua(lua.stack_value(idx), lua.lua())
Self::from_lua(lua.stack_value(idx, None), lua.lua())
}

/// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`.
Expand Down Expand Up @@ -875,7 +875,7 @@ pub trait FromLuaMulti: Sized {
unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result<Self> {
let mut values = MultiValue::with_capacity(nvals as usize);
for idx in 0..nvals {
values.push_back(lua.stack_value(-nvals + idx));
values.push_back(lua.stack_value(-nvals + idx, None));
}
if nvals > 0 {
// It's safe to clear the stack as all references moved to ref thread
Expand Down
52 changes: 52 additions & 0 deletions tests/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ fn test_string_into_lua() -> Result<()> {
Ok(())
}

#[test]
fn test_string_from_lua() -> Result<()> {
let lua = Lua::new();

// From stack
let f = lua.create_function(|_, s: mlua::String| Ok(s))?;
let s = f.call::<String>("hello, world!")?;
assert_eq!(s, "hello, world!");

// Should fallback to default conversion
let s = f.call::<String>(42)?;
assert_eq!(s, "42");

Ok(())
}

#[test]
fn test_table_into_lua() -> Result<()> {
let lua = Lua::new();
Expand Down Expand Up @@ -157,6 +173,42 @@ fn test_registry_key_from_lua() -> Result<()> {
Ok(())
}

#[test]
fn test_integer_from_lua() -> Result<()> {
let lua = Lua::new();

// From stack
let f = lua.create_function(|_, i: i32| Ok(i))?;
assert_eq!(f.call::<i32>(42)?, 42);

// Out of range
let err = f.call::<i32>(i64::MAX).err().unwrap().to_string();
assert!(err.starts_with("bad argument #1: error converting Lua number to i32 (out of range)"));

// Should fallback to default conversion
assert_eq!(f.call::<i32>("42")?, 42);

Ok(())
}

#[test]
fn test_float_from_lua() -> Result<()> {
let lua = Lua::new();

// From stack
let f = lua.create_function(|_, f: f32| Ok(f))?;
assert_eq!(f.call::<f32>(42.0)?, 42.0);

// Out of range (but never fails)
let val = f.call::<f32>(f64::MAX)?;
assert!(val.is_infinite());

// Should fallback to default conversion
assert_eq!(f.call::<f32>("42.0")?, 42.0);

Ok(())
}

#[test]
fn test_conv_vec() -> Result<()> {
let lua = Lua::new();
Expand Down

0 comments on commit 7957c68

Please sign in to comment.