Compare commits

..

No commits in common. "94e1cf2eb0debbe1c46d92462edef29882ca7c45" and "8f6fc64f7aa643f7d65481d2c30cc8ca2c1e4bfd" have entirely different histories.

11 changed files with 16 additions and 1643 deletions

1307
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
[workspace]
members = [
"crates/lb_core",
"crates/luaffi",
"crates/luaffi_impl",
"crates/luaify",
@ -13,13 +12,4 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.40", features = ["derive"] }
console-subscriber = "0.4.1"
lb_core = { version = "0.1.0", path = "crates/lb_core" }
luaffi = { version = "0.1.0", path = "crates/luaffi" }
luajit = { version = "0.1.0", path = "crates/luajit", features = ["runtime"] }
mimalloc = "0.1.47"
owo-colors = "4.2.1"
tokio = { version = "1.45.1", features = ["full", "tracing"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
luajit = { version = "0.1.0", path = "crates/luajit" }

View File

@ -1,10 +0,0 @@
[package]
name = "lb_core"
version = "0.1.0"
edition = "2024"
[dependencies]
luaffi = { version = "0.1.0", path = "../luaffi" }
luajit = { version = "0.1.0", path = "../luajit" }
owo-colors = "4.2.1"
tokio = { version = "1.45.1", features = ["full"] }

View File

@ -1,92 +0,0 @@
use luaffi::{cdef, metatype};
use luajit::State;
use owo_colors::OwoColorize;
use std::{cell::RefCell, fmt, process};
#[derive(Debug)]
pub struct GlobalState(State);
impl GlobalState {
thread_local! {
static STATE: RefCell<Option<GlobalState>> = RefCell::new(None);
}
pub fn set(state: State) -> Option<State> {
Self::STATE.with_borrow_mut(|s| s.replace(Self(state)).map(|s| s.0))
}
pub fn with_current<T>(f: impl FnOnce(&State) -> T) -> T {
Self::STATE.with_borrow(|s| f(&s.as_ref().expect("lua state not initialised").0))
}
pub fn with_current_mut<T>(f: impl FnOnce(&mut State) -> T) -> T {
Self::STATE.with_borrow_mut(|s| f(&mut s.as_mut().expect("lua state not initialised").0))
}
pub fn new_thread() -> State {
Self::with_current(|s| s.new_thread())
}
pub fn uncaught_error(err: luajit::Error) {
let mut err = PrettyError::from(err);
if let Some(task) = tokio::task::try_id() {
err.prepend(format_args!("uncaught error in task {task}"));
}
eprintln!("{err}");
process::abort()
}
}
#[derive(Debug, Clone)]
pub struct PrettyError {
msg: String,
trace: Option<String>,
}
impl PrettyError {
pub fn new(msg: impl fmt::Display) -> Self {
Self {
msg: format!("{msg}"),
trace: None,
}
}
pub fn with_trace(mut self, trace: impl fmt::Display) -> Self {
self.trace = Some(format!("{trace}"));
self
}
pub fn prepend(&mut self, msg: impl fmt::Display) -> &mut Self {
if self.msg.is_empty() {
self.msg = format!("{msg}");
} else {
self.msg = format!("{msg}:\n{}", self.msg);
}
self
}
}
impl From<luajit::Error> for PrettyError {
fn from(value: luajit::Error) -> Self {
match value {
luajit::Error::Resume { msg, trace } => Self::new(msg).with_trace(trace),
err => Self::new(err),
}
}
}
impl fmt::Display for PrettyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.trace {
Some(ref trace) => write!(f, "{}\n{trace}", self.msg.red()),
None => write!(f, "{}", self.msg.red()),
}
}
}
#[cdef]
pub struct lb_core;
#[metatype]
impl lb_core {}

View File

@ -32,7 +32,7 @@ unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
}
const CACHE_LIBS: &[(&str, &str)] = &[
// libs in global
// preloaded
("table", "table"),
("string", "string"),
("math", "math"),
@ -143,18 +143,13 @@ impl Registry {
}
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.include::<T>()
.funcs
self.include::<T>();
self.funcs
.insert(name.to_string())
.then(|| writeln!(self.cdef, "{};", T::cdecl(name)).unwrap());
self
}
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
self.include::<T>();
self
}
pub fn done(&self) -> String {
self.to_string()
}

View File

@ -7,7 +7,6 @@ edition = "2024"
proc-macro = true
[dependencies]
darling = "0.20.11"
proc-macro2 = "1.0.95"
quote = "1.0.40"
syn = { version = "2.0.103", features = ["full", "visit-mut"] }

View File

@ -1,13 +1,9 @@
use crate::utils::{ffi_crate, syn_assert, syn_error};
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{spanned::*, *};
#[derive(Debug, FromMeta)]
pub struct Args {}
pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
pub fn transform(mut item: Item) -> Result<TokenStream> {
let (name, impl_type, impl_cdef) = match item {
Item::Struct(ref mut str) => (
str.ident.clone(),

View File

@ -1,4 +1,3 @@
use darling::{FromMeta, ast::NestedMeta};
use proc_macro::TokenStream as TokenStream1;
use quote::ToTokens;
use syn::parse_macro_input;
@ -9,9 +8,7 @@ mod utils;
#[proc_macro_attribute]
pub fn cdef(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
NestedMeta::parse_meta_list(args.into())
.and_then(|meta| cdef::Args::from_list(&meta).map_err(Into::into))
.and_then(|args| cdef::transform(args, syn::parse(input)?))
cdef::transform(parse_macro_input!(input))
.unwrap_or_else(|err| err.into_compile_error().into_token_stream())
.into()
}

View File

@ -26,36 +26,25 @@ pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
}
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ty = imp.self_ty.clone();
let ty_name = ty_name(&ty)?;
let ffi = ffi_crate();
let ffi_funcs = get_ffi_functions(imp)?;
let ffi_wrappers: Vec<_> = ffi_funcs
.iter()
.map(generate_ffi_wrapper)
.collect::<Result<_>>()?;
let ffi_register: Vec<_> = ffi_funcs
.iter()
.map(generate_ffi_register)
.collect::<Result<_>>()?;
let ffi_drop_fn = format_ident!("__ffi_drop");
let ffi_drop_name = format!("{ty_name}_drop");
let ffi_exports = {
let mut names = vec![&ffi_drop_fn];
names.extend(ffi_funcs.iter().map(|f| &f.rust_name));
generate_ffi_exports(&ty, names.into_iter())?
};
let lua_funcs = get_lua_functions(imp)?;
let lua_register: Vec<_> = lua_funcs
.iter()
.map(generate_lua_register)
.collect::<Result<_>>()?;
let ty = &*imp.self_ty;
Ok(quote! {
unsafe impl #ffi::Metatype for #ty {
type Target = Self;
@ -63,22 +52,10 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
fn build(b: &mut #ffi::MetatypeBuilder) {
#(#ffi_register)*
#(#lua_register)*
b.declare::<unsafe extern "C" fn(*mut Self)>(#ffi_drop_name);
b.metatable_raw("gc", ::std::format_args!("C.{}", #ffi_drop_name));
}
}
impl #ty {
#(#ffi_wrappers)*
#[unsafe(export_name = #ffi_drop_name)]
unsafe extern "C" fn #ffi_drop_fn(&mut self) {
unsafe { ::std::ptr::drop_in_place(self) }
}
}
#ffi_exports
impl #ty { #(#ffi_wrappers)* }
})
}
@ -236,19 +213,6 @@ fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
})
}
fn generate_ffi_exports<'a>(
ty: &Type,
names: impl Iterator<Item = &'a Ident>,
) -> Result<TokenStream> {
Ok(quote! {
// hack to prevent ffi functions from being dead code-eliminated
#[used]
static __FFI_EXPORTS: &[fn()] = unsafe {
&[#(::std::mem::transmute(#ty::#names as *const ())),*]
};
})
}
struct LuaFunction {
name: String,
params: Vec<PatType>,

View File

@ -1,168 +1,3 @@
use clap::Parser;
use lb_core::{GlobalState, PrettyError};
use mimalloc::MiMalloc;
use owo_colors::OwoColorize;
use std::{backtrace::Backtrace, net::SocketAddr, num::NonZero, panic, thread};
use tokio::{runtime, task::LocalSet};
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
fn panic_cb(panic: &panic::PanicHookInfo) {
let trace = Backtrace::force_capture();
let location = panic.location().unwrap();
let payload = panic.payload();
let msg = if let Some(s) = payload.downcast_ref::<&'static str>() {
s
} else if let Some(s) = payload.downcast_ref::<String>() {
s.as_str()
} else {
"unknown error"
};
eprint!(
"{}",
PrettyError::new(msg)
.with_trace(trace)
.prepend(format_args!(
"thread '{}' panicked at {location}",
thread::current().name().unwrap_or("<unnamed>")
))
);
}
#[derive(Debug, Parser)]
struct Args {
/// Paths to scripts to execute.
#[clap(value_name = "SCRIPTS")]
paths: Vec<String>,
/// Strings to execute.
#[clap(long, short = 'e', value_name = "CHUNK")]
evals: Vec<String>,
/// Libraries to require on startup.
#[clap(long, short = 'l', value_name = "NAME")]
libs: Vec<String>,
/// Console log level.
#[clap(long, value_name = "LEVEL", default_value = "debug")]
log_level: tracing::Level,
/// Number of runtime worker threads.
#[clap(long, value_name = "THREADS", default_value_t = Self::threads())]
threads: NonZero<usize>,
/// Number of runtime blocking threads.
#[clap(long, value_name = "THREADS", default_value_t = Self::blocking_threads())]
blocking_threads: NonZero<usize>,
/// Enable tokio-console integration.
#[clap(long)]
enable_console: bool,
/// tokio-console publish address.
#[clap(long, value_name = "ADDRESS", default_value = "127.0.0.1:6669")]
console_addr: SocketAddr,
}
impl Args {
fn threads() -> NonZero<usize> {
thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap())
}
fn blocking_threads() -> NonZero<usize> {
NonZero::new(1024).unwrap()
}
}
fn main() {
panic::set_hook(Box::new(panic_cb));
let args = Args::parse();
init_logger(&args);
let runtime = init_runtime(&args);
GlobalState::set(init_vm(&args));
let main = LocalSet::new();
main.spawn_local(run(args));
runtime.block_on(main);
}
fn init_logger(args: &Args) {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{Layer, util::*};
let console = tracing_subscriber::fmt()
.compact()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(LevelFilter::from(args.log_level).into())
.from_env_lossy(),
)
.with_file(false)
.with_line_number(false)
.with_target(false)
.finish();
if args.enable_console {
console_subscriber::ConsoleLayer::builder()
.with_default_env()
.server_addr(args.console_addr)
.spawn()
.with_subscriber(console)
.init()
} else {
console.init()
}
}
fn init_runtime(args: &Args) -> runtime::Runtime {
if args.threads.get() == 1 {
runtime::Builder::new_current_thread()
} else {
runtime::Builder::new_multi_thread()
}
.enable_all()
.thread_name("lb")
.worker_threads(args.threads.get() - 1)
.max_blocking_threads(args.blocking_threads.get())
.build()
.unwrap_or_else(|err| panic!("failed to initialise runtime: {err}"))
}
fn init_vm(_args: &Args) -> luajit::State {
let mut state =
luajit::State::new().unwrap_or_else(|err| panic!("failed to initialise runtime: {err}"));
let mut registry = luaffi::Registry::new();
registry.include::<lb_core::lb_core>();
println!("{registry}");
state
.load(Some("@[luby]"), registry.done(), luajit::LoadMode::TEXT)
.and_then(|()| state.call(0, 0))
.unwrap_or_else(|err| panic!("failed to load modules: {err}"));
state
}
async fn run(args: Args) {
let mut state = GlobalState::new_thread();
for ref path in args.paths {
let chunk = match std::fs::read(path) {
Ok(chunk) => chunk,
Err(err) => return eprintln!("{}", format!("{path}: {err}").red()),
};
if let Err(err) = state.load(Some(format!("@{path}")), chunk, Default::default()) {
return eprintln!("{}", err.red());
}
state
.call_async(0)
.await
.unwrap_or_else(GlobalState::uncaught_error);
}
println!("Hello, world!");
}

View File

@ -1,6 +0,0 @@
local ffi = require("ffi")
local lb = ffi.new("struct lb_core")
print(lb)
lb.spawn("")