From 8f6fc64f7aa643f7d65481d2c30cc8ca2c1e4bfd Mon Sep 17 00:00:00 2001 From: luaneko Date: Thu, 19 Jun 2025 21:54:42 +1000 Subject: [PATCH] Add luajit --- .gitmodules | 3 + Cargo.lock | 393 +++++++++++++ Cargo.toml | 8 +- crates/luajit/Cargo.toml | 24 + crates/luajit/build.rs | 217 ++++++++ crates/luajit/lib.rs | 1138 ++++++++++++++++++++++++++++++++++++++ crates/luajit/src | 1 + 7 files changed, 1783 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 crates/luajit/Cargo.toml create mode 100644 crates/luajit/build.rs create mode 100644 crates/luajit/lib.rs create mode 160000 crates/luajit/src diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6e62b72 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "luajit"] + path = crates/luajit/src + url = https://github.com/LuaJIT/LuaJIT.git diff --git a/Cargo.lock b/Cargo.lock index b934f6f..8ce95cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "bstr" version = "1.12.0" @@ -13,6 +48,106 @@ dependencies = [ "serde", ] +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "luaffi" version = "0.1.0" @@ -43,9 +178,25 @@ dependencies = [ "syn", ] +[[package]] +name = "luajit" +version = "0.1.0" +dependencies = [ + "bindgen", + "bitflags", + "bstr", + "cc", + "luaffi", + "thiserror", + "which", +] + [[package]] name = "luby" version = "0.1.0" +dependencies = [ + "luajit", +] [[package]] name = "memchr" @@ -53,6 +204,32 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "prettyplease" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -71,11 +248,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" @@ -83,6 +283,19 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "serde" version = "1.0.219" @@ -103,6 +316,12 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "simdutf8" version = "0.1.5" @@ -126,8 +345,182 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" diff --git a/Cargo.toml b/Cargo.toml index d14bc60..7e6c57c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,10 @@ [workspace] -members = ["crates/luaffi", "crates/luaffi_impl", "crates/luaify"] +members = [ + "crates/luaffi", + "crates/luaffi_impl", + "crates/luaify", + "crates/luajit", +] [package] name = "luby" @@ -7,3 +12,4 @@ version = "0.1.0" edition = "2024" [dependencies] +luajit = { version = "0.1.0", path = "crates/luajit" } diff --git a/crates/luajit/Cargo.toml b/crates/luajit/Cargo.toml new file mode 100644 index 0000000..3e2c0f0 --- /dev/null +++ b/crates/luajit/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "luajit" +version = "0.1.0" +edition = "2024" + +[lib] +path = "lib.rs" + +[features] +default = ["enable-jit", "enable-ffi"] +runtime = [] +enable-jit = [] +enable-ffi = [] + +[dependencies] +bitflags = { version = "2.9.1", features = ["std"] } +bstr = "1.12.0" +luaffi = { version = "0.1.0", path = "../luaffi" } +thiserror = "2.0.12" + +[build-dependencies] +bindgen = "0.71.1" +cc = "1.2.26" +which = "8.0.0" diff --git a/crates/luajit/build.rs b/crates/luajit/build.rs new file mode 100644 index 0000000..8049b5b --- /dev/null +++ b/crates/luajit/build.rs @@ -0,0 +1,217 @@ +use std::{ + env, + fs::{copy, create_dir_all, read_dir, write}, + io, mem, + path::{Path, PathBuf}, + process::Command, +}; +use which::which; + +macro_rules! panic_err { + ($value:expr) => { $value.unwrap_or_else(|err| panic!("{err}")) }; + ($value:expr, $($arg:expr)+) => { $value.unwrap_or_else(|err| panic!("{}: {err}", format_args!($($arg),+))) }; +} + +macro_rules! env_name { + ($($arg:expr),+) => { format!($($arg),+).replace("-", "_").to_uppercase() }; +} + +macro_rules! env { + ($($arg:expr),+) => { panic_err!(env::var(env_name!($($arg),+))) }; +} + +macro_rules! cfg { + ($($arg:expr),+) => { env!("CARGO_CFG_{}", format_args!($($arg),+)) }; +} + +macro_rules! feature { + ($($arg:expr),+) => { env::var_os(env_name!("CARGO_FEATURE_{}", format_args!($($arg),+))).is_some() }; +} + +fn main() { + let src_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); + let out_path = PathBuf::from(env!("OUT_DIR")); + + panic_err!( + copy_recursive(&src_path, &out_path), + "failed to copy luajit source (is the Git submodule initialised?)" + ); + + panic_err!(write(out_path.join(".relver"), get_relver())); + println!("cargo::rerun-if-changed={}", src_path.display()); + + build_runtime(&out_path.join("src")); + build_bindings(&out_path.join("src")); +} + +fn get_relver() -> String { + let out = panic_err!( + Command::new("git") + .args(["show", "-s", "--format=%ct"]) + .output(), + "failed to obtain release version (is the Git command available?)" + ); + + String::from_utf8_lossy(&out.stdout).into() +} + +fn copy_recursive(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + create_dir_all(dst.as_ref())?; + + for entry in read_dir(src)? { + let entry = entry?; + if entry.file_name().to_str() == Some(".git") { + continue; + } + + let path = dst.as_ref().join(entry.file_name()); + if entry.file_type()?.is_dir() { + copy_recursive(entry.path(), path)?; + } else { + copy(entry.path(), path)?; + } + } + + Ok(()) +} + +fn find_make() -> Command { + let name = match parse_target(&env!("HOST")) { + (_, _, "freebsd" | "openbsd" | "netbsd" | "dragonfly", _) => "gmake", + _ => "make", + }; + + Command::new(panic_err!(which(name), "failed to find make")) +} + +fn find_cc(target: impl AsRef) -> PathBuf { + let cc = cc::Build::new().target(target.as_ref()).get_compiler(); + let path = panic_err!(which(cc.path()), "failed to find cc"); + path.to_str().unwrap().into() +} + +fn parse_target(target: &str) -> (&str, &str, &str, Option<&str>) { + let mut i = target.splitn(4, "-"); + let arch = i.next().unwrap(); + let vendor = i.next().unwrap(); + let os = i.next().unwrap(); + let env = i.next(); + (arch, vendor, os, env) +} + +fn build_runtime(src_path: &Path) { + let mut make = find_make(); + let opt_level = env!("OPT_LEVEL"); + let debug = opt_level == "0"; + + let host = env!("HOST"); + let host_cc = find_cc(&host).to_str().unwrap().to_owned(); + let host_ptr_width = 8 * mem::size_of::(); + + let target = env!("TARGET"); + let target_cc = find_cc(&target).to_str().unwrap().to_owned(); + // let target_features: HashSet<&str> = HashSet::from_iter(cfg!("target_feature").split(",")); + let target_ptr_width: usize = cfg!("target_pointer_width").parse().unwrap(); + + if target == host { + println!("--- begin compile for {target}:"); + } else { + println!("--- begin cross-compile on {host} for {target}:"); + } + + make.current_dir(&src_path) + .env_clear() + .arg("-e") + .env("PATH", env!("PATH")) + .env("MAKEFLAGS", env!("CARGO_MAKEFLAGS")) + .env("BUILDMODE", "static"); + + let ccopt = vec![format!("-O{opt_level} -fomit-frame-pointer")]; + + let mut xcflags = vec![ + "-DLUAJIT_ENABLE_LUA52COMPAT", // lua 5.2 compatibility + "-DLUAJIT_USE_SYSMALLOC", // disable bundled allocator + "-DLUAJIT_UNWIND_EXTERNAL -funwind-tables", // use external frame unwinding + ]; + + if debug { + make.env("CCDEBUG", "-g"); // generate debug information + xcflags.push("-DLUAJIT_USE_GDBJIT"); // gdb support + xcflags.push("-DLUA_USE_APICHECK -DLUA_USE_ASSERT"); // enable assertions + } + + if !feature!("enable-jit") { + xcflags.push("-DLUAJIT_DISABLE_JIT"); + } + + if !feature!("enable-ffi") { + xcflags.push("-DLUAJIT_DISABLE_FFI"); + } + + match (host_ptr_width, target_ptr_width) { + (64, 64) | (32, 32) => make.env("HOST_CC", &host_cc), + (64, 32) => make.env("HOST_CC", format!("{} -m32", host_cc)), + (n, m) if n != m => panic!("cannot cross-compile on {n}-bit host for {m}-bit target"), + (n, _) => panic!("unsupported {n}-bit architecture"), + }; + + match cfg!("target_os").as_str() { + "linux" | "android" => make.env("TARGET_SYS", "Linux"), + "windows" => make.env("TARGET_SYS", "Windows"), + "macos" => make.env("TARGET_SYS", "Darwin").env( + "MACOSX_DEPLOYMENT_TARGET", + env::var("MACOSX_DEPLOYMENT_TARGET").as_deref().unwrap_or( + match cfg!("target_arch").as_str() { + "x86_64" | "x86_64h" => "10.12", + "aarch64" | "arm64" | "arm64e" => "11.0", + arch => panic!("unsupported MacOS target architecture '{arch}'"), + }, + ), + ), + "ios" => make.env("TARGET_SYS", "iOS"), + "solaris" => make.env("TARGET_SYS", "SunOS"), + _ => make.env("TARGET_SYS", "Other"), + }; + + if let Some(cross) = target_cc.strip_suffix("gcc") { + make.env("CC", "gcc").env("CROSS", cross); + } else if let Some(cross) = target_cc.strip_suffix("clang") { + make.env("CC", "clang").env("CROSS", cross); + } else { + let path = Path::new(&target_cc); + make.env("CC", path.file_name().unwrap()) + .env("CROSS", format!("{}/", path.parent().unwrap().display())); + } + + if let Some(path) = env::var("RUSTC_LINKER").ok() { + make.env("TARGET_LD", panic_err!(which(path), "failed to find ld")); + } + + make.env("CCOPT", ccopt.join(" ")) + .env("XCFLAGS", xcflags.join(" ")); + + let status = panic_err!(make.status(), "failed to execute make"); + (!status.success()).then(|| panic!("failed to compile luajit: {status}: {make:?}")); + + if feature!("runtime") { + println!("cargo::rustc-link-search=native={}", src_path.display()); + println!("cargo::rustc-link-lib=static=luajit"); + } +} + +fn build_bindings(src_path: &Path) { + let mut bindgen = bindgen::builder() + .clang_arg(format!("--target={}", env!("TARGET"))) + .allowlist_item(r"^(lua|LUA).*_.+$") + .formatter(bindgen::Formatter::None) + .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) + .generate_cstr(true); + + for header in ["lua.h", "lualib.h", "lauxlib.h", "luaconf.h", "luajit.h"] { + bindgen = bindgen.header(src_path.join(header).to_str().unwrap()); + } + + let path = src_path.join("bindgen.rs"); + panic_err!(panic_err!(bindgen.generate(), "failed to generate bindings").write_to_file(&path)); + println!("cargo::rustc-env=LJ_BINDINGS={}", path.display()); +} diff --git a/crates/luajit/lib.rs b/crates/luajit/lib.rs new file mode 100644 index 0000000..e8d1a71 --- /dev/null +++ b/crates/luajit/lib.rs @@ -0,0 +1,1138 @@ +#![allow(non_camel_case_types, non_snake_case)] +use bitflags::bitflags; +use bstr::{BStr, BString, ByteSlice}; +use luaffi::future::lua_pollable; +use std::{ + alloc::{Layout, alloc, dealloc, realloc}, + ffi::{CString, NulError}, + fmt, + ops::{Deref, DerefMut}, + os::raw::{c_char, c_int, c_void}, + process, + ptr::{self, NonNull}, + rc::Rc, + slice, + str::Utf8Error, +}; +use thiserror::Error; + +include!(env!("LJ_BINDINGS")); + +// constants not exposed by lua.h +pub const LUA_TPROTO: c_int = LUA_TTHREAD + 1; +pub const LUA_TCDATA: c_int = LUA_TTHREAD + 2; + +// macros not translated by bindgen +pub unsafe fn lua_upvalueindex(i: c_int) -> c_int { + LUA_GLOBALSINDEX - i +} + +pub unsafe fn lua_pop(L: *mut lua_State, n: c_int) { + unsafe { lua_settop(L, -n - 1) } +} + +pub unsafe fn lua_newtable(L: *mut lua_State) { + unsafe { lua_createtable(L, 0, 0) } +} + +pub unsafe fn lua_register(L: *mut lua_State, n: *const c_char, f: lua_CFunction) { + unsafe { (lua_pushcfunction(L, f), lua_setglobal(L, n)) }; +} + +pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { + unsafe { lua_pushcclosure(L, f, 0) } +} + +pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize { + unsafe { lua_objlen(L, i) } +} + +pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TFUNCTION) as c_int } +} + +pub unsafe fn lua_istable(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TTABLE) as c_int } +} + +pub unsafe fn lua_islightuserdata(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TLIGHTUSERDATA) as c_int } +} + +pub unsafe fn lua_isnil(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TNIL) as c_int } +} + +pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TBOOLEAN) as c_int } +} + +pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TTHREAD) as c_int } +} + +pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) == LUA_TNONE) as c_int } +} + +pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { + unsafe { (lua_type(L, n) <= LUA_TNIL) as c_int } +} + +pub unsafe fn lua_pushliteral(L: *mut lua_State, s: impl AsRef<[u8]>) { + unsafe { lua_pushlstring(L, s.as_ref().as_ptr().cast(), s.as_ref().len()) } +} + +pub unsafe fn lua_setglobal(L: *mut lua_State, s: *const c_char) { + unsafe { lua_setfield(L, LUA_GLOBALSINDEX, s) } +} + +pub unsafe fn lua_getglobal(L: *mut lua_State, s: *const c_char) { + unsafe { lua_getfield(L, LUA_GLOBALSINDEX, s) } +} + +pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { + unsafe { lua_tolstring(L, i, ptr::null_mut()) } +} + +pub unsafe fn lua_open() -> *mut lua_State { + unsafe { luaL_newstate() } +} + +pub unsafe fn lua_getregistry(L: *mut lua_State) { + unsafe { lua_pushvalue(L, LUA_REGISTRYINDEX) } +} + +pub unsafe fn lua_getgccount(L: *mut lua_State) -> c_int { + unsafe { lua_gc(L, LUA_GCCOUNT, 0) } +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("out of memory")] + OutOfMemory, + #[error("{msg}")] + Syntax { chunk: BString, msg: BString }, + #[error("bad chunk name: {0}")] + BadChunkName(NulError), + #[error("{msg}")] + Call { msg: BString }, + #[error("{msg}")] + Resume { msg: BString, trace: BString }, + #[error("{0} expected, got {1}")] + InvalidType(&'static str, &'static str), + #[error("{0}")] + InvalidUtf8(#[from] Utf8Error), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Type { + #[default] + Nil, + Boolean, + Lightuserdata, + Number, + String, + Table, + Function, + Userdata, + Thread, + Cdata, +} + +impl Type { + pub fn from_code(code: c_int) -> Option { + Some(match code { + LUA_TNIL => Self::Nil, + LUA_TBOOLEAN => Self::Boolean, + LUA_TLIGHTUSERDATA => Self::Lightuserdata, + LUA_TNUMBER => Self::Number, + LUA_TSTRING => Self::String, + LUA_TTABLE => Self::Table, + LUA_TFUNCTION => Self::Function, + LUA_TUSERDATA => Self::Userdata, + LUA_TTHREAD => Self::Thread, + LUA_TCDATA => Self::Cdata, + _ => return None, + }) + } + + pub fn name(&self) -> &'static str { + match self { + Self::Nil => "nil", + Self::Boolean => "boolean", + Self::Lightuserdata => "lightuserdata", + Self::Number => "number", + Self::String => "string", + Self::Table => "table", + Self::Function => "function", + Self::Userdata => "userdata", + Self::Thread => "thread", + Self::Cdata => "cdata", + } + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Status { + #[default] + Normal, + Dead, + Suspended, +} + +impl Status { + pub fn from_code(code: c_int) -> Option { + Some(match code { + LUA_OK => Self::Normal, + LUA_YIELD => Self::Suspended, + LUA_ERRRUN | LUA_ERRSYNTAX | LUA_ERRMEM | LUA_ERRERR => Self::Dead, + _ => return None, + }) + } + + pub fn name(&self) -> &'static str { + match self { + Status::Normal => "normal", + Status::Dead => "dead", + Status::Suspended => "suspended", + } + } +} + +impl fmt::Display for Status { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ResumeStatus { + #[default] + Ok, + Suspended, +} + +impl ResumeStatus { + pub fn name(&self) -> &'static str { + match self { + ResumeStatus::Ok => "ok", + ResumeStatus::Suspended => "suspended", + } + } +} + +impl fmt::Display for ResumeStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +bitflags! { + pub struct LoadMode: usize { + const AUTO = 0b_00_11; + const TEXT = 0b_00_01; + const BINARY = 0b_00_10; + const GC64 = 0b_01_10; + const GC32 = 0b_10_10; + } +} + +impl Default for LoadMode { + fn default() -> Self { + Self::AUTO + } +} + +impl LoadMode { + fn mode_str(&self) -> CString { + let mut s = String::new(); + + if self.contains(Self::TEXT) { + s.push_str("t"); + } + + if self.contains(Self::BINARY) { + s.push_str("b"); + if self.contains(Self::GC64) { + s.push_str("X"); + } else if self.contains(Self::GC32) { + s.push_str("W"); + } + } + + CString::new(s).unwrap() + } +} + +bitflags! { + pub struct DumpMode: usize { + const DEFAULT = 0b_00_1_0; + const STRIP = 0b_00_0_1; + const DETERMINISTIC = 0b_00_1_0; + const GC64 = 0b_01_0_0; + const GC32 = 0b_10_0_0; + } +} + +impl Default for DumpMode { + fn default() -> Self { + Self::DEFAULT + } +} + +impl DumpMode { + fn mode_str(&self) -> CString { + let mut s = String::new(); + + if self.contains(Self::STRIP) { + s.push_str("s"); + } + + if self.contains(Self::DETERMINISTIC) { + s.push_str("d"); + } + + if self.contains(Self::GC64) { + s.push_str("X"); + } else if self.contains(Self::GC32) { + s.push_str("W"); + } + + CString::new(s).unwrap() + } +} + +#[derive(Debug)] +struct GlobalState { + ptr: NonNull, +} + +impl GlobalState { + pub fn new() -> Result { + unsafe { + let ptr = NonNull::new(lua_newstate(Some(Self::alloc_cb), ptr::null_mut())) + .ok_or(Error::OutOfMemory)?; + + lua_atpanic(ptr.as_ptr(), Some(Self::panic_cb)); + Ok(Self { ptr }) + } + } + + unsafe extern "C" fn alloc_cb( + _ud: *mut c_void, + ptr: *mut c_void, + osize: usize, + nsize: usize, + ) -> *mut c_void { + // https://github.com/tikv/jemallocator/blob/main/jemallocator/src/lib.rs + #[cfg(any(target_arch = "arm", target_arch = "mips", target_arch = "powerpc"))] + const ALIGNOF_MAX_ALIGN_T: usize = 8; + #[cfg(any( + target_arch = "x86", + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc64" + ))] + const ALIGNOF_MAX_ALIGN_T: usize = 16; + + let old_layout = Layout::from_size_align(osize, ALIGNOF_MAX_ALIGN_T) + .expect("alloc error: requested osize is too large"); + let new_layout = Layout::from_size_align(nsize, ALIGNOF_MAX_ALIGN_T) + .expect("alloc error: requested nsize is too large"); + + if nsize != 0 { + if osize != 0 { + unsafe { realloc(ptr.cast(), old_layout, nsize).cast() } + } else { + unsafe { alloc(new_layout).cast() } + } + } else { + if osize != 0 { + unsafe { dealloc(ptr.cast(), old_layout) } + } + ptr::null_mut() + } + } + + unsafe extern "C" fn panic_cb(L: *mut lua_State) -> c_int { + // we cannot recover from uncaught lua exceptions; abort instead + let stack = unsafe { Stack::new_unchecked(L) }; + let msg = stack.string(-1).unwrap_or(b"unknown lua panic".into()); + eprintln!("lua panicked: {msg}"); + process::abort() + } + + pub fn as_ptr(&self) -> *mut lua_State { + self.ptr.as_ptr() + } +} + +impl Drop for GlobalState { + fn drop(&mut self) { + unsafe { lua_close(self.as_ptr()) } + } +} + +#[derive(Debug)] +pub struct State { + stack: Stack, + ref_key: c_int, + global: Rc, +} + +impl State { + pub fn new() -> Result { + let global = Rc::new(GlobalState::new()?); + let mut state = Self { + stack: unsafe { Stack::new_unchecked(global.as_ptr()) }, + ref_key: LUA_NOREF, + global, + }; + + state.push_function(Some(Self::open_cb)); + state.call(0, 0)?; + Ok(state) + } + + unsafe extern "C" fn open_cb(L: *mut lua_State) -> c_int { + unsafe { luaL_openlibs(L) } + 0 + } + + pub fn new_thread(&self) -> Self { + // SAFETY: lua_newthread never returns null, but may panic on oom + self.ensure(1); + Self { + stack: unsafe { Stack::new_unchecked(lua_newthread(self.as_ptr())) }, + ref_key: unsafe { luaL_ref(self.as_ptr(), LUA_REGISTRYINDEX) }, + global: Rc::clone(&self.global), + } + } +} + +impl Deref for State { + type Target = Stack; + + fn deref(&self) -> &Self::Target { + &self.stack + } +} + +impl DerefMut for State { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.stack + } +} + +impl Drop for State { + fn drop(&mut self) { + unsafe { luaL_unref(self.global.as_ptr(), LUA_REGISTRYINDEX, self.ref_key) } + } +} + +pub struct Stack { + ptr: NonNull, +} + +impl Stack { + unsafe fn new_unchecked(ptr: *mut lua_State) -> Self { + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + Self { ptr } + } + + pub fn as_ptr(&self) -> *mut lua_State { + self.ptr.as_ptr() + } + + pub fn top(&self) -> c_int { + unsafe { lua_gettop(self.as_ptr()) } + } + + pub fn set_top(&mut self, idx: c_int) { + assert!(0 <= idx, "cannot resize to {idx}"); + // lua_settop throws on oom when growing, so we call ensure first + let top = self.top(); + (top < idx).then(|| self.ensure(idx - top)); + unsafe { lua_settop(self.as_ptr(), idx) } + } + + pub fn ensure(&self, n: c_int) { + // lua_checkstack throws on oom in puc lua 5.1, but it is fine in luajit + unsafe { assert!(lua_checkstack(self.as_ptr(), n) != 0, "stack out of memory") } + } + + pub fn pop(&mut self, n: c_int) { + assert!(0 <= n && n <= self.top(), "cannot pop {n}: {self:?}"); + unsafe { lua_pop(self.as_ptr(), n) } + } + + pub fn pop_replace(&mut self, idx: c_int) { + assert!(1 <= self.top(), "cannot pop 1: {self:?}"); + unsafe { lua_replace(self.as_ptr(), self.index(idx).index()) } + } + + pub fn status(&self) -> Status { + Status::from_code(unsafe { lua_status(self.as_ptr()) }).unwrap() + } + + pub fn iter<'s>(&'s self) -> StackIter<'s> { + StackIter::new(self) + } + + pub fn guard<'s>(&'s mut self) -> StackGuard<'s> { + StackGuard::new(self) + } + + pub fn index<'s>(&'s self, idx: c_int) -> Value<'s> { + self.try_index(idx) + .unwrap_or_else(|| panic!("invalid index {idx}: {self:?}")) + } + + pub fn try_index<'s>(&'s self, idx: c_int) -> Option> { + self.absindex(idx) + .map(|idx| unsafe { Value::new_unchecked(self, idx) }) + } + + fn absindex(&self, idx: c_int) -> Option { + if LUA_REGISTRYINDEX < idx && idx <= 0 { + // SAFETY: must check any relative index that gets passed to index2adr in lj_api.c + // luajit doesn't check out-of-bounds access for relative indices with assertions disabled + let top = self.top(); + let idx = top + idx + 1; + (0 < idx && idx <= top).then_some(idx) + } else { + unsafe { lua_type(self.as_ptr(), idx) != LUA_TNONE }.then_some(idx) + } + } + + pub fn type_of(&self, idx: c_int) -> Type { + self.index(idx).type_of() + } + + pub fn parse<'s, T: Parse<'s>>(&'s self, idx: c_int) -> Result { + self.index(idx).parse() + } + + pub fn boolean(&self, idx: c_int) -> bool { + self.index(idx).boolean() + } + + pub fn lightuserdata(&self, idx: c_int) -> *mut T { + self.index(idx).lightuserdata() + } + + pub fn number(&self, idx: c_int) -> Option { + self.index(idx).number() + } + + pub fn integer(&self, idx: c_int) -> Option { + self.index(idx).integer() + } + + pub fn string(&self, idx: c_int) -> Option<&BStr> { + self.index(idx).string() + } + + pub fn function(&self, idx: c_int) -> lua_CFunction { + self.index(idx).function() + } + + pub fn cdata(&self, idx: c_int) -> *const T { + self.index(idx).cdata() + } + + pub fn pointer(&self, idx: c_int) -> *const c_void { + self.index(idx).pointer() + } + + pub fn push(&mut self, value: T) { + value.push(self) + } + + pub fn push_table(&mut self) { + self.ensure(1); + unsafe { lua_newtable(self.as_ptr()) } + } + + pub fn push_function(&mut self, f: lua_CFunction) { + assert!(f.is_some(), "function must not be null"); + self.ensure(1); + unsafe { lua_pushcfunction(self.as_ptr(), f) } + } + + pub fn push_thread(&mut self) -> bool { + self.ensure(1); + unsafe { lua_pushthread(self.as_ptr()) != 0 } + } + + pub fn push_index(&mut self, idx: c_int) { + self.ensure(1); + unsafe { lua_pushvalue(self.as_ptr(), self.index(idx).index()) } + } + + pub fn get(&mut self, idx: c_int) { + assert!(1 <= self.top(), "expected 1 value: {self:?}"); + unsafe { lua_rawget(self.as_ptr(), self.index(idx).index()) } + } + + pub fn geti(&mut self, idx: c_int, n: c_int) { + self.ensure(1); + unsafe { lua_rawgeti(self.as_ptr(), self.index(idx).index(), n) } + } + + pub fn set(&mut self, idx: c_int) { + assert!(2 <= self.top(), "expected 2 values: {self:?}"); + unsafe { lua_rawset(self.as_ptr(), self.index(idx).index()) } + } + + pub fn seti(&mut self, idx: c_int, n: c_int) { + assert!(1 <= self.top(), "expected 1 value: {self:?}"); + unsafe { lua_rawseti(self.as_ptr(), self.index(idx).index(), n) } + } + + pub fn load( + &mut self, + name: Option>, + chunk: impl AsRef<[u8]>, + mode: LoadMode, + ) -> Result<(), Error> { + let mode = mode.mode_str(); + let name = name + .map(|s| CString::new(s.as_ref())) + .transpose() + .map_err(Error::BadChunkName)? + .unwrap_or(c"?".into()); + + type State<'s> = Option<&'s [u8]>; + let mut state: State = Some(chunk.as_ref()); + + unsafe extern "C" fn reader_cb( + _L: *mut lua_State, + state: *mut c_void, + size: *mut usize, + ) -> *const c_char { + unsafe { + if let Some(chunk) = (*(state as *mut State)).take() { + *size = chunk.len(); + chunk.as_ptr().cast() + } else { + *size = 0; + ptr::null() + } + } + } + + self.ensure(1); + + match unsafe { + lua_loadx( + self.as_ptr(), + Some(reader_cb), + &raw mut state as *mut c_void, + name.as_ptr(), + mode.as_ptr(), + ) + } { + LUA_OK => Ok(()), + LUA_ERRMEM => { + self.pop(1); + Err(Error::OutOfMemory) + } + LUA_ERRSYNTAX => { + let chunk = name.into_bytes().into(); + let msg = self.string(-1).unwrap_or(b"unknown error".into()).into(); + self.pop(1); + Err(Error::Syntax { chunk, msg }) + } + _ => unreachable!(), + } + } + + pub fn dump(&mut self, idx: c_int, mode: DumpMode) -> Result { + let value = self.index(idx); + let idx = match value.type_of() { + Type::Function => value.index(), + _ => panic!("expected function at index {}: {self:?}", value.index()), + }; + + let mut s = self.guard(); + s.push("string"); + s.get(LUA_GLOBALSINDEX); + s.push("dump"); + s.get(-2); // local dump = string.dump + s.push_index(idx); + s.push(mode.mode_str().as_bytes()); + s.call(2, 1)?; + s.index(-1).parse() // return dump(idx, mode) + } + + pub fn call(&mut self, narg: c_int, nret: c_int) -> Result { + assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET)); + + let top = self.top(); + let need = narg + 1; // need the function on the stack + assert!(need <= top, "expected {need} values: {self:?}"); + assert!( + self.status() == Status::Normal, + "thread {:p} called in wrong state", + self.as_ptr() + ); + + let base = top - need; + match unsafe { lua_pcall(self.as_ptr(), narg, nret, 0) } { + LUA_OK => { + let n = self.top() - base; + assert!( + n == nret || nret == LUA_MULTRET, + "expected {nret} values, got {n}: {self:?}" + ); + Ok(n) + } + LUA_ERRMEM => { + self.pop(1); + Err(Error::OutOfMemory) + } + LUA_ERRRUN | LUA_ERRERR => { + let msg = self.string(-1).unwrap_or(b"unknown error".into()).into(); + self.pop(1); + Err(Error::Call { msg }) + } + _ => unreachable!(), + } + } + + pub async fn call_async(&mut self, mut narg: c_int) -> Result<(), Error> { + loop { + match self.resume(narg)? { + ResumeStatus::Ok => return Ok(()), + ResumeStatus::Suspended => { + self.set_top(1); + let value = self.index(1); + let ptr = value.cdata::().cast_mut(); + if ptr.is_null() { + return Err(Error::InvalidType("cdata", value.type_of().name())); + } else { + unsafe { (&mut *ptr).await } + narg = 1; + } + } + } + } + } + + pub fn resume(&mut self, narg: c_int) -> Result { + assert!(0 <= narg); + let need = match self.status() { + Status::Suspended => narg, + _ => narg + 1, // need the function on the stack + }; + assert!(need <= self.top(), "expected {need} values: {self:?}"); + + match unsafe { lua_resume(self.as_ptr(), narg) } { + LUA_OK => Ok(ResumeStatus::Ok), + LUA_YIELD => Ok(ResumeStatus::Suspended), + LUA_ERRMEM => { + self.pop(1); + Err(Error::OutOfMemory) + } + LUA_ERRRUN | LUA_ERRERR => { + unsafe { luaL_traceback(self.as_ptr(), self.as_ptr(), ptr::null(), 0) } + + let msg = self.string(-2).unwrap_or(b"unknown error".into()).into(); + let trace = self + .string(-1) + .map(|s| s.strip_prefix(b"stack traceback:\n").unwrap_or(s).as_bstr()) + .filter(|s| !s.is_empty()) + .unwrap_or(b"".into()) + .into(); + + self.pop(2); + Err(Error::Resume { msg, trace }) + } + _ => unreachable!(), + } + } +} + +impl fmt::Debug for Stack { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + struct PointerValue(&'static str, *const c_void); + impl<'s> fmt::Debug for PointerValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:p}", self.0, self.1) + } + } + + struct Values<'s>(&'s Stack); + impl<'s> fmt::Debug for Values<'s> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut list = f.debug_list(); + for value in self.0.iter() { + match value.type_of() { + Type::Nil => list.entry(&"nil"), + Type::Boolean => list.entry(&value.boolean()), + Type::Number => list.entry(&value.number()), + Type::String => list.entry(&value.string().unwrap()), + ty => list.entry(&PointerValue(ty.name(), value.pointer())), + }; + } + list.finish() + } + } + + f.debug_struct("Stack") + .field("ptr", &self.ptr) + .field("values", &Values(self)) + .finish() + } +} + +#[derive(Debug)] +pub struct StackGuard<'s> { + stack: &'s mut Stack, + idx: c_int, +} + +impl<'s> StackGuard<'s> { + pub fn new(stack: &'s mut Stack) -> Self { + let idx = stack.top(); + Self { stack, idx } + } +} + +impl<'s> Deref for StackGuard<'s> { + type Target = Stack; + + fn deref(&self) -> &Self::Target { + self.stack + } +} + +impl<'s> DerefMut for StackGuard<'s> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.stack + } +} + +impl<'s> Drop for StackGuard<'s> { + fn drop(&mut self) { + self.stack.set_top(self.idx); + } +} + +#[derive(Debug)] +pub struct StackIter<'s> { + stack: &'s Stack, + idx: c_int, + top: c_int, +} + +impl<'s> StackIter<'s> { + pub fn new(stack: &'s Stack) -> Self { + let top = stack.top(); + Self { stack, idx: 0, top } + } +} + +impl<'s> Iterator for StackIter<'s> { + type Item = Value<'s>; + + fn next(&mut self) -> Option { + (self.idx < self.top).then(|| { + self.idx += 1; + unsafe { Value::new_unchecked(self.stack, self.idx) } + }) + } +} + +pub struct Value<'s> { + stack: &'s Stack, + idx: c_int, +} + +impl<'s> Value<'s> { + unsafe fn new_unchecked(stack: &'s Stack, idx: c_int) -> Self { + Self { stack, idx } + } + + pub fn index(&self) -> c_int { + self.idx + } + + pub fn type_of(&self) -> Type { + Type::from_code(unsafe { lua_type(self.stack.as_ptr(), self.idx) }).unwrap_or(Type::Nil) + } + + pub fn parse>(&self) -> Result { + T::parse(self) + } + + pub fn boolean(&self) -> bool { + self.parse().unwrap_or(false) + } + + pub fn lightuserdata(&self) -> *mut T { + self.parse().unwrap_or(ptr::null_mut()) + } + + pub fn number(&self) -> Option { + self.parse().ok() + } + + pub fn integer(&self) -> Option { + self.parse().ok() + } + + pub fn string(&self) -> Option<&'s BStr> { + self.parse().ok() + } + + pub fn function(&self) -> lua_CFunction { + unsafe { lua_tocfunction(self.stack.as_ptr(), self.idx) } + } + + pub fn cdata(&self) -> *const T { + (self.type_of() == Type::Cdata) + .then(|| self.pointer().cast()) + .unwrap_or(ptr::null_mut()) + } + + pub fn pointer(&self) -> *const c_void { + unsafe { lua_topointer(self.stack.as_ptr(), self.idx).cast() } + } +} + +pub trait Push { + fn push(&self, stack: &mut Stack); +} + +impl Push for &T { + fn push(&self, stack: &mut Stack) { + T::push(self, stack) + } +} + +impl Push for &mut T { + fn push(&self, stack: &mut Stack) { + T::push(self, stack) + } +} + +impl Push for Option { + fn push(&self, stack: &mut Stack) { + match self { + Some(value) => value.push(stack), + None => ().push(stack), + } + } +} + +impl Push for () { + fn push(&self, stack: &mut Stack) { + stack.ensure(1); + unsafe { lua_pushnil(stack.as_ptr()) } + } +} + +impl Push for bool { + fn push(&self, stack: &mut Stack) { + stack.ensure(1); + unsafe { lua_pushboolean(stack.as_ptr(), *self as c_int) } + } +} + +macro_rules! impl_push_ptr { + ($type:ty) => { + impl Push for $type { + fn push(&self, stack: &mut Stack) { + stack.ensure(1); + unsafe { lua_pushlightuserdata(stack.as_ptr(), *self as *mut c_void) } + } + } + }; +} + +impl_push_ptr!(*const T); +impl_push_ptr!(*mut T); + +macro_rules! impl_push_num { + ($type:ty) => { + impl Push for $type { + fn push(&self, stack: &mut Stack) { + stack.ensure(1); + unsafe { lua_pushnumber(stack.as_ptr(), *self as lua_Number) } + } + } + }; +} + +impl_push_num!(f32); +impl_push_num!(f64); + +macro_rules! impl_push_int { + ($type:ty) => { + impl Push for $type { + fn push(&self, stack: &mut Stack) { + stack.ensure(1); + unsafe { lua_pushinteger(stack.as_ptr(), *self as lua_Integer) } + } + } + }; +} + +impl_push_int!(u8); +impl_push_int!(u16); +impl_push_int!(u32); +impl_push_int!(u64); +impl_push_int!(usize); +impl_push_int!(i8); +impl_push_int!(i16); +impl_push_int!(i32); +impl_push_int!(i64); +impl_push_int!(isize); + +macro_rules! impl_push_str { + ($type:ty) => { + impl Push for $type { + fn push(&self, stack: &mut Stack) { + stack.ensure(1); + unsafe { lua_pushliteral(stack.as_ptr(), self) } + } + } + }; +} + +impl_push_str!(&[u8]); +impl_push_str!(Vec); +impl_push_str!(&BStr); +impl_push_str!(BString); +impl_push_str!(&str); +impl_push_str!(String); + +pub trait Parse<'s>: Sized { + fn parse(value: &Value<'s>) -> Result; +} + +impl Parse<'_> for () { + fn parse(value: &Value) -> Result { + match value.type_of() { + Type::Nil => Ok(()), + ty => Err(Error::InvalidType("nil", ty.name())), + } + } +} + +impl Parse<'_> for bool { + fn parse(value: &Value) -> Result { + Ok(unsafe { lua_toboolean(value.stack.as_ptr(), value.idx) != 0 }) + } +} + +macro_rules! impl_parse_ptr { + ($type:ty) => { + impl Parse<'_> for $type { + fn parse(value: &Value) -> Result { + let ptr = unsafe { lua_touserdata(value.stack.as_ptr(), value.idx) }; + if !ptr.is_null() { + Ok(ptr as $type) + } else { + Err(Error::InvalidType("lightuserdata", value.type_of().name())) + } + } + } + }; +} + +impl_parse_ptr!(*mut T); +impl_parse_ptr!(*const T); + +macro_rules! impl_parse_num { + ($type:ty) => { + impl Parse<'_> for $type { + fn parse(value: &Value) -> Result { + let mut isnum = 0; + let n = unsafe { lua_tonumberx(value.stack.as_ptr(), value.idx, &raw mut isnum) }; + if isnum != 0 { + Ok(n as $type) + } else { + Err(Error::InvalidType("number", value.type_of().name())) + } + } + } + }; +} + +impl_parse_num!(f32); +impl_parse_num!(f64); + +macro_rules! impl_parse_int { + ($type:ty) => { + impl Parse<'_> for $type { + fn parse(value: &Value) -> Result { + let mut isnum = 0; + let n = unsafe { lua_tointegerx(value.stack.as_ptr(), value.idx, &raw mut isnum) }; + if isnum != 0 { + Ok(n as $type) + } else { + Err(Error::InvalidType("number", value.type_of().name())) + } + } + } + }; +} + +impl_parse_int!(u8); +impl_parse_int!(u16); +impl_parse_int!(u32); +impl_parse_int!(u64); +impl_parse_int!(usize); +impl_parse_int!(i8); +impl_parse_int!(i16); +impl_parse_int!(i32); +impl_parse_int!(i64); +impl_parse_int!(isize); + +macro_rules! impl_parse_str { + ($type:ty) => { + impl<'s> Parse<'s> for $type { + fn parse(value: &Value<'s>) -> Result { + let mut len = 0; + let ptr = unsafe { lua_tolstring(value.stack.as_ptr(), value.idx, &mut len) }; + if !ptr.is_null() { + Ok(unsafe { slice::from_raw_parts(ptr.cast(), len).into() }) + } else { + Err(Error::InvalidType("string", value.type_of().name())) + } + } + } + }; +} + +macro_rules! impl_parse_str_utf8 { + ($type:ty) => { + impl<'s> Parse<'s> for $type { + fn parse(value: &Value<'s>) -> Result { + Ok(std::str::from_utf8(Parse::parse(value)?)?.into()) + } + } + }; +} + +impl_parse_str!(&'s [u8]); +impl_parse_str!(&'s BStr); +impl_parse_str!(Vec); +impl_parse_str!(BString); +impl_parse_str_utf8!(&'s str); +impl_parse_str_utf8!(String); diff --git a/crates/luajit/src b/crates/luajit/src new file mode 160000 index 0000000..f9140a6 --- /dev/null +++ b/crates/luajit/src @@ -0,0 +1 @@ +Subproject commit f9140a622a0c44a99efb391cc1c2358bc8098ab7