Skip to main content

qcode/value/insn/
intrinsic.rs

1//! Pure intrinsic functions: named, side-effect-free operations such as `rol`
2//! and `ror`.
3//!
4//! An intrinsic is represented by a single [`Mnemonic::Intrinsic`] variant
5//! carrying an [`IntrinsicId`] plus its operands — there is no dedicated
6//! mnemonic per intrinsic and no control-flow call. Semantics live in an
7//! [`Intrinsic`] definition looked up from a process-global registry.
8//!
9//! # Purity
10//!
11//! `Mnemonic::Intrinsic` is *categorically pure*: it has no memory effects and
12//! no observable side effects. Passes treat the variant itself as the purity
13//! contract (DCE may drop an unused intrinsic; GVN/CSE may dedup one). Anything
14//! impure (syscalls, `rdtsc`, …) stays a [`PCodeOp`](super::PCodeOp).
15//!
16//! # Registry
17//!
18//! Built-in intrinsics self-register with [`inventory`] via
19//! [`register_intrinsic!`], mirroring the pass registry. The id-indexed table
20//! and the name→id map are built once, lazily, from `inventory::iter`. An
21//! [`IntrinsicId`] is a runtime handle (cheap to copy/match/hash) but
22//! *serializes by name*, so the on-disk form is stable regardless of link
23//! order and an unknown name is a clean deserialize error.
24
25use rustc_hash::FxHashMap as HashMap;
26use std::sync::OnceLock;
27
28use super::binop::IntBinop;
29use super::mnemonic::{Args, MnemonicKind};
30use crate::{
31    types::{TypeId, TypeManager},
32    value::{BodyView, InstructionId, LocalValueId, QCodeView, ValueId, ValueRef},
33};
34use smallvec::SmallVec;
35
36/// A stable-by-name handle into the intrinsic registry.
37///
38/// Cheap to copy, match, and hash in hot pass code; serialized as the
39/// intrinsic's name so the id is never written to disk.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct IntrinsicId(usize);
42
43impl IntrinsicId {
44    /// Resolve an intrinsic by name, or `None` if no intrinsic is registered
45    /// under `name`.
46    pub fn from_name(name: &str) -> Option<Self> {
47        registry().by_name.get(name).copied()
48    }
49
50    /// The registered name of this intrinsic (e.g. `"rol"`).
51    pub fn name(self) -> &'static str {
52        self.desc().name()
53    }
54
55    /// The [`Intrinsic`] definition this id resolves to.
56    pub fn desc(self) -> &'static dyn Intrinsic {
57        registry().descs[self.0]
58    }
59}
60
61impl serde::Serialize for IntrinsicId {
62    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
63        s.serialize_str(self.name())
64    }
65}
66
67impl<'de> serde::Deserialize<'de> for IntrinsicId {
68    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
69        let name = <std::borrow::Cow<'de, str>>::deserialize(d)?;
70        IntrinsicId::from_name(&name)
71            .ok_or_else(|| serde::de::Error::custom(format!("unknown intrinsic `{name}`")))
72    }
73}
74
75/// The IR shape an intrinsic's idiom is rooted at, used to gate recognition so
76/// only relevant recognizers run per instruction.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78pub enum RootOp {
79    /// Rooted at an integer binop (e.g. `rol`/`ror` root at `IntBinop::Or`).
80    IntBinop(IntBinop),
81}
82
83/// The definition of one *kind* of intrinsic — its name, arity, result typing,
84/// shared evaluator, and optional recognition / simplification behaviour.
85///
86/// An [`IntrinsicId`] is a by-name handle resolving to a single `&'static dyn
87/// Intrinsic`; the [`IntrinsicApp`] mnemonic is an *application* of that
88/// definition to operands. Built-in definitions are unit structs registered via
89/// [`register_intrinsic!`](crate::register_intrinsic).
90pub trait Intrinsic: Sync {
91    /// Textual name, e.g. `"rol"`. Unique across the registry.
92    fn name(&self) -> &'static str;
93
94    /// Number of operands the intrinsic takes.
95    fn arity(&self) -> usize;
96
97    /// The result type for an application to operands of types `args`. A sized
98    /// integer is just a type, so a width-only intrinsic accesses the published
99    /// canonical integer; sequence-producing intrinsics likewise require their
100    /// result type to have been created before this method is called.
101    fn result_type(&self, types: &TypeManager, args: &[TypeId]) -> TypeId;
102
103    /// Evaluate on concrete operands `(bits, byte_width)`, producing an
104    /// `out_size`-byte result. `None` means "not foldable / trap" — constant
105    /// folding bails, the emulator raises.
106    fn eval(&self, args: &[(u128, usize)], out_size: usize) -> Option<u128>;
107
108    /// IR shape this intrinsic's idiom roots at, if it participates in
109    /// recognition.
110    fn root_op(&self) -> Option<RootOp> {
111        None
112    }
113
114    /// Recognize the raw-IR idiom rooted at `at`, returning the intrinsic's
115    /// operands when the instruction matches. Reads the IR through a
116    /// [`BodyView`] and mints any derived operand literals through the shared
117    /// interner (e.g. a rotate amount
118    /// recovered as the `log2` of a strength-reduced multiplier).
119    fn recognize(&self, _view: BodyView<'_, '_>, _at: InstructionId) -> Option<Vec<ValueId>> {
120        None
121    }
122
123    /// Algebraic simplification on the intrinsic's own operands — e.g.
124    /// `rol(x, 0) → x` or `rol(a, c) → rol(a, c mod bits)`. Receives the
125    /// applied [`IntrinsicId`] (so a shared simplifier can branch on `rol` vs
126    /// `ror`), the result byte width, and the operands. Reads through a
127    /// [`BodyView`] and mints replacement literals through the shared interner.
128    fn simplify(
129        &self,
130        _view: BodyView<'_, '_>,
131        _id: IntrinsicId,
132        _out_size: usize,
133        _args: &[ValueId],
134    ) -> Option<Simplified> {
135        None
136    }
137}
138
139/// The result of an intrinsic's [`simplify`](Intrinsic::simplify) hook.
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub enum Simplified {
142    /// Forward all uses of the intrinsic to this existing value, e.g.
143    /// `rol(x, 0) → x` or `ror(rol(a, c), c) → a`.
144    Value(ValueId),
145    /// Replace the intrinsic instruction with a new expression of the same
146    /// width. The mnemonic is free to be a different intrinsic (e.g.
147    /// `rol(a, c) → ror(a, bits - c)`) or a plain operation (e.g. an
148    /// [`IntBinop`]-rooted `+`).
149    Expression(super::Mnemonic),
150}
151
152/// One intrinsic's registration, submitted via [`inventory::submit!`] (see
153/// `register_intrinsic!`) and collected into the global registry.
154pub struct IntrinsicRegistration(pub &'static dyn Intrinsic);
155
156inventory::collect!(IntrinsicRegistration);
157
158struct Registry {
159    /// Definitions indexed by [`IntrinsicId`].
160    descs: Vec<&'static dyn Intrinsic>,
161    /// Name → id, for parsing and serde.
162    by_name: HashMap<&'static str, IntrinsicId>,
163    /// Ids grouped by recognition root, so the recognizer pass can fetch only
164    /// the relevant ones per instruction.
165    by_root: HashMap<RootOp, Vec<IntrinsicId>>,
166}
167
168fn registry() -> &'static Registry {
169    static REG: OnceLock<Registry> = OnceLock::new();
170    REG.get_or_init(|| {
171        // Sort by name so ids are deterministic within a build regardless of
172        // inventory iteration order.
173        let mut descs: Vec<&'static dyn Intrinsic> = inventory::iter::<IntrinsicRegistration>()
174            .map(|r| r.0)
175            .collect();
176        descs.sort_by_key(|d| d.name());
177
178        let mut by_name = HashMap::default();
179        let mut by_root: HashMap<RootOp, Vec<IntrinsicId>> = HashMap::default();
180        for (idx, desc) in descs.iter().enumerate() {
181            let id = IntrinsicId(idx);
182            let prev = by_name.insert(desc.name(), id);
183            assert!(
184                prev.is_none(),
185                "duplicate intrinsic registration: {}",
186                desc.name()
187            );
188            if let Some(root) = desc.root_op() {
189                by_root.entry(root).or_default().push(id);
190            }
191        }
192
193        Registry {
194            descs,
195            by_name,
196            by_root,
197        }
198    })
199}
200
201/// Every intrinsic whose recognition idiom roots at `root`. Empty if none.
202pub fn recognizers_for(root: RootOp) -> &'static [IntrinsicId] {
203    registry()
204        .by_root
205        .get(&root)
206        .map(Vec::as_slice)
207        .unwrap_or(&[])
208}
209
210/// A pure intrinsic instruction: an [`IntrinsicId`] applied to its operands.
211#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
212pub struct IntrinsicApp {
213    pub id: IntrinsicId,
214    pub args: Vec<LocalValueId>,
215}
216
217impl MnemonicKind for IntrinsicApp {
218    fn opcode(&self) -> &'static str {
219        self.id.name()
220    }
221
222    fn args(&self) -> Args {
223        SmallVec::from_vec(self.args.clone())
224    }
225}
226
227// ---------------------------------------------------------------------------
228// Registration macro
229// ---------------------------------------------------------------------------
230
231/// Register a built-in intrinsic with the global registry.
232///
233/// Takes a unit-struct value implementing [`Intrinsic`]; the registry stores it
234/// as a `&'static dyn Intrinsic`.
235///
236/// ```ignore
237/// struct Rol;
238/// impl Intrinsic for Rol { /* … */ }
239/// register_intrinsic!(Rol);
240/// ```
241#[macro_export]
242macro_rules! register_intrinsic {
243    ($def:expr $(,)?) => {
244        inventory::submit! {
245            $crate::value::insn::IntrinsicRegistration(&$def)
246        }
247    };
248}
249
250// ---------------------------------------------------------------------------
251// Helpers shared by the built-in intrinsics (see `crate::intrinsics`)
252// ---------------------------------------------------------------------------
253
254/// The unsigned mask for a `bytes`-wide value, saturating at 128 bits.
255pub(crate) fn mask_for(bytes: usize) -> u128 {
256    let bits = (bytes * 8).min(128);
257    if bits == 0 {
258        0
259    } else if bits == 128 {
260        u128::MAX
261    } else {
262        (1u128 << bits) - 1
263    }
264}
265
266/// The non-symbolic constant value of `v`, or `None`.
267pub(crate) fn const_u64<'ctx, 'str: 'ctx>(
268    view: impl QCodeView<'ctx, 'str>,
269    v: ValueId,
270) -> Option<u64> {
271    match ValueRef::from_view(view, v) {
272        ValueRef::Literal(lit) => {
273            let ValueId::Literal(id) = v else {
274                return None;
275            };
276            if view.shared().values.literals[id].symbolic.is_some() {
277                return None;
278            }
279            Some(lit.value())
280        }
281        _ => None,
282    }
283}
284
285/// If `v` is defined by an `IntBinop::want`, return its `(lhs, rhs)`.
286pub(crate) fn as_int_binop<'ctx, 'str: 'ctx>(
287    view: impl QCodeView<'ctx, 'str>,
288    v: ValueId,
289    want: IntBinop,
290) -> Option<(ValueId, ValueId)> {
291    use super::{Binary, Binop, Mnemonic};
292    let ValueId::Instruction(id) = v else {
293        return None;
294    };
295    match view.instruction(id).mnemonic() {
296        Mnemonic::Binop(Binary {
297            lhs,
298            rhs,
299            op: Binop::Int(op),
300        }) if *op == want => Some((lhs.qualify(id.func), rhs.qualify(id.func))),
301        _ => None,
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn intrinsic_id_serializes_by_name() {
311        let config = bincode::config::standard();
312        let rol = IntrinsicId::from_name("rol").unwrap();
313
314        // Serialized form is the name string, so it round-trips by name.
315        let bytes = bincode::serde::encode_to_vec(rol, config).unwrap();
316        let name: String = bincode::serde::decode_from_slice(&bytes, config).unwrap().0;
317        assert_eq!(name, "rol");
318
319        let (back, _): (IntrinsicId, usize) =
320            bincode::serde::decode_from_slice(&bytes, config).unwrap();
321        assert_eq!(back, rol);
322
323        // An unknown name is a clean error, not a corrupt id.
324        let bad = bincode::serde::encode_to_vec("nope", config).unwrap();
325        assert!(bincode::serde::decode_from_slice::<IntrinsicId, _>(&bad, config).is_err());
326    }
327}