Skip to main content

lua_stdlib/
bit32_lib.rs

1//! `bit32` — the Lua 5.2/5.3 32-bit bitwise library (port of `lbitlib.c`).
2//!
3//! `bit32` was introduced in Lua 5.2 and removed in 5.4 once native 64-bit
4//! bitwise operators (`&` `|` `~` `<<` `>>`) arrived in 5.3. In a stock build it
5//! is present in **both** 5.2 and 5.3 — 5.3 keeps it under the default-on
6//! `LUA_COMPAT_BITLIB` flag — so [`init`](crate::init) registers it under the
7//! `V52 | V53` gate. Verified against the reference binaries: `type(bit32)` is
8//! nil / table / table / nil for 5.1 / 5.2 / 5.3 / 5.4. That gate is
9//! load-bearing; narrowing it to 5.2-only would drop a 5.3 builtin.
10//!
11//! Every operation masks its operands and result to **32 bits** (`mod 2^32`):
12//! this unsigned 32-bit window is the library's defining semantics and is what
13//! distinguishes it from 5.3's native 64-bit operators.
14//!
15//! ## Graduation (idiomatization sprint 2)
16//!
17//! The whole 5.2/5.3 surface is implemented and reference-pinned:
18//! `band` `bor` `bxor` `bnot` `btest` `lshift` `rshift` `arshift` `lrotate`
19//! `rrotate` `extract` `replace`. The behavioral net lives in
20//! `crates/lua-stdlib/tests/bit32_strengthen.rs` (pinned to lua5.2.4, with a
21//! lua5.3.6 contrast for the one version-specific behavior — see [`arg_u32`]).
22
23use crate::state_stub::{LuaState, LuaStateStubExt as _};
24use lua_types::{LuaError, LuaValue, NumberModel};
25
26type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
27
28/// Coerce a Lua number argument to its unsigned 32-bit image, matching
29/// `lbitlib.c`'s argument handling — which differs by host version:
30///
31/// - **5.2 (the `FloatOnly` number model):** `lbitlib.c` uses
32///   `luaL_checkunsigned` → `lua_tounsigned`, which rounds the number to the
33///   nearest integer (ties to even) and reduces it `mod 2^32`. A fractional
34///   float is therefore accepted, e.g. `bit32.band(1.5) == 2`.
35/// - **5.3 (the `Dual` model, under `LUA_COMPAT_BITLIB`):** `lbitlib.c` uses
36///   `luaL_checkinteger`, which rejects a non-integer-valued float with
37///   `number has no integer representation`.
38///
39/// A non-number argument raises `number expected, got <type>` in both, which is
40/// exactly what `check_number` / `check_integer` already produce.
41fn arg_u32(state: &mut LuaState, arg: i32) -> Result<u32, LuaError> {
42    let model = state.global().lua_version.number_model();
43    match model {
44        NumberModel::FloatOnly => {
45            let n = state.check_number(arg)?;
46            Ok(n.round_ties_even().rem_euclid(4_294_967_296.0) as u32)
47        }
48        NumberModel::Dual => {
49            let n = state.check_integer(arg)?;
50            Ok(n as u32)
51        }
52    }
53}
54
55/// Coerce a Lua number argument to a signed integer for `bit32`'s **count**
56/// arguments — the shift/rotate displacement and the `extract`/`replace` field
57/// and width. `lbitlib.c` reads these with `luaL_checkint`/`luaL_checkinteger`,
58/// which (unlike the operand path in [`arg_u32`]) **truncates toward zero**
59/// under 5.2's `FloatOnly` model — e.g. `bit32.lshift(1, 1.5)` shifts by 1, and
60/// `bit32.extract(0xAA, 1.5)` reads bit 1. 5.3 keeps `luaL_checkinteger`'s
61/// reject-on-fraction behavior.
62fn arg_int(state: &mut LuaState, arg: i32) -> Result<i64, LuaError> {
63    let model = state.global().lua_version.number_model();
64    match model {
65        NumberModel::FloatOnly => Ok(state.check_number(arg)?.trunc() as i64),
66        NumberModel::Dual => state.check_integer(arg),
67    }
68}
69
70/// Push an unsigned 32-bit result as a Lua integer.
71fn push_u32(state: &mut LuaState, v: u32) {
72    state.push(LuaValue::Int(v as i64));
73}
74
75/// Fold a variadic AND/OR/XOR over every argument, starting from `init`.
76fn fold(state: &mut LuaState, init: u32, op: fn(u32, u32) -> u32) -> Result<usize, LuaError> {
77    let top = state.get_top();
78    let mut acc = init;
79    for i in 1..=top {
80        acc = op(acc, arg_u32(state, i)?);
81    }
82    push_u32(state, acc);
83    Ok(1)
84}
85
86fn bit_band(state: &mut LuaState) -> Result<usize, LuaError> {
87    fold(state, 0xFFFF_FFFF, |a, b| a & b)
88}
89
90fn bit_bor(state: &mut LuaState) -> Result<usize, LuaError> {
91    fold(state, 0, |a, b| a | b)
92}
93
94fn bit_bxor(state: &mut LuaState) -> Result<usize, LuaError> {
95    fold(state, 0, |a, b| a ^ b)
96}
97
98fn bit_bnot(state: &mut LuaState) -> Result<usize, LuaError> {
99    let a = arg_u32(state, 1)?;
100    push_u32(state, !a);
101    Ok(1)
102}
103
104fn bit_lshift(state: &mut LuaState) -> Result<usize, LuaError> {
105    let a = arg_u32(state, 1)?;
106    let disp = arg_int(state, 2)?;
107    push_u32(state, shift(a, disp));
108    Ok(1)
109}
110
111fn bit_rshift(state: &mut LuaState) -> Result<usize, LuaError> {
112    let a = arg_u32(state, 1)?;
113    let disp = arg_int(state, 2)?;
114    push_u32(state, shift(a, -disp));
115    Ok(1)
116}
117
118/// `bit32` logical shift: positive `disp` shifts left, negative shifts right;
119/// a displacement of 32 or more (in magnitude) yields 0, matching the reference.
120fn shift(x: u32, disp: i64) -> u32 {
121    if disp <= -32 || disp >= 32 {
122        0
123    } else if disp >= 0 {
124        x << disp
125    } else {
126        x >> (-disp)
127    }
128}
129
130/// `w` low bits set, matching `bit32`'s field mask (`width` in `1..=32`).
131fn mask_w(w: u32) -> u32 {
132    if w >= 32 {
133        0xFFFF_FFFF
134    } else {
135        (1u32 << w) - 1
136    }
137}
138
139/// Validate and return the `(field, width)` pair for `extract`/`replace`,
140/// matching Lua 5.2's `fieldargs` bounds checks. `width_arg` defaults to 1.
141fn field_args(
142    state: &mut LuaState,
143    field_arg: i32,
144    width_arg: i32,
145) -> Result<(u32, u32), LuaError> {
146    let f = arg_int(state, field_arg)?;
147    let w = if state.get_top() >= width_arg {
148        arg_int(state, width_arg)?
149    } else {
150        1
151    };
152    if f < 0 {
153        return Err(LuaError::arg_error(field_arg, "field cannot be negative"));
154    }
155    if w < 1 {
156        return Err(LuaError::arg_error(width_arg, "width must be positive"));
157    }
158    if f + w > 32 {
159        return Err(LuaError::arg_error(
160            field_arg,
161            "trying to access non-existent bits",
162        ));
163    }
164    Ok((f as u32, w as u32))
165}
166
167/// `bit32.btest(...)` — true iff the AND of all arguments is non-zero.
168fn bit_btest(state: &mut LuaState) -> Result<usize, LuaError> {
169    let top = state.get_top();
170    let mut acc: u32 = 0xFFFF_FFFF;
171    for i in 1..=top {
172        acc &= arg_u32(state, i)?;
173    }
174    state.push(LuaValue::Bool(acc != 0));
175    Ok(1)
176}
177
178/// `bit32.extract(n, field [, width])` — the `width` bits of `n` at `field`.
179fn bit_extract(state: &mut LuaState) -> Result<usize, LuaError> {
180    let n = arg_u32(state, 1)?;
181    let (f, w) = field_args(state, 2, 3)?;
182    push_u32(state, (n >> f) & mask_w(w));
183    Ok(1)
184}
185
186/// `bit32.replace(n, v, field [, width])` — `n` with its `width` bits at
187/// `field` replaced by the low bits of `v`.
188fn bit_replace(state: &mut LuaState) -> Result<usize, LuaError> {
189    let n = arg_u32(state, 1)?;
190    let v = arg_u32(state, 2)?;
191    let (f, w) = field_args(state, 3, 4)?;
192    let m = mask_w(w);
193    push_u32(state, (n & !(m << f)) | ((v & m) << f));
194    Ok(1)
195}
196
197/// `bit32.arshift(x, disp)` — arithmetic right shift (sign-propagating);
198/// negative `disp` shifts left.
199fn bit_arshift(state: &mut LuaState) -> Result<usize, LuaError> {
200    let x = arg_u32(state, 1)?;
201    let disp = arg_int(state, 2)?;
202    let r = if disp < 0 {
203        shift(x, -disp)
204    } else if disp >= 32 {
205        if x & 0x8000_0000 != 0 {
206            0xFFFF_FFFF
207        } else {
208            0
209        }
210    } else if x & 0x8000_0000 != 0 {
211        (x >> disp) | !(0xFFFF_FFFFu32 >> disp)
212    } else {
213        x >> disp
214    };
215    push_u32(state, r);
216    Ok(1)
217}
218
219/// 32-bit rotate left by `disp` (mod 32); negative rotates right.
220fn rotate(x: u32, disp: i64) -> u32 {
221    let d = (((disp % 32) + 32) % 32) as u32;
222    if d == 0 {
223        x
224    } else {
225        (x << d) | (x >> (32 - d))
226    }
227}
228
229fn bit_lrotate(state: &mut LuaState) -> Result<usize, LuaError> {
230    let x = arg_u32(state, 1)?;
231    let disp = arg_int(state, 2)?;
232    push_u32(state, rotate(x, disp));
233    Ok(1)
234}
235
236fn bit_rrotate(state: &mut LuaState) -> Result<usize, LuaError> {
237    let x = arg_u32(state, 1)?;
238    let disp = arg_int(state, 2)?;
239    push_u32(state, rotate(x, -disp));
240    Ok(1)
241}
242
243/// The `bit32` function roster — the full Lua 5.2/5.3 surface.
244const BIT32_FUNCS: &[(&[u8], LuaCFunction)] = &[
245    (b"band", bit_band),
246    (b"bor", bit_bor),
247    (b"bxor", bit_bxor),
248    (b"bnot", bit_bnot),
249    (b"lshift", bit_lshift),
250    (b"rshift", bit_rshift),
251    (b"btest", bit_btest),
252    (b"extract", bit_extract),
253    (b"replace", bit_replace),
254    (b"arshift", bit_arshift),
255    (b"lrotate", bit_lrotate),
256    (b"rrotate", bit_rrotate),
257];
258
259/// Open the `bit32` library, leaving the populated table on the stack.
260pub fn open_bit32(state: &mut LuaState) -> Result<usize, LuaError> {
261    state.new_lib(BIT32_FUNCS)?;
262    Ok(1)
263}
264
265// ──────────────────────────────────────────────────────────────────────────
266// PORT STATUS
267//   source:        src/lbitlib.c (Lua 5.2/5.3)
268//   target_crate:  lua-stdlib
269//   confidence:    high
270//   todos:         0
271//   port_notes:    0
272//   unsafe_blocks: 0
273//   notes:         Full 5.2/5.3 bit32 surface (band/bor/bxor/bnot/btest/lshift/
274//                  rshift/arshift/lrotate/rrotate/extract/replace), registered
275//                  under the V52|V53 gate. mod-2^32 masking and the version-
276//                  specific float coercion (5.2 rounds, 5.3 rejects fractions)
277//                  are reference-pinned in tests/bit32_strengthen.rs.
278// ──────────────────────────────────────────────────────────────────────────