Skip to content

Commit 8173ba0

Browse files
committed
update
1 parent d0fd4a0 commit 8173ba0

File tree

3 files changed

+238
-83
lines changed

3 files changed

+238
-83
lines changed

crates/luars/src/stdlib/basic.rs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -194,28 +194,33 @@ fn lua_tostring(vm: &mut LuaVM) -> LuaResult<MultiValue> {
194194
}
195195

196196
/// select(index, ...) - Return subset of arguments
197-
/// OPTIMIZED: Avoid Vec allocation for common case
197+
/// ULTRA-OPTIMIZED: Fast path for "#" and single-value returns
198198
fn lua_select(vm: &mut LuaVM) -> LuaResult<MultiValue> {
199199
let frame = vm.current_frame();
200200
let base_ptr = frame.base_ptr as usize;
201201
let top = frame.top as usize;
202202

203-
// Get index argument (at register 1)
204-
let index_arg = if base_ptr + 1 < vm.register_stack.len() && 1 < top {
205-
vm.register_stack[base_ptr + 1]
206-
} else {
203+
if top <= 1 {
207204
return Err(vm.error("bad argument #1 to 'select' (value expected)".to_string()));
208-
};
205+
}
206+
207+
let index_arg = vm.register_stack[base_ptr + 1];
208+
209+
// Total args after index = top - 2 (subtract function slot and index slot)
210+
let vararg_count = top.saturating_sub(2);
209211

210-
// Handle "#" special case - return count of varargs
212+
// FAST PATH: Check for "#" using string interning
213+
// StringId can be compared directly without string lookup
211214
if let Some(string_id) = index_arg.as_string_id() {
215+
// Check if this is the interned "#" string
216+
// Note: Most Lua code uses "#" as literal, so it's likely interned
212217
if let Some(s) = vm.object_pool.get_string(string_id) {
213-
if s.as_str() == "#" {
214-
// Count of extra arguments (excluding index itself)
215-
let count = top.saturating_sub(2); // top - 1 (index) - 1 (function)
216-
return Ok(MultiValue::single(LuaValue::integer(count as i64)));
218+
let s_str = s.as_str();
219+
if s_str.len() == 1 && s_str.as_bytes()[0] == b'#' {
220+
return Ok(MultiValue::single(LuaValue::integer(vararg_count as i64)));
217221
}
218222
}
223+
return Err(vm.error("bad argument #1 to 'select' (number expected)".to_string()));
219224
}
220225

221226
let index = index_arg
@@ -226,26 +231,34 @@ fn lua_select(vm: &mut LuaVM) -> LuaResult<MultiValue> {
226231
return Err(vm.error("bad argument #1 to 'select' (index out of range)".to_string()));
227232
}
228233

229-
let arg_count = top.saturating_sub(1); // Exclude function register
230-
231-
let start = if index > 0 {
232-
index as usize
234+
// Calculate start position (1-based to 0-based)
235+
let start_idx = if index > 0 {
236+
(index - 1) as usize
233237
} else {
234-
(arg_count as i64 + index) as usize
238+
// Negative index: count from end
239+
let abs_idx = (-index) as usize;
240+
if abs_idx > vararg_count {
241+
return Err(vm.error("bad argument #1 to 'select' (index out of range)".to_string()));
242+
}
243+
vararg_count - abs_idx
235244
};
236245

237-
if start >= arg_count {
246+
if start_idx >= vararg_count {
238247
return Ok(MultiValue::empty());
239248
}
240249

241-
// Collect result directly from registers
242-
let result_count = arg_count - start;
250+
// FAST PATH: Single value return (most common case: select(n, ...))
251+
let result_count = vararg_count - start_idx;
252+
if result_count == 1 {
253+
let reg_idx = base_ptr + 2 + start_idx;
254+
return Ok(MultiValue::single(vm.register_stack[reg_idx]));
255+
}
256+
257+
// Multi-value return: collect from registers
243258
let mut result = Vec::with_capacity(result_count);
244259
for i in 0..result_count {
245-
let reg_idx = base_ptr + 1 + start + i;
246-
if reg_idx < vm.register_stack.len() {
247-
result.push(vm.register_stack[reg_idx]);
248-
}
260+
let reg_idx = base_ptr + 2 + start_idx + i;
261+
result.push(vm.register_stack[reg_idx]);
249262
}
250263

251264
Ok(MultiValue::multiple(result))

crates/luars/src/stdlib/math.rs

Lines changed: 147 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,54 @@ pub fn create_math_lib() -> LibraryModule {
4343
module
4444
}
4545

46+
/// OPTIMIZED: Get number without allocating error message on success path
47+
#[inline(always)]
48+
fn get_number_fast(vm: &LuaVM, idx: usize) -> Option<f64> {
49+
let frame = vm.current_frame();
50+
let base_ptr = frame.base_ptr as usize;
51+
let top = frame.top as usize;
52+
53+
if idx < top {
54+
let value = vm.register_stack[base_ptr + idx];
55+
value.as_number()
56+
} else {
57+
None
58+
}
59+
}
60+
4661
fn get_number(vm: &mut LuaVM, idx: usize, func_name: &str) -> LuaResult<f64> {
47-
let value = require_arg(vm, idx, func_name)?;
48-
if let Some(value) = value.as_number() {
49-
Ok(value)
62+
if let Some(n) = get_number_fast(vm, idx) {
63+
Ok(n)
5064
} else {
5165
Err(vm.error(format!(
5266
"bad argument #{} to '{}' (number expected)",
53-
idx + 1,
67+
idx,
5468
func_name
5569
)))
5670
}
5771
}
5872

5973
fn math_abs(vm: &mut LuaVM) -> LuaResult<MultiValue> {
60-
let x = get_number(vm, 1, "math.abs")?;
61-
Ok(MultiValue::single(LuaValue::float(x.abs())))
74+
let frame = vm.current_frame();
75+
let base_ptr = frame.base_ptr as usize;
76+
let top = frame.top as usize;
77+
78+
if top <= 1 {
79+
return Err(vm.error("bad argument #1 to 'abs' (number expected)".to_string()));
80+
}
81+
82+
let value = vm.register_stack[base_ptr + 1];
83+
84+
// Fast path: preserve integer type
85+
if let Some(i) = value.as_integer() {
86+
return Ok(MultiValue::single(LuaValue::integer(i.abs())));
87+
}
88+
89+
if let Some(f) = value.as_float() {
90+
return Ok(MultiValue::single(LuaValue::float(f.abs())));
91+
}
92+
93+
Err(vm.error("bad argument #1 to 'abs' (number expected)".to_string()))
6294
}
6395

6496
fn math_acos(vm: &mut LuaVM) -> LuaResult<MultiValue> {
@@ -78,8 +110,26 @@ fn math_atan(vm: &mut LuaVM) -> LuaResult<MultiValue> {
78110
}
79111

80112
fn math_ceil(vm: &mut LuaVM) -> LuaResult<MultiValue> {
81-
let x = get_number(vm, 1, "math.ceil")?;
82-
Ok(MultiValue::single(LuaValue::float(x.ceil())))
113+
let frame = vm.current_frame();
114+
let base_ptr = frame.base_ptr as usize;
115+
let top = frame.top as usize;
116+
117+
if top <= 1 {
118+
return Err(vm.error("bad argument #1 to 'ceil' (number expected)".to_string()));
119+
}
120+
121+
let value = vm.register_stack[base_ptr + 1];
122+
123+
// Fast path: integers are already ceil'd
124+
if let Some(i) = value.as_integer() {
125+
return Ok(MultiValue::single(LuaValue::integer(i)));
126+
}
127+
128+
if let Some(f) = value.as_float() {
129+
return Ok(MultiValue::single(LuaValue::integer(f.ceil() as i64)));
130+
}
131+
132+
Err(vm.error("bad argument #1 to 'ceil' (number expected)".to_string()))
83133
}
84134

85135
fn math_cos(vm: &mut LuaVM) -> LuaResult<MultiValue> {
@@ -98,8 +148,26 @@ fn math_exp(vm: &mut LuaVM) -> LuaResult<MultiValue> {
98148
}
99149

100150
fn math_floor(vm: &mut LuaVM) -> LuaResult<MultiValue> {
101-
let x = get_number(vm, 1, "math.floor")?;
102-
Ok(MultiValue::single(LuaValue::integer(x.floor() as i64)))
151+
let frame = vm.current_frame();
152+
let base_ptr = frame.base_ptr as usize;
153+
let top = frame.top as usize;
154+
155+
if top <= 1 {
156+
return Err(vm.error("bad argument #1 to 'floor' (number expected)".to_string()));
157+
}
158+
159+
let value = vm.register_stack[base_ptr + 1];
160+
161+
// Fast path: integers are already floor'd
162+
if let Some(i) = value.as_integer() {
163+
return Ok(MultiValue::single(LuaValue::integer(i)));
164+
}
165+
166+
if let Some(f) = value.as_float() {
167+
return Ok(MultiValue::single(LuaValue::integer(f.floor() as i64)));
168+
}
169+
170+
Err(vm.error("bad argument #1 to 'floor' (number expected)".to_string()))
103171
}
104172

105173
fn math_fmod(vm: &mut LuaVM) -> LuaResult<MultiValue> {
@@ -117,62 +185,66 @@ fn math_log(vm: &mut LuaVM) -> LuaResult<MultiValue> {
117185
Ok(MultiValue::single(LuaValue::float(result)))
118186
}
119187

188+
/// OPTIMIZED: Direct stack access without get_arg overhead
120189
fn math_max(vm: &mut LuaVM) -> LuaResult<MultiValue> {
121-
use crate::lib_registry::{arg_count, get_arg};
190+
let frame = vm.current_frame();
191+
let base_ptr = frame.base_ptr as usize;
192+
let top = frame.top as usize;
193+
let argc = top.saturating_sub(1);
122194

123-
let argc = arg_count(vm);
124195
if argc == 0 {
125196
return Err(vm.error("bad argument to 'math.max' (value expected)".to_string()));
126197
}
127198

128-
// Get first argument
129-
let first = get_arg(vm, 1).unwrap();
199+
// Get first argument directly
200+
let first = vm.register_stack[base_ptr + 1];
130201
let mut max_val = first
131202
.as_number()
132203
.ok_or_else(|| vm.error("bad argument to 'math.max' (number expected)".to_string()))?;
133204
let mut max_arg = first;
134205

135-
// Compare with rest
206+
// Compare with rest - direct stack access
136207
for i in 2..=argc {
137-
if let Some(arg) = get_arg(vm, i) {
138-
let val = arg.as_number().ok_or_else(|| {
139-
vm.error("bad argument to 'math.max' (number expected)".to_string())
140-
})?;
141-
if val > max_val {
142-
max_val = val;
143-
max_arg = arg;
144-
}
208+
let arg = vm.register_stack[base_ptr + i];
209+
let val = arg.as_number().ok_or_else(|| {
210+
vm.error("bad argument to 'math.max' (number expected)".to_string())
211+
})?;
212+
if val > max_val {
213+
max_val = val;
214+
max_arg = arg;
145215
}
146216
}
147217

148218
Ok(MultiValue::single(max_arg))
149219
}
150220

221+
/// OPTIMIZED: Direct stack access without get_arg overhead
151222
fn math_min(vm: &mut LuaVM) -> LuaResult<MultiValue> {
152-
use crate::lib_registry::{arg_count, get_arg};
223+
let frame = vm.current_frame();
224+
let base_ptr = frame.base_ptr as usize;
225+
let top = frame.top as usize;
226+
let argc = top.saturating_sub(1);
153227

154-
let argc = arg_count(vm);
155228
if argc == 0 {
156229
return Err(vm.error("bad argument to 'math.min' (value expected)".to_string()));
157230
}
158231

159-
// Get first argument
160-
let first = get_arg(vm, 1).unwrap();
232+
// Get first argument directly
233+
let first = vm.register_stack[base_ptr + 1];
161234
let mut min_val = first
162235
.as_number()
163236
.ok_or_else(|| vm.error("bad argument to 'math.min' (number expected)".to_string()))?;
164237
let mut min_arg = first;
165238

166-
// Compare with rest
239+
// Compare with rest - direct stack access
167240
for i in 2..=argc {
168-
if let Some(arg) = get_arg(vm, i) {
169-
let val = arg.as_number().ok_or_else(|| {
170-
vm.error("bad argument to 'math.min' (number expected)".to_string())
171-
})?;
172-
if val < min_val {
173-
min_val = val;
174-
min_arg = arg;
175-
}
241+
let arg = vm.register_stack[base_ptr + i];
242+
let val = arg.as_number().ok_or_else(|| {
243+
vm.error("bad argument to 'math.min' (number expected)".to_string())
244+
})?;
245+
if val < min_val {
246+
min_val = val;
247+
min_arg = arg;
176248
}
177249
}
178250

@@ -195,28 +267,51 @@ fn math_rad(vm: &mut LuaVM) -> LuaResult<MultiValue> {
195267
Ok(MultiValue::single(LuaValue::float(x.to_radians())))
196268
}
197269

198-
fn math_random(vm: &mut LuaVM) -> LuaResult<MultiValue> {
199-
use std::collections::hash_map::RandomState;
200-
use std::hash::{BuildHasher, Hash, Hasher};
270+
/// Thread-local random state using xorshift64 algorithm
271+
/// Much faster than creating new RandomState each call
272+
use std::cell::Cell;
273+
thread_local! {
274+
static RANDOM_STATE: Cell<u64> = Cell::new(0x853c49e6748fea9b_u64);
275+
}
201276

202-
let argc = crate::lib_registry::arg_count(vm);
277+
/// Fast xorshift64 random number generator
278+
#[inline(always)]
279+
fn xorshift64() -> u64 {
280+
RANDOM_STATE.with(|state| {
281+
let mut x = state.get();
282+
x ^= x << 13;
283+
x ^= x >> 7;
284+
x ^= x << 17;
285+
state.set(x);
286+
x
287+
})
288+
}
203289

204-
// Simple pseudo-random using hash
205-
let mut hasher = RandomState::new().build_hasher();
206-
std::time::SystemTime::now().hash(&mut hasher);
207-
let hash = hasher.finish();
208-
let random = (hash % 1000000) as f64 / 1000000.0;
290+
fn math_random(vm: &mut LuaVM) -> LuaResult<MultiValue> {
291+
let frame = vm.current_frame();
292+
let top = frame.top as usize;
293+
let argc = top.saturating_sub(1);
294+
295+
// Generate random u64 and convert to [0, 1) float
296+
let rand_u64 = xorshift64();
297+
let random = (rand_u64 >> 11) as f64 / (1u64 << 53) as f64;
209298

210299
match argc {
211300
0 => Ok(MultiValue::single(LuaValue::float(random))),
212301
1 => {
213302
let m = get_number(vm, 1, "math.random")? as i64;
303+
if m < 1 {
304+
return Err(vm.error("bad argument #1 to 'random' (interval is empty)".to_string()));
305+
}
214306
let result = (random * m as f64).floor() as i64 + 1;
215307
Ok(MultiValue::single(LuaValue::integer(result)))
216308
}
217309
_ => {
218310
let m = get_number(vm, 1, "math.random")? as i64;
219311
let n = get_number(vm, 2, "math.random")? as i64;
312+
if m > n {
313+
return Err(vm.error("bad argument #1 to 'random' (interval is empty)".to_string()));
314+
}
220315
let range = (n - m + 1) as f64;
221316
let result = m + (random * range).floor() as i64;
222317
Ok(MultiValue::single(LuaValue::integer(result)))
@@ -225,8 +320,13 @@ fn math_random(vm: &mut LuaVM) -> LuaResult<MultiValue> {
225320
}
226321

227322
fn math_randomseed(vm: &mut LuaVM) -> LuaResult<MultiValue> {
228-
// Seed is ignored in our simple implementation
229-
let _x = get_number(vm, 1, "math.randomseed")?;
323+
let x = get_number(vm, 1, "math.randomseed")? as u64;
324+
// Seed the random state
325+
RANDOM_STATE.with(|state| {
326+
// Ensure seed is non-zero for xorshift
327+
let seed = if x == 0 { 0x853c49e6748fea9b_u64 } else { x };
328+
state.set(seed);
329+
});
230330
Ok(MultiValue::empty())
231331
}
232332

0 commit comments

Comments
 (0)