Skip to main content

qcode/value/
view.rs

1//! Static immutable providers for qcode IR reads.
2//!
3//! [`ModuleView`] resolves any function body in an unchanged [`Context`].
4//! [`BodyView`] resolves exactly one body plus shared data and published
5//! interfaces. Both implement [`QCodeView`], which is deliberately read-only.
6
7use jstd::registry::Registry;
8
9use crate::{
10    context::{Context, Shared},
11    types::TypeId,
12    value::{
13        BasicBlock, BlockId, BlockParamRef, BlockRef, FunctionBody, FunctionId, FunctionRef,
14        Instruction, InstructionRef, Temp, TempId, TempRef, TempSpace, TempSpaceId, TempSpaceRef,
15        ValueId,
16        block::{EdgeData, EdgeId},
17        block_param::{BlockParam, BlockParamId},
18        function::FunctionInterface,
19        insn::InstructionId,
20    },
21};
22
23/// Read-only resolution capability shared by module and single-body views.
24///
25/// The explicit lifetimes let provider-generic refs return data with the
26/// provider's underlying borrow lifetime, rather than tying results to a short
27/// borrow of the thin provider value.
28pub trait QCodeView<'ctx, 'str>: Copy
29where
30    'str: 'ctx,
31{
32    fn shared(self) -> &'ctx Shared<'str>;
33    fn interface(self, id: FunctionId) -> &'ctx FunctionInterface<'str>;
34    fn function(self, id: FunctionId) -> &'ctx FunctionBody<'str>;
35
36    /// The single function this view is scoped to, if any. A whole-module view
37    /// returns `None` (it may read every function); a function-pass [`BodyView`]
38    /// returns its owner, so callers can avoid a cross-function read that the
39    /// locality guard would panic on (e.g. rendering a foreign `SymbolicRef::Block`
40    /// falls back to the numeric form instead of resolving its name).
41    fn owner(self) -> Option<FunctionId> {
42        None
43    }
44
45    fn instruction(self, id: InstructionId) -> &'ctx Instruction<'str> {
46        &self.function(id.func).insns[id.local]
47    }
48
49    fn contains_instruction(self, id: InstructionId) -> bool {
50        self.function(id.func).insns.contains(id.local)
51    }
52
53    fn block(self, id: BlockId) -> &'ctx BasicBlock<'str> {
54        &self.function(id.func).blocks[id.local]
55    }
56
57    fn contains_block(self, id: BlockId) -> bool {
58        self.function(id.func).blocks.contains(id.local)
59    }
60
61    fn block_param(self, id: BlockParamId) -> &'ctx BlockParam<'str> {
62        &self.function(id.func).params[id.local]
63    }
64
65    fn contains_block_param(self, id: BlockParamId) -> bool {
66        self.function(id.func).params.contains(id.local)
67    }
68
69    fn edge(self, function: FunctionId, id: EdgeId) -> &'ctx EdgeData {
70        &self.function(function).edges[id]
71    }
72
73    #[track_caller]
74    fn temp_space(self, id: TempSpaceId) -> &'ctx TempSpace {
75        debug_assert!(
76            self.contains_temp_space(id),
77            "missing temporary space {id:?} in function {:?}",
78            id.func
79        );
80        &self.function(id.func).temp_spaces[id.local]
81    }
82
83    fn contains_temp_space(self, id: TempSpaceId) -> bool {
84        usize::from(id.local) < self.function(id.func).temp_spaces.len()
85    }
86
87    #[track_caller]
88    fn temp(self, id: TempId) -> &'ctx Temp<'str> {
89        debug_assert!(
90            self.contains_temp(id),
91            "missing temporary {id:?} in function {:?}",
92            id.func
93        );
94        &self.function(id.func).temps[id.local]
95    }
96
97    fn contains_temp(self, id: TempId) -> bool {
98        usize::from(id.local) < self.function(id.func).temps.len()
99    }
100
101    fn type_of(self, id: ValueId) -> TypeId {
102        let shared = self.shared();
103        match id {
104            ValueId::Literal(id) => shared.values.literals[id].type_id,
105            ValueId::Bytes(id) => shared.values.bytes[id].type_id,
106            ValueId::Instruction(id) => self.instruction(id).type_id,
107            ValueId::BlockParam(id) => self.block_param(id).type_id,
108            ValueId::Varnode(id) => shared
109                .values
110                .varnode_types
111                .get(&id)
112                .copied()
113                .unwrap_or_else(|| {
114                    shared
115                        .types
116                        .get_or_make_int(shared.values.varnodes[id].size_bytes())
117                }),
118            ValueId::Temp(id) => shared.types.get_or_make_int(self.temp(id).size),
119            ValueId::Poison(id) => shared.values.poisons[id].type_id,
120            ValueId::BasicBlock(_) | ValueId::Function(_) => shared.types.get_or_make_int(0),
121        }
122    }
123
124    fn stored_type_of(self, id: ValueId) -> Option<TypeId> {
125        let shared = self.shared();
126        match id {
127            ValueId::Literal(id) => Some(shared.values.literals[id].type_id),
128            ValueId::Bytes(id) => Some(shared.values.bytes[id].type_id),
129            ValueId::Instruction(id) => Some(self.instruction(id).type_id),
130            ValueId::BlockParam(id) => Some(self.block_param(id).type_id),
131            ValueId::Varnode(id) => shared.values.varnode_types.get(&id).copied(),
132            ValueId::Poison(id) => Some(shared.values.poisons[id].type_id),
133            ValueId::Temp(_) => None,
134            ValueId::BasicBlock(_) | ValueId::Function(_) => None,
135        }
136    }
137
138    fn block_ref(self, id: BlockId) -> BlockRef<'str, 'ctx, Self>
139    where
140        Self: Sized,
141    {
142        let _ = self.block(id);
143        BlockRef::new(self, id)
144    }
145
146    fn insn_ref(self, id: InstructionId) -> InstructionRef<'str, 'ctx, Self>
147    where
148        Self: Sized,
149    {
150        let _ = self.instruction(id);
151        InstructionRef::new(self, id)
152    }
153
154    fn param_ref(self, id: BlockParamId) -> BlockParamRef<'str, 'ctx, Self>
155    where
156        Self: Sized,
157    {
158        let _ = self.block_param(id);
159        BlockParamRef::new(self, id)
160    }
161
162    fn function_ref(self, id: FunctionId) -> FunctionRef<'str, 'ctx, Self>
163    where
164        Self: Sized,
165    {
166        let _ = self.function(id);
167        FunctionRef::new(self, id)
168    }
169
170    fn temp_space_ref(self, id: TempSpaceId) -> TempSpaceRef<'str, 'ctx, Self>
171    where
172        Self: Sized,
173    {
174        let _ = self.temp_space(id);
175        TempSpaceRef::new(self, id)
176    }
177
178    fn temp_ref(self, id: TempId) -> TempRef<'str, 'ctx, Self>
179    where
180        Self: Sized,
181    {
182        let _ = self.temp(id);
183        TempRef::new(self, id)
184    }
185}
186
187/// Whole-module immutable provider.
188#[derive(Clone, Copy)]
189pub struct ModuleView<'ctx, 'str> {
190    context: &'ctx Context<'str>,
191}
192
193impl<'ctx, 'str> ModuleView<'ctx, 'str> {
194    pub fn new(context: &'ctx Context<'str>) -> Self {
195        Self { context }
196    }
197
198    /// Whole-context access is intentionally module-only and absent from
199    /// [`QCodeView`] / [`BodyView`].
200    pub fn context(self) -> &'ctx Context<'str> {
201        self.context
202    }
203}
204
205impl<'ctx, 'str: 'ctx> QCodeView<'ctx, 'str> for ModuleView<'ctx, 'str> {
206    fn shared(self) -> &'ctx Shared<'str> {
207        &self.context.shared
208    }
209
210    fn interface(self, id: FunctionId) -> &'ctx FunctionInterface<'str> {
211        &self.context.interfaces[id]
212    }
213
214    fn function(self, id: FunctionId) -> &'ctx FunctionBody<'str> {
215        &self.context.bodies[id]
216    }
217}
218
219/// Single-body immutable provider used by function passes.
220#[derive(Clone, Copy)]
221pub struct BodyView<'ctx, 'str> {
222    body: &'ctx FunctionBody<'str>,
223    shared: &'ctx Shared<'str>,
224    interfaces: &'ctx Registry<FunctionId, FunctionInterface<'str>>,
225}
226
227impl<'ctx, 'str> BodyView<'ctx, 'str> {
228    pub fn new(
229        body: &'ctx FunctionBody<'str>,
230        shared: &'ctx Shared<'str>,
231        interfaces: &'ctx Registry<FunctionId, FunctionInterface<'str>>,
232    ) -> Self {
233        // Block ownership is derived from the storing arena (a rostered block
234        // lives in `body`'s own arena by construction), so there is no
235        // reattribution state left to scan for here.
236        Self {
237            body,
238            shared,
239            interfaces,
240        }
241    }
242
243    pub fn function_id(self) -> FunctionId {
244        self.body.id()
245    }
246}
247
248impl<'ctx, 'str: 'ctx> QCodeView<'ctx, 'str> for BodyView<'ctx, 'str> {
249    fn shared(self) -> &'ctx Shared<'str> {
250        self.shared
251    }
252
253    fn interface(self, id: FunctionId) -> &'ctx FunctionInterface<'str> {
254        &self.interfaces[id]
255    }
256
257    fn function(self, id: FunctionId) -> &'ctx FunctionBody<'str> {
258        let owner = self.body.id();
259        assert!(
260            id == owner,
261            "A function pass running on `{}` ({owner:?}) attempted to read `{}` ({id:?})",
262            self.interfaces[owner].name,
263            self.interfaces[id].name,
264        );
265        self.body
266    }
267
268    fn owner(self) -> Option<FunctionId> {
269        Some(self.body.id())
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use crate::{
276        context::Context,
277        space::{LocalMemorySpaceId, MemorySpaceId},
278        value::{
279            BasicBlock, FunctionBody, LocalValueId, Temp, TempSpace, ValueId, ValueRef,
280            insn::{InstructionRef, Load, Mnemonic, Unary, Unop},
281        },
282    };
283
284    use super::*;
285
286    #[test]
287    fn module_and_body_views_resolve_identical_local_data() {
288        let mut ctx = Context::new();
289        let function = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
290        let block = BasicBlock::make(&mut ctx, function).id;
291        let value = ctx.get_const(7, 8).id();
292        let insn = ctx.builder(block).push_return(value).id;
293
294        let module = ModuleView::new(&ctx);
295        let body = BodyView::new(&ctx.bodies[function], &ctx.shared, &ctx.interfaces);
296
297        assert_eq!(module.block(block).address, body.block(block).address);
298        assert!(std::ptr::eq(
299            module.instruction(insn),
300            body.instruction(insn)
301        ));
302        assert_eq!(
303            module.type_of(ValueId::Instruction(insn)),
304            body.type_of(ValueId::Instruction(insn))
305        );
306        assert_eq!(module.insn_ref(insn).id, body.insn_ref(insn).id);
307        assert_eq!(
308            module.insn_ref(insn).as_statement().to_string(),
309            body.insn_ref(insn).as_statement().to_string()
310        );
311        assert_eq!(
312            module
313                .function_ref(function)
314                .iter()
315                .map(|block| block.id)
316                .collect::<Vec<_>>(),
317            body.function_ref(function)
318                .iter()
319                .map(|block| block.id)
320                .collect::<Vec<_>>()
321        );
322        assert_eq!(
323            ValueRef::from_view(module, ValueId::Instruction(insn)).to_string(),
324            ValueRef::from_view(body, ValueId::Instruction(insn)).to_string()
325        );
326    }
327
328    #[test]
329    fn body_view_reads_foreign_interfaces_but_not_foreign_bodies() {
330        let mut ctx = Context::new();
331        let own = FunctionBody::make(&mut ctx, "own".into()).unwrap().id;
332        let foreign = FunctionBody::make(&mut ctx, "foreign".into()).unwrap().id;
333        let view = BodyView::new(&ctx.bodies[own], &ctx.shared, &ctx.interfaces);
334
335        assert_eq!(view.interface(foreign).name.as_ref(), "foreign");
336        let panic = match std::panic::catch_unwind(|| view.function(foreign)) {
337            Ok(_) => panic!("foreign body read unexpectedly succeeded"),
338            Err(panic) => panic,
339        };
340        let message = panic
341            .downcast_ref::<String>()
342            .map(String::as_str)
343            .or_else(|| panic.downcast_ref::<&str>().copied())
344            .expect("BodyView panic should carry a string message");
345        assert_eq!(
346            message,
347            format!(
348                "A function pass running on `own` ({own:?}) attempted to read `foreign` ({foreign:?})"
349            )
350        );
351    }
352
353    #[test]
354    fn body_view_rejects_foreign_composite_ids() {
355        let mut ctx = Context::new();
356        let own = FunctionBody::make(&mut ctx, "own".into()).unwrap().id;
357        let foreign = FunctionBody::make(&mut ctx, "foreign".into()).unwrap().id;
358        let block = BasicBlock::make(&mut ctx, foreign).id;
359        let view = BodyView::new(&ctx.bodies[own], &ctx.shared, &ctx.interfaces);
360
361        assert!(std::panic::catch_unwind(|| view.block(block)).is_err());
362    }
363
364    #[test]
365    fn temporary_ids_are_qualified_by_their_body() {
366        let mut ctx = Context::new();
367        let first = FunctionBody::make(&mut ctx, "first".into()).unwrap().id;
368        let second = FunctionBody::make(&mut ctx, "second".into()).unwrap().id;
369
370        let first_space = ctx.bodies[first].push_temp_space(TempSpace::new(None, 1, 8));
371        let second_space = ctx.bodies[second].push_temp_space(TempSpace::new(None, 1, 8));
372        assert_eq!(first_space.local, second_space.local);
373        assert_ne!(first_space, second_space);
374
375        let first_temp = ctx.bodies[first].push_temp(Temp::new(0x20, 4, first_space.local));
376        let second_temp = ctx.bodies[second].push_temp(Temp::new(0x20, 4, second_space.local));
377        assert_eq!(first_temp.local, second_temp.local);
378        assert_ne!(first_temp, second_temp);
379
380        let module = ModuleView::new(&ctx);
381        assert_eq!(module.temp_ref(first_temp).space().id, first_space);
382        assert_eq!(module.temp_ref(second_temp).space().id, second_space);
383
384        let body = BodyView::new(&ctx.bodies[first], &ctx.shared, &ctx.interfaces);
385        assert_eq!(body.temp_ref(first_temp).size(), 4);
386        assert!(std::panic::catch_unwind(|| body.temp(second_temp)).is_err());
387        assert!(std::panic::catch_unwind(|| body.temp_space(second_space)).is_err());
388    }
389
390    #[test]
391    fn temporary_arena_ids_survive_context_round_trip() {
392        let mut ctx = Context::new();
393        let function = FunctionBody::make(&mut ctx, "roundtrip".into()).unwrap().id;
394        let space = ctx.bodies[function].push_temp_space(TempSpace::new(Some("local"), 1, 8));
395        let temp = ctx.bodies[function].push_temp(Temp::new(0x30, 2, space.local));
396
397        let bytes = bincode::serde::encode_to_vec(&ctx, bincode::config::standard()).unwrap();
398        let (restored, consumed): (Context<'static>, _) =
399            bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
400        assert_eq!(consumed, bytes.len());
401
402        let view = ModuleView::new(&restored);
403        assert_eq!(view.temp_space_ref(space).name(), Some("local"));
404        assert_eq!(view.temp_ref(temp).address(), 0x30);
405        assert_eq!(view.temp_ref(temp).space().id, space);
406    }
407
408    #[test]
409    fn temporary_names_are_body_local_and_qualified_at_lookup() {
410        let mut ctx = Context::new();
411        let first = FunctionBody::make(&mut ctx, "first".into()).unwrap().id;
412        let second = FunctionBody::make(&mut ctx, "second".into()).unwrap().id;
413        let first_space = ctx.bodies[first].push_temp_space(TempSpace::new(None, 1, 8));
414        let second_space = ctx.bodies[second].push_temp_space(TempSpace::new(None, 1, 8));
415        let first_temp = ctx.bodies[first]
416            .push_temp(Temp::new(0x20, 4, first_space.local).with_name("scratch".into()));
417        let second_temp = ctx.bodies[second]
418            .push_temp(Temp::new(0x20, 4, second_space.local).with_name("scratch".into()));
419
420        assert_eq!(first_temp.local, second_temp.local);
421        assert_eq!(
422            ctx.bodies[first].names.get("scratch"),
423            Some(LocalValueId::Temp(first_temp.local))
424        );
425        assert_eq!(
426            FunctionBody::from_id(&ctx, first).local_named("scratch"),
427            Some(ValueId::Temp(first_temp))
428        );
429        assert_eq!(
430            FunctionBody::from_id(&ctx, second).local_named("scratch"),
431            Some(ValueId::Temp(second_temp))
432        );
433        assert_eq!(ctx.get_named("scratch"), None);
434    }
435
436    #[test]
437    fn temporary_values_and_spaces_render_and_preserve_qualified_provenance() {
438        let mut ctx = Context::new();
439        let function = FunctionBody::make(&mut ctx, "temporary_ir".into())
440            .unwrap()
441            .id;
442        let space = ctx.bodies[function].push_temp_space(TempSpace::new(Some("scratch"), 1, 8));
443        let temp = ctx.bodies[function].push_temp(Temp::new(0x20, 8, space.local));
444        ctx.bodies[function].temps[temp.local].label = Some(9);
445
446        let pointer_type = ctx
447            .shared
448            .types
449            .get_or_make_space_address(8, MemorySpaceId::Temp(space));
450        let pointer = InstructionRef::from_mnemonic_with_type(
451            &mut ctx,
452            function,
453            Mnemonic::Unop(Unary {
454                op: Unop::IntNot,
455                src: ValueId::Temp(temp).localize(function),
456            }),
457            pointer_type,
458        )
459        .id;
460        let load_type = ctx.shared.types.get_or_make_int(4);
461        let load = InstructionRef::from_mnemonic_with_type(
462            &mut ctx,
463            function,
464            Mnemonic::Load(Load {
465                space: LocalMemorySpaceId::Temp(space.local),
466                ptr: ValueId::Instruction(pointer).localize(function),
467                size: 4,
468            }),
469            load_type,
470        )
471        .id;
472
473        let view = ModuleView::new(&ctx);
474        assert_eq!(
475            view.insn_ref(pointer).memory_space(),
476            Some(MemorySpaceId::Temp(space))
477        );
478        assert_eq!(
479            view.shared()
480                .types
481                .size_of(view.type_of(ValueId::Temp(temp))),
482            8
483        );
484        assert_eq!(view.temp_ref(temp).to_string(), "v9");
485        assert_eq!(
486            view.insn_ref(load).as_statement().to_string(),
487            "i32 %tmp1 = load($temp0:4, i64 %tmp0);"
488        );
489    }
490}