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    /// Moves `insn` and everything after it in its block — the terminator
1471    /// included — into a fresh block, and returns that block.
1472    ///
1473    /// The original block keeps its identity, its address, its parameters and
1474    /// its incoming edges, and is left *unterminated*: the caller ends it,
1475    /// typically with a branch to the new block or a conditional branch that
1476    /// reaches the new block one way or another. The outgoing edges follow the
1477    /// terminator to the new block. Values defined before the split stay
1478    /// visible to the instructions after it, as SSA allows across blocks.
1479    ///
1480    /// Unlike an address split this moves code rather than discarding it, so
1481    /// it is for rewriting a block in place — inserting a conditional detour —
1482    /// not for establishing a new branch target in the guest.
1483    pub fn split_block_before(&mut self, block: BlockId, insn: InstructionId) -> BlockId {
1484        assert_eq!(block.func, self.id(), "block belongs to another function");
1485        assert_eq!(
1486            insn.func,
1487            self.id(),
1488            "instruction belongs to another function"
1489        );
1490        let index = self
1491            .block(block)
1492            .instructions
1493            .iter()
1494            .position(|&local| local == insn.local)
1495            .expect("split point is not in the block");
1496        let tail = self.make_block();
1497        let moved: Vec<LocalInsnId> = self.block_mut(block).instructions.split_off(index);
1498        for &local in &moved {
1499            self.insn_mut(InstructionId::new(self.id(), local)).parent = Some(tail.local);
1500        }
1501        self.block_mut(tail).instructions = moved;
1502        self.rehome_outgoing_edges(tail, block);
1503        tail
1504    }
1505
1506    pub fn rehome_outgoing_edges(&mut self, keep: BlockId, remove: BlockId) {
1507        let outgoing: Vec<EdgeId> = {
1508            let block = self.block(remove);
1509            block
1510                .edges
1511                .iter()
1512                .copied()
1513                .filter(|&e| self.edge(e).from == remove.local)
1514                .collect()
1515        };
1516        for eid in outgoing {
1517            self.edges[eid].from = keep.local;
1518            self.block_mut(keep).edges.insert(eid);
1519            self.block_mut(remove).edges.remove(&eid);
1520        }
1521    }
1522
1523    /// Replace an instruction's mnemonic in place, keeping the reverse use-map in
1524    /// sync.
1525    pub fn replace_instruction_mnemonic(&mut self, id: InstructionId, mnemonic: Mnemonic) {
1526        assert_eq!(
1527            id.func,
1528            self.id(),
1529            "instruction belongs to another function"
1530        );
1531        self.replace_instruction_mnemonic_local(id.local, mnemonic);
1532    }
1533
1534    /// Replace an instruction's mnemonic in place, keeping the reverse use-map in
1535    /// sync, over a **body-local** instruction id (id-free; the twin of
1536    /// [`replace_instruction_mnemonic`](Self::replace_instruction_mnemonic),
1537    /// usable on a detached body).
1538    pub fn replace_instruction_mnemonic_local(&mut self, id: LocalInsnId, mnemonic: Mnemonic) {
1539        let old_args = self.insns[id]
1540            .mnemonic()
1541            .args()
1542            .into_iter()
1543            .collect::<Vec<_>>();
1544        for arg in old_args {
1545            let now_empty = if let Some(users) = self.users.get_mut(&arg) {
1546                users.retain(|&local| local != id);
1547                users.is_empty()
1548            } else {
1549                false
1550            };
1551            if now_empty {
1552                self.users.remove(&arg);
1553            }
1554        }
1555        *self.insns[id].mnemonic_mut() = mnemonic;
1556        let new_args = self.insns[id]
1557            .mnemonic()
1558            .args()
1559            .into_iter()
1560            .collect::<Vec<_>>();
1561        for arg in new_args {
1562            self.users.entry(arg).or_default().push(id);
1563        }
1564    }
1565
1566    /// Set `block`'s name and register it in this body's local name table, over a
1567    /// **body-local** block id (id-free; the twin of
1568    /// [`BaseRef::rename_local`](crate::value::util::base_ref::BaseRef::rename_local)
1569    /// restricted to the id-free local parts, usable on a detached body). Errors
1570    /// only on a duplicate name.
1571    pub fn rename_block_local(&mut self, block: LocalBlockId, name: Cow<'str, str>) -> Result<()> {
1572        let target = LocalValueId::BasicBlock(block);
1573        if let Some(existing) = self.names.get(&name) {
1574            return if existing == target {
1575                Ok(())
1576            } else {
1577                Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
1578            };
1579        }
1580        let old_name = self.blocks[block].local_name().map(str::to_owned);
1581        self.names
1582            .register(name.clone(), target, old_name.as_deref())?;
1583        self.blocks[block].set_name(Some(name));
1584        Ok(())
1585    }
1586
1587    /// Drop `block` from this body's ownership roster. Ownership is derived from
1588    /// the storing arena (`block.func`).
1589    pub fn unroster_block(&mut self, block: BlockId) {
1590        self.roster.retain(|&b| b != block.localize(block.func));
1591    }
1592
1593    /// Remove `block` from this body: unlink every incident CFG edge, remove its
1594    /// instructions and params, clear ownership metadata, then drop its payload.
1595    /// Empties `block` of code, keeping the block itself.
1596    ///
1597    /// Only the *outgoing* edges go, because those are owned by the terminator
1598    /// being removed; the incoming ones belong to other blocks' terminators,
1599    /// which still name this block and must keep resolving to it. That is the
1600    /// point of clearing rather than deleting: every branch already targeting
1601    /// this block stays valid while its contents are rebuilt.
1602    pub fn clear_block_instructions(&mut self, block: BlockId) {
1603        assert_eq!(block.func, self.id(), "block belongs to another function");
1604        let mut outgoing: Vec<EdgeId> = self
1605            .block(block)
1606            .edges
1607            .iter()
1608            .copied()
1609            .filter(|&edge| self.edges[edge].from == block.local)
1610            .collect();
1611        outgoing.sort_unstable();
1612        for edge in outgoing {
1613            self.remove_cfg_edge(edge);
1614        }
1615        // The list is emptied in one move and the instructions purged as a
1616        // set. Removing them one at a time means re-scanning the very list
1617        // being emptied for each one, which is quadratic — and the blocks this
1618        // clears are absorbed guest basic blocks, thousands of instructions
1619        // long. Splitting one used to cost more than lifting it did.
1620        let insns = std::mem::take(&mut self.block_mut(block).instructions);
1621        let dead: FxHashSet<LocalInsnId> = insns.iter().copied().collect();
1622        let names: Vec<Cow<'str, str>> = insns
1623            .iter()
1624            .filter_map(|&local| self.insns[local].name.clone())
1625            .collect();
1626        self.purge_instructions(&dead, names);
1627    }
1628
1629    pub fn delete_block(&mut self, block: BlockId) {
1630        assert_eq!(block.func, self.id(), "block belongs to another function");
1631        let mut edges: Vec<EdgeId> = self.block(block).edges.iter().copied().collect();
1632        edges.sort_unstable();
1633        for edge in edges {
1634            self.remove_cfg_edge(edge);
1635        }
1636        let insns: Vec<InstructionId> = self
1637            .block(block)
1638            .instructions
1639            .iter()
1640            .map(|&local| InstructionId::new(self.id(), local))
1641            .collect();
1642        for insn in insns {
1643            self.remove_instruction(insn);
1644        }
1645        let params: Vec<BlockParamId> = self
1646            .block(block)
1647            .params
1648            .iter()
1649            .map(|&local| BlockParamId::new(self.id(), local))
1650            .collect();
1651        for param in params {
1652            self.remove_block_param(param);
1653        }
1654        let name = self.block(block).local_name().map(str::to_owned);
1655        self.unroster_block(block);
1656        if self.root == Some(block.local) {
1657            self.root = None;
1658        }
1659        if let Some(name) = name {
1660            self.names.forget(&name);
1661        }
1662        self.blocks.remove(block.local);
1663    }
1664
1665    /// Absorb `other` into `keep`: drop `keep`'s terminal branch, append `other`'s
1666    /// instructions, rehome its outgoing edges, and remove it. `edge_ab` is the
1667    /// direct edge `keep -> other`.
1668    pub fn absorb_block(&mut self, keep: BlockId, other: BlockId, edge_ab: EdgeId) {
1669        assert_eq!(
1670            keep.func, other.func,
1671            "cannot absorb across function arenas"
1672        );
1673        let (branch_id, branch_args) = self
1674            .block(keep)
1675            .instructions
1676            .last()
1677            .and_then(
1678                |&local| match self.insn(InstructionId::new(keep.func, local)).mnemonic() {
1679                    Mnemonic::Branch(branch) if BlockId::new(keep.func, branch.target) == other => {
1680                        Some((InstructionId::new(keep.func, local), branch.args.clone()))
1681                    }
1682                    _ => None,
1683                },
1684            )
1685            .expect("absorbed block must be reached by keep's terminal branch");
1686        let other_params: Vec<_> = self
1687            .block(other)
1688            .params
1689            .iter()
1690            .map(|&local| BlockParamId::new(other.func, local))
1691            .collect();
1692        if !other_params.is_empty() {
1693            assert_eq!(
1694                other_params.len(),
1695                branch_args.len(),
1696                "cannot absorb block with {} params through branch with {} args",
1697                other_params.len(),
1698                branch_args.len()
1699            );
1700            for (param, arg) in other_params.iter().copied().zip(branch_args) {
1701                self.replace_all_uses_with(ValueId::BlockParam(param), arg.qualify(keep.func));
1702            }
1703        }
1704        self.remove_cfg_edge(edge_ab);
1705        self.remove_instruction(branch_id);
1706        let b_insns = std::mem::take(&mut self.block_mut(other).instructions);
1707        for &local in &b_insns {
1708            self.insn_mut(InstructionId::new(other.func, local)).parent = Some(keep.local);
1709        }
1710        self.block_mut(keep).instructions.extend(b_insns);
1711        self.rehome_outgoing_edges(keep, other);
1712        let (b_addr, b_extra, b_name) = {
1713            let b = self.block(other);
1714            (
1715                b.address,
1716                b.extra_addresses.clone(),
1717                b.local_name().map(str::to_owned),
1718            )
1719        };
1720        for param in other_params {
1721            self.remove_block_param(param);
1722        }
1723        self.unroster_block(other);
1724        if self.root == Some(other.local) {
1725            self.root = Some(keep.local);
1726        }
1727        if let Some(name) = b_name {
1728            self.names.forget(&name);
1729        }
1730        self.blocks.remove(other.local);
1731        if let Some(addr) = b_addr {
1732            self.block_mut(keep).extra_addresses.push(addr);
1733        }
1734        self.block_mut(keep).extra_addresses.extend(b_extra);
1735    }
1736
1737    /// Register `name` for `id` in this body's local name table (block/instruction/
1738    /// param). A global-scoped `id` reads `shared` for the duplicate check but
1739    /// cannot be *registered* through a body (its shared table is read-only here);
1740    /// no body verb reaches that arm.
1741    pub fn register_local_name(
1742        &mut self,
1743        shared: &crate::context::Shared<'str>,
1744        id: ValueId,
1745        name: Cow<'str, str>,
1746        old_name: Option<&str>,
1747    ) -> Result<()> {
1748        if id.name_scope_function().is_none() {
1749            return match shared.get_named(&name) {
1750                Some(existing) if existing == id => Ok(()),
1751                Some(_) => Err(Error::spanless(ErrorTy::DuplicateName(name.to_string()))),
1752                None => unimplemented!(
1753                    "a function body cannot register a global name (shared is read-only)"
1754                ),
1755            };
1756        }
1757        self.register_body_name(id, name, old_name)
1758    }
1759
1760    /// Register `name` for the function-scoped `id` (block/instruction/param/Temp)
1761    /// in this body's local name table. The shared-arm-free canon behind
1762    /// [`register_local_name`](Self::register_local_name); panics on a
1763    /// global-scoped `id`. Errors only on a duplicate name.
1764    pub fn register_body_name(
1765        &mut self,
1766        id: ValueId,
1767        name: Cow<'str, str>,
1768        old_name: Option<&str>,
1769    ) -> Result<()> {
1770        assert!(
1771            id.name_scope_function().is_some(),
1772            "register_body_name on a global-scoped value {id:?}"
1773        );
1774        if let Some(existing) = self.names.get(&name).map(|id| id.qualify(self.id())) {
1775            return if existing == id {
1776                Ok(())
1777            } else {
1778                Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
1779            };
1780        }
1781        self.names.register(name, id.localize(self.id()), old_name)
1782    }
1783
1784    /// Gets a reference to a function from its ID
1785    pub fn from_id<'ctx>(ctx: &'ctx Context<'str>, id: FunctionId) -> FunctionRef<'str, 'ctx> {
1786        FunctionRef::new(ModuleView::new(ctx), id)
1787    }
1788
1789    /// Gets a mutable reference to a function from its ID
1790    pub fn from_id_mut<'ctx>(
1791        ctx: &'ctx mut Context<'str>,
1792        id: FunctionId,
1793    ) -> FunctionMutRef<'str, 'ctx> {
1794        FunctionMutRef::new(ctx, id)
1795    }
1796
1797    /// Gets a reference to a function by name
1798    pub fn from_name<'ctx>(
1799        ctx: &'ctx Context<'str>,
1800        name: &str,
1801    ) -> Option<FunctionRef<'str, 'ctx>> {
1802        ctx.get_named(name)
1803            .and_then(ValueId::as_function)
1804            .map(|id| FunctionBody::from_id(ctx, id))
1805    }
1806
1807    /// Create a new function
1808    pub fn make<'ctx>(
1809        ctx: &'ctx mut Context<'str>,
1810        name: Cow<'str, str>,
1811    ) -> Result<FunctionMutRef<'str, 'ctx>> {
1812        let id = FunctionId::from(ctx.bodies.len());
1813        let pushed = ctx.push_function(
1814            FunctionInterface::new(name.clone()),
1815            FunctionBody::empty_with_id(id),
1816        );
1817        debug_assert_eq!(pushed, id);
1818        ctx.update_name(name, id.into(), None)?;
1819        Ok(Self::from_id_mut(ctx, id))
1820    }
1821
1822    /// Create a new pure value-level lambda function.
1823    pub fn make_lambda<'ctx>(
1824        ctx: &'ctx mut Context<'str>,
1825        name: Cow<'str, str>,
1826    ) -> Result<FunctionMutRef<'str, 'ctx>> {
1827        let mut function = Self::make(ctx, name)?;
1828        function.interface_mut().kind = FunctionKind::Lambda;
1829        function.set_is_pure(true);
1830        function.set_register_effects(RegisterChannelState::Materialized(
1831            RegisterInterfaceMap::default(),
1832        ));
1833        Ok(function)
1834    }
1835
1836    /// Create a new function at a given address, generating a name if necessary.
1837    pub fn make_at_addr<'ctx>(
1838        ctx: &'ctx mut Context<'str>,
1839        address: u64,
1840        name: Option<Cow<'str, str>>,
1841    ) -> FunctionMutRef<'str, 'ctx> {
1842        let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
1843        Self::make_at_addr_indexed(ctx, &mut addresses, address, name)
1844    }
1845
1846    /// Indexed construction variant of [`make_at_addr`](Self::make_at_addr).
1847    pub fn make_at_addr_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 name = name.unwrap_or_else(|| Cow::Owned(format!("fn_{address:x}")));
1854        let id = FunctionId::from(ctx.bodies.len());
1855        let pushed = ctx.push_function(
1856            FunctionInterface::new(name.clone()),
1857            FunctionBody::empty_with_id(id),
1858        );
1859        debug_assert_eq!(pushed, id);
1860
1861        Self::from_id_mut(ctx, id)
1862            .with_name(name)
1863            .expect("Function name is not unique")
1864            .with_address_indexed(addresses, address)
1865            .expect("Function address is not unique")
1866    }
1867
1868    /// Like [`FunctionBody::make_at_addr`] but marks the result as external.
1869    ///
1870    /// External functions have no lifted body; the recursive disassembler will
1871    /// not try to explore them.
1872    pub fn make_external<'ctx>(
1873        ctx: &'ctx mut Context<'str>,
1874        address: u64,
1875        name: Option<Cow<'str, str>>,
1876    ) -> FunctionMutRef<'str, 'ctx> {
1877        let mut f = Self::make_at_addr(ctx, address, name);
1878        f.interface_mut().is_external = true;
1879        f
1880    }
1881
1882    /// Indexed construction variant of [`make_external`](Self::make_external).
1883    pub fn make_external_indexed<'ctx>(
1884        ctx: &'ctx mut Context<'str>,
1885        addresses: &mut crate::address_index::AddressIndex,
1886        address: u64,
1887        name: Option<Cow<'str, str>>,
1888    ) -> FunctionMutRef<'str, 'ctx> {
1889        let mut function = Self::make_at_addr_indexed(ctx, addresses, address, name);
1890        function.interface_mut().is_external = true;
1891        function
1892    }
1893
1894    /// Returns the [`FunctionId`] for `addr`, creating a named stub if absent.
1895    pub fn from_addr_or_create<'ctx>(
1896        ctx: &'ctx mut Context<'str>,
1897        address: u64,
1898    ) -> FunctionMutRef<'str, 'ctx> {
1899        let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
1900        Self::from_addr_or_create_indexed(ctx, &mut addresses, address)
1901    }
1902
1903    /// Indexed construction variant of
1904    /// [`from_addr_or_create`](Self::from_addr_or_create).
1905    pub fn from_addr_or_create_indexed<'ctx>(
1906        ctx: &'ctx mut Context<'str>,
1907        addresses: &mut crate::address_index::AddressIndex,
1908        address: u64,
1909    ) -> FunctionMutRef<'str, 'ctx> {
1910        match addresses.function_at(address) {
1911            Some(id) => Self::from_id_mut(ctx, id),
1912            None => Self::make_at_addr_indexed(ctx, addresses, address, None),
1913        }
1914    }
1915}
1916
1917impl<'s, 'ctx: 's, 'str: 'ctx, R> FunctionRef<'str, 'ctx, R>
1918where
1919    R: QCodeView<'ctx, 'str>,
1920{
1921    fn inner(&'s self) -> &'ctx FunctionBody<'str> {
1922        self.view.function(self.id)
1923    }
1924
1925    /// This function's published interface (never checked out; always read from
1926    /// the shared registry).
1927    fn interface(&'s self) -> &'ctx FunctionInterface<'str> {
1928        self.view.interface(self.id)
1929    }
1930
1931    fn size(&self) -> usize {
1932        0
1933    }
1934
1935    /// The function interface's entry address.
1936    pub fn address(&'s self) -> Option<u64> {
1937        self.interface().address
1938    }
1939
1940    /// Whether the function interface marks this function external.
1941    pub fn is_external(&'s self) -> bool {
1942        self.interface().is_external
1943    }
1944
1945    /// The ordinal this import was brought in at, for a PE import resolved from
1946    /// an ordinal-only entry. `None` for named imports and local functions.
1947    pub fn import_ordinal(&'s self) -> Option<u16> {
1948        self.interface().import_ordinal
1949    }
1950
1951    /// A reference to the function interface's signature, if any.
1952    pub fn signature(&'s self) -> Option<&'ctx FunctionSignature> {
1953        self.interface().signature.as_ref()
1954    }
1955
1956    /// This function's instructions that use `value` as an operand. See
1957    /// [`FunctionBody::users_of`]; this is the function-scoped read every pass wants
1958    /// for an SSA value (all its users are intra-function).
1959    pub fn users_of(&'s self, value: ValueId) -> Vec<InstructionId> {
1960        let func = self.id;
1961        if value.owning_function().is_some_and(|owner| owner != func) {
1962            return Vec::new();
1963        }
1964        self.inner().users_of(value)
1965    }
1966
1967    /// This function's users of `value` in their stored, body-local form.
1968    ///
1969    /// Borrowed rather than built: a pass that reads the list once per
1970    /// instruction should not allocate one per instruction to do it. Qualify
1971    /// with this function's id when a whole [`InstructionId`] is needed.
1972    pub fn local_users_of(&'s self, value: ValueId) -> &'ctx [LocalInsnId] {
1973        let func = self.id;
1974        if value.owning_function().is_some_and(|owner| owner != func) {
1975            return &[];
1976        }
1977        self.inner().local_users_of(value)
1978    }
1979
1980    /// Whether this function uses `value` at all, without building the user
1981    /// list to ask. See [`FunctionBody::has_users`].
1982    pub fn has_users(&'s self, value: ValueId) -> bool {
1983        let func = self.id;
1984        if value.owning_function().is_some_and(|owner| owner != func) {
1985            return false;
1986        }
1987        self.inner().has_users(value)
1988    }
1989
1990    /// Iterate this function's recorded `(value, users)` reverse-use entries
1991    /// (see [`FunctionBody::user_map_entries`]).
1992    pub fn user_map_entries(&'s self) -> impl Iterator<Item = (ValueId, Vec<InstructionId>)> + 's {
1993        let func = self.id;
1994        self.inner().user_map_entries().map(move |(v, u)| {
1995            (
1996                v.qualify(func),
1997                u.iter()
1998                    .map(|&local| InstructionId::new(func, local))
1999                    .collect(),
2000            )
2001        })
2002    }
2003
2004    /// Resolve a block/instruction/param/Temp `name` within this function's local name
2005    /// table (see `FunctionBody::names`). `None` if this function has no such name.
2006    pub fn local_named(&'s self, name: &str) -> Option<ValueId> {
2007        self.inner().names.get(name).map(|id| id.qualify(self.id))
2008    }
2009
2010    /// The inferred pointer attributes for positional argument `index`, or `None`
2011    /// when this function has no analyzed attributes (treat conservatively: the
2012    /// argument escapes and may be written through). See
2013    /// [`FunctionSignature::param_attrs`].
2014    pub fn param_attr(&'s self, index: usize) -> Option<ParamAttrs> {
2015        self.interface().param_attr(index)
2016    }
2017
2018    /// The full per-parameter attribute vector, if analyzed.
2019    pub fn param_attrs(&'s self) -> Option<&'ctx [ParamAttrs]> {
2020        self.interface()
2021            .signature
2022            .as_ref()
2023            .and_then(|s| s.param_attrs.as_deref())
2024    }
2025
2026    /// The non-register memory spaces this function may (transitively) write, as
2027    /// set by analysis. `Some(spaces)` is exact (a space not listed is never
2028    /// written); `None` conflates "unstamped" and "stamped unbounded" — both are
2029    /// treated conservatively (may write any space) by consumers. For the
2030    /// tri-state distinction use [`written_spaces_state`](Self::written_spaces_state).
2031    /// See `FunctionSignature::written_spaces`.
2032    pub fn written_spaces(&'s self) -> Option<&'ctx [crate::space::SpaceId]> {
2033        match &self.interface().effects.memory.coarse {
2034            WrittenSpacesState::Bounded(spaces) => Some(spaces),
2035            _ => None,
2036        }
2037    }
2038
2039    /// The tri-state `written_spaces` verdict, distinguishing a never-stamped
2040    /// fresh mint ([`WrittenSpaces::Unstamped`]) from a deliberately recorded
2041    /// ⊤ ([`WrittenSpaces::Unbounded`]). See `FunctionSignature::written_spaces`.
2042    pub fn written_spaces_state(&'s self) -> WrittenSpaces<'ctx> {
2043        match &self.interface().effects.memory.coarse {
2044            WrittenSpacesState::Unstamped => WrittenSpaces::Unstamped,
2045            WrittenSpacesState::Unbounded => WrittenSpaces::Unbounded,
2046            WrittenSpacesState::Bounded(spaces) => WrittenSpaces::Bounded(spaces),
2047        }
2048    }
2049
2050    /// Whether this function's register interface has been materialized (argpromote
2051    /// v2) — i.e. its [`effects`](FunctionInterface::effects) are
2052    /// [`RegisterChannelState::Materialized`]. Legacy name for the register-channel
2053    /// "functionalized" predicate.
2054    pub fn is_reg_materialized(&'s self) -> bool {
2055        matches!(
2056            self.interface().effects.register,
2057            RegisterChannelState::Materialized(_)
2058        )
2059    }
2060
2061    /// This function's call-graph-closed register [`FunctionEffects`] summary.
2062    /// [`RegisterChannelState::Unsolved`] until the effect-analysis pass runs (and
2063    /// after a snapshot load). See [`FunctionInterface::effects`].
2064    pub fn effects(&'s self) -> &'ctx FunctionEffects {
2065        &self.interface().effects
2066    }
2067
2068    /// Whether argpromote has functionalized *every* side-effect channel of this
2069    /// function — it is a deterministic pure function of its by-value params,
2070    /// touching no caller-visible memory or registers. Strictly stronger than
2071    /// [`is_reg_materialized`](Self::is_reg_materialized). See [`FunctionSignature::is_pure`].
2072    pub fn is_pure(&'s self) -> bool {
2073        self.interface()
2074            .signature
2075            .as_ref()
2076            .is_some_and(|s| s.is_pure)
2077    }
2078
2079    /// Whether this is a pure value-level lambda rather than a machine function.
2080    pub fn is_lambda(&'s self) -> bool {
2081        self.interface().kind == FunctionKind::Lambda
2082    }
2083
2084    pub fn kind(&'s self) -> FunctionKind {
2085        self.interface().kind
2086    }
2087
2088    /// The C-prototype-derived external call interface, if `external_sigs`
2089    /// planned one. Read by `argpromote_external` to rewrite call sites. See
2090    /// [`FunctionSignature::extern_interface`].
2091    pub fn extern_interface(&'s self) -> Option<&'ctx crate::value::ExternInterface> {
2092        self.interface()
2093            .signature
2094            .as_ref()
2095            .and_then(|s| s.extern_interface.as_ref())
2096    }
2097
2098    /// The C-prototype-derived argmem summary for a prototyped external, or `None`
2099    /// when this function is not a prototyped external. See
2100    /// [`FunctionSignature::argmem`].
2101    pub fn argmem(&'s self) -> Option<&'ctx crate::value::ExternArgmem> {
2102        self.interface()
2103            .signature
2104            .as_ref()
2105            .and_then(|s| s.argmem.as_ref())
2106    }
2107
2108    /// The display name for the call-site argument bound to input `index`: the
2109    /// name of the callee's root block param at `index`, or — for a bodyless
2110    /// external with no root block — the C-prototype argument name recorded in
2111    /// its [`extern_interface`](Self::extern_interface). `None` when there is no
2112    /// input at `index` or it is unnamed.
2113    pub fn input_arg_name(&'s self, index: usize) -> Option<String> {
2114        // The root block param at `index` is the interface element a call
2115        // argument actually binds to, named after its register by
2116        // `argpromote_registers` or `stack_<addr>` by mem2reg's
2117        // `block_param_name_for_var`. Prefer it: it is the source of truth and is
2118        // populated even for `pure_reg` functions.
2119        if let Some(root) = self.root()
2120            && let Some(name) = root
2121                .params()
2122                .nth(index)
2123                .and_then(|p| p.name().map(str::to_owned))
2124        {
2125            return Some(name);
2126        }
2127
2128        // Fall back to the C-prototype-derived external call interface, the
2129        // source of truth for bodyless externals which have no root block.
2130        self.extern_interface()
2131            .and_then(|iface| iface.args.get(index))
2132            .and_then(|a| a.name.as_ref().map(|n| n.to_string()))
2133    }
2134
2135    /// Whether this function performs an unresolved/dynamic stack read (or
2136    /// forwards a stack pointer into one). See
2137    /// [`FunctionSignature::reads_unbounded_stack`].
2138    pub fn reads_unbounded_stack(&'s self) -> bool {
2139        self.interface()
2140            .signature
2141            .as_ref()
2142            .is_some_and(|s| s.reads_unbounded_stack)
2143    }
2144
2145    /// Whether this function hands a pointer into its own frame to a callee that
2146    /// may read it unboundedly. See
2147    /// [`FunctionSignature::frame_escapes_to_unbounded`].
2148    pub fn frame_escapes_to_unbounded(&'s self) -> bool {
2149        self.interface()
2150            .signature
2151            .as_ref()
2152            .is_some_and(|s| s.frame_escapes_to_unbounded)
2153    }
2154
2155    /// The function interface's name.
2156    pub fn name(&'s self) -> &'ctx str {
2157        self.interface().name.as_ref()
2158    }
2159
2160    /// The addresses of every machine instruction lifted into this function, in
2161    /// ascending order. Unlike [`blocks`](Self::blocks), this is stable across
2162    /// optimization, so it drives the raw disassembly view.
2163    pub fn instruction_addrs(&'s self) -> impl Iterator<Item = u64> + 'ctx {
2164        self.inner().instruction_addrs.iter().copied()
2165    }
2166
2167    /// Whether this function contains at least one [`Map`](Mnemonic::Map)
2168    /// instruction — a lane-wise array map operation. Surfaced as an advanced
2169    /// filter in the function list.
2170    pub fn has_map(&'s self) -> bool {
2171        self.blocks().any(|block| {
2172            block
2173                .instructions()
2174                .any(|insn| matches!(insn.mnemonic(), Mnemonic::Map(_)))
2175        })
2176    }
2177
2178    /// Whether this function contains at least one [`Scan`](Mnemonic::Scan)
2179    /// instruction — a lane-wise prefix-fold array operation. Surfaced as an
2180    /// advanced filter in the function list, alongside [`has_map`](Self::has_map).
2181    pub fn has_scan(&'s self) -> bool {
2182        self.blocks().any(|block| {
2183            block
2184                .instructions()
2185                .any(|insn| matches!(insn.mnemonic(), Mnemonic::Scan(_)))
2186        })
2187    }
2188
2189    /// The root block of this function, if it exists.
2190    pub fn root(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
2191        self.inner()
2192            .root
2193            .map(|local| BlockRef::new(self.view, BlockId::new(self.id, local)))
2194    }
2195
2196    /// An iterator over the (live) blocks belonging to this function.
2197    pub fn blocks(&'s self) -> impl Iterator<Item = BlockRef<'str, 'ctx, R>> + 's {
2198        let view = self.view;
2199        let mut ids = self.block_ids();
2200        // Total order: primarily by machine address, but break ties by the
2201        // function-local index. Address-less blocks (e.g. fallthrough splits,
2202        // whose `address()` is `None`) must still order deterministically.
2203        ids.sort_by_key(|&id| (BlockRef::new(view, id).address(), id.local));
2204        ids.into_iter().map(move |id| BlockRef::new(view, id))
2205    }
2206
2207    /// The composite ids of this function's live blocks, in roster order.
2208    pub fn block_ids(&'s self) -> Vec<BlockId> {
2209        let func = self.id;
2210        self.inner()
2211            .roster
2212            .iter()
2213            .copied()
2214            .map(|local| BlockId::new(func, local))
2215            .collect()
2216    }
2217
2218    /// The composite IDs of this function's live instructions, in dense physical
2219    /// order — including any currently detached (`parent == None`).
2220    pub fn instruction_ids(&'s self) -> Vec<InstructionId> {
2221        let func = self.id;
2222        self.inner()
2223            .insns
2224            .iter()
2225            .map(|i| InstructionId::new(func, i.id))
2226            .collect()
2227    }
2228
2229    /// The IDs of every live CFG edge in this function's edge arena, in dense
2230    /// physical order.
2231    pub fn edge_ids(&'s self) -> Vec<crate::value::block::EdgeId> {
2232        self.inner().edges.iter().map(|e| e.id).collect()
2233    }
2234
2235    /// Iterates over the (live) blocks in this function in arena order (i.e. not
2236    /// sorted by address, unlike [`blocks`](Self::blocks)).
2237    pub fn iter(&'s self) -> BlockIter<'str, 'ctx, R> {
2238        BlockIter {
2239            view: self.view,
2240            inner: self.block_ids().into_iter(),
2241            marker: PhantomData,
2242        }
2243    }
2244
2245    fn fmt(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
2246        if self.is_external() {
2247            return writeln!(f, "extern fn {};", self.name());
2248        }
2249        let keyword = match self.kind() {
2250            FunctionKind::Machine => "fn",
2251            FunctionKind::Lambda => "lambda",
2252        };
2253        writeln!(f, "{keyword} {}:", self.name())?;
2254        for block in self.blocks() {
2255            block.fmt(f)?;
2256        }
2257        Ok(())
2258    }
2259}
2260
2261#[derive(Clone, Copy)]
2262pub struct FunctionRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
2263    pub id: FunctionId,
2264    pub(in crate::value) view: R,
2265    marker: PhantomData<&'ctx &'str ()>,
2266}
2267
2268impl<'str, 'ctx, R> FunctionRef<'str, 'ctx, R> {
2269    pub fn new(view: R, id: FunctionId) -> Self {
2270        Self {
2271            id,
2272            view,
2273            marker: PhantomData,
2274        }
2275    }
2276
2277    pub fn id(&self) -> ValueId {
2278        self.id.into()
2279    }
2280}
2281
2282impl<'str, 'ctx> FunctionRef<'str, 'ctx> {
2283    pub fn from_id(ctx: &'ctx Context<'str>, id: FunctionId) -> Self {
2284        Self::new(ModuleView::new(ctx), id)
2285    }
2286}
2287
2288impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 'ctx, 'str> for FunctionRef<'str, 'ctx> {
2289    fn ctx(&'s self) -> &'ctx Context<'str> {
2290        // Module-scope-only escape hatch: shared-only reads go through
2291        // `host().shr()`; only whole-module walks (callees/callers) reach here,
2292        // and those panic on a checked-out host by design (context-split Pin B).
2293        self.view.context()
2294    }
2295}
2296
2297impl<'str: 'ctx, 'ctx, R> Named for FunctionRef<'str, 'ctx, R>
2298where
2299    R: QCodeView<'ctx, 'str>,
2300{
2301    fn name(&self) -> Option<&str> {
2302        Some(self.view.interface(self.id).name.as_ref())
2303    }
2304}
2305
2306impl<'str: 'ctx, 'ctx, R> Display for FunctionRef<'str, 'ctx, R>
2307where
2308    R: QCodeView<'ctx, 'str>,
2309{
2310    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2311        FunctionRef::fmt(self, f)
2312    }
2313}
2314
2315impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for FunctionRef<'str, 'ctx, R>
2316where
2317    R: QCodeView<'ctx, 'str>,
2318{
2319    fn id(&self) -> ValueId {
2320        self.id()
2321    }
2322
2323    fn size(&self) -> usize {
2324        FunctionRef::size(self)
2325    }
2326}
2327
2328pub struct BlockIter<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
2329    view: R,
2330    inner: std::vec::IntoIter<BlockId>,
2331    marker: PhantomData<&'ctx &'str ()>,
2332}
2333
2334impl<'str: 'ctx, 'ctx, R> Iterator for BlockIter<'str, 'ctx, R>
2335where
2336    R: QCodeView<'ctx, 'str>,
2337{
2338    type Item = BlockRef<'str, 'ctx, R>;
2339
2340    fn next(&mut self) -> Option<Self::Item> {
2341        self.inner.next().map(|id| BlockRef::new(self.view, id))
2342    }
2343}
2344
2345impl<'str: 'ctx, 'ctx, R> IntoIterator for &FunctionRef<'str, 'ctx, R>
2346where
2347    R: QCodeView<'ctx, 'str>,
2348{
2349    type Item = BlockRef<'str, 'ctx, R>;
2350    type IntoIter = BlockIter<'str, 'ctx, R>;
2351
2352    fn into_iter(self) -> Self::IntoIter {
2353        self.iter()
2354    }
2355}
2356
2357pub type FunctionMutRef<'str, 'ctx> = BaseRef<&'ctx mut Context<'str>, FunctionId>;
2358
2359impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 's, 'str> for FunctionMutRef<'str, 'ctx> {
2360    fn ctx(&'s self) -> &'s Context<'str> {
2361        self.ctx
2362    }
2363}
2364
2365impl<'s, 'ctx: 's, 'str: 'ctx> WithCtxMut<'s, 'str> for FunctionMutRef<'str, 'ctx> {
2366    fn ctx_mut(&'s mut self) -> &'s mut Context<'str> {
2367        self.ctx
2368    }
2369}
2370
2371impl Display for FunctionMutRef<'_, '_> {
2372    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2373        self.as_ref().fmt(f)
2374    }
2375}
2376
2377impl<'ctx, 'str> Value<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
2378    fn id(&self) -> ValueId {
2379        self.id()
2380    }
2381
2382    fn size(&self) -> usize {
2383        self.as_ref().size()
2384    }
2385}
2386
2387impl Named for FunctionMutRef<'_, '_> {
2388    fn name(&self) -> Option<&str> {
2389        Some(self.ctx.interfaces[self.id].name.as_ref())
2390    }
2391}
2392
2393impl<'str, 'ctx> Renameable<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
2394    fn rename(&mut self, name: Cow<'str, str>) -> Result<()> {
2395        let id = self.id();
2396        let old_name = self.ctx.interfaces[self.id].name.as_ref().to_owned();
2397        update_context_name(id, self.ctx, name.clone(), Some(old_name.as_ref()))?;
2398        self.ctx.interfaces[self.id].name = name;
2399        Ok(())
2400    }
2401}
2402
2403impl<'str, 'ctx> FunctionMutRef<'str, 'ctx> {
2404    pub fn as_ref(&self) -> FunctionRef<'str, '_> {
2405        FunctionRef::new(ModuleView::new(self.ctx), self.id)
2406    }
2407
2408    fn inner(&self) -> &FunctionBody<'str> {
2409        self.ctx.function(self.id)
2410    }
2411
2412    fn interface(&self) -> &FunctionInterface<'str> {
2413        &self.ctx.interfaces[self.id]
2414    }
2415
2416    fn address(&self) -> Option<u64> {
2417        self.interface().address
2418    }
2419
2420    pub fn name(&self) -> &str {
2421        self.interface().name.as_ref()
2422    }
2423
2424    pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'str, '_>> {
2425        self.as_ref().blocks().collect::<Vec<_>>().into_iter()
2426    }
2427
2428    pub fn root(&self) -> Option<BlockRef<'str, '_>> {
2429        self.as_ref().root()
2430    }
2431
2432    pub(crate) fn inner_mut(&mut self) -> &mut FunctionBody<'str> {
2433        &mut self.ctx.bodies[self.id]
2434    }
2435
2436    /// This function's published interface (mutable). Interface writes are
2437    /// module-scope only; this is the write path for the setters below.
2438    pub(crate) fn interface_mut(&mut self) -> &mut FunctionInterface<'str> {
2439        &mut self.ctx.interfaces[self.id]
2440    }
2441
2442    fn set_address(&mut self, address: u64) -> Result<()> {
2443        let mut addresses = crate::address_index::AddressIndex::analyze(&*self.ctx);
2444        self.set_address_indexed(&mut addresses, address)
2445    }
2446
2447    fn set_address_indexed(
2448        &mut self,
2449        addresses: &mut crate::address_index::AddressIndex,
2450        address: u64,
2451    ) -> Result<()> {
2452        let old_address = self.interface().address;
2453        self.interface_mut().address = Some(address);
2454        if let Err(error) = self
2455            .ctx
2456            .set_address_indexed(addresses, address, self.id.into())
2457        {
2458            self.interface_mut().address = old_address;
2459            return Err(error);
2460        }
2461        Ok(())
2462    }
2463
2464    fn with_address_indexed(
2465        mut self,
2466        addresses: &mut crate::address_index::AddressIndex,
2467        address: u64,
2468    ) -> Result<Self> {
2469        self.set_address_indexed(addresses, address)?;
2470        Ok(self)
2471    }
2472
2473    /// Sets a block as the root of this function.
2474    /// This will also add the block to the function's block list if it's not already present.
2475    /// This will also set the address of the function/block to the address of the root block/function if both addresses are unset.
2476    /// Panics if the function already has an address that doesn't match the root block's address.
2477    pub fn set_root(&mut self, id: BlockId) -> Result<()> {
2478        assert_eq!(
2479            id.func, self.id,
2480            "cannot root a function at a block stored in another function arena"
2481        );
2482        self.add_block(id);
2483        self.inner_mut().root = Some(id.localize(self.id));
2484
2485        let block_addr = BasicBlock::from_id(&*self.ctx, id).address();
2486        let self_addr = self.address();
2487
2488        match (self_addr, block_addr) {
2489            (Some(fn_addr), Some(block_addr)) if fn_addr != block_addr => {
2490                return Err(Error::spanless(ErrorTy::FunctionRootAddressMismatch {
2491                    fn_addr,
2492                    block_addr,
2493                }));
2494            }
2495            (None, Some(addr)) => {
2496                self.set_address(addr)
2497                    .expect("This address should be valid");
2498            }
2499            (Some(addr), None) => {
2500                BasicBlock::from_id_mut(self.ctx, id)
2501                    .set_address(addr)
2502                    .expect("This address should be valid");
2503            }
2504            _ => {}
2505        }
2506        Ok(())
2507    }
2508
2509    pub fn make_root(&mut self) -> BlockRef<'str, '_> {
2510        let func = self.id;
2511        let root = BasicBlock::make(self.ctx, func).id;
2512        self.set_root(root).expect("We just created the block");
2513        BasicBlock::from_id(&*self.ctx, root)
2514    }
2515
2516    pub fn ensure_root(&mut self, id: BlockId) -> Result<()> {
2517        assert_eq!(
2518            id.func, self.id,
2519            "cannot ensure a function root from another function arena"
2520        );
2521        if let Some(root) = self.inner().root {
2522            if root != id.localize(self.id) {
2523                return Err(Error::spanless(ErrorTy::FunctionRootMismatch {
2524                    expected: BlockId::new(self.id, root),
2525                    actual: id,
2526                }));
2527            }
2528            Ok(())
2529        } else {
2530            self.set_root(id)
2531        }
2532    }
2533
2534    pub fn set_external(&mut self, is_external: bool) {
2535        self.interface_mut().is_external = is_external;
2536        assert!(
2537            self.inner().blocks.is_empty(),
2538            "External functions should not have blocks"
2539        );
2540    }
2541
2542    /// Record the ordinal a by-ordinal PE import was brought in at. Set by the
2543    /// `resolve_ordinals` pass before it renames the stub, so the by-ordinal
2544    /// origin survives the rename.
2545    pub fn set_import_ordinal(&mut self, ordinal: Option<u16>) {
2546        self.interface_mut().import_ordinal = ordinal;
2547    }
2548
2549    pub fn set_kind(&mut self, kind: FunctionKind) {
2550        self.interface_mut().kind = kind;
2551        if kind == FunctionKind::Lambda {
2552            self.set_is_pure(true);
2553            self.set_register_effects(RegisterChannelState::Materialized(
2554                RegisterInterfaceMap::default(),
2555            ));
2556        }
2557    }
2558
2559    pub fn set_signature(&mut self, sig: FunctionSignature) {
2560        self.ctx.interfaces[self.id].signature = Some(sig);
2561    }
2562
2563    /// Records the inferred per-parameter pointer attributes on this function.
2564    /// See [`FunctionSignature::param_attrs`].
2565    pub fn set_param_attrs(&mut self, attrs: Vec<ParamAttrs>) {
2566        self.interface_mut()
2567            .signature
2568            .get_or_insert_default()
2569            .param_attrs = Some(attrs);
2570    }
2571
2572    /// Drops any inferred per-parameter attributes (e.g. after a signature
2573    /// rewrite changed the parameter list, invalidating the index alignment).
2574    pub fn clear_param_attrs(&mut self) {
2575        if let Some(sig) = self.interface_mut().signature.as_mut() {
2576            sig.param_attrs = None;
2577        }
2578    }
2579
2580    /// Records the analysis-computed set of non-register spaces this function may
2581    /// write. This is always a deliberate stamp: `Some(spaces)` is a bounded
2582    /// witnessed set, `None` records *stamped unbounded* (⊤) — never clears the
2583    /// stamp back to unstamped. See `FunctionSignature::written_spaces` and
2584    /// [`FunctionRef::written_spaces_state`].
2585    pub fn set_written_spaces(&mut self, spaces: Option<Vec<crate::space::SpaceId>>) {
2586        let coarse = match spaces {
2587            Some(spaces) => WrittenSpacesState::Bounded(spaces),
2588            None => WrittenSpacesState::Unbounded,
2589        };
2590        // Coarse-only setter: every other component of the channel is left
2591        // exactly as it was.
2592        let precise = self.interface_mut().effects.memory.precise.take();
2593        self.set_memory_solved(coarse, precise);
2594    }
2595
2596    /// Records the C-prototype-derived external call interface on this function.
2597    /// See [`FunctionSignature::extern_interface`]; set by `external_sigs`,
2598    /// consumed by `argpromote_external`.
2599    pub fn set_extern_interface(&mut self, iface: crate::value::ExternInterface) {
2600        self.interface_mut()
2601            .signature
2602            .get_or_insert_default()
2603            .extern_interface = Some(iface);
2604    }
2605
2606    /// Records the C-prototype-derived argmem summary on this external. See
2607    /// [`FunctionSignature::argmem`]; set by `external_sigs`, read by the RAM
2608    /// effect channel's `external_leaf`.
2609    pub fn set_argmem(&mut self, argmem: crate::value::ExternArgmem) {
2610        self.interface_mut()
2611            .signature
2612            .get_or_insert_default()
2613            .argmem = Some(argmem);
2614    }
2615
2616    /// Records this function's register-channel effect state, preserving the
2617    /// memory channel (read-modify-write). See [`FunctionInterface::effects`].
2618    pub fn set_register_effects(&mut self, register: RegisterChannelState) {
2619        self.interface_mut().effects.register = register;
2620    }
2621
2622    /// Records this function's memory-channel effect state, preserving the
2623    /// register channel (read-modify-write). See [`FunctionInterface::effects`].
2624    ///
2625    /// Replaces **every** component of the memory channel. The channel has two
2626    /// independent writers — the effect solve owns `coarse`/`precise`, the RAM
2627    /// channel's rewrite owns `materialized` — so a caller that computes only
2628    /// one writer's components must not build a whole state and pass it here:
2629    /// the other writer's field would be silently lost. Use
2630    /// [`set_memory_solved`](Self::set_memory_solved) or
2631    /// [`set_memory_interface`](Self::set_memory_interface) instead.
2632    pub fn set_memory_effects(&mut self, memory: MemoryChannelState) {
2633        self.interface_mut().effects.memory = memory;
2634    }
2635
2636    /// Records the *solved* components of the memory channel — the coarse
2637    /// written-space verdict and the precise footprint the same solve derived —
2638    /// leaving the materialized interface untouched.
2639    ///
2640    /// The effect solve does not compute the interface, so it must not clear it.
2641    pub fn set_memory_solved(&mut self, coarse: WrittenSpacesState, precise: Option<Footprint>) {
2642        let memory = &mut self.interface_mut().effects.memory;
2643        memory.coarse = coarse;
2644        memory.precise = precise;
2645    }
2646
2647    /// Records the materialized memory interface, leaving the solved components
2648    /// untouched. `None` marks the memory channel as not materialized.
2649    pub fn set_memory_interface(&mut self, materialized: Option<MemoryInterfaceMap>) {
2650        self.interface_mut().effects.memory.materialized = materialized;
2651    }
2652
2653    /// Marks this function as fully functionalized over *every* side-effect
2654    /// channel — a deterministic pure function of its params. See
2655    /// [`FunctionSignature::is_pure`].
2656    pub fn set_is_pure(&mut self, value: bool) {
2657        self.interface_mut()
2658            .signature
2659            .get_or_insert_default()
2660            .is_pure = value;
2661    }
2662
2663    /// Records whether this function performs an unresolved/dynamic stack read.
2664    /// See [`FunctionSignature::reads_unbounded_stack`].
2665    pub fn set_reads_unbounded_stack(&mut self, value: bool) {
2666        self.interface_mut()
2667            .signature
2668            .get_or_insert_default()
2669            .reads_unbounded_stack = value;
2670    }
2671
2672    /// Records whether this function hands a pointer into its own frame to a
2673    /// callee that may read it unboundedly. See
2674    /// [`FunctionSignature::frame_escapes_to_unbounded`].
2675    pub fn set_frame_escapes_to_unbounded(&mut self, value: bool) {
2676        self.interface_mut()
2677            .signature
2678            .get_or_insert_default()
2679            .frame_escapes_to_unbounded = value;
2680    }
2681
2682    /// Records the address of a machine instruction lifted into this function.
2683    pub fn add_instruction_addr(&mut self, addr: u64) {
2684        self.inner_mut().instruction_addrs.insert(addr);
2685    }
2686
2687    /// Associates `block` with `function` by setting the block's `parent` field.
2688    ///
2689    /// With per-function block arenas, membership *is* arena ownership: a block
2690    /// lives in the arena of the function it was born into (`id.func`), and that
2691    /// must equal `self.id`. Ownership is derived from the arena, so this only
2692    /// ensures the roster lists the block; it no longer moves storage between
2693    /// functions.
2694    pub fn add_block(&mut self, id: BlockId) {
2695        assert_eq!(
2696            id.func, self.id,
2697            "cannot add a block stored in another function arena"
2698        );
2699        let local = id.localize(self.id);
2700        // Ensure the roster lists it exactly once (a freshly `make`d block is
2701        // auto-rostered, so this is usually a no-op).
2702        if !self.inner().roster.contains(&local) {
2703            self.inner_mut().roster.push(local);
2704        }
2705    }
2706}
2707
2708#[cfg(test)]
2709mod tests {
2710    use wazabin_qcode_macro::qcode;
2711
2712    use super::*;
2713
2714    fn foreign_block_fixture() -> (Context<'static>, FunctionId, BlockId) {
2715        let mut ctx = Context::new();
2716        let owner = FunctionBody::make(&mut ctx, "block_owner".into())
2717            .unwrap()
2718            .id;
2719        let destination = FunctionBody::make(&mut ctx, "block_destination".into())
2720            .unwrap()
2721            .id;
2722        let block = BasicBlock::make(&mut ctx, owner).id;
2723        (ctx, destination, block)
2724    }
2725
2726    #[test]
2727    fn raw_root_and_roster_are_local_while_refs_qualify_per_function() {
2728        let mut ctx = Context::new();
2729        let a = FunctionBody::make(&mut ctx, "local_root_a".into())
2730            .unwrap()
2731            .id;
2732        let b = FunctionBody::make(&mut ctx, "local_root_b".into())
2733            .unwrap()
2734            .id;
2735        let a_root = BasicBlock::make(&mut ctx, a).id;
2736        let b_root = BasicBlock::make(&mut ctx, b).id;
2737        assert_eq!(a_root.local, b_root.local, "arena-local ids should collide");
2738        FunctionBody::from_id_mut(&mut ctx, a)
2739            .set_root(a_root)
2740            .unwrap();
2741        FunctionBody::from_id_mut(&mut ctx, b)
2742            .set_root(b_root)
2743            .unwrap();
2744
2745        assert_eq!(ctx.bodies[a].root_id(), Some(a_root.local));
2746        assert_eq!(ctx.bodies[b].root_id(), Some(b_root.local));
2747        assert_eq!(ctx.bodies[a].roster, vec![a_root.local]);
2748        assert_eq!(ctx.bodies[b].roster, vec![b_root.local]);
2749        assert_eq!(
2750            FunctionBody::from_id(&ctx, a).root().map(|root| root.id),
2751            Some(a_root)
2752        );
2753        assert_eq!(
2754            FunctionBody::from_id(&ctx, b).root().map(|root| root.id),
2755            Some(b_root)
2756        );
2757    }
2758
2759    #[test]
2760    #[should_panic(expected = "cannot add a block stored in another function arena")]
2761    fn add_block_rejects_foreign_storage() {
2762        let (mut ctx, destination, block) = foreign_block_fixture();
2763        FunctionBody::from_id_mut(&mut ctx, destination).add_block(block);
2764    }
2765
2766    #[test]
2767    #[should_panic(expected = "cannot root a function at a block stored in another function arena")]
2768    fn set_root_rejects_foreign_storage() {
2769        let (mut ctx, destination, block) = foreign_block_fixture();
2770        FunctionBody::from_id_mut(&mut ctx, destination)
2771            .set_root(block)
2772            .unwrap();
2773    }
2774
2775    #[test]
2776    #[should_panic(expected = "cannot ensure a function root from another function arena")]
2777    fn ensure_root_rejects_foreign_storage() {
2778        let (mut ctx, destination, block) = foreign_block_fixture();
2779        FunctionBody::from_id_mut(&mut ctx, destination)
2780            .ensure_root(block)
2781            .unwrap();
2782    }
2783
2784    #[test]
2785    fn function_ref_users_of_rejects_foreign_owned_values() {
2786        let mut ctx = Context::new();
2787        qcode!(
2788            ctx,
2789            "
2790            fn users_a:
2791                <a_entry>
2792                    %a_def = i64 1 + i64 2;
2793                    %a_user = %a_def + i64 3;
2794                    return at %a_user;
2795
2796            fn users_b:
2797                <b_entry>
2798                    %b_def = i64 1 + i64 2;
2799                    %b_user = %b_def + i64 3;
2800                    return at %b_user;
2801            "
2802        );
2803
2804        let a_ids = FunctionRef::from_id(&ctx, users_a)
2805            .root()
2806            .unwrap()
2807            .instruction_ids();
2808        let a_def = ValueId::Instruction(a_ids[0]);
2809        assert_eq!(
2810            FunctionRef::from_id(&ctx, users_a).users_of(a_def),
2811            vec![a_ids[1]]
2812        );
2813        assert!(
2814            FunctionRef::from_id(&ctx, users_b)
2815                .users_of(a_def)
2816                .is_empty()
2817        );
2818
2819        let one = ctx.get_const(1, 8).id();
2820        assert!(!FunctionRef::from_id(&ctx, users_b).users_of(one).is_empty());
2821    }
2822
2823    fn colliding_body_ids() -> (
2824        Context<'static>,
2825        FunctionId,
2826        FunctionId,
2827        BlockId,
2828        BlockId,
2829        InstructionId,
2830        InstructionId,
2831        BlockParamId,
2832        BlockParamId,
2833    ) {
2834        let mut ctx = Context::new();
2835        qcode!(
2836            ctx,
2837            "
2838            fn raw_a:
2839                <a_entry @a:i64>
2840                    %a_def = i64 1 + i64 2;
2841                    return at %a_def;
2842            fn raw_b:
2843                <b_entry @b:i64>
2844                    %b_def = i64 1 + i64 2;
2845                    return at %b_def;
2846            "
2847        );
2848        let a_root = FunctionRef::from_id(&ctx, raw_a).root().unwrap();
2849        let b_root = FunctionRef::from_id(&ctx, raw_b).root().unwrap();
2850        let a_block = a_root.id;
2851        let b_block = b_root.id;
2852        let a_insn = a_root.instruction_ids()[0];
2853        let b_insn = b_root.instruction_ids()[0];
2854        let a_param = a_root.params().next().unwrap().id;
2855        let b_param = b_root.params().next().unwrap().id;
2856        assert_eq!(a_block.local, b_block.local);
2857        assert_eq!(a_insn.local, b_insn.local);
2858        assert_eq!(a_param.local, b_param.local);
2859        (
2860            ctx, raw_a, raw_b, a_block, b_block, a_insn, b_insn, a_param, b_param,
2861        )
2862    }
2863
2864    /// `replace_instruction(id, id)` must be a no-op: forwarding uses to itself
2865    /// does nothing, so deleting `id` would strand its still-live users. A pass
2866    /// that resolves an instruction to itself must leave it in place.
2867    #[test]
2868    fn replace_instruction_with_itself_is_a_noop() {
2869        let mut ctx = Context::new();
2870        qcode!(
2871            ctx,
2872            "
2873            fn f:
2874            <entry @a:i32>
2875                %x = @a + 1;
2876                %y = %x + 2;
2877                return %y;
2878            "
2879        );
2880        // `%x` is used by `%y`; find both.
2881        let root = FunctionBody::from_id(&ctx, f).root().unwrap().id;
2882        let insns: Vec<InstructionId> = BasicBlock::from_id(&ctx, root)
2883            .instruction_ids()
2884            .into_iter()
2885            .collect();
2886        let x = insns[0];
2887        let users_before = ctx.bodies[f].users_of(ValueId::Instruction(x));
2888        assert!(!users_before.is_empty(), "x should have a user (%y)");
2889
2890        // Replace x with itself — must not delete x or disturb its users.
2891        ctx.bodies[f].replace_instruction(x, ValueId::Instruction(x));
2892
2893        assert!(
2894            ctx.bodies[f].insns.contains(x.local),
2895            "x must survive a self-replacement"
2896        );
2897        assert_eq!(
2898            ctx.bodies[f].users_of(ValueId::Instruction(x)),
2899            users_before,
2900            "x's users must be unchanged"
2901        );
2902    }
2903
2904    #[test]
2905    fn body_users_of_rejects_foreign_owned_values() {
2906        let (ctx, a, b, _, _, a_insn, _, _, _) = colliding_body_ids();
2907        assert!(
2908            ctx.bodies[b]
2909                .users_of(ValueId::Instruction(a_insn))
2910                .is_empty()
2911        );
2912        assert!(
2913            !ctx.bodies[a]
2914                .users_of(ValueId::Instruction(a_insn))
2915                .is_empty()
2916        );
2917    }
2918
2919    #[test]
2920    #[should_panic(expected = "cannot replace uses of a value owned by another function")]
2921    fn body_replace_uses_rejects_foreign_old() {
2922        let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
2923        ctx.bodies[b]
2924            .replace_all_uses_with(ValueId::Instruction(a_insn), ValueId::Instruction(b_insn));
2925    }
2926
2927    #[test]
2928    #[should_panic(expected = "cannot replace uses with a value owned by another function")]
2929    fn body_replace_uses_rejects_foreign_new() {
2930        let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
2931        ctx.bodies[b]
2932            .replace_all_uses_with(ValueId::Instruction(b_insn), ValueId::Instruction(a_insn));
2933    }
2934
2935    #[test]
2936    #[should_panic(expected = "block belongs to another function")]
2937    fn body_block_access_rejects_colliding_foreign_id() {
2938        let (ctx, _, b, a_block, _, _, _, _, _) = colliding_body_ids();
2939        let _ = ctx.bodies[b].block(a_block);
2940    }
2941
2942    #[test]
2943    #[should_panic(expected = "instruction belongs to another function")]
2944    fn body_insn_access_rejects_colliding_foreign_id() {
2945        let (ctx, _, b, _, _, a_insn, _, _, _) = colliding_body_ids();
2946        let _ = ctx.bodies[b].insn(a_insn);
2947    }
2948
2949    #[test]
2950    #[should_panic(expected = "block parameter belongs to another function")]
2951    fn body_param_access_rejects_colliding_foreign_id() {
2952        let (ctx, _, b, _, _, _, _, a_param, _) = colliding_body_ids();
2953        let _ = ctx.bodies[b].block_param(a_param);
2954    }
2955
2956    // The `push_block` foreign-parent asserts are gone (stage 2): block ownership
2957    // is derived from the storing arena, so a block pushed into body `b` is owned
2958    // by `b` by construction — a foreign parent is unrepresentable.
2959
2960    #[test]
2961    fn make_function_creates_function_with_correct_name_root_address() {
2962        let mut ctx = Context::new();
2963        let f = FunctionBody::make(&mut ctx, "main".into()).unwrap();
2964        assert_eq!(f.name(), "main");
2965    }
2966
2967    #[test]
2968    fn get_function_by_name_returns_correct_function() {
2969        let mut ctx = Context::new();
2970        let id = FunctionBody::make(&mut ctx, "foo".into()).unwrap().id();
2971        let f = FunctionBody::from_name(&ctx, "foo").unwrap();
2972        assert_eq!(f.id(), id);
2973        assert_eq!(f.name(), "foo");
2974    }
2975
2976    #[test]
2977    fn get_function_by_name_returns_none_if_not_found() {
2978        let ctx = Context::new();
2979        assert!(FunctionBody::from_name(&ctx, "nonexistent").is_none());
2980    }
2981
2982    #[test]
2983    fn get_function_by_addr_returns_correct_function() {
2984        let mut ctx = Context::new();
2985        let id = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id();
2986        let addresses = crate::address_index::AddressIndex::analyze(&ctx);
2987        let f = FunctionBody::from_id(&ctx, addresses.function_at(0x2000).unwrap());
2988        assert_eq!(f.id(), id);
2989        assert_eq!(f.address(), Some(0x2000));
2990        assert_eq!(f.name(), "fn_2000");
2991    }
2992
2993    #[test]
2994    fn get_function_by_addr_returns_none_if_missing() {
2995        let ctx = Context::new();
2996        let addresses = crate::address_index::AddressIndex::analyze(&ctx);
2997        assert!(addresses.function_at(0xdeadbeef).is_none());
2998    }
2999
3000    #[test]
3001    fn add_block_via_function_mut_ref_updates_blocks_list() {
3002        let mut ctx = Context::new();
3003        let baz_id = FunctionBody::make(&mut ctx, "baz".into()).unwrap().id;
3004        let root = BasicBlock::make(&mut ctx, baz_id).id;
3005        let extra = BasicBlock::make(&mut ctx, baz_id).id;
3006
3007        let mut baz = FunctionBody::from_id_mut(&mut ctx, baz_id);
3008        baz.add_block(root);
3009        baz.add_block(extra);
3010
3011        let block_ids: Vec<_> = baz.blocks().map(|b| b.id).collect();
3012        assert!(block_ids.contains(&root));
3013        assert!(block_ids.contains(&extra));
3014    }
3015
3016    #[test]
3017    fn display_shows_function_name_and_block_contents() {
3018        let mut ctx = Context::new();
3019        FunctionBody::make(&mut ctx, "display_test".into()).unwrap();
3020
3021        let f = FunctionBody::from_name(&ctx, "display_test").unwrap();
3022
3023        let s = f.to_string();
3024        assert!(s.contains("fn display_test:"));
3025    }
3026
3027    #[test]
3028    fn iter_yields_all_blocks() {
3029        let mut ctx = Context::new();
3030        let f_id = FunctionBody::make(&mut ctx, "iter_fn".into()).unwrap().id;
3031        let root = BasicBlock::make(&mut ctx, f_id).id;
3032        let extra = BasicBlock::make(&mut ctx, f_id).id;
3033        let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
3034        f.add_block(root);
3035        f.add_block(extra);
3036
3037        let f = FunctionBody::from_name(&ctx, "iter_fn").unwrap();
3038        let ids: Vec<_> = f.iter().map(|b| b.id).collect();
3039        assert!(ids.contains(&root));
3040        assert!(ids.contains(&extra));
3041    }
3042
3043    #[test]
3044    fn into_iterator_for_function_ref_matches_iter() {
3045        let mut ctx = Context::new();
3046        let f_id = FunctionBody::make(&mut ctx, "into_iter_fn".into())
3047            .unwrap()
3048            .id;
3049        let b1 = BasicBlock::make(&mut ctx, f_id).id;
3050        let b2 = BasicBlock::make(&mut ctx, f_id).id;
3051        let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
3052        f.add_block(b1);
3053        f.add_block(b2);
3054
3055        let f = FunctionBody::from_name(&ctx, "into_iter_fn").unwrap();
3056        let mut via_iter: Vec<usize> = f.iter().map(|b| usize::from(b.id.local)).collect();
3057        let mut via_into: Vec<usize> = (&f).into_iter().map(|b| usize::from(b.id.local)).collect();
3058        via_iter.sort();
3059        via_into.sort();
3060        assert_eq!(via_iter, via_into);
3061    }
3062
3063    #[test]
3064    fn qcode_fn_single_block_populates_function() {
3065        let mut ctx = Context::new();
3066        qcode!(
3067            ctx,
3068            "
3069            fn simple:
3070                <entry>
3071                    return at 0;
3072            "
3073        );
3074
3075        let f = FunctionBody::from_name(&ctx, "simple").unwrap();
3076        assert_eq!(f.name(), "simple");
3077        assert!(f.root().is_some());
3078        assert_eq!(f.root().unwrap().name().unwrap(), "entry");
3079        assert_eq!(f.blocks().count(), 1);
3080    }
3081
3082    #[test]
3083    fn qcode_fn_multi_block_populates_all_blocks() {
3084        let mut ctx = Context::new();
3085        qcode!(
3086            ctx,
3087            "
3088            fn multiblock:
3089                <bb1>
3090                    if i8 1 goto <bb2> else goto <bb3>;
3091
3092                <bb2>
3093                    goto <bb3>;
3094
3095                <bb3>
3096                    return at 0;
3097            "
3098        );
3099
3100        let f = FunctionBody::from_name(&ctx, "multiblock").unwrap();
3101        assert_eq!(f.root().unwrap().name().unwrap(), "bb1");
3102        let block_names: Vec<_> = f.blocks().filter_map(|b| b.name()).collect();
3103        assert!(block_names.contains(&"bb1"), "missing bb1");
3104        assert!(block_names.contains(&"bb2"), "missing bb2");
3105        assert!(block_names.contains(&"bb3"), "missing bb3");
3106        assert_eq!(f.blocks().count(), 3);
3107    }
3108
3109    #[test]
3110    fn qcode_fn_id_variable_is_set() {
3111        let mut ctx = Context::new();
3112        qcode!(
3113            ctx,
3114            "
3115            fn myfn:
3116                <start>
3117                    return at 0;
3118            "
3119        );
3120
3121        let by_name = FunctionBody::from_name(&ctx, "myfn").unwrap();
3122        assert_eq!(by_name.name(), "myfn");
3123    }
3124
3125    /// Split construction lets a rootless function temporarily win an address
3126    /// occupied by a block that has not yet been rehomed into its arena.
3127    #[test]
3128    fn indexed_address_registration_keeps_foreign_block_rootless() {
3129        let mut ctx = Context::new();
3130
3131        // Simulate a branch-target block created at 0x1000 before the function
3132        // stub exists (as happens with tail-jumps to sibling functions).
3133        let block_id = {
3134            let __f = ctx.anon_function();
3135            BasicBlock::make(&mut ctx, __f)
3136        }
3137        .id;
3138        let mut addresses = crate::address_index::AddressIndex::analyze(&ctx);
3139        addresses
3140            .register(
3141                &mut ctx,
3142                0x1000,
3143                crate::address_index::AddressTarget::Block(block_id),
3144            )
3145            .unwrap();
3146
3147        let fn_id = FunctionBody::make(&mut ctx, "fn_1000".into()).unwrap().id;
3148        addresses
3149            .register(
3150                &mut ctx,
3151                0x1000,
3152                crate::address_index::AddressTarget::Function(fn_id),
3153            )
3154            .unwrap();
3155
3156        assert_eq!(addresses.function_at(0x1000), Some(fn_id));
3157        assert_eq!(addresses.block_at(0x1000), None);
3158        assert!(FunctionBody::from_id(&ctx, fn_id).root().is_none());
3159        assert_ne!(block_id.func, fn_id);
3160    }
3161}
3162
3163#[cfg(test)]
3164mod memory_interface_tests {
3165    use super::*;
3166
3167    fn slot() -> InterfaceSlot {
3168        InterfaceSlot {
3169            base: SlotBase::Arg(0),
3170            offset: 8,
3171            size: 8,
3172        }
3173    }
3174
3175    /// The interface survives a round-trip through the snapshot wire format.
3176    ///
3177    /// Back-compat is *not* tested here and is not provided: the payload is
3178    /// bincode under a hard version lock (`session.rs` `FORMAT_VERSION`, bumped
3179    /// for this field), so snapshots written before it are rejected outright
3180    /// rather than defaulted.
3181    #[test]
3182    fn memory_interface_round_trips_through_the_wire_format() {
3183        let state = MemoryChannelState {
3184            materialized: Some(MemoryInterfaceMap {
3185                inputs: vec![slot()],
3186                outputs: vec![InterfaceSlot {
3187                    base: SlotBase::Global(0x2000),
3188                    offset: 0,
3189                    size: 4,
3190                }],
3191            }),
3192            ..MemoryChannelState::default()
3193        };
3194        let config = bincode::config::standard();
3195        let bytes = bincode::serde::encode_to_vec(&state, config).expect("encode memory state");
3196        let (decoded, _): (MemoryChannelState, _) =
3197            bincode::serde::decode_from_slice(&bytes, config).expect("decode memory state");
3198        assert_eq!(decoded, state);
3199    }
3200
3201    /// A default (unmaterialized) channel reports no interface.
3202    #[test]
3203    fn default_memory_state_is_not_materialized() {
3204        assert_eq!(MemoryChannelState::default().materialized(), None);
3205    }
3206
3207    /// The coarse-only setter must not disturb the other two components: a
3208    /// re-stamp of the written-space set is not a re-materialization.
3209    #[test]
3210    fn stamping_written_spaces_preserves_the_materialized_interface() {
3211        let mut ctx = Context::new();
3212        let fid = FunctionBody::make(&mut ctx, "keeps_interface".into())
3213            .unwrap()
3214            .id;
3215        let map = MemoryInterfaceMap {
3216            inputs: vec![slot()],
3217            outputs: vec![],
3218        };
3219        let mut body = FunctionBody::from_id_mut(&mut ctx, fid);
3220        body.set_memory_effects(MemoryChannelState {
3221            materialized: Some(map.clone()),
3222            ..MemoryChannelState::default()
3223        });
3224        body.set_written_spaces(None);
3225
3226        let effects = FunctionBody::from_id(&ctx, fid).effects().memory.clone();
3227        assert_eq!(effects.materialized(), Some(&map));
3228        assert_eq!(effects.coarse, WrittenSpacesState::Unbounded);
3229    }
3230
3231    /// `Unmappable` and `Global` are both address-less bases, but only the
3232    /// second is bindable. A consumer that collapsed them would synthesize a
3233    /// load from a bogus absolute address for a base it never resolved.
3234    #[test]
3235    fn an_unmappable_base_is_distinct_from_a_global_and_is_not_bindable() {
3236        let unmappable = InterfaceSlot {
3237            base: SlotBase::Unmappable,
3238            offset: 0,
3239            size: 8,
3240        };
3241        let global = InterfaceSlot {
3242            base: SlotBase::Global(0),
3243            offset: 0,
3244            size: 8,
3245        };
3246        assert_ne!(unmappable, global);
3247        assert!(!unmappable.is_bindable());
3248        assert!(global.is_bindable());
3249        assert!(
3250            InterfaceSlot {
3251                base: SlotBase::Arg(0),
3252                offset: -8,
3253                size: 8,
3254            }
3255            .is_bindable()
3256        );
3257    }
3258}