diff --git a/Cargo.lock b/Cargo.lock index b05cba1e84..4913316dfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,6 +1212,7 @@ dependencies = [ "proptest", "proptest-recurse", "rstest", + "strum", "thiserror 2.0.12", ] diff --git a/hugr-core/src/ops/constant.rs b/hugr-core/src/ops/constant.rs index 18f3974d4f..56e77186d8 100644 --- a/hugr-core/src/ops/constant.rs +++ b/hugr-core/src/ops/constant.rs @@ -585,6 +585,7 @@ pub(crate) mod test { use crate::extension::PRELUDE; use crate::std_extensions::arithmetic::int_types::ConstInt; use crate::std_extensions::collections::array::{array_type, ArrayValue}; + use crate::std_extensions::collections::value_array::{value_array_type, VArrayValue}; use crate::{ builder::{BuildError, DFGBuilder, Dataflow, DataflowHugr}, extension::{ @@ -754,6 +755,11 @@ pub(crate) mod test { ArrayValue::new(bool_t(), [Value::true_val(), Value::false_val()]).into() } + #[fixture] + fn const_value_array_bool() -> Value { + VArrayValue::new(bool_t(), [Value::true_val(), Value::false_val()]).into() + } + #[fixture] fn const_array_options() -> Value { let some_true = Value::some([Value::true_val()]); @@ -762,17 +768,35 @@ pub(crate) mod test { ArrayValue::new(elem_ty.into(), [some_true, none]).into() } + #[fixture] + fn const_value_array_options() -> Value { + let some_true = Value::some([Value::true_val()]); + let none = Value::none(vec![bool_t()]); + let elem_ty = SumType::new_option(vec![bool_t()]); + VArrayValue::new(elem_ty.into(), [some_true, none]).into() + } + #[rstest] #[case(Value::unit(), Type::UNIT, "const:seq:{}")] #[case(const_usize(), usize_t(), "const:custom:ConstUsize(")] #[case(serialized_float(17.4), float64_type(), "const:custom:json:Object")] #[case(const_tuple(), Type::new_tuple(vec![usize_t(), bool_t()]), "const:seq:{")] #[case(const_array_bool(), array_type(2, bool_t()), "const:custom:array")] + #[case( + const_value_array_bool(), + value_array_type(2, bool_t()), + "const:custom:value_array" + )] #[case( const_array_options(), array_type(2, SumType::new_option(vec![bool_t()]).into()), "const:custom:array" )] + #[case( + const_value_array_options(), + value_array_type(2, SumType::new_option(vec![bool_t()]).into()), + "const:custom:value_array" + )] fn const_type( #[case] const_value: Value, #[case] expected_type: Type, @@ -792,7 +816,9 @@ pub(crate) mod test { #[case(const_serialized_usize(), const_usize())] #[case(const_tuple_serialized(), const_tuple())] #[case(const_array_bool(), const_array_bool())] + #[case(const_value_array_bool(), const_value_array_bool())] #[case(const_array_options(), const_array_options())] + #[case(const_value_array_options(), const_value_array_options())] // Opaque constants don't get resolved into concrete types when running miri, // as the `typetag` machinery is not available. #[cfg_attr(miri, ignore)] diff --git a/hugr-core/src/std_extensions.rs b/hugr-core/src/std_extensions.rs index 7892e8fec5..cf582f8a16 100644 --- a/hugr-core/src/std_extensions.rs +++ b/hugr-core/src/std_extensions.rs @@ -21,6 +21,7 @@ pub fn std_reg() -> ExtensionRegistry { collections::array::EXTENSION.to_owned(), collections::list::EXTENSION.to_owned(), collections::static_array::EXTENSION.to_owned(), + collections::value_array::EXTENSION.to_owned(), logic::EXTENSION.to_owned(), ptr::EXTENSION.to_owned(), ]); diff --git a/hugr-core/src/std_extensions/collections.rs b/hugr-core/src/std_extensions/collections.rs index 13f5c007e3..efd53c805e 100644 --- a/hugr-core/src/std_extensions/collections.rs +++ b/hugr-core/src/std_extensions/collections.rs @@ -3,3 +3,4 @@ pub mod array; pub mod list; pub mod static_array; +pub mod value_array; diff --git a/hugr-core/src/std_extensions/collections/array.rs b/hugr-core/src/std_extensions/collections/array.rs index 2e7ee5b753..177d84a1e2 100644 --- a/hugr-core/src/std_extensions/collections/array.rs +++ b/hugr-core/src/std_extensions/collections/array.rs @@ -1,158 +1,94 @@ //! Fixed-length array type and operations extension. +mod array_clone; +mod array_conversion; +mod array_discard; +mod array_kind; mod array_op; mod array_repeat; mod array_scan; +mod array_value; pub mod op_builder; use std::sync::Arc; -use itertools::Itertools as _; +use delegate::delegate; use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; - -use crate::extension::resolution::{ - resolve_type_extensions, resolve_value_extensions, ExtensionResolutionError, - WeakExtensionRegistry, -}; -use crate::extension::simple_op::{MakeOpDef, MakeRegisteredOp}; + +use crate::builder::{BuildError, Dataflow}; +use crate::extension::resolution::{ExtensionResolutionError, WeakExtensionRegistry}; +use crate::extension::simple_op::{HasConcrete, MakeOpDef, MakeRegisteredOp}; use crate::extension::{ExtensionId, SignatureError, TypeDef, TypeDefBound}; -use crate::ops::constant::{maybe_hash_values, CustomConst, TryHash, ValueName}; -use crate::ops::{ExtensionOp, OpName, Value}; +use crate::ops::constant::{CustomConst, ValueName}; +use crate::ops::{ExtensionOp, OpName}; use crate::types::type_param::{TypeArg, TypeParam}; -use crate::types::{CustomCheckFailure, CustomType, Type, TypeBound, TypeName}; -use crate::Extension; +use crate::types::{CustomCheckFailure, Type, TypeBound, TypeName}; +use crate::{Extension, Wire}; + +pub use array_clone::{GenericArrayClone, GenericArrayCloneDef, ARRAY_CLONE_OP_ID}; +pub use array_conversion::{Direction, GenericArrayConvert, GenericArrayConvertDef, FROM, INTO}; +pub use array_discard::{GenericArrayDiscard, GenericArrayDiscardDef, ARRAY_DISCARD_OP_ID}; +pub use array_kind::ArrayKind; +pub use array_op::{GenericArrayOp, GenericArrayOpDef}; +pub use array_repeat::{GenericArrayRepeat, GenericArrayRepeatDef, ARRAY_REPEAT_OP_ID}; +pub use array_scan::{GenericArrayScan, GenericArrayScanDef, ARRAY_SCAN_OP_ID}; +pub use array_value::GenericArrayValue; -pub use array_op::{ArrayOp, ArrayOpDef, ArrayOpDefIter}; -pub use array_repeat::{ArrayRepeat, ArrayRepeatDef, ARRAY_REPEAT_OP_ID}; -pub use array_scan::{ArrayScan, ArrayScanDef, ARRAY_SCAN_OP_ID}; -pub use op_builder::ArrayOpBuilder; +use op_builder::GenericArrayOpBuilder; /// Reported unique name of the array type. pub const ARRAY_TYPENAME: TypeName = TypeName::new_inline("array"); +/// Reported unique name of the array value. +pub const ARRAY_VALUENAME: TypeName = TypeName::new_inline("array"); /// Reported unique name of the extension pub const EXTENSION_ID: ExtensionId = ExtensionId::new_unchecked("collections.array"); /// Extension version. pub const VERSION: semver::Version = semver::Version::new(0, 1, 0); -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -/// Statically sized array of values, all of the same type. -pub struct ArrayValue { - values: Vec, - typ: Type, -} - -impl ArrayValue { - /// Name of the constructor for creating constant arrays. - pub(crate) const CTR_NAME: &'static str = "collections.array.const"; - - /// Create a new [CustomConst] for an array of values of type `typ`. - /// That all values are of type `typ` is not checked here. - pub fn new(typ: Type, contents: impl IntoIterator) -> Self { - Self { - values: contents.into_iter().collect_vec(), - typ, - } - } - - /// Create a new [CustomConst] for an empty array of values of type `typ`. - pub fn new_empty(typ: Type) -> Self { - Self { - values: vec![], - typ, - } - } +/// A linear, fixed-length collection of values. +/// +/// Arrays are linear, even if their elements are copyable. +#[derive(Clone, Copy, Debug, derive_more::Display, Eq, PartialEq, Default)] +pub struct Array; - /// Returns the type of the `[ArrayValue]` as a `[CustomType]`.` - pub fn custom_type(&self) -> CustomType { - array_custom_type(self.values.len() as u64, self.typ.clone()) - } +impl ArrayKind for Array { + const EXTENSION_ID: ExtensionId = EXTENSION_ID; + const TYPE_NAME: TypeName = ARRAY_TYPENAME; + const VALUE_NAME: ValueName = ARRAY_VALUENAME; - /// Returns the type of values inside the `[ArrayValue]`. - pub fn get_element_type(&self) -> &Type { - &self.typ + fn extension() -> &'static Arc { + &EXTENSION } - /// Returns the values contained inside the `[ArrayValue]`. - pub fn get_contents(&self) -> &[Value] { - &self.values + fn type_def() -> &'static TypeDef { + EXTENSION.get_type(&ARRAY_TYPENAME).unwrap() } } -impl TryHash for ArrayValue { - fn try_hash(&self, mut st: &mut dyn Hasher) -> bool { - maybe_hash_values(&self.values, &mut st) && { - self.typ.hash(&mut st); - true - } - } -} - -#[typetag::serde] -impl CustomConst for ArrayValue { - fn name(&self) -> ValueName { - ValueName::new_inline("array") - } - - fn get_type(&self) -> Type { - self.custom_type().into() - } - - fn validate(&self) -> Result<(), CustomCheckFailure> { - let typ = self.custom_type(); - - EXTENSION - .get_type(&ARRAY_TYPENAME) - .unwrap() - .check_custom(&typ) - .map_err(|_| { - CustomCheckFailure::Message(format!( - "Custom typ {typ} is not a valid instantiation of array." - )) - })?; - - // constant can only hold classic type. - let ty = match typ.args() { - [TypeArg::BoundedNat { n }, TypeArg::Type { ty }] - if *n as usize == self.values.len() => - { - ty - } - _ => { - return Err(CustomCheckFailure::Message(format!( - "Invalid array type arguments: {:?}", - typ.args() - ))) - } - }; - - // check all values are instances of the element type - for v in &self.values { - if v.get_type() != *ty { - return Err(CustomCheckFailure::Message(format!( - "Array element {v:?} is not of expected type {ty}" - ))); - } - } - - Ok(()) - } - - fn equal_consts(&self, other: &dyn CustomConst) -> bool { - crate::ops::constant::downcast_equal_consts(self, other) - } - - fn update_extensions( - &mut self, - extensions: &WeakExtensionRegistry, - ) -> Result<(), ExtensionResolutionError> { - for val in &mut self.values { - resolve_value_extensions(val, extensions)?; - } - resolve_type_extensions(&mut self.typ, extensions) - } -} +/// Array operation definitions. +pub type ArrayOpDef = GenericArrayOpDef; +/// Array clone operation definition. +pub type ArrayCloneDef = GenericArrayCloneDef; +/// Array discard operation definition. +pub type ArrayDiscardDef = GenericArrayDiscardDef; +/// Array repeat operation definition. +pub type ArrayRepeatDef = GenericArrayRepeatDef; +/// Array scan operation definition. +pub type ArrayScanDef = GenericArrayScanDef; + +/// Array operations. +pub type ArrayOp = GenericArrayOp; +/// The array clone operation. +pub type ArrayClone = GenericArrayClone; +/// The array discard operation. +pub type ArrayDiscard = GenericArrayDiscard; +/// The array repeat operation. +pub type ArrayRepeat = GenericArrayRepeat; +/// The array scan operation. +pub type ArrayScan = GenericArrayScan; + +/// An array extension value. +pub type ArrayValue = GenericArrayValue; lazy_static! { /// Extension for array operations. @@ -162,22 +98,49 @@ lazy_static! { ARRAY_TYPENAME, vec![ TypeParam::max_nat(), TypeBound::Any.into()], "Fixed-length array".into(), - TypeDefBound::from_params(vec![1] ), + // Default array is linear, even if the elements are copyable + TypeDefBound::any(), extension_ref, ) .unwrap(); - array_op::ArrayOpDef::load_all_ops(extension, extension_ref).unwrap(); - array_repeat::ArrayRepeatDef.add_to_extension(extension, extension_ref).unwrap(); - array_scan::ArrayScanDef.add_to_extension(extension, extension_ref).unwrap(); + ArrayOpDef::load_all_ops(extension, extension_ref).unwrap(); + ArrayCloneDef::new().add_to_extension(extension, extension_ref).unwrap(); + ArrayDiscardDef::new().add_to_extension(extension, extension_ref).unwrap(); + ArrayRepeatDef::new().add_to_extension(extension, extension_ref).unwrap(); + ArrayScanDef::new().add_to_extension(extension, extension_ref).unwrap(); }) }; } +impl ArrayValue { + /// Name of the constructor for creating constant arrays. + pub(crate) const CTR_NAME: &'static str = "collections.array.const"; +} + +#[typetag::serde(name = "ArrayValue")] +impl CustomConst for ArrayValue { + delegate! { + to self { + fn name(&self) -> ValueName; + fn validate(&self) -> Result<(), CustomCheckFailure>; + fn update_extensions( + &mut self, + extensions: &WeakExtensionRegistry, + ) -> Result<(), ExtensionResolutionError>; + fn get_type(&self) -> Type; + } + } + + fn equal_consts(&self, other: &dyn CustomConst) -> bool { + crate::ops::constant::downcast_equal_consts(self, other) + } +} + /// Gets the [TypeDef] for arrays. Note that instantiations are more easily /// created via [array_type] and [array_type_parametric] pub fn array_type_def() -> &'static TypeDef { - EXTENSION.get_type(&ARRAY_TYPENAME).unwrap() + Array::type_def() } /// Instantiate a new array type given a size argument and element type. @@ -185,7 +148,7 @@ pub fn array_type_def() -> &'static TypeDef { /// This method is equivalent to [`array_type_parametric`], but uses concrete /// arguments types to ensure no errors are possible. pub fn array_type(size: u64, element_ty: Type) -> Type { - array_custom_type(size, element_ty).into() + Array::ty(size, element_ty) } /// Instantiate a new array type given the size and element type parameters. @@ -195,28 +158,7 @@ pub fn array_type_parametric( size: impl Into, element_ty: impl Into, ) -> Result { - instantiate_array(array_type_def(), size, element_ty) -} - -fn array_custom_type(size: impl Into, element_ty: impl Into) -> CustomType { - instantiate_array_custom(array_type_def(), size, element_ty) - .expect("array parameters are valid") -} - -fn instantiate_array_custom( - array_def: &TypeDef, - size: impl Into, - element_ty: impl Into, -) -> Result { - array_def.instantiate(vec![size.into(), element_ty.into()]) -} - -fn instantiate_array( - array_def: &TypeDef, - size: impl Into, - element_ty: impl Into, -) -> Result { - instantiate_array_custom(array_def, size, element_ty).map(Into::into) + Array::ty_parametric(size, element_ty) } /// Name of the operation in the prelude for creating new arrays. @@ -224,18 +166,246 @@ pub const NEW_ARRAY_OP_ID: OpName = OpName::new_inline("new_array"); /// Initialize a new array op of element type `element_ty` of length `size` pub fn new_array_op(element_ty: Type, size: u64) -> ExtensionOp { - let op = array_op::ArrayOpDef::new_array.to_concrete(element_ty, size); + let op = ArrayOpDef::new_array.to_concrete(element_ty, size); op.to_extension_op().unwrap() } +/// Trait for building array operations in a dataflow graph. +pub trait ArrayOpBuilder: GenericArrayOpBuilder { + /// Adds a new array operation to the dataflow graph and return the wire + /// representing the new array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `values` - An iterator over the values to initialize the array with. + /// + /// # Errors + /// + /// If building the operation fails. + /// + /// # Returns + /// + /// The wire representing the new array. + fn add_new_array( + &mut self, + elem_ty: Type, + values: impl IntoIterator, + ) -> Result { + self.add_new_generic_array::(elem_ty, values) + } + + /// Adds an array clone operation to the dataflow graph and return the wires + /// representing the originala and cloned array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// If building the operation fails. + /// + /// # Returns + /// + /// The wires representing the original and cloned array. + fn add_array_clone( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result<(Wire, Wire), BuildError> { + self.add_generic_array_clone::(elem_ty, size, input) + } + + /// Adds an array discard operation to the dataflow graph. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// If building the operation fails. + fn add_array_discard( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result<(), BuildError> { + self.add_generic_array_discard::(elem_ty, size, input) + } + + /// Adds an array get operation to the dataflow graph. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// * `index` - The wire representing the index to get. + /// + /// # Errors + /// + /// If building the operation fails. + /// + /// # Returns + /// + /// * The wire representing the value at the specified index in the array + /// * The wire representing the array + fn add_array_get( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + index: Wire, + ) -> Result<(Wire, Wire), BuildError> { + self.add_generic_array_get::(elem_ty, size, input, index) + } + + /// Adds an array set operation to the dataflow graph. + /// + /// This operation sets the value at a specified index in the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// * `index` - The wire representing the index to set. + /// * `value` - The wire representing the value to set at the specified index. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the updated array after the set operation. + fn add_array_set( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + index: Wire, + value: Wire, + ) -> Result { + self.add_generic_array_set::(elem_ty, size, input, index, value) + } + + /// Adds an array swap operation to the dataflow graph. + /// + /// This operation swaps the values at two specified indices in the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// * `index1` - The wire representing the first index to swap. + /// * `index2` - The wire representing the second index to swap. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the updated array after the swap operation. + fn add_array_swap( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + index1: Wire, + index2: Wire, + ) -> Result { + let op = GenericArrayOpDef::::swap.instantiate(&[size.into(), elem_ty.into()])?; + let [out] = self + .add_dataflow_op(op, vec![input, index1, index2])? + .outputs_arr(); + Ok(out) + } + + /// Adds an array pop-left operation to the dataflow graph. + /// + /// This operation removes the leftmost element from the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the Option> + fn add_array_pop_left( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result { + self.add_generic_array_pop_left::(elem_ty, size, input) + } + + /// Adds an array pop-right operation to the dataflow graph. + /// + /// This operation removes the rightmost element from the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the Option> + fn add_array_pop_right( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result { + self.add_generic_array_pop_right::(elem_ty, size, input) + } + + /// Adds an operation to discard an empty array from the dataflow graph. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + fn add_array_discard_empty(&mut self, elem_ty: Type, input: Wire) -> Result<(), BuildError> { + self.add_generic_array_discard_empty::(elem_ty, input) + } +} + +impl ArrayOpBuilder for D {} + #[cfg(test)] mod test { use crate::builder::{inout_sig, DFGBuilder, Dataflow, DataflowHugr}; - use crate::extension::prelude::{qb_t, usize_t, ConstUsize}; - use crate::ops::constant::CustomConst; - use crate::std_extensions::arithmetic::float_types::ConstF64; + use crate::extension::prelude::qb_t; - use super::{array_type, new_array_op, ArrayValue}; + use super::{array_type, new_array_op}; #[test] /// Test building a HUGR involving a new_array operation. @@ -251,20 +421,4 @@ mod test { b.finish_hugr_with_outputs(out.outputs()).unwrap(); } - - #[test] - fn test_array_value() { - let array_value = ArrayValue { - values: vec![ConstUsize::new(3).into()], - typ: usize_t(), - }; - - array_value.validate().unwrap(); - - let wrong_array_value = ArrayValue { - values: vec![ConstF64::new(1.2).into()], - typ: usize_t(), - }; - assert!(wrong_array_value.validate().is_err()); - } } diff --git a/hugr-core/src/std_extensions/collections/array/array_clone.rs b/hugr-core/src/std_extensions/collections/array/array_clone.rs new file mode 100644 index 0000000000..c532da199f --- /dev/null +++ b/hugr-core/src/std_extensions/collections/array/array_clone.rs @@ -0,0 +1,234 @@ +//! Definition of the array clone operation. + +use std::marker::PhantomData; +use std::str::FromStr; +use std::sync::{Arc, Weak}; + +use crate::extension::simple_op::{ + HasConcrete, HasDef, MakeExtensionOp, MakeOpDef, MakeRegisteredOp, OpLoadError, +}; +use crate::extension::{ExtensionId, OpDef, SignatureError, SignatureFunc, TypeDef}; +use crate::ops::{ExtensionOp, NamedOp, OpName}; +use crate::types::type_param::{TypeArg, TypeParam}; +use crate::types::{FuncValueType, PolyFuncTypeRV, Type, TypeBound}; +use crate::Extension; + +use super::array_kind::ArrayKind; + +/// Name of the operation to clone an array +pub const ARRAY_CLONE_OP_ID: OpName = OpName::new_inline("clone"); + +/// Definition of the array clone operation. Generic over the concrete array implementation. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct GenericArrayCloneDef(PhantomData); + +impl GenericArrayCloneDef { + /// Creates a new clone operation definition. + pub fn new() -> Self { + GenericArrayCloneDef(PhantomData) + } +} + +impl Default for GenericArrayCloneDef { + fn default() -> Self { + Self::new() + } +} + +impl NamedOp for GenericArrayCloneDef { + fn name(&self) -> OpName { + ARRAY_CLONE_OP_ID + } +} + +impl FromStr for GenericArrayCloneDef { + type Err = (); + + fn from_str(s: &str) -> Result { + if s == ARRAY_CLONE_OP_ID { + Ok(GenericArrayCloneDef::new()) + } else { + Err(()) + } + } +} + +impl GenericArrayCloneDef { + /// To avoid recursion when defining the extension, take the type definition as an argument. + fn signature_from_def(&self, array_def: &TypeDef) -> SignatureFunc { + let params = vec![TypeParam::max_nat(), TypeBound::Copyable.into()]; + let size = TypeArg::new_var_use(0, TypeParam::max_nat()); + let element_ty = Type::new_var_use(1, TypeBound::Copyable); + let array_ty = AK::instantiate_ty(array_def, size, element_ty) + .expect("Array type instantiation failed"); + PolyFuncTypeRV::new( + params, + FuncValueType::new(array_ty.clone(), vec![array_ty; 2]), + ) + .into() + } +} + +impl MakeOpDef for GenericArrayCloneDef { + fn from_def(op_def: &OpDef) -> Result + where + Self: Sized, + { + crate::extension::simple_op::try_from_name(op_def.name(), op_def.extension_id()) + } + + fn init_signature(&self, _extension_ref: &Weak) -> SignatureFunc { + self.signature_from_def(AK::type_def()) + } + + fn extension_ref(&self) -> Weak { + Arc::downgrade(AK::extension()) + } + + fn extension(&self) -> ExtensionId { + AK::EXTENSION_ID + } + + fn description(&self) -> String { + "Clones an array with copyable elements".into() + } + + /// Add an operation implemented as a [MakeOpDef], which can provide the data + /// required to define an [OpDef], to an extension. + // + // This method is re-defined here since we need to pass the array type def while + // computing the signature, to avoid recursive loops initializing the extension. + fn add_to_extension( + &self, + extension: &mut Extension, + extension_ref: &Weak, + ) -> Result<(), crate::extension::ExtensionBuildError> { + let sig = self.signature_from_def(extension.get_type(&AK::TYPE_NAME).unwrap()); + let def = extension.add_op(self.name(), self.description(), sig, extension_ref)?; + self.post_opdef(def); + Ok(()) + } +} + +/// Definition of the array clone op. Generic over the concrete array implementation. +#[derive(Clone, Debug, PartialEq)] +pub struct GenericArrayClone { + /// The element type of the array. + pub elem_ty: Type, + /// Size of the array. + pub size: u64, + _kind: PhantomData, +} + +impl GenericArrayClone { + /// Creates a new array clone op. + /// + /// # Errors + /// + /// If the provided element type is not copyable. + pub fn new(elem_ty: Type, size: u64) -> Result { + elem_ty + .copyable() + .then_some(GenericArrayClone { + elem_ty, + size, + _kind: PhantomData, + }) + .ok_or(SignatureError::InvalidTypeArgs.into()) + } +} + +impl NamedOp for GenericArrayClone { + fn name(&self) -> OpName { + ARRAY_CLONE_OP_ID + } +} + +impl MakeExtensionOp for GenericArrayClone { + fn from_extension_op(ext_op: &ExtensionOp) -> Result + where + Self: Sized, + { + let def = GenericArrayCloneDef::::from_def(ext_op.def())?; + def.instantiate(ext_op.args()) + } + + fn type_args(&self) -> Vec { + vec![ + TypeArg::BoundedNat { n: self.size }, + self.elem_ty.clone().into(), + ] + } +} + +impl MakeRegisteredOp for GenericArrayClone { + fn extension_id(&self) -> ExtensionId { + AK::EXTENSION_ID + } + + fn extension_ref(&self) -> Weak { + Arc::downgrade(AK::extension()) + } +} + +impl HasDef for GenericArrayClone { + type Def = GenericArrayCloneDef; +} + +impl HasConcrete for GenericArrayCloneDef { + type Concrete = GenericArrayClone; + + fn instantiate(&self, type_args: &[TypeArg]) -> Result { + match type_args { + [TypeArg::BoundedNat { n }, TypeArg::Type { ty }] if ty.copyable() => { + Ok(GenericArrayClone::new(ty.clone(), *n).unwrap()) + } + _ => Err(SignatureError::InvalidTypeArgs.into()), + } + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use crate::extension::prelude::bool_t; + use crate::std_extensions::collections::array::Array; + use crate::{ + extension::prelude::qb_t, + ops::{OpTrait, OpType}, + }; + + use super::*; + + #[rstest] + #[case(Array)] + fn test_clone_def(#[case] _kind: AK) { + let op = GenericArrayClone::::new(bool_t(), 2).unwrap(); + let optype: OpType = op.clone().into(); + let new_op: GenericArrayClone = optype.cast().unwrap(); + assert_eq!(new_op, op); + + assert_eq!( + GenericArrayClone::::new(qb_t(), 2), + Err(OpLoadError::InvalidArgs(SignatureError::InvalidTypeArgs)) + ); + } + + #[rstest] + #[case(Array)] + fn test_clone(#[case] _kind: AK) { + let size = 2; + let element_ty = bool_t(); + let op = GenericArrayClone::::new(element_ty.clone(), size).unwrap(); + let optype: OpType = op.into(); + let sig = optype.dataflow_signature().unwrap(); + assert_eq!( + sig.io(), + ( + &vec![AK::ty(size, element_ty.clone())].into(), + &vec![AK::ty(size, element_ty.clone()); 2].into(), + ) + ); + } +} diff --git a/hugr-core/src/std_extensions/collections/array/array_conversion.rs b/hugr-core/src/std_extensions/collections/array/array_conversion.rs new file mode 100644 index 0000000000..3cec4c3fef --- /dev/null +++ b/hugr-core/src/std_extensions/collections/array/array_conversion.rs @@ -0,0 +1,318 @@ +//! Operations for converting between the different array extensions + +use std::marker::PhantomData; +use std::str::FromStr; +use std::sync::{Arc, Weak}; + +use crate::extension::simple_op::{ + HasConcrete, HasDef, MakeExtensionOp, MakeOpDef, MakeRegisteredOp, OpLoadError, +}; +use crate::extension::{ExtensionId, OpDef, SignatureError, SignatureFunc, TypeDef}; +use crate::ops::{ExtensionOp, NamedOp, OpName}; +use crate::types::type_param::{TypeArg, TypeParam}; +use crate::types::{FuncValueType, PolyFuncTypeRV, Type, TypeBound}; +use crate::Extension; + +use super::array_kind::ArrayKind; + +/// Array conversion direction. +/// +/// Either the current array type [INTO] the other one, or the current array type [FROM] the +/// other one. +pub type Direction = bool; + +/// Array conversion direction to turn the current array type [INTO] the other one. +pub const INTO: Direction = true; + +/// Array conversion direction to obtain the current array type [FROM] the other one. +pub const FROM: Direction = false; + +/// Definition of array conversion operations. +/// +/// Generic over the concrete array implementation of the extension containing the operation, as +/// well as over another array implementation that should be converted between. Also generic over +/// the conversion [Direction]. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct GenericArrayConvertDef( + PhantomData, + PhantomData, +); + +impl + GenericArrayConvertDef +{ + /// Creates a new array conversion definition. + pub fn new() -> Self { + GenericArrayConvertDef(PhantomData, PhantomData) + } +} + +impl Default + for GenericArrayConvertDef +{ + fn default() -> Self { + Self::new() + } +} + +impl NamedOp + for GenericArrayConvertDef +{ + fn name(&self) -> OpName { + match DIR { + INTO => format!("to_{}", OtherAK::TYPE_NAME).into(), + FROM => format!("from_{}", OtherAK::TYPE_NAME).into(), + } + } +} + +impl FromStr + for GenericArrayConvertDef +{ + type Err = (); + + fn from_str(s: &str) -> Result { + let def = GenericArrayConvertDef::new(); + if s == def.name() { + Ok(def) + } else { + Err(()) + } + } +} + +impl + GenericArrayConvertDef +{ + /// To avoid recursion when defining the extension, take the type definition as an argument. + fn signature_from_def(&self, array_def: &TypeDef) -> SignatureFunc { + let params = vec![TypeParam::max_nat(), TypeBound::Any.into()]; + let size = TypeArg::new_var_use(0, TypeParam::max_nat()); + let element_ty = Type::new_var_use(1, TypeBound::Any); + + let this_ty = AK::instantiate_ty(array_def, size.clone(), element_ty.clone()) + .expect("Array type instantiation failed"); + let other_ty = + OtherAK::ty_parametric(size, element_ty).expect("Array type instantiation failed"); + + let sig = match DIR { + INTO => FuncValueType::new(this_ty, other_ty), + FROM => FuncValueType::new(other_ty, this_ty), + }; + PolyFuncTypeRV::new(params, sig).into() + } +} + +impl MakeOpDef + for GenericArrayConvertDef +{ + fn from_def(op_def: &OpDef) -> Result + where + Self: Sized, + { + crate::extension::simple_op::try_from_name(op_def.name(), op_def.extension_id()) + } + + fn init_signature(&self, _extension_ref: &Weak) -> SignatureFunc { + self.signature_from_def(AK::type_def()) + } + + fn extension_ref(&self) -> Weak { + Arc::downgrade(AK::extension()) + } + + fn extension(&self) -> ExtensionId { + AK::EXTENSION_ID + } + + fn description(&self) -> String { + match DIR { + INTO => format!("Turns `{}` into `{}`", AK::TYPE_NAME, OtherAK::TYPE_NAME), + FROM => format!("Turns `{}` into `{}`", OtherAK::TYPE_NAME, AK::TYPE_NAME), + } + } + + /// Add an operation implemented as a [MakeOpDef], which can provide the data + /// required to define an [OpDef], to an extension. + // + // This method is re-defined here since we need to pass the array type def while + // computing the signature, to avoid recursive loops initializing the extension. + fn add_to_extension( + &self, + extension: &mut Extension, + extension_ref: &Weak, + ) -> Result<(), crate::extension::ExtensionBuildError> { + let sig = self.signature_from_def(extension.get_type(&AK::TYPE_NAME).unwrap()); + let def = extension.add_op(self.name(), self.description(), sig, extension_ref)?; + self.post_opdef(def); + Ok(()) + } +} + +/// Definition of the array conversion op. +/// +/// Generic over the concrete array implementation of the extension containing the operation, as +/// well as over another array implementation that should be converted between. Also generic over +/// the conversion [Direction]. +#[derive(Clone, Debug, PartialEq)] +pub struct GenericArrayConvert { + /// The element type of the array. + pub elem_ty: Type, + /// Size of the array. + pub size: u64, + _kind: PhantomData, + _other_kind: PhantomData, +} + +impl + GenericArrayConvert +{ + /// Creates a new array conversion op. + pub fn new(elem_ty: Type, size: u64) -> Self { + GenericArrayConvert { + elem_ty, + size, + _kind: PhantomData, + _other_kind: PhantomData, + } + } +} + +impl NamedOp + for GenericArrayConvert +{ + fn name(&self) -> OpName { + match DIR { + INTO => format!("to_{}", OtherAK::TYPE_NAME).into(), + FROM => format!("from_{}", OtherAK::TYPE_NAME).into(), + } + } +} + +impl MakeExtensionOp + for GenericArrayConvert +{ + fn from_extension_op(ext_op: &ExtensionOp) -> Result + where + Self: Sized, + { + let def = GenericArrayConvertDef::::from_def(ext_op.def())?; + def.instantiate(ext_op.args()) + } + + fn type_args(&self) -> Vec { + vec![ + TypeArg::BoundedNat { n: self.size }, + self.elem_ty.clone().into(), + ] + } +} + +impl MakeRegisteredOp + for GenericArrayConvert +{ + fn extension_id(&self) -> ExtensionId { + AK::EXTENSION_ID + } + + fn extension_ref(&self) -> Weak { + Arc::downgrade(AK::extension()) + } +} + +impl HasDef + for GenericArrayConvert +{ + type Def = GenericArrayConvertDef; +} + +impl HasConcrete + for GenericArrayConvertDef +{ + type Concrete = GenericArrayConvert; + + fn instantiate(&self, type_args: &[TypeArg]) -> Result { + match type_args { + [TypeArg::BoundedNat { n }, TypeArg::Type { ty }] => { + Ok(GenericArrayConvert::new(ty.clone(), *n)) + } + _ => Err(SignatureError::InvalidTypeArgs.into()), + } + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use crate::extension::prelude::bool_t; + use crate::ops::{OpTrait, OpType}; + use crate::std_extensions::collections::array::Array; + use crate::std_extensions::collections::value_array::ValueArray; + + use super::*; + + #[rstest] + #[case(ValueArray, Array)] + fn test_convert_from_def( + #[case] _kind: AK, + #[case] _other_kind: OtherAK, + ) { + let op = GenericArrayConvert::::new(bool_t(), 2); + let optype: OpType = op.clone().into(); + let new_op: GenericArrayConvert = optype.cast().unwrap(); + assert_eq!(new_op, op); + } + + #[rstest] + #[case(ValueArray, Array)] + fn test_convert_into_def( + #[case] _kind: AK, + #[case] _other_kind: OtherAK, + ) { + let op = GenericArrayConvert::::new(bool_t(), 2); + let optype: OpType = op.clone().into(); + let new_op: GenericArrayConvert = optype.cast().unwrap(); + assert_eq!(new_op, op); + } + + #[rstest] + #[case(ValueArray, Array)] + fn test_convert_from( + #[case] _kind: AK, + #[case] _other_kind: OtherAK, + ) { + let size = 2; + let element_ty = bool_t(); + let op = GenericArrayConvert::::new(element_ty.clone(), size); + let optype: OpType = op.into(); + let sig = optype.dataflow_signature().unwrap(); + assert_eq!( + sig.io(), + ( + &vec![OtherAK::ty(size, element_ty.clone())].into(), + &vec![AK::ty(size, element_ty.clone())].into(), + ) + ); + } + + #[rstest] + #[case(ValueArray, Array)] + fn test_convert_into( + #[case] _kind: AK, + #[case] _other_kind: OtherAK, + ) { + let size = 2; + let element_ty = bool_t(); + let op = GenericArrayConvert::::new(element_ty.clone(), size); + let optype: OpType = op.into(); + let sig = optype.dataflow_signature().unwrap(); + assert_eq!( + sig.io(), + ( + &vec![AK::ty(size, element_ty.clone())].into(), + &vec![OtherAK::ty(size, element_ty.clone())].into(), + ) + ); + } +} diff --git a/hugr-core/src/std_extensions/collections/array/array_discard.rs b/hugr-core/src/std_extensions/collections/array/array_discard.rs new file mode 100644 index 0000000000..7cdfc9b4f8 --- /dev/null +++ b/hugr-core/src/std_extensions/collections/array/array_discard.rs @@ -0,0 +1,220 @@ +//! Definition of the array discard operation. + +use std::marker::PhantomData; +use std::str::FromStr; +use std::sync::{Arc, Weak}; + +use crate::extension::simple_op::{ + HasConcrete, HasDef, MakeExtensionOp, MakeOpDef, MakeRegisteredOp, OpLoadError, +}; +use crate::extension::{ExtensionId, OpDef, SignatureError, SignatureFunc, TypeDef}; +use crate::ops::{ExtensionOp, NamedOp, OpName}; +use crate::types::type_param::{TypeArg, TypeParam}; +use crate::types::{FuncValueType, PolyFuncTypeRV, Type, TypeBound}; +use crate::{type_row, Extension}; + +use super::array_kind::ArrayKind; + +/// Name of the operation to discard an array +pub const ARRAY_DISCARD_OP_ID: OpName = OpName::new_inline("discard"); + +/// Definition of the array discard op. Generic over the concrete array implementation. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct GenericArrayDiscardDef(PhantomData); + +impl GenericArrayDiscardDef { + /// Creates a new array discard operation definition. + pub fn new() -> Self { + GenericArrayDiscardDef(PhantomData) + } +} + +impl Default for GenericArrayDiscardDef { + fn default() -> Self { + Self::new() + } +} + +impl NamedOp for GenericArrayDiscardDef { + fn name(&self) -> OpName { + ARRAY_DISCARD_OP_ID + } +} + +impl FromStr for GenericArrayDiscardDef { + type Err = (); + + fn from_str(s: &str) -> Result { + if s == ARRAY_DISCARD_OP_ID { + Ok(GenericArrayDiscardDef::new()) + } else { + Err(()) + } + } +} + +impl GenericArrayDiscardDef { + /// To avoid recursion when defining the extension, take the type definition as an argument. + fn signature_from_def(&self, array_def: &TypeDef) -> SignatureFunc { + let params = vec![TypeParam::max_nat(), TypeBound::Copyable.into()]; + let size = TypeArg::new_var_use(0, TypeParam::max_nat()); + let element_ty = Type::new_var_use(1, TypeBound::Copyable); + let array_ty = AK::instantiate_ty(array_def, size, element_ty) + .expect("Array type instantiation failed"); + PolyFuncTypeRV::new(params, FuncValueType::new(array_ty, type_row![])).into() + } +} + +impl MakeOpDef for GenericArrayDiscardDef { + fn from_def(op_def: &OpDef) -> Result + where + Self: Sized, + { + crate::extension::simple_op::try_from_name(op_def.name(), op_def.extension_id()) + } + + fn init_signature(&self, _extension_ref: &Weak) -> SignatureFunc { + self.signature_from_def(AK::type_def()) + } + + fn extension_ref(&self) -> Weak { + Arc::downgrade(AK::extension()) + } + + fn extension(&self) -> ExtensionId { + AK::EXTENSION_ID + } + + fn description(&self) -> String { + "Discards an array with copyable elements".into() + } + + /// Add an operation implemented as a [MakeOpDef], which can provide the data + /// required to define an [OpDef], to an extension. + // + // This method is re-defined here since we need to pass the array type def while + // computing the signature, to avoid recursive loops initializing the extension. + fn add_to_extension( + &self, + extension: &mut Extension, + extension_ref: &Weak, + ) -> Result<(), crate::extension::ExtensionBuildError> { + let sig = self.signature_from_def(extension.get_type(&AK::TYPE_NAME).unwrap()); + let def = extension.add_op(self.name(), self.description(), sig, extension_ref)?; + self.post_opdef(def); + Ok(()) + } +} + +/// Definition of the array discard op. Generic over the concrete array implementation. +#[derive(Clone, Debug, PartialEq)] +pub struct GenericArrayDiscard { + /// The element type of the array. + pub elem_ty: Type, + /// Size of the array. + pub size: u64, + _kind: PhantomData, +} + +impl GenericArrayDiscard { + /// Creates a new array discard op. + pub fn new(elem_ty: Type, size: u64) -> Option { + elem_ty.copyable().then_some(GenericArrayDiscard { + elem_ty, + size, + _kind: PhantomData, + }) + } +} + +impl NamedOp for GenericArrayDiscard { + fn name(&self) -> OpName { + ARRAY_DISCARD_OP_ID + } +} + +impl MakeExtensionOp for GenericArrayDiscard { + fn from_extension_op(ext_op: &ExtensionOp) -> Result + where + Self: Sized, + { + let def = GenericArrayDiscardDef::::from_def(ext_op.def())?; + def.instantiate(ext_op.args()) + } + + fn type_args(&self) -> Vec { + vec![ + TypeArg::BoundedNat { n: self.size }, + self.elem_ty.clone().into(), + ] + } +} + +impl MakeRegisteredOp for GenericArrayDiscard { + fn extension_id(&self) -> ExtensionId { + AK::EXTENSION_ID + } + + fn extension_ref(&self) -> Weak { + Arc::downgrade(AK::extension()) + } +} + +impl HasDef for GenericArrayDiscard { + type Def = GenericArrayDiscardDef; +} + +impl HasConcrete for GenericArrayDiscardDef { + type Concrete = GenericArrayDiscard; + + fn instantiate(&self, type_args: &[TypeArg]) -> Result { + match type_args { + [TypeArg::BoundedNat { n }, TypeArg::Type { ty }] if ty.copyable() => { + Ok(GenericArrayDiscard::new(ty.clone(), *n).unwrap()) + } + _ => Err(SignatureError::InvalidTypeArgs.into()), + } + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use crate::extension::prelude::bool_t; + use crate::std_extensions::collections::array::Array; + use crate::{ + extension::prelude::qb_t, + ops::{OpTrait, OpType}, + }; + + use super::*; + + #[rstest] + #[case(Array)] + fn test_discard_def(#[case] _kind: AK) { + let op = GenericArrayDiscard::::new(bool_t(), 2).unwrap(); + let optype: OpType = op.clone().into(); + let new_op: GenericArrayDiscard = optype.cast().unwrap(); + assert_eq!(new_op, op); + + assert_eq!(GenericArrayDiscard::::new(qb_t(), 2), None); + } + + #[rstest] + #[case(Array)] + fn test_discard(#[case] _kind: AK) { + let size = 2; + let element_ty = bool_t(); + let op = GenericArrayDiscard::::new(element_ty.clone(), size).unwrap(); + let optype: OpType = op.into(); + let sig = optype.dataflow_signature().unwrap(); + assert_eq!( + sig.io(), + ( + &vec![AK::ty(size, element_ty.clone())].into(), + &vec![].into(), + ) + ); + } +} diff --git a/hugr-core/src/std_extensions/collections/array/array_kind.rs b/hugr-core/src/std_extensions/collections/array/array_kind.rs new file mode 100644 index 0000000000..88c729d3cd --- /dev/null +++ b/hugr-core/src/std_extensions/collections/array/array_kind.rs @@ -0,0 +1,119 @@ +use std::sync::Arc; + +use crate::std_extensions::collections::array::op_builder::GenericArrayOpBuilder; +use crate::{ + builder::{BuildError, Dataflow}, + extension::{ExtensionId, SignatureError, TypeDef}, + ops::constant::ValueName, + types::{CustomType, Type, TypeArg, TypeName}, + Extension, Wire, +}; + +/// Trait capturing a concrete array implementation in an extension. +/// +/// Array operations are generically defined over this trait so the different +/// array extensions can share parts of their implementation. See for example +/// [`GenericArrayOpDef`] or [`GenericArrayValue`] +/// +/// Currently the available kinds of array are [`Array`] (the default one) and +/// [`ValueArray`]. +/// +/// [`GenericArrayOpDef`]: super::GenericArrayOpDef +/// [`GenericArrayValue`]: super::GenericArrayValue +/// [`Array`]: super::Array +/// [`ValueArray`]: crate::std_extensions::collections::value_array::ValueArray +pub trait ArrayKind: + Clone + + Copy + + std::fmt::Debug + + std::fmt::Display + + Eq + + PartialEq + + Default + + Send + + Sync + + 'static +{ + /// Identifier of the extension containing the array. + const EXTENSION_ID: ExtensionId; + + /// Name of the array type. + const TYPE_NAME: TypeName; + + /// Name of the array value. + const VALUE_NAME: ValueName; + + /// Returns the extension containing the array. + fn extension() -> &'static Arc; + + /// Returns the definition for the array type. + fn type_def() -> &'static TypeDef; + + /// Instantiates an array [CustomType] from its definition given a size and + /// element type argument. + fn instantiate_custom_ty( + array_def: &TypeDef, + size: impl Into, + element_ty: impl Into, + ) -> Result { + array_def.instantiate(vec![size.into(), element_ty.into()]) + } + + /// Instantiates an array type from its definition given a size and element + /// type argument. + fn instantiate_ty( + array_def: &TypeDef, + size: impl Into, + element_ty: impl Into, + ) -> Result { + Self::instantiate_custom_ty(array_def, size, element_ty).map(Into::into) + } + + /// Instantiates an array [CustomType] given a size and element type argument. + fn custom_ty(size: impl Into, element_ty: impl Into) -> CustomType { + Self::instantiate_custom_ty(Self::type_def(), size, element_ty) + .expect("array parameters are valid") + } + + /// Instantiate a new array type given a size argument and element type. + /// + /// This method is equivalent to [`ArrayKind::ty_parametric`], but uses concrete + /// arguments types to ensure no errors are possible. + fn ty(size: u64, element_ty: Type) -> Type { + Self::custom_ty(size, element_ty).into() + } + + /// Instantiate a new array type given the size and element type parameters. + /// + /// This is a generic version of [`ArrayKind::ty`]. + fn ty_parametric( + size: impl Into, + element_ty: impl Into, + ) -> Result { + Self::instantiate_ty(Self::type_def(), size, element_ty) + } + + /// Adds a operation to a dataflow graph that clones an array of copyable values. + /// + /// The default implementation uses the array clone operation. + fn build_clone( + builder: &mut D, + elem_ty: Type, + size: u64, + arr: Wire, + ) -> Result<(Wire, Wire), BuildError> { + builder.add_generic_array_clone::(elem_ty, size, arr) + } + + /// Adds a operation to a dataflow graph that clones an array of copyable values. + /// + /// The default implementation uses the array clone operation. + fn build_discard( + builder: &mut D, + elem_ty: Type, + size: u64, + arr: Wire, + ) -> Result<(), BuildError> { + builder.add_generic_array_discard::(elem_ty, size, arr) + } +} diff --git a/hugr-core/src/std_extensions/collections/array/array_op.rs b/hugr-core/src/std_extensions/collections/array/array_op.rs index 197536032e..e5b63f855a 100644 --- a/hugr-core/src/std_extensions/collections/array/array_op.rs +++ b/hugr-core/src/std_extensions/collections/array/array_op.rs @@ -1,5 +1,6 @@ //! Definitions of `ArrayOp` and `ArrayOpDef`. +use std::marker::PhantomData; use std::sync::{Arc, Weak}; use strum::{EnumIter, EnumString, IntoStaticStr}; @@ -12,25 +13,25 @@ use crate::extension::{ ExtensionId, OpDef, SignatureError, SignatureFromArgs, SignatureFunc, TypeDef, }; use crate::ops::{ExtensionOp, NamedOp, OpName}; -use crate::std_extensions::collections::array::instantiate_array; use crate::type_row; use crate::types::type_param::{TypeArg, TypeParam}; use crate::types::{FuncValueType, PolyFuncTypeRV, Type, TypeBound}; +use crate::utils::Never; use crate::Extension; -use super::{array_type, array_type_def, ARRAY_TYPENAME}; +use super::array_kind::ArrayKind; -/// Array operation definitions. -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, EnumIter, IntoStaticStr, EnumString)] +/// Array operation definitions. Generic over the conrete array implementation. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, IntoStaticStr, EnumIter, EnumString)] #[allow(non_camel_case_types)] #[non_exhaustive] -pub enum ArrayOpDef { +pub enum GenericArrayOpDef { /// Makes a new array, given distinct inputs equal to its length: /// `new_array: (elemty)^SIZE -> array` /// where `SIZE` must be statically known (not a variable) new_array, /// Copies an element out of the array ([TypeBound::Copyable] elements only): - /// `get: array, index -> option` + /// `get: array, index -> option, array` get, /// Exchanges an element of the array with an external value: /// `set: array, index, elemty -> either(elemty, array | elemty, array)` @@ -53,26 +54,30 @@ pub enum ArrayOpDef { /// Allows discarding a 0-element array of linear type. /// `discard_empty: array<0, elemty> -> ` (no outputs) discard_empty, + /// Not an actual operation definition, but an unhabitable variant that + /// references `AK` to ensure that the type parameter is used. + #[strum(disabled)] + _phantom(PhantomData, Never), } /// Static parameters for array operations. Includes array size. Type is part of the type scheme. const STATIC_SIZE_PARAM: &[TypeParam; 1] = &[TypeParam::max_nat()]; -impl SignatureFromArgs for ArrayOpDef { +impl SignatureFromArgs for GenericArrayOpDef { fn compute_signature(&self, arg_values: &[TypeArg]) -> Result { let [TypeArg::BoundedNat { n }] = *arg_values else { return Err(SignatureError::InvalidTypeArgs); }; let elem_ty_var = Type::new_var_use(0, TypeBound::Any); - let array_ty = array_type(n, elem_ty_var.clone()); + let array_ty = AK::ty(n, elem_ty_var.clone()); let params = vec![TypeBound::Any.into()]; let poly_func_ty = match self { - ArrayOpDef::new_array => PolyFuncTypeRV::new( + GenericArrayOpDef::new_array => PolyFuncTypeRV::new( params, FuncValueType::new(vec![elem_ty_var.clone(); n as usize], array_ty), ), - ArrayOpDef::pop_left | ArrayOpDef::pop_right => { - let popped_array_ty = array_type(n - 1, elem_ty_var.clone()); + GenericArrayOpDef::pop_left | GenericArrayOpDef::pop_right => { + let popped_array_ty = AK::ty(n - 1, elem_ty_var.clone()); PolyFuncTypeRV::new( params, FuncValueType::new( @@ -81,6 +86,7 @@ impl SignatureFromArgs for ArrayOpDef { ), ) } + GenericArrayOpDef::_phantom(_, never) => match *never {}, _ => unreachable!( "Operation {} should not need custom computation.", self.name() @@ -94,16 +100,16 @@ impl SignatureFromArgs for ArrayOpDef { } } -impl ArrayOpDef { +impl GenericArrayOpDef { /// Instantiate a new array operation with the given element type and array size. - pub fn to_concrete(self, elem_ty: Type, size: u64) -> ArrayOp { - if self == ArrayOpDef::discard_empty { + pub fn to_concrete(self, elem_ty: Type, size: u64) -> GenericArrayOp { + if self == GenericArrayOpDef::discard_empty { debug_assert_eq!( size, 0, "discard_empty should only be called on empty arrays" ); } - ArrayOp { + GenericArrayOp { def: self, elem_ty, size, @@ -116,7 +122,7 @@ impl ArrayOpDef { array_def: &TypeDef, _extension_ref: &Weak, ) -> SignatureFunc { - use ArrayOpDef::*; + use GenericArrayOpDef::*; if let new_array | pop_left | pop_right = self { // implements SignatureFromArgs // signature computed dynamically, so can rely on type definition in extension. @@ -124,7 +130,7 @@ impl ArrayOpDef { } else { let size_var = TypeArg::new_var_use(0, TypeParam::max_nat()); let elem_ty_var = Type::new_var_use(1, TypeBound::Any); - let array_ty = instantiate_array(array_def, size_var.clone(), elem_ty_var.clone()) + let array_ty = AK::instantiate_ty(array_def, size_var.clone(), elem_ty_var.clone()) .expect("Array type instantiation failed"); let standard_params = vec![TypeParam::max_nat(), TypeBound::Any.into()]; @@ -137,12 +143,15 @@ impl ArrayOpDef { let params = vec![TypeParam::max_nat(), TypeBound::Copyable.into()]; let copy_elem_ty = Type::new_var_use(1, TypeBound::Copyable); let copy_array_ty = - instantiate_array(array_def, size_var, copy_elem_ty.clone()) + AK::instantiate_ty(array_def, size_var, copy_elem_ty.clone()) .expect("Array type instantiation failed"); let option_type: Type = option_type(copy_elem_ty).into(); PolyFuncTypeRV::new( params, - FuncValueType::new(vec![copy_array_ty, usize_t], option_type), + FuncValueType::new( + vec![copy_array_ty.clone(), usize_t], + vec![option_type, copy_array_ty], + ), ) } set => { @@ -166,11 +175,12 @@ impl ArrayOpDef { discard_empty => PolyFuncTypeRV::new( vec![TypeBound::Any.into()], FuncValueType::new( - instantiate_array(array_def, 0, Type::new_var_use(0, TypeBound::Any)) + AK::instantiate_ty(array_def, 0, Type::new_var_use(0, TypeBound::Any)) .expect("Array type instantiation failed"), type_row![], ), ), + _phantom(_, never) => match *never {}, new_array | pop_left | pop_right => unreachable!(), } .into() @@ -178,7 +188,7 @@ impl ArrayOpDef { } } -impl MakeOpDef for ArrayOpDef { +impl MakeOpDef for GenericArrayOpDef { fn from_def(op_def: &OpDef) -> Result where Self: Sized, @@ -187,26 +197,27 @@ impl MakeOpDef for ArrayOpDef { } fn init_signature(&self, extension_ref: &Weak) -> SignatureFunc { - self.signature_from_def(array_type_def(), extension_ref) + self.signature_from_def(AK::type_def(), extension_ref) } fn extension_ref(&self) -> Weak { - Arc::downgrade(&super::EXTENSION) + Arc::downgrade(AK::extension()) } fn extension(&self) -> ExtensionId { - super::EXTENSION_ID + AK::EXTENSION_ID } fn description(&self) -> String { match self { - ArrayOpDef::new_array => "Create a new array from elements", - ArrayOpDef::get => "Get an element from an array", - ArrayOpDef::set => "Set an element in an array", - ArrayOpDef::swap => "Swap two elements in an array", - ArrayOpDef::pop_left => "Pop an element from the left of an array", - ArrayOpDef::pop_right => "Pop an element from the right of an array", - ArrayOpDef::discard_empty => "Discard an empty array", + GenericArrayOpDef::new_array => "Create a new array from elements", + GenericArrayOpDef::get => "Get an element from an array", + GenericArrayOpDef::set => "Set an element in an array", + GenericArrayOpDef::swap => "Swap two elements in an array", + GenericArrayOpDef::pop_left => "Pop an element from the left of an array", + GenericArrayOpDef::pop_right => "Pop an element from the right of an array", + GenericArrayOpDef::discard_empty => "Discard an empty array", + GenericArrayOpDef::_phantom(_, never) => match *never {}, } .into() } @@ -222,7 +233,7 @@ impl MakeOpDef for ArrayOpDef { extension_ref: &Weak, ) -> Result<(), crate::extension::ExtensionBuildError> { let sig = - self.signature_from_def(extension.get_type(&ARRAY_TYPENAME).unwrap(), extension_ref); + self.signature_from_def(extension.get_type(&AK::TYPE_NAME).unwrap(), extension_ref); let def = extension.add_op(self.name(), self.description(), sig, extension_ref)?; self.post_opdef(def); @@ -232,33 +243,33 @@ impl MakeOpDef for ArrayOpDef { } #[derive(Clone, Debug, PartialEq)] -/// Concrete array operation. -pub struct ArrayOp { +/// Concrete array operation. Generic over the actual array implemenation. +pub struct GenericArrayOp { /// The operation definition. - pub def: ArrayOpDef, + pub def: GenericArrayOpDef, /// The element type of the array. pub elem_ty: Type, /// The size of the array. pub size: u64, } -impl NamedOp for ArrayOp { +impl NamedOp for GenericArrayOp { fn name(&self) -> OpName { self.def.name() } } -impl MakeExtensionOp for ArrayOp { +impl MakeExtensionOp for GenericArrayOp { fn from_extension_op(ext_op: &ExtensionOp) -> Result where Self: Sized, { - let def = ArrayOpDef::from_def(ext_op.def())?; + let def = GenericArrayOpDef::from_def(ext_op.def())?; def.instantiate(ext_op.args()) } fn type_args(&self) -> Vec { - use ArrayOpDef::*; + use GenericArrayOpDef::*; let ty_arg = TypeArg::Type { ty: self.elem_ty.clone(), }; @@ -273,30 +284,31 @@ impl MakeExtensionOp for ArrayOp { new_array | pop_left | pop_right | get | set | swap => { vec![TypeArg::BoundedNat { n: self.size }, ty_arg] } + _phantom(_, never) => match never {}, } } } -impl MakeRegisteredOp for ArrayOp { +impl MakeRegisteredOp for GenericArrayOp { fn extension_id(&self) -> ExtensionId { - super::EXTENSION_ID + AK::EXTENSION_ID } fn extension_ref(&self) -> Weak { - Arc::downgrade(&super::EXTENSION) + Arc::downgrade(AK::extension()) } } -impl HasDef for ArrayOp { - type Def = ArrayOpDef; +impl HasDef for GenericArrayOp { + type Def = GenericArrayOpDef; } -impl HasConcrete for ArrayOpDef { - type Concrete = ArrayOp; +impl HasConcrete for GenericArrayOpDef { + type Concrete = GenericArrayOp; fn instantiate(&self, type_args: &[TypeArg]) -> Result { let (ty, size) = match (self, type_args) { - (ArrayOpDef::discard_empty, [TypeArg::Type { ty }]) => (ty.clone(), 0), + (GenericArrayOpDef::discard_empty, [TypeArg::Type { ty }]) => (ty.clone(), 0), (_, [TypeArg::BoundedNat { n }, TypeArg::Type { ty }]) => (ty.clone(), *n), _ => return Err(SignatureError::InvalidTypeArgs.into()), }; @@ -307,11 +319,13 @@ impl HasConcrete for ArrayOpDef { #[cfg(test)] mod tests { + use rstest::rstest; use strum::IntoEnumIterator; use crate::extension::prelude::usize_t; use crate::std_extensions::arithmetic::float_types::float64_type; - use crate::std_extensions::collections::array::new_array_op; + use crate::std_extensions::collections::array::Array; + use crate::std_extensions::collections::value_array::ValueArray; use crate::{ builder::{inout_sig, DFGBuilder, Dataflow, DataflowHugr}, extension::prelude::{bool_t, qb_t}, @@ -320,46 +334,51 @@ mod tests { use super::*; - #[test] - fn test_array_ops() { - for def in ArrayOpDef::iter() { - let ty = if def == ArrayOpDef::get { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_array_ops(#[case] _kind: AK) { + for def in GenericArrayOpDef::::iter() { + let ty = if def == GenericArrayOpDef::get { bool_t() } else { qb_t() }; - let size = if def == ArrayOpDef::discard_empty { + let size = if def == GenericArrayOpDef::discard_empty { 0 } else { 2 }; let op = def.to_concrete(ty, size); let optype: OpType = op.clone().into(); - let new_op: ArrayOp = optype.cast().unwrap(); + let new_op: GenericArrayOp = optype.cast().unwrap(); assert_eq!(new_op, op); } } - #[test] + #[rstest] + #[case(Array)] + #[case(ValueArray)] /// Test building a HUGR involving a new_array operation. - fn test_new_array() { - let mut b = - DFGBuilder::new(inout_sig(vec![qb_t(), qb_t()], array_type(2, qb_t()))).unwrap(); + fn test_new_array(#[case] _kind: AK) { + let mut b = DFGBuilder::new(inout_sig(vec![qb_t(), qb_t()], AK::ty(2, qb_t()))).unwrap(); let [q1, q2] = b.input_wires_arr(); - let op = new_array_op(qb_t(), 2); + let op = GenericArrayOpDef::::new_array.to_concrete(qb_t(), 2); let out = b.add_dataflow_op(op, [q1, q2]).unwrap(); b.finish_hugr_with_outputs(out.outputs()).unwrap(); } - #[test] - fn test_get() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_get(#[case] _kind: AK) { let size = 2; let element_ty = bool_t(); - let op = ArrayOpDef::get.to_concrete(element_ty.clone(), size); + let op = GenericArrayOpDef::::get.to_concrete(element_ty.clone(), size); let optype: OpType = op.into(); @@ -368,22 +387,28 @@ mod tests { assert_eq!( sig.io(), ( - &vec![array_type(size, element_ty.clone()), usize_t()].into(), - &vec![option_type(element_ty.clone()).into()].into() + &vec![AK::ty(size, element_ty.clone()), usize_t()].into(), + &vec![ + option_type(element_ty.clone()).into(), + AK::ty(size, element_ty.clone()) + ] + .into() ) ); } - #[test] - fn test_set() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_set(#[case] _kind: AK) { let size = 2; let element_ty = bool_t(); - let op = ArrayOpDef::set.to_concrete(element_ty.clone(), size); + let op = GenericArrayOpDef::::set.to_concrete(element_ty.clone(), size); let optype: OpType = op.into(); let sig = optype.dataflow_signature().unwrap(); - let array_ty = array_type(size, element_ty.clone()); + let array_ty = AK::ty(size, element_ty.clone()); let result_row = vec![element_ty.clone(), array_ty.clone()]; assert_eq!( sig.io(), @@ -394,16 +419,18 @@ mod tests { ); } - #[test] - fn test_swap() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_swap(#[case] _kind: AK) { let size = 2; let element_ty = bool_t(); - let op = ArrayOpDef::swap.to_concrete(element_ty.clone(), size); + let op = GenericArrayOpDef::::swap.to_concrete(element_ty.clone(), size); let optype: OpType = op.into(); let sig = optype.dataflow_signature().unwrap(); - let array_ty = array_type(size, element_ty.clone()); + let array_ty = AK::ty(size, element_ty.clone()); assert_eq!( sig.io(), ( @@ -413,11 +440,18 @@ mod tests { ); } - #[test] - fn test_pops() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_pops(#[case] _kind: AK) { let size = 2; let element_ty = bool_t(); - for op in [ArrayOpDef::pop_left, ArrayOpDef::pop_right].iter() { + for op in [ + GenericArrayOpDef::::pop_left, + GenericArrayOpDef::::pop_right, + ] + .iter() + { let op = op.to_concrete(element_ty.clone(), size); let optype: OpType = op.into(); @@ -426,10 +460,10 @@ mod tests { assert_eq!( sig.io(), ( - &vec![array_type(size, element_ty.clone())].into(), + &vec![AK::ty(size, element_ty.clone())].into(), &vec![option_type(vec![ element_ty.clone(), - array_type(size - 1, element_ty.clone()) + AK::ty(size - 1, element_ty.clone()) ]) .into()] .into() @@ -438,11 +472,13 @@ mod tests { } } - #[test] - fn test_discard_empty() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_discard_empty(#[case] _kind: AK) { let size = 0; let element_ty = bool_t(); - let op = ArrayOpDef::discard_empty.to_concrete(element_ty.clone(), size); + let op = GenericArrayOpDef::::discard_empty.to_concrete(element_ty.clone(), size); let optype: OpType = op.into(); @@ -450,19 +486,18 @@ mod tests { assert_eq!( sig.io(), - ( - &vec![array_type(size, element_ty.clone())].into(), - &type_row![] - ) + (&vec![AK::ty(size, element_ty.clone())].into(), &type_row![]) ); } - #[test] + #[rstest] + #[case(Array)] + #[case(ValueArray)] /// Initialize an array operation where the element type is not from the prelude. - fn test_non_prelude_op() { + fn test_non_prelude_op(#[case] _kind: AK) { let size = 2; let element_ty = float64_type(); - let op = ArrayOpDef::get.to_concrete(element_ty.clone(), size); + let op = GenericArrayOpDef::::get.to_concrete(element_ty.clone(), size); let optype: OpType = op.into(); @@ -471,8 +506,12 @@ mod tests { assert_eq!( sig.io(), ( - &vec![array_type(size, element_ty.clone()), usize_t()].into(), - &vec![option_type(element_ty.clone()).into()].into() + &vec![AK::ty(size, element_ty.clone()), usize_t()].into(), + &vec![ + option_type(element_ty.clone()).into(), + AK::ty(size, element_ty.clone()) + ] + .into() ) ); } diff --git a/hugr-core/src/std_extensions/collections/array/array_repeat.rs b/hugr-core/src/std_extensions/collections/array/array_repeat.rs index a31505cb26..10a52308a6 100644 --- a/hugr-core/src/std_extensions/collections/array/array_repeat.rs +++ b/hugr-core/src/std_extensions/collections/array/array_repeat.rs @@ -1,5 +1,6 @@ //! Definition of the array repeat operation. +use std::marker::PhantomData; use std::str::FromStr; use std::sync::{Arc, Weak}; @@ -12,46 +13,60 @@ use crate::types::type_param::{TypeArg, TypeParam}; use crate::types::{FuncValueType, PolyFuncTypeRV, Signature, Type, TypeBound}; use crate::Extension; -use super::{array_type_def, instantiate_array, ARRAY_TYPENAME}; +use super::array_kind::ArrayKind; /// Name of the operation to repeat a value multiple times pub const ARRAY_REPEAT_OP_ID: OpName = OpName::new_inline("repeat"); -/// Definition of the array repeat op. +/// Definition of the array repeat op. Generic over the concrete array implementation. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct ArrayRepeatDef; +pub struct GenericArrayRepeatDef(PhantomData); -impl NamedOp for ArrayRepeatDef { +impl GenericArrayRepeatDef { + /// Creates a new array repeat operation definition. + pub fn new() -> Self { + GenericArrayRepeatDef(PhantomData) + } +} + +impl Default for GenericArrayRepeatDef { + fn default() -> Self { + Self::new() + } +} + +impl NamedOp for GenericArrayRepeatDef { fn name(&self) -> OpName { ARRAY_REPEAT_OP_ID } } -impl FromStr for ArrayRepeatDef { +impl FromStr for GenericArrayRepeatDef { type Err = (); fn from_str(s: &str) -> Result { - if s == ArrayRepeatDef.name() { - Ok(Self) + if s == ARRAY_REPEAT_OP_ID { + Ok(GenericArrayRepeatDef::new()) } else { Err(()) } } } -impl ArrayRepeatDef { +impl GenericArrayRepeatDef { /// To avoid recursion when defining the extension, take the type definition as an argument. fn signature_from_def(&self, array_def: &TypeDef) -> SignatureFunc { let params = vec![TypeParam::max_nat(), TypeBound::Any.into()]; let n = TypeArg::new_var_use(0, TypeParam::max_nat()); let t = Type::new_var_use(1, TypeBound::Any); let func = Type::new_function(Signature::new(vec![], vec![t.clone()])); - let array_ty = instantiate_array(array_def, n, t).expect("Array type instantiation failed"); + let array_ty = + AK::instantiate_ty(array_def, n, t).expect("Array type instantiation failed"); PolyFuncTypeRV::new(params, FuncValueType::new(vec![func], array_ty)).into() } } -impl MakeOpDef for ArrayRepeatDef { +impl MakeOpDef for GenericArrayRepeatDef { fn from_def(op_def: &OpDef) -> Result where Self: Sized, @@ -60,15 +75,15 @@ impl MakeOpDef for ArrayRepeatDef { } fn init_signature(&self, _extension_ref: &Weak) -> SignatureFunc { - self.signature_from_def(array_type_def()) + self.signature_from_def(AK::type_def()) } fn extension_ref(&self) -> Weak { - Arc::downgrade(&super::EXTENSION) + Arc::downgrade(AK::extension()) } fn extension(&self) -> ExtensionId { - super::EXTENSION_ID + AK::EXTENSION_ID } fn description(&self) -> String { @@ -87,7 +102,7 @@ impl MakeOpDef for ArrayRepeatDef { extension: &mut Extension, extension_ref: &Weak, ) -> Result<(), crate::extension::ExtensionBuildError> { - let sig = self.signature_from_def(extension.get_type(&ARRAY_TYPENAME).unwrap()); + let sig = self.signature_from_def(extension.get_type(&AK::TYPE_NAME).unwrap()); let def = extension.add_op(self.name(), self.description(), sig, extension_ref)?; self.post_opdef(def); @@ -96,34 +111,39 @@ impl MakeOpDef for ArrayRepeatDef { } } -/// Definition of the array repeat op. +/// Definition of the array repeat op. Generic over the concrete array implementation. #[derive(Clone, Debug, PartialEq)] -pub struct ArrayRepeat { +pub struct GenericArrayRepeat { /// The element type of the resulting array. pub elem_ty: Type, /// Size of the array. pub size: u64, + _kind: PhantomData, } -impl ArrayRepeat { +impl GenericArrayRepeat { /// Creates a new array repeat op. pub fn new(elem_ty: Type, size: u64) -> Self { - ArrayRepeat { elem_ty, size } + GenericArrayRepeat { + elem_ty, + size, + _kind: PhantomData, + } } } -impl NamedOp for ArrayRepeat { +impl NamedOp for GenericArrayRepeat { fn name(&self) -> OpName { ARRAY_REPEAT_OP_ID } } -impl MakeExtensionOp for ArrayRepeat { +impl MakeExtensionOp for GenericArrayRepeat { fn from_extension_op(ext_op: &ExtensionOp) -> Result where Self: Sized, { - let def = ArrayRepeatDef::from_def(ext_op.def())?; + let def = GenericArrayRepeatDef::::from_def(ext_op.def())?; def.instantiate(ext_op.args()) } @@ -135,27 +155,27 @@ impl MakeExtensionOp for ArrayRepeat { } } -impl MakeRegisteredOp for ArrayRepeat { +impl MakeRegisteredOp for GenericArrayRepeat { fn extension_id(&self) -> ExtensionId { - super::EXTENSION_ID + AK::EXTENSION_ID } fn extension_ref(&self) -> Weak { - Arc::downgrade(&super::EXTENSION) + Arc::downgrade(AK::extension()) } } -impl HasDef for ArrayRepeat { - type Def = ArrayRepeatDef; +impl HasDef for GenericArrayRepeat { + type Def = GenericArrayRepeatDef; } -impl HasConcrete for ArrayRepeatDef { - type Concrete = ArrayRepeat; +impl HasConcrete for GenericArrayRepeatDef { + type Concrete = GenericArrayRepeat; fn instantiate(&self, type_args: &[TypeArg]) -> Result { match type_args { [TypeArg::BoundedNat { n }, TypeArg::Type { ty }] => { - Ok(ArrayRepeat::new(ty.clone(), *n)) + Ok(GenericArrayRepeat::new(ty.clone(), *n)) } _ => Err(SignatureError::InvalidTypeArgs.into()), } @@ -164,7 +184,10 @@ impl HasConcrete for ArrayRepeatDef { #[cfg(test)] mod tests { - use crate::std_extensions::collections::array::array_type; + use rstest::rstest; + + use crate::std_extensions::collections::array::Array; + use crate::std_extensions::collections::value_array::ValueArray; use crate::{ extension::prelude::qb_t, ops::{OpTrait, OpType}, @@ -173,19 +196,23 @@ mod tests { use super::*; - #[test] - fn test_repeat_def() { - let op = ArrayRepeat::new(qb_t(), 2); + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_repeat_def(#[case] _kind: AK) { + let op = GenericArrayRepeat::::new(qb_t(), 2); let optype: OpType = op.clone().into(); - let new_op: ArrayRepeat = optype.cast().unwrap(); + let new_op: GenericArrayRepeat = optype.cast().unwrap(); assert_eq!(new_op, op); } - #[test] - fn test_repeat() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_repeat(#[case] _kind: AK) { let size = 2; let element_ty = qb_t(); - let op = ArrayRepeat::new(element_ty.clone(), size); + let op = GenericArrayRepeat::::new(element_ty.clone(), size); let optype: OpType = op.into(); @@ -195,7 +222,7 @@ mod tests { sig.io(), ( &vec![Type::new_function(Signature::new(vec![], vec![qb_t()]))].into(), - &vec![array_type(size, element_ty.clone())].into(), + &vec![AK::ty(size, element_ty.clone())].into(), ) ); } diff --git a/hugr-core/src/std_extensions/collections/array/array_scan.rs b/hugr-core/src/std_extensions/collections/array/array_scan.rs index 8064a73d09..fcd06b628c 100644 --- a/hugr-core/src/std_extensions/collections/array/array_scan.rs +++ b/hugr-core/src/std_extensions/collections/array/array_scan.rs @@ -1,5 +1,6 @@ //! Array scanning operation +use std::marker::PhantomData; use std::str::FromStr; use std::sync::{Arc, Weak}; @@ -14,34 +15,47 @@ use crate::types::type_param::{TypeArg, TypeParam}; use crate::types::{FuncTypeBase, PolyFuncTypeRV, RowVariable, Type, TypeBound, TypeRV}; use crate::Extension; -use super::{array_type_def, instantiate_array, ARRAY_TYPENAME}; +use super::array_kind::ArrayKind; /// Name of the operation for the combined map/fold operation pub const ARRAY_SCAN_OP_ID: OpName = OpName::new_inline("scan"); -/// Definition of the array scan op. +/// Definition of the array scan op. Generic over the concrete array implementation. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct ArrayScanDef; +pub struct GenericArrayScanDef(PhantomData); -impl NamedOp for ArrayScanDef { +impl GenericArrayScanDef { + /// Creates a new array scan operation definition. + pub fn new() -> Self { + GenericArrayScanDef(PhantomData) + } +} + +impl Default for GenericArrayScanDef { + fn default() -> Self { + Self::new() + } +} + +impl NamedOp for GenericArrayScanDef { fn name(&self) -> OpName { ARRAY_SCAN_OP_ID } } -impl FromStr for ArrayScanDef { +impl FromStr for GenericArrayScanDef { type Err = (); fn from_str(s: &str) -> Result { - if s == ArrayScanDef.name() { - Ok(Self) + if s == ARRAY_SCAN_OP_ID { + Ok(Self::new()) } else { Err(()) } } } -impl ArrayScanDef { +impl GenericArrayScanDef { /// To avoid recursion when defining the extension, take the type definition /// and a reference to the extension as an argument. fn signature_from_def(&self, array_def: &TypeDef) -> SignatureFunc { @@ -60,7 +74,7 @@ impl ArrayScanDef { params, FuncTypeBase::::new( vec![ - instantiate_array(array_def, n.clone(), t1.clone()) + AK::instantiate_ty(array_def, n.clone(), t1.clone()) .expect("Array type instantiation failed") .into(), Type::new_function(FuncTypeBase::::new( @@ -71,7 +85,7 @@ impl ArrayScanDef { s.clone(), ], vec![ - instantiate_array(array_def, n, t2) + AK::instantiate_ty(array_def, n, t2) .expect("Array type instantiation failed") .into(), s, @@ -82,7 +96,7 @@ impl ArrayScanDef { } } -impl MakeOpDef for ArrayScanDef { +impl MakeOpDef for GenericArrayScanDef { fn from_def(op_def: &OpDef) -> Result where Self: Sized, @@ -91,15 +105,15 @@ impl MakeOpDef for ArrayScanDef { } fn init_signature(&self, _extension_ref: &Weak) -> SignatureFunc { - self.signature_from_def(array_type_def()) + self.signature_from_def(AK::type_def()) } fn extension_ref(&self) -> Weak { - Arc::downgrade(&super::EXTENSION) + Arc::downgrade(AK::extension()) } fn extension(&self) -> ExtensionId { - super::EXTENSION_ID + AK::EXTENSION_ID } fn description(&self) -> String { @@ -120,7 +134,7 @@ impl MakeOpDef for ArrayScanDef { extension: &mut Extension, extension_ref: &Weak, ) -> Result<(), crate::extension::ExtensionBuildError> { - let sig = self.signature_from_def(extension.get_type(&ARRAY_TYPENAME).unwrap()); + let sig = self.signature_from_def(extension.get_type(&AK::TYPE_NAME).unwrap()); let def = extension.add_op(self.name(), self.description(), sig, extension_ref)?; self.post_opdef(def); @@ -129,9 +143,9 @@ impl MakeOpDef for ArrayScanDef { } } -/// Definition of the array scan op. +/// Definition of the array scan op. Generic over the concrete array implementation. #[derive(Clone, Debug, PartialEq)] -pub struct ArrayScan { +pub struct GenericArrayScan { /// The element type of the input array. pub src_ty: Type, /// The target element type of the output array. @@ -140,32 +154,34 @@ pub struct ArrayScan { pub acc_tys: Vec, /// Size of the array. pub size: u64, + _kind: PhantomData, } -impl ArrayScan { +impl GenericArrayScan { /// Creates a new array scan op. pub fn new(src_ty: Type, tgt_ty: Type, acc_tys: Vec, size: u64) -> Self { - ArrayScan { + GenericArrayScan { src_ty, tgt_ty, acc_tys, size, + _kind: PhantomData, } } } -impl NamedOp for ArrayScan { +impl NamedOp for GenericArrayScan { fn name(&self) -> OpName { ARRAY_SCAN_OP_ID } } -impl MakeExtensionOp for ArrayScan { +impl MakeExtensionOp for GenericArrayScan { fn from_extension_op(ext_op: &ExtensionOp) -> Result where Self: Sized, { - let def = ArrayScanDef::from_def(ext_op.def())?; + let def = GenericArrayScanDef::::from_def(ext_op.def())?; def.instantiate(ext_op.args()) } @@ -181,22 +197,22 @@ impl MakeExtensionOp for ArrayScan { } } -impl MakeRegisteredOp for ArrayScan { +impl MakeRegisteredOp for GenericArrayScan { fn extension_id(&self) -> ExtensionId { - super::EXTENSION_ID + AK::EXTENSION_ID } fn extension_ref(&self) -> Weak { - Arc::downgrade(&super::EXTENSION) + Arc::downgrade(AK::extension()) } } -impl HasDef for ArrayScan { - type Def = ArrayScanDef; +impl HasDef for GenericArrayScan { + type Def = GenericArrayScanDef; } -impl HasConcrete for ArrayScanDef { - type Concrete = ArrayScan; +impl HasConcrete for GenericArrayScanDef { + type Concrete = GenericArrayScan; fn instantiate(&self, type_args: &[TypeArg]) -> Result { match type_args { @@ -209,7 +225,12 @@ impl HasConcrete for ArrayScanDef { _ => Err(SignatureError::InvalidTypeArgs.into()), }) .collect(); - Ok(ArrayScan::new(src_ty.clone(), tgt_ty.clone(), acc_tys?, *n)) + Ok(GenericArrayScan::new( + src_ty.clone(), + tgt_ty.clone(), + acc_tys?, + *n, + )) } _ => Err(SignatureError::InvalidTypeArgs.into()), } @@ -218,9 +239,11 @@ impl HasConcrete for ArrayScanDef { #[cfg(test)] mod tests { + use rstest::rstest; use crate::extension::prelude::usize_t; - use crate::std_extensions::collections::array::array_type; + use crate::std_extensions::collections::array::Array; + use crate::std_extensions::collections::value_array::ValueArray; use crate::{ extension::prelude::{bool_t, qb_t}, ops::{OpTrait, OpType}, @@ -229,21 +252,25 @@ mod tests { use super::*; - #[test] - fn test_scan_def() { - let op = ArrayScan::new(bool_t(), qb_t(), vec![usize_t()], 2); + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_scan_def(#[case] _kind: AK) { + let op = GenericArrayScan::::new(bool_t(), qb_t(), vec![usize_t()], 2); let optype: OpType = op.clone().into(); - let new_op: ArrayScan = optype.cast().unwrap(); + let new_op: GenericArrayScan = optype.cast().unwrap(); assert_eq!(new_op, op); } - #[test] - fn test_scan_map() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_scan_map(#[case] _kind: AK) { let size = 2; let src_ty = qb_t(); let tgt_ty = bool_t(); - let op = ArrayScan::new(src_ty.clone(), tgt_ty.clone(), vec![], size); + let op = GenericArrayScan::::new(src_ty.clone(), tgt_ty.clone(), vec![], size); let optype: OpType = op.into(); let sig = optype.dataflow_signature().unwrap(); @@ -251,24 +278,26 @@ mod tests { sig.io(), ( &vec![ - array_type(size, src_ty.clone()), + AK::ty(size, src_ty.clone()), Type::new_function(Signature::new(vec![src_ty], vec![tgt_ty.clone()])) ] .into(), - &vec![array_type(size, tgt_ty)].into(), + &vec![AK::ty(size, tgt_ty)].into(), ) ); } - #[test] - fn test_scan_accs() { + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_scan_accs(#[case] _kind: AK) { let size = 2; let src_ty = qb_t(); let tgt_ty = bool_t(); let acc_ty1 = usize_t(); let acc_ty2 = qb_t(); - let op = ArrayScan::new( + let op = GenericArrayScan::::new( src_ty.clone(), tgt_ty.clone(), vec![acc_ty1.clone(), acc_ty2.clone()], @@ -281,7 +310,7 @@ mod tests { sig.io(), ( &vec![ - array_type(size, src_ty.clone()), + AK::ty(size, src_ty.clone()), Type::new_function(Signature::new( vec![src_ty, acc_ty1.clone(), acc_ty2.clone()], vec![tgt_ty.clone(), acc_ty1.clone(), acc_ty2.clone()] @@ -290,7 +319,7 @@ mod tests { acc_ty2.clone() ] .into(), - &vec![array_type(size, tgt_ty), acc_ty1, acc_ty2].into(), + &vec![AK::ty(size, tgt_ty), acc_ty1, acc_ty2].into(), ) ); } diff --git a/hugr-core/src/std_extensions/collections/array/array_value.rs b/hugr-core/src/std_extensions/collections/array/array_value.rs new file mode 100644 index 0000000000..2909acf291 --- /dev/null +++ b/hugr-core/src/std_extensions/collections/array/array_value.rs @@ -0,0 +1,159 @@ +use itertools::Itertools as _; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; + +use crate::extension::resolution::{ + resolve_type_extensions, resolve_value_extensions, ExtensionResolutionError, + WeakExtensionRegistry, +}; +use crate::ops::constant::{maybe_hash_values, TryHash, ValueName}; +use crate::ops::Value; +use crate::types::type_param::TypeArg; +use crate::types::{CustomCheckFailure, CustomType, Type}; + +use super::array_kind::ArrayKind; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +/// Statically sized array of values, all of the same type. +pub struct GenericArrayValue { + values: Vec, + typ: Type, + _kind: PhantomData, +} + +impl GenericArrayValue { + /// Create a new [CustomConst] for an array of values of type `typ`. + /// That all values are of type `typ` is not checked here. + /// + /// [CustomConst]: crate::ops::constant::CustomConst + pub fn new(typ: Type, contents: impl IntoIterator) -> Self { + Self { + values: contents.into_iter().collect_vec(), + typ, + _kind: PhantomData, + } + } + + /// Create a new [CustomConst] for an empty array of values of type `typ`. + /// + /// [CustomConst]: crate::ops::constant::CustomConst + pub fn new_empty(typ: Type) -> Self { + Self { + values: vec![], + typ, + _kind: PhantomData, + } + } + + /// Returns the type of the `[GenericArrayValue]` as a `[CustomType]`.` + pub fn custom_type(&self) -> CustomType { + AK::custom_ty(self.values.len() as u64, self.typ.clone()) + } + + /// Returns the type of the `[GenericArrayValue]`. + pub fn get_type(&self) -> Type { + self.custom_type().into() + } + + /// Returns the type of values inside the `[ArrayValue]`. + pub fn get_element_type(&self) -> &Type { + &self.typ + } + + /// Returns the values contained inside the `[ArrayValue]`. + pub fn get_contents(&self) -> &[Value] { + &self.values + } + + /// Returns the name of the value. + pub fn name(&self) -> ValueName { + AK::VALUE_NAME + } + + /// Validates the array value. + pub fn validate(&self) -> Result<(), CustomCheckFailure> { + let typ = self.custom_type(); + + AK::extension() + .get_type(&AK::TYPE_NAME) + .unwrap() + .check_custom(&typ) + .map_err(|_| { + CustomCheckFailure::Message(format!( + "Custom typ {typ} is not a valid instantiation of array." + )) + })?; + + // constant can only hold classic type. + let ty = match typ.args() { + [TypeArg::BoundedNat { n }, TypeArg::Type { ty }] + if *n as usize == self.values.len() => + { + ty + } + _ => { + return Err(CustomCheckFailure::Message(format!( + "Invalid array type arguments: {:?}", + typ.args() + ))) + } + }; + + // check all values are instances of the element type + for v in &self.values { + if v.get_type() != *ty { + return Err(CustomCheckFailure::Message(format!( + "Array element {v:?} is not of expected type {ty}" + ))); + } + } + + Ok(()) + } + + /// Update the extensions associated with the internal values. + pub fn update_extensions( + &mut self, + extensions: &WeakExtensionRegistry, + ) -> Result<(), ExtensionResolutionError> { + for val in &mut self.values { + resolve_value_extensions(val, extensions)?; + } + resolve_type_extensions(&mut self.typ, extensions) + } +} + +impl TryHash for GenericArrayValue { + fn try_hash(&self, mut st: &mut dyn Hasher) -> bool { + maybe_hash_values(&self.values, &mut st) && { + self.typ.hash(&mut st); + true + } + } +} + +#[cfg(test)] +mod test { + use rstest::rstest; + + use crate::extension::prelude::{usize_t, ConstUsize}; + use crate::std_extensions::arithmetic::float_types::ConstF64; + + use crate::std_extensions::collections::array::Array; + use crate::std_extensions::collections::value_array::ValueArray; + + use super::*; + + #[rstest] + #[case(Array)] + #[case(ValueArray)] + fn test_array_value(#[case] _kind: AK) { + let array_value = GenericArrayValue::::new(usize_t(), vec![ConstUsize::new(3).into()]); + array_value.validate().unwrap(); + + let wrong_array_value = + GenericArrayValue::::new(usize_t(), vec![ConstF64::new(1.2).into()]); + assert!(wrong_array_value.validate().is_err()); + } +} diff --git a/hugr-core/src/std_extensions/collections/array/op_builder.rs b/hugr-core/src/std_extensions/collections/array/op_builder.rs index 6234433478..4d8f7ce4e2 100644 --- a/hugr-core/src/std_extensions/collections/array/op_builder.rs +++ b/hugr-core/src/std_extensions/collections/array/op_builder.rs @@ -1,6 +1,7 @@ //! Builder trait for array operations in the dataflow graph. -use crate::std_extensions::collections::array::{new_array_op, ArrayOpDef}; +use crate::std_extensions::collections::array::GenericArrayOpDef; +use crate::std_extensions::collections::value_array::ValueArray; use crate::{ builder::{BuildError, Dataflow}, extension::simple_op::HasConcrete as _, @@ -9,8 +10,15 @@ use crate::{ }; use itertools::Itertools as _; -/// Trait for building array operations in a dataflow graph. -pub trait ArrayOpBuilder: Dataflow { +use super::{Array, ArrayKind, GenericArrayClone, GenericArrayDiscard}; + +use crate::extension::prelude::{ + either_type, option_type, usize_t, ConstUsize, UnwrapBuilder as _, +}; + +/// Trait for building array operations in a dataflow graph that are generic +/// over the concrete array implementation. +pub trait GenericArrayOpBuilder: Dataflow { /// Adds a new array operation to the dataflow graph and return the wire /// representing the new array. /// @@ -26,18 +34,70 @@ pub trait ArrayOpBuilder: Dataflow { /// # Returns /// /// The wire representing the new array. - fn add_new_array( + fn add_new_generic_array( &mut self, elem_ty: Type, values: impl IntoIterator, ) -> Result { let inputs = values.into_iter().collect_vec(); let [out] = self - .add_dataflow_op(new_array_op(elem_ty, inputs.len() as u64), inputs)? + .add_dataflow_op( + GenericArrayOpDef::::new_array.to_concrete(elem_ty, inputs.len() as u64), + inputs, + )? .outputs_arr(); Ok(out) } + /// Adds an array clone operation to the dataflow graph and return the wires + /// representing the originala and cloned array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// If building the operation fails. + /// + /// # Returns + /// + /// The wires representing the original and cloned array. + fn add_generic_array_clone( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result<(Wire, Wire), BuildError> { + let op = GenericArrayClone::::new(elem_ty, size).unwrap(); + let [arr1, arr2] = self.add_dataflow_op(op, vec![input])?.outputs_arr(); + Ok((arr1, arr2)) + } + + /// Adds an array discard operation to the dataflow graph. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// If building the operation fails. + fn add_generic_array_discard( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result<(), BuildError> { + let op = GenericArrayDiscard::::new(elem_ty, size).unwrap(); + let [] = self.add_dataflow_op(op, vec![input])?.outputs_arr(); + Ok(()) + } + /// Adds an array get operation to the dataflow graph. /// /// # Arguments @@ -53,17 +113,18 @@ pub trait ArrayOpBuilder: Dataflow { /// /// # Returns /// - /// The wire representing the value at the specified index in the array. - fn add_array_get( + /// * The wire representing the value at the specified index in the array + /// * The wire representing the array + fn add_generic_array_get( &mut self, elem_ty: Type, size: u64, input: Wire, index: Wire, - ) -> Result { - let op = ArrayOpDef::get.instantiate(&[size.into(), elem_ty.into()])?; - let [out] = self.add_dataflow_op(op, vec![input, index])?.outputs_arr(); - Ok(out) + ) -> Result<(Wire, Wire), BuildError> { + let op = GenericArrayOpDef::::get.instantiate(&[size.into(), elem_ty.into()])?; + let [out, arr] = self.add_dataflow_op(op, vec![input, index])?.outputs_arr(); + Ok((out, arr)) } /// Adds an array set operation to the dataflow graph. @@ -85,7 +146,7 @@ pub trait ArrayOpBuilder: Dataflow { /// # Returns /// /// The wire representing the updated array after the set operation. - fn add_array_set( + fn add_generic_array_set( &mut self, elem_ty: Type, size: u64, @@ -93,7 +154,7 @@ pub trait ArrayOpBuilder: Dataflow { index: Wire, value: Wire, ) -> Result { - let op = ArrayOpDef::set.instantiate(&[size.into(), elem_ty.into()])?; + let op = GenericArrayOpDef::::set.instantiate(&[size.into(), elem_ty.into()])?; let [out] = self .add_dataflow_op(op, vec![input, index, value])? .outputs_arr(); @@ -119,7 +180,7 @@ pub trait ArrayOpBuilder: Dataflow { /// # Returns /// /// The wire representing the updated array after the swap operation. - fn add_array_swap( + fn add_generic_array_swap( &mut self, elem_ty: Type, size: u64, @@ -127,7 +188,7 @@ pub trait ArrayOpBuilder: Dataflow { index1: Wire, index2: Wire, ) -> Result { - let op = ArrayOpDef::swap.instantiate(&[size.into(), elem_ty.into()])?; + let op = GenericArrayOpDef::::swap.instantiate(&[size.into(), elem_ty.into()])?; let [out] = self .add_dataflow_op(op, vec![input, index1, index2])? .outputs_arr(); @@ -151,13 +212,13 @@ pub trait ArrayOpBuilder: Dataflow { /// # Returns /// /// The wire representing the Option> - fn add_array_pop_left( + fn add_generic_array_pop_left( &mut self, elem_ty: Type, size: u64, input: Wire, ) -> Result { - let op = ArrayOpDef::pop_left.instantiate(&[size.into(), elem_ty.into()])?; + let op = GenericArrayOpDef::::pop_left.instantiate(&[size.into(), elem_ty.into()])?; Ok(self.add_dataflow_op(op, vec![input])?.out_wire(0)) } @@ -178,13 +239,13 @@ pub trait ArrayOpBuilder: Dataflow { /// # Returns /// /// The wire representing the Option> - fn add_array_pop_right( + fn add_generic_array_pop_right( &mut self, elem_ty: Type, size: u64, input: Wire, ) -> Result { - let op = ArrayOpDef::pop_right.instantiate(&[size.into(), elem_ty.into()])?; + let op = GenericArrayOpDef::::pop_right.instantiate(&[size.into(), elem_ty.into()])?; Ok(self.add_dataflow_op(op, vec![input])?.out_wire(0)) } @@ -198,9 +259,13 @@ pub trait ArrayOpBuilder: Dataflow { /// # Errors /// /// Returns an error if building the operation fails. - fn add_array_discard_empty(&mut self, elem_ty: Type, input: Wire) -> Result<(), BuildError> { + fn add_generic_array_discard_empty( + &mut self, + elem_ty: Type, + input: Wire, + ) -> Result<(), BuildError> { self.add_dataflow_op( - ArrayOpDef::discard_empty + GenericArrayOpDef::::discard_empty .instantiate(&[elem_ty.into()]) .unwrap(), [input], @@ -209,77 +274,104 @@ pub trait ArrayOpBuilder: Dataflow { } } -impl ArrayOpBuilder for D {} - -#[cfg(test)] -mod test { - use crate::std_extensions::collections::array::array_type; - use crate::{ - builder::{DFGBuilder, HugrBuilder}, - extension::prelude::{either_type, option_type, usize_t, ConstUsize, UnwrapBuilder as _}, - types::Signature, - Hugr, - }; - use rstest::rstest; +impl GenericArrayOpBuilder for D {} - use super::*; - - #[rstest::fixture] - #[default(DFGBuilder)] - fn all_array_ops( - #[default(DFGBuilder::new(Signature::new_endo(Type::EMPTY_TYPEROW)).unwrap())] - mut builder: B, - ) -> B { - let us0 = builder.add_load_value(ConstUsize::new(0)); - let us1 = builder.add_load_value(ConstUsize::new(1)); - let us2 = builder.add_load_value(ConstUsize::new(2)); - let arr = builder.add_new_array(usize_t(), [us1, us2]).unwrap(); - let [arr] = { - let r = builder.add_array_swap(usize_t(), 2, arr, us0, us1).unwrap(); - let res_sum_ty = { - let array_type = array_type(2, usize_t()); - either_type(array_type.clone(), array_type) - }; - builder.build_unwrap_sum(1, res_sum_ty, r).unwrap() +/// Helper function to build a Hugr that contains all basic array operations. +/// +/// Generic over the concrete array implementation. +pub fn build_all_array_ops_generic(mut builder: B) -> B { + let us0 = builder.add_load_value(ConstUsize::new(0)); + let us1 = builder.add_load_value(ConstUsize::new(1)); + let us2 = builder.add_load_value(ConstUsize::new(2)); + let arr = builder + .add_new_generic_array::(usize_t(), [us1, us2]) + .unwrap(); + let [arr] = { + let r = builder + .add_generic_array_swap::(usize_t(), 2, arr, us0, us1) + .unwrap(); + let res_sum_ty = { + let array_type = AK::ty(2, usize_t()); + either_type(array_type.clone(), array_type) }; + builder.build_unwrap_sum(1, res_sum_ty, r).unwrap() + }; - let [elem_0] = { - let r = builder.add_array_get(usize_t(), 2, arr, us0).unwrap(); + let ([elem_0], arr) = { + let (r, arr) = builder + .add_generic_array_get::(usize_t(), 2, arr, us0) + .unwrap(); + ( builder .build_unwrap_sum(1, option_type(usize_t()), r) - .unwrap() - }; - - let [_elem_1, arr] = { - let r = builder - .add_array_set(usize_t(), 2, arr, us1, elem_0) - .unwrap(); - let res_sum_ty = { - let row = vec![usize_t(), array_type(2, usize_t())]; - either_type(row.clone(), row) - }; - builder.build_unwrap_sum(1, res_sum_ty, r).unwrap() - }; + .unwrap(), + arr, + ) + }; - let [_elem_left, arr] = { - let r = builder.add_array_pop_left(usize_t(), 2, arr).unwrap(); - builder - .build_unwrap_sum(1, option_type(vec![usize_t(), array_type(1, usize_t())]), r) - .unwrap() - }; - let [_elem_right, arr] = { - let r = builder.add_array_pop_right(usize_t(), 1, arr).unwrap(); - builder - .build_unwrap_sum(1, option_type(vec![usize_t(), array_type(0, usize_t())]), r) - .unwrap() + let [_elem_1, arr] = { + let r = builder + .add_generic_array_set::(usize_t(), 2, arr, us1, elem_0) + .unwrap(); + let res_sum_ty = { + let row = vec![usize_t(), AK::ty(2, usize_t())]; + either_type(row.clone(), row) }; + builder.build_unwrap_sum(1, res_sum_ty, r).unwrap() + }; - builder.add_array_discard_empty(usize_t(), arr).unwrap(); + let [_elem_left, arr] = { + let r = builder + .add_generic_array_pop_left::(usize_t(), 2, arr) + .unwrap(); + builder + .build_unwrap_sum(1, option_type(vec![usize_t(), AK::ty(1, usize_t())]), r) + .unwrap() + }; + let [_elem_right, arr] = { + let r = builder + .add_generic_array_pop_right::(usize_t(), 1, arr) + .unwrap(); builder + .build_unwrap_sum(1, option_type(vec![usize_t(), AK::ty(0, usize_t())]), r) + .unwrap() + }; + + builder + .add_generic_array_discard_empty::(usize_t(), arr) + .unwrap(); + builder +} + +/// Helper function to build a Hugr that contains all basic array operations. +pub fn build_all_array_ops(builder: B) -> B { + build_all_array_ops_generic::(builder) +} + +/// Helper function to build a Hugr that contains all basic array operations. +pub fn build_all_value_array_ops(builder: B) -> B { + build_all_array_ops_generic::(builder) +} + +/// Testing utilities to generate Hugrs that contain array operations. +#[cfg(test)] +mod test { + use crate::builder::{DFGBuilder, HugrBuilder}; + use crate::types::Signature; + + use super::*; + + #[test] + fn all_array_ops() { + let sig = Signature::new_endo(Type::EMPTY_TYPEROW); + let builder = DFGBuilder::new(sig).unwrap(); + build_all_array_ops(builder).finish_hugr().unwrap(); } - #[rstest] - fn build_all_ops(all_array_ops: DFGBuilder) { - all_array_ops.finish_hugr().unwrap(); + #[test] + fn all_value_array_ops() { + let sig = Signature::new_endo(Type::EMPTY_TYPEROW); + let builder = DFGBuilder::new(sig).unwrap(); + build_all_value_array_ops(builder).finish_hugr().unwrap(); } } diff --git a/hugr-core/src/std_extensions/collections/value_array.rs b/hugr-core/src/std_extensions/collections/value_array.rs new file mode 100644 index 0000000000..a1731cd7c6 --- /dev/null +++ b/hugr-core/src/std_extensions/collections/value_array.rs @@ -0,0 +1,349 @@ +//! A version of the standard fixed-length array extension where arrays of copyable types +//! are copyable themselves. +//! +//! Supports all regular array operations apart from `clone` and `discard`. + +use std::sync::Arc; + +use delegate::delegate; +use lazy_static::lazy_static; + +use crate::builder::{BuildError, Dataflow}; +use crate::extension::resolution::{ExtensionResolutionError, WeakExtensionRegistry}; +use crate::extension::simple_op::{HasConcrete, MakeOpDef}; +use crate::extension::{ExtensionId, SignatureError, TypeDef, TypeDefBound}; +use crate::ops::constant::{CustomConst, ValueName}; +use crate::types::type_param::{TypeArg, TypeParam}; +use crate::types::{CustomCheckFailure, Type, TypeBound, TypeName}; +use crate::{Extension, Wire}; + +use super::array::op_builder::GenericArrayOpBuilder; +use super::array::{ + Array, ArrayKind, GenericArrayConvert, GenericArrayConvertDef, GenericArrayOp, + GenericArrayOpDef, GenericArrayRepeat, GenericArrayRepeatDef, GenericArrayScan, + GenericArrayScanDef, GenericArrayValue, FROM, INTO, +}; + +/// Reported unique name of the value array type. +pub const VALUE_ARRAY_TYPENAME: TypeName = TypeName::new_inline("value_array"); +/// Reported unique name of the value array value. +pub const VALUE_ARRAY_VALUENAME: TypeName = TypeName::new_inline("value_array"); +/// Reported unique name of the extension +pub const EXTENSION_ID: ExtensionId = ExtensionId::new_static_unchecked("collections.value_array"); +/// Extension version. +pub const VERSION: semver::Version = semver::Version::new(0, 1, 0); + +/// A fixed-length collection of values. +/// +/// A value array inherits its linearity from its elements. +#[derive(Clone, Copy, Debug, derive_more::Display, Eq, PartialEq, Default)] +pub struct ValueArray; + +impl ArrayKind for ValueArray { + const EXTENSION_ID: ExtensionId = EXTENSION_ID; + const TYPE_NAME: TypeName = VALUE_ARRAY_TYPENAME; + const VALUE_NAME: ValueName = VALUE_ARRAY_VALUENAME; + + fn extension() -> &'static Arc { + &EXTENSION + } + + fn type_def() -> &'static TypeDef { + EXTENSION.get_type(&VALUE_ARRAY_TYPENAME).unwrap() + } + + fn build_clone( + _builder: &mut D, + _elem_ty: Type, + _size: u64, + arr: Wire, + ) -> Result<(Wire, Wire), BuildError> { + Ok((arr, arr)) + } + + fn build_discard( + _builder: &mut D, + _elem_ty: Type, + _size: u64, + _arr: Wire, + ) -> Result<(), BuildError> { + Ok(()) + } +} + +/// Value array operation definitions. +pub type VArrayOpDef = GenericArrayOpDef; +/// Value array repeat operation definition. +pub type VArrayRepeatDef = GenericArrayRepeatDef; +/// Value array scan operation definition. +pub type VArrayScanDef = GenericArrayScanDef; +/// Value array to default array conversion operation definition. +pub type VArrayToArrayDef = GenericArrayConvertDef; +/// Value array from default array conversion operation definition. +pub type VArrayFromArrayDef = GenericArrayConvertDef; + +/// Value array operations. +pub type VArrayOp = GenericArrayOp; +/// The value array repeat operation. +pub type VArrayRepeat = GenericArrayRepeat; +/// The value array scan operation. +pub type VArrayScan = GenericArrayScan; +/// The value array to default array conversion operation. +pub type VArrayToArray = GenericArrayConvert; +/// The value array from default array conversion operation. +pub type VArrayFromArray = GenericArrayConvert; + +/// A value array extension value. +pub type VArrayValue = GenericArrayValue; + +lazy_static! { + /// Extension for value array operations. + pub static ref EXTENSION: Arc = { + Extension::new_arc(EXTENSION_ID, VERSION, |extension, extension_ref| { + extension.add_type( + VALUE_ARRAY_TYPENAME, + vec![ TypeParam::max_nat(), TypeBound::Any.into()], + "Fixed-length value array".into(), + // Value arrays are copyable iff their elements are + TypeDefBound::from_params(vec![1]), + extension_ref, + ) + .unwrap(); + + VArrayOpDef::load_all_ops(extension, extension_ref).unwrap(); + VArrayRepeatDef::new().add_to_extension(extension, extension_ref).unwrap(); + VArrayScanDef::new().add_to_extension(extension, extension_ref).unwrap(); + VArrayToArrayDef::new().add_to_extension(extension, extension_ref).unwrap(); + VArrayFromArrayDef::new().add_to_extension(extension, extension_ref).unwrap(); + }) + }; +} + +#[typetag::serde(name = "VArrayValue")] +impl CustomConst for VArrayValue { + delegate! { + to self { + fn name(&self) -> ValueName; + fn validate(&self) -> Result<(), CustomCheckFailure>; + fn update_extensions( + &mut self, + extensions: &WeakExtensionRegistry, + ) -> Result<(), ExtensionResolutionError>; + fn get_type(&self) -> Type; + } + } + + fn equal_consts(&self, other: &dyn CustomConst) -> bool { + crate::ops::constant::downcast_equal_consts(self, other) + } +} + +/// Gets the [TypeDef] for value arrays. Note that instantiations are more easily +/// created via [value_array_type] and [value_array_type_parametric] +pub fn value_array_type_def() -> &'static TypeDef { + ValueArray::type_def() +} + +/// Instantiate a new value array type given a size argument and element type. +/// +/// This method is equivalent to [`value_array_type_parametric`], but uses concrete +/// arguments types to ensure no errors are possible. +pub fn value_array_type(size: u64, element_ty: Type) -> Type { + ValueArray::ty(size, element_ty) +} + +/// Instantiate a new value array type given the size and element type parameters. +/// +/// This is a generic version of [`value_array_type`]. +pub fn value_array_type_parametric( + size: impl Into, + element_ty: impl Into, +) -> Result { + ValueArray::ty_parametric(size, element_ty) +} + +/// Trait for building value array operations in a dataflow graph. +pub trait VArrayOpBuilder: GenericArrayOpBuilder { + /// Adds a new array operation to the dataflow graph and return the wire + /// representing the new array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `values` - An iterator over the values to initialize the array with. + /// + /// # Errors + /// + /// If building the operation fails. + /// + /// # Returns + /// + /// The wire representing the new array. + fn add_new_value_array( + &mut self, + elem_ty: Type, + values: impl IntoIterator, + ) -> Result { + self.add_new_generic_array::(elem_ty, values) + } + + /// Adds an array get operation to the dataflow graph. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// * `index` - The wire representing the index to get. + /// + /// # Errors + /// + /// If building the operation fails. + /// + /// # Returns + /// + /// * The wire representing the value at the specified index in the array + /// * The wire representing the array + fn add_value_array_get( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + index: Wire, + ) -> Result<(Wire, Wire), BuildError> { + self.add_generic_array_get::(elem_ty, size, input, index) + } + + /// Adds an array set operation to the dataflow graph. + /// + /// This operation sets the value at a specified index in the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// * `index` - The wire representing the index to set. + /// * `value` - The wire representing the value to set at the specified index. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the updated array after the set operation. + fn add_value_array_set( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + index: Wire, + value: Wire, + ) -> Result { + self.add_generic_array_set::(elem_ty, size, input, index, value) + } + + /// Adds an array swap operation to the dataflow graph. + /// + /// This operation swaps the values at two specified indices in the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// * `index1` - The wire representing the first index to swap. + /// * `index2` - The wire representing the second index to swap. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the updated array after the swap operation. + fn add_value_array_swap( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + index1: Wire, + index2: Wire, + ) -> Result { + let op = + GenericArrayOpDef::::swap.instantiate(&[size.into(), elem_ty.into()])?; + let [out] = self + .add_dataflow_op(op, vec![input, index1, index2])? + .outputs_arr(); + Ok(out) + } + + /// Adds an array pop-left operation to the dataflow graph. + /// + /// This operation removes the leftmost element from the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the Option> + fn add_array_pop_left( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result { + self.add_generic_array_pop_left::(elem_ty, size, input) + } + + /// Adds an array pop-right operation to the dataflow graph. + /// + /// This operation removes the rightmost element from the array. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `size` - The size of the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + /// + /// # Returns + /// + /// The wire representing the Option> + fn add_array_pop_right( + &mut self, + elem_ty: Type, + size: u64, + input: Wire, + ) -> Result { + self.add_generic_array_pop_right::(elem_ty, size, input) + } + + /// Adds an operation to discard an empty array from the dataflow graph. + /// + /// # Arguments + /// + /// * `elem_ty` - The type of the elements in the array. + /// * `input` - The wire representing the array. + /// + /// # Errors + /// + /// Returns an error if building the operation fails. + fn add_array_discard_empty(&mut self, elem_ty: Type, input: Wire) -> Result<(), BuildError> { + self.add_generic_array_discard_empty::(elem_ty, input) + } +} + +impl VArrayOpBuilder for D {} diff --git a/hugr-core/src/utils.rs b/hugr-core/src/utils.rs index f44b075f13..efa0eef844 100644 --- a/hugr-core/src/utils.rs +++ b/hugr-core/src/utils.rs @@ -101,6 +101,18 @@ pub(crate) fn is_default(t: &T) -> bool { *t == Default::default() } +/// An empty type. +/// +/// # Example +/// +/// ```ignore +/// fn foo(never: Never) -> ! { +/// match never {} +/// } +/// ``` +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum Never {} + #[cfg(test)] pub(crate) mod test_quantum_extension { use std::sync::Arc; diff --git a/hugr-llvm/src/emit/libc.rs b/hugr-llvm/src/emit/libc.rs index 4eb481b633..69a8754a09 100644 --- a/hugr-llvm/src/emit/libc.rs +++ b/hugr-llvm/src/emit/libc.rs @@ -1,6 +1,9 @@ use anyhow::Result; use hugr_core::{HugrView, Node}; -use inkwell::{values::BasicMetadataValueEnum, AddressSpace}; +use inkwell::{ + values::{BasicMetadataValueEnum, BasicValueEnum}, + AddressSpace, +}; use crate::emit::func::EmitFuncContext; @@ -26,3 +29,39 @@ pub fn emit_libc_printf>( context.builder().build_call(printf, args, "")?; Ok(()) } + +/// Emits a call to the libc `void* malloc(size_t size)` function. +pub fn emit_libc_malloc<'c, H: HugrView>( + context: &mut EmitFuncContext<'c, '_, H>, + size: BasicMetadataValueEnum<'c>, +) -> Result> { + let iw_ctx = context.typing_session().iw_context(); + let malloc_sig = iw_ctx + .i8_type() + .ptr_type(AddressSpace::default()) + .fn_type(&[iw_ctx.i64_type().into()], false); + let malloc = context.get_extern_func("malloc", malloc_sig)?; + let res = context + .builder() + .build_call(malloc, &[size], "")? + .try_as_basic_value() + .unwrap_left(); + Ok(res) +} + +/// Emits a call to the libc `void free(void* ptr)` function. +pub fn emit_libc_free>( + context: &mut EmitFuncContext, + ptr: BasicMetadataValueEnum, +) -> Result<()> { + let iw_ctx = context.typing_session().iw_context(); + let ptr_ty = iw_ctx.i8_type().ptr_type(AddressSpace::default()); + let ptr = context + .builder() + .build_bit_cast(ptr.into_pointer_value(), ptr_ty, "")?; + + let free_sig = iw_ctx.void_type().fn_type(&[ptr_ty.into()], false); + let free = context.get_extern_func("free", free_sig)?; + context.builder().build_call(free, &[ptr.into()], "")?; + Ok(()) +} diff --git a/hugr-llvm/src/extension/collections/array.rs b/hugr-llvm/src/extension/collections/array.rs index 0216e9014d..d1f7b6e455 100644 --- a/hugr-llvm/src/extension/collections/array.rs +++ b/hugr-llvm/src/extension/collections/array.rs @@ -1,24 +1,42 @@ //! Codegen for prelude array operations. +//! +//! An `array` is now lowered to a fat pointer `{ptr, usize}` that is allocated +//! to at least `n * sizeof(T)` bytes. The extra `usize` is an offset pointing to the +//! first element, i.e. the first element is at address `ptr + offset * sizeof(T)`. +//! +//! The rational behind the additional offset is the `pop_left` operation which bumps +//! the offset instead of mutating the pointer. This way, we can still free the original +//! pointer when the array is discarded after a pop. +//! +//! We provide utility functions [array_fat_pointer_ty], [build_array_fat_pointer], and +//! [decompose_array_fat_pointer] to work with array fat pointers. +//! +//! The [DefaultArrayCodegen] extension allocates all arrays on the heap using the +//! standard libc `malloc` and `free` functions. This behaviour can be customised +//! by providing a different implementation for [ArrayCodegen::emit_allocate_array] +//! and [ArrayCodegen::emit_free_array]. use std::iter; use anyhow::{anyhow, Ok, Result}; -use hugr_core::extension::prelude::option_type; +use hugr_core::extension::prelude::{option_type, usize_t}; use hugr_core::extension::simple_op::{MakeExtensionOp, MakeRegisteredOp}; use hugr_core::ops::DataflowOpTrait; use hugr_core::std_extensions::collections::array::{ - self, array_type, ArrayOp, ArrayOpDef, ArrayRepeat, ArrayScan, + self, array_type, ArrayClone, ArrayDiscard, ArrayOp, ArrayOpDef, ArrayRepeat, ArrayScan, }; use hugr_core::types::{TypeArg, TypeEnum}; use hugr_core::{HugrView, Node}; -use inkwell::builder::{Builder, BuilderError}; -use inkwell::types::{BasicType, BasicTypeEnum}; +use inkwell::builder::Builder; +use inkwell::intrinsics::Intrinsic; +use inkwell::types::{BasicType, BasicTypeEnum, IntType, StructType}; use inkwell::values::{ - ArrayValue, BasicValue as _, BasicValueEnum, CallableValue, IntValue, PointerValue, + BasicValue as _, BasicValueEnum, CallableValue, IntValue, PointerValue, StructValue, }; -use inkwell::IntPredicate; +use inkwell::{AddressSpace, IntPredicate}; use itertools::Itertools; use crate::emit::emit_value; +use crate::emit::libc::{emit_libc_free, emit_libc_malloc}; use crate::{ emit::{deaggregate_call_result, EmitFuncContext, RowPromise}, types::{HugrType, TypingSession}, @@ -41,15 +59,52 @@ impl<'a, H: HugrView + 'a> CodegenExtsBuilder<'a, H> { /// A helper trait for customising the lowering of [hugr_core::std_extensions::collections::array] /// types, [hugr_core::ops::constant::CustomConst]s, and ops. +/// +/// An `array` is now lowered to a fat pointer `{ptr, usize}` that is allocated +/// to at least `n * sizeof(T)` bytes. The extra `usize` is an offset pointing to the +/// first element, i.e. the first element is at address `ptr + offset * sizeof(T)`. +/// +/// The rational behind the additional offset is the `pop_left` operation which bumps +/// the offset instead of mutating the pointer. This way, we can still free the original +/// pointer when the array is discarded after a pop. +/// +/// By default, all arrays are allocated on the heap using the standard libc `malloc` +/// and `free` functions. This behaviour can be customised by providing a different +/// implementation for [ArrayCodegen::emit_allocate_array] and +/// [ArrayCodegen::emit_free_array]. pub trait ArrayCodegen: Clone { + /// Emit an allocation of `size` bytes and return the corresponding pointer. + /// + /// The default implementation allocates on the heap by emitting a call to the + /// standard libc `malloc` function. + fn emit_allocate_array<'c, H: HugrView>( + &self, + ctx: &mut EmitFuncContext<'c, '_, H>, + size: IntValue<'c>, + ) -> Result> { + let ptr = emit_libc_malloc(ctx, size.into())?; + Ok(ptr.into_pointer_value()) + } + + /// Emit an deallocation of a pointer. + /// + /// The default implementation emits a call to the standard libc `free` function. + fn emit_free_array<'c, H: HugrView>( + &self, + ctx: &mut EmitFuncContext<'c, '_, H>, + ptr: PointerValue<'c>, + ) -> Result<()> { + emit_libc_free(ctx, ptr.into()) + } + /// Return the llvm type of [hugr_core::std_extensions::collections::array::ARRAY_TYPENAME]. fn array_type<'c>( &self, - _session: &TypingSession<'c, '_>, + session: &TypingSession<'c, '_>, elem_ty: BasicTypeEnum<'c>, - size: u64, + _size: u64, ) -> impl BasicType<'c> { - elem_ty.array_type(size as u32) + array_fat_pointer_ty(session, elem_ty) } /// Emit a [hugr_core::std_extensions::collections::array::ArrayValue]. @@ -72,6 +127,26 @@ pub trait ArrayCodegen: Clone { emit_array_op(self, ctx, op, inputs, outputs) } + /// Emit a [hugr_core::std_extensions::collections::array::ArrayClone] operation. + fn emit_array_clone<'c, H: HugrView>( + &self, + ctx: &mut EmitFuncContext<'c, '_, H>, + op: ArrayClone, + array_v: BasicValueEnum<'c>, + ) -> Result<(BasicValueEnum<'c>, BasicValueEnum<'c>)> { + emit_clone_op(self, ctx, op, array_v) + } + + /// Emit a [hugr_core::std_extensions::collections::array::ArrayDiscard] operation. + fn emit_array_discard<'c, H: HugrView>( + &self, + ctx: &mut EmitFuncContext<'c, '_, H>, + op: ArrayDiscard, + array_v: BasicValueEnum<'c>, + ) -> Result<()> { + emit_array_discard(self, ctx, op, array_v) + } + /// Emit a [hugr_core::std_extensions::collections::array::ArrayRepeat] op. fn emit_array_repeat<'c, H: HugrView>( &self, @@ -79,7 +154,7 @@ pub trait ArrayCodegen: Clone { op: ArrayRepeat, func: BasicValueEnum<'c>, ) -> Result> { - emit_repeat_op(ctx, op, func) + emit_repeat_op(self, ctx, op, func) } /// Emit a [hugr_core::std_extensions::collections::array::ArrayScan] op. @@ -93,7 +168,14 @@ pub trait ArrayCodegen: Clone { func: BasicValueEnum<'c>, initial_accs: &[BasicValueEnum<'c>], ) -> Result<(BasicValueEnum<'c>, Vec>)> { - emit_scan_op(ctx, op, src_array, func, initial_accs) + emit_scan_op( + self, + ctx, + op, + src_array.into_struct_value(), + func, + initial_accs, + ) } } @@ -153,6 +235,24 @@ impl CodegenExtension for ArrayCodegenExtension { ) } }) + .extension_op(array::EXTENSION_ID, array::ARRAY_CLONE_OP_ID, { + let ccg = self.0.clone(); + move |context, args| { + let arr = args.inputs[0]; + let op = ArrayClone::from_extension_op(args.node().as_ref())?; + let (arr1, arr2) = ccg.emit_array_clone(context, op, arr)?; + args.outputs.finish(context.builder(), [arr1, arr2]) + } + }) + .extension_op(array::EXTENSION_ID, array::ARRAY_DISCARD_OP_ID, { + let ccg = self.0.clone(); + move |context, args| { + let arr = args.inputs[0]; + let op = ArrayDiscard::from_extension_op(args.node().as_ref())?; + ccg.emit_array_discard(context, op, arr)?; + args.outputs.finish(context.builder(), []) + } + }) .extension_op(array::EXTENSION_ID, array::ARRAY_REPEAT_OP_ID, { let ccg = self.0.clone(); move |context, args| { @@ -178,41 +278,85 @@ impl CodegenExtension for ArrayCodegenExtension { } } -/// Helper function to allocate an array on the stack. -/// -/// Returns two pointers: The first one is a pointer to the first element of the -/// array (i.e. it is of type `array.get_element_type().ptr_type()`) whereas the -/// second one points to the whole array value, i.e. it is of type `array.ptr_type()`. -fn build_array_alloca<'c>( +fn usize_ty<'c>(ts: &TypingSession<'c, '_>) -> IntType<'c> { + ts.llvm_type(&usize_t()) + .expect("Prelude codegen is registered") + .into_int_type() +} + +/// Returns the LLVM representation of an array value as a fat pointer. +pub fn array_fat_pointer_ty<'c>( + session: &TypingSession<'c, '_>, + elem_ty: BasicTypeEnum<'c>, +) -> StructType<'c> { + let iw_ctx = session.iw_context(); + iw_ctx.struct_type( + &[ + elem_ty.ptr_type(AddressSpace::default()).into(), + usize_ty(session).into(), + ], + false, + ) +} + +/// Constructs an array fat pointer value. +pub fn build_array_fat_pointer<'c, H: HugrView>( + ctx: &mut EmitFuncContext<'c, '_, H>, + ptr: PointerValue<'c>, + offset: IntValue<'c>, +) -> Result> { + let array_ty = array_fat_pointer_ty( + &ctx.typing_session(), + ptr.get_type().get_element_type().try_into().unwrap(), + ); + let array_v = array_ty.get_poison(); + let array_v = ctx + .builder() + .build_insert_value(array_v, ptr.as_basic_value_enum(), 0, "")?; + let array_v = ctx + .builder() + .build_insert_value(array_v, offset.as_basic_value_enum(), 1, "")?; + Ok(array_v.into_struct_value()) +} + +/// Returns the underlying pointer and offset stored in a fat array pointer. +pub fn decompose_array_fat_pointer<'c>( builder: &Builder<'c>, - array: ArrayValue<'c>, -) -> Result<(PointerValue<'c>, PointerValue<'c>), BuilderError> { - let array_ty = array.get_type(); - let array_len: IntValue<'c> = { - let ctx = builder.get_insert_block().unwrap().get_context(); - ctx.i32_type().const_int(array_ty.len() as u64, false) - }; - let ptr = builder.build_array_alloca(array_ty.get_element_type(), array_len, "")?; - let array_ptr = builder - .build_bit_cast(ptr, array_ty.ptr_type(Default::default()), "")? - .into_pointer_value(); - builder.build_store(array_ptr, array)?; - Result::Ok((ptr, array_ptr)) + array_v: BasicValueEnum<'c>, +) -> Result<(PointerValue<'c>, IntValue<'c>)> { + let array_v = array_v.into_struct_value(); + let array_ptr = builder.build_extract_value(array_v, 0, "array_ptr")?; + let array_offset = builder.build_extract_value(array_v, 1, "array_offset")?; + Ok(( + array_ptr.into_pointer_value(), + array_offset.into_int_value(), + )) } -/// Helper function to allocate an array on the stack and pass a pointer to it -/// to a closure. +/// Helper function to allocate a fat array pointer. /// -/// The pointer forwarded to the closure is a pointer to the first element of -/// the array. I.e. it is of type `array.get_element_type().ptr_type()` not -/// `array.ptr_type()` -fn with_array_alloca<'c, T, E: From>( - builder: &Builder<'c>, - array: ArrayValue<'c>, - go: impl FnOnce(PointerValue<'c>) -> Result, -) -> Result { - let (ptr, _) = build_array_alloca(builder, array)?; - go(ptr) +/// Returns a pointer and a struct: The pointer points to the first element of the array (i.e. it +/// is of type `elem_ty.ptr_type()`). The struct is the fat pointer of the that stores an additional +/// offset (initialised to be 0). +fn build_array_alloc<'c, H: HugrView>( + ctx: &mut EmitFuncContext<'c, '_, H>, + ccg: &impl ArrayCodegen, + elem_ty: BasicTypeEnum<'c>, + size: u64, +) -> Result<(PointerValue<'c>, StructValue<'c>)> { + let usize_t = usize_ty(&ctx.typing_session()); + let length = usize_t.const_int(size, false); + let size_value = ctx + .builder() + .build_int_mul(length, elem_ty.size_of().unwrap(), "")?; + let ptr = ccg.emit_allocate_array(ctx, size_value)?; + let elem_ptr = ctx + .builder() + .build_bit_cast(ptr, elem_ty.ptr_type(AddressSpace::default()), "")? + .into_pointer_value(); + let offset = usize_t.const_zero(); + let array_v = build_array_fat_pointer(ctx, elem_ptr, offset)?; + Ok((elem_ptr, array_v)) } /// Helper function to build a loop that repeats for a given number of iterations. @@ -225,7 +369,7 @@ fn build_loop<'c, T, H: HugrView>( go: impl FnOnce(&mut EmitFuncContext<'c, '_, H>, IntValue<'c>) -> Result, ) -> Result { let builder = ctx.builder(); - let idx_ty = ctx.iw_context().i32_type(); + let idx_ty = usize_ty(&ctx.typing_session()); let idx_ptr = builder.build_alloca(idx_ty, "")?; builder.build_store(idx_ptr, idx_ty.const_zero())?; @@ -257,31 +401,26 @@ fn build_loop<'c, T, H: HugrView>( Ok(val) } +/// Emits an [array::ArrayValue]. pub fn emit_array_value<'c, H: HugrView>( ccg: &impl ArrayCodegen, ctx: &mut EmitFuncContext<'c, '_, H>, value: &array::ArrayValue, ) -> Result> { let ts = ctx.typing_session(); - let llvm_array_ty = ccg - .array_type( - &ts, - ts.llvm_type(value.get_element_type())?, - value.get_contents().len() as u64, - ) - .as_basic_type_enum() - .into_array_type(); - let mut array_v = llvm_array_ty.get_undef(); + let elem_ty = ts.llvm_type(value.get_element_type())?; + let (elem_ptr, array_v) = + build_array_alloc(ctx, ccg, elem_ty, value.get_contents().len() as u64)?; for (i, v) in value.get_contents().iter().enumerate() { let llvm_v = emit_value(ctx, v)?; - array_v = ctx - .builder() - .build_insert_value(array_v, llvm_v, i as u32, "")? - .into_array_value(); + let idx = ts.iw_context().i32_type().const_int(i as u64, true); + let elem_addr = unsafe { ctx.builder().build_in_bounds_gep(elem_ptr, &[idx], "")? }; + ctx.builder().build_store(elem_addr, llvm_v)?; } Ok(array_v.into()) } +/// Emits an [ArrayOp]. pub fn emit_array_op<'c, H: HugrView>( ccg: &impl ArrayCodegen, ctx: &mut EmitFuncContext<'c, '_, H>, @@ -299,28 +438,26 @@ pub fn emit_array_op<'c, H: HugrView>( .into_owned(); let ArrayOp { def, - ref elem_ty, + elem_ty: ref hugr_elem_ty, size, } = op; - let llvm_array_ty = ccg - .array_type(&ts, ts.llvm_type(elem_ty)?, size) - .as_basic_type_enum() - .into_array_type(); + let elem_ty = ts.llvm_type(hugr_elem_ty)?; match def { ArrayOpDef::new_array => { - let mut array_v = llvm_array_ty.get_undef(); + let (elem_ptr, array_v) = build_array_alloc(ctx, ccg, elem_ty, size)?; + let usize_t = usize_ty(&ctx.typing_session()); for (i, v) in inputs.into_iter().enumerate() { - array_v = builder - .build_insert_value(array_v, v, i as u32, "")? - .into_array_value(); + let idx = usize_t.const_int(i as u64, true); + let elem_addr = unsafe { ctx.builder().build_in_bounds_gep(elem_ptr, &[idx], "")? }; + ctx.builder().build_store(elem_addr, v)?; } - outputs.finish(builder, [array_v.as_basic_value_enum()]) + outputs.finish(ctx.builder(), [array_v.into()]) } ArrayOpDef::get => { let [array_v, index_v] = inputs .try_into() .map_err(|_| anyhow!("ArrayOpDef::get expects two arguments"))?; - let array_v = array_v.into_array_value(); + let (array_ptr, array_offset) = decompose_array_fat_pointer(builder, array_v)?; let index_v = index_v.into_int_value(); let res_hugr_ty = sig .output() @@ -334,7 +471,7 @@ pub fn emit_array_op<'c, H: HugrView>( ts.llvm_sum_type(st.clone())? }; - let exit_rmb = ctx.new_row_mail_box([res_hugr_ty], "")?; + let exit_rmb = ctx.new_row_mail_box(sig.output.iter(), "")?; let exit_block = ctx.build_positioned_new_block("", None, |ctx, bb| { outputs.finish(ctx.builder(), exit_rmb.read_vec(ctx.builder(), [])?)?; @@ -344,15 +481,13 @@ pub fn emit_array_op<'c, H: HugrView>( let success_block = ctx.build_positioned_new_block("", Some(exit_block), |ctx, bb| { let builder = ctx.builder(); - let elem_v = with_array_alloca(builder, array_v, |ptr| { - // inside `success_block` we know `index_v` to be in - // bounds. - let elem_addr = - unsafe { builder.build_in_bounds_gep(ptr, &[index_v], "")? }; - builder.build_load(elem_addr, "") - })?; + // inside `success_block` we know `index_v` to be in bounds + let index_v = builder.build_int_add(index_v, array_offset, "")?; + let elem_addr = + unsafe { builder.build_in_bounds_gep(array_ptr, &[index_v], "")? }; + let elem_v = builder.build_load(elem_addr, "")?; let success_v = res_sum_ty.build_tag(builder, 1, vec![elem_v])?; - exit_rmb.write(ctx.builder(), [success_v.into()])?; + exit_rmb.write(ctx.builder(), [success_v.into(), array_v])?; builder.build_unconditional_branch(exit_block)?; Ok(bb) })?; @@ -361,7 +496,7 @@ pub fn emit_array_op<'c, H: HugrView>( ctx.build_positioned_new_block("", Some(success_block), |ctx, bb| { let builder = ctx.builder(); let failure_v = res_sum_ty.build_tag(builder, 0, vec![])?; - exit_rmb.write(ctx.builder(), [failure_v.into()])?; + exit_rmb.write(ctx.builder(), [failure_v.into(), array_v])?; builder.build_unconditional_branch(exit_block)?; Ok(bb) })?; @@ -379,10 +514,10 @@ pub fn emit_array_op<'c, H: HugrView>( Ok(()) } ArrayOpDef::set => { - let [array_v0, index_v, value_v] = inputs + let [array_v, index_v, value_v] = inputs .try_into() .map_err(|_| anyhow!("ArrayOpDef::set expects three arguments"))?; - let array_v = array_v0.into_array_value(); + let (array_ptr, array_offset) = decompose_array_fat_pointer(builder, array_v)?; let index_v = index_v.into_int_value(); let res_hugr_ty = sig @@ -407,23 +542,12 @@ pub fn emit_array_op<'c, H: HugrView>( let success_block = ctx.build_positioned_new_block("", Some(exit_block), |ctx, bb| { let builder = ctx.builder(); - let (elem_v, array_v) = with_array_alloca(builder, array_v, |ptr| { - // inside `success_block` we know `index_v` to be in - // bounds. - let elem_addr = - unsafe { builder.build_in_bounds_gep(ptr, &[index_v], "")? }; - let elem_v = builder.build_load(elem_addr, "")?; - builder.build_store(elem_addr, value_v)?; - let ptr = builder - .build_bit_cast( - ptr, - array_v.get_type().ptr_type(Default::default()), - "", - )? - .into_pointer_value(); - let array_v = builder.build_load(ptr, "")?; - Ok((elem_v, array_v)) - })?; + // inside `success_block` we know `index_v` to be in bounds. + let index_v = builder.build_int_add(index_v, array_offset, "")?; + let elem_addr = + unsafe { builder.build_in_bounds_gep(array_ptr, &[index_v], "")? }; + let elem_v = builder.build_load(elem_addr, "")?; + builder.build_store(elem_addr, value_v)?; let success_v = res_sum_ty.build_tag(builder, 1, vec![elem_v, array_v])?; exit_rmb.write(ctx.builder(), [success_v.into()])?; builder.build_unconditional_branch(exit_block)?; @@ -433,8 +557,7 @@ pub fn emit_array_op<'c, H: HugrView>( let failure_block = ctx.build_positioned_new_block("", Some(success_block), |ctx, bb| { let builder = ctx.builder(); - let failure_v = - res_sum_ty.build_tag(builder, 0, vec![value_v, array_v.into()])?; + let failure_v = res_sum_ty.build_tag(builder, 0, vec![value_v, array_v])?; exit_rmb.write(ctx.builder(), [failure_v.into()])?; builder.build_unconditional_branch(exit_block)?; Ok(bb) @@ -452,10 +575,10 @@ pub fn emit_array_op<'c, H: HugrView>( Ok(()) } ArrayOpDef::swap => { - let [array_v0, index1_v, index2_v] = inputs + let [array_v, index1_v, index2_v] = inputs .try_into() .map_err(|_| anyhow!("ArrayOpDef::swap expects three arguments"))?; - let array_v = array_v0.into_array_value(); + let (array_ptr, array_offset) = decompose_array_fat_pointer(builder, array_v)?; let index1_v = index1_v.into_int_value(); let index2_v = index2_v.into_int_value(); @@ -488,26 +611,18 @@ pub fn emit_array_op<'c, H: HugrView>( // the cost of worse code in cases where it cannot. // For now we choose the simpler option of omitting the check. let builder = ctx.builder(); - let array_v = with_array_alloca(builder, array_v, |ptr| { - // inside `success_block` we know `index1_v` and `index2_v` - // to be in bounds. - let elem1_addr = - unsafe { builder.build_in_bounds_gep(ptr, &[index1_v], "")? }; - let elem1_v = builder.build_load(elem1_addr, "")?; - let elem2_addr = - unsafe { builder.build_in_bounds_gep(ptr, &[index2_v], "")? }; - let elem2_v = builder.build_load(elem2_addr, "")?; - builder.build_store(elem1_addr, elem2_v)?; - builder.build_store(elem2_addr, elem1_v)?; - let ptr = builder - .build_bit_cast( - ptr, - array_v.get_type().ptr_type(Default::default()), - "", - )? - .into_pointer_value(); - builder.build_load(ptr, "") - })?; + // inside `success_block` we know `index1_v` and `index2_v` + // to be in bounds. + let index1_v = builder.build_int_add(index1_v, array_offset, "")?; + let index2_v = builder.build_int_add(index2_v, array_offset, "")?; + let elem1_addr = + unsafe { builder.build_in_bounds_gep(array_ptr, &[index1_v], "")? }; + let elem1_v = builder.build_load(elem1_addr, "")?; + let elem2_addr = + unsafe { builder.build_in_bounds_gep(array_ptr, &[index2_v], "")? }; + let elem2_v = builder.build_load(elem2_addr, "")?; + builder.build_store(elem1_addr, elem2_v)?; + builder.build_store(elem2_addr, elem1_v)?; let success_v = res_sum_ty.build_tag(builder, 1, vec![array_v])?; exit_rmb.write(ctx.builder(), [success_v.into()])?; builder.build_unconditional_branch(exit_block)?; @@ -517,7 +632,7 @@ pub fn emit_array_op<'c, H: HugrView>( let failure_block = ctx.build_positioned_new_block("", Some(success_block), |ctx, bb| { let builder = ctx.builder(); - let failure_v = res_sum_ty.build_tag(builder, 0, vec![array_v.into()])?; + let failure_v = res_sum_ty.build_tag(builder, 0, vec![array_v])?; exit_rmb.write(ctx.builder(), [failure_v.into()])?; builder.build_unconditional_branch(exit_block)?; Ok(bb) @@ -548,11 +663,10 @@ pub fn emit_array_op<'c, H: HugrView>( .try_into() .map_err(|_| anyhow!("ArrayOpDef::pop_left expects one argument"))?; let r = emit_pop_op( - builder, - &ts, - elem_ty.clone(), + ctx, + hugr_elem_ty.clone(), size, - array_v.into_array_value(), + array_v.into_struct_value(), true, )?; outputs.finish(ctx.builder(), [r]) @@ -562,29 +676,95 @@ pub fn emit_array_op<'c, H: HugrView>( .try_into() .map_err(|_| anyhow!("ArrayOpDef::pop_right expects one argument"))?; let r = emit_pop_op( - builder, - &ts, - elem_ty.clone(), + ctx, + hugr_elem_ty.clone(), size, - array_v.into_array_value(), + array_v.into_struct_value(), false, )?; outputs.finish(ctx.builder(), [r]) } - ArrayOpDef::discard_empty => Ok(()), + ArrayOpDef::discard_empty => { + let [array_v] = inputs + .try_into() + .map_err(|_| anyhow!("ArrayOpDef::discard_empty expects one argument"))?; + let (ptr, _) = decompose_array_fat_pointer(builder, array_v)?; + ccg.emit_free_array(ctx, ptr)?; + outputs.finish(ctx.builder(), []) + } _ => todo!(), } } -/// Helper function to emit the pop operations. -fn emit_pop_op<'c>( - builder: &Builder<'c>, - ts: &TypingSession<'c, '_>, +/// Emits an [ArrayClone] op. +pub fn emit_clone_op<'c, H: HugrView>( + ccg: &impl ArrayCodegen, + ctx: &mut EmitFuncContext<'c, '_, H>, + op: ArrayClone, + array_v: BasicValueEnum<'c>, +) -> Result<(BasicValueEnum<'c>, BasicValueEnum<'c>)> { + let elem_ty = ctx.llvm_type(&op.elem_ty)?; + let (array_ptr, array_offset) = decompose_array_fat_pointer(ctx.builder(), array_v)?; + let (other_ptr, other_array_v) = build_array_alloc(ctx, ccg, elem_ty, op.size)?; + let src_ptr = unsafe { + ctx.builder() + .build_in_bounds_gep(array_ptr, &[array_offset], "")? + }; + let length = usize_ty(&ctx.typing_session()).const_int(op.size, false); + let size_value = ctx + .builder() + .build_int_mul(length, elem_ty.size_of().unwrap(), "")?; + let is_volatile = ctx.iw_context().bool_type().const_zero(); + + let memcpy_intrinsic = Intrinsic::find("llvm.memcpy").unwrap(); + let memcpy = memcpy_intrinsic + .get_declaration( + ctx.get_current_module(), + &[ + other_ptr.get_type().into(), + src_ptr.get_type().into(), + size_value.get_type().into(), + ], + ) + .unwrap(); + ctx.builder().build_call( + memcpy, + &[ + other_ptr.into(), + src_ptr.into(), + size_value.into(), + is_volatile.into(), + ], + "", + )?; + Ok((array_v, other_array_v.into())) +} + +/// Emits an [ArrayDiscard] op. +pub fn emit_array_discard<'c, H: HugrView>( + ccg: &impl ArrayCodegen, + ctx: &mut EmitFuncContext<'c, '_, H>, + _op: ArrayDiscard, + array_v: BasicValueEnum<'c>, +) -> Result<()> { + let array_ptr = + ctx.builder() + .build_extract_value(array_v.into_struct_value(), 0, "array_ptr")?; + ccg.emit_free_array(ctx, array_ptr.into_pointer_value())?; + Ok(()) +} + +/// Emits the [ArrayOpDef::pop_left] and [ArrayOpDef::pop_right] operations. +fn emit_pop_op<'c, H: HugrView>( + ctx: &mut EmitFuncContext<'c, '_, H>, elem_ty: HugrType, size: u64, - array_v: ArrayValue<'c>, + array_v: StructValue<'c>, pop_left: bool, ) -> Result> { + let ts = ctx.typing_session(); + let builder = ctx.builder(); + let (array_ptr, array_offset) = decompose_array_fat_pointer(builder, array_v.into())?; let ret_ty = ts.llvm_sum_type(option_type(vec![ elem_ty.clone(), array_type(size.saturating_add_signed(-1), elem_ty), @@ -592,44 +772,43 @@ fn emit_pop_op<'c>( if size == 0 { return Ok(ret_ty.build_tag(builder, 0, vec![])?.into()); } - let ctx = builder.get_insert_block().unwrap().get_context(); - let (elem_v, array_v) = with_array_alloca(builder, array_v, |ptr| { - let (elem_ptr, ptr) = { - if pop_left { - let rest_ptr = - unsafe { builder.build_gep(ptr, &[ctx.i32_type().const_int(1, false)], "") }?; - (ptr, rest_ptr) - } else { - let elem_ptr = unsafe { - builder.build_gep(ptr, &[ctx.i32_type().const_int(size - 1, false)], "") - }?; - (elem_ptr, ptr) - } - }; - let elem_v = builder.build_load(elem_ptr, "")?; - let new_array_ty = array_v - .get_type() - .get_element_type() - .array_type(size as u32 - 1); - let ptr = builder - .build_bit_cast(ptr, new_array_ty.ptr_type(Default::default()), "")? - .into_pointer_value(); - let array_v = builder.build_load(ptr, "")?; - Ok((elem_v, array_v)) - })?; - Ok(ret_ty.build_tag(builder, 1, vec![elem_v, array_v])?.into()) + let (elem_ptr, new_array_offset) = { + if pop_left { + let new_array_offset = builder.build_int_add( + array_offset, + usize_ty(&ts).const_int(1, false), + "new_offset", + )?; + let elem_ptr = unsafe { builder.build_in_bounds_gep(array_ptr, &[array_offset], "") }?; + (elem_ptr, new_array_offset) + } else { + let idx = builder.build_int_add( + array_offset, + usize_ty(&ts).const_int(size - 1, false), + "", + )?; + let elem_ptr = unsafe { builder.build_in_bounds_gep(array_ptr, &[idx], "") }?; + (elem_ptr, array_offset) + } + }; + let elem_v = builder.build_load(elem_ptr, "")?; + let new_array_v = build_array_fat_pointer(ctx, array_ptr, new_array_offset)?; + + Ok(ret_ty + .build_tag(ctx.builder(), 1, vec![elem_v, new_array_v.into()])? + .into()) } /// Emits an [ArrayRepeat] op. pub fn emit_repeat_op<'c, H: HugrView>( + ccg: &impl ArrayCodegen, ctx: &mut EmitFuncContext<'c, '_, H>, op: ArrayRepeat, func: BasicValueEnum<'c>, ) -> Result> { - let builder = ctx.builder(); - let array_len = ctx.iw_context().i32_type().const_int(op.size, false); - let array_ty = ctx.llvm_type(&op.elem_ty)?.array_type(op.size as u32); - let (ptr, array_ptr) = build_array_alloca(builder, array_ty.get_undef())?; + let elem_ty = ctx.llvm_type(&op.elem_ty)?; + let (ptr, array_v) = build_array_alloc(ctx, ccg, elem_ty, op.size)?; + let array_len = usize_ty(&ctx.typing_session()).const_int(op.size, false); build_loop(ctx, array_len, |ctx, idx| { let builder = ctx.builder(); let func_ptr = CallableValue::try_from(func.into_pointer_value()) @@ -643,30 +822,32 @@ pub fn emit_repeat_op<'c, H: HugrView>( builder.build_store(elem_addr, v)?; Ok(()) })?; - - let builder = ctx.builder(); - let array_v = builder.build_load(array_ptr, "")?; - Ok(array_v) + Ok(array_v.into()) } /// Emits an [ArrayScan] op. /// /// Returns the resulting array and the final values of the accumulators. pub fn emit_scan_op<'c, H: HugrView>( + ccg: &impl ArrayCodegen, ctx: &mut EmitFuncContext<'c, '_, H>, op: ArrayScan, - src_array: BasicValueEnum<'c>, + src_array_v: StructValue<'c>, func: BasicValueEnum<'c>, initial_accs: &[BasicValueEnum<'c>], ) -> Result<(BasicValueEnum<'c>, Vec>)> { + let (src_ptr, src_offset) = decompose_array_fat_pointer(ctx.builder(), src_array_v.into())?; + let tgt_elem_ty = ctx.llvm_type(&op.tgt_ty)?; + // TODO: If `sizeof(op.src_ty) >= sizeof(op.tgt_ty)`, we could reuse the memory + // from `src` instead of allocating a fresh array + let (tgt_ptr, tgt_array_v) = build_array_alloc(ctx, ccg, tgt_elem_ty, op.size)?; + let array_len = usize_ty(&ctx.typing_session()).const_int(op.size, false); + let acc_tys: Vec<_> = op + .acc_tys + .iter() + .map(|ty| ctx.llvm_type(ty)) + .try_collect()?; let builder = ctx.builder(); - let ts = ctx.typing_session(); - let array_len = ctx.iw_context().i32_type().const_int(op.size, false); - let tgt_array_ty = ts.llvm_type(&op.tgt_ty)?.array_type(op.size as u32); - let (src_ptr, _) = build_array_alloca(builder, src_array.into_array_value())?; - let (tgt_ptr, tgt_array_ptr) = build_array_alloca(builder, tgt_array_ty.get_undef())?; - - let acc_tys: Vec<_> = op.acc_tys.iter().map(|ty| ts.llvm_type(ty)).try_collect()?; let acc_ptrs: Vec<_> = acc_tys .iter() .map(|ty| builder.build_alloca(*ty, "")) @@ -679,7 +860,8 @@ pub fn emit_scan_op<'c, H: HugrView>( let builder = ctx.builder(); let func_ptr = CallableValue::try_from(func.into_pointer_value()) .map_err(|_| anyhow!("ArrayOpDef::scan expects a function pointer"))?; - let src_elem_addr = unsafe { builder.build_in_bounds_gep(src_ptr, &[idx], "")? }; + let src_idx = builder.build_int_add(idx, src_offset, "")?; + let src_elem_addr = unsafe { builder.build_in_bounds_gep(src_ptr, &[src_idx], "")? }; let src_elem = builder.build_load(src_elem_addr, "")?; let mut args = vec![src_elem.into()]; for ptr in acc_ptrs.iter() { @@ -695,13 +877,13 @@ pub fn emit_scan_op<'c, H: HugrView>( Ok(()) })?; + ccg.emit_free_array(ctx, src_ptr)?; let builder = ctx.builder(); - let tgt_array_v = builder.build_load(tgt_array_ptr, "")?; let final_accs = acc_ptrs .into_iter() .map(|ptr| builder.build_load(ptr, "")) .try_collect()?; - Ok((tgt_array_v, final_accs)) + Ok((tgt_array_v.into(), final_accs)) } #[cfg(test)] @@ -709,6 +891,7 @@ mod test { use hugr_core::builder::Container as _; use hugr_core::extension::prelude::either_type; use hugr_core::ops::Tag; + use hugr_core::std_extensions::collections::array::op_builder::build_all_array_ops; use hugr_core::std_extensions::collections::array::{self, array_type, ArrayRepeat, ArrayScan}; use hugr_core::std_extensions::STD_REG; use hugr_core::types::Type; @@ -724,7 +907,6 @@ mod test { int_ops::{self}, int_types::{self, int_type, ConstInt}, }, - collections::array::ArrayOpBuilder, logic, }, type_row, @@ -737,66 +919,15 @@ mod test { check_emission, emit::test::SimpleHugrConfig, test::{exec_ctx, llvm_ctx, TestContext}, - utils::{IntOpBuilder, LogicOpBuilder}, + utils::{ArrayOpBuilder, IntOpBuilder, LogicOpBuilder}, }; - /// Build all array ops - /// Copied from `hugr_core::std_extensions::collections::array::builder::test` - fn all_array_ops(mut builder: B) -> B { - let us0 = builder.add_load_value(ConstUsize::new(0)); - let us1 = builder.add_load_value(ConstUsize::new(1)); - let us2 = builder.add_load_value(ConstUsize::new(2)); - let arr = builder.add_new_array(usize_t(), [us1, us2]).unwrap(); - let [arr] = { - let r = builder.add_array_swap(usize_t(), 2, arr, us0, us1).unwrap(); - let res_sum_ty = { - let array_type = array_type(2, usize_t()); - either_type(array_type.clone(), array_type) - }; - builder.build_unwrap_sum(1, res_sum_ty, r).unwrap() - }; - - let [elem_0] = { - let r = builder.add_array_get(usize_t(), 2, arr, us0).unwrap(); - builder - .build_unwrap_sum(1, option_type(usize_t()), r) - .unwrap() - }; - - let [_elem_1, arr] = { - let r = builder - .add_array_set(usize_t(), 2, arr, us1, elem_0) - .unwrap(); - let res_sum_ty = { - let row = vec![usize_t(), array_type(2, usize_t())]; - either_type(row.clone(), row) - }; - builder.build_unwrap_sum(1, res_sum_ty, r).unwrap() - }; - - let [_elem_left, arr] = { - let r = builder.add_array_pop_left(usize_t(), 2, arr).unwrap(); - builder - .build_unwrap_sum(1, option_type(vec![usize_t(), array_type(1, usize_t())]), r) - .unwrap() - }; - let [_elem_right, arr] = { - let r = builder.add_array_pop_right(usize_t(), 1, arr).unwrap(); - builder - .build_unwrap_sum(1, option_type(vec![usize_t(), array_type(0, usize_t())]), r) - .unwrap() - }; - - builder.add_array_discard_empty(usize_t(), arr).unwrap(); - builder - } - #[rstest] fn emit_all_ops(mut llvm_ctx: TestContext) { let hugr = SimpleHugrConfig::new() .with_extensions(STD_REG.to_owned()) .finish(|mut builder| { - all_array_ops(builder.dfg_builder_endo([]).unwrap()) + build_all_array_ops(builder.dfg_builder_endo([]).unwrap()) .finish_sub_container() .unwrap(); builder.finish_sub_container().unwrap() @@ -816,7 +947,28 @@ mod test { let us1 = builder.add_load_value(ConstUsize::new(1)); let us2 = builder.add_load_value(ConstUsize::new(2)); let arr = builder.add_new_array(usize_t(), [us1, us2]).unwrap(); - builder.add_array_get(usize_t(), 2, arr, us1).unwrap(); + let (_, arr) = builder.add_array_get(usize_t(), 2, arr, us1).unwrap(); + builder.add_array_discard(usize_t(), 2, arr).unwrap(); + builder.finish_with_outputs([]).unwrap() + }); + llvm_ctx.add_extensions(|cge| { + cge.add_default_prelude_extensions() + .add_default_array_extensions() + }); + check_emission!(hugr, llvm_ctx); + } + + #[rstest] + fn emit_clone(mut llvm_ctx: TestContext) { + let hugr = SimpleHugrConfig::new() + .with_extensions(STD_REG.to_owned()) + .finish(|mut builder| { + let us1 = builder.add_load_value(ConstUsize::new(1)); + let us2 = builder.add_load_value(ConstUsize::new(2)); + let arr = builder.add_new_array(usize_t(), [us1, us2]).unwrap(); + let (arr1, arr2) = builder.add_array_clone(usize_t(), 2, arr).unwrap(); + builder.add_array_discard(usize_t(), 2, arr1).unwrap(); + builder.add_array_discard(usize_t(), 2, arr2).unwrap(); builder.finish_with_outputs([]).unwrap() }); llvm_ctx.add_extensions(|cge| { @@ -872,7 +1024,8 @@ mod test { let us2 = builder.add_load_value(ConstUsize::new(2)); let arr = builder.add_new_array(usize_t(), [us1, us2]).unwrap(); let i = builder.add_load_value(ConstUsize::new(index)); - let get_r = builder.add_array_get(usize_t(), 2, arr, i).unwrap(); + let (get_r, arr) = builder.add_array_get(usize_t(), 2, arr, i).unwrap(); + builder.add_array_discard(usize_t(), 2, arr).unwrap(); let r = { let ot = option_type(usize_t()); let variants = (0..ot.num_variants()) @@ -963,23 +1116,20 @@ mod test { builder.add_load_value(ConstInt::new_u(3, expected_arr[0]).unwrap()); let expected_arr_1 = builder.add_load_value(ConstInt::new_u(3, expected_arr[1]).unwrap()); - let [arr_0] = { - let r = builder.add_array_get(int_ty.clone(), 2, arr, us0).unwrap(); - builder - .build_unwrap_sum(1, option_type(int_ty.clone()), r) - .unwrap() - }; - let [arr_1] = { - let r = builder.add_array_get(int_ty.clone(), 2, arr, us1).unwrap(); - builder - .build_unwrap_sum(1, option_type(int_ty.clone()), r) - .unwrap() - }; + let (r, arr) = builder.add_array_get(int_ty.clone(), 2, arr, us0).unwrap(); + let [arr_0] = builder + .build_unwrap_sum(1, option_type(int_ty.clone()), r) + .unwrap(); + let (r, arr) = builder.add_array_get(int_ty.clone(), 2, arr, us1).unwrap(); + let [arr_1] = builder + .build_unwrap_sum(1, option_type(int_ty.clone()), r) + .unwrap(); let elem_eq = builder.add_ieq(3, elem, expected_elem).unwrap(); let arr_0_eq = builder.add_ieq(3, arr_0, expected_arr_0).unwrap(); let arr_1_eq = builder.add_ieq(3, arr_1, expected_arr_1).unwrap(); let r = builder.add_and(elem_eq, arr_0_eq).unwrap(); let r = builder.add_and(r, arr_1_eq).unwrap(); + builder.add_array_discard(int_ty.clone(), 2, arr).unwrap(); builder.finish_with_outputs([r]).unwrap(); } builder.finish_sub_container().unwrap().out_wire(0) @@ -1074,18 +1224,14 @@ mod test { } conditional.finish_sub_container().unwrap().outputs_arr() }; - let elem_0 = { - let r = builder.add_array_get(int_ty.clone(), 2, arr, us0).unwrap(); - builder - .build_unwrap_sum::<1>(1, option_type(int_ty.clone()), r) - .unwrap()[0] - }; - let elem_1 = { - let r = builder.add_array_get(int_ty.clone(), 2, arr, us1).unwrap(); - builder - .build_unwrap_sum::<1>(1, option_type(int_ty), r) - .unwrap()[0] - }; + let (r, arr) = builder.add_array_get(int_ty.clone(), 2, arr, us0).unwrap(); + let elem_0 = builder + .build_unwrap_sum::<1>(1, option_type(int_ty.clone()), r) + .unwrap()[0]; + let (r, arr) = builder.add_array_get(int_ty.clone(), 2, arr, us1).unwrap(); + let elem_1 = builder + .build_unwrap_sum::<1>(1, option_type(int_ty.clone()), r) + .unwrap()[0]; let expected_elem_0 = builder.add_load_value(ConstInt::new_u(3, expected_arr[0]).unwrap()); let elem_0_ok = builder.add_ieq(3, elem_0, expected_elem_0).unwrap(); @@ -1110,6 +1256,7 @@ mod test { .unwrap(); conditional.finish_sub_container().unwrap().out_wire(0) }; + builder.add_array_discard(int_ty.clone(), 2, arr).unwrap(); builder.finish_with_outputs([r]).unwrap() }); exec_ctx.add_extensions(|cge| { @@ -1122,20 +1269,71 @@ mod test { } #[rstest] - #[case(true, 0, 0)] - #[case(true, 1, 1)] - #[case(true, 2, 3)] - #[case(true, 3, 7)] - #[case(false, 0, 0)] - #[case(false, 1, 4)] - #[case(false, 2, 6)] - #[case(false, 3, 7)] - fn exec_pop( - mut exec_ctx: TestContext, - #[case] from_left: bool, - #[case] num: usize, - #[case] expected: u64, - ) { + #[case(0, 5)] + #[case(1, 5)] + fn exec_clone(mut exec_ctx: TestContext, #[case] index: u64, #[case] new_v: u64) { + // We build a HUGR that: + // - Creates an array: [1, 2] + // - Clones the array + // - Mutates the original at the given index + // - Returns the unchanged element of the cloned array + + let int_ty = int_type(3); + let arr_ty = array_type(2, int_ty.clone()); + let hugr = SimpleHugrConfig::new() + .with_outs(int_ty.clone()) + .with_extensions(exec_registry()) + .finish(|mut builder| { + let idx = builder.add_load_value(ConstUsize::new(index)); + let i1 = builder.add_load_value(ConstInt::new_u(3, 1).unwrap()); + let i2 = builder.add_load_value(ConstInt::new_u(3, 2).unwrap()); + let inew = builder.add_load_value(ConstInt::new_u(3, new_v).unwrap()); + let arr = builder.add_new_array(int_ty.clone(), [i1, i2]).unwrap(); + + let (arr, arr_clone) = builder.add_array_clone(int_ty.clone(), 2, arr).unwrap(); + let r = builder + .add_array_set(int_ty.clone(), 2, arr, idx, inew) + .unwrap(); + let [_, arr] = builder + .build_unwrap_sum( + 1, + either_type( + vec![int_ty.clone(), arr_ty.clone()], + vec![int_ty.clone(), arr_ty.clone()], + ), + r, + ) + .unwrap(); + let (r, arr_clone) = builder + .add_array_get(int_ty.clone(), 2, arr_clone, idx) + .unwrap(); + let [elem] = builder + .build_unwrap_sum(1, option_type(int_ty.clone()), r) + .unwrap(); + builder.add_array_discard(int_ty.clone(), 2, arr).unwrap(); + builder + .add_array_discard(int_ty.clone(), 2, arr_clone) + .unwrap(); + builder.finish_with_outputs([elem]).unwrap() + }); + exec_ctx.add_extensions(|cge| { + cge.add_default_prelude_extensions() + .add_default_array_extensions() + .add_default_int_extensions() + .add_logic_extensions() + }); + assert_eq!([1, 2][index as usize], exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(&[], 0)] + #[case(&[true], 1)] + #[case(&[false], 4)] + #[case(&[true, true], 3)] + #[case(&[false, false], 6)] + #[case(&[true, false, true], 7)] + #[case(&[false, true, false], 7)] + fn exec_pop(mut exec_ctx: TestContext, #[case] from_left: &[bool], #[case] expected: u64) { // We build a HUGR that: // - Creates an array: [1,2,4] // - Pops `num` elements from the left or right @@ -1155,9 +1353,9 @@ mod test { let mut arr = builder .add_new_array(int_ty.clone(), new_array_args) .unwrap(); - for i in 0..num { + for (i, left) in from_left.iter().enumerate() { let array_size = (array_contents.len() - i) as u64; - let pop_res = if from_left { + let pop_res = if *left { builder .add_array_pop_left(int_ty.clone(), array_size, arr) .unwrap() @@ -1179,6 +1377,13 @@ mod test { arr = new_arr; r = builder.add_iadd(6, r, elem).unwrap(); } + builder + .add_array_discard( + int_ty.clone(), + (array_contents.len() - from_left.len()) as u64, + arr, + ) + .unwrap(); builder.finish_with_outputs([r]).unwrap() }); exec_ctx.add_extensions(|cge| { @@ -1223,12 +1428,15 @@ mod test { .unwrap() .out_wire(0); let idx_v = builder.add_load_value(ConstUsize::new(idx)); - let get_res = builder + let (get_res, arr) = builder .add_array_get(int_ty.clone(), size, arr, idx_v) .unwrap(); let [elem] = builder .build_unwrap_sum(1, option_type(vec![int_ty.clone()]), get_res) .unwrap(); + builder + .add_array_discard(int_ty.clone(), size, arr) + .unwrap(); builder.finish_with_outputs([elem]).unwrap() }); exec_ctx.add_extensions(|cge| { @@ -1297,6 +1505,9 @@ mod test { arr = new_arr; r = builder.add_iadd(6, r, elem).unwrap(); } + builder + .add_array_discard_empty(int_ty.clone(), arr) + .unwrap(); builder.finish_with_outputs([r]).unwrap() }); exec_ctx.add_extensions(|cge| { @@ -1348,10 +1559,11 @@ mod test { let func_v = builder.load_func(func_id.handle(), &[]).unwrap(); let scan = ArrayScan::new(int_ty.clone(), Type::UNIT, vec![int_ty.clone()], size); let zero = builder.add_load_value(ConstInt::new_u(6, 0).unwrap()); - let sum = builder + let [arr, sum] = builder .add_dataflow_op(scan, [arr, func_v, zero]) .unwrap() - .out_wire(1); + .outputs_arr(); + builder.add_array_discard(Type::UNIT, size, arr).unwrap(); builder.finish_with_outputs([sum]).unwrap() }); exec_ctx.add_extensions(|cge| { diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@llvm14.snap index bc9aa19c6c..3f4d8cecb2 100644 --- a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@llvm14.snap +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@llvm14.snap @@ -21,221 +21,235 @@ alloca_block: br label %entry_block entry_block: ; preds = %alloca_block - %0 = insertvalue [2 x i64] undef, i64 1, 0 - %1 = insertvalue [2 x i64] %0, i64 2, 1 - %2 = icmp ult i64 0, 2 - %3 = icmp ult i64 1, 2 - %4 = and i1 %2, %3 - br i1 %4, label %7, label %5 - -5: ; preds = %entry_block - %6 = insertvalue { i1, [2 x i64] } { i1 false, [2 x i64] poison }, [2 x i64] %1, 1 - br label %17 - -7: ; preds = %entry_block - %8 = alloca i64, i32 2, align 8 - %9 = bitcast i64* %8 to [2 x i64]* - store [2 x i64] %1, [2 x i64]* %9, align 4 - %10 = getelementptr inbounds i64, i64* %8, i64 0 - %11 = load i64, i64* %10, align 4 - %12 = getelementptr inbounds i64, i64* %8, i64 1 - %13 = load i64, i64* %12, align 4 - store i64 %13, i64* %10, align 4 - store i64 %11, i64* %12, align 4 - %14 = bitcast i64* %8 to [2 x i64]* - %15 = load [2 x i64], [2 x i64]* %14, align 4 - %16 = insertvalue { i1, [2 x i64] } { i1 true, [2 x i64] poison }, [2 x i64] %15, 1 - br label %17 - -17: ; preds = %5, %7 - %"0.0" = phi { i1, [2 x i64] } [ %16, %7 ], [ %6, %5 ] - %18 = extractvalue { i1, [2 x i64] } %"0.0", 0 - switch i1 %18, label %19 [ - i1 true, label %21 + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i64 0 + store i64 1, i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i64 1 + store i64 2, i64* %5, align 4 + %array_ptr = extractvalue { i64*, i64 } %3, 0 + %array_offset = extractvalue { i64*, i64 } %3, 1 + %6 = icmp ult i64 0, 2 + %7 = icmp ult i64 1, 2 + %8 = and i1 %6, %7 + br i1 %8, label %11, label %9 + +9: ; preds = %entry_block + %10 = insertvalue { i1, { i64*, i64 } } { i1 false, { i64*, i64 } poison }, { i64*, i64 } %3, 1 + br label %19 + +11: ; preds = %entry_block + %12 = add i64 0, %array_offset + %13 = add i64 1, %array_offset + %14 = getelementptr inbounds i64, i64* %array_ptr, i64 %12 + %15 = load i64, i64* %14, align 4 + %16 = getelementptr inbounds i64, i64* %array_ptr, i64 %13 + %17 = load i64, i64* %16, align 4 + store i64 %17, i64* %14, align 4 + store i64 %15, i64* %16, align 4 + %18 = insertvalue { i1, { i64*, i64 } } { i1 true, { i64*, i64 } poison }, { i64*, i64 } %3, 1 + br label %19 + +19: ; preds = %9, %11 + %"0.0" = phi { i1, { i64*, i64 } } [ %18, %11 ], [ %10, %9 ] + %20 = extractvalue { i1, { i64*, i64 } } %"0.0", 0 + switch i1 %20, label %21 [ + i1 true, label %23 ] -19: ; preds = %17 - %20 = extractvalue { i1, [2 x i64] } %"0.0", 1 +21: ; preds = %19 + %22 = extractvalue { i1, { i64*, i64 } } %"0.0", 1 br label %cond_16_case_0 -21: ; preds = %17 - %22 = extractvalue { i1, [2 x i64] } %"0.0", 1 +23: ; preds = %19 + %24 = extractvalue { i1, { i64*, i64 } } %"0.0", 1 br label %cond_16_case_1 -cond_16_case_0: ; preds = %19 - %23 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, 0 - %24 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, 1 - %25 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %23, i8* %24) +cond_16_case_0: ; preds = %21 + %25 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, 0 + %26 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, 1 + %27 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %25, i8* %26) call void @abort() br label %cond_exit_16 -cond_16_case_1: ; preds = %21 +cond_16_case_1: ; preds = %23 br label %cond_exit_16 cond_exit_16: ; preds = %cond_16_case_1, %cond_16_case_0 - %"08.0" = phi [2 x i64] [ zeroinitializer, %cond_16_case_0 ], [ %22, %cond_16_case_1 ] - %26 = icmp ult i64 0, 2 - br i1 %26, label %28, label %27 - -27: ; preds = %cond_exit_16 - br label %34 - -28: ; preds = %cond_exit_16 - %29 = alloca i64, i32 2, align 8 - %30 = bitcast i64* %29 to [2 x i64]* - store [2 x i64] %"08.0", [2 x i64]* %30, align 4 - %31 = getelementptr inbounds i64, i64* %29, i64 0 - %32 = load i64, i64* %31, align 4 - %33 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %32, 1 - br label %34 - -34: ; preds = %27, %28 - %"020.0" = phi { i1, i64 } [ %33, %28 ], [ { i1 false, i64 poison }, %27 ] - %35 = extractvalue { i1, i64 } %"020.0", 0 - switch i1 %35, label %36 [ - i1 true, label %37 + %"08.0" = phi { i64*, i64 } [ zeroinitializer, %cond_16_case_0 ], [ %24, %cond_16_case_1 ] + %array_ptr20 = extractvalue { i64*, i64 } %"08.0", 0 + %array_offset21 = extractvalue { i64*, i64 } %"08.0", 1 + %28 = icmp ult i64 0, 2 + br i1 %28, label %30, label %29 + +29: ; preds = %cond_exit_16 + br label %35 + +30: ; preds = %cond_exit_16 + %31 = add i64 0, %array_offset21 + %32 = getelementptr inbounds i64, i64* %array_ptr20, i64 %31 + %33 = load i64, i64* %32, align 4 + %34 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %33, 1 + br label %35 + +35: ; preds = %29, %30 + %"022.0" = phi { i1, i64 } [ %34, %30 ], [ { i1 false, i64 poison }, %29 ] + %36 = extractvalue { i1, i64 } %"022.0", 0 + switch i1 %36, label %37 [ + i1 true, label %38 ] -36: ; preds = %34 +37: ; preds = %35 br label %cond_28_case_0 -37: ; preds = %34 - %38 = extractvalue { i1, i64 } %"020.0", 1 +38: ; preds = %35 + %39 = extractvalue { i1, i64 } %"022.0", 1 br label %cond_28_case_1 -cond_28_case_0: ; preds = %36 - %39 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @1, i32 0, i32 0) }, 0 - %40 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @1, i32 0, i32 0) }, 1 - %41 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.1, i32 0, i32 0), i32 %39, i8* %40) +cond_28_case_0: ; preds = %37 + %40 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @1, i32 0, i32 0) }, 0 + %41 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @1, i32 0, i32 0) }, 1 + %42 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.1, i32 0, i32 0), i32 %40, i8* %41) call void @abort() br label %cond_exit_28 -cond_28_case_1: ; preds = %37 +cond_28_case_1: ; preds = %38 br label %cond_exit_28 cond_exit_28: ; preds = %cond_28_case_1, %cond_28_case_0 - %"023.0" = phi i64 [ 0, %cond_28_case_0 ], [ %38, %cond_28_case_1 ] - %42 = icmp ult i64 1, 2 - br i1 %42, label %46, label %43 - -43: ; preds = %cond_exit_28 - %44 = insertvalue { i1, i64, [2 x i64] } { i1 false, i64 poison, [2 x i64] poison }, i64 %"023.0", 1 - %45 = insertvalue { i1, i64, [2 x i64] } %44, [2 x i64] %"08.0", 2 - br label %55 - -46: ; preds = %cond_exit_28 - %47 = alloca i64, i32 2, align 8 - %48 = bitcast i64* %47 to [2 x i64]* - store [2 x i64] %"08.0", [2 x i64]* %48, align 4 - %49 = getelementptr inbounds i64, i64* %47, i64 1 + %"026.0" = phi i64 [ 0, %cond_28_case_0 ], [ %39, %cond_28_case_1 ] + %array_ptr36 = extractvalue { i64*, i64 } %"08.0", 0 + %array_offset37 = extractvalue { i64*, i64 } %"08.0", 1 + %43 = icmp ult i64 1, 2 + br i1 %43, label %47, label %44 + +44: ; preds = %cond_exit_28 + %45 = insertvalue { i1, { i64*, i64 }, i64 } { i1 false, { i64*, i64 } poison, i64 poison }, i64 %"026.0", 2 + %46 = insertvalue { i1, { i64*, i64 }, i64 } %45, { i64*, i64 } %"08.0", 1 + br label %53 + +47: ; preds = %cond_exit_28 + %48 = add i64 1, %array_offset37 + %49 = getelementptr inbounds i64, i64* %array_ptr36, i64 %48 %50 = load i64, i64* %49, align 4 - store i64 %"023.0", i64* %49, align 4 - %51 = bitcast i64* %47 to [2 x i64]* - %52 = load [2 x i64], [2 x i64]* %51, align 4 - %53 = insertvalue { i1, i64, [2 x i64] } { i1 true, i64 poison, [2 x i64] poison }, i64 %50, 1 - %54 = insertvalue { i1, i64, [2 x i64] } %53, [2 x i64] %52, 2 - br label %55 - -55: ; preds = %43, %46 - %"033.0" = phi { i1, i64, [2 x i64] } [ %54, %46 ], [ %45, %43 ] - %56 = extractvalue { i1, i64, [2 x i64] } %"033.0", 0 - switch i1 %56, label %57 [ - i1 true, label %60 + store i64 %"026.0", i64* %49, align 4 + %51 = insertvalue { i1, { i64*, i64 }, i64 } { i1 true, { i64*, i64 } poison, i64 poison }, i64 %50, 2 + %52 = insertvalue { i1, { i64*, i64 }, i64 } %51, { i64*, i64 } %"08.0", 1 + br label %53 + +53: ; preds = %44, %47 + %"038.0" = phi { i1, { i64*, i64 }, i64 } [ %52, %47 ], [ %46, %44 ] + %54 = extractvalue { i1, { i64*, i64 }, i64 } %"038.0", 0 + switch i1 %54, label %55 [ + i1 true, label %58 ] -57: ; preds = %55 - %58 = extractvalue { i1, i64, [2 x i64] } %"033.0", 1 - %59 = extractvalue { i1, i64, [2 x i64] } %"033.0", 2 - br label %cond_40_case_0 +55: ; preds = %53 + %56 = extractvalue { i1, { i64*, i64 }, i64 } %"038.0", 2 + %57 = extractvalue { i1, { i64*, i64 }, i64 } %"038.0", 1 + br label %cond_39_case_0 -60: ; preds = %55 - %61 = extractvalue { i1, i64, [2 x i64] } %"033.0", 1 - %62 = extractvalue { i1, i64, [2 x i64] } %"033.0", 2 - br label %cond_40_case_1 +58: ; preds = %53 + %59 = extractvalue { i1, { i64*, i64 }, i64 } %"038.0", 2 + %60 = extractvalue { i1, { i64*, i64 }, i64 } %"038.0", 1 + br label %cond_39_case_1 -cond_40_case_0: ; preds = %57 - %63 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @2, i32 0, i32 0) }, 0 - %64 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @2, i32 0, i32 0) }, 1 - %65 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.2, i32 0, i32 0), i32 %63, i8* %64) +cond_39_case_0: ; preds = %55 + %61 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @2, i32 0, i32 0) }, 0 + %62 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @2, i32 0, i32 0) }, 1 + %63 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.2, i32 0, i32 0), i32 %61, i8* %62) call void @abort() - br label %cond_exit_40 - -cond_40_case_1: ; preds = %60 - br label %cond_exit_40 - -cond_exit_40: ; preds = %cond_40_case_1, %cond_40_case_0 - %"036.0" = phi i64 [ 0, %cond_40_case_0 ], [ %61, %cond_40_case_1 ] - %"1.0" = phi [2 x i64] [ zeroinitializer, %cond_40_case_0 ], [ %62, %cond_40_case_1 ] - %66 = alloca i64, i32 2, align 8 - %67 = bitcast i64* %66 to [2 x i64]* - store [2 x i64] %"1.0", [2 x i64]* %67, align 4 - %68 = getelementptr i64, i64* %66, i32 1 - %69 = load i64, i64* %66, align 4 - %70 = bitcast i64* %68 to [1 x i64]* - %71 = load [1 x i64], [1 x i64]* %70, align 4 - %72 = insertvalue { i1, i64, [1 x i64] } { i1 true, i64 poison, [1 x i64] poison }, i64 %69, 1 - %73 = insertvalue { i1, i64, [1 x i64] } %72, [1 x i64] %71, 2 - %74 = extractvalue { i1, i64, [1 x i64] } %73, 0 - switch i1 %74, label %75 [ - i1 true, label %76 + br label %cond_exit_39 + +cond_39_case_1: ; preds = %58 + br label %cond_exit_39 + +cond_exit_39: ; preds = %cond_39_case_1, %cond_39_case_0 + %"041.0" = phi i64 [ 0, %cond_39_case_0 ], [ %59, %cond_39_case_1 ] + %"142.0" = phi { i64*, i64 } [ zeroinitializer, %cond_39_case_0 ], [ %60, %cond_39_case_1 ] + %array_ptr61 = extractvalue { i64*, i64 } %"142.0", 0 + %array_offset62 = extractvalue { i64*, i64 } %"142.0", 1 + %new_offset = add i64 %array_offset62, 1 + %64 = getelementptr inbounds i64, i64* %array_ptr61, i64 %array_offset62 + %65 = load i64, i64* %64, align 4 + %66 = insertvalue { i64*, i64 } poison, i64* %array_ptr61, 0 + %67 = insertvalue { i64*, i64 } %66, i64 %new_offset, 1 + %68 = insertvalue { i1, { i64*, i64 }, i64 } { i1 true, { i64*, i64 } poison, i64 poison }, i64 %65, 2 + %69 = insertvalue { i1, { i64*, i64 }, i64 } %68, { i64*, i64 } %67, 1 + %70 = extractvalue { i1, { i64*, i64 }, i64 } %69, 0 + switch i1 %70, label %71 [ + i1 true, label %72 ] -75: ; preds = %cond_exit_40 - br label %cond_51_case_0 +71: ; preds = %cond_exit_39 + br label %cond_50_case_0 -76: ; preds = %cond_exit_40 - %77 = extractvalue { i1, i64, [1 x i64] } %73, 1 - %78 = extractvalue { i1, i64, [1 x i64] } %73, 2 - br label %cond_51_case_1 +72: ; preds = %cond_exit_39 + %73 = extractvalue { i1, { i64*, i64 }, i64 } %69, 2 + %74 = extractvalue { i1, { i64*, i64 }, i64 } %69, 1 + br label %cond_50_case_1 -cond_51_case_0: ; preds = %75 - %79 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @3, i32 0, i32 0) }, 0 - %80 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @3, i32 0, i32 0) }, 1 - %81 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.3, i32 0, i32 0), i32 %79, i8* %80) +cond_50_case_0: ; preds = %71 + %75 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @3, i32 0, i32 0) }, 0 + %76 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @3, i32 0, i32 0) }, 1 + %77 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.3, i32 0, i32 0), i32 %75, i8* %76) call void @abort() - br label %cond_exit_51 - -cond_51_case_1: ; preds = %76 - br label %cond_exit_51 - -cond_exit_51: ; preds = %cond_51_case_1, %cond_51_case_0 - %"056.0" = phi i64 [ 0, %cond_51_case_0 ], [ %77, %cond_51_case_1 ] - %"157.0" = phi [1 x i64] [ zeroinitializer, %cond_51_case_0 ], [ %78, %cond_51_case_1 ] - %82 = alloca i64, align 8 - %83 = bitcast i64* %82 to [1 x i64]* - store [1 x i64] %"157.0", [1 x i64]* %83, align 4 - %84 = getelementptr i64, i64* %82, i32 0 - %85 = load i64, i64* %84, align 4 - %86 = bitcast i64* %82 to [0 x i64]* - %87 = load [0 x i64], [0 x i64]* %86, align 4 - %88 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %85, 1 - %89 = extractvalue { i1, i64 } %88, 0 - switch i1 %89, label %90 [ - i1 true, label %91 + br label %cond_exit_50 + +cond_50_case_1: ; preds = %72 + br label %cond_exit_50 + +cond_exit_50: ; preds = %cond_50_case_1, %cond_50_case_0 + %"064.0" = phi i64 [ 0, %cond_50_case_0 ], [ %73, %cond_50_case_1 ] + %"165.0" = phi { i64*, i64 } [ zeroinitializer, %cond_50_case_0 ], [ %74, %cond_50_case_1 ] + %array_ptr78 = extractvalue { i64*, i64 } %"165.0", 0 + %array_offset79 = extractvalue { i64*, i64 } %"165.0", 1 + %78 = add i64 %array_offset79, 0 + %79 = getelementptr inbounds i64, i64* %array_ptr78, i64 %78 + %80 = load i64, i64* %79, align 4 + %81 = insertvalue { i64*, i64 } poison, i64* %array_ptr78, 0 + %82 = insertvalue { i64*, i64 } %81, i64 %array_offset79, 1 + %83 = insertvalue { i1, { i64*, i64 }, i64 } { i1 true, { i64*, i64 } poison, i64 poison }, i64 %80, 2 + %84 = insertvalue { i1, { i64*, i64 }, i64 } %83, { i64*, i64 } %82, 1 + %85 = extractvalue { i1, { i64*, i64 }, i64 } %84, 0 + switch i1 %85, label %86 [ + i1 true, label %87 ] -90: ; preds = %cond_exit_51 - br label %cond_62_case_0 +86: ; preds = %cond_exit_50 + br label %cond_61_case_0 -91: ; preds = %cond_exit_51 - %92 = extractvalue { i1, i64 } %88, 1 - br label %cond_62_case_1 +87: ; preds = %cond_exit_50 + %88 = extractvalue { i1, { i64*, i64 }, i64 } %84, 2 + %89 = extractvalue { i1, { i64*, i64 }, i64 } %84, 1 + br label %cond_61_case_1 -cond_62_case_0: ; preds = %90 - %93 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @4, i32 0, i32 0) }, 0 - %94 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @4, i32 0, i32 0) }, 1 - %95 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.4, i32 0, i32 0), i32 %93, i8* %94) +cond_61_case_0: ; preds = %86 + %90 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @4, i32 0, i32 0) }, 0 + %91 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @4, i32 0, i32 0) }, 1 + %92 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.4, i32 0, i32 0), i32 %90, i8* %91) call void @abort() - br label %cond_exit_62 - -cond_62_case_1: ; preds = %91 - br label %cond_exit_62 - -cond_exit_62: ; preds = %cond_62_case_1, %cond_62_case_0 - %"071.0" = phi i64 [ 0, %cond_62_case_0 ], [ %92, %cond_62_case_1 ] + br label %cond_exit_61 + +cond_61_case_1: ; preds = %87 + br label %cond_exit_61 + +cond_exit_61: ; preds = %cond_61_case_1, %cond_61_case_0 + %"081.0" = phi i64 [ 0, %cond_61_case_0 ], [ %88, %cond_61_case_1 ] + %"182.0" = phi { i64*, i64 } [ zeroinitializer, %cond_61_case_0 ], [ %89, %cond_61_case_1 ] + %array_ptr95 = extractvalue { i64*, i64 } %"182.0", 0 + %array_offset96 = extractvalue { i64*, i64 } %"182.0", 1 + %93 = bitcast i64* %array_ptr95 to i8* + call void @free(i8* %93) ret void } +declare i8* @malloc(i64) + declare i32 @printf(i8*, ...) declare void @abort() + +declare void @free(i8*) diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@pre-mem2reg@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@pre-mem2reg@llvm14.snap index 9b294486d4..c4b46a02a8 100644 --- a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@pre-mem2reg@llvm14.snap +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_all_ops@pre-mem2reg@llvm14.snap @@ -20,67 +20,69 @@ define void @_hl.main.1() { alloca_block: %"12_0" = alloca i64, align 8 %"10_0" = alloca i64, align 8 - %"13_0" = alloca [2 x i64], align 8 + %"13_0" = alloca { i64*, i64 }, align 8 %"8_0" = alloca i64, align 8 - %"14_0" = alloca { i1, [2 x i64] }, align 8 - %"0" = alloca { i1, [2 x i64] }, align 8 - %"16_0" = alloca [2 x i64], align 8 - %"08" = alloca [2 x i64], align 8 - %"010" = alloca [2 x i64], align 8 + %"14_0" = alloca { i1, { i64*, i64 } }, align 8 + %"0" = alloca { i1, { i64*, i64 } }, align 8 + %"16_0" = alloca { i64*, i64 }, align 8 + %"08" = alloca { i64*, i64 }, align 8 + %"010" = alloca { i64*, i64 }, align 8 %"21_0" = alloca { i32, i8* }, align 8 - %"18_0" = alloca [2 x i64], align 8 - %"22_0" = alloca [2 x i64], align 8 - %"015" = alloca [2 x i64], align 8 - %"24_0" = alloca [2 x i64], align 8 + %"18_0" = alloca { i64*, i64 }, align 8 + %"22_0" = alloca { i64*, i64 }, align 8 + %"015" = alloca { i64*, i64 }, align 8 + %"24_0" = alloca { i64*, i64 }, align 8 %"26_0" = alloca { i1, i64 }, align 8 - %"020" = alloca { i1, i64 }, align 8 + %"26_1" = alloca { i64*, i64 }, align 8 + %"022" = alloca { i1, i64 }, align 8 + %"1" = alloca { i64*, i64 }, align 8 %"28_0" = alloca i64, align 8 - %"023" = alloca i64, align 8 + %"026" = alloca i64, align 8 %"33_0" = alloca { i32, i8* }, align 8 %"34_0" = alloca i64, align 8 - %"027" = alloca i64, align 8 + %"030" = alloca i64, align 8 %"36_0" = alloca i64, align 8 - %"38_0" = alloca { i1, i64, [2 x i64] }, align 8 - %"033" = alloca { i1, i64, [2 x i64] }, align 8 - %"40_0" = alloca i64, align 8 - %"40_1" = alloca [2 x i64], align 8 - %"036" = alloca i64, align 8 - %"1" = alloca [2 x i64], align 8 - %"039" = alloca i64, align 8 - %"140" = alloca [2 x i64], align 8 - %"45_0" = alloca { i32, i8* }, align 8 - %"42_0" = alloca i64, align 8 - %"42_1" = alloca [2 x i64], align 8 - %"46_0" = alloca i64, align 8 - %"46_1" = alloca [2 x i64], align 8 - %"048" = alloca i64, align 8 - %"149" = alloca [2 x i64], align 8 - %"48_0" = alloca i64, align 8 - %"48_1" = alloca [2 x i64], align 8 - %"50_0" = alloca { i1, i64, [1 x i64] }, align 8 - %"51_0" = alloca i64, align 8 - %"51_1" = alloca [1 x i64], align 8 - %"056" = alloca i64, align 8 - %"157" = alloca [1 x i64], align 8 - %"56_0" = alloca { i32, i8* }, align 8 - %"57_0" = alloca i64, align 8 - %"57_1" = alloca [1 x i64], align 8 - %"063" = alloca i64, align 8 - %"164" = alloca [1 x i64], align 8 - %"59_0" = alloca i64, align 8 - %"59_1" = alloca [1 x i64], align 8 - %"61_0" = alloca { i1, i64 }, align 8 - %"62_0" = alloca i64, align 8 - %"62_1" = alloca [0 x i64], align 8 + %"38_0" = alloca { i1, { i64*, i64 }, i64 }, align 8 + %"038" = alloca { i1, { i64*, i64 }, i64 }, align 8 + %"39_0" = alloca i64, align 8 + %"39_1" = alloca { i64*, i64 }, align 8 + %"041" = alloca i64, align 8 + %"142" = alloca { i64*, i64 }, align 8 + %"045" = alloca i64, align 8 + %"146" = alloca { i64*, i64 }, align 8 + %"44_0" = alloca { i32, i8* }, align 8 + %"41_0" = alloca i64, align 8 + %"41_1" = alloca { i64*, i64 }, align 8 + %"45_0" = alloca i64, align 8 + %"45_1" = alloca { i64*, i64 }, align 8 + %"054" = alloca i64, align 8 + %"155" = alloca { i64*, i64 }, align 8 + %"47_0" = alloca i64, align 8 + %"47_1" = alloca { i64*, i64 }, align 8 + %"49_0" = alloca { i1, { i64*, i64 }, i64 }, align 8 + %"50_0" = alloca i64, align 8 + %"50_1" = alloca { i64*, i64 }, align 8 + %"064" = alloca i64, align 8 + %"165" = alloca { i64*, i64 }, align 8 + %"55_0" = alloca { i32, i8* }, align 8 + %"56_0" = alloca i64, align 8 + %"56_1" = alloca { i64*, i64 }, align 8 %"071" = alloca i64, align 8 - %"172" = alloca [0 x i64], align 8 - %"67_0" = alloca { i32, i8* }, align 8 - %"68_0" = alloca i64, align 8 - %"68_1" = alloca [0 x i64], align 8 - %"078" = alloca i64, align 8 - %"179" = alloca [0 x i64], align 8 - %"70_0" = alloca i64, align 8 - %"70_1" = alloca [0 x i64], align 8 + %"172" = alloca { i64*, i64 }, align 8 + %"58_0" = alloca i64, align 8 + %"58_1" = alloca { i64*, i64 }, align 8 + %"60_0" = alloca { i1, { i64*, i64 }, i64 }, align 8 + %"61_0" = alloca i64, align 8 + %"61_1" = alloca { i64*, i64 }, align 8 + %"081" = alloca i64, align 8 + %"182" = alloca { i64*, i64 }, align 8 + %"66_0" = alloca { i32, i8* }, align 8 + %"67_0" = alloca i64, align 8 + %"67_1" = alloca { i64*, i64 }, align 8 + %"088" = alloca i64, align 8 + %"189" = alloca { i64*, i64 }, align 8 + %"69_0" = alloca i64, align 8 + %"69_1" = alloca { i64*, i64 }, align 8 br label %entry_block entry_block: ; preds = %alloca_block @@ -88,345 +90,362 @@ entry_block: ; preds = %alloca_block store i64 1, i64* %"10_0", align 4 %"10_01" = load i64, i64* %"10_0", align 4 %"12_02" = load i64, i64* %"12_0", align 4 - %0 = insertvalue [2 x i64] undef, i64 %"10_01", 0 - %1 = insertvalue [2 x i64] %0, i64 %"12_02", 1 - store [2 x i64] %1, [2 x i64]* %"13_0", align 4 + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i64 0 + store i64 %"10_01", i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i64 1 + store i64 %"12_02", i64* %5, align 4 + store { i64*, i64 } %3, { i64*, i64 }* %"13_0", align 8 store i64 0, i64* %"8_0", align 4 - %"13_03" = load [2 x i64], [2 x i64]* %"13_0", align 4 + %"13_03" = load { i64*, i64 }, { i64*, i64 }* %"13_0", align 8 %"8_04" = load i64, i64* %"8_0", align 4 %"10_05" = load i64, i64* %"10_0", align 4 - %2 = icmp ult i64 %"8_04", 2 - %3 = icmp ult i64 %"10_05", 2 - %4 = and i1 %2, %3 - br i1 %4, label %7, label %5 - -5: ; preds = %entry_block - %6 = insertvalue { i1, [2 x i64] } { i1 false, [2 x i64] poison }, [2 x i64] %"13_03", 1 - store { i1, [2 x i64] } %6, { i1, [2 x i64] }* %"0", align 4 - br label %17 - -7: ; preds = %entry_block - %8 = alloca i64, i32 2, align 8 - %9 = bitcast i64* %8 to [2 x i64]* - store [2 x i64] %"13_03", [2 x i64]* %9, align 4 - %10 = getelementptr inbounds i64, i64* %8, i64 %"8_04" - %11 = load i64, i64* %10, align 4 - %12 = getelementptr inbounds i64, i64* %8, i64 %"10_05" - %13 = load i64, i64* %12, align 4 - store i64 %13, i64* %10, align 4 - store i64 %11, i64* %12, align 4 - %14 = bitcast i64* %8 to [2 x i64]* - %15 = load [2 x i64], [2 x i64]* %14, align 4 - %16 = insertvalue { i1, [2 x i64] } { i1 true, [2 x i64] poison }, [2 x i64] %15, 1 - store { i1, [2 x i64] } %16, { i1, [2 x i64] }* %"0", align 4 - br label %17 - -17: ; preds = %5, %7 - %"06" = load { i1, [2 x i64] }, { i1, [2 x i64] }* %"0", align 4 - store { i1, [2 x i64] } %"06", { i1, [2 x i64] }* %"14_0", align 4 - %"14_07" = load { i1, [2 x i64] }, { i1, [2 x i64] }* %"14_0", align 4 - %18 = extractvalue { i1, [2 x i64] } %"14_07", 0 - switch i1 %18, label %19 [ - i1 true, label %21 + %array_ptr = extractvalue { i64*, i64 } %"13_03", 0 + %array_offset = extractvalue { i64*, i64 } %"13_03", 1 + %6 = icmp ult i64 %"8_04", 2 + %7 = icmp ult i64 %"10_05", 2 + %8 = and i1 %6, %7 + br i1 %8, label %11, label %9 + +9: ; preds = %entry_block + %10 = insertvalue { i1, { i64*, i64 } } { i1 false, { i64*, i64 } poison }, { i64*, i64 } %"13_03", 1 + store { i1, { i64*, i64 } } %10, { i1, { i64*, i64 } }* %"0", align 8 + br label %19 + +11: ; preds = %entry_block + %12 = add i64 %"8_04", %array_offset + %13 = add i64 %"10_05", %array_offset + %14 = getelementptr inbounds i64, i64* %array_ptr, i64 %12 + %15 = load i64, i64* %14, align 4 + %16 = getelementptr inbounds i64, i64* %array_ptr, i64 %13 + %17 = load i64, i64* %16, align 4 + store i64 %17, i64* %14, align 4 + store i64 %15, i64* %16, align 4 + %18 = insertvalue { i1, { i64*, i64 } } { i1 true, { i64*, i64 } poison }, { i64*, i64 } %"13_03", 1 + store { i1, { i64*, i64 } } %18, { i1, { i64*, i64 } }* %"0", align 8 + br label %19 + +19: ; preds = %9, %11 + %"06" = load { i1, { i64*, i64 } }, { i1, { i64*, i64 } }* %"0", align 8 + store { i1, { i64*, i64 } } %"06", { i1, { i64*, i64 } }* %"14_0", align 8 + %"14_07" = load { i1, { i64*, i64 } }, { i1, { i64*, i64 } }* %"14_0", align 8 + %20 = extractvalue { i1, { i64*, i64 } } %"14_07", 0 + switch i1 %20, label %21 [ + i1 true, label %23 ] -19: ; preds = %17 - %20 = extractvalue { i1, [2 x i64] } %"14_07", 1 - store [2 x i64] %20, [2 x i64]* %"010", align 4 +21: ; preds = %19 + %22 = extractvalue { i1, { i64*, i64 } } %"14_07", 1 + store { i64*, i64 } %22, { i64*, i64 }* %"010", align 8 br label %cond_16_case_0 -21: ; preds = %17 - %22 = extractvalue { i1, [2 x i64] } %"14_07", 1 - store [2 x i64] %22, [2 x i64]* %"015", align 4 +23: ; preds = %19 + %24 = extractvalue { i1, { i64*, i64 } } %"14_07", 1 + store { i64*, i64 } %24, { i64*, i64 }* %"015", align 8 br label %cond_16_case_1 -cond_16_case_0: ; preds = %19 - %"011" = load [2 x i64], [2 x i64]* %"010", align 4 +cond_16_case_0: ; preds = %21 + %"011" = load { i64*, i64 }, { i64*, i64 }* %"010", align 8 store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, { i32, i8* }* %"21_0", align 8 - store [2 x i64] %"011", [2 x i64]* %"18_0", align 4 + store { i64*, i64 } %"011", { i64*, i64 }* %"18_0", align 8 %"21_012" = load { i32, i8* }, { i32, i8* }* %"21_0", align 8 - %"18_013" = load [2 x i64], [2 x i64]* %"18_0", align 4 - %23 = extractvalue { i32, i8* } %"21_012", 0 - %24 = extractvalue { i32, i8* } %"21_012", 1 - %25 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %23, i8* %24) + %"18_013" = load { i64*, i64 }, { i64*, i64 }* %"18_0", align 8 + %25 = extractvalue { i32, i8* } %"21_012", 0 + %26 = extractvalue { i32, i8* } %"21_012", 1 + %27 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %25, i8* %26) call void @abort() - store [2 x i64] zeroinitializer, [2 x i64]* %"22_0", align 4 - %"22_014" = load [2 x i64], [2 x i64]* %"22_0", align 4 - store [2 x i64] %"22_014", [2 x i64]* %"08", align 4 + store { i64*, i64 } zeroinitializer, { i64*, i64 }* %"22_0", align 8 + %"22_014" = load { i64*, i64 }, { i64*, i64 }* %"22_0", align 8 + store { i64*, i64 } %"22_014", { i64*, i64 }* %"08", align 8 br label %cond_exit_16 -cond_16_case_1: ; preds = %21 - %"016" = load [2 x i64], [2 x i64]* %"015", align 4 - store [2 x i64] %"016", [2 x i64]* %"24_0", align 4 - %"24_017" = load [2 x i64], [2 x i64]* %"24_0", align 4 - store [2 x i64] %"24_017", [2 x i64]* %"08", align 4 +cond_16_case_1: ; preds = %23 + %"016" = load { i64*, i64 }, { i64*, i64 }* %"015", align 8 + store { i64*, i64 } %"016", { i64*, i64 }* %"24_0", align 8 + %"24_017" = load { i64*, i64 }, { i64*, i64 }* %"24_0", align 8 + store { i64*, i64 } %"24_017", { i64*, i64 }* %"08", align 8 br label %cond_exit_16 cond_exit_16: ; preds = %cond_16_case_1, %cond_16_case_0 - %"09" = load [2 x i64], [2 x i64]* %"08", align 4 - store [2 x i64] %"09", [2 x i64]* %"16_0", align 4 - %"16_018" = load [2 x i64], [2 x i64]* %"16_0", align 4 + %"09" = load { i64*, i64 }, { i64*, i64 }* %"08", align 8 + store { i64*, i64 } %"09", { i64*, i64 }* %"16_0", align 8 + %"16_018" = load { i64*, i64 }, { i64*, i64 }* %"16_0", align 8 %"8_019" = load i64, i64* %"8_0", align 4 - %26 = icmp ult i64 %"8_019", 2 - br i1 %26, label %28, label %27 - -27: ; preds = %cond_exit_16 - store { i1, i64 } { i1 false, i64 poison }, { i1, i64 }* %"020", align 4 - br label %34 - -28: ; preds = %cond_exit_16 - %29 = alloca i64, i32 2, align 8 - %30 = bitcast i64* %29 to [2 x i64]* - store [2 x i64] %"16_018", [2 x i64]* %30, align 4 - %31 = getelementptr inbounds i64, i64* %29, i64 %"8_019" - %32 = load i64, i64* %31, align 4 - %33 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %32, 1 - store { i1, i64 } %33, { i1, i64 }* %"020", align 4 - br label %34 - -34: ; preds = %27, %28 - %"021" = load { i1, i64 }, { i1, i64 }* %"020", align 4 - store { i1, i64 } %"021", { i1, i64 }* %"26_0", align 4 - %"26_022" = load { i1, i64 }, { i1, i64 }* %"26_0", align 4 - %35 = extractvalue { i1, i64 } %"26_022", 0 - switch i1 %35, label %36 [ - i1 true, label %37 + %array_ptr20 = extractvalue { i64*, i64 } %"16_018", 0 + %array_offset21 = extractvalue { i64*, i64 } %"16_018", 1 + %28 = icmp ult i64 %"8_019", 2 + br i1 %28, label %30, label %29 + +29: ; preds = %cond_exit_16 + store { i1, i64 } { i1 false, i64 poison }, { i1, i64 }* %"022", align 4 + store { i64*, i64 } %"16_018", { i64*, i64 }* %"1", align 8 + br label %35 + +30: ; preds = %cond_exit_16 + %31 = add i64 %"8_019", %array_offset21 + %32 = getelementptr inbounds i64, i64* %array_ptr20, i64 %31 + %33 = load i64, i64* %32, align 4 + %34 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %33, 1 + store { i1, i64 } %34, { i1, i64 }* %"022", align 4 + store { i64*, i64 } %"16_018", { i64*, i64 }* %"1", align 8 + br label %35 + +35: ; preds = %29, %30 + %"023" = load { i1, i64 }, { i1, i64 }* %"022", align 4 + %"124" = load { i64*, i64 }, { i64*, i64 }* %"1", align 8 + store { i1, i64 } %"023", { i1, i64 }* %"26_0", align 4 + store { i64*, i64 } %"124", { i64*, i64 }* %"26_1", align 8 + %"26_025" = load { i1, i64 }, { i1, i64 }* %"26_0", align 4 + %36 = extractvalue { i1, i64 } %"26_025", 0 + switch i1 %36, label %37 [ + i1 true, label %38 ] -36: ; preds = %34 +37: ; preds = %35 br label %cond_28_case_0 -37: ; preds = %34 - %38 = extractvalue { i1, i64 } %"26_022", 1 - store i64 %38, i64* %"027", align 4 +38: ; preds = %35 + %39 = extractvalue { i1, i64 } %"26_025", 1 + store i64 %39, i64* %"030", align 4 br label %cond_28_case_1 -cond_28_case_0: ; preds = %36 +cond_28_case_0: ; preds = %37 store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @1, i32 0, i32 0) }, { i32, i8* }* %"33_0", align 8 - %"33_025" = load { i32, i8* }, { i32, i8* }* %"33_0", align 8 - %39 = extractvalue { i32, i8* } %"33_025", 0 - %40 = extractvalue { i32, i8* } %"33_025", 1 - %41 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.1, i32 0, i32 0), i32 %39, i8* %40) + %"33_028" = load { i32, i8* }, { i32, i8* }* %"33_0", align 8 + %40 = extractvalue { i32, i8* } %"33_028", 0 + %41 = extractvalue { i32, i8* } %"33_028", 1 + %42 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.1, i32 0, i32 0), i32 %40, i8* %41) call void @abort() store i64 0, i64* %"34_0", align 4 - %"34_026" = load i64, i64* %"34_0", align 4 - store i64 %"34_026", i64* %"023", align 4 + %"34_029" = load i64, i64* %"34_0", align 4 + store i64 %"34_029", i64* %"026", align 4 br label %cond_exit_28 -cond_28_case_1: ; preds = %37 - %"028" = load i64, i64* %"027", align 4 - store i64 %"028", i64* %"36_0", align 4 - %"36_029" = load i64, i64* %"36_0", align 4 - store i64 %"36_029", i64* %"023", align 4 +cond_28_case_1: ; preds = %38 + %"031" = load i64, i64* %"030", align 4 + store i64 %"031", i64* %"36_0", align 4 + %"36_032" = load i64, i64* %"36_0", align 4 + store i64 %"36_032", i64* %"026", align 4 br label %cond_exit_28 cond_exit_28: ; preds = %cond_28_case_1, %cond_28_case_0 - %"024" = load i64, i64* %"023", align 4 - store i64 %"024", i64* %"28_0", align 4 - %"16_030" = load [2 x i64], [2 x i64]* %"16_0", align 4 - %"10_031" = load i64, i64* %"10_0", align 4 - %"28_032" = load i64, i64* %"28_0", align 4 - %42 = icmp ult i64 %"10_031", 2 - br i1 %42, label %46, label %43 - -43: ; preds = %cond_exit_28 - %44 = insertvalue { i1, i64, [2 x i64] } { i1 false, i64 poison, [2 x i64] poison }, i64 %"28_032", 1 - %45 = insertvalue { i1, i64, [2 x i64] } %44, [2 x i64] %"16_030", 2 - store { i1, i64, [2 x i64] } %45, { i1, i64, [2 x i64] }* %"033", align 4 - br label %55 - -46: ; preds = %cond_exit_28 - %47 = alloca i64, i32 2, align 8 - %48 = bitcast i64* %47 to [2 x i64]* - store [2 x i64] %"16_030", [2 x i64]* %48, align 4 - %49 = getelementptr inbounds i64, i64* %47, i64 %"10_031" + %"027" = load i64, i64* %"026", align 4 + store i64 %"027", i64* %"28_0", align 4 + %"26_133" = load { i64*, i64 }, { i64*, i64 }* %"26_1", align 8 + %"10_034" = load i64, i64* %"10_0", align 4 + %"28_035" = load i64, i64* %"28_0", align 4 + %array_ptr36 = extractvalue { i64*, i64 } %"26_133", 0 + %array_offset37 = extractvalue { i64*, i64 } %"26_133", 1 + %43 = icmp ult i64 %"10_034", 2 + br i1 %43, label %47, label %44 + +44: ; preds = %cond_exit_28 + %45 = insertvalue { i1, { i64*, i64 }, i64 } { i1 false, { i64*, i64 } poison, i64 poison }, i64 %"28_035", 2 + %46 = insertvalue { i1, { i64*, i64 }, i64 } %45, { i64*, i64 } %"26_133", 1 + store { i1, { i64*, i64 }, i64 } %46, { i1, { i64*, i64 }, i64 }* %"038", align 8 + br label %53 + +47: ; preds = %cond_exit_28 + %48 = add i64 %"10_034", %array_offset37 + %49 = getelementptr inbounds i64, i64* %array_ptr36, i64 %48 %50 = load i64, i64* %49, align 4 - store i64 %"28_032", i64* %49, align 4 - %51 = bitcast i64* %47 to [2 x i64]* - %52 = load [2 x i64], [2 x i64]* %51, align 4 - %53 = insertvalue { i1, i64, [2 x i64] } { i1 true, i64 poison, [2 x i64] poison }, i64 %50, 1 - %54 = insertvalue { i1, i64, [2 x i64] } %53, [2 x i64] %52, 2 - store { i1, i64, [2 x i64] } %54, { i1, i64, [2 x i64] }* %"033", align 4 - br label %55 - -55: ; preds = %43, %46 - %"034" = load { i1, i64, [2 x i64] }, { i1, i64, [2 x i64] }* %"033", align 4 - store { i1, i64, [2 x i64] } %"034", { i1, i64, [2 x i64] }* %"38_0", align 4 - %"38_035" = load { i1, i64, [2 x i64] }, { i1, i64, [2 x i64] }* %"38_0", align 4 - %56 = extractvalue { i1, i64, [2 x i64] } %"38_035", 0 - switch i1 %56, label %57 [ - i1 true, label %60 + store i64 %"28_035", i64* %49, align 4 + %51 = insertvalue { i1, { i64*, i64 }, i64 } { i1 true, { i64*, i64 } poison, i64 poison }, i64 %50, 2 + %52 = insertvalue { i1, { i64*, i64 }, i64 } %51, { i64*, i64 } %"26_133", 1 + store { i1, { i64*, i64 }, i64 } %52, { i1, { i64*, i64 }, i64 }* %"038", align 8 + br label %53 + +53: ; preds = %44, %47 + %"039" = load { i1, { i64*, i64 }, i64 }, { i1, { i64*, i64 }, i64 }* %"038", align 8 + store { i1, { i64*, i64 }, i64 } %"039", { i1, { i64*, i64 }, i64 }* %"38_0", align 8 + %"38_040" = load { i1, { i64*, i64 }, i64 }, { i1, { i64*, i64 }, i64 }* %"38_0", align 8 + %54 = extractvalue { i1, { i64*, i64 }, i64 } %"38_040", 0 + switch i1 %54, label %55 [ + i1 true, label %58 ] -57: ; preds = %55 - %58 = extractvalue { i1, i64, [2 x i64] } %"38_035", 1 - %59 = extractvalue { i1, i64, [2 x i64] } %"38_035", 2 - store i64 %58, i64* %"039", align 4 - store [2 x i64] %59, [2 x i64]* %"140", align 4 - br label %cond_40_case_0 - -60: ; preds = %55 - %61 = extractvalue { i1, i64, [2 x i64] } %"38_035", 1 - %62 = extractvalue { i1, i64, [2 x i64] } %"38_035", 2 - store i64 %61, i64* %"048", align 4 - store [2 x i64] %62, [2 x i64]* %"149", align 4 - br label %cond_40_case_1 - -cond_40_case_0: ; preds = %57 - %"041" = load i64, i64* %"039", align 4 - %"142" = load [2 x i64], [2 x i64]* %"140", align 4 - store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @2, i32 0, i32 0) }, { i32, i8* }* %"45_0", align 8 - store i64 %"041", i64* %"42_0", align 4 - store [2 x i64] %"142", [2 x i64]* %"42_1", align 4 - %"45_043" = load { i32, i8* }, { i32, i8* }* %"45_0", align 8 - %"42_044" = load i64, i64* %"42_0", align 4 - %"42_145" = load [2 x i64], [2 x i64]* %"42_1", align 4 - %63 = extractvalue { i32, i8* } %"45_043", 0 - %64 = extractvalue { i32, i8* } %"45_043", 1 - %65 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.2, i32 0, i32 0), i32 %63, i8* %64) +55: ; preds = %53 + %56 = extractvalue { i1, { i64*, i64 }, i64 } %"38_040", 2 + %57 = extractvalue { i1, { i64*, i64 }, i64 } %"38_040", 1 + store i64 %56, i64* %"045", align 4 + store { i64*, i64 } %57, { i64*, i64 }* %"146", align 8 + br label %cond_39_case_0 + +58: ; preds = %53 + %59 = extractvalue { i1, { i64*, i64 }, i64 } %"38_040", 2 + %60 = extractvalue { i1, { i64*, i64 }, i64 } %"38_040", 1 + store i64 %59, i64* %"054", align 4 + store { i64*, i64 } %60, { i64*, i64 }* %"155", align 8 + br label %cond_39_case_1 + +cond_39_case_0: ; preds = %55 + %"047" = load i64, i64* %"045", align 4 + %"148" = load { i64*, i64 }, { i64*, i64 }* %"146", align 8 + store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @2, i32 0, i32 0) }, { i32, i8* }* %"44_0", align 8 + store i64 %"047", i64* %"41_0", align 4 + store { i64*, i64 } %"148", { i64*, i64 }* %"41_1", align 8 + %"44_049" = load { i32, i8* }, { i32, i8* }* %"44_0", align 8 + %"41_050" = load i64, i64* %"41_0", align 4 + %"41_151" = load { i64*, i64 }, { i64*, i64 }* %"41_1", align 8 + %61 = extractvalue { i32, i8* } %"44_049", 0 + %62 = extractvalue { i32, i8* } %"44_049", 1 + %63 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.2, i32 0, i32 0), i32 %61, i8* %62) call void @abort() - store i64 0, i64* %"46_0", align 4 - store [2 x i64] zeroinitializer, [2 x i64]* %"46_1", align 4 - %"46_046" = load i64, i64* %"46_0", align 4 - %"46_147" = load [2 x i64], [2 x i64]* %"46_1", align 4 - store i64 %"46_046", i64* %"036", align 4 - store [2 x i64] %"46_147", [2 x i64]* %"1", align 4 - br label %cond_exit_40 - -cond_40_case_1: ; preds = %60 - %"050" = load i64, i64* %"048", align 4 - %"151" = load [2 x i64], [2 x i64]* %"149", align 4 - store i64 %"050", i64* %"48_0", align 4 - store [2 x i64] %"151", [2 x i64]* %"48_1", align 4 - %"48_052" = load i64, i64* %"48_0", align 4 - %"48_153" = load [2 x i64], [2 x i64]* %"48_1", align 4 - store i64 %"48_052", i64* %"036", align 4 - store [2 x i64] %"48_153", [2 x i64]* %"1", align 4 - br label %cond_exit_40 - -cond_exit_40: ; preds = %cond_40_case_1, %cond_40_case_0 - %"037" = load i64, i64* %"036", align 4 - %"138" = load [2 x i64], [2 x i64]* %"1", align 4 - store i64 %"037", i64* %"40_0", align 4 - store [2 x i64] %"138", [2 x i64]* %"40_1", align 4 - %"40_154" = load [2 x i64], [2 x i64]* %"40_1", align 4 - %66 = alloca i64, i32 2, align 8 - %67 = bitcast i64* %66 to [2 x i64]* - store [2 x i64] %"40_154", [2 x i64]* %67, align 4 - %68 = getelementptr i64, i64* %66, i32 1 - %69 = load i64, i64* %66, align 4 - %70 = bitcast i64* %68 to [1 x i64]* - %71 = load [1 x i64], [1 x i64]* %70, align 4 - %72 = insertvalue { i1, i64, [1 x i64] } { i1 true, i64 poison, [1 x i64] poison }, i64 %69, 1 - %73 = insertvalue { i1, i64, [1 x i64] } %72, [1 x i64] %71, 2 - store { i1, i64, [1 x i64] } %73, { i1, i64, [1 x i64] }* %"50_0", align 4 - %"50_055" = load { i1, i64, [1 x i64] }, { i1, i64, [1 x i64] }* %"50_0", align 4 - %74 = extractvalue { i1, i64, [1 x i64] } %"50_055", 0 - switch i1 %74, label %75 [ - i1 true, label %76 + store i64 0, i64* %"45_0", align 4 + store { i64*, i64 } zeroinitializer, { i64*, i64 }* %"45_1", align 8 + %"45_052" = load i64, i64* %"45_0", align 4 + %"45_153" = load { i64*, i64 }, { i64*, i64 }* %"45_1", align 8 + store i64 %"45_052", i64* %"041", align 4 + store { i64*, i64 } %"45_153", { i64*, i64 }* %"142", align 8 + br label %cond_exit_39 + +cond_39_case_1: ; preds = %58 + %"056" = load i64, i64* %"054", align 4 + %"157" = load { i64*, i64 }, { i64*, i64 }* %"155", align 8 + store i64 %"056", i64* %"47_0", align 4 + store { i64*, i64 } %"157", { i64*, i64 }* %"47_1", align 8 + %"47_058" = load i64, i64* %"47_0", align 4 + %"47_159" = load { i64*, i64 }, { i64*, i64 }* %"47_1", align 8 + store i64 %"47_058", i64* %"041", align 4 + store { i64*, i64 } %"47_159", { i64*, i64 }* %"142", align 8 + br label %cond_exit_39 + +cond_exit_39: ; preds = %cond_39_case_1, %cond_39_case_0 + %"043" = load i64, i64* %"041", align 4 + %"144" = load { i64*, i64 }, { i64*, i64 }* %"142", align 8 + store i64 %"043", i64* %"39_0", align 4 + store { i64*, i64 } %"144", { i64*, i64 }* %"39_1", align 8 + %"39_160" = load { i64*, i64 }, { i64*, i64 }* %"39_1", align 8 + %array_ptr61 = extractvalue { i64*, i64 } %"39_160", 0 + %array_offset62 = extractvalue { i64*, i64 } %"39_160", 1 + %new_offset = add i64 %array_offset62, 1 + %64 = getelementptr inbounds i64, i64* %array_ptr61, i64 %array_offset62 + %65 = load i64, i64* %64, align 4 + %66 = insertvalue { i64*, i64 } poison, i64* %array_ptr61, 0 + %67 = insertvalue { i64*, i64 } %66, i64 %new_offset, 1 + %68 = insertvalue { i1, { i64*, i64 }, i64 } { i1 true, { i64*, i64 } poison, i64 poison }, i64 %65, 2 + %69 = insertvalue { i1, { i64*, i64 }, i64 } %68, { i64*, i64 } %67, 1 + store { i1, { i64*, i64 }, i64 } %69, { i1, { i64*, i64 }, i64 }* %"49_0", align 8 + %"49_063" = load { i1, { i64*, i64 }, i64 }, { i1, { i64*, i64 }, i64 }* %"49_0", align 8 + %70 = extractvalue { i1, { i64*, i64 }, i64 } %"49_063", 0 + switch i1 %70, label %71 [ + i1 true, label %72 ] -75: ; preds = %cond_exit_40 - br label %cond_51_case_0 - -76: ; preds = %cond_exit_40 - %77 = extractvalue { i1, i64, [1 x i64] } %"50_055", 1 - %78 = extractvalue { i1, i64, [1 x i64] } %"50_055", 2 - store i64 %77, i64* %"063", align 4 - store [1 x i64] %78, [1 x i64]* %"164", align 4 - br label %cond_51_case_1 - -cond_51_case_0: ; preds = %75 - store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @3, i32 0, i32 0) }, { i32, i8* }* %"56_0", align 8 - %"56_060" = load { i32, i8* }, { i32, i8* }* %"56_0", align 8 - %79 = extractvalue { i32, i8* } %"56_060", 0 - %80 = extractvalue { i32, i8* } %"56_060", 1 - %81 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.3, i32 0, i32 0), i32 %79, i8* %80) +71: ; preds = %cond_exit_39 + br label %cond_50_case_0 + +72: ; preds = %cond_exit_39 + %73 = extractvalue { i1, { i64*, i64 }, i64 } %"49_063", 2 + %74 = extractvalue { i1, { i64*, i64 }, i64 } %"49_063", 1 + store i64 %73, i64* %"071", align 4 + store { i64*, i64 } %74, { i64*, i64 }* %"172", align 8 + br label %cond_50_case_1 + +cond_50_case_0: ; preds = %71 + store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @3, i32 0, i32 0) }, { i32, i8* }* %"55_0", align 8 + %"55_068" = load { i32, i8* }, { i32, i8* }* %"55_0", align 8 + %75 = extractvalue { i32, i8* } %"55_068", 0 + %76 = extractvalue { i32, i8* } %"55_068", 1 + %77 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.3, i32 0, i32 0), i32 %75, i8* %76) call void @abort() - store i64 0, i64* %"57_0", align 4 - store [1 x i64] zeroinitializer, [1 x i64]* %"57_1", align 4 - %"57_061" = load i64, i64* %"57_0", align 4 - %"57_162" = load [1 x i64], [1 x i64]* %"57_1", align 4 - store i64 %"57_061", i64* %"056", align 4 - store [1 x i64] %"57_162", [1 x i64]* %"157", align 4 - br label %cond_exit_51 - -cond_51_case_1: ; preds = %76 - %"065" = load i64, i64* %"063", align 4 - %"166" = load [1 x i64], [1 x i64]* %"164", align 4 - store i64 %"065", i64* %"59_0", align 4 - store [1 x i64] %"166", [1 x i64]* %"59_1", align 4 - %"59_067" = load i64, i64* %"59_0", align 4 - %"59_168" = load [1 x i64], [1 x i64]* %"59_1", align 4 - store i64 %"59_067", i64* %"056", align 4 - store [1 x i64] %"59_168", [1 x i64]* %"157", align 4 - br label %cond_exit_51 - -cond_exit_51: ; preds = %cond_51_case_1, %cond_51_case_0 - %"058" = load i64, i64* %"056", align 4 - %"159" = load [1 x i64], [1 x i64]* %"157", align 4 - store i64 %"058", i64* %"51_0", align 4 - store [1 x i64] %"159", [1 x i64]* %"51_1", align 4 - %"51_169" = load [1 x i64], [1 x i64]* %"51_1", align 4 - %82 = alloca i64, align 8 - %83 = bitcast i64* %82 to [1 x i64]* - store [1 x i64] %"51_169", [1 x i64]* %83, align 4 - %84 = getelementptr i64, i64* %82, i32 0 - %85 = load i64, i64* %84, align 4 - %86 = bitcast i64* %82 to [0 x i64]* - %87 = load [0 x i64], [0 x i64]* %86, align 4 - %88 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %85, 1 - store { i1, i64 } %88, { i1, i64 }* %"61_0", align 4 - %"61_070" = load { i1, i64 }, { i1, i64 }* %"61_0", align 4 - %89 = extractvalue { i1, i64 } %"61_070", 0 - switch i1 %89, label %90 [ - i1 true, label %91 + store i64 0, i64* %"56_0", align 4 + store { i64*, i64 } zeroinitializer, { i64*, i64 }* %"56_1", align 8 + %"56_069" = load i64, i64* %"56_0", align 4 + %"56_170" = load { i64*, i64 }, { i64*, i64 }* %"56_1", align 8 + store i64 %"56_069", i64* %"064", align 4 + store { i64*, i64 } %"56_170", { i64*, i64 }* %"165", align 8 + br label %cond_exit_50 + +cond_50_case_1: ; preds = %72 + %"073" = load i64, i64* %"071", align 4 + %"174" = load { i64*, i64 }, { i64*, i64 }* %"172", align 8 + store i64 %"073", i64* %"58_0", align 4 + store { i64*, i64 } %"174", { i64*, i64 }* %"58_1", align 8 + %"58_075" = load i64, i64* %"58_0", align 4 + %"58_176" = load { i64*, i64 }, { i64*, i64 }* %"58_1", align 8 + store i64 %"58_075", i64* %"064", align 4 + store { i64*, i64 } %"58_176", { i64*, i64 }* %"165", align 8 + br label %cond_exit_50 + +cond_exit_50: ; preds = %cond_50_case_1, %cond_50_case_0 + %"066" = load i64, i64* %"064", align 4 + %"167" = load { i64*, i64 }, { i64*, i64 }* %"165", align 8 + store i64 %"066", i64* %"50_0", align 4 + store { i64*, i64 } %"167", { i64*, i64 }* %"50_1", align 8 + %"50_177" = load { i64*, i64 }, { i64*, i64 }* %"50_1", align 8 + %array_ptr78 = extractvalue { i64*, i64 } %"50_177", 0 + %array_offset79 = extractvalue { i64*, i64 } %"50_177", 1 + %78 = add i64 %array_offset79, 0 + %79 = getelementptr inbounds i64, i64* %array_ptr78, i64 %78 + %80 = load i64, i64* %79, align 4 + %81 = insertvalue { i64*, i64 } poison, i64* %array_ptr78, 0 + %82 = insertvalue { i64*, i64 } %81, i64 %array_offset79, 1 + %83 = insertvalue { i1, { i64*, i64 }, i64 } { i1 true, { i64*, i64 } poison, i64 poison }, i64 %80, 2 + %84 = insertvalue { i1, { i64*, i64 }, i64 } %83, { i64*, i64 } %82, 1 + store { i1, { i64*, i64 }, i64 } %84, { i1, { i64*, i64 }, i64 }* %"60_0", align 8 + %"60_080" = load { i1, { i64*, i64 }, i64 }, { i1, { i64*, i64 }, i64 }* %"60_0", align 8 + %85 = extractvalue { i1, { i64*, i64 }, i64 } %"60_080", 0 + switch i1 %85, label %86 [ + i1 true, label %87 ] -90: ; preds = %cond_exit_51 - br label %cond_62_case_0 - -91: ; preds = %cond_exit_51 - %92 = extractvalue { i1, i64 } %"61_070", 1 - store i64 %92, i64* %"078", align 4 - store [0 x i64] undef, [0 x i64]* %"179", align 4 - br label %cond_62_case_1 - -cond_62_case_0: ; preds = %90 - store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @4, i32 0, i32 0) }, { i32, i8* }* %"67_0", align 8 - %"67_075" = load { i32, i8* }, { i32, i8* }* %"67_0", align 8 - %93 = extractvalue { i32, i8* } %"67_075", 0 - %94 = extractvalue { i32, i8* } %"67_075", 1 - %95 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.4, i32 0, i32 0), i32 %93, i8* %94) +86: ; preds = %cond_exit_50 + br label %cond_61_case_0 + +87: ; preds = %cond_exit_50 + %88 = extractvalue { i1, { i64*, i64 }, i64 } %"60_080", 2 + %89 = extractvalue { i1, { i64*, i64 }, i64 } %"60_080", 1 + store i64 %88, i64* %"088", align 4 + store { i64*, i64 } %89, { i64*, i64 }* %"189", align 8 + br label %cond_61_case_1 + +cond_61_case_0: ; preds = %86 + store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @4, i32 0, i32 0) }, { i32, i8* }* %"66_0", align 8 + %"66_085" = load { i32, i8* }, { i32, i8* }* %"66_0", align 8 + %90 = extractvalue { i32, i8* } %"66_085", 0 + %91 = extractvalue { i32, i8* } %"66_085", 1 + %92 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template.4, i32 0, i32 0), i32 %90, i8* %91) call void @abort() - store i64 0, i64* %"68_0", align 4 - store [0 x i64] zeroinitializer, [0 x i64]* %"68_1", align 4 - %"68_076" = load i64, i64* %"68_0", align 4 - %"68_177" = load [0 x i64], [0 x i64]* %"68_1", align 4 - store i64 %"68_076", i64* %"071", align 4 - store [0 x i64] %"68_177", [0 x i64]* %"172", align 4 - br label %cond_exit_62 - -cond_62_case_1: ; preds = %91 - %"080" = load i64, i64* %"078", align 4 - %"181" = load [0 x i64], [0 x i64]* %"179", align 4 - store i64 %"080", i64* %"70_0", align 4 - store [0 x i64] %"181", [0 x i64]* %"70_1", align 4 - %"70_082" = load i64, i64* %"70_0", align 4 - %"70_183" = load [0 x i64], [0 x i64]* %"70_1", align 4 - store i64 %"70_082", i64* %"071", align 4 - store [0 x i64] %"70_183", [0 x i64]* %"172", align 4 - br label %cond_exit_62 - -cond_exit_62: ; preds = %cond_62_case_1, %cond_62_case_0 - %"073" = load i64, i64* %"071", align 4 - %"174" = load [0 x i64], [0 x i64]* %"172", align 4 - store i64 %"073", i64* %"62_0", align 4 - store [0 x i64] %"174", [0 x i64]* %"62_1", align 4 - %"62_184" = load [0 x i64], [0 x i64]* %"62_1", align 4 + store i64 0, i64* %"67_0", align 4 + store { i64*, i64 } zeroinitializer, { i64*, i64 }* %"67_1", align 8 + %"67_086" = load i64, i64* %"67_0", align 4 + %"67_187" = load { i64*, i64 }, { i64*, i64 }* %"67_1", align 8 + store i64 %"67_086", i64* %"081", align 4 + store { i64*, i64 } %"67_187", { i64*, i64 }* %"182", align 8 + br label %cond_exit_61 + +cond_61_case_1: ; preds = %87 + %"090" = load i64, i64* %"088", align 4 + %"191" = load { i64*, i64 }, { i64*, i64 }* %"189", align 8 + store i64 %"090", i64* %"69_0", align 4 + store { i64*, i64 } %"191", { i64*, i64 }* %"69_1", align 8 + %"69_092" = load i64, i64* %"69_0", align 4 + %"69_193" = load { i64*, i64 }, { i64*, i64 }* %"69_1", align 8 + store i64 %"69_092", i64* %"081", align 4 + store { i64*, i64 } %"69_193", { i64*, i64 }* %"182", align 8 + br label %cond_exit_61 + +cond_exit_61: ; preds = %cond_61_case_1, %cond_61_case_0 + %"083" = load i64, i64* %"081", align 4 + %"184" = load { i64*, i64 }, { i64*, i64 }* %"182", align 8 + store i64 %"083", i64* %"61_0", align 4 + store { i64*, i64 } %"184", { i64*, i64 }* %"61_1", align 8 + %"61_194" = load { i64*, i64 }, { i64*, i64 }* %"61_1", align 8 + %array_ptr95 = extractvalue { i64*, i64 } %"61_194", 0 + %array_offset96 = extractvalue { i64*, i64 } %"61_194", 1 + %93 = bitcast i64* %array_ptr95 to i8* + call void @free(i8* %93) ret void } +declare i8* @malloc(i64) + declare i32 @printf(i8*, ...) declare void @abort() + +declare void @free(i8*) diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@llvm14.snap index 3a718f7f23..0a84535edb 100644 --- a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@llvm14.snap +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@llvm14.snap @@ -5,10 +5,20 @@ expression: mod_str ; ModuleID = 'test_context' source_filename = "test_context" -define [2 x i64] @_hl.main.1() { +define { i64*, i64 } @_hl.main.1() { alloca_block: br label %entry_block entry_block: ; preds = %alloca_block - ret [2 x i64] [i64 1, i64 2] + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i32 0 + store i64 1, i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i32 1 + store i64 2, i64* %5, align 4 + ret { i64*, i64 } %3 } + +declare i8* @malloc(i64) diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@pre-mem2reg@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@pre-mem2reg@llvm14.snap index 5befaf3dfb..a908cbb3da 100644 --- a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@pre-mem2reg@llvm14.snap +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_array_value@pre-mem2reg@llvm14.snap @@ -5,16 +5,26 @@ expression: mod_str ; ModuleID = 'test_context' source_filename = "test_context" -define [2 x i64] @_hl.main.1() { +define { i64*, i64 } @_hl.main.1() { alloca_block: - %"0" = alloca [2 x i64], align 8 - %"5_0" = alloca [2 x i64], align 8 + %"0" = alloca { i64*, i64 }, align 8 + %"5_0" = alloca { i64*, i64 }, align 8 br label %entry_block entry_block: ; preds = %alloca_block - store [2 x i64] [i64 1, i64 2], [2 x i64]* %"5_0", align 4 - %"5_01" = load [2 x i64], [2 x i64]* %"5_0", align 4 - store [2 x i64] %"5_01", [2 x i64]* %"0", align 4 - %"02" = load [2 x i64], [2 x i64]* %"0", align 4 - ret [2 x i64] %"02" + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i32 0 + store i64 1, i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i32 1 + store i64 2, i64* %5, align 4 + store { i64*, i64 } %3, { i64*, i64 }* %"5_0", align 8 + %"5_01" = load { i64*, i64 }, { i64*, i64 }* %"5_0", align 8 + store { i64*, i64 } %"5_01", { i64*, i64 }* %"0", align 8 + %"02" = load { i64*, i64 }, { i64*, i64 }* %"0", align 8 + ret { i64*, i64 } %"02" } + +declare i8* @malloc(i64) diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_clone@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_clone@llvm14.snap new file mode 100644 index 0000000000..d03fdb1c39 --- /dev/null +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_clone@llvm14.snap @@ -0,0 +1,45 @@ +--- +source: hugr-llvm/src/extension/collections/array.rs +expression: mod_str +--- +; ModuleID = 'test_context' +source_filename = "test_context" + +define void @_hl.main.1() { +alloca_block: + br label %entry_block + +entry_block: ; preds = %alloca_block + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i64 0 + store i64 1, i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i64 1 + store i64 2, i64* %5, align 4 + %array_ptr = extractvalue { i64*, i64 } %3, 0 + %array_offset = extractvalue { i64*, i64 } %3, 1 + %6 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %7 = bitcast i8* %6 to i64* + %8 = insertvalue { i64*, i64 } poison, i64* %7, 0 + %9 = insertvalue { i64*, i64 } %8, i64 0, 1 + %10 = getelementptr inbounds i64, i64* %array_ptr, i64 %array_offset + call void @llvm.memcpy.p0i64.p0i64.i64(i64* %7, i64* %10, i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2), i1 false) + %array_ptr5 = extractvalue { i64*, i64 } %9, 0 + %11 = bitcast i64* %array_ptr5 to i8* + call void @free(i8* %11) + %array_ptr7 = extractvalue { i64*, i64 } %3, 0 + %12 = bitcast i64* %array_ptr7 to i8* + call void @free(i8* %12) + ret void +} + +declare i8* @malloc(i64) + +; Function Attrs: argmemonly nofree nounwind willreturn +declare void @llvm.memcpy.p0i64.p0i64.i64(i64* noalias nocapture writeonly, i64* noalias nocapture readonly, i64, i1 immarg) #0 + +declare void @free(i8*) + +attributes #0 = { argmemonly nofree nounwind willreturn } diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_clone@pre-mem2reg@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_clone@pre-mem2reg@llvm14.snap new file mode 100644 index 0000000000..3fd1b276f6 --- /dev/null +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_clone@pre-mem2reg@llvm14.snap @@ -0,0 +1,60 @@ +--- +source: hugr-llvm/src/extension/collections/array.rs +expression: mod_str +--- +; ModuleID = 'test_context' +source_filename = "test_context" + +define void @_hl.main.1() { +alloca_block: + %"7_0" = alloca i64, align 8 + %"5_0" = alloca i64, align 8 + %"8_0" = alloca { i64*, i64 }, align 8 + %"9_0" = alloca { i64*, i64 }, align 8 + %"9_1" = alloca { i64*, i64 }, align 8 + br label %entry_block + +entry_block: ; preds = %alloca_block + store i64 2, i64* %"7_0", align 4 + store i64 1, i64* %"5_0", align 4 + %"5_01" = load i64, i64* %"5_0", align 4 + %"7_02" = load i64, i64* %"7_0", align 4 + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i64 0 + store i64 %"5_01", i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i64 1 + store i64 %"7_02", i64* %5, align 4 + store { i64*, i64 } %3, { i64*, i64 }* %"8_0", align 8 + %"8_03" = load { i64*, i64 }, { i64*, i64 }* %"8_0", align 8 + %array_ptr = extractvalue { i64*, i64 } %"8_03", 0 + %array_offset = extractvalue { i64*, i64 } %"8_03", 1 + %6 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %7 = bitcast i8* %6 to i64* + %8 = insertvalue { i64*, i64 } poison, i64* %7, 0 + %9 = insertvalue { i64*, i64 } %8, i64 0, 1 + %10 = getelementptr inbounds i64, i64* %array_ptr, i64 %array_offset + call void @llvm.memcpy.p0i64.p0i64.i64(i64* %7, i64* %10, i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2), i1 false) + store { i64*, i64 } %"8_03", { i64*, i64 }* %"9_0", align 8 + store { i64*, i64 } %9, { i64*, i64 }* %"9_1", align 8 + %"9_14" = load { i64*, i64 }, { i64*, i64 }* %"9_1", align 8 + %array_ptr5 = extractvalue { i64*, i64 } %"9_14", 0 + %11 = bitcast i64* %array_ptr5 to i8* + call void @free(i8* %11) + %"9_06" = load { i64*, i64 }, { i64*, i64 }* %"9_0", align 8 + %array_ptr7 = extractvalue { i64*, i64 } %"9_06", 0 + %12 = bitcast i64* %array_ptr7 to i8* + call void @free(i8* %12) + ret void +} + +declare i8* @malloc(i64) + +; Function Attrs: argmemonly nofree nounwind willreturn +declare void @llvm.memcpy.p0i64.p0i64.i64(i64* noalias nocapture writeonly, i64* noalias nocapture readonly, i64, i1 immarg) #0 + +declare void @free(i8*) + +attributes #0 = { argmemonly nofree nounwind willreturn } diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@llvm14.snap index 1c638784d5..a975ed0439 100644 --- a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@llvm14.snap +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@llvm14.snap @@ -10,24 +10,37 @@ alloca_block: br label %entry_block entry_block: ; preds = %alloca_block - %0 = insertvalue [2 x i64] undef, i64 1, 0 - %1 = insertvalue [2 x i64] %0, i64 2, 1 - %2 = icmp ult i64 1, 2 - br i1 %2, label %4, label %3 + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i64 0 + store i64 1, i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i64 1 + store i64 2, i64* %5, align 4 + %array_ptr = extractvalue { i64*, i64 } %3, 0 + %array_offset = extractvalue { i64*, i64 } %3, 1 + %6 = icmp ult i64 1, 2 + br i1 %6, label %8, label %7 -3: ; preds = %entry_block - br label %10 +7: ; preds = %entry_block + br label %13 -4: ; preds = %entry_block - %5 = alloca i64, i32 2, align 8 - %6 = bitcast i64* %5 to [2 x i64]* - store [2 x i64] %1, [2 x i64]* %6, align 4 - %7 = getelementptr inbounds i64, i64* %5, i64 1 - %8 = load i64, i64* %7, align 4 - %9 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %8, 1 - br label %10 +8: ; preds = %entry_block + %9 = add i64 1, %array_offset + %10 = getelementptr inbounds i64, i64* %array_ptr, i64 %9 + %11 = load i64, i64* %10, align 4 + %12 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %11, 1 + br label %13 -10: ; preds = %3, %4 - %"0.0" = phi { i1, i64 } [ %9, %4 ], [ { i1 false, i64 poison }, %3 ] +13: ; preds = %7, %8 + %"0.0" = phi { i1, i64 } [ %12, %8 ], [ { i1 false, i64 poison }, %7 ] + %array_ptr8 = extractvalue { i64*, i64 } %3, 0 + %14 = bitcast i64* %array_ptr8 to i8* + call void @free(i8* %14) ret void } + +declare i8* @malloc(i64) + +declare void @free(i8*) diff --git a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@pre-mem2reg@llvm14.snap b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@pre-mem2reg@llvm14.snap index 15902b579b..6b7dfcffeb 100644 --- a/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@pre-mem2reg@llvm14.snap +++ b/hugr-llvm/src/extension/collections/snapshots/hugr_llvm__extension__collections__array__test__emit_get@pre-mem2reg@llvm14.snap @@ -9,9 +9,11 @@ define void @_hl.main.1() { alloca_block: %"7_0" = alloca i64, align 8 %"5_0" = alloca i64, align 8 - %"8_0" = alloca [2 x i64], align 8 + %"8_0" = alloca { i64*, i64 }, align 8 %"9_0" = alloca { i1, i64 }, align 8 + %"9_1" = alloca { i64*, i64 }, align 8 %"0" = alloca { i1, i64 }, align 8 + %"1" = alloca { i64*, i64 }, align 8 br label %entry_block entry_block: ; preds = %alloca_block @@ -19,30 +21,48 @@ entry_block: ; preds = %alloca_block store i64 1, i64* %"5_0", align 4 %"5_01" = load i64, i64* %"5_0", align 4 %"7_02" = load i64, i64* %"7_0", align 4 - %0 = insertvalue [2 x i64] undef, i64 %"5_01", 0 - %1 = insertvalue [2 x i64] %0, i64 %"7_02", 1 - store [2 x i64] %1, [2 x i64]* %"8_0", align 4 - %"8_03" = load [2 x i64], [2 x i64]* %"8_0", align 4 + %0 = call i8* @malloc(i64 mul (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2)) + %1 = bitcast i8* %0 to i64* + %2 = insertvalue { i64*, i64 } poison, i64* %1, 0 + %3 = insertvalue { i64*, i64 } %2, i64 0, 1 + %4 = getelementptr inbounds i64, i64* %1, i64 0 + store i64 %"5_01", i64* %4, align 4 + %5 = getelementptr inbounds i64, i64* %1, i64 1 + store i64 %"7_02", i64* %5, align 4 + store { i64*, i64 } %3, { i64*, i64 }* %"8_0", align 8 + %"8_03" = load { i64*, i64 }, { i64*, i64 }* %"8_0", align 8 %"5_04" = load i64, i64* %"5_0", align 4 - %2 = icmp ult i64 %"5_04", 2 - br i1 %2, label %4, label %3 + %array_ptr = extractvalue { i64*, i64 } %"8_03", 0 + %array_offset = extractvalue { i64*, i64 } %"8_03", 1 + %6 = icmp ult i64 %"5_04", 2 + br i1 %6, label %8, label %7 -3: ; preds = %entry_block +7: ; preds = %entry_block store { i1, i64 } { i1 false, i64 poison }, { i1, i64 }* %"0", align 4 - br label %10 + store { i64*, i64 } %"8_03", { i64*, i64 }* %"1", align 8 + br label %13 -4: ; preds = %entry_block - %5 = alloca i64, i32 2, align 8 - %6 = bitcast i64* %5 to [2 x i64]* - store [2 x i64] %"8_03", [2 x i64]* %6, align 4 - %7 = getelementptr inbounds i64, i64* %5, i64 %"5_04" - %8 = load i64, i64* %7, align 4 - %9 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %8, 1 - store { i1, i64 } %9, { i1, i64 }* %"0", align 4 - br label %10 +8: ; preds = %entry_block + %9 = add i64 %"5_04", %array_offset + %10 = getelementptr inbounds i64, i64* %array_ptr, i64 %9 + %11 = load i64, i64* %10, align 4 + %12 = insertvalue { i1, i64 } { i1 true, i64 poison }, i64 %11, 1 + store { i1, i64 } %12, { i1, i64 }* %"0", align 4 + store { i64*, i64 } %"8_03", { i64*, i64 }* %"1", align 8 + br label %13 -10: ; preds = %3, %4 +13: ; preds = %7, %8 %"05" = load { i1, i64 }, { i1, i64 }* %"0", align 4 + %"16" = load { i64*, i64 }, { i64*, i64 }* %"1", align 8 store { i1, i64 } %"05", { i1, i64 }* %"9_0", align 4 + store { i64*, i64 } %"16", { i64*, i64 }* %"9_1", align 8 + %"9_17" = load { i64*, i64 }, { i64*, i64 }* %"9_1", align 8 + %array_ptr8 = extractvalue { i64*, i64 } %"9_17", 0 + %14 = bitcast i64* %array_ptr8 to i8* + call void @free(i8* %14) ret void } + +declare i8* @malloc(i64) + +declare void @free(i8*) diff --git a/hugr-passes/Cargo.toml b/hugr-passes/Cargo.toml index e4e4f90872..ffeb420eec 100644 --- a/hugr-passes/Cargo.toml +++ b/hugr-passes/Cargo.toml @@ -25,6 +25,7 @@ lazy_static = { workspace = true } paste = { workspace = true } thiserror = { workspace = true } petgraph = { workspace = true } +strum = { workspace = true } [dev-dependencies] rstest = { workspace = true } diff --git a/hugr-passes/README.md b/hugr-passes/README.md index c2bca21244..4aa9ea8849 100644 --- a/hugr-passes/README.md +++ b/hugr-passes/README.md @@ -47,4 +47,4 @@ This project is licensed under Apache License, Version 2.0 ([LICENSE][] or http: [crates]: https://img.shields.io/crates/v/hugr-passes [codecov]: https://img.shields.io/codecov/c/gh/CQCL/hugr?logo=codecov [LICENSE]: https://github.com/CQCL/hugr/blob/main/LICENCE - [CHANGELOG]: https://github.com/CQCL/hugr/blob/main/hugr-passes/CHANGELOG.md \ No newline at end of file + [CHANGELOG]: https://github.com/CQCL/hugr/blob/main/hugr-passes/CHANGELOG.md diff --git a/hugr-passes/src/lib.rs b/hugr-passes/src/lib.rs index 83ff71b675..d803b817c8 100644 --- a/hugr-passes/src/lib.rs +++ b/hugr-passes/src/lib.rs @@ -11,6 +11,8 @@ mod dead_funcs; pub use dead_funcs::{remove_dead_funcs, RemoveDeadFuncsError, RemoveDeadFuncsPass}; pub mod force_order; mod half_node; +pub mod linearize_array; +pub use linearize_array::LinearizeArrayPass; pub mod lower; pub mod merge_bbs; mod monomorphize; diff --git a/hugr-passes/src/linearize_array.rs b/hugr-passes/src/linearize_array.rs new file mode 100644 index 0000000000..56ba7a7d0e --- /dev/null +++ b/hugr-passes/src/linearize_array.rs @@ -0,0 +1,397 @@ +//! Provides [LinearizeArrayPass] which turns 'value_array`s into regular linear `array`s. + +use hugr_core::{ + extension::{ + prelude::Noop, + simple_op::{HasConcrete, MakeRegisteredOp}, + }, + hugr::hugrmut::HugrMut, + ops::NamedOp, + std_extensions::collections::{ + array::{ + array_type_def, array_type_parametric, Array, ArrayKind, ArrayOpDef, ArrayRepeatDef, + ArrayScanDef, ArrayValue, ARRAY_REPEAT_OP_ID, ARRAY_SCAN_OP_ID, + }, + value_array::{self, VArrayFromArrayDef, VArrayToArrayDef, VArrayValue, ValueArray}, + }, + types::Transformable, + Node, +}; +use itertools::Itertools; +use strum::IntoEnumIterator; + +use crate::{ + replace_types::{ + handlers::copy_discard_array, DelegatingLinearizer, NodeTemplate, ReplaceTypesError, + }, + ComposablePass, ReplaceTypes, +}; + +/// A HUGR -> HUGR pass that turns 'value_array`s into regular linear `array`s. +/// +/// # Panics +/// +/// - If the Hugr has inter-graph edges whose type contains `value_array`s +/// - If the Hugr contains [`ArrayOpDef::get`] operations on `value_array`s that +/// contain nested `value_array`s. +#[derive(Clone)] +pub struct LinearizeArrayPass(ReplaceTypes); + +impl Default for LinearizeArrayPass { + fn default() -> Self { + let mut pass = ReplaceTypes::default(); + pass.replace_parametrized_type(ValueArray::type_def(), |args| { + Some(Array::ty_parametric(args[0].clone(), args[1].clone()).unwrap()) + }); + pass.replace_consts_parametrized(ValueArray::type_def(), |v, replacer| { + let v: &VArrayValue = v.value().downcast_ref().unwrap(); + let mut ty = v.get_element_type().clone(); + let mut contents = v.get_contents().iter().cloned().collect_vec(); + ty.transform(replacer).unwrap(); + contents.iter_mut().for_each(|v| { + replacer.change_value(v).unwrap(); + }); + Ok(Some(ArrayValue::new(ty, contents).into())) + }); + for op_def in ArrayOpDef::iter() { + pass.replace_parametrized_op( + value_array::EXTENSION.get_op(&op_def.name()).unwrap(), + move |args| { + // `get` is only allowed for copyable elements. Assuming the Hugr was + // valid when we started, the only way for the element to become linear + // is if it used to contain nested `value_array`s. In that case, we + // have to get rid of the `get`. + // TODO: But what should we replace it with? Can't be a `set` since we + // don't have anything to put in. Maybe we need a new `get_copy` op + // that takes a function ptr to copy the element? For now, let's just + // error out and make sure we're not emitting `get`s for nested value + // arrays. + if op_def == ArrayOpDef::get && !args[1].as_type().unwrap().copyable() { + panic!( + "Cannot linearise arrays in this Hugr: \ + Contains a `get` operation on nested value arrays" + ); + } + Some(NodeTemplate::SingleOp( + op_def.instantiate(args).unwrap().into(), + )) + }, + ); + } + pass.replace_parametrized_op( + value_array::EXTENSION.get_op(&ARRAY_REPEAT_OP_ID).unwrap(), + |args| { + Some(NodeTemplate::SingleOp( + ArrayRepeatDef::new().instantiate(args).unwrap().into(), + )) + }, + ); + pass.replace_parametrized_op( + value_array::EXTENSION.get_op(&ARRAY_SCAN_OP_ID).unwrap(), + |args| { + Some(NodeTemplate::SingleOp( + ArrayScanDef::new().instantiate(args).unwrap().into(), + )) + }, + ); + pass.replace_parametrized_op( + value_array::EXTENSION + .get_op(&VArrayFromArrayDef::new().name()) + .unwrap(), + |args| { + let array_ty = array_type_parametric(args[0].clone(), args[1].clone()).unwrap(); + Some(NodeTemplate::SingleOp( + Noop::new(array_ty).to_extension_op().unwrap().into(), + )) + }, + ); + pass.replace_parametrized_op( + value_array::EXTENSION + .get_op(&VArrayToArrayDef::new().name()) + .unwrap(), + |args| { + let array_ty = array_type_parametric(args[0].clone(), args[1].clone()).unwrap(); + Some(NodeTemplate::SingleOp( + Noop::new(array_ty).to_extension_op().unwrap().into(), + )) + }, + ); + pass.linearizer() + .register_callback(array_type_def(), copy_discard_array); + Self(pass) + } +} + +impl ComposablePass for LinearizeArrayPass { + type Node = Node; + type Error = ReplaceTypesError; + type Result = bool; + + fn run(&self, hugr: &mut impl HugrMut) -> Result { + self.0.run(hugr) + } +} + +impl LinearizeArrayPass { + /// Returns a new [`LinearizeArrayPass`] that handles all standard extensions. + pub fn new() -> Self { + Self::default() + } + + /// Allows to configure how to clone and discard arrays that are nested + /// inside opaque extension values. + pub fn linearizer(&mut self) -> &mut DelegatingLinearizer { + self.0.linearizer() + } +} + +#[cfg(test)] +mod test { + use hugr_core::builder::ModuleBuilder; + use hugr_core::extension::prelude::{ConstUsize, Noop}; + use hugr_core::ops::handle::NodeHandle; + use hugr_core::ops::{Const, OpType}; + use hugr_core::std_extensions::collections::array::{ + self, array_type, ArrayValue, Direction, FROM, INTO, + }; + use hugr_core::std_extensions::collections::value_array::{ + VArrayFromArray, VArrayRepeat, VArrayScan, VArrayToArray, VArrayValue, + }; + use hugr_core::types::Transformable; + use hugr_core::{ + builder::{Container, DFGBuilder, Dataflow, HugrBuilder}, + extension::prelude::{qb_t, usize_t}, + std_extensions::collections::{ + array::{ + op_builder::{build_all_array_ops, build_all_value_array_ops}, + ArrayRepeat, ArrayScan, + }, + value_array::{self, value_array_type}, + }, + types::{Signature, Type}, + HugrView, + }; + use itertools::Itertools; + use rstest::rstest; + + use crate::{composable::ValidatingPass, ComposablePass}; + + use super::LinearizeArrayPass; + + #[test] + fn all_value_array_ops() { + let sig = Signature::new_endo(Type::EMPTY_TYPEROW); + let mut hugr = build_all_value_array_ops(DFGBuilder::new(sig.clone()).unwrap()) + .finish_hugr() + .unwrap(); + ValidatingPass::new(LinearizeArrayPass::default()) + .run(&mut hugr) + .unwrap(); + + let target_hugr = build_all_array_ops(DFGBuilder::new(sig).unwrap()) + .finish_hugr() + .unwrap(); + for (n1, n2) in hugr.nodes().zip_eq(target_hugr.nodes()) { + assert_eq!(hugr.get_optype(n1), target_hugr.get_optype(n2)); + } + } + + #[rstest] + #[case(usize_t(), 2)] + #[case(qb_t(), 2)] + #[case(value_array_type(4, usize_t()), 2)] + fn repeat(#[case] elem_ty: Type, #[case] size: u64) { + let mut builder = ModuleBuilder::new(); + let repeat_decl = builder + .declare( + "foo", + Signature::new(Type::EMPTY_TYPEROW, elem_ty.clone()).into(), + ) + .unwrap(); + let mut f = builder + .define_function( + "bar", + Signature::new(Type::EMPTY_TYPEROW, value_array_type(size, elem_ty.clone())), + ) + .unwrap(); + let repeat_f = f.load_func(&repeat_decl, &[]).unwrap(); + let repeat = f + .add_dataflow_op(VArrayRepeat::new(elem_ty.clone(), size), [repeat_f]) + .unwrap(); + let [arr] = repeat.outputs_arr(); + f.set_outputs([arr]).unwrap(); + let mut hugr = builder.finish_hugr().unwrap(); + + let pass = LinearizeArrayPass::default(); + ValidatingPass::new(pass.clone()).run(&mut hugr).unwrap(); + let new_repeat: ArrayRepeat = hugr.get_optype(repeat.node()).cast().unwrap(); + let mut new_elem_ty = elem_ty.clone(); + new_elem_ty.transform(&pass.0).unwrap(); + assert_eq!(new_repeat, ArrayRepeat::new(new_elem_ty, size)); + } + + #[rstest] + #[case(usize_t(), qb_t(), 2)] + #[case(usize_t(), value_array_type(4, usize_t()), 2)] + #[case(value_array_type(4, usize_t()), value_array_type(8, usize_t()), 2)] + fn scan(#[case] src_ty: Type, #[case] tgt_ty: Type, #[case] size: u64) { + let mut builder = ModuleBuilder::new(); + let scan_decl = builder + .declare("foo", Signature::new(src_ty.clone(), tgt_ty.clone()).into()) + .unwrap(); + let mut f = builder + .define_function( + "bar", + Signature::new( + value_array_type(size, src_ty.clone()), + value_array_type(size, tgt_ty.clone()), + ), + ) + .unwrap(); + let [arr] = f.input_wires_arr(); + let scan_f = f.load_func(&scan_decl, &[]).unwrap(); + let scan = f + .add_dataflow_op( + VArrayScan::new(src_ty.clone(), tgt_ty.clone(), vec![], size), + [arr, scan_f], + ) + .unwrap(); + let [arr] = scan.outputs_arr(); + f.set_outputs([arr]).unwrap(); + let mut hugr = builder.finish_hugr().unwrap(); + + let pass = LinearizeArrayPass::default(); + ValidatingPass::new(pass.clone()).run(&mut hugr).unwrap(); + let new_scan: ArrayScan = hugr.get_optype(scan.node()).cast().unwrap(); + let mut new_src_ty = src_ty.clone(); + let mut new_tgt_ty = tgt_ty.clone(); + new_src_ty.transform(&pass.0).unwrap(); + new_tgt_ty.transform(&pass.0).unwrap(); + + assert_eq!( + new_scan, + ArrayScan::new(new_src_ty, new_tgt_ty, vec![], size) + ); + } + + #[rstest] + #[case(INTO, usize_t(), 2)] + #[case(FROM, usize_t(), 2)] + #[case(INTO, array_type(4, usize_t()), 2)] + #[case(FROM, array_type(4, usize_t()), 2)] + #[case(INTO, value_array_type(4, usize_t()), 2)] + #[case(FROM, value_array_type(4, usize_t()), 2)] + fn convert(#[case] dir: Direction, #[case] elem_ty: Type, #[case] size: u64) { + let (src, tgt) = match dir { + INTO => ( + value_array_type(size, elem_ty.clone()), + array_type(size, elem_ty.clone()), + ), + FROM => ( + array_type(size, elem_ty.clone()), + value_array_type(size, elem_ty.clone()), + ), + }; + let sig = Signature::new(src, tgt); + let mut builder = DFGBuilder::new(sig).unwrap(); + let [arr] = builder.input_wires_arr(); + let op: OpType = match dir { + INTO => VArrayToArray::new(elem_ty.clone(), size).into(), + FROM => VArrayFromArray::new(elem_ty.clone(), size).into(), + }; + let convert = builder.add_dataflow_op(op, [arr]).unwrap(); + let [arr] = convert.outputs_arr(); + builder.set_outputs(vec![arr]).unwrap(); + let mut hugr = builder.finish_hugr().unwrap(); + + let pass = LinearizeArrayPass::default(); + ValidatingPass::new(pass.clone()).run(&mut hugr).unwrap(); + let new_convert: Noop = hugr.get_optype(convert.node()).cast().unwrap(); + let mut new_elem_ty = elem_ty.clone(); + new_elem_ty.transform(&pass.0).unwrap(); + + assert_eq!(new_convert, Noop::new(array_type(size, new_elem_ty))); + } + + #[rstest] + #[case(value_array_type(2, usize_t()))] + #[case(value_array_type(2, value_array_type(4, usize_t())))] + #[case(value_array_type(2, Type::new_tuple(vec![usize_t(), value_array_type(4, usize_t())])))] + fn implicit_clone(#[case] array_ty: Type) { + let sig = Signature::new(array_ty.clone(), vec![array_ty; 2]); + let mut builder = DFGBuilder::new(sig).unwrap(); + let [arr] = builder.input_wires_arr(); + builder.set_outputs(vec![arr, arr]).unwrap(); + + let mut hugr = builder.finish_hugr().unwrap(); + ValidatingPass::new(LinearizeArrayPass::default()) + .run(&mut hugr) + .unwrap(); + } + + #[rstest] + #[case(value_array_type(2, usize_t()))] + #[case(value_array_type(2, value_array_type(4, usize_t())))] + #[case(value_array_type(2, Type::new_tuple(vec![usize_t(), value_array_type(4, usize_t())])))] + fn implicit_discard(#[case] array_ty: Type) { + let sig = Signature::new(array_ty, Type::EMPTY_TYPEROW); + let mut builder = DFGBuilder::new(sig).unwrap(); + builder.set_outputs(vec![]).unwrap(); + + let mut hugr = builder.finish_hugr().unwrap(); + ValidatingPass::new(LinearizeArrayPass::default()) + .run(&mut hugr) + .unwrap(); + } + + #[test] + fn array_value() { + let mut builder = ModuleBuilder::new(); + let array_v = VArrayValue::new(usize_t(), vec![ConstUsize::new(1).into()]); + let c = builder.add_constant(Const::new(array_v.clone().into())); + + let mut hugr = builder.finish_hugr().unwrap(); + ValidatingPass::new(LinearizeArrayPass::default()) + .run(&mut hugr) + .unwrap(); + + let new_array_v: &ArrayValue = hugr + .get_optype(c.node()) + .as_const() + .unwrap() + .get_custom_value() + .unwrap(); + + assert_eq!(new_array_v.get_element_type(), array_v.get_element_type()); + assert_eq!(new_array_v.get_contents(), array_v.get_contents()); + } + + #[test] + fn array_value_nested() { + let mut builder = ModuleBuilder::new(); + let array_v_inner = VArrayValue::new(usize_t(), vec![ConstUsize::new(1).into()]); + let array_v: array::GenericArrayValue = VArrayValue::new( + value_array_type(1, usize_t()), + vec![array_v_inner.clone().into()], + ); + let c = builder.add_constant(Const::new(array_v.clone().into())); + + let mut hugr = builder.finish_hugr().unwrap(); + ValidatingPass::new(LinearizeArrayPass::default()) + .run(&mut hugr) + .unwrap(); + + let new_array_v: &ArrayValue = hugr + .get_optype(c.node()) + .as_const() + .unwrap() + .get_custom_value() + .unwrap(); + + assert_eq!(new_array_v.get_element_type(), &array_type(1, usize_t())); + assert_eq!( + new_array_v.get_contents()[0], + ArrayValue::new(usize_t(), vec![ConstUsize::new(1).into()]).into() + ); + } +} diff --git a/hugr-passes/src/monomorphize.rs b/hugr-passes/src/monomorphize.rs index cfe2c9514f..6505b274e6 100644 --- a/hugr-passes/src/monomorphize.rs +++ b/hugr-passes/src/monomorphize.rs @@ -335,7 +335,8 @@ mod test { use hugr_core::extension::simple_op::MakeRegisteredOp as _; use hugr_core::std_extensions::arithmetic::int_types::INT_TYPES; use hugr_core::std_extensions::collections; - use hugr_core::std_extensions::collections::array::{array_type_parametric, ArrayOpDef}; + use hugr_core::std_extensions::collections::array::ArrayKind; + use hugr_core::std_extensions::collections::value_array::{VArrayOpDef, ValueArray}; use hugr_core::types::type_param::TypeParam; use itertools::Itertools; @@ -480,23 +481,32 @@ mod test { let mut outer = FunctionBuilder::new( "mainish", Signature::new( - array_type_parametric(sa(n), array_type_parametric(sa(2), usize_t()).unwrap()) - .unwrap(), + ValueArray::ty_parametric( + sa(n), + ValueArray::ty_parametric(sa(2), usize_t()).unwrap(), + ) + .unwrap(), vec![usize_t(); 2], ), ) .unwrap(); - let arr2u = || array_type_parametric(sa(2), usize_t()).unwrap(); + let arr2u = || ValueArray::ty_parametric(sa(2), usize_t()).unwrap(); let pf1t = PolyFuncType::new( [TypeParam::max_nat()], - Signature::new(array_type_parametric(sv(0), arr2u()).unwrap(), usize_t()), + Signature::new( + ValueArray::ty_parametric(sv(0), arr2u()).unwrap(), + usize_t(), + ), ); let mut pf1 = outer.define_function("pf1", pf1t).unwrap(); let pf2t = PolyFuncType::new( [TypeParam::max_nat(), TypeBound::Copyable.into()], - Signature::new(vec![array_type_parametric(sv(0), tv(1)).unwrap()], tv(1)), + Signature::new( + vec![ValueArray::ty_parametric(sv(0), tv(1)).unwrap()], + tv(1), + ), ); let mut pf2 = pf1.define_function("pf2", pf2t).unwrap(); @@ -510,10 +520,10 @@ mod test { let pf2 = { let [inw] = pf2.input_wires_arr(); let [idx] = pf2.call(mono_func.handle(), &[], []).unwrap().outputs_arr(); - let op_def = collections::array::EXTENSION.get_op("get").unwrap(); + let op_def = collections::value_array::EXTENSION.get_op("get").unwrap(); let op = hugr_core::ops::ExtensionOp::new(op_def.clone(), vec![sv(0), tv(1).into()]) .unwrap(); - let [get] = pf2.add_dataflow_op(op, [inw, idx]).unwrap().outputs_arr(); + let [get, _] = pf2.add_dataflow_op(op, [inw, idx]).unwrap().outputs_arr(); let [got] = pf2 .build_unwrap_sum(1, SumType::new([vec![], vec![tv(1)]]), get) .unwrap(); @@ -536,7 +546,7 @@ mod test { .call(pf1.handle(), &[sa(n)], outer.input_wires()) .unwrap() .outputs_arr(); - let popleft = ArrayOpDef::pop_left.to_concrete(arr2u(), n); + let popleft = VArrayOpDef::pop_left.to_concrete(arr2u(), n); let ar2 = outer .add_dataflow_op(popleft.clone(), outer.input_wires()) .unwrap(); diff --git a/hugr-passes/src/replace_types.rs b/hugr-passes/src/replace_types.rs index 05d0168c88..b5b98e8871 100644 --- a/hugr-passes/src/replace_types.rs +++ b/hugr-passes/src/replace_types.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use handlers::list_const; use hugr_core::std_extensions::collections::array::array_type_def; use hugr_core::std_extensions::collections::list::list_type_def; +use hugr_core::std_extensions::collections::value_array::value_array_type_def; use thiserror::Error; use hugr_core::builder::{BuildError, BuildHandle, Dataflow}; @@ -214,6 +215,7 @@ impl Default for ReplaceTypes { let mut res = Self::new_empty(); res.linearize = DelegatingLinearizer::default(); res.replace_consts_parametrized(array_type_def(), handlers::array_const); + res.replace_consts_parametrized(value_array_type_def(), handlers::value_array_const); res.replace_consts_parametrized(list_type_def(), list_const); res } @@ -590,6 +592,7 @@ impl From<&OpDef> for ParametricOp { mod test { use std::sync::Arc; + use crate::replace_types::handlers::generic_array_const; use hugr_core::builder::{ inout_sig, BuildError, Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, FunctionBuilder, HugrBuilder, ModuleBuilder, SubContainer, TailLoopBuilder, @@ -600,14 +603,20 @@ mod test { use hugr_core::extension::{simple_op::MakeExtensionOp, TypeDefBound, Version}; use hugr_core::hugr::hugrmut::HugrMut; use hugr_core::hugr::{IdentList, ValidationError}; + use hugr_core::ops::constant::CustomConst; use hugr_core::ops::constant::OpaqueValue; use hugr_core::ops::{ExtensionOp, NamedOp, OpTrait, OpType, Tag, Value}; - use hugr_core::std_extensions::arithmetic::conversions::ConvertOpDef; - use hugr_core::std_extensions::arithmetic::int_types::{ConstInt, INT_TYPES}; - use hugr_core::std_extensions::collections::{ - array::{array_type, array_type_def, ArrayOp, ArrayOpDef, ArrayValue}, - list::{list_type, list_type_def, ListOp, ListValue}, + use hugr_core::std_extensions::arithmetic::int_types::ConstInt; + use hugr_core::std_extensions::arithmetic::{conversions::ConvertOpDef, int_types::INT_TYPES}; + use hugr_core::std_extensions::collections::array::Array; + use hugr_core::std_extensions::collections::array::{ArrayKind, GenericArrayValue}; + use hugr_core::std_extensions::collections::list::{ + list_type, list_type_def, ListOp, ListValue, }; + use hugr_core::std_extensions::collections::value_array::{ + value_array_type, VArrayOp, VArrayOpDef, VArrayValue, ValueArray, + }; + use hugr_core::types::{PolyFuncType, Signature, SumType, Type, TypeArg, TypeBound, TypeRow}; use hugr_core::{type_row, Extension, HugrView}; use itertools::Itertools; @@ -680,7 +689,7 @@ mod test { new: impl Fn(Signature) -> Result, ) -> T { let mut dfb = new(Signature::new( - vec![array_type(64, elem_ty.clone()), i64_t()], + vec![value_array_type(64, elem_ty.clone()), i64_t()], elem_ty.clone(), )) .unwrap(); @@ -689,8 +698,11 @@ mod test { .add_dataflow_op(ConvertOpDef::itousize.without_log_width(), [idx]) .unwrap() .outputs_arr(); - let [opt] = dfb - .add_dataflow_op(ArrayOpDef::get.to_concrete(elem_ty.clone(), 64), [val, idx]) + let [opt, _] = dfb + .add_dataflow_op( + VArrayOpDef::get.to_concrete(elem_ty.clone(), 64), + [val, idx], + ) .unwrap() .outputs_arr(); let [res] = dfb @@ -706,7 +718,7 @@ mod test { lw.replace_type(pv.instantiate([bool_t().into()]).unwrap(), i64_t()); lw.replace_parametrized_type( pv, - Box::new(|args: &[TypeArg]| Some(array_type(64, just_elem_type(args).clone()))), + Box::new(|args: &[TypeArg]| Some(value_array_type(64, just_elem_type(args).clone()))), ); lw.replace_op( &read_op(ext, bool_t()), @@ -835,10 +847,10 @@ mod test { // The PackedVec> becomes an array let [array_get] = ext_ops .into_iter() - .filter_map(|e| ArrayOp::from_extension_op(e).ok()) + .filter_map(|e| VArrayOp::from_extension_op(e).ok()) .collect_array() .unwrap(); - assert_eq!(array_get, ArrayOpDef::get.to_concrete(i64_t(), 64)); + assert_eq!(array_get, VArrayOpDef::get.to_concrete(i64_t(), 64)); } #[test] @@ -868,7 +880,7 @@ mod test { // 1. Lower List to Array<10, T> UNLESS T is usize_t() or i64_t lowerer.replace_parametrized_type(list_type_def(), |args| { let ty = just_elem_type(args); - (![usize_t(), i64_t()].contains(ty)).then_some(array_type(10, ty.clone())) + (![usize_t(), i64_t()].contains(ty)).then_some(value_array_type(10, ty.clone())) }); { let mut h = backup.clone(); @@ -876,7 +888,7 @@ mod test { let sig = h.signature(h.root()).unwrap(); assert_eq!( sig.input(), - &TypeRow::from(vec![list_type(usize_t()), array_type(10, bool_t())]) + &TypeRow::from(vec![list_type(usize_t()), value_array_type(10, bool_t())]) ); assert_eq!(sig.input(), sig.output()); } @@ -898,7 +910,7 @@ mod test { let sig = h.signature(h.root()).unwrap(); assert_eq!( sig.input(), - &TypeRow::from(vec![list_type(i64_t()), array_type(10, bool_t())]) + &TypeRow::from(vec![list_type(i64_t()), value_array_type(10, bool_t())]) ); assert_eq!(sig.input(), sig.output()); // This will have to update inside the Const @@ -915,7 +927,7 @@ mod test { let mut h = backup; lowerer.replace_parametrized_type( list_type_def(), - Box::new(|args: &[TypeArg]| Some(array_type(4, just_elem_type(args).clone()))), + Box::new(|args: &[TypeArg]| Some(value_array_type(4, just_elem_type(args).clone()))), ); lowerer.replace_consts_parametrized(list_type_def(), |opaq, repl| { // First recursively transform the contents @@ -925,7 +937,7 @@ mod test { let lv = opaq.value().downcast_ref::().unwrap(); Ok(Some( - ArrayValue::new(lv.get_element_type().clone(), lv.get_contents().to_vec()).into(), + VArrayValue::new(lv.get_element_type().clone(), lv.get_contents().to_vec()).into(), )) }); lowerer.run(&mut h).unwrap(); @@ -934,7 +946,10 @@ mod test { h.get_optype(pred.node()) .as_load_constant() .map(|lc| lc.constant_type()), - Some(&Type::new_sum(vec![Type::from(array_type(4, i64_t())); 2])) + Some(&Type::new_sum(vec![ + Type::from(value_array_type(4, i64_t())); + 2 + ])) ); } @@ -1023,17 +1038,19 @@ mod test { } #[rstest] - #[case(&[])] - #[case(&[3])] - #[case(&[5,7,11,13,17,19])] - fn array_const(#[case] vals: &[u64]) { - use super::handlers::array_const; - let mut dfb = DFGBuilder::new(inout_sig( - type_row![], - array_type(vals.len() as _, usize_t()), - )) - .unwrap(); - let c = dfb.add_load_value(ArrayValue::new( + #[case(&[], Array)] + #[case(&[], ValueArray)] + #[case(&[3], Array)] + #[case(&[3], ValueArray)] + #[case(&[5,7,11,13,17,19], Array)] + #[case(&[5,7,11,13,17,19], ValueArray)] + fn array_const(#[case] vals: &[u64], #[case] _kind: AK) + where + GenericArrayValue: CustomConst, + { + let mut dfb = + DFGBuilder::new(inout_sig(type_row![], AK::ty(vals.len() as _, usize_t()))).unwrap(); + let c = dfb.add_load_value(GenericArrayValue::::new( usize_t(), vals.iter().map(|u| ConstUsize::new(*u).into()), )); @@ -1053,7 +1070,7 @@ mod test { matches!(h.validate(), Err(ValidationError::IncompatiblePorts {from, to, ..}) if backup.get_optype(from).is_const() && to == c.node()) ); - repl.replace_consts_parametrized(array_type_def(), array_const); + repl.replace_consts_parametrized(AK::type_def(), generic_array_const::); let mut h = backup; repl.run(&mut h).unwrap(); h.validate().unwrap(); diff --git a/hugr-passes/src/replace_types/handlers.rs b/hugr-passes/src/replace_types/handlers.rs index 573188340b..848c09682a 100644 --- a/hugr-passes/src/replace_types/handlers.rs +++ b/hugr-passes/src/replace_types/handlers.rs @@ -3,15 +3,18 @@ use hugr_core::builder::{endo_sig, inout_sig, DFGBuilder, Dataflow, DataflowHugr}; use hugr_core::extension::prelude::{option_type, UnwrapBuilder}; +use hugr_core::ops::constant::CustomConst; use hugr_core::ops::{constant::OpaqueValue, Value}; use hugr_core::ops::{OpTrait, OpType, Tag}; use hugr_core::std_extensions::arithmetic::conversions::ConvertOpDef; use hugr_core::std_extensions::arithmetic::int_ops::IntOpDef; use hugr_core::std_extensions::arithmetic::int_types::{ConstInt, INT_TYPES}; use hugr_core::std_extensions::collections::array::{ - array_type, ArrayOpDef, ArrayRepeat, ArrayScan, ArrayValue, + array_type, Array, ArrayClone, ArrayDiscard, ArrayKind, ArrayOpBuilder, GenericArrayOpDef, + GenericArrayRepeat, GenericArrayScan, GenericArrayValue, }; use hugr_core::std_extensions::collections::list::ListValue; +use hugr_core::std_extensions::collections::value_array::ValueArray; use hugr_core::type_row; use hugr_core::types::{SumType, Transformable, Type, TypeArg}; use itertools::Itertools; @@ -43,14 +46,17 @@ pub fn list_const( Ok(Some(ListValue::new(elem_t, vals).into())) } -/// Handler for [ArrayValue] constants that recursively +/// Handler for [GenericArrayValue] constants that recursively /// [ReplaceTypes::change_value]s the elements of the list. /// Included in [ReplaceTypes::default]. -pub fn array_const( +pub fn generic_array_const( val: &OpaqueValue, repl: &ReplaceTypes, -) -> Result, ReplaceTypesError> { - let Some(av) = val.value().downcast_ref::() else { +) -> Result, ReplaceTypesError> +where + GenericArrayValue: CustomConst, +{ + let Some(av) = val.value().downcast_ref::>() else { return Ok(None); }; let mut elem_t = av.get_element_type().clone(); @@ -63,14 +69,37 @@ pub fn array_const( for v in vals.iter_mut() { repl.change_value(v)?; } - Ok(Some(ArrayValue::new(elem_t, vals).into())) + Ok(Some(GenericArrayValue::::new(elem_t, vals).into())) +} + +/// Handler for [ArrayValue] constants that recursively +/// [ReplaceTypes::change_value]s the elements of the list. +/// Included in [ReplaceTypes::default]. +/// +/// [ArrayValue]: hugr_core::std_extensions::collections::array::ArrayValue +pub fn array_const( + val: &OpaqueValue, + repl: &ReplaceTypes, +) -> Result, ReplaceTypesError> { + generic_array_const::(val, repl) +} + +/// Handler for [VArrayValue] constants that recursively +/// [ReplaceTypes::change_value]s the elements of the list. +/// Included in [ReplaceTypes::default]. +/// +/// [VArrayValue]: hugr_core::std_extensions::collections::value_array::VArrayValue +pub fn value_array_const( + val: &OpaqueValue, + repl: &ReplaceTypes, +) -> Result, ReplaceTypesError> { + generic_array_const::(val, repl) } /// Handler for copying/discarding arrays if their elements have become linear. -/// Included in [ReplaceTypes::default] and [DelegatingLinearizer::default]. /// -/// [DelegatingLinearizer::default]: super::DelegatingLinearizer::default -pub fn linearize_array( +/// Generic over the concrete array implementation. +pub fn linearize_generic_array( args: &[TypeArg], num_outports: usize, lin: &CallbackHandler, @@ -92,8 +121,8 @@ pub fn linearize_array( dfb.finish_hugr_with_outputs([ret]).unwrap() }; // Now array.scan that over the input array to get an array of unit (which can be discarded) - let array_scan = ArrayScan::new(ty.clone(), Type::UNIT, vec![], *n); - let in_type = array_type(*n, ty.clone()); + let array_scan = GenericArrayScan::::new(ty.clone(), Type::UNIT, vec![], *n); + let in_type = AK::ty(*n, ty.clone()); return Ok(NodeTemplate::CompoundOp(Box::new({ let mut dfb = DFGBuilder::new(inout_sig(in_type, type_row![])).unwrap(); let [in_array] = dfb.input_wires_arr(); @@ -101,14 +130,18 @@ pub fn linearize_array( hugr: Box::new(map_fn), }); // scan has one output, an array of unit, so just ignore/discard that - dfb.add_dataflow_op(array_scan, [in_array, map_fn]).unwrap(); + let unit_arr = dfb + .add_dataflow_op(array_scan, [in_array, map_fn]) + .unwrap() + .out_wire(0); + AK::build_discard(&mut dfb, Type::UNIT, *n, unit_arr).unwrap(); dfb.finish_hugr_with_outputs([]).unwrap() }))); }; // The num_outports>1 case will simplify, and unify with the previous, when we have a // more general ArrayScan https://github.com/CQCL/hugr/issues/2041. In the meantime: let num_new = num_outports - 1; - let array_ty = array_type(*n, ty.clone()); + let array_ty = AK::ty(*n, ty.clone()); let mut dfb = DFGBuilder::new(inout_sig( array_ty.clone(), vec![array_ty.clone(); num_outports], @@ -126,7 +159,7 @@ pub fn linearize_array( .unwrap(); dfb.finish_hugr_with_outputs(none.outputs()).unwrap() }; - let repeats = vec![ArrayRepeat::new(option_ty.clone(), *n); num_new]; + let repeats = vec![GenericArrayRepeat::::new(option_ty.clone(), *n); num_new]; let fn_none = dfb.add_load_value(Value::function(fn_none).unwrap()); repeats .into_iter() @@ -140,7 +173,7 @@ pub fn linearize_array( // 2. use a scan through the input array, copying the element num_outputs times; // return the first copy, and put each of the other copies into one of the array