Skip to main content

qcode/value/
insn.rs

1//! Core SSA value type: instructions. An instruction is a value that is defined by an operation and can be used by other instructions.
2//! Each instruction has a mnemonic, which is the operation that it performs, and a size
3//! in bytes of the value it defines.
4//! Instructions that do not define a value (e.g. terminators) have a size of 0.
5use crate::value::QCodeMut;
6use crate::{
7    context::Context,
8    error::Result,
9    space::{MemorySpaceId, Space, SpaceId, SpaceRef, SpaceType},
10    types::TypeId,
11    value::{
12        BlockId, BlockRef, FunctionId, FunctionRef, LocalBlockId, ModuleView, QCodeView, Value,
13        ValueId,
14        util::{
15            base_ref::{BaseRef, WithCtx, WithCtxMut},
16            named::{Named, Renameable},
17        },
18    },
19};
20use jstd::Identifier;
21use std::{
22    borrow::Cow,
23    fmt::{Display, Formatter},
24    marker::PhantomData,
25};
26
27mod aggregate;
28mod assert;
29mod binop;
30mod bits;
31mod casting;
32mod flags;
33pub(crate) mod intrinsic;
34mod map;
35mod memory;
36mod mnemonic;
37mod pcode_op;
38mod scan;
39pub mod segment;
40mod terminator;
41mod unop;
42
43pub use aggregate::{Extract, Gep, Tuple};
44pub use assert::Assert;
45pub use binop::{Binary, Binop, FloatBinop, IntBinop};
46pub use casting::{FloatToFloat, FloatToInt, IntToFloat, Range, Sext, Zext};
47pub use flags::{Carry, IsFloatNaN, LzCount, PopCount, SBorrow, SCarry};
48pub use intrinsic::{
49    Intrinsic, IntrinsicApp, IntrinsicId, IntrinsicRegistration, RootOp, Simplified,
50    recognizers_for,
51};
52pub use map::Map;
53pub use memory::{Load, Store};
54pub use mnemonic::Mnemonic;
55pub use pcode_op::{PCodeOp, PCodeOpId};
56pub use scan::Scan;
57pub use terminator::{
58    Apply, BadInsn, Branch, BranchInd, CBranch, Call, CallInd, CallTag, Callee, Return,
59    ReturnValue, Switch, SwitchArm, TailCall,
60};
61pub use unop::{Unary, Unop};
62
63/// Function-local instruction index. Storage detail: indexes the owning
64/// [`FunctionBody`](crate::value::FunctionBody)'s instruction arena. Pass composite
65/// [`InstructionId`]s around in pass code, not these.
66#[derive(Identifier)]
67pub struct LocalInsnId(u32);
68
69crate::composite_id!(InstructionId, LocalInsnId);
70
71/// A local SSA value, which is a value that is defined by an instruction and can be used by other instructions.
72/// Local values are not associated with any particular memory location.
73#[derive(Clone, serde::Serialize, serde::Deserialize)]
74pub struct Instruction<'str> {
75    /// The name of this instruction
76    pub(crate) name: Option<Cow<'str, str>>,
77
78    /// The type of this instruction's result value (encodes size and semantic kind).
79    pub(crate) type_id: TypeId,
80
81    /// The instruction which defines this value.
82    mnemonic: Mnemonic,
83
84    /// The block that this instruction belongs to, if any (bare body-local index;
85    /// strict IR locality means the parent block lives in the same arena as the
86    /// instruction, so its owning `FunctionId` is the instruction's own `id.func`).
87    /// Instructions that are not part of any block (e.g. lifted from data sections) have `None` here.
88    pub(crate) parent: Option<LocalBlockId>,
89
90    // Address of the binary instruction
91    address: Option<u64>,
92
93    _marker: std::marker::PhantomData<&'str ()>,
94}
95
96impl<'str> Instruction<'str> {
97    pub(crate) fn new(type_id: TypeId, mnemonic: Mnemonic) -> Self {
98        Self {
99            name: None,
100            parent: None,
101            type_id,
102            mnemonic,
103            address: None,
104            _marker: std::marker::PhantomData,
105        }
106    }
107
108    pub fn mnemonic(&self) -> &Mnemonic {
109        &self.mnemonic
110    }
111
112    /// Mutable access to this instruction's mnemonic (crate-internal; used by the
113    /// generic mutation host to rewrite operands).
114    pub(crate) fn mnemonic_mut(&mut self) -> &mut Mnemonic {
115        &mut self.mnemonic
116    }
117
118    /// Sets this instruction's machine address (crate-internal; used by the
119    /// generic builder, which routes through the mutation host).
120    pub(crate) fn set_address(&mut self, address: u64) {
121        self.address = Some(address);
122    }
123
124    pub fn from_id<'ctx>(
125        ctx: &'ctx Context<'str>,
126        id: InstructionId,
127    ) -> InstructionRef<'str, 'ctx> {
128        InstructionRef::new(ModuleView::new(ctx), id)
129    }
130
131    pub fn from_id_mut<'ctx>(
132        ctx: &'ctx mut Context<'str>,
133        id: InstructionId,
134    ) -> InstructionMutRef<'str, 'ctx> {
135        InstructionMutRef::from_id(ctx, id)
136    }
137}
138
139impl<'s, 'ctx: 's, 'str: 'ctx, R> InstructionRef<'str, 'ctx, R>
140where
141    R: QCodeView<'ctx, 'str>,
142{
143    fn inner(&'s self) -> &'ctx Instruction<'str> {
144        self.view.instruction(self.id)
145    }
146
147    /// The name of this instruction's output value
148    pub fn name(&'s self) -> Option<&'ctx str> {
149        self.inner().name.as_deref()
150    }
151
152    /// The [`TypeId`] of this instruction's result value.
153    pub fn type_id(&'s self) -> TypeId {
154        self.inner().type_id
155    }
156
157    /// The size in bytes of the instruction's output value
158    pub fn size(&'s self) -> usize {
159        self.view.shared().types.size_of(self.inner().type_id)
160    }
161
162    /// The basic block that this instruction belongs to, if any.
163    pub fn parent(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
164        self.inner()
165            .parent
166            .map(|local| BlockRef::new(self.view, BlockId::new(self.id.func, local)))
167    }
168
169    pub fn block(&'s self) -> Option<BlockRef<'str, 'ctx, R>> {
170        self.parent()
171    }
172
173    /// The function that this instruction belongs to, if any.
174    pub fn function(&'s self) -> Option<FunctionRef<'str, 'ctx, R>> {
175        self.parent().and_then(|block| block.parent())
176    }
177
178    /// The mnemonic of the instruction
179    pub fn mnemonic(&'s self) -> &'ctx Mnemonic {
180        &self.inner().mnemonic
181    }
182
183    /// The operands consumed by this instruction, as qualified [`ValueId`]s.
184    ///
185    /// This is the func-qualifying, pass-facing operand accessor (stage 6a §11,
186    /// option b): it is the routing target for the `insn.mnemonic().args()`
187    /// call sites. Today it forwards `MnemonicKind::args`
188    /// verbatim; once in-body operand storage flips to `LocalValueId`, only this
189    /// body changes — it qualifies each local operand with the owning function
190    /// (`self.id.func`), which a bare `&Mnemonic` cannot do — so every caller
191    /// keeps seeing qualified `ValueId`s unchanged.
192    pub fn operands(&'s self) -> smallvec::SmallVec<[ValueId; 2]> {
193        let func = self.id.func;
194        self.mnemonic()
195            .args()
196            .into_iter()
197            .map(|v| v.qualify(func))
198            .collect()
199    }
200
201    /// The address of the corresponding instruction
202    pub fn address(&'s self) -> Option<u64> {
203        self.inner().address
204    }
205
206    /// The address-space provenance for this instruction's result, if any.
207    ///
208    /// Returns `Some` only for instructions whose result type is a pointer to a
209    /// known memory space (e.g. `StackAddress`).
210    pub fn space(&'s self) -> Option<SpaceRef<'ctx>> {
211        self.view
212            .shared()
213            .types
214            .space_of(self.inner().type_id)
215            .and_then(MemorySpaceId::shared)
216            .map(|id| Space::from_id(self.view.shared(), id))
217    }
218
219    /// Qualified address-space provenance, including body-local temporary
220    /// spaces that cannot be represented by [`SpaceRef`].
221    pub fn memory_space(&'s self) -> Option<MemorySpaceId> {
222        self.view.shared().types.space_of(self.inner().type_id)
223    }
224
225    /// The opcode for this instruction
226    pub fn opcode(&'s self) -> &'static str {
227        self.mnemonic().opcode()
228    }
229
230    /// Is this instruction a terminator (i.e. does it end a basic block)?
231    pub fn is_terminator(&'s self) -> bool {
232        self.mnemonic().is_terminator()
233    }
234
235    fn fmt(&'s self, f: &mut Formatter<'_>) -> std::fmt::Result {
236        let ty = self.view.shared().types.type_name(self.type_id());
237
238        if let Some(name) = self.name() {
239            write!(f, "{ty} %{name}")
240        } else {
241            // Function-local index: `%tmp{local}` is unique within a function,
242            // which is the scope the parser resolves names in.
243            let id: usize = self.id.local.into();
244            write!(f, "{ty} %tmp{id:x}")
245        }
246    }
247}
248
249// Own-instruction mutations, emitted for each concrete mutation backing —
250// `&mut Context` (module) and `BodyMut` (checked-out function pass) — so a
251// `FunctionPass` can retype and rename the instructions it owns whether the
252// function lives in the module registry or has been checked out. Mirror the
253// `&mut Context`-only [`InstructionMutRef::set_type`] / `Renameable` impls.
254// The own-instruction mutation verbs, written once over any [`QCodeMut`]
255// backing — `&mut Context` (module) and `BodyMut` (checked-out function pass).
256impl<'str, H: QCodeMut<'str>> BaseRef<H, InstructionId> {
257    /// Sets this instruction's result type (own-instruction edit, host-routed).
258    /// Panics on an incompatible same-nonzero-size change, exactly like
259    /// [`InstructionMutRef::set_type`].
260    pub fn set_result_type(&mut self, new_type: TypeId) {
261        let current = self.ctx.body(self.id.func).insn(self.id).type_id;
262        let (current_size, new_size) = {
263            let types = &self.ctx.shr().types;
264            (types.size_of(current), types.size_of(new_type))
265        };
266        assert!(
267            current_size == 0 || current_size == new_size,
268            "cannot change instruction result type: size {current_size} → {new_size}",
269        );
270        self.ctx.instruction_mut(self.id).type_id = new_type;
271    }
272
273    /// Renames this instruction in its owning function's local name table
274    /// (own-instruction edit, host-routed). Errors only on a duplicate name.
275    pub fn rename_local(&mut self, name: Cow<'str, str>) -> Result<()> {
276        let old_name = self
277            .ctx
278            .body(self.id.func)
279            .insn(self.id)
280            .name
281            .as_deref()
282            .map(str::to_owned);
283        self.ctx
284            .register_body_name(self.id.into(), name.clone(), old_name.as_deref())?;
285        self.ctx.instruction_mut(self.id).name = Some(name);
286        Ok(())
287    }
288}
289
290#[derive(Clone, Copy)]
291pub struct InstructionRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
292    pub id: InstructionId,
293    pub(in crate::value) view: R,
294    marker: PhantomData<&'ctx &'str ()>,
295}
296
297impl<'str, 'ctx, R> InstructionRef<'str, 'ctx, R> {
298    pub fn new(view: R, id: InstructionId) -> Self {
299        Self {
300            id,
301            view,
302            marker: PhantomData,
303        }
304    }
305
306    pub fn id(&self) -> ValueId {
307        self.id.into()
308    }
309
310    /// Format this instruction as a string, with the mnemonic and operands.
311    pub fn as_statement(&self) -> InstructionStatement<'_, 'str, 'ctx, R> {
312        InstructionStatement(self)
313    }
314}
315
316impl<'str, 'ctx> InstructionRef<'str, 'ctx> {
317    pub fn from_id(ctx: &'ctx Context<'str>, id: InstructionId) -> Self {
318        Self::new(ModuleView::new(ctx), id)
319    }
320
321    /// Creates an instruction with a plain `Int(size)` result type, born into
322    /// `func`'s instruction arena. The result is detached (`parent == None`)
323    /// until a block appends it.
324    pub fn from_mnemonic(
325        ctx: &'ctx mut Context<'str>,
326        func: FunctionId,
327        mnemonic: Mnemonic,
328        size: usize,
329    ) -> Self {
330        let type_id = ctx.shared.types.get_or_make_int(size);
331        let insn = Instruction::new(type_id, mnemonic);
332        let id = ctx.push_insn(func, insn);
333        InstructionRef::new(ModuleView::new(ctx), id)
334    }
335
336    /// Creates an instruction with an explicit [`TypeId`], born into `func`.
337    ///
338    /// Pass a `StackAddress` type id when the
339    /// result is a stack-space pointer. Register-space provenance is silently
340    /// demoted to `Int` (pointer arithmetic on registers is not meaningful).
341    pub fn from_mnemonic_with_type(
342        ctx: &'ctx mut Context<'str>,
343        func: FunctionId,
344        mnemonic: Mnemonic,
345        type_id: TypeId,
346    ) -> Self {
347        let insn = Instruction::new(type_id, mnemonic);
348        let id = ctx.push_insn(func, insn);
349        InstructionRef::new(ModuleView::new(ctx), id)
350    }
351
352    /// Creates an instruction, deriving the result type from an optional space tag.
353    ///
354    /// This is a migration shim that types the result as `Int(size)` regardless of
355    /// the `space` tag. New code should use `from_mnemonic_with_type` directly.
356    pub fn from_mnemonic_with_space(
357        ctx: &'ctx mut Context<'str>,
358        func: FunctionId,
359        mnemonic: Mnemonic,
360        size: usize,
361        _space: Option<SpaceId>,
362    ) -> Self {
363        let type_id = ctx.shared.types.get_or_make_int(size);
364        let insn = Instruction::new(type_id, mnemonic);
365        let id = ctx.push_insn(func, insn);
366        InstructionRef::new(ModuleView::new(ctx), id)
367    }
368}
369
370impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 'ctx, 'str> for InstructionRef<'str, 'ctx> {
371    fn ctx(&'s self) -> &'ctx Context<'str> {
372        // Module-scope-only escape hatch: shared-only reads go through
373        // `host().shr()`; only whole-module walks (callees/callers) reach here,
374        // and those panic on a checked-out host by design (context-split Pin B).
375        self.view.context()
376    }
377}
378
379impl<'str: 'ctx, 'ctx, R> Named for InstructionRef<'str, 'ctx, R>
380where
381    R: QCodeView<'ctx, 'str>,
382{
383    fn name(&self) -> Option<&str> {
384        self.view.instruction(self.id).name.as_deref()
385    }
386}
387
388impl<'str: 'ctx, 'ctx, R> Display for InstructionRef<'str, 'ctx, R>
389where
390    R: QCodeView<'ctx, 'str>,
391{
392    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
393        InstructionRef::fmt(self, f)
394    }
395}
396
397impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for InstructionRef<'str, 'ctx, R>
398where
399    R: QCodeView<'ctx, 'str>,
400{
401    fn id(&self) -> ValueId {
402        self.id()
403    }
404
405    fn size(&self) -> usize {
406        InstructionRef::size(self)
407    }
408}
409
410pub type InstructionMutRef<'str, 'ctx> = BaseRef<&'ctx mut Context<'str>, InstructionId>;
411
412impl<'str, 'ctx> InstructionMutRef<'str, 'ctx> {
413    pub fn as_ref(&self) -> InstructionRef<'str, '_> {
414        InstructionRef::new(ModuleView::new(self.ctx), self.id)
415    }
416
417    fn inner(&self) -> &Instruction<'str> {
418        self.ctx.instruction(self.id)
419    }
420
421    pub fn inner_mut(&mut self) -> &mut Instruction<'str> {
422        self.ctx.instruction_mut(self.id)
423    }
424
425    pub fn mnemonic_mut(&mut self) -> &mut Mnemonic {
426        &mut self.inner_mut().mnemonic
427    }
428
429    /// Replace this instruction's mnemonic while keeping the reverse use-def
430    /// map in sync.
431    pub fn set_mnemonic(&mut self, mnemonic: Mnemonic) {
432        let old_args = self.inner().mnemonic.args();
433        let new_args = mnemonic.args();
434
435        // Operand uses are recorded in this instruction's own function map.
436        let func = self.id.func;
437        for arg in old_args {
438            if let Some(users) = self.ctx.bodies[func].users.get_mut(&arg) {
439                users.retain(|&local| local != self.id.localize(func));
440            }
441        }
442
443        for arg in new_args {
444            self.ctx.bodies[func]
445                .users
446                .entry(arg)
447                .or_default()
448                .push(self.id.localize(func));
449        }
450
451        self.inner_mut().mnemonic = mnemonic;
452    }
453
454    pub fn address_mut(&mut self) -> &mut Option<u64> {
455        &mut self.inner_mut().address
456    }
457
458    pub fn set_address(&mut self, address: u64) {
459        *self.address_mut() = Some(address);
460    }
461
462    /// Sets the type of this instruction's result.
463    ///
464    /// Panics if the instruction already has a type that is incompatible with
465    /// `new_type` (same size but different kind).
466    pub fn set_type(&mut self, new_type: TypeId) {
467        let current = self.inner().type_id;
468        let current_size = self.ctx.shared.types.size_of(current);
469        let new_size = self.ctx.shared.types.size_of(new_type);
470        if current_size != 0 && current_size != new_size {
471            panic!(
472                "Cannot change type of instruction {}: size {} → {}",
473                self, current_size, new_size
474            );
475        }
476        self.inner_mut().type_id = new_type;
477    }
478
479    /// Sets the result type, allowing the byte size to change.
480    ///
481    /// Unlike [`set_type`](Self::set_type), this does not enforce size
482    /// invariance: it exists for transforms that legitimately resize an
483    /// aggregate result, such as trimming dead fields from a returned write-set
484    /// (`dead_signature`). Prefer `set_type` for any same-size retype.
485    pub fn set_type_resized(&mut self, new_type: TypeId) {
486        self.inner_mut().type_id = new_type;
487    }
488
489    /// Sets the address-space provenance of this instruction's result.
490    ///
491    /// A non-register space promotes the result to a
492    /// [`SpaceAddress`](crate::types::SpaceAddress) of the same byte width.
493    /// Register spaces are ignored (pointer arithmetic is not allowed in the
494    /// register space).
495    pub fn set_space(&mut self, space: SpaceId) {
496        // Pointer arithmetic is not allowed in the register space.
497        if matches!(
498            Space::from_id(&self.ctx.shared, space).ty,
499            SpaceType::Register
500        ) {
501            return;
502        }
503        let size = self.ctx.shared.types.size_of(self.inner().type_id);
504        let type_id = self.ctx.shared.types.get_or_make_space_address(size, space);
505        self.inner_mut().type_id = type_id;
506    }
507}
508
509impl<'s, 'ctx: 's, 'str: 'ctx> WithCtx<'s, 's, 'str> for InstructionMutRef<'str, 'ctx> {
510    fn ctx(&'s self) -> &'s Context<'str> {
511        self.ctx
512    }
513}
514
515impl<'s, 'ctx: 's, 'str: 'ctx> WithCtxMut<'s, 'str> for InstructionMutRef<'str, 'ctx> {
516    fn ctx_mut(&'s mut self) -> &'s mut Context<'str> {
517        self.ctx
518    }
519}
520
521// Reading an instruction's name stays concrete per host: `Named::name`'s
522// signature-pinned return lifetime needs `'str` to outlive the `&self` borrow,
523// which only a host type that carries `'str` (not a generic `H`) can prove.
524impl Named for InstructionMutRef<'_, '_> {
525    fn name(&self) -> Option<&str> {
526        self.ctx.instruction(self.id).name.as_deref()
527    }
528}
529
530impl Display for InstructionMutRef<'_, '_> {
531    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
532        self.as_ref().fmt(f)
533    }
534}
535
536impl<'str, 'ctx> Value<'str, 'ctx> for InstructionMutRef<'str, 'ctx> {
537    fn id(&self) -> ValueId {
538        self.id()
539    }
540
541    fn size(&self) -> usize {
542        self.as_ref().size()
543    }
544}
545
546// Renaming works over any mutation host (instruction names are function-local).
547impl<'str, 'ctx, H: QCodeMut<'str>> Renameable<'str, 'ctx> for BaseRef<H, InstructionId>
548where
549    Self: Named,
550{
551    fn rename(&mut self, name: Cow<'str, str>) -> Result<()> {
552        self.rename_local(name)
553    }
554}
555
556/// A formattable wrapper around an instruction reference, which formats the instruction as a string with its mnemonic and operands.
557pub struct InstructionStatement<'a, 'str, 'ctx, R = ModuleView<'ctx, 'str>>(
558    &'a InstructionRef<'str, 'ctx, R>,
559);
560
561impl<'str: 'ctx, 'ctx, R> Display for InstructionStatement<'_, 'str, 'ctx, R>
562where
563    R: QCodeView<'ctx, 'str>,
564{
565    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
566        // The statement's rendering (result binding + mnemonic) is defined once,
567        // as tokens, in `segment`; the `Display` form is those tokens joined.
568        for token in segment::instruction_segments(self.0) {
569            write!(f, "{}", token.text)?;
570        }
571        Ok(())
572    }
573}