Skip to content

Commit 2bee5ed

Browse files
committed
Support binary modules for Luau on cfg(unix)
1 parent 2d77569 commit 2bee5ed

File tree

8 files changed

+139
-35
lines changed

8 files changed

+139
-35
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ lua52 = ["ffi/lua52"]
3232
lua51 = ["ffi/lua51"]
3333
luajit = ["ffi/luajit"]
3434
luajit52 = ["luajit", "ffi/luajit52"]
35-
luau = ["ffi/luau"]
35+
luau = ["ffi/luau", "libloading"]
3636
luau-jit = ["luau", "ffi/luau-codegen"]
3737
luau-vector4 = ["luau", "ffi/luau-vector4"]
3838
vendored = ["ffi/vendored"]
@@ -57,6 +57,9 @@ parking_lot = { version = "0.12", optional = true }
5757

5858
ffi = { package = "mlua-sys", version = "0.3.2", path = "mlua-sys" }
5959

60+
[target.'cfg(unix)'.dependencies]
61+
libloading = { version = "0.8", optional = true }
62+
6063
[dev-dependencies]
6164
rustyline = "12.0"
6265
criterion = { version = "0.5", features = ["async_tokio"] }

mlua-sys/build/main_inner.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ cfg_if::cfg_if! {
99
}
1010

1111
fn main() {
12-
#[cfg(all(feature = "luau", feature = "module"))]
13-
compile_error!("Luau does not support `module` mode");
12+
#[cfg(all(feature = "luau", feature = "module", windows))]
13+
compile_error!("Luau does not support `module` mode on Windows");
1414

1515
#[cfg(all(feature = "module", feature = "vendored"))]
1616
compile_error!("`vendored` and `module` features are mutually exclusive");

src/lua.rs

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ impl LuaOptions {
222222

223223
#[cfg(feature = "async")]
224224
pub(crate) static ASYNC_POLL_PENDING: u8 = 0;
225-
#[cfg(not(feature = "luau"))]
226225
pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
227226

228227
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
@@ -359,23 +358,22 @@ impl Lua {
359358
///
360359
/// [`StdLib`]: crate::StdLib
361360
pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua {
361+
// Workaround to avoid stripping a few unused Lua symbols that could be imported
362+
// by C modules in unsafe mode
363+
let mut _symbols: Vec<*const extern "C-unwind" fn()> =
364+
vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _];
365+
362366
#[cfg(not(feature = "luau"))]
367+
_symbols.extend_from_slice(&[
368+
ffi::lua_atpanic as _,
369+
ffi::luaL_loadstring as _,
370+
ffi::luaL_openlibs as _,
371+
]);
372+
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
363373
{
364-
// Workaround to avoid stripping a few unused Lua symbols that could be imported
365-
// by C modules in unsafe mode
366-
let mut _symbols: Vec<*const extern "C-unwind" fn()> = vec![
367-
ffi::lua_atpanic as _,
368-
ffi::lua_isuserdata as _,
369-
ffi::lua_tocfunction as _,
370-
ffi::luaL_loadstring as _,
371-
ffi::luaL_openlibs as _,
372-
];
373-
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
374-
{
375-
_symbols.push(ffi::lua_getglobal as _);
376-
_symbols.push(ffi::lua_setglobal as _);
377-
_symbols.push(ffi::luaL_setfuncs as _);
378-
}
374+
_symbols.push(ffi::lua_getglobal as _);
375+
_symbols.push(ffi::lua_setglobal as _);
376+
_symbols.push(ffi::luaL_setfuncs as _);
379377
}
380378

381379
Self::inner_new(libs, options)
@@ -3232,22 +3230,13 @@ impl<'a> Drop for StateGuard<'a> {
32323230
}
32333231
}
32343232

3235-
#[cfg(feature = "luau")]
32363233
unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
3237-
(*ffi::lua_callbacks(state)).userdata as *mut ExtraData
3238-
}
3239-
3240-
#[cfg(feature = "luau")]
3241-
unsafe fn set_extra_data(
3242-
state: *mut ffi::lua_State,
3243-
extra: &Arc<UnsafeCell<ExtraData>>,
3244-
) -> Result<()> {
3245-
(*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _;
3246-
Ok(())
3247-
}
3234+
#[cfg(feature = "luau")]
3235+
if cfg!(not(feature = "module")) {
3236+
// In the main app we can use `lua_callbacks` to access ExtraData
3237+
return (*ffi::lua_callbacks(state)).userdata as *mut _;
3238+
}
32483239

3249-
#[cfg(not(feature = "luau"))]
3250-
unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
32513240
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
32523241
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
32533242
// `ExtraData` can be null only when Lua state is foreign.
@@ -3260,11 +3249,16 @@ unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
32603249
(*extra_ptr).get()
32613250
}
32623251

3263-
#[cfg(not(feature = "luau"))]
32643252
unsafe fn set_extra_data(
32653253
state: *mut ffi::lua_State,
32663254
extra: &Arc<UnsafeCell<ExtraData>>,
32673255
) -> Result<()> {
3256+
#[cfg(feature = "luau")]
3257+
if cfg!(not(feature = "module")) {
3258+
(*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _;
3259+
return Ok(());
3260+
}
3261+
32683262
push_gc_userdata(state, Arc::clone(extra), true)?;
32693263
protect_lua!(state, 1, 0, fn(state) {
32703264
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;

src/luau.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,42 @@ use crate::table::Table;
1212
use crate::types::RegistryKey;
1313
use crate::value::{IntoLua, Value};
1414

15+
#[cfg(unix)]
16+
use {libloading::Library, rustc_hash::FxHashMap};
17+
1518
// Since Luau has some missing standard function, we re-implement them here
1619

20+
#[cfg(unix)]
21+
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1;
22+
23+
#[cfg(all(unix, feature = "module"))]
24+
#[no_mangle]
25+
#[used]
26+
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
27+
1728
// We keep reference to the `package` table in registry under this key
1829
struct PackageKey(RegistryKey);
1930

31+
// We keep reference to the loaded dylibs in application data
32+
#[cfg(unix)]
33+
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
34+
35+
#[cfg(unix)]
36+
impl std::ops::Deref for LoadedDylibs {
37+
type Target = FxHashMap<PathBuf, Library>;
38+
39+
fn deref(&self) -> &Self::Target {
40+
&self.0
41+
}
42+
}
43+
44+
#[cfg(unix)]
45+
impl std::ops::DerefMut for LoadedDylibs {
46+
fn deref_mut(&mut self) -> &mut Self::Target {
47+
&mut self.0
48+
}
49+
}
50+
2051
impl Lua {
2152
pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> {
2253
let globals = self.globals();
@@ -162,6 +193,22 @@ fn create_package_table(lua: &Lua) -> Result<Table> {
162193
}
163194
package.raw_set("path", search_path)?;
164195

196+
// Set `package.cpath`
197+
#[cfg(unix)]
198+
{
199+
let mut search_cpath = env::var("LUAU_CPATH")
200+
.or_else(|_| env::var("LUA_CPATH"))
201+
.unwrap_or_default();
202+
if search_cpath.is_empty() {
203+
if cfg!(any(target_os = "macos", target_os = "ios")) {
204+
search_cpath = "?.dylib".to_string();
205+
} else {
206+
search_cpath = "?.so".to_string();
207+
}
208+
}
209+
package.raw_set("cpath", search_cpath)?;
210+
}
211+
165212
// Set `package.loaded` (table with a list of loaded modules)
166213
let loaded = lua.create_table()?;
167214
package.raw_set("loaded", loaded.clone())?;
@@ -170,6 +217,12 @@ fn create_package_table(lua: &Lua) -> Result<Table> {
170217
// Set `package.loaders`
171218
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
172219
package.raw_set("loaders", loaders.clone())?;
220+
#[cfg(unix)]
221+
{
222+
loaders.push(lua.create_function(dylib_loader)?)?;
223+
let loaded_dylibs = LoadedDylibs(FxHashMap::default());
224+
lua.set_app_data(loaded_dylibs);
225+
}
173226
lua.set_named_registry_value("_LOADERS", loaders)?;
174227

175228
Ok(package)
@@ -225,3 +278,54 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
225278

226279
Ok(Value::Nil)
227280
}
281+
282+
/// Tries to load a dynamic library
283+
#[cfg(unix)]
284+
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
285+
let package = {
286+
let key = lua.app_data_ref::<PackageKey>().unwrap();
287+
lua.registry_value::<Table>(&key.0)
288+
}?;
289+
let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default();
290+
291+
let find_symbol = |lib: &Library| unsafe {
292+
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes())
293+
{
294+
return lua.create_c_function(*entry).map(Value::Function);
295+
}
296+
// Try all in one mode
297+
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(
298+
format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes(),
299+
) {
300+
return lua.create_c_function(*entry).map(Value::Function);
301+
}
302+
"cannot find module entrypoint".into_lua(lua)
303+
};
304+
305+
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
306+
let file_path = file_path.canonicalize()?;
307+
// Load the library and check for symbol
308+
unsafe {
309+
// Check if it's already loaded
310+
if let Some(lib) = lua.app_data_ref::<LoadedDylibs>().unwrap().get(&file_path) {
311+
return find_symbol(lib);
312+
}
313+
if let Ok(lib) = Library::new(&file_path) {
314+
// Check version
315+
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
316+
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
317+
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
318+
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
319+
return err.into_lua(lua);
320+
}
321+
let symbol = find_symbol(&lib);
322+
lua.app_data_mut::<LoadedDylibs>()
323+
.unwrap()
324+
.insert(file_path, lib);
325+
return symbol;
326+
}
327+
}
328+
}
329+
330+
Ok(Value::Nil)
331+
}

src/memory.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::ptr;
44

55
pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator;
66

7+
#[repr(C)]
78
#[derive(Default)]
89
pub(crate) struct MemoryState {
910
used_memory: isize,

tests/module/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ lua53 = ["mlua/lua53"]
1818
lua52 = ["mlua/lua52"]
1919
lua51 = ["mlua/lua51"]
2020
luajit = ["mlua/luajit"]
21+
luau = ["mlua/luau"]
2122

2223
[dependencies]
2324
mlua = { path = "../..", features = ["module"] }

tests/module/loader/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ lua53 = ["mlua/lua53"]
1010
lua52 = ["mlua/lua52"]
1111
lua51 = ["mlua/lua51"]
1212
luajit = ["mlua/luajit"]
13+
luau = ["mlua/luau"]
1314
vendored = ["mlua/vendored"]
1415

1516
[dependencies]

tests/module/loader/tests/load.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::path::PathBuf;
44
use mlua::{Lua, Result};
55

66
#[test]
7-
fn test_module() -> Result<()> {
7+
fn test_module_simple() -> Result<()> {
88
let lua = make_lua()?;
99
lua.load(
1010
r#"

0 commit comments

Comments
 (0)