Skip to main content

qcode/value/
function.rs

1use jstd::{Identifier, registry::Registry, stable_arena::StableArena};
2use rustc_hash::{FxHashMap, FxHashSet};
3use std::{
4    borrow::Cow,
5    collections::BTreeSet,
6    fmt::{Display, Formatter},
7    marker::PhantomData,
8};
9
10mod footprint;
11pub use footprint::{Footprint, RamBase, RamField, RamLocations, RamObject, RamRegion};
12
13mod signature;
14pub use signature::{
15    ArgMemKind, ExternArg, ExternArgmem, ExternInterface, ExternSlot, FunctionSignature, ParamAttrs,
16};
17
18use crate::{
19    context::Context,
20    error::{Error, ErrorTy, Result},
21    value::{
22        BasicBlock, BlockId, BlockRef, Instruction, InstructionId, LocalValueId, ModuleView,
23        QCodeView, Temp, TempId, TempSpace, TempSpaceId, Value, ValueId, VarnodeId,
24        block::EdgeData,
25        block::cfg::{EdgeId, LocalBlockId},
26        block_param::{BlockParam, BlockParamId, LocalParamId},
27        insn::{LocalInsnId, Mnemonic},
28        util::{
29            base_ref::{BaseRef, WithCtx, WithCtxMut},
30            named::{Named, Renameable, update_context_name},
31        },
32    },
33};
34
35#[derive(Identifier)]
36pub struct FunctionId(u32);
37
38/// Everything a *caller* reasons about a function: its name, address, semantic
39/// kind, external-ness, and ABI/analysis signature. This is the caller-reasoning
40/// surface (ruling 1 of the context-split design): it is precisely the data a
41/// function pass may read about *another* function. Its counterpart is the
42/// function *body* (arenas, roster, users, local names) — everything only the
43/// function's own passes touch.
44///
45/// Interfaces are stored in their own
46/// [`Context::interfaces`](crate::context::Context::interfaces)
47/// registry, held in lockstep with the function bodies under the same
48/// [`FunctionId`] and never checked out — so a caller always reads the real
49/// interface even while a callee's body is checked out to a worker.
50#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
51pub struct FunctionInterface<'str> {
52    /// The function's name.
53    pub name: Cow<'str, str>,
54
55    /// Optional entry address (from binary).
56    pub address: Option<u64>,
57
58    /// Whether this is an external (imported) function.
59    ///
60    /// External functions have no lifted body — they are stubs for calls that
61    /// go outside the binary (e.g. PLT thunks for shared-library functions).
62    /// The recursive disassembler will not attempt to lift their body.
63    pub is_external: bool,
64
65    /// Optional ABI description used by alias analysis.
66    pub signature: Option<FunctionSignature>,
67
68    /// What semantic class this function belongs to.
69    #[serde(default)]
70    pub kind: FunctionKind,
71
72    /// Call-graph-closed implicit *effect summary* of this function, per the
73    /// argpromote v2 design (`ARGPROMOTE_REGISTERS_V2.md`). Solved by the
74    /// effect-analysis pass and read by the materialize/regpure passes, the
75    /// emulator, alias analysis, and the verifier.
76    ///
77    /// Serialized into the `.harbinger` wire shape so that rewritten regpure
78    /// call sites and materialized interfaces stay in sync with the snapshot.
79    /// Older snapshots that predate this field load as
80    /// [`RegisterChannelState::Unsolved`] via `#[serde(default)]`.
81    #[serde(default)]
82    pub effects: FunctionEffects,
83
84    /// For a PE import brought in by ordinal only, the ordinal it was imported
85    /// at. Recorded before the stub is renamed to its real export name.
86    #[serde(default)]
87    pub import_ordinal: Option<u16>,
88}
89
90/// A function's full effect summary, one component per side-effect channel:
91/// the register-lifecycle state and the memory write-space verdict. Serialized
92/// as part of [`FunctionInterface`].
93#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
94pub struct FunctionEffects {
95    /// Register-channel lifecycle summary (argpromote v2).
96    #[serde(default)]
97    pub register: RegisterChannelState,
98    /// Memory-channel effect summary: the coarse written-space verdict plus the
99    /// precise RAM footprint.
100    #[serde(default)]
101    pub memory: MemoryChannelState,
102}
103
104impl FunctionEffects {
105    /// The materialized register interface mapping, if the register channel has
106    /// been materialized. Delegates to [`RegisterChannelState::materialized`].
107    pub fn materialized(&self) -> Option<&RegisterInterfaceMap> {
108        self.register.materialized()
109    }
110
111    /// Whether the register channel is solved. Delegates to
112    /// [`RegisterChannelState::is_solved`].
113    pub fn is_solved(&self) -> bool {
114        self.register.is_solved()
115    }
116}
117
118/// The state of a function's register-channel effect summary (argpromote v2).
119///
120/// Purity has moved from a function flag (`pure_reg`) to per-call-site tags, but
121/// the *interface mapping* a materialized function exposes still lives on the
122/// function — the emulator's implicit call convention, alias analysis, and the
123/// verifier all consume it. This enum records how far the summary has advanced.
124#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
125pub enum RegisterChannelState {
126    /// Not yet solved by the effect-analysis pass (the default / post-load
127    /// state).
128    #[default]
129    Unsolved,
130    /// ⊤ — unknowable: the function contains an unresolved indirect call, calls
131    /// a ⊤ function, or is a prototype-less external. Its register effects stay
132    /// modelled conservatively (clobbers-all) at every call site.
133    Top,
134    /// Solved to a finite effect set but the interface is not yet materialized
135    /// (no by-value params / return pack added). Call sites still bind
136    /// implicitly, but the solved read/write register sets are precise: a call
137    /// to this function reads at most `reads` and writes at most `writes`.
138    Solved(RegisterEffectSets),
139    /// Materialized: the function carries by-value register params and a return
140    /// pack, and this mapping records which register each interface slot binds.
141    /// Consumed by the dual binding convention.
142    Materialized(RegisterInterfaceMap),
143}
144
145impl RegisterChannelState {
146    /// The materialized interface mapping, if this function has been
147    /// materialized.
148    pub fn materialized(&self) -> Option<&RegisterInterfaceMap> {
149        match self {
150            RegisterChannelState::Materialized(map) => Some(map),
151            _ => None,
152        }
153    }
154
155    /// Whether the summary is solved (either not-yet- or already-materialized),
156    /// i.e. its register effects are known precisely rather than ⊤.
157    pub fn is_solved(&self) -> bool {
158        matches!(
159            self,
160            RegisterChannelState::Solved(_) | RegisterChannelState::Materialized(_)
161        )
162    }
163}
164
165/// The memory-channel component of a function's effects: the coarse
166/// written-space tri-state plus the precise RAM [`Footprint`] the same solve
167/// derived.
168///
169/// The two components are independently ⊤: `coarse` is deliberately laxer, so a
170/// function whose footprint defies classification (`precise == None`) usually
171/// still has a bounded space set.
172#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
173pub struct MemoryChannelState {
174    /// The coarse set of non-register spaces this function may (transitively)
175    /// write. Subsumes the retired `written_spaces` + `written_spaces_stamped`
176    /// signature pair.
177    #[serde(default)]
178    pub coarse: WrittenSpacesState,
179    /// The exhaustive outward memory footprint this function may touch, or
180    /// `None` for ⊤ — inexpressible in the lattice (an unclassifiable access, a
181    /// non-lockstep interface, budget saturation, or an unrebasable call edge).
182    ///
183    /// Persisted so that a memory-channel effect delta can compare *addresses*,
184    /// not merely space granularity: two solves that both write `{ram}` at
185    /// different addresses must not compare `Equal`, since `Equal` is the one
186    /// verdict that licenses stopping invalidation propagation.
187    ///
188    /// `#[serde(default)]` (→ `None`, i.e. ⊤) so snapshots written before the
189    /// footprint was persisted still load, conservatively.
190    #[serde(default)]
191    pub precise: Option<Footprint>,
192
193    /// The materialized memory interface: where each by-value memory input is
194    /// bound from and each write-set output replayed to, once the RAM channel
195    /// has functionalized this function. `None` while the memory channel is not
196    /// materialized (the default and, today, the only state any pass sets).
197    ///
198    /// The memory analogue of
199    /// [`RegisterChannelState::Materialized`].
200    /// Unlike the register channel this is a field rather than a lattice state,
201    /// because `coarse` and `precise` are independently ⊤ and materialization is
202    /// orthogonal to both.
203    ///
204    /// `#[serde(default)]` (→ `None`) so snapshots predating the memory
205    /// interface load unchanged.
206    #[serde(default)]
207    pub materialized: Option<MemoryInterfaceMap>,
208}
209
210impl MemoryChannelState {
211    /// The materialized memory interface, if the memory channel has been
212    /// materialized.
213    pub fn materialized(&self) -> Option<&MemoryInterfaceMap> {
214        self.materialized.as_ref()
215    }
216}
217
218/// Owned tri-state of a function's coarse written-space verdict, subsuming the
219/// old `written_spaces: Option<Vec<SpaceId>>` + `written_spaces_stamped: bool`
220/// pair. The borrowing view [`WrittenSpaces`] is derived from this.
221#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
222pub enum WrittenSpacesState {
223    /// Analysis has never recorded a verdict — a freshly minted function.
224    /// (Was `written_spaces_stamped == false`.)
225    #[default]
226    Unstamped,
227    /// Recorded, but unbounded (⊤): the function may write any space.
228    /// (Was stamped with `written_spaces == None`.)
229    Unbounded,
230    /// A recorded exact witnessed bound: a space not listed is never written.
231    /// (Was stamped with `written_spaces == Some(sorted)`.)
232    Bounded(Vec<crate::space::SpaceId>),
233}
234
235/// The solved (transitive) register effect of a function whose interface is
236/// *not* materialized: the registers a call to it may read / write, callee
237/// effects included. Sorted, deduplicated varnode lists — the persistable form
238/// of the register channel's solved lattice value.
239#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
240pub struct RegisterEffectSets {
241    /// Registers a call may read (sorted).
242    #[serde(alias = "loads")]
243    pub reads: Vec<VarnodeId>,
244    /// Registers a call may write (sorted).
245    #[serde(alias = "stores")]
246    pub writes: Vec<VarnodeId>,
247}
248
249/// A register whose value on return is *computed* rather than carried in the
250/// return pack: the linked function is this function's **projection** for that
251/// one output — a pure function of its inputs, returning what the register
252/// would have held.
253///
254/// Recording the projection is what lets the register leave both
255/// [`outputs`](RegisterInterfaceMap::outputs) and, once nothing else reads it,
256/// [`inputs`](RegisterInterfaceMap::inputs) without the fact being lost. The
257/// motivating case is the stack pointer: every function "returns" `SP + k`,
258/// which is a true statement about the machine code and no part of what the
259/// function means. Leaving it in the pack put `RSP` in every signature; deleting
260/// it outright would discard a real effect. A projection does neither.
261///
262/// The projection is an ordinary function and says what it reads through its
263/// *own* interface — this record deliberately does not restate the binding, so
264/// there is nothing here to desync from the function it names.
265///
266/// Entries are disjoint from `outputs`: a register is either packed or derived,
267/// never both.
268#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
269pub struct DerivedOutput {
270    /// The register this projection computes.
271    pub register: VarnodeId,
272    /// The projection: a pure function whose return value is `register`'s value
273    /// on return from the parent.
274    pub projection: crate::value::insn::Callee,
275}
276
277/// The ordered, machine-readable register interface of a *materialized*
278/// function: which register each by-value input parameter binds, and which
279/// register each return-pack slot stores back. Slot `i` of `inputs` is the
280/// `i`-th register param; slot `i` of `outputs` is the `i`-th pack field.
281///
282/// Both the register channel's own rewrite (regpure calls) and the emulator's
283/// implicit (zero-arg) convention read this: implicitly, param `i` is seeded
284/// from `inputs[i]` at entry and pack slot `i` is stored back to `outputs[i]`
285/// on return.
286///
287/// A third category sits alongside those two: a register that is neither an
288/// input nor packed, because it is *derived* — see
289/// [`projections`](Self::projections).
290#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
291pub struct RegisterInterfaceMap {
292    /// Register bound by each by-value input parameter, in parameter order.
293    pub inputs: Vec<VarnodeId>,
294    /// Register written back by each return-pack slot, in pack order. Ordered
295    /// returns-first: slots `..returns` carry real computed values, the rest
296    /// are clobbers (undefined — poison — at a rewritten call site).
297    pub outputs: Vec<VarnodeId>,
298    /// How many leading `outputs` slots are return values (the rest are
299    /// clobbers). A bodied function replays every slot as a real store, so its
300    /// `returns == outputs.len()`; a prototyped external returns only its ABI
301    /// return register(s) and clobbers the caller-saved tail.
302    pub returns: usize,
303    /// Registers whose returned value is computed by a linked projection rather
304    /// than carried in the pack (see [`DerivedOutput`]). Unordered and disjoint
305    /// from `outputs`; a consumer that needs such a register's value evaluates
306    /// its projection instead of reading a pack field.
307    ///
308    /// `#[serde(default)]` (→ empty) covers self-describing formats only. A
309    /// `.harbinger` session payload is *positional* bincode under a hard version
310    /// lock with no migration path, so adding this field changed the payload
311    /// layout and required a `harbinger_session::session::FORMAT_VERSION` bump —
312    /// old sessions are rejected, not defaulted.
313    #[serde(default)]
314    pub projections: Vec<DerivedOutput>,
315}
316
317/// Where one materialized interface input is bound from, or one write-set
318/// output replayed to, at a call site that does not pass it explicitly.
319///
320/// This is the memory channel's analogue of [`RegisterInterfaceMap`]'s bare
321/// [`VarnodeId`]: a register input needs no descriptor beyond the register
322/// itself, but a memory input has to say *which address* the caller reads. It
323/// generalizes [`ExternSlot`], which
324/// describes the same thing for prototyped externals only.
325///
326/// Every slot is `mem[base + offset]` of `size` bytes: a memory input is, by
327/// construction, a dereference. The interface says *where*, in terms the caller
328/// can evaluate — never in terms of the callee's body.
329///
330/// Deliberately **non-recursive**: a base that must itself be loaded is the
331/// "re-dereference whose address is loaded at runtime" case the RAM channel
332/// already rejects as unmodellable — such a base records
333/// [`SlotBase::Unmappable`] rather than being described.
334#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
335pub struct InterfaceSlot {
336    pub base: SlotBase,
337    pub offset: i64,
338    pub size: usize,
339}
340
341/// What an [`InterfaceSlot`]'s address is relative to.
342///
343/// Deliberately **register-free**. A materialized function is on its way to
344/// being a pure function of its arguments, and its interface should carry no
345/// notion of a register file: the register channel has already turned every
346/// register input into a by-value argument, so a base that *was* a register is
347/// simply the argument bound to it. The property that matters is that a caller
348/// can express the address — hence the vocabulary is "an argument you pass" or
349/// "an address that is the same everywhere".
350///
351/// The address-less cases are kept apart on purpose: an absolute address and a
352/// base we failed to describe are both address-less, but only the first is
353/// bindable. Collapsing them would let a consumer load from a bogus absolute
354/// address for a base it never resolved.
355#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
356pub enum SlotBase {
357    /// The callee's `i`-th positional argument: `mem[args[i] + offset]`.
358    ///
359    /// Bindable at any site that passes that argument — which is *site*-relative,
360    /// and honestly so: a pointer the caller supplies cannot be reconstructed
361    /// without a caller. A caller-less function is still materialized; it simply
362    /// binds nowhere.
363    Arg(usize),
364    /// An absolute address (a global): `mem[addr + offset]`. Bindable anywhere,
365    /// including at an implicit or indirect site, since the address is the same
366    /// in every caller.
367    Global(u64),
368    /// The base could not be expressed. **Not bindable**: a consumer must refuse
369    /// such a slot rather than treat it as absolute.
370    Unmappable,
371}
372
373impl InterfaceSlot {
374    /// Whether this slot's address is expressible at a call site at all.
375    ///
376    /// A [`SlotBase::Arg`] slot additionally needs the site to actually pass
377    /// that argument; this reports only the address-independent half.
378    pub fn is_bindable(&self) -> bool {
379        !matches!(self.base, SlotBase::Unmappable)
380    }
381}
382
383/// The ordered, machine-readable *memory* interface of a function whose memory
384/// channel has been materialized: where each by-value memory input parameter is
385/// loaded from, and where each memory write-set output is replayed to.
386///
387/// The memory analogue of [`RegisterInterfaceMap`]. Memory input parameters
388/// follow the register inputs in root-parameter order, so `inputs[i]` describes
389/// root param `register_inputs.len() + i`.
390///
391/// Read by the emulator's implicit binding convention (evaluate each input
392/// slot's address in the *caller's* state at the call, replay each output slot
393/// on return) and by the decompiler.
394#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
395pub struct MemoryInterfaceMap {
396    /// Where each by-value memory input parameter is bound from, in parameter
397    /// order (after the register inputs).
398    pub inputs: Vec<InterfaceSlot>,
399    /// Where each memory write-set output slot is replayed to, in pack order
400    /// (after the register outputs).
401    pub outputs: Vec<InterfaceSlot>,
402}
403
404/// A function *body*: arenas, roster, root, reverse use-def, local names. The
405/// caller-reasoning surface lives separately in [`FunctionInterface`], stored in
406/// [`Context::interfaces`](crate::context::Context::interfaces)
407/// under the same [`FunctionId`].
408#[derive(Clone, serde::Serialize, serde::Deserialize)]
409pub struct FunctionBody<'str> {
410    /// Immutable identity of this body in the lockstep function registries, or
411    /// `None` while the body is *detached* (freshly minted by a pass, not yet
412    /// installed under a registry key).
413    ///
414    /// This field is deliberately absent from the serialized body wire shape.
415    /// Bodies are serialized and deserialized as part of [`Context`], whose
416    /// custom deserializer restores the registry key here. Standalone body
417    /// deserialization therefore does not establish a usable identity. A detached
418    /// body is never serialized (bodies are installed at the mint barrier before
419    /// any save), so the `None` state never reaches the wire.
420    #[serde(skip)]
421    id: Option<FunctionId>,
422
423    /// The entry block (dominates all other blocks in this function).
424    /// Private: read via [`FunctionBody::root_id`], write via
425    /// [`FunctionBody::set_root_id`] (stage 6a §11).
426    root: Option<LocalBlockId>,
427
428    /// Instruction storage for this function. Function-scoped: the composite
429    /// [`InstructionId`](crate::value::InstructionId) `{ func, local }` indexes
430    /// here via `local`. Live payloads are dense; logical IDs are monotonic and
431    /// never reused after physical removal.
432    pub(crate) insns: StableArena<LocalInsnId, Instruction<'str>>,
433
434    /// Basic-block storage for this function. A block is born here and keeps its
435    /// `id.func` for life; live payloads are dense while logical IDs remain
436    /// stable and are never reused.
437    pub(crate) blocks: StableArena<LocalBlockId, BasicBlock<'str>>,
438
439    /// Body-local ids of the blocks this function owns, in order. Path A forbids
440    /// cross-arena ownership, so every entry indexes this function's `blocks`
441    /// arena. Kept in sync with each block's `parent`.
442    #[serde(default)]
443    pub(crate) roster: Vec<LocalBlockId>,
444
445    /// Block-parameter storage for this function.
446    pub(crate) params: StableArena<LocalParamId, BlockParam<'str>>,
447
448    /// CFG-edge storage for this function. Keyed by the plain body-local
449    /// [`EdgeId`](crate::value::block::EdgeId) (stage 4).
450    pub(crate) edges: StableArena<EdgeId, EdgeData>,
451
452    /// Append-only function-local temporary-space storage. Producers migrate
453    /// here in later plan-10 commits; the arena is intentionally empty until
454    /// then.
455    pub(crate) temp_spaces: Registry<crate::value::LocalTempSpaceId, TempSpace>,
456
457    /// Append-only function-local temporary-value storage.
458    pub(crate) temps: Registry<crate::value::LocalTempId, Temp<'str>>,
459
460    /// Addresses of every machine instruction lifted into this function, in
461    /// ascending order. Recorded during recursive disassembly and preserved
462    /// across optimization (which merges blocks and rewrites the IR), so the
463    /// raw disassembly view can be reconstructed regardless of CFG changes.
464    pub instruction_addrs: BTreeSet<u64>,
465
466    /// Function-local name table for this function's block, instruction,
467    /// block-param, and Temp names (ruling 1 of the parallel-passes plan).
468    /// Keeping these out of the global [`name_map`](crate::context::Context)
469    /// lets two functions name values independently — a prerequisite for
470    /// parallel function passes.
471    /// A value's own `name` field is the source of truth for rendering; this only
472    /// enforces uniqueness and resolves names within the function.
473    #[serde(default)]
474    pub(crate) names: crate::context::NameTable<'str, LocalValueId>,
475
476    /// Reverse use-def map, scoped to this function: for each [`ValueId`] the
477    /// list of *this function's* instructions that use it as an operand. By the
478    /// SSA ownership invariant every user of an instruction/param value is
479    /// intra-function, so an SSA def's users all live here. Shared values
480    /// (literals, varnodes) may be used by many functions; each records only its
481    /// own uses, which is all any pass needs (no pass queries a shared value's
482    /// users program-wide). Kept in sync by
483    /// [`push_insn`](crate::context::Context::push_insn),
484    /// [`remove_instructions`](crate::context::Context::remove_instructions),
485    /// [`Context::replace_all_uses_with`](crate::context::Context::replace_all_uses_with),
486    /// and [`Context::replace_instruction_mnemonic`](crate::context::Context::replace_instruction_mnemonic).
487    ///
488    /// Keyed by the body-local [`LocalValueId`] form of each used value (the
489    /// owning func is this body's, so it is stripped — see
490    /// [`ValueId::strip_func`]); the value list stays composite
491    /// [`InstructionId`]s.
492    #[serde(default)]
493    pub(crate) users: FxHashMap<LocalValueId, Vec<LocalInsnId>>,
494}
495
496/// Aggregate storage statistics for one kind of function-body entity.
497///
498/// `structural_bytes` counts the payload capacity reserved by the current body
499/// arenas. It deliberately excludes allocations owned by payload fields (for
500/// example mnemonic operands and block vectors); the Stage 7 probe measures
501/// those with allocator accounting in a separate process.
502#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
503pub struct BodyArenaKindStats {
504    pub issued: usize,
505    pub live: usize,
506    pub dead: usize,
507    pub capacity: usize,
508    pub structural_bytes: usize,
509}
510
511/// Aggregate statistics for all four arenas across function bodies.
512#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
513pub struct BodyArenaStats {
514    pub instructions: BodyArenaKindStats,
515    pub blocks: BodyArenaKindStats,
516    pub params: BodyArenaKindStats,
517    pub edges: BodyArenaKindStats,
518}
519
520impl BodyArenaKindStats {
521    fn stable_arena<Id: jstd::registry::Identifier, T>(arena: &StableArena<Id, T>) -> Self {
522        let issued = arena.issued_len();
523        let live = arena.len();
524        Self {
525            issued,
526            live,
527            dead: issued - live,
528            capacity: arena.capacity(),
529            structural_bytes: arena.structural_bytes(),
530        }
531    }
532
533    fn add_assign(&mut self, other: Self) {
534        self.issued += other.issued;
535        self.live += other.live;
536        self.dead += other.dead;
537        self.capacity += other.capacity;
538        self.structural_bytes += other.structural_bytes;
539    }
540}
541
542impl BodyArenaStats {
543    pub(crate) fn add_assign(&mut self, other: Self) {
544        self.instructions.add_assign(other.instructions);
545        self.blocks.add_assign(other.blocks);
546        self.params.add_assign(other.params);
547        self.edges.add_assign(other.edges);
548    }
549}
550
551/// Tri-state view of a function's `written_spaces` verdict, distinguishing a
552/// never-computed fresh mint from a deliberately recorded ⊤. See
553/// [`FunctionSignature::written_spaces`](super::function::FunctionSignature) and
554/// [`FunctionRef::written_spaces_state`].
555#[derive(Debug, Clone, Copy, PartialEq, Eq)]
556pub enum WrittenSpaces<'a> {
557    /// Analysis has never recorded a verdict — a freshly minted function.
558    /// Treated conservatively (may write any space) but distinct from a
559    /// recorded ⊤: it is a candidate for (re-)seeding, not a stale bound.
560    Unstamped,
561    /// Recorded, but unbounded (⊤): the function may write any space.
562    Unbounded,
563    /// A recorded exact witnessed bound: a space not listed is never written.
564    Bounded(&'a [crate::space::SpaceId]),
565}
566
567#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
568pub enum FunctionKind {
569    #[default]
570    Machine,
571    Lambda,
572}
573
574impl<'str> FunctionInterface<'str> {
575    /// A fresh interface named `name`, with default (empty) signature/kind.
576    pub fn new(name: Cow<'str, str>) -> Self {
577        Self {
578            name,
579            address: None,
580            is_external: false,
581            signature: None,
582            kind: FunctionKind::Machine,
583            effects: FunctionEffects::default(),
584            import_ordinal: None,
585        }
586    }
587
588    /// The inferred pointer attributes for positional argument `index`, or
589    /// `None` when this function has no analyzed attributes.
590    pub fn param_attr(&self, index: usize) -> Option<ParamAttrs> {
591        self.signature
592            .as_ref()
593            .and_then(|s| s.param_attrs.as_ref())
594            .and_then(|attrs| attrs.get(index))
595            .copied()
596    }
597}
598
599impl<'str> FunctionBody<'str> {
600    /// Reports the current arena footprint and logical liveness.
601    pub fn arena_stats(&self) -> BodyArenaStats {
602        BodyArenaStats {
603            instructions: BodyArenaKindStats::stable_arena(&self.insns),
604            blocks: BodyArenaKindStats::stable_arena(&self.blocks),
605            params: BodyArenaKindStats::stable_arena(&self.params),
606            edges: BodyArenaKindStats::stable_arena(&self.edges),
607        }
608    }
609
610    /// Releases structural capacity retained from peak analysis churn.
611    ///
612    /// Covers the four body arenas plus the block-owned instruction/parameter/
613    /// edge collections, the roster, and the reverse-use map. IDs, liveness,
614    /// ordering, and every semantic invariant are unchanged — this is an
615    /// allocator hint for explicit end-of-mutation boundaries, never a
616    /// correctness barrier.
617    pub fn shrink_to_fit(&mut self) {
618        self.insns.shrink_to_fit();
619        self.blocks.shrink_to_fit();
620        self.params.shrink_to_fit();
621        self.edges.shrink_to_fit();
622        self.roster.shrink_to_fit();
623        for mut block in self.blocks.iter_mut() {
624            block.instructions.shrink_to_fit();
625            block.params.shrink_to_fit();
626            block.edges.shrink_to_fit();
627        }
628        for insns in self.users.values_mut() {
629            insns.shrink_to_fit();
630        }
631        self.users.shrink_to_fit();
632    }
633
634    /// Install a registry ID onto a freshly [`detached`](Self::detached) body at
635    /// the mint barrier. Panics if the body already carries an id.
636    pub fn install_id(&mut self, id: FunctionId) {
637        assert!(self.id.is_none(), "body already installed");
638        self.id = Some(id);
639    }
640
641    /// Resolve one pass-local callee slot throughout this detached or installed
642    /// body. Returns the number of call-like instructions patched.
643    pub fn resolve_minted_callee(&mut self, slot: u32, real: FunctionId) -> usize {
644        let mut patched = 0;
645        for mut insn in self.insns.iter_mut() {
646            patched += usize::from(insn.mnemonic_mut().resolve_minted_callee(slot, real));
647        }
648        patched
649    }
650
651    /// Resolve every pass-local callee slot in this body against the installed
652    /// mapping (`installed[k]` is the real function for slot `k`) in one arena
653    /// walk. Returns the number of call-like instructions patched, or the first
654    /// slot with no installed function.
655    pub fn resolve_minted_callees(
656        &mut self,
657        installed: &[FunctionId],
658    ) -> std::result::Result<usize, u32> {
659        let mut patched = 0;
660        for mut insn in self.insns.iter_mut() {
661            let mnemonic = insn.mnemonic_mut();
662            let Some(slot) = mnemonic.minted_callee_slot() else {
663                continue;
664            };
665            let Some(&real) = installed.get(slot as usize) else {
666                return Err(slot);
667            };
668            mnemonic.resolve_minted_callee(slot, real);
669            patched += 1;
670        }
671        Ok(patched)
672    }
673
674    /// An empty function *body* carrying the identity `id`. Used for bodies
675    /// installed under a known registry key at creation
676    /// ([`make`](Self::make)-family constructors). Pass-minted bodies instead use
677    /// [`detached`](Self::detached) + [`install_id`](Self::install_id).
678    pub fn empty_with_id(id: FunctionId) -> Self {
679        Self {
680            id: Some(id),
681            root: None,
682            insns: StableArena::default(),
683            blocks: StableArena::default(),
684            roster: Vec::new(),
685            params: StableArena::default(),
686            edges: StableArena::default(),
687            temp_spaces: Registry::default(),
688            temps: Registry::default(),
689            instruction_addrs: BTreeSet::new(),
690            names: crate::context::NameTable::default(),
691            users: FxHashMap::default(),
692        }
693    }
694
695    /// An empty *detached* function body: no root, empty arenas, and **no**
696    /// registry identity yet ([`id`](Self::id) panics until
697    /// [`install_id`](Self::install_id) runs at the mint barrier). The interface
698    /// lives separately in
699    /// [`Context::interfaces`](crate::context::Context::interfaces). This is how a
700    /// pass mints a function; the id is stamped by
701    /// [`install_id`](Self::install_id) at the mint barrier.
702    pub fn detached() -> Self {
703        Self {
704            id: None,
705            root: None,
706            insns: StableArena::default(),
707            blocks: StableArena::default(),
708            roster: Vec::new(),
709            params: StableArena::default(),
710            edges: StableArena::default(),
711            temp_spaces: Registry::default(),
712            temps: Registry::default(),
713            instruction_addrs: BTreeSet::new(),
714            names: crate::context::NameTable::default(),
715            users: FxHashMap::default(),
716        }
717    }
718
719    /// This body's immutable function identity. Panics on a detached body (one
720    /// minted but not yet installed) — the loud, release-active tripwire against
721    /// laundering an owner ID through an uninstalled body.
722    pub fn id(&self) -> FunctionId {
723        self.id.expect("detached body: no registry id yet")
724    }
725
726    /// This body's registry identity, or `None` while detached. The honest
727    /// accessor for the install barrier and verifiers.
728    pub fn try_id(&self) -> Option<FunctionId> {
729        self.id
730    }
731
732    /// Restore the skipped identity field from the body's registry key after
733    /// deserialization. Serialized function bodies remain wire-compatible with
734    /// sessions written before the identity became intrinsic.
735    pub(crate) fn rehydrate_id(&mut self, id: FunctionId) {
736        self.id = Some(id);
737    }
738
739    /// This function's instructions that use `value` as an operand (see
740    /// [`users`](Self::users)). Empty for a value this function never uses.
741    pub(crate) fn local_users_of(&self, value: ValueId) -> &[LocalInsnId] {
742        self.users
743            .get(&value.strip_func())
744            .map(Vec::as_slice)
745            .unwrap_or(&[])
746    }
747
748    /// Whether any instruction in this body uses `value`.
749    ///
750    /// The question `users_of(v).is_empty()` asks, without the allocation it
751    /// takes to answer it that way. Dead-code elimination asks it once per
752    /// instruction per round, which made building those vectors the single
753    /// largest cost of lifting a block.
754    pub fn has_users(&self, value: ValueId) -> bool {
755        if value
756            .owning_function()
757            .is_some_and(|owner| owner != self.id())
758        {
759            return false;
760        }
761        !self.local_users_of(value).is_empty()
762    }
763
764    /// This body's qualified instruction IDs that use `value`. A value owned by
765    /// another function has no users in this body, even if its local index
766    /// collides with one of this body's values.
767    pub fn users_of(&self, value: ValueId) -> Vec<InstructionId> {
768        if value
769            .owning_function()
770            .is_some_and(|owner| owner != self.id())
771        {
772            return Vec::new();
773        }
774        self.local_users_of(value)
775            .iter()
776            .map(|&local| InstructionId::new(self.id(), local))
777            .collect()
778    }
779
780    /// Iterate this function's recorded `(value, users)` reverse-use entries, with
781    /// keys in their stored body-local form (qualify via the owning func at the
782    /// [`FunctionRef`] wrapper). Read-only; used by the users-map consistency verifier.
783    pub fn user_map_entries(&self) -> impl Iterator<Item = (LocalValueId, &[LocalInsnId])> {
784        self.users.iter().map(|(v, u)| (*v, u.as_slice()))
785    }
786
787    /// This function's body-local entry block id, if any (raw accessor).
788    pub fn root_id(&self) -> Option<LocalBlockId> {
789        self.root
790    }
791
792    /// Sets this function's body-local entry block id directly, without rostering /
793    /// address bookkeeping of [`FunctionMutRef::set_root`]. Routing target for
794    /// the raw `.root = …` field writes whose callers have already rostered the
795    /// block (stage 6a §11).
796    pub fn set_root_id(&mut self, root: Option<LocalBlockId>) {
797        self.root = root;
798    }
799
800    // ---- function-local raw arena accessors (context-split stage 5a) --------
801    //
802    // Resolve a composite id against *this* body by its `local` half alone,
803    // ignoring `id.func`. Under strict IR locality a body only ever stores its
804    // own values, so `id.func` is always this function's id; naming the body
805    // explicitly (`ctx.body(fid).block(id)`) instead of routing through
806    // `id.func` (`BasicBlock::from_id(ctx, id)`) is what makes the stage-4
807    // `func`-strip mechanical — after it, `id` *is* the local index and these
808    // bodies are unchanged. These return raw `&`/`&mut` arena values; for the
809    // wrapper-ref surface (`successors()`, `name()`, …) use the `*_ref`
810    // constructors on a [`QCodeView`] or a [`FunctionRef`].
811
812    /// The block `id`, by its function-local index (see the note above).
813    pub fn block(&self, id: BlockId) -> &BasicBlock<'str> {
814        assert_eq!(id.func, self.id(), "block belongs to another function");
815        &self.blocks[id.local]
816    }
817    /// The block `id`, mutably.
818    pub fn block_mut(&mut self, id: BlockId) -> &mut BasicBlock<'str> {
819        assert_eq!(id.func, self.id(), "block belongs to another function");
820        &mut self.blocks[id.local]
821    }
822
823    /// Whether `id` currently names a live block payload in this body.
824    pub fn contains_block(&self, id: BlockId) -> bool {
825        id.func == self.id() && self.blocks.contains(id.local)
826    }
827    /// The instruction `id`, by its function-local index.
828    pub fn insn(&self, id: InstructionId) -> &Instruction<'str> {
829        assert_eq!(
830            id.func,
831            self.id(),
832            "instruction belongs to another function"
833        );
834        &self.insns[id.local]
835    }
836    /// The instruction `id`, mutably.
837    pub fn insn_mut(&mut self, id: InstructionId) -> &mut Instruction<'str> {
838        assert_eq!(
839            id.func,
840            self.id(),
841            "instruction belongs to another function"
842        );
843        &mut self.insns[id.local]
844    }
845
846    /// Whether `id` currently names a live instruction payload in this body.
847    pub fn contains_instruction(&self, id: InstructionId) -> bool {
848        id.func == self.id() && self.insns.contains(id.local)
849    }
850    /// The block parameter `id`, by its function-local index.
851    pub fn block_param(&self, id: BlockParamId) -> &BlockParam<'str> {
852        assert_eq!(
853            id.func,
854            self.id(),
855            "block parameter belongs to another function"
856        );
857        &self.params[id.local]
858    }
859    /// The block parameter `id`, mutably.
860    pub fn block_param_mut(&mut self, id: BlockParamId) -> &mut BlockParam<'str> {
861        assert_eq!(
862            id.func,
863            self.id(),
864            "block parameter belongs to another function"
865        );
866        &mut self.params[id.local]
867    }
868
869    /// Whether `id` currently names a live block-parameter payload in this body.
870    pub fn contains_block_param(&self, id: BlockParamId) -> bool {
871        id.func == self.id() && self.params.contains(id.local)
872    }
873
874    /// The result type of a **body-local** operand, resolved without a registry
875    /// identity. The shared arms (`Literal`/`Bytes`/`Varnode`/`Function`) route
876    /// through `shared`; the arena arms (`Instruction`/`BlockParam`/`Temp`/
877    /// `BasicBlock`) index this body's own arenas by their bare local index. This
878    /// is the id-less twin of [`QCodeView::type_of`] — usable on a detached body.
879    pub fn local_type_of(
880        &self,
881        shared: &crate::context::Shared<'str>,
882        id: crate::value::LocalValueId,
883    ) -> crate::types::TypeId {
884        use crate::value::LocalValueId;
885        match id {
886            LocalValueId::Literal(id) => shared.values.literals[id].type_id,
887            LocalValueId::Bytes(id) => shared.values.bytes[id].type_id,
888            LocalValueId::Instruction(local) => self.insns[local].type_id,
889            LocalValueId::BlockParam(local) => self.params[local].type_id,
890            LocalValueId::Varnode(id) => shared
891                .values
892                .varnode_types
893                .get(&id)
894                .copied()
895                .unwrap_or_else(|| {
896                    shared
897                        .types
898                        .get_or_make_int(shared.values.varnodes[id].size_bytes())
899                }),
900            LocalValueId::Temp(local) => shared.types.get_or_make_int(self.temps[local].size),
901            LocalValueId::Poison(id) => shared.values.poisons[id].type_id,
902            LocalValueId::BasicBlock(_) | LocalValueId::Function(_) => {
903                shared.types.get_or_make_int(0)
904            }
905        }
906    }
907
908    /// The stored type of a **body-local** operand, or `None` where the operand
909    /// carries no stored type (untyped varnode, temp, block, function). The
910    /// id-less twin of [`QCodeView::stored_type_of`].
911    pub fn local_stored_type_of(
912        &self,
913        shared: &crate::context::Shared<'str>,
914        id: crate::value::LocalValueId,
915    ) -> Option<crate::types::TypeId> {
916        use crate::value::LocalValueId;
917        match id {
918            LocalValueId::Literal(id) => Some(shared.values.literals[id].type_id),
919            LocalValueId::Bytes(id) => Some(shared.values.bytes[id].type_id),
920            LocalValueId::Instruction(local) => Some(self.insns[local].type_id),
921            LocalValueId::BlockParam(local) => Some(self.params[local].type_id),
922            LocalValueId::Varnode(id) => shared.values.varnode_types.get(&id).copied(),
923            LocalValueId::Poison(id) => Some(shared.values.poisons[id].type_id),
924            LocalValueId::Temp(_) | LocalValueId::BasicBlock(_) | LocalValueId::Function(_) => None,
925        }
926    }
927
928    /// Appends a body-local temporary space and returns its qualified ID.
929    pub fn push_temp_space(&mut self, space: TempSpace) -> TempSpaceId {
930        TempSpaceId::new(self.id(), self.temp_spaces.push(space))
931    }
932
933    /// Appends a body-local temporary value and returns its qualified ID.
934    pub fn push_temp(&mut self, temp: Temp<'str>) -> TempId {
935        assert!(
936            usize::from(temp.space) < self.temp_spaces.len(),
937            "temporary references a missing local space"
938        );
939        let name = temp.name.clone();
940        if let Some(name) = &name {
941            assert!(
942                !self.names.contains(name),
943                "temporary name {name:?} is already registered in this function"
944            );
945        }
946        let local = self.temps.push(temp);
947        if let Some(name) = name {
948            self.names
949                .register(name, LocalValueId::Temp(local), None)
950                .expect("temporary name was checked before insertion");
951        }
952        TempId::new(self.id(), local)
953    }
954
955    /// Resolves a qualified temporary-space ID against this body.
956    #[track_caller]
957    pub fn temp_space(&self, id: TempSpaceId) -> &TempSpace {
958        assert_eq!(
959            id.func,
960            self.id(),
961            "temporary space belongs to another function"
962        );
963        debug_assert!(
964            self.contains_temp_space(id),
965            "missing temporary space {id:?} in function {:?} (arena length {})",
966            self.id(),
967            self.temp_spaces.len()
968        );
969        &self.temp_spaces[id.local]
970    }
971
972    /// Iterates over every temporary space owned by this body, in id order.
973    pub fn temp_spaces(&self) -> impl Iterator<Item = (TempSpaceId, &TempSpace)> + '_ {
974        let func = self.id();
975        self.temp_spaces
976            .iter()
977            .map(move |space| (TempSpaceId::new(func, space.id), space.inner))
978    }
979
980    /// Whether `id` names a temporary space in this body.
981    pub fn contains_temp_space(&self, id: TempSpaceId) -> bool {
982        id.func == self.id() && usize::from(id.local) < self.temp_spaces.len()
983    }
984
985    /// Resolves a qualified temporary-value ID against this body.
986    #[track_caller]
987    pub fn temp(&self, id: TempId) -> &Temp<'str> {
988        assert_eq!(id.func, self.id(), "temporary belongs to another function");
989        debug_assert!(
990            self.contains_temp(id),
991            "missing temporary {id:?} in function {:?} (arena length {})",
992            self.id(),
993            self.temps.len()
994        );
995        &self.temps[id.local]
996    }
997
998    /// Whether `id` names a temporary value in this body.
999    pub fn contains_temp(&self, id: TempId) -> bool {
1000        id.func == self.id() && usize::from(id.local) < self.temps.len()
1001    }
1002
1003    /// Physically removes a block parameter and its local bookkeeping.
1004    /// Positional block and edge-argument rewrites belong to the caller. Those
1005    /// rewrites may occur later in the same transformation, so outstanding uses
1006    /// are allowed while the transformation is in progress.
1007    pub fn remove_block_param(&mut self, id: BlockParamId) {
1008        assert!(
1009            self.contains_block_param(id),
1010            "cannot remove stale param {id:?}"
1011        );
1012        let key = ValueId::BlockParam(id).strip_func();
1013        let name = self.params[id.local].name.clone();
1014        if let Some(name) = name {
1015            self.names.forget(name.as_ref());
1016        }
1017        self.users.remove(&key);
1018        self.params.remove(id.local);
1019    }
1020    /// The CFG edge `id`, by its function-local index.
1021    pub fn edge(&self, id: EdgeId) -> &EdgeData {
1022        &self.edges[id]
1023    }
1024
1025    // ---- structural mutation verbs (context-split stage 5b-ii(b)) -----------
1026    //
1027    // The single-homed IR mutation surface for a function *body* (design ruling
1028    // 6). Each verb operates directly on this body's own arenas, reading shared
1029    // data (types for minting) through an explicit `&Context` where needed. These
1030    // are the algorithm bodies formerly living on the checked-out mutation path
1031    // (`value::util::body_mut`), ported here with the routing indirection dropped:
1032    // `self.function_mut(f)` collapses to `self`, `self.view()` to `self`'s
1033    // own arena accessors. The owning [`FunctionId`] comes from [`id`](Self::id).
1034
1035    /// Push a fresh instruction into this body's arena, recording each operand's
1036    /// use in the reverse-use map.
1037    pub fn push_insn(&mut self, insn: Instruction<'str>) -> InstructionId {
1038        InstructionId::new(self.id(), self.push_insn_local(insn))
1039    }
1040
1041    /// Push a fresh instruction into this body's arena, recording each operand's
1042    /// use in the reverse-use map, and return its **body-local** id. The id-less
1043    /// twin of [`push_insn`](Self::push_insn), usable on a detached body.
1044    pub fn push_insn_local(&mut self, insn: Instruction<'str>) -> LocalInsnId {
1045        let args: Vec<LocalValueId> = insn.mnemonic().args().into_iter().collect();
1046        let local = self.insns.push(insn);
1047        for arg in args {
1048            self.users.entry(arg).or_default().push(local);
1049        }
1050        local
1051    }
1052
1053    /// Push a fresh block into this body's arena and onto its ownership roster.
1054    /// Ownership is derived from the storing arena: the returned id's `func` is
1055    /// this body's own id.
1056    pub fn push_block(&mut self, block: BasicBlock<'str>) -> BlockId {
1057        let func = self.id();
1058        BlockId::new(func, self.push_block_local(block))
1059    }
1060
1061    /// Push a fresh block into this body's arena and roster, returning its
1062    /// **body-local** id. The id-less twin of [`push_block`](Self::push_block),
1063    /// usable on a detached (uninstalled) body.
1064    pub fn push_block_local(&mut self, block: BasicBlock<'str>) -> LocalBlockId {
1065        let local = self.blocks.push(block);
1066        self.roster.push(local);
1067        local
1068    }
1069
1070    /// Mint a fresh empty block, owned by this function (arena membership) and
1071    /// rostered.
1072    pub fn make_block(&mut self) -> BlockId {
1073        self.push_block(BasicBlock::detached())
1074    }
1075
1076    /// Mint a fresh empty block, returning its **body-local** id. The id-less twin
1077    /// of [`make_block`](Self::make_block), usable on a detached body.
1078    pub fn make_block_local(&mut self) -> LocalBlockId {
1079        self.push_block_local(BasicBlock::detached())
1080    }
1081
1082    /// This body's block `block`, by its function-local index (id-free read,
1083    /// usable on a detached body).
1084    pub fn block_local(&self, block: LocalBlockId) -> &BasicBlock<'str> {
1085        &self.blocks[block]
1086    }
1087
1088    /// This body's instruction mnemonic, by its function-local index (id-free
1089    /// read, usable on a detached body).
1090    pub fn mnemonic_local(&self, insn: LocalInsnId) -> &Mnemonic {
1091        self.insns[insn].mnemonic()
1092    }
1093
1094    /// Push a fresh block parameter into this body's arena.
1095    pub fn push_block_param(&mut self, param: BlockParam<'str>) -> BlockParamId {
1096        let local = self.params.push(param);
1097        BlockParamId::new(self.id(), local)
1098    }
1099
1100    /// Push a fresh block parameter into this body's arena and wire it into
1101    /// `block`'s parameter list, returning its **body-local** id. The id-less twin
1102    /// of [`push_block_param`](Self::push_block_param), usable on a detached body.
1103    pub fn push_block_param_local(
1104        &mut self,
1105        block: LocalBlockId,
1106        param: BlockParam<'str>,
1107    ) -> LocalParamId {
1108        let local = self.params.push(param);
1109        self.blocks[block].params.push(local);
1110        local
1111    }
1112
1113    /// Append an already-created instruction to the end of `block`, setting its
1114    /// parent (id-free; the mutation twin of [`BaseRef::push_insn`], usable on a
1115    /// detached body).
1116    pub fn append_insn_local(&mut self, block: LocalBlockId, insn: LocalInsnId) {
1117        self.insns[insn].parent = Some(block);
1118        self.blocks[block].instructions.push(insn);
1119    }
1120
1121    /// Mint an `Int(size)`-typed instruction with `mnemonic` (the type is minted
1122    /// in `shared`'s interner through its `&self` path).
1123    pub fn push_mnemonic(
1124        &mut self,
1125        shared: &crate::context::Shared<'str>,
1126        mnemonic: Mnemonic,
1127        size: usize,
1128    ) -> InstructionId {
1129        let type_id = shared.types.get_or_make_int(size);
1130        let insn = Instruction::new(type_id, mnemonic);
1131        self.push_insn(insn)
1132    }
1133
1134    /// Mint an instruction with `mnemonic` and an explicit result `type_id`.
1135    pub fn push_mnemonic_with_type(
1136        &mut self,
1137        mnemonic: Mnemonic,
1138        type_id: crate::types::TypeId,
1139    ) -> InstructionId {
1140        let insn = Instruction::new(type_id, mnemonic);
1141        self.push_insn(insn)
1142    }
1143
1144    /// Mint an instruction with `mnemonic` and an explicit result `type_id`,
1145    /// returning its **body-local** id. The id-less twin of
1146    /// [`push_mnemonic_with_type`](Self::push_mnemonic_with_type).
1147    pub fn push_mnemonic_with_type_local(
1148        &mut self,
1149        mnemonic: Mnemonic,
1150        type_id: crate::types::TypeId,
1151    ) -> LocalInsnId {
1152        self.push_insn_local(Instruction::new(type_id, mnemonic))
1153    }
1154
1155    /// Insert `insn` immediately before `before` in `block`. Panics if `before`
1156    /// is not in `block`.
1157    pub fn insert_insn_before(
1158        &mut self,
1159        block: BlockId,
1160        before: InstructionId,
1161        insn: InstructionId,
1162    ) {
1163        let index = self
1164            .block(block)
1165            .instructions
1166            .iter()
1167            .position(|&local| InstructionId::new(block.func, local) == before)
1168            .expect("before not in block");
1169        self.insn_mut(insn).parent = Some(block.local);
1170        self.block_mut(block)
1171            .instructions
1172            .insert(index, insn.localize(block.func));
1173    }
1174
1175    /// Move the live, non-terminator instruction `insn` immediately before the
1176    /// live instruction `before`, inferring the destination block from
1177    /// `before`. The moved instruction keeps its ID, payload, name, and use-map
1178    /// entries. Supports both cross-block motion and reordering within one
1179    /// block.
1180    pub fn move_insn_before(&mut self, insn: InstructionId, before: InstructionId) {
1181        let id = self.id();
1182        assert_eq!(insn.func, id, "instruction belongs to another function");
1183        assert_eq!(
1184            before.func, id,
1185            "anchor instruction belongs to another function"
1186        );
1187        if insn == before {
1188            return;
1189        }
1190        assert!(
1191            !self.insn(insn).mnemonic().is_terminator(),
1192            "moving a terminator requires updating its CFG edges"
1193        );
1194
1195        let source = self
1196            .insn(insn)
1197            .parent
1198            .map(|local| BlockId::new(id, local))
1199            .expect("moved instruction must belong to a block");
1200        let target = self
1201            .insn(before)
1202            .parent
1203            .map(|local| BlockId::new(id, local))
1204            .expect("anchor instruction must belong to a block");
1205        let source_index = self
1206            .block(source)
1207            .instructions
1208            .iter()
1209            .position(|&local| local == insn.local)
1210            .expect("moved instruction missing from its parent block");
1211        let before_index = self
1212            .block(target)
1213            .instructions
1214            .iter()
1215            .position(|&local| local == before.local)
1216            .expect("anchor instruction missing from its parent block");
1217        let insert_index = if source == target && source_index < before_index {
1218            before_index - 1
1219        } else {
1220            before_index
1221        };
1222
1223        self.block_mut(source).instructions.remove(source_index);
1224        self.block_mut(target)
1225            .instructions
1226            .insert(insert_index, insn.local);
1227        self.insn_mut(insn).parent = Some(target.local);
1228    }
1229
1230    /// Add a directed CFG edge `from -> to`, stored in this body's edge arena and
1231    /// linked into both incident blocks' edge sets.
1232    pub fn add_cfg_edge(&mut self, from: BlockId, to: BlockId) -> EdgeId {
1233        self.add_cfg_edge_local(from.local, to.local)
1234    }
1235
1236    /// Add a directed CFG edge `from -> to` over **body-local** block ids (id-free;
1237    /// the twin of [`add_cfg_edge`](Self::add_cfg_edge), usable on a detached body).
1238    pub fn add_cfg_edge_local(&mut self, from: LocalBlockId, to: LocalBlockId) -> EdgeId {
1239        let edge_id = self.edges.push(EdgeData { from, to });
1240        self.blocks[from].edges.insert(edge_id);
1241        self.blocks[to].edges.insert(edge_id);
1242        edge_id
1243    }
1244
1245    /// Remove CFG edge `edge_id`, unlinking it from both incident blocks and
1246    /// physically dropping its payload.
1247    pub fn remove_cfg_edge(&mut self, edge_id: EdgeId) {
1248        let EdgeData { from, to } = *self.edge(edge_id);
1249        let func = self.id();
1250        self.block_mut(BlockId::new(func, from))
1251            .edges
1252            .remove(&edge_id);
1253        self.block_mut(BlockId::new(func, to))
1254            .edges
1255            .remove(&edge_id);
1256        self.edges.remove(edge_id);
1257    }
1258
1259    /// Replace every use of `old` with `new` across this body's instructions and
1260    /// update the reverse use-map (SSA defs only; `old` is intra-function).
1261    pub fn replace_all_uses_with(&mut self, old: ValueId, new: ValueId) {
1262        if old == new {
1263            return;
1264        }
1265        let Some(func) = old.owning_function() else {
1266            return;
1267        };
1268        assert_eq!(
1269            func,
1270            self.id(),
1271            "cannot replace uses of a value owned by another function"
1272        );
1273        if let Some(new_owner) = new.owning_function() {
1274            assert_eq!(
1275                new_owner,
1276                self.id(),
1277                "cannot replace uses with a value owned by another function"
1278            );
1279        }
1280        let users = self.users_of(old);
1281        let old = old.localize(func);
1282        let new = new.localize(func);
1283        for user in users {
1284            self.insn_mut(user).mnemonic_mut().replace_value(old, new);
1285            self.users.entry(new).or_default().push(user.localize(func));
1286        }
1287        self.users.remove(&old);
1288    }
1289
1290    /// Replace every use of instruction `id` with `new`, then remove `id` —
1291    /// the standard "rewrite to a cheaper value" epilogue
1292    /// ([`replace_all_uses_with`](Self::replace_all_uses_with) +
1293    /// [`remove_instruction`](Self::remove_instruction)).
1294    pub fn replace_instruction(&mut self, id: InstructionId, new: ValueId) {
1295        // Replacing an instruction with itself is a contradiction: the use
1296        // forwarding is a no-op, so removing `id` would delete a value that is
1297        // still referenced. Leave it in place.
1298        if new == ValueId::Instruction(id) {
1299            return;
1300        }
1301        self.replace_all_uses_with(ValueId::Instruction(id), new);
1302        self.remove_instruction(id);
1303    }
1304
1305    /// Physically removes a set of instructions after pruning their operands
1306    /// from the reverse-use map. Call after removing them from their parent
1307    /// blocks and unlinking any CFG edges owned by terminators.
1308    pub fn remove_instructions(&mut self, dead: &FxHashSet<LocalInsnId>) {
1309        let mut ids: Vec<_> = dead.iter().copied().collect();
1310        ids.sort_unstable();
1311        let mut affected_args: FxHashSet<LocalValueId> = FxHashSet::default();
1312        for &id in &ids {
1313            assert!(
1314                self.insns.contains(id),
1315                "cannot remove stale instruction {id:?}"
1316            );
1317            affected_args.extend(self.insns[id].mnemonic().args());
1318        }
1319        for arg in affected_args {
1320            let remove_key = if let Some(users) = self.users.get_mut(&arg) {
1321                users.retain(|local| !dead.contains(local));
1322                users.is_empty()
1323            } else {
1324                false
1325            };
1326            if remove_key {
1327                self.users.remove(&arg);
1328            }
1329        }
1330        for id in ids {
1331            self.users.remove(&LocalValueId::Instruction(id));
1332            self.insns.remove(id);
1333        }
1334    }
1335
1336    /// Remove instruction `id` from its block, unlink its outgoing CFG edges if a
1337    /// terminator, clear its name, prune its operand use-lists, and physically
1338    /// drop its payload.
1339    pub fn remove_instruction(&mut self, id: InstructionId) {
1340        assert_eq!(
1341            id.func,
1342            self.id(),
1343            "instruction belongs to another function"
1344        );
1345        let func = self.id();
1346        let (parent, name, is_terminator, args) = {
1347            let insn = self.insn(id);
1348            (
1349                insn.parent.map(|l| BlockId::new(self.id(), l)),
1350                insn.name.clone(),
1351                insn.mnemonic().is_terminator(),
1352                insn.mnemonic().args().into_iter().collect::<Vec<_>>(),
1353            )
1354        };
1355
1356        if let Some(block_id) = parent {
1357            self.block_mut(block_id)
1358                .instructions
1359                .retain(|&local| local != id.localize(block_id.func));
1360            if is_terminator {
1361                let mut succ: Vec<EdgeId> = {
1362                    let block = self.block(block_id);
1363                    block
1364                        .edges
1365                        .iter()
1366                        .copied()
1367                        .filter(|&e| self.edge(e).from == block_id.local)
1368                        .collect()
1369                };
1370                succ.sort_unstable();
1371                for edge_id in succ {
1372                    self.remove_cfg_edge(edge_id);
1373                }
1374            }
1375        }
1376
1377        if let Some(n) = name {
1378            self.names.forget(n.as_ref());
1379        }
1380        for arg in args {
1381            let remove_key = if let Some(users) = self.users.get_mut(&arg) {
1382                users.retain(|&local| local != id.localize(func));
1383                users.is_empty()
1384            } else {
1385                false
1386            };
1387            if remove_key {
1388                self.users.remove(&arg);
1389            }
1390        }
1391        self.users.remove(&ValueId::Instruction(id).strip_func());
1392        self.insns.remove(id.local);
1393    }
1394
1395    /// Removes several non-terminator instructions of one block at once.
1396    ///
1397    /// [`remove_instruction`](Self::remove_instruction) walks the block's
1398    /// instruction list to unlink each one, so removing *n* of them costs
1399    /// `n × block`. Lifting an absorbed guest basic block deletes hundreds of
1400    /// instructions from a block hundreds long, and that product was a real
1401    /// share of translation time. Here the list is walked once however many go.
1402    ///
1403    /// Terminators are rejected rather than handled: removing one has to tear
1404    /// down CFG edges too, and no caller of this deletes one — dead-code
1405    /// elimination will not touch a terminator, and store forwarding removes
1406    /// only loads and stores.
1407    pub fn remove_block_instructions(&mut self, block_id: BlockId, dead: &FxHashSet<LocalInsnId>) {
1408        assert_eq!(
1409            block_id.func,
1410            self.id(),
1411            "block belongs to another function"
1412        );
1413        if dead.is_empty() {
1414            return;
1415        }
1416
1417        let mut names = Vec::new();
1418        for &id in dead {
1419            let insn = &self.insns[id];
1420            assert!(
1421                !insn.mnemonic().is_terminator(),
1422                "bulk removal does not unlink CFG edges; {id:?} is a terminator"
1423            );
1424            if let Some(name) = insn.name.clone() {
1425                names.push(name);
1426            }
1427        }
1428
1429        self.block_mut(block_id)
1430            .instructions
1431            .retain(|local| !dead.contains(local));
1432        self.purge_instructions(dead, names);
1433    }
1434
1435    /// Forgets `dead`'s names, prunes them from every user list they appear in,
1436    /// and drops their payloads.
1437    ///
1438    /// The shared tail of removing instructions in bulk. It does not touch any
1439    /// block's instruction list — the caller has already dealt with that, which
1440    /// is the whole point: doing it per instruction is what makes removal
1441    /// quadratic in the size of the block.
1442    fn purge_instructions(&mut self, dead: &FxHashSet<LocalInsnId>, names: Vec<Cow<'str, str>>) {
1443        for name in names {
1444            self.names.forget(name.as_ref());
1445        }
1446        // Each operand's user list is pruned once, not once per dead user.
1447        let mut operands: FxHashSet<LocalValueId> = FxHashSet::default();
1448        for &id in dead {
1449            operands.extend(self.insns[id].mnemonic().args());
1450        }
1451        for arg in operands {
1452            let now_empty = if let Some(users) = self.users.get_mut(&arg) {
1453                users.retain(|local| !dead.contains(local));
1454                users.is_empty()
1455            } else {
1456                false
1457            };
1458            if now_empty {
1459                self.users.remove(&arg);
1460            }
1461        }
1462        for &id in dead {
1463            self.users.remove(&LocalValueId::Instruction(id));
1464            self.insns.remove(id);
1465        }
1466    }
1467
1468    /// Rehome `remove`'s outgoing CFG edges onto `keep`. The direct edge and
1469    /// `keep`'s forwarding terminator have already been removed by the caller.
1470    pub fn rehome_outgoing_edges(&mut self, keep: BlockId, remove: BlockId) {
1471        let outgoing: Vec<EdgeId> = {
1472            let block = self.block(remove);
1473            block
1474                .edges
1475                .iter()
1476                .copied()
1477                .filter(|&e| self.edge(e).from == remove.local)
1478                .collect()
1479        };
1480        for eid in outgoing {
1481            self.edges[eid].from = keep.local;
1482            self.block_mut(keep).edges.insert(eid);
1483            self.block_mut(remove).edges.remove(&eid);
1484        }
1485    }
1486
1487    /// Replace an instruction's mnemonic in place, keeping the reverse use-map in
1488    /// sync.
1489    pub fn replace_instruction_mnemonic(&mut self, id: InstructionId, mnemonic: Mnemonic) {
1490        assert_eq!(
1491            id.func,
1492            self.id(),
1493            "instruction belongs to another function"
1494        );
1495        self.replace_instruction_mnemonic_local(id.local, mnemonic);
1496    }
1497
1498    /// Replace an instruction's mnemonic in place, keeping the reverse use-map in
1499    /// sync, over a **body-local** instruction id (id-free; the twin of
1500    /// [`replace_instruction_mnemonic`](Self::replace_instruction_mnemonic),
1501    /// usable on a detached body).
1502    pub fn replace_instruction_mnemonic_local(&mut self, id: LocalInsnId, mnemonic: Mnemonic) {
1503        let old_args = self.insns[id]
1504            .mnemonic()
1505            .args()
1506            .into_iter()
1507            .collect::<Vec<_>>();
1508        for arg in old_args {
1509            let now_empty = if let Some(users) = self.users.get_mut(&arg) {
1510                users.retain(|&local| local != id);
1511                users.is_empty()
1512            } else {
1513                false
1514            };
1515            if now_empty {
1516                self.users.remove(&arg);
1517            }
1518        }
1519        *self.insns[id].mnemonic_mut() = mnemonic;
1520        let new_args = self.insns[id]
1521            .mnemonic()
1522            .args()
1523            .into_iter()
1524            .collect::<Vec<_>>();
1525        for arg in new_args {
1526            self.users.entry(arg).or_default().push(id);
1527        }
1528    }
1529
1530    /// Set `block`'s name and register it in this body's local name table, over a
1531    /// **body-local** block id (id-free; the twin of
1532    /// [`BaseRef::rename_local`](crate::value::util::base_ref::BaseRef::rename_local)
1533    /// restricted to the id-free local parts, usable on a detached body). Errors
1534    /// only on a duplicate name.
1535    pub fn rename_block_local(&mut self, block: LocalBlockId, name: Cow<'str, str>) -> Result<()> {
1536        let target = LocalValueId::BasicBlock(block);
1537        if let Some(existing) = self.names.get(&name) {
1538            return if existing == target {
1539                Ok(())
1540            } else {
1541                Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
1542            };
1543        }
1544        let old_name = self.blocks[block].local_name().map(str::to_owned);
1545        self.names
1546            .register(name.clone(), target, old_name.as_deref())?;
1547        self.blocks[block].set_name(Some(name));
1548        Ok(())
1549    }
1550
1551    /// Drop `block` from this body's ownership roster. Ownership is derived from
1552    /// the storing arena (`block.func`).
1553    pub fn unroster_block(&mut self, block: BlockId) {
1554        self.roster.retain(|&b| b != block.localize(block.func));
1555    }
1556
1557    /// Remove `block` from this body: unlink every incident CFG edge, remove its
1558    /// instructions and params, clear ownership metadata, then drop its payload.
1559    /// Empties `block` of code, keeping the block itself.
1560    ///
1561    /// Only the *outgoing* edges go, because those are owned by the terminator
1562    /// being removed; the incoming ones belong to other blocks' terminators,
1563    /// which still name this block and must keep resolving to it. That is the
1564    /// point of clearing rather than deleting: every branch already targeting
1565    /// this block stays valid while its contents are rebuilt.
1566    pub fn clear_block_instructions(&mut self, block: BlockId) {
1567        assert_eq!(block.func, self.id(), "block belongs to another function");
1568        let mut outgoing: Vec<EdgeId> = self
1569            .block(block)
1570            .edges
1571            .iter()
1572            .copied()
1573            .filter(|&edge| self.edges[edge].from == block.local)
1574            .collect();
1575        outgoing.sort_unstable();
1576        for edge in outgoing {
1577            self.remove_cfg_edge(edge);
1578        }
1579        // The list is emptied in one move and the instructions purged as a
1580        // set. Removing them one at a time means re-scanning the very list
1581        // being emptied for each one, which is quadratic — and the blocks this
1582        // clears are absorbed guest basic blocks, thousands of instructions
1583        // long. Splitting one used to cost more than lifting it did.
1584        let insns = std::mem::take(&mut self.block_mut(block).instructions);
1585        let dead: FxHashSet<LocalInsnId> = insns.iter().copied().collect();
1586        let names: Vec<Cow<'str, str>> = insns
1587            .iter()
1588            .filter_map(|&local| self.insns[local].name.clone())
1589            .collect();
1590        self.purge_instructions(&dead, names);
1591    }
1592
1593    pub fn delete_block(&mut self, block: BlockId) {
1594        assert_eq!(block.func, self.id(), "block belongs to another function");
1595        let mut edges: Vec<EdgeId> = self.block(block).edges.iter().copied().collect();
1596        edges.sort_unstable();
1597        for edge in edges {
1598            self.remove_cfg_edge(edge);
1599        }
1600        let insns: Vec<InstructionId> = self
1601            .block(block)
1602            .instructions
1603            .iter()
1604            .map(|&local| InstructionId::new(self.id(), local))
1605            .collect();
1606        for insn in insns {
1607            self.remove_instruction(insn);
1608        }
1609        let params: Vec<BlockParamId> = self
1610            .block(block)
1611            .params
1612            .iter()
1613            .map(|&local| BlockParamId::new(self.id(), local))
1614            .collect();
1615        for param in params {
1616            self.remove_block_param(param);
1617        }
1618        let name = self.block(block).local_name().map(str::to_owned);
1619        self.unroster_block(block);
1620        if self.root == Some(block.local) {
1621            self.root = None;
1622        }
1623        if let Some(name) = name {
1624            self.names.forget(&name);
1625        }
1626        self.blocks.remove(block.local);
1627    }
1628
1629    /// Absorb `other` into `keep`: drop `keep`'s terminal branch, append `other`'s
1630    /// instructions, rehome its outgoing edges, and remove it. `edge_ab` is the
1631    /// direct edge `keep -> other`.
1632    pub fn absorb_block(&mut self, keep: BlockId, other: BlockId, edge_ab: EdgeId) {
1633        assert_eq!(
1634            keep.func, other.func,
1635            "cannot absorb across function arenas"
1636        );
1637        let (branch_id, branch_args) = self
1638            .block(keep)
1639            .instructions
1640            .last()
1641            .and_then(
1642                |&local| match self.insn(InstructionId::new(keep.func, local)).mnemonic() {
1643                    Mnemonic::Branch(branch) if BlockId::new(keep.func, branch.target) == other => {
1644                        Some((InstructionId::new(keep.func, local), branch.args.clone()))
1645                    }
1646                    _ => None,
1647                },
1648            )
1649            .expect("absorbed block must be reached by keep's terminal branch");
1650        let other_params: Vec<_> = self
1651            .block(other)
1652            .params
1653            .iter()
1654            .map(|&local| BlockParamId::new(other.func, local))
1655            .collect();
1656        if !other_params.is_empty() {
1657            assert_eq!(
1658                other_params.len(),
1659                branch_args.len(),
1660                "cannot absorb block with {} params through branch with {} args",
1661                other_params.len(),
1662                branch_args.len()
1663            );
1664            for (param, arg) in other_params.iter().copied().zip(branch_args) {
1665                self.replace_all_uses_with(ValueId::BlockParam(param), arg.qualify(keep.func));
1666            }
1667        }
1668        self.remove_cfg_edge(edge_ab);
1669        self.remove_instruction(branch_id);
1670        let b_insns = std::mem::take(&mut self.block_mut(other).instructions);
1671        for &local in &b_insns {
1672            self.insn_mut(InstructionId::new(other.func, local)).parent = Some(keep.local);
1673        }
1674        self.block_mut(keep).instructions.extend(b_insns);
1675        self.rehome_outgoing_edges(keep, other);
1676        let (b_addr, b_extra, b_name) = {
1677            let b = self.block(other);
1678            (
1679                b.address,
1680                b.extra_addresses.clone(),
1681                b.local_name().map(str::to_owned),
1682            )
1683        };
1684        for param in other_params {
1685            self.remove_block_param(param);
1686        }
1687        self.unroster_block(other);
1688        if self.root == Some(other.local) {
1689            self.root = Some(keep.local);
1690        }
1691        if let Some(name) = b_name {
1692            self.names.forget(&name);
1693        }
1694        self.blocks.remove(other.local);
1695        if let Some(addr) = b_addr {
1696            self.block_mut(keep).extra_addresses.push(addr);
1697        }
1698        self.block_mut(keep).extra_addresses.extend(b_extra);
1699    }
1700
1701    /// Register `name` for `id` in this body's local name table (block/instruction/
1702    /// param). A global-scoped `id` reads `shared` for the duplicate check but
1703    /// cannot be *registered* through a body (its shared table is read-only here);
1704    /// no body verb reaches that arm.
1705    pub fn register_local_name(
1706        &mut self,
1707        shared: &crate::context::Shared<'str>,
1708        id: ValueId,
1709        name: Cow<'str, str>,
1710        old_name: Option<&str>,
1711    ) -> Result<()> {
1712        if id.name_scope_function().is_none() {
1713            return match shared.get_named(&name) {
1714                Some(existing) if existing == id => Ok(()),
1715                Some(_) => Err(Error::spanless(ErrorTy::DuplicateName(name.to_string()))),
1716                None => unimplemented!(
1717                    "a function body cannot register a global name (shared is read-only)"
1718                ),
1719            };
1720        }
1721        self.register_body_name(id, name, old_name)
1722    }
1723
1724    /// Register `name` for the function-scoped `id` (block/instruction/param/Temp)
1725    /// in this body's local name table. The shared-arm-free canon behind
1726    /// [`register_local_name`](Self::register_local_name); panics on a
1727    /// global-scoped `id`. Errors only on a duplicate name.
1728    pub fn register_body_name(
1729        &mut self,
1730        id: ValueId,
1731        name: Cow<'str, str>,
1732        old_name: Option<&str>,
1733    ) -> Result<()> {
1734        assert!(
1735            id.name_scope_function().is_some(),
1736            "register_body_name on a global-scoped value {id:?}"
1737        );
1738        if let Some(existing) = self.names.get(&name).map(|id| id.qualify(self.id())) {
1739            return if existing == id {
1740                Ok(())
1741            } else {
1742                Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
1743            };
1744        }
1745        self.names.register(name, id.localize(self.id()), old_name)
1746    }
1747
1748    /// Gets a reference to a function from its ID
1749    pub fn from_id<'ctx>(ctx: &'ctx Context<'str>, id: FunctionId) -> FunctionRef<'str, 'ctx> {
1750        FunctionRef::new(ModuleView::new(ctx), id)
1751    }
1752
1753    /// Gets a mutable reference to a function from its ID
1754    pub fn from_id_mut<'ctx>(
1755        ctx: &'ctx mut Context<'str>,
1756        id: FunctionId,
1757    ) -> FunctionMutRef<'str, 'ctx> {
1758        FunctionMutRef::new(ctx, id)
1759    }
1760
1761    /// Gets a reference to a function by name
1762    pub fn from_name<'ctx>(
1763        ctx: &'ctx Context<'str>,
1764        name: &str,
1765    ) -> Option<FunctionRef<'str, 'ctx>> {
1766        ctx.get_named(name)
1767            .and_then(ValueId::as_function)
1768            .map(|id| FunctionBody::from_id(ctx, id))
1769    }
1770
1771    /// Create a new function
1772    pub fn make<'ctx>(
1773        ctx: &'ctx mut Context<'str>,
1774        name: Cow<'str, str>,
1775    ) -> Result<FunctionMutRef<'str, 'ctx>> {
1776        let id = FunctionId::from(ctx.bodies.len());
1777        let pushed = ctx.push_function(
1778            FunctionInterface::new(name.clone()),
1779            FunctionBody::empty_with_id(id),
1780        );
1781        debug_assert_eq!(pushed, id);
1782        ctx.update_name(name, id.into(), None)?;
1783        Ok(Self::from_id_mut(ctx, id))
1784    }
1785
1786    /// Create a new pure value-level lambda function.
1787    pub fn make_lambda<'ctx>(
1788        ctx: &'ctx mut Context<'str>,
1789        name: Cow<'str, str>,
1790    ) -> Result<FunctionMutRef<'str, 'ctx>> {
1791        let mut function = Self::make(ctx, name)?;
1792        function.interface_mut().kind = FunctionKind::Lambda;
1793        function.set_is_pure(true);
1794        function.set_register_effects(RegisterChannelState::Materialized(
1795            RegisterInterfaceMap::default(),
1796        ));
1797        Ok(function)
1798    }
1799
1800    /// Create a new function at a given address, generating a name if necessary.
1801    pub fn make_at_addr<'ctx>(
1802        ctx: &'ctx mut Context<'str>,
1803        address: u64,
1804        name: Option<Cow<'str, str>>,
1805    ) -> FunctionMutRef<'str, 'ctx> {
1806        let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
1807        Self::make_at_addr_indexed(ctx, &mut addresses, address, name)
1808    }
1809
1810    /// Indexed construction variant of [`make_at_addr`](Self::make_at_addr).
1811    pub fn make_at_addr_indexed<'ctx>(
1812        ctx: &'ctx mut Context<'str>,
1813        addresses: &mut crate::address_index::AddressIndex,
1814        address: u64,
1815        name: Option<Cow<'str, str>>,
1816    ) -> FunctionMutRef<'str, 'ctx> {
1817        let name = name.unwrap_or_else(|| Cow::Owned(format!("fn_{address:x}")));
1818        let id = FunctionId::from(ctx.bodies.len());
1819        let pushed = ctx.push_function(
1820            FunctionInterface::new(name.clone()),
1821            FunctionBody::empty_with_id(id),
1822        );
1823        debug_assert_eq!(pushed, id);
1824
1825        Self::from_id_mut(ctx, id)
1826            .with_name(name)
1827            .expect("Function name is not unique")
1828            .with_address_indexed(addresses, address)
1829            .expect("Function address is not unique")
1830    }
1831
1832    /// Like [`FunctionBody::make_at_addr`] but marks the result as external.
1833    ///
1834    /// External functions have no lifted body; the recursive disassembler will
1835    /// not try to explore them.
1836    pub fn make_external<'ctx>(
1837        ctx: &'ctx mut Context<'str>,
1838        address: u64,
1839        name: Option<Cow<'str, str>>,
1840    ) -> FunctionMutRef<'str, 'ctx> {
1841        let mut f = Self::make_at_addr(ctx, address, name);
1842        f.interface_mut().is_external = true;
1843        f
1844    }
1845
1846    /// Indexed construction variant of [`make_external`](Self::make_external).
1847    pub fn make_external_indexed<'ctx>(
1848        ctx: &'ctx mut Context<'str>,
1849        addresses: &mut crate::address_index::AddressIndex,
1850        address: u64,
1851        name: Option<Cow<'str, str>>,
1852    ) -> FunctionMutRef<'str, 'ctx> {
1853        let mut function = Self::make_at_addr_indexed(ctx, addresses, address, name);
1854        function.interface_mut().is_external = true;
1855        function
1856    }
1857
1858    /// Returns the [`FunctionId`] for `addr`, creating a named stub if absent.
1859    pub fn from_addr_or_create<'ctx>(
1860        ctx: &'ctx mut Context<'str>,
1861        address: u64,
1862    ) -> FunctionMutRef<'str, 'ctx> {
1863        let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
1864        Self::from_addr_or_create_indexed(ctx, &mut addresses, address)
1865    }
1866
1867    /// Indexed construction variant of
1868    /// [`from_addr_or_create`](Self::from_addr_or_create).
1869    pub fn from_addr_or_create_indexed<'ctx>(
1870        ctx: &'ctx mut Context<'str>,
1871        addresses: &mut crate::address_index::AddressIndex,
1872        address: u64,
1873    ) -> FunctionMutRef<'str, 'ctx> {
1874        match addresses.function_at(address) {
1875            Some(id) => Self::from_id_mut(ctx, id),
1876            None => Self::make_at_addr_indexed(ctx, addresses, address, None),
1877        }
1878    }
1879}
1880
1881impl<'s, 'ctx: 's, 'str: 'ctx, R> FunctionRef<'str, 'ctx, R>
1882where
1883    R: QCodeView<'ctx, 'str>,
1884{
1885    fn inner(&'s self) -> &'ctx FunctionBody<'str> {
1886        self.view.function(self.id)
1887    }
1888
1889    /// This function's published interface (never checked out; always read from
1890    /// the shared registry).
1891    fn interface(&'s self) -> &'ctx FunctionInterface<'str> {
1892        self.view.interface(self.id)
1893    }
1894
1895    fn size(&self) -> usize {
1896        0
1897    }
1898
1899    /// The function interface's entry address.
1900    pub fn address(&'s self) -> Option<u64> {
1901        self.interface().address
1902    }
1903
1904    /// Whether the function interface marks this function external.
1905    pub fn is_external(&'s self) -> bool {
1906        self.interface().is_external
1907    }
1908
1909    /// The ordinal this import was brought in at, for a PE import resolved from
1910    /// an ordinal-only entry. `None` for named imports and local functions.
1911    pub fn import_ordinal(&'s self) -> Option<u16> {
1912        self.interface().import_ordinal
1913    }
1914
1915    /// A reference to the function interface's signature, if any.
1916    pub fn signature(&'s self) -> Option<&'ctx FunctionSignature> {
1917        self.interface().signature.as_ref()
1918    }
1919
1920    /// This function's instructions that use `value` as an operand. See
1921    /// [`FunctionBody::users_of`]; this is the function-scoped read every pass wants
1922    /// for an SSA value (all its users are intra-function).
1923    pub fn users_of(&'s self, value: ValueId) -> Vec<InstructionId> {
1924        let func = self.id;
1925        if value.owning_function().is_some_and(|owner| owner != func) {
1926            return Vec::new();
1927        }
1928        self.inner().users_of(value)
1929    }
1930
1931    /// This function's users of `value` in their stored, body-local form.
1932    ///
1933    /// Borrowed rather than built: a pass that reads the list once per
1934    /// instruction should not allocate one per instruction to do it. Qualify
1935    /// with this function's id when a whole [`InstructionId`] is needed.
1936    pub fn local_users_of(&'s self, value: ValueId) -> &'ctx [LocalInsnId] {
1937        let func = self.id;
1938        if value.owning_function().is_some_and(|owner| owner != func) {
1939            return &[];
1940        }
1941        self.inner().local_users_of(value)
1942    }
1943
1944    /// Whether this function uses `value` at all, without building the user
1945    /// list to ask. See [`FunctionBody::has_users`].
1946    pub fn has_users(&'s self, value: ValueId) -> bool {
1947        let func = self.id;
1948        if value.owning_function().is_some_and(|owner| owner != func) {
1949            return false;
1950        }
1951        self.inner().has_users(value)
1952    }
1953
1954    /// Iterate this function's recorded `(value, users)` reverse-use entries
1955    /// (see [`FunctionBody::user_map_entries`]).
1956    pub fn user_map_entries(&'s self) -> impl Iterator<Item = (ValueId, Vec<InstructionId>)> + 's {
1957        let func = self.id;
1958        self.inner().user_map_entries().map(move |(v, u)| {
1959            (
1960                v.qualify(func),
1961                u.iter()
1962                    .map(|&local| InstructionId::new(func, local))
1963                    .collect(),
1964            )
1965        })
1966    }
1967
1968    /// Resolve a block/instruction/param/Temp `name` within this function's local name
1969    /// table (see `FunctionBody::names`). `None` if this function has no such name.
1970    pub fn local_named(&'s self, name: &str) -> Option<ValueId> {
1971        self.inner().names.get(name).map(|id| id.qualify(self.id))
1972    }
1973
1974    /// The inferred pointer attributes for positional argument `index`, or `None`
1975    /// when this function has no analyzed attributes (treat conservatively: the
1976    /// argument escapes and may be written through). See
1977    /// [`FunctionSignature::param_attrs`].
1978    pub fn param_attr(&'s self, index: usize) -> Option<ParamAttrs> {
1979        self.interface().param_attr(index)
1980    }
1981
1982    /// The full per-parameter attribute vector, if analyzed.
1983    pub fn param_attrs(&'s self) -> Option<&'ctx [ParamAttrs]> {
1984        self.interface()
1985            .signature
1986            .as_ref()
1987            .and_then(|s| s.param_attrs.as_deref())
1988    }
1989
1990    /// The non-register memory spaces this function may (transitively) write, as
1991    /// set by analysis. `Some(spaces)` is exact (a space not listed is never
1992    /// written); `None` conflates "unstamped" and "stamped unbounded" — both are
1993    /// treated conservatively (may write any space) by consumers. For the
1994    /// tri-state distinction use [`written_spaces_state`](Self::written_spaces_state).
1995    /// See `FunctionSignature::written_spaces`.
1996    pub fn written_spaces(&'s self) -> Option<&'ctx [crate::space::SpaceId]> {
1997        match &self.interface().effects.memory.coarse {
1998            WrittenSpacesState::Bounded(spaces) => Some(spaces),
1999            _ => None,
2000        }
2001    }
2002
2003    /// The tri-state `written_spaces` verdict, distinguishing a never-stamped
2004    /// fresh mint ([`WrittenSpaces::Unstamped`]) from a deliberately recorded
2005    /// ⊤ ([`WrittenSpaces::Unbounded`]). See `FunctionSignature::written_spaces`.
2006    pub fn written_spaces_state(&'s self) -> WrittenSpaces<'ctx> {
2007        match &self.interface().effects.memory.coarse {
2008            WrittenSpacesState::Unstamped => WrittenSpaces::Unstamped,
2009            WrittenSpacesState::Unbounded => WrittenSpaces::Unbounded,
2010            WrittenSpacesState::Bounded(spaces) => WrittenSpaces::Bounded(spaces),
2011        }
2012    }
2013
2014    /// Whether this function's register interface has been materialized (argpromote
2015    /// v2) — i.e. its [`effects`](FunctionInterface::effects) are
2016    /// [`RegisterChannelState::Materialized`]. Legacy name for the register-channel
2017    /// "functionalized" predicate.
2018    pub fn is_reg_materialized(&'s self) -> bool {
2019        matches!(
2020            self.interface().effects.register,
2021            RegisterChannelState::Materialized(_)
2022        )
2023    }
2024
2025    /// This function's call-graph-closed register [`FunctionEffects`] summary.
2026    /// [`RegisterChannelState::Unsolved`] until the effect-analysis pass runs (and
2027    /// after a snapshot load). See [`FunctionInterface::effects`].
2028    pub fn effects(&'s self) -> &'ctx FunctionEffects {
2029        &self.interface().effects
2030    }
2031
2032    /// Whether argpromote has functionalized *every* side-effect channel of this
2033    /// function — it is a deterministic pure function of its by-value params,
2034    /// touching no caller-visible memory or registers. Strictly stronger than
2035    /// [`is_reg_materialized`](Self::is_reg_materialized). See [`FunctionSignature::is_pure`].
2036    pub fn is_pure(&'s self) -> bool {
2037        self.interface()
2038            .signature
2039            .as_ref()
2040            .is_some_and(|s| s.is_pure)
2041    }
2042
2043    /// Whether this is a pure value-level lambda rather than a machine function.
2044    pub fn is_lambda(&'s self) -> bool {
2045        self.interface().kind == FunctionKind::Lambda
2046    }
2047
2048    pub fn kind(&'s self) -> FunctionKind {
2049        self.interface().kind
2050    }
2051
2052    /// The C-prototype-derived external call interface, if `external_sigs`
2053    /// planned one. Read by `argpromote_external` to rewrite call sites. See
2054    /// [`FunctionSignature::extern_interface`].
2055    pub fn extern_interface(&'s self) -> Option<&'ctx crate::value::ExternInterface> {
2056        self.interface()
2057            .signature
2058            .as_ref()
2059            .and_then(|s| s.extern_interface.as_ref())
2060    }
2061
2062    /// The C-prototype-derived argmem summary for a prototyped external, or `None`
2063    /// when this function is not a prototyped external. See
2064    /// [`FunctionSignature::argmem`].
2065    pub fn argmem(&'s self) -> Option<&'ctx crate::value::ExternArgmem> {
2066        self.interface()
2067            .signature
2068            .as_ref()
2069            .and_then(|s| s.argmem.as_ref())
2070    }
2071
2072    /// The display name for the call-site argument bound to input `index`: the
2073    /// name of the callee's root block param at `index`, or — for a bodyless
2074    /// external with no root block — the C-prototype argument name recorded in
2075    /// its [`extern_interface`](Self::extern_interface). `None` when there is no
2076    /// input at `index` or it is unnamed.
2077    pub fn input_arg_name(&'s self, index: usize) -> Option<String> {
2078        // The root block param at `index` is the interface element a call
2079        // argument actually binds to, named after its register by
2080        // `argpromote_registers` or `stack_<addr>` by mem2reg's
2081        // `block_param_name_for_var`. Prefer it: it is the source of truth and is
2082        // populated even for `pure_reg` functions.
2083        if let Some(root) = self.root()
2084            && let Some(name) = root
2085                .params()
2086                .nth(index)
2087                .and_then(|p| p.name().map(str::to_owned))
2088        {
2089            return Some(name);
2090        }
2091
2092        // Fall back to the C-prototype-derived external call interface, the
2093        // source of truth for bodyless externals which have no root block.
2094        self.extern_interface()
2095            .and_then(|iface| iface.args.get(index))
2096            .and_then(|a| a.name.as_ref().map(|n| n.to_string()))
2097    }
2098
2099    /// Whether this function performs an unresolved/dynamic stack read (or
2100    /// forwards a stack pointer into one). See
2101    /// [`FunctionSignature::reads_unbounded_stack`].
2102    pub fn reads_unbounded_stack(&'s self) -> bool {
2103        self.interface()
2104            .signature
2105            .as_ref()
2106            .is_some_and(|s| s.reads_unbounded_stack)
2107    }
2108
2109    /// Whether this function hands a pointer into its own frame to a callee that
2110    /// may read it unboundedly. See
2111    /// [`FunctionSignature::frame_escapes_to_unbounded`].
2112    pub fn frame_escapes_to_unbounded(&'s self) -> bool {
2113        self.interface()
2114            .signature
2115            .as_ref()
2116            .is_some_and(|s| s.frame_escapes_to_unbounded)
2117    }
2118
2119    /// The function interface's name.
2120    pub fn name(&'s self) -> &'ctx str {
2121        self.interface().name.as_ref()
2122    }
2123
2124    /// The addresses of every machine instruction lifted into this function, in
2125    /// ascending order. Unlike [`blocks`](Self::blocks), this is stable across
2126    /// optimization, so it drives the raw disassembly view.
2127    pub fn instruction_addrs(&'s self) -> impl Iterator<Item = u64> + 'ctx {
2128        self.inner().instruction_addrs.iter().copied()
2129    }
2130
2131    /// Whether this function contains at least one [`Map`](Mnemonic::Map)
2132    /// instruction — a lane-wise array map operation. Surfaced as an advanced
2133    /// filter in the function list.
2134    pub fn has_map(&'s self) -> bool {
2135        self.blocks().any(|block| {
2136            block
2137                .instructions()
2138                .any(|insn| matches!(insn.mnemonic(), Mnemonic::Map(_)))
2139        })
2140    }
2141
2142    /// Whether this function contains at least one [`Scan`](Mnemonic::Scan)
2143    /// instruction — a lane-wise prefix-fold array operation. Surfaced as an
2144    /// advanced filter in the function list, alongside [`has_map`](Self::has_map).
2145    pub fn has_scan(&'s self) -> bool {
2146        self.blocks().any(|block| {
2147            block
2148                .instructions()
2149                .any(|insn| matches!(insn.mnemonic(), Mnemonic::Scan(_)))
2150        })
2151    }
2152
2153    /// The root block of this function, if it exists.
2154    pub fn root(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
2155        self.inner()
2156            .root
2157            .map(|local| BlockRef::new(self.view, BlockId::new(self.id, local)))
2158    }
2159
2160    /// An iterator over the (live) blocks belonging to this function.
2161    pub fn blocks(&'s self) -> impl Iterator<Item = BlockRef<'str, 'ctx, R>> + 's {
2162        let view = self.view;
2163        let mut ids = self.block_ids();
2164        // Total order: primarily by machine address, but break ties by the
2165        // function-local index. Address-less blocks (e.g. fallthrough splits,
2166        // whose `address()` is `None`) must still order deterministically.
2167        ids.sort_by_key(|&id| (BlockRef::new(view, id).address(), id.local));
2168        ids.into_iter().map(move |id| BlockRef::new(view, id))
2169    }
2170
2171    /// The composite ids of this function's live blocks, in roster order.
2172    pub fn block_ids(&'s self) -> Vec<BlockId> {
2173        let func = self.id;
2174        self.inner()
2175            .roster
2176            .iter()
2177            .copied()
2178            .map(|local| BlockId::new(func, local))
2179            .collect()
2180    }
2181
2182    /// The composite IDs of this function's live instructions, in dense physical
2183    /// order — including any currently detached (`parent == None`).
2184    pub fn instruction_ids(&'s self) -> Vec<InstructionId> {
2185        let func = self.id;
2186        self.inner()
2187            .insns
2188            .iter()
2189            .map(|i| InstructionId::new(func, i.id))
2190            .collect()
2191    }
2192
2193    /// The IDs of every live CFG edge in this function's edge arena, in dense
2194    /// physical order.
2195    pub fn edge_ids(&'s self) -> Vec<crate::value::block::EdgeId> {
2196        self.inner().edges.iter().map(|e| e.id).collect()
2197    }
2198
2199    /// Iterates over the (live) blocks in this function in arena order (i.e. not
2200    /// sorted by address, unlike [`blocks`](Self::blocks)).
2201    pub fn iter(&'s self) -> BlockIter<'str, 'ctx, R> {
2202        BlockIter {
2203            view: self.view,
2204            inner: self.block_ids().into_iter(),
2205            marker: PhantomData,
2206        }
2207    }
2208
2209    fn fmt(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
2210        if self.is_external() {
2211            return writeln!(f, "extern fn {};", self.name());
2212        }
2213        let keyword = match self.kind() {
2214            FunctionKind::Machine => "fn",
2215            FunctionKind::Lambda => "lambda",
2216        };
2217        writeln!(f, "{keyword} {}:", self.name())?;
2218        for block in self.blocks() {
2219            block.fmt(f)?;
2220        }
2221        Ok(())
2222    }
2223}
2224
2225#[derive(Clone, Copy)]
2226pub struct FunctionRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
2227    pub id: FunctionId,
2228    pub(in crate::value) view: R,
2229    marker: PhantomData<&'ctx &'str ()>,
2230}
2231
2232impl<'str, 'ctx, R> FunctionRef<'str, 'ctx, R> {
2233    pub fn new(view: R, id: FunctionId) -> Self {
2234        Self {
2235            id,
2236            view,
2237            marker: PhantomData,
2238        }
2239    }
2240
2241    pub fn id(&self) -> ValueId {
2242        self.id.into()
2243    }
2244}
2245
2246impl<'str, 'ctx> FunctionRef<'str, 'ctx> {
2247    pub fn from_id(ctx: &'ctx Context<'str>, id: FunctionId) -> Self {
2248        Self::new(ModuleView::new(ctx), id)
2249    }
2250}
2251
2252impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 'ctx, 'str> for FunctionRef<'str, 'ctx> {
2253    fn ctx(&'s self) -> &'ctx Context<'str> {
2254        // Module-scope-only escape hatch: shared-only reads go through
2255        // `host().shr()`; only whole-module walks (callees/callers) reach here,
2256        // and those panic on a checked-out host by design (context-split Pin B).
2257        self.view.context()
2258    }
2259}
2260
2261impl<'str: 'ctx, 'ctx, R> Named for FunctionRef<'str, 'ctx, R>
2262where
2263    R: QCodeView<'ctx, 'str>,
2264{
2265    fn name(&self) -> Option<&str> {
2266        Some(self.view.interface(self.id).name.as_ref())
2267    }
2268}
2269
2270impl<'str: 'ctx, 'ctx, R> Display for FunctionRef<'str, 'ctx, R>
2271where
2272    R: QCodeView<'ctx, 'str>,
2273{
2274    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2275        FunctionRef::fmt(self, f)
2276    }
2277}
2278
2279impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for FunctionRef<'str, 'ctx, R>
2280where
2281    R: QCodeView<'ctx, 'str>,
2282{
2283    fn id(&self) -> ValueId {
2284        self.id()
2285    }
2286
2287    fn size(&self) -> usize {
2288        FunctionRef::size(self)
2289    }
2290}
2291
2292pub struct BlockIter<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
2293    view: R,
2294    inner: std::vec::IntoIter<BlockId>,
2295    marker: PhantomData<&'ctx &'str ()>,
2296}
2297
2298impl<'str: 'ctx, 'ctx, R> Iterator for BlockIter<'str, 'ctx, R>
2299where
2300    R: QCodeView<'ctx, 'str>,
2301{
2302    type Item = BlockRef<'str, 'ctx, R>;
2303
2304    fn next(&mut self) -> Option<Self::Item> {
2305        self.inner.next().map(|id| BlockRef::new(self.view, id))
2306    }
2307}
2308
2309impl<'str: 'ctx, 'ctx, R> IntoIterator for &FunctionRef<'str, 'ctx, R>
2310where
2311    R: QCodeView<'ctx, 'str>,
2312{
2313    type Item = BlockRef<'str, 'ctx, R>;
2314    type IntoIter = BlockIter<'str, 'ctx, R>;
2315
2316    fn into_iter(self) -> Self::IntoIter {
2317        self.iter()
2318    }
2319}
2320
2321pub type FunctionMutRef<'str, 'ctx> = BaseRef<&'ctx mut Context<'str>, FunctionId>;
2322
2323impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 's, 'str> for FunctionMutRef<'str, 'ctx> {
2324    fn ctx(&'s self) -> &'s Context<'str> {
2325        self.ctx
2326    }
2327}
2328
2329impl<'s, 'ctx: 's, 'str: 'ctx> WithCtxMut<'s, 'str> for FunctionMutRef<'str, 'ctx> {
2330    fn ctx_mut(&'s mut self) -> &'s mut Context<'str> {
2331        self.ctx
2332    }
2333}
2334
2335impl Display for FunctionMutRef<'_, '_> {
2336    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2337        self.as_ref().fmt(f)
2338    }
2339}
2340
2341impl<'ctx, 'str> Value<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
2342    fn id(&self) -> ValueId {
2343        self.id()
2344    }
2345
2346    fn size(&self) -> usize {
2347        self.as_ref().size()
2348    }
2349}
2350
2351impl Named for FunctionMutRef<'_, '_> {
2352    fn name(&self) -> Option<&str> {
2353        Some(self.ctx.interfaces[self.id].name.as_ref())
2354    }
2355}
2356
2357impl<'str, 'ctx> Renameable<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
2358    fn rename(&mut self, name: Cow<'str, str>) -> Result<()> {
2359        let id = self.id();
2360        let old_name = self.ctx.interfaces[self.id].name.as_ref().to_owned();
2361        update_context_name(id, self.ctx, name.clone(), Some(old_name.as_ref()))?;
2362        self.ctx.interfaces[self.id].name = name;
2363        Ok(())
2364    }
2365}
2366
2367impl<'str, 'ctx> FunctionMutRef<'str, 'ctx> {
2368    pub fn as_ref(&self) -> FunctionRef<'str, '_> {
2369        FunctionRef::new(ModuleView::new(self.ctx), self.id)
2370    }
2371
2372    fn inner(&self) -> &FunctionBody<'str> {
2373        self.ctx.function(self.id)
2374    }
2375
2376    fn interface(&self) -> &FunctionInterface<'str> {
2377        &self.ctx.interfaces[self.id]
2378    }
2379
2380    fn address(&self) -> Option<u64> {
2381        self.interface().address
2382    }
2383
2384    pub fn name(&self) -> &str {
2385        self.interface().name.as_ref()
2386    }
2387
2388    pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'str, '_>> {
2389        self.as_ref().blocks().collect::<Vec<_>>().into_iter()
2390    }
2391
2392    pub fn root(&self) -> Option<BlockRef<'str, '_>> {
2393        self.as_ref().root()
2394    }
2395
2396    pub(crate) fn inner_mut(&mut self) -> &mut FunctionBody<'str> {
2397        &mut self.ctx.bodies[self.id]
2398    }
2399
2400    /// This function's published interface (mutable). Interface writes are
2401    /// module-scope only; this is the write path for the setters below.
2402    pub(crate) fn interface_mut(&mut self) -> &mut FunctionInterface<'str> {
2403        &mut self.ctx.interfaces[self.id]
2404    }
2405
2406    fn set_address(&mut self, address: u64) -> Result<()> {
2407        let mut addresses = crate::address_index::AddressIndex::analyze(&*self.ctx);
2408        self.set_address_indexed(&mut addresses, address)
2409    }
2410
2411    fn set_address_indexed(
2412        &mut self,
2413        addresses: &mut crate::address_index::AddressIndex,
2414        address: u64,
2415    ) -> Result<()> {
2416        let old_address = self.interface().address;
2417        self.interface_mut().address = Some(address);
2418        if let Err(error) = self
2419            .ctx
2420            .set_address_indexed(addresses, address, self.id.into())
2421        {
2422            self.interface_mut().address = old_address;
2423            return Err(error);
2424        }
2425        Ok(())
2426    }
2427
2428    fn with_address_indexed(
2429        mut self,
2430        addresses: &mut crate::address_index::AddressIndex,
2431        address: u64,
2432    ) -> Result<Self> {
2433        self.set_address_indexed(addresses, address)?;
2434        Ok(self)
2435    }
2436
2437    /// Sets a block as the root of this function.
2438    /// This will also add the block to the function's block list if it's not already present.
2439    /// This will also set the address of the function/block to the address of the root block/function if both addresses are unset.
2440    /// Panics if the function already has an address that doesn't match the root block's address.
2441    pub fn set_root(&mut self, id: BlockId) -> Result<()> {
2442        assert_eq!(
2443            id.func, self.id,
2444            "cannot root a function at a block stored in another function arena"
2445        );
2446        self.add_block(id);
2447        self.inner_mut().root = Some(id.localize(self.id));
2448
2449        let block_addr = BasicBlock::from_id(&*self.ctx, id).address();
2450        let self_addr = self.address();
2451
2452        match (self_addr, block_addr) {
2453            (Some(fn_addr), Some(block_addr)) if fn_addr != block_addr => {
2454                return Err(Error::spanless(ErrorTy::FunctionRootAddressMismatch {
2455                    fn_addr,
2456                    block_addr,
2457                }));
2458            }
2459            (None, Some(addr)) => {
2460                self.set_address(addr)
2461                    .expect("This address should be valid");
2462            }
2463            (Some(addr), None) => {
2464                BasicBlock::from_id_mut(self.ctx, id)
2465                    .set_address(addr)
2466                    .expect("This address should be valid");
2467            }
2468            _ => {}
2469        }
2470        Ok(())
2471    }
2472
2473    pub fn make_root(&mut self) -> BlockRef<'str, '_> {
2474        let func = self.id;
2475        let root = BasicBlock::make(self.ctx, func).id;
2476        self.set_root(root).expect("We just created the block");
2477        BasicBlock::from_id(&*self.ctx, root)
2478    }
2479
2480    pub fn ensure_root(&mut self, id: BlockId) -> Result<()> {
2481        assert_eq!(
2482            id.func, self.id,
2483            "cannot ensure a function root from another function arena"
2484        );
2485        if let Some(root) = self.inner().root {
2486            if root != id.localize(self.id) {
2487                return Err(Error::spanless(ErrorTy::FunctionRootMismatch {
2488                    expected: BlockId::new(self.id, root),
2489                    actual: id,
2490                }));
2491            }
2492            Ok(())
2493        } else {
2494            self.set_root(id)
2495        }
2496    }
2497
2498    pub fn set_external(&mut self, is_external: bool) {
2499        self.interface_mut().is_external = is_external;
2500        assert!(
2501            self.inner().blocks.is_empty(),
2502            "External functions should not have blocks"
2503        );
2504    }
2505
2506    /// Record the ordinal a by-ordinal PE import was brought in at. Set by the
2507    /// `resolve_ordinals` pass before it renames the stub, so the by-ordinal
2508    /// origin survives the rename.
2509    pub fn set_import_ordinal(&mut self, ordinal: Option<u16>) {
2510        self.interface_mut().import_ordinal = ordinal;
2511    }
2512
2513    pub fn set_kind(&mut self, kind: FunctionKind) {
2514        self.interface_mut().kind = kind;
2515        if kind == FunctionKind::Lambda {
2516            self.set_is_pure(true);
2517            self.set_register_effects(RegisterChannelState::Materialized(
2518                RegisterInterfaceMap::default(),
2519            ));
2520        }
2521    }
2522
2523    pub fn set_signature(&mut self, sig: FunctionSignature) {
2524        self.ctx.interfaces[self.id].signature = Some(sig);
2525    }
2526
2527    /// Records the inferred per-parameter pointer attributes on this function.
2528    /// See [`FunctionSignature::param_attrs`].
2529    pub fn set_param_attrs(&mut self, attrs: Vec<ParamAttrs>) {
2530        self.interface_mut()
2531            .signature
2532            .get_or_insert_default()
2533            .param_attrs = Some(attrs);
2534    }
2535
2536    /// Drops any inferred per-parameter attributes (e.g. after a signature
2537    /// rewrite changed the parameter list, invalidating the index alignment).
2538    pub fn clear_param_attrs(&mut self) {
2539        if let Some(sig) = self.interface_mut().signature.as_mut() {
2540            sig.param_attrs = None;
2541        }
2542    }
2543
2544    /// Records the analysis-computed set of non-register spaces this function may
2545    /// write. This is always a deliberate stamp: `Some(spaces)` is a bounded
2546    /// witnessed set, `None` records *stamped unbounded* (⊤) — never clears the
2547    /// stamp back to unstamped. See `FunctionSignature::written_spaces` and
2548    /// [`FunctionRef::written_spaces_state`].
2549    pub fn set_written_spaces(&mut self, spaces: Option<Vec<crate::space::SpaceId>>) {
2550        let coarse = match spaces {
2551            Some(spaces) => WrittenSpacesState::Bounded(spaces),
2552            None => WrittenSpacesState::Unbounded,
2553        };
2554        // Coarse-only setter: every other component of the channel is left
2555        // exactly as it was.
2556        let precise = self.interface_mut().effects.memory.precise.take();
2557        self.set_memory_solved(coarse, precise);
2558    }
2559
2560    /// Records the C-prototype-derived external call interface on this function.
2561    /// See [`FunctionSignature::extern_interface`]; set by `external_sigs`,
2562    /// consumed by `argpromote_external`.
2563    pub fn set_extern_interface(&mut self, iface: crate::value::ExternInterface) {
2564        self.interface_mut()
2565            .signature
2566            .get_or_insert_default()
2567            .extern_interface = Some(iface);
2568    }
2569
2570    /// Records the C-prototype-derived argmem summary on this external. See
2571    /// [`FunctionSignature::argmem`]; set by `external_sigs`, read by the RAM
2572    /// effect channel's `external_leaf`.
2573    pub fn set_argmem(&mut self, argmem: crate::value::ExternArgmem) {
2574        self.interface_mut()
2575            .signature
2576            .get_or_insert_default()
2577            .argmem = Some(argmem);
2578    }
2579
2580    /// Records this function's register-channel effect state, preserving the
2581    /// memory channel (read-modify-write). See [`FunctionInterface::effects`].
2582    pub fn set_register_effects(&mut self, register: RegisterChannelState) {
2583        self.interface_mut().effects.register = register;
2584    }
2585
2586    /// Records this function's memory-channel effect state, preserving the
2587    /// register channel (read-modify-write). See [`FunctionInterface::effects`].
2588    ///
2589    /// Replaces **every** component of the memory channel. The channel has two
2590    /// independent writers — the effect solve owns `coarse`/`precise`, the RAM
2591    /// channel's rewrite owns `materialized` — so a caller that computes only
2592    /// one writer's components must not build a whole state and pass it here:
2593    /// the other writer's field would be silently lost. Use
2594    /// [`set_memory_solved`](Self::set_memory_solved) or
2595    /// [`set_memory_interface`](Self::set_memory_interface) instead.
2596    pub fn set_memory_effects(&mut self, memory: MemoryChannelState) {
2597        self.interface_mut().effects.memory = memory;
2598    }
2599
2600    /// Records the *solved* components of the memory channel — the coarse
2601    /// written-space verdict and the precise footprint the same solve derived —
2602    /// leaving the materialized interface untouched.
2603    ///
2604    /// The effect solve does not compute the interface, so it must not clear it.
2605    pub fn set_memory_solved(&mut self, coarse: WrittenSpacesState, precise: Option<Footprint>) {
2606        let memory = &mut self.interface_mut().effects.memory;
2607        memory.coarse = coarse;
2608        memory.precise = precise;
2609    }
2610
2611    /// Records the materialized memory interface, leaving the solved components
2612    /// untouched. `None` marks the memory channel as not materialized.
2613    pub fn set_memory_interface(&mut self, materialized: Option<MemoryInterfaceMap>) {
2614        self.interface_mut().effects.memory.materialized = materialized;
2615    }
2616
2617    /// Marks this function as fully functionalized over *every* side-effect
2618    /// channel — a deterministic pure function of its params. See
2619    /// [`FunctionSignature::is_pure`].
2620    pub fn set_is_pure(&mut self, value: bool) {
2621        self.interface_mut()
2622            .signature
2623            .get_or_insert_default()
2624            .is_pure = value;
2625    }
2626
2627    /// Records whether this function performs an unresolved/dynamic stack read.
2628    /// See [`FunctionSignature::reads_unbounded_stack`].
2629    pub fn set_reads_unbounded_stack(&mut self, value: bool) {
2630        self.interface_mut()
2631            .signature
2632            .get_or_insert_default()
2633            .reads_unbounded_stack = value;
2634    }
2635
2636    /// Records whether this function hands a pointer into its own frame to a
2637    /// callee that may read it unboundedly. See
2638    /// [`FunctionSignature::frame_escapes_to_unbounded`].
2639    pub fn set_frame_escapes_to_unbounded(&mut self, value: bool) {
2640        self.interface_mut()
2641            .signature
2642            .get_or_insert_default()
2643            .frame_escapes_to_unbounded = value;
2644    }
2645
2646    /// Records the address of a machine instruction lifted into this function.
2647    pub fn add_instruction_addr(&mut self, addr: u64) {
2648        self.inner_mut().instruction_addrs.insert(addr);
2649    }
2650
2651    /// Associates `block` with `function` by setting the block's `parent` field.
2652    ///
2653    /// With per-function block arenas, membership *is* arena ownership: a block
2654    /// lives in the arena of the function it was born into (`id.func`), and that
2655    /// must equal `self.id`. Ownership is derived from the arena, so this only
2656    /// ensures the roster lists the block; it no longer moves storage between
2657    /// functions.
2658    pub fn add_block(&mut self, id: BlockId) {
2659        assert_eq!(
2660            id.func, self.id,
2661            "cannot add a block stored in another function arena"
2662        );
2663        let local = id.localize(self.id);
2664        // Ensure the roster lists it exactly once (a freshly `make`d block is
2665        // auto-rostered, so this is usually a no-op).
2666        if !self.inner().roster.contains(&local) {
2667            self.inner_mut().roster.push(local);
2668        }
2669    }
2670}
2671
2672#[cfg(test)]
2673mod tests {
2674    use wazabin_qcode_macro::qcode;
2675
2676    use super::*;
2677
2678    fn foreign_block_fixture() -> (Context<'static>, FunctionId, BlockId) {
2679        let mut ctx = Context::new();
2680        let owner = FunctionBody::make(&mut ctx, "block_owner".into())
2681            .unwrap()
2682            .id;
2683        let destination = FunctionBody::make(&mut ctx, "block_destination".into())
2684            .unwrap()
2685            .id;
2686        let block = BasicBlock::make(&mut ctx, owner).id;
2687        (ctx, destination, block)
2688    }
2689
2690    #[test]
2691    fn raw_root_and_roster_are_local_while_refs_qualify_per_function() {
2692        let mut ctx = Context::new();
2693        let a = FunctionBody::make(&mut ctx, "local_root_a".into())
2694            .unwrap()
2695            .id;
2696        let b = FunctionBody::make(&mut ctx, "local_root_b".into())
2697            .unwrap()
2698            .id;
2699        let a_root = BasicBlock::make(&mut ctx, a).id;
2700        let b_root = BasicBlock::make(&mut ctx, b).id;
2701        assert_eq!(a_root.local, b_root.local, "arena-local ids should collide");
2702        FunctionBody::from_id_mut(&mut ctx, a)
2703            .set_root(a_root)
2704            .unwrap();
2705        FunctionBody::from_id_mut(&mut ctx, b)
2706            .set_root(b_root)
2707            .unwrap();
2708
2709        assert_eq!(ctx.bodies[a].root_id(), Some(a_root.local));
2710        assert_eq!(ctx.bodies[b].root_id(), Some(b_root.local));
2711        assert_eq!(ctx.bodies[a].roster, vec![a_root.local]);
2712        assert_eq!(ctx.bodies[b].roster, vec![b_root.local]);
2713        assert_eq!(
2714            FunctionBody::from_id(&ctx, a).root().map(|root| root.id),
2715            Some(a_root)
2716        );
2717        assert_eq!(
2718            FunctionBody::from_id(&ctx, b).root().map(|root| root.id),
2719            Some(b_root)
2720        );
2721    }
2722
2723    #[test]
2724    #[should_panic(expected = "cannot add a block stored in another function arena")]
2725    fn add_block_rejects_foreign_storage() {
2726        let (mut ctx, destination, block) = foreign_block_fixture();
2727        FunctionBody::from_id_mut(&mut ctx, destination).add_block(block);
2728    }
2729
2730    #[test]
2731    #[should_panic(expected = "cannot root a function at a block stored in another function arena")]
2732    fn set_root_rejects_foreign_storage() {
2733        let (mut ctx, destination, block) = foreign_block_fixture();
2734        FunctionBody::from_id_mut(&mut ctx, destination)
2735            .set_root(block)
2736            .unwrap();
2737    }
2738
2739    #[test]
2740    #[should_panic(expected = "cannot ensure a function root from another function arena")]
2741    fn ensure_root_rejects_foreign_storage() {
2742        let (mut ctx, destination, block) = foreign_block_fixture();
2743        FunctionBody::from_id_mut(&mut ctx, destination)
2744            .ensure_root(block)
2745            .unwrap();
2746    }
2747
2748    #[test]
2749    fn function_ref_users_of_rejects_foreign_owned_values() {
2750        let mut ctx = Context::new();
2751        qcode!(
2752            ctx,
2753            "
2754            fn users_a:
2755                <a_entry>
2756                    %a_def = i64 1 + i64 2;
2757                    %a_user = %a_def + i64 3;
2758                    return at %a_user;
2759
2760            fn users_b:
2761                <b_entry>
2762                    %b_def = i64 1 + i64 2;
2763                    %b_user = %b_def + i64 3;
2764                    return at %b_user;
2765            "
2766        );
2767
2768        let a_ids = FunctionRef::from_id(&ctx, users_a)
2769            .root()
2770            .unwrap()
2771            .instruction_ids();
2772        let a_def = ValueId::Instruction(a_ids[0]);
2773        assert_eq!(
2774            FunctionRef::from_id(&ctx, users_a).users_of(a_def),
2775            vec![a_ids[1]]
2776        );
2777        assert!(
2778            FunctionRef::from_id(&ctx, users_b)
2779                .users_of(a_def)
2780                .is_empty()
2781        );
2782
2783        let one = ctx.get_const(1, 8).id();
2784        assert!(!FunctionRef::from_id(&ctx, users_b).users_of(one).is_empty());
2785    }
2786
2787    fn colliding_body_ids() -> (
2788        Context<'static>,
2789        FunctionId,
2790        FunctionId,
2791        BlockId,
2792        BlockId,
2793        InstructionId,
2794        InstructionId,
2795        BlockParamId,
2796        BlockParamId,
2797    ) {
2798        let mut ctx = Context::new();
2799        qcode!(
2800            ctx,
2801            "
2802            fn raw_a:
2803                <a_entry @a:i64>
2804                    %a_def = i64 1 + i64 2;
2805                    return at %a_def;
2806            fn raw_b:
2807                <b_entry @b:i64>
2808                    %b_def = i64 1 + i64 2;
2809                    return at %b_def;
2810            "
2811        );
2812        let a_root = FunctionRef::from_id(&ctx, raw_a).root().unwrap();
2813        let b_root = FunctionRef::from_id(&ctx, raw_b).root().unwrap();
2814        let a_block = a_root.id;
2815        let b_block = b_root.id;
2816        let a_insn = a_root.instruction_ids()[0];
2817        let b_insn = b_root.instruction_ids()[0];
2818        let a_param = a_root.params().next().unwrap().id;
2819        let b_param = b_root.params().next().unwrap().id;
2820        assert_eq!(a_block.local, b_block.local);
2821        assert_eq!(a_insn.local, b_insn.local);
2822        assert_eq!(a_param.local, b_param.local);
2823        (
2824            ctx, raw_a, raw_b, a_block, b_block, a_insn, b_insn, a_param, b_param,
2825        )
2826    }
2827
2828    /// `replace_instruction(id, id)` must be a no-op: forwarding uses to itself
2829    /// does nothing, so deleting `id` would strand its still-live users. A pass
2830    /// that resolves an instruction to itself must leave it in place.
2831    #[test]
2832    fn replace_instruction_with_itself_is_a_noop() {
2833        let mut ctx = Context::new();
2834        qcode!(
2835            ctx,
2836            "
2837            fn f:
2838            <entry @a:i32>
2839                %x = @a + 1;
2840                %y = %x + 2;
2841                return %y;
2842            "
2843        );
2844        // `%x` is used by `%y`; find both.
2845        let root = FunctionBody::from_id(&ctx, f).root().unwrap().id;
2846        let insns: Vec<InstructionId> = BasicBlock::from_id(&ctx, root)
2847            .instruction_ids()
2848            .into_iter()
2849            .collect();
2850        let x = insns[0];
2851        let users_before = ctx.bodies[f].users_of(ValueId::Instruction(x));
2852        assert!(!users_before.is_empty(), "x should have a user (%y)");
2853
2854        // Replace x with itself — must not delete x or disturb its users.
2855        ctx.bodies[f].replace_instruction(x, ValueId::Instruction(x));
2856
2857        assert!(
2858            ctx.bodies[f].insns.contains(x.local),
2859            "x must survive a self-replacement"
2860        );
2861        assert_eq!(
2862            ctx.bodies[f].users_of(ValueId::Instruction(x)),
2863            users_before,
2864            "x's users must be unchanged"
2865        );
2866    }
2867
2868    #[test]
2869    fn body_users_of_rejects_foreign_owned_values() {
2870        let (ctx, a, b, _, _, a_insn, _, _, _) = colliding_body_ids();
2871        assert!(
2872            ctx.bodies[b]
2873                .users_of(ValueId::Instruction(a_insn))
2874                .is_empty()
2875        );
2876        assert!(
2877            !ctx.bodies[a]
2878                .users_of(ValueId::Instruction(a_insn))
2879                .is_empty()
2880        );
2881    }
2882
2883    #[test]
2884    #[should_panic(expected = "cannot replace uses of a value owned by another function")]
2885    fn body_replace_uses_rejects_foreign_old() {
2886        let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
2887        ctx.bodies[b]
2888            .replace_all_uses_with(ValueId::Instruction(a_insn), ValueId::Instruction(b_insn));
2889    }
2890
2891    #[test]
2892    #[should_panic(expected = "cannot replace uses with a value owned by another function")]
2893    fn body_replace_uses_rejects_foreign_new() {
2894        let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
2895        ctx.bodies[b]
2896            .replace_all_uses_with(ValueId::Instruction(b_insn), ValueId::Instruction(a_insn));
2897    }
2898
2899    #[test]
2900    #[should_panic(expected = "block belongs to another function")]
2901    fn body_block_access_rejects_colliding_foreign_id() {
2902        let (ctx, _, b, a_block, _, _, _, _, _) = colliding_body_ids();
2903        let _ = ctx.bodies[b].block(a_block);
2904    }
2905
2906    #[test]
2907    #[should_panic(expected = "instruction belongs to another function")]
2908    fn body_insn_access_rejects_colliding_foreign_id() {
2909        let (ctx, _, b, _, _, a_insn, _, _, _) = colliding_body_ids();
2910        let _ = ctx.bodies[b].insn(a_insn);
2911    }
2912
2913    #[test]
2914    #[should_panic(expected = "block parameter belongs to another function")]
2915    fn body_param_access_rejects_colliding_foreign_id() {
2916        let (ctx, _, b, _, _, _, _, a_param, _) = colliding_body_ids();
2917        let _ = ctx.bodies[b].block_param(a_param);
2918    }
2919
2920    // The `push_block` foreign-parent asserts are gone (stage 2): block ownership
2921    // is derived from the storing arena, so a block pushed into body `b` is owned
2922    // by `b` by construction — a foreign parent is unrepresentable.
2923
2924    #[test]
2925    fn make_function_creates_function_with_correct_name_root_address() {
2926        let mut ctx = Context::new();
2927        let f = FunctionBody::make(&mut ctx, "main".into()).unwrap();
2928        assert_eq!(f.name(), "main");
2929    }
2930
2931    #[test]
2932    fn get_function_by_name_returns_correct_function() {
2933        let mut ctx = Context::new();
2934        let id = FunctionBody::make(&mut ctx, "foo".into()).unwrap().id();
2935        let f = FunctionBody::from_name(&ctx, "foo").unwrap();
2936        assert_eq!(f.id(), id);
2937        assert_eq!(f.name(), "foo");
2938    }
2939
2940    #[test]
2941    fn get_function_by_name_returns_none_if_not_found() {
2942        let ctx = Context::new();
2943        assert!(FunctionBody::from_name(&ctx, "nonexistent").is_none());
2944    }
2945
2946    #[test]
2947    fn get_function_by_addr_returns_correct_function() {
2948        let mut ctx = Context::new();
2949        let id = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id();
2950        let addresses = crate::address_index::AddressIndex::analyze(&ctx);
2951        let f = FunctionBody::from_id(&ctx, addresses.function_at(0x2000).unwrap());
2952        assert_eq!(f.id(), id);
2953        assert_eq!(f.address(), Some(0x2000));
2954        assert_eq!(f.name(), "fn_2000");
2955    }
2956
2957    #[test]
2958    fn get_function_by_addr_returns_none_if_missing() {
2959        let ctx = Context::new();
2960        let addresses = crate::address_index::AddressIndex::analyze(&ctx);
2961        assert!(addresses.function_at(0xdeadbeef).is_none());
2962    }
2963
2964    #[test]
2965    fn add_block_via_function_mut_ref_updates_blocks_list() {
2966        let mut ctx = Context::new();
2967        let baz_id = FunctionBody::make(&mut ctx, "baz".into()).unwrap().id;
2968        let root = BasicBlock::make(&mut ctx, baz_id).id;
2969        let extra = BasicBlock::make(&mut ctx, baz_id).id;
2970
2971        let mut baz = FunctionBody::from_id_mut(&mut ctx, baz_id);
2972        baz.add_block(root);
2973        baz.add_block(extra);
2974
2975        let block_ids: Vec<_> = baz.blocks().map(|b| b.id).collect();
2976        assert!(block_ids.contains(&root));
2977        assert!(block_ids.contains(&extra));
2978    }
2979
2980    #[test]
2981    fn display_shows_function_name_and_block_contents() {
2982        let mut ctx = Context::new();
2983        FunctionBody::make(&mut ctx, "display_test".into()).unwrap();
2984
2985        let f = FunctionBody::from_name(&ctx, "display_test").unwrap();
2986
2987        let s = f.to_string();
2988        assert!(s.contains("fn display_test:"));
2989    }
2990
2991    #[test]
2992    fn iter_yields_all_blocks() {
2993        let mut ctx = Context::new();
2994        let f_id = FunctionBody::make(&mut ctx, "iter_fn".into()).unwrap().id;
2995        let root = BasicBlock::make(&mut ctx, f_id).id;
2996        let extra = BasicBlock::make(&mut ctx, f_id).id;
2997        let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
2998        f.add_block(root);
2999        f.add_block(extra);
3000
3001        let f = FunctionBody::from_name(&ctx, "iter_fn").unwrap();
3002        let ids: Vec<_> = f.iter().map(|b| b.id).collect();
3003        assert!(ids.contains(&root));
3004        assert!(ids.contains(&extra));
3005    }
3006
3007    #[test]
3008    fn into_iterator_for_function_ref_matches_iter() {
3009        let mut ctx = Context::new();
3010        let f_id = FunctionBody::make(&mut ctx, "into_iter_fn".into())
3011            .unwrap()
3012            .id;
3013        let b1 = BasicBlock::make(&mut ctx, f_id).id;
3014        let b2 = BasicBlock::make(&mut ctx, f_id).id;
3015        let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
3016        f.add_block(b1);
3017        f.add_block(b2);
3018
3019        let f = FunctionBody::from_name(&ctx, "into_iter_fn").unwrap();
3020        let mut via_iter: Vec<usize> = f.iter().map(|b| usize::from(b.id.local)).collect();
3021        let mut via_into: Vec<usize> = (&f).into_iter().map(|b| usize::from(b.id.local)).collect();
3022        via_iter.sort();
3023        via_into.sort();
3024        assert_eq!(via_iter, via_into);
3025    }
3026
3027    #[test]
3028    fn qcode_fn_single_block_populates_function() {
3029        let mut ctx = Context::new();
3030        qcode!(
3031            ctx,
3032            "
3033            fn simple:
3034                <entry>
3035                    return at 0;
3036            "
3037        );
3038
3039        let f = FunctionBody::from_name(&ctx, "simple").unwrap();
3040        assert_eq!(f.name(), "simple");
3041        assert!(f.root().is_some());
3042        assert_eq!(f.root().unwrap().name().unwrap(), "entry");
3043        assert_eq!(f.blocks().count(), 1);
3044    }
3045
3046    #[test]
3047    fn qcode_fn_multi_block_populates_all_blocks() {
3048        let mut ctx = Context::new();
3049        qcode!(
3050            ctx,
3051            "
3052            fn multiblock:
3053                <bb1>
3054                    if i8 1 goto <bb2> else goto <bb3>;
3055
3056                <bb2>
3057                    goto <bb3>;
3058
3059                <bb3>
3060                    return at 0;
3061            "
3062        );
3063
3064        let f = FunctionBody::from_name(&ctx, "multiblock").unwrap();
3065        assert_eq!(f.root().unwrap().name().unwrap(), "bb1");
3066        let block_names: Vec<_> = f.blocks().filter_map(|b| b.name()).collect();
3067        assert!(block_names.contains(&"bb1"), "missing bb1");
3068        assert!(block_names.contains(&"bb2"), "missing bb2");
3069        assert!(block_names.contains(&"bb3"), "missing bb3");
3070        assert_eq!(f.blocks().count(), 3);
3071    }
3072
3073    #[test]
3074    fn qcode_fn_id_variable_is_set() {
3075        let mut ctx = Context::new();
3076        qcode!(
3077            ctx,
3078            "
3079            fn myfn:
3080                <start>
3081                    return at 0;
3082            "
3083        );
3084
3085        let by_name = FunctionBody::from_name(&ctx, "myfn").unwrap();
3086        assert_eq!(by_name.name(), "myfn");
3087    }
3088
3089    /// Split construction lets a rootless function temporarily win an address
3090    /// occupied by a block that has not yet been rehomed into its arena.
3091    #[test]
3092    fn indexed_address_registration_keeps_foreign_block_rootless() {
3093        let mut ctx = Context::new();
3094
3095        // Simulate a branch-target block created at 0x1000 before the function
3096        // stub exists (as happens with tail-jumps to sibling functions).
3097        let block_id = {
3098            let __f = ctx.anon_function();
3099            BasicBlock::make(&mut ctx, __f)
3100        }
3101        .id;
3102        let mut addresses = crate::address_index::AddressIndex::analyze(&ctx);
3103        addresses
3104            .register(
3105                &mut ctx,
3106                0x1000,
3107                crate::address_index::AddressTarget::Block(block_id),
3108            )
3109            .unwrap();
3110
3111        let fn_id = FunctionBody::make(&mut ctx, "fn_1000".into()).unwrap().id;
3112        addresses
3113            .register(
3114                &mut ctx,
3115                0x1000,
3116                crate::address_index::AddressTarget::Function(fn_id),
3117            )
3118            .unwrap();
3119
3120        assert_eq!(addresses.function_at(0x1000), Some(fn_id));
3121        assert_eq!(addresses.block_at(0x1000), None);
3122        assert!(FunctionBody::from_id(&ctx, fn_id).root().is_none());
3123        assert_ne!(block_id.func, fn_id);
3124    }
3125}
3126
3127#[cfg(test)]
3128mod memory_interface_tests {
3129    use super::*;
3130
3131    fn slot() -> InterfaceSlot {
3132        InterfaceSlot {
3133            base: SlotBase::Arg(0),
3134            offset: 8,
3135            size: 8,
3136        }
3137    }
3138
3139    /// The interface survives a round-trip through the snapshot wire format.
3140    ///
3141    /// Back-compat is *not* tested here and is not provided: the payload is
3142    /// bincode under a hard version lock (`session.rs` `FORMAT_VERSION`, bumped
3143    /// for this field), so snapshots written before it are rejected outright
3144    /// rather than defaulted.
3145    #[test]
3146    fn memory_interface_round_trips_through_the_wire_format() {
3147        let state = MemoryChannelState {
3148            materialized: Some(MemoryInterfaceMap {
3149                inputs: vec![slot()],
3150                outputs: vec![InterfaceSlot {
3151                    base: SlotBase::Global(0x2000),
3152                    offset: 0,
3153                    size: 4,
3154                }],
3155            }),
3156            ..MemoryChannelState::default()
3157        };
3158        let config = bincode::config::standard();
3159        let bytes = bincode::serde::encode_to_vec(&state, config).expect("encode memory state");
3160        let (decoded, _): (MemoryChannelState, _) =
3161            bincode::serde::decode_from_slice(&bytes, config).expect("decode memory state");
3162        assert_eq!(decoded, state);
3163    }
3164
3165    /// A default (unmaterialized) channel reports no interface.
3166    #[test]
3167    fn default_memory_state_is_not_materialized() {
3168        assert_eq!(MemoryChannelState::default().materialized(), None);
3169    }
3170
3171    /// The coarse-only setter must not disturb the other two components: a
3172    /// re-stamp of the written-space set is not a re-materialization.
3173    #[test]
3174    fn stamping_written_spaces_preserves_the_materialized_interface() {
3175        let mut ctx = Context::new();
3176        let fid = FunctionBody::make(&mut ctx, "keeps_interface".into())
3177            .unwrap()
3178            .id;
3179        let map = MemoryInterfaceMap {
3180            inputs: vec![slot()],
3181            outputs: vec![],
3182        };
3183        let mut body = FunctionBody::from_id_mut(&mut ctx, fid);
3184        body.set_memory_effects(MemoryChannelState {
3185            materialized: Some(map.clone()),
3186            ..MemoryChannelState::default()
3187        });
3188        body.set_written_spaces(None);
3189
3190        let effects = FunctionBody::from_id(&ctx, fid).effects().memory.clone();
3191        assert_eq!(effects.materialized(), Some(&map));
3192        assert_eq!(effects.coarse, WrittenSpacesState::Unbounded);
3193    }
3194
3195    /// `Unmappable` and `Global` are both address-less bases, but only the
3196    /// second is bindable. A consumer that collapsed them would synthesize a
3197    /// load from a bogus absolute address for a base it never resolved.
3198    #[test]
3199    fn an_unmappable_base_is_distinct_from_a_global_and_is_not_bindable() {
3200        let unmappable = InterfaceSlot {
3201            base: SlotBase::Unmappable,
3202            offset: 0,
3203            size: 8,
3204        };
3205        let global = InterfaceSlot {
3206            base: SlotBase::Global(0),
3207            offset: 0,
3208            size: 8,
3209        };
3210        assert_ne!(unmappable, global);
3211        assert!(!unmappable.is_bindable());
3212        assert!(global.is_bindable());
3213        assert!(
3214            InterfaceSlot {
3215                base: SlotBase::Arg(0),
3216                offset: -8,
3217                size: 8,
3218            }
3219            .is_bindable()
3220        );
3221    }
3222}