Skip to content

Commit 18497f1

Browse files
committed
Add library constants support for Luau compiler.
Add ability to specify compile-time constants for known library members.
1 parent 28e8f56 commit 18497f1

File tree

4 files changed

+152
-12
lines changed

4 files changed

+152
-12
lines changed

src/chunk.rs

+113-5
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,28 @@ pub enum ChunkMode {
130130
Binary,
131131
}
132132

133+
/// Represents a constant value that can be used by Luau compiler.
134+
#[cfg(any(feature = "luau", doc))]
135+
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
136+
#[derive(Clone, Debug)]
137+
pub enum CompileConstant {
138+
Nil,
139+
Boolean(bool),
140+
Number(crate::Number),
141+
Vector(crate::Vector),
142+
String(String),
143+
}
144+
145+
#[cfg(feature = "luau")]
146+
impl From<&'static str> for CompileConstant {
147+
fn from(s: &'static str) -> Self {
148+
CompileConstant::String(s.to_string())
149+
}
150+
}
151+
152+
#[cfg(any(feature = "luau", doc))]
153+
type LibraryMemberConstantMap = std::sync::Arc<HashMap<(String, String), CompileConstant>>;
154+
133155
/// Luau compiler
134156
#[cfg(any(feature = "luau", doc))]
135157
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -144,6 +166,9 @@ pub struct Compiler {
144166
vector_type: Option<String>,
145167
mutable_globals: Vec<String>,
146168
userdata_types: Vec<String>,
169+
libraries_with_known_members: Vec<String>,
170+
library_constants: Option<LibraryMemberConstantMap>,
171+
disabled_builtins: Vec<String>,
147172
}
148173

149174
#[cfg(any(feature = "luau", doc))]
@@ -168,6 +193,9 @@ impl Compiler {
168193
vector_type: None,
169194
mutable_globals: Vec::new(),
170195
userdata_types: Vec::new(),
196+
libraries_with_known_members: Vec::new(),
197+
library_constants: None,
198+
disabled_builtins: Vec::new(),
171199
}
172200
}
173201

@@ -200,6 +228,7 @@ impl Compiler {
200228
/// Possible values:
201229
/// * 0 - generate for native modules (default)
202230
/// * 1 - generate for all modules
231+
#[must_use]
203232
pub const fn set_type_info_level(mut self, level: u8) -> Self {
204233
self.type_info_level = level;
205234
self
@@ -242,23 +271,56 @@ impl Compiler {
242271
///
243272
/// It disables the import optimization for fields accessed through these.
244273
#[must_use]
245-
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
246-
self.mutable_globals = globals;
274+
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: Vec<S>) -> Self {
275+
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
247276
self
248277
}
249278

250279
/// Sets a list of userdata types that will be included in the type information.
251280
#[must_use]
252-
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
253-
self.userdata_types = types;
281+
pub fn set_userdata_types<S: Into<String>>(mut self, types: Vec<S>) -> Self {
282+
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
283+
self
284+
}
285+
286+
/// Sets constants for known library members.
287+
///
288+
/// The constants are used by the compiler to optimize the generated bytecode.
289+
/// Optimization level must be at least 2 for this to have any effect.
290+
///
291+
/// The first element of the tuple is the library name,the second is the member name, and the
292+
/// third is the constant value.
293+
#[must_use]
294+
pub fn set_library_constants<L, M>(mut self, constants: Vec<(L, M, CompileConstant)>) -> Self
295+
where
296+
L: Into<String>,
297+
M: Into<String>,
298+
{
299+
let map = constants
300+
.into_iter()
301+
.map(|(lib, member, cons)| ((lib.into(), member.into()), cons))
302+
.collect::<HashMap<_, _>>();
303+
self.library_constants = Some(std::sync::Arc::new(map));
304+
self.libraries_with_known_members = (self.library_constants.clone())
305+
.map(|map| map.keys().map(|(lib, _)| lib.clone()).collect())
306+
.unwrap_or_default();
307+
self
308+
}
309+
310+
/// Sets a list of builtins that should be disabled.
311+
#[must_use]
312+
pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: Vec<S>) -> Self {
313+
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
254314
self
255315
}
256316

257317
/// Compiles the `source` into bytecode.
258318
///
259319
/// Returns [`Error::SyntaxError`] if the source code is invalid.
260320
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
261-
use std::os::raw::c_int;
321+
use std::cell::RefCell;
322+
use std::ffi::CStr;
323+
use std::os::raw::{c_char, c_int};
262324
use std::ptr;
263325

264326
let vector_lib = self.vector_lib.clone();
@@ -290,6 +352,44 @@ impl Compiler {
290352

291353
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
292354
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
355+
vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
356+
vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
357+
358+
thread_local! {
359+
static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
360+
}
361+
362+
#[cfg(feature = "luau")]
363+
unsafe extern "C-unwind" fn library_member_constant_callback(
364+
library: *const c_char,
365+
member: *const c_char,
366+
constant: *mut ffi::lua_CompileConstant,
367+
) {
368+
let library = CStr::from_ptr(library).to_string_lossy();
369+
let member = CStr::from_ptr(member).to_string_lossy();
370+
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
371+
if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
372+
match cons {
373+
CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
374+
CompileConstant::Boolean(b) => {
375+
ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
376+
}
377+
CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
378+
CompileConstant::Vector(v) => {
379+
#[cfg(not(feature = "luau-vector4"))]
380+
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
381+
#[cfg(feature = "luau-vector4")]
382+
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
383+
}
384+
CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
385+
constant,
386+
s.as_ptr() as *const c_char,
387+
s.len(),
388+
),
389+
}
390+
}
391+
})
392+
}
293393

294394
let bytecode = unsafe {
295395
let mut options = ffi::lua_CompileOptions::default();
@@ -302,6 +402,14 @@ impl Compiler {
302402
options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
303403
options.mutableGlobals = mutable_globals_ptr;
304404
options.userdataTypes = userdata_types_ptr;
405+
options.librariesWithKnownMembers = libraries_with_known_members_ptr;
406+
if let Some(map) = self.library_constants.as_ref() {
407+
if !self.libraries_with_known_members.is_empty() {
408+
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
409+
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
410+
}
411+
}
412+
options.disabledBuiltins = disabled_builtins_ptr;
305413
ffi::luau_compile(source.as_ref(), options)
306414
};
307415

src/lib.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,12 @@ pub use crate::hook::HookTriggers;
126126

127127
#[cfg(any(feature = "luau", doc))]
128128
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
129-
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector};
129+
pub use crate::{
130+
buffer::Buffer,
131+
chunk::{CompileConstant, Compiler},
132+
function::CoverageInfo,
133+
vector::Vector,
134+
};
130135

131136
#[cfg(feature = "async")]
132137
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]

src/prelude.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ pub use crate::HookTriggers as LuaHookTriggers;
2222

2323
#[cfg(feature = "luau")]
2424
#[doc(no_inline)]
25-
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector};
25+
pub use crate::{
26+
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, Vector as LuaVector,
27+
};
2628

2729
#[cfg(feature = "async")]
2830
#[doc(no_inline)]

tests/chunk.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,6 @@ fn test_chunk_macro() -> Result<()> {
9696
#[cfg(feature = "luau")]
9797
#[test]
9898
fn test_compiler() -> Result<()> {
99-
use std::vec;
100-
10199
let compiler = mlua::Compiler::new()
102100
.set_optimization_level(2)
103101
.set_debug_level(2)
@@ -106,10 +104,11 @@ fn test_compiler() -> Result<()> {
106104
.set_vector_lib("vector")
107105
.set_vector_ctor("new")
108106
.set_vector_type("vector")
109-
.set_mutable_globals(vec!["mutable_global".into()])
110-
.set_userdata_types(vec!["MyUserdata".into()]);
107+
.set_mutable_globals(vec!["mutable_global"])
108+
.set_userdata_types(vec!["MyUserdata"])
109+
.set_disabled_builtins(vec!["tostring"]);
111110

112-
assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok());
111+
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
113112

114113
// Error
115114
match compiler.compile("%") {
@@ -122,6 +121,32 @@ fn test_compiler() -> Result<()> {
122121
Ok(())
123122
}
124123

124+
#[cfg(feature = "luau")]
125+
#[test]
126+
fn test_compiler_library_constants() {
127+
use mlua::{CompileConstant, Compiler, Vector};
128+
129+
let compiler = Compiler::new()
130+
.set_optimization_level(2)
131+
.set_library_constants(vec![
132+
("mylib", "const_bool", CompileConstant::Boolean(true)),
133+
("mylib", "const_num", CompileConstant::Number(123.0)),
134+
("mylib", "const_vec", CompileConstant::Vector(Vector::zero())),
135+
("mylib", "const_str", "value1".into()),
136+
]);
137+
138+
let lua = Lua::new();
139+
lua.set_compiler(compiler);
140+
let const_bool = lua.load("return mylib.const_bool").eval::<bool>().unwrap();
141+
assert_eq!(const_bool, true);
142+
let const_num = lua.load("return mylib.const_num").eval::<f64>().unwrap();
143+
assert_eq!(const_num, 123.0);
144+
let const_vec = lua.load("return mylib.const_vec").eval::<Vector>().unwrap();
145+
assert_eq!(const_vec, Vector::zero());
146+
let const_str = lua.load("return mylib.const_str").eval::<String>();
147+
assert_eq!(const_str.unwrap(), "value1");
148+
}
149+
125150
#[test]
126151
fn test_chunk_wrap() -> Result<()> {
127152
let lua = Lua::new();

0 commit comments

Comments
 (0)