qcode/value/insn/
memory.rs1use 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 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}