Skip to main content

qcode/value/insn/
memory.rs

1use crate::{space::LocalMemorySpaceId, value::LocalValueId};
2
3use super::mnemonic::{Args, MnemonicKind};
4use smallvec::smallvec;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
7pub struct Load {
8    pub space: LocalMemorySpaceId,
9    pub ptr: LocalValueId,
10    pub size: usize,
11}
12
13impl MnemonicKind for Load {
14    fn opcode(&self) -> &'static str {
15        "load"
16    }
17
18    fn args(&self) -> Args {
19        smallvec![self.ptr]
20    }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
24pub struct Store {
25    pub space: LocalMemorySpaceId,
26    pub ptr: LocalValueId,
27    pub src: LocalValueId,
28    pub size: usize,
29}
30
31impl Store {
32    /// Create a `Load` instruction that would read from the same memory location as this `Store`.
33    pub fn get_matching_load(&self) -> Load {
34        Load {
35            space: self.space,
36            ptr: self.ptr,
37            size: self.size,
38        }
39    }
40}
41
42impl MnemonicKind for Store {
43    fn opcode(&self) -> &'static str {
44        "store"
45    }
46
47    fn args(&self) -> Args {
48        smallvec![self.ptr, self.src]
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use wazabin_qcode_macro::qcode;
55
56    use crate::context::Context;
57    use crate::value::{
58        BasicBlock, LiteralId, Varnode, VarnodeId,
59        insn::{Instruction, Mnemonic},
60    };
61
62    use super::*;
63
64    #[test]
65    fn test_load_display() {
66        let mut ctx = Context::new();
67
68        qcode!(
69            ctx,
70            "
71            varnode i32 v0;
72
73            <block>
74                %ptr = i64 &v0 + i64 0x2;
75                %v = load(v0:4, %ptr);
76                goto <0x1001>;
77            "
78        );
79
80        let v = Instruction::from_id(&ctx, v);
81
82        if !matches!(v.mnemonic(), Mnemonic::Load(Load { .. })) {
83            panic!("expected memory instruction");
84        }
85
86        assert_eq!(v.size(), 4);
87        assert!(v.space().is_none());
88        assert_eq!(
89            v.as_statement().to_string(),
90            "i32 %v = load(v0:4, i32 %ptr);"
91        );
92    }
93
94    #[test]
95    fn test_store_display() {
96        let mut ctx = Context::new();
97
98        qcode!(
99            ctx,
100            "
101            varnode i32 V0;
102
103            <block>
104                %v0 = load(V0:4, V0);
105                %ptr = i32 %v0 + i32 0x2;
106                store(ram:4, %ptr <- i32 0x7);
107                goto <0x1001>;
108            "
109        );
110
111        let block = BasicBlock::from_id(&ctx, block);
112        let store = block.iter().nth(2).expect("expected store instruction");
113
114        if !matches!(store.mnemonic(), Mnemonic::Store(Store { .. })) {
115            panic!("expected memory instruction");
116        }
117
118        assert_eq!(store.size(), 0);
119        assert!(store.space().is_none());
120        assert_eq!(
121            store.as_statement().to_string(),
122            "store(ram:4, i32 %ptr <- i32 0x7);"
123        );
124    }
125
126    #[test]
127    fn test_matching_load() {
128        let store = Store {
129            space: crate::space::SpaceId::from(0).into(),
130            ptr: LocalValueId::Varnode(VarnodeId::from(1)),
131            src: LocalValueId::Literal(LiteralId::from(2)),
132            size: 4,
133        };
134
135        let load = store.get_matching_load();
136        assert_eq!(load.space, store.space);
137        assert_eq!(load.ptr, store.ptr);
138        assert_eq!(load.size, store.size);
139    }
140
141    #[test]
142    fn test_computed_pointer_load_inherits_instruction_space() {
143        let mut ctx = Context::new();
144
145        qcode!(
146            ctx,
147            "
148            varnode i64 A;
149
150            <block>
151                %ptr = i64 &A + i64 0x2;
152                %value = load(A:8, %ptr);
153                goto <0x1001>;
154            "
155        );
156
157        let block = BasicBlock::from_id(&ctx, block);
158        let a = Varnode::from_id(&ctx, A);
159        let ptr = block.iter().next().expect("expected pointer arithmetic");
160        let value = Instruction::from_id(&ctx, value);
161
162        assert_eq!(ptr.space().and_then(|s| s.name.as_deref()), Some("A"));
163
164        let Mnemonic::Load(load) = value.mnemonic() else {
165            panic!("expected load instruction");
166        };
167        assert_eq!(load.space, a.space().id);
168        assert_eq!(
169            value.as_statement().to_string(),
170            "i64 %value = load(A:8, i64 %ptr);"
171        );
172    }
173
174    #[test]
175    fn test_computed_pointer_store_inherits_instruction_space() {
176        let mut ctx = Context::new();
177
178        qcode!(
179            ctx,
180            "
181            varnode i64 A;
182
183            <block>
184                %ptr = i64 &A + i64 0x2;
185                store(A:8, %ptr <- i64 0x7);
186                goto <0x1001>;
187            "
188        );
189
190        let block = BasicBlock::from_id(&ctx, block);
191        let a = Varnode::from_id(&ctx, A);
192        let store = block.iter().nth(1).expect("expected store instruction");
193
194        let Mnemonic::Store(store_mnemonic) = store.mnemonic() else {
195            panic!("expected store instruction");
196        };
197        assert_eq!(store_mnemonic.space, a.space().id);
198        assert_eq!(
199            store.as_statement().to_string(),
200            "store(A:8, i64 %ptr <- i64 0x7);"
201        );
202    }
203
204    #[test]
205    fn test_computed_pointer_without_provenance_falls_back_to_default_space() {
206        let mut ctx = Context::new();
207
208        qcode!(
209            ctx,
210            "
211            <block>
212                %ptr = i64 0x10 + i64 0x2;
213                %v = load(ram:8, %ptr);
214                goto <0x1001>;
215            "
216        );
217
218        let value = Instruction::from_id(&ctx, v);
219        let ptr = Instruction::from_id(&ctx, ptr);
220
221        assert_eq!(
222            ptr.space().map(|s| s.id),
223            Some(ctx.shared.default_space),
224            "expected default space for pointer arithmetic without provenance"
225        );
226
227        let Mnemonic::Load(load) = value.mnemonic() else {
228            panic!("expected load instruction");
229        };
230        assert_eq!(load.space, ctx.shared.default_space);
231        assert_eq!(
232            value.as_statement().to_string(),
233            "i64 %v = load(ram:8, i64 %ptr);"
234        );
235    }
236}