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    fn push_binop_local(
969        &mut self,
970        op: Binop,
971        lhs: LocalValueId,
972        rhs: LocalValueId,
973        size: Option<usize>,
974    ) -> LocalInsnId {
975        let lhs_size = self.lsize_of(lhs);
976        let rhs_size = self.lsize_of(rhs);
977        let operand_size = match (
978            lhs_size == rhs_size,
979            self.is_literal(lhs),
980            self.is_literal(rhs),
981        ) {
982            (true, _, _) => lhs_size,
983            (false, true, false) => rhs_size,
984            (false, false, true) => lhs_size,
985            (false, true, true) => lhs_size.max(rhs_size),
986            // Two non-literal operands of differing size cannot be repaired here
987            // without choosing a semantic cast. Lifters should emit explicit
988            // zext/sext/range operations before constructing the binop.
989            (false, false, false) => lhs_size,
990        };
991        let lhs = self.coerce_literal_size(lhs, operand_size);
992        let rhs = self.coerce_literal_size(rhs, operand_size);
993        assert_eq!(
994            self.lsize_of(lhs),
995            self.lsize_of(rhs),
996            "push_binop: operands must have equal size; emit an explicit cast first"
997        );
998
999        // Determine result type using the TypeManager's arithmetic rules.
1000        let result_type = {
1001            let lhs_type = self.ltype_of(lhs);
1002            let rhs_type = self.ltype_of(rhs);
1003            self.shr().types.binop_result(lhs_type, op, rhs_type)
1004        };
1005
1006        // Comparisons always override the result size to 1.
1007        let result_type = if let Some(forced_size) = size {
1008            let current_size = self.shr().types.size_of(result_type);
1009            if forced_size != current_size {
1010                self.shr().types.get_or_make_int(forced_size)
1011            } else {
1012                result_type
1013            }
1014        } else {
1015            result_type
1016        };
1017
1018        // Restore address-space provenance for pointer arithmetic. When the
1019        // result is not already a space pointer (e.g. a `StackAddress` produced
1020        // from a stack-base operand), `Add`/`Sub` inherit the space of whichever
1021        // operand carries one — so `&A + k` points into `A`'s space. Register
1022        // spaces are excluded (pointer arithmetic is not allowed there).
1023        let result_type = if self.shr().types.space_of(result_type).is_none()
1024            && matches!(op, Binop::Int(IntBinop::Add | IntBinop::Sub))
1025        {
1026            match self.merge_space_ids(lhs, rhs) {
1027                Some(space)
1028                    if !matches!(Space::from_id(self.shr(), space).ty, SpaceType::Register) =>
1029                {
1030                    let size = self.shr().types.size_of(result_type);
1031                    self.shr().types.get_or_make_space_address(size, space)
1032                }
1033                _ => result_type,
1034            }
1035        } else {
1036            result_type
1037        };
1038
1039        // Rule 2: `ptr + ptr` — both operands carry a (non-register) space — is
1040        // ambiguous (into which space does the sum point?), so `Add`/`Sub` drops
1041        // the result to a plain integer. `binop_result` would otherwise propagate
1042        // the left operand's space unconditionally.
1043        let result_type = if matches!(op, Binop::Int(IntBinop::Add | IntBinop::Sub))
1044            && self.shr().types.space_of(result_type).is_some()
1045        {
1046            let spaced = |b: &Self, v| {
1047                b.lspace_of(v)
1048                    .is_some_and(|s| !matches!(Space::from_id(b.shr(), s).ty, SpaceType::Register))
1049            };
1050            if spaced(self, lhs) && spaced(self, rhs) {
1051                let size = self.shr().types.size_of(result_type);
1052                self.shr().types.get_or_make_int(size)
1053            } else {
1054                result_type
1055            }
1056        } else {
1057            result_type
1058        };
1059
1060        self.store_insn_with_type(Mnemonic::Binop(Binary { op, lhs, rhs }), result_type)
1061    }
1062
1063    // --- Arithmetic ---
1064
1065    binop_leaf!(push_mul, push_mul_local, Binop::Int(IntBinop::Mul), None);
1066    binop_leaf!(push_div, push_div_local, Binop::Int(IntBinop::Div), None);
1067    binop_leaf!(push_sdiv, push_sdiv_local, Binop::Int(IntBinop::Sdiv), None);
1068    binop_leaf!(push_mod, push_mod_local, Binop::Int(IntBinop::Rem), None);
1069    binop_leaf!(push_smod, push_smod_local, Binop::Int(IntBinop::Srem), None);
1070    binop_leaf!(push_add, push_add_local, Binop::Int(IntBinop::Add), None);
1071    binop_leaf!(push_sub, push_sub_local, Binop::Int(IntBinop::Sub), None);
1072
1073    // --- Float Arithmetic ---
1074
1075    binop_leaf!(
1076        push_fdiv,
1077        push_fdiv_local,
1078        Binop::Float(FloatBinop::Div),
1079        None
1080    );
1081    binop_leaf!(
1082        push_fmul,
1083        push_fmul_local,
1084        Binop::Float(FloatBinop::Mul),
1085        None
1086    );
1087    binop_leaf!(
1088        push_fadd,
1089        push_fadd_local,
1090        Binop::Float(FloatBinop::Add),
1091        None
1092    );
1093    binop_leaf!(
1094        push_fsub,
1095        push_fsub_local,
1096        Binop::Float(FloatBinop::Sub),
1097        None
1098    );
1099
1100    // --- Shifts ---
1101
1102    binop_leaf!(
1103        push_shl,
1104        push_shl_local,
1105        Binop::Int(IntBinop::ShiftLeft),
1106        None
1107    );
1108    binop_leaf!(
1109        push_shr,
1110        push_shr_local,
1111        Binop::Int(IntBinop::ShiftRight),
1112        None
1113    );
1114    binop_leaf!(
1115        push_sshr,
1116        push_sshr_local,
1117        Binop::Int(IntBinop::SShiftRight),
1118        None
1119    );
1120
1121    // --- Integer Comparisons ---
1122    // Greater-than variants swap operands of the less-than op.
1123
1124    cmp_pair!(
1125        push_slt,
1126        push_slt_local,
1127        push_sgt,
1128        push_sgt_local,
1129        Binop::Int(IntBinop::SLess)
1130    );
1131    cmp_pair!(
1132        push_sle,
1133        push_sle_local,
1134        push_sge,
1135        push_sge_local,
1136        Binop::Int(IntBinop::SLessEqual)
1137    );
1138    cmp_pair!(
1139        push_lt,
1140        push_lt_local,
1141        push_gt,
1142        push_gt_local,
1143        Binop::Int(IntBinop::Less)
1144    );
1145    cmp_pair!(
1146        push_le,
1147        push_le_local,
1148        push_ge,
1149        push_ge_local,
1150        Binop::Int(IntBinop::LessEqual)
1151    );
1152
1153    // --- Float Comparisons ---
1154
1155    cmp_pair!(
1156        push_flt,
1157        push_flt_local,
1158        push_fgt,
1159        push_fgt_local,
1160        Binop::Float(FloatBinop::Less)
1161    );
1162    cmp_pair!(
1163        push_fle,
1164        push_fle_local,
1165        push_fge,
1166        push_fge_local,
1167        Binop::Float(FloatBinop::LessEqual)
1168    );
1169
1170    // --- Integer Equality ---
1171
1172    binop_leaf!(push_eq, push_eq_local, Binop::Int(IntBinop::Equal), Some(1));
1173    binop_leaf!(
1174        push_ne,
1175        push_ne_local,
1176        Binop::Int(IntBinop::NotEqual),
1177        Some(1)
1178    );
1179    binop_leaf!(
1180        push_feq,
1181        push_feq_local,
1182        Binop::Float(FloatBinop::Equal),
1183        Some(1)
1184    );
1185    binop_leaf!(
1186        push_fne,
1187        push_fne_local,
1188        Binop::Float(FloatBinop::NotEqual),
1189        Some(1)
1190    );
1191
1192    // --- Bitwise ---
1193
1194    /// Logical XOR of two `bool` operands — a bitwise `Xor` over `bool`, which
1195    /// yields `bool` (exact on the `{0,1}` domain).
1196    pub fn push_bool_xor(
1197        &mut self,
1198        lhs: ValueId,
1199        rhs: ValueId,
1200    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1201        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1202        let local = self.push_bool_xor_local(lhs, rhs);
1203        self.insn_ref(local)
1204    }
1205
1206    /// Body-local sibling of [`push_bool_xor`](Self::push_bool_xor).
1207    pub fn push_bool_xor_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1208        debug_assert!(
1209            self.both_bool(lhs, rhs),
1210            "push_bool_xor: operands must be bool"
1211        );
1212        self.push_binop_local(Binop::Int(IntBinop::Xor), lhs, rhs, None)
1213    }
1214
1215    /// Logical AND of two `bool` operands (bitwise `And` over `bool`).
1216    pub fn push_bool_and(
1217        &mut self,
1218        lhs: ValueId,
1219        rhs: ValueId,
1220    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1221        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1222        let local = self.push_bool_and_local(lhs, rhs);
1223        self.insn_ref(local)
1224    }
1225
1226    /// Body-local sibling of [`push_bool_and`](Self::push_bool_and).
1227    pub fn push_bool_and_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1228        debug_assert!(
1229            self.both_bool(lhs, rhs),
1230            "push_bool_and: operands must be bool"
1231        );
1232        self.push_binop_local(Binop::Int(IntBinop::And), lhs, rhs, None)
1233    }
1234
1235    /// Logical OR of two `bool` operands (bitwise `Or` over `bool`).
1236    pub fn push_bool_or(
1237        &mut self,
1238        lhs: ValueId,
1239        rhs: ValueId,
1240    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1241        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1242        let local = self.push_bool_or_local(lhs, rhs);
1243        self.insn_ref(local)
1244    }
1245
1246    /// Body-local sibling of [`push_bool_or`](Self::push_bool_or).
1247    pub fn push_bool_or_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1248        debug_assert!(
1249            self.both_bool(lhs, rhs),
1250            "push_bool_or: operands must be bool"
1251        );
1252        self.push_binop_local(Binop::Int(IntBinop::Or), lhs, rhs, None)
1253    }
1254
1255    /// Whether both operands carry the `bool` type (a `debug_assert` guard).
1256    fn both_bool(&self, lhs: LocalValueId, rhs: LocalValueId) -> bool {
1257        let is_bool = |v: LocalValueId| {
1258            self.lstored_type_of(v)
1259                .is_some_and(|t| self.shr().types.is_bool(t))
1260        };
1261        is_bool(lhs) && is_bool(rhs)
1262    }
1263
1264    binop_leaf!(
1265        push_bit_xor,
1266        push_bit_xor_local,
1267        Binop::Int(IntBinop::Xor),
1268        None
1269    );
1270    binop_leaf!(
1271        push_bit_or,
1272        push_bit_or_local,
1273        Binop::Int(IntBinop::Or),
1274        None
1275    );
1276    binop_leaf!(
1277        push_bit_and,
1278        push_bit_and_local,
1279        Binop::Int(IntBinop::And),
1280        None
1281    );
1282
1283    // --- Extensions & Conversions ---
1284
1285    pub fn push_is_nan(&mut self, src: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1286        let src = self.loc(src);
1287        let local = self.push_is_nan_local(src);
1288        self.insn_ref(local)
1289    }
1290
1291    /// Body-local sibling of [`push_is_nan`](Self::push_is_nan).
1292    pub fn push_is_nan_local(&mut self, src: LocalValueId) -> LocalInsnId {
1293        assert!(
1294            !matches!(src, LocalValueId::Varnode(_)),
1295            "push_is_nan: varnode operand not allowed"
1296        );
1297        self.store_insn(Mnemonic::IsFloatNaN(IsFloatNaN { src }), 1)
1298    }
1299
1300    unop_leaf!(push_abs, push_abs_local, Unop::FloatAbs);
1301    unop_leaf!(push_sqrt, push_sqrt_local, Unop::FloatSqrt);
1302    unop_leaf!(push_floor, push_floor_local, Unop::FloatFloor);
1303    unop_leaf!(push_ceil, push_ceil_local, Unop::FloatCeil);
1304    unop_leaf!(push_round, push_round_local, Unop::FloatRound);
1305
1306    conv_leaf!(
1307        push_int_to_float,
1308        push_int_to_float_local,
1309        "push_int_to_float: varnode operand not allowed",
1310        IntToFloat
1311    );
1312    conv_leaf!(
1313        push_float_to_float,
1314        push_float_to_float_local,
1315        "push_float_to_float: varnode operand not allowed",
1316        FloatToFloat
1317    );
1318    conv_leaf!(
1319        push_trunc,
1320        push_trunc_local,
1321        "push_trunc: varnode operand not allowed",
1322        FloatToInt
1323    );
1324    conv_leaf!(
1325        push_zext,
1326        push_zext_local,
1327        "push_zext: varnode operand not allowed",
1328        Zext
1329    );
1330    conv_leaf!(
1331        push_sext,
1332        push_sext_local,
1333        "push_sext: varnode operand not allowed",
1334        Sext
1335    );
1336
1337    /// Builds an aggregate value from `fields` using default field names
1338    /// (`field1`, `field2`, ...). The result type is the
1339    /// [`Aggregate`](crate::types::TypeRepr::Aggregate) of the named fields'
1340    /// types.
1341    pub fn push_tuple(
1342        &mut self,
1343        fields: Vec<ValueId>,
1344    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1345        let fields = self.loc_vec(fields);
1346        let local = self.push_tuple_local(fields);
1347        self.insn_ref(local)
1348    }
1349
1350    /// Body-local sibling of [`push_tuple`](Self::push_tuple).
1351    pub fn push_tuple_local(&mut self, fields: Vec<LocalValueId>) -> LocalInsnId {
1352        let named_fields = fields
1353            .into_iter()
1354            .enumerate()
1355            .map(|(i, value)| (format!("field{}", i + 1), value))
1356            .collect();
1357        self.push_named_tuple_local(named_fields)
1358    }
1359
1360    /// Builds an aggregate value from ordered named fields.
1361    pub fn push_named_tuple(
1362        &mut self,
1363        fields: Vec<(String, ValueId)>,
1364    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1365        let fields = fields
1366            .into_iter()
1367            .map(|(name, v)| (name, self.loc(v)))
1368            .collect();
1369        let local = self.push_named_tuple_local(fields);
1370        self.insn_ref(local)
1371    }
1372
1373    /// Body-local sibling of [`push_named_tuple`](Self::push_named_tuple).
1374    pub fn push_named_tuple_local(&mut self, fields: Vec<(String, LocalValueId)>) -> LocalInsnId {
1375        let field_types: Vec<TypeId> = fields.iter().map(|(_, f)| self.ltype_of(*f)).collect();
1376        let aggregate_fields = fields
1377            .iter()
1378            .zip(field_types)
1379            .map(|((name, _), type_id)| AggregateField::new(name.clone(), type_id))
1380            .collect();
1381        let ty = self
1382            .shr()
1383            .types
1384            .get_or_make_named_aggregate(aggregate_fields);
1385        self.push_named_tuple_local_with_type(fields, ty)
1386    }
1387
1388    /// Build a named tuple using an explicitly selected aggregate-like type.
1389    /// Used for nominal function-return records whose identity must not be
1390    /// structurally interned by [`push_named_tuple_local`](Self::push_named_tuple_local).
1391    pub fn push_named_tuple_with_type(
1392        &mut self,
1393        fields: Vec<(String, ValueId)>,
1394        ty: TypeId,
1395    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1396        let fields = fields
1397            .into_iter()
1398            .map(|(name, value)| (name, self.loc(value)))
1399            .collect();
1400        let local = self.push_named_tuple_local_with_type(fields, ty);
1401        self.insn_ref(local)
1402    }
1403
1404    /// Body-local sibling of [`push_named_tuple_with_type`](Self::push_named_tuple_with_type).
1405    pub fn push_named_tuple_local_with_type(
1406        &mut self,
1407        fields: Vec<(String, LocalValueId)>,
1408        ty: TypeId,
1409    ) -> LocalInsnId {
1410        debug_assert_eq!(
1411            self.shr().types.aggregate_fields(ty).map(<[_]>::len),
1412            Some(fields.len()),
1413            "explicit tuple type must declare every tuple field"
1414        );
1415        debug_assert!(fields.iter().enumerate().all(|(index, (name, value))| {
1416            self.shr()
1417                .types
1418                .aggregate_fields(ty)
1419                .and_then(|declared| declared.get(index))
1420                .is_some_and(|declared| {
1421                    declared.name == *name && declared.type_id == self.ltype_of(*value)
1422                })
1423        }));
1424        let values = fields.into_iter().map(|(_, value)| value).collect();
1425        self.store_insn_with_type(Mnemonic::Tuple(Tuple { fields: values }), ty)
1426    }
1427
1428    /// Projects field `index` out of the aggregate value `agg`. The result type
1429    /// is that field's type. Panics if `agg` is not an aggregate with that field.
1430    pub fn push_extract(
1431        &mut self,
1432        agg: ValueId,
1433        index: usize,
1434    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1435        let agg = self.loc(agg);
1436        let local = self.push_extract_local(agg, index);
1437        self.insn_ref(local)
1438    }
1439
1440    /// Body-local sibling of [`push_extract`](Self::push_extract).
1441    pub fn push_extract_local(&mut self, agg: LocalValueId, index: usize) -> LocalInsnId {
1442        let agg_ty = self.ltype_of(agg);
1443        let ty = self
1444            .shr()
1445            .types
1446            .field_type(agg_ty, index)
1447            .expect("push_extract: agg is not an aggregate with that field index");
1448        self.store_insn_with_type(Mnemonic::Extract(Extract { agg, index }), ty)
1449    }
1450
1451    /// Builds a total element-wise map `out[i] = body(src[i], captures…)` over the
1452    /// array value `src`. The body is **unary** in the element (index-aware bodies
1453    /// take an [`enumerate`](crate::value::insn::Intrinsic) tuple as that element);
1454    /// `body` is a function symbol, not an operand. Soundness of the body (pure,
1455    /// element-local) is the recognizer's obligation; the builder only wires the
1456    /// value graph.
1457    ///
1458    /// The result is `[U; N]` where `N` is `src`'s element count and `U` is the
1459    /// body's return type — which need not equal the input element type (e.g. a
1460    /// map over `enumerate(arr)` consumes tuples but returns bare elements). When
1461    /// the body is a bare symbol with no return (or `src` is not an array), the
1462    /// result falls back to `src`'s type.
1463    pub fn push_map(
1464        &mut self,
1465        body: impl Into<Callee>,
1466        src: ValueId,
1467        captures: Vec<ValueId>,
1468    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1469        let (src, captures) = (self.loc(src), self.loc_vec(captures));
1470        let local = self.push_map_local(body, src, captures);
1471        self.insn_ref(local)
1472    }
1473
1474    /// Body-local sibling of [`push_map`](Self::push_map).
1475    pub fn push_map_local(
1476        &mut self,
1477        body: impl Into<Callee>,
1478        src: LocalValueId,
1479        captures: Vec<LocalValueId>,
1480    ) -> LocalInsnId {
1481        let body = body.into();
1482        let src_ty = self.ltype_of(src);
1483        // `map` preserves the source's sequence kind: an array maps to an array,
1484        // a list (e.g. `take_while`'s result) maps to a list of the same bound.
1485        let seq = self.shr().types.seq_of(src_ty);
1486        let ret_ty = body.real().and_then(|body| self.map_body_return_type(body));
1487        let ty = match (seq, ret_ty) {
1488            (Some((_, len, is_list)), Some(rt)) => {
1489                self.shr().types.get_or_make_seq(rt, len, is_list)
1490            }
1491            _ => src_ty,
1492        };
1493        self.push_map_typed_local(body, src, captures, ty)
1494    }
1495
1496    /// Builds a map with an explicitly prepared result type. Use this when the
1497    /// body is foreign to this Builder and its body-derived return type is not
1498    /// part of the published function interface.
1499    pub fn push_map_typed(
1500        &mut self,
1501        body: impl Into<Callee>,
1502        src: ValueId,
1503        captures: Vec<ValueId>,
1504        result_type: TypeId,
1505    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1506        let (src, captures) = (self.loc(src), self.loc_vec(captures));
1507        let local = self.push_map_typed_local(body, src, captures, result_type);
1508        self.insn_ref(local)
1509    }
1510
1511    /// Body-local sibling of [`push_map_typed`](Self::push_map_typed).
1512    pub fn push_map_typed_local(
1513        &mut self,
1514        body: impl Into<Callee>,
1515        src: LocalValueId,
1516        captures: Vec<LocalValueId>,
1517        result_type: TypeId,
1518    ) -> LocalInsnId {
1519        self.store_insn_with_type(
1520            Mnemonic::Map(Map {
1521                body: body.into(),
1522                src,
1523                captures,
1524            }),
1525            result_type,
1526        )
1527    }
1528
1529    /// Builds a total left-scan `out[i] = body(acc_i, src[i], captures…)` with
1530    /// `acc_0 = init` over the array value `src` (see [`Scan`]). The body is
1531    /// **binary** in `(accumulator, element)` — index-aware bodies take an
1532    /// [`enumerate`](crate::value::insn::Intrinsic) tuple as the element; `body`
1533    /// is a function symbol, not an operand. Soundness of the body (pure, with the
1534    /// accumulator threaded only through the scan) is the recognizer's obligation.
1535    ///
1536    /// The result is `[U; N]` where `N` is `src`'s element count and `U` is the
1537    /// body's return type (also the accumulator type). When the body is a bare
1538    /// symbol with no return (or `src` is not an array), the result falls back to
1539    /// `src`'s type.
1540    pub fn push_scan(
1541        &mut self,
1542        body: impl Into<Callee>,
1543        init: ValueId,
1544        src: ValueId,
1545        captures: Vec<ValueId>,
1546    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1547        let (init, src, captures) = (self.loc(init), self.loc(src), self.loc_vec(captures));
1548        let local = self.push_scan_local(body, init, src, captures);
1549        self.insn_ref(local)
1550    }
1551
1552    /// Body-local sibling of [`push_scan`](Self::push_scan).
1553    pub fn push_scan_local(
1554        &mut self,
1555        body: impl Into<Callee>,
1556        init: LocalValueId,
1557        src: LocalValueId,
1558        captures: Vec<LocalValueId>,
1559    ) -> LocalInsnId {
1560        let body = body.into();
1561        let src_ty = self.ltype_of(src);
1562        // Like `map`, a scan preserves the source's sequence kind and takes its
1563        // element type from the body's return type (the accumulator type).
1564        let seq = self.shr().types.seq_of(src_ty);
1565        let ret_ty = body.real().and_then(|body| self.map_body_return_type(body));
1566        let ty = match (seq, ret_ty) {
1567            (Some((_, len, is_list)), Some(rt)) => {
1568                self.shr().types.get_or_make_seq(rt, len, is_list)
1569            }
1570            _ => src_ty,
1571        };
1572        self.push_scan_typed_local(body, init, src, captures, ty)
1573    }
1574
1575    /// Builds a scan with an explicitly prepared result type. This is the
1576    /// foreign-body counterpart to [`push_map_typed`](Self::push_map_typed).
1577    pub fn push_scan_typed(
1578        &mut self,
1579        body: impl Into<Callee>,
1580        init: ValueId,
1581        src: ValueId,
1582        captures: Vec<ValueId>,
1583        result_type: TypeId,
1584    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1585        let (init, src, captures) = (self.loc(init), self.loc(src), self.loc_vec(captures));
1586        let local = self.push_scan_typed_local(body, init, src, captures, result_type);
1587        self.insn_ref(local)
1588    }
1589
1590    /// Body-local sibling of [`push_scan_typed`](Self::push_scan_typed).
1591    pub fn push_scan_typed_local(
1592        &mut self,
1593        body: impl Into<Callee>,
1594        init: LocalValueId,
1595        src: LocalValueId,
1596        captures: Vec<LocalValueId>,
1597        result_type: TypeId,
1598    ) -> LocalInsnId {
1599        self.store_insn_with_type(
1600            Mnemonic::Scan(Scan {
1601                body: body.into(),
1602                init,
1603                src,
1604                captures,
1605            }),
1606            result_type,
1607        )
1608    }
1609
1610    /// Builds a value-level application of a pure lambda function. Unlike
1611    /// [`push_call`](Self::push_call), this is an ordinary SSA instruction and
1612    /// does not terminate the current block.
1613    pub fn push_apply(
1614        &mut self,
1615        target: impl Into<Callee>,
1616        args: Vec<ValueId>,
1617    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1618        let args = self.loc_vec(args);
1619        let local = self.push_apply_local(target, args);
1620        self.insn_ref(local)
1621    }
1622
1623    /// Body-local sibling of [`push_apply`](Self::push_apply).
1624    pub fn push_apply_local(
1625        &mut self,
1626        target: impl Into<Callee>,
1627        args: Vec<LocalValueId>,
1628    ) -> LocalInsnId {
1629        let target = target.into();
1630        let ty = target
1631            .real()
1632            .and_then(|target| self.lambda_return_type(target))
1633            .unwrap_or_else(|| {
1634                args.first()
1635                    .map(|&arg| self.ltype_of(arg))
1636                    .unwrap_or_else(|| self.shr().types.get_or_make_int(0))
1637            });
1638        self.store_insn_with_type(Mnemonic::Apply(Apply { target, args }), ty)
1639    }
1640
1641    /// The type of the value returned by `body`'s first `Return`, or `None` if
1642    /// `body` is not this (self) body, has no root, or returns nothing — used to
1643    /// size a [`push_map`] result. Id-less: reads this body's own arenas.
1644    fn map_body_return_type(&self, body: FunctionId) -> Option<TypeId> {
1645        if self.body.try_id() != Some(body) {
1646            return None;
1647        }
1648        let root = self.body.root_id()?;
1649        self.body.blocks[root].instructions.iter().find_map(|&i| {
1650            match self.body.insns[i].mnemonic() {
1651                Mnemonic::Return(r) => r.value.and_then(|v| self.lstored_type_of(v)),
1652                _ => None,
1653            }
1654        })
1655    }
1656
1657    /// The type of the first value returned by a lambda body (this body). Id-less.
1658    fn lambda_return_type(&self, body: FunctionId) -> Option<TypeId> {
1659        if self.body.try_id() != Some(body) {
1660            return None;
1661        }
1662        self.body
1663            .roster
1664            .iter()
1665            .flat_map(|&b| self.body.blocks[b].instructions.iter().copied())
1666            .find_map(|i| match self.body.insns[i].mnemonic() {
1667                Mnemonic::ReturnValue(r) => self.lstored_type_of(r.value),
1668                _ => None,
1669            })
1670    }
1671
1672    /// Computes the address of the field at byte `offset` of the struct that
1673    /// `base` points at: `gep(base, offset)`. `base` must have a
1674    /// [`StructPointer`](crate::types::TypeRepr::StructPointer) type whose
1675    /// pointee has a field at exactly `offset`. The result type is a pointer
1676    /// (same width as `base`) to that field's type. Panics otherwise.
1677    pub fn push_gep(
1678        &mut self,
1679        base: ValueId,
1680        offset: usize,
1681    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1682        let base = self.loc(base);
1683        let local = self.push_gep_local(base, offset);
1684        self.insn_ref(local)
1685    }
1686
1687    /// Body-local sibling of [`push_gep`](Self::push_gep).
1688    pub fn push_gep_local(&mut self, base: LocalValueId, offset: usize) -> LocalInsnId {
1689        let base_ty = self.ltype_of(base);
1690        let types = &self.shr().types;
1691        let ptr_width = types.size_of(base_ty);
1692        let pointee = types
1693            .pointee_of(base_ty)
1694            .expect("push_gep: base is not a struct pointer");
1695        let field_ty = types
1696            .field_by_offset(pointee, offset)
1697            .map(|(_, field)| field.type_id)
1698            .expect("push_gep: no field at that offset in the pointee struct");
1699        let ty = self
1700            .shr()
1701            .types
1702            .get_or_make_struct_pointer(ptr_width, field_ty);
1703        self.store_insn_with_type(Mnemonic::Gep(Gep { base, offset }), ty)
1704    }
1705
1706    /// Like [`push_gep`](Builder::push_gep) but selects the field by name,
1707    /// resolving it to a byte offset via the pointee struct of `base`. Panics if
1708    /// `base` is not a struct pointer or has no field of that name.
1709    pub fn push_gep_field(
1710        &mut self,
1711        base: ValueId,
1712        name: &str,
1713    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1714        let base = self.loc(base);
1715        let local = self.push_gep_field_local(base, name);
1716        self.insn_ref(local)
1717    }
1718
1719    /// Body-local sibling of [`push_gep_field`](Self::push_gep_field).
1720    pub fn push_gep_field_local(&mut self, base: LocalValueId, name: &str) -> LocalInsnId {
1721        let base_ty = self.ltype_of(base);
1722        let types = &self.shr().types;
1723        let pointee = types
1724            .pointee_of(base_ty)
1725            .expect("push_gep_field: base is not a struct pointer");
1726        let offset = types
1727            .aggregate_fields(pointee)
1728            .and_then(|fields| fields.iter().find(|f| f.name == name))
1729            .map(|f| f.offset)
1730            .expect("push_gep_field: pointee struct has no field of that name");
1731        self.push_gep_local(base, offset)
1732    }
1733
1734    pub fn push_popcount(
1735        &mut self,
1736        src: ValueId,
1737        size: usize,
1738    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1739        let src = self.loc(src);
1740        let local = self.push_popcount_local(src, size);
1741        self.insn_ref(local)
1742    }
1743
1744    /// Body-local sibling of [`push_popcount`](Self::push_popcount).
1745    pub fn push_popcount_local(&mut self, src: LocalValueId, size: usize) -> LocalInsnId {
1746        assert!(
1747            !matches!(src, LocalValueId::Varnode(_)),
1748            "push_popcount: varnode operand not allowed"
1749        );
1750        self.store_insn(Mnemonic::PopCount(PopCount { src }), size)
1751    }
1752
1753    pub fn push_lzcount(
1754        &mut self,
1755        src: ValueId,
1756        size: usize,
1757    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1758        let src = self.loc(src);
1759        let local = self.push_lzcount_local(src, size);
1760        self.insn_ref(local)
1761    }
1762
1763    /// Body-local sibling of [`push_lzcount`](Self::push_lzcount).
1764    pub fn push_lzcount_local(&mut self, src: LocalValueId, size: usize) -> LocalInsnId {
1765        assert!(
1766            !matches!(src, LocalValueId::Varnode(_)),
1767            "push_lzcount: varnode operand not allowed"
1768        );
1769        self.store_insn(Mnemonic::LzCount(LzCount { src }), size)
1770    }
1771
1772    pub fn push_carry(
1773        &mut self,
1774        lhs: ValueId,
1775        rhs: ValueId,
1776    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1777        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1778        let local = self.push_carry_local(lhs, rhs);
1779        self.insn_ref(local)
1780    }
1781
1782    /// Body-local sibling of [`push_carry`](Self::push_carry).
1783    pub fn push_carry_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1784        assert!(
1785            !matches!(lhs, LocalValueId::Varnode(_)) && !matches!(rhs, LocalValueId::Varnode(_)),
1786            "push_carry: varnode operand not allowed"
1787        );
1788        self.store_insn(Mnemonic::Carry(Carry { lhs, rhs }), 1)
1789    }
1790
1791    pub fn push_scarry(
1792        &mut self,
1793        lhs: ValueId,
1794        rhs: ValueId,
1795    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1796        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1797        let local = self.push_scarry_local(lhs, rhs);
1798        self.insn_ref(local)
1799    }
1800
1801    /// Body-local sibling of [`push_scarry`](Self::push_scarry).
1802    pub fn push_scarry_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1803        assert!(
1804            !matches!(lhs, LocalValueId::Varnode(_)) && !matches!(rhs, LocalValueId::Varnode(_)),
1805            "push_scarry: varnode operand not allowed"
1806        );
1807        self.store_insn(Mnemonic::SCarry(SCarry { lhs, rhs }), 1)
1808    }
1809
1810    pub fn push_sborrow(
1811        &mut self,
1812        lhs: ValueId,
1813        rhs: ValueId,
1814    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1815        let (lhs, rhs) = (self.loc(lhs), self.loc(rhs));
1816        let local = self.push_sborrow_local(lhs, rhs);
1817        self.insn_ref(local)
1818    }
1819
1820    /// Body-local sibling of [`push_sborrow`](Self::push_sborrow).
1821    pub fn push_sborrow_local(&mut self, lhs: LocalValueId, rhs: LocalValueId) -> LocalInsnId {
1822        assert!(
1823            !matches!(lhs, LocalValueId::Varnode(_)) && !matches!(rhs, LocalValueId::Varnode(_)),
1824            "push_sborrow: varnode operand not allowed"
1825        );
1826        self.store_insn(Mnemonic::SBorrow(SBorrow { lhs, rhs }), 1)
1827    }
1828
1829    pub fn push_pcode_op(
1830        &mut self,
1831        id: PCodeOpId,
1832        args: Vec<ValueId>,
1833        dst: Option<ValueId>,
1834        size: usize,
1835    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1836        let args = self.loc_vec(args);
1837        let dst = dst.map(|d| self.loc(d));
1838        let local = self.push_pcode_op_local(id, args, dst, size);
1839        self.insn_ref(local)
1840    }
1841
1842    /// Body-local sibling of [`push_pcode_op`](Self::push_pcode_op).
1843    pub fn push_pcode_op_local(
1844        &mut self,
1845        id: PCodeOpId,
1846        args: Vec<LocalValueId>,
1847        dst: Option<LocalValueId>,
1848        size: usize,
1849    ) -> LocalInsnId {
1850        let args = args
1851            .into_iter()
1852            .map(|arg| self.ensure_local_local(arg))
1853            .collect::<Vec<_>>();
1854
1855        self.store_insn(Mnemonic::PCodeOp(PCodeOp { id, args, dst }), size)
1856    }
1857
1858    /// Creates a pure intrinsic instruction (e.g. `rol`, `ror`, `enumerate`).
1859    ///
1860    /// Validates the operand count against the intrinsic's declared arity and
1861    /// types the node via the intrinsic's
1862    /// [`result_type`](crate::value::insn::Intrinsic::result_type)
1863    /// rule, so the result carries its full type (not just a width) — an array
1864    /// or aggregate result is projectable. Panics on an arity mismatch.
1865    #[track_caller]
1866    pub fn push_intrinsic(
1867        &mut self,
1868        id: IntrinsicId,
1869        args: Vec<ValueId>,
1870    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1871        let args = self.loc_vec(args);
1872        let local = self.push_intrinsic_local(id, args);
1873        self.insn_ref(local)
1874    }
1875
1876    /// Body-local sibling of [`push_intrinsic`](Self::push_intrinsic).
1877    #[track_caller]
1878    pub fn push_intrinsic_local(
1879        &mut self,
1880        id: IntrinsicId,
1881        args: Vec<LocalValueId>,
1882    ) -> LocalInsnId {
1883        let desc = id.desc();
1884        assert_eq!(
1885            args.len(),
1886            desc.arity(),
1887            "intrinsic `{}` expects {} args, got {}",
1888            desc.name(),
1889            desc.arity(),
1890            args.len()
1891        );
1892
1893        let args = args
1894            .into_iter()
1895            .map(|arg| self.ensure_local_local(arg))
1896            .collect::<Vec<_>>();
1897
1898        let arg_types = args
1899            .iter()
1900            .map(|&arg| self.ltype_of(arg))
1901            .collect::<Vec<_>>();
1902        let type_id = desc.result_type(&self.shr().types, &arg_types);
1903
1904        self.store_insn_with_type(Mnemonic::Intrinsic(IntrinsicApp { id, args }), type_id)
1905    }
1906
1907    // --- Loads & Stores ---
1908
1909    /// Creates a copy instruction from `src` to `dst`.
1910    /// 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.
1911    /// If `dst` is a varnode, we aren't allowed to write to it, this is a store operation
1912    /// If `src` is a varnode, we need to read from it first, then write to dst
1913    /// For values wider than 64 bits (e.g. XMM/YMM/ZMM registers), emits one store per 64-bit lane.
1914    pub fn push_copy(
1915        &mut self,
1916        src: ValueId,
1917        dst: impl Into<ValueId>,
1918    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
1919        let src = self.loc(src);
1920        let dst = self.loc(dst.into());
1921        let local = self.push_copy_local(src, dst);
1922        self.insn_ref(local)
1923    }
1924
1925    /// Body-local sibling of [`push_copy`](Self::push_copy).
1926    pub fn push_copy_local(&mut self, src: LocalValueId, dst: LocalValueId) -> LocalInsnId {
1927        let (size, space, name) = match dst {
1928            LocalValueId::Varnode(vid) => {
1929                let node = Varnode::from_id(self.shr(), vid);
1930                (
1931                    node.size(),
1932                    LocalMemorySpaceId::Shared(node.space().id),
1933                    node.name().map(str::to_owned),
1934                )
1935            }
1936            LocalValueId::Temp(tlocal) => {
1937                let temp = &self.body.temps[tlocal];
1938                (
1939                    temp.size,
1940                    LocalMemorySpaceId::Temp(temp.space),
1941                    temp.name.as_deref().map(str::to_owned),
1942                )
1943            }
1944            _ => panic!("copy destination must be a varnode or body-local temporary"),
1945        };
1946
1947        const LANE_SIZE: usize = 8;
1948
1949        if size > LANE_SIZE {
1950            let num_lanes = size.div_ceil(LANE_SIZE);
1951            let mut first_id = None;
1952
1953            for lane in 0..num_lanes {
1954                let offset = lane * LANE_SIZE;
1955                let lane_size = cmp::min(LANE_SIZE, size - offset);
1956
1957                let src_lane = self
1958                    .get_range_local(src, offset..offset + lane_size)
1959                    .expect("lane range in bounds");
1960                let src_lane = self.ensure_local_local(src_lane);
1961
1962                let dst_lane = self
1963                    .get_range_local(dst, offset..offset + lane_size)
1964                    .expect("lane range in bounds");
1965
1966                let id = self.store_insn(
1967                    Mnemonic::Store(Store {
1968                        src: src_lane,
1969                        ptr: dst_lane,
1970                        space,
1971                        size: lane_size,
1972                    }),
1973                    0,
1974                );
1975
1976                if let Some(name) = &name {
1977                    let name = Cow::Owned(format!("{}_lane{lane}", name.to_lowercase()));
1978                    let _ = self.rename_insn_local(id, name);
1979                }
1980
1981                first_id.get_or_insert(id);
1982            }
1983
1984            first_id.unwrap()
1985        } else {
1986            let src = self.ensure_local_local(src);
1987            // If dst is a varnode, we need to emit a store from src to dst
1988            let id = self.store_insn(
1989                Mnemonic::Store(Store {
1990                    src,
1991                    ptr: dst,
1992                    space,
1993                    size,
1994                }),
1995                0,
1996            );
1997
1998            // Add a name hint for the store instruction for easier debugging
1999            if let Some(name) = &name {
2000                let lowered = name.to_lowercase();
2001                let name = self.body.names.unique(Cow::Owned(lowered));
2002                self.rename_insn_local(id, name)
2003                    .expect("This name was deduplicated");
2004            }
2005
2006            id
2007        }
2008    }
2009
2010    #[track_caller]
2011    pub fn push_store(
2012        &mut self,
2013        src: ValueId,
2014        ptr: ValueId,
2015        space: impl Into<LocalMemorySpaceId>,
2016    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2017        let (src, ptr) = (self.loc(src), self.loc(ptr));
2018        let local = self.push_store_local(src, ptr, space);
2019        self.insn_ref(local)
2020    }
2021
2022    /// Body-local sibling of [`push_store`](Self::push_store).
2023    #[track_caller]
2024    pub fn push_store_local(
2025        &mut self,
2026        src: LocalValueId,
2027        ptr: LocalValueId,
2028        space: impl Into<LocalMemorySpaceId>,
2029    ) -> LocalInsnId {
2030        let space = space.into();
2031        let src = self.ensure_local_local(src);
2032        let size = self.lsize_of(src);
2033
2034        match ptr {
2035            LocalValueId::Varnode(id) => {
2036                let varnode = Varnode::from_id(self.shr(), id);
2037                if varnode.space().id != space {
2038                    panic!(
2039                        "push_store: ptr is a varnode but its space {:?} does not match the store space {:?}; \
2040                             call ensure_local on the ptr first",
2041                        varnode.space().id,
2042                        space
2043                    );
2044                }
2045            }
2046
2047            LocalValueId::Instruction(local) => {
2048                self.set_insn_space_local(local, space);
2049            }
2050
2051            _ => {}
2052        }
2053
2054        self.store_insn(
2055            Mnemonic::Store(Store {
2056                src,
2057                ptr,
2058                space,
2059                size,
2060            }),
2061            0,
2062        )
2063    }
2064
2065    // --- Branches & Calls ---
2066
2067    /// Declares a new parameter on the current block.
2068    pub fn push_param(&mut self, size: usize) -> BlockParamId {
2069        let local = self.push_param_local(size);
2070        crate::value::block_param::BlockParamId::new(self.func(), local)
2071    }
2072
2073    /// Body-local sibling of [`push_param`](Self::push_param).
2074    pub fn push_param_local(&mut self, size: usize) -> crate::value::LocalParamId {
2075        let block = self.block;
2076        let index = self.body.blocks[block].params.len();
2077        let type_id = self.shared.types.get_or_make_int(size);
2078        let local = self.body.params.push(BlockParam {
2079            index,
2080            type_id,
2081            parent: Some(block),
2082            name: None,
2083            origin: None,
2084        });
2085        self.body.blocks[block].params.push(local);
2086        local
2087    }
2088
2089    /// Terminates the current block with a branch to an already-resolved local
2090    /// target. Module/address discovery must happen before the Builder borrow.
2091    pub fn finalize(mut self, target: BlockId) {
2092        self.finalize_local(target.local)
2093    }
2094
2095    /// Body-local sibling of [`finalize`](Self::finalize).
2096    pub fn finalize_local(&mut self, target: LocalBlockId) {
2097        if !self.is_terminated() {
2098            let branch = self.push_branch_local(target);
2099            if let Some(address) = self.address {
2100                self.body.insns[branch].set_address(address);
2101            }
2102        }
2103    }
2104
2105    /// Add a CFG edge from the working block to `target`, both body-local
2106    /// (id-less twin of [`FunctionBody::add_cfg_edge`]).
2107    fn add_cfg_edge_local(&mut self, from: LocalBlockId, to: LocalBlockId) {
2108        let edge_id = self
2109            .body
2110            .edges
2111            .push(crate::value::block::cfg::EdgeData { from, to });
2112        self.body.blocks[from].edges.insert(edge_id);
2113        self.body.blocks[to].edges.insert(edge_id);
2114    }
2115
2116    /// Terminates this block with an unconditional jump to the given target block.
2117    /// The builder is now safe to drop without panicking, and the block is properly terminated.
2118    pub fn push_branch(&mut self, target: BlockId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2119        let local = self.push_branch_local(target.local);
2120        self.insn_ref(local)
2121    }
2122
2123    /// Body-local sibling of [`push_branch`](Self::push_branch).
2124    pub fn push_branch_local(&mut self, target: LocalBlockId) -> LocalInsnId {
2125        self.push_branch_with_args_local(target, vec![])
2126    }
2127
2128    /// Unconditional branch passing `args` to the target block's parameters.
2129    pub fn push_branch_with_args(
2130        &mut self,
2131        target: BlockId,
2132        args: Vec<ValueId>,
2133    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2134        let args = self.loc_vec(args);
2135        let local = self.push_branch_with_args_local(target.local, args);
2136        self.insn_ref(local)
2137    }
2138
2139    /// Body-local sibling of [`push_branch_with_args`](Self::push_branch_with_args).
2140    pub fn push_branch_with_args_local(
2141        &mut self,
2142        target: LocalBlockId,
2143        args: Vec<LocalValueId>,
2144    ) -> LocalInsnId {
2145        let current = self.block;
2146        self.add_cfg_edge_local(current, target);
2147        let id = self.store_insn(Mnemonic::Branch(Branch { target, args }), 0);
2148        self.is_terminated = true;
2149        id
2150    }
2151
2152    pub fn push_cbranch(
2153        &mut self,
2154        condition: ValueId,
2155        target: BlockId,
2156        fallthrough: BlockId,
2157    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2158        self.push_cbranch_with_args(condition, target, vec![], fallthrough, vec![])
2159    }
2160
2161    /// Body-local sibling of [`push_cbranch`](Self::push_cbranch).
2162    pub fn push_cbranch_local(
2163        &mut self,
2164        condition: LocalValueId,
2165        target: LocalBlockId,
2166        fallthrough: LocalBlockId,
2167    ) -> LocalInsnId {
2168        self.push_cbranch_with_args_local(condition, target, vec![], fallthrough, vec![])
2169    }
2170
2171    /// Conditional branch with per-target arguments.
2172    pub fn push_cbranch_with_args(
2173        &mut self,
2174        condition: ValueId,
2175        target: BlockId,
2176        target_args: Vec<ValueId>,
2177        fallthrough: BlockId,
2178        fallthrough_args: Vec<ValueId>,
2179    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2180        let condition = self.loc(condition);
2181        let target_args = self.loc_vec(target_args);
2182        let fallthrough_args = self.loc_vec(fallthrough_args);
2183        let local = self.push_cbranch_with_args_local(
2184            condition,
2185            target.local,
2186            target_args,
2187            fallthrough.local,
2188            fallthrough_args,
2189        );
2190        self.insn_ref(local)
2191    }
2192
2193    /// Body-local sibling of
2194    /// [`push_cbranch_with_args`](Self::push_cbranch_with_args).
2195    pub fn push_cbranch_with_args_local(
2196        &mut self,
2197        condition: LocalValueId,
2198        target: LocalBlockId,
2199        target_args: Vec<LocalValueId>,
2200        fallthrough: LocalBlockId,
2201        fallthrough_args: Vec<LocalValueId>,
2202    ) -> LocalInsnId {
2203        assert!(
2204            !matches!(condition, LocalValueId::Varnode(_)),
2205            "push_cbranch: varnode condition not allowed; load the value first"
2206        );
2207        let current = self.block;
2208        self.add_cfg_edge_local(current, target);
2209        self.add_cfg_edge_local(current, fallthrough);
2210        let id = self.store_insn(
2211            Mnemonic::CBranch(CBranch {
2212                success_block: target,
2213                success_args: target_args,
2214                condition,
2215                failure_block: fallthrough,
2216                failure_args: fallthrough_args,
2217            }),
2218            0,
2219        );
2220        self.is_terminated = true;
2221        id
2222    }
2223
2224    /// Multi-way dispatch on `scrutinee`. Wires a CFG edge to every arm and to
2225    /// the default, exactly as the conditional branch wires its two.
2226    pub fn push_switch(
2227        &mut self,
2228        scrutinee: ValueId,
2229        cases: Vec<(u64, BlockId, Vec<ValueId>)>,
2230        default: Option<(BlockId, Vec<ValueId>)>,
2231    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2232        let scrutinee = self.loc(scrutinee);
2233        let cases = cases
2234            .into_iter()
2235            .map(|(value, target, args)| (value, target.local, self.loc_vec(args)))
2236            .collect();
2237        let default = default.map(|(target, args)| (target.local, self.loc_vec(args)));
2238        let local = self.push_switch_local(scrutinee, cases, default);
2239        self.insn_ref(local)
2240    }
2241
2242    /// Body-local sibling of [`push_switch`](Self::push_switch).
2243    pub fn push_switch_local(
2244        &mut self,
2245        scrutinee: LocalValueId,
2246        cases: Vec<(u64, LocalBlockId, Vec<LocalValueId>)>,
2247        default: Option<(LocalBlockId, Vec<LocalValueId>)>,
2248    ) -> LocalInsnId {
2249        assert!(
2250            !matches!(scrutinee, LocalValueId::Varnode(_)),
2251            "push_switch: varnode scrutinee not allowed; load the value first"
2252        );
2253        let current = self.block;
2254        for &(_, target, _) in &cases {
2255            self.add_cfg_edge_local(current, target);
2256        }
2257        if let Some((target, _)) = &default {
2258            self.add_cfg_edge_local(current, *target);
2259        }
2260        let (default_block, default_args) = match default {
2261            Some((target, args)) => (Some(target), args),
2262            None => (None, Vec::new()),
2263        };
2264        let id = self.store_insn(
2265            Mnemonic::Switch(Switch {
2266                scrutinee,
2267                cases: cases
2268                    .into_iter()
2269                    .map(|(value, target, args)| SwitchArm {
2270                        value,
2271                        target,
2272                        args,
2273                    })
2274                    .collect(),
2275                default: default_block,
2276                default_args,
2277            }),
2278            0,
2279        );
2280        self.is_terminated = true;
2281        id
2282    }
2283
2284    pub fn push_branchind(&mut self, ptr: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2285        let ptr = self.loc(ptr);
2286        let local = self.push_branchind_local(ptr);
2287        self.insn_ref(local)
2288    }
2289
2290    /// Body-local sibling of [`push_branchind`](Self::push_branchind).
2291    pub fn push_branchind_local(&mut self, ptr: LocalValueId) -> LocalInsnId {
2292        let id = self.store_insn(Mnemonic::BranchInd(BranchInd { ptr }), 0);
2293        self.is_terminated = true;
2294        id
2295    }
2296
2297    pub fn push_call(
2298        &mut self,
2299        target: impl Into<Callee>,
2300    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2301        self.push_call_with_args(target, vec![])
2302    }
2303
2304    pub fn push_call_with_args(
2305        &mut self,
2306        target: impl Into<Callee>,
2307        args: Vec<ValueId>,
2308    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2309        let args = self.loc_vec(args);
2310        let local = self.push_call_with_args_local(target, args);
2311        self.insn_ref(local)
2312    }
2313
2314    /// Body-local sibling of [`push_call`](Self::push_call).
2315    pub fn push_call_local(&mut self, target: impl Into<Callee>) -> LocalInsnId {
2316        self.push_call_with_args_local(target, vec![])
2317    }
2318
2319    /// Body-local sibling of [`push_call_with_args`](Self::push_call_with_args).
2320    pub fn push_call_with_args_local(
2321        &mut self,
2322        target: impl Into<Callee>,
2323        args: Vec<LocalValueId>,
2324    ) -> LocalInsnId {
2325        let target = target.into();
2326        let id = self.store_insn(
2327            Mnemonic::Call(Call {
2328                target,
2329                args,
2330                clobbers: vec![],
2331                tag: Default::default(),
2332            }),
2333            0,
2334        );
2335        self.is_terminated = true;
2336        id
2337    }
2338
2339    /// Tail call to another function's entry — a function-level terminator with
2340    /// no intra-function CFG successor (see [`TailCall`]).
2341    /// Unlike [`push_branch`](Self::push_branch), this wires no CFG edge: control
2342    /// leaves the function.
2343    pub fn push_tail_call(
2344        &mut self,
2345        target: impl Into<Callee>,
2346    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2347        self.push_tail_call_with_args(target, vec![])
2348    }
2349
2350    pub fn push_tail_call_with_args(
2351        &mut self,
2352        target: impl Into<Callee>,
2353        args: Vec<ValueId>,
2354    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2355        let args = self.loc_vec(args);
2356        let local = self.push_tail_call_with_args_local(target, args);
2357        self.insn_ref(local)
2358    }
2359
2360    /// Body-local sibling of [`push_tail_call`](Self::push_tail_call).
2361    pub fn push_tail_call_local(&mut self, target: impl Into<Callee>) -> LocalInsnId {
2362        self.push_tail_call_with_args_local(target, vec![])
2363    }
2364
2365    /// Body-local sibling of
2366    /// [`push_tail_call_with_args`](Self::push_tail_call_with_args).
2367    pub fn push_tail_call_with_args_local(
2368        &mut self,
2369        target: impl Into<Callee>,
2370        args: Vec<LocalValueId>,
2371    ) -> LocalInsnId {
2372        let target = target.into();
2373        let id = self.store_insn(Mnemonic::TailCall(TailCall { target, args }), 0);
2374        self.is_terminated = true;
2375        id
2376    }
2377
2378    pub fn push_call_ind(&mut self, ptr: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2379        self.push_call_ind_with_args(ptr, vec![])
2380    }
2381
2382    pub fn push_call_ind_with_args(
2383        &mut self,
2384        ptr: ValueId,
2385        args: Vec<ValueId>,
2386    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2387        let (ptr, args) = (self.loc(ptr), self.loc_vec(args));
2388        let local = self.push_call_ind_with_args_local(ptr, args);
2389        self.insn_ref(local)
2390    }
2391
2392    /// Body-local sibling of [`push_call_ind`](Self::push_call_ind).
2393    pub fn push_call_ind_local(&mut self, ptr: LocalValueId) -> LocalInsnId {
2394        self.push_call_ind_with_args_local(ptr, vec![])
2395    }
2396
2397    /// Body-local sibling of
2398    /// [`push_call_ind_with_args`](Self::push_call_ind_with_args).
2399    pub fn push_call_ind_with_args_local(
2400        &mut self,
2401        ptr: LocalValueId,
2402        args: Vec<LocalValueId>,
2403    ) -> LocalInsnId {
2404        let id = self.store_insn(Mnemonic::CallInd(CallInd { ptr, args }), 0);
2405        self.is_terminated = true;
2406        id
2407    }
2408
2409    pub fn push_return(&mut self, ptr: ValueId) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2410        let ptr = self.loc(ptr);
2411        let local = self.push_return_local(ptr);
2412        self.insn_ref(local)
2413    }
2414
2415    /// Body-local sibling of [`push_return`](Self::push_return).
2416    pub fn push_return_local(&mut self, ptr: LocalValueId) -> LocalInsnId {
2417        self.push_return_at_local(None, ptr)
2418    }
2419
2420    pub fn push_return_with_value(
2421        &mut self,
2422        value: ValueId,
2423        ptr: ValueId,
2424    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2425        let (value, ptr) = (self.loc(value), self.loc(ptr));
2426        let local = self.push_return_at_local(Some(value), ptr);
2427        self.insn_ref(local)
2428    }
2429
2430    /// Body-local sibling of
2431    /// [`push_return_with_value`](Self::push_return_with_value).
2432    pub fn push_return_with_value_local(
2433        &mut self,
2434        value: LocalValueId,
2435        ptr: LocalValueId,
2436    ) -> LocalInsnId {
2437        self.push_return_at_local(Some(value), ptr)
2438    }
2439
2440    fn push_return_at_local(
2441        &mut self,
2442        value: Option<LocalValueId>,
2443        ptr: LocalValueId,
2444    ) -> LocalInsnId {
2445        let id = self.store_insn(Mnemonic::Return(Return { ptr, value }), 0);
2446        self.is_terminated = true;
2447        id
2448    }
2449
2450    pub fn push_return_value(
2451        &mut self,
2452        value: ValueId,
2453    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2454        let value = self.loc(value);
2455        let local = self.push_return_value_local(value);
2456        self.insn_ref(local)
2457    }
2458
2459    /// Terminate the current block with [`BadInsn`](crate::value::insn::BadInsn): bytes that did not decode to
2460    /// a valid instruction. No successors, no operands.
2461    pub fn push_bad_insn(&mut self) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2462        let local = self.push_bad_insn_local();
2463        self.insn_ref(local)
2464    }
2465
2466    /// Body-local sibling of [`push_bad_insn`](Self::push_bad_insn).
2467    pub fn push_bad_insn_local(&mut self) -> LocalInsnId {
2468        let id = self.store_insn(Mnemonic::BadInsn(crate::value::insn::BadInsn), 0);
2469        self.is_terminated = true;
2470        id
2471    }
2472
2473    /// Body-local sibling of [`push_return_value`](Self::push_return_value).
2474    pub fn push_return_value_local(&mut self, value: LocalValueId) -> LocalInsnId {
2475        let id = self.store_insn(Mnemonic::ReturnValue(ReturnValue { value }), 0);
2476        self.is_terminated = true;
2477        id
2478    }
2479
2480    // --- Assert ---
2481
2482    /// Asserts that a condition holds at this point in execution
2483    pub fn push_assert(
2484        &mut self,
2485        condition: ValueId,
2486    ) -> InstructionRef<'str, '_, BodyView<'_, 'str>> {
2487        let condition = self.loc(condition);
2488        let local = self.push_assert_local(condition);
2489        self.insn_ref(local)
2490    }
2491
2492    /// Body-local sibling of [`push_assert`](Self::push_assert).
2493    pub fn push_assert_local(&mut self, condition: LocalValueId) -> LocalInsnId {
2494        self.store_insn(Mnemonic::Assert(Assert { condition }), 0)
2495    }
2496}
2497
2498#[cfg(test)]
2499mod tests {
2500    use wazabin_qcode_macro::qcode;
2501
2502    use super::*;
2503    use crate::{context::Context, value::ModuleView};
2504
2505    #[test]
2506    fn checked_builder_matches_module_builder() {
2507        use crate::value::{
2508            FunctionId, FunctionRef, block::BasicBlock, function::FunctionBody,
2509            util::body_mut::BodyMut,
2510        };
2511
2512        // The same body over any host: consts and a couple of binops (exercising
2513        // type + literal minting through the interners' `&self` paths), then a
2514        // branch to a freshly minted local-label block. No varnode/temp-space
2515        // minting — a checked-out host has read-only shared access.
2516        fn body<'str>(b: &mut Builder<'str, '_>) {
2517            let c1 = b.shr().get_const(7, 8);
2518            let c2 = b.shr().get_const(9, 8);
2519            let sum = b.push_add(c1, c2).id();
2520            let _doubled = b.push_add(sum, sum).id();
2521            let lbl = b.get_or_make_local_label("next".into());
2522            b.push_branch(lbl);
2523        }
2524
2525        // Structural snapshot: per block (name, per-instruction mnemonic Debug —
2526        // which includes the operand ids — and sorted successor block names).
2527        type BSnap = Vec<(String, Vec<String>, Vec<String>)>;
2528        fn snap(ctx: &Context, fid: FunctionId) -> BSnap {
2529            FunctionRef::from_id(ctx, fid)
2530                .blocks()
2531                .map(|blk| {
2532                    let name = blk.name().unwrap_or("?").to_string();
2533                    let insns: Vec<String> = blk
2534                        .instructions()
2535                        .map(|i| format!("{:?}", i.mnemonic()))
2536                        .collect();
2537                    let mut succ: Vec<String> = blk
2538                        .successors()
2539                        .map(|(_, s)| {
2540                            BasicBlock::from_id(ctx, s)
2541                                .name()
2542                                .unwrap_or("?")
2543                                .to_string()
2544                        })
2545                        .collect();
2546                    succ.sort();
2547                    (name, insns, succ)
2548                })
2549                .collect()
2550        }
2551
2552        // ---- (a) module builder ---------------------------------------------
2553        let mut ctx_a = Context::new();
2554        let fid_a = FunctionBody::make(&mut ctx_a, "foo".into()).unwrap().id;
2555        let entry_a = FunctionBody::from_id_mut(&mut ctx_a, fid_a).make_root().id;
2556        {
2557            let mut b = ctx_a.builder(entry_a);
2558            body(&mut b);
2559        }
2560        let snap_a = snap(&ctx_a, fid_a);
2561        assert!(
2562            snap_a.iter().any(|(_, i, _)| !i.is_empty()),
2563            "sanity: built IR"
2564        );
2565
2566        // ---- (b) checked-out builder ----------------------------------------
2567        let mut ctx_b = Context::new();
2568        let fid_b = FunctionBody::make(&mut ctx_b, "foo".into()).unwrap().id;
2569        let entry_b = FunctionBody::from_id_mut(&mut ctx_b, fid_b).make_root().id;
2570        {
2571            let mut host = BodyMut::new(&mut ctx_b.bodies[fid_b], &ctx_b.shared, &ctx_b.interfaces);
2572            let mut b = host.builder(entry_b);
2573            body(&mut b);
2574        }
2575        let snap_b = snap(&ctx_b, fid_b);
2576
2577        assert_eq!(
2578            snap_a, snap_b,
2579            "a body built through a checked-out builder must match the module-built body"
2580        );
2581    }
2582
2583    /// Address arithmetic inherits the pointer operand's memory space:
2584    /// `ptr + literal` (rule 1) keeps `ptr`'s space, so folding `x + 0 → x` (which
2585    /// returns `lhs`) is space-preserving; `ptr + ptr` (rule 2) is ambiguous and
2586    /// drops to a plain integer; `int + literal` is unaffected.
2587    #[test]
2588    fn address_arithmetic_inherits_pointer_space() {
2589        use crate::value::function::FunctionBody;
2590
2591        let mut ctx = Context::new();
2592        let fid = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
2593        let entry = FunctionBody::from_id_mut(&mut ctx, fid).make_root().id;
2594
2595        let ram = ctx.shared.default_space;
2596        let ptr_ty = ctx.shared.types.get_or_make_space_address(8, ram);
2597        let ram_mem = ctx.shared.types.space_of(ptr_ty);
2598
2599        // Two space-typed pointer values: fresh instructions stamped into `ram`.
2600        let (addr_a, addr_b) = {
2601            let mut b = ctx.builder(entry);
2602            let c1 = b.shr().get_const(0x1000, 8);
2603            let c2 = b.shr().get_const(0x2000, 8);
2604            (b.push_add(c1, c1).id(), b.push_add(c2, c2).id())
2605        };
2606        for v in [addr_a, addr_b] {
2607            if let ValueId::Instruction(i) = v {
2608                crate::value::Instruction::from_id_mut(&mut ctx, i).set_type(ptr_ty);
2609            }
2610        }
2611
2612        let (add_ptr_lit, add_lit_ptr, add_ptr_ptr, add_int_lit) = {
2613            let mut b = ctx.builder(entry);
2614            let k = b.shr().get_const(4, 8);
2615            let zero = b.shr().get_const(0, 8);
2616            (
2617                b.push_add(addr_a, k).id(),      // ptr + literal
2618                b.push_add(k, addr_a).id(),      // literal + ptr
2619                b.push_add(addr_a, addr_b).id(), // ptr + ptr
2620                b.push_add(k, zero).id(),        // int + literal
2621            )
2622        };
2623
2624        let space_of = |v: ValueId| ctx.shared.types.space_of(ctx.type_of(v));
2625        assert_eq!(
2626            space_of(add_ptr_lit),
2627            ram_mem,
2628            "ptr + literal keeps the pointer's space (rule 1)"
2629        );
2630        assert_eq!(
2631            space_of(add_lit_ptr),
2632            ram_mem,
2633            "literal + ptr keeps the pointer's space (rule 1, commutative)"
2634        );
2635        assert_eq!(
2636            space_of(add_ptr_ptr),
2637            None,
2638            "ptr + ptr is ambiguous and drops to a plain integer (rule 2)"
2639        );
2640        assert_eq!(
2641            space_of(add_int_lit),
2642            None,
2643            "int + literal carries no space"
2644        );
2645
2646        // Folding `x + 0 → x` returns `lhs`; since `lhs` (the ptr+literal above)
2647        // carries `ram`, the fold is space-preserving — the property the argpromote
2648        // rule-4 rewrite relies on.
2649        assert_eq!(
2650            space_of(addr_a),
2651            ram_mem,
2652            "the pointer operand a fold would return still carries its space"
2653        );
2654    }
2655
2656    /// An id-less (detached) body can be driven by `Builder::new_local` and the
2657    /// `push_*_local` verbs without ever acquiring a registry identity: the
2658    /// builder's engine is fully body-local.
2659    #[test]
2660    fn detached_body_builds_through_local_verbs() {
2661        let ctx = Context::new();
2662        let mut body = FunctionBody::detached();
2663        assert_eq!(body.try_id(), None, "sanity: body starts detached");
2664
2665        // Mint entry and target blocks through the body's local verbs.
2666        let entry = body.push_block_local(BasicBlock::detached());
2667        let target = body.push_block_local(BasicBlock::detached());
2668        body.set_root_id(Some(entry));
2669
2670        let c1 = ctx.shared.get_const(7, 8).strip_func();
2671        let c2 = ctx.shared.get_const(9, 8).strip_func();
2672
2673        {
2674            let mut b = Builder::new_local(&mut body, &ctx.shared, &ctx.interfaces, entry);
2675            let sum = b.push_add_local(c1, c2);
2676            let sum = LocalValueId::Instruction(sum);
2677            let doubled = b.push_add_local(sum, sum);
2678            let _cmp = b.push_eq_local(LocalValueId::Instruction(doubled), c2);
2679            b.push_branch_local(target);
2680            assert!(b.is_terminated());
2681            b.switch_to_block_local(target);
2682            let ret = b.push_return_value_local(sum);
2683            let _ = ret;
2684        }
2685
2686        // Instructions landed in the arenas, wired to their blocks.
2687        assert_eq!(body.blocks[entry].instructions.len(), 4);
2688        assert_eq!(body.blocks[target].instructions.len(), 1);
2689        let last = *body.blocks[entry].instructions.last().unwrap();
2690        assert!(body.insns[last].mnemonic().is_terminator());
2691
2692        // The body never acquired an identity: it is still detached.
2693        assert_eq!(body.try_id(), None);
2694    }
2695
2696    /// `map` preserves its source's sequence kind: mapping over a `List<T>`
2697    /// (e.g. a `take_while` result) yields a `List<U>`, not a fixed array.
2698    #[test]
2699    fn map_over_a_list_yields_a_list() {
2700        use crate::value::{FunctionBody, insn::Return};
2701
2702        let mut ctx = Context::new();
2703        let i8 = ctx.shared.types.get_or_make_int(1);
2704
2705        // body: fn(i8) -> i8 returning its param (so the map result elem is i8).
2706        let body = FunctionBody::make(&mut ctx, "body".into()).unwrap().id;
2707        let broot = FunctionBody::from_id_mut(&mut ctx, body).make_root().id;
2708        let bp = BasicBlock::from_id_mut(&mut ctx, broot).push_param(1).id;
2709        let dummy = ctx.get_const(0, 8).id();
2710        let ret = InstructionRef::from_mnemonic_with_type(
2711            &mut ctx,
2712            body,
2713            Mnemonic::Return(Return {
2714                ptr: dummy.localize(body),
2715                value: Some(ValueId::BlockParam(bp).localize(body)),
2716            }),
2717            i8,
2718        )
2719        .id;
2720        BasicBlock::from_id_mut(&mut ctx, broot).push_insn(ret);
2721
2722        // host: a value typed `List<i8>` (bound 4) to map over.
2723        let host = FunctionBody::make(&mut ctx, "host".into()).unwrap().id;
2724        let hentry = FunctionBody::from_id_mut(&mut ctx, host).make_root().id;
2725        let list_ty = ctx.shared.types.get_or_make_list(i8, 4);
2726        let src_pid = BasicBlock::from_id_mut(&mut ctx, hentry).push_param(4).id;
2727        ctx.block_param_mut(src_pid).type_id = list_ty;
2728        let src = ValueId::BlockParam(src_pid);
2729
2730        let map_ty = {
2731            let mut b = ctx.builder(hentry);
2732            b.push_map(body, src, Vec::new()).type_id()
2733        };
2734
2735        assert_eq!(
2736            ctx.shared.types.array_of(map_ty),
2737            None,
2738            "map of a list is not an array"
2739        );
2740        assert_eq!(
2741            ctx.shared.types.list_of(map_ty),
2742            Some((i8, Some(4))),
2743            "map of List<i8> (bound 4) is List<i8> (bound 4)"
2744        );
2745    }
2746
2747    #[test]
2748    fn cfg_branch_adds_one_node_and_one_edge() {
2749        let mut ctx = Context::new();
2750        qcode!(
2751            ctx,
2752            "
2753            <entry>
2754                goto <done>;
2755            <done>
2756                goto <0x1001>;
2757        "
2758        );
2759        // entry + done + 1001 = 3 nodes; entry -> done, done -> 1001 = 2 edges
2760        assert_eq!(ctx.block_ids().len(), 3);
2761        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 2);
2762    }
2763
2764    #[test]
2765    fn cfg_cbranch_adds_two_edges() {
2766        let mut ctx = Context::new();
2767        qcode!(
2768            ctx,
2769            "
2770            varnode i8 cond;
2771
2772            <entry>
2773                %c = load(cond:1, cond);
2774                if %c goto <then_lbl> else goto <else_lbl>;
2775
2776            <then_lbl>
2777                goto <0x1001>;
2778
2779            <else_lbl>
2780                goto <0x1001>;
2781        "
2782        );
2783        // entry + then_lbl + else_lbl + 1001 = 4 nodes
2784        // entry->then_lbl, entry->else_lbl, then_lbl->1001, else_lbl->1001 = 4 edges
2785        assert_eq!(ctx.block_ids().len(), 4);
2786        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 4);
2787    }
2788
2789    #[test]
2790    fn cfg_branchind_adds_node_but_no_outgoing_edge() {
2791        let mut ctx = Context::new();
2792        qcode!(
2793            ctx,
2794            "
2795            <entry>
2796                local i64 ptr;
2797                goto [ptr];
2798        "
2799        );
2800
2801        assert_eq!(ctx.block_ids().len(), 1);
2802        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 0);
2803
2804        assert_eq!(BasicBlock::from_id(&ctx, entry).successors().count(), 0);
2805    }
2806
2807    #[test]
2808    fn cfg_return_adds_node_but_no_outgoing_edge() {
2809        let mut ctx = Context::new();
2810        qcode!(
2811            ctx,
2812            "
2813            <entry>
2814                local i64 ptr;
2815                return at ptr;
2816        "
2817        );
2818        assert_eq!(ctx.block_ids().len(), 1);
2819        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 0);
2820        assert_eq!(BasicBlock::from_id(&ctx, entry).successors().count(), 0);
2821    }
2822
2823    #[test]
2824    fn cfg_multi_block_qcode_program() {
2825        let mut ctx = Context::new();
2826        qcode!(
2827            ctx,
2828            "
2829            varnode i32 v;
2830
2831            <entry>
2832                goto <body>;
2833
2834            <body>
2835                i64 %v0 = i64 &v + i64 1;
2836                goto <0x1001>;
2837        "
2838        );
2839
2840        // entry + body + 1001 = 3 nodes
2841        // entry -> body, body -> 1001 = 2 edges
2842        assert_eq!(ctx.block_ids().len(), 3);
2843        assert_eq!(ctx.functions().flat_map(|f| f.edge_ids()).count(), 2);
2844    }
2845
2846    #[test]
2847    fn test_builder_finalize() {
2848        // Test code for Builder drop behavior
2849        // This test will compile and run without panicking because we finalize the builder properly
2850        let mut ctx = Context::new();
2851        let block_id = {
2852            let __f = ctx.anon_function();
2853            ctx.get_or_make_block(0, __f)
2854        };
2855        let target = ctx.get_or_make_block(0x1000, block_id.func);
2856
2857        {
2858            let builder = ctx.builder(block_id);
2859            builder.finalize(target);
2860        }
2861    }
2862
2863    #[test]
2864    fn test_named_temp_duplicate() {
2865        let mut ctx = Context::new();
2866        let mut builder = ctx.builder_at(0x1000);
2867
2868        let value = builder.make_named_temp("dup".into(), 4);
2869        let other_value = builder.make_named_temp("dup".into(), 4);
2870        let target = builder.current_block();
2871        builder.finalize(target);
2872
2873        assert_eq!(
2874            TempRef::new(ModuleView::new(&ctx), value).name(),
2875            Some("dup")
2876        );
2877        assert_eq!(
2878            TempRef::new(ModuleView::new(&ctx), other_value).name(),
2879            Some("dup_1")
2880        );
2881    }
2882
2883    #[test]
2884    fn same_label_temps_in_different_functions_are_isolated() {
2885        let mut ctx = Context::new();
2886
2887        let first = {
2888            let mut builder = ctx.builder_at(0x1000);
2889            let temp = builder.make_temp_labeled(7, 4);
2890            let target = builder.current_block();
2891            builder.finalize(target);
2892            temp
2893        };
2894        let second = {
2895            let mut builder = ctx.builder_at(0x2000);
2896            let temp = builder.make_temp_labeled(7, 4);
2897            let target = builder.current_block();
2898            builder.finalize(target);
2899            temp
2900        };
2901
2902        assert_ne!(first, second);
2903        let first = TempRef::new(ModuleView::new(&ctx), first);
2904        let second = TempRef::new(ModuleView::new(&ctx), second);
2905        assert_eq!((first.label(), second.label()), (Some(7), Some(7)));
2906        assert_ne!(first.space().id, second.space().id);
2907    }
2908
2909    #[test]
2910    fn qcode_local_decl_creates_named_temp() {
2911        let mut ctx = Context::new();
2912
2913        qcode!(ctx, "varnode i64 ptr; <block> goto <0x1001>;");
2914
2915        let ptr = Varnode::from_id(&ctx, ptr);
2916
2917        assert_eq!(ptr.size(), 8);
2918        assert_eq!(ptr.name(), Some("ptr"));
2919    }
2920
2921    #[test]
2922    fn qcode_standalone_local_decl_creates_named_temp() {
2923        let mut ctx = Context::new();
2924        qcode!(ctx, "varnode i64 ptr; <block> goto <0x1001>;");
2925
2926        let ptr = Varnode::from_id(&ctx, ptr);
2927
2928        assert_eq!(ptr.size(), 8);
2929        assert_eq!(ptr.name(), Some("ptr"));
2930    }
2931
2932    #[test]
2933    fn qcode_varnode_decl_before_entry_block() {
2934        let mut ctx = Context::new();
2935        qcode!(ctx, "varnode i64 ptr; <block> goto <0x1001>;");
2936
2937        let ptr = Varnode::from_id(&ctx, ptr);
2938
2939        assert_eq!(ptr.size(), 8);
2940        assert_eq!(ptr.name(), Some("ptr"));
2941    }
2942
2943    #[test]
2944    fn push_param_via_builder_visible_on_block() {
2945        let mut ctx = Context::new();
2946        let block_id = {
2947            let __f = ctx.anon_function();
2948            ctx.get_or_make_block(0x1000, __f)
2949        };
2950        let mut builder = ctx.builder(block_id);
2951
2952        let p0 = builder.push_param(8);
2953        let p0_id = p0;
2954        let p1 = builder.push_param(4);
2955        let p1_id = p1;
2956
2957        drop(builder);
2958
2959        let block = BasicBlock::from_id(&ctx, block_id);
2960        assert_eq!(block.num_params(), 2);
2961        let param_ids: Vec<_> = block.params().map(|p| p.id).collect();
2962        assert_eq!(param_ids, [p0_id, p1_id]);
2963        assert_eq!(block.instruction_ids().len(), 0);
2964    }
2965
2966    #[test]
2967    fn push_branch_with_args_via_builder() {
2968        let mut ctx = Context::new();
2969        // A branch edge is intra-function: source and target live in one function.
2970        let f = ctx.anon_function();
2971        let src_id = ctx.get_or_make_block(0x1000, f);
2972        let dst_id = ctx.get_or_make_block(0x2000, f);
2973
2974        let param_val = BasicBlock::from_id_mut(&mut ctx, dst_id).push_param(8).id();
2975
2976        {
2977            let mut builder = ctx.builder(src_id);
2978            builder.push_branch_with_args(dst_id, vec![param_val]);
2979        }
2980
2981        let block = BasicBlock::from_id(&ctx, src_id);
2982        let last = block.iter().last().expect("branch was added");
2983        let crate::value::insn::Mnemonic::Branch(branch) = last.mnemonic() else {
2984            panic!("expected branch");
2985        };
2986        assert_eq!(branch.target, dst_id.local);
2987        assert_eq!(branch.args.len(), 1);
2988        assert_eq!(branch.args[0], param_val.strip_func());
2989    }
2990
2991    #[test]
2992    fn test_builder_adds_address_to_qcode() {
2993        let mut ctx = Context::new();
2994        let id_42 = ctx.get_const(42, 8).id();
2995
2996        let not_insn_id = {
2997            let source = ctx.builder_at(0x1000).current_block();
2998            let target = ctx.get_or_make_block(0x1001, source.func);
2999            let mut builder = ctx.builder(source);
3000            builder.set_address(0x1000);
3001            let not_insn_id = builder.push_bit_negate(id_42).id;
3002            builder.finalize(target);
3003
3004            not_insn_id
3005        };
3006
3007        let insn = Instruction::from_id(&ctx, not_insn_id);
3008
3009        assert_eq!(insn.address().unwrap(), 0x1000);
3010    }
3011
3012    #[test]
3013    fn builder_at_materializes_root_in_registered_function_arena() {
3014        let mut ctx = Context::new();
3015        let func = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id;
3016        assert!(FunctionBody::from_id(&ctx, func).root().is_none());
3017
3018        let block = {
3019            let builder = ctx.builder_at(0x2000);
3020            builder.current_block()
3021        };
3022
3023        assert_eq!(block.func, func);
3024        assert_eq!(
3025            FunctionBody::from_id(&ctx, func).root().map(|root| root.id),
3026            Some(block)
3027        );
3028        assert!(FunctionBody::from_name(&ctx, "blk_2000").is_none());
3029    }
3030
3031    #[test]
3032    fn append_after_terminated_block_panic_includes_current_address() {
3033        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3034            let mut ctx = Context::new();
3035            let value = ctx.get_const(0, 1).id();
3036            let source = ctx.builder_at(0x4010).current_block();
3037            let target = ctx.get_or_make_block(0x4020, source.func);
3038            let mut builder = ctx.builder(source);
3039
3040            builder.push_branch(target);
3041            builder.set_address(0x4015);
3042            builder.push_bit_negate(value);
3043        }));
3044
3045        let panic = result.expect_err("append should panic after a terminator");
3046        let message = panic
3047            .downcast_ref::<String>()
3048            .map(String::as_str)
3049            .or_else(|| panic.downcast_ref::<&'static str>().copied())
3050            .expect("panic should carry a string message");
3051
3052        assert!(
3053            message.contains("cannot append instruction to a terminated block at 0x4015"),
3054            "unexpected panic message: {message}"
3055        );
3056    }
3057
3058    #[test]
3059    fn push_copy_supports_partial_final_lane() {
3060        let mut ctx = Context::new();
3061        let block_id = {
3062            let __f = ctx.anon_function();
3063            ctx.get_or_make_block(0x1000, __f)
3064        };
3065
3066        {
3067            let target = ctx.get_or_make_block(0x1001, block_id.func);
3068            let mut builder = ctx.builder(block_id);
3069            let src = builder.make_named_temp("src".into(), 9);
3070            let dst = builder.make_named_temp("dst".into(), 9);
3071            builder.push_copy(src.into(), dst);
3072            builder.finalize(target);
3073        }
3074
3075        let store_sizes = BasicBlock::from_id(&ctx, block_id)
3076            .iter()
3077            .filter_map(|insn| match insn.mnemonic() {
3078                Mnemonic::Store(store) => Some(store.size),
3079                _ => None,
3080            })
3081            .collect::<Vec<_>>();
3082        assert_eq!(store_sizes, [8, 1]);
3083    }
3084
3085    // --- insert-point tests ---
3086
3087    #[test]
3088    fn insert_point_to_start_prepends_before_existing_instruction() {
3089        let mut ctx = Context::new();
3090        let block_id = {
3091            let __f = ctx.anon_function();
3092            ctx.get_or_make_block(0x1000, __f)
3093        };
3094        let val = ctx.get_const(0, 8).id();
3095
3096        let existing_id = {
3097            let mut b = ctx.builder(block_id);
3098
3099            b.push_bit_negate(val).id
3100        };
3101
3102        let prepended_id = {
3103            let mut b = ctx.builder(block_id);
3104            b.set_insert_point_to_start();
3105            b.push_bit_negate(val).id
3106        };
3107
3108        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3109        assert_eq!(ids, [prepended_id, existing_id]);
3110    }
3111
3112    #[test]
3113    fn multiple_pushes_with_insert_point_to_start_preserve_push_order() {
3114        let mut ctx = Context::new();
3115        let block_id = {
3116            let __f = ctx.anon_function();
3117            ctx.get_or_make_block(0x1000, __f)
3118        };
3119        let val = ctx.get_const(0, 8).id();
3120
3121        let existing_id = {
3122            let mut b = ctx.builder(block_id);
3123
3124            b.push_bit_negate(val).id
3125        };
3126
3127        let (id0, id1, id2) = {
3128            let mut b = ctx.builder(block_id);
3129            b.set_insert_point_to_start();
3130            (
3131                b.push_bit_negate(val).id,
3132                b.push_bit_negate(val).id,
3133                b.push_bit_negate(val).id,
3134            )
3135        };
3136
3137        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3138        assert_eq!(ids, [id0, id1, id2, existing_id]);
3139    }
3140
3141    #[test]
3142    fn insert_point_before_existing_instruction_inserts_before_target() {
3143        let mut ctx = Context::new();
3144        let block_id = {
3145            let __f = ctx.anon_function();
3146            ctx.get_or_make_block(0x1000, __f)
3147        };
3148        let val = ctx.get_const(0, 8).id();
3149
3150        let (first_id, target_id) = {
3151            let mut b = ctx.builder(block_id);
3152            (b.push_bit_negate(val).id, b.push_bit_negate(val).id)
3153        };
3154
3155        let (inserted0, inserted1) = {
3156            let mut b = ctx.builder(block_id);
3157            b.set_insert_point_before(target_id);
3158            (b.push_bit_negate(val).id, b.push_bit_negate(val).id)
3159        };
3160
3161        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3162        assert_eq!(ids, [first_id, inserted0, inserted1, target_id]);
3163    }
3164
3165    #[test]
3166    fn insert_point_to_start_allows_push_into_terminated_block() {
3167        let mut ctx = Context::new();
3168        qcode!(ctx, "<entry> goto <0x1001>;");
3169
3170        let val = ctx.get_const(1, 1).id();
3171        let new_id = {
3172            let mut b = ctx.builder(entry);
3173            b.set_insert_point_to_start();
3174            b.push_bit_negate(val).id
3175        };
3176
3177        let block = BasicBlock::from_id(&ctx, entry);
3178        assert_eq!(block.instruction_ids()[0], new_id);
3179        // The original branch terminator is still present
3180        assert!(block.is_terminated());
3181    }
3182
3183    #[test]
3184    fn set_insert_point_to_end_restores_append_mode() {
3185        let mut ctx = Context::new();
3186        let block_id = {
3187            let __f = ctx.anon_function();
3188            ctx.get_or_make_block(0x1000, __f)
3189        };
3190        let val = ctx.get_const(0, 8).id();
3191
3192        let (first_id, middle_id, last_id) = {
3193            let mut b = ctx.builder(block_id);
3194            let first = b.push_bit_negate(val).id; // appended → index 0
3195            b.set_insert_point_to_start();
3196            let middle = b.push_bit_negate(val).id; // inserted at 0, first shifts to 1
3197            b.set_insert_point_to_end();
3198            let last = b.push_bit_negate(val).id; // appended → index 2
3199            (first, middle, last)
3200        };
3201
3202        let ids = BasicBlock::from_id(&ctx, block_id).instruction_ids();
3203        assert_eq!(ids, [middle_id, first_id, last_id]);
3204    }
3205}