Skip to main content

qcode/
builder.rs

1//! Fluent IR builder: emit instructions into a [`BasicBlock`].
2//!
3//! The [`Builder`] is the primary way to construct IR. It holds a mutable
4//! reference to a block inside a [`Context`](crate::context::Context) and exposes typed `push_*` methods
5//! for every instruction kind.
6//!
7//! Terminating the block is the caller's responsibility ([`Builder::finalize`]
8//! pushes the final branch for the common case); the invariant that every
9//! rostered block ends in a terminator is enforced by the IR verifier, not at
10//! builder drop. Appending *past* a terminator, however, panics immediately.
11//! A builder may freely be dropped mid-block — e.g. after splicing
12//! instructions before an existing anchor via
13//! [`Builder::set_insert_point_before`].
14//!
15//! # Typical usage
16//!
17//! ```rust,no_run
18//! use qcode::context::Context;
19//!
20//! let mut ctx = Context::new();
21//!
22//! // Create a builder positioned at machine address 0x1000.
23//! let source = ctx.builder_at(0x1000).current_block();
24//! let target = ctx.get_or_make_block(0x1010, source.func);
25//! let b = ctx.builder(source);
26//!
27//! // Emit instructions …
28//!
29//! // Terminate the block with an unconditional branch to `target`.
30//! // This consumes the builder, so there is no need to call drop explicitly.
31//! b.finalize(target);
32//! ```
33//!
34//! # Namespaces
35//!
36//! The builder maintains a *local namespace*: a map from string names to
37//! [`ValueId`]s. This is used by the [`qcode!`](wazabin_qcode_macro::qcode) macro and
38//! the parser to resolve identifiers within a single block. Names in the
39//! namespace do not need to match the IR-level name hints stored on values.
40
41use std::{borrow::Cow, cmp};
42
43use rustc_hash::FxHashMap as HashMap;
44
45use crate::{
46    space::{LocalMemorySpaceId, SPACE_CONST, Space, SpaceId, SpaceType},
47    types::{AggregateField, TypeId},
48    value::{
49        BodyView, FunctionBody, Instruction, LocalBlockId, LocalValueId, Temp, TempId, TempSpace,
50        ValueId, ValueRef,
51        block::{BasicBlock, BlockId},
52        block_param::{BlockParam, BlockParamId},
53        function::FunctionId,
54        insn::{
55            Apply, Assert, Binary, Binop, Branch, BranchInd, CBranch, Call, CallInd, Callee, Carry,
56            Extract, FloatBinop, FloatToFloat, FloatToInt, Gep, InstructionId, InstructionRef,
57            IntBinop, IntToFloat, IntrinsicApp, IntrinsicId, IsFloatNaN, Load, LocalInsnId,
58            LzCount, Map, Mnemonic, PCodeOp, PCodeOpId, PopCount, Range, Return, ReturnValue,
59            SBorrow, SCarry, Scan, Sext, Store, Switch, SwitchArm, TailCall, Tuple, Unary, Unop,
60            Zext,
61        },
62        varnode::Varnode,
63    },
64};
65
66#[cfg(test)]
67use crate::value::TempRef;
68
69/// A builder for constructing instructions in a block.
70/// This provides a convenient API for creating instructions, and automatically
71/// manages temporary values and labels.
72pub struct Builder<'str, 'ctx> {
73    body: &'ctx mut FunctionBody<'str>,
74    shared: &'ctx crate::context::Shared<'str>,
75    interfaces:
76        &'ctx jstd::registry::Registry<FunctionId, crate::value::function::FunctionInterface<'str>>,
77
78    /// The block currently receiving emitted instructions, as a body-local id.
79    /// The engine never routes through its owning `FunctionId`, so a detached
80    /// (id-less) body can be built. Composite callers read it via
81    /// [`Builder::current_block`].
82    pub(crate) block: LocalBlockId,
83
84    /// Converts from names to value IDs in the current scope.
85    namespace: HashMap<Cow<'str, str>, ValueId>,
86
87    /// Names of local labels to their corresponding body-local block IDs.
88    local_labels: HashMap<Cow<'str, str>, LocalBlockId>,
89
90    /// The address at which instructions are added
91    address: Option<u64>,
92
93    /// Is the block terminated, i.e. does it end with a terminator
94    /// If it is not the case, the block might be invalid
95    pub(crate) is_terminated: bool,
96
97    /// Explicit insert position for new instructions.
98    ///
99    /// `None` (default) appends to the end of the block.
100    /// `Some(n)` inserts at index `n` and auto-advances after each push,
101    /// so consecutive pushes form a contiguous sequence starting at `n`.
102    insert_point: Option<usize>,
103}
104
105/// Generates a canonical comparison method and its "greater-than" mirror
106/// (operands swapped), each with a composite skin and a body-local sibling.
107macro_rules! cmp_pair {
108    ($fwd:ident, $fwd_local:ident, $rev:ident, $rev_local:ident, $op:expr) => {
109        pub fn $fwd(
110            &mut self,
111            lhs: ValueId,
112            rhs: ValueId,
113        ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
114            let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
115            let local = self.$fwd_local(lhs, rhs);
116            self.insn_ref(local)
117        }
118        pub fn $fwd_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
119            self.push_binop_local($op, lhs, rhs, Some(1))
120        }
121        pub fn $rev(
122            &mut self,
123            lhs: ValueId,
124            rhs: ValueId,
125        ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
126            let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
127            let local = self.$rev_local(lhs, rhs);
128            self.insn_ref(local)
129        }
130        pub fn $rev_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
131            self.push_binop_local($op, rhs, lhs, Some(1))
132        }
133    };
134}
135
136/// Generates a simple unary-op push method (composite skin + body-local sibling)
137/// that delegates to [`push_unop_local`](Builder::push_unop_local).
138macro_rules! unop_leaf {
139    ($(#[$m:meta])* $name:ident, $lname:ident, $op:expr) => {
140        $(#[$m])*
141        pub fn $name(&mut self, src: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
142            let src = self.loc(src);
143            let local = self.$lname(src);
144            self.insn_ref(local)
145        }
146        /// Body-local sibling.
147        pub fn $lname(&mut self, src: LocalValueId) -> LocalInsnId {
148            self.push_unop_local($op, src)
149        }
150    };
151}
152
153/// Generates a simple binary-op push method (composite skin + body-local sibling)
154/// that delegates to [`push_binop_local`](Builder::push_binop_local) with no
155/// forced result size.
156macro_rules! binop_leaf {
157    ($(#[$m:meta])* $name:ident, $lname:ident, $op:expr, $size:expr) => {
158        $(#[$m])*
159        pub fn $name(
160            &mut self,
161            lhs: ValueId,
162            rhs: ValueId,
163        ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
164            let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
165            let local = self.$lname(lhs, rhs);
166            self.insn_ref(local)
167        }
168        /// Body-local sibling.
169        pub fn $lname(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
170            self.push_binop_local($op, lhs, rhs, $size)
171        }
172    };
173}
174
175/// Generates a size-taking conversion push method (composite skin + body-local
176/// sibling) whose mnemonic variant and payload type share the ident `$variant`
177/// and carry a `{ src, size }` shape.
178macro_rules! conv_leaf {
179    ($name:ident, $lname:ident, $err:literal, $variant:ident) => {
180        pub fn $name(
181            &mut self,
182            src: ValueId,
183            size: usize,
184        ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
185            let src = self.loc(src);
186            let local = self.$lname(src, size);
187            self.insn_ref(local)
188        }
189        /// Body-local sibling.
190        pub fn $lname(&mut self, src: LocalValueId, size: usize) -> LocalInsnId {
191            assert!(!matches!(src, LocalValueId::Varnode(_)), $err);
192            self.store_insn(Mnemonic::$variant($variant { src, size }), size)
193        }
194    };
195}
196
197impl<'str, 'ctx> Builder<'str, 'ctx> {
198    fn fresh_temp_space(&mut self, name: Option<&str>) -> crate::value::TempSpaceId {
199        let (word_size, addr_size) = {
200            let default = self.shr().space(self.shr().default_space);
201            (default.word_size, default.addr_size)
202        };
203        self.body
204            .push_temp_space(TempSpace::new(name, word_size, addr_size))
205    }
206
207    /// Creates an anonymous body-local temporary memory value.
208    pub fn make_temp(&mut self, size: usize) -> TempId {
209        let space = self.fresh_temp_space(None);
210        self.body.push_temp(Temp::new(0, size, space.local))
211    }
212
213    /// Creates a named body-local temporary memory value.
214    pub fn make_named_temp(&mut self, name: Cow<'str, str>, size: usize) -> TempId {
215        let unique = self.body.names.unique(name);
216        let space = self.fresh_temp_space(Some(unique.as_ref()));
217        self.body
218            .push_temp(Temp::new(0, size, space.local).with_name(unique))
219    }
220
221    /// Creates a body-local temporary identified by a SLEIGH local label.
222    pub fn make_temp_labeled(&mut self, label: u32, size: usize) -> TempId {
223        let space = self.fresh_temp_space(None);
224        let mut temp = Temp::new(0, size, space.local);
225        temp.label = Some(label);
226        self.body.push_temp(temp)
227    }
228
229    /// Creates a builder positioned at `block`.
230    ///
231    /// The block is borrowed mutably for the lifetime `'ctx`. New instructions
232    /// will be appended to the end of `block`.
233    pub fn new(
234        body: &'ctx mut FunctionBody<'str>,
235        shared: &'ctx crate::context::Shared<'str>,
236        interfaces: &'ctx jstd::registry::Registry<
237            FunctionId,
238            crate::value::function::FunctionInterface<'str>,
239        >,
240        block: BlockId,
241    ) -> Self {
242        assert_eq!(
243            body.id(),
244            block.func,
245            "Builder block must belong to its body"
246        );
247        Self::new_local(body, shared, interfaces, block.local)
248    }
249
250    /// Creates a builder positioned at a **body-local** block, without ever
251    /// consulting the body's registry identity. This is the id-less constructor:
252    /// it works on a detached (uninstalled) body just as well as an installed
253    /// one. `is_terminated` is read straight from the block's own arena (its last
254    /// instruction's mnemonic), never through the composite `BodyView` path.
255    pub fn new_local(
256        body: &'ctx mut FunctionBody<'str>,
257        shared: &'ctx crate::context::Shared<'str>,
258        interfaces: &'ctx jstd::registry::Registry<
259            FunctionId,
260            crate::value::function::FunctionInterface<'str>,
261        >,
262        block: LocalBlockId,
263    ) -> Self {
264        let is_terminated = body.blocks[block]
265            .instructions
266            .last()
267            .is_some_and(|&i| body.insns[i].mnemonic().is_terminator());
268        Self {
269            body,
270            shared,
271            interfaces,
272            is_terminated,
273            block,
274            namespace: HashMap::default(),
275            local_labels: HashMap::default(),
276            address: None,
277            insert_point: None,
278        }
279    }
280
281    /// This builder's owning function id. Skin-only: composite entry points call
282    /// this to qualify local ids back to the boundary [`ValueId`] surface. Never
283    /// invoked on the id-less (`new_local` + `push_*_local`) path.
284    #[inline]
285    fn func(&self) -> FunctionId {
286        self.body.id()
287    }
288
289    /// Qualify a body-local instruction id into an [`InstructionRef`]. Skin-only.
290    fn insn_ref(&self, local: LocalInsnId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
291        let id = InstructionId::new(self.func(), local);
292        InstructionRef::new(self.view(), id)
293    }
294
295    /// A `Copy` read view over the builder's backing, for arena reads. The builder
296    /// reads through the backing's static [`QCodeView`](crate::value::QCodeView).
297    pub fn view(&self) -> BodyView<'_, 'str> {
298        BodyView::new(&*self.body, self.shared, self.interfaces)
299    }
300
301    /// Returns `true` if the current block ends with a terminator instruction.
302    pub fn is_terminated(&self) -> bool {
303        self.block_is_terminated(self.block)
304    }
305
306    /// Whether a body-local block ends with a terminator, read straight from the
307    /// arenas (id-less).
308    fn block_is_terminated(&self, block: LocalBlockId) -> bool {
309        self.body.blocks[block]
310            .instructions
311            .last()
312            .is_some_and(|&i| self.body.insns[i].mnemonic().is_terminator())
313    }
314
315    /// Sets the current address for instructions added by this builder.
316    pub fn set_address(&mut self, addr: u64) {
317        self.address = Some(addr);
318    }
319
320    /// Remove the current address
321    pub fn clear_address(&mut self) {
322        self.address = None;
323    }
324
325    /// Positions the builder at the beginning of the block.
326    ///
327    /// Subsequent `push_*` calls insert instructions starting at index 0,
328    /// advancing by 1 after each push, so they appear in push order as a
329    /// contiguous prefix before any pre-existing instructions.
330    ///
331    /// This allows inserting synthetic preamble instructions (e.g. a
332    /// symbolic stack-pointer initialization) into a block that already
333    /// contains lifted code, without disturbing the relative order of
334    /// either the new or the existing instructions.
335    pub fn set_insert_point_to_start(&mut self) {
336        self.insert_point = Some(0);
337    }
338
339    /// Positions the builder immediately before an existing instruction in the
340    /// current block.
341    ///
342    /// Subsequent `push_*` calls insert instructions starting at that position,
343    /// advancing by 1 after each push, so they appear in push order immediately
344    /// before `before_id` and after any earlier inserted instructions.
345    ///
346    /// Panics if `before_id` is not an instruction in the current block.
347    pub fn set_insert_point_before(&mut self, before_id: InstructionId) {
348        let index = self.body.blocks[self.block]
349            .instructions
350            .iter()
351            .position(|&id| id == before_id.local)
352            .expect("before_id not found in block");
353        self.insert_point = Some(index);
354    }
355
356    /// Resets the insert point to append mode (the default).
357    pub fn set_insert_point_to_end(&mut self) {
358        self.insert_point = None;
359    }
360
361    /// Gets a sub-value from a given value, specified by a byte range.
362    pub fn get_range(
363        &mut self,
364        src: ValueId,
365        range: std::ops::Range<usize>,
366    ) -> Option<ValueRef<'str, '_, BodyView<'_, 'str>>> {
367        let src = self.loc(src);
368        let dst = self.get_range_local(src, range)?;
369        Some(self.get_value(dst.qualify(self.func())))
370    }
371
372    /// Body-local core of [`get_range`](Self::get_range): folds a literal/temp
373    /// sub-range in place and emits a `Range` instruction for varnode/instruction
374    /// sources. Operands and result are body-local; no registry identity is used.
375    pub fn get_range_local(
376        &mut self,
377        src: LocalValueId,
378        range: std::ops::Range<usize>,
379    ) -> Option<LocalValueId> {
380        if range.is_empty() {
381            return None;
382        }
383
384        let dst = match src {
385            LocalValueId::Literal(lit) => {
386                let value = self.shr().values.literals[lit].value;
387                let id = self.shr().get_const(value, range.len());
388                id.strip_func()
389            }
390
391            LocalValueId::Varnode(vid) => {
392                let size = Varnode::from_id(self.shr(), vid).size();
393                if range.end > size {
394                    return None;
395                }
396                let local = self.store_insn(
397                    Mnemonic::Range(Range {
398                        src,
399                        start: range.start,
400                        size: range.len(),
401                    }),
402                    range.len(),
403                );
404                LocalValueId::Instruction(local)
405            }
406
407            LocalValueId::Temp(tlocal) => {
408                let (address, size, space) = {
409                    let temp = &self.body.temps[tlocal];
410                    (temp.address, temp.size, temp.space)
411                };
412                if range.end > size {
413                    return None;
414                }
415                let temp = Temp::new(address + range.start as i64, range.len(), space);
416                let local = self.body.temps.push(temp);
417                LocalValueId::Temp(local)
418            }
419
420            LocalValueId::Instruction(_) => {
421                let size = self.lsize_of(src);
422                if range.end > size {
423                    return None;
424                }
425                let local = self.store_insn(
426                    Mnemonic::Range(Range {
427                        src,
428                        start: range.start,
429                        size: range.len(),
430                    }),
431                    range.len(),
432                );
433                LocalValueId::Instruction(local)
434            }
435
436            // Functions, blocks, and other non-data values have no byte range
437            _ => return None,
438        };
439
440        Some(dst)
441    }
442
443    /// Pushes a `Range` instruction extracting `size` bytes starting at byte
444    /// `start` of `src`. Unlike [`get_range`](Self::get_range), this always emits
445    /// a `Range` instruction (no constant/varnode folding), so the result is a
446    /// fresh SSA value — used by the `qcode!` macro's `src[start:end]` form.
447    pub fn push_range(
448        &mut self,
449        src: ValueId,
450        start: usize,
451        size: usize,
452    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
453        let local = self.push_range_local(self.loc(src), start, size);
454        self.insn_ref(local)
455    }
456
457    /// Body-local core of [`push_range`](Self::push_range).
458    pub fn push_range_local(
459        &mut self,
460        src: LocalValueId,
461        start: usize,
462        size: usize,
463    ) -> LocalInsnId {
464        self.store_insn(Mnemonic::Range(Range { src, start, size }), size)
465    }
466
467    /// Removes a name from the local namespace, freeing it for reuse.
468    pub fn remove_alias(&mut self, name: &str) {
469        self.namespace.remove(name);
470    }
471
472    /// Sets a name in the local alias map without changing the qcode name hint.
473    /// The alias map maps sleigh names to values for macro lookups; re-aliasing is allowed.
474    pub fn set_alias(&mut self, name: Cow<'str, str>, id: ValueId) {
475        self.namespace.insert(name, id);
476    }
477
478    pub fn switch_to_block(&mut self, block: BlockId) {
479        self.switch_to_block_local(block.local);
480    }
481
482    /// Reposition the builder onto a body-local block (id-less).
483    pub fn switch_to_block_local(&mut self, block: LocalBlockId) {
484        self.block = block;
485        self.is_terminated = self.block_is_terminated(block);
486    }
487
488    /// The block the builder is currently appending to.
489    pub fn current_block(&self) -> BlockId {
490        BlockId::new(self.func(), self.block)
491    }
492
493    /// Gets the ID of a value in the current namespace
494    pub fn try_get_value(&self, name: &str) -> Option<ValueRef<'str, '_, BodyView<'_, 'str>>> {
495        self.namespace.get(name).map(|&id| self.get_value(id))
496    }
497
498    /// The module's shared IR state (read) — types/literals/spaces/registers.
499    pub fn shr(&self) -> &crate::context::Shared<'str> {
500        self.shared
501    }
502
503    /// Retype instruction `local`'s result as a pointer into `space`. Register
504    /// spaces are left untyped (pointer arithmetic is not allowed there). A
505    /// body-local **temporary** space needs the registry identity to name its
506    /// owner; on a detached body that retype is skipped (an install-time nicety,
507    /// like debug naming). Body-local and id-free for the shared-space path.
508    fn set_insn_space_local(&mut self, local: LocalInsnId, space: LocalMemorySpaceId) {
509        if space.shared().is_some_and(|space| {
510            matches!(Space::from_id(self.shr(), space).ty, SpaceType::Register)
511        }) {
512            return;
513        }
514        let qualified = match space {
515            LocalMemorySpaceId::Shared(id) => crate::space::MemorySpaceId::Shared(id),
516            LocalMemorySpaceId::Temp(t) => match self.body.try_id() {
517                Some(func) => {
518                    crate::space::MemorySpaceId::Temp(crate::value::TempSpaceId::new(func, t))
519                }
520                None => return,
521            },
522        };
523        let cur_type = self.body.insns[local].type_id;
524        let size = self.shr().types.size_of(cur_type);
525        let type_id = self.shr().types.get_or_make_space_address(size, qualified);
526        self.body.insns[local].type_id = type_id;
527    }
528
529    /// Rename instruction `local`'s result. On an installed body this registers
530    /// the (function-local) name in the owning function's table (uniqueness
531    /// enforced); on a detached body it sets only the arena field — the source of
532    /// truth for rendering — since the local name table keys on the registry id.
533    fn rename_insn_local(
534        &mut self,
535        local: LocalInsnId,
536        name: Cow<'str, str>,
537    ) -> crate::error::Result<()> {
538        if self.body.try_id().is_some() {
539            let id = InstructionId::new(self.func(), local);
540            let old = self.body.insns[local].name.clone();
541            self.body.register_local_name(
542                self.shared,
543                ValueId::Instruction(id),
544                name.clone(),
545                old.as_deref(),
546            )?;
547        }
548        self.body.insns[local].name = Some(name);
549        Ok(())
550    }
551
552    /// Rename an instruction's result — composite skin over
553    /// [`rename_insn_local`](Self::rename_insn_local).
554    pub(crate) fn rename_insn(
555        &mut self,
556        id: InstructionId,
557        name: Cow<'str, str>,
558    ) -> crate::error::Result<()> {
559        self.rename_insn_local(id.local, name)
560    }
561
562    /// Adds an instruction with an explicit result type, for callers that compute
563    /// the type themselves. Needed by passes that reference a *minted*
564    /// (not-yet-installed) function from a `Map`/`Scan`/`Apply`: the typed
565    /// `push_map`/`push_scan`/`push_apply` read the body function's return type
566    /// through the shared context, where a minted placeholder has no installed
567    /// body — so the pass supplies the type it already knows instead.
568    #[track_caller]
569    pub fn push_mnemonic_with_type(
570        &mut self,
571        mnemonic: Mnemonic,
572        type_id: TypeId,
573    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
574        let local = self.store_insn_with_type(mnemonic, type_id);
575        self.insn_ref(local)
576    }
577
578    /// Body-local instruction-storage core: mint an `Int(size)`-typed
579    /// instruction and append it to the working block.
580    #[track_caller]
581    fn store_insn(&mut self, mnemonic: Mnemonic, size: usize) -> LocalInsnId {
582        let type_id = self.shr().types.get_or_make_int(size);
583        self.store_insn_with_type(mnemonic, type_id)
584    }
585
586    /// Body-local instruction-storage core: appends `mnemonic` (typed `type_id`)
587    /// into the working block's arena, records reverse-uses, honours the address
588    /// and insert-point cursors, and returns the fresh body-local id. Consults no
589    /// registry identity, so it drives an id-less (detached) body.
590    #[track_caller]
591    fn store_insn_with_type(&mut self, mnemonic: Mnemonic, type_id: TypeId) -> LocalInsnId {
592        if self.is_terminated && self.insert_point.is_none() {
593            let block_address = self.body.blocks[self.block].address;
594            if let Some(address) = self.address.or(block_address) {
595                panic!("cannot append instruction to a terminated block at {address:#x}");
596            }
597            panic!("cannot append instruction to a terminated block");
598        }
599
600        let block = self.block;
601        let insn = Instruction::new(type_id, mnemonic);
602        // Inlined `FunctionBody::push_insn`, id-less: append to the arena and
603        // record each operand's reverse-use, keyed by its body-local form.
604        // `Mnemonic::args()` uses a two-element SmallVec. Keep that inline
605        // representation: materializing a `Vec` here allocated once for every
606        // emitted QCode instruction, even for the overwhelmingly common unary
607        // and binary operations.
608        let args = insn.mnemonic().args();
609        let local = self.body.insns.push(insn);
610        for arg in args {
611            self.body.users.entry(arg).or_default().push(local);
612        }
613
614        if let Some(address) = self.address {
615            self.body.insns[local].set_address(address);
616        }
617
618        match self.insert_point {
619            None => {
620                self.body.insns[local].parent = Some(block);
621                self.body.blocks[block].instructions.push(local);
622            }
623            Some(ref mut pos) => {
624                let index = *pos;
625                self.body.insns[local].parent = Some(block);
626                self.body.blocks[block].instructions.insert(index, local);
627                *pos += 1;
628            }
629        }
630
631        local
632    }
633
634    fn get_value(&self, id: ValueId) -> ValueRef<'str, '_, BodyView<'_, 'str>> {
635        // Route through the host's read view so a checked-out builder resolves its
636        // own function's SSA values (which live in the owned function, not the
637        // shared context) correctly.
638        ValueRef::from_view(self.view(), id)
639    }
640
641    /// Localize a qualified operand id for storage in a mnemonic. Skin-only: the
642    /// composite entry points call this to drop the (installed) owning
643    /// `FunctionId` before handing operands to a body-local core.
644    fn loc(&self, id: ValueId) -> LocalValueId {
645        id.localize(self.func())
646    }
647
648    /// Localize a whole operand list (call/branch/tuple/intrinsic args). Skin-only.
649    fn loc_vec(&self, ids: Vec<ValueId>) -> Vec<LocalValueId> {
650        let func = self.func();
651        ids.into_iter().map(|v| v.localize(func)).collect()
652    }
653
654    /// The stored type of `id`, host-routed — composite skin over
655    /// [`lstored_type_of`](Self::lstored_type_of).
656    pub(crate) fn stored_type_of(&self, id: ValueId) -> Option<TypeId> {
657        self.lstored_type_of(self.loc(id))
658    }
659
660    /// The result type of a body-local operand (id-less; see
661    /// [`FunctionBody::local_type_of`]).
662    fn ltype_of(&self, id: LocalValueId) -> TypeId {
663        self.body.local_type_of(self.shared, id)
664    }
665
666    /// The stored type of a body-local operand, or `None` (id-less; see
667    /// [`FunctionBody::local_stored_type_of`]).
668    fn lstored_type_of(&self, id: LocalValueId) -> Option<TypeId> {
669        self.body.local_stored_type_of(self.shared, id)
670    }
671
672    /// The size in bytes of a body-local operand.
673    fn lsize_of(&self, id: LocalValueId) -> usize {
674        self.shr().types.size_of(self.ltype_of(id))
675    }
676
677    /// The address-space provenance of a body-local operand, if any. Only
678    /// varnodes and space-pointer instructions carry one (mirrors
679    /// [`ValueRef::space`]).
680    fn lspace_of(&self, id: LocalValueId) -> Option<SpaceId> {
681        match id {
682            LocalValueId::Varnode(vid) => Some(Varnode::from_id(self.shr(), vid).space().id),
683            LocalValueId::Instruction(local) => {
684                let ty = self.body.insns[local].type_id;
685                self.shr().types.space_of(ty).and_then(|m| m.shared())
686            }
687            _ => None,
688        }
689    }
690
691    pub(crate) fn set_insn_type(&mut self, id: InstructionId, type_id: TypeId) {
692        self.body.insn_mut(id).type_id = type_id;
693    }
694
695    pub(crate) fn constrain_param_size(&mut self, id: BlockParamId, size: usize) {
696        self.body.block_param_mut(id).type_id = self.shared.types.get_or_make_int(size);
697    }
698
699    pub fn set_param_type(&mut self, id: BlockParamId, type_id: TypeId) {
700        self.body.block_param_mut(id).type_id = type_id;
701    }
702
703    pub(crate) fn add_cfg_edge(&mut self, from: BlockId, to: BlockId) {
704        self.body.add_cfg_edge(from, to);
705    }
706
707    /// The common address-space provenance of two pointer-arithmetic operands.
708    ///
709    /// Returns the space carried by whichever operand has one (varnodes carry
710    /// their space; pointer-typed instructions carry theirs), or `None` when the
711    /// two disagree or neither has a space.
712    fn merge_space_ids(&self, lhs: LocalValueId, rhs: LocalValueId) -> Option<SpaceId> {
713        match (self.lspace_of(lhs), self.lspace_of(rhs)) {
714            // `ptr + literal` (exactly one operand carries a space) keeps that
715            // operand's space. `ptr + ptr` is ambiguous — which space does the
716            // sum point into? — so it drops to a plain integer.
717            (Some(space), None) | (None, Some(space)) => Some(space),
718            _ => None,
719        }
720    }
721
722    fn is_literal(&self, id: LocalValueId) -> bool {
723        matches!(id, LocalValueId::Literal(_))
724    }
725
726    fn coerce_literal_size(&mut self, id: LocalValueId, size: usize) -> LocalValueId {
727        let LocalValueId::Literal(lit_id) = id else {
728            return id;
729        };
730        let literal = self.shr().values.literals[lit_id].clone();
731        let current_size = self.shr().types.size_of(literal.type_id);
732        if current_size == size || literal.symbolic.is_some() {
733            return id;
734        }
735        self.shr().get_const(literal.value, size).strip_func()
736    }
737
738    pub fn get_or_make_local_label(&mut self, name: Cow<'str, str>) -> BlockId {
739        if let Some(&local) = self.local_labels.get(name.as_ref()) {
740            return BlockId::new(self.func(), local);
741        }
742        // SLEIGH pcode label names (e.g. `start`, `end`) are only unique within a
743        // single instruction's lowering, but block names are function-scoped.
744        // Deduplicate with a numeric suffix; the `local_labels` map stays keyed by
745        // the original name so within-instruction references still resolve here.
746        // Routed through the host so a checked-out builder mints the block into its
747        // owned function's arena (and registers the name in that function's table).
748        let unique_name = self.body.names.unique(name.clone());
749        let id = self.body.push_block(BasicBlock::detached());
750        self.body
751            .register_local_name(
752                self.shared,
753                ValueId::BasicBlock(id),
754                unique_name.clone(),
755                None,
756            )
757            .expect("name was deduplicated");
758        self.body.block_mut(id).set_name(Some(unique_name));
759        self.local_labels.insert(name, id.local);
760        id
761    }
762
763    /// Resolve a canonical textual `$tempN` token to one body-local temporary
764    /// space, creating it on first use. This is a lowering compatibility seam;
765    /// analysis and lifter producers append their spaces directly to the body.
766    pub fn get_or_make_local_temp_space(&mut self, name: &str) -> LocalMemorySpaceId {
767        let (word_size, addr_size) = {
768            let default = self.shr().space(self.shr().default_space);
769            (default.word_size, default.addr_size)
770        };
771        let body = &mut *self.body;
772        for index in 0..body.temp_spaces.len() {
773            let local = crate::value::LocalTempSpaceId::from(index);
774            if body.temp_spaces[local].name.as_deref() == Some(name) {
775                return LocalMemorySpaceId::Temp(local);
776            }
777        }
778        let id = body.push_temp_space(TempSpace::new(Some(name), word_size, addr_size));
779        LocalMemorySpaceId::Temp(id.local)
780    }
781
782    /// Ensures an operand is not a memory value.
783    /// If the operand is a shared varnode or body-local temporary, emits a load
784    /// and returns its SSA result. Other values are already directly usable.
785    pub fn ensure_local(&mut self, src: ValueId) -> ValueId {
786        let src = self.loc(src);
787        self.ensure_local_local(src).qualify(self.func())
788    }
789
790    /// Body-local core of [`ensure_local`](Self::ensure_local): loads a shared
791    /// varnode or body-local temporary into an SSA value, giving the load a
792    /// related debug name; other operands pass through. Id-free.
793    pub fn ensure_local_local(&mut self, src: LocalValueId) -> LocalValueId {
794        match src {
795            LocalValueId::Varnode(vid) => {
796                let node = Varnode::from_id(self.shr(), vid);
797                let size = node.size();
798                let space = node.space().id;
799                let name = node.name().map(str::to_owned);
800                let id = self.push_load_local::<false>(src, size, space);
801
802                // If the varnode has a name, give the load a related name.
803                if let (Some(name), LocalValueId::Instruction(local)) = (name, id) {
804                    let unique = self.body.names.unique(name.to_lowercase().into());
805                    self.rename_insn_local(local, unique)
806                        .expect("This name was deduplicated");
807                }
808
809                id
810            }
811
812            LocalValueId::Temp(tlocal) => {
813                let (size, space, name) = {
814                    let temp = &self.body.temps[tlocal];
815                    (
816                        temp.size,
817                        LocalMemorySpaceId::Temp(temp.space),
818                        temp.name.clone(),
819                    )
820                };
821                let id = self.push_load_local::<false>(src, size, space);
822                let LocalValueId::Instruction(local) = id else {
823                    unreachable!("non-constant temporary load creates an instruction");
824                };
825
826                if let Some(name) = name {
827                    let unique = self.body.names.unique(Cow::Owned(name.to_lowercase()));
828                    self.rename_insn_local(local, unique)
829                        .expect("temporary load name was deduplicated");
830                }
831
832                id
833            }
834
835            _ => src,
836        }
837    }
838
839    /// Loads a value from memory, given a pointer value. Optionally specify the address space and size of the load.
840    /// If the load space is the special `CONST` space, the pointer is treated as an immediate value rather than an address.
841    ///
842    /// # Panics
843    ///
844    /// Panics if `space` is [`SPACE_CONST`] and `src` is not a `Literal` value.
845    #[track_caller]
846    pub fn push_load<const CHECK_LOCAL: bool>(
847        &mut self,
848        src: ValueId,
849        size: usize,
850        space: impl Into<LocalMemorySpaceId>,
851    ) -> ValueRef<'str, '_, BodyView<'_, 'str>> {
852        let src = self.loc(src);
853        let id = self.push_load_local::<CHECK_LOCAL>(src, size, space);
854        self.get_value(id.qualify(self.func()))
855    }
856
857    /// Body-local core of [`push_load`](Self::push_load). Operand and result are
858    /// body-local; consults no registry identity.
859    #[track_caller]
860    pub fn push_load_local<const CHECK_LOCAL: bool>(
861        &mut self,
862        mut src: LocalValueId,
863        size: usize,
864        space: impl Into<LocalMemorySpaceId>,
865    ) -> LocalValueId {
866        let space = space.into();
867        if CHECK_LOCAL {
868            src = self.ensure_local_local(src);
869        }
870
871        if space == SPACE_CONST {
872            match src {
873                LocalValueId::Literal(lit) => {
874                    let value = self.shr().values.literals[lit].value;
875                    let id = self.shr().get_const(value, size);
876                    id.strip_func()
877                }
878
879                _ => panic!("Expected literal value for CONST space load"),
880            }
881        } else {
882            // Invariant: if the ptr is a varnode, it must live in the same space as the load.
883            // A cross-space access (e.g. *[ram]:8 RSP) requires ensure_local first so that
884            // the varnode's *value* is used as the address, not the varnode itself.
885
886            match src {
887                LocalValueId::Varnode(id) => {
888                    let varnode = Varnode::from_id(self.shr(), id);
889                    if varnode.space().id != space {
890                        panic!(
891                            "push_load: ptr is a varnode but its space {:?} does not match the load space {:?}; \
892                             call ensure_local on the ptr first",
893                            varnode.space().id,
894                            space
895                        );
896                    }
897                }
898
899                LocalValueId::Instruction(local) => {
900                    self.set_insn_space_local(local, space);
901                }
902
903                _ => {}
904            }
905
906            let local = self.store_insn(
907                Mnemonic::Load(Load {
908                    ptr: src,
909                    space,
910                    size,
911                }),
912                size,
913            );
914            LocalValueId::Instruction(local)
915        }
916    }
917
918    // --- Unary Ops ---
919
920    fn push_unop_local(&mut self, op: Unop, src: LocalValueId) -> LocalInsnId {
921        assert!(
922            !matches!(src, LocalValueId::Varnode(_)),
923            "push_unop: varnode operand is not allowed; use ensure_local or &name addressof syntax"
924        );
925        let size = self.lsize_of(src);
926        self.store_insn(Mnemonic::Unop(Unary { op, src }), size)
927    }
928
929    /// Logical NOT of a `bool` value, canonically `src == false`.
930    pub fn push_bool_not(&mut self, src: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
931        let src = self.loc(src);
932        let local = self.push_bool_not_local(src);
933        self.insn_ref(local)
934    }
935
936    /// Body-local sibling of [`push_bool_not`](Self::push_bool_not).
937    pub fn push_bool_not_local(&mut self, src: LocalValueId) -> LocalInsnId {
938        debug_assert!(
939            self.lstored_type_of(src)
940                .is_some_and(|t| self.shr().types.is_bool(t)),
941            "push_bool_not: operand must be bool-typed"
942        );
943        let f = self.shr().get_bool_const(false).strip_func();
944        self.push_binop_local(Binop::Int(IntBinop::Equal), src, f, Some(1))
945    }
946
947    unop_leaf!(
948        /// Creates a bitwise NOT operation on the given value.
949        push_bit_negate,
950        push_bit_negate_local,
951        Unop::IntNot
952    );
953
954    unop_leaf!(
955        /// Creates a negation operation on the given value.
956        push_neg,
957        push_neg_local,
958        Unop::IntNegate
959    );
960
961    unop_leaf!(
962        /// Creates a float negation operation on the given value.
963        push_fneg,
964        push_fneg_local,
965        Unop::FloatNegate
966    );
967
968    /// Creates any integer or float binary operation. Comparisons yield a
969    /// one-byte result; the rest take the operands' width.
970    pub fn push_binop(
971        &mut self,
972        op: Binop,
973        lhs: ValueId,
974        rhs: ValueId,
975    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
976        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
977        let size = op.is_comparison().then_some(1);
978        let local = self.push_binop_local(op, lhs, rhs, size);
979        self.insn_ref(local)
980    }
981
982    fn push_binop_local(
983        &mut self,
984        op: Binop,
985        lhs: LocalValueId,
986        rhs: LocalValueId,
987        size: Option<usize>,
988    ) -> LocalInsnId {
989        let lhs_size = self.lsize_of(lhs);
990        let rhs_size = self.lsize_of(rhs);
991        let operand_size = match (
992            lhs_size == rhs_size,
993            self.is_literal(lhs),
994            self.is_literal(rhs),
995        ) {
996            (true, _, _) => lhs_size,
997            (false, true, false) => rhs_size,
998            (false, false, true) => lhs_size,
999            (false, true, true) => lhs_size.max(rhs_size),
1000            // Two non-literal operands of differing size cannot be repaired here
1001            // without choosing a semantic cast. Lifters should emit explicit
1002            // zext/sext/range operations before constructing the binop.
1003            (false, false, false) => lhs_size,
1004        };
1005        let lhs = self.coerce_literal_size(lhs, operand_size);
1006        let rhs = self.coerce_literal_size(rhs, operand_size);
1007        assert_eq!(
1008            self.lsize_of(lhs),
1009            self.lsize_of(rhs),
1010            "push_binop: operands must have equal size; emit an explicit cast first"
1011        );
1012
1013        // Determine result type using the TypeManager's arithmetic rules.
1014        let result_type = {
1015            let lhs_type = self.ltype_of(lhs);
1016            let rhs_type = self.ltype_of(rhs);
1017            self.shr().types.binop_result(lhs_type, op, rhs_type)
1018        };
1019
1020        // Comparisons always override the result size to 1.
1021        let result_type = if let Some(forced_size) = size {
1022            let current_size = self.shr().types.size_of(result_type);
1023            if forced_size != current_size {
1024                self.shr().types.get_or_make_int(forced_size)
1025            } else {
1026                result_type
1027            }
1028        } else {
1029            result_type
1030        };
1031
1032        // Restore address-space provenance for pointer arithmetic. When the
1033        // result is not already a space pointer (e.g. a `StackAddress` produced
1034        // from a stack-base operand), `Add`/`Sub` inherit the space of whichever
1035        // operand carries one — so `&A + k` points into `A`'s space. Register
1036        // spaces are excluded (pointer arithmetic is not allowed there).
1037        let result_type = if self.shr().types.space_of(result_type).is_none()
1038            && matches!(op, Binop::Int(IntBinop::Add | IntBinop::Sub))
1039        {
1040            match self.merge_space_ids(lhs, rhs) {
1041                Some(space)
1042                    if !matches!(Space::from_id(self.shr(), space).ty, SpaceType::Register) =>
1043                {
1044                    let size = self.shr().types.size_of(result_type);
1045                    self.shr().types.get_or_make_space_address(size, space)
1046                }
1047                _ => result_type,
1048            }
1049        } else {
1050            result_type
1051        };
1052
1053        // Rule 2: `ptr + ptr` — both operands carry a (non-register) space — is
1054        // ambiguous (into which space does the sum point?), so `Add`/`Sub` drops
1055        // the result to a plain integer. `binop_result` would otherwise propagate
1056        // the left operand's space unconditionally.
1057        let result_type = if matches!(op, Binop::Int(IntBinop::Add | IntBinop::Sub))
1058            && self.shr().types.space_of(result_type).is_some()
1059        {
1060            let spaced = |b: &Self, v| {
1061                b.lspace_of(v)
1062                    .is_some_and(|s| !matches!(Space::from_id(b.shr(), s).ty, SpaceType::Register))
1063            };
1064            if spaced(self, lhs) && spaced(self, rhs) {
1065                let size = self.shr().types.size_of(result_type);
1066                self.shr().types.get_or_make_int(size)
1067            } else {
1068                result_type
1069            }
1070        } else {
1071            result_type
1072        };
1073
1074        self.store_insn_with_type(Mnemonic::Binop(Binary { op, lhs, rhs }), result_type)
1075    }
1076
1077    // --- Arithmetic ---
1078
1079    binop_leaf!(push_mul, push_mul_local, Binop::Int(IntBinop::Mul), None);
1080    binop_leaf!(push_div, push_div_local, Binop::Int(IntBinop::Div), None);
1081    binop_leaf!(push_sdiv, push_sdiv_local, Binop::Int(IntBinop::Sdiv), None);
1082    binop_leaf!(push_mod, push_mod_local, Binop::Int(IntBinop::Rem), None);
1083    binop_leaf!(push_smod, push_smod_local, Binop::Int(IntBinop::Srem), None);
1084    binop_leaf!(push_add, push_add_local, Binop::Int(IntBinop::Add), None);
1085    binop_leaf!(push_sub, push_sub_local, Binop::Int(IntBinop::Sub), None);
1086
1087    // --- Float Arithmetic ---
1088
1089    binop_leaf!(
1090        push_fdiv,
1091        push_fdiv_local,
1092        Binop::Float(FloatBinop::Div),
1093        None
1094    );
1095    binop_leaf!(
1096        push_fmul,
1097        push_fmul_local,
1098        Binop::Float(FloatBinop::Mul),
1099        None
1100    );
1101    binop_leaf!(
1102        push_fadd,
1103        push_fadd_local,
1104        Binop::Float(FloatBinop::Add),
1105        None
1106    );
1107    binop_leaf!(
1108        push_fsub,
1109        push_fsub_local,
1110        Binop::Float(FloatBinop::Sub),
1111        None
1112    );
1113
1114    // --- Shifts ---
1115
1116    binop_leaf!(
1117        push_shl,
1118        push_shl_local,
1119        Binop::Int(IntBinop::ShiftLeft),
1120        None
1121    );
1122    binop_leaf!(
1123        push_shr,
1124        push_shr_local,
1125        Binop::Int(IntBinop::ShiftRight),
1126        None
1127    );
1128    binop_leaf!(
1129        push_sshr,
1130        push_sshr_local,
1131        Binop::Int(IntBinop::SShiftRight),
1132        None
1133    );
1134
1135    // --- Integer Comparisons ---
1136    // Greater-than variants swap operands of the less-than op.
1137
1138    cmp_pair!(
1139        push_slt,
1140        push_slt_local,
1141        push_sgt,
1142        push_sgt_local,
1143        Binop::Int(IntBinop::SLess)
1144    );
1145    cmp_pair!(
1146        push_sle,
1147        push_sle_local,
1148        push_sge,
1149        push_sge_local,
1150        Binop::Int(IntBinop::SLessEqual)
1151    );
1152    cmp_pair!(
1153        push_lt,
1154        push_lt_local,
1155        push_gt,
1156        push_gt_local,
1157        Binop::Int(IntBinop::Less)
1158    );
1159    cmp_pair!(
1160        push_le,
1161        push_le_local,
1162        push_ge,
1163        push_ge_local,
1164        Binop::Int(IntBinop::LessEqual)
1165    );
1166
1167    // --- Float Comparisons ---
1168
1169    cmp_pair!(
1170        push_flt,
1171        push_flt_local,
1172        push_fgt,
1173        push_fgt_local,
1174        Binop::Float(FloatBinop::Less)
1175    );
1176    cmp_pair!(
1177        push_fle,
1178        push_fle_local,
1179        push_fge,
1180        push_fge_local,
1181        Binop::Float(FloatBinop::LessEqual)
1182    );
1183
1184    // --- Integer Equality ---
1185
1186    binop_leaf!(push_eq, push_eq_local, Binop::Int(IntBinop::Equal), Some(1));
1187    binop_leaf!(
1188        push_ne,
1189        push_ne_local,
1190        Binop::Int(IntBinop::NotEqual),
1191        Some(1)
1192    );
1193    binop_leaf!(
1194        push_feq,
1195        push_feq_local,
1196        Binop::Float(FloatBinop::Equal),
1197        Some(1)
1198    );
1199    binop_leaf!(
1200        push_fne,
1201        push_fne_local,
1202        Binop::Float(FloatBinop::NotEqual),
1203        Some(1)
1204    );
1205
1206    // --- Bitwise ---
1207
1208    /// Logical XOR of two `bool` operands — a bitwise `Xor` over `bool`, which
1209    /// yields `bool` (exact on the `{0,1}` domain).
1210    pub fn push_bool_xor(
1211        &mut self,
1212        lhs: ValueId,
1213        rhs: ValueId,
1214    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1215        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1216        let local = self.push_bool_xor_local(lhs, rhs);
1217        self.insn_ref(local)
1218    }
1219
1220    /// Body-local sibling of [`push_bool_xor`](Self::push_bool_xor).
1221    pub fn push_bool_xor_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1222        debug_assert!(
1223            self.both_bool(lhs, rhs),
1224            "push_bool_xor: operands must be bool"
1225        );
1226        self.push_binop_local(Binop::Int(IntBinop::Xor), lhs, rhs, None)
1227    }
1228
1229    /// Logical AND of two `bool` operands (bitwise `And` over `bool`).
1230    pub fn push_bool_and(
1231        &mut self,
1232        lhs: ValueId,
1233        rhs: ValueId,
1234    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1235        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1236        let local = self.push_bool_and_local(lhs, rhs);
1237        self.insn_ref(local)
1238    }
1239
1240    /// Body-local sibling of [`push_bool_and`](Self::push_bool_and).
1241    pub fn push_bool_and_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1242        debug_assert!(
1243            self.both_bool(lhs, rhs),
1244            "push_bool_and: operands must be bool"
1245        );
1246        self.push_binop_local(Binop::Int(IntBinop::And), lhs, rhs, None)
1247    }
1248
1249    /// Logical OR of two `bool` operands (bitwise `Or` over `bool`).
1250    pub fn push_bool_or(
1251        &mut self,
1252        lhs: ValueId,
1253        rhs: ValueId,
1254    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1255        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1256        let local = self.push_bool_or_local(lhs, rhs);
1257        self.insn_ref(local)
1258    }
1259
1260    /// Body-local sibling of [`push_bool_or`](Self::push_bool_or).
1261    pub fn push_bool_or_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1262        debug_assert!(
1263            self.both_bool(lhs, rhs),
1264            "push_bool_or: operands must be bool"
1265        );
1266        self.push_binop_local(Binop::Int(IntBinop::Or), lhs, rhs, None)
1267    }
1268
1269    /// Whether both operands carry the `bool` type (a `debug_assert` guard).
1270    fn both_bool(&self, lhs: LocalValueId, rhs: LocalValueId) -> bool {
1271        let is_bool = |v: LocalValueId| {
1272            self.lstored_type_of(v)
1273                .is_some_and(|t| self.shr().types.is_bool(t))
1274        };
1275        is_bool(lhs) && is_bool(rhs)
1276    }
1277
1278    binop_leaf!(
1279        push_bit_xor,
1280        push_bit_xor_local,
1281        Binop::Int(IntBinop::Xor),
1282        None
1283    );
1284    binop_leaf!(
1285        push_bit_or,
1286        push_bit_or_local,
1287        Binop::Int(IntBinop::Or),
1288        None
1289    );
1290    binop_leaf!(
1291        push_bit_and,
1292        push_bit_and_local,
1293        Binop::Int(IntBinop::And),
1294        None
1295    );
1296
1297    // --- Extensions & Conversions ---
1298
1299    pub fn push_is_nan(&mut self, src: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1300        let src = self.loc(src);
1301        let local = self.push_is_nan_local(src);
1302        self.insn_ref(local)
1303    }
1304
1305    /// Body-local sibling of [`push_is_nan`](Self::push_is_nan).
1306    pub fn push_is_nan_local(&mut self, src: LocalValueId) -> LocalInsnId {
1307        assert!(
1308            !matches!(src, LocalValueId::Varnode(_)),
1309            "push_is_nan: varnode operand not allowed"
1310        );
1311        self.store_insn(Mnemonic::IsFloatNaN(IsFloatNaN { src }), 1)
1312    }
1313
1314    unop_leaf!(push_abs, push_abs_local, Unop::FloatAbs);
1315    unop_leaf!(push_sqrt, push_sqrt_local, Unop::FloatSqrt);
1316    unop_leaf!(push_floor, push_floor_local, Unop::FloatFloor);
1317    unop_leaf!(push_ceil, push_ceil_local, Unop::FloatCeil);
1318    unop_leaf!(push_round, push_round_local, Unop::FloatRound);
1319
1320    conv_leaf!(
1321        push_int_to_float,
1322        push_int_to_float_local,
1323        "push_int_to_float: varnode operand not allowed",
1324        IntToFloat
1325    );
1326    conv_leaf!(
1327        push_float_to_float,
1328        push_float_to_float_local,
1329        "push_float_to_float: varnode operand not allowed",
1330        FloatToFloat
1331    );
1332    conv_leaf!(
1333        push_trunc,
1334        push_trunc_local,
1335        "push_trunc: varnode operand not allowed",
1336        FloatToInt
1337    );
1338    conv_leaf!(
1339        push_zext,
1340        push_zext_local,
1341        "push_zext: varnode operand not allowed",
1342        Zext
1343    );
1344    conv_leaf!(
1345        push_sext,
1346        push_sext_local,
1347        "push_sext: varnode operand not allowed",
1348        Sext
1349    );
1350
1351    /// Builds an aggregate value from `fields` using default field names
1352    /// (`field1`, `field2`, ...). The result type is the
1353    /// [`Aggregate`](crate::types::TypeRepr::Aggregate) of the named fields'
1354    /// types.
1355    pub fn push_tuple(
1356        &mut self,
1357        fields: Vec<ValueId>,
1358    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1359        let fields = self.loc_vec(fields);
1360        let local = self.push_tuple_local(fields);
1361        self.insn_ref(local)
1362    }
1363
1364    /// Body-local sibling of [`push_tuple`](Self::push_tuple).
1365    pub fn push_tuple_local(&mut self, fields: Vec<LocalValueId>) -> LocalInsnId {
1366        let named_fields = fields
1367            .into_iter()
1368            .enumerate()
1369            .map(|(i, value)| (format!("field{}", i + 1), value))
1370            .collect();
1371        self.push_named_tuple_local(named_fields)
1372    }
1373
1374    /// Builds an aggregate value from ordered named fields.
1375    pub fn push_named_tuple(
1376        &mut self,
1377        fields: Vec<(String, ValueId)>,
1378    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1379        let fields = fields
1380            .into_iter()
1381            .map(|(name, v)| (name, self.loc(v)))
1382            .collect();
1383        let local = self.push_named_tuple_local(fields);
1384        self.insn_ref(local)
1385    }
1386
1387    /// Body-local sibling of [`push_named_tuple`](Self::push_named_tuple).
1388    pub fn push_named_tuple_local(&mut self, fields: Vec<(String, LocalValueId)>) -> LocalInsnId {
1389        let field_types: Vec<TypeId> = fields.iter().map(|(_, f)| self.ltype_of(*f)).collect();
1390        let aggregate_fields = fields
1391            .iter()
1392            .zip(field_types)
1393            .map(|((name, _), type_id)| AggregateField::new(name.clone(), type_id))
1394            .collect();
1395        let ty = self
1396            .shr()
1397            .types
1398            .get_or_make_named_aggregate(aggregate_fields);
1399        self.push_named_tuple_local_with_type(fields, ty)
1400    }
1401
1402    /// Build a named tuple using an explicitly selected aggregate-like type.
1403    /// Used for nominal function-return records whose identity must not be
1404    /// structurally interned by [`push_named_tuple_local`](Self::push_named_tuple_local).
1405    pub fn push_named_tuple_with_type(
1406        &mut self,
1407        fields: Vec<(String, ValueId)>,
1408        ty: TypeId,
1409    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1410        let fields = fields
1411            .into_iter()
1412            .map(|(name, value)| (name, self.loc(value)))
1413            .collect();
1414        let local = self.push_named_tuple_local_with_type(fields, ty);
1415        self.insn_ref(local)
1416    }
1417
1418    /// Body-local sibling of [`push_named_tuple_with_type`](Self::push_named_tuple_with_type).
1419    pub fn push_named_tuple_local_with_type(
1420        &mut self,
1421        fields: Vec<(String, LocalValueId)>,
1422        ty: TypeId,
1423    ) -> LocalInsnId {
1424        debug_assert_eq!(
1425            self.shr().types.aggregate_fields(ty).map(<[_]>::len),
1426            Some(fields.len()),
1427            "explicit tuple type must declare every tuple field"
1428        );
1429        debug_assert!(fields.iter().enumerate().all(|(index, (name, value))| {
1430            self.shr()
1431                .types
1432                .aggregate_fields(ty)
1433                .and_then(|declared| declared.get(index))
1434                .is_some_and(|declared| {
1435                    declared.name == *name && declared.type_id == self.ltype_of(*value)
1436                })
1437        }));
1438        let values = fields.into_iter().map(|(_, value)| value).collect();
1439        self.store_insn_with_type(Mnemonic::Tuple(Tuple { fields: values }), ty)
1440    }
1441
1442    /// Projects field `index` out of the aggregate value `agg`. The result type
1443    /// is that field's type. Panics if `agg` is not an aggregate with that field.
1444    pub fn push_extract(
1445        &mut self,
1446        agg: ValueId,
1447        index: usize,
1448    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1449        let agg = self.loc(agg);
1450        let local = self.push_extract_local(agg, index);
1451        self.insn_ref(local)
1452    }
1453
1454    /// Body-local sibling of [`push_extract`](Self::push_extract).
1455    pub fn push_extract_local(&mut self, agg: LocalValueId, index: usize) -> LocalInsnId {
1456        let agg_ty = self.ltype_of(agg);
1457        let ty = self
1458            .shr()
1459            .types
1460            .field_type(agg_ty, index)
1461            .expect("push_extract: agg is not an aggregate with that field index");
1462        self.store_insn_with_type(Mnemonic::Extract(Extract { agg, index }), ty)
1463    }
1464
1465    /// Builds a total element-wise map `out[i] = body(src[i], captures…)` over the
1466    /// array value `src`. The body is **unary** in the element (index-aware bodies
1467    /// take an [`enumerate`](crate::value::insn::Intrinsic) tuple as that element);
1468    /// `body` is a function symbol, not an operand. Soundness of the body (pure,
1469    /// element-local) is the recognizer's obligation; the builder only wires the
1470    /// value graph.
1471    ///
1472    /// The result is `[U; N]` where `N` is `src`'s element count and `U` is the
1473    /// body's return type — which need not equal the input element type (e.g. a
1474    /// map over `enumerate(arr)` consumes tuples but returns bare elements). When
1475    /// the body is a bare symbol with no return (or `src` is not an array), the
1476    /// result falls back to `src`'s type.
1477    pub fn push_map(
1478        &mut self,
1479        body: impl Into<Callee>,
1480        src: ValueId,
1481        captures: Vec<ValueId>,
1482    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1483        let (src, captures) = (self.loc(src), self.loc_vec(captures));
1484        let local = self.push_map_local(body, src, captures);
1485        self.insn_ref(local)
1486    }
1487
1488    /// Body-local sibling of [`push_map`](Self::push_map).
1489    pub fn push_map_local(
1490        &mut self,
1491        body: impl Into<Callee>,
1492        src: LocalValueId,
1493        captures: Vec<LocalValueId>,
1494    ) -> LocalInsnId {
1495        let body = body.into();
1496        let src_ty = self.ltype_of(src);
1497        // `map` preserves the source's sequence kind: an array maps to an array,
1498        // a list (e.g. `take_while`'s result) maps to a list of the same bound.
1499        let seq = self.shr().types.seq_of(src_ty);
1500        let ret_ty = body.real().and_then(|body| self.map_body_return_type(body));
1501        let ty = match (seq, ret_ty) {
1502            (Some((_, len, is_list)), Some(rt)) => {
1503                self.shr().types.get_or_make_seq(rt, len, is_list)
1504            }
1505            _ => src_ty,
1506        };
1507        self.push_map_typed_local(body, src, captures, ty)
1508    }
1509
1510    /// Builds a map with an explicitly prepared result type. Use this when the
1511    /// body is foreign to this Builder and its body-derived return type is not
1512    /// part of the published function interface.
1513    pub fn push_map_typed(
1514        &mut self,
1515        body: impl Into<Callee>,
1516        src: ValueId,
1517        captures: Vec<ValueId>,
1518        result_type: TypeId,
1519    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1520        let (src, captures) = (self.loc(src), self.loc_vec(captures));
1521        let local = self.push_map_typed_local(body, src, captures, result_type);
1522        self.insn_ref(local)
1523    }
1524
1525    /// Body-local sibling of [`push_map_typed`](Self::push_map_typed).
1526    pub fn push_map_typed_local(
1527        &mut self,
1528        body: impl Into<Callee>,
1529        src: LocalValueId,
1530        captures: Vec<LocalValueId>,
1531        result_type: TypeId,
1532    ) -> LocalInsnId {
1533        self.store_insn_with_type(
1534            Mnemonic::Map(Map {
1535                body: body.into(),
1536                src,
1537                captures,
1538            }),
1539            result_type,
1540        )
1541    }
1542
1543    /// Builds a total left-scan `out[i] = body(acc_i, src[i], captures…)` with
1544    /// `acc_0 = init` over the array value `src` (see [`Scan`]). The body is
1545    /// **binary** in `(accumulator, element)` — index-aware bodies take an
1546    /// [`enumerate`](crate::value::insn::Intrinsic) tuple as the element; `body`
1547    /// is a function symbol, not an operand. Soundness of the body (pure, with the
1548    /// accumulator threaded only through the scan) is the recognizer's obligation.
1549    ///
1550    /// The result is `[U; N]` where `N` is `src`'s element count and `U` is the
1551    /// body's return type (also the accumulator type). When the body is a bare
1552    /// symbol with no return (or `src` is not an array), the result falls back to
1553    /// `src`'s type.
1554    pub fn push_scan(
1555        &mut self,
1556        body: impl Into<Callee>,
1557        init: ValueId,
1558        src: ValueId,
1559        captures: Vec<ValueId>,
1560    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1561        let (init, src, captures) = (self.loc(init), self.loc(src), self.loc_vec(captures));
1562        let local = self.push_scan_local(body, init, src, captures);
1563        self.insn_ref(local)
1564    }
1565
1566    /// Body-local sibling of [`push_scan`](Self::push_scan).
1567    pub fn push_scan_local(
1568        &mut self,
1569        body: impl Into<Callee>,
1570        init: LocalValueId,
1571        src: LocalValueId,
1572        captures: Vec<LocalValueId>,
1573    ) -> LocalInsnId {
1574        let body = body.into();
1575        let src_ty = self.ltype_of(src);
1576        // Like `map`, a scan preserves the source's sequence kind and takes its
1577        // element type from the body's return type (the accumulator type).
1578        let seq = self.shr().types.seq_of(src_ty);
1579        let ret_ty = body.real().and_then(|body| self.map_body_return_type(body));
1580        let ty = match (seq, ret_ty) {
1581            (Some((_, len, is_list)), Some(rt)) => {
1582                self.shr().types.get_or_make_seq(rt, len, is_list)
1583            }
1584            _ => src_ty,
1585        };
1586        self.push_scan_typed_local(body, init, src, captures, ty)
1587    }
1588
1589    /// Builds a scan with an explicitly prepared result type. This is the
1590    /// foreign-body counterpart to [`push_map_typed`](Self::push_map_typed).
1591    pub fn push_scan_typed(
1592        &mut self,
1593        body: impl Into<Callee>,
1594        init: ValueId,
1595        src: ValueId,
1596        captures: Vec<ValueId>,
1597        result_type: TypeId,
1598    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1599        let (init, src, captures) = (self.loc(init), self.loc(src), self.loc_vec(captures));
1600        let local = self.push_scan_typed_local(body, init, src, captures, result_type);
1601        self.insn_ref(local)
1602    }
1603
1604    /// Body-local sibling of [`push_scan_typed`](Self::push_scan_typed).
1605    pub fn push_scan_typed_local(
1606        &mut self,
1607        body: impl Into<Callee>,
1608        init: LocalValueId,
1609        src: LocalValueId,
1610        captures: Vec<LocalValueId>,
1611        result_type: TypeId,
1612    ) -> LocalInsnId {
1613        self.store_insn_with_type(
1614            Mnemonic::Scan(Scan {
1615                body: body.into(),
1616                init,
1617                src,
1618                captures,
1619            }),
1620            result_type,
1621        )
1622    }
1623
1624    /// Builds a value-level application of a pure lambda function. Unlike
1625    /// [`push_call`](Self::push_call), this is an ordinary SSA instruction and
1626    /// does not terminate the current block.
1627    pub fn push_apply(
1628        &mut self,
1629        target: impl Into<Callee>,
1630        args: Vec<ValueId>,
1631    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1632        let args = self.loc_vec(args);
1633        let local = self.push_apply_local(target, args);
1634        self.insn_ref(local)
1635    }
1636
1637    /// Body-local sibling of [`push_apply`](Self::push_apply).
1638    pub fn push_apply_local(
1639        &mut self,
1640        target: impl Into<Callee>,
1641        args: Vec<LocalValueId>,
1642    ) -> LocalInsnId {
1643        let target = target.into();
1644        let ty = target
1645            .real()
1646            .and_then(|target| self.lambda_return_type(target))
1647            .unwrap_or_else(|| {
1648                args.first()
1649                    .map(|&arg| self.ltype_of(arg))
1650                    .unwrap_or_else(|| self.shr().types.get_or_make_int(0))
1651            });
1652        self.store_insn_with_type(Mnemonic::Apply(Apply { target, args }), ty)
1653    }
1654
1655    /// The type of the value returned by `body`'s first `Return`, or `None` if
1656    /// `body` is not this (self) body, has no root, or returns nothing — used to
1657    /// size a [`push_map`] result. Id-less: reads this body's own arenas.
1658    fn map_body_return_type(&self, body: FunctionId) -> Option<TypeId> {
1659        if self.body.try_id() != Some(body) {
1660            return None;
1661        }
1662        let root = self.body.root_id()?;
1663        self.body.blocks[root].instructions.iter().find_map(|&i| {
1664            match self.body.insns[i].mnemonic() {
1665                Mnemonic::Return(r) => r.value.and_then(|v| self.lstored_type_of(v)),
1666                _ => None,
1667            }
1668        })
1669    }
1670
1671    /// The type of the first value returned by a lambda body (this body). Id-less.
1672    fn lambda_return_type(&self, body: FunctionId) -> Option<TypeId> {
1673        if self.body.try_id() != Some(body) {
1674            return None;
1675        }
1676        self.body
1677            .roster
1678            .iter()
1679            .flat_map(|&b| self.body.blocks[b].instructions.iter().copied())
1680            .find_map(|i| match self.body.insns[i].mnemonic() {
1681                Mnemonic::ReturnValue(r) => self.lstored_type_of(r.value),
1682                _ => None,
1683            })
1684    }
1685
1686    /// Computes the address of the field at byte `offset` of the struct that
1687    /// `base` points at: `gep(base, offset)`. `base` must have a
1688    /// [`StructPointer`](crate::types::TypeRepr::StructPointer) type whose
1689    /// pointee has a field at exactly `offset`. The result type is a pointer
1690    /// (same width as `base`) to that field's type. Panics otherwise.
1691    pub fn push_gep(
1692        &mut self,
1693        base: ValueId,
1694        offset: usize,
1695    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1696        let base = self.loc(base);
1697        let local = self.push_gep_local(base, offset);
1698        self.insn_ref(local)
1699    }
1700
1701    /// Body-local sibling of [`push_gep`](Self::push_gep).
1702    pub fn push_gep_local(&mut self, base: LocalValueId, offset: usize) -> LocalInsnId {
1703        let base_ty = self.ltype_of(base);
1704        let types = &self.shr().types;
1705        let ptr_width = types.size_of(base_ty);
1706        let pointee = types
1707            .pointee_of(base_ty)
1708            .expect("push_gep: base is not a struct pointer");
1709        let field_ty = types
1710            .field_by_offset(pointee, offset)
1711            .map(|(_, field)| field.type_id)
1712            .expect("push_gep: no field at that offset in the pointee struct");
1713        let ty = self
1714            .shr()
1715            .types
1716            .get_or_make_struct_pointer(ptr_width, field_ty);
1717        self.store_insn_with_type(Mnemonic::Gep(Gep { base, offset }), ty)
1718    }
1719
1720    /// Like [`push_gep`](Builder::push_gep) but selects the field by name,
1721    /// resolving it to a byte offset via the pointee struct of `base`. Panics if
1722    /// `base` is not a struct pointer or has no field of that name.
1723    pub fn push_gep_field(
1724        &mut self,
1725        base: ValueId,
1726        name: &str,
1727    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1728        let base = self.loc(base);
1729        let local = self.push_gep_field_local(base, name);
1730        self.insn_ref(local)
1731    }
1732
1733    /// Body-local sibling of [`push_gep_field`](Self::push_gep_field).
1734    pub fn push_gep_field_local(&mut self, base: LocalValueId, name: &str) -> LocalInsnId {
1735        let base_ty = self.ltype_of(base);
1736        let types = &self.shr().types;
1737        let pointee = types
1738            .pointee_of(base_ty)
1739            .expect("push_gep_field: base is not a struct pointer");
1740        let offset = types
1741            .aggregate_fields(pointee)
1742            .and_then(|fields| fields.iter().find(|f| f.name == name))
1743            .map(|f| f.offset)
1744            .expect("push_gep_field: pointee struct has no field of that name");
1745        self.push_gep_local(base, offset)
1746    }
1747
1748    pub fn push_popcount(
1749        &mut self,
1750        src: ValueId,
1751        size: usize,
1752    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1753        let src = self.loc(src);
1754        let local = self.push_popcount_local(src, size);
1755        self.insn_ref(local)
1756    }
1757
1758    /// Body-local sibling of [`push_popcount`](Self::push_popcount).
1759    pub fn push_popcount_local(&mut self, src: LocalValueId, size: usize) -> LocalInsnId {
1760        assert!(
1761            !matches!(src, LocalValueId::Varnode(_)),
1762            "push_popcount: varnode operand not allowed"
1763        );
1764        self.store_insn(Mnemonic::PopCount(PopCount { src }), size)
1765    }
1766
1767    pub fn push_lzcount(
1768        &mut self,
1769        src: ValueId,
1770        size: usize,
1771    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1772        let src = self.loc(src);
1773        let local = self.push_lzcount_local(src, size);
1774        self.insn_ref(local)
1775    }
1776
1777    /// Body-local sibling of [`push_lzcount`](Self::push_lzcount).
1778    pub fn push_lzcount_local(&mut self, src: LocalValueId, size: usize) -> LocalInsnId {
1779        assert!(
1780            !matches!(src, LocalValueId::Varnode(_)),
1781            "push_lzcount: varnode operand not allowed"
1782        );
1783        self.store_insn(Mnemonic::LzCount(LzCount { src }), size)
1784    }
1785
1786    pub fn push_carry(
1787        &mut self,
1788        lhs: ValueId,
1789        rhs: ValueId,
1790    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1791        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1792        let local = self.push_carry_local(lhs, rhs);
1793        self.insn_ref(local)
1794    }
1795
1796    /// Body-local sibling of [`push_carry`](Self::push_carry).
1797    pub fn push_carry_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1798        assert!(
1799            !matches!(lhs, LocalValueId::Varnode(_)) && !matches!(rhs, LocalValueId::Varnode(_)),
1800            "push_carry: varnode operand not allowed"
1801        );
1802        self.store_insn(Mnemonic::Carry(Carry { lhs, rhs }), 1)
1803    }
1804
1805    pub fn push_scarry(
1806        &mut self,
1807        lhs: ValueId,
1808        rhs: ValueId,
1809    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1810        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1811        let local = self.push_scarry_local(lhs, rhs);
1812        self.insn_ref(local)
1813    }
1814
1815    /// Body-local sibling of [`push_scarry`](Self::push_scarry).
1816    pub fn push_scarry_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1817        assert!(
1818            !matches!(lhs, LocalValueId::Varnode(_)) && !matches!(rhs, LocalValueId::Varnode(_)),
1819            "push_scarry: varnode operand not allowed"
1820        );
1821        self.store_insn(Mnemonic::SCarry(SCarry { lhs, rhs }), 1)
1822    }
1823
1824    pub fn push_sborrow(
1825        &mut self,
1826        lhs: ValueId,
1827        rhs: ValueId,
1828    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1829        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1830        let local = self.push_sborrow_local(lhs, rhs);
1831        self.insn_ref(local)
1832    }
1833
1834    /// Body-local sibling of [`push_sborrow`](Self::push_sborrow).
1835    pub fn push_sborrow_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1836        assert!(
1837            !matches!(lhs, LocalValueId::Varnode(_)) && !matches!(rhs, LocalValueId::Varnode(_)),
1838            "push_sborrow: varnode operand not allowed"
1839        );
1840        self.store_insn(Mnemonic::SBorrow(SBorrow { lhs, rhs }), 1)
1841    }
1842
1843    pub fn push_pcode_op(
1844        &mut self,
1845        id: PCodeOpId,
1846        args: Vec<ValueId>,
1847        dst: Option<ValueId>,
1848        size: usize,
1849    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1850        let args = self.loc_vec(args);
1851        let dst = dst.map(|d| self.loc(d));
1852        let local = self.push_pcode_op_local(id, args, dst, size);
1853        self.insn_ref(local)
1854    }
1855
1856    /// Body-local sibling of [`push_pcode_op`](Self::push_pcode_op).
1857    pub fn push_pcode_op_local(
1858        &mut self,
1859        id: PCodeOpId,
1860        args: Vec<LocalValueId>,
1861        dst: Option<LocalValueId>,
1862        size: usize,
1863    ) -> LocalInsnId {
1864        let args = args
1865            .into_iter()
1866            .map(|arg| self.ensure_local_local(arg))
1867            .collect::<Vec<_>>();
1868
1869        self.store_insn(Mnemonic::PCodeOp(PCodeOp { id, args, dst }), size)
1870    }
1871
1872    /// Creates a pure intrinsic instruction (e.g. `rol`, `ror`, `enumerate`).
1873    ///
1874    /// Validates the operand count against the intrinsic's declared arity and
1875    /// types the node via the intrinsic's
1876    /// [`result_type`](crate::value::insn::Intrinsic::result_type)
1877    /// rule, so the result carries its full type (not just a width) — an array
1878    /// or aggregate result is projectable. Panics on an arity mismatch.
1879    #[track_caller]
1880    pub fn push_intrinsic(
1881        &mut self,
1882        id: IntrinsicId,
1883        args: Vec<ValueId>,
1884    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1885        let args = self.loc_vec(args);
1886        let local = self.push_intrinsic_local(id, args);
1887        self.insn_ref(local)
1888    }
1889
1890    /// Body-local sibling of [`push_intrinsic`](Self::push_intrinsic).
1891    #[track_caller]
1892    pub fn push_intrinsic_local(
1893        &mut self,
1894        id: IntrinsicId,
1895        args: Vec<LocalValueId>,
1896    ) -> LocalInsnId {
1897        let desc = id.desc();
1898        assert_eq!(
1899            args.len(),
1900            desc.arity(),
1901            "intrinsic `{}` expects {} args, got {}",
1902            desc.name(),
1903            desc.arity(),
1904            args.len()
1905        );
1906
1907        let args = args
1908            .into_iter()
1909            .map(|arg| self.ensure_local_local(arg))
1910            .collect::<Vec<_>>();
1911
1912        let arg_types = args
1913            .iter()
1914            .map(|&arg| self.ltype_of(arg))
1915            .collect::<Vec<_>>();
1916        let type_id = desc.result_type(&self.shr().types, &arg_types);
1917
1918        self.store_insn_with_type(Mnemonic::Intrinsic(IntrinsicApp { id, args }), type_id)
1919    }
1920
1921    // --- Loads & Stores ---
1922
1923    /// Creates a copy instruction from `src` to `dst`.
1924    /// Note that `dst` must already exist as a [`Value`](crate::value::Value) in the current context, and this will not create a new temporary value.
1925    /// If `dst` is a varnode, we aren't allowed to write to it, this is a store operation
1926    /// If `src` is a varnode, we need to read from it first, then write to dst
1927    /// For values wider than 64 bits (e.g. XMM/YMM/ZMM registers), emits one store per 64-bit lane.
1928    pub fn push_copy(
1929        &mut self,
1930        src: ValueId,
1931        dst: impl Into<ValueId>,
1932    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1933        let src = self.loc(src);
1934        let dst = self.loc(dst.into());
1935        let local = self.push_copy_local(src, dst);
1936        self.insn_ref(local)
1937    }
1938
1939    /// Body-local sibling of [`push_copy`](Self::push_copy).
1940    pub fn push_copy_local(&mut self, src: LocalValueId, dst: LocalValueId) -> LocalInsnId {
1941        let (size, space, name) = match dst {
1942            LocalValueId::Varnode(vid) => {
1943                let node = Varnode::from_id(self.shr(), vid);
1944                (
1945                    node.size(),
1946                    LocalMemorySpaceId::Shared(node.space().id),
1947                    node.name().map(str::to_owned),
1948                )
1949            }
1950            LocalValueId::Temp(tlocal) => {
1951                let temp = &self.body.temps[tlocal];
1952                (
1953                    temp.size,
1954                    LocalMemorySpaceId::Temp(temp.space),
1955                    temp.name.as_deref().map(str::to_owned),
1956                )
1957            }
1958            _ => panic!("copy destination must be a varnode or body-local temporary"),
1959        };
1960
1961        const LANE_SIZE: usize = 8;
1962
1963        if size > LANE_SIZE {
1964            let num_lanes = size.div_ceil(LANE_SIZE);
1965            let mut first_id = None;
1966
1967            for lane in 0..num_lanes {
1968                let offset = lane * LANE_SIZE;
1969                let lane_size = cmp::min(LANE_SIZE, size - offset);
1970
1971                let src_lane = self
1972                    .get_range_local(src, offset..offset + lane_size)
1973                    .expect("lane range in bounds");
1974                let src_lane = self.ensure_local_local(src_lane);
1975
1976                let dst_lane = self
1977                    .get_range_local(dst, offset..offset + lane_size)
1978                    .expect("lane range in bounds");
1979
1980                let id = self.store_insn(
1981                    Mnemonic::Store(Store {
1982                        src: src_lane,
1983                        ptr: dst_lane,
1984                        space,
1985                        size: lane_size,
1986                    }),
1987                    0,
1988                );
1989
1990                if let Some(name) = &name {
1991                    let name = Cow::Owned(format!("{}_lane{lane}", name.to_lowercase()));
1992                    let _ = self.rename_insn_local(id, name);
1993                }
1994
1995                first_id.get_or_insert(id);
1996            }
1997
1998            first_id.unwrap()
1999        } else {
2000            let src = self.ensure_local_local(src);
2001            // If dst is a varnode, we need to emit a store from src to dst
2002            let id = self.store_insn(
2003                Mnemonic::Store(Store {
2004                    src,
2005                    ptr: dst,
2006                    space,
2007                    size,
2008                }),
2009                0,
2010            );
2011
2012            // Add a name hint for the store instruction for easier debugging
2013            if let Some(name) = &name {
2014                let lowered = name.to_lowercase();
2015                let name = self.body.names.unique(Cow::Owned(lowered));
2016                self.rename_insn_local(id, name)
2017                    .expect("This name was deduplicated");
2018            }
2019
2020            id
2021        }
2022    }
2023
2024    #[track_caller]
2025    pub fn push_store(
2026        &mut self,
2027        src: ValueId,
2028        ptr: ValueId,
2029        space: impl Into<LocalMemorySpaceId>,
2030    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2031        let (src, ptr) = (self.loc(src), self.loc(ptr));
2032        let local = self.push_store_local(src, ptr, space);
2033        self.insn_ref(local)
2034    }
2035
2036    /// Body-local sibling of [`push_store`](Self::push_store).
2037    #[track_caller]
2038    pub fn push_store_local(
2039        &mut self,
2040        src: LocalValueId,
2041        ptr: LocalValueId,
2042        space: impl Into<LocalMemorySpaceId>,
2043    ) -> LocalInsnId {
2044        let space = space.into();
2045        let src = self.ensure_local_local(src);
2046        let size = self.lsize_of(src);
2047
2048        match ptr {
2049            LocalValueId::Varnode(id) => {
2050                let varnode = Varnode::from_id(self.shr(), id);
2051                if varnode.space().id != space {
2052                    panic!(
2053                        "push_store: ptr is a varnode but its space {:?} does not match the store space {:?}; \
2054                             call ensure_local on the ptr first",
2055                        varnode.space().id,
2056                        space
2057                    );
2058                }
2059            }
2060
2061            LocalValueId::Instruction(local) => {
2062                self.set_insn_space_local(local, space);
2063            }
2064
2065            _ => {}
2066        }
2067
2068        self.store_insn(
2069            Mnemonic::Store(Store {
2070                src,
2071                ptr,
2072                space,
2073                size,
2074            }),
2075            0,
2076        )
2077    }
2078
2079    // --- Branches & Calls ---
2080
2081    /// Declares a new parameter on the current block.
2082    pub fn push_param(&mut self, size: usize) -> BlockParamId {
2083        let local = self.push_param_local(size);
2084        crate::value::block_param::BlockParamId::new(self.func(), local)
2085    }
2086
2087    /// Body-local sibling of [`push_param`](Self::push_param).
2088    pub fn push_param_local(&mut self, size: usize) -> crate::value::LocalParamId {
2089        let block = self.block;
2090        let index = self.body.blocks[block].params.len();
2091        let type_id = self.shared.types.get_or_make_int(size);
2092        let local = self.body.params.push(BlockParam {
2093            index,
2094            type_id,
2095            parent: Some(block),
2096            name: None,
2097            origin: None,
2098        });
2099        self.body.blocks[block].params.push(local);
2100        local
2101    }
2102
2103    /// Terminates the current block with a branch to an already-resolved local
2104    /// target. Module/address discovery must happen before the Builder borrow.
2105    pub fn finalize(mut self, target: BlockId) {
2106        self.finalize_local(target.local)
2107    }
2108
2109    /// Body-local sibling of [`finalize`](Self::finalize).
2110    pub fn finalize_local(&mut self, target: LocalBlockId) {
2111        if !self.is_terminated() {
2112            let branch = self.push_branch_local(target);
2113            if let Some(address) = self.address {
2114                self.body.insns[branch].set_address(address);
2115            }
2116        }
2117    }
2118
2119    /// Add a CFG edge from the working block to `target`, both body-local
2120    /// (id-less twin of [`FunctionBody::add_cfg_edge`]).
2121    fn add_cfg_edge_local(&mut self, from: LocalBlockId, to: LocalBlockId) {
2122        let edge_id = self
2123            .body
2124            .edges
2125            .push(crate::value::block::cfg::EdgeData { from, to });
2126        self.body.blocks[from].edges.insert(edge_id);
2127        self.body.blocks[to].edges.insert(edge_id);
2128    }
2129
2130    /// Terminates this block with an unconditional jump to the given target block.
2131    /// The builder is now safe to drop without panicking, and the block is properly terminated.
2132    pub fn push_branch(&mut self, target: BlockId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2133        let local = self.push_branch_local(target.local);
2134        self.insn_ref(local)
2135    }
2136
2137    /// Body-local sibling of [`push_branch`](Self::push_branch).
2138    pub fn push_branch_local(&mut self, target: LocalBlockId) -> LocalInsnId {
2139        self.push_branch_with_args_local(target, vec![])
2140    }
2141
2142    /// Unconditional branch passing `args` to the target block's parameters.
2143    pub fn push_branch_with_args(
2144        &mut self,
2145        target: BlockId,
2146        args: Vec<ValueId>,
2147    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2148        let args = self.loc_vec(args);
2149        let local = self.push_branch_with_args_local(target.local, args);
2150        self.insn_ref(local)
2151    }
2152
2153    /// Body-local sibling of [`push_branch_with_args`](Self::push_branch_with_args).
2154    pub fn push_branch_with_args_local(
2155        &mut self,
2156        target: LocalBlockId,
2157        args: Vec<LocalValueId>,
2158    ) -> LocalInsnId {
2159        let current = self.block;
2160        self.add_cfg_edge_local(current, target);
2161        let id = self.store_insn(Mnemonic::Branch(Branch { target, args }), 0);
2162        self.is_terminated = true;
2163        id
2164    }
2165
2166    pub fn push_cbranch(
2167        &mut self,
2168        condition: ValueId,
2169        target: BlockId,
2170        fallthrough: BlockId,
2171    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2172        self.push_cbranch_with_args(condition, target, vec![], fallthrough, vec![])
2173    }
2174
2175    /// Body-local sibling of [`push_cbranch`](Self::push_cbranch).
2176    pub fn push_cbranch_local(
2177        &mut self,
2178        condition: LocalValueId,
2179        target: LocalBlockId,
2180        fallthrough: LocalBlockId,
2181    ) -> LocalInsnId {
2182        self.push_cbranch_with_args_local(condition, target, vec![], fallthrough, vec![])
2183    }
2184
2185    /// Conditional branch with per-target arguments.
2186    pub fn push_cbranch_with_args(
2187        &mut self,
2188        condition: ValueId,
2189        target: BlockId,
2190        target_args: Vec<ValueId>,
2191        fallthrough: BlockId,
2192        fallthrough_args: Vec<ValueId>,
2193    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2194        let condition = self.loc(condition);
2195        let target_args = self.loc_vec(target_args);
2196        let fallthrough_args = self.loc_vec(fallthrough_args);
2197        let local = self.push_cbranch_with_args_local(
2198            condition,
2199            target.local,
2200            target_args,
2201            fallthrough.local,
2202            fallthrough_args,
2203        );
2204        self.insn_ref(local)
2205    }
2206
2207    /// Body-local sibling of
2208    /// [`push_cbranch_with_args`](Self::push_cbranch_with_args).
2209    pub fn push_cbranch_with_args_local(
2210        &mut self,
2211        condition: LocalValueId,
2212        target: LocalBlockId,
2213        target_args: Vec<LocalValueId>,
2214        fallthrough: LocalBlockId,
2215        fallthrough_args: Vec<LocalValueId>,
2216    ) -> LocalInsnId {
2217        assert!(
2218            !matches!(condition, LocalValueId::Varnode(_)),
2219            "push_cbranch: varnode condition not allowed; load the value first"
2220        );
2221        let current = self.block;
2222        self.add_cfg_edge_local(current, target);
2223        self.add_cfg_edge_local(current, fallthrough);
2224        let id = self.store_insn(
2225            Mnemonic::CBranch(CBranch {
2226                success_block: target,
2227                success_args: target_args,
2228                condition,
2229                failure_block: fallthrough,
2230                failure_args: fallthrough_args,
2231            }),
2232            0,
2233        );
2234        self.is_terminated = true;
2235        id
2236    }
2237
2238    /// Multi-way dispatch on `scrutinee`. Wires a CFG edge to every arm and to
2239    /// the default, exactly as the conditional branch wires its two.
2240    pub fn push_switch(
2241        &mut self,
2242        scrutinee: ValueId,
2243        cases: Vec<(u64, BlockId, Vec<ValueId>)>,
2244        default: Option<(BlockId, Vec<ValueId>)>,
2245    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2246        let scrutinee = self.loc(scrutinee);
2247        let cases = cases
2248            .into_iter()
2249            .map(|(value, target, args)| (value, target.local, self.loc_vec(args)))
2250            .collect();
2251        let default = default.map(|(target, args)| (target.local, self.loc_vec(args)));
2252        let local = self.push_switch_local(scrutinee, cases, default);
2253        self.insn_ref(local)
2254    }
2255
2256    /// Body-local sibling of [`push_switch`](Self::push_switch).
2257    pub fn push_switch_local(
2258        &mut self,
2259        scrutinee: LocalValueId,
2260        cases: Vec<(u64, LocalBlockId, Vec<LocalValueId>)>,
2261        default: Option<(LocalBlockId, Vec<LocalValueId>)>,
2262    ) -> LocalInsnId {
2263        assert!(
2264            !matches!(scrutinee, LocalValueId::Varnode(_)),
2265            "push_switch: varnode scrutinee not allowed; load the value first"
2266        );
2267        let current = self.block;
2268        for &(_, target, _) in &cases {
2269            self.add_cfg_edge_local(current, target);
2270        }
2271        if let Some((target, _)) = &default {
2272            self.add_cfg_edge_local(current, *target);
2273        }
2274        let (default_block, default_args) = match default {
2275            Some((target, args)) => (Some(target), args),
2276            None => (None, Vec::new()),
2277        };
2278        let id = self.store_insn(
2279            Mnemonic::Switch(Switch {
2280                scrutinee,
2281                cases: cases
2282                    .into_iter()
2283                    .map(|(value, target, args)| SwitchArm {
2284                        value,
2285                        target,
2286                        args,
2287                    })
2288                    .collect(),
2289                default: default_block,
2290                default_args,
2291            }),
2292            0,
2293        );
2294        self.is_terminated = true;
2295        id
2296    }
2297
2298    pub fn push_branchind(&mut self, ptr: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2299        let ptr = self.loc(ptr);
2300        let local = self.push_branchind_local(ptr);
2301        self.insn_ref(local)
2302    }
2303
2304    /// Body-local sibling of [`push_branchind`](Self::push_branchind).
2305    pub fn push_branchind_local(&mut self, ptr: LocalValueId) -> LocalInsnId {
2306        let id = self.store_insn(Mnemonic::BranchInd(BranchInd { ptr }), 0);
2307        self.is_terminated = true;
2308        id
2309    }
2310
2311    pub fn push_call(
2312        &mut self,
2313        target: impl Into<Callee>,
2314    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2315        self.push_call_with_args(target, vec![])
2316    }
2317
2318    pub fn push_call_with_args(
2319        &mut self,
2320        target: impl Into<Callee>,
2321        args: Vec<ValueId>,
2322    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2323        let args = self.loc_vec(args);
2324        let local = self.push_call_with_args_local(target, args);
2325        self.insn_ref(local)
2326    }
2327
2328    /// Body-local sibling of [`push_call`](Self::push_call).
2329    pub fn push_call_local(&mut self, target: impl Into<Callee>) -> LocalInsnId {
2330        self.push_call_with_args_local(target, vec![])
2331    }
2332
2333    /// Body-local sibling of [`push_call_with_args`](Self::push_call_with_args).
2334    pub fn push_call_with_args_local(
2335        &mut self,
2336        target: impl Into<Callee>,
2337        args: Vec<LocalValueId>,
2338    ) -> LocalInsnId {
2339        let target = target.into();
2340        let id = self.store_insn(
2341            Mnemonic::Call(Call {
2342                target,
2343                args,
2344                clobbers: vec![],
2345                tag: Default::default(),
2346            }),
2347            0,
2348        );
2349        self.is_terminated = true;
2350        id
2351    }
2352
2353    /// Tail call to another function's entry — a function-level terminator with
2354    /// no intra-function CFG successor (see [`TailCall`]).
2355    /// Unlike [`push_branch`](Self::push_branch), this wires no CFG edge: control
2356    /// leaves the function.
2357    pub fn push_tail_call(
2358        &mut self,
2359        target: impl Into<Callee>,
2360    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2361        self.push_tail_call_with_args(target, vec![])
2362    }
2363
2364    pub fn push_tail_call_with_args(
2365        &mut self,
2366        target: impl Into<Callee>,
2367        args: Vec<ValueId>,
2368    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2369        let args = self.loc_vec(args);
2370        let local = self.push_tail_call_with_args_local(target, args);
2371        self.insn_ref(local)
2372    }
2373
2374    /// Body-local sibling of [`push_tail_call`](Self::push_tail_call).
2375    pub fn push_tail_call_local(&mut self, target: impl Into<Callee>) -> LocalInsnId {
2376        self.push_tail_call_with_args_local(target, vec![])
2377    }
2378
2379    /// Body-local sibling of
2380    /// [`push_tail_call_with_args`](Self::push_tail_call_with_args).
2381    pub fn push_tail_call_with_args_local(
2382        &mut self,
2383        target: impl Into<Callee>,
2384        args: Vec<LocalValueId>,
2385    ) -> LocalInsnId {
2386        let target = target.into();
2387        let id = self.store_insn(Mnemonic::TailCall(TailCall { target, args }), 0);
2388        self.is_terminated = true;
2389        id
2390    }
2391
2392    pub fn push_call_ind(&mut self, ptr: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2393        self.push_call_ind_with_args(ptr, vec![])
2394    }
2395
2396    pub fn push_call_ind_with_args(
2397        &mut self,
2398        ptr: ValueId,
2399        args: Vec<ValueId>,
2400    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2401        let (ptr, args) = (self.loc(ptr), self.loc_vec(args));
2402        let local = self.push_call_ind_with_args_local(ptr, args);
2403        self.insn_ref(local)
2404    }
2405
2406    /// Body-local sibling of [`push_call_ind`](Self::push_call_ind).
2407    pub fn push_call_ind_local(&mut self, ptr: LocalValueId) -> LocalInsnId {
2408        self.push_call_ind_with_args_local(ptr, vec![])
2409    }
2410
2411    /// Body-local sibling of
2412    /// [`push_call_ind_with_args`](Self::push_call_ind_with_args).
2413    pub fn push_call_ind_with_args_local(
2414        &mut self,
2415        ptr: LocalValueId,
2416        args: Vec<LocalValueId>,
2417    ) -> LocalInsnId {
2418        let id = self.store_insn(Mnemonic::CallInd(CallInd { ptr, args }), 0);
2419        self.is_terminated = true;
2420        id
2421    }
2422
2423    pub fn push_return(&mut self, ptr: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2424        let ptr = self.loc(ptr);
2425        let local = self.push_return_local(ptr);
2426        self.insn_ref(local)
2427    }
2428
2429    /// Body-local sibling of [`push_return`](Self::push_return).
2430    pub fn push_return_local(&mut self, ptr: LocalValueId) -> LocalInsnId {
2431        self.push_return_at_local(None, ptr)
2432    }
2433
2434    pub fn push_return_with_value(
2435        &mut self,
2436        value: ValueId,
2437        ptr: ValueId,
2438    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2439        let (value, ptr) = (self.loc(value), self.loc(ptr));
2440        let local = self.push_return_at_local(Some(value), ptr);
2441        self.insn_ref(local)
2442    }
2443
2444    /// Body-local sibling of
2445    /// [`push_return_with_value`](Self::push_return_with_value).
2446    pub fn push_return_with_value_local(
2447        &mut self,
2448        value: LocalValueId,
2449        ptr: LocalValueId,
2450    ) -> LocalInsnId {
2451        self.push_return_at_local(Some(value), ptr)
2452    }
2453
2454    fn push_return_at_local(
2455        &mut self,
2456        value: Option<LocalValueId>,
2457        ptr: LocalValueId,
2458    ) -> LocalInsnId {
2459        let id = self.store_insn(Mnemonic::Return(Return { ptr, value }), 0);
2460        self.is_terminated = true;
2461        id
2462    }
2463
2464    pub fn push_return_value(
2465        &mut self,
2466        value: ValueId,
2467    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2468        let value = self.loc(value);
2469        let local = self.push_return_value_local(value);
2470        self.insn_ref(local)
2471    }
2472
2473    /// Terminate the current block with [`BadInsn`](crate::value::insn::BadInsn): bytes that did not decode to
2474    /// a valid instruction. No successors, no operands.
2475    pub fn push_bad_insn(&mut self) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2476        let local = self.push_bad_insn_local();
2477        self.insn_ref(local)
2478    }
2479
2480    /// Body-local sibling of [`push_bad_insn`](Self::push_bad_insn).
2481    pub fn push_bad_insn_local(&mut self) -> LocalInsnId {
2482        let id = self.store_insn(Mnemonic::BadInsn(crate::value::insn::BadInsn), 0);
2483        self.is_terminated = true;
2484        id
2485    }
2486
2487    /// Body-local sibling of [`push_return_value`](Self::push_return_value).
2488    pub fn push_return_value_local(&mut self, value: LocalValueId) -> LocalInsnId {
2489        let id = self.store_insn(Mnemonic::ReturnValue(ReturnValue { value }), 0);
2490        self.is_terminated = true;
2491        id
2492    }
2493
2494    // --- Assert ---
2495
2496    /// Asserts that a condition holds at this point in execution
2497    pub fn push_assert(
2498        &mut self,
2499        condition: ValueId,
2500    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2501        let condition = self.loc(condition);
2502        let local = self.push_assert_local(condition);
2503        self.insn_ref(local)
2504    }
2505
2506    /// Body-local sibling of [`push_assert`](Self::push_assert).
2507    pub fn push_assert_local(&mut self, condition: LocalValueId) -> LocalInsnId {
2508        self.store_insn(Mnemonic::Assert(Assert { condition }), 0)
2509    }
2510}
2511
2512#[cfg(test)]
2513mod tests {
2514    use wazabin_qcode_macro::qcode;
2515
2516    use super::*;
2517    use crate::{context::Context, value::ModuleView};
2518
2519    #[test]
2520    fn checked_builder_matches_module_builder() {
2521        use crate::value::{
2522            FunctionId, FunctionRef, block::BasicBlock, function::FunctionBody,
2523            util::body_mut::BodyMut,
2524        };
2525
2526        // The same body over any host: consts and a couple of binops (exercising
2527        // type + literal minting through the interners' `&self` paths), then a
2528        // branch to a freshly minted local-label block. No varnode/temp-space
2529        // minting — a checked-out host has read-only shared access.
2530        fn body<'str>(b: &mut Builder<'str, '_>) {
2531            let c1 = b.shr().get_const(7, 8);
2532            let c2 = b.shr().get_const(9, 8);
2533            let sum = b.push_add(c1, c2).id();
2534            let _doubled = b.push_add(sum, sum).id();
2535            let lbl = b.get_or_make_local_label("next".into());
2536            b.push_branch(lbl);
2537        }
2538
2539        // Structural snapshot: per block (name, per-instruction mnemonic Debug —
2540        // which includes the operand ids — and sorted successor block names).
2541        type BSnap = Vec<(String, Vec<String>, Vec<String>)>;
2542        fn snap(ctx: &Context, fid: FunctionId) -> BSnap {
2543            FunctionRef::from_id(ctx, fid)
2544                .blocks()
2545                .map(|blk| {
2546                    let name = blk.name().unwrap_or("?").to_string();
2547                    let insns: Vec<String> = blk
2548                        .instructions()
2549                        .map(|i| format!("{:?}", i.mnemonic()))
2550                        .collect();
2551                    let mut succ: Vec<String> = blk
2552                        .successors()
2553                        .map(|(_, s)| {
2554                            BasicBlock::from_id(ctx, s)
2555                                .name()
2556                                .unwrap_or("?")
2557                                .to_string()
2558                        })
2559                        .collect();
2560                    succ.sort();
2561                    (name, insns, succ)
2562                })
2563                .collect()
2564        }
2565
2566        // ---- (a) module builder ---------------------------------------------
2567        let mut ctx_a = Context::new();
2568        let fid_a = FunctionBody::make(&mut ctx_a, "foo".into()).unwrap().id;
2569        let entry_a = FunctionBody::from_id_mut(&mut ctx_a, fid_a).make_root().id;
2570        {
2571            let mut b = ctx_a.builder(entry_a);
2572            body(&mut b);
2573        }
2574        let snap_a = snap(&ctx_a, fid_a);
2575        assert!(
2576            snap_a.iter().any(|(_, i, _)| !i.is_empty()),
2577            "sanity: built IR"
2578        );
2579
2580        // ---- (b) checked-out builder ----------------------------------------
2581        let mut ctx_b = Context::new();
2582        let fid_b = FunctionBody::make(&mut ctx_b, "foo".into()).unwrap().id;
2583        let entry_b = FunctionBody::from_id_mut(&mut ctx_b, fid_b).make_root().id;
2584        {
2585            let mut host = BodyMut::new(&mut ctx_b.bodies[fid_b], &ctx_b.shared, &ctx_b.interfaces);
2586            let mut b = host.builder(entry_b);
2587            body(&mut b);
2588        }
2589        let snap_b = snap(&ctx_b, fid_b);
2590
2591        assert_eq!(
2592            snap_a, snap_b,
2593            "a body built through a checked-out builder must match the module-built body"
2594        );
2595    }
2596
2597    /// Address arithmetic inherits the pointer operand's memory space:
2598    /// `ptr + literal` (rule 1) keeps `ptr`'s space, so folding `x + 0 → x` (which
2599    /// returns `lhs`) is space-preserving; `ptr + ptr` (rule 2) is ambiguous and
2600    /// drops to a plain integer; `int + literal` is unaffected.
2601    #[test]
2602    fn address_arithmetic_inherits_pointer_space() {
2603        use crate::value::function::FunctionBody;
2604
2605        let mut ctx = Context::new();
2606        let fid = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
2607        let entry = FunctionBody::from_id_mut(&mut ctx, fid).make_root().id;
2608
2609        let ram = ctx.shared.default_space;
2610        let ptr_ty = ctx.shared.types.get_or_make_space_address(8, ram);
2611        let ram_mem = ctx.shared.types.space_of(ptr_ty);
2612
2613        // Two space-typed pointer values: fresh instructions stamped into `ram`.
2614        let (addr_a, addr_b) = {
2615            let mut b = ctx.builder(entry);
2616            let c1 = b.shr().get_const(0x1000, 8);
2617            let c2 = b.shr().get_const(0x2000, 8);
2618            (b.push_add(c1, c1).id(), b.push_add(c2, c2).id())
2619        };
2620        for v in [addr_a, addr_b] {
2621            if let ValueId::Instruction(i) = v {
2622                crate::value::Instruction::from_id_mut(&mut ctx, i).set_type(ptr_ty);
2623            }
2624        }
2625
2626        let (add_ptr_lit, add_lit_ptr, add_ptr_ptr, add_int_lit) = {
2627            let mut b = ctx.builder(entry);
2628            let k = b.shr().get_const(4, 8);
2629            let zero = b.shr().get_const(0, 8);
2630            (
2631                b.push_add(addr_a, k).id(),      // ptr + literal
2632                b.push_add(k, addr_a).id(),      // literal + ptr
2633                b.push_add(addr_a, addr_b).id(), // ptr + ptr
2634                b.push_add(k, zero).id(),        // int + literal
2635            )
2636        };
2637
2638        let space_of = |v: ValueId| ctx.shared.types.space_of(ctx.type_of(v));
2639        assert_eq!(
2640            space_of(add_ptr_lit),
2641            ram_mem,
2642            "ptr + literal keeps the pointer's space (rule 1)"
2643        );
2644        assert_eq!(
2645            space_of(add_lit_ptr),
2646            ram_mem,
2647            "literal + ptr keeps the pointer's space (rule 1, commutative)"
2648        );
2649        assert_eq!(
2650            space_of(add_ptr_ptr),
2651            None,
2652            "ptr + ptr is ambiguous and drops to a plain integer (rule 2)"
2653        );
2654        assert_eq!(
2655            space_of(add_int_lit),
2656            None,
2657            "int + literal carries no space"
2658        );
2659
2660        // Folding `x + 0 → x` returns `lhs`; since `lhs` (the ptr+literal above)
2661        // carries `ram`, the fold is space-preserving — the property the argpromote
2662        // rule-4 rewrite relies on.
2663        assert_eq!(
2664            space_of(addr_a),
2665            ram_mem,
2666            "the pointer operand a fold would return still carries its space"
2667        );
2668    }
2669
2670    /// An id-less (detached) body can be driven by `Builder::new_local` and the
2671    /// `push_*_local` verbs without ever acquiring a registry identity: the
2672    /// builder's engine is fully body-local.
2673    #[test]
2674    fn detached_body_builds_through_local_verbs() {
2675        let ctx = Context::new();
2676        let mut body = FunctionBody::detached();
2677        assert_eq!(body.try_id(), None, "sanity: body starts detached");
2678
2679        // Mint entry and target blocks through the body's local verbs.
2680        let entry = body.push_block_local(BasicBlock::detached());
2681        let target = body.push_block_local(BasicBlock::detached());
2682        body.set_root_id(Some(entry));
2683
2684        let c1 = ctx.shared.get_const(7, 8).strip_func();
2685        let c2 = ctx.shared.get_const(9, 8).strip_func();
2686
2687        {
2688            let mut b = Builder::new_local(&mut body, &ctx.shared, &ctx.interfaces, entry);
2689            let sum = b.push_add_local(c1, c2);
2690            let sum = LocalValueId::Instruction(sum);
2691            let doubled = b.push_add_local(sum, sum);
2692            let _cmp = b.push_eq_local(LocalValueId::Instruction(doubled), c2);
2693            b.push_branch_local(target);
2694            assert!(b.is_terminated());
2695            b.switch_to_block_local(target);
2696            let ret = b.push_return_value_local(sum);
2697            let _ = ret;
2698        }
2699
2700        // Instructions landed in the arenas, wired to their blocks.
2701        assert_eq!(body.blocks[entry].instructions.len(), 4);
2702        assert_eq!(body.blocks[target].instructions.len(), 1);
2703        let last = *body.blocks[entry].instructions.last().unwrap();
2704        assert!(body.insns[last].mnemonic().is_terminator());
2705
2706        // The body never acquired an identity: it is still detached.
2707        assert_eq!(body.try_id(), None);
2708    }
2709
2710    /// `map` preserves its source's sequence kind: mapping over a `List<T>`
2711    /// (e.g. a `take_while` result) yields a `List<U>`, not a fixed array.
2712    #[test]
2713    fn map_over_a_list_yields_a_list() {
2714        use crate::value::{FunctionBody, insn::Return};
2715
2716        let mut ctx = Context::new();
2717        let i8 = ctx.shared.types.get_or_make_int(1);
2718
2719        // body: fn(i8) -> i8 returning its param (so the map result elem is i8).
2720        let body = FunctionBody::make(&mut ctx, "body".into()).unwrap().id;
2721        let broot = FunctionBody::from_id_mut(&mut ctx, body).make_root().id;
2722        let bp = BasicBlock::from_id_mut(&mut ctx, broot).push_param(1).id;
2723        let dummy = ctx.get_const(0, 8).id();
2724        let ret = InstructionRef::from_mnemonic_with_type(
2725            &mut ctx,
2726            body,
2727            Mnemonic::Return(Return {
2728                ptr: dummy.localize(body),
2729                value: Some(ValueId::BlockParam(bp).localize(body)),
2730            }),
2731            i8,
2732        )
2733        .id;
2734        BasicBlock::from_id_mut(&mut ctx, broot).push_insn(ret);
2735
2736        // host: a value typed `List<i8>` (bound 4) to map over.
2737        let host = FunctionBody::make(&mut ctx, "host".into()).unwrap().id;
2738        let hentry = FunctionBody::from_id_mut(&mut ctx, host).make_root().id;
2739        let list_ty = ctx.shared.types.get_or_make_list(i8, 4);
2740        let src_pid = BasicBlock::from_id_mut(&mut ctx, hentry).push_param(4).id;
2741        ctx.block_param_mut(src_pid).type_id = list_ty;
2742        let src = ValueId::BlockParam(src_pid);
2743
2744        let map_ty = {
2745            let mut b = ctx.builder(hentry);
2746            b.push_map(body, src, Vec::new()).type_id()
2747        };
2748
2749        assert_eq!(
2750            ctx.shared.types.array_of(map_ty),
2751            None,
2752            "map of a list is not an array"
2753        );
2754        assert_eq!(
2755            ctx.shared.types.list_of(map_ty),
2756            Some((i8, Some(4))),
2757            "map of List<i8> (bound 4) is List<i8> (bound 4)"
2758        );
2759    }
2760
2761    #[test]
2762    fn cfg_branch_adds_one_node_and_one_edge() {
2763        let mut ctx = Context::new();
2764        qcode!(
2765            ctx,
2766            "
2767            <entry>
2768                goto <done>;
2769            <done>
2770                goto <0x1001>;
2771        "
2772        );
2773        // entry + done + 1001 = 3 nodes; entry -> done, done -> 1001 = 2 edges
2774        assert_eq!(ctx.block_ids().len(), 3);
2775        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 2);
2776    }
2777
2778    #[test]
2779    fn cfg_cbranch_adds_two_edges() {
2780        let mut ctx = Context::new();
2781        qcode!(
2782            ctx,
2783            "
2784            varnode i8 cond;
2785
2786            <entry>
2787                %c = load(cond:1, cond);
2788                if %c goto <then_lbl> else goto <else_lbl>;
2789
2790            <then_lbl>
2791                goto <0x1001>;
2792
2793            <else_lbl>
2794                goto <0x1001>;
2795        "
2796        );
2797        // entry + then_lbl + else_lbl + 1001 = 4 nodes
2798        // entry->then_lbl, entry->else_lbl, then_lbl->1001, else_lbl->1001 = 4 edges
2799        assert_eq!(ctx.block_ids().len(), 4);
2800        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 4);
2801    }
2802
2803    #[test]
2804    fn cfg_branchind_adds_node_but_no_outgoing_edge() {
2805        let mut ctx = Context::new();
2806        qcode!(
2807            ctx,
2808            "
2809            <entry>
2810                local i64 ptr;
2811                goto [ptr];
2812        "
2813        );
2814
2815        assert_eq!(ctx.block_ids().len(), 1);
2816        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 0);
2817
2818        assert_eq!(BasicBlock::from_id(&ctx, entry).successors().count(), 0);
2819    }
2820
2821    #[test]
2822    fn cfg_return_adds_node_but_no_outgoing_edge() {
2823        let mut ctx = Context::new();
2824        qcode!(
2825            ctx,
2826            "
2827            <entry>
2828                local i64 ptr;
2829                return at ptr;
2830        "
2831        );
2832        assert_eq!(ctx.block_ids().len(), 1);
2833        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 0);
2834        assert_eq!(BasicBlock::from_id(&ctx, entry).successors().count(), 0);
2835    }
2836
2837    #[test]
2838    fn cfg_multi_block_qcode_program() {
2839        let mut ctx = Context::new();
2840        qcode!(
2841            ctx,
2842            "
2843            varnode i32 v;
2844
2845            <entry>
2846                goto <body>;
2847
2848            <body>
2849                i64 %v0 = i64 &v + i64 1;
2850                goto <0x1001>;
2851        "
2852        );
2853
2854        // entry + body + 1001 = 3 nodes
2855        // entry -> body, body -> 1001 = 2 edges
2856        assert_eq!(ctx.block_ids().len(), 3);
2857        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 2);
2858    }
2859
2860    #[test]
2861    fn test_builder_finalize() {
2862        // Test code for Builder drop behavior
2863        // This test will compile and run without panicking because we finalize the builder properly
2864        let mut ctx = Context::new();
2865        let block_id = {
2866            let __f = ctx.anon_function();
2867            ctx.get_or_make_block(0, __f)
2868        };
2869        let target = ctx.get_or_make_block(0x1000, block_id.func);
2870
2871        {
2872            let builder = ctx.builder(block_id);
2873            builder.finalize(target);
2874        }
2875    }
2876
2877    #[test]
2878    fn test_named_temp_duplicate() {
2879        let mut ctx = Context::new();
2880        let mut builder = ctx.builder_at(0x1000);
2881
2882        let value = builder.make_named_temp("dup".into(), 4);
2883        let other_value = builder.make_named_temp("dup".into(), 4);
2884        let target = builder.current_block();
2885        builder.finalize(target);
2886
2887        assert_eq!(
2888            TempRef::new(ModuleView::new(&ctx), value).name(),
2889            Some("dup")
2890        );
2891        assert_eq!(
2892            TempRef::new(ModuleView::new(&ctx), other_value).name(),
2893            Some("dup_1")
2894        );
2895    }
2896
2897    #[test]
2898    fn same_label_temps_in_different_functions_are_isolated() {
2899        let mut ctx = Context::new();
2900
2901        let first = {
2902            let mut builder = ctx.builder_at(0x1000);
2903            let temp = builder.make_temp_labeled(7, 4);
2904            let target = builder.current_block();
2905            builder.finalize(target);
2906            temp
2907        };
2908        let second = {
2909            let mut builder = ctx.builder_at(0x2000);
2910            let temp = builder.make_temp_labeled(7, 4);
2911            let target = builder.current_block();
2912            builder.finalize(target);
2913            temp
2914        };
2915
2916        assert_ne!(first, second);
2917        let first = TempRef::new(ModuleView::new(&ctx), first);
2918        let second = TempRef::new(ModuleView::new(&ctx), second);
2919        assert_eq!((first.label(), second.label()), (Some(7), Some(7)));
2920        assert_ne!(first.space().id, second.space().id);
2921    }
2922
2923    #[test]
2924    fn qcode_local_decl_creates_named_temp() {
2925        let mut ctx = Context::new();
2926
2927        qcode!(ctx, "varnode i64 ptr; <block> goto <0x1001>;");
2928
2929        let ptr = Varnode::from_id(&ctx, ptr);
2930
2931        assert_eq!(ptr.size(), 8);
2932        assert_eq!(ptr.name(), Some("ptr"));
2933    }
2934
2935    #[test]
2936    fn qcode_standalone_local_decl_creates_named_temp() {
2937        let mut ctx = Context::new();
2938        qcode!(ctx, "varnode i64 ptr; <block> goto <0x1001>;");
2939
2940        let ptr = Varnode::from_id(&ctx, ptr);
2941
2942        assert_eq!(ptr.size(), 8);
2943        assert_eq!(ptr.name(), Some("ptr"));
2944    }
2945
2946    #[test]
2947    fn qcode_varnode_decl_before_entry_block() {
2948        let mut ctx = Context::new();
2949        qcode!(ctx, "varnode i64 ptr; <block> goto <0x1001>;");
2950
2951        let ptr = Varnode::from_id(&ctx, ptr);
2952
2953        assert_eq!(ptr.size(), 8);
2954        assert_eq!(ptr.name(), Some("ptr"));
2955    }
2956
2957    #[test]
2958    fn push_param_via_builder_visible_on_block() {
2959        let mut ctx = Context::new();
2960        let block_id = {
2961            let __f = ctx.anon_function();
2962            ctx.get_or_make_block(0x1000, __f)
2963        };
2964        let mut builder = ctx.builder(block_id);
2965
2966        let p0 = builder.push_param(8);
2967        let p0_id = p0;
2968        let p1 = builder.push_param(4);
2969        let p1_id = p1;
2970
2971        drop(builder);
2972
2973        let block = BasicBlock::from_id(&ctx, block_id);
2974        assert_eq!(block.num_params(), 2);
2975        let param_ids: Vec<_> = block.params().map(|p| p.id).collect();
2976        assert_eq!(param_ids, [p0_id, p1_id]);
2977        assert_eq!(block.instruction_ids().len(), 0);
2978    }
2979
2980    #[test]
2981    fn push_branch_with_args_via_builder() {
2982        let mut ctx = Context::new();
2983        // A branch edge is intra-function: source and target live in one function.
2984        let f = ctx.anon_function();
2985        let src_id = ctx.get_or_make_block(0x1000, f);
2986        let dst_id = ctx.get_or_make_block(0x2000, f);
2987
2988        let param_val = BasicBlock::from_id_mut(&mut ctx, dst_id).push_param(8).id();
2989
2990        {
2991            let mut builder = ctx.builder(src_id);
2992            builder.push_branch_with_args(dst_id, vec![param_val]);
2993        }
2994
2995        let block = BasicBlock::from_id(&ctx, src_id);
2996        let last = block.iter().last().expect("branch was added");
2997        let crate::value::insn::Mnemonic::Branch(branch) = last.mnemonic() else {
2998            panic!("expected branch");
2999        };
3000        assert_eq!(branch.target, dst_id.local);
3001        assert_eq!(branch.args.len(), 1);
3002        assert_eq!(branch.args[0], param_val.strip_func());
3003    }
3004
3005    #[test]
3006    fn test_builder_adds_address_to_qcode() {
3007        let mut ctx = Context::new();
3008        let id_42 = ctx.get_const(42, 8).id();
3009
3010        let not_insn_id = {
3011            let source = ctx.builder_at(0x1000).current_block();
3012            let target = ctx.get_or_make_block(0x1001, source.func);
3013            let mut builder = ctx.builder(source);
3014            builder.set_address(0x1000);
3015            let not_insn_id = builder.push_bit_negate(id_42).id;
3016            builder.finalize(target);
3017
3018            not_insn_id
3019        };
3020
3021        let insn = Instruction::from_id(&ctx, not_insn_id);
3022
3023        assert_eq!(insn.address().unwrap(), 0x1000);
3024    }
3025
3026    #[test]
3027    fn builder_at_materializes_root_in_registered_function_arena() {
3028        let mut ctx = Context::new();
3029        let func = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id;
3030        assert!(FunctionBody::from_id(&ctx, func).root().is_none());
3031
3032        let block = {
3033            let builder = ctx.builder_at(0x2000);
3034            builder.current_block()
3035        };
3036
3037        assert_eq!(block.func, func);
3038        assert_eq!(
3039            FunctionBody::from_id(&ctx, func).root().map(|root| root.id),
3040            Some(block)
3041        );
3042        assert!(FunctionBody::from_name(&ctx, "blk_2000").is_none());
3043    }
3044
3045    #[test]
3046    fn append_after_terminated_block_panic_includes_current_address() {
3047        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3048            let mut ctx = Context::new();
3049            let value = ctx.get_const(0, 1).id();
3050            let source = ctx.builder_at(0x4010).current_block();
3051            let target = ctx.get_or_make_block(0x4020, source.func);
3052            let mut builder = ctx.builder(source);
3053
3054            builder.push_branch(target);
3055            builder.set_address(0x4015);
3056            builder.push_bit_negate(value);
3057        }));
3058
3059        let panic = result.expect_err("append should panic after a terminator");
3060        let message = panic
3061            .downcast_ref::<String>()
3062            .map(String::as_str)
3063            .or_else(|| panic.downcast_ref::<&'static str>().copied())
3064            .expect("panic should carry a string message");
3065
3066        assert!(
3067            message.contains("cannot append instruction to a terminated block at 0x4015"),
3068            "unexpected panic message: {message}"
3069        );
3070    }
3071
3072    #[test]
3073    fn push_copy_supports_partial_final_lane() {
3074        let mut ctx = Context::new();
3075        let block_id = {
3076            let __f = ctx.anon_function();
3077            ctx.get_or_make_block(0x1000, __f)
3078        };
3079
3080        {
3081            let target = ctx.get_or_make_block(0x1001, block_id.func);
3082            let mut builder = ctx.builder(block_id);
3083            let src = builder.make_named_temp("src".into(), 9);
3084            let dst = builder.make_named_temp("dst".into(), 9);
3085            builder.push_copy(src.into(), dst);
3086            builder.finalize(target);
3087        }
3088
3089        let store_sizes = BasicBlock::from_id(&ctx, block_id)
3090            .iter()
3091            .filter_map(|insn| match insn.mnemonic() {
3092                Mnemonic::Store(store) => Some(store.size),
3093                _ => None,
3094            })
3095            .collect::<Vec<_>>();
3096        assert_eq!(store_sizes, [8, 1]);
3097    }
3098
3099    // --- insert-point tests ---
3100
3101    #[test]
3102    fn insert_point_to_start_prepends_before_existing_instruction() {
3103        let mut ctx = Context::new();
3104        let block_id = {
3105            let __f = ctx.anon_function();
3106            ctx.get_or_make_block(0x1000, __f)
3107        };
3108        let val = ctx.get_const(0, 8).id();
3109
3110        let existing_id = {
3111            let mut b = ctx.builder(block_id);
3112
3113            b.push_bit_negate(val).id
3114        };
3115
3116        let prepended_id = {
3117            let mut b = ctx.builder(block_id);
3118            b.set_insert_point_to_start();
3119            b.push_bit_negate(val).id
3120        };
3121
3122        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3123        assert_eq!(ids, [prepended_id, existing_id]);
3124    }
3125
3126    #[test]
3127    fn multiple_pushes_with_insert_point_to_start_preserve_push_order() {
3128        let mut ctx = Context::new();
3129        let block_id = {
3130            let __f = ctx.anon_function();
3131            ctx.get_or_make_block(0x1000, __f)
3132        };
3133        let val = ctx.get_const(0, 8).id();
3134
3135        let existing_id = {
3136            let mut b = ctx.builder(block_id);
3137
3138            b.push_bit_negate(val).id
3139        };
3140
3141        let (id0, id1, id2) = {
3142            let mut b = ctx.builder(block_id);
3143            b.set_insert_point_to_start();
3144            (
3145                b.push_bit_negate(val).id,
3146                b.push_bit_negate(val).id,
3147                b.push_bit_negate(val).id,
3148            )
3149        };
3150
3151        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3152        assert_eq!(ids, [id0, id1, id2, existing_id]);
3153    }
3154
3155    #[test]
3156    fn insert_point_before_existing_instruction_inserts_before_target() {
3157        let mut ctx = Context::new();
3158        let block_id = {
3159            let __f = ctx.anon_function();
3160            ctx.get_or_make_block(0x1000, __f)
3161        };
3162        let val = ctx.get_const(0, 8).id();
3163
3164        let (first_id, target_id) = {
3165            let mut b = ctx.builder(block_id);
3166            (b.push_bit_negate(val).id, b.push_bit_negate(val).id)
3167        };
3168
3169        let (inserted0, inserted1) = {
3170            let mut b = ctx.builder(block_id);
3171            b.set_insert_point_before(target_id);
3172            (b.push_bit_negate(val).id, b.push_bit_negate(val).id)
3173        };
3174
3175        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3176        assert_eq!(ids, [first_id, inserted0, inserted1, target_id]);
3177    }
3178
3179    #[test]
3180    fn insert_point_to_start_allows_push_into_terminated_block() {
3181        let mut ctx = Context::new();
3182        qcode!(ctx, "<entry> goto <0x1001>;");
3183
3184        let val = ctx.get_const(1, 1).id();
3185        let new_id = {
3186            let mut b = ctx.builder(entry);
3187            b.set_insert_point_to_start();
3188            b.push_bit_negate(val).id
3189        };
3190
3191        let block = BasicBlock::from_id(&ctx, entry);
3192        assert_eq!(block.instruction_ids()[0], new_id);
3193        // The original branch terminator is still present
3194        assert!(block.is_terminated());
3195    }
3196
3197    #[test]
3198    fn set_insert_point_to_end_restores_append_mode() {
3199        let mut ctx = Context::new();
3200        let block_id = {
3201            let __f = ctx.anon_function();
3202            ctx.get_or_make_block(0x1000, __f)
3203        };
3204        let val = ctx.get_const(0, 8).id();
3205
3206        let (first_id, middle_id, last_id) = {
3207            let mut b = ctx.builder(block_id);
3208            let first = b.push_bit_negate(val).id; // appended → index 0
3209            b.set_insert_point_to_start();
3210            let middle = b.push_bit_negate(val).id; // inserted at 0, first shifts to 1
3211            b.set_insert_point_to_end();
3212            let last = b.push_bit_negate(val).id; // appended → index 2
3213            (first, middle, last)
3214        };
3215
3216        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3217        assert_eq!(ids, [middle_id, first_id, last_id]);
3218    }
3219}