1use 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
23pub 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 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#[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 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#[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 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}