Skip to main content

qcode/value/insn/
aggregate.rs

1//! Aggregate construction and projection.
2//!
3//! These are the functional-IR counterpart of a tuple: [`Tuple`] groups several
4//! values into one aggregate-typed result, and [`Extract`] projects a single
5//! field back out. They preserve the one-value-per-instruction invariant (an
6//! `Extract` *is* the field value it names), so no multi-result instruction is
7//! needed. `argpromote` uses them to return `(real_return, write-set)`.
8
9use crate::{
10    context::Context,
11    value::{LocalValueId, QCodeView, function::FunctionId},
12};
13
14use super::mnemonic::{Args, MnemonicKind};
15use smallvec::{SmallVec, smallvec};
16
17/// Builds an aggregate value from its ordered fields. The instruction's result
18/// type is the [`Aggregate`](crate::types::TypeRepr::Aggregate) of the fields'
19/// types.
20#[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/// Projects field `index` out of an aggregate value. The instruction's result
36/// type is that field's type.
37#[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/// Computes the address of a struct field: `gep(base, offset)` ≡
70/// `base + offset`, but the result is *typed* `PtrTo<field.type>` and prints by
71/// field **name** instead of the raw offset.
72///
73/// Unlike [`Extract`] — which projects a field *value* out of an in-register
74/// aggregate — `Gep` does **no memory access**: it is pure pointer arithmetic.
75/// The field value is obtained by a separate `load` of the `Gep` result. The
76/// field name is recovered from the pointee of `base`'s
77/// [`StructPointer`](crate::types::TypeRepr::StructPointer) type, keyed by the
78/// byte `offset`.
79#[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    /// The name of the field this `Gep` addresses, recovered from the nominal
87    /// struct that `base` points at. `None` if `base` is not a typed struct
88    /// pointer or the offset matches no field.
89    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        // The tuple groups the two loaded values, in order.
152        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        // The tuple's result type is an aggregate of (i32, i64).
171        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        // The extract projects field 1, so its result is 8 bytes wide.
189        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        // `Inner { val: i32 @ 0x08 }` (0x08 via leading padding), `%p : Inner*`.
211        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        // Prints by field name, not the raw 0x8 offset.
229        assert!(
230            gep.as_statement().to_string().contains("gep(%p.val)"),
231            "got: {}",
232            gep.as_statement()
233        );
234
235        // Result type is a pointer (width 8) to the i32 field.
236        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}