qcode/value/insn/
aggregate.rs1use crate::{
10 context::Context,
11 value::{LocalValueId, QCodeView, function::FunctionId},
12};
13
14use super::mnemonic::{Args, MnemonicKind};
15use smallvec::{SmallVec, smallvec};
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
21pub struct Tuple {
22 pub fields: Vec<LocalValueId>,
23}
24
25impl MnemonicKind for Tuple {
26 fn opcode(&self) -> &'static str {
27 "pack"
28 }
29
30 fn args(&self) -> Args {
31 SmallVec::from_vec(self.fields.clone())
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
38pub struct Extract {
39 pub agg: LocalValueId,
40 pub index: usize,
41}
42
43impl Extract {
44 pub fn field_name<'a>(&self, ctx: &'a Context<'_>, func: FunctionId) -> Option<&'a str> {
45 let agg_ty = ctx.stored_type_of(self.agg.qualify(func))?;
46 ctx.shared.types.field_name(agg_ty, self.index)
47 }
48
49 pub fn field_name_view<'ctx, 'str: 'ctx>(
50 &self,
51 view: impl QCodeView<'ctx, 'str>,
52 func: FunctionId,
53 ) -> Option<&'ctx str> {
54 let agg_ty = view.stored_type_of(self.agg.qualify(func))?;
55 view.shared().types.field_name(agg_ty, self.index)
56 }
57}
58
59impl MnemonicKind for Extract {
60 fn opcode(&self) -> &'static str {
61 "extract"
62 }
63
64 fn args(&self) -> Args {
65 smallvec![self.agg]
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
80pub struct Gep {
81 pub base: LocalValueId,
82 pub offset: usize,
83}
84
85impl Gep {
86 pub fn field_name<'a>(&self, ctx: &'a Context<'_>, func: FunctionId) -> Option<&'a str> {
90 let base_ty = ctx.stored_type_of(self.base.qualify(func))?;
91 let pointee = ctx.shared.types.pointee_of(base_ty)?;
92 ctx.shared
93 .types
94 .field_by_offset(pointee, self.offset)
95 .map(|(_, field)| field.name.as_str())
96 }
97
98 pub fn field_name_view<'ctx, 'str: 'ctx>(
99 &self,
100 view: impl QCodeView<'ctx, 'str>,
101 func: FunctionId,
102 ) -> Option<&'ctx str> {
103 let base_ty = view.stored_type_of(self.base.qualify(func))?;
104 let pointee = view.shared().types.pointee_of(base_ty)?;
105 view.shared()
106 .types
107 .field_by_offset(pointee, self.offset)
108 .map(|(_, field)| field.name.as_str())
109 }
110}
111
112impl MnemonicKind for Gep {
113 fn opcode(&self) -> &'static str {
114 "gep"
115 }
116
117 fn args(&self) -> Args {
118 smallvec![self.base]
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use wazabin_qcode_macro::qcode;
125
126 use crate::{
127 context::Context,
128 value::{BasicBlock, ValueId, insn::Mnemonic},
129 };
130
131 #[test]
132 fn tuple_and_extract_roundtrip() {
133 let mut ctx = Context::new();
134 qcode!(
135 ctx,
136 "
137 <block>
138 %a = i32 5 + i32 0;
139 %b = i64 7 + i64 0;
140 %t = pack(lhs=%a, rhs=%b);
141 %x = extract(%t.rhs);
142 return at i64 0;
143 "
144 );
145
146 let insns: Vec<Mnemonic> = BasicBlock::from_id(&ctx, block)
147 .iter()
148 .map(|i| i.mnemonic().clone())
149 .collect();
150
151 let tuple = insns
153 .iter()
154 .find_map(|m| match m {
155 Mnemonic::Tuple(t) => Some(t.clone()),
156 _ => None,
157 })
158 .expect("tuple instruction");
159 assert_eq!(tuple.fields.len(), 2);
160 assert_eq!(
161 BasicBlock::from_id(&ctx, block)
162 .iter()
163 .find(|i| matches!(i.mnemonic(), Mnemonic::Tuple(_)))
164 .unwrap()
165 .as_statement()
166 .to_string(),
167 "i96 %t = pack(lhs=i32 %a, rhs=i64 %b);"
168 );
169
170 let tuple_id = BasicBlock::from_id(&ctx, block)
172 .iter()
173 .find(|i| matches!(i.mnemonic(), Mnemonic::Tuple(_)))
174 .unwrap()
175 .id;
176 let agg_ty = ctx.type_of(ValueId::Instruction(tuple_id));
177 let fields = ctx
178 .shared
179 .types
180 .aggregate_fields(agg_ty)
181 .expect("tuple result is an aggregate");
182 assert_eq!(fields.len(), 2);
183 assert_eq!(fields[0].name, "lhs");
184 assert_eq!(fields[1].name, "rhs");
185 assert_eq!(ctx.shared.types.size_of(fields[0].type_id), 4);
186 assert_eq!(ctx.shared.types.size_of(fields[1].type_id), 8);
187
188 let extract_id = BasicBlock::from_id(&ctx, block)
190 .iter()
191 .find(|i| matches!(i.mnemonic(), Mnemonic::Extract(e) if e.index == 1))
192 .expect("extract instruction with index 1")
193 .id;
194 assert_eq!(
195 BasicBlock::from_id(&ctx, block)
196 .iter()
197 .find(|i| matches!(i.mnemonic(), Mnemonic::Extract(_)))
198 .unwrap()
199 .as_statement()
200 .to_string(),
201 "i64 %x = extract(%t.rhs);"
202 );
203 let extract_ty = ctx.type_of(ValueId::Instruction(extract_id));
204 assert_eq!(ctx.shared.types.size_of(extract_ty), 8);
205 }
206
207 #[test]
208 fn gep_via_qcode_resolves_field_name_and_pointer_type() {
209 let mut ctx = Context::new();
210 qcode!(
212 ctx,
213 "
214 type Inner { _: 8, val: 4 };
215 varnode i64 base;
216 <block>
217 Inner* %p = load(base:8, base);
218 %f = gep(%p.val);
219 return at i64 0;
220 "
221 );
222
223 let gep = BasicBlock::from_id(&ctx, block)
224 .iter()
225 .find(|i| matches!(i.mnemonic(), Mnemonic::Gep(_)))
226 .expect("gep instruction");
227 let gep_id = gep.id;
228 assert!(
230 gep.as_statement().to_string().contains("gep(%p.val)"),
231 "got: {}",
232 gep.as_statement()
233 );
234
235 let gep_ty = ctx.type_of(ValueId::Instruction(gep_id));
237 assert_eq!(ctx.shared.types.size_of(gep_ty), 8);
238 let pointee = ctx
239 .shared
240 .types
241 .pointee_of(gep_ty)
242 .expect("gep result is a pointer");
243 assert_eq!(ctx.shared.types.size_of(pointee), 4);
244 }
245}