Skip to content

Commit d8bed54

Browse files
committed
update
1 parent 4caf094 commit d8bed54

File tree

5 files changed

+259
-82
lines changed

5 files changed

+259
-82
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,34 @@ fn parse_lua_int(text: &str) -> i64 {
5151
text.parse::<i64>().unwrap_or(0)
5252
}
5353

54+
/// Lua left shift: x << n (returns 0 if |n| >= 64)
55+
/// Negative n means right shift
56+
#[inline(always)]
57+
fn lua_shl(l: i64, r: i64) -> i64 {
58+
if r >= 64 || r <= -64 {
59+
0
60+
} else if r >= 0 {
61+
(l as u64).wrapping_shl(r as u32) as i64
62+
} else {
63+
// Negative shift means right shift (logical)
64+
(l as u64).wrapping_shr((-r) as u32) as i64
65+
}
66+
}
67+
68+
/// Lua right shift: x >> n (logical shift, returns 0 if |n| >= 64)
69+
/// Negative n means left shift
70+
#[inline(always)]
71+
fn lua_shr(l: i64, r: i64) -> i64 {
72+
if r >= 64 || r <= -64 {
73+
0
74+
} else if r >= 0 {
75+
(l as u64).wrapping_shr(r as u32) as i64
76+
} else {
77+
// Negative shift means left shift
78+
(l as u64).wrapping_shl((-r) as u32) as i64
79+
}
80+
}
81+
5482
//======================================================================================
5583
// NEW API: ExpDesc-based expression compilation (Lua 5.4 compatible)
5684
//======================================================================================
@@ -129,7 +157,14 @@ fn compile_literal_expr_desc(c: &mut Compiler, expr: &LuaLiteralExpr) -> Result<
129157
&& !text.contains('.')
130158
&& !text.to_lowercase().contains('p');
131159

132-
if !num.is_float() || is_hex_int {
160+
// Check if text has decimal point or exponent (should be treated as float)
161+
// This handles cases like 1.0e19 or 9223372036854775808.0
162+
let text_lower = text.to_lowercase();
163+
let has_decimal_or_exp = text.contains('.') ||
164+
(!text_lower.starts_with("0x") && text_lower.contains('e'));
165+
166+
// Treat as integer only if: no decimal/exponent OR is hex int
167+
if (!num.is_float() && !has_decimal_or_exp) || is_hex_int {
133168
// Parse as integer - use our custom parser for hex numbers
134169
// Lua 5.4: 0xFFFFFFFFFFFFFFFF should be interpreted as -1 (two's complement)
135170
let int_val = parse_lua_int(text);
@@ -833,8 +868,15 @@ fn compile_literal_expr(
833868
&& !text.contains('.')
834869
&& !text.to_lowercase().contains('p');
835870

871+
// Check if text has decimal point or exponent (should be treated as float)
872+
// This handles cases like 1.0e19 or 9223372036854775808.0
873+
let text_lower = text.to_lowercase();
874+
let has_decimal_or_exp = text.contains('.') ||
875+
(!text_lower.starts_with("0x") && text_lower.contains('e'));
876+
836877
// Lua 5.4 optimization: Try LoadI for integers, LoadF for simple floats
837-
if !num.is_float() || is_hex_int {
878+
// Treat as integer only if: parser says integer AND no decimal/exponent AND is hex int
879+
if (!num.is_float() && !has_decimal_or_exp) || is_hex_int {
838880
let int_val = parse_lua_int(text);
839881
// Try LoadI first (fast path for small integers)
840882
if let Some(_) = emit_loadi(c, reg, int_val) {
@@ -944,8 +986,8 @@ fn try_eval_const_int(expr: &LuaExpr) -> Option<i64> {
944986
BinaryOperator::OpBAnd => Some(left_val & right_val),
945987
BinaryOperator::OpBOr => Some(left_val | right_val),
946988
BinaryOperator::OpBXor => Some(left_val ^ right_val),
947-
BinaryOperator::OpShl => Some(left_val << (right_val & 0x3f)),
948-
BinaryOperator::OpShr => Some(left_val >> (right_val & 0x3f)),
989+
BinaryOperator::OpShl => Some(lua_shl(left_val, right_val)),
990+
BinaryOperator::OpShr => Some(lua_shr(left_val, right_val)),
949991
_ => None,
950992
}
951993
}
@@ -1048,8 +1090,8 @@ fn compile_binary_expr_to(
10481090
BinaryOperator::OpBAnd => Some((left_int & right_int) as f64),
10491091
BinaryOperator::OpBOr => Some((left_int | right_int) as f64),
10501092
BinaryOperator::OpBXor => Some((left_int ^ right_int) as f64),
1051-
BinaryOperator::OpShl => Some((left_int << (right_int & 0x3f)) as f64),
1052-
BinaryOperator::OpShr => Some((left_int >> (right_int & 0x3f)) as f64),
1093+
BinaryOperator::OpShl => Some(lua_shl(left_int, right_int) as f64),
1094+
BinaryOperator::OpShr => Some(lua_shr(left_int, right_int) as f64),
10531095
_ => None,
10541096
};
10551097

@@ -1125,8 +1167,8 @@ fn compile_binary_expr_to(
11251167
BinaryOperator::OpBAnd => left_int & right_int,
11261168
BinaryOperator::OpBOr => left_int | right_int,
11271169
BinaryOperator::OpBXor => left_int ^ right_int,
1128-
BinaryOperator::OpShl => left_int << (right_int & 0x3f),
1129-
BinaryOperator::OpShr => left_int >> (right_int & 0x3f),
1170+
BinaryOperator::OpShl => lua_shl(left_int, right_int),
1171+
BinaryOperator::OpShr => lua_shr(left_int, right_int),
11301172
_ => unreachable!(),
11311173
};
11321174
Some(result_int as f64)
@@ -1504,18 +1546,14 @@ fn compile_binary_expr_to(
15041546
alloc_register(c)
15051547
}
15061548
});
1507-
// Lua 5.4: Use ShlI for immediate left shift
1508-
// Note: ShlI uses negated immediate: sC << R[B] where sC is the immediate
1509-
// To shift left by N, we use -N as the immediate
1510-
let neg_imm = if int_val < 0 {
1511-
((-int_val) + 256) as u32
1512-
} else {
1513-
((-int_val) + 512) as u32 % 512
1514-
};
1549+
// Lua 5.4: x << n is equivalent to x >> -n
1550+
// Use ShrI with negated immediate: R[A] = R[B] >> sC
1551+
// sC encoding: (val + 127) & 0xff, where val = -n
1552+
let neg_imm = ((-int_val + 127) & 0xff) as u32;
15151553
emit(
15161554
c,
15171555
Instruction::encode_abc(
1518-
OpCode::ShlI,
1556+
OpCode::ShrI,
15191557
result_reg,
15201558
left_reg,
15211559
neg_imm,
@@ -2288,11 +2326,16 @@ pub fn compile_call_expr_with_returns_and_dest(
22882326
let nactvar = c.nactvar as u32;
22892327
let args_start = d + 1;
22902328

2291-
// Check if args_start would overwrite active locals
2329+
// Check if dest or args would overwrite active locals
22922330
// Active locals occupy R[0] through R[nactvar-1]
2293-
// If args_start < nactvar, arguments would overwrite locals!
2294-
if args_start < nactvar {
2295-
// Conflict! Arguments would overwrite active locals
2331+
// - If d < nactvar: moving function to d would overwrite a local before args are compiled!
2332+
// - If args_start < nactvar: arguments would overwrite locals!
2333+
//
2334+
// Example: `x = tonumber(x)` where x is in R[0]:
2335+
// - d = 0, nactvar = 1, args_start = 1
2336+
// - If we move tonumber to R[0] first, we overwrite x before reading it as argument!
2337+
if d < nactvar || args_start < nactvar {
2338+
// Conflict! Either dest or arguments would overwrite active locals
22962339
// Solution: Place function at freereg (after all locals) and move result back later
22972340
let new_func_reg = if c.freereg < nactvar {
22982341
// Ensure freereg is at least nactvar

crates/luars/src/lua_value/lua_value.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,17 @@ impl LuaValue {
295295
Some(self.secondary as i64)
296296
} else if (self.primary & TAG_MASK) == TAG_FLOAT {
297297
// Lua 5.4 semantics: floats with zero fraction are integers
298+
// But must be in i64 range!
298299
let f = f64::from_bits(self.secondary);
299300
if f.fract() == 0.0 && f.is_finite() {
300-
Some(f as i64)
301+
// Check if in i64 range before casting
302+
const MIN_INT_F: f64 = i64::MIN as f64;
303+
const MAX_INT_F: f64 = i64::MAX as f64;
304+
if f >= MIN_INT_F && f <= MAX_INT_F {
305+
Some(f as i64)
306+
} else {
307+
None
308+
}
301309
} else {
302310
None
303311
}

crates/luars/src/lua_vm/execute/arithmetic_instructions.rs

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -865,28 +865,34 @@ pub fn exec_shl(vm: &mut LuaVM, instr: u32, pc: &mut usize, base_ptr: usize) {
865865
// Fast path: both are integers
866866
if let (Some(l), Some(r)) = (left.as_integer_strict(), right.as_integer_strict()) {
867867
// Lua shift semantics: negative r means shift right
868-
let result = if r >= 0 {
869-
l.wrapping_shl((r & 63) as u32)
870-
} else {
871-
(l as u64).wrapping_shr(((-r) & 63) as u32) as i64
872-
};
868+
// If |r| >= 64, result is 0
869+
let result = lua_shl(l, r);
873870
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
874871
*pc += 1;
875872
return;
876873
}
877874
// Slow path
878875
if let (Some(l), Some(r)) = (left.as_integer(), right.as_integer()) {
879-
let result = if r >= 0 {
880-
l.wrapping_shl((r & 63) as u32)
881-
} else {
882-
(l as u64).wrapping_shr(((-r) & 63) as u32) as i64
883-
};
876+
let result = lua_shl(l, r);
884877
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
885878
*pc += 1;
886879
}
887880
}
888881
}
889882

883+
/// Lua left shift: x << n (returns 0 if |n| >= 64)
884+
#[inline(always)]
885+
fn lua_shl(l: i64, r: i64) -> i64 {
886+
if r >= 64 || r <= -64 {
887+
0
888+
} else if r >= 0 {
889+
(l as u64).wrapping_shl(r as u32) as i64
890+
} else {
891+
// Negative shift means right shift (logical)
892+
(l as u64).wrapping_shr((-r) as u32) as i64
893+
}
894+
}
895+
890896
/// SHR: R[A] = R[B] >> R[C]
891897
/// OPTIMIZED: Fast path + logical shift (unsigned)
892898
#[inline(always)]
@@ -902,28 +908,34 @@ pub fn exec_shr(vm: &mut LuaVM, instr: u32, pc: &mut usize, base_ptr: usize) {
902908
// Fast path: both are integers
903909
if let (Some(l), Some(r)) = (left.as_integer_strict(), right.as_integer_strict()) {
904910
// Lua uses logical (unsigned) right shift
905-
let result = if r >= 0 {
906-
(l as u64).wrapping_shr((r & 63) as u32) as i64
907-
} else {
908-
l.wrapping_shl(((-r) & 63) as u32)
909-
};
911+
// If shift amount is >= 64 or <= -64, result is 0
912+
let result = lua_shr(l, r);
910913
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
911914
*pc += 1;
912915
return;
913916
}
914917
// Slow path
915918
if let (Some(l), Some(r)) = (left.as_integer(), right.as_integer()) {
916-
let result = if r >= 0 {
917-
(l as u64).wrapping_shr((r & 63) as u32) as i64
918-
} else {
919-
l.wrapping_shl(((-r) & 63) as u32)
920-
};
919+
let result = lua_shr(l, r);
921920
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
922921
*pc += 1;
923922
}
924923
}
925924
}
926925

926+
/// Lua right shift: x >> n (logical shift, returns 0 if |n| >= 64)
927+
#[inline(always)]
928+
fn lua_shr(l: i64, r: i64) -> i64 {
929+
if r >= 64 || r <= -64 {
930+
0
931+
} else if r >= 0 {
932+
(l as u64).wrapping_shr(r as u32) as i64
933+
} else {
934+
// Negative shift means left shift
935+
(l as u64).wrapping_shl((-r) as u32) as i64
936+
}
937+
}
938+
927939
/// BANDK: R[A] = R[B] & K[C]
928940
/// OPTIMIZED: Fast path for integer-integer
929941
#[inline(always)]
@@ -1033,22 +1045,14 @@ pub fn exec_shri(vm: &mut LuaVM, instr: u32, pc: &mut usize, base_ptr: usize) {
10331045

10341046
// Fast path
10351047
if let Some(l) = left.as_integer_strict() {
1036-
let result = if sc >= 0 {
1037-
(l as u64).wrapping_shr((sc & 63) as u32) as i64
1038-
} else {
1039-
l.wrapping_shl(((-sc) & 63) as u32)
1040-
};
1048+
let result = lua_shr(l, sc as i64);
10411049
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
10421050
*pc += 1;
10431051
return;
10441052
}
10451053
// Slow path
10461054
if let Some(l) = left.as_integer() {
1047-
let result = if sc >= 0 {
1048-
(l as u64).wrapping_shr((sc & 63) as u32) as i64
1049-
} else {
1050-
l.wrapping_shl(((-sc) & 63) as u32)
1051-
};
1055+
let result = lua_shr(l, sc as i64);
10521056
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
10531057
*pc += 1;
10541058
}
@@ -1068,22 +1072,14 @@ pub fn exec_shli(vm: &mut LuaVM, instr: u32, pc: &mut usize, base_ptr: usize) {
10681072

10691073
// Fast path
10701074
if let Some(r) = right.as_integer_strict() {
1071-
let result = if r >= 0 {
1072-
(sc as i64).wrapping_shl((r & 63) as u32)
1073-
} else {
1074-
((sc as i64) as u64).wrapping_shr(((-r) & 63) as u32) as i64
1075-
};
1075+
let result = lua_shl(sc as i64, r);
10761076
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
10771077
*pc += 1;
10781078
return;
10791079
}
10801080
// Slow path
10811081
if let Some(r) = right.as_integer() {
1082-
let result = if r >= 0 {
1083-
(sc as i64).wrapping_shl((r & 63) as u32)
1084-
} else {
1085-
((sc as i64) as u64).wrapping_shr(((-r) & 63) as u32) as i64
1086-
};
1082+
let result = lua_shl(sc as i64, r);
10871083
*vm.register_stack.as_mut_ptr().add(base_ptr + a) = LuaValue::integer(result);
10881084
*pc += 1;
10891085
}
@@ -1275,8 +1271,19 @@ pub fn exec_mmbin(
12751271
if let Some(result) = vm.call_metamethod(&metamethod, &[ra, rb])? {
12761272
*vm.register_stack.as_mut_ptr().add(base_ptr + dest_reg) = result;
12771273
}
1274+
} else {
1275+
// No metamethod found - check if this is a bitwise operation that needs error handling
1276+
// TM_BAND=13, TM_BOR=14, TM_BXOR=15, TM_SHL=16, TM_SHR=17, TM_BNOT=19
1277+
if (13..=17).contains(&c) || c == 19 {
1278+
// Check if operands can be converted to integer
1279+
if ra.as_integer().is_none() {
1280+
return Err(vm.error("number has no integer representation"));
1281+
}
1282+
if rb.as_integer().is_none() {
1283+
return Err(vm.error("number has no integer representation"));
1284+
}
1285+
}
12781286
}
1279-
// If no metamethod, leave the instruction result as-is (error will be caught elsewhere)
12801287
}
12811288
Ok(())
12821289
}

0 commit comments

Comments
 (0)