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    /// Whether `id` names a temporary space in this body.
973    pub fn contains_temp_space(&self, id: TempSpaceId) -> bool {
974        id.func == self.id() && usize::from(id.local) < self.temp_spaces.len()
975    }
976
977    /// Resolves a qualified temporary-value ID against this body.
978    #[track_caller]
979    pub fn temp(&self, id: TempId) -> &Temp<'str> {
980        assert_eq!(id.func, self.id(), "temporary belongs to another function");
981        debug_assert!(
982            self.contains_temp(id),
983            "missing temporary {id:?} in function {:?} (arena length {})",
984            self.id(),
985            self.temps.len()
986        );
987        &self.temps[id.local]
988    }
989
990    /// Whether `id` names a temporary value in this body.
991    pub fn contains_temp(&self, id: TempId) -> bool {
992        id.func == self.id() && usize::from(id.local) < self.temps.len()
993    }
994
995    /// Physically removes a block parameter and its local bookkeeping.
996    /// Positional block and edge-argument rewrites belong to the caller. Those
997    /// rewrites may occur later in the same transformation, so outstanding uses
998    /// are allowed while the transformation is in progress.
999    pub fn remove_block_param(&mut self, id: BlockParamId) {
1000        assert!(
1001            self.contains_block_param(id),
1002            "cannot remove stale param {id:?}"
1003        );
1004        let key = ValueId::BlockParam(id).strip_func();
1005        let name = self.params[id.local].name.clone();
1006        if let Some(name) = name {
1007            self.names.forget(name.as_ref());
1008        }
1009        self.users.remove(&key);
1010        self.params.remove(id.local);
1011    }
1012    /// The CFG edge `id`, by its function-local index.
1013    pub fn edge(&self, id: EdgeId) -> &EdgeData {
1014        &self.edges[id]
1015    }
1016
1017    // ---- structural mutation verbs (context-split stage 5b-ii(b)) -----------
1018    //
1019    // The single-homed IR mutation surface for a function *body* (design ruling
1020    // 6). Each verb operates directly on this body's own arenas, reading shared
1021    // data (types for minting) through an explicit `&Context` where needed. These
1022    // are the algorithm bodies formerly living on the checked-out mutation path
1023    // (`value::util::body_mut`), ported here with the routing indirection dropped:
1024    // `self.function_mut(f)` collapses to `self`, `self.view()` to `self`'s
1025    // own arena accessors. The owning [`FunctionId`] comes from [`id`](Self::id).
1026
1027    /// Push a fresh instruction into this body's arena, recording each operand's
1028    /// use in the reverse-use map.
1029    pub fn push_insn(&mut self, insn: Instruction<'str>) -> InstructionId {
1030        InstructionId::new(self.id(), self.push_insn_local(insn))
1031    }
1032
1033    /// Push a fresh instruction into this body's arena, recording each operand's
1034    /// use in the reverse-use map, and return its **body-local** id. The id-less
1035    /// twin of [`push_insn`](Self::push_insn), usable on a detached body.
1036    pub fn push_insn_local(&mut self, insn: Instruction<'str>) -> LocalInsnId {
1037        let args: Vec<LocalValueId> = insn.mnemonic().args().into_iter().collect();
1038        let local = self.insns.push(insn);
1039        for arg in args {
1040            self.users.entry(arg).or_default().push(local);
1041        }
1042        local
1043    }
1044
1045    /// Push a fresh block into this body's arena and onto its ownership roster.
1046    /// Ownership is derived from the storing arena: the returned id's `func` is
1047    /// this body's own id.
1048    pub fn push_block(&mut self, block: BasicBlock<'str>) -> BlockId {
1049        let func = self.id();
1050        BlockId::new(func, self.push_block_local(block))
1051    }
1052
1053    /// Push a fresh block into this body's arena and roster, returning its
1054    /// **body-local** id. The id-less twin of [`push_block`](Self::push_block),
1055    /// usable on a detached (uninstalled) body.
1056    pub fn push_block_local(&mut self, block: BasicBlock<'str>) -> LocalBlockId {
1057        let local = self.blocks.push(block);
1058        self.roster.push(local);
1059        local
1060    }
1061
1062    /// Mint a fresh empty block, owned by this function (arena membership) and
1063    /// rostered.
1064    pub fn make_block(&mut self) -> BlockId {
1065        self.push_block(BasicBlock::detached())
1066    }
1067
1068    /// Mint a fresh empty block, returning its **body-local** id. The id-less twin
1069    /// of [`make_block`](Self::make_block), usable on a detached body.
1070    pub fn make_block_local(&mut self) -> LocalBlockId {
1071        self.push_block_local(BasicBlock::detached())
1072    }
1073
1074    /// This body's block `block`, by its function-local index (id-free read,
1075    /// usable on a detached body).
1076    pub fn block_local(&self, block: LocalBlockId) -> &BasicBlock<'str> {
1077        &self.blocks[block]
1078    }
1079
1080    /// This body's instruction mnemonic, by its function-local index (id-free
1081    /// read, usable on a detached body).
1082    pub fn mnemonic_local(&self, insn: LocalInsnId) -> &Mnemonic {
1083        self.insns[insn].mnemonic()
1084    }
1085
1086    /// Push a fresh block parameter into this body's arena.
1087    pub fn push_block_param(&mut self, param: BlockParam<'str>) -> BlockParamId {
1088        let local = self.params.push(param);
1089        BlockParamId::new(self.id(), local)
1090    }
1091
1092    /// Push a fresh block parameter into this body's arena and wire it into
1093    /// `block`'s parameter list, returning its **body-local** id. The id-less twin
1094    /// of [`push_block_param`](Self::push_block_param), usable on a detached body.
1095    pub fn push_block_param_local(
1096        &mut self,
1097        block: LocalBlockId,
1098        param: BlockParam<'str>,
1099    ) -> LocalParamId {
1100        let local = self.params.push(param);
1101        self.blocks[block].params.push(local);
1102        local
1103    }
1104
1105    /// Append an already-created instruction to the end of `block`, setting its
1106    /// parent (id-free; the mutation twin of [`BaseRef::push_insn`], usable on a
1107    /// detached body).
1108    pub fn append_insn_local(&mut self, block: LocalBlockId, insn: LocalInsnId) {
1109        self.insns[insn].parent = Some(block);
1110        self.blocks[block].instructions.push(insn);
1111    }
1112
1113    /// Mint an `Int(size)`-typed instruction with `mnemonic` (the type is minted
1114    /// in `shared`'s interner through its `&self` path).
1115    pub fn push_mnemonic(
1116        &mut self,
1117        shared: &crate::context::Shared<'str>,
1118        mnemonic: Mnemonic,
1119        size: usize,
1120    ) -> InstructionId {
1121        let type_id = shared.types.get_or_make_int(size);
1122        let insn = Instruction::new(type_id, mnemonic);
1123        self.push_insn(insn)
1124    }
1125
1126    /// Mint an instruction with `mnemonic` and an explicit result `type_id`.
1127    pub fn push_mnemonic_with_type(
1128        &mut self,
1129        mnemonic: Mnemonic,
1130        type_id: crate::types::TypeId,
1131    ) -> InstructionId {
1132        let insn = Instruction::new(type_id, mnemonic);
1133        self.push_insn(insn)
1134    }
1135
1136    /// Mint an instruction with `mnemonic` and an explicit result `type_id`,
1137    /// returning its **body-local** id. The id-less twin of
1138    /// [`push_mnemonic_with_type`](Self::push_mnemonic_with_type).
1139    pub fn push_mnemonic_with_type_local(
1140        &mut self,
1141        mnemonic: Mnemonic,
1142        type_id: crate::types::TypeId,
1143    ) -> LocalInsnId {
1144        self.push_insn_local(Instruction::new(type_id, mnemonic))
1145    }
1146
1147    /// Insert `insn` immediately before `before` in `block`. Panics if `before`
1148    /// is not in `block`.
1149    pub fn insert_insn_before(
1150        &mut self,
1151        block: BlockId,
1152        before: InstructionId,
1153        insn: InstructionId,
1154    ) {
1155        let index = self
1156            .block(block)
1157            .instructions
1158            .iter()
1159            .position(|&local| InstructionId::new(block.func, local) == before)
1160            .expect("before not in block");
1161        self.insn_mut(insn).parent = Some(block.local);
1162        self.block_mut(block)
1163            .instructions
1164            .insert(index, insn.localize(block.func));
1165    }
1166
1167    /// Move the live, non-terminator instruction `insn` immediately before the
1168    /// live instruction `before`, inferring the destination block from
1169    /// `before`. The moved instruction keeps its ID, payload, name, and use-map
1170    /// entries. Supports both cross-block motion and reordering within one
1171    /// block.
1172    pub fn move_insn_before(&mut self, insn: InstructionId, before: InstructionId) {
1173        let id = self.id();
1174        assert_eq!(insn.func, id, "instruction belongs to another function");
1175        assert_eq!(
1176            before.func, id,
1177            "anchor instruction belongs to another function"
1178        );
1179        if insn == before {
1180            return;
1181        }
1182        assert!(
1183            !self.insn(insn).mnemonic().is_terminator(),
1184            "moving a terminator requires updating its CFG edges"
1185        );
1186
1187        let source = self
1188            .insn(insn)
1189            .parent
1190            .map(|local| BlockId::new(id, local))
1191            .expect("moved instruction must belong to a block");
1192        let target = self
1193            .insn(before)
1194            .parent
1195            .map(|local| BlockId::new(id, local))
1196            .expect("anchor instruction must belong to a block");
1197        let source_index = self
1198            .block(source)
1199            .instructions
1200            .iter()
1201            .position(|&local| local == insn.local)
1202            .expect("moved instruction missing from its parent block");
1203        let before_index = self
1204            .block(target)
1205            .instructions
1206            .iter()
1207            .position(|&local| local == before.local)
1208            .expect("anchor instruction missing from its parent block");
1209        let insert_index = if source == target && source_index < before_index {
1210            before_index - 1
1211        } else {
1212            before_index
1213        };
1214
1215        self.block_mut(source).instructions.remove(source_index);
1216        self.block_mut(target)
1217            .instructions
1218            .insert(insert_index, insn.local);
1219        self.insn_mut(insn).parent = Some(target.local);
1220    }
1221
1222    /// Add a directed CFG edge `from -> to`, stored in this body's edge arena and
1223    /// linked into both incident blocks' edge sets.
1224    pub fn add_cfg_edge(&mut self, from: BlockId, to: BlockId) -> EdgeId {
1225        self.add_cfg_edge_local(from.local, to.local)
1226    }
1227
1228    /// Add a directed CFG edge `from -> to` over **body-local** block ids (id-free;
1229    /// the twin of [`add_cfg_edge`](Self::add_cfg_edge), usable on a detached body).
1230    pub fn add_cfg_edge_local(&mut self, from: LocalBlockId, to: LocalBlockId) -> EdgeId {
1231        let edge_id = self.edges.push(EdgeData { from, to });
1232        self.blocks[from].edges.insert(edge_id);
1233        self.blocks[to].edges.insert(edge_id);
1234        edge_id
1235    }
1236
1237    /// Remove CFG edge `edge_id`, unlinking it from both incident blocks and
1238    /// physically dropping its payload.
1239    pub fn remove_cfg_edge(&mut self, edge_id: EdgeId) {
1240        let EdgeData { from, to } = *self.edge(edge_id);
1241        let func = self.id();
1242        self.block_mut(BlockId::new(func, from))
1243            .edges
1244            .remove(&edge_id);
1245        self.block_mut(BlockId::new(func, to))
1246            .edges
1247            .remove(&edge_id);
1248        self.edges.remove(edge_id);
1249    }
1250
1251    /// Replace every use of `old` with `new` across this body's instructions and
1252    /// update the reverse use-map (SSA defs only; `old` is intra-function).
1253    pub fn replace_all_uses_with(&mut self, old: ValueId, new: ValueId) {
1254        if old == new {
1255            return;
1256        }
1257        let Some(func) = old.owning_function() else {
1258            return;
1259        };
1260        assert_eq!(
1261            func,
1262            self.id(),
1263            "cannot replace uses of a value owned by another function"
1264        );
1265        if let Some(new_owner) = new.owning_function() {
1266            assert_eq!(
1267                new_owner,
1268                self.id(),
1269                "cannot replace uses with a value owned by another function"
1270            );
1271        }
1272        let users = self.users_of(old);
1273        let old = old.localize(func);
1274        let new = new.localize(func);
1275        for user in users {
1276            self.insn_mut(user).mnemonic_mut().replace_value(old, new);
1277            self.users.entry(new).or_default().push(user.localize(func));
1278        }
1279        self.users.remove(&old);
1280    }
1281
1282    /// Replace every use of instruction `id` with `new`, then remove `id` —
1283    /// the standard "rewrite to a cheaper value" epilogue
1284    /// ([`replace_all_uses_with`](Self::replace_all_uses_with) +
1285    /// [`remove_instruction`](Self::remove_instruction)).
1286    pub fn replace_instruction(&mut self, id: InstructionId, new: ValueId) {
1287        // Replacing an instruction with itself is a contradiction: the use
1288        // forwarding is a no-op, so removing `id` would delete a value that is
1289        // still referenced. Leave it in place.
1290        if new == ValueId::Instruction(id) {
1291            return;
1292        }
1293        self.replace_all_uses_with(ValueId::Instruction(id), new);
1294        self.remove_instruction(id);
1295    }
1296
1297    /// Physically removes a set of instructions after pruning their operands
1298    /// from the reverse-use map. Call after removing them from their parent
1299    /// blocks and unlinking any CFG edges owned by terminators.
1300    pub fn remove_instructions(&mut self, dead: &FxHashSet<LocalInsnId>) {
1301        let mut ids: Vec<_> = dead.iter().copied().collect();
1302        ids.sort_unstable();
1303        let mut affected_args: FxHashSet<LocalValueId> = FxHashSet::default();
1304        for &id in &ids {
1305            assert!(
1306                self.insns.contains(id),
1307                "cannot remove stale instruction {id:?}"
1308            );
1309            affected_args.extend(self.insns[id].mnemonic().args());
1310        }
1311        for arg in affected_args {
1312            let remove_key = if let Some(users) = self.users.get_mut(&arg) {
1313                users.retain(|local| !dead.contains(local));
1314                users.is_empty()
1315            } else {
1316                false
1317            };
1318            if remove_key {
1319                self.users.remove(&arg);
1320            }
1321        }
1322        for id in ids {
1323            self.users.remove(&LocalValueId::Instruction(id));
1324            self.insns.remove(id);
1325        }
1326    }
1327
1328    /// Remove instruction `id` from its block, unlink its outgoing CFG edges if a
1329    /// terminator, clear its name, prune its operand use-lists, and physically
1330    /// drop its payload.
1331    pub fn remove_instruction(&mut self, id: InstructionId) {
1332        assert_eq!(
1333            id.func,
1334            self.id(),
1335            "instruction belongs to another function"
1336        );
1337        let func = self.id();
1338        let (parent, name, is_terminator, args) = {
1339            let insn = self.insn(id);
1340            (
1341                insn.parent.map(|l| BlockId::new(self.id(), l)),
1342                insn.name.clone(),
1343                insn.mnemonic().is_terminator(),
1344                insn.mnemonic().args().into_iter().collect::<Vec<_>>(),
1345            )
1346        };
1347
1348        if let Some(block_id) = parent {
1349            self.block_mut(block_id)
1350                .instructions
1351                .retain(|&local| local != id.localize(block_id.func));
1352            if is_terminator {
1353                let mut succ: Vec<EdgeId> = {
1354                    let block = self.block(block_id);
1355                    block
1356                        .edges
1357                        .iter()
1358                        .copied()
1359                        .filter(|&e| self.edge(e).from == block_id.local)
1360                        .collect()
1361                };
1362                succ.sort_unstable();
1363                for edge_id in succ {
1364                    self.remove_cfg_edge(edge_id);
1365                }
1366            }
1367        }
1368
1369        if let Some(n) = name {
1370            self.names.forget(n.as_ref());
1371        }
1372        for arg in args {
1373            let remove_key = if let Some(users) = self.users.get_mut(&arg) {
1374                users.retain(|&local| local != id.localize(func));
1375                users.is_empty()
1376            } else {
1377                false
1378            };
1379            if remove_key {
1380                self.users.remove(&arg);
1381            }
1382        }
1383        self.users.remove(&ValueId::Instruction(id).strip_func());
1384        self.insns.remove(id.local);
1385    }
1386
1387    /// Removes several non-terminator instructions of one block at once.
1388    ///
1389    /// [`remove_instruction`](Self::remove_instruction) walks the block's
1390    /// instruction list to unlink each one, so removing *n* of them costs
1391    /// `n × block`. Lifting an absorbed guest basic block deletes hundreds of
1392    /// instructions from a block hundreds long, and that product was a real
1393    /// share of translation time. Here the list is walked once however many go.
1394    ///
1395    /// Terminators are rejected rather than handled: removing one has to tear
1396    /// down CFG edges too, and no caller of this deletes one — dead-code
1397    /// elimination will not touch a terminator, and store forwarding removes
1398    /// only loads and stores.
1399    pub fn remove_block_instructions(&mut self, block_id: BlockId, dead: &FxHashSet<LocalInsnId>) {
1400        assert_eq!(
1401            block_id.func,
1402            self.id(),
1403            "block belongs to another function"
1404        );
1405        if dead.is_empty() {
1406            return;
1407        }
1408
1409        let mut names = Vec::new();
1410        for &id in dead {
1411            let insn = &self.insns[id];
1412            assert!(
1413                !insn.mnemonic().is_terminator(),
1414                "bulk removal does not unlink CFG edges; {id:?} is a terminator"
1415            );
1416            if let Some(name) = insn.name.clone() {
1417                names.push(name);
1418            }
1419        }
1420
1421        self.block_mut(block_id)
1422            .instructions
1423            .retain(|local| !dead.contains(local));
1424        self.purge_instructions(dead, names);
1425    }
1426
1427    /// Forgets `dead`'s names, prunes them from every user list they appear in,
1428    /// and drops their payloads.
1429    ///
1430    /// The shared tail of removing instructions in bulk. It does not touch any
1431    /// block's instruction list — the caller has already dealt with that, which
1432    /// is the whole point: doing it per instruction is what makes removal
1433    /// quadratic in the size of the block.
1434    fn purge_instructions(&mut self, dead: &FxHashSet<LocalInsnId>, names: Vec<Cow<'str, str>>) {
1435        for name in names {
1436            self.names.forget(name.as_ref());
1437        }
1438        // Each operand's user list is pruned once, not once per dead user.
1439        let mut operands: FxHashSet<LocalValueId> = FxHashSet::default();
1440        for &id in dead {
1441            operands.extend(self.insns[id].mnemonic().args());
1442        }
1443        for arg in operands {
1444            let now_empty = if let Some(users) = self.users.get_mut(&arg) {
1445                users.retain(|local| !dead.contains(local));
1446                users.is_empty()
1447            } else {
1448                false
1449            };
1450            if now_empty {
1451                self.users.remove(&arg);
1452            }
1453        }
1454        for &id in dead {
1455            self.users.remove(&LocalValueId::Instruction(id));
1456            self.insns.remove(id);
1457        }
1458    }
1459
1460    /// Rehome `remove`'s outgoing CFG edges onto `keep`. The direct edge and
1461    /// `keep`'s forwarding terminator have already been removed by the caller.
1462    pub fn rehome_outgoing_edges(&mut self, keep: BlockId, remove: BlockId) {
1463        let outgoing: Vec<EdgeId> = {
1464            let block = self.block(remove);
1465            block
1466                .edges
1467                .iter()
1468                .copied()
1469                .filter(|&e| self.edge(e).from == remove.local)
1470                .collect()
1471        };
1472        for eid in outgoing {
1473            self.edges[eid].from = keep.local;
1474            self.block_mut(keep).edges.insert(eid);
1475            self.block_mut(remove).edges.remove(&eid);
1476        }
1477    }
1478
1479    /// Replace an instruction's mnemonic in place, keeping the reverse use-map in
1480    /// sync.
1481    pub fn replace_instruction_mnemonic(&mut self, id: InstructionId, mnemonic: Mnemonic) {
1482        assert_eq!(
1483            id.func,
1484            self.id(),
1485            "instruction belongs to another function"
1486        );
1487        self.replace_instruction_mnemonic_local(id.local, mnemonic);
1488    }
1489
1490    /// Replace an instruction's mnemonic in place, keeping the reverse use-map in
1491    /// sync, over a **body-local** instruction id (id-free; the twin of
1492    /// [`replace_instruction_mnemonic`](Self::replace_instruction_mnemonic),
1493    /// usable on a detached body).
1494    pub fn replace_instruction_mnemonic_local(&mut self, id: LocalInsnId, mnemonic: Mnemonic) {
1495        let old_args = self.insns[id]
1496            .mnemonic()
1497            .args()
1498            .into_iter()
1499            .collect::<Vec<_>>();
1500        for arg in old_args {
1501            let now_empty = if let Some(users) = self.users.get_mut(&arg) {
1502                users.retain(|&local| local != id);
1503                users.is_empty()
1504            } else {
1505                false
1506            };
1507            if now_empty {
1508                self.users.remove(&arg);
1509            }
1510        }
1511        *self.insns[id].mnemonic_mut() = mnemonic;
1512        let new_args = self.insns[id]
1513            .mnemonic()
1514            .args()
1515            .into_iter()
1516            .collect::<Vec<_>>();
1517        for arg in new_args {
1518            self.users.entry(arg).or_default().push(id);
1519        }
1520    }
1521
1522    /// Set `block`'s name and register it in this body's local name table, over a
1523    /// **body-local** block id (id-free; the twin of
1524    /// [`BaseRef::rename_local`](crate::value::util::base_ref::BaseRef::rename_local)
1525    /// restricted to the id-free local parts, usable on a detached body). Errors
1526    /// only on a duplicate name.
1527    pub fn rename_block_local(&mut self, block: LocalBlockId, name: Cow<'str, str>) -> Result<()> {
1528        let target = LocalValueId::BasicBlock(block);
1529        if let Some(existing) = self.names.get(&name) {
1530            return if existing == target {
1531                Ok(())
1532            } else {
1533                Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
1534            };
1535        }
1536        let old_name = self.blocks[block].local_name().map(str::to_owned);
1537        self.names
1538            .register(name.clone(), target, old_name.as_deref())?;
1539        self.blocks[block].set_name(Some(name));
1540        Ok(())
1541    }
1542
1543    /// Drop `block` from this body's ownership roster. Ownership is derived from
1544    /// the storing arena (`block.func`).
1545    pub fn unroster_block(&mut self, block: BlockId) {
1546        self.roster.retain(|&b| b != block.localize(block.func));
1547    }
1548
1549    /// Remove `block` from this body: unlink every incident CFG edge, remove its
1550    /// instructions and params, clear ownership metadata, then drop its payload.
1551    /// Empties `block` of code, keeping the block itself.
1552    ///
1553    /// Only the *outgoing* edges go, because those are owned by the terminator
1554    /// being removed; the incoming ones belong to other blocks' terminators,
1555    /// which still name this block and must keep resolving to it. That is the
1556    /// point of clearing rather than deleting: every branch already targeting
1557    /// this block stays valid while its contents are rebuilt.
1558    pub fn clear_block_instructions(&mut self, block: BlockId) {
1559        assert_eq!(block.func, self.id(), "block belongs to another function");
1560        let mut outgoing: Vec<EdgeId> = self
1561            .block(block)
1562            .edges
1563            .iter()
1564            .copied()
1565            .filter(|&edge| self.edges[edge].from == block.local)
1566            .collect();
1567        outgoing.sort_unstable();
1568        for edge in outgoing {
1569            self.remove_cfg_edge(edge);
1570        }
1571        // The list is emptied in one move and the instructions purged as a
1572        // set. Removing them one at a time means re-scanning the very list
1573        // being emptied for each one, which is quadratic — and the blocks this
1574        // clears are absorbed guest basic blocks, thousands of instructions
1575        // long. Splitting one used to cost more than lifting it did.
1576        let insns = std::mem::take(&mut self.block_mut(block).instructions);
1577        let dead: FxHashSet<LocalInsnId> = insns.iter().copied().collect();
1578        let names: Vec<Cow<'str, str>> = insns
1579            .iter()
1580            .filter_map(|&local| self.insns[local].name.clone())
1581            .collect();
1582        self.purge_instructions(&dead, names);
1583    }
1584
1585    pub fn delete_block(&mut self, block: BlockId) {
1586        assert_eq!(block.func, self.id(), "block belongs to another function");
1587        let mut edges: Vec<EdgeId> = self.block(block).edges.iter().copied().collect();
1588        edges.sort_unstable();
1589        for edge in edges {
1590            self.remove_cfg_edge(edge);
1591        }
1592        let insns: Vec<InstructionId> = self
1593            .block(block)
1594            .instructions
1595            .iter()
1596            .map(|&local| InstructionId::new(self.id(), local))
1597            .collect();
1598        for insn in insns {
1599            self.remove_instruction(insn);
1600        }
1601        let params: Vec<BlockParamId> = self
1602            .block(block)
1603            .params
1604            .iter()
1605            .map(|&local| BlockParamId::new(self.id(), local))
1606            .collect();
1607        for param in params {
1608            self.remove_block_param(param);
1609        }
1610        let name = self.block(block).local_name().map(str::to_owned);
1611        self.unroster_block(block);
1612        if self.root == Some(block.local) {
1613            self.root = None;
1614        }
1615        if let Some(name) = name {
1616            self.names.forget(&name);
1617        }
1618        self.blocks.remove(block.local);
1619    }
1620
1621    /// Absorb `other` into `keep`: drop `keep`'s terminal branch, append `other`'s
1622    /// instructions, rehome its outgoing edges, and remove it. `edge_ab` is the
1623    /// direct edge `keep -> other`.
1624    pub fn absorb_block(&mut self, keep: BlockId, other: BlockId, edge_ab: EdgeId) {
1625        assert_eq!(
1626            keep.func, other.func,
1627            "cannot absorb across function arenas"
1628        );
1629        let (branch_id, branch_args) = self
1630            .block(keep)
1631            .instructions
1632            .last()
1633            .and_then(
1634                |&local| match self.insn(InstructionId::new(keep.func, local)).mnemonic() {
1635                    Mnemonic::Branch(branch) if BlockId::new(keep.func, branch.target) == other => {
1636                        Some((InstructionId::new(keep.func, local), branch.args.clone()))
1637                    }
1638                    _ => None,
1639                },
1640            )
1641            .expect("absorbed block must be reached by keep's terminal branch");
1642        let other_params: Vec<_> = self
1643            .block(other)
1644            .params
1645            .iter()
1646            .map(|&local| BlockParamId::new(other.func, local))
1647            .collect();
1648        if !other_params.is_empty() {
1649            assert_eq!(
1650                other_params.len(),
1651                branch_args.len(),
1652                "cannot absorb block with {} params through branch with {} args",
1653                other_params.len(),
1654                branch_args.len()
1655            );
1656            for (param, arg) in other_params.iter().copied().zip(branch_args) {
1657                self.replace_all_uses_with(ValueId::BlockParam(param), arg.qualify(keep.func));
1658            }
1659        }
1660        self.remove_cfg_edge(edge_ab);
1661        self.remove_instruction(branch_id);
1662        let b_insns = std::mem::take(&mut self.block_mut(other).instructions);
1663        for &local in &b_insns {
1664            self.insn_mut(InstructionId::new(other.func, local)).parent = Some(keep.local);
1665        }
1666        self.block_mut(keep).instructions.extend(b_insns);
1667        self.rehome_outgoing_edges(keep, other);
1668        let (b_addr, b_extra, b_name) = {
1669            let b = self.block(other);
1670            (
1671                b.address,
1672                b.extra_addresses.clone(),
1673                b.local_name().map(str::to_owned),
1674            )
1675        };
1676        for param in other_params {
1677            self.remove_block_param(param);
1678        }
1679        self.unroster_block(other);
1680        if self.root == Some(other.local) {
1681            self.root = Some(keep.local);
1682        }
1683        if let Some(name) = b_name {
1684            self.names.forget(&name);
1685        }
1686        self.blocks.remove(other.local);
1687        if let Some(addr) = b_addr {
1688            self.block_mut(keep).extra_addresses.push(addr);
1689        }
1690        self.block_mut(keep).extra_addresses.extend(b_extra);
1691    }
1692
1693    /// Register `name` for `id` in this body's local name table (block/instruction/
1694    /// param). A global-scoped `id` reads `shared` for the duplicate check but
1695    /// cannot be *registered* through a body (its shared table is read-only here);
1696    /// no body verb reaches that arm.
1697    pub fn register_local_name(
1698        &mut self,
1699        shared: &crate::context::Shared<'str>,
1700        id: ValueId,
1701        name: Cow<'str, str>,
1702        old_name: Option<&str>,
1703    ) -> Result<()> {
1704        if id.name_scope_function().is_none() {
1705            return match shared.get_named(&name) {
1706                Some(existing) if existing == id => Ok(()),
1707                Some(_) => Err(Error::spanless(ErrorTy::DuplicateName(name.to_string()))),
1708                None => unimplemented!(
1709                    "a function body cannot register a global name (shared is read-only)"
1710                ),
1711            };
1712        }
1713        self.register_body_name(id, name, old_name)
1714    }
1715
1716    /// Register `name` for the function-scoped `id` (block/instruction/param/Temp)
1717    /// in this body's local name table. The shared-arm-free canon behind
1718    /// [`register_local_name`](Self::register_local_name); panics on a
1719    /// global-scoped `id`. Errors only on a duplicate name.
1720    pub fn register_body_name(
1721        &mut self,
1722        id: ValueId,
1723        name: Cow<'str, str>,
1724        old_name: Option<&str>,
1725    ) -> Result<()> {
1726        assert!(
1727            id.name_scope_function().is_some(),
1728            "register_body_name on a global-scoped value {id:?}"
1729        );
1730        if let Some(existing) = self.names.get(&name).map(|id| id.qualify(self.id())) {
1731            return if existing == id {
1732                Ok(())
1733            } else {
1734                Err(Error::spanless(ErrorTy::DuplicateName(name.to_string())))
1735            };
1736        }
1737        self.names.register(name, id.localize(self.id()), old_name)
1738    }
1739
1740    /// Gets a reference to a function from its ID
1741    pub fn from_id<'ctx>(ctx: &'ctx Context<'str>, id: FunctionId) -> FunctionRef<'str, 'ctx> {
1742        FunctionRef::new(ModuleView::new(ctx), id)
1743    }
1744
1745    /// Gets a mutable reference to a function from its ID
1746    pub fn from_id_mut<'ctx>(
1747        ctx: &'ctx mut Context<'str>,
1748        id: FunctionId,
1749    ) -> FunctionMutRef<'str, 'ctx> {
1750        FunctionMutRef::new(ctx, id)
1751    }
1752
1753    /// Gets a reference to a function by name
1754    pub fn from_name<'ctx>(
1755        ctx: &'ctx Context<'str>,
1756        name: &str,
1757    ) -> Option<FunctionRef<'str, 'ctx>> {
1758        ctx.get_named(name)
1759            .and_then(ValueId::as_function)
1760            .map(|id| FunctionBody::from_id(ctx, id))
1761    }
1762
1763    /// Create a new function
1764    pub fn make<'ctx>(
1765        ctx: &'ctx mut Context<'str>,
1766        name: Cow<'str, str>,
1767    ) -> Result<FunctionMutRef<'str, 'ctx>> {
1768        let id = FunctionId::from(ctx.bodies.len());
1769        let pushed = ctx.push_function(
1770            FunctionInterface::new(name.clone()),
1771            FunctionBody::empty_with_id(id),
1772        );
1773        debug_assert_eq!(pushed, id);
1774        ctx.update_name(name, id.into(), None)?;
1775        Ok(Self::from_id_mut(ctx, id))
1776    }
1777
1778    /// Create a new pure value-level lambda function.
1779    pub fn make_lambda<'ctx>(
1780        ctx: &'ctx mut Context<'str>,
1781        name: Cow<'str, str>,
1782    ) -> Result<FunctionMutRef<'str, 'ctx>> {
1783        let mut function = Self::make(ctx, name)?;
1784        function.interface_mut().kind = FunctionKind::Lambda;
1785        function.set_is_pure(true);
1786        function.set_register_effects(RegisterChannelState::Materialized(
1787            RegisterInterfaceMap::default(),
1788        ));
1789        Ok(function)
1790    }
1791
1792    /// Create a new function at a given address, generating a name if necessary.
1793    pub fn make_at_addr<'ctx>(
1794        ctx: &'ctx mut Context<'str>,
1795        address: u64,
1796        name: Option<Cow<'str, str>>,
1797    ) -> FunctionMutRef<'str, 'ctx> {
1798        let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
1799        Self::make_at_addr_indexed(ctx, &mut addresses, address, name)
1800    }
1801
1802    /// Indexed construction variant of [`make_at_addr`](Self::make_at_addr).
1803    pub fn make_at_addr_indexed<'ctx>(
1804        ctx: &'ctx mut Context<'str>,
1805        addresses: &mut crate::address_index::AddressIndex,
1806        address: u64,
1807        name: Option<Cow<'str, str>>,
1808    ) -> FunctionMutRef<'str, 'ctx> {
1809        let name = name.unwrap_or_else(|| Cow::Owned(format!("fn_{address:x}")));
1810        let id = FunctionId::from(ctx.bodies.len());
1811        let pushed = ctx.push_function(
1812            FunctionInterface::new(name.clone()),
1813            FunctionBody::empty_with_id(id),
1814        );
1815        debug_assert_eq!(pushed, id);
1816
1817        Self::from_id_mut(ctx, id)
1818            .with_name(name)
1819            .expect("Function name is not unique")
1820            .with_address_indexed(addresses, address)
1821            .expect("Function address is not unique")
1822    }
1823
1824    /// Like [`FunctionBody::make_at_addr`] but marks the result as external.
1825    ///
1826    /// External functions have no lifted body; the recursive disassembler will
1827    /// not try to explore them.
1828    pub fn make_external<'ctx>(
1829        ctx: &'ctx mut Context<'str>,
1830        address: u64,
1831        name: Option<Cow<'str, str>>,
1832    ) -> FunctionMutRef<'str, 'ctx> {
1833        let mut f = Self::make_at_addr(ctx, address, name);
1834        f.interface_mut().is_external = true;
1835        f
1836    }
1837
1838    /// Indexed construction variant of [`make_external`](Self::make_external).
1839    pub fn make_external_indexed<'ctx>(
1840        ctx: &'ctx mut Context<'str>,
1841        addresses: &mut crate::address_index::AddressIndex,
1842        address: u64,
1843        name: Option<Cow<'str, str>>,
1844    ) -> FunctionMutRef<'str, 'ctx> {
1845        let mut function = Self::make_at_addr_indexed(ctx, addresses, address, name);
1846        function.interface_mut().is_external = true;
1847        function
1848    }
1849
1850    /// Returns the [`FunctionId`] for `addr`, creating a named stub if absent.
1851    pub fn from_addr_or_create<'ctx>(
1852        ctx: &'ctx mut Context<'str>,
1853        address: u64,
1854    ) -> FunctionMutRef<'str, 'ctx> {
1855        let mut addresses = crate::address_index::AddressIndex::analyze(ctx);
1856        Self::from_addr_or_create_indexed(ctx, &mut addresses, address)
1857    }
1858
1859    /// Indexed construction variant of
1860    /// [`from_addr_or_create`](Self::from_addr_or_create).
1861    pub fn from_addr_or_create_indexed<'ctx>(
1862        ctx: &'ctx mut Context<'str>,
1863        addresses: &mut crate::address_index::AddressIndex,
1864        address: u64,
1865    ) -> FunctionMutRef<'str, 'ctx> {
1866        match addresses.function_at(address) {
1867            Some(id) => Self::from_id_mut(ctx, id),
1868            None => Self::make_at_addr_indexed(ctx, addresses, address, None),
1869        }
1870    }
1871}
1872
1873impl<'s, 'ctx: 's, 'str: 'ctx, R> FunctionRef<'str, 'ctx, R>
1874where
1875    R: QCodeView<'ctx, 'str>,
1876{
1877    fn inner(&'s self) -> &'ctx FunctionBody<'str> {
1878        self.view.function(self.id)
1879    }
1880
1881    /// This function's published interface (never checked out; always read from
1882    /// the shared registry).
1883    fn interface(&'s self) -> &'ctx FunctionInterface<'str> {
1884        self.view.interface(self.id)
1885    }
1886
1887    fn size(&self) -> usize {
1888        0
1889    }
1890
1891    /// The function interface's entry address.
1892    pub fn address(&'s self) -> Option<u64> {
1893        self.interface().address
1894    }
1895
1896    /// Whether the function interface marks this function external.
1897    pub fn is_external(&'s self) -> bool {
1898        self.interface().is_external
1899    }
1900
1901    /// The ordinal this import was brought in at, for a PE import resolved from
1902    /// an ordinal-only entry. `None` for named imports and local functions.
1903    pub fn import_ordinal(&'s self) -> Option<u16> {
1904        self.interface().import_ordinal
1905    }
1906
1907    /// A reference to the function interface's signature, if any.
1908    pub fn signature(&'s self) -> Option<&'ctx FunctionSignature> {
1909        self.interface().signature.as_ref()
1910    }
1911
1912    /// This function's instructions that use `value` as an operand. See
1913    /// [`FunctionBody::users_of`]; this is the function-scoped read every pass wants
1914    /// for an SSA value (all its users are intra-function).
1915    pub fn users_of(&'s self, value: ValueId) -> Vec<InstructionId> {
1916        let func = self.id;
1917        if value.owning_function().is_some_and(|owner| owner != func) {
1918            return Vec::new();
1919        }
1920        self.inner().users_of(value)
1921    }
1922
1923    /// This function's users of `value` in their stored, body-local form.
1924    ///
1925    /// Borrowed rather than built: a pass that reads the list once per
1926    /// instruction should not allocate one per instruction to do it. Qualify
1927    /// with this function's id when a whole [`InstructionId`] is needed.
1928    pub fn local_users_of(&'s self, value: ValueId) -> &'ctx [LocalInsnId] {
1929        let func = self.id;
1930        if value.owning_function().is_some_and(|owner| owner != func) {
1931            return &[];
1932        }
1933        self.inner().local_users_of(value)
1934    }
1935
1936    /// Whether this function uses `value` at all, without building the user
1937    /// list to ask. See [`FunctionBody::has_users`].
1938    pub fn has_users(&'s self, value: ValueId) -> bool {
1939        let func = self.id;
1940        if value.owning_function().is_some_and(|owner| owner != func) {
1941            return false;
1942        }
1943        self.inner().has_users(value)
1944    }
1945
1946    /// Iterate this function's recorded `(value, users)` reverse-use entries
1947    /// (see [`FunctionBody::user_map_entries`]).
1948    pub fn user_map_entries(&'s self) -> impl Iterator<Item = (ValueId, Vec<InstructionId>)> + 's {
1949        let func = self.id;
1950        self.inner().user_map_entries().map(move |(v, u)| {
1951            (
1952                v.qualify(func),
1953                u.iter()
1954                    .map(|&local| InstructionId::new(func, local))
1955                    .collect(),
1956            )
1957        })
1958    }
1959
1960    /// Resolve a block/instruction/param/Temp `name` within this function's local name
1961    /// table (see `FunctionBody::names`). `None` if this function has no such name.
1962    pub fn local_named(&'s self, name: &str) -> Option<ValueId> {
1963        self.inner().names.get(name).map(|id| id.qualify(self.id))
1964    }
1965
1966    /// The inferred pointer attributes for positional argument `index`, or `None`
1967    /// when this function has no analyzed attributes (treat conservatively: the
1968    /// argument escapes and may be written through). See
1969    /// [`FunctionSignature::param_attrs`].
1970    pub fn param_attr(&'s self, index: usize) -> Option<ParamAttrs> {
1971        self.interface().param_attr(index)
1972    }
1973
1974    /// The full per-parameter attribute vector, if analyzed.
1975    pub fn param_attrs(&'s self) -> Option<&'ctx [ParamAttrs]> {
1976        self.interface()
1977            .signature
1978            .as_ref()
1979            .and_then(|s| s.param_attrs.as_deref())
1980    }
1981
1982    /// The non-register memory spaces this function may (transitively) write, as
1983    /// set by analysis. `Some(spaces)` is exact (a space not listed is never
1984    /// written); `None` conflates "unstamped" and "stamped unbounded" — both are
1985    /// treated conservatively (may write any space) by consumers. For the
1986    /// tri-state distinction use [`written_spaces_state`](Self::written_spaces_state).
1987    /// See `FunctionSignature::written_spaces`.
1988    pub fn written_spaces(&'s self) -> Option<&'ctx [crate::space::SpaceId]> {
1989        match &self.interface().effects.memory.coarse {
1990            WrittenSpacesState::Bounded(spaces) => Some(spaces),
1991            _ => None,
1992        }
1993    }
1994
1995    /// The tri-state `written_spaces` verdict, distinguishing a never-stamped
1996    /// fresh mint ([`WrittenSpaces::Unstamped`]) from a deliberately recorded
1997    /// ⊤ ([`WrittenSpaces::Unbounded`]). See `FunctionSignature::written_spaces`.
1998    pub fn written_spaces_state(&'s self) -> WrittenSpaces<'ctx> {
1999        match &self.interface().effects.memory.coarse {
2000            WrittenSpacesState::Unstamped => WrittenSpaces::Unstamped,
2001            WrittenSpacesState::Unbounded => WrittenSpaces::Unbounded,
2002            WrittenSpacesState::Bounded(spaces) => WrittenSpaces::Bounded(spaces),
2003        }
2004    }
2005
2006    /// Whether this function's register interface has been materialized (argpromote
2007    /// v2) — i.e. its [`effects`](FunctionInterface::effects) are
2008    /// [`RegisterChannelState::Materialized`]. Legacy name for the register-channel
2009    /// "functionalized" predicate.
2010    pub fn is_reg_materialized(&'s self) -> bool {
2011        matches!(
2012            self.interface().effects.register,
2013            RegisterChannelState::Materialized(_)
2014        )
2015    }
2016
2017    /// This function's call-graph-closed register [`FunctionEffects`] summary.
2018    /// [`RegisterChannelState::Unsolved`] until the effect-analysis pass runs (and
2019    /// after a snapshot load). See [`FunctionInterface::effects`].
2020    pub fn effects(&'s self) -> &'ctx FunctionEffects {
2021        &self.interface().effects
2022    }
2023
2024    /// Whether argpromote has functionalized *every* side-effect channel of this
2025    /// function — it is a deterministic pure function of its by-value params,
2026    /// touching no caller-visible memory or registers. Strictly stronger than
2027    /// [`is_reg_materialized`](Self::is_reg_materialized). See [`FunctionSignature::is_pure`].
2028    pub fn is_pure(&'s self) -> bool {
2029        self.interface()
2030            .signature
2031            .as_ref()
2032            .is_some_and(|s| s.is_pure)
2033    }
2034
2035    /// Whether this is a pure value-level lambda rather than a machine function.
2036    pub fn is_lambda(&'s self) -> bool {
2037        self.interface().kind == FunctionKind::Lambda
2038    }
2039
2040    pub fn kind(&'s self) -> FunctionKind {
2041        self.interface().kind
2042    }
2043
2044    /// The C-prototype-derived external call interface, if `external_sigs`
2045    /// planned one. Read by `argpromote_external` to rewrite call sites. See
2046    /// [`FunctionSignature::extern_interface`].
2047    pub fn extern_interface(&'s self) -> Option<&'ctx crate::value::ExternInterface> {
2048        self.interface()
2049            .signature
2050            .as_ref()
2051            .and_then(|s| s.extern_interface.as_ref())
2052    }
2053
2054    /// The C-prototype-derived argmem summary for a prototyped external, or `None`
2055    /// when this function is not a prototyped external. See
2056    /// [`FunctionSignature::argmem`].
2057    pub fn argmem(&'s self) -> Option<&'ctx crate::value::ExternArgmem> {
2058        self.interface()
2059            .signature
2060            .as_ref()
2061            .and_then(|s| s.argmem.as_ref())
2062    }
2063
2064    /// The display name for the call-site argument bound to input `index`: the
2065    /// name of the callee's root block param at `index`, or — for a bodyless
2066    /// external with no root block — the C-prototype argument name recorded in
2067    /// its [`extern_interface`](Self::extern_interface). `None` when there is no
2068    /// input at `index` or it is unnamed.
2069    pub fn input_arg_name(&'s self, index: usize) -> Option<String> {
2070        // The root block param at `index` is the interface element a call
2071        // argument actually binds to, named after its register by
2072        // `argpromote_registers` or `stack_<addr>` by mem2reg's
2073        // `block_param_name_for_var`. Prefer it: it is the source of truth and is
2074        // populated even for `pure_reg` functions.
2075        if let Some(root) = self.root()
2076            && let Some(name) = root
2077                .params()
2078                .nth(index)
2079                .and_then(|p| p.name().map(str::to_owned))
2080        {
2081            return Some(name);
2082        }
2083
2084        // Fall back to the C-prototype-derived external call interface, the
2085        // source of truth for bodyless externals which have no root block.
2086        self.extern_interface()
2087            .and_then(|iface| iface.args.get(index))
2088            .and_then(|a| a.name.as_ref().map(|n| n.to_string()))
2089    }
2090
2091    /// Whether this function performs an unresolved/dynamic stack read (or
2092    /// forwards a stack pointer into one). See
2093    /// [`FunctionSignature::reads_unbounded_stack`].
2094    pub fn reads_unbounded_stack(&'s self) -> bool {
2095        self.interface()
2096            .signature
2097            .as_ref()
2098            .is_some_and(|s| s.reads_unbounded_stack)
2099    }
2100
2101    /// Whether this function hands a pointer into its own frame to a callee that
2102    /// may read it unboundedly. See
2103    /// [`FunctionSignature::frame_escapes_to_unbounded`].
2104    pub fn frame_escapes_to_unbounded(&'s self) -> bool {
2105        self.interface()
2106            .signature
2107            .as_ref()
2108            .is_some_and(|s| s.frame_escapes_to_unbounded)
2109    }
2110
2111    /// The function interface's name.
2112    pub fn name(&'s self) -> &'ctx str {
2113        self.interface().name.as_ref()
2114    }
2115
2116    /// The addresses of every machine instruction lifted into this function, in
2117    /// ascending order. Unlike [`blocks`](Self::blocks), this is stable across
2118    /// optimization, so it drives the raw disassembly view.
2119    pub fn instruction_addrs(&'s self) -> impl Iterator<Item = u64> + 'ctx {
2120        self.inner().instruction_addrs.iter().copied()
2121    }
2122
2123    /// Whether this function contains at least one [`Map`](Mnemonic::Map)
2124    /// instruction — a lane-wise array map operation. Surfaced as an advanced
2125    /// filter in the function list.
2126    pub fn has_map(&'s self) -> bool {
2127        self.blocks().any(|block| {
2128            block
2129                .instructions()
2130                .any(|insn| matches!(insn.mnemonic(), Mnemonic::Map(_)))
2131        })
2132    }
2133
2134    /// Whether this function contains at least one [`Scan`](Mnemonic::Scan)
2135    /// instruction — a lane-wise prefix-fold array operation. Surfaced as an
2136    /// advanced filter in the function list, alongside [`has_map`](Self::has_map).
2137    pub fn has_scan(&'s self) -> bool {
2138        self.blocks().any(|block| {
2139            block
2140                .instructions()
2141                .any(|insn| matches!(insn.mnemonic(), Mnemonic::Scan(_)))
2142        })
2143    }
2144
2145    /// The root block of this function, if it exists.
2146    pub fn root(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
2147        self.inner()
2148            .root
2149            .map(|local| BlockRef::new(self.view, BlockId::new(self.id, local)))
2150    }
2151
2152    /// An iterator over the (live) blocks belonging to this function.
2153    pub fn blocks(&'s self) -> impl Iterator<Item = BlockRef<'str, 'ctx, R>> + 's {
2154        let view = self.view;
2155        let mut ids = self.block_ids();
2156        // Total order: primarily by machine address, but break ties by the
2157        // function-local index. Address-less blocks (e.g. fallthrough splits,
2158        // whose `address()` is `None`) must still order deterministically.
2159        ids.sort_by_key(|&id| (BlockRef::new(view, id).address(), id.local));
2160        ids.into_iter().map(move |id| BlockRef::new(view, id))
2161    }
2162
2163    /// The composite ids of this function's live blocks, in roster order.
2164    pub fn block_ids(&'s self) -> Vec<BlockId> {
2165        let func = self.id;
2166        self.inner()
2167            .roster
2168            .iter()
2169            .copied()
2170            .map(|local| BlockId::new(func, local))
2171            .collect()
2172    }
2173
2174    /// The composite IDs of this function's live instructions, in dense physical
2175    /// order — including any currently detached (`parent == None`).
2176    pub fn instruction_ids(&'s self) -> Vec<InstructionId> {
2177        let func = self.id;
2178        self.inner()
2179            .insns
2180            .iter()
2181            .map(|i| InstructionId::new(func, i.id))
2182            .collect()
2183    }
2184
2185    /// The IDs of every live CFG edge in this function's edge arena, in dense
2186    /// physical order.
2187    pub fn edge_ids(&'s self) -> Vec<crate::value::block::EdgeId> {
2188        self.inner().edges.iter().map(|e| e.id).collect()
2189    }
2190
2191    /// Iterates over the (live) blocks in this function in arena order (i.e. not
2192    /// sorted by address, unlike [`blocks`](Self::blocks)).
2193    pub fn iter(&'s self) -> BlockIter<'str, 'ctx, R> {
2194        BlockIter {
2195            view: self.view,
2196            inner: self.block_ids().into_iter(),
2197            marker: PhantomData,
2198        }
2199    }
2200
2201    fn fmt(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
2202        if self.is_external() {
2203            return writeln!(f, "extern fn {};", self.name());
2204        }
2205        let keyword = match self.kind() {
2206            FunctionKind::Machine => "fn",
2207            FunctionKind::Lambda => "lambda",
2208        };
2209        writeln!(f, "{keyword} {}:", self.name())?;
2210        for block in self.blocks() {
2211            block.fmt(f)?;
2212        }
2213        Ok(())
2214    }
2215}
2216
2217#[derive(Clone, Copy)]
2218pub struct FunctionRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
2219    pub id: FunctionId,
2220    pub(in crate::value) view: R,
2221    marker: PhantomData<&'ctx &'str ()>,
2222}
2223
2224impl<'str, 'ctx, R> FunctionRef<'str, 'ctx, R> {
2225    pub fn new(view: R, id: FunctionId) -> Self {
2226        Self {
2227            id,
2228            view,
2229            marker: PhantomData,
2230        }
2231    }
2232
2233    pub fn id(&self) -> ValueId {
2234        self.id.into()
2235    }
2236}
2237
2238impl<'str, 'ctx> FunctionRef<'str, 'ctx> {
2239    pub fn from_id(ctx: &'ctx Context<'str>, id: FunctionId) -> Self {
2240        Self::new(ModuleView::new(ctx), id)
2241    }
2242}
2243
2244impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 'ctx, 'str> for FunctionRef<'str, 'ctx> {
2245    fn ctx(&'s self) -> &'ctx Context<'str> {
2246        // Module-scope-only escape hatch: shared-only reads go through
2247        // `host().shr()`; only whole-module walks (callees/callers) reach here,
2248        // and those panic on a checked-out host by design (context-split Pin B).
2249        self.view.context()
2250    }
2251}
2252
2253impl<'str: 'ctx, 'ctx, R> Named for FunctionRef<'str, 'ctx, R>
2254where
2255    R: QCodeView<'ctx, 'str>,
2256{
2257    fn name(&self) -> Option<&str> {
2258        Some(self.view.interface(self.id).name.as_ref())
2259    }
2260}
2261
2262impl<'str: 'ctx, 'ctx, R> Display for FunctionRef<'str, 'ctx, R>
2263where
2264    R: QCodeView<'ctx, 'str>,
2265{
2266    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2267        FunctionRef::fmt(self, f)
2268    }
2269}
2270
2271impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for FunctionRef<'str, 'ctx, R>
2272where
2273    R: QCodeView<'ctx, 'str>,
2274{
2275    fn id(&self) -> ValueId {
2276        self.id()
2277    }
2278
2279    fn size(&self) -> usize {
2280        FunctionRef::size(self)
2281    }
2282}
2283
2284pub struct BlockIter<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
2285    view: R,
2286    inner: std::vec::IntoIter<BlockId>,
2287    marker: PhantomData<&'ctx &'str ()>,
2288}
2289
2290impl<'str: 'ctx, 'ctx, R> Iterator for BlockIter<'str, 'ctx, R>
2291where
2292    R: QCodeView<'ctx, 'str>,
2293{
2294    type Item = BlockRef<'str, 'ctx, R>;
2295
2296    fn next(&mut self) -> Option<Self::Item> {
2297        self.inner.next().map(|id| BlockRef::new(self.view, id))
2298    }
2299}
2300
2301impl<'str: 'ctx, 'ctx, R> IntoIterator for &FunctionRef<'str, 'ctx, R>
2302where
2303    R: QCodeView<'ctx, 'str>,
2304{
2305    type Item = BlockRef<'str, 'ctx, R>;
2306    type IntoIter = BlockIter<'str, 'ctx, R>;
2307
2308    fn into_iter(self) -> Self::IntoIter {
2309        self.iter()
2310    }
2311}
2312
2313pub type FunctionMutRef<'str, 'ctx> = BaseRef<&'ctx mut Context<'str>, FunctionId>;
2314
2315impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 's, 'str> for FunctionMutRef<'str, 'ctx> {
2316    fn ctx(&'s self) -> &'s Context<'str> {
2317        self.ctx
2318    }
2319}
2320
2321impl<'s, 'ctx: 's, 'str: 'ctx> WithCtxMut<'s, 'str> for FunctionMutRef<'str, 'ctx> {
2322    fn ctx_mut(&'s mut self) -> &'s mut Context<'str> {
2323        self.ctx
2324    }
2325}
2326
2327impl Display for FunctionMutRef<'_, '_> {
2328    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2329        self.as_ref().fmt(f)
2330    }
2331}
2332
2333impl<'ctx, 'str> Value<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
2334    fn id(&self) -> ValueId {
2335        self.id()
2336    }
2337
2338    fn size(&self) -> usize {
2339        self.as_ref().size()
2340    }
2341}
2342
2343impl Named for FunctionMutRef<'_, '_> {
2344    fn name(&self) -> Option<&str> {
2345        Some(self.ctx.interfaces[self.id].name.as_ref())
2346    }
2347}
2348
2349impl<'str, 'ctx> Renameable<'str, 'ctx> for FunctionMutRef<'str, 'ctx> {
2350    fn rename(&mut self, name: Cow<'str, str>) -> Result<()> {
2351        let id = self.id();
2352        let old_name = self.ctx.interfaces[self.id].name.as_ref().to_owned();
2353        update_context_name(id, self.ctx, name.clone(), Some(old_name.as_ref()))?;
2354        self.ctx.interfaces[self.id].name = name;
2355        Ok(())
2356    }
2357}
2358
2359impl<'str, 'ctx> FunctionMutRef<'str, 'ctx> {
2360    pub fn as_ref(&self) -> FunctionRef<'str, '_> {
2361        FunctionRef::new(ModuleView::new(self.ctx), self.id)
2362    }
2363
2364    fn inner(&self) -> &FunctionBody<'str> {
2365        self.ctx.function(self.id)
2366    }
2367
2368    fn interface(&self) -> &FunctionInterface<'str> {
2369        &self.ctx.interfaces[self.id]
2370    }
2371
2372    fn address(&self) -> Option<u64> {
2373        self.interface().address
2374    }
2375
2376    pub fn name(&self) -> &str {
2377        self.interface().name.as_ref()
2378    }
2379
2380    pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'str, '_>> {
2381        self.as_ref().blocks().collect::<Vec<_>>().into_iter()
2382    }
2383
2384    pub fn root(&self) -> Option<BlockRef<'str, '_>> {
2385        self.as_ref().root()
2386    }
2387
2388    pub(crate) fn inner_mut(&mut self) -> &mut FunctionBody<'str> {
2389        &mut self.ctx.bodies[self.id]
2390    }
2391
2392    /// This function's published interface (mutable). Interface writes are
2393    /// module-scope only; this is the write path for the setters below.
2394    pub(crate) fn interface_mut(&mut self) -> &mut FunctionInterface<'str> {
2395        &mut self.ctx.interfaces[self.id]
2396    }
2397
2398    fn set_address(&mut self, address: u64) -> Result<()> {
2399        let mut addresses = crate::address_index::AddressIndex::analyze(&*self.ctx);
2400        self.set_address_indexed(&mut addresses, address)
2401    }
2402
2403    fn set_address_indexed(
2404        &mut self,
2405        addresses: &mut crate::address_index::AddressIndex,
2406        address: u64,
2407    ) -> Result<()> {
2408        let old_address = self.interface().address;
2409        self.interface_mut().address = Some(address);
2410        if let Err(error) = self
2411            .ctx
2412            .set_address_indexed(addresses, address, self.id.into())
2413        {
2414            self.interface_mut().address = old_address;
2415            return Err(error);
2416        }
2417        Ok(())
2418    }
2419
2420    fn with_address_indexed(
2421        mut self,
2422        addresses: &mut crate::address_index::AddressIndex,
2423        address: u64,
2424    ) -> Result<Self> {
2425        self.set_address_indexed(addresses, address)?;
2426        Ok(self)
2427    }
2428
2429    /// Sets a block as the root of this function.
2430    /// This will also add the block to the function's block list if it's not already present.
2431    /// This will also set the address of the function/block to the address of the root block/function if both addresses are unset.
2432    /// Panics if the function already has an address that doesn't match the root block's address.
2433    pub fn set_root(&mut self, id: BlockId) -> Result<()> {
2434        assert_eq!(
2435            id.func, self.id,
2436            "cannot root a function at a block stored in another function arena"
2437        );
2438        self.add_block(id);
2439        self.inner_mut().root = Some(id.localize(self.id));
2440
2441        let block_addr = BasicBlock::from_id(&*self.ctx, id).address();
2442        let self_addr = self.address();
2443
2444        match (self_addr, block_addr) {
2445            (Some(fn_addr), Some(block_addr)) if fn_addr != block_addr => {
2446                return Err(Error::spanless(ErrorTy::FunctionRootAddressMismatch {
2447                    fn_addr,
2448                    block_addr,
2449                }));
2450            }
2451            (None, Some(addr)) => {
2452                self.set_address(addr)
2453                    .expect("This address should be valid");
2454            }
2455            (Some(addr), None) => {
2456                BasicBlock::from_id_mut(self.ctx, id)
2457                    .set_address(addr)
2458                    .expect("This address should be valid");
2459            }
2460            _ => {}
2461        }
2462        Ok(())
2463    }
2464
2465    pub fn make_root(&mut self) -> BlockRef<'str, '_> {
2466        let func = self.id;
2467        let root = BasicBlock::make(self.ctx, func).id;
2468        self.set_root(root).expect("We just created the block");
2469        BasicBlock::from_id(&*self.ctx, root)
2470    }
2471
2472    pub fn ensure_root(&mut self, id: BlockId) -> Result<()> {
2473        assert_eq!(
2474            id.func, self.id,
2475            "cannot ensure a function root from another function arena"
2476        );
2477        if let Some(root) = self.inner().root {
2478            if root != id.localize(self.id) {
2479                return Err(Error::spanless(ErrorTy::FunctionRootMismatch {
2480                    expected: BlockId::new(self.id, root),
2481                    actual: id,
2482                }));
2483            }
2484            Ok(())
2485        } else {
2486            self.set_root(id)
2487        }
2488    }
2489
2490    pub fn set_external(&mut self, is_external: bool) {
2491        self.interface_mut().is_external = is_external;
2492        assert!(
2493            self.inner().blocks.is_empty(),
2494            "External functions should not have blocks"
2495        );
2496    }
2497
2498    /// Record the ordinal a by-ordinal PE import was brought in at. Set by the
2499    /// `resolve_ordinals` pass before it renames the stub, so the by-ordinal
2500    /// origin survives the rename.
2501    pub fn set_import_ordinal(&mut self, ordinal: Option<u16>) {
2502        self.interface_mut().import_ordinal = ordinal;
2503    }
2504
2505    pub fn set_kind(&mut self, kind: FunctionKind) {
2506        self.interface_mut().kind = kind;
2507        if kind == FunctionKind::Lambda {
2508            self.set_is_pure(true);
2509            self.set_register_effects(RegisterChannelState::Materialized(
2510                RegisterInterfaceMap::default(),
2511            ));
2512        }
2513    }
2514
2515    pub fn set_signature(&mut self, sig: FunctionSignature) {
2516        self.ctx.interfaces[self.id].signature = Some(sig);
2517    }
2518
2519    /// Records the inferred per-parameter pointer attributes on this function.
2520    /// See [`FunctionSignature::param_attrs`].
2521    pub fn set_param_attrs(&mut self, attrs: Vec<ParamAttrs>) {
2522        self.interface_mut()
2523            .signature
2524            .get_or_insert_default()
2525            .param_attrs = Some(attrs);
2526    }
2527
2528    /// Drops any inferred per-parameter attributes (e.g. after a signature
2529    /// rewrite changed the parameter list, invalidating the index alignment).
2530    pub fn clear_param_attrs(&mut self) {
2531        if let Some(sig) = self.interface_mut().signature.as_mut() {
2532            sig.param_attrs = None;
2533        }
2534    }
2535
2536    /// Records the analysis-computed set of non-register spaces this function may
2537    /// write. This is always a deliberate stamp: `Some(spaces)` is a bounded
2538    /// witnessed set, `None` records *stamped unbounded* (⊤) — never clears the
2539    /// stamp back to unstamped. See `FunctionSignature::written_spaces` and
2540    /// [`FunctionRef::written_spaces_state`].
2541    pub fn set_written_spaces(&mut self, spaces: Option<Vec<crate::space::SpaceId>>) {
2542        let coarse = match spaces {
2543            Some(spaces) => WrittenSpacesState::Bounded(spaces),
2544            None => WrittenSpacesState::Unbounded,
2545        };
2546        // Coarse-only setter: every other component of the channel is left
2547        // exactly as it was.
2548        let precise = self.interface_mut().effects.memory.precise.take();
2549        self.set_memory_solved(coarse, precise);
2550    }
2551
2552    /// Records the C-prototype-derived external call interface on this function.
2553    /// See [`FunctionSignature::extern_interface`]; set by `external_sigs`,
2554    /// consumed by `argpromote_external`.
2555    pub fn set_extern_interface(&mut self, iface: crate::value::ExternInterface) {
2556        self.interface_mut()
2557            .signature
2558            .get_or_insert_default()
2559            .extern_interface = Some(iface);
2560    }
2561
2562    /// Records the C-prototype-derived argmem summary on this external. See
2563    /// [`FunctionSignature::argmem`]; set by `external_sigs`, read by the RAM
2564    /// effect channel's `external_leaf`.
2565    pub fn set_argmem(&mut self, argmem: crate::value::ExternArgmem) {
2566        self.interface_mut()
2567            .signature
2568            .get_or_insert_default()
2569            .argmem = Some(argmem);
2570    }
2571
2572    /// Records this function's register-channel effect state, preserving the
2573    /// memory channel (read-modify-write). See [`FunctionInterface::effects`].
2574    pub fn set_register_effects(&mut self, register: RegisterChannelState) {
2575        self.interface_mut().effects.register = register;
2576    }
2577
2578    /// Records this function's memory-channel effect state, preserving the
2579    /// register channel (read-modify-write). See [`FunctionInterface::effects`].
2580    ///
2581    /// Replaces **every** component of the memory channel. The channel has two
2582    /// independent writers — the effect solve owns `coarse`/`precise`, the RAM
2583    /// channel's rewrite owns `materialized` — so a caller that computes only
2584    /// one writer's components must not build a whole state and pass it here:
2585    /// the other writer's field would be silently lost. Use
2586    /// [`set_memory_solved`](Self::set_memory_solved) or
2587    /// [`set_memory_interface`](Self::set_memory_interface) instead.
2588    pub fn set_memory_effects(&mut self, memory: MemoryChannelState) {
2589        self.interface_mut().effects.memory = memory;
2590    }
2591
2592    /// Records the *solved* components of the memory channel — the coarse
2593    /// written-space verdict and the precise footprint the same solve derived —
2594    /// leaving the materialized interface untouched.
2595    ///
2596    /// The effect solve does not compute the interface, so it must not clear it.
2597    pub fn set_memory_solved(&mut self, coarse: WrittenSpacesState, precise: Option<Footprint>) {
2598        let memory = &mut self.interface_mut().effects.memory;
2599        memory.coarse = coarse;
2600        memory.precise = precise;
2601    }
2602
2603    /// Records the materialized memory interface, leaving the solved components
2604    /// untouched. `None` marks the memory channel as not materialized.
2605    pub fn set_memory_interface(&mut self, materialized: Option<MemoryInterfaceMap>) {
2606        self.interface_mut().effects.memory.materialized = materialized;
2607    }
2608
2609    /// Marks this function as fully functionalized over *every* side-effect
2610    /// channel — a deterministic pure function of its params. See
2611    /// [`FunctionSignature::is_pure`].
2612    pub fn set_is_pure(&mut self, value: bool) {
2613        self.interface_mut()
2614            .signature
2615            .get_or_insert_default()
2616            .is_pure = value;
2617    }
2618
2619    /// Records whether this function performs an unresolved/dynamic stack read.
2620    /// See [`FunctionSignature::reads_unbounded_stack`].
2621    pub fn set_reads_unbounded_stack(&mut self, value: bool) {
2622        self.interface_mut()
2623            .signature
2624            .get_or_insert_default()
2625            .reads_unbounded_stack = value;
2626    }
2627
2628    /// Records whether this function hands a pointer into its own frame to a
2629    /// callee that may read it unboundedly. See
2630    /// [`FunctionSignature::frame_escapes_to_unbounded`].
2631    pub fn set_frame_escapes_to_unbounded(&mut self, value: bool) {
2632        self.interface_mut()
2633            .signature
2634            .get_or_insert_default()
2635            .frame_escapes_to_unbounded = value;
2636    }
2637
2638    /// Records the address of a machine instruction lifted into this function.
2639    pub fn add_instruction_addr(&mut self, addr: u64) {
2640        self.inner_mut().instruction_addrs.insert(addr);
2641    }
2642
2643    /// Associates `block` with `function` by setting the block's `parent` field.
2644    ///
2645    /// With per-function block arenas, membership *is* arena ownership: a block
2646    /// lives in the arena of the function it was born into (`id.func`), and that
2647    /// must equal `self.id`. Ownership is derived from the arena, so this only
2648    /// ensures the roster lists the block; it no longer moves storage between
2649    /// functions.
2650    pub fn add_block(&mut self, id: BlockId) {
2651        assert_eq!(
2652            id.func, self.id,
2653            "cannot add a block stored in another function arena"
2654        );
2655        let local = id.localize(self.id);
2656        // Ensure the roster lists it exactly once (a freshly `make`d block is
2657        // auto-rostered, so this is usually a no-op).
2658        if !self.inner().roster.contains(&local) {
2659            self.inner_mut().roster.push(local);
2660        }
2661    }
2662}
2663
2664#[cfg(test)]
2665mod tests {
2666    use wazabin_qcode_macro::qcode;
2667
2668    use super::*;
2669
2670    fn foreign_block_fixture() -> (Context<'static>, FunctionId, BlockId) {
2671        let mut ctx = Context::new();
2672        let owner = FunctionBody::make(&mut ctx, "block_owner".into())
2673            .unwrap()
2674            .id;
2675        let destination = FunctionBody::make(&mut ctx, "block_destination".into())
2676            .unwrap()
2677            .id;
2678        let block = BasicBlock::make(&mut ctx, owner).id;
2679        (ctx, destination, block)
2680    }
2681
2682    #[test]
2683    fn raw_root_and_roster_are_local_while_refs_qualify_per_function() {
2684        let mut ctx = Context::new();
2685        let a = FunctionBody::make(&mut ctx, "local_root_a".into())
2686            .unwrap()
2687            .id;
2688        let b = FunctionBody::make(&mut ctx, "local_root_b".into())
2689            .unwrap()
2690            .id;
2691        let a_root = BasicBlock::make(&mut ctx, a).id;
2692        let b_root = BasicBlock::make(&mut ctx, b).id;
2693        assert_eq!(a_root.local, b_root.local, "arena-local ids should collide");
2694        FunctionBody::from_id_mut(&mut ctx, a)
2695            .set_root(a_root)
2696            .unwrap();
2697        FunctionBody::from_id_mut(&mut ctx, b)
2698            .set_root(b_root)
2699            .unwrap();
2700
2701        assert_eq!(ctx.bodies[a].root_id(), Some(a_root.local));
2702        assert_eq!(ctx.bodies[b].root_id(), Some(b_root.local));
2703        assert_eq!(ctx.bodies[a].roster, vec![a_root.local]);
2704        assert_eq!(ctx.bodies[b].roster, vec![b_root.local]);
2705        assert_eq!(
2706            FunctionBody::from_id(&ctx, a).root().map(|root| root.id),
2707            Some(a_root)
2708        );
2709        assert_eq!(
2710            FunctionBody::from_id(&ctx, b).root().map(|root| root.id),
2711            Some(b_root)
2712        );
2713    }
2714
2715    #[test]
2716    #[should_panic(expected = "cannot add a block stored in another function arena")]
2717    fn add_block_rejects_foreign_storage() {
2718        let (mut ctx, destination, block) = foreign_block_fixture();
2719        FunctionBody::from_id_mut(&mut ctx, destination).add_block(block);
2720    }
2721
2722    #[test]
2723    #[should_panic(expected = "cannot root a function at a block stored in another function arena")]
2724    fn set_root_rejects_foreign_storage() {
2725        let (mut ctx, destination, block) = foreign_block_fixture();
2726        FunctionBody::from_id_mut(&mut ctx, destination)
2727            .set_root(block)
2728            .unwrap();
2729    }
2730
2731    #[test]
2732    #[should_panic(expected = "cannot ensure a function root from another function arena")]
2733    fn ensure_root_rejects_foreign_storage() {
2734        let (mut ctx, destination, block) = foreign_block_fixture();
2735        FunctionBody::from_id_mut(&mut ctx, destination)
2736            .ensure_root(block)
2737            .unwrap();
2738    }
2739
2740    #[test]
2741    fn function_ref_users_of_rejects_foreign_owned_values() {
2742        let mut ctx = Context::new();
2743        qcode!(
2744            ctx,
2745            "
2746            fn users_a:
2747                <a_entry>
2748                    %a_def = i64 1 + i64 2;
2749                    %a_user = %a_def + i64 3;
2750                    return at %a_user;
2751
2752            fn users_b:
2753                <b_entry>
2754                    %b_def = i64 1 + i64 2;
2755                    %b_user = %b_def + i64 3;
2756                    return at %b_user;
2757            "
2758        );
2759
2760        let a_ids = FunctionRef::from_id(&ctx, users_a)
2761            .root()
2762            .unwrap()
2763            .instruction_ids();
2764        let a_def = ValueId::Instruction(a_ids[0]);
2765        assert_eq!(
2766            FunctionRef::from_id(&ctx, users_a).users_of(a_def),
2767            vec![a_ids[1]]
2768        );
2769        assert!(
2770            FunctionRef::from_id(&ctx, users_b)
2771                .users_of(a_def)
2772                .is_empty()
2773        );
2774
2775        let one = ctx.get_const(1, 8).id();
2776        assert!(!FunctionRef::from_id(&ctx, users_b).users_of(one).is_empty());
2777    }
2778
2779    fn colliding_body_ids() -> (
2780        Context<'static>,
2781        FunctionId,
2782        FunctionId,
2783        BlockId,
2784        BlockId,
2785        InstructionId,
2786        InstructionId,
2787        BlockParamId,
2788        BlockParamId,
2789    ) {
2790        let mut ctx = Context::new();
2791        qcode!(
2792            ctx,
2793            "
2794            fn raw_a:
2795                <a_entry @a:i64>
2796                    %a_def = i64 1 + i64 2;
2797                    return at %a_def;
2798            fn raw_b:
2799                <b_entry @b:i64>
2800                    %b_def = i64 1 + i64 2;
2801                    return at %b_def;
2802            "
2803        );
2804        let a_root = FunctionRef::from_id(&ctx, raw_a).root().unwrap();
2805        let b_root = FunctionRef::from_id(&ctx, raw_b).root().unwrap();
2806        let a_block = a_root.id;
2807        let b_block = b_root.id;
2808        let a_insn = a_root.instruction_ids()[0];
2809        let b_insn = b_root.instruction_ids()[0];
2810        let a_param = a_root.params().next().unwrap().id;
2811        let b_param = b_root.params().next().unwrap().id;
2812        assert_eq!(a_block.local, b_block.local);
2813        assert_eq!(a_insn.local, b_insn.local);
2814        assert_eq!(a_param.local, b_param.local);
2815        (
2816            ctx, raw_a, raw_b, a_block, b_block, a_insn, b_insn, a_param, b_param,
2817        )
2818    }
2819
2820    /// `replace_instruction(id, id)` must be a no-op: forwarding uses to itself
2821    /// does nothing, so deleting `id` would strand its still-live users. A pass
2822    /// that resolves an instruction to itself must leave it in place.
2823    #[test]
2824    fn replace_instruction_with_itself_is_a_noop() {
2825        let mut ctx = Context::new();
2826        qcode!(
2827            ctx,
2828            "
2829            fn f:
2830            <entry @a:i32>
2831                %x = @a + 1;
2832                %y = %x + 2;
2833                return %y;
2834            "
2835        );
2836        // `%x` is used by `%y`; find both.
2837        let root = FunctionBody::from_id(&ctx, f).root().unwrap().id;
2838        let insns: Vec<InstructionId> = BasicBlock::from_id(&ctx, root)
2839            .instruction_ids()
2840            .into_iter()
2841            .collect();
2842        let x = insns[0];
2843        let users_before = ctx.bodies[f].users_of(ValueId::Instruction(x));
2844        assert!(!users_before.is_empty(), "x should have a user (%y)");
2845
2846        // Replace x with itself — must not delete x or disturb its users.
2847        ctx.bodies[f].replace_instruction(x, ValueId::Instruction(x));
2848
2849        assert!(
2850            ctx.bodies[f].insns.contains(x.local),
2851            "x must survive a self-replacement"
2852        );
2853        assert_eq!(
2854            ctx.bodies[f].users_of(ValueId::Instruction(x)),
2855            users_before,
2856            "x's users must be unchanged"
2857        );
2858    }
2859
2860    #[test]
2861    fn body_users_of_rejects_foreign_owned_values() {
2862        let (ctx, a, b, _, _, a_insn, _, _, _) = colliding_body_ids();
2863        assert!(
2864            ctx.bodies[b]
2865                .users_of(ValueId::Instruction(a_insn))
2866                .is_empty()
2867        );
2868        assert!(
2869            !ctx.bodies[a]
2870                .users_of(ValueId::Instruction(a_insn))
2871                .is_empty()
2872        );
2873    }
2874
2875    #[test]
2876    #[should_panic(expected = "cannot replace uses of a value owned by another function")]
2877    fn body_replace_uses_rejects_foreign_old() {
2878        let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
2879        ctx.bodies[b]
2880            .replace_all_uses_with(ValueId::Instruction(a_insn), ValueId::Instruction(b_insn));
2881    }
2882
2883    #[test]
2884    #[should_panic(expected = "cannot replace uses with a value owned by another function")]
2885    fn body_replace_uses_rejects_foreign_new() {
2886        let (mut ctx, _, b, _, _, a_insn, b_insn, _, _) = colliding_body_ids();
2887        ctx.bodies[b]
2888            .replace_all_uses_with(ValueId::Instruction(b_insn), ValueId::Instruction(a_insn));
2889    }
2890
2891    #[test]
2892    #[should_panic(expected = "block belongs to another function")]
2893    fn body_block_access_rejects_colliding_foreign_id() {
2894        let (ctx, _, b, a_block, _, _, _, _, _) = colliding_body_ids();
2895        let _ = ctx.bodies[b].block(a_block);
2896    }
2897
2898    #[test]
2899    #[should_panic(expected = "instruction belongs to another function")]
2900    fn body_insn_access_rejects_colliding_foreign_id() {
2901        let (ctx, _, b, _, _, a_insn, _, _, _) = colliding_body_ids();
2902        let _ = ctx.bodies[b].insn(a_insn);
2903    }
2904
2905    #[test]
2906    #[should_panic(expected = "block parameter belongs to another function")]
2907    fn body_param_access_rejects_colliding_foreign_id() {
2908        let (ctx, _, b, _, _, _, _, a_param, _) = colliding_body_ids();
2909        let _ = ctx.bodies[b].block_param(a_param);
2910    }
2911
2912    // The `push_block` foreign-parent asserts are gone (stage 2): block ownership
2913    // is derived from the storing arena, so a block pushed into body `b` is owned
2914    // by `b` by construction — a foreign parent is unrepresentable.
2915
2916    #[test]
2917    fn make_function_creates_function_with_correct_name_root_address() {
2918        let mut ctx = Context::new();
2919        let f = FunctionBody::make(&mut ctx, "main".into()).unwrap();
2920        assert_eq!(f.name(), "main");
2921    }
2922
2923    #[test]
2924    fn get_function_by_name_returns_correct_function() {
2925        let mut ctx = Context::new();
2926        let id = FunctionBody::make(&mut ctx, "foo".into()).unwrap().id();
2927        let f = FunctionBody::from_name(&ctx, "foo").unwrap();
2928        assert_eq!(f.id(), id);
2929        assert_eq!(f.name(), "foo");
2930    }
2931
2932    #[test]
2933    fn get_function_by_name_returns_none_if_not_found() {
2934        let ctx = Context::new();
2935        assert!(FunctionBody::from_name(&ctx, "nonexistent").is_none());
2936    }
2937
2938    #[test]
2939    fn get_function_by_addr_returns_correct_function() {
2940        let mut ctx = Context::new();
2941        let id = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id();
2942        let addresses = crate::address_index::AddressIndex::analyze(&ctx);
2943        let f = FunctionBody::from_id(&ctx, addresses.function_at(0x2000).unwrap());
2944        assert_eq!(f.id(), id);
2945        assert_eq!(f.address(), Some(0x2000));
2946        assert_eq!(f.name(), "fn_2000");
2947    }
2948
2949    #[test]
2950    fn get_function_by_addr_returns_none_if_missing() {
2951        let ctx = Context::new();
2952        let addresses = crate::address_index::AddressIndex::analyze(&ctx);
2953        assert!(addresses.function_at(0xdeadbeef).is_none());
2954    }
2955
2956    #[test]
2957    fn add_block_via_function_mut_ref_updates_blocks_list() {
2958        let mut ctx = Context::new();
2959        let baz_id = FunctionBody::make(&mut ctx, "baz".into()).unwrap().id;
2960        let root = BasicBlock::make(&mut ctx, baz_id).id;
2961        let extra = BasicBlock::make(&mut ctx, baz_id).id;
2962
2963        let mut baz = FunctionBody::from_id_mut(&mut ctx, baz_id);
2964        baz.add_block(root);
2965        baz.add_block(extra);
2966
2967        let block_ids: Vec<_> = baz.blocks().map(|b| b.id).collect();
2968        assert!(block_ids.contains(&root));
2969        assert!(block_ids.contains(&extra));
2970    }
2971
2972    #[test]
2973    fn display_shows_function_name_and_block_contents() {
2974        let mut ctx = Context::new();
2975        FunctionBody::make(&mut ctx, "display_test".into()).unwrap();
2976
2977        let f = FunctionBody::from_name(&ctx, "display_test").unwrap();
2978
2979        let s = f.to_string();
2980        assert!(s.contains("fn display_test:"));
2981    }
2982
2983    #[test]
2984    fn iter_yields_all_blocks() {
2985        let mut ctx = Context::new();
2986        let f_id = FunctionBody::make(&mut ctx, "iter_fn".into()).unwrap().id;
2987        let root = BasicBlock::make(&mut ctx, f_id).id;
2988        let extra = BasicBlock::make(&mut ctx, f_id).id;
2989        let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
2990        f.add_block(root);
2991        f.add_block(extra);
2992
2993        let f = FunctionBody::from_name(&ctx, "iter_fn").unwrap();
2994        let ids: Vec<_> = f.iter().map(|b| b.id).collect();
2995        assert!(ids.contains(&root));
2996        assert!(ids.contains(&extra));
2997    }
2998
2999    #[test]
3000    fn into_iterator_for_function_ref_matches_iter() {
3001        let mut ctx = Context::new();
3002        let f_id = FunctionBody::make(&mut ctx, "into_iter_fn".into())
3003            .unwrap()
3004            .id;
3005        let b1 = BasicBlock::make(&mut ctx, f_id).id;
3006        let b2 = BasicBlock::make(&mut ctx, f_id).id;
3007        let mut f = FunctionBody::from_id_mut(&mut ctx, f_id);
3008        f.add_block(b1);
3009        f.add_block(b2);
3010
3011        let f = FunctionBody::from_name(&ctx, "into_iter_fn").unwrap();
3012        let mut via_iter: Vec<usize> = f.iter().map(|b| usize::from(b.id.local)).collect();
3013        let mut via_into: Vec<usize> = (&f).into_iter().map(|b| usize::from(b.id.local)).collect();
3014        via_iter.sort();
3015        via_into.sort();
3016        assert_eq!(via_iter, via_into);
3017    }
3018
3019    #[test]
3020    fn qcode_fn_single_block_populates_function() {
3021        let mut ctx = Context::new();
3022        qcode!(
3023            ctx,
3024            "
3025            fn simple:
3026                <entry>
3027                    return at 0;
3028            "
3029        );
3030
3031        let f = FunctionBody::from_name(&ctx, "simple").unwrap();
3032        assert_eq!(f.name(), "simple");
3033        assert!(f.root().is_some());
3034        assert_eq!(f.root().unwrap().name().unwrap(), "entry");
3035        assert_eq!(f.blocks().count(), 1);
3036    }
3037
3038    #[test]
3039    fn qcode_fn_multi_block_populates_all_blocks() {
3040        let mut ctx = Context::new();
3041        qcode!(
3042            ctx,
3043            "
3044            fn multiblock:
3045                <bb1>
3046                    if i8 1 goto <bb2> else goto <bb3>;
3047
3048                <bb2>
3049                    goto <bb3>;
3050
3051                <bb3>
3052                    return at 0;
3053            "
3054        );
3055
3056        let f = FunctionBody::from_name(&ctx, "multiblock").unwrap();
3057        assert_eq!(f.root().unwrap().name().unwrap(), "bb1");
3058        let block_names: Vec<_> = f.blocks().filter_map(|b| b.name()).collect();
3059        assert!(block_names.contains(&"bb1"), "missing bb1");
3060        assert!(block_names.contains(&"bb2"), "missing bb2");
3061        assert!(block_names.contains(&"bb3"), "missing bb3");
3062        assert_eq!(f.blocks().count(), 3);
3063    }
3064
3065    #[test]
3066    fn qcode_fn_id_variable_is_set() {
3067        let mut ctx = Context::new();
3068        qcode!(
3069            ctx,
3070            "
3071            fn myfn:
3072                <start>
3073                    return at 0;
3074            "
3075        );
3076
3077        let by_name = FunctionBody::from_name(&ctx, "myfn").unwrap();
3078        assert_eq!(by_name.name(), "myfn");
3079    }
3080
3081    /// Split construction lets a rootless function temporarily win an address
3082    /// occupied by a block that has not yet been rehomed into its arena.
3083    #[test]
3084    fn indexed_address_registration_keeps_foreign_block_rootless() {
3085        let mut ctx = Context::new();
3086
3087        // Simulate a branch-target block created at 0x1000 before the function
3088        // stub exists (as happens with tail-jumps to sibling functions).
3089        let block_id = {
3090            let __f = ctx.anon_function();
3091            BasicBlock::make(&mut ctx, __f)
3092        }
3093        .id;
3094        let mut addresses = crate::address_index::AddressIndex::analyze(&ctx);
3095        addresses
3096            .register(
3097                &mut ctx,
3098                0x1000,
3099                crate::address_index::AddressTarget::Block(block_id),
3100            )
3101            .unwrap();
3102
3103        let fn_id = FunctionBody::make(&mut ctx, "fn_1000".into()).unwrap().id;
3104        addresses
3105            .register(
3106                &mut ctx,
3107                0x1000,
3108                crate::address_index::AddressTarget::Function(fn_id),
3109            )
3110            .unwrap();
3111
3112        assert_eq!(addresses.function_at(0x1000), Some(fn_id));
3113        assert_eq!(addresses.block_at(0x1000), None);
3114        assert!(FunctionBody::from_id(&ctx, fn_id).root().is_none());
3115        assert_ne!(block_id.func, fn_id);
3116    }
3117}
3118
3119#[cfg(test)]
3120mod memory_interface_tests {
3121    use super::*;
3122
3123    fn slot() -> InterfaceSlot {
3124        InterfaceSlot {
3125            base: SlotBase::Arg(0),
3126            offset: 8,
3127            size: 8,
3128        }
3129    }
3130
3131    /// The interface survives a round-trip through the snapshot wire format.
3132    ///
3133    /// Back-compat is *not* tested here and is not provided: the payload is
3134    /// bincode under a hard version lock (`session.rs` `FORMAT_VERSION`, bumped
3135    /// for this field), so snapshots written before it are rejected outright
3136    /// rather than defaulted.
3137    #[test]
3138    fn memory_interface_round_trips_through_the_wire_format() {
3139        let state = MemoryChannelState {
3140            materialized: Some(MemoryInterfaceMap {
3141                inputs: vec![slot()],
3142                outputs: vec![InterfaceSlot {
3143                    base: SlotBase::Global(0x2000),
3144                    offset: 0,
3145                    size: 4,
3146                }],
3147            }),
3148            ..MemoryChannelState::default()
3149        };
3150        let config = bincode::config::standard();
3151        let bytes = bincode::serde::encode_to_vec(&state, config).expect("encode memory state");
3152        let (decoded, _): (MemoryChannelState, _) =
3153            bincode::serde::decode_from_slice(&bytes, config).expect("decode memory state");
3154        assert_eq!(decoded, state);
3155    }
3156
3157    /// A default (unmaterialized) channel reports no interface.
3158    #[test]
3159    fn default_memory_state_is_not_materialized() {
3160        assert_eq!(MemoryChannelState::default().materialized(), None);
3161    }
3162
3163    /// The coarse-only setter must not disturb the other two components: a
3164    /// re-stamp of the written-space set is not a re-materialization.
3165    #[test]
3166    fn stamping_written_spaces_preserves_the_materialized_interface() {
3167        let mut ctx = Context::new();
3168        let fid = FunctionBody::make(&mut ctx, "keeps_interface".into())
3169            .unwrap()
3170            .id;
3171        let map = MemoryInterfaceMap {
3172            inputs: vec![slot()],
3173            outputs: vec![],
3174        };
3175        let mut body = FunctionBody::from_id_mut(&mut ctx, fid);
3176        body.set_memory_effects(MemoryChannelState {
3177            materialized: Some(map.clone()),
3178            ..MemoryChannelState::default()
3179        });
3180        body.set_written_spaces(None);
3181
3182        let effects = FunctionBody::from_id(&ctx, fid).effects().memory.clone();
3183        assert_eq!(effects.materialized(), Some(&map));
3184        assert_eq!(effects.coarse, WrittenSpacesState::Unbounded);
3185    }
3186
3187    /// `Unmappable` and `Global` are both address-less bases, but only the
3188    /// second is bindable. A consumer that collapsed them would synthesize a
3189    /// load from a bogus absolute address for a base it never resolved.
3190    #[test]
3191    fn an_unmappable_base_is_distinct_from_a_global_and_is_not_bindable() {
3192        let unmappable = InterfaceSlot {
3193            base: SlotBase::Unmappable,
3194            offset: 0,
3195            size: 8,
3196        };
3197        let global = InterfaceSlot {
3198            base: SlotBase::Global(0),
3199            offset: 0,
3200            size: 8,
3201        };
3202        assert_ne!(unmappable, global);
3203        assert!(!unmappable.is_bindable());
3204        assert!(global.is_bindable());
3205        assert!(
3206            InterfaceSlot {
3207                base: SlotBase::Arg(0),
3208                offset: -8,
3209                size: 8,
3210            }
3211            .is_bindable()
3212        );
3213    }
3214}