Skip to main content

hugr_core/ops/
dataflow.rs

1//! Dataflow operations.
2
3use std::borrow::Cow;
4
5use super::{OpTag, OpTrait, impl_op_name};
6
7use crate::extension::SignatureError;
8use crate::ops::StaticTag;
9use crate::types::{
10    EdgeKind, PolyFuncType, Signature, Substitution, Type, TypeArg, TypeRow, TypeRowLike,
11};
12use crate::{IncomingPort, type_row};
13
14#[cfg(test)]
15use {crate::types::proptest_utils::any_serde_type_arg_vec, proptest_derive::Arbitrary};
16
17/// Trait implemented by all dataflow operations.
18pub trait DataflowOpTrait: Sized {
19    /// Tag identifying the operation.
20    const TAG: OpTag;
21
22    /// A human-readable description of the operation.
23    fn description(&self) -> &str;
24
25    /// The signature of the operation.
26    fn signature(&self) -> Cow<'_, Signature>;
27
28    /// The edge kind for the non-dataflow or constant inputs of the operation,
29    /// not described by the signature.
30    ///
31    /// If not None, a single extra output multiport of that kind will be
32    /// present.
33    #[inline]
34    fn other_input(&self) -> Option<EdgeKind> {
35        Some(EdgeKind::StateOrder)
36    }
37    /// The edge kind for the non-dataflow outputs of the operation, not
38    /// described by the signature.
39    ///
40    /// If not None, a single extra output multiport of that kind will be
41    /// present.
42    #[inline]
43    fn other_output(&self) -> Option<EdgeKind> {
44        Some(EdgeKind::StateOrder)
45    }
46
47    /// The edge kind for a single constant input of the operation, not
48    /// described by the dataflow signature.
49    ///
50    /// If not None, an extra input port of that kind will be present after the
51    /// dataflow input ports and before any [`DataflowOpTrait::other_input`] ports.
52    #[inline]
53    fn static_input(&self) -> Option<EdgeKind> {
54        None
55    }
56
57    /// Apply a type-level substitution to this `OpType`, i.e. replace
58    /// [type variables](TypeArg::new_var_use) with new types.
59    fn substitute(&self, _subst: &Substitution) -> Self;
60}
61
62/// Helpers to construct input and output nodes
63pub trait IOTrait {
64    /// Construct a new I/O node from a type row with no extension requirements
65    fn new(types: impl Into<TypeRow>) -> Self;
66}
67
68/// An input node.
69/// The outputs of this node are the inputs to the function.
70#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
71#[cfg_attr(test, derive(Arbitrary))]
72pub struct Input {
73    /// Input value types
74    pub types: TypeRow,
75}
76
77impl_op_name!(Input);
78
79impl IOTrait for Input {
80    fn new(types: impl Into<TypeRow>) -> Self {
81        Input {
82            types: types.into(),
83        }
84    }
85}
86
87/// An output node. The inputs are the outputs of the function.
88#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
89#[cfg_attr(test, derive(Arbitrary))]
90pub struct Output {
91    /// Output value types
92    pub types: TypeRow,
93}
94
95impl_op_name!(Output);
96
97impl IOTrait for Output {
98    fn new(types: impl Into<TypeRow>) -> Self {
99        Output {
100            types: types.into(),
101        }
102    }
103}
104
105impl DataflowOpTrait for Input {
106    const TAG: OpTag = OpTag::Input;
107
108    fn description(&self) -> &'static str {
109        "The input node for this dataflow subgraph"
110    }
111
112    fn other_input(&self) -> Option<EdgeKind> {
113        None
114    }
115
116    fn signature(&self) -> Cow<'_, Signature> {
117        // TODO: Store a cached signature
118        Cow::Owned(Signature::new(TypeRow::new(), self.types.clone()))
119    }
120
121    fn substitute(&self, subst: &Substitution) -> Self {
122        Self {
123            types: self.types.substitute(subst),
124        }
125    }
126}
127impl DataflowOpTrait for Output {
128    const TAG: OpTag = OpTag::Output;
129
130    fn description(&self) -> &'static str {
131        "The output node for this dataflow subgraph"
132    }
133
134    // Note: We know what the input extensions should be, so we *could* give an
135    // instantiated Signature instead
136    fn signature(&self) -> Cow<'_, Signature> {
137        // TODO: Store a cached signature
138        Cow::Owned(Signature::new(self.types.clone(), TypeRow::new()))
139    }
140
141    fn other_output(&self) -> Option<EdgeKind> {
142        None
143    }
144
145    fn substitute(&self, subst: &Substitution) -> Self {
146        Self {
147            types: self.types.substitute(subst),
148        }
149    }
150}
151
152impl<T: DataflowOpTrait + Clone> OpTrait for T {
153    fn description(&self) -> &str {
154        DataflowOpTrait::description(self)
155    }
156
157    fn tag(&self) -> OpTag {
158        T::TAG
159    }
160
161    fn dataflow_signature(&self) -> Option<Cow<'_, Signature>> {
162        Some(DataflowOpTrait::signature(self))
163    }
164
165    fn other_input(&self) -> Option<EdgeKind> {
166        DataflowOpTrait::other_input(self)
167    }
168
169    fn other_output(&self) -> Option<EdgeKind> {
170        DataflowOpTrait::other_output(self)
171    }
172
173    fn static_input(&self) -> Option<EdgeKind> {
174        DataflowOpTrait::static_input(self)
175    }
176
177    fn substitute(&self, subst: &crate::types::Substitution) -> Self {
178        DataflowOpTrait::substitute(self, subst)
179    }
180}
181impl<T: DataflowOpTrait> StaticTag for T {
182    const TAG: OpTag = T::TAG;
183}
184
185/// Call a function directly.
186///
187/// The first ports correspond to the signature of the function being called.
188/// The port immediately following those those is connected to the def/declare
189/// block with a [`EdgeKind::Function`] edge.
190#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
191#[cfg_attr(test, derive(Arbitrary))]
192pub struct Call {
193    /// Signature of function being called.
194    pub func_sig: PolyFuncType,
195    /// The type arguments that instantiate `func_sig`.
196    #[cfg_attr(test, proptest(strategy = "any_serde_type_arg_vec()"))]
197    pub type_args: Vec<TypeArg>,
198    /// The instantiation of `func_sig`.
199    pub instantiation: Signature, // Cache, so we can fail in try_new() not in signature()
200}
201impl_op_name!(Call);
202
203impl DataflowOpTrait for Call {
204    const TAG: OpTag = OpTag::FnCall;
205
206    fn description(&self) -> &'static str {
207        "Call a function directly"
208    }
209
210    fn signature(&self) -> Cow<'_, Signature> {
211        Cow::Borrowed(&self.instantiation)
212    }
213
214    fn static_input(&self) -> Option<EdgeKind> {
215        Some(EdgeKind::Function(self.called_function_type().clone()))
216    }
217
218    fn substitute(&self, subst: &Substitution) -> Self {
219        let type_args = self
220            .type_args
221            .iter()
222            .map(|ta| ta.substitute(subst))
223            .collect::<Vec<_>>();
224        let instantiation = self.instantiation.substitute(subst);
225        debug_assert_eq!(
226            self.func_sig.instantiate(&type_args).as_ref(),
227            Ok(&instantiation)
228        );
229        Self {
230            type_args,
231            instantiation,
232            func_sig: self.func_sig.clone(),
233        }
234    }
235}
236impl Call {
237    /// Try to make a new Call. Returns an error if the `type_args`` do not fit the [TypeParam]s
238    /// declared by the function.
239    ///
240    /// [TypeParam]: crate::types::type_param::TypeParam
241    pub fn try_new(
242        func_sig: PolyFuncType,
243        type_args: impl Into<Vec<TypeArg>>,
244    ) -> Result<Self, SignatureError> {
245        let type_args: Vec<_> = type_args.into();
246        let instantiation = func_sig.instantiate(&type_args)?;
247        Ok(Self {
248            func_sig,
249            type_args,
250            instantiation,
251        })
252    }
253
254    #[inline]
255    /// Return the signature of the function called by this op.
256    #[must_use]
257    pub fn called_function_type(&self) -> &PolyFuncType {
258        &self.func_sig
259    }
260
261    /// The `IncomingPort` which links to the function being called.
262    ///
263    /// This matches [`OpType::static_input_port`].
264    ///
265    /// ```
266    /// # use hugr::ops::dataflow::Call;
267    /// # use hugr::ops::OpType;
268    /// # use hugr::types::Signature;
269    /// # use hugr::extension::prelude::qb_t;
270    /// # use hugr::extension::PRELUDE_REGISTRY;
271    /// let signature = Signature::new(vec![qb_t(), qb_t()], vec![qb_t(), qb_t()]);
272    /// let call = Call::try_new(signature.into(), &[]).unwrap();
273    /// let op = OpType::Call(call.clone());
274    /// assert_eq!(op.static_input_port(), Some(call.called_function_port()));
275    /// ```
276    ///
277    /// [`OpType::static_input_port`]: crate::ops::OpType::static_input_port
278    #[inline]
279    #[must_use]
280    pub fn called_function_port(&self) -> IncomingPort {
281        self.instantiation.input_count().into()
282    }
283
284    pub(crate) fn validate(&self) -> Result<(), SignatureError> {
285        let other = Self::try_new(self.func_sig.clone(), self.type_args.clone())?;
286        if other.instantiation == self.instantiation {
287            Ok(())
288        } else {
289            Err(SignatureError::CallIncorrectlyAppliesType {
290                cached: Box::new(self.instantiation.clone()),
291                expected: Box::new(other.instantiation.clone()),
292            })
293        }
294    }
295}
296
297/// Call a function indirectly. Like call, but the function input is a value
298/// (runtime, not static) dataflow edge, and thus does not need any type-args.
299#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
300#[cfg_attr(test, derive(Arbitrary))]
301pub struct CallIndirect {
302    /// Signature of function being called
303    pub signature: Signature,
304}
305impl_op_name!(CallIndirect);
306
307impl DataflowOpTrait for CallIndirect {
308    const TAG: OpTag = OpTag::DataflowChild;
309
310    fn description(&self) -> &'static str {
311        "Call a function indirectly"
312    }
313
314    fn signature(&self) -> Cow<'_, Signature> {
315        // TODO: Store a cached signature
316        let mut s = self.signature.clone();
317        s.input
318            .to_mut()
319            .insert(0, Type::new_function(self.signature.clone()));
320        Cow::Owned(s)
321    }
322
323    fn substitute(&self, subst: &Substitution) -> Self {
324        Self {
325            signature: self.signature.substitute(subst),
326        }
327    }
328}
329
330/// Load a static constant in to the local dataflow graph.
331#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
332#[cfg_attr(test, derive(Arbitrary))]
333pub struct LoadConstant {
334    /// Constant type
335    pub datatype: Type,
336}
337impl_op_name!(LoadConstant);
338impl DataflowOpTrait for LoadConstant {
339    const TAG: OpTag = OpTag::LoadConst;
340
341    fn description(&self) -> &'static str {
342        "Load a static constant in to the local dataflow graph"
343    }
344
345    fn signature(&self) -> Cow<'_, Signature> {
346        // TODO: Store a cached signature
347        Cow::Owned(Signature::new(TypeRow::new(), vec![self.datatype.clone()]))
348    }
349
350    fn static_input(&self) -> Option<EdgeKind> {
351        Some(EdgeKind::Const(self.constant_type().clone()))
352    }
353
354    fn substitute(&self, _subst: &Substitution) -> Self {
355        // Constants cannot refer to TypeArgs, so neither can loading them
356        self.clone()
357    }
358}
359
360impl LoadConstant {
361    #[inline]
362    /// The type of the constant loaded by this op.
363    #[must_use]
364    pub fn constant_type(&self) -> &Type {
365        &self.datatype
366    }
367
368    /// The `IncomingPort` which links to the loaded constant.
369    ///
370    /// This matches [`OpType::static_input_port`].
371    ///
372    /// ```
373    /// # use hugr::ops::dataflow::LoadConstant;
374    /// # use hugr::ops::OpType;
375    /// # use hugr::types::Type;
376    /// let datatype = Type::UNIT;
377    /// let load_constant = LoadConstant { datatype };
378    /// let op = OpType::LoadConstant(load_constant.clone());
379    /// assert_eq!(op.static_input_port(), Some(load_constant.constant_port()));
380    /// ```
381    ///
382    /// [`OpType::static_input_port`]: crate::ops::OpType::static_input_port
383    #[inline]
384    #[must_use]
385    pub fn constant_port(&self) -> IncomingPort {
386        0.into()
387    }
388}
389
390/// Load a static function in to the local dataflow graph.
391#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
392#[cfg_attr(test, derive(Arbitrary))]
393pub struct LoadFunction {
394    /// Signature of the function
395    pub func_sig: PolyFuncType,
396    /// The type arguments that instantiate `func_sig`.
397    #[cfg_attr(test, proptest(strategy = "any_serde_type_arg_vec()"))]
398    pub type_args: Vec<TypeArg>,
399    /// The instantiation of `func_sig`.
400    pub instantiation: Signature, // Cache, so we can fail in try_new() not in signature()
401}
402impl_op_name!(LoadFunction);
403impl DataflowOpTrait for LoadFunction {
404    const TAG: OpTag = OpTag::LoadFunc;
405
406    fn description(&self) -> &'static str {
407        "Load a static function in to the local dataflow graph"
408    }
409
410    fn signature(&self) -> Cow<'_, Signature> {
411        Cow::Owned(Signature::new(
412            type_row![],
413            [Type::new_function(self.instantiation.clone())],
414        ))
415    }
416
417    fn static_input(&self) -> Option<EdgeKind> {
418        Some(EdgeKind::Function(self.func_sig.clone()))
419    }
420
421    fn substitute(&self, subst: &Substitution) -> Self {
422        let type_args = self
423            .type_args
424            .iter()
425            .map(|ta| ta.substitute(subst))
426            .collect::<Vec<_>>();
427        let instantiation = self.instantiation.substitute(subst);
428        debug_assert_eq!(
429            self.func_sig.instantiate(&type_args).as_ref(),
430            Ok(&instantiation)
431        );
432        Self {
433            func_sig: self.func_sig.clone(),
434            type_args,
435            instantiation,
436        }
437    }
438}
439impl LoadFunction {
440    /// Try to make a new LoadFunction op. Returns an error if the `type_args`` do not fit
441    /// the [TypeParam]s declared by the function.
442    ///
443    /// [TypeParam]: crate::types::type_param::TypeParam
444    pub fn try_new(
445        func_sig: PolyFuncType,
446        type_args: impl Into<Vec<TypeArg>>,
447    ) -> Result<Self, SignatureError> {
448        let type_args: Vec<_> = type_args.into();
449        let instantiation = func_sig.instantiate(&type_args)?;
450        Ok(Self {
451            func_sig,
452            type_args,
453            instantiation,
454        })
455    }
456
457    #[inline]
458    /// Return the type of the function loaded by this op.
459    #[must_use]
460    pub fn function_type(&self) -> &PolyFuncType {
461        &self.func_sig
462    }
463
464    /// The `IncomingPort` which links to the loaded function.
465    ///
466    /// This matches [`OpType::static_input_port`].
467    ///
468    /// [`OpType::static_input_port`]: crate::ops::OpType::static_input_port
469    #[inline]
470    #[must_use]
471    pub fn function_port(&self) -> IncomingPort {
472        0.into()
473    }
474
475    pub(crate) fn validate(&self) -> Result<(), SignatureError> {
476        let other = Self::try_new(self.func_sig.clone(), self.type_args.clone())?;
477        if other.instantiation == self.instantiation {
478            Ok(())
479        } else {
480            Err(SignatureError::LoadFunctionIncorrectlyAppliesType {
481                cached: Box::new(self.instantiation.clone()),
482                expected: Box::new(other.instantiation.clone()),
483            })
484        }
485    }
486}
487
488/// An operation that is the parent of a dataflow graph.
489///
490/// The children region contains an input and an output node matching the
491/// signature returned by [`DataflowParent::inner_signature`].
492pub trait DataflowParent {
493    /// Signature of the inner dataflow graph.
494    fn inner_signature(&self) -> Cow<'_, Signature>;
495}
496
497/// A simply nested dataflow graph.
498#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
499#[cfg_attr(test, derive(Arbitrary))]
500pub struct DFG {
501    /// Signature of DFG node
502    pub signature: Signature,
503}
504
505impl_op_name!(DFG);
506
507impl DataflowParent for DFG {
508    fn inner_signature(&self) -> Cow<'_, Signature> {
509        Cow::Borrowed(&self.signature)
510    }
511}
512
513impl DataflowOpTrait for DFG {
514    const TAG: OpTag = OpTag::Dfg;
515
516    fn description(&self) -> &'static str {
517        "A simply nested dataflow graph"
518    }
519
520    fn signature(&self) -> Cow<'_, Signature> {
521        self.inner_signature()
522    }
523
524    fn substitute(&self, subst: &Substitution) -> Self {
525        Self {
526            signature: self.signature.substitute(subst),
527        }
528    }
529}