Skip to main content

lua_stdlib/
bit32_lib.rs

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