1use crate::value::QCodeMut;
4use std::{borrow::Cow, fmt::Display};
5
6use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
7
8use crate::{
9 assumption::{Certainty, KnownContradiction, PassName, Proposition, Truth, Violation},
10 error::{Error, ErrorTy, Result},
11 pass_scope,
12 space::{LocalMemorySpaceId, MemorySpaceId, Space, SpaceId, SpaceStore},
13 types::TypeManager,
14 value::{
15 BasicBlock, BlockParamRef, FunctionBody, FunctionId, FunctionRef, Instruction, ModuleView,
16 QCodeView, TempId, TempSpaceId, ValueId,
17 block::{BlockId, BlockRef, EdgeData, EdgeId},
18 block_param::{BlockParam, BlockParamId},
19 insn::{InstructionId, InstructionRef, Mnemonic, PCodeOpId},
20 literal::{LiteralId, LiteralRef},
21 registry::ValueRegistry,
22 varnode::{Varnode, VarnodeId, VarnodeRef, register::RegisterId},
23 },
24};
25use jstd::registry::{self, Identified, Registry};
26
27#[derive(Default, Clone, serde::Serialize)]
55pub struct Context<'str> {
56 pub shared: Shared<'str>,
62
63 #[serde(default)]
68 pub interfaces: Registry<FunctionId, crate::value::function::FunctionInterface<'str>>,
69
70 pub bodies: Registry<FunctionId, FunctionBody<'str>>,
76}
77
78#[derive(serde::Deserialize)]
79struct ContextWire<'str> {
80 shared: Shared<'str>,
81 #[serde(default)]
82 interfaces: Registry<FunctionId, crate::value::function::FunctionInterface<'str>>,
83 bodies: Registry<FunctionId, FunctionBody<'str>>,
84}
85
86impl<'de, 'str> serde::Deserialize<'de> for Context<'str> {
87 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
88 where
89 D: serde::Deserializer<'de>,
90 {
91 let ContextWire {
92 shared,
93 interfaces,
94 mut bodies,
95 } = ContextWire::deserialize(deserializer)?;
96 if interfaces.len() != bodies.len() {
97 return Err(serde::de::Error::custom(
98 "function body/interface registries drifted",
99 ));
100 }
101 for mut body in bodies.iter_mut() {
102 let id = body.id;
103 body.rehydrate_id(id);
104 }
105 Ok(Self {
106 shared,
107 interfaces,
108 bodies,
109 })
110 }
111}
112
113#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
121pub struct Shared<'str> {
122 pub default_space: SpaceId,
123
124 pub(crate) spaces: Registry<SpaceId, Space>,
126
127 pub pcode_ops: Registry<PCodeOpId, Box<str>>,
129
130 pub named_spaces: HashMap<Box<str>, SpaceId>,
132
133 pub(crate) name_map: NameTable<'str>,
139
140 pub registers: HashMap<RegisterId, VarnodeId>,
142
143 pub values: ValueRegistry<'str>,
145
146 pub types: TypeManager,
148
149 #[serde(default)]
156 pub(crate) protections_known: bool,
157
158 #[serde(default)]
162 pub(crate) primary_entrypoint: Option<u64>,
163
164 #[serde(default)]
171 pub(crate) discoveries: crate::discovery::DiscoveryQueue,
172
173 #[serde(default)]
178 pub(crate) target_os: TargetOs,
179
180 #[serde(default)]
186 pub(crate) linked_libraries: Vec<String>,
187
188 #[serde(default)]
194 pub(crate) ignored_functions: HashSet<u64>,
195
196 #[serde(skip)]
204 pub(crate) assumed_call_convention: Option<crate::assumption::AssumedCallEffect>,
205}
206
207impl SpaceStore for Shared<'_> {
210 fn spaces(&self) -> &Registry<SpaceId, Space> {
211 &self.spaces
212 }
213}
214
215impl SpaceStore for Context<'_> {
217 fn spaces(&self) -> &Registry<SpaceId, Space> {
218 &self.shared.spaces
219 }
220}
221
222impl<'str> Shared<'str> {
223 pub fn get_named(&self, name: &str) -> Option<ValueId> {
226 self.name_map.get(name)
227 }
228
229 pub fn varnode(&self, id: VarnodeId) -> &crate::value::Varnode<'str> {
231 &self.values.varnodes[id]
232 }
233
234 pub fn space(&self, id: SpaceId) -> &Space {
236 &self.spaces[id]
237 }
238
239 pub fn spaces(&self) -> impl Iterator<Item = Identified<SpaceId, &Space>> + '_ {
242 self.spaces.iter()
243 }
244
245 pub fn get_const(&self, value: u64, size: usize) -> ValueId {
249 let type_id = self.types.get_or_make_int(size);
250 ValueId::Literal(self.values.get_or_make_typed_literal(value, type_id, size))
251 }
252
253 pub fn pcode_op(&mut self, name: &str) -> PCodeOpId {
259 if let Some(op) = self.pcode_ops.iter().find(|op| op.as_ref() == name) {
260 return op.id;
261 }
262 self.pcode_ops.push(Box::from(name))
263 }
264
265 pub fn vm_interrupt_op(&mut self) -> PCodeOpId {
268 self.pcode_op(crate::value::insn::VM_INTERRUPT)
269 }
270
271 pub fn get_bool_const(&self, value: bool) -> ValueId {
274 let type_id = self.types.get_or_make_bool();
275 ValueId::Literal(
276 self.values
277 .get_or_make_typed_literal(u64::from(value), type_id, 1),
278 )
279 }
280
281 pub fn get_typed_const(&self, value: u64, type_id: crate::types::TypeId) -> ValueId {
284 let size = self.types.size_of(type_id);
285 ValueId::Literal(self.values.get_or_make_typed_literal(value, type_id, size))
286 }
287
288 pub fn get_bytes(&self, data: Vec<u8>) -> ValueId {
291 let i8_ty = self.types.get_or_make_int(1);
292 let type_id = self.types.get_or_make_array(i8_ty, data.len());
293 ValueId::Bytes(
294 self.values
295 .bytes
296 .push(crate::value::Bytes { data, type_id }),
297 )
298 }
299
300 pub fn bytes_display(&self, id: crate::value::BytesId) -> crate::value::BytesDisplay {
305 self.values
306 .bytes_display
307 .get(&id)
308 .copied()
309 .unwrap_or_default()
310 }
311
312 pub fn get_typed_bytes(&self, data: Vec<u8>, type_id: crate::types::TypeId) -> ValueId {
316 ValueId::Bytes(
317 self.values
318 .bytes
319 .push(crate::value::Bytes { data, type_id }),
320 )
321 }
322
323 pub fn truth(&self, prop: Proposition) -> Option<Truth> {
327 self.values.truths.get(&prop).copied()
328 }
329
330 pub fn assumed_call_convention(&self) -> Option<&crate::assumption::AssumedCallEffect> {
336 self.assumed_call_convention.as_ref()
337 }
338
339 pub fn varnodes(&self) -> impl Iterator<Item = crate::value::VarnodeRef<'str, '_>> + '_ {
342 self.values
343 .varnodes
344 .iter()
345 .map(move |v| crate::value::Varnode::from_id(self, v.id))
346 }
347
348 pub fn varnode_count(&self) -> usize {
351 self.values.varnodes.len()
352 }
353
354 pub fn stored_type_of(&self, id: ValueId) -> Option<crate::types::TypeId> {
361 match id {
362 ValueId::Literal(lid) => Some(self.values.literals[lid].type_id),
363 ValueId::Bytes(bid) => Some(self.values.bytes[bid].type_id),
364 ValueId::Varnode(vid) => self.values.varnode_types.get(&vid).copied(),
365 ValueId::Poison(pid) => Some(self.values.poisons[pid].type_id),
366 ValueId::Instruction(_)
367 | ValueId::BlockParam(_)
368 | ValueId::BasicBlock(_)
369 | ValueId::Temp(_)
370 | ValueId::Function(_) => None,
371 }
372 }
373}
374
375pub use wazabin_binary::TargetOs;
379
380impl<'str> Context<'str> {
381 pub fn new() -> Self {
387 let mut ctx = Self::default();
388 ctx.shared.spaces.push(Space::new(Some("const"), 1, 8));
390 let default_space = Space::new(Some("ram"), 1, 8);
392 ctx.shared.default_space = ctx.shared.spaces.push(default_space);
393 ctx
394 }
395
396 pub fn try_get_space(&self, name: &str) -> Option<SpaceId> {
399 self.shared.named_spaces.get(name).copied()
400 }
401
402 pub fn get_or_make_named_space(&mut self, name: &str) -> SpaceId {
407 if let Some(id) = self.try_get_space(name) {
408 return id;
409 }
410 let default_id = self.shared.default_space;
411 if self.shared.spaces[default_id].name.as_deref() == Some(name) {
412 return default_id;
413 }
414 let default = &self.shared.spaces[self.shared.default_space];
415 let space = Space::new(Some(name), default.word_size, default.addr_size);
416 self.add_space(space)
417 }
418
419 pub fn add_space(&mut self, space: Space) -> SpaceId {
421 let name_key: Option<Box<str>> = space.name.clone();
422 let id = self.shared.spaces.push(space);
423 if let Some(name) = name_key {
424 self.shared.named_spaces.insert(name, id);
425 }
426 id
427 }
428
429 pub fn space_count(&self) -> usize {
431 self.shared.spaces.len()
432 }
433
434 pub fn spaces(&self) -> impl Iterator<Item = Identified<SpaceId, &Space>> + '_ {
437 self.shared.spaces()
438 }
439
440 pub fn set_primary_entrypoint(&mut self, entrypoint: Option<u64>) {
441 self.shared.primary_entrypoint = entrypoint;
442 }
443
444 pub fn primary_entrypoint(&self) -> Option<u64> {
445 self.shared.primary_entrypoint
446 }
447
448 pub fn set_ignored_functions(&mut self, addrs: HashSet<u64>) {
452 self.shared.ignored_functions = addrs;
453 }
454
455 pub fn ignored_functions(&self) -> &HashSet<u64> {
457 &self.shared.ignored_functions
458 }
459
460 pub fn is_function_ignored(&self, addr: Option<u64>) -> bool {
463 addr.is_some_and(|a| self.shared.ignored_functions.contains(&a))
464 }
465
466 pub fn set_target_os(&mut self, os: TargetOs) {
469 self.shared.target_os = os;
470 }
471
472 pub fn target_os(&self) -> TargetOs {
474 self.shared.target_os
475 }
476
477 pub fn set_linked_libraries(&mut self, libs: Vec<String>) {
480 self.shared.linked_libraries = libs;
481 }
482
483 pub fn linked_libraries(&self) -> &[String] {
486 &self.shared.linked_libraries
487 }
488
489 pub fn load_spaces(&mut self, spaces: registry::Registry<SpaceId, Space>) {
491 self.shared.spaces = spaces;
492 }
493
494 pub fn mark_protections_known(&mut self) {
498 self.shared.protections_known = true;
499 }
500
501 pub fn protections_known(&self) -> bool {
503 self.shared.protections_known
504 }
505
506 pub fn assume_executable(
525 &mut self,
526 binary: &dyn wazabin_binary::BinaryFormat,
527 addr: u64,
528 ) -> bool {
529 let bounds = binary.segment_bounds(addr);
530 if let Some((start, end)) = bounds
531 && let Some(known) = self.known(Proposition::ExecutableMemory { start, end })
532 {
533 return known;
534 }
535 if !self.shared.protections_known || binary.is_executable(addr) {
536 return true;
537 }
538 if let Some((start, end)) = bounds {
539 self.set_known(Proposition::ExecutableMemory { start, end }, false);
540 }
541 false
542 }
543
544 pub fn unique_name(&mut self, name: Cow<'str, str>) -> Cow<'str, str> {
547 self.shared.name_map.unique(name)
548 }
549
550 pub fn discover(&mut self, discovery: crate::discovery::Discovery) -> bool {
553 self.shared.discoveries.insert(discovery)
554 }
555
556 pub fn discover_code(&mut self, func_entry: u64, source_block: u64, target: u64) {
565 self.shared.discoveries.insert(
566 crate::discovery::Discovery::block(target, func_entry)
567 .with_edge_kind(crate::discovery::EdgeKind::JumpTableTarget)
568 .from_block_addr(source_block)
569 .with_provenance(crate::discovery::DiscoveryProvenance::Optimization {
570 pass: "handle_jump_tables".to_string(),
571 assumption: None,
572 }),
573 );
574 }
575
576 pub fn drain_discoveries(&mut self) -> Vec<crate::discovery::Discovery> {
578 self.shared.discoveries.drain()
579 }
580
581 pub fn discoveries(&self) -> impl Iterator<Item = &crate::discovery::Discovery> + '_ {
583 self.shared.discoveries.iter()
584 }
585
586 pub fn discovery_records(
590 &self,
591 ) -> impl Iterator<
592 Item = (
593 &crate::discovery::DiscoveryKey,
594 &crate::discovery::Discovery,
595 &crate::discovery::DiscoveryState,
596 ),
597 > + '_ {
598 self.shared.discoveries.records()
599 }
600
601 pub fn has_no_discoveries(&self) -> bool {
603 self.shared.discoveries.is_empty()
604 }
605
606 pub fn lifted_code_seeds(&self) -> Vec<crate::discovery::CodeSeed> {
611 self.shared.discoveries.lifted_seeds()
612 }
613
614 pub fn seed_code(&mut self, seeds: impl IntoIterator<Item = crate::discovery::CodeSeed>) {
620 for seed in seeds {
621 self.shared.discoveries.insert(seed.into_discovery());
622 }
623 }
624
625 pub fn mark_discovery_lifted(&mut self, key: crate::discovery::DiscoveryKey) {
626 self.shared.discoveries.mark_lifted(key);
627 }
628
629 pub fn mark_discovery_failed(
630 &mut self,
631 key: crate::discovery::DiscoveryKey,
632 reason: impl Into<String>,
633 ) {
634 self.shared.discoveries.mark_failed(key, reason);
635 }
636
637 pub fn mark_discovery_skipped(
638 &mut self,
639 key: crate::discovery::DiscoveryKey,
640 reason: impl Into<String>,
641 ) {
642 self.shared.discoveries.mark_skipped(key, reason);
643 }
644
645 pub fn get_or_make_block(&mut self, addr: u64, func: FunctionId) -> BlockId {
650 let mut addresses = crate::address_index::AddressIndex::analyze(self);
651 self.get_or_make_block_indexed(&mut addresses, addr, func)
652 }
653
654 #[track_caller]
658 pub fn get_or_make_block_indexed(
659 &mut self,
660 addresses: &mut crate::address_index::AddressIndex,
661 addr: u64,
662 func: FunctionId,
663 ) -> BlockId {
664 use crate::address_index::AddressTarget;
665
666 if let Some(AddressTarget::Function(owner)) = addresses.get(addr) {
667 assert_eq!(
668 owner, func,
669 "cannot create a block at an address owned by another function"
670 );
671 }
672 let existing = match addresses.get(addr) {
673 Some(AddressTarget::Block(block)) => Some(block),
674 Some(AddressTarget::Function(function)) => FunctionBody::from_id(self, function)
675 .root()
676 .map(|root| root.id),
677 None => None,
678 };
679 match existing {
680 Some(block) => {
681 if self.block(block).address != Some(addr)
691 && block.func == func
692 && self.block(block).extra_addresses.contains(&addr)
693 {
694 return self.split_block_at_address(addresses, block, addr);
695 }
696 if block.func != func {
697 let stored = FunctionBody::from_id(self, block.func);
698 let requested = FunctionBody::from_id(self, func);
699 let parent = Some(block.func);
703 let caller = std::panic::Location::caller();
704 let detail = format!(
705 "cannot reuse a block stored in another function arena: block={block:?} address=0x{addr:x}; stored={:?} name={:?} entry={:?} parent={parent:?}; requested={:?} name={:?} entry={:?}; caller={caller}",
706 block.func,
707 stored.name(),
708 stored.address(),
709 func,
710 requested.name(),
711 requested.address(),
712 );
713 log::error!(
714 target: "qcode::arena",
715 "{detail}\nbacktrace:\n{}",
716 std::backtrace::Backtrace::force_capture()
717 );
718 panic!("{detail}");
719 }
720 block
721 }
722 None => {
723 BasicBlock::make(self, func)
724 .with_address_indexed(addresses, addr)
725 .id
726 }
727 }
728 }
729
730 pub fn split_block_at_address(
750 &mut self,
751 addresses: &mut crate::address_index::AddressIndex,
752 block: BlockId,
753 addr: u64,
754 ) -> BlockId {
755 addresses.mark_boundary(addr);
758 let tail = BasicBlock::make(self, block.func).id;
759
760 self.bodies[block.func].clear_block_instructions(block);
763
764 let absorbed = std::mem::take(&mut self.block_mut(block).extra_addresses);
768 for absorbed_addr in absorbed {
769 addresses.forget(absorbed_addr);
770 }
771 BasicBlock::from_id_mut(self, tail)
772 .in_function(block.func)
773 .with_address_indexed(addresses, addr);
774 tail
775 }
776
777 pub fn split_block_before(&mut self, block: BlockId, insn: InstructionId) -> BlockId {
781 self.bodies[block.func].split_block_before(block, insn)
782 }
783
784 pub fn builder(&mut self, block: BlockId) -> crate::builder::Builder<'str, '_> {
787 let body = &mut self.bodies[block.func];
788 crate::builder::Builder::new(body, &self.shared, &self.interfaces, block)
789 }
790
791 pub fn builder_at(&mut self, address: u64) -> crate::builder::Builder<'str, '_> {
794 use crate::address_index::AddressTarget;
795
796 let mut addresses = crate::address_index::AddressIndex::analyze(self);
797 let block = match addresses.get(address) {
798 Some(AddressTarget::Function(function)) => self.bodies[function]
799 .root_id()
800 .map(|local| BlockId::new(function, local))
801 .unwrap_or_else(|| {
802 self.get_or_make_block_indexed(&mut addresses, address, function)
803 }),
804 Some(AddressTarget::Block(block)) => block,
805 None => {
806 let function = FunctionBody::make(self, Cow::Owned(format!("blk_{address:x}")))
807 .expect("anonymous host function")
808 .id;
809 self.get_or_make_block_indexed(&mut addresses, address, function)
810 }
811 };
812 let mut builder = self.builder(block);
813 builder.set_address(address);
814 builder
815 }
816
817 pub fn bytes_display(&self, id: crate::value::BytesId) -> crate::value::BytesDisplay {
820 self.shared
821 .values
822 .bytes_display
823 .get(&id)
824 .copied()
825 .unwrap_or_default()
826 }
827
828 pub fn set_bytes_display(
832 &mut self,
833 id: crate::value::BytesId,
834 mode: crate::value::BytesDisplay,
835 ) {
836 if mode == crate::value::BytesDisplay::Auto {
837 self.shared.values.bytes_display.remove(&id);
838 } else {
839 self.shared.values.bytes_display.insert(id, mode);
840 }
841 }
842
843 pub fn block_ids(&self) -> Vec<BlockId> {
845 self.functions().flat_map(|f| f.block_ids()).collect()
846 }
847
848 pub fn instruction_ids(&self) -> Vec<InstructionId> {
852 let mut ids: Vec<_> = self.functions().flat_map(|f| f.instruction_ids()).collect();
853 ids.sort_unstable();
854 ids
855 }
856
857 pub fn function_ids(&self) -> Vec<FunctionId> {
859 self.interfaces.iter().map(|i| i.id).collect()
860 }
861
862 pub fn anon_function(&mut self) -> FunctionId {
868 let name = self.get_unique_name(std::borrow::Cow::Borrowed("anon"));
869 crate::value::FunctionBody::make(self, name)
870 .expect("unique anon function name")
871 .id
872 }
873
874 pub fn instruction_arena_stats(&self) -> (usize, usize) {
876 let mut total = 0;
877 let mut dead = 0;
878 for f in self.bodies.iter() {
879 total += f.insns.issued_len();
880 dead += f.insns.issued_len() - f.insns.len();
881 }
882 (total, dead)
883 }
884
885 pub fn body_arena_stats(&self) -> crate::value::BodyArenaStats {
891 let mut total = crate::value::BodyArenaStats::default();
892 for body in self.bodies.iter() {
893 total.add_assign(body.arena_stats());
894 }
895 total
896 }
897
898 pub fn shrink_bodies_to_fit(&mut self) {
905 for mut body in self.bodies.iter_mut() {
906 body.shrink_to_fit();
907 }
908 }
909
910 pub fn instructions(&self) -> impl Iterator<Item = InstructionRef<'str, '_>> + '_ {
912 self.instruction_ids()
913 .into_iter()
914 .map(move |id| Instruction::from_id(self, id))
915 }
916
917 pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'str, '_>> + '_ {
919 self.block_ids()
920 .into_iter()
921 .map(move |id| BlockRef::from_id(self, id))
922 }
923
924 pub fn functions(&self) -> FunctionIter<'str, '_> {
926 FunctionIter {
927 ctx: self,
928 inner: self.bodies.iter(),
929 }
930 }
931
932 pub fn iter(&self) -> FunctionIter<'str, '_> {
935 self.functions()
936 }
937
938 pub fn varnodes(&self) -> impl Iterator<Item = VarnodeRef<'str, '_>> + '_ {
939 self.shared.varnodes()
940 }
941
942 pub fn varnode_count(&self) -> usize {
947 self.shared.varnode_count()
948 }
949
950 pub fn remove_cfg_edge(&mut self, func: FunctionId, edge_id: EdgeId) {
954 self.bodies[func].remove_cfg_edge(edge_id);
955 }
956
957 pub fn rehome_owned_blocks(
977 &mut self,
978 addresses: &mut crate::address_index::AddressIndex,
979 target: FunctionId,
980 olds: &[BlockId],
981 ) -> HashMap<BlockId, BlockId> {
982 let mut needed_temps: HashSet<TempId> = HashSet::default();
986 let mut needed_temp_spaces: HashSet<TempSpaceId> = HashSet::default();
987 for &old in olds {
988 for ¶m_local in &self.block(old).params {
989 let param = self.block_param(BlockParamId::new(old.func, param_local));
990 if let Some(crate::value::LocalValueId::Temp(temp)) = param.origin {
991 needed_temps.insert(TempId::new(old.func, temp));
992 }
993 if let Some(MemorySpaceId::Temp(space)) = self.shared.types.space_of(param.type_id)
994 {
995 needed_temp_spaces.insert(space);
996 }
997 }
998 for &insn_local in &self.block(old).instructions {
999 let insn = self.instruction(InstructionId::new(old.func, insn_local));
1000 for arg in insn.mnemonic().args() {
1001 if let crate::value::LocalValueId::Temp(temp) = arg {
1002 needed_temps.insert(TempId::new(old.func, temp));
1003 }
1004 }
1005 let explicit_space = match insn.mnemonic() {
1006 Mnemonic::Load(load) => Some(load.space),
1007 Mnemonic::Store(store) => Some(store.space),
1008 _ => None,
1009 };
1010 if let Some(LocalMemorySpaceId::Temp(space)) = explicit_space {
1011 needed_temp_spaces.insert(TempSpaceId::new(old.func, space));
1012 }
1013 if let Some(MemorySpaceId::Temp(space)) = self.shared.types.space_of(insn.type_id) {
1014 needed_temp_spaces.insert(space);
1015 }
1016 }
1017 }
1018 for &temp in &needed_temps {
1019 let data = &self.bodies[temp.func].temps[temp.local];
1020 needed_temp_spaces.insert(TempSpaceId::new(temp.func, data.space));
1021 }
1022
1023 let mut needed_temp_spaces: Vec<_> = needed_temp_spaces.into_iter().collect();
1024 needed_temp_spaces.sort_unstable();
1025 let mut temp_space_map: HashMap<TempSpaceId, TempSpaceId> = HashMap::default();
1026 for old in needed_temp_spaces {
1027 if old.func == target {
1028 continue;
1029 }
1030 let space = self.bodies[old.func].temp_spaces[old.local].clone();
1031 let new = self.bodies[target].push_temp_space(space);
1032 temp_space_map.insert(old, new);
1033 }
1034
1035 let mut needed_temps: Vec<_> = needed_temps.into_iter().collect();
1036 needed_temps.sort_unstable();
1037 let mut value_map: HashMap<ValueId, ValueId> = HashMap::default();
1038 for old in needed_temps {
1039 if old.func == target {
1040 continue;
1041 }
1042 let mut temp = self.bodies[old.func].temps[old.local].clone();
1043 temp.space = temp_space_map[&TempSpaceId::new(old.func, temp.space)].local;
1044 if let Some(name) = temp.name.take() {
1045 temp.name = Some(self.bodies[target].names.unique(name));
1046 }
1047 let new = self.bodies[target].push_temp(temp);
1048 value_map.insert(ValueId::Temp(old), ValueId::Temp(new));
1049 }
1050
1051 let mut block_map: HashMap<BlockId, BlockId> = HashMap::default();
1054 for &old in olds {
1055 let new = BasicBlock::clone_block_into(self, old, target, &mut value_map);
1056 block_map.insert(old, new);
1057 }
1058
1059 for (&old, &new) in &block_map {
1064 let old_params = self.block(old).params.clone();
1065 let new_params = self.block(new).params.clone();
1066 for (old_local, new_local) in old_params.into_iter().zip(new_params) {
1067 let old_param = BlockParamId::new(old.func, old_local);
1068 let new_param = BlockParamId::new(new.func, new_local);
1069 let type_id = remap_rehomed_type(
1070 self,
1071 self.block_param(new_param).type_id,
1072 target,
1073 &temp_space_map,
1074 );
1075 self.block_param_mut(new_param).type_id = type_id;
1076 let Some(origin) = self.block_param(new_param).origin else {
1077 continue;
1078 };
1079 let qualified = origin.qualify(old.func);
1080 let remapped = value_map.get(&qualified).copied().unwrap_or(qualified);
1081 debug_assert!(
1082 remapped.owning_function().is_none_or(|f| f == target),
1083 "rehome: relocated block param {old_param:?} has an origin in another \
1084 function ({qualified:?}); the relocated set is not closed",
1085 );
1086 self.block_param_mut(new_param).origin = Some(remapped.localize(new.func));
1087 }
1088
1089 let insns = self.block(new).instructions.clone();
1090 for insn_local in insns {
1091 let insn_id = InstructionId::new(new.func, insn_local);
1092 let type_id = remap_rehomed_type(
1093 self,
1094 self.instruction(insn_id).type_id,
1095 target,
1096 &temp_space_map,
1097 );
1098 self.instruction_mut(insn_id).type_id = type_id;
1099 let mut mnemonic = self.instruction(insn_id).mnemonic().clone();
1100 let mut pairs = Vec::new();
1101 for arg in mnemonic.args() {
1102 let qualified = arg.qualify(old.func);
1106 if let Some(&new_val) = value_map.get(&qualified) {
1107 pairs.push((arg, new_val.localize(new.func)));
1108 } else if let Some(new_lit) =
1109 remap_symbolic_block_literal(&self.shared.values.literals, arg, &block_map)
1110 {
1111 pairs.push((arg, new_lit));
1112 } else {
1113 debug_assert!(
1118 qualified.owning_function().is_none_or(|f| f == target),
1119 "rehome: relocated block references a value in another \
1120 function ({qualified:?}); the relocated set is not closed",
1121 );
1122 }
1123 }
1124 crate::value::block::substitute_operands(&mut mnemonic, &pairs);
1125 remap_rehomed_memory_space(&mut mnemonic, old.func, target, &temp_space_map);
1126 remap_block_targets(&mut mnemonic, old.func, new.func, &block_map);
1127 *self.instruction_mut(insn_id).mnemonic_mut() = mnemonic;
1128 }
1129 }
1130
1131 let mut incident: HashSet<(FunctionId, EdgeId)> = HashSet::default();
1138 for &old in olds {
1139 incident.extend(self.block(old).edges.iter().map(|&e| (old.func, e)));
1140 }
1141 let mut incident: Vec<_> = incident.into_iter().collect();
1142 incident.sort_unstable();
1143 for (edge_func, edge) in incident {
1144 let EdgeData { from, to } = *self.edge(edge_func, edge);
1145 let from = BlockId::new(edge_func, from);
1146 let to = BlockId::new(edge_func, to);
1147 let new_from = block_map.get(&from).copied().unwrap_or(from);
1148 let new_to = block_map.get(&to).copied().unwrap_or(to);
1149 self.add_cfg_edge(new_from, new_to);
1150 }
1151
1152 for &old in olds {
1158 let Some(addr) = self.block(old).address else {
1159 continue;
1160 };
1161 let new = block_map[&old];
1162 let extra = self.block(old).extra_addresses.clone();
1163 addresses.rehome_block(addr, old, new);
1164 for &e in &extra {
1165 addresses.rehome_block(e, old, new);
1166 }
1167 self.block_mut(new).extra_addresses = extra;
1168 self.block_mut(new).address = Some(addr);
1169 }
1170
1171 for &old in olds {
1174 BasicBlock::from_id_mut(self, old).delete();
1175 }
1176
1177 self.rebuild_users(target);
1180 block_map
1181 }
1182
1183 fn function_registered_at_block(
1187 &self,
1188 addresses: &crate::address_index::AddressIndex,
1189 block: BlockId,
1190 ) -> Option<FunctionId> {
1191 self.block(block)
1192 .address
1193 .and_then(|addr| addresses.function_at(addr))
1194 }
1195
1196 fn split_tail(
1205 &self,
1206 addresses: &crate::address_index::AddressIndex,
1207 block: BlockId,
1208 g: FunctionId,
1209 ) -> Vec<BlockId> {
1210 let mut seen: HashSet<BlockId> = HashSet::default();
1211 seen.insert(block);
1212 let mut queue = vec![block];
1213 while let Some(b) = queue.pop() {
1214 let succs: Vec<BlockId> = BasicBlock::from_id(self, b)
1215 .successors()
1216 .map(|(_, s)| s)
1217 .collect();
1218 for s in succs {
1219 if seen.contains(&s) {
1220 continue;
1221 }
1222 if let Some(entry_func) = self.function_registered_at_block(addresses, s)
1226 && entry_func != g
1227 {
1228 continue;
1229 }
1230 seen.insert(s);
1231 queue.push(s);
1232 }
1233 }
1234 let mut tail: Vec<BlockId> = seen.into_iter().collect();
1235 tail.sort_unstable_by_key(|&b| (self.block(b).address, b.local, b.func));
1236 tail
1237 }
1238
1239 pub fn split_function_at(&mut self, block: BlockId) -> FunctionId {
1263 let mut addresses = crate::address_index::AddressIndex::analyze(self);
1264 self.split_function_at_indexed(&mut addresses, block)
1265 }
1266
1267 pub fn split_function_at_indexed(
1270 &mut self,
1271 addresses: &mut crate::address_index::AddressIndex,
1272 block: BlockId,
1273 ) -> FunctionId {
1274 use crate::value::insn::{Branch, CBranch, Callee, TailCall};
1275
1276 let addr = self
1277 .block(block)
1278 .address
1279 .expect("split_function_at: block has no machine address");
1280
1281 let g = match addresses.function_at(addr) {
1286 Some(existing) => existing,
1287 None => FunctionBody::make_at_addr_indexed(self, addresses, addr, None).id,
1288 };
1289
1290 loop {
1303 let tail_set: HashSet<BlockId> =
1304 self.split_tail(addresses, block, g).into_iter().collect();
1305 let mut promote: Option<BlockId> = None;
1306 'scan: for b in self.block_ids() {
1307 if tail_set.contains(&b) {
1308 continue;
1310 }
1311 let Some(mnemonic) = BasicBlock::from_id(self, b)
1312 .instructions()
1313 .last()
1314 .map(|t| t.mnemonic().clone())
1315 else {
1316 continue;
1317 };
1318 let targets = match &mnemonic {
1319 Mnemonic::Branch(Branch { target, .. }) => vec![*target],
1320 Mnemonic::CBranch(CBranch {
1321 success_block,
1322 failure_block,
1323 ..
1324 }) => vec![*success_block, *failure_block],
1325 _ => vec![],
1326 };
1327 for t in targets {
1328 let tid = BlockId::new(b.func, t);
1329 if tid == block || !tail_set.contains(&tid) {
1333 continue;
1334 }
1335 promote = Some(tid);
1344 break 'scan;
1345 }
1346 }
1347 match promote {
1348 Some(tid) => {
1349 self.split_function_at_indexed(addresses, tid);
1350 }
1351 None => break,
1352 }
1353 }
1354
1355 let mut tail = self.split_tail(addresses, block, g);
1358 let mut tail_set: HashSet<BlockId> = tail.iter().copied().collect();
1359
1360 let mut prev_owners: HashSet<FunctionId> = HashSet::default();
1363 for &b in &tail {
1364 prev_owners.insert(b.func);
1366 }
1367
1368 let effective_owner = |_ctx: &Context, candidate: BlockId| {
1372 if tail_set.contains(&candidate) {
1373 Some(g)
1374 } else {
1375 Some(candidate.func)
1377 }
1378 };
1379
1380 let foreign_entry =
1383 |ctx: &Context, target: BlockId, owner: FunctionId| -> Option<FunctionId> {
1384 let callee = if target == block {
1385 g
1386 } else {
1387 ctx.function_registered_at_block(addresses, target)?
1388 };
1389 (callee != owner).then_some(callee)
1390 };
1391
1392 let mut tail_calls: Vec<(InstructionId, FunctionId)> = Vec::new();
1398 let mut cond_calls: Vec<(
1405 InstructionId,
1406 BlockId,
1407 FunctionId,
1408 crate::value::LocalBlockId,
1409 )> = Vec::new();
1410 let relevant: Vec<BlockId> = self.block_ids();
1411 for b in relevant {
1412 let Some(owner) = effective_owner(self, b) else {
1413 continue;
1414 };
1415 let Some((term_id, mnemonic)) = BasicBlock::from_id(self, b)
1416 .instructions()
1417 .last()
1418 .map(|t| (t.id, t.mnemonic().clone()))
1419 else {
1420 continue;
1421 };
1422 match mnemonic {
1425 Mnemonic::Branch(Branch { target, .. }) => {
1426 if let Some(callee) = foreign_entry(self, BlockId::new(b.func, target), owner) {
1427 tail_calls.push((term_id, callee));
1428 }
1429 }
1430 Mnemonic::CBranch(CBranch {
1431 success_block,
1432 failure_block,
1433 ..
1434 }) => {
1435 if let Some(callee) =
1436 foreign_entry(self, BlockId::new(b.func, success_block), owner)
1437 {
1438 cond_calls.push((term_id, b, callee, success_block));
1439 }
1440 if let Some(callee) =
1441 foreign_entry(self, BlockId::new(b.func, failure_block), owner)
1442 {
1443 cond_calls.push((term_id, b, callee, failure_block));
1444 }
1445 }
1446 _ => {}
1447 }
1448 }
1449
1450 for (insn, callee) in tail_calls {
1451 self.replace_instruction_mnemonic(
1452 insn,
1453 Mnemonic::TailCall(TailCall {
1454 target: Callee::Real(callee),
1455 args: vec![],
1456 }),
1457 );
1458 }
1459 for (insn, owner_block, callee, arm_target) in cond_calls {
1460 let tramp = BasicBlock::make(self, owner_block.func).id;
1462 (self).builder(tramp).push_tail_call(callee);
1463 self.add_cfg_edge(owner_block, tramp);
1464
1465 if tail_set.contains(&owner_block) {
1473 tail.push(tramp);
1474 tail_set.insert(tramp);
1475 }
1476
1477 let Mnemonic::CBranch(mut cb) = self.instruction(insn).mnemonic().clone() else {
1478 continue;
1479 };
1480 let tramp_local = tramp.localize(insn.func);
1485 if cb.success_block == arm_target {
1486 cb.success_block = tramp_local;
1487 }
1488 if cb.failure_block == arm_target {
1489 cb.failure_block = tramp_local;
1490 }
1491 self.replace_instruction_mnemonic(insn, Mnemonic::CBranch(cb));
1492 }
1493
1494 let moved_owner = |candidate: BlockId| {
1503 if tail_set.contains(&candidate) {
1504 g
1505 } else {
1506 candidate.func
1507 }
1508 };
1509 let mut stale: HashSet<(FunctionId, EdgeId)> = HashSet::default();
1510 for &b in &tail {
1511 for edge in self.block(b).edges.iter().copied() {
1512 let &EdgeData { from, to } = self.edge(b.func, edge);
1513 let from = BlockId::new(b.func, from);
1514 let to = BlockId::new(b.func, to);
1515 let cross = moved_owner(from) != moved_owner(to);
1516 let touches_tail = tail_set.contains(&from) || tail_set.contains(&to);
1517 if cross && touches_tail {
1518 stale.insert((b.func, edge));
1519 }
1520 }
1521 }
1522 let mut stale: Vec<_> = stale.into_iter().collect();
1523 stale.sort_unstable();
1524 for (func, edge) in stale {
1525 self.remove_cfg_edge(func, edge);
1526 }
1527
1528 let moved = self.rehome_owned_blocks(addresses, g, &tail);
1532 self.bodies[g].set_root_id(Some(moved[&block].local));
1533
1534 self.recompute_instruction_addrs(g);
1536 for owner in prev_owners {
1537 if owner != g {
1538 self.recompute_instruction_addrs(owner);
1539 }
1540 }
1541
1542 g
1543 }
1544
1545 fn recompute_instruction_addrs(&mut self, func: FunctionId) {
1548 let blocks = FunctionBody::from_id(self, func).block_ids();
1549 let mut addrs = std::collections::BTreeSet::new();
1550 for b in blocks {
1551 for insn in BasicBlock::from_id(self, b).instructions() {
1552 if let Some(a) = insn.address() {
1553 addrs.insert(a);
1554 }
1555 }
1556 }
1557 self.bodies[func].instruction_addrs = addrs;
1558 }
1559
1560 fn rebuild_users(&mut self, func: FunctionId) {
1564 let live: Vec<InstructionId> = FunctionBody::from_id(self, func).instruction_ids();
1565 let users = &mut self.bodies[func].users;
1566 users.clear();
1567 for id in live {
1568 let args = self.bodies[func].insns[id.local].mnemonic().args();
1569 let users = &mut self.bodies[func].users;
1570 for arg in args {
1571 users.entry(arg).or_default().push(id.localize(func));
1572 }
1573 }
1574 }
1575
1576 pub fn assume_true(&mut self, prop: Proposition) -> bool {
1581 self.assume(prop, true)
1582 }
1583
1584 pub fn assume_false(&mut self, prop: Proposition) -> bool {
1586 self.assume(prop, false)
1587 }
1588
1589 fn assume(&mut self, prop: Proposition, value: bool) -> bool {
1590 match self.shared.values.truths.get(&prop) {
1591 Some(t) => t.value == value,
1592 None => {
1593 self.shared.values.truths.insert(
1594 prop,
1595 Truth {
1596 value,
1597 certainty: Certainty::Assumed,
1598 pass: PassName(pass_scope::current_pass()),
1599 },
1600 );
1601 true
1602 }
1603 }
1604 }
1605
1606 pub fn set_known(&mut self, prop: Proposition, value: bool) -> bool {
1614 let pass = PassName(pass_scope::current_pass());
1615 let novel = match self.shared.values.truths.get(&prop) {
1616 Some(prior) => {
1617 if prior.certainty == Certainty::Known && prior.value != value {
1622 self.shared
1623 .values
1624 .known_contradictions
1625 .push(KnownContradiction {
1626 prop,
1627 known: prior.value,
1628 proven: value,
1629 known_pass: prior.pass,
1630 proven_pass: pass,
1631 });
1632 return false;
1633 }
1634 if prior.certainty == Certainty::Assumed && prior.value != value {
1635 self.shared.values.violations.push(Violation {
1636 prop,
1637 assumed: prior.value,
1638 assuming_pass: prior.pass,
1639 asserting_pass: pass,
1640 });
1641 true
1642 } else {
1643 false
1644 }
1645 }
1646 None => true,
1647 };
1648 self.shared.values.truths.insert(
1649 prop,
1650 Truth {
1651 value,
1652 certainty: Certainty::Known,
1653 pass,
1654 },
1655 );
1656 novel
1657 }
1658
1659 pub fn seed_known(&mut self, prop: Proposition, value: bool, pass: PassName) {
1669 let prior = self.shared.values.truths.insert(
1670 prop,
1671 Truth {
1672 value,
1673 certainty: Certainty::Known,
1674 pass,
1675 },
1676 );
1677 debug_assert!(prior.is_none(), "seeding {prop:?} over an existing truth");
1678 }
1679
1680 pub fn truth(&self, prop: Proposition) -> Option<Truth> {
1682 self.shared.values.truths.get(&prop).copied()
1683 }
1684
1685 pub fn set_assumed_call_convention(
1691 &mut self,
1692 effect: Option<crate::assumption::AssumedCallEffect>,
1693 ) {
1694 self.shared.assumed_call_convention = effect;
1695 }
1696
1697 pub fn assumed_call_convention(&self) -> Option<&crate::assumption::AssumedCallEffect> {
1701 self.shared.assumed_call_convention.as_ref()
1702 }
1703
1704 pub fn known(&self, prop: Proposition) -> Option<bool> {
1706 self.truth(prop)
1707 .filter(|t| t.certainty == Certainty::Known)
1708 .map(|t| t.value)
1709 }
1710
1711 pub fn truths(&self) -> impl Iterator<Item = (Proposition, Truth)> + '_ {
1713 self.shared.values.truths.iter().map(|(&p, &t)| (p, t))
1714 }
1715
1716 pub fn known_facts(&self) -> impl Iterator<Item = (Proposition, bool, PassName)> + '_ {
1719 self.truths()
1720 .filter(|(_, t)| t.certainty == Certainty::Known)
1721 .map(|(p, t)| (p, t.value, t.pass))
1722 }
1723
1724 pub fn violations(&self) -> &[Violation] {
1727 &self.shared.values.violations
1728 }
1729
1730 pub fn known_contradictions(&self) -> &[KnownContradiction] {
1734 &self.shared.values.known_contradictions
1735 }
1736
1737 pub fn get_literal_value(&self, id: LiteralId) -> u64 {
1739 self.shared.values.literals[id].value
1740 }
1741
1742 pub fn get_insn(&self, id: InstructionId) -> InstructionRef<'str, '_> {
1744 InstructionRef::from_id(self, id)
1745 }
1746
1747 pub fn body(&self, fid: FunctionId) -> &crate::value::FunctionBody<'str> {
1757 &self.bodies[fid]
1758 }
1759
1760 pub fn body_mut(&mut self, fid: FunctionId) -> &mut crate::value::FunctionBody<'str> {
1762 &mut self.bodies[fid]
1763 }
1764
1765 pub fn push_insn(&mut self, func: FunctionId, insn: Instruction<'str>) -> InstructionId {
1780 let args = insn.mnemonic().args();
1781 let local = self.bodies[func].insns.push(insn);
1782 let id = InstructionId::new(func, local);
1783 for arg in args {
1784 self.bodies[func]
1785 .users
1786 .entry(arg)
1787 .or_default()
1788 .push(id.localize(func));
1789 }
1790 id
1791 }
1792
1793 pub fn instruction(&self, id: InstructionId) -> &Instruction<'str> {
1795 &self.bodies[id.func].insns[id.local]
1796 }
1797
1798 pub fn instruction_mut(&mut self, id: InstructionId) -> &mut Instruction<'str> {
1800 &mut self.bodies[id.func].insns[id.local]
1801 }
1802
1803 pub fn contains_instruction(&self, id: InstructionId) -> bool {
1805 Into::<usize>::into(id.func) < self.bodies.len()
1806 && self.bodies[id.func].insns.contains(id.local)
1807 }
1808
1809 pub fn block(&self, id: BlockId) -> &BasicBlock<'str> {
1811 &self.bodies[id.func].blocks[id.local]
1812 }
1813
1814 pub fn block_mut(&mut self, id: BlockId) -> &mut BasicBlock<'str> {
1816 &mut self.bodies[id.func].blocks[id.local]
1817 }
1818
1819 pub fn contains_block(&self, id: BlockId) -> bool {
1821 Into::<usize>::into(id.func) < self.bodies.len()
1822 && self.bodies[id.func].blocks.contains(id.local)
1823 }
1824
1825 pub fn block_param(&self, id: BlockParamId) -> &BlockParam<'str> {
1827 &self.bodies[id.func].params[id.local]
1828 }
1829
1830 pub fn block_param_mut(&mut self, id: BlockParamId) -> &mut BlockParam<'str> {
1832 &mut self.bodies[id.func].params[id.local]
1833 }
1834
1835 pub fn contains_block_param(&self, id: BlockParamId) -> bool {
1837 Into::<usize>::into(id.func) < self.bodies.len()
1838 && self.bodies[id.func].params.contains(id.local)
1839 }
1840
1841 pub fn edge(&self, func: FunctionId, id: EdgeId) -> &EdgeData {
1843 &self.bodies[func].edges[id]
1844 }
1845
1846 pub fn edge_mut(&mut self, func: FunctionId, id: EdgeId) -> &mut EdgeData {
1848 &mut self.bodies[func].edges[id]
1849 }
1850
1851 pub fn users_of(&self, value: ValueId) -> Vec<InstructionId> {
1856 match value.owning_function() {
1857 Some(func) => self.bodies[func].users_of(value),
1858 None => Vec::new(),
1859 }
1860 }
1861
1862 pub fn has_users(&self, value: ValueId) -> bool {
1864 match value.owning_function() {
1865 Some(func) => self.bodies[func].has_users(value),
1866 None => false,
1867 }
1868 }
1869
1870 pub fn push_block(&mut self, func: FunctionId, block: BasicBlock<'str>) -> BlockId {
1871 let local = self.bodies[func].blocks.push(block);
1872 let id = BlockId::new(func, local);
1873 self.bodies[func].roster.push(local);
1875 id
1876 }
1877
1878 pub fn push_block_param(&mut self, func: FunctionId, param: BlockParam<'str>) -> BlockParamId {
1879 let local = self.bodies[func].params.push(param);
1880 BlockParamId::new(func, local)
1881 }
1882
1883 pub fn push_edge(&mut self, func: FunctionId, edge: EdgeData) -> EdgeId {
1884 self.bodies[func].edges.push(edge)
1885 }
1886
1887 pub fn push_function(
1890 &mut self,
1891 interface: crate::value::function::FunctionInterface<'str>,
1892 body: FunctionBody<'str>,
1893 ) -> FunctionId {
1894 let expected = FunctionId::from(self.bodies.len());
1895 assert_eq!(
1896 body.id(),
1897 expected,
1898 "function body id does not match its registry slot"
1899 );
1900 let id = self.bodies.push(body);
1901 let iid = self.interfaces.push(interface);
1902 debug_assert_eq!(
1903 Into::<usize>::into(id),
1904 Into::<usize>::into(iid),
1905 "function body/interface registries drifted"
1906 );
1907 id
1908 }
1909
1910 pub fn get_register(&self, id: RegisterId) -> VarnodeRef<'str, '_> {
1913 Varnode::from_id(self, self.shared.registers[&id])
1914 }
1915
1916 pub fn get_const(&self, value: u64, size: usize) -> LiteralRef<'str, '_> {
1918 let type_id = self.shared.types.get_or_make_int(size);
1919 let id = self
1920 .shared
1921 .values
1922 .get_or_make_typed_literal(value, type_id, size);
1923 LiteralRef::from_id(self, id)
1924 }
1925
1926 pub fn get_bool_const(&self, value: bool) -> LiteralRef<'str, '_> {
1929 let type_id = self.shared.types.get_or_make_bool();
1930 let id = self
1931 .shared
1932 .values
1933 .get_or_make_typed_literal(u64::from(value), type_id, 1);
1934 LiteralRef::from_id(self, id)
1935 }
1936
1937 pub fn get_poison(&self, type_id: crate::types::TypeId) -> ValueId {
1941 ValueId::Poison(self.shared.values.push_poison(type_id))
1942 }
1943
1944 pub fn get_typed_const(
1950 &self,
1951 value: u64,
1952 type_id: crate::types::TypeId,
1953 ) -> LiteralRef<'str, '_> {
1954 let size = self.shared.types.size_of(type_id);
1955 let id = self
1956 .shared
1957 .values
1958 .get_or_make_typed_literal(value, type_id, size);
1959 LiteralRef::from_id(self, id)
1960 }
1961
1962 pub fn get_bytes(&self, data: Vec<u8>) -> crate::value::BytesRef<'str, '_> {
1970 let i8_ty = self.shared.types.get_or_make_int(1);
1971 let type_id = self.shared.types.get_or_make_array(i8_ty, data.len());
1972 self.get_typed_bytes(data, type_id)
1973 }
1974
1975 pub fn get_typed_bytes(
1981 &self,
1982 data: Vec<u8>,
1983 type_id: crate::types::TypeId,
1984 ) -> crate::value::BytesRef<'str, '_> {
1985 let id = self
1986 .shared
1987 .values
1988 .bytes
1989 .push(crate::value::Bytes { data, type_id });
1990 crate::value::BytesRef::from_id(self, id)
1991 }
1992
1993 pub fn type_of(&self, id: ValueId) -> crate::types::TypeId {
1998 match id {
1999 ValueId::Literal(lid) => self.shared.values.literals[lid].type_id,
2000 ValueId::Bytes(bid) => self.shared.values.bytes[bid].type_id,
2001 ValueId::Instruction(iid) => self.instruction(iid).type_id,
2002 ValueId::BlockParam(pid) => self.block_param(pid).type_id,
2003 ValueId::Varnode(vid) => {
2004 if let Some(&ty) = self.shared.values.varnode_types.get(&vid) {
2005 return ty;
2006 }
2007 let size = self.shared.values.varnodes[vid].size_bytes();
2008 self.shared.types.get_or_make_int(size)
2009 }
2010 ValueId::Temp(id) => self
2011 .shared
2012 .types
2013 .get_or_make_int(self.bodies[id.func].temps[id.local].size),
2014 ValueId::Poison(pid) => self.shared.values.poisons[pid].type_id,
2015 ValueId::BasicBlock(_) | ValueId::Function(_) => self.shared.types.get_or_make_int(0),
2018 }
2019 }
2020
2021 pub fn stored_type_of(&self, id: ValueId) -> Option<crate::types::TypeId> {
2027 match id {
2028 ValueId::Literal(lid) => Some(self.shared.values.literals[lid].type_id),
2029 ValueId::Bytes(bid) => Some(self.shared.values.bytes[bid].type_id),
2030 ValueId::Instruction(iid) => Some(self.instruction(iid).type_id),
2031 ValueId::BlockParam(pid) => Some(self.block_param(pid).type_id),
2032 ValueId::Varnode(vid) => self.shared.values.varnode_types.get(&vid).copied(),
2033 ValueId::Poison(pid) => Some(self.shared.values.poisons[pid].type_id),
2034 ValueId::Temp(_) => None,
2035 ValueId::BasicBlock(_) | ValueId::Function(_) => None,
2036 }
2037 }
2038
2039 pub fn set_varnode_type(&mut self, varnode: VarnodeId, type_id: crate::types::TypeId) {
2044 self.shared.values.varnode_types.insert(varnode, type_id);
2045 }
2046
2047 pub fn users(&self, value: impl Into<ValueId>) -> Vec<InstructionId> {
2055 self.users_of(value.into())
2056 }
2057
2058 pub fn users_across_functions(&self, value: impl Into<ValueId>) -> Vec<InstructionId> {
2063 let value = value.into();
2064 if value.owning_function().is_some() {
2065 self.users_of(value)
2066 } else {
2067 self.functions().flat_map(|f| f.users_of(value)).collect()
2068 }
2069 }
2070
2071 pub fn view(&self) -> ModuleView<'_, 'str> {
2080 ModuleView::new(self)
2081 }
2082 pub fn shr(&self) -> &Shared<'str> {
2087 &self.shared
2088 }
2089 pub fn function(&self, f: FunctionId) -> &FunctionBody<'str> {
2091 &self.bodies[f]
2092 }
2093 pub fn function_mut(&mut self, f: FunctionId) -> &mut FunctionBody<'str> {
2095 &mut self.bodies[f]
2096 }
2097
2098 pub fn block_ref(&self, id: BlockId) -> BlockRef<'str, '_, ModuleView<'_, 'str>> {
2100 self.view().block_ref(id)
2101 }
2102 pub fn insn_ref(&self, id: InstructionId) -> InstructionRef<'str, '_, ModuleView<'_, 'str>> {
2104 self.view().insn_ref(id)
2105 }
2106 pub fn param_ref(&self, id: BlockParamId) -> BlockParamRef<'str, '_, ModuleView<'_, 'str>> {
2108 self.view().param_ref(id)
2109 }
2110 pub fn function_ref(&self, id: FunctionId) -> FunctionRef<'str, '_, ModuleView<'_, 'str>> {
2112 self.view().function_ref(id)
2113 }
2114
2115 pub fn push_mnemonic(
2117 &mut self,
2118 func: FunctionId,
2119 mnemonic: Mnemonic,
2120 size: usize,
2121 ) -> InstructionId {
2122 let type_id = self.shared.types.get_or_make_int(size);
2123 self.push_insn(func, Instruction::new(type_id, mnemonic))
2124 }
2125
2126 pub fn push_mnemonic_with_type(
2129 &mut self,
2130 func: FunctionId,
2131 mnemonic: Mnemonic,
2132 type_id: crate::types::TypeId,
2133 ) -> InstructionId {
2134 self.push_insn(func, Instruction::new(type_id, mnemonic))
2135 }
2136
2137 pub fn make_block(&mut self, func: FunctionId) -> BlockId {
2140 self.push_block(func, BasicBlock::detached())
2141 }
2142
2143 pub fn register_local_name(
2146 &mut self,
2147 id: ValueId,
2148 name: Cow<'str, str>,
2149 old_name: Option<&str>,
2150 ) -> Result<()> {
2151 let existing = match id.name_scope_function() {
2152 Some(func) => self
2153 .function(func)
2154 .names
2155 .get(&name)
2156 .map(|id| id.qualify(func)),
2157 None => self.get_named(&name),
2158 };
2159 if let Some(existing) = existing {
2160 return if existing == id {
2161 Ok(())
2162 } else {
2163 Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
2164 };
2165 }
2166 match id.name_scope_function() {
2167 Some(func) => self
2168 .function_mut(func)
2169 .names
2170 .register(name, id.localize(func), old_name),
2171 None => self.update_name(name, id, old_name),
2172 }
2173 }
2174
2175 pub(crate) fn set_address_indexed(
2177 &mut self,
2178 addresses: &mut crate::address_index::AddressIndex,
2179 addr: u64,
2180 id: ValueId,
2181 ) -> crate::error::Result<()> {
2182 let target = match id {
2183 ValueId::Function(id) => crate::address_index::AddressTarget::Function(id),
2184 ValueId::BasicBlock(id) => crate::address_index::AddressTarget::Block(id),
2185 _ => unreachable!("only functions and blocks have module addresses"),
2186 };
2187 addresses.register(self, addr, target)
2188 }
2189
2190 pub fn update_name(
2193 &mut self,
2194 name: Cow<'str, str>,
2195 id: ValueId,
2196 old_name: Option<&str>,
2197 ) -> Result<()> {
2198 match id.name_scope_function() {
2199 Some(func) => self.bodies[func]
2200 .names
2201 .register(name, id.localize(func), old_name),
2202 None => self.shared.name_map.register(name, id, old_name),
2203 }
2204 }
2205
2206 pub fn get_named_in_scope(&self, id: ValueId, name: &str) -> Option<ValueId> {
2211 match id.name_scope_function() {
2212 Some(func) => self.bodies[func].names.get(name).map(|id| id.qualify(func)),
2213 None => self.shared.name_map.get(name),
2214 }
2215 }
2216
2217 pub fn get_named(&self, name: &str) -> Option<ValueId> {
2226 self.shared.name_map.get(name)
2227 }
2228
2229 pub fn get_unique_name(&mut self, name: Cow<'str, str>) -> Cow<'str, str> {
2234 self.shared.name_map.unique(name)
2235 }
2236
2237 pub fn get_unique_name_in(&mut self, func: FunctionId, name: Cow<'str, str>) -> Cow<'str, str> {
2241 self.bodies[func].names.unique(name)
2242 }
2243}
2244
2245#[derive(Clone, serde::Serialize, serde::Deserialize)]
2256pub struct NameTable<'str, Id = ValueId> {
2257 map: HashMap<Cow<'str, str>, Id>,
2259 #[serde(skip)]
2263 suffix_hint: HashMap<String, u32>,
2264}
2265
2266impl<Id> Default for NameTable<'_, Id> {
2267 fn default() -> Self {
2268 Self {
2269 map: HashMap::default(),
2270 suffix_hint: HashMap::default(),
2271 }
2272 }
2273}
2274
2275impl<'str, Id: Copy + Eq> NameTable<'str, Id> {
2276 pub(crate) fn entries(&self) -> impl Iterator<Item = (&str, Id)> + '_ {
2277 self.map.iter().map(|(name, &value)| (name.as_ref(), value))
2278 }
2279
2280 pub fn get(&self, name: &str) -> Option<Id> {
2282 self.map.get(name).copied()
2283 }
2284
2285 pub fn contains(&self, name: &str) -> bool {
2287 self.map.contains_key(name)
2288 }
2289
2290 pub fn register(&mut self, name: Cow<'str, str>, id: Id, old_name: Option<&str>) -> Result<()> {
2294 if let Some(old_name) = old_name {
2295 self.forget(old_name);
2296 }
2297 match self.map.insert(name.clone(), id) {
2298 Some(_) => Err(Error::spanless(ErrorTy::DuplicateName(name.to_string()))),
2299 None => Ok(()),
2300 }
2301 }
2302
2303 pub fn forget(&mut self, name: &str) {
2307 self.map.remove(name);
2308 if let Some((base, suffix)) = split_generated_suffix(name)
2309 && let Some(hint) = self.suffix_hint.get_mut(base)
2310 {
2311 *hint = (*hint).min(suffix);
2312 }
2313 }
2314
2315 pub fn unique(&mut self, name: Cow<'str, str>) -> Cow<'str, str> {
2320 use std::fmt::Write as _;
2321
2322 if !self.map.contains_key(&name) {
2323 return name;
2324 }
2325 let base: &str = &name;
2326 let mut suffix = self.suffix_hint.get(base).copied().unwrap_or(1).max(1);
2327 let mut unique_name = format!("{base}_{suffix}");
2328 while self.map.contains_key(unique_name.as_str()) {
2329 suffix += 1;
2330 unique_name.clear();
2331 let _ = write!(unique_name, "{base}_{suffix}");
2332 }
2333 self.suffix_hint.insert(base.to_string(), suffix);
2334 Cow::Owned(unique_name)
2335 }
2336}
2337
2338fn split_generated_suffix(name: &str) -> Option<(&str, u32)> {
2343 let (base, digits) = name.rsplit_once('_')?;
2344 if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
2345 return None;
2346 }
2347 Some((base, digits.parse().ok()?))
2348}
2349
2350fn remap_rehomed_type(
2353 ctx: &Context<'_>,
2354 type_id: crate::types::TypeId,
2355 target: FunctionId,
2356 temp_space_map: &HashMap<TempSpaceId, TempSpaceId>,
2357) -> crate::types::TypeId {
2358 let Some(MemorySpaceId::Temp(old_space)) = ctx.shared.types.space_of(type_id) else {
2359 return type_id;
2360 };
2361 let Some(&new_space) = temp_space_map.get(&old_space) else {
2362 debug_assert_eq!(
2363 old_space.func, target,
2364 "rehome: result type references unmapped foreign temporary space {old_space:?}"
2365 );
2366 return type_id;
2367 };
2368 ctx.shared.types.get_or_make_space_address(
2369 ctx.shared.types.size_of(type_id),
2370 MemorySpaceId::Temp(new_space),
2371 )
2372}
2373
2374fn remap_rehomed_memory_space(
2377 mnemonic: &mut Mnemonic,
2378 old_func: FunctionId,
2379 target: FunctionId,
2380 temp_space_map: &HashMap<TempSpaceId, TempSpaceId>,
2381) {
2382 let remap = |space: &mut LocalMemorySpaceId| {
2383 let LocalMemorySpaceId::Temp(old_local) = *space else {
2384 return;
2385 };
2386 let old = TempSpaceId::new(old_func, old_local);
2387 if let Some(&new) = temp_space_map.get(&old) {
2388 *space = LocalMemorySpaceId::Temp(new.local);
2389 } else {
2390 debug_assert_eq!(
2391 old_func, target,
2392 "rehome: mnemonic references unmapped foreign temporary space {old:?}"
2393 );
2394 }
2395 };
2396 match mnemonic {
2397 Mnemonic::Load(load) => remap(&mut load.space),
2398 Mnemonic::Store(store) => remap(&mut store.space),
2399 _ => {}
2400 }
2401}
2402
2403fn remap_symbolic_block_literal(
2429 literals: &crate::value::interner::LiteralInterner,
2430 arg: crate::value::LocalValueId,
2431 block_map: &HashMap<BlockId, BlockId>,
2432) -> Option<crate::value::LocalValueId> {
2433 use crate::value::literal::SymbolicRef;
2434
2435 let crate::value::LocalValueId::Literal(lid) = arg else {
2436 return None;
2437 };
2438 let literal = literals[lid].clone();
2439 let Some(SymbolicRef::Block(old_block)) = literal.symbolic else {
2440 return None;
2441 };
2442 let &new_block = block_map.get(&old_block)?;
2443 let new_lit = literals.push_literal(crate::value::literal::Literal {
2444 symbolic: Some(SymbolicRef::Block(new_block)),
2445 ..literal
2446 });
2447 Some(crate::value::LocalValueId::Literal(new_lit))
2448}
2449
2450fn remap_block_targets(
2451 mnemonic: &mut Mnemonic,
2452 old_func: FunctionId,
2453 new_func: FunctionId,
2454 block_map: &HashMap<BlockId, BlockId>,
2455) {
2456 let remap = |b: &mut crate::value::LocalBlockId| {
2457 if let Some(&new) = block_map.get(&BlockId::new(old_func, *b)) {
2458 *b = new.localize(new_func);
2459 }
2460 };
2461 match mnemonic {
2462 Mnemonic::Branch(branch) => remap(&mut branch.target),
2463 Mnemonic::CBranch(cbranch) => {
2464 remap(&mut cbranch.success_block);
2465 remap(&mut cbranch.failure_block);
2466 }
2467 _ => {}
2468 }
2469}
2470
2471impl Display for Context<'_> {
2472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2473 self.functions().try_for_each(|fun| fun.fmt(f))?;
2474
2475 self.blocks()
2476 .filter(|block| block.parent().is_none())
2477 .try_for_each(|block| block.fmt(f))
2478 }
2479}
2480
2481pub struct FunctionIter<'str, 'ctx> {
2482 ctx: &'ctx Context<'str>,
2483 inner: registry::Iter<'ctx, FunctionId, FunctionBody<'str>>,
2484}
2485
2486impl<'str, 'ctx> Iterator for FunctionIter<'str, 'ctx> {
2487 type Item = FunctionRef<'str, 'ctx>;
2488
2489 fn next(&mut self) -> Option<Self::Item> {
2490 let ctx = self.ctx;
2491 self.inner.next().map(|f| FunctionRef::from_id(ctx, f.id))
2492 }
2493}
2494
2495impl<'str, 'ctx> IntoIterator for &'ctx Context<'str> {
2496 type Item = FunctionRef<'str, 'ctx>;
2497 type IntoIter = FunctionIter<'str, 'ctx>;
2498
2499 fn into_iter(self) -> Self::IntoIter {
2500 self.iter()
2501 }
2502}
2503
2504#[cfg(test)]
2505mod tests {
2506 use super::*;
2507 use crate::value::{
2508 BasicBlock, FunctionBody, ValueId,
2509 insn::{Binary, Binop, Call, Callee, IntBinop, Load, Mnemonic},
2510 };
2511 use wazabin_qcode_macro::qcode;
2512
2513 fn make_fn_with_blocks(ctx: &mut Context<'static>, name: &'static str, n: usize) -> FunctionId {
2514 let f = FunctionBody::make(ctx, name.into()).unwrap().id;
2516 for _ in 0..n {
2517 BasicBlock::make(ctx, f);
2518 }
2519 f
2520 }
2521
2522 #[test]
2523 #[should_panic(expected = "cannot reuse a block stored in another function arena")]
2524 fn get_or_make_block_rejects_foreign_storage_at_address() {
2525 let mut ctx = Context::new();
2526 let a = FunctionBody::make(&mut ctx, "address_owner".into())
2527 .unwrap()
2528 .id;
2529 let b = FunctionBody::make(&mut ctx, "address_requester".into())
2530 .unwrap()
2531 .id;
2532 BasicBlock::make(&mut ctx, a).with_address(0x1000);
2533
2534 ctx.get_or_make_block(0x1000, b);
2535 }
2536
2537 #[test]
2538 #[should_panic(expected = "cannot create a block at an address owned by another function")]
2539 fn get_or_make_block_rejects_foreign_function_address_without_root() {
2540 let mut ctx = Context::new();
2541 FunctionBody::make_at_addr(&mut ctx, 0x1000, None);
2542 let requester = FunctionBody::make(&mut ctx, "address_requester".into())
2543 .unwrap()
2544 .id;
2545
2546 ctx.get_or_make_block(0x1000, requester);
2547 }
2548
2549 #[test]
2550 fn functions_iter_yields_all_functions() {
2551 let mut ctx = Context::new();
2552 let alpha = make_fn_with_blocks(&mut ctx, "alpha", 1);
2553 let beta = make_fn_with_blocks(&mut ctx, "beta", 1);
2554
2555 let names: Vec<_> = ctx.functions().map(|f| f.name().to_string()).collect();
2556 assert!(names.contains(&"alpha".to_string()));
2557 assert!(names.contains(&"beta".to_string()));
2558 assert_eq!(names.len(), 2);
2559 assert_eq!(ctx.function_ids(), vec![alpha, beta]);
2560 assert_eq!(ctx.function_ids().len(), ctx.interfaces.len());
2561 }
2562
2563 #[test]
2564 fn body_view_reads_match_module_reads() {
2565 use crate::value::{BodyView, FunctionId, FunctionRef, ModuleView, QCodeView};
2566
2567 let mut ctx = Context::new();
2568 qcode!(
2569 ctx,
2570 "
2571 fn foo:
2572 <bb1>
2573 if i8 1 goto <bb2> else goto <bb3>;
2574 <bb2>
2575 goto <bb3>;
2576 <bb3>
2577 return at 0;
2578 "
2579 );
2580 let fid = FunctionBody::from_name(&ctx, "foo").unwrap().id();
2581 let fid = ValueId::as_function(fid).unwrap();
2582
2583 type Snap = (String, Vec<(String, Vec<String>, Vec<String>, usize)>);
2588 fn snapshot<'a, 'str: 'a>(view: impl QCodeView<'a, 'str>, fid: FunctionId) -> Snap {
2589 let f = FunctionRef::new(view, fid);
2590 let blocks = f
2591 .blocks()
2592 .map(|b| {
2593 let name = b.name().unwrap_or("?").to_string();
2594 let mut succ: Vec<String> = b
2595 .successors()
2596 .map(|(_, s)| BlockRef::new(view, s).name().unwrap_or("?").to_string())
2597 .collect();
2598 succ.sort();
2599 let ops: Vec<String> =
2600 b.instructions().map(|i| i.opcode().to_string()).collect();
2601 (name, succ, ops, b.num_params())
2602 })
2603 .collect();
2604 (f.name().to_string(), blocks)
2605 }
2606
2607 let module_snap = snapshot(ModuleView::new(&ctx), fid);
2608 assert!(!module_snap.1.is_empty(), "sanity: foo has blocks");
2609
2610 let checked = BodyView::new(&ctx.bodies[fid], &ctx.shared, &ctx.interfaces);
2613 let checked_snap = snapshot(checked, fid);
2614 assert_eq!(
2615 module_snap, checked_snap,
2616 "reads through BodyView must match the module reads"
2617 );
2618 }
2619
2620 #[test]
2621 fn body_mut_mut_matches_module_mut() {
2622 use crate::value::{
2623 BlockParam, FunctionId, FunctionRef, InstructionId, Renameable,
2624 block::BlockId,
2625 block_param::BlockParamId,
2626 util::{base_ref::BaseRef, body_mut::BodyMut},
2627 };
2628
2629 fn build(mut ctx: &mut Context<'static>) -> (FunctionId, BlockId, BlockId, InstructionId) {
2630 qcode!(
2631 ctx,
2632 "
2633 varnode i64 x;
2634 fn foo:
2635 <entry>
2636 %a = load(x:8, &x);
2637 %b = load(x:8, &x);
2638 goto <bb1>;
2639 <bb1>
2640 return at %a;
2641 "
2642 );
2643 let fid = foo;
2644 let entry = FunctionRef::from_id(ctx, fid).root().unwrap().id;
2645 let bb1 = FunctionRef::from_id(ctx, fid)
2646 .blocks()
2647 .map(|b| b.id)
2648 .find(|&b| b != entry)
2649 .unwrap();
2650 let insns = BasicBlock::from_id(ctx, entry).instruction_ids();
2651 (fid, entry, bb1, insns[0])
2652 }
2653
2654 fn add_param(ctx: &mut Context<'static>, bb1: BlockId) -> BlockParamId {
2656 BasicBlock::from_id_mut(ctx, bb1).push_param(8).id
2657 }
2658
2659 type MSnap = Vec<(String, Option<String>, Vec<usize>, Vec<String>, Vec<String>)>;
2662 fn snap(ctx: &Context, fid: FunctionId) -> MSnap {
2663 FunctionRef::from_id(ctx, fid)
2664 .blocks()
2665 .map(|b| {
2666 let name = b.name().unwrap_or("?").to_string();
2667 let comment = b.comment().map(str::to_string);
2668 let params: Vec<usize> = b.params().map(|p| p.size()).collect();
2669 let ops: Vec<String> =
2670 b.instructions().map(|i| i.opcode().to_string()).collect();
2671 let mut succ: Vec<String> = b
2672 .successors()
2673 .map(|(_, s)| {
2674 BasicBlock::from_id(ctx, s)
2675 .name()
2676 .unwrap_or("?")
2677 .to_string()
2678 })
2679 .collect();
2680 succ.sort();
2681 (name, comment, params, ops, succ)
2682 })
2683 .collect()
2684 }
2685
2686 let mut ctx_a = Context::new();
2688 let (fid, entry, bb1, a) = build(&mut ctx_a);
2689 let param = add_param(&mut ctx_a, bb1);
2690 let b = BasicBlock::from_id(&ctx_a, entry).instruction_ids()[1];
2691 BasicBlock::from_id_mut(&mut ctx_a, entry).set_comment(Some("c".into()));
2692 BasicBlock::from_id_mut(&mut ctx_a, entry)
2693 .rename("start".into())
2694 .unwrap();
2695 let e = ctx_a.add_cfg_edge(entry, bb1);
2696 ctx_a.remove_cfg_edge(entry.func, e);
2697 ctx_a.replace_instruction(a, ValueId::Instruction(b));
2698 BlockParam::from_id_mut(&mut ctx_a, param).set_size(4);
2699 let snap_a = snap(&ctx_a, fid);
2700
2701 let mut ctx_b = Context::new();
2703 let (fid_b, entry_b, bb1_b, a_b) = build(&mut ctx_b);
2704 let param_b = add_param(&mut ctx_b, bb1_b);
2705 let b_b = BasicBlock::from_id(&ctx_b, entry_b).instruction_ids()[1];
2706
2707 {
2708 let mut host = BodyMut::new(&mut ctx_b.bodies[fid_b], &ctx_b.shared, &ctx_b.interfaces);
2709 let mut r = BaseRef::new(host.reborrow(), entry_b);
2710 r.set_comment(Some("c".into()));
2711 let mut r = BaseRef::new(host.reborrow(), entry_b);
2712 r.rename("start".into()).unwrap();
2713 let e = host.add_cfg_edge(entry_b, bb1_b);
2714 host.remove_cfg_edge(e);
2715 host.replace_instruction(a_b, ValueId::Instruction(b_b));
2716 let mut r = BaseRef::new(host.reborrow(), param_b);
2717 r.set_size(4);
2718 }
2719 let snap_b = snap(&ctx_b, fid_b);
2720
2721 assert_eq!(
2722 snap_a, snap_b,
2723 "mutations through a pass-scoped host must match the module-path mutations"
2724 );
2725 }
2726
2727 #[test]
2728 fn into_iterator_for_context_matches_functions() {
2729 let mut ctx = Context::new();
2730 make_fn_with_blocks(&mut ctx, "f1", 1);
2731 make_fn_with_blocks(&mut ctx, "f2", 1);
2732
2733 let via_method: Vec<_> = ctx.functions().map(|f| f.id()).collect();
2734 let via_into: Vec<_> = (&ctx).into_iter().map(|f| f.id()).collect();
2735 assert_eq!(via_method, via_into);
2736 }
2737
2738 #[test]
2739 fn blocks_iter_yields_all_blocks() {
2740 let mut ctx = Context::new();
2741 make_fn_with_blocks(&mut ctx, "g", 3);
2742
2743 let count = ctx.blocks().count();
2744 assert_eq!(count, 3);
2745 }
2746
2747 #[test]
2748 fn instructions_iter_yields_all_instructions() {
2749 let mut ctx = Context::new();
2750
2751 qcode!(
2752 ctx,
2753 "
2754 varnode i64 ptr;
2755
2756 <block>
2757 store(ptr:8, &ptr <- i64 0x1234);
2758 return at ptr;
2759 "
2760 );
2761
2762 let count = ctx.instructions().count();
2763 assert!(count >= 1, "expected at least one instruction, got {count}");
2764 }
2765
2766 #[test]
2767 fn move_insn_before_preserves_id_and_supports_arbitrary_anchors() {
2768 let mut ctx = Context::new();
2769 qcode!(
2770 ctx,
2771 "
2772 fn f:
2773 <source>
2774 %a = i64 0x1 + i64 0x2;
2775 %free = i64 0x5 + i64 0x6;
2776 goto <target>;
2777 <target>
2778 %b = i64 0x3 + i64 0x4;
2779 %consumer = %a + %b;
2780 return %consumer;
2781 "
2782 );
2783
2784 assert!(ctx.users(a).contains(&consumer));
2785 ctx.move_insn_before(a, b);
2786
2787 assert!(ctx.contains_instruction(a), "moving keeps the ID live");
2788 assert_eq!(ctx.get_insn(a).parent().map(|block| block.id), Some(target));
2789 assert!(
2790 !BasicBlock::from_id(&ctx, source)
2791 .instruction_ids()
2792 .contains(&a)
2793 );
2794 assert_eq!(
2795 BasicBlock::from_id(&ctx, target).instruction_ids()[..3],
2796 [a, b, consumer]
2797 );
2798 assert!(
2799 ctx.users(a).contains(&consumer),
2800 "moving preserves use-map entries"
2801 );
2802
2803 ctx.move_insn_before(b, a);
2805 assert_eq!(
2806 BasicBlock::from_id(&ctx, target).instruction_ids()[..3],
2807 [b, a, consumer]
2808 );
2809
2810 let return_id = *BasicBlock::from_id(&ctx, target)
2812 .instruction_ids()
2813 .last()
2814 .unwrap();
2815 ctx.move_insn_before(free, return_id);
2816 assert_eq!(
2817 BasicBlock::from_id(&ctx, target).instruction_ids()[..4],
2818 [b, a, consumer, free]
2819 );
2820 }
2821
2822 #[test]
2823 fn remove_instruction_removes_from_block() {
2824 let mut ctx = Context::new();
2825 qcode!(
2826 ctx,
2827 "
2828 varnode i64 x;
2829 <block>
2830 %a = load(x:8, &x);
2831 %b = load(x:8, &x);
2832 return at %a;
2833 "
2834 );
2835 let block_ref = BasicBlock::from_id(&ctx, block);
2836 let ids = block_ref.instruction_ids();
2837 let load_a = ids[0];
2838 let original_len = ids.len();
2839
2840 ctx.remove_instruction(load_a);
2841
2842 let remaining = BasicBlock::from_id(&ctx, block).instruction_ids();
2843 assert_eq!(remaining.len(), original_len - 1);
2844 assert!(!remaining.contains(&load_a));
2845 }
2846
2847 #[test]
2848 fn remove_instruction_drops_payload() {
2849 let mut ctx = Context::new();
2850 qcode!(
2851 ctx,
2852 "
2853 varnode i64 x;
2854 <block>
2855 %a = load(x:8, &x);
2856 return at %a;
2857 "
2858 );
2859 let load_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
2860
2861 ctx.remove_instruction(load_id);
2862
2863 assert!(!ctx.contains_instruction(load_id));
2864 }
2865
2866 #[test]
2867 fn remove_instruction_frees_name() {
2868 let mut ctx = Context::new();
2869 qcode!(
2870 ctx,
2871 "
2872 varnode i64 x;
2873 <block>
2874 %a = load(x:8, &x);
2875 return at %a;
2876 "
2877 );
2878 let load_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
2879 assert!(
2881 ctx.get_named_in_scope(load_id.into(), "a").is_some(),
2882 "name should be in map before removal"
2883 );
2884
2885 ctx.remove_instruction(load_id);
2886
2887 assert!(
2888 ctx.get_named_in_scope(load_id.into(), "a").is_none(),
2889 "name should be gone after removal"
2890 );
2891 assert!(!ctx.contains_instruction(load_id));
2892 }
2893
2894 #[test]
2895 fn remove_instruction_frees_name_for_reuse() {
2896 let mut ctx = Context::new();
2897 qcode!(
2898 ctx,
2899 "
2900 varnode i64 x;
2901 <block>
2902 %a = load(x:8, &x);
2903 return at %a;
2904 "
2905 );
2906 let load_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
2907
2908 ctx.remove_instruction(load_id);
2909
2910 qcode!(
2912 ctx,
2913 "
2914 varnode i64 y;
2915 <block2>
2916 %a = load(y:8, &y);
2917 return at %a;
2918 "
2919 );
2920 let a2 = BasicBlock::from_id(&ctx, block2).instruction_ids()[0];
2921 assert!(
2922 ctx.get_named_in_scope(a2.into(), "a").is_some(),
2923 "name should be reusable after removal"
2924 );
2925 }
2926
2927 #[test]
2928 fn remove_instruction_updates_users_map() {
2929 let mut ctx = Context::new();
2930 qcode!(
2931 ctx,
2932 "
2933 varnode i64 x;
2934 <block>
2935 %a = load(x:8, &x);
2936 %b = %a + i64 1;
2937 return at %b;
2938 "
2939 );
2940 let ids = BasicBlock::from_id(&ctx, block).instruction_ids();
2941 let load_id = ids[0];
2942 let add_id = ids[1];
2943
2944 assert!(
2945 ctx.users(load_id).contains(&add_id),
2946 "add should be a user of load before removal"
2947 );
2948
2949 ctx.remove_instruction(add_id);
2950
2951 assert!(
2952 ctx.users(load_id).is_empty(),
2953 "load should have no users after add is removed"
2954 );
2955 }
2956
2957 #[test]
2958 fn removed_instruction_is_absent_and_not_iterated() {
2959 let mut ctx = Context::new();
2964 qcode!(
2965 ctx,
2966 "
2967 varnode i64 x;
2968 <block>
2969 %a = load(x:8, &x);
2970 %dead = %a + i64 1;
2971 return at i64 0;
2972 "
2973 );
2974 let ids = BasicBlock::from_id(&ctx, block).instruction_ids();
2975 let dead_id = ids[1]; assert!(
2978 ctx.instructions().any(|i| i.id == dead_id),
2979 "the instruction is iterated while live"
2980 );
2981
2982 ctx.remove_instruction(dead_id);
2983
2984 assert!(!ctx.contains_instruction(dead_id));
2985 assert!(
2986 !ctx.instructions().any(|i| i.id == dead_id),
2987 "a deleted instruction must not be yielded by ctx.instructions()"
2988 );
2989 }
2990
2991 #[test]
2992 fn replace_instruction_mnemonic_rewrites_callind_users() {
2993 let mut ctx = Context::new();
2994 qcode!(
2995 ctx,
2996 "
2997 varnode i64 ptr;
2998 <block>
2999 call [ptr];
3000 "
3001 );
3002 let call_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
3003 let ptr = match ctx.get_insn(call_id).mnemonic() {
3004 Mnemonic::CallInd(call) => call.ptr.qualify(call_id.func),
3005 other => panic!("expected CallInd, got {other:?}"),
3006 };
3007 assert_eq!(ctx.users_across_functions(ptr), vec![call_id]);
3009
3010 let target = FunctionBody::make(&mut ctx, "target".into()).unwrap().id;
3011 ctx.replace_instruction_mnemonic(
3012 call_id,
3013 Mnemonic::Call(Call {
3014 target: Callee::Real(target),
3015 args: vec![],
3016 clobbers: vec![],
3017 tag: Default::default(),
3018 }),
3019 );
3020
3021 assert!(
3022 ctx.users_across_functions(ptr).is_empty(),
3023 "old indirect pointer should no longer list the rewritten call"
3024 );
3025 assert!(matches!(
3026 ctx.get_insn(call_id).mnemonic(),
3027 Mnemonic::Call(Call {
3028 target: actual,
3029 args,
3030 ..
3031 }) if *actual == Callee::Real(target) && args.is_empty()
3032 ));
3033 }
3034
3035 #[test]
3036 fn users_across_functions_keeps_ssa_users_in_the_owning_function() {
3037 let mut ctx = Context::new();
3038 qcode!(
3039 ctx,
3040 "
3041 fn f:
3042 <f_entry>
3043 %fx = i64 1 + i64 2;
3044 %fuse = %fx + i64 3;
3045 return at %fuse;
3046 fn g:
3047 <g_entry>
3048 %gx = i64 4 + i64 5;
3049 %guse = %gx + i64 6;
3050 return at %guse;
3051 "
3052 );
3053 let f_ids = BasicBlock::from_id(&ctx, f_entry).instruction_ids();
3054 let g_ids = BasicBlock::from_id(&ctx, g_entry).instruction_ids();
3055 assert_eq!(
3056 f_ids[0].local, g_ids[0].local,
3057 "precondition: arena-local ids collide"
3058 );
3059 assert_eq!(
3060 ctx.users_across_functions(ValueId::Instruction(f_ids[0])),
3061 vec![f_ids[1]],
3062 "an SSA query must not pick up the same local key from another function"
3063 );
3064 }
3065
3066 #[test]
3067 fn replace_instruction_mnemonic_moves_operand_users() {
3068 let mut ctx = Context::new();
3069 qcode!(
3070 ctx,
3071 "
3072 varnode i64 x;
3073 varnode i64 y;
3074 <block>
3075 %a = load(x:8, x);
3076 return at %a;
3077 "
3078 );
3079 let load_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
3080 let old_ptr = ValueId::Varnode(x);
3081 let new_ptr = ValueId::Varnode(y);
3082 assert_eq!(ctx.users_across_functions(old_ptr), vec![load_id]);
3084 assert!(ctx.users_across_functions(new_ptr).is_empty());
3085
3086 ctx.replace_instruction_mnemonic(
3087 load_id,
3088 Mnemonic::Load(Load {
3089 space: ctx.shared.default_space.into(),
3090 ptr: new_ptr.localize(load_id.func),
3091 size: 8,
3092 }),
3093 );
3094
3095 assert!(ctx.users_across_functions(old_ptr).is_empty());
3096 assert_eq!(ctx.users_across_functions(new_ptr), vec![load_id]);
3097 }
3098
3099 #[test]
3100 fn replace_instruction_mnemonic_tracks_repeated_operands() {
3101 let mut ctx = Context::new();
3102 qcode!(
3103 ctx,
3104 "
3105 varnode i64 x;
3106 varnode i64 y;
3107 <block>
3108 %a = load(x:8, x);
3109 return at %a;
3110 "
3111 );
3112 let load_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
3113 let old_ptr = ValueId::Varnode(x);
3114 let new_arg = ValueId::Varnode(y);
3115
3116 ctx.replace_instruction_mnemonic(
3117 load_id,
3118 Mnemonic::Binop(Binary {
3119 op: Binop::Int(IntBinop::Add),
3120 lhs: new_arg.localize(load_id.func),
3121 rhs: new_arg.localize(load_id.func),
3122 }),
3123 );
3124
3125 assert!(ctx.users_across_functions(old_ptr).is_empty());
3126 assert_eq!(
3127 ctx.users_across_functions(new_arg),
3128 vec![load_id, load_id],
3129 "a mnemonic using the same operand twice should record both uses"
3130 );
3131 }
3132
3133 #[test]
3134 fn remove_instruction_unparented_noop() {
3135 let mut ctx = Context::new();
3136 qcode!(
3137 ctx,
3138 "
3139 varnode i64 x;
3140 <block>
3141 %a = load(x:8, &x);
3142 return at %a;
3143 "
3144 );
3145 let load_id = BasicBlock::from_id(&ctx, block).instruction_ids()[0];
3146
3147 ctx.instruction_mut(load_id).parent = None;
3150
3151 ctx.remove_instruction(load_id);
3153
3154 assert!(ctx.get_named("a").is_none());
3155 }
3156
3157 #[test]
3158 fn add_cfg_edge_returns_id_and_remove_unlinks_both_blocks() {
3159 let mut ctx = Context::new();
3160 let f = ctx.anon_function();
3162 let a = BasicBlock::make(&mut ctx, f).id;
3163 let b = BasicBlock::make(&mut ctx, f).id;
3164 let c = BasicBlock::make(&mut ctx, f).id;
3165
3166 let edge = ctx.add_cfg_edge(a, b);
3167 let surviving_edge = ctx.add_cfg_edge(b, c);
3168 assert_eq!(
3169 BasicBlock::from_id(&ctx, a)
3170 .successors()
3171 .collect::<Vec<_>>(),
3172 vec![(edge, b)]
3173 );
3174 assert_eq!(
3175 BasicBlock::from_id(&ctx, b)
3176 .predecessors()
3177 .collect::<Vec<_>>(),
3178 vec![(edge, a)]
3179 );
3180
3181 ctx.remove_cfg_edge(a.func, edge);
3182 assert!(BasicBlock::from_id(&ctx, a).successors().next().is_none());
3183 assert!(BasicBlock::from_id(&ctx, b).predecessors().next().is_none());
3184 assert!(!ctx.bodies[a.func].edges.contains(edge));
3185 let surviving = ctx.edge(a.func, surviving_edge);
3186 assert_eq!(
3187 surviving.from, b.local,
3188 "swap removal must preserve the source"
3189 );
3190 assert_eq!(
3191 surviving.to, c.local,
3192 "swap removal must preserve the target"
3193 );
3194 assert_eq!(ctx.bodies[a.func].edges.len(), 1);
3195
3196 let self_edge = ctx.add_cfg_edge(a, a);
3197 ctx.remove_cfg_edge(a.func, self_edge);
3198 assert!(!ctx.bodies[a.func].edges.contains(self_edge));
3199 assert!(ctx.block(a).edges.is_empty());
3200
3201 let parallel_a = ctx.add_cfg_edge(a, b);
3202 let parallel_b = ctx.add_cfg_edge(a, b);
3203 ctx.remove_cfg_edge(a.func, parallel_a);
3204 assert!(!ctx.bodies[a.func].edges.contains(parallel_a));
3205 assert!(ctx.bodies[a.func].edges.contains(parallel_b));
3206 assert_eq!(
3207 BasicBlock::from_id(&ctx, a)
3208 .successors()
3209 .collect::<Vec<_>>(),
3210 vec![(parallel_b, b)],
3211 );
3212 }
3213
3214 #[test]
3215 fn truth_map_tracks_four_states_and_conflicts() {
3216 let mut ctx = Context::new();
3217 let callee = FunctionBody::make(&mut ctx, "callee".into()).unwrap().id;
3218 let prop = Proposition::FunctionReturns(callee);
3219
3220 assert!(ctx.assume_true(prop));
3222 assert!(ctx.assume_true(prop));
3223 assert!(!ctx.assume_false(prop));
3224 assert_eq!(ctx.known(prop), None, "assumed is not known");
3225
3226 let snapshot = ctx.clone();
3228
3229 let scope = pass_scope::enter("verifier");
3232 assert!(ctx.set_known(prop, false), "overturning is novel");
3233 drop(scope);
3234 assert_eq!(ctx.known(prop), Some(false));
3235 let [v] = ctx.violations() else {
3236 panic!("expected one violation")
3237 };
3238 assert_eq!(v.prop, prop);
3239 assert!(v.assumed);
3240 assert_eq!(v.asserting_pass, "verifier");
3241
3242 assert!(!ctx.set_known(prop, false));
3244
3245 assert!(snapshot.violations().is_empty());
3247 assert_eq!(snapshot.known(prop), None);
3248
3249 assert!(!ctx.assume_true(prop));
3251 assert!(ctx.assume_false(prop));
3252 }
3253
3254 #[test]
3255 fn seeded_facts_are_not_novel() {
3256 let mut ctx = Context::new();
3257 let callee = FunctionBody::make(&mut ctx, "exit".into()).unwrap().id;
3258 let prop = Proposition::FunctionReturns(callee);
3259
3260 ctx.seed_known(prop, false, PassName("seed"));
3261 assert_eq!(ctx.known(prop), Some(false));
3262 assert!(!ctx.assume_true(prop), "seeded fact blocks opposite assume");
3263 assert!(
3264 !ctx.set_known(prop, false),
3265 "re-proving a seed is not novel"
3266 );
3267 assert!(ctx.violations().is_empty());
3268 }
3269
3270 #[test]
3271 fn discovered_code_records_and_survives_round_trip() {
3272 let mut ctx = Context::new();
3273 ctx.discover_code(0x1000, 0x10f0, 0x1100);
3274 ctx.discover_code(0x1000, 0x10f0, 0x1200);
3275 ctx.discover_code(0x1000, 0x10f0, 0x1100); let targets: Vec<u64> = ctx.discoveries().map(|d| d.target).collect();
3278 assert_eq!(targets, vec![0x1100, 0x1200]);
3279
3280 let config = bincode::config::standard();
3281 let bytes = bincode::serde::encode_to_vec(&ctx, config).expect("encode");
3282 let (restored, _): (Context<'static>, usize) =
3283 bincode::serde::decode_from_slice(&bytes, config).expect("decode");
3284 assert_eq!(
3285 restored.discoveries().map(|d| d.target).collect::<Vec<_>>(),
3286 targets
3287 );
3288 }
3289
3290 #[test]
3291 fn assume_executable_narrows_once_protections_known() {
3292 let mut ctx = Context::new();
3293 let mut image = crate::memory_image::MemoryImage::default();
3294 image.add_segment(0x1000, vec![0u8; 4], true, false); image.add_segment(0x2000, vec![0u8; 4], false, true); let binary: &dyn wazabin_binary::BinaryFormat = ℑ
3297
3298 assert!(ctx.assume_executable(binary, 0x1000));
3301 assert!(ctx.assume_executable(binary, 0x2000));
3302 assert!(ctx.assume_executable(binary, 0x9999));
3303
3304 ctx.mark_protections_known();
3305 assert!(
3306 ctx.assume_executable(binary, 0x1000),
3307 "code region stays liftable"
3308 );
3309 assert!(
3310 !ctx.assume_executable(binary, 0x2000),
3311 "data region is skipped once protections are known"
3312 );
3313 assert!(
3314 !ctx.assume_executable(binary, 0x9999),
3315 "unmapped is skipped once known"
3316 );
3317 assert_eq!(
3319 ctx.known(Proposition::ExecutableMemory {
3320 start: 0x2000,
3321 end: 0x2004,
3322 }),
3323 Some(false),
3324 );
3325 }
3326
3327 #[test]
3328 fn assume_executable_honors_region_override() {
3329 let mut ctx = Context::new();
3330 let mut image = crate::memory_image::MemoryImage::default();
3331 image.add_segment(0x1000, vec![0u8; 4], true, false); image.add_segment(0x2000, vec![0u8; 4], false, true); let binary: &dyn wazabin_binary::BinaryFormat = ℑ
3334 ctx.mark_protections_known();
3335
3336 ctx.seed_known(
3338 Proposition::ExecutableMemory {
3339 start: 0x2000,
3340 end: 0x2004,
3341 },
3342 true,
3343 PassName("override"),
3344 );
3345 ctx.seed_known(
3346 Proposition::ExecutableMemory {
3347 start: 0x1000,
3348 end: 0x1004,
3349 },
3350 false,
3351 PassName("override"),
3352 );
3353
3354 assert!(
3355 ctx.assume_executable(binary, 0x2000),
3356 "override wins over the non-executable segment flag"
3357 );
3358 assert!(
3359 !ctx.assume_executable(binary, 0x1000),
3360 "override wins over the executable segment flag"
3361 );
3362 }
3363
3364 #[test]
3365 fn context_survives_bincode_round_trip() {
3366 let mut ctx = Context::new();
3367 qcode!(
3368 ctx,
3369 "
3370 varnode i64 ptr;
3371 <block>
3372 %a = load(ptr:8, &ptr);
3373 %b = %a + i64 0x10;
3374 store(ptr:8, &ptr <- i64 0x1234);
3375 return at %b;
3376 "
3377 );
3378
3379 let some_space = ctx.get_or_make_named_space("scratch");
3381 let sa = ctx.shared.types.get_or_make_space_address(8, some_space);
3382 let sa_size = ctx.shared.types.size_of(sa);
3383
3384 let blocks_before = ctx.block_ids().len();
3385 let insns_before = ctx.instruction_ids().len();
3386 let funcs_before = ctx.function_ids().len();
3387
3388 let config = bincode::config::standard();
3389 let bytes = bincode::serde::encode_to_vec(&ctx, config).expect("encode");
3390 let (restored, _): (Context<'static>, usize) =
3391 bincode::serde::decode_from_slice(&bytes, config).expect("decode");
3392
3393 assert_eq!(restored.block_ids().len(), blocks_before);
3394 assert_eq!(restored.instruction_ids().len(), insns_before);
3395 assert_eq!(restored.function_ids().len(), funcs_before);
3396 for function_id in restored.function_ids() {
3397 assert_eq!(restored.bodies[function_id].id(), function_id);
3398 }
3399 assert_eq!(restored.shared.types.size_of(sa), sa_size);
3401 assert_eq!(
3402 restored.shared.types.space_of(sa),
3403 Some(crate::space::MemorySpaceId::Shared(some_space))
3404 );
3405 }
3406
3407 #[test]
3408 fn compact_edge_arena_preserves_ids_across_round_trip() {
3409 let mut ctx = Context::new();
3410 let function = ctx.anon_function();
3411 let a = BasicBlock::make(&mut ctx, function).id;
3412 let b = BasicBlock::make(&mut ctx, function).id;
3413 let c = BasicBlock::make(&mut ctx, function).id;
3414 let d = BasicBlock::make(&mut ctx, function).id;
3415 let first = ctx.add_cfg_edge(a, b);
3416 let removed = ctx.add_cfg_edge(b, c);
3417 let last = ctx.add_cfg_edge(c, d);
3418 ctx.remove_cfg_edge(function, removed);
3419
3420 let physical_order: Vec<_> = ctx.bodies[function]
3421 .edges
3422 .iter()
3423 .map(|edge| edge.id)
3424 .collect();
3425 assert_eq!(physical_order, vec![first, last]);
3426
3427 let config = bincode::config::standard();
3428 let bytes = bincode::serde::encode_to_vec(&ctx, config).expect("encode");
3429 let (mut restored, _): (Context<'static>, usize) =
3430 bincode::serde::decode_from_slice(&bytes, config).expect("decode");
3431
3432 assert!(!restored.bodies[function].edges.contains(removed));
3433 assert_eq!(
3434 restored.bodies[function]
3435 .edges
3436 .iter()
3437 .map(|edge| edge.id)
3438 .collect::<Vec<_>>(),
3439 physical_order,
3440 );
3441 assert_eq!(restored.edge(function, first).to, b.local);
3442 assert_eq!(restored.edge(function, last).from, c.local);
3443
3444 let fresh = restored.add_cfg_edge(a, d);
3445 assert!(fresh > last);
3446 assert_ne!(fresh, removed, "removed edge IDs must never be reused");
3447 }
3448
3449 #[test]
3450 fn compact_instruction_arena_preserves_ids_across_round_trip() {
3451 let mut ctx = Context::new();
3452 qcode!(
3453 ctx,
3454 "
3455 <block>
3456 %first = i64 1 + i64 2;
3457 %removed = i64 3 + i64 4;
3458 return at %first;
3459 "
3460 );
3461 let ids = BasicBlock::from_id(&ctx, block).instruction_ids();
3462 let first = ids[0];
3463 let removed = ids[1];
3464 let last = ids[2];
3465 ctx.remove_instruction(removed);
3466
3467 let physical_order: Vec<_> = ctx.bodies[first.func]
3468 .insns
3469 .iter()
3470 .map(|insn| insn.id)
3471 .collect();
3472 assert_eq!(physical_order, vec![first.local, last.local]);
3473
3474 let config = bincode::config::standard();
3475 let bytes = bincode::serde::encode_to_vec(&ctx, config).expect("encode");
3476 let (mut restored, _): (Context<'static>, usize) =
3477 bincode::serde::decode_from_slice(&bytes, config).expect("decode");
3478
3479 assert!(!restored.contains_instruction(removed));
3480 assert_eq!(
3481 restored.bodies[first.func]
3482 .insns
3483 .iter()
3484 .map(|insn| insn.id)
3485 .collect::<Vec<_>>(),
3486 physical_order,
3487 );
3488 assert!(restored.contains_instruction(first));
3489 assert!(restored.contains_instruction(last));
3490
3491 let template = restored.instruction(last).clone();
3492 let fresh = restored.push_insn(first.func, template);
3493 assert!(fresh.local > last.local);
3494 assert_ne!(
3495 fresh, removed,
3496 "removed instruction IDs must never be reused"
3497 );
3498 }
3499
3500 #[test]
3501 fn compact_param_arena_preserves_ids_across_round_trip() {
3502 let mut ctx = Context::new();
3503 let function = ctx.anon_function();
3504 let block = BasicBlock::make(&mut ctx, function).id;
3505 let first = BasicBlock::from_id_mut(&mut ctx, block).push_param(8).id;
3506 let removed = BasicBlock::from_id_mut(&mut ctx, block).push_param(8).id;
3507 let last = BasicBlock::from_id_mut(&mut ctx, block).push_param(8).id;
3508
3509 ctx.block_mut(block).params.remove(1);
3510 ctx.block_param_mut(last).index = 1;
3511 ctx.remove_block_param(removed);
3512
3513 let physical_order: Vec<_> = ctx.bodies[function]
3514 .params
3515 .iter()
3516 .map(|param| param.id)
3517 .collect();
3518 assert_eq!(physical_order, vec![first.local, last.local]);
3519 assert_eq!(ctx.block_param(first).index, 0);
3520 assert_eq!(ctx.block_param(last).index, 1);
3521
3522 let config = bincode::config::standard();
3523 let bytes = bincode::serde::encode_to_vec(&ctx, config).expect("encode");
3524 let (mut restored, _): (Context<'static>, usize) =
3525 bincode::serde::decode_from_slice(&bytes, config).expect("decode");
3526
3527 assert!(!restored.contains_block_param(removed));
3528 assert_eq!(
3529 restored.bodies[function]
3530 .params
3531 .iter()
3532 .map(|param| param.id)
3533 .collect::<Vec<_>>(),
3534 physical_order,
3535 );
3536 assert!(restored.contains_block_param(first));
3537 assert!(restored.contains_block_param(last));
3538
3539 let fresh = BasicBlock::from_id_mut(&mut restored, block)
3540 .push_param(8)
3541 .id;
3542 assert!(fresh.local > last.local);
3543 assert_ne!(fresh, removed, "removed parameter IDs must never be reused");
3544 }
3545
3546 #[test]
3547 fn compact_block_arena_preserves_ids_across_round_trip() {
3548 let mut ctx = Context::new();
3549 let function = ctx.anon_function();
3550 let first = BasicBlock::make(&mut ctx, function).id;
3551 let removed = BasicBlock::make(&mut ctx, function).id;
3552 let last = BasicBlock::make(&mut ctx, function).id;
3553 FunctionBody::from_id_mut(&mut ctx, function)
3554 .set_root(first)
3555 .expect("set root");
3556
3557 ctx.delete_block(removed);
3558
3559 let physical_order: Vec<_> = ctx.bodies[function]
3560 .blocks
3561 .iter()
3562 .map(|block| block.id)
3563 .collect();
3564 assert_eq!(physical_order, vec![first.local, last.local]);
3565 assert_eq!(ctx.block_ids(), vec![first, last]);
3566
3567 let config = bincode::config::standard();
3568 let bytes = bincode::serde::encode_to_vec(&ctx, config).expect("encode");
3569 let (mut restored, _): (Context<'static>, usize) =
3570 bincode::serde::decode_from_slice(&bytes, config).expect("decode");
3571
3572 assert!(!restored.contains_block(removed));
3573 assert_eq!(
3574 restored.bodies[function]
3575 .blocks
3576 .iter()
3577 .map(|block| block.id)
3578 .collect::<Vec<_>>(),
3579 physical_order,
3580 );
3581 assert!(restored.contains_block(first));
3582 assert!(restored.contains_block(last));
3583 assert_eq!(
3584 FunctionBody::from_id(&restored, function)
3585 .root()
3586 .map(|block| block.id),
3587 Some(first),
3588 );
3589
3590 let fresh = BasicBlock::make(&mut restored, function).id;
3591 assert!(fresh.local > last.local);
3592 assert_ne!(fresh, removed, "removed block IDs must never be reused");
3593 }
3594
3595 #[test]
3596 fn deleting_root_clears_function_root() {
3597 let mut ctx = Context::new();
3598 let function = ctx.anon_function();
3599 let root = BasicBlock::make(&mut ctx, function).id;
3600 FunctionBody::from_id_mut(&mut ctx, function)
3601 .set_root(root)
3602 .expect("set root");
3603
3604 ctx.delete_block(root);
3605
3606 assert!(!ctx.contains_block(root));
3607 assert!(FunctionBody::from_id(&ctx, function).root().is_none());
3608 assert!(ctx.block_ids().is_empty());
3609 }
3610
3611 #[test]
3612 fn get_unique_name_resumes_probe_and_reuses_freed_suffixes() {
3613 use crate::value::VarnodeId;
3614
3615 let mut ctx = Context::new();
3616 let id = ValueId::Varnode(VarnodeId::from(0usize));
3617
3618 fn take(ctx: &mut Context<'static>, id: ValueId, base: &str) -> String {
3620 let name = ctx
3621 .get_unique_name(Cow::Owned(base.to_string()))
3622 .to_string();
3623 ctx.update_name(Cow::Owned(name.clone()), id, None).unwrap();
3624 name
3625 }
3626
3627 assert_eq!(take(&mut ctx, id, "tmp"), "tmp");
3629 assert_eq!(take(&mut ctx, id, "tmp"), "tmp_1");
3630 assert_eq!(take(&mut ctx, id, "tmp"), "tmp_2");
3631 assert_eq!(take(&mut ctx, id, "tmp"), "tmp_3");
3632
3633 assert_eq!(take(&mut ctx, id, "x"), "x");
3635 assert_eq!(take(&mut ctx, id, "x"), "x_1");
3636
3637 ctx.update_name(Cow::Borrowed("relocated"), id, Some("tmp_1"))
3640 .unwrap();
3641 assert_eq!(take(&mut ctx, id, "tmp"), "tmp_1");
3642 assert_eq!(take(&mut ctx, id, "tmp"), "tmp_4");
3644 }
3645
3646 mod split_function_at {
3649 use super::*;
3650
3651 use crate::value::insn::{Callee, Mnemonic, TailCall};
3652 use crate::value::{BasicBlock, FunctionBody, Instruction, Value};
3653 use std::borrow::Cow;
3654
3655 fn block_at(ctx: &mut Context<'static>, func: FunctionId, addr: u64) -> BlockId {
3656 BasicBlock::make(ctx, func).with_address(addr).id
3657 }
3658
3659 fn branch_at(ctx: &mut Context<'static>, block: BlockId, target: BlockId, addr: u64) {
3660 let id = (ctx).builder(block).push_branch(target).id;
3661 Instruction::from_id_mut(ctx, id).set_address(addr);
3662 }
3663
3664 fn cbranch_at(
3665 ctx: &mut Context<'static>,
3666 block: BlockId,
3667 success: BlockId,
3668 failure: BlockId,
3669 addr: u64,
3670 ) {
3671 let cond = ctx.get_const(1, 1).id();
3672 let id = (ctx).builder(block).push_cbranch(cond, success, failure).id;
3673 Instruction::from_id_mut(ctx, id).set_address(addr);
3674 }
3675
3676 fn return_at(ctx: &mut Context<'static>, block: BlockId, addr: u64) {
3677 let zero = ctx.get_const(0, 8).id();
3678 let id = (ctx).builder(block).push_return(zero).id;
3679 Instruction::from_id_mut(ctx, id).set_address(addr);
3680 }
3681
3682 fn block_at_addr(ctx: &Context, func: FunctionId, addr: u64) -> BlockId {
3683 FunctionBody::from_id(ctx, func)
3684 .block_ids()
3685 .into_iter()
3686 .find(|b| ctx.block(*b).address == Some(addr))
3687 .unwrap_or_else(|| panic!("{func:?} has no block at {addr:#x}"))
3688 }
3689
3690 fn addrs(ctx: &Context, func: FunctionId) -> Vec<u64> {
3691 let mut got: Vec<u64> = FunctionBody::from_id(ctx, func)
3692 .block_ids()
3693 .into_iter()
3694 .filter_map(|b| ctx.block(b).address)
3695 .collect();
3696 got.sort_unstable();
3697 got
3698 }
3699
3700 #[test]
3705 fn splits_absorbed_body_reusing_the_stub() {
3706 let mut ctx = Context::new();
3707 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("thunk"))).id;
3708 let b0 = block_at(&mut ctx, f, 0x1000);
3709 let b1 = block_at(&mut ctx, f, 0x2000);
3710 let b2 = block_at(&mut ctx, f, 0x2005);
3711 branch_at(&mut ctx, b0, b1, 0x1000);
3712 branch_at(&mut ctx, b1, b2, 0x2000);
3713 return_at(&mut ctx, b2, 0x2005);
3714 {
3715 let mut func = FunctionBody::from_id_mut(&mut ctx, f);
3716 func.set_root(b0).unwrap();
3717 }
3718 let g = FunctionBody::make_at_addr(&mut ctx, 0x2000, Some(Cow::Borrowed("real"))).id;
3720
3721 let split_g = ctx.split_function_at(b1);
3722 assert_eq!(
3723 split_g, g,
3724 "the split must reuse the existing stub at 0x2000"
3725 );
3726
3727 assert_eq!(addrs(&ctx, f), vec![0x1000]);
3728 assert_eq!(addrs(&ctx, g), vec![0x2000, 0x2005]);
3729 let g_entry = block_at_addr(&ctx, g, 0x2000);
3730 assert_eq!(ctx.bodies[g].root_id(), Some(g_entry.local));
3731
3732 for b in FunctionBody::from_id(&ctx, g).block_ids() {
3734 assert_eq!(b.func, g);
3735 }
3736
3737 let f_entry = block_at_addr(&ctx, f, 0x1000);
3739 assert_eq!(BasicBlock::from_id(&ctx, f_entry).successors().count(), 0);
3740 let term = BasicBlock::from_id(&ctx, f_entry)
3741 .instructions()
3742 .last()
3743 .map(|i| i.mnemonic().clone());
3744 assert!(
3745 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(g)),
3746 "thunk branch must become TailCall(G), got {term:?}",
3747 );
3748 }
3749
3750 #[test]
3751 fn split_rehomes_temporary_values_spaces_and_pointer_types() {
3752 let mut ctx = Context::new();
3753 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
3754 let entry = block_at(&mut ctx, f, 0x1000);
3755 let tail = block_at(&mut ctx, f, 0x2000);
3756 branch_at(&mut ctx, entry, tail, 0x1000);
3757 FunctionBody::from_id_mut(&mut ctx, f)
3758 .set_root(entry)
3759 .unwrap();
3760
3761 let temp = ctx
3762 .builder(tail)
3763 .make_named_temp(Cow::Borrowed("scratch"), 8);
3764 ctx.builder(entry)
3765 .make_named_temp(Cow::Borrowed("unused"), 4);
3766 let temp_space = ctx.bodies[f].temps[temp.local].space;
3767 let load = {
3768 let mut builder = ctx.builder(tail);
3769 let ValueId::Instruction(load) = builder
3770 .push_load::<false>(
3771 ValueId::Temp(temp),
3772 8,
3773 LocalMemorySpaceId::Temp(temp_space),
3774 )
3775 .id()
3776 else {
3777 unreachable!()
3778 };
3779 builder.push_return(ValueId::Instruction(load));
3780 load
3781 };
3782 let pointer_type = ctx
3783 .shared
3784 .types
3785 .get_or_make_space_address(8, MemorySpaceId::Temp(TempSpaceId::new(f, temp_space)));
3786 ctx.instruction_mut(load).type_id = pointer_type;
3787
3788 let g =
3789 FunctionBody::make_at_addr(&mut ctx, 0x2000, Some(Cow::Borrowed("discovered"))).id;
3790 assert_eq!(ctx.split_function_at(tail), g);
3791
3792 let diagnostics = crate::verify_body_arena_integrity(&ctx);
3793 assert!(diagnostics.is_empty(), "{diagnostics:#?}");
3794 assert_eq!(ctx.bodies[g].temp_spaces.len(), 1);
3795 assert_eq!(ctx.bodies[g].temps.len(), 1);
3796 assert_eq!(ctx.bodies[f].temps.len(), 2, "source arenas remain intact");
3797
3798 let moved_load = FunctionBody::from_id(&ctx, g)
3799 .blocks()
3800 .flat_map(|block| block.instructions())
3801 .find(|insn| matches!(insn.mnemonic(), Mnemonic::Load(_)))
3802 .expect("load moved with the split");
3803 let Mnemonic::Load(moved) = moved_load.mnemonic() else {
3804 unreachable!()
3805 };
3806 let LocalMemorySpaceId::Temp(moved_space) = moved.space else {
3807 panic!("load lost temporary-space provenance")
3808 };
3809 assert!(matches!(moved.ptr, crate::value::LocalValueId::Temp(_)));
3810 assert!(usize::from(moved_space) < ctx.bodies[g].temp_spaces.len());
3811 assert_eq!(
3812 ctx.shared.types.space_of(moved_load.type_id()),
3813 Some(MemorySpaceId::Temp(TempSpaceId::new(g, moved_space)))
3814 );
3815
3816 let rendered = FunctionBody::from_id(&ctx, g).to_string();
3818 assert!(rendered.contains("scratch"));
3819 }
3820
3821 #[test]
3822 fn split_stops_at_a_foreign_rootless_stub_address() {
3823 let mut ctx = Context::new();
3824 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
3825 let entry = block_at(&mut ctx, f, 0x1000);
3826 let split = block_at(&mut ctx, f, 0x2000);
3827 let foreign_entry = block_at(&mut ctx, f, 0x3000);
3828 let foreign_body = block_at(&mut ctx, f, 0x3005);
3829 branch_at(&mut ctx, entry, split, 0x1000);
3830 branch_at(&mut ctx, split, foreign_entry, 0x2000);
3831 branch_at(&mut ctx, foreign_entry, foreign_body, 0x3000);
3832 return_at(&mut ctx, foreign_body, 0x3005);
3833 FunctionBody::from_id_mut(&mut ctx, f)
3834 .set_root(entry)
3835 .unwrap();
3836
3837 let g = FunctionBody::make_at_addr(&mut ctx, 0x2000, Some(Cow::Borrowed("g"))).id;
3838 let h = FunctionBody::make_at_addr(&mut ctx, 0x3000, Some(Cow::Borrowed("h"))).id;
3839 assert!(FunctionBody::from_id(&ctx, g).root().is_none());
3840 assert!(FunctionBody::from_id(&ctx, h).root().is_none());
3841
3842 assert_eq!(ctx.split_function_at(split), g);
3843 assert_eq!(addrs(&ctx, g), vec![0x2000]);
3844 assert_eq!(addrs(&ctx, f), vec![0x1000, 0x3000, 0x3005]);
3845 assert!(FunctionBody::from_id(&ctx, h).root().is_none());
3846
3847 let g_entry = block_at_addr(&ctx, g, 0x2000);
3848 let term = BasicBlock::from_id(&ctx, g_entry)
3849 .instructions()
3850 .last()
3851 .map(|i| i.mnemonic().clone());
3852 assert!(
3853 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(h)),
3854 "split tail must stop and tail-call rootless stub H, got {term:?}",
3855 );
3856 }
3857
3858 #[test]
3859 fn split_rehomes_block_param_origin_into_destination_arena() {
3860 let mut ctx = Context::new();
3861 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
3862 let entry = block_at(&mut ctx, f, 0x1000);
3863 let tail = block_at(&mut ctx, f, 0x2000);
3864 let param = BasicBlock::from_id_mut(&mut ctx, tail).push_param(8).id;
3865 crate::value::BlockParam::from_id_mut(&mut ctx, param)
3866 .set_origin(ValueId::BlockParam(param));
3867
3868 let arg = ctx.get_const(7, 8).id();
3869 let branch = ctx.builder(entry).push_branch_with_args(tail, vec![arg]).id;
3870 Instruction::from_id_mut(&mut ctx, branch).set_address(0x1000);
3871 let ret = ctx.builder(tail).push_return(ValueId::BlockParam(param)).id;
3872 Instruction::from_id_mut(&mut ctx, ret).set_address(0x2000);
3873 FunctionBody::from_id_mut(&mut ctx, f)
3874 .set_root(entry)
3875 .unwrap();
3876
3877 let g = ctx.split_function_at(tail);
3878 let new_tail = block_at_addr(&ctx, g, 0x2000);
3879 let new_param = BasicBlock::from_id(&ctx, new_tail).params().next().unwrap();
3880 assert_eq!(new_param.origin(), Some(ValueId::BlockParam(new_param.id)));
3881 }
3882
3883 #[test]
3891 fn split_rehomes_symbolic_block_literals() {
3892 use crate::value::literal::SymbolicRef;
3893
3894 let mut ctx = Context::new();
3895 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
3896 let entry = block_at(&mut ctx, f, 0x1000);
3897 let tail = block_at(&mut ctx, f, 0x2000);
3898 let landing = block_at(&mut ctx, f, 0x2008);
3899
3900 let lit = ctx.get_const(0x2008, 8).id();
3903 let ValueId::Literal(lit_id) = lit else {
3904 panic!("expected a literal");
3905 };
3906 ctx.shared.values.literals[lit_id].symbolic = Some(SymbolicRef::Block(landing));
3907
3908 branch_at(&mut ctx, entry, tail, 0x1000);
3909 let ind = ctx.builder(tail).push_branchind(lit).id;
3911 Instruction::from_id_mut(&mut ctx, ind).set_address(0x2000);
3912 ctx.add_cfg_edge(tail, landing);
3913 return_at(&mut ctx, landing, 0x2008);
3914 FunctionBody::from_id_mut(&mut ctx, f)
3915 .set_root(entry)
3916 .unwrap();
3917
3918 let g = ctx.split_function_at(tail);
3919
3920 let new_landing = block_at_addr(&ctx, g, 0x2008);
3921 let new_tail = block_at_addr(&ctx, g, 0x2000);
3922 let Mnemonic::BranchInd(b) = BasicBlock::from_id(&ctx, new_tail)
3923 .instructions()
3924 .last()
3925 .unwrap()
3926 .mnemonic()
3927 .clone()
3928 else {
3929 panic!("tail must still end in an indirect branch");
3930 };
3931 let crate::value::LocalValueId::Literal(new_lit) = b.ptr else {
3932 panic!("indirect branch operand must still be a literal");
3933 };
3934 assert_eq!(
3935 ctx.shared.values.literals[new_lit].symbolic,
3936 Some(SymbolicRef::Block(new_landing)),
3937 "the relocated literal must name the clone, not the deleted original",
3938 );
3939 assert_eq!(
3940 ctx.shared.values.literals[new_lit].value, 0x2008,
3941 "re-pointing the symbol must not disturb the numeric value",
3942 );
3943 }
3944
3945 #[test]
3949 fn conditional_arm_into_split_block_uses_a_trampoline() {
3950 let mut ctx = Context::new();
3951 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
3952 let entry = block_at(&mut ctx, f, 0x1000);
3953 let cont = block_at(&mut ctx, f, 0x1008);
3954 let tail = block_at(&mut ctx, f, 0x2000);
3955 cbranch_at(&mut ctx, entry, tail, cont, 0x1000);
3956 return_at(&mut ctx, cont, 0x1008);
3957 return_at(&mut ctx, tail, 0x2000);
3958 FunctionBody::from_id_mut(&mut ctx, f)
3959 .set_root(entry)
3960 .unwrap();
3961
3962 let g = ctx.split_function_at(tail);
3963
3964 let entry = block_at_addr(&ctx, f, 0x1000);
3965 let cont = block_at_addr(&ctx, f, 0x1008);
3966 assert_eq!(addrs(&ctx, g), vec![0x2000]);
3967
3968 let Mnemonic::CBranch(cb) = BasicBlock::from_id(&ctx, entry)
3969 .instructions()
3970 .last()
3971 .unwrap()
3972 .mnemonic()
3973 .clone()
3974 else {
3975 panic!("entry must still end in a cbranch");
3976 };
3977 assert_eq!(cb.failure_block, cont.local, "fall-through arm untouched");
3978 let tramp = BlockId::new(entry.func, cb.success_block);
3979 assert_eq!(
3980 BasicBlock::from_id(&ctx, tramp).parent().map(|f| f.id),
3981 Some(f),
3982 "trampoline lives in F",
3983 );
3984 let term = BasicBlock::from_id(&ctx, tramp)
3985 .instructions()
3986 .last()
3987 .map(|i| i.mnemonic().clone());
3988 assert!(
3989 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(g)),
3990 "trampoline must tail-call G, got {term:?}",
3991 );
3992 for (_, s) in BasicBlock::from_id(&ctx, entry).successors() {
3994 assert_eq!(BasicBlock::from_id(&ctx, s).parent().map(|f| f.id), Some(f));
3995 }
3996 }
3997
3998 #[test]
4001 fn mints_a_conventional_function_when_no_stub_exists() {
4002 let mut ctx = Context::new();
4003 wazabin_qcode_macro::qcode!(
4004 ctx,
4005 "
4006 fn f:
4007 <entry>
4008 goto <0x1008>;
4009 <0x1008>
4010 return 0x0;
4011 "
4012 );
4013
4014 let mid = block_at_addr(&ctx, f, 0x1008);
4015 let g = ctx.split_function_at(mid);
4016 assert_eq!(FunctionBody::from_id(&ctx, g).name(), "fn_1008");
4017 assert_eq!(FunctionBody::from_id(&ctx, f).block_ids().len(), 1);
4019 assert_eq!(addrs(&ctx, g), vec![0x1008]);
4020 let addresses = crate::address_index::AddressIndex::analyze(&ctx);
4021 assert_eq!(addresses.function_at(0x1008), Some(g));
4022 for b in FunctionBody::from_id(&ctx, g).block_ids() {
4023 assert_eq!(b.func, g);
4024 }
4025 }
4026
4027 fn assert_no_dangling_terminators(ctx: &Context) {
4031 for b in ctx.block_ids() {
4032 let Some(mnemonic) = BasicBlock::from_id(ctx, b)
4033 .instructions()
4034 .last()
4035 .map(|t| t.mnemonic().clone())
4036 else {
4037 continue;
4038 };
4039 let targets = match &mnemonic {
4040 Mnemonic::Branch(crate::value::insn::Branch { target, .. }) => vec![*target],
4041 Mnemonic::CBranch(crate::value::insn::CBranch {
4042 success_block,
4043 failure_block,
4044 ..
4045 }) => vec![*success_block, *failure_block],
4046 _ => vec![],
4047 };
4048 let succs: std::collections::HashSet<BlockId> = BasicBlock::from_id(ctx, b)
4049 .successors()
4050 .map(|(_, s)| s)
4051 .collect();
4052 for t in targets {
4053 let tid = BlockId::new(b.func, t);
4054 assert!(
4055 ctx.contains_block(tid),
4056 "block {b:?} terminator names dead block {tid:?}"
4057 );
4058 assert!(
4059 succs.contains(&tid),
4060 "block {b:?} terminator target {tid:?} has no CFG edge (operand/edge desync)"
4061 );
4062 }
4063 }
4064 }
4065
4066 #[test]
4071 fn retained_predecessor_into_mid_tail_promotes_the_landing() {
4072 let mut ctx = Context::new();
4073 wazabin_qcode_macro::qcode!(
4076 ctx,
4077 "
4078 fn f:
4079 <entry @c:i8>
4080 if @c goto <0x2000> else goto <0x1008>;
4081 <0x1008>
4082 goto <0x2008>;
4083 <0x2000>
4084 goto <0x2008>;
4085 <0x2008>
4086 return 0x0;
4087 "
4088 );
4089
4090 let tail = block_at_addr(&ctx, f, 0x2000);
4091 let g = ctx.split_function_at(tail);
4092
4093 let addresses = crate::address_index::AddressIndex::analyze(&ctx);
4096 let landing_fn = addresses
4097 .function_at(0x2008)
4098 .expect("mid-tail landing must be promoted to a function");
4099 assert_ne!(landing_fn, g);
4100 assert_eq!(addrs(&ctx, g), vec![0x2000]);
4101 assert_no_dangling_terminators(&ctx);
4102
4103 for (holder, addr) in [(f, 0x1008u64), (g, 0x2000u64)] {
4104 let block = block_at_addr(&ctx, holder, addr);
4105 let term = BasicBlock::from_id(&ctx, block)
4106 .instructions()
4107 .last()
4108 .map(|i| i.mnemonic().clone());
4109 assert!(
4110 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(landing_fn)),
4111 "branch at {addr:#x} into the landing must tail-call it, got {term:?}",
4112 );
4113 }
4114 }
4115
4116 #[test]
4124 fn tail_conditional_to_own_registered_entry_uses_a_trampoline() {
4125 let mut ctx = Context::new();
4126 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
4127 let entry = block_at(&mut ctx, f, 0x1000);
4128 let tail = block_at(&mut ctx, f, 0x2000);
4129 let cont = block_at(&mut ctx, f, 0x2008);
4130 branch_at(&mut ctx, entry, tail, 0x1000);
4131 cbranch_at(&mut ctx, tail, entry, cont, 0x2000);
4133 return_at(&mut ctx, cont, 0x2008);
4134 FunctionBody::from_id_mut(&mut ctx, f)
4135 .set_root(entry)
4136 .unwrap();
4137
4138 let g = ctx.split_function_at(tail);
4139
4140 assert_no_dangling_terminators(&ctx);
4141 let diagnostics = crate::verify_body_arena_integrity(&ctx);
4142 assert!(diagnostics.is_empty(), "{diagnostics:#?}");
4143
4144 let moved_tail = block_at_addr(&ctx, g, 0x2000);
4147 let Mnemonic::CBranch(cb) = BasicBlock::from_id(&ctx, moved_tail)
4148 .instructions()
4149 .last()
4150 .unwrap()
4151 .mnemonic()
4152 .clone()
4153 else {
4154 panic!("moved tail must still end in a cbranch");
4155 };
4156 let tramp = BlockId::new(g, cb.success_block);
4157 assert_eq!(tramp.func, g, "trampoline must have relocated into g");
4158 let term = BasicBlock::from_id(&ctx, tramp)
4159 .instructions()
4160 .last()
4161 .map(|i| i.mnemonic().clone());
4162 assert!(
4163 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(f)),
4164 "back-edge trampoline must tail-call f, got {term:?}",
4165 );
4166 }
4167
4168 #[test]
4173 fn tail_conditional_to_foreign_entry_relocates_its_trampoline() {
4174 let mut ctx = Context::new();
4175 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
4176 let entry = block_at(&mut ctx, f, 0x1000);
4177 let tail = block_at(&mut ctx, f, 0x2000);
4178 let cont = block_at(&mut ctx, f, 0x2008);
4179 let foreign = block_at(&mut ctx, f, 0x3000);
4180 branch_at(&mut ctx, entry, tail, 0x1000);
4181 cbranch_at(&mut ctx, tail, foreign, cont, 0x2000);
4183 return_at(&mut ctx, cont, 0x2008);
4184 return_at(&mut ctx, foreign, 0x3000);
4185 FunctionBody::from_id_mut(&mut ctx, f)
4186 .set_root(entry)
4187 .unwrap();
4188 let h = FunctionBody::make_at_addr(&mut ctx, 0x3000, Some(Cow::Borrowed("h"))).id;
4189
4190 let g = ctx.split_function_at(tail);
4191
4192 assert_no_dangling_terminators(&ctx);
4193 let diagnostics = crate::verify_body_arena_integrity(&ctx);
4194 assert!(diagnostics.is_empty(), "{diagnostics:#?}");
4195
4196 let moved_tail = block_at_addr(&ctx, g, 0x2000);
4199 let Mnemonic::CBranch(cb) = BasicBlock::from_id(&ctx, moved_tail)
4200 .instructions()
4201 .last()
4202 .unwrap()
4203 .mnemonic()
4204 .clone()
4205 else {
4206 panic!("moved tail must still end in a cbranch");
4207 };
4208 let tramp = BlockId::new(g, cb.success_block);
4209 assert_eq!(tramp.func, g, "trampoline must have relocated into g");
4210 let term = BasicBlock::from_id(&ctx, tramp)
4211 .instructions()
4212 .last()
4213 .map(|i| i.mnemonic().clone());
4214 assert!(
4215 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(h)),
4216 "relocated trampoline must tail-call H, got {term:?}",
4217 );
4218 }
4219
4220 #[test]
4224 fn conditional_failure_arm_into_split_block_uses_a_trampoline() {
4225 let mut ctx = Context::new();
4226 wazabin_qcode_macro::qcode!(
4229 ctx,
4230 "
4231 fn f:
4232 <entry @c:i8>
4233 if @c goto <0x1008> else goto <0x2000>;
4234 <0x1008>
4235 return 0x0;
4236 <0x2000>
4237 return 0x0;
4238 "
4239 );
4240
4241 let tail = block_at_addr(&ctx, f, 0x2000);
4242 let g = ctx.split_function_at(tail);
4243
4244 assert_no_dangling_terminators(&ctx);
4245 let entry = BlockId::new(f, ctx.bodies[f].root_id().unwrap());
4246 let cont = block_at_addr(&ctx, f, 0x1008);
4247 let Mnemonic::CBranch(cb) = BasicBlock::from_id(&ctx, entry)
4248 .instructions()
4249 .last()
4250 .unwrap()
4251 .mnemonic()
4252 .clone()
4253 else {
4254 panic!("entry must still end in a cbranch");
4255 };
4256 assert_eq!(
4257 cb.success_block, cont.local,
4258 "success (fall-through) untouched"
4259 );
4260 let tramp = BlockId::new(entry.func, cb.failure_block);
4261 let term = BasicBlock::from_id(&ctx, tramp)
4262 .instructions()
4263 .last()
4264 .map(|i| i.mnemonic().clone());
4265 assert!(
4266 matches!(term, Some(Mnemonic::TailCall(TailCall { target, .. })) if target == Callee::Real(g)),
4267 "failure arm must route through a trampoline tail-calling G, got {term:?}",
4268 );
4269 }
4270
4271 #[test]
4274 fn moved_tail_internal_conditional_remaps_both_arms() {
4275 let mut ctx = Context::new();
4276 wazabin_qcode_macro::qcode!(
4279 ctx,
4280 "
4281 fn f:
4282 <entry>
4283 goto <0x2000>;
4284 <0x2000>
4285 %c = 0x0 == 0x0;
4286 if %c goto <0x2008> else goto <0x2010>;
4287 <0x2008>
4288 return 0x0;
4289 <0x2010>
4290 return 0x0;
4291 "
4292 );
4293
4294 let tail = block_at_addr(&ctx, f, 0x2000);
4295 let g = ctx.split_function_at(tail);
4296
4297 assert_eq!(addrs(&ctx, g), vec![0x2000, 0x2008, 0x2010]);
4298 assert_no_dangling_terminators(&ctx);
4299 let moved_tail = block_at_addr(&ctx, g, 0x2000);
4300 let Mnemonic::CBranch(cb) = BasicBlock::from_id(&ctx, moved_tail)
4301 .instructions()
4302 .last()
4303 .unwrap()
4304 .mnemonic()
4305 .clone()
4306 else {
4307 panic!("moved tail must still end in a cbranch");
4308 };
4309 let a = block_at_addr(&ctx, g, 0x2008);
4310 let b = block_at_addr(&ctx, g, 0x2010);
4311 assert_eq!(cb.success_block, a.local, "success arm re-pointed to clone");
4312 assert_eq!(cb.failure_block, b.local, "failure arm re-pointed to clone");
4313 }
4314
4315 #[test]
4318 fn moved_tail_internal_branch_remaps_target() {
4319 let mut ctx = Context::new();
4320 wazabin_qcode_macro::qcode!(
4321 ctx,
4322 "
4323 fn f:
4324 <entry>
4325 goto <0x2000>;
4326 <0x2000>
4327 goto <0x2008>;
4328 <0x2008>
4329 return 0x0;
4330 "
4331 );
4332
4333 let tail = block_at_addr(&ctx, f, 0x2000);
4334 let g = ctx.split_function_at(tail);
4335
4336 assert_eq!(addrs(&ctx, g), vec![0x2000, 0x2008]);
4337 assert_no_dangling_terminators(&ctx);
4338 let moved_tail = block_at_addr(&ctx, g, 0x2000);
4339 let Mnemonic::Branch(br) = BasicBlock::from_id(&ctx, moved_tail)
4340 .instructions()
4341 .last()
4342 .unwrap()
4343 .mnemonic()
4344 .clone()
4345 else {
4346 panic!("moved tail must still end in a branch");
4347 };
4348 let end = block_at_addr(&ctx, g, 0x2008);
4349 assert_eq!(br.target, end.local, "internal branch re-pointed to clone");
4350 }
4351
4352 #[test]
4355 fn split_rehomes_store_temporary_space() {
4356 let mut ctx = Context::new();
4357 let f = FunctionBody::make_at_addr(&mut ctx, 0x1000, Some(Cow::Borrowed("f"))).id;
4358 let entry = block_at(&mut ctx, f, 0x1000);
4359 let tail = block_at(&mut ctx, f, 0x2000);
4360 branch_at(&mut ctx, entry, tail, 0x1000);
4361
4362 let slot = ctx.builder(tail).make_named_temp(Cow::Borrowed("slot"), 8);
4363 let space = ctx.bodies[f].temps[slot.local].space;
4364 let value = ctx.get_const(0x2a, 8).id();
4365 {
4366 let mut builder = ctx.builder(tail);
4367 builder.push_store(value, ValueId::Temp(slot), LocalMemorySpaceId::Temp(space));
4368 builder.push_return(value);
4369 }
4370 FunctionBody::from_id_mut(&mut ctx, f)
4371 .set_root(entry)
4372 .unwrap();
4373
4374 let g = ctx.split_function_at(tail);
4375 let diagnostics = crate::verify_body_arena_integrity(&ctx);
4376 assert!(diagnostics.is_empty(), "{diagnostics:#?}");
4377
4378 let moved_store = FunctionBody::from_id(&ctx, g)
4379 .blocks()
4380 .flat_map(|block| block.instructions())
4381 .find(|insn| matches!(insn.mnemonic(), Mnemonic::Store(_)))
4382 .expect("store moved with the split");
4383 let Mnemonic::Store(moved) = moved_store.mnemonic() else {
4384 unreachable!()
4385 };
4386 let LocalMemorySpaceId::Temp(moved_space) = moved.space else {
4387 panic!("store lost temporary-space provenance")
4388 };
4389 assert!(usize::from(moved_space) < ctx.bodies[g].temp_spaces.len());
4390 }
4391 }
4392}