Skip to main content

lua_stdlib/
bit32_lib.rs

1//! `bit32` — the Lua 5.2/5.3 32-bit bitwise library.
2//!
3//! This library was present (default-on) in Lua 5.3 and removed in Lua 5.4
4//! (`specs/research/5.3-upstream-delta.md` delta #11). Its operations mask
5//! every operand and result to **32 bits**, which is distinct from 5.3's
6//! native 64-bit `&`/`|`/`~`/`<<`/`>>` operators (`5.3-upstream-delta.md`
7//! risk #5). We register it only under the 5.3 backend.
8//!
9//! PRELIMINARY: this is a minimal, exploratory subset proving the per-version
10//! stdlib-roster seam — it implements the most common operations. The full
11//! 5.2/5.3 surface (`btest`, `extract`, `replace`, `lrotate`, `rrotate`,
12//! `arshift`) is left as a clear TODO below.
13
14use crate::state_stub::{LuaState, LuaStateStubExt as _};
15use lua_types::{LuaError, LuaValue};
16
17type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
18
19/// Mask a Lua integer argument down to an unsigned 32-bit value, matching
20/// `bit32`'s `lua_Unsigned`-truncation semantics.
21fn arg_u32(state: &mut LuaState, arg: i32) -> Result<u32, LuaError> {
22    let n = state.check_integer(arg)?;
23    Ok(n as u32)
24}
25
26/// Push an unsigned 32-bit result as a Lua integer.
27fn push_u32(state: &mut LuaState, v: u32) {
28    state.push(LuaValue::Int(v as i64));
29}
30
31/// Fold a variadic AND/OR/XOR over every argument, starting from `init`.
32fn fold(
33    state: &mut LuaState,
34    init: u32,
35    op: fn(u32, u32) -> u32,
36) -> Result<usize, LuaError> {
37    let top = state.get_top();
38    let mut acc = init;
39    for i in 1..=top {
40        acc = op(acc, arg_u32(state, i)?);
41    }
42    push_u32(state, acc & 0xFFFF_FFFF);
43    Ok(1)
44}
45
46fn bit_band(state: &mut LuaState) -> Result<usize, LuaError> {
47    fold(state, 0xFFFF_FFFF, |a, b| a & b)
48}
49
50fn bit_bor(state: &mut LuaState) -> Result<usize, LuaError> {
51    fold(state, 0, |a, b| a | b)
52}
53
54fn bit_bxor(state: &mut LuaState) -> Result<usize, LuaError> {
55    fold(state, 0, |a, b| a ^ b)
56}
57
58fn bit_bnot(state: &mut LuaState) -> Result<usize, LuaError> {
59    let a = arg_u32(state, 1)?;
60    push_u32(state, !a);
61    Ok(1)
62}
63
64fn bit_lshift(state: &mut LuaState) -> Result<usize, LuaError> {
65    let a = arg_u32(state, 1)?;
66    let disp = state.check_integer(2)?;
67    push_u32(state, shift(a, disp));
68    Ok(1)
69}
70
71fn bit_rshift(state: &mut LuaState) -> Result<usize, LuaError> {
72    let a = arg_u32(state, 1)?;
73    let disp = state.check_integer(2)?;
74    push_u32(state, shift(a, -disp));
75    Ok(1)
76}
77
78/// `bit32` logical shift: positive `disp` shifts left, negative shifts right;
79/// a displacement of 32 or more (in magnitude) yields 0, matching 5.3.
80fn shift(x: u32, disp: i64) -> u32 {
81    if disp <= -32 || disp >= 32 {
82        0
83    } else if disp >= 0 {
84        x << disp
85    } else {
86        x >> (-disp)
87    }
88}
89
90/// `w` low bits set, matching `bit32`'s field mask (`width` in `1..=32`).
91fn mask_w(w: u32) -> u32 {
92    if w >= 32 {
93        0xFFFF_FFFF
94    } else {
95        (1u32 << w) - 1
96    }
97}
98
99/// Validate and return the `(field, width)` pair for `extract`/`replace`,
100/// matching Lua 5.2's `fieldargs` bounds checks. `width_arg` defaults to 1.
101fn field_args(
102    state: &mut LuaState,
103    field_arg: i32,
104    width_arg: i32,
105) -> Result<(u32, u32), LuaError> {
106    let f = state.check_integer(field_arg)?;
107    let w = if state.get_top() >= width_arg {
108        state.check_integer(width_arg)?
109    } else {
110        1
111    };
112    if f < 0 {
113        return Err(LuaError::arg_error(field_arg, "field cannot be negative"));
114    }
115    if w < 1 {
116        return Err(LuaError::arg_error(width_arg, "width must be positive"));
117    }
118    if f + w > 32 {
119        return Err(LuaError::arg_error(
120            field_arg,
121            "trying to access non-existent bits",
122        ));
123    }
124    Ok((f as u32, w as u32))
125}
126
127/// `bit32.btest(...)` — true iff the AND of all arguments is non-zero.
128fn bit_btest(state: &mut LuaState) -> Result<usize, LuaError> {
129    let top = state.get_top();
130    let mut acc: u32 = 0xFFFF_FFFF;
131    for i in 1..=top {
132        acc &= arg_u32(state, i)?;
133    }
134    state.push(LuaValue::Bool(acc != 0));
135    Ok(1)
136}
137
138/// `bit32.extract(n, field [, width])` — the `width` bits of `n` at `field`.
139fn bit_extract(state: &mut LuaState) -> Result<usize, LuaError> {
140    let n = arg_u32(state, 1)?;
141    let (f, w) = field_args(state, 2, 3)?;
142    push_u32(state, (n >> f) & mask_w(w));
143    Ok(1)
144}
145
146/// `bit32.replace(n, v, field [, width])` — `n` with its `width` bits at
147/// `field` replaced by the low bits of `v`.
148fn bit_replace(state: &mut LuaState) -> Result<usize, LuaError> {
149    let n = arg_u32(state, 1)?;
150    let v = arg_u32(state, 2)?;
151    let (f, w) = field_args(state, 3, 4)?;
152    let m = mask_w(w);
153    push_u32(state, (n & !(m << f)) | ((v & m) << f));
154    Ok(1)
155}
156
157/// `bit32.arshift(x, disp)` — arithmetic right shift (sign-propagating);
158/// negative `disp` shifts left.
159fn bit_arshift(state: &mut LuaState) -> Result<usize, LuaError> {
160    let x = arg_u32(state, 1)?;
161    let disp = state.check_integer(2)?;
162    let r = if disp < 0 {
163        shift(x, -disp)
164    } else if disp >= 32 {
165        if x & 0x8000_0000 != 0 {
166            0xFFFF_FFFF
167        } else {
168            0
169        }
170    } else if x & 0x8000_0000 != 0 {
171        (x >> disp) | !(0xFFFF_FFFFu32 >> disp)
172    } else {
173        x >> disp
174    };
175    push_u32(state, r);
176    Ok(1)
177}
178
179/// 32-bit rotate left by `disp` (mod 32); negative rotates right.
180fn rotate(x: u32, disp: i64) -> u32 {
181    let d = (((disp % 32) + 32) % 32) as u32;
182    if d == 0 {
183        x
184    } else {
185        (x << d) | (x >> (32 - d))
186    }
187}
188
189fn bit_lrotate(state: &mut LuaState) -> Result<usize, LuaError> {
190    let x = arg_u32(state, 1)?;
191    let disp = state.check_integer(2)?;
192    push_u32(state, rotate(x, disp));
193    Ok(1)
194}
195
196fn bit_rrotate(state: &mut LuaState) -> Result<usize, LuaError> {
197    let x = arg_u32(state, 1)?;
198    let disp = state.check_integer(2)?;
199    push_u32(state, rotate(x, -disp));
200    Ok(1)
201}
202
203/// The `bit32` function roster — the full Lua 5.2/5.3 surface.
204const BIT32_FUNCS: &[(&[u8], LuaCFunction)] = &[
205    (b"band", bit_band),
206    (b"bor", bit_bor),
207    (b"bxor", bit_bxor),
208    (b"bnot", bit_bnot),
209    (b"lshift", bit_lshift),
210    (b"rshift", bit_rshift),
211    (b"btest", bit_btest),
212    (b"extract", bit_extract),
213    (b"replace", bit_replace),
214    (b"arshift", bit_arshift),
215    (b"lrotate", bit_lrotate),
216    (b"rrotate", bit_rrotate),
217];
218
219/// Open the `bit32` library, leaving the populated table on the stack.
220pub fn open_bit32(state: &mut LuaState) -> Result<usize, LuaError> {
221    state.new_lib(BIT32_FUNCS)?;
222    Ok(1)
223}
224
225// ──────────────────────────────────────────────────────────────────────────
226// PORT STATUS
227//   source:        src/lbitlib.c (Lua 5.2/5.3)
228//   target_crate:  lua-stdlib
229//   confidence:    low (preliminary multiversion scaffold)
230//   todos:         1
231//   port_notes:    0
232//   unsafe_blocks: 0
233//   notes:         Minimal 5.3-only bit32 subset (band/bor/bxor/bnot/lshift/
234//                  rshift) proving the per-version stdlib roster seam. The
235//                  remaining functions and exact error/range checks are TODO.
236// ──────────────────────────────────────────────────────────────────────────