Skip to main content

qcode/value/
view_mut.rs

1//! The mutable-host counterpart of [`QCodeView`]: one trait carrying the
2//! body-local mutation verbs, implemented by both mutation hosts.
3//!
4//! [`Context`] resolves any function body in the module (the sequential/module
5//! path); [`BodyMut`] resolves exactly one checked-out body (the function-pass
6//! path, which panics on a foreign [`FunctionId`]). Every provided verb is a
7//! one-liner into the canonical inherent implementation on [`FunctionBody`], so
8//! the verb logic exists in exactly one place and the hosts cannot drift.
9//!
10//! The trait's contract is strictly **body-local** mutation. Anything that
11//! writes shared module state — name registration in the global table, varnode
12//! or function minting — is deliberately absent and stays inherent on
13//! [`Context`], so "a checked-out pass cannot touch shared state" is documented
14//! by the type system. The births (`push_block`, `push_insn`, `push_mnemonic*`,
15//! …) and `remove_cfg_edge` also stay inherent per host: their spellings
16//! diverge (the module path names the function, the checked-out path doesn't),
17//! and they are thin arena pushes with no drift-prone logic.
18
19use jstd::registry::Registry;
20use rustc_hash::{FxHashMap, FxHashSet};
21
22use std::borrow::Cow;
23
24use crate::{
25    context::{Context, Shared},
26    error::Result,
27    value::{
28        BasicBlock, BodyView, FunctionBody, FunctionId, Instruction, ModuleView, QCodeView,
29        ValueId,
30        block::{BlockId, EdgeId},
31        block_param::BlockParam,
32        block_param::BlockParamId,
33        function::FunctionInterface,
34        insn::{InstructionId, LocalInsnId, Mnemonic},
35        util::body_mut::BodyMut,
36    },
37};
38
39/// Body-local mutation capability shared by the module host ([`Context`]) and
40/// the checked-out pass host ([`BodyMut`]).
41///
42/// The primitives (`function_mut`, `shr`, `interfaces`, `view`) are the whole
43/// per-host surface; every verb is a provided method delegating to the
44/// [`FunctionBody`] canon through the function named by its arguments' ids.
45pub trait QCodeMut<'str> {
46    /// The host's `Copy` read provider ([`ModuleView`] or [`BodyView`]).
47    type View<'v>: QCodeView<'v, 'str>
48    where
49        Self: 'v,
50        'str: 'v;
51
52    /// The storage of the function `id` (write). The checked-out host panics if
53    /// `id` is not its own function.
54    fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str>;
55
56    /// The storage of the function `id` (read), tied to `&self`. The
57    /// borrow-friendly read primitive for host-generic ref code: a fully
58    /// generic `H` cannot prove `'str` outlives a [`view`](Self::view) GAT
59    /// borrow, but a plain `&self`-tied borrow needs no such proof.
60    fn body(&self, id: FunctionId) -> &FunctionBody<'str>;
61
62    /// The module's shared IR state (read-only through this trait).
63    fn shr(&self) -> &Shared<'str>;
64
65    /// Every function's published interface (the caller-reasoning surface).
66    fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>>;
67
68    /// The static immutable provider for reads over this host.
69    fn view(&self) -> Self::View<'_>;
70
71    // ---- derived arena accessors ---------------------------------------------
72
73    /// Mutably borrows the instruction `id` from its owning function's arena.
74    fn instruction_mut(&mut self, id: InstructionId) -> &mut Instruction<'str> {
75        &mut self.function_mut(id.func).insns[id.local]
76    }
77
78    /// Mutably borrows the block `id` from its owning function's arena.
79    fn block_mut(&mut self, id: BlockId) -> &mut BasicBlock<'str> {
80        &mut self.function_mut(id.func).blocks[id.local]
81    }
82
83    /// Mutably borrows the block parameter `id` from its owning function's arena.
84    fn block_param_mut(&mut self, id: BlockParamId) -> &mut BlockParam<'str> {
85        &mut self.function_mut(id.func).params[id.local]
86    }
87
88    // ---- body-local verbs (canon: inherent methods on `FunctionBody`) -------
89
90    /// Register `name` for the function-scoped `id` (block/instruction/param/
91    /// Temp) in its owning function's local name table. Panics on a
92    /// global-scoped `id` — shared-name registration is a module-only operation
93    /// outside this trait's body-local contract. Errors only on a duplicate
94    /// name.
95    fn register_body_name(
96        &mut self,
97        id: ValueId,
98        name: Cow<'str, str>,
99        old_name: Option<&str>,
100    ) -> Result<()> {
101        let func = id
102            .name_scope_function()
103            .expect("register_body_name on a global-scoped value");
104        self.function_mut(func)
105            .register_body_name(id, name, old_name)
106    }
107
108    /// Physically removes a block parameter and its local bookkeeping.
109    /// Positional block and edge-argument rewrites belong to the caller and may
110    /// complete later in the same transformation.
111    fn remove_block_param(&mut self, id: BlockParamId) {
112        self.function_mut(id.func).remove_block_param(id);
113    }
114
115    /// Insert `insn` immediately before `before` in `block`, setting its parent.
116    fn insert_insn_before(&mut self, block: BlockId, before: InstructionId, insn: InstructionId) {
117        self.function_mut(block.func)
118            .insert_insn_before(block, before, insn);
119    }
120
121    /// Move `insn` immediately before the arbitrary live instruction `before`,
122    /// preserving the moved instruction's stable ID. Both instructions must
123    /// belong to the same function; the destination block is inferred from the
124    /// anchor.
125    fn move_insn_before(&mut self, insn: InstructionId, before: InstructionId) {
126        self.function_mut(insn.func).move_insn_before(insn, before);
127    }
128
129    /// Adds a directed edge in the CFG from `from` to `to`, returning its id.
130    ///
131    /// Both endpoints must belong to the same function: CFG edges are strictly
132    /// intra-function (context-split ruling 2). An inter-procedural transfer is
133    /// a function-level `TailCall`/`Call`, never an edge — the lifter emits
134    /// those at construction, so no producer creates a cross-function edge. The
135    /// permanent `debug_assert` below is the tripwire that keeps that invariant
136    /// honest (it is the probe from 06a §10, now a keeper because the invariant
137    /// finally holds).
138    fn add_cfg_edge(&mut self, from: BlockId, to: BlockId) -> EdgeId {
139        debug_assert_eq!(
140            from.func, to.func,
141            "cross-function CFG edge {from:?} -> {to:?} (strict IR locality, ruling 2)"
142        );
143        self.function_mut(from.func).add_cfg_edge(from, to)
144    }
145
146    /// Replace every use of `old` with `new` across `old`'s owning function and
147    /// update the reverse use-map (SSA defs only; all uses are intra-function).
148    fn replace_all_uses_with(&mut self, old: impl Into<ValueId>, new: impl Into<ValueId>) {
149        let old = old.into();
150        let new = new.into();
151        if old == new {
152            return;
153        }
154        // A shared value (literal/bytes/varnode) has no owning function and no
155        // locatable user list; replacing its uses is a no-op here.
156        let Some(func) = old.owning_function() else {
157            return;
158        };
159        self.function_mut(func).replace_all_uses_with(old, new);
160    }
161
162    /// Remove instruction `id` from its block, unlink its outgoing CFG edges if
163    /// a terminator, clear its name, prune its operand use-lists, and
164    /// physically drop its payload.
165    fn remove_instruction(&mut self, id: InstructionId) {
166        self.function_mut(id.func).remove_instruction(id);
167    }
168
169    /// Replace every use of instruction `id` with `new`, then remove `id` —
170    /// the standard "rewrite to a cheaper value" epilogue
171    /// ([`replace_all_uses_with`](Self::replace_all_uses_with) +
172    /// [`remove_instruction`](Self::remove_instruction)).
173    fn replace_instruction(&mut self, id: InstructionId, new: impl Into<ValueId>) {
174        self.function_mut(id.func)
175            .replace_instruction(id, new.into());
176    }
177
178    /// Physically removes a set of instructions after pruning their operands
179    /// from the reverse-use maps, grouped per owning function. Call after
180    /// removing them from their parent blocks and unlinking any CFG edges owned
181    /// by terminators.
182    fn remove_instructions(&mut self, dead: &FxHashSet<InstructionId>) {
183        let mut by_func: FxHashMap<FunctionId, FxHashSet<LocalInsnId>> = FxHashMap::default();
184        for &id in dead {
185            by_func.entry(id.func).or_default().insert(id.local);
186        }
187        for (func, dead) in by_func {
188            self.function_mut(func).remove_instructions(&dead);
189        }
190    }
191
192    /// Rehome `remove`'s outgoing CFG edges onto `keep`. The direct edge and
193    /// `keep`'s forwarding terminator have already been removed by the caller.
194    fn rehome_outgoing_edges(&mut self, keep: BlockId, remove: BlockId) {
195        self.function_mut(keep.func)
196            .rehome_outgoing_edges(keep, remove);
197    }
198
199    /// Replace an instruction's mnemonic in place, keeping the reverse use-map
200    /// in sync. For transforms that change an instruction without changing its
201    /// identity, parent block, address, or result type.
202    fn replace_instruction_mnemonic(&mut self, id: InstructionId, mnemonic: Mnemonic) {
203        self.function_mut(id.func)
204            .replace_instruction_mnemonic(id, mnemonic);
205    }
206
207    /// Drop `block` from its function's ownership roster. Ownership is derived
208    /// from the storing arena (`block.func`); the arena slot is untouched.
209    fn unroster_block(&mut self, block: BlockId) {
210        self.function_mut(block.func).unroster_block(block);
211    }
212
213    /// Remove `block` from its function: unlink every incident CFG edge, remove
214    /// its instructions and params, clear ownership metadata, then drop its
215    /// payload.
216    fn delete_block(&mut self, block: BlockId) {
217        self.function_mut(block.func).delete_block(block);
218    }
219
220    /// Absorb `other` into `keep`: drop `keep`'s terminal branch, append
221    /// `other`'s instructions, rehome its outgoing edges, and remove it.
222    /// `edge_ab` is the direct edge `keep -> other`.
223    fn absorb_block(&mut self, keep: BlockId, other: BlockId, edge_ab: EdgeId) {
224        self.function_mut(keep.func)
225            .absorb_block(keep, other, edge_ab);
226    }
227}
228
229/// A `&mut` to a host is itself a host, so a `BaseRef<&mut Context, _>`
230/// mutation ref (whose `ctx` field is a reborrowable `&mut Context`) satisfies
231/// the same generic bound as a by-value `BodyMut` host.
232impl<'str, H: QCodeMut<'str>> QCodeMut<'str> for &mut H {
233    type View<'v>
234        = H::View<'v>
235    where
236        Self: 'v,
237        'str: 'v;
238
239    fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str> {
240        (**self).function_mut(id)
241    }
242
243    fn body(&self, id: FunctionId) -> &FunctionBody<'str> {
244        (**self).body(id)
245    }
246
247    fn shr(&self) -> &Shared<'str> {
248        (**self).shr()
249    }
250
251    fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>> {
252        (**self).interfaces()
253    }
254
255    fn view(&self) -> Self::View<'_> {
256        (**self).view()
257    }
258}
259
260impl<'str> QCodeMut<'str> for Context<'str> {
261    type View<'v>
262        = ModuleView<'v, 'str>
263    where
264        Self: 'v,
265        'str: 'v;
266
267    fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str> {
268        &mut self.bodies[id]
269    }
270
271    fn body(&self, id: FunctionId) -> &FunctionBody<'str> {
272        &self.bodies[id]
273    }
274
275    fn shr(&self) -> &Shared<'str> {
276        &self.shared
277    }
278
279    fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>> {
280        &self.interfaces
281    }
282
283    fn view(&self) -> ModuleView<'_, 'str> {
284        ModuleView::new(self)
285    }
286}
287
288impl<'a, 'str> QCodeMut<'str> for BodyMut<'a, 'str> {
289    type View<'v>
290        = BodyView<'v, 'str>
291    where
292        Self: 'v,
293        'str: 'v;
294
295    fn function_mut(&mut self, id: FunctionId) -> &mut FunctionBody<'str> {
296        BodyMut::function_mut(self, id)
297    }
298
299    fn body(&self, id: FunctionId) -> &FunctionBody<'str> {
300        BodyMut::function(self, id)
301    }
302
303    fn shr(&self) -> &Shared<'str> {
304        self.shared
305    }
306
307    fn interfaces(&self) -> &Registry<FunctionId, FunctionInterface<'str>> {
308        self.interfaces
309    }
310
311    fn view(&self) -> BodyView<'_, 'str> {
312        BodyMut::view(self)
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319    use crate::value::{BasicBlock, insn::InstructionId};
320
321    /// The same generic transform runs unchanged over both mutation hosts.
322    fn absorb_forwarding_pair<'str>(host: &mut impl QCodeMut<'str>, func: FunctionId) {
323        let (keep, other, edge) = {
324            let view = host.view();
325            let ids = view.function_ref(func).block_ids();
326            let [keep, other] = ids[..] else {
327                panic!("expected exactly two rostered blocks");
328            };
329            let edge = *view.block(keep).edges.iter().next().expect("edge");
330            (keep, other, edge)
331        };
332        host.absorb_block(keep, other, edge);
333    }
334
335    fn forwarding_pair(ctx: &mut Context<'_>) -> (FunctionId, InstructionId) {
336        let func = FunctionBody::make(ctx, "f".into()).unwrap().id;
337        let keep = BasicBlock::make(ctx, func).id;
338        let other = BasicBlock::make(ctx, func).id;
339        ctx.bodies[func].set_root_id(Some(keep.local));
340        let value = ctx.get_const(7, 8).id();
341        let ret = ctx.builder(other).push_return(value).id;
342        ctx.builder(keep).push_branch(other);
343        (func, ret)
344    }
345
346    #[test]
347    fn module_host_runs_generic_transform() {
348        let mut ctx = Context::new();
349        let (func, ret) = forwarding_pair(&mut ctx);
350        absorb_forwarding_pair(&mut ctx, func);
351        assert_eq!(ctx.view().function_ref(func).block_ids().len(), 1);
352        assert!(ctx.contains_instruction(ret));
353    }
354
355    #[test]
356    fn checked_out_host_runs_generic_transform() {
357        let mut ctx = Context::new();
358        let (func, ret) = forwarding_pair(&mut ctx);
359        {
360            let mut host = BodyMut::new(&mut ctx.bodies[func], &ctx.shared, &ctx.interfaces);
361            absorb_forwarding_pair(&mut host, func);
362            assert_eq!(
363                QCodeMut::view(&host).function_ref(func).block_ids().len(),
364                1
365            );
366        }
367        assert!(ctx.contains_instruction(ret));
368    }
369
370    #[test]
371    fn shared_value_rauw_is_a_noop() {
372        let mut ctx = Context::new();
373        let (_, ret) = forwarding_pair(&mut ctx);
374        let lit = ctx.get_const(7, 8).id();
375        let other = ctx.get_const(9, 8).id();
376        QCodeMut::replace_all_uses_with(&mut ctx, lit, other);
377        assert!(ctx.contains_instruction(ret));
378    }
379}