Skip to main content

luadec_rust/lua51/
instruction.rs

1use crate::lua51::opcodes::{OpCode, OpMode};
2
3/// Bit layout of a Lua 5.1 instruction (32-bit unsigned):
4///
5/// ```text
6///  MSB                                                         LSB
7///  +--------+---------+---------+--------+--------+
8///  |   B(9) |   C(9)  |   A(8)  | OpCode(6)      |
9///  +--------+---------+---------+--------+--------+
10///  31    23  22    14  13      6  5             0
11///
12///  For ABx/AsBx format, B and C are merged into Bx (18 bits).
13/// ```
14
15const SIZE_OP: u32 = 6;
16const SIZE_A: u32 = 8;
17const SIZE_C: u32 = 9;
18const SIZE_B: u32 = 9;
19const SIZE_BX: u32 = SIZE_C + SIZE_B; // 18
20
21const POS_OP: u32 = 0;
22const POS_A: u32 = POS_OP + SIZE_OP; // 6
23const POS_C: u32 = POS_A + SIZE_A; // 14
24const POS_B: u32 = POS_C + SIZE_C; // 23
25const POS_BX: u32 = POS_C; // 14
26
27const MAXARG_BX: u32 = (1 << SIZE_BX) - 1; // 262143
28const MAXARG_SBX: i32 = (MAXARG_BX >> 1) as i32; // 131071
29
30/// Bit 8 of a B/C field indicates a constant index rather than a register.
31pub const BITRK: u32 = 1 << (SIZE_B - 1); // 256
32
33/// Check whether a B/C value refers to a constant (vs register).
34#[inline]
35pub fn is_k(x: u32) -> bool {
36    x & BITRK != 0
37}
38
39/// Extract the constant index from an RK value.
40#[inline]
41pub fn index_k(x: u32) -> u32 {
42    x & !BITRK
43}
44
45/// Number of list items to accumulate before a SETLIST instruction.
46pub const LFIELDS_PER_FLUSH: u32 = 50;
47
48/// A decoded Lua 5.1 instruction.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct Instruction {
51    pub raw: u32,
52    pub op: OpCode,
53    pub a: u32,
54    pub fields: InstructionFields,
55}
56
57/// The variable part of a decoded instruction, depending on the opcode format.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum InstructionFields {
60    ABC { b: u32, c: u32 },
61    ABx { bx: u32 },
62    AsBx { sbx: i32 },
63}
64
65#[inline]
66fn mask(n: u32) -> u32 {
67    (1u32 << n) - 1
68}
69
70impl Instruction {
71    /// Decode a raw 32-bit value into a structured `Instruction`.
72    pub fn decode(raw: u32) -> Option<Instruction> {
73        let opcode_val = (raw >> POS_OP) & mask(SIZE_OP);
74        let op = OpCode::from_u8(opcode_val as u8)?;
75        let a = (raw >> POS_A) & mask(SIZE_A);
76
77        let fields = match op.props().mode {
78            OpMode::ABC => {
79                let b = (raw >> POS_B) & mask(SIZE_B);
80                let c = (raw >> POS_C) & mask(SIZE_C);
81                InstructionFields::ABC { b, c }
82            }
83            OpMode::ABx => {
84                let bx = (raw >> POS_BX) & mask(SIZE_BX);
85                InstructionFields::ABx { bx }
86            }
87            OpMode::AsBx => {
88                let bx = (raw >> POS_BX) & mask(SIZE_BX);
89                let sbx = bx as i32 - MAXARG_SBX;
90                InstructionFields::AsBx { sbx }
91            }
92        };
93
94        Some(Instruction {
95            raw,
96            op,
97            a,
98            fields,
99        })
100    }
101
102    /// Get the B field (only valid for ABC format).
103    pub fn b(&self) -> u32 {
104        match self.fields {
105            InstructionFields::ABC { b, .. } => b,
106            _ => panic!("b() called on non-ABC instruction"),
107        }
108    }
109
110    /// Get the C field (only valid for ABC format).
111    pub fn c(&self) -> u32 {
112        match self.fields {
113            InstructionFields::ABC { c, .. } => c,
114            _ => panic!("c() called on non-ABC instruction"),
115        }
116    }
117
118    /// Get the Bx field (only valid for ABx format).
119    pub fn bx(&self) -> u32 {
120        match self.fields {
121            InstructionFields::ABx { bx } => bx,
122            _ => panic!("bx() called on non-ABx instruction"),
123        }
124    }
125
126    /// Get the sBx field (only valid for AsBx format).
127    pub fn sbx(&self) -> i32 {
128        match self.fields {
129            InstructionFields::AsBx { sbx } => sbx,
130            _ => panic!("sbx() called on non-AsBx instruction"),
131        }
132    }
133}
134
135impl std::fmt::Display for Instruction {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        match self.fields {
138            InstructionFields::ABC { b, c } => {
139                write!(f, "{:<12} {} {} {}", self.op, self.a, b, c)
140            }
141            InstructionFields::ABx { bx } => {
142                write!(f, "{:<12} {} {}", self.op, self.a, bx)
143            }
144            InstructionFields::AsBx { sbx } => {
145                write!(f, "{:<12} {} {}", self.op, self.a, sbx)
146            }
147        }
148    }
149}
150
151/// Encode helpers (useful for testing)
152pub fn encode_abc(op: OpCode, a: u32, b: u32, c: u32) -> u32 {
153    ((op as u32) << POS_OP) | (a << POS_A) | (b << POS_B) | (c << POS_C)
154}
155
156pub fn encode_abx(op: OpCode, a: u32, bx: u32) -> u32 {
157    ((op as u32) << POS_OP) | (a << POS_A) | (bx << POS_BX)
158}
159
160pub fn encode_asbx(op: OpCode, a: u32, sbx: i32) -> u32 {
161    let bx = (sbx + MAXARG_SBX) as u32;
162    ((op as u32) << POS_OP) | (a << POS_A) | (bx << POS_BX)
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn decode_move() {
171        // MOVE A=1 B=2
172        let raw = encode_abc(OpCode::Move, 1, 2, 0);
173        let inst = Instruction::decode(raw).unwrap();
174        assert_eq!(inst.op, OpCode::Move);
175        assert_eq!(inst.a, 1);
176        assert_eq!(inst.b(), 2);
177        assert_eq!(inst.c(), 0);
178    }
179
180    #[test]
181    fn decode_loadk() {
182        // LOADK A=5 Bx=100
183        let raw = encode_abx(OpCode::LoadK, 5, 100);
184        let inst = Instruction::decode(raw).unwrap();
185        assert_eq!(inst.op, OpCode::LoadK);
186        assert_eq!(inst.a, 5);
187        assert_eq!(inst.bx(), 100);
188    }
189
190    #[test]
191    fn decode_jmp() {
192        // JMP sBx=10
193        let raw = encode_asbx(OpCode::Jmp, 0, 10);
194        let inst = Instruction::decode(raw).unwrap();
195        assert_eq!(inst.op, OpCode::Jmp);
196        assert_eq!(inst.sbx(), 10);
197
198        // JMP sBx=-5
199        let raw = encode_asbx(OpCode::Jmp, 0, -5);
200        let inst = Instruction::decode(raw).unwrap();
201        assert_eq!(inst.op, OpCode::Jmp);
202        assert_eq!(inst.sbx(), -5);
203    }
204
205    #[test]
206    fn decode_add() {
207        // ADD A=0 B=256(K0) C=1
208        let raw = encode_abc(OpCode::Add, 0, BITRK, 1);
209        let inst = Instruction::decode(raw).unwrap();
210        assert_eq!(inst.op, OpCode::Add);
211        assert_eq!(inst.a, 0);
212        assert!(is_k(inst.b()));
213        assert_eq!(index_k(inst.b()), 0);
214        assert!(!is_k(inst.c()));
215    }
216
217    #[test]
218    fn roundtrip_all_formats() {
219        // ABC
220        let raw = encode_abc(OpCode::Call, 3, 2, 1);
221        let inst = Instruction::decode(raw).unwrap();
222        assert_eq!(inst.raw, raw);
223        assert_eq!(inst.op, OpCode::Call);
224        assert_eq!(inst.a, 3);
225        assert_eq!(inst.b(), 2);
226        assert_eq!(inst.c(), 1);
227
228        // ABx
229        let raw = encode_abx(OpCode::Closure, 0, 42);
230        let inst = Instruction::decode(raw).unwrap();
231        assert_eq!(inst.raw, raw);
232        assert_eq!(inst.bx(), 42);
233
234        // AsBx
235        let raw = encode_asbx(OpCode::ForLoop, 2, -100);
236        let inst = Instruction::decode(raw).unwrap();
237        assert_eq!(inst.raw, raw);
238        assert_eq!(inst.sbx(), -100);
239    }
240
241    #[test]
242    fn invalid_opcode() {
243        // Opcode 38 doesn't exist
244        let raw = 38u32;
245        assert!(Instruction::decode(raw).is_none());
246    }
247
248    #[test]
249    fn display_format() {
250        let raw = encode_abc(OpCode::Move, 1, 2, 0);
251        let inst = Instruction::decode(raw).unwrap();
252        let s = format!("{}", inst);
253        assert!(s.contains("MOVE"));
254        assert!(s.contains("1"));
255        assert!(s.contains("2"));
256    }
257}