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