Skip to main content

qcode/value/insn/
casting.rs

1use crate::value::{
2    LocalValueId,
3    insn::bits::{mask_for_size, signed_value},
4};
5
6use super::mnemonic::{Args, MnemonicKind};
7use smallvec::smallvec;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
10pub struct Zext {
11    pub src: LocalValueId,
12    pub size: usize,
13}
14
15impl Zext {
16    /// Zero-extends `value` to `dst_size` bytes by masking off any higher bits.
17    pub fn eval(value: u128, dst_size: usize) -> u128 {
18        value & mask_for_size(dst_size)
19    }
20}
21
22impl MnemonicKind for Zext {
23    fn opcode(&self) -> &'static str {
24        "zext"
25    }
26
27    fn args(&self) -> Args {
28        smallvec![self.src]
29    }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
33pub struct Sext {
34    pub src: LocalValueId,
35    pub size: usize,
36}
37
38impl Sext {
39    /// Sign-extends `value` from `src_size` bytes to `dst_size` bytes.
40    pub fn eval(value: u128, src_size: usize, dst_size: usize) -> u128 {
41        if src_size == 0 {
42            return 0;
43        }
44        signed_value(value & mask_for_size(src_size), src_size) as u128 & mask_for_size(dst_size)
45    }
46}
47
48impl MnemonicKind for Sext {
49    fn opcode(&self) -> &'static str {
50        "sext"
51    }
52
53    fn args(&self) -> Args {
54        smallvec![self.src]
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
59pub struct Range {
60    pub src: LocalValueId,
61    pub start: usize,
62    pub size: usize,
63}
64
65impl Range {
66    /// Extracts `size` bytes starting at byte offset `start` from `value`.
67    pub fn eval(value: u128, start: usize, size: usize) -> u128 {
68        use super::bits::mask_for_size;
69        let shift = start.saturating_mul(8);
70        if shift >= u128::BITS as usize {
71            0
72        } else {
73            (value >> shift) & mask_for_size(size)
74        }
75    }
76}
77
78impl MnemonicKind for Range {
79    fn opcode(&self) -> &'static str {
80        "range"
81    }
82
83    fn args(&self) -> Args {
84        smallvec![self.src]
85    }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
89pub struct IntToFloat {
90    pub src: LocalValueId,
91    pub size: usize,
92}
93
94impl MnemonicKind for IntToFloat {
95    fn opcode(&self) -> &'static str {
96        "int_to_float"
97    }
98
99    fn args(&self) -> Args {
100        smallvec![self.src]
101    }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
105pub struct FloatToFloat {
106    pub src: LocalValueId,
107    pub size: usize,
108}
109
110impl MnemonicKind for FloatToFloat {
111    fn opcode(&self) -> &'static str {
112        "float_to_float"
113    }
114
115    fn args(&self) -> Args {
116        smallvec![self.src]
117    }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
121pub struct FloatToInt {
122    pub src: LocalValueId,
123    pub size: usize,
124}
125
126impl MnemonicKind for FloatToInt {
127    fn opcode(&self) -> &'static str {
128        "float_to_int"
129    }
130
131    fn args(&self) -> Args {
132        smallvec![self.src]
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use wazabin_qcode_macro::qcode;
139
140    use crate::context::Context;
141    use crate::value::insn::{Instruction, Mnemonic};
142
143    use super::*;
144
145    #[test]
146    fn test_zext_display() {
147        let mut ctx = Context::new();
148
149        qcode!(
150            ctx,
151            "
152            <block>
153                local i32 V0;
154                %v0 = load(V0:4, V0);
155                i64 %v = zext(i64, i32 %v0);
156                goto <0x1001>;
157            "
158        );
159
160        let v = Instruction::from_id(&ctx, v);
161
162        if !matches!(v.mnemonic(), Mnemonic::Zext(Zext { size: 8, .. })) {
163            panic!("expected zext instruction");
164        }
165
166        assert_eq!(v.as_statement().to_string(), "i64 %v = zext(i64, i32 %v0);");
167    }
168
169    #[test]
170    fn test_sext_display() {
171        let mut ctx = Context::new();
172
173        qcode!(
174            ctx,
175            "
176            <block>
177                local i32 V0;
178                %v0 = load(V0:4, V0);
179                i64 %v = sext(i64, i32 %v0);
180                goto <0x1001>;
181            "
182        );
183
184        let v = Instruction::from_id(&ctx, v);
185
186        if !matches!(v.mnemonic(), Mnemonic::Sext(Sext { size: 8, .. })) {
187            panic!("expected sext instruction");
188        }
189
190        assert_eq!(v.as_statement().to_string(), "i64 %v = sext(i64, i32 %v0);");
191    }
192
193    #[test]
194    fn test_int2float_display() {
195        let mut ctx = Context::new();
196
197        qcode!(
198            ctx,
199            "
200            <block>
201                local i32 V0;
202                %v0 = load(V0:4, V0);
203                i64 %v = int2float(f32, i32 %v0);
204                goto <0x1001>;
205            "
206        );
207
208        let v = Instruction::from_id(&ctx, v);
209
210        if !matches!(
211            v.mnemonic(),
212            Mnemonic::IntToFloat(IntToFloat { size: 4, .. })
213        ) {
214            panic!("expected int2float instruction");
215        }
216
217        assert_eq!(
218            v.as_statement().to_string(),
219            "i32 %v = int2float(f32, i32 %v0);"
220        );
221    }
222
223    #[test]
224    fn test_float2float_display() {
225        let mut ctx = Context::new();
226
227        qcode!(
228            ctx,
229            "
230            <block>
231                local i32 V0;
232                %v0 = load(V0:4, V0);
233                i64 %v = float2float(f64, i32 %v0);
234                goto <0x1001>;
235            "
236        );
237
238        let v = Instruction::from_id(&ctx, v);
239
240        if !matches!(
241            v.mnemonic(),
242            Mnemonic::FloatToFloat(FloatToFloat { size: 8, .. })
243        ) {
244            panic!("expected float2float instruction");
245        }
246
247        assert_eq!(
248            v.as_statement().to_string(),
249            "i64 %v = float2float(f64, i32 %v0);"
250        );
251    }
252
253    #[test]
254    fn test_trunc_display() {
255        let mut ctx = Context::new();
256
257        qcode!(
258            ctx,
259            "
260            <block>
261                local i32 V0;
262                %v0 = load(V0:4, V0);
263                i16 %v = trunc(i16, i32 %v0);
264                goto <0x1001>;
265            "
266        );
267
268        let v = Instruction::from_id(&ctx, v);
269
270        if !matches!(
271            v.mnemonic(),
272            Mnemonic::FloatToInt(FloatToInt { size: 2, .. })
273        ) {
274            panic!("expected float2int instruction");
275        }
276
277        assert_eq!(
278            v.as_statement().to_string(),
279            "i16 %v = trunc(i16, i32 %v0);"
280        );
281    }
282
283    #[test]
284    fn test_zext_eval() {
285        // zero-extend preserves low bytes, masks off anything above dst_size
286        assert_eq!(Zext::eval(0xFF, 4), 0xFF);
287        assert_eq!(Zext::eval(0xDEAD_BEEF_FFFF_FFFF, 4), 0xFFFF_FFFF);
288        assert_eq!(Zext::eval(0, 8), 0);
289    }
290
291    #[test]
292    fn test_sext_eval() {
293        // positive value (sign bit clear) → unchanged
294        assert_eq!(Sext::eval(0x7F, 1, 4), 0x7F);
295        // negative value (sign bit set) → sign-extended
296        assert_eq!(Sext::eval(0xFF, 1, 4), 0xFFFF_FFFF);
297        assert_eq!(Sext::eval(0x80, 1, 4), 0xFFFF_FF80);
298        // zero → zero
299        assert_eq!(Sext::eval(0, 1, 4), 0);
300    }
301
302    #[test]
303    fn test_range_eval() {
304        // extract 2 bytes starting at byte offset 1 from 0xDEAD_BEEF
305        // bytes: EF BE AD DE → bytes 1..3 = BE AD → value 0xADBE
306        assert_eq!(Range::eval(0xDEAD_BEEF, 1, 2), 0xADBE);
307        // extract first byte
308        assert_eq!(Range::eval(0xDEAD_BEEF, 0, 1), 0xEF);
309        // extract from offset beyond value → 0
310        assert_eq!(Range::eval(0xFF, 16, 1), 0);
311    }
312}