hugr_llvm/extension/collections/
borrow_array.rs

1//! Codegen for prelude borrow array operations.
2//!
3//! A `borrow_array<n, T>` is lowered to a fat pointer `{ptr, mask_ptr, usize}` that is
4//! allocated to at least `n * sizeof(T)` bytes. The second pointer is a bit-packed mask
5//! storing which array elements have been borrowed (1=borrowed, 0=available). It should
6//! be allocated to at least `ceil(n / sizeof(usize)) * sizeof(usize)` bytes. The extra
7//! `usize` is an offset pointing to the first element, i.e. the first element is at address
8//! `ptr + offset * sizeof(T)`.
9//!
10//! The rationale behind the additional offset is the `pop_left` operation which bumps
11//! the offset instead of mutating the pointer. This way, we can still free the original
12//! pointer when the array is discarded after a pop.
13//!
14//! We provide utility functions [`barray_fat_pointer_ty`], [`build_barray_fat_pointer`], and
15//! [`decompose_barray_fat_pointer`] to work with borrow-array fat pointers.
16//!
17//! The [`DefaultBorrowArrayCodegen`] extension allocates all arrays on the heap using the
18//! standard libc `malloc` and `free` functions. This behaviour can be customised
19//! by providing a different implementation for [`BorrowArrayCodegen::emit_allocate_array`]
20//! and [`BorrowArrayCodegen::emit_free_array`].
21use std::iter;
22use std::sync::LazyLock;
23
24use anyhow::{Ok, Result, anyhow};
25use hugr_core::extension::prelude::{ConstError, option_type, usize_t};
26use hugr_core::extension::simple_op::{MakeExtensionOp, MakeOpDef, MakeRegisteredOp};
27use hugr_core::ops::DataflowOpTrait;
28use hugr_core::std_extensions::collections::array;
29use hugr_core::std_extensions::collections::borrow_array::{
30    self, BArrayClone, BArrayDiscard, BArrayFromArray, BArrayFromArrayDef, BArrayOp, BArrayOpDef,
31    BArrayRepeat, BArrayScan, BArrayToArray, BArrayToArrayDef, BArrayUnsafeOp, BArrayUnsafeOpDef,
32    borrow_array_type,
33};
34use hugr_core::types::{TypeArg, TypeEnum};
35use hugr_core::{HugrView, Node};
36use inkwell::builder::Builder;
37use inkwell::intrinsics::Intrinsic;
38use inkwell::types::{BasicType, BasicTypeEnum, IntType, StructType};
39use inkwell::values::{
40    BasicValue as _, BasicValueEnum, CallableValue, IntValue, PointerValue, StructValue,
41};
42use inkwell::{AddressSpace, IntPredicate};
43use itertools::Itertools;
44
45use crate::emit::emit_value;
46use crate::emit::func::get_or_make_function;
47use crate::emit::libc::{emit_libc_free, emit_libc_malloc};
48use crate::extension::PreludeCodegen;
49use crate::extension::collections::array::{build_array_fat_pointer, decompose_array_fat_pointer};
50use crate::{CodegenExtension, CodegenExtsBuilder};
51use crate::{
52    emit::{EmitFuncContext, RowPromise, deaggregate_call_result},
53    types::{HugrType, TypingSession},
54};
55
56static ERR_ALREADY_BORROWED: LazyLock<ConstError> = LazyLock::new(|| ConstError {
57    signal: 2,
58    message: "Array element is already borrowed".to_string(),
59});
60
61static ERR_NOT_BORROWED: LazyLock<ConstError> = LazyLock::new(|| ConstError {
62    signal: 2,
63    message: "Array already contains an element at this index".to_string(),
64});
65
66static ERR_OUT_OF_BOUNDS: LazyLock<ConstError> = LazyLock::new(|| ConstError {
67    signal: 2,
68    message: "Index out of bounds".to_string(),
69});
70
71static ERR_NOT_ALL_BORROWED: LazyLock<ConstError> = LazyLock::new(|| ConstError {
72    signal: 2,
73    message: "Array contains non-borrowed elements and cannot be discarded".to_string(),
74});
75
76static ERR_SOME_BORROWED: LazyLock<ConstError> = LazyLock::new(|| ConstError {
77    signal: 2,
78    message: "Some array elements have been borrowed".to_string(),
79});
80
81impl<'a, H: HugrView<Node = Node> + 'a> CodegenExtsBuilder<'a, H> {
82    /// Add a [`BorrowArrayCodegenExtension`] to the given [`CodegenExtsBuilder`] using
83    /// [`DefaultBorrowArrayCodegen`] as the implementation.
84    #[must_use]
85    pub fn add_default_borrow_array_extensions(self, pcg: impl PreludeCodegen + 'a) -> Self {
86        self.add_borrow_array_extensions(DefaultBorrowArrayCodegen(pcg))
87    }
88
89    /// Add a [`BorrowArrayCodegenExtension`] to the given [`CodegenExtsBuilder`] using `ccg`
90    /// as the implementation.
91    pub fn add_borrow_array_extensions(self, ccg: impl BorrowArrayCodegen + 'a) -> Self {
92        self.add_extension(BorrowArrayCodegenExtension::from(ccg))
93    }
94}
95
96/// A helper trait for customising the lowering of [`borrow_array`], including its
97/// types, [`hugr_core::ops::constant::CustomConst`]s, and ops.
98///
99/// By default, all arrays are allocated on the heap using the standard libc `malloc`
100/// and `free` functions. This behaviour can be customised by providing a different
101/// implementation for [`BorrowArrayCodegen::emit_allocate_array`] and
102/// [`BorrowArrayCodegen::emit_free_array`].
103///
104/// See [`crate::extension::collections::borrow_array`] for details.
105pub trait BorrowArrayCodegen: Clone {
106    /// Emit instructions to halt execution with the error `err`.
107    ///
108    /// This should be consistent with the panic implementation from the [PreludeCodegen].
109    fn emit_panic<H: HugrView<Node = Node>>(
110        &self,
111        ctx: &mut EmitFuncContext<H>,
112        err: BasicValueEnum,
113    ) -> Result<()>;
114
115    /// Emit an allocation of `size` bytes and return the corresponding pointer.
116    ///
117    /// The default implementation allocates on the heap by emitting a call to the
118    /// standard libc `malloc` function.
119    fn emit_allocate_array<'c, H: HugrView<Node = Node>>(
120        &self,
121        ctx: &mut EmitFuncContext<'c, '_, H>,
122        size: IntValue<'c>,
123    ) -> Result<PointerValue<'c>> {
124        let ptr = emit_libc_malloc(ctx, size.into())?;
125        Ok(ptr.into_pointer_value())
126    }
127
128    /// Emit an deallocation of a pointer.
129    ///
130    /// The default implementation emits a call to the standard libc `free` function.
131    fn emit_free_array<'c, H: HugrView<Node = Node>>(
132        &self,
133        ctx: &mut EmitFuncContext<'c, '_, H>,
134        ptr: PointerValue<'c>,
135    ) -> Result<()> {
136        emit_libc_free(ctx, ptr.into())
137    }
138
139    /// Return the llvm type of [`hugr_core::std_extensions::collections::borrow_array::BORROW_ARRAY_TYPENAME`].
140    fn array_type<'c>(
141        &self,
142        session: &TypingSession<'c, '_>,
143        elem_ty: BasicTypeEnum<'c>,
144        _size: u64,
145    ) -> impl BasicType<'c> {
146        barray_fat_pointer_ty(session, elem_ty)
147    }
148
149    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayValue`].
150    fn emit_array_value<'c, H: HugrView<Node = Node>>(
151        &self,
152        ctx: &mut EmitFuncContext<'c, '_, H>,
153        value: &borrow_array::BArrayValue,
154    ) -> Result<BasicValueEnum<'c>> {
155        emit_barray_value(self, ctx, value)
156    }
157
158    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayOp`].
159    fn emit_array_op<'c, H: HugrView<Node = Node>>(
160        &self,
161        ctx: &mut EmitFuncContext<'c, '_, H>,
162        op: BArrayOp,
163        inputs: Vec<BasicValueEnum<'c>>,
164        outputs: RowPromise<'c>,
165    ) -> Result<()> {
166        emit_barray_op(self, ctx, op, inputs, outputs)
167    }
168
169    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayUnsafeOp`].
170    fn emit_array_unsafe_op<'c, H: HugrView<Node = Node>>(
171        &self,
172        ctx: &mut EmitFuncContext<'c, '_, H>,
173        op: BArrayUnsafeOp,
174        inputs: Vec<BasicValueEnum<'c>>,
175        outputs: RowPromise<'c>,
176    ) -> Result<()> {
177        emit_barray_unsafe_op(self, ctx, op, inputs, outputs)
178    }
179
180    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayClone`] operation.
181    fn emit_array_clone<'c, H: HugrView<Node = Node>>(
182        &self,
183        ctx: &mut EmitFuncContext<'c, '_, H>,
184        op: BArrayClone,
185        array_v: BasicValueEnum<'c>,
186    ) -> Result<(BasicValueEnum<'c>, BasicValueEnum<'c>)> {
187        emit_clone_op(self, ctx, op, array_v)
188    }
189
190    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayDiscard`] operation.
191    fn emit_array_discard<'c, H: HugrView<Node = Node>>(
192        &self,
193        ctx: &mut EmitFuncContext<'c, '_, H>,
194        op: BArrayDiscard,
195        array_v: BasicValueEnum<'c>,
196    ) -> Result<()> {
197        emit_barray_discard(self, ctx, op, array_v)
198    }
199
200    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayRepeat`] op.
201    fn emit_array_repeat<'c, H: HugrView<Node = Node>>(
202        &self,
203        ctx: &mut EmitFuncContext<'c, '_, H>,
204        op: BArrayRepeat,
205        func: BasicValueEnum<'c>,
206    ) -> Result<BasicValueEnum<'c>> {
207        emit_repeat_op(self, ctx, op, func)
208    }
209
210    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayScan`] op.
211    ///
212    /// Returns the resulting array and the final values of the accumulators.
213    fn emit_array_scan<'c, H: HugrView<Node = Node>>(
214        &self,
215        ctx: &mut EmitFuncContext<'c, '_, H>,
216        op: BArrayScan,
217        src_array: BasicValueEnum<'c>,
218        func: BasicValueEnum<'c>,
219        initial_accs: &[BasicValueEnum<'c>],
220    ) -> Result<(BasicValueEnum<'c>, Vec<BasicValueEnum<'c>>)> {
221        emit_scan_op(
222            self,
223            ctx,
224            op,
225            src_array.into_struct_value(),
226            func,
227            initial_accs,
228        )
229    }
230
231    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayToArray`].
232    fn emit_to_array_op<'c, H: HugrView<Node = Node>>(
233        &self,
234        ctx: &mut EmitFuncContext<'c, '_, H>,
235        op: BArrayToArray,
236        barray_v: BasicValueEnum<'c>,
237    ) -> Result<BasicValueEnum<'c>> {
238        emit_to_array_op(self, ctx, op, barray_v)
239    }
240
241    /// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayFromArray`].
242    fn emit_from_array_op<'c, H: HugrView<Node = Node>>(
243        &self,
244        ctx: &mut EmitFuncContext<'c, '_, H>,
245        op: BArrayFromArray,
246        array_v: BasicValueEnum<'c>,
247    ) -> Result<BasicValueEnum<'c>> {
248        emit_from_array_op(self, ctx, op, array_v)
249    }
250}
251
252/// A trivial implementation of [`BorrowArrayCodegen`] which passes all methods
253/// through to their default implementations.
254#[derive(Default, Clone)]
255pub struct DefaultBorrowArrayCodegen<PCG>(PCG);
256
257impl<PCG: PreludeCodegen> BorrowArrayCodegen for DefaultBorrowArrayCodegen<PCG> {
258    fn emit_panic<H: HugrView<Node = Node>>(
259        &self,
260        ctx: &mut EmitFuncContext<H>,
261        err: BasicValueEnum,
262    ) -> Result<()> {
263        self.0.emit_panic(ctx, err)
264    }
265}
266
267#[derive(Clone, Debug, Default)]
268pub struct BorrowArrayCodegenExtension<CCG>(CCG);
269
270impl<CCG: BorrowArrayCodegen> BorrowArrayCodegenExtension<CCG> {
271    pub fn new(ccg: CCG) -> Self {
272        Self(ccg)
273    }
274}
275
276impl<CCG: BorrowArrayCodegen> From<CCG> for BorrowArrayCodegenExtension<CCG> {
277    fn from(ccg: CCG) -> Self {
278        Self::new(ccg)
279    }
280}
281
282impl<CCG: BorrowArrayCodegen> CodegenExtension for BorrowArrayCodegenExtension<CCG> {
283    fn add_extension<'a, H: HugrView<Node = Node> + 'a>(
284        self,
285        builder: CodegenExtsBuilder<'a, H>,
286    ) -> CodegenExtsBuilder<'a, H>
287    where
288        Self: 'a,
289    {
290        builder
291            .custom_type(
292                (
293                    borrow_array::EXTENSION_ID,
294                    borrow_array::BORROW_ARRAY_TYPENAME,
295                ),
296                {
297                    let ccg = self.0.clone();
298                    move |ts, hugr_type| {
299                        let [TypeArg::BoundedNat(n), TypeArg::Runtime(ty)] = hugr_type.args()
300                        else {
301                            return Err(anyhow!("Invalid type args for array type"));
302                        };
303                        let elem_ty = ts.llvm_type(ty)?;
304                        Ok(ccg.array_type(&ts, elem_ty, *n).as_basic_type_enum())
305                    }
306                },
307            )
308            .custom_const::<borrow_array::BArrayValue>({
309                let ccg = self.0.clone();
310                move |context, k| ccg.emit_array_value(context, k)
311            })
312            .simple_extension_op::<BArrayOpDef>({
313                let ccg = self.0.clone();
314                move |context, args, _| {
315                    ccg.emit_array_op(
316                        context,
317                        BArrayOp::from_extension_op(args.node().as_ref())?,
318                        args.inputs,
319                        args.outputs,
320                    )
321                }
322            })
323            .simple_extension_op::<BArrayUnsafeOpDef>({
324                let ccg = self.0.clone();
325                move |context, args, _| {
326                    ccg.emit_array_unsafe_op(
327                        context,
328                        BArrayUnsafeOp::from_extension_op(args.node().as_ref())?,
329                        args.inputs,
330                        args.outputs,
331                    )
332                }
333            })
334            .extension_op(borrow_array::EXTENSION_ID, array::ARRAY_CLONE_OP_ID, {
335                let ccg = self.0.clone();
336                move |context, args| {
337                    let arr = args.inputs[0];
338                    let op = BArrayClone::from_extension_op(args.node().as_ref())?;
339                    let (arr1, arr2) = ccg.emit_array_clone(context, op, arr)?;
340                    args.outputs.finish(context.builder(), [arr1, arr2])
341                }
342            })
343            .extension_op(borrow_array::EXTENSION_ID, array::ARRAY_DISCARD_OP_ID, {
344                let ccg = self.0.clone();
345                move |context, args| {
346                    let arr = args.inputs[0];
347                    let op = BArrayDiscard::from_extension_op(args.node().as_ref())?;
348                    ccg.emit_array_discard(context, op, arr)?;
349                    args.outputs.finish(context.builder(), [])
350                }
351            })
352            .extension_op(borrow_array::EXTENSION_ID, array::ARRAY_REPEAT_OP_ID, {
353                let ccg = self.0.clone();
354                move |context, args| {
355                    let func = args.inputs[0];
356                    let op = BArrayRepeat::from_extension_op(args.node().as_ref())?;
357                    let arr = ccg.emit_array_repeat(context, op, func)?;
358                    args.outputs.finish(context.builder(), [arr])
359                }
360            })
361            .extension_op(borrow_array::EXTENSION_ID, array::ARRAY_SCAN_OP_ID, {
362                let ccg = self.0.clone();
363                move |context, args| {
364                    let src_array = args.inputs[0];
365                    let func = args.inputs[1];
366                    let initial_accs = &args.inputs[2..];
367                    let op = BArrayScan::from_extension_op(args.node().as_ref())?;
368                    let (tgt_array, final_accs) =
369                        ccg.emit_array_scan(context, op, src_array, func, initial_accs)?;
370                    args.outputs
371                        .finish(context.builder(), iter::once(tgt_array).chain(final_accs))
372                }
373            })
374            .extension_op(
375                borrow_array::EXTENSION_ID,
376                BArrayToArrayDef::new().opdef_id(),
377                {
378                    let ccg = self.0.clone();
379                    move |context, args| {
380                        let barray = args.inputs[0];
381                        let op = BArrayToArray::from_extension_op(args.node().as_ref())?;
382                        let array = ccg.emit_to_array_op(context, op, barray)?;
383                        args.outputs.finish(context.builder(), [array])
384                    }
385                },
386            )
387            .extension_op(
388                borrow_array::EXTENSION_ID,
389                BArrayFromArrayDef::new().opdef_id(),
390                {
391                    let ccg = self.0.clone();
392                    move |context, args| {
393                        let array = args.inputs[0];
394                        let op = BArrayFromArray::from_extension_op(args.node().as_ref())?;
395                        let barray = ccg.emit_from_array_op(context, op, array)?;
396                        args.outputs.finish(context.builder(), [barray])
397                    }
398                },
399            )
400    }
401}
402
403fn usize_ty<'c>(ts: &TypingSession<'c, '_>) -> IntType<'c> {
404    ts.llvm_type(&usize_t())
405        .expect("Prelude codegen is registered")
406        .into_int_type()
407}
408
409/// Returns the LLVM representation of a borrow array value as a fat pointer.
410#[must_use]
411pub fn barray_fat_pointer_ty<'c>(
412    session: &TypingSession<'c, '_>,
413    elem_ty: BasicTypeEnum<'c>,
414) -> StructType<'c> {
415    let iw_ctx = session.iw_context();
416    let usize_t = usize_ty(session);
417    iw_ctx.struct_type(
418        &[
419            // Pointer to the first array element
420            elem_ty.ptr_type(AddressSpace::default()).into(),
421            // Pointer to the bitarray mask storing whether values have been borrowed
422            usize_t.ptr_type(AddressSpace::default()).into(),
423            // Offset
424            usize_t.into(),
425        ],
426        false,
427    )
428}
429
430/// Constructs a borrow array fat pointer value.
431pub fn build_barray_fat_pointer<'c, H: HugrView<Node = Node>>(
432    ctx: &mut EmitFuncContext<'c, '_, H>,
433    BArrayFatPtrComponents {
434        elems_ptr,
435        mask_ptr,
436        offset,
437    }: BArrayFatPtrComponents<'c>,
438) -> Result<StructValue<'c>> {
439    let array_ty = barray_fat_pointer_ty(
440        &ctx.typing_session(),
441        elems_ptr.get_type().get_element_type().try_into().unwrap(),
442    );
443    let array_v = array_ty.get_poison();
444    let array_v =
445        ctx.builder()
446            .build_insert_value(array_v, elems_ptr.as_basic_value_enum(), 0, "")?;
447    let array_v =
448        ctx.builder()
449            .build_insert_value(array_v, mask_ptr.as_basic_value_enum(), 1, "")?;
450    let array_v = ctx
451        .builder()
452        .build_insert_value(array_v, offset.as_basic_value_enum(), 2, "")?;
453    Ok(array_v.into_struct_value())
454}
455
456pub struct BArrayFatPtrComponents<'a> {
457    pub elems_ptr: PointerValue<'a>,
458    pub mask_ptr: PointerValue<'a>,
459    pub offset: IntValue<'a>,
460}
461
462/// Returns the underlying pointer, mask and offset stored in a fat borrow array pointer.
463pub fn decompose_barray_fat_pointer<'c>(
464    builder: &Builder<'c>,
465    array_v: BasicValueEnum<'c>,
466) -> Result<BArrayFatPtrComponents<'c>> {
467    let array_v = array_v.into_struct_value();
468    let elems_ptr = builder
469        .build_extract_value(array_v, 0, "array_ptr")?
470        .into_pointer_value();
471    let mask_ptr = builder
472        .build_extract_value(array_v, 1, "array_mask_ptr")?
473        .into_pointer_value();
474    let offset = builder
475        .build_extract_value(array_v, 2, "array_offset")?
476        .into_int_value();
477    Ok(BArrayFatPtrComponents {
478        elems_ptr,
479        mask_ptr,
480        offset,
481    })
482}
483
484/// Helper function to allocate a typed array of `num_elems` elements of type `elem_ty`.
485/// Returns the pointer to the first element and the size in memory of the allocation.
486fn alloc_typed_array<'c, H: HugrView<Node = Node>>(
487    ccg: &impl BorrowArrayCodegen,
488    ctx: &mut EmitFuncContext<'c, '_, H>,
489    elem_ty: BasicTypeEnum<'c>,
490    num_elems: IntValue<'c>,
491) -> Result<(PointerValue<'c>, IntValue<'c>)> {
492    let size = ctx
493        .builder()
494        .build_int_mul(num_elems, elem_ty.size_of().unwrap(), "array_size")?;
495    let ptr = ccg.emit_allocate_array(ctx, size)?;
496    Ok((
497        ctx.builder()
498            .build_bit_cast(ptr, elem_ty.ptr_type(AddressSpace::default()), "")?
499            .into_pointer_value(),
500        size,
501    ))
502}
503
504/// Helper function to allocate a fat borrow array pointer.
505///
506/// Returns a pointer and a struct:
507/// * The pointer points to the first element of the array (i.e. it is of type `elem_ty.ptr_type()`).
508/// * The struct is the fat pointer that stores also the pointer to the mask and an additional offset (initialised to 0).
509pub fn build_barray_alloc<'c, H: HugrView<Node = Node>>(
510    ctx: &mut EmitFuncContext<'c, '_, H>,
511    ccg: &impl BorrowArrayCodegen,
512    elem_ty: BasicTypeEnum<'c>,
513    size: u64,
514    set_borrowed: bool,
515) -> Result<(PointerValue<'c>, StructValue<'c>)> {
516    let usize_t = usize_ty(&ctx.typing_session());
517    let length = usize_t.const_int(size, false);
518    let (elems_ptr, _) = alloc_typed_array(ccg, ctx, elem_ty, length)?;
519
520    // Mask is bit-packed into an array of values of type usize
521    let mask_length = usize_t.const_int(size.div_ceil(usize_t.get_bit_width() as u64), false);
522    let (mask_ptr, mask_size_value) = alloc_typed_array(ccg, ctx, usize_t.into(), mask_length)?;
523    fill_mask(ctx, mask_ptr, mask_size_value, set_borrowed)?;
524
525    let offset = usize_t.const_zero();
526    let array_v = build_barray_fat_pointer(
527        ctx,
528        BArrayFatPtrComponents {
529            elems_ptr,
530            mask_ptr,
531            offset,
532        },
533    )?;
534    Ok((elems_ptr, array_v))
535}
536
537/// Emits instructions to fill the entire mask with a bit value.
538fn fill_mask<H: HugrView<Node = Node>>(
539    ctx: &mut EmitFuncContext<H>,
540    mask_ptr: PointerValue,
541    size: IntValue,
542    value: bool,
543) -> Result<()> {
544    let memset = Intrinsic::find("llvm.memset")
545        .unwrap()
546        .get_declaration(
547            ctx.get_current_module(),
548            &[mask_ptr.get_type().into(), size.get_type().into()],
549        )
550        .unwrap();
551    let i8t = ctx.iw_context().i8_type(); // Value to fill with is always this size
552    let val = if value {
553        i8t.const_all_ones()
554    } else {
555        i8t.const_zero()
556    };
557    let volatile = ctx.iw_context().bool_type().const_zero().into();
558    ctx.builder().build_call(
559        memset,
560        &[mask_ptr.into(), val.into(), size.into(), volatile],
561        "",
562    )?;
563    Ok(())
564}
565
566/// Enum for mask operations that can be performed on a single bit of the mask.
567#[derive(Clone, Copy, Debug, PartialEq, Eq)]
568enum MaskCheck {
569    /// Check the element is borrowed, panicking if it isnt; then mark as returned.
570    Return,
571    /// Check the element is not borrowed, panicking if it is. (Do not change the bit.)
572    CheckNotBorrowed,
573    /// Check the element is not borrowed, panicking if it is; then mark as borrowed.
574    Borrow,
575}
576
577impl MaskCheck {
578    fn func_name(&self) -> &'static str {
579        match self {
580            MaskCheck::Return => "__barray_mask_return",
581            MaskCheck::CheckNotBorrowed => "__barray_mask_check_not_borrowed",
582            MaskCheck::Borrow => "__barray_mask_borrow",
583        }
584    }
585
586    /// Generate code to perform the check on the specified bit of the mask.
587    /// (Does not check the index is in bounds.)
588    fn emit<'c, H: HugrView<Node = Node>>(
589        &self,
590        ccg: &impl BorrowArrayCodegen,
591        ctx: &mut EmitFuncContext<'c, '_, H>,
592        mask_ptr: PointerValue<'c>,
593        idx: IntValue<'c>,
594    ) -> Result<()> {
595        get_or_make_function(
596            ctx,
597            self.func_name(),
598            [mask_ptr.into(), idx.into()],
599            None,
600            |ctx, [mask_ptr, idx]| {
601                // Compute mask bitarray block index via `idx // BLOCK_SIZE`
602                let usize_t = usize_ty(&ctx.typing_session());
603                let (
604                    BlockData {
605                        block_ptr,
606                        block,
607                        idx_in_block,
608                    },
609                    bit,
610                ) = inspect_mask_idx_bit(ctx, mask_ptr, idx)?;
611
612                let panic_bb = ctx.build_positioned_new_block("panic", None, |ctx, panic_bb| {
613                    let err: &ConstError = match self {
614                        MaskCheck::CheckNotBorrowed | MaskCheck::Borrow => &ERR_ALREADY_BORROWED,
615                        MaskCheck::Return => &ERR_NOT_BORROWED,
616                    };
617                    let err_val = ctx.emit_custom_const(err).unwrap();
618                    ccg.emit_panic(ctx, err_val)?;
619                    ctx.builder().build_unreachable()?;
620                    Ok(panic_bb)
621                })?;
622                let ok_bb = ctx.build_positioned_new_block("ok", None, |ctx, ok_bb| {
623                    if let MaskCheck::Return | MaskCheck::Borrow = self {
624                        // Update the mask to mark the element as borrowed or free
625                        let builder = ctx.builder();
626                        let update = builder.build_left_shift(
627                            usize_t.const_int(1, false),
628                            idx_in_block,
629                            "",
630                        )?;
631                        let block = builder.build_xor(block, update, "")?;
632                        builder.build_store(block_ptr, block)?;
633                    }
634                    ctx.builder().build_return(None)?;
635                    Ok(ok_bb)
636                })?;
637                let (if_borrowed, if_present) = match self {
638                    MaskCheck::CheckNotBorrowed | MaskCheck::Borrow => (panic_bb, ok_bb),
639                    MaskCheck::Return => (ok_bb, panic_bb),
640                };
641                ctx.builder()
642                    .build_conditional_branch(bit, if_borrowed, if_present)?;
643                Ok(None)
644            },
645        )?;
646        Ok(())
647    }
648}
649
650struct BlockData<'c> {
651    block_ptr: PointerValue<'c>,
652    block: IntValue<'c>,
653    idx_in_block: IntValue<'c>,
654}
655
656fn inspect_mask_idx_bit<'c, H: HugrView<Node = Node>>(
657    ctx: &mut EmitFuncContext<'c, '_, H>,
658    mask_ptr: BasicValueEnum<'c>,
659    idx: BasicValueEnum<'c>,
660) -> Result<(BlockData<'c>, IntValue<'c>)> {
661    let usize_t = usize_ty(&ctx.typing_session());
662    let mask_ptr = mask_ptr.into_pointer_value();
663    let idx = idx.into_int_value();
664    let block_size = usize_t.const_int(usize_t.get_bit_width() as u64, false);
665    let builder = ctx.builder();
666    let block_idx = builder.build_int_unsigned_div(idx, block_size, "")?;
667    let block_ptr = unsafe { builder.build_in_bounds_gep(mask_ptr, &[block_idx], "")? };
668    let block = builder.build_load(block_ptr, "")?.into_int_value();
669    let idx_in_block = builder.build_int_unsigned_rem(idx, block_size, "")?;
670    let block_shifted = builder.build_right_shift(block, idx_in_block, false, "")?;
671    let bit = builder.build_int_truncate(block_shifted, ctx.iw_context().bool_type(), "")?;
672    Ok((
673        BlockData {
674            block_ptr,
675            block,
676            idx_in_block,
677        },
678        bit,
679    ))
680}
681
682struct MaskInfo<'a> {
683    mask_ptr: PointerValue<'a>,
684    offset: IntValue<'a>,
685    size: IntValue<'a>,
686}
687
688/// Emits instructions to check all blocks of the borrowed mask are equal to `expected_val`
689/// or else panic with the specified error.
690fn check_all_mask_eq<'c, H: HugrView<Node = Node>>(
691    ccg: &impl BorrowArrayCodegen,
692    ctx: &mut EmitFuncContext<'c, '_, H>,
693    mask_info: &MaskInfo<'c>,
694    expected: bool,
695    err: &ConstError,
696) -> Result<()> {
697    build_mask_padding1d(
698        ctx,
699        mask_info.mask_ptr,
700        mask_info.offset,
701        BitsToPad::Before,
702        expected,
703    )?;
704    let usize_t = usize_ty(&ctx.typing_session());
705    let builder = ctx.builder();
706    let last_idx = builder.build_int_sub(
707        builder.build_int_add(mask_info.offset, mask_info.size, "")?,
708        usize_t.const_int(1, false),
709        "last_valid",
710    )?;
711    build_mask_padding1d(
712        ctx,
713        mask_info.mask_ptr,
714        last_idx,
715        BitsToPad::After,
716        expected,
717    )?;
718
719    let builder = ctx.builder();
720    let expected_val = if expected {
721        usize_t.const_all_ones()
722    } else {
723        usize_t.const_zero()
724    };
725    let block_size = usize_t.const_int(usize_t.get_bit_width() as u64, false);
726    let start_block = builder.build_int_unsigned_div(mask_info.offset, block_size, "")?;
727    let end_block = builder.build_int_unsigned_div(last_idx, block_size, "")?;
728
729    let iters = builder.build_int_sub(end_block, start_block, "")?;
730    let iters = builder.build_int_add(iters, usize_t.const_int(1, false), "")?;
731    build_loop(ctx, iters, |ctx, idx| {
732        let builder = ctx.builder();
733        let block_idx = builder.build_int_add(idx, start_block, "")?;
734        let block_addr =
735            unsafe { builder.build_in_bounds_gep(mask_info.mask_ptr, &[block_idx], "")? };
736        let block = builder.build_load(block_addr, "")?.into_int_value();
737        let err_bb = ctx.build_positioned_new_block("mask_block_err", None, |ctx, bb| {
738            let err_val = ctx.emit_custom_const(err).unwrap();
739            ccg.emit_panic(ctx, err_val)?;
740            ctx.builder().build_unreachable()?;
741            Ok(bb)
742        })?;
743        let ok_bb = ctx.build_positioned_new_block("mask_block_ok", None, |_, bb| bb);
744        let builder = ctx.builder();
745        let cond = builder.build_int_compare(IntPredicate::EQ, block, expected_val, "")?;
746        builder.build_conditional_branch(cond, ok_bb, err_bb)?;
747        builder.position_at_end(ok_bb);
748        Ok(())
749    })
750}
751
752#[derive(Clone, Copy, Debug, PartialEq, Eq)]
753enum BitsToPad {
754    Before,
755    After,
756}
757
758/// Emits instructions to update the mask, overwriting unused bits with a value.
759fn build_mask_padding1d<'c, H: HugrView<Node = Node>>(
760    ctx: &mut EmitFuncContext<'c, '_, H>,
761    mask_ptr: PointerValue<'c>,
762    idx: IntValue<'c>,
763    end: BitsToPad,
764    value: bool,
765) -> Result<()> {
766    let builder = ctx.builder();
767    let usize_t = usize_ty(&ctx.typing_session());
768    let block_size = usize_t.const_int(usize_t.get_bit_width() as u64, false);
769
770    // Find the first block that contain some used bits
771    let block_idx = builder.build_int_unsigned_div(idx, block_size, "")?;
772    let block_addr = unsafe { builder.build_in_bounds_gep(mask_ptr, &[block_idx], "")? };
773    let block = builder.build_load(block_addr, "")?.into_int_value();
774
775    let all_ones = usize_t.const_all_ones();
776    let one = usize_t.const_int(1, false);
777    let idx_in_block = builder.build_int_unsigned_rem(idx, block_size, "")?;
778    let new_block = if value {
779        // Pad with ones.
780        let (num_used, shifted) = match end {
781            BitsToPad::Before => {
782                let fst_block_used = builder.build_int_sub(block_size, idx_in_block, "")?;
783                let rsh = builder.build_right_shift(all_ones, fst_block_used, false, "")?;
784                (fst_block_used, rsh)
785            }
786            BitsToPad::After => {
787                // 0<=`idx_in_block`<block_size from int_unsigned_rem, so add one to get
788                // the number of used bits in the last block.
789                let lst_block_used = builder.build_int_add(idx_in_block, one, "")?;
790                let lsh = builder.build_left_shift(all_ones, lst_block_used, "")?;
791                (lst_block_used, lsh)
792            }
793        };
794        // If the shift amount is the block_size, LLVM defines the shift to be a no-op, but we want a zero.
795        let all_used = builder.build_int_compare(IntPredicate::EQ, num_used, block_size, "")?;
796        let pad = builder
797            .build_select(all_used, usize_t.const_zero(), shifted, "")?
798            .into_int_value();
799        builder.build_or(block, pad, "")?
800    } else {
801        // Pad with zeroes.
802        let pad = match end {
803            BitsToPad::Before => builder.build_left_shift(all_ones, idx_in_block, "")?,
804            BitsToPad::After => {
805                // 0<=`idx_in_block`<block_size from int_unsigned_rem, so add one to get
806                // the number of used bits in the last block.
807                let lst_block_used = builder.build_int_add(idx_in_block, one, "")?;
808                let lst_block_unused = builder.build_int_sub(block_size, lst_block_used, "")?;
809                builder.build_right_shift(all_ones, lst_block_unused, false, "")?
810            }
811        };
812        builder.build_and(block, pad, "")?
813    };
814    builder.build_store(block_addr, new_block)?;
815    Ok(())
816}
817
818/// Emits a check that returns whether a specific array element is borrowed (true) or not (false).
819pub fn build_is_borrowed_bit<'c, H: HugrView<Node = Node>>(
820    ctx: &mut EmitFuncContext<'c, '_, H>,
821    mask_ptr: PointerValue<'c>,
822    idx: IntValue<'c>,
823) -> Result<inkwell::values::IntValue<'c>> {
824    // Wrap the check into a function instead of inlining
825    const FUNC_NAME: &str = "__barray_is_borrowed";
826    get_or_make_function(
827        ctx,
828        FUNC_NAME,
829        [mask_ptr.into(), idx.into()],
830        Some(ctx.iw_context().bool_type().into()),
831        |ctx, [mask_ptr, idx]| {
832            let (_, bit) = inspect_mask_idx_bit(ctx, mask_ptr, idx)?;
833            Ok(Some(bit.into()))
834        },
835    )
836    .map(|v| v.expect("i1 return value").into_int_value())
837}
838
839/// Emits a check that no array elements have been borrowed.
840pub fn build_none_borrowed_check<'c, H: HugrView<Node = Node>>(
841    ccg: &impl BorrowArrayCodegen,
842    ctx: &mut EmitFuncContext<'c, '_, H>,
843    mask_ptr: PointerValue<'c>,
844    offset: IntValue<'c>,
845    size: u64,
846) -> Result<()> {
847    if size == 0 {
848        return Ok(());
849    }
850    // Wrap the check into a function instead of inlining
851    const FUNC_NAME: &str = "__barray_check_none_borrowed";
852    let usize_t = usize_ty(&ctx.typing_session());
853    let size = usize_t.const_int(size, false);
854    get_or_make_function(
855        ctx,
856        FUNC_NAME,
857        [mask_ptr.into(), offset.into(), size.into()],
858        None,
859        |ctx, [mask_ptr, offset, size]| {
860            let mask_ptr = mask_ptr.into_pointer_value();
861            let offset = offset.into_int_value();
862            let size = size.into_int_value();
863            // Pad unused bits to zero
864            let info = MaskInfo {
865                mask_ptr,
866                offset,
867                size,
868            };
869            check_all_mask_eq(ccg, ctx, &info, false, &ERR_SOME_BORROWED)?;
870            Ok(None)
871        },
872    )?;
873    Ok(())
874}
875
876/// Emits a check that all array elements have been borrowed.
877pub fn build_all_borrowed_check<'c, H: HugrView<Node = Node>>(
878    ccg: &impl BorrowArrayCodegen,
879    ctx: &mut EmitFuncContext<'c, '_, H>,
880    mask_ptr: PointerValue<'c>,
881    offset: IntValue<'c>,
882    size: u64,
883) -> Result<()> {
884    if size == 0 {
885        return Ok(());
886    }
887    // Wrap the check into a function instead of inlining
888    const FUNC_NAME: &str = "__barray_check_all_borrowed";
889    let usize_t = usize_ty(&ctx.typing_session());
890    let size = usize_t.const_int(size, false);
891    get_or_make_function(
892        ctx,
893        FUNC_NAME,
894        [mask_ptr.into(), offset.into(), size.into()],
895        None,
896        |ctx, [mask_ptr, offset, size]| {
897            let mask_ptr = mask_ptr.into_pointer_value();
898            let offset = offset.into_int_value();
899            let size = size.into_int_value();
900            // Pad unused bits to one
901            let info = MaskInfo {
902                mask_ptr,
903                offset,
904                size,
905            };
906            check_all_mask_eq(ccg, ctx, &info, true, &ERR_NOT_ALL_BORROWED)?;
907            Ok(None)
908        },
909    )?;
910    Ok(())
911}
912
913/// Emits a check that a specified (unsigned) index is less than the size of the array
914pub fn build_bounds_check<'c, H: HugrView<Node = Node>>(
915    ccg: &impl BorrowArrayCodegen,
916    ctx: &mut EmitFuncContext<'c, '_, H>,
917    size: u64,
918    idx: IntValue<'c>,
919) -> Result<()> {
920    // Wrap the check into a function instead of inlining
921    const FUNC_NAME: &str = "__barray_check_bounds";
922    let size = usize_ty(&ctx.typing_session()).const_int(size, false);
923    get_or_make_function(
924        ctx,
925        FUNC_NAME,
926        [size.into(), idx.into()],
927        None,
928        |ctx, [size, idx]| {
929            let size = size.into_int_value();
930            let idx = idx.into_int_value();
931            let in_bounds = ctx
932                .builder()
933                .build_int_compare(IntPredicate::ULT, idx, size, "")?;
934            let ok_bb = ctx.build_positioned_new_block("ok", None, |_, bb| bb);
935            let err_bb = ctx.build_positioned_new_block("out_of_bounds", None, |ctx, bb| {
936                let err: &ConstError = &ERR_OUT_OF_BOUNDS;
937                let err_val = ctx.emit_custom_const(err).unwrap();
938                ccg.emit_panic(ctx, err_val)?;
939                ctx.builder().build_unreachable()?;
940                Ok(bb)
941            })?;
942            ctx.builder()
943                .build_conditional_branch(in_bounds, ok_bb, err_bb)?;
944            ctx.builder().position_at_end(ok_bb);
945            Ok(None)
946        },
947    )?;
948    Ok(())
949}
950
951/// Helper function to build a loop that repeats for a given number of iterations.
952///
953/// The provided closure is called to build the loop body. Afterwards, the builder is positioned at
954/// the end of the loop exit block.
955fn build_loop<'c, T, H: HugrView<Node = Node>>(
956    ctx: &mut EmitFuncContext<'c, '_, H>,
957    iters: IntValue<'c>,
958    go: impl FnOnce(&mut EmitFuncContext<'c, '_, H>, IntValue<'c>) -> Result<T>,
959) -> Result<T> {
960    let builder = ctx.builder();
961    let idx_ty = usize_ty(&ctx.typing_session());
962    let idx_ptr = builder.build_alloca(idx_ty, "")?;
963    builder.build_store(idx_ptr, idx_ty.const_zero())?;
964    let exit_block = ctx.new_basic_block("", None);
965
966    let (body_start_block, body_end_block, val) =
967        ctx.build_positioned_new_block("", Some(exit_block), |ctx, body_start_bb| {
968            let idx = ctx.builder().build_load(idx_ptr, "")?.into_int_value();
969            let val = go(ctx, idx)?;
970            let builder = ctx.builder();
971            let body_end_bb = builder.get_insert_block().unwrap();
972            let inc_idx = builder.build_int_add(idx, idx_ty.const_int(1, false), "")?;
973            builder.build_store(idx_ptr, inc_idx)?;
974            // Branch to the head is built later
975            Ok((body_start_bb, body_end_bb, val))
976        })?;
977
978    let head_block = ctx.build_positioned_new_block("", Some(body_start_block), |ctx, bb| {
979        let builder = ctx.builder();
980        let idx = builder.build_load(idx_ptr, "")?.into_int_value();
981        let cmp = builder.build_int_compare(IntPredicate::ULT, idx, iters, "")?;
982        builder.build_conditional_branch(cmp, body_start_block, exit_block)?;
983        Ok(bb)
984    })?;
985
986    let builder = ctx.builder();
987    builder.build_unconditional_branch(head_block)?;
988    builder.position_at_end(body_end_block);
989    builder.build_unconditional_branch(head_block)?;
990    ctx.builder().position_at_end(exit_block);
991    Ok(val)
992}
993
994/// Emits an [`borrow_array::BArrayValue`].
995pub fn emit_barray_value<'c, H: HugrView<Node = Node>>(
996    ccg: &impl BorrowArrayCodegen,
997    ctx: &mut EmitFuncContext<'c, '_, H>,
998    value: &borrow_array::BArrayValue,
999) -> Result<BasicValueEnum<'c>> {
1000    let ts = ctx.typing_session();
1001    let elem_ty = ts.llvm_type(value.get_element_type())?;
1002    let (elem_ptr, array_v) =
1003        build_barray_alloc(ctx, ccg, elem_ty, value.get_contents().len() as u64, false)?;
1004    for (i, v) in value.get_contents().iter().enumerate() {
1005        let llvm_v = emit_value(ctx, v)?;
1006        let idx = ts.iw_context().i32_type().const_int(i as u64, true);
1007        let elem_addr = unsafe { ctx.builder().build_in_bounds_gep(elem_ptr, &[idx], "")? };
1008        ctx.builder().build_store(elem_addr, llvm_v)?;
1009    }
1010    Ok(array_v.into())
1011}
1012
1013/// Emits an [`BArrayOp`].
1014pub fn emit_barray_op<'c, H: HugrView<Node = Node>>(
1015    ccg: &impl BorrowArrayCodegen,
1016    ctx: &mut EmitFuncContext<'c, '_, H>,
1017    op: BArrayOp,
1018    inputs: Vec<BasicValueEnum<'c>>,
1019    outputs: RowPromise<'c>,
1020) -> Result<()> {
1021    let builder = ctx.builder();
1022    let ts = ctx.typing_session();
1023    let sig = op
1024        .clone()
1025        .to_extension_op()
1026        .unwrap()
1027        .signature()
1028        .into_owned();
1029    let BArrayOp {
1030        def,
1031        elem_ty: ref hugr_elem_ty,
1032        size,
1033    } = op;
1034    let elem_ty = ts.llvm_type(hugr_elem_ty)?;
1035    match def {
1036        BArrayOpDef::new_array => {
1037            let (elem_ptr, array_v) = build_barray_alloc(ctx, ccg, elem_ty, size, false)?;
1038            let usize_t = usize_ty(&ctx.typing_session());
1039            for (i, v) in inputs.into_iter().enumerate() {
1040                let idx = usize_t.const_int(i as u64, true);
1041                let elem_addr = unsafe { ctx.builder().build_in_bounds_gep(elem_ptr, &[idx], "")? };
1042                ctx.builder().build_store(elem_addr, v)?;
1043            }
1044            outputs.finish(ctx.builder(), [array_v.into()])
1045        }
1046        BArrayOpDef::unpack => {
1047            let [array_v] = inputs
1048                .try_into()
1049                .map_err(|_| anyhow!("BArrayOpDef::unpack expects one argument"))?;
1050            let BArrayFatPtrComponents {
1051                elems_ptr,
1052                mask_ptr,
1053                offset,
1054            } = decompose_barray_fat_pointer(builder, array_v)?;
1055
1056            let mut result = Vec::with_capacity(size as usize);
1057            let usize_t = usize_ty(&ctx.typing_session());
1058
1059            for i in 0..size {
1060                let idx = ctx
1061                    .builder()
1062                    .build_int_add(offset, usize_t.const_int(i, false), "")?;
1063                MaskCheck::CheckNotBorrowed.emit(ccg, ctx, mask_ptr, idx)?;
1064                let elem_addr =
1065                    unsafe { ctx.builder().build_in_bounds_gep(elems_ptr, &[idx], "")? };
1066                let elem_v = ctx.builder().build_load(elem_addr, "")?;
1067                result.push(elem_v);
1068            }
1069
1070            outputs.finish(ctx.builder(), result)
1071        }
1072        BArrayOpDef::get => {
1073            let [array_v, index_v] = inputs
1074                .try_into()
1075                .map_err(|_| anyhow!("BArrayOpDef::get expects two arguments"))?;
1076            let BArrayFatPtrComponents {
1077                elems_ptr,
1078                mask_ptr,
1079                offset,
1080            } = decompose_barray_fat_pointer(builder, array_v)?;
1081            let index_v = index_v.into_int_value();
1082            let res_hugr_ty = sig
1083                .output()
1084                .get(0)
1085                .ok_or(anyhow!("BArrayOp::get has no outputs"))?;
1086
1087            let res_sum_ty = {
1088                let TypeEnum::Sum(st) = res_hugr_ty.as_type_enum() else {
1089                    Err(anyhow!("BArrayOp::get output is not a sum type"))?
1090                };
1091                ts.llvm_sum_type(st.clone())?
1092            };
1093
1094            let exit_rmb = ctx.new_row_mail_box(sig.output.iter(), "")?;
1095
1096            let exit_block = ctx.build_positioned_new_block("", None, |ctx, bb| {
1097                outputs.finish(ctx.builder(), exit_rmb.read_vec(ctx.builder(), [])?)?;
1098                Ok(bb)
1099            })?;
1100
1101            let success_block =
1102                ctx.build_positioned_new_block("", Some(exit_block), |ctx, bb| {
1103                    // inside `success_block` we know `index_v` to be in bounds
1104                    let index_v = ctx.builder().build_int_add(index_v, offset, "")?;
1105                    MaskCheck::CheckNotBorrowed.emit(ccg, ctx, mask_ptr, index_v)?;
1106                    let builder = ctx.builder();
1107                    let elem_addr =
1108                        unsafe { builder.build_in_bounds_gep(elems_ptr, &[index_v], "")? };
1109                    let elem_v = builder.build_load(elem_addr, "")?;
1110                    let success_v = res_sum_ty.build_tag(builder, 1, vec![elem_v])?;
1111                    exit_rmb.write(ctx.builder(), [success_v.into(), array_v])?;
1112                    builder.build_unconditional_branch(exit_block)?;
1113                    Ok(bb)
1114                })?;
1115
1116            let failure_block =
1117                ctx.build_positioned_new_block("", Some(success_block), |ctx, bb| {
1118                    let builder = ctx.builder();
1119                    let failure_v = res_sum_ty.build_tag(builder, 0, vec![])?;
1120                    exit_rmb.write(ctx.builder(), [failure_v.into(), array_v])?;
1121                    builder.build_unconditional_branch(exit_block)?;
1122                    Ok(bb)
1123                })?;
1124
1125            let builder = ctx.builder();
1126            let is_success = builder.build_int_compare(
1127                IntPredicate::ULT,
1128                index_v,
1129                index_v.get_type().const_int(size, false),
1130                "",
1131            )?;
1132
1133            builder.build_conditional_branch(is_success, success_block, failure_block)?;
1134            builder.position_at_end(exit_block);
1135            Ok(())
1136        }
1137        BArrayOpDef::set => {
1138            let [array_v, index_v, value_v] = inputs
1139                .try_into()
1140                .map_err(|_| anyhow!("BArrayOpDef::set expects three arguments"))?;
1141            let BArrayFatPtrComponents {
1142                elems_ptr,
1143                mask_ptr,
1144                offset,
1145            } = decompose_barray_fat_pointer(builder, array_v)?;
1146            let index_v = index_v.into_int_value();
1147
1148            let res_hugr_ty = sig
1149                .output()
1150                .get(0)
1151                .ok_or(anyhow!("BArrayOp::set has no outputs"))?;
1152
1153            let res_sum_ty = {
1154                let TypeEnum::Sum(st) = res_hugr_ty.as_type_enum() else {
1155                    Err(anyhow!("BArrayOp::set output is not a sum type"))?
1156                };
1157                ts.llvm_sum_type(st.clone())?
1158            };
1159
1160            let exit_rmb = ctx.new_row_mail_box([res_hugr_ty], "")?;
1161
1162            let exit_block = ctx.build_positioned_new_block("", None, |ctx, bb| {
1163                outputs.finish(ctx.builder(), exit_rmb.read_vec(ctx.builder(), [])?)?;
1164                Ok(bb)
1165            })?;
1166
1167            let success_block =
1168                ctx.build_positioned_new_block("", Some(exit_block), |ctx, bb| {
1169                    // inside `success_block` we know `index_v` to be in bounds.
1170                    let index_v = ctx.builder().build_int_add(index_v, offset, "")?;
1171                    MaskCheck::CheckNotBorrowed.emit(ccg, ctx, mask_ptr, index_v)?;
1172                    let builder = ctx.builder();
1173                    let elem_addr =
1174                        unsafe { builder.build_in_bounds_gep(elems_ptr, &[index_v], "")? };
1175                    let elem_v = builder.build_load(elem_addr, "")?;
1176                    builder.build_store(elem_addr, value_v)?;
1177                    let success_v = res_sum_ty.build_tag(builder, 1, vec![elem_v, array_v])?;
1178                    exit_rmb.write(ctx.builder(), [success_v.into()])?;
1179                    builder.build_unconditional_branch(exit_block)?;
1180                    Ok(bb)
1181                })?;
1182
1183            let failure_block =
1184                ctx.build_positioned_new_block("", Some(success_block), |ctx, bb| {
1185                    let builder = ctx.builder();
1186                    let failure_v = res_sum_ty.build_tag(builder, 0, vec![value_v, array_v])?;
1187                    exit_rmb.write(ctx.builder(), [failure_v.into()])?;
1188                    builder.build_unconditional_branch(exit_block)?;
1189                    Ok(bb)
1190                })?;
1191
1192            let builder = ctx.builder();
1193            let is_success = builder.build_int_compare(
1194                IntPredicate::ULT,
1195                index_v,
1196                index_v.get_type().const_int(size, false),
1197                "",
1198            )?;
1199            builder.build_conditional_branch(is_success, success_block, failure_block)?;
1200            builder.position_at_end(exit_block);
1201            Ok(())
1202        }
1203        BArrayOpDef::swap => {
1204            let [array_v, index1_v, index2_v] = inputs
1205                .try_into()
1206                .map_err(|_| anyhow!("BArrayOpDef::swap expects three arguments"))?;
1207            let BArrayFatPtrComponents {
1208                elems_ptr,
1209                mask_ptr,
1210                offset,
1211            } = decompose_barray_fat_pointer(builder, array_v)?;
1212            let index1_v = index1_v.into_int_value();
1213            let index2_v = index2_v.into_int_value();
1214
1215            let res_hugr_ty = sig
1216                .output()
1217                .get(0)
1218                .ok_or(anyhow!("BArrayOp::swap has no outputs"))?;
1219
1220            let res_sum_ty = {
1221                let TypeEnum::Sum(st) = res_hugr_ty.as_type_enum() else {
1222                    Err(anyhow!("BArrayOp::swap output is not a sum type"))?
1223                };
1224                ts.llvm_sum_type(st.clone())?
1225            };
1226
1227            let exit_rmb = ctx.new_row_mail_box([res_hugr_ty], "")?;
1228
1229            let exit_block = ctx.build_positioned_new_block("", None, |ctx, bb| {
1230                outputs.finish(ctx.builder(), exit_rmb.read_vec(ctx.builder(), [])?)?;
1231                Ok(bb)
1232            })?;
1233
1234            let success_block =
1235                ctx.build_positioned_new_block("", Some(exit_block), |ctx, bb| {
1236                    // if `index1_v` == `index2_v` then the following is a no-op.
1237                    // We could check for this: either with a select instruction
1238                    // here, or by branching to another case in earlier.
1239                    // Doing so would generate better code in cases where the
1240                    // optimiser can determine that the indices are the same, at
1241                    // the cost of worse code in cases where it cannot.
1242                    // For now we choose the simpler option of omitting the check.
1243                    let builder = ctx.builder();
1244                    // inside `success_block` we know `index1_v` and `index2_v`
1245                    // to be in bounds.
1246                    let index1_v = builder.build_int_add(index1_v, offset, "")?;
1247                    let index2_v = builder.build_int_add(index2_v, offset, "")?;
1248                    MaskCheck::CheckNotBorrowed.emit(ccg, ctx, mask_ptr, index1_v)?;
1249                    MaskCheck::CheckNotBorrowed.emit(ccg, ctx, mask_ptr, index2_v)?;
1250                    let builder = ctx.builder();
1251                    let elem1_addr =
1252                        unsafe { builder.build_in_bounds_gep(elems_ptr, &[index1_v], "")? };
1253                    let elem1_v = builder.build_load(elem1_addr, "")?;
1254                    let elem2_addr =
1255                        unsafe { builder.build_in_bounds_gep(elems_ptr, &[index2_v], "")? };
1256                    let elem2_v = builder.build_load(elem2_addr, "")?;
1257                    builder.build_store(elem1_addr, elem2_v)?;
1258                    builder.build_store(elem2_addr, elem1_v)?;
1259                    let success_v = res_sum_ty.build_tag(builder, 1, vec![array_v])?;
1260                    exit_rmb.write(ctx.builder(), [success_v.into()])?;
1261                    builder.build_unconditional_branch(exit_block)?;
1262                    Ok(bb)
1263                })?;
1264
1265            let failure_block =
1266                ctx.build_positioned_new_block("", Some(success_block), |ctx, bb| {
1267                    let builder = ctx.builder();
1268                    let failure_v = res_sum_ty.build_tag(builder, 0, vec![array_v])?;
1269                    exit_rmb.write(ctx.builder(), [failure_v.into()])?;
1270                    builder.build_unconditional_branch(exit_block)?;
1271                    Ok(bb)
1272                })?;
1273
1274            let builder = ctx.builder();
1275            let is_success = {
1276                let index1_ok = builder.build_int_compare(
1277                    IntPredicate::ULT,
1278                    index1_v,
1279                    index1_v.get_type().const_int(size, false),
1280                    "",
1281                )?;
1282                let index2_ok = builder.build_int_compare(
1283                    IntPredicate::ULT,
1284                    index2_v,
1285                    index2_v.get_type().const_int(size, false),
1286                    "",
1287                )?;
1288                builder.build_and(index1_ok, index2_ok, "")?
1289            };
1290            builder.build_conditional_branch(is_success, success_block, failure_block)?;
1291            builder.position_at_end(exit_block);
1292            Ok(())
1293        }
1294        BArrayOpDef::pop_left => {
1295            let [array_v] = inputs
1296                .try_into()
1297                .map_err(|_| anyhow!("BArrayOpDef::pop_left expects one argument"))?;
1298            let r = emit_pop_op(
1299                ccg,
1300                ctx,
1301                hugr_elem_ty.clone(),
1302                size,
1303                array_v.into_struct_value(),
1304                true,
1305            )?;
1306            outputs.finish(ctx.builder(), [r])
1307        }
1308        BArrayOpDef::pop_right => {
1309            let [array_v] = inputs
1310                .try_into()
1311                .map_err(|_| anyhow!("BArrayOpDef::pop_right expects one argument"))?;
1312            let r = emit_pop_op(
1313                ccg,
1314                ctx,
1315                hugr_elem_ty.clone(),
1316                size,
1317                array_v.into_struct_value(),
1318                false,
1319            )?;
1320            outputs.finish(ctx.builder(), [r])
1321        }
1322        BArrayOpDef::discard_empty => {
1323            let [array_v] = inputs
1324                .try_into()
1325                .map_err(|_| anyhow!("BArrayOpDef::discard_empty expects one argument"))?;
1326            let BArrayFatPtrComponents {
1327                elems_ptr,
1328                mask_ptr,
1329                offset: _,
1330            } = decompose_barray_fat_pointer(builder, array_v)?;
1331            ccg.emit_free_array(ctx, elems_ptr)?;
1332            ccg.emit_free_array(ctx, mask_ptr)?;
1333            outputs.finish(ctx.builder(), [])
1334        }
1335        _ => todo!(),
1336    }
1337}
1338
1339/// Emits an [`BArrayClone`] op.
1340pub fn emit_clone_op<'c, H: HugrView<Node = Node>>(
1341    ccg: &impl BorrowArrayCodegen,
1342    ctx: &mut EmitFuncContext<'c, '_, H>,
1343    op: BArrayClone,
1344    array_v: BasicValueEnum<'c>,
1345) -> Result<(BasicValueEnum<'c>, BasicValueEnum<'c>)> {
1346    let elem_ty = ctx.llvm_type(&op.elem_ty)?;
1347    let BArrayFatPtrComponents {
1348        elems_ptr,
1349        mask_ptr,
1350        offset,
1351    } = decompose_barray_fat_pointer(ctx.builder(), array_v)?;
1352    build_none_borrowed_check(ccg, ctx, mask_ptr, offset, op.size)?;
1353    let (other_ptr, other_array_v) = build_barray_alloc(ctx, ccg, elem_ty, op.size, false)?;
1354    let src_ptr = unsafe {
1355        ctx.builder()
1356            .build_in_bounds_gep(elems_ptr, &[offset], "")?
1357    };
1358    let length = usize_ty(&ctx.typing_session()).const_int(op.size, false);
1359    let size_value = ctx
1360        .builder()
1361        .build_int_mul(length, elem_ty.size_of().unwrap(), "")?;
1362    let is_volatile = ctx.iw_context().bool_type().const_zero();
1363
1364    let memcpy_intrinsic = Intrinsic::find("llvm.memcpy").unwrap();
1365    let memcpy = memcpy_intrinsic
1366        .get_declaration(
1367            ctx.get_current_module(),
1368            &[
1369                other_ptr.get_type().into(),
1370                src_ptr.get_type().into(),
1371                size_value.get_type().into(),
1372            ],
1373        )
1374        .unwrap();
1375    ctx.builder().build_call(
1376        memcpy,
1377        &[
1378            other_ptr.into(),
1379            src_ptr.into(),
1380            size_value.into(),
1381            is_volatile.into(),
1382        ],
1383        "",
1384    )?;
1385    Ok((array_v, other_array_v.into()))
1386}
1387
1388/// Emits an [`BArrayDiscard`] op.
1389pub fn emit_barray_discard<'c, H: HugrView<Node = Node>>(
1390    ccg: &impl BorrowArrayCodegen,
1391    ctx: &mut EmitFuncContext<'c, '_, H>,
1392    _op: BArrayDiscard,
1393    array_v: BasicValueEnum<'c>,
1394) -> Result<()> {
1395    let array_ptr =
1396        ctx.builder()
1397            .build_extract_value(array_v.into_struct_value(), 0, "array_ptr")?;
1398    ccg.emit_free_array(ctx, array_ptr.into_pointer_value())?;
1399    Ok(())
1400}
1401
1402/// Emits the [`BArrayOpDef::pop_left`] and [`BArrayOpDef::pop_right`] operations.
1403fn emit_pop_op<'c, H: HugrView<Node = Node>>(
1404    ccg: &impl BorrowArrayCodegen,
1405    ctx: &mut EmitFuncContext<'c, '_, H>,
1406    elem_ty: HugrType,
1407    size: u64,
1408    array_v: StructValue<'c>,
1409    pop_left: bool,
1410) -> Result<BasicValueEnum<'c>> {
1411    let ts = ctx.typing_session();
1412    let builder = ctx.builder();
1413    let fp = decompose_barray_fat_pointer(builder, array_v.into())?;
1414    let ret_ty = ts.llvm_sum_type(option_type(vec![
1415        elem_ty.clone(),
1416        borrow_array_type(size.saturating_add_signed(-1), elem_ty),
1417    ]))?;
1418    if size == 0 {
1419        return Ok(ret_ty.build_tag(builder, 0, vec![])?.into());
1420    }
1421    let (elem_ptr, new_array_offset) = {
1422        if pop_left {
1423            let new_array_offset = builder.build_int_add(
1424                fp.offset,
1425                usize_ty(&ts).const_int(1, false),
1426                "new_offset",
1427            )?;
1428            MaskCheck::CheckNotBorrowed.emit(ccg, ctx, fp.mask_ptr, fp.offset)?;
1429            let elem_ptr = unsafe {
1430                ctx.builder()
1431                    .build_in_bounds_gep(fp.elems_ptr, &[fp.offset], "")
1432            }?;
1433            (elem_ptr, new_array_offset)
1434        } else {
1435            let idx =
1436                builder.build_int_add(fp.offset, usize_ty(&ts).const_int(size - 1, false), "")?;
1437            MaskCheck::CheckNotBorrowed.emit(ccg, ctx, fp.mask_ptr, idx)?;
1438            let elem_ptr = unsafe { ctx.builder().build_in_bounds_gep(fp.elems_ptr, &[idx], "") }?;
1439            (elem_ptr, fp.offset)
1440        }
1441    };
1442    let elem_v = ctx.builder().build_load(elem_ptr, "")?;
1443    let new_array_v = build_barray_fat_pointer(
1444        ctx,
1445        BArrayFatPtrComponents {
1446            offset: new_array_offset,
1447            ..fp
1448        },
1449    )?;
1450
1451    Ok(ret_ty
1452        .build_tag(ctx.builder(), 1, vec![elem_v, new_array_v.into()])?
1453        .into())
1454}
1455
1456/// Emits an [`BArrayRepeat`] op.
1457pub fn emit_repeat_op<'c, H: HugrView<Node = Node>>(
1458    ccg: &impl BorrowArrayCodegen,
1459    ctx: &mut EmitFuncContext<'c, '_, H>,
1460    op: BArrayRepeat,
1461    func: BasicValueEnum<'c>,
1462) -> Result<BasicValueEnum<'c>> {
1463    let elem_ty = ctx.llvm_type(&op.elem_ty)?;
1464    let (ptr, array_v) = build_barray_alloc(ctx, ccg, elem_ty, op.size, false)?;
1465    let array_len = usize_ty(&ctx.typing_session()).const_int(op.size, false);
1466    build_loop(ctx, array_len, |ctx, idx| {
1467        let builder = ctx.builder();
1468        let func_ptr = CallableValue::try_from(func.into_pointer_value())
1469            .map_err(|()| anyhow!("BArrayOpDef::repeat expects a function pointer"))?;
1470        let v = builder
1471            .build_call(func_ptr, &[], "")?
1472            .try_as_basic_value()
1473            .left()
1474            .ok_or(anyhow!("BArrayOpDef::repeat function must return a value"))?;
1475        let elem_addr = unsafe { builder.build_in_bounds_gep(ptr, &[idx], "")? };
1476        builder.build_store(elem_addr, v)?;
1477        Ok(())
1478    })?;
1479    Ok(array_v.into())
1480}
1481
1482/// Emits an [`BArrayScan`] op.
1483///
1484/// Returns the resulting array and the final values of the accumulators.
1485pub fn emit_scan_op<'c, H: HugrView<Node = Node>>(
1486    ccg: &impl BorrowArrayCodegen,
1487    ctx: &mut EmitFuncContext<'c, '_, H>,
1488    op: BArrayScan,
1489    src_array_v: StructValue<'c>,
1490    func: BasicValueEnum<'c>,
1491    initial_accs: &[BasicValueEnum<'c>],
1492) -> Result<(BasicValueEnum<'c>, Vec<BasicValueEnum<'c>>)> {
1493    let BArrayFatPtrComponents {
1494        elems_ptr: src_ptr,
1495        mask_ptr: src_mask_ptr,
1496        offset: src_offset,
1497    } = decompose_barray_fat_pointer(ctx.builder(), src_array_v.into())?;
1498    build_none_borrowed_check(ccg, ctx, src_mask_ptr, src_offset, op.size)?;
1499    let tgt_elem_ty = ctx.llvm_type(&op.tgt_ty)?;
1500    // TODO: If `sizeof(op.src_ty) >= sizeof(op.tgt_ty)`, we could reuse the memory
1501    // from `src` instead of allocating a fresh array
1502    let (tgt_ptr, tgt_array_v) = build_barray_alloc(ctx, ccg, tgt_elem_ty, op.size, false)?;
1503    let array_len = usize_ty(&ctx.typing_session()).const_int(op.size, false);
1504    let acc_tys: Vec<_> = op
1505        .acc_tys
1506        .iter()
1507        .map(|ty| ctx.llvm_type(ty))
1508        .try_collect()?;
1509    let builder = ctx.builder();
1510    let acc_ptrs: Vec<_> = acc_tys
1511        .iter()
1512        .map(|ty| builder.build_alloca(*ty, ""))
1513        .try_collect()?;
1514    for (ptr, initial_val) in acc_ptrs.iter().zip(initial_accs) {
1515        builder.build_store(*ptr, *initial_val)?;
1516    }
1517
1518    build_loop(ctx, array_len, |ctx, idx| {
1519        let builder = ctx.builder();
1520        let func_ptr = CallableValue::try_from(func.into_pointer_value())
1521            .map_err(|()| anyhow!("BArrayOpDef::scan expects a function pointer"))?;
1522        let src_idx = builder.build_int_add(idx, src_offset, "")?;
1523        let src_elem_addr = unsafe { builder.build_in_bounds_gep(src_ptr, &[src_idx], "")? };
1524        let src_elem = builder.build_load(src_elem_addr, "")?;
1525        let mut args = vec![src_elem.into()];
1526        for ptr in &acc_ptrs {
1527            args.push(builder.build_load(*ptr, "")?.into());
1528        }
1529        let call = builder.build_call(func_ptr, args.as_slice(), "")?;
1530        let call_results = deaggregate_call_result(builder, call, 1 + acc_tys.len())?;
1531        let tgt_elem_addr = unsafe { builder.build_in_bounds_gep(tgt_ptr, &[idx], "")? };
1532        builder.build_store(tgt_elem_addr, call_results[0])?;
1533        for (ptr, next_act) in acc_ptrs.iter().zip(call_results[1..].iter()) {
1534            builder.build_store(*ptr, *next_act)?;
1535        }
1536        Ok(())
1537    })?;
1538
1539    ccg.emit_free_array(ctx, src_ptr)?;
1540    ccg.emit_free_array(ctx, src_mask_ptr)?;
1541    let builder = ctx.builder();
1542    let final_accs = acc_ptrs
1543        .into_iter()
1544        .map(|ptr| builder.build_load(ptr, ""))
1545        .try_collect()?;
1546    Ok((tgt_array_v.into(), final_accs))
1547}
1548
1549/// Emits an [`BArrayUnsafeOp`].
1550pub fn emit_barray_unsafe_op<'c, H: HugrView<Node = Node>>(
1551    ccg: &impl BorrowArrayCodegen,
1552    ctx: &mut EmitFuncContext<'c, '_, H>,
1553    op: BArrayUnsafeOp,
1554    inputs: Vec<BasicValueEnum<'c>>,
1555    outputs: RowPromise<'c>,
1556) -> Result<()> {
1557    let builder = ctx.builder();
1558    let BArrayUnsafeOp {
1559        def,
1560        elem_ty: ref hugr_elem_ty,
1561        size,
1562    } = op;
1563
1564    match def {
1565        BArrayUnsafeOpDef::borrow => {
1566            let [array_v, index_v] = inputs
1567                .try_into()
1568                .map_err(|_| anyhow!("BArrayUnsafeOpDef::borrow expects two arguments"))?;
1569            let BArrayFatPtrComponents {
1570                elems_ptr,
1571                mask_ptr,
1572                offset,
1573            } = decompose_barray_fat_pointer(builder, array_v)?;
1574            let index_v = index_v.into_int_value();
1575            build_bounds_check(ccg, ctx, size, index_v)?;
1576            let offset_index_v = ctx.builder().build_int_add(index_v, offset, "")?;
1577            MaskCheck::Borrow.emit(ccg, ctx, mask_ptr, offset_index_v)?;
1578            let builder = ctx.builder();
1579            let elem_addr =
1580                unsafe { builder.build_in_bounds_gep(elems_ptr, &[offset_index_v], "")? };
1581            let elem_v = builder.build_load(elem_addr, "")?;
1582            outputs.finish(builder, [array_v, elem_v])
1583        }
1584        BArrayUnsafeOpDef::r#return => {
1585            let [array_v, index_v, elem_v] = inputs
1586                .try_into()
1587                .map_err(|_| anyhow!("BArrayUnsafeOpDef::return expects three arguments"))?;
1588            let BArrayFatPtrComponents {
1589                elems_ptr,
1590                mask_ptr,
1591                offset,
1592            } = decompose_barray_fat_pointer(builder, array_v)?;
1593            let index_v = index_v.into_int_value();
1594            build_bounds_check(ccg, ctx, size, index_v)?;
1595            let offset_index_v = ctx.builder().build_int_add(index_v, offset, "")?;
1596            MaskCheck::Return.emit(ccg, ctx, mask_ptr, offset_index_v)?;
1597            let builder = ctx.builder();
1598            let elem_addr =
1599                unsafe { builder.build_in_bounds_gep(elems_ptr, &[offset_index_v], "")? };
1600            builder.build_store(elem_addr, elem_v)?;
1601            outputs.finish(builder, [array_v])
1602        }
1603        BArrayUnsafeOpDef::discard_all_borrowed => {
1604            let [array_v] = inputs.try_into().map_err(|_| {
1605                anyhow!("BArrayUnsafeOpDef::discard_all_borrowed expects one argument")
1606            })?;
1607            let BArrayFatPtrComponents {
1608                elems_ptr,
1609                mask_ptr,
1610                offset,
1611            } = decompose_barray_fat_pointer(builder, array_v)?;
1612            build_all_borrowed_check(ccg, ctx, mask_ptr, offset, size)?;
1613            ccg.emit_free_array(ctx, elems_ptr)?;
1614            ccg.emit_free_array(ctx, mask_ptr)?;
1615            outputs.finish(ctx.builder(), [])
1616        }
1617        BArrayUnsafeOpDef::new_all_borrowed => {
1618            let elem_ty = ctx.llvm_type(hugr_elem_ty)?;
1619            let (_, array_v) = build_barray_alloc(ctx, ccg, elem_ty, size, true)?;
1620            outputs.finish(ctx.builder(), [array_v.into()])
1621        }
1622        BArrayUnsafeOpDef::is_borrowed => {
1623            let [array_v, index_v] = inputs
1624                .try_into()
1625                .map_err(|_| anyhow!("BArrayUnsafeOpDef::is_borrowed expects two arguments"))?;
1626            let BArrayFatPtrComponents {
1627                mask_ptr, offset, ..
1628            } = decompose_barray_fat_pointer(builder, array_v)?;
1629            let index_v = index_v.into_int_value();
1630            build_bounds_check(ccg, ctx, size, index_v)?;
1631            let offset_index_v = ctx.builder().build_int_add(index_v, offset, "")?;
1632            // let bit = build_is_borrowed_check(ctx, mask_ptr, offset_index_v)?;
1633            let bit = build_is_borrowed_bit(ctx, mask_ptr, offset_index_v)?;
1634            outputs.finish(ctx.builder(), [array_v, bit.into()])
1635        }
1636        _ => todo!(),
1637    }
1638}
1639
1640/// Emits an [`BArrayToArray`] op.
1641pub fn emit_to_array_op<'c, H: HugrView<Node = Node>>(
1642    ccg: &impl BorrowArrayCodegen,
1643    ctx: &mut EmitFuncContext<'c, '_, H>,
1644    op: BArrayToArray,
1645    barray_v: BasicValueEnum<'c>,
1646) -> Result<BasicValueEnum<'c>> {
1647    let BArrayFatPtrComponents {
1648        elems_ptr,
1649        mask_ptr,
1650        offset,
1651    } = decompose_barray_fat_pointer(ctx.builder(), barray_v)?;
1652    build_none_borrowed_check(ccg, ctx, mask_ptr, offset, op.size)?;
1653    Ok(build_array_fat_pointer(ctx, elems_ptr, offset)?.into())
1654}
1655
1656/// Emits an [`BArrayFromArray`] op.
1657pub fn emit_from_array_op<'c, H: HugrView<Node = Node>>(
1658    ccg: &impl BorrowArrayCodegen,
1659    ctx: &mut EmitFuncContext<'c, '_, H>,
1660    op: BArrayFromArray,
1661    array_v: BasicValueEnum<'c>,
1662) -> Result<BasicValueEnum<'c>> {
1663    // We reuse the allocation from the array but we have to allocate a fresh mask.
1664    // Note that the mask must have size at least `size + offset` so the offsets match up.
1665    let usize_t = usize_ty(&ctx.typing_session());
1666    let builder = ctx.builder();
1667    let (ptr, offset) = decompose_array_fat_pointer(builder, array_v)?;
1668    let size = usize_t.const_int(op.size, false);
1669    let mask_bits = builder.build_int_add(size, offset, "")?;
1670    let mask_blocks = builder.build_int_unsigned_div(mask_bits, usize_t.size_of(), "")?;
1671    // Increment by one to account for potential rounding down
1672    let mask_blocks = builder.build_int_add(mask_blocks, usize_t.const_int(1, false), "")?;
1673    let (mask_ptr, mask_size) = alloc_typed_array(ccg, ctx, usize_t.into(), mask_blocks)?;
1674    fill_mask(ctx, mask_ptr, mask_size, false)?;
1675    Ok(build_barray_fat_pointer(
1676        ctx,
1677        BArrayFatPtrComponents {
1678            elems_ptr: ptr,
1679            mask_ptr,
1680            offset,
1681        },
1682    )?
1683    .into())
1684}
1685
1686#[cfg(test)]
1687mod test {
1688    use hugr_core::Wire;
1689    use hugr_core::builder::{DataflowHugr, HugrBuilder};
1690    use hugr_core::extension::prelude::either_type;
1691    use hugr_core::ops::Tag;
1692    use hugr_core::std_extensions::STD_REG;
1693    use hugr_core::std_extensions::arithmetic::conversions::ConvertOpDef;
1694    use hugr_core::std_extensions::arithmetic::int_ops::IntOpDef;
1695    use hugr_core::std_extensions::collections::array::ArrayOpBuilder;
1696    use hugr_core::std_extensions::collections::array::op_builder::build_all_borrow_array_ops;
1697    use hugr_core::std_extensions::collections::borrow_array::{
1698        self, BArrayOpBuilder, BArrayRepeat, BArrayScan, BArrayToArray, borrow_array_type,
1699    };
1700    use hugr_core::types::Type;
1701    use hugr_core::{
1702        builder::{Dataflow, DataflowSubContainer, SubContainer},
1703        extension::{
1704            ExtensionRegistry,
1705            prelude::{self, ConstUsize, UnwrapBuilder as _, bool_t, option_type, usize_t},
1706        },
1707        ops::Value,
1708        std_extensions::{
1709            arithmetic::{
1710                int_ops::{self},
1711                int_types::{self, ConstInt, int_type},
1712            },
1713            logic,
1714        },
1715        type_row,
1716        types::Signature,
1717    };
1718    use itertools::Itertools as _;
1719    use rstest::rstest;
1720
1721    use crate::emit::test::PanicTestPreludeCodegen;
1722    use crate::extension::DefaultPreludeCodegen;
1723    use crate::types::HugrType;
1724    use crate::{
1725        check_emission,
1726        emit::test::SimpleHugrConfig,
1727        test::{TestContext, exec_ctx, llvm_ctx},
1728        utils::{IntOpBuilder, LogicOpBuilder},
1729    };
1730
1731    #[rstest]
1732    fn emit_all_ops(mut llvm_ctx: TestContext) {
1733        let hugr = SimpleHugrConfig::new()
1734            .with_extensions(STD_REG.to_owned())
1735            .finish(|mut builder| {
1736                build_all_borrow_array_ops(builder.dfg_builder_endo([]).unwrap())
1737                    .finish_sub_container()
1738                    .unwrap();
1739                builder.finish_hugr().unwrap()
1740            });
1741        llvm_ctx.add_extensions(|cge| {
1742            cge.add_default_prelude_extensions()
1743                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
1744        });
1745        check_emission!(hugr, llvm_ctx);
1746    }
1747
1748    #[rstest]
1749    fn emit_get(mut llvm_ctx: TestContext) {
1750        let hugr = SimpleHugrConfig::new()
1751            .with_extensions(STD_REG.to_owned())
1752            .finish(|mut builder| {
1753                let us1 = builder.add_load_value(ConstUsize::new(1));
1754                let us2 = builder.add_load_value(ConstUsize::new(2));
1755                let arr = builder.add_new_borrow_array(usize_t(), [us1, us2]).unwrap();
1756                let (_, arr) = builder
1757                    .add_borrow_array_get(usize_t(), 2, arr, us1)
1758                    .unwrap();
1759                builder.add_borrow_array_discard(usize_t(), 2, arr).unwrap();
1760                builder.finish_hugr_with_outputs([]).unwrap()
1761            });
1762        llvm_ctx.add_extensions(|cge| {
1763            cge.add_default_prelude_extensions()
1764                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
1765        });
1766        check_emission!(hugr, llvm_ctx);
1767    }
1768
1769    #[rstest]
1770    fn emit_clone(mut llvm_ctx: TestContext) {
1771        let hugr = SimpleHugrConfig::new()
1772            .with_extensions(STD_REG.to_owned())
1773            .finish(|mut builder| {
1774                let us1 = builder.add_load_value(ConstUsize::new(1));
1775                let us2 = builder.add_load_value(ConstUsize::new(2));
1776                let arr = builder.add_new_borrow_array(usize_t(), [us1, us2]).unwrap();
1777                let (arr1, arr2) = builder.add_borrow_array_clone(usize_t(), 2, arr).unwrap();
1778                builder
1779                    .add_borrow_array_discard(usize_t(), 2, arr1)
1780                    .unwrap();
1781                builder
1782                    .add_borrow_array_discard(usize_t(), 2, arr2)
1783                    .unwrap();
1784                builder.finish_hugr_with_outputs([]).unwrap()
1785            });
1786        llvm_ctx.add_extensions(|cge| {
1787            cge.add_default_prelude_extensions()
1788                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
1789        });
1790        check_emission!(hugr, llvm_ctx);
1791    }
1792
1793    #[rstest]
1794    fn emit_array_value(mut llvm_ctx: TestContext) {
1795        let hugr = SimpleHugrConfig::new()
1796            .with_extensions(STD_REG.to_owned())
1797            .with_outs(vec![borrow_array_type(2, usize_t())])
1798            .finish(|mut builder| {
1799                let vs = vec![ConstUsize::new(1).into(), ConstUsize::new(2).into()];
1800                let arr = builder.add_load_value(borrow_array::BArrayValue::new(usize_t(), vs));
1801                builder.finish_hugr_with_outputs([arr]).unwrap()
1802            });
1803        llvm_ctx.add_extensions(|cge| {
1804            cge.add_default_prelude_extensions()
1805                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
1806        });
1807        check_emission!(hugr, llvm_ctx);
1808    }
1809
1810    // #[rstest]
1811    // #[case(1, 2, 3)]
1812    // #[case(0, 0, 0)]
1813    // #[case(10, 20, 30)]
1814    // fn exec_unpack_and_sum(mut exec_ctx: TestContext, #[case] a: u64, #[case] b: u64, #[case] expected: u64) {
1815    //     let hugr = SimpleHugrConfig::new()
1816    //         .with_extensions(exec_registry())
1817    //         .with_outs(vec![usize_t()])
1818    //         .finish(|mut builder| {
1819    //             // Create an array with the test values
1820    //             let values = vec![ConstUsize::new(a).into(), ConstUsize::new(b).into()];
1821    //             let arr = builder.add_load_value(borrow_array::BArrayValue::new(usize_t(), values));
1822
1823    //             // Unpack the array
1824    //             let [val_a, val_b] = builder.add_array_unpack(usize_t(), 2, arr).unwrap().try_into().unwrap();
1825
1826    //             // Add the values
1827    //             let sum = {
1828    //                 let int_ty = int_type(6);
1829    //                 let a_int = builder.cast(val_a, int_ty.clone()).unwrap();
1830    //                 let b_int = builder.cast(val_b, int_ty.clone()).unwrap();
1831    //                 let sum_int = builder.add_iadd(6, a_int, b_int).unwrap();
1832    //                 builder.cast(sum_int, usize_t()).unwrap()
1833    //             };
1834
1835    //             builder.finish_hugr_with_outputs([sum]).unwrap()
1836    //         });
1837    //     exec_ctx.add_extensions(|cge| {
1838    //         cge.add_default_prelude_extensions()
1839    //             .add_default_borrow_array_extensions()
1840    //             .add_default_int_extensions()
1841    //     });
1842    //     assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
1843    // }
1844
1845    fn exec_registry() -> ExtensionRegistry {
1846        ExtensionRegistry::new([
1847            int_types::EXTENSION.to_owned(),
1848            int_ops::EXTENSION.to_owned(),
1849            logic::EXTENSION.to_owned(),
1850            prelude::PRELUDE.to_owned(),
1851            borrow_array::EXTENSION.to_owned(),
1852        ])
1853    }
1854
1855    #[rstest]
1856    #[case(0, 1)]
1857    #[case(1, 2)]
1858    #[case(3, 0)]
1859    #[case(999999, 0)]
1860    fn exec_get(mut exec_ctx: TestContext, #[case] index: u64, #[case] expected: u64) {
1861        // We build a HUGR that:
1862        // - Creates an array of [1,2]
1863        // - Gets the element at the given index
1864        // - Returns the element if the index is in bounds, otherwise 0
1865        let hugr = SimpleHugrConfig::new()
1866            .with_outs(usize_t())
1867            .with_extensions(exec_registry())
1868            .finish(|mut builder| {
1869                let us0 = builder.add_load_value(ConstUsize::new(0));
1870                let us1 = builder.add_load_value(ConstUsize::new(1));
1871                let us2 = builder.add_load_value(ConstUsize::new(2));
1872                let arr = builder.add_new_borrow_array(usize_t(), [us1, us2]).unwrap();
1873                let i = builder.add_load_value(ConstUsize::new(index));
1874                let (get_r, arr) = builder.add_borrow_array_get(usize_t(), 2, arr, i).unwrap();
1875                builder.add_borrow_array_discard(usize_t(), 2, arr).unwrap();
1876                let r = {
1877                    let ot = option_type(usize_t());
1878                    let variants = (0..ot.num_variants())
1879                        .map(|i| ot.get_variant(i).cloned().unwrap().try_into().unwrap())
1880                        .collect_vec();
1881                    let mut builder = builder
1882                        .conditional_builder((variants, get_r), [], usize_t().into())
1883                        .unwrap();
1884                    {
1885                        let failure_case = builder.case_builder(0).unwrap();
1886                        failure_case.finish_with_outputs([us0]).unwrap();
1887                    }
1888                    {
1889                        let success_case = builder.case_builder(1).unwrap();
1890                        let inputs = success_case.input_wires();
1891                        success_case.finish_with_outputs(inputs).unwrap();
1892                    }
1893                    builder.finish_sub_container().unwrap().out_wire(0)
1894                };
1895                builder.finish_hugr_with_outputs([r]).unwrap()
1896            });
1897        exec_ctx.add_extensions(|cge| {
1898            cge.add_default_prelude_extensions()
1899                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
1900        });
1901        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
1902    }
1903
1904    #[rstest]
1905    #[case(0, 3, 1, [3,2])]
1906    #[case(1, 3, 2, [1,3])]
1907    #[case(2, 3, 3, [1,2])]
1908    #[case(999999, 3, 3, [1,2])]
1909    fn exec_set(
1910        mut exec_ctx: TestContext,
1911        #[case] index: u64,
1912        #[case] value: u64,
1913        #[case] expected_elem: u64,
1914        #[case] expected_arr: [u64; 2],
1915    ) {
1916        // We build a HUGR that
1917        // - Creates an array: [1,2]
1918        // - Sets the element at the given index to the given value
1919        // - Checks the following, returning 1 iff they are all true:
1920        //   - The element returned from set is `expected_elem`
1921        //   - The Oth element of the resulting array is `expected_arr_0`
1922
1923        use hugr_core::extension::prelude::either_type;
1924        let int_ty = int_type(3);
1925        let hugr = SimpleHugrConfig::new()
1926            .with_outs(usize_t())
1927            .with_extensions(exec_registry())
1928            .finish(|mut builder| {
1929                let us0 = builder.add_load_value(ConstUsize::new(0));
1930                let us1 = builder.add_load_value(ConstUsize::new(1));
1931                let i1 = builder.add_load_value(ConstInt::new_u(3, 1).unwrap());
1932                let i2 = builder.add_load_value(ConstInt::new_u(3, 2).unwrap());
1933                let arr = builder
1934                    .add_new_borrow_array(int_ty.clone(), [i1, i2])
1935                    .unwrap();
1936                let index = builder.add_load_value(ConstUsize::new(index));
1937                let value = builder.add_load_value(ConstInt::new_u(3, value).unwrap());
1938                let get_r = builder
1939                    .add_borrow_array_set(int_ty.clone(), 2, arr, index, value)
1940                    .unwrap();
1941                let r = {
1942                    let res_sum_ty = {
1943                        let row = vec![int_ty.clone(), borrow_array_type(2, int_ty.clone())];
1944                        either_type(row.clone(), row)
1945                    };
1946                    let variants = (0..res_sum_ty.num_variants())
1947                        .map(|i| {
1948                            res_sum_ty
1949                                .get_variant(i)
1950                                .cloned()
1951                                .unwrap()
1952                                .try_into()
1953                                .unwrap()
1954                        })
1955                        .collect_vec();
1956                    let mut builder = builder
1957                        .conditional_builder((variants, get_r), [], bool_t().into())
1958                        .unwrap();
1959                    for i in 0..2 {
1960                        let mut builder = builder.case_builder(i).unwrap();
1961                        let [elem, arr] = builder.input_wires_arr();
1962                        let expected_elem =
1963                            builder.add_load_value(ConstInt::new_u(3, expected_elem).unwrap());
1964                        let expected_arr_0 =
1965                            builder.add_load_value(ConstInt::new_u(3, expected_arr[0]).unwrap());
1966                        let expected_arr_1 =
1967                            builder.add_load_value(ConstInt::new_u(3, expected_arr[1]).unwrap());
1968                        let (r, arr) = builder
1969                            .add_borrow_array_get(int_ty.clone(), 2, arr, us0)
1970                            .unwrap();
1971                        let [arr_0] = builder
1972                            .build_unwrap_sum(1, option_type(int_ty.clone()), r)
1973                            .unwrap();
1974                        let (r, arr) = builder
1975                            .add_borrow_array_get(int_ty.clone(), 2, arr, us1)
1976                            .unwrap();
1977                        let [arr_1] = builder
1978                            .build_unwrap_sum(1, option_type(int_ty.clone()), r)
1979                            .unwrap();
1980                        let elem_eq = builder.add_ieq(3, elem, expected_elem).unwrap();
1981                        let arr_0_eq = builder.add_ieq(3, arr_0, expected_arr_0).unwrap();
1982                        let arr_1_eq = builder.add_ieq(3, arr_1, expected_arr_1).unwrap();
1983                        let r = builder.add_and(elem_eq, arr_0_eq).unwrap();
1984                        let r = builder.add_and(r, arr_1_eq).unwrap();
1985                        builder
1986                            .add_borrow_array_discard(int_ty.clone(), 2, arr)
1987                            .unwrap();
1988                        builder.finish_with_outputs([r]).unwrap();
1989                    }
1990                    builder.finish_sub_container().unwrap().out_wire(0)
1991                };
1992                let r = {
1993                    let mut conditional = builder
1994                        .conditional_builder(([type_row![], type_row![]], r), [], usize_t().into())
1995                        .unwrap();
1996                    conditional
1997                        .case_builder(0)
1998                        .unwrap()
1999                        .finish_with_outputs([us0])
2000                        .unwrap();
2001                    conditional
2002                        .case_builder(1)
2003                        .unwrap()
2004                        .finish_with_outputs([us1])
2005                        .unwrap();
2006                    conditional.finish_sub_container().unwrap().out_wire(0)
2007                };
2008                builder.finish_hugr_with_outputs([r]).unwrap()
2009            });
2010        exec_ctx.add_extensions(|cge| {
2011            cge.add_default_prelude_extensions()
2012                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2013                .add_default_int_extensions()
2014                .add_logic_extensions()
2015        });
2016        assert_eq!(1, exec_ctx.exec_hugr_u64(hugr, "main"));
2017    }
2018
2019    #[rstest]
2020    #[case(0, 1, [2,1], true)]
2021    #[case(0, 0, [1,2], true)]
2022    #[case(0, 2, [1,2], false)]
2023    #[case(2, 0, [1,2], false)]
2024    #[case(9999999, 0, [1,2], false)]
2025    #[case(0, 9999999, [1,2], false)]
2026    fn exec_swap(
2027        mut exec_ctx: TestContext,
2028        #[case] index1: u64,
2029        #[case] index2: u64,
2030        #[case] expected_arr: [u64; 2],
2031        #[case] expected_succeeded: bool,
2032    ) {
2033        // We build a HUGR that:
2034        // - Creates an array: [1 ,2]
2035        // - Swaps the elements at the given indices
2036        // - Checks the following, returning 1 iff the following are all true:
2037        //  - The element at index 0 is `expected_elem_0`
2038        //  - The swap operation succeeded iff `expected_succeeded`
2039
2040        let int_ty = int_type(3);
2041        let arr_ty = borrow_array_type(2, int_ty.clone());
2042        let hugr = SimpleHugrConfig::new()
2043            .with_outs(usize_t())
2044            .with_extensions(exec_registry())
2045            .finish(|mut builder| {
2046                let us0 = builder.add_load_value(ConstUsize::new(0));
2047                let us1 = builder.add_load_value(ConstUsize::new(1));
2048                let i1 = builder.add_load_value(ConstInt::new_u(3, 1).unwrap());
2049                let i2 = builder.add_load_value(ConstInt::new_u(3, 2).unwrap());
2050                let arr = builder
2051                    .add_new_borrow_array(int_ty.clone(), [i1, i2])
2052                    .unwrap();
2053
2054                let index1 = builder.add_load_value(ConstUsize::new(index1));
2055                let index2 = builder.add_load_value(ConstUsize::new(index2));
2056                let r = builder
2057                    .add_borrow_array_swap(int_ty.clone(), 2, arr, index1, index2)
2058                    .unwrap();
2059                let [arr, was_expected_success] = {
2060                    let mut conditional = builder
2061                        .conditional_builder(
2062                            (
2063                                [vec![arr_ty.clone()].into(), vec![arr_ty.clone()].into()],
2064                                r,
2065                            ),
2066                            [],
2067                            vec![arr_ty, bool_t()].into(),
2068                        )
2069                        .unwrap();
2070                    for i in 0..2 {
2071                        let mut case = conditional.case_builder(i).unwrap();
2072                        let [arr] = case.input_wires_arr();
2073                        let was_expected_success =
2074                            case.add_load_value(if (i == 1) == expected_succeeded {
2075                                Value::true_val()
2076                            } else {
2077                                Value::false_val()
2078                            });
2079                        case.finish_with_outputs([arr, was_expected_success])
2080                            .unwrap();
2081                    }
2082                    conditional.finish_sub_container().unwrap().outputs_arr()
2083                };
2084                let (r, arr) = builder
2085                    .add_borrow_array_get(int_ty.clone(), 2, arr, us0)
2086                    .unwrap();
2087                let elem_0 = builder
2088                    .build_unwrap_sum::<1>(1, option_type(int_ty.clone()), r)
2089                    .unwrap()[0];
2090                let (r, arr) = builder
2091                    .add_borrow_array_get(int_ty.clone(), 2, arr, us1)
2092                    .unwrap();
2093                let elem_1 = builder
2094                    .build_unwrap_sum::<1>(1, option_type(int_ty.clone()), r)
2095                    .unwrap()[0];
2096                let expected_elem_0 =
2097                    builder.add_load_value(ConstInt::new_u(3, expected_arr[0]).unwrap());
2098                let elem_0_ok = builder.add_ieq(3, elem_0, expected_elem_0).unwrap();
2099                let expected_elem_1 =
2100                    builder.add_load_value(ConstInt::new_u(3, expected_arr[1]).unwrap());
2101                let elem_1_ok = builder.add_ieq(3, elem_1, expected_elem_1).unwrap();
2102                let r = builder.add_and(was_expected_success, elem_0_ok).unwrap();
2103                let r = builder.add_and(r, elem_1_ok).unwrap();
2104                let r = {
2105                    let mut conditional = builder
2106                        .conditional_builder(([type_row![], type_row![]], r), [], usize_t().into())
2107                        .unwrap();
2108                    conditional
2109                        .case_builder(0)
2110                        .unwrap()
2111                        .finish_with_outputs([us0])
2112                        .unwrap();
2113                    conditional
2114                        .case_builder(1)
2115                        .unwrap()
2116                        .finish_with_outputs([us1])
2117                        .unwrap();
2118                    conditional.finish_sub_container().unwrap().out_wire(0)
2119                };
2120                builder
2121                    .add_borrow_array_discard(int_ty.clone(), 2, arr)
2122                    .unwrap();
2123                builder.finish_hugr_with_outputs([r]).unwrap()
2124            });
2125        exec_ctx.add_extensions(|cge| {
2126            cge.add_default_prelude_extensions()
2127                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2128                .add_default_int_extensions()
2129                .add_logic_extensions()
2130        });
2131        assert_eq!(1, exec_ctx.exec_hugr_u64(hugr, "main"));
2132    }
2133
2134    #[rstest]
2135    #[case(0, 5)]
2136    #[case(1, 5)]
2137    fn exec_clone(mut exec_ctx: TestContext, #[case] index: u64, #[case] new_v: u64) {
2138        // We build a HUGR that:
2139        // - Creates an array: [1, 2]
2140        // - Clones the array
2141        // - Mutates the original at the given index
2142        // - Returns the unchanged element of the cloned array
2143
2144        let int_ty = int_type(3);
2145        let arr_ty = borrow_array_type(2, int_ty.clone());
2146        let hugr = SimpleHugrConfig::new()
2147            .with_outs(int_ty.clone())
2148            .with_extensions(exec_registry())
2149            .finish(|mut builder| {
2150                let idx = builder.add_load_value(ConstUsize::new(index));
2151                let i1 = builder.add_load_value(ConstInt::new_u(3, 1).unwrap());
2152                let i2 = builder.add_load_value(ConstInt::new_u(3, 2).unwrap());
2153                let inew = builder.add_load_value(ConstInt::new_u(3, new_v).unwrap());
2154                let arr = builder
2155                    .add_new_borrow_array(int_ty.clone(), [i1, i2])
2156                    .unwrap();
2157
2158                let (arr, arr_clone) = builder
2159                    .add_borrow_array_clone(int_ty.clone(), 2, arr)
2160                    .unwrap();
2161                let r = builder
2162                    .add_borrow_array_set(int_ty.clone(), 2, arr, idx, inew)
2163                    .unwrap();
2164                let [_, arr] = builder
2165                    .build_unwrap_sum(
2166                        1,
2167                        either_type(
2168                            vec![int_ty.clone(), arr_ty.clone()],
2169                            vec![int_ty.clone(), arr_ty.clone()],
2170                        ),
2171                        r,
2172                    )
2173                    .unwrap();
2174                let (r, arr_clone) = builder
2175                    .add_borrow_array_get(int_ty.clone(), 2, arr_clone, idx)
2176                    .unwrap();
2177                let [elem] = builder
2178                    .build_unwrap_sum(1, option_type(int_ty.clone()), r)
2179                    .unwrap();
2180                builder
2181                    .add_borrow_array_discard(int_ty.clone(), 2, arr)
2182                    .unwrap();
2183                builder
2184                    .add_borrow_array_discard(int_ty.clone(), 2, arr_clone)
2185                    .unwrap();
2186                builder.finish_hugr_with_outputs([elem]).unwrap()
2187            });
2188        exec_ctx.add_extensions(|cge| {
2189            cge.add_default_prelude_extensions()
2190                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2191                .add_default_int_extensions()
2192                .add_logic_extensions()
2193        });
2194        assert_eq!([1, 2][index as usize], exec_ctx.exec_hugr_u64(hugr, "main"));
2195    }
2196
2197    #[rstest]
2198    #[case(&[], 0)]
2199    #[case(&[true], 1)]
2200    #[case(&[false], 4)]
2201    #[case(&[true, true], 3)]
2202    #[case(&[false, false], 6)]
2203    #[case(&[true, false, true], 7)]
2204    #[case(&[false, true, false], 7)]
2205    fn exec_pop(mut exec_ctx: TestContext, #[case] from_left: &[bool], #[case] expected: u64) {
2206        // We build a HUGR that:
2207        // - Creates an array: [1,2,4]
2208        // - Pops `num` elements from the left or right
2209        // - Returns the sum of the popped elements
2210
2211        let array_contents = [1, 2, 4];
2212        let int_ty = int_type(6);
2213        let hugr = SimpleHugrConfig::new()
2214            .with_outs(int_ty.clone())
2215            .with_extensions(exec_registry())
2216            .finish(|mut builder| {
2217                let mut r = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2218                let new_array_args = array_contents
2219                    .iter()
2220                    .map(|&i| builder.add_load_value(ConstInt::new_u(6, i).unwrap()))
2221                    .collect_vec();
2222                let mut arr = builder
2223                    .add_new_borrow_array(int_ty.clone(), new_array_args)
2224                    .unwrap();
2225                for (i, left) in from_left.iter().enumerate() {
2226                    let array_size = (array_contents.len() - i) as u64;
2227                    let pop_res = if *left {
2228                        builder
2229                            .add_borrow_array_pop_left(int_ty.clone(), array_size, arr)
2230                            .unwrap()
2231                    } else {
2232                        builder
2233                            .add_borrow_array_pop_right(int_ty.clone(), array_size, arr)
2234                            .unwrap()
2235                    };
2236                    let [elem, new_arr] = builder
2237                        .build_unwrap_sum(
2238                            1,
2239                            option_type(vec![
2240                                int_ty.clone(),
2241                                borrow_array_type(array_size - 1, int_ty.clone()),
2242                            ]),
2243                            pop_res,
2244                        )
2245                        .unwrap();
2246                    arr = new_arr;
2247                    r = builder.add_iadd(6, r, elem).unwrap();
2248                }
2249                builder
2250                    .add_borrow_array_discard(
2251                        int_ty.clone(),
2252                        (array_contents.len() - from_left.len()) as u64,
2253                        arr,
2254                    )
2255                    .unwrap();
2256                builder.finish_hugr_with_outputs([r]).unwrap()
2257            });
2258        exec_ctx.add_extensions(|cge| {
2259            cge.add_default_prelude_extensions()
2260                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2261                .add_default_int_extensions()
2262        });
2263        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2264    }
2265
2266    #[rstest]
2267    #[case(&[], 0)]
2268    #[case(&[1, 2], 3)]
2269    #[case(&[6, 6, 6], 18)]
2270    fn exec_unpack(
2271        mut exec_ctx: TestContext,
2272        #[case] array_contents: &[u64],
2273        #[case] expected: u64,
2274    ) {
2275        // We build a HUGR that:
2276        // - Loads an array with the given contents
2277        // - Unpacks all the elements
2278        // - Returns the sum of the elements
2279
2280        let int_ty = int_type(6);
2281        let hugr = SimpleHugrConfig::new()
2282            .with_outs(int_ty.clone())
2283            .with_extensions(exec_registry())
2284            .finish(|mut builder| {
2285                let array = borrow_array::BArrayValue::new(
2286                    int_ty.clone(),
2287                    array_contents
2288                        .iter()
2289                        .map(|&i| ConstInt::new_u(6, i).unwrap().into())
2290                        .collect_vec(),
2291                );
2292                let array = builder.add_load_value(array);
2293                let unpacked = builder
2294                    .add_borrow_array_unpack(int_ty.clone(), array_contents.len() as u64, array)
2295                    .unwrap();
2296                let mut r = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2297                for elem in unpacked {
2298                    r = builder.add_iadd(6, r, elem).unwrap();
2299                }
2300
2301                builder.finish_hugr_with_outputs([r]).unwrap()
2302            });
2303        exec_ctx.add_extensions(|cge| {
2304            cge.add_default_prelude_extensions()
2305                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2306                .add_default_int_extensions()
2307        });
2308        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2309    }
2310
2311    #[rstest]
2312    #[case(5, 42, 0)]
2313    #[case(5, 42, 1)]
2314    #[case(5, 42, 2)]
2315    #[case(5, 42, 3)]
2316    #[case(5, 42, 4)]
2317    fn exec_repeat(
2318        mut exec_ctx: TestContext,
2319        #[case] size: u64,
2320        #[case] value: u64,
2321        #[case] idx: u64,
2322    ) {
2323        // We build a HUGR that:
2324        // - Contains a nested function that returns `value`
2325        // - Creates an array of length `size` populated via this function
2326        // - Looks up the value at `idx` and returns it
2327
2328        let int_ty = int_type(6);
2329        let hugr = SimpleHugrConfig::new()
2330            .with_outs(int_ty.clone())
2331            .with_extensions(exec_registry())
2332            .finish(|mut builder| {
2333                let mut mb = builder.module_root_builder();
2334                let mut func = mb
2335                    .define_function("foo", Signature::new(vec![], vec![int_ty.clone()]))
2336                    .unwrap();
2337                let v = func.add_load_value(ConstInt::new_u(6, value).unwrap());
2338                let func_id = func.finish_with_outputs(vec![v]).unwrap();
2339                let func_v = builder.load_func(func_id.handle(), &[]).unwrap();
2340                let repeat = BArrayRepeat::new(int_ty.clone(), size);
2341                let arr = builder
2342                    .add_dataflow_op(repeat, vec![func_v])
2343                    .unwrap()
2344                    .out_wire(0);
2345                let idx_v = builder.add_load_value(ConstUsize::new(idx));
2346                let (get_res, arr) = builder
2347                    .add_borrow_array_get(int_ty.clone(), size, arr, idx_v)
2348                    .unwrap();
2349                let [elem] = builder
2350                    .build_unwrap_sum(1, option_type(vec![int_ty.clone()]), get_res)
2351                    .unwrap();
2352                builder
2353                    .add_borrow_array_discard(int_ty.clone(), size, arr)
2354                    .unwrap();
2355                builder.finish_hugr_with_outputs([elem]).unwrap()
2356            });
2357        exec_ctx.add_extensions(|cge| {
2358            cge.add_default_prelude_extensions()
2359                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2360                .add_default_int_extensions()
2361        });
2362        assert_eq!(value, exec_ctx.exec_hugr_u64(hugr, "main"));
2363    }
2364
2365    #[rstest]
2366    #[case(10, 1)]
2367    #[case(10, 2)]
2368    #[case(0, 1)]
2369    fn exec_scan_map(mut exec_ctx: TestContext, #[case] size: u64, #[case] inc: u64) {
2370        // We build a HUGR that:
2371        // - Creates an array [1, 2, 3, ..., size]
2372        // - Maps a function that increments each element by `inc`
2373        // - Returns the sum of the array elements
2374
2375        let int_ty = int_type(6);
2376        let hugr = SimpleHugrConfig::new()
2377            .with_outs(int_ty.clone())
2378            .with_extensions(exec_registry())
2379            .finish(|mut builder| {
2380                let mut r = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2381                let new_array_args = (0..size)
2382                    .map(|i| builder.add_load_value(ConstInt::new_u(6, i).unwrap()))
2383                    .collect_vec();
2384                let arr = builder
2385                    .add_new_borrow_array(int_ty.clone(), new_array_args)
2386                    .unwrap();
2387
2388                let mut mb = builder.module_root_builder();
2389                let mut func = mb
2390                    .define_function(
2391                        "foo",
2392                        Signature::new(vec![int_ty.clone()], vec![int_ty.clone()]),
2393                    )
2394                    .unwrap();
2395                let [elem] = func.input_wires_arr();
2396                let delta = func.add_load_value(ConstInt::new_u(6, inc).unwrap());
2397                let out = func.add_iadd(6, elem, delta).unwrap();
2398                let func_id = func.finish_with_outputs(vec![out]).unwrap();
2399                let func_v = builder.load_func(func_id.handle(), &[]).unwrap();
2400                let scan = BArrayScan::new(int_ty.clone(), int_ty.clone(), vec![], size);
2401                let mut arr = builder
2402                    .add_dataflow_op(scan, [arr, func_v])
2403                    .unwrap()
2404                    .out_wire(0);
2405
2406                for i in 0..size {
2407                    let array_size = size - i;
2408                    let pop_res = builder
2409                        .add_borrow_array_pop_left(int_ty.clone(), array_size, arr)
2410                        .unwrap();
2411                    let [elem, new_arr] = builder
2412                        .build_unwrap_sum(
2413                            1,
2414                            option_type(vec![
2415                                int_ty.clone(),
2416                                borrow_array_type(array_size - 1, int_ty.clone()),
2417                            ]),
2418                            pop_res,
2419                        )
2420                        .unwrap();
2421                    arr = new_arr;
2422                    r = builder.add_iadd(6, r, elem).unwrap();
2423                }
2424                builder
2425                    .add_borrow_array_discard_empty(int_ty.clone(), arr)
2426                    .unwrap();
2427                builder.finish_hugr_with_outputs([r]).unwrap()
2428            });
2429        exec_ctx.add_extensions(|cge| {
2430            cge.add_default_prelude_extensions()
2431                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2432                .add_default_int_extensions()
2433        });
2434        let expected: u64 = (inc..size + inc).sum();
2435        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2436    }
2437
2438    #[rstest]
2439    #[case(0)]
2440    #[case(1)]
2441    #[case(10)]
2442    fn exec_scan_fold(mut exec_ctx: TestContext, #[case] size: u64) {
2443        // We build a HUGR that:
2444        // - Creates an array [1, 2, 3, ..., size]
2445        // - Sums up the elements of the array using a scan and returns that sum
2446
2447        let int_ty = int_type(6);
2448        let hugr = SimpleHugrConfig::new()
2449            .with_outs(int_ty.clone())
2450            .with_extensions(exec_registry())
2451            .finish(|mut builder| {
2452                let new_array_args = (0..size)
2453                    .map(|i| builder.add_load_value(ConstInt::new_u(6, i).unwrap()))
2454                    .collect_vec();
2455                let arr = builder
2456                    .add_new_borrow_array(int_ty.clone(), new_array_args)
2457                    .unwrap();
2458
2459                let mut mb = builder.module_root_builder();
2460                let mut func = mb
2461                    .define_function(
2462                        "foo",
2463                        Signature::new(
2464                            vec![int_ty.clone(), int_ty.clone()],
2465                            vec![Type::UNIT, int_ty.clone()],
2466                        ),
2467                    )
2468                    .unwrap();
2469                let [elem, acc] = func.input_wires_arr();
2470                let acc = func.add_iadd(6, elem, acc).unwrap();
2471                let unit = func
2472                    .add_dataflow_op(Tag::new(0, vec![type_row![]]), [])
2473                    .unwrap()
2474                    .out_wire(0);
2475                let func_id = func.finish_with_outputs(vec![unit, acc]).unwrap();
2476                let func_v = builder.load_func(func_id.handle(), &[]).unwrap();
2477                let scan = BArrayScan::new(int_ty.clone(), Type::UNIT, vec![int_ty.clone()], size);
2478                let zero = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2479                let [arr, sum] = builder
2480                    .add_dataflow_op(scan, [arr, func_v, zero])
2481                    .unwrap()
2482                    .outputs_arr();
2483                builder
2484                    .add_borrow_array_discard(Type::UNIT, size, arr)
2485                    .unwrap();
2486                builder.finish_hugr_with_outputs([sum]).unwrap()
2487            });
2488        exec_ctx.add_extensions(|cge| {
2489            cge.add_default_prelude_extensions()
2490                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2491                .add_default_int_extensions()
2492        });
2493        let expected: u64 = (0..size).sum();
2494        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2495    }
2496
2497    fn build_pops(
2498        builder: &mut impl Dataflow,
2499        elem_ty: HugrType,
2500        size: u64,
2501        mut array: Wire,
2502        num_pops: u64,
2503    ) -> Wire {
2504        for i in 0..num_pops {
2505            let res = builder
2506                .add_borrow_array_pop_left(elem_ty.clone(), size - i, array)
2507                .unwrap();
2508            let [_, arr] = builder
2509                .build_unwrap_sum(
2510                    1,
2511                    option_type(vec![
2512                        elem_ty.clone(),
2513                        borrow_array_type(size - i - 1, elem_ty.clone()),
2514                    ]),
2515                    res,
2516                )
2517                .unwrap();
2518            array = arr;
2519        }
2520        array
2521    }
2522
2523    #[rstest]
2524    #[case::single_block(48, 10, &[0, 11, 12, 13, 36, 37])]
2525    #[case::block_boundary1(65, 0, &[63, 64])]
2526    #[case::block_boundary2(65, 10, &[53, 54])]
2527    #[case::block_boundary3(129, 0, &[63, 64, 127, 128])]
2528    fn exec_borrow_return(
2529        mut exec_ctx: TestContext,
2530        #[case] mut size: u64,
2531        #[case] num_pops: u64,
2532        #[case] indices: &[u64],
2533    ) {
2534        // We build a HUGR that:
2535        // - Loads an array filled with 0..size
2536        // - Pops specified numbers from the left to introduce an offset
2537        // - Borrows the elements at the provided indices and sums them up
2538        // - Puts 0s in the borrowed places in reverse order
2539        // - Borrows the indices again
2540
2541        let int_ty = int_type(6);
2542        let hugr = SimpleHugrConfig::new()
2543            .with_outs(int_ty.clone())
2544            .with_extensions(exec_registry())
2545            .finish(|mut builder| {
2546                let array = borrow_array::BArrayValue::new(
2547                    int_ty.clone(),
2548                    (0..size)
2549                        .map(|i| ConstInt::new_u(6, i).unwrap().into())
2550                        .collect_vec(),
2551                );
2552                let mut array = builder.add_load_value(array);
2553                array = build_pops(&mut builder, int_ty.clone(), size, array, num_pops);
2554                size -= num_pops;
2555                let mut r = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2556                for &i in indices {
2557                    let i = builder.add_load_value(ConstUsize::new(i));
2558                    let (arr, val) = builder
2559                        .add_borrow_array_borrow(int_ty.clone(), size, array, i)
2560                        .unwrap();
2561                    r = builder.add_iadd(6, r, val).unwrap();
2562                    array = arr;
2563                }
2564                let zero = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2565                for &i in indices.iter().rev() {
2566                    let i = builder.add_load_value(ConstUsize::new(i));
2567                    array = builder
2568                        .add_borrow_array_return(int_ty.clone(), size, array, i, zero)
2569                        .unwrap();
2570                }
2571                for &i in indices {
2572                    let i = builder.add_load_value(ConstUsize::new(i));
2573                    let (arr, val) = builder
2574                        .add_borrow_array_borrow(int_ty.clone(), size, array, i)
2575                        .unwrap();
2576                    r = builder.add_iadd(6, r, val).unwrap();
2577                    array = arr;
2578                }
2579                builder
2580                    .add_borrow_array_discard(int_ty.clone(), size, array)
2581                    .unwrap();
2582                builder.finish_hugr_with_outputs([r]).unwrap()
2583            });
2584        exec_ctx.add_extensions(|cge| {
2585            cge.add_default_prelude_extensions()
2586                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2587                .add_default_int_extensions()
2588        });
2589        let expected: u64 = indices.iter().map(|i| i + num_pops).sum();
2590        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2591    }
2592
2593    #[rstest]
2594    #[case::empty(0, 0)]
2595    #[case::basic(32, 0)]
2596    #[case::boundary(65, 0)]
2597    #[case::pop1(65, 10)]
2598    #[case::pop2(200, 32)]
2599    fn exec_discard_all_borrowed(
2600        mut exec_ctx: TestContext,
2601        #[case] mut size: u64,
2602        #[case] num_pops: u64,
2603    ) {
2604        // We build a HUGR that:
2605        // - Loads an array filled with 0..size
2606        // - Pops specified numbers from the left to introduce an offset
2607        // - Borrows the remaining elements and sums them up
2608        // - Discards the array using `discard_all_borrowed`
2609
2610        let int_ty = int_type(6);
2611        let hugr = SimpleHugrConfig::new()
2612            .with_outs(int_ty.clone())
2613            .with_extensions(exec_registry())
2614            .finish(|mut builder| {
2615                let array = borrow_array::BArrayValue::new(
2616                    int_ty.clone(),
2617                    (0..size)
2618                        .map(|i| ConstInt::new_u(6, i).unwrap().into())
2619                        .collect_vec(),
2620                );
2621                let mut array = builder.add_load_value(array);
2622                array = build_pops(&mut builder, int_ty.clone(), size, array, num_pops);
2623                size -= num_pops;
2624                let mut r = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2625                for i in 0..size {
2626                    let i = builder.add_load_value(ConstUsize::new(i));
2627                    let (arr, val) = builder
2628                        .add_borrow_array_borrow(int_ty.clone(), size, array, i)
2629                        .unwrap();
2630                    r = builder.add_iadd(6, r, val).unwrap();
2631                    array = arr;
2632                }
2633                builder
2634                    .add_discard_all_borrowed(int_ty.clone(), size, array)
2635                    .unwrap();
2636                builder.finish_hugr_with_outputs([r]).unwrap()
2637            });
2638        exec_ctx.add_extensions(|cge| {
2639            cge.add_default_prelude_extensions()
2640                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2641                .add_default_int_extensions()
2642        });
2643        let expected: u64 = (num_pops..size + num_pops).sum();
2644        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2645    }
2646
2647    #[rstest]
2648    #[case::small(10, 0)]
2649    #[case::small_pop(10, 2)]
2650    #[case::oneword(64, 0)]
2651    #[case::oneword_pop(64, 5)]
2652    #[case::big(97, 0)]
2653    #[case::big_pop(97, 5)]
2654    #[case::big_pop_until_small(97, 65)]
2655    fn exec_discard_all_borrowed2(
2656        mut exec_ctx: TestContext,
2657        #[case] size: u64,
2658        #[case] num_pops: u64,
2659    ) {
2660        let int_ty = int_type(6);
2661        let hugr = SimpleHugrConfig::new()
2662            .with_extensions(exec_registry())
2663            .finish(|mut builder| {
2664                let mut array = builder.add_new_all_borrowed(int_ty.clone(), size).unwrap();
2665                let val = builder.add_load_value(ConstInt::new_u(6, 15).unwrap());
2666                for i in 0..num_pops {
2667                    let i = builder.add_load_value(ConstUsize::new(i));
2668                    array = builder
2669                        .add_borrow_array_return(int_ty.clone(), size, array, i, val)
2670                        .unwrap();
2671                }
2672                let array = build_pops(&mut builder, int_ty.clone(), size, array, num_pops);
2673                builder
2674                    .add_discard_all_borrowed(int_ty.clone(), size - num_pops, array)
2675                    .unwrap();
2676                builder.finish_hugr_with_outputs([]).unwrap()
2677            });
2678
2679        exec_ctx.add_extensions(|cge| {
2680            cge.add_prelude_extensions(PanicTestPreludeCodegen)
2681                .add_default_borrow_array_extensions(PanicTestPreludeCodegen)
2682                .add_default_int_extensions()
2683        });
2684        assert_eq!(&exec_ctx.exec_hugr_panicking(hugr, "main"), "");
2685    }
2686
2687    #[rstest]
2688    #[case::basic(32, 0)]
2689    #[case::boundary(65, 0)]
2690    #[case::pop1(65, 10)]
2691    #[case::pop2(200, 32)]
2692    fn exec_conversion_roundtrip(
2693        mut exec_ctx: TestContext,
2694        #[case] mut size: u64,
2695        #[case] num_pops: u64,
2696    ) {
2697        // We build a HUGR that:
2698        // - Loads a borrow array filled with 0..size
2699        // - Pops specified numbers from the left to introduce an offset
2700        // - Converts it into a regular array
2701        // - Converts it back into a borrow array
2702        // - Borrows all elements, sums them up, and returns the sum
2703
2704        let int_ty = int_type(6);
2705        let hugr = SimpleHugrConfig::new()
2706            .with_outs(int_ty.clone())
2707            .with_extensions(exec_registry())
2708            .finish(|mut builder| {
2709                use hugr_core::std_extensions::collections::borrow_array::BArrayFromArray;
2710
2711                let barray = borrow_array::BArrayValue::new(
2712                    int_ty.clone(),
2713                    (0..size)
2714                        .map(|i| ConstInt::new_u(6, i).unwrap().into())
2715                        .collect_vec(),
2716                );
2717                let barray = builder.add_load_value(barray);
2718                let barray = build_pops(&mut builder, int_ty.clone(), size, barray, num_pops);
2719                size -= num_pops;
2720                let array = builder
2721                    .add_dataflow_op(BArrayToArray::new(int_ty.clone(), size), [barray])
2722                    .unwrap()
2723                    .out_wire(0);
2724                let mut barray = builder
2725                    .add_dataflow_op(BArrayFromArray::new(int_ty.clone(), size), [array])
2726                    .unwrap()
2727                    .out_wire(0);
2728
2729                let mut r = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2730                for i in 0..size {
2731                    let i = builder.add_load_value(ConstUsize::new(i));
2732                    let (arr, val) = builder
2733                        .add_borrow_array_borrow(int_ty.clone(), size, barray, i)
2734                        .unwrap();
2735                    r = builder.add_iadd(6, r, val).unwrap();
2736                    barray = arr;
2737                }
2738                builder
2739                    .add_discard_all_borrowed(int_ty.clone(), size, barray)
2740                    .unwrap();
2741                builder.finish_hugr_with_outputs([r]).unwrap()
2742            });
2743        exec_ctx.add_extensions(|cge| {
2744            cge.add_default_prelude_extensions()
2745                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
2746                .add_default_array_extensions()
2747                .add_default_int_extensions()
2748        });
2749        let expected: u64 = (num_pops..size + num_pops).sum();
2750        assert_eq!(expected, exec_ctx.exec_hugr_u64(hugr, "main"));
2751    }
2752
2753    #[rstest]
2754    #[case::oob(1, 0, [0, 1], "Index out of bounds")]
2755    #[case::double_borrow(32, 0, [0, 0], "Array element is already borrowed")]
2756    #[case::double_borrow_pop(32, 1, [1, 0], "Array element is already borrowed")]
2757    #[case::double_borrow_boundary(65, 1, [64, 63], "Array element is already borrowed")]
2758    #[case::pop_borrowed(32, 1, [0, 10], "Array element is already borrowed")]
2759    fn exec_borrow_panics(
2760        mut exec_ctx: TestContext,
2761        #[case] mut size: u64,
2762        #[case] num_pops: u64,
2763        #[case] indices: [u64; 2],
2764        #[case] msg: &str,
2765    ) {
2766        // We build a HUGR that:
2767        // - Loads an array filled with 0..size
2768        // - Borrows element `indices[0]`
2769        // - Pops specified numbers from the left to introduce an offset
2770        // - Borrows element `indices[1]`
2771        // - Checks that the program panics with the given message
2772
2773        let int_ty = int_type(6);
2774        let hugr = SimpleHugrConfig::new()
2775            .with_extensions(exec_registry())
2776            .finish(|mut builder| {
2777                let array = borrow_array::BArrayValue::new(
2778                    int_ty.clone(),
2779                    (0..size)
2780                        .map(|i| ConstInt::new_u(6, i).unwrap().into())
2781                        .collect_vec(),
2782                );
2783                let mut array = builder.add_load_value(array);
2784                let i1 = builder.add_load_value(ConstUsize::new(indices[0]));
2785                let i2 = builder.add_load_value(ConstUsize::new(indices[1]));
2786                array = builder
2787                    .add_borrow_array_borrow(int_ty.clone(), size, array, i1)
2788                    .unwrap()
2789                    .0;
2790                array = build_pops(&mut builder, int_ty.clone(), size, array, num_pops);
2791                size -= num_pops;
2792                array = builder
2793                    .add_borrow_array_borrow(int_ty.clone(), size, array, i2)
2794                    .unwrap()
2795                    .0;
2796                builder
2797                    .add_borrow_array_discard(int_ty.clone(), size, array)
2798                    .unwrap();
2799                builder.finish_hugr_with_outputs([]).unwrap()
2800            });
2801
2802        exec_ctx.add_extensions(|cge| {
2803            cge.add_prelude_extensions(PanicTestPreludeCodegen)
2804                .add_default_borrow_array_extensions(PanicTestPreludeCodegen)
2805                .add_default_int_extensions()
2806        });
2807        assert_eq!(&exec_ctx.exec_hugr_panicking(hugr, "main"), msg);
2808    }
2809
2810    #[rstest]
2811    #[case::oob(1, 0, [0, 1], "Index out of bounds")]
2812    #[case::pop_borrowed(32, 1, [1, 2], "Array element is already borrowed")]
2813    #[case::return_twice(32, 0, [0, 0], "Array already contains an element at this index")]
2814    #[case::return_twice_boundary(65, 0, [64, 64], "Array already contains an element at this index")]
2815    fn exec_return_panics(
2816        mut exec_ctx: TestContext,
2817        #[case] mut size: u64,
2818        #[case] num_pops: u64,
2819        #[case] indices: [u64; 2],
2820        #[case] msg: &str,
2821    ) {
2822        // We build a HUGR that:
2823        // - Creates an empty array with `new_all_borrowed`
2824        // - Returns an element to index `indices[0]`
2825        // - Pops specified numbers from the left to introduce an offset
2826        // - Returns an element to index `indices[1]`
2827        // - Checks that the program panics with the given message
2828
2829        let int_ty = int_type(6);
2830        let hugr = SimpleHugrConfig::new()
2831            .with_extensions(exec_registry())
2832            .finish(|mut builder| {
2833                let mut array = builder.add_new_all_borrowed(int_ty.clone(), size).unwrap();
2834                let i1 = builder.add_load_value(ConstUsize::new(indices[0]));
2835                let i2 = builder.add_load_value(ConstUsize::new(indices[1]));
2836                let zero = builder.add_load_value(ConstInt::new_u(6, 0).unwrap());
2837                array = builder
2838                    .add_borrow_array_return(int_ty.clone(), size, array, i1, zero)
2839                    .unwrap();
2840                array = build_pops(&mut builder, int_ty.clone(), size, array, num_pops);
2841                size -= num_pops;
2842                array = builder
2843                    .add_borrow_array_return(int_ty.clone(), size, array, i2, zero)
2844                    .unwrap();
2845                builder
2846                    .add_borrow_array_discard(int_ty.clone(), size, array)
2847                    .unwrap();
2848                builder.finish_hugr_with_outputs([]).unwrap()
2849            });
2850
2851        exec_ctx.add_extensions(|cge| {
2852            cge.add_prelude_extensions(PanicTestPreludeCodegen)
2853                .add_default_borrow_array_extensions(PanicTestPreludeCodegen)
2854                .add_default_int_extensions()
2855        });
2856        assert_eq!(&exec_ctx.exec_hugr_panicking(hugr, "main"), msg);
2857    }
2858
2859    #[rstest]
2860    fn exec_shift_by_64(mut exec_ctx: TestContext, #[values(false, true)] right: bool) {
2861        const SHIFTED_VAL: u64 = 35;
2862        // This test generates LLVM code to shift a 64-bit value right/left by 64 places.
2863        // This is a no-op in LLVM, rather than producing 0 as one might expect.
2864        use hugr_core::std_extensions::arithmetic::int_ops::IntOpDef;
2865        let int_ty = int_type(6); // 64-bit integer
2866        let hugr = SimpleHugrConfig::new()
2867            .with_outs(int_ty.clone())
2868            .with_extensions(exec_registry())
2869            .finish(|mut builder| {
2870                let minus_one = builder.add_load_value(ConstInt::new_u(6, SHIFTED_VAL).unwrap());
2871                let shift = builder.add_load_value(ConstInt::new_u(6, 64).unwrap());
2872                let op = if right {
2873                    IntOpDef::ishr
2874                } else {
2875                    IntOpDef::ishl
2876                };
2877                let result = builder
2878                    .add_dataflow_op(op.with_log_width(6), [minus_one, shift])
2879                    .unwrap()
2880                    .out_wire(0);
2881                builder.finish_hugr_with_outputs([result]).unwrap()
2882            });
2883        exec_ctx.add_extensions(|cge| {
2884            cge.add_default_prelude_extensions()
2885                .add_default_int_extensions()
2886        });
2887        assert_eq!(exec_ctx.exec_hugr_u64(hugr, "main"), SHIFTED_VAL);
2888    }
2889
2890    #[rstest]
2891    #[case::small(10, 0, 0)]
2892    #[case::small_right(10, 0, 9)]
2893    #[case::small_pop(10, 2, 0)]
2894    #[case::small_right_pop(10, 2, 7)]
2895    #[case::oneword(64, 0, 0)]
2896    #[case::oneword_pop(64, 2, 0)]
2897    #[case::big_right(97, 0, 96)]
2898    #[case::big_pop(97, 5, 0)]
2899    #[case::big_popmany(97, 65, 0)]
2900    fn exec_discard_all_borrowed_panic(
2901        mut exec_ctx: TestContext,
2902        #[case] size: u64,
2903        #[case] num_pops: u64,
2904        #[case] ret_index: u64,
2905    ) {
2906        let int_ty = int_type(6);
2907        let hugr = SimpleHugrConfig::new()
2908            .with_extensions(exec_registry())
2909            .finish(|mut builder| {
2910                let mut array = builder.add_new_all_borrowed(int_ty.clone(), size).unwrap();
2911                let val = builder.add_load_value(ConstInt::new_u(6, 15).unwrap());
2912                for i in 0..num_pops {
2913                    let i = builder.add_load_value(ConstUsize::new(i));
2914                    array = builder
2915                        .add_borrow_array_return(int_ty.clone(), size, array, i, val)
2916                        .unwrap();
2917                }
2918                let array = build_pops(&mut builder, int_ty.clone(), size, array, num_pops);
2919                let size = size - num_pops;
2920                let ret_index = builder.add_load_value(ConstUsize::new(ret_index));
2921                let array = builder
2922                    .add_borrow_array_return(int_ty.clone(), size, array, ret_index, val)
2923                    .unwrap();
2924                builder
2925                    .add_discard_all_borrowed(int_ty.clone(), size, array)
2926                    .unwrap();
2927                builder.finish_hugr_with_outputs([]).unwrap()
2928            });
2929
2930        exec_ctx.add_extensions(|cge| {
2931            cge.add_prelude_extensions(PanicTestPreludeCodegen)
2932                .add_default_borrow_array_extensions(PanicTestPreludeCodegen)
2933                .add_default_int_extensions()
2934        });
2935        let msg = "Array contains non-borrowed elements and cannot be discarded";
2936        assert_eq!(&exec_ctx.exec_hugr_panicking(hugr, "main"), msg);
2937    }
2938
2939    #[rstest]
2940    fn exec_to_array_panic(mut exec_ctx: TestContext) {
2941        let int_ty = int_type(6);
2942        let size = 10;
2943        let hugr = SimpleHugrConfig::new()
2944            .with_extensions(exec_registry())
2945            .finish(|mut builder| {
2946                let barray = borrow_array::BArrayValue::new(
2947                    int_ty.clone(),
2948                    (0..size)
2949                        .map(|i| ConstInt::new_u(6, i).unwrap().into())
2950                        .collect_vec(),
2951                );
2952                let barray = builder.add_load_value(barray);
2953                let idx = builder.add_load_value(ConstUsize::new(0));
2954                let (barray, _) = builder
2955                    .add_borrow_array_borrow(int_ty.clone(), size, barray, idx)
2956                    .unwrap();
2957                let array = builder
2958                    .add_dataflow_op(BArrayToArray::new(int_ty.clone(), size), [barray])
2959                    .unwrap()
2960                    .out_wire(0);
2961                builder
2962                    .add_array_discard(int_ty.clone(), size, array)
2963                    .unwrap();
2964                builder.finish_hugr_with_outputs([]).unwrap()
2965            });
2966
2967        exec_ctx.add_extensions(|cge| {
2968            cge.add_prelude_extensions(PanicTestPreludeCodegen)
2969                .add_default_borrow_array_extensions(PanicTestPreludeCodegen)
2970                .add_default_array_extensions()
2971                .add_default_int_extensions()
2972        });
2973        let msg = "Some array elements have been borrowed";
2974        assert_eq!(&exec_ctx.exec_hugr_panicking(hugr, "main"), msg);
2975    }
2976
2977    #[rstest]
2978    fn exec_is_borrowed_basic(mut exec_ctx: TestContext) {
2979        // We build a HUGR that:
2980        // - Creates a borrow array [1,2,3]
2981        // - Borrows index 1
2982        // - Checks is_borrowed for indices 0, 1
2983        // - Returns 1 if [false, true], else 0
2984        let int_ty = int_type(6);
2985        let size = 3;
2986        let hugr = SimpleHugrConfig::new()
2987            .with_outs(int_ty.clone())
2988            .with_extensions(exec_registry())
2989            .finish(|mut builder| {
2990                let barray = borrow_array::BArrayValue::new(
2991                    int_ty.clone(),
2992                    (1..=3)
2993                        .map(|i| ConstInt::new_u(6, i).unwrap().into())
2994                        .collect_vec(),
2995                );
2996                let barray = builder.add_load_value(barray);
2997                let idx1 = builder.add_load_value(ConstUsize::new(1));
2998                let (barray, _) = builder
2999                    .add_borrow_array_borrow(int_ty.clone(), size, barray, idx1)
3000                    .unwrap();
3001
3002                let idx0 = builder.add_load_value(ConstUsize::new(0));
3003                let (arr, b0_bools) =
3004                    [idx0, idx1]
3005                        .iter()
3006                        .fold((barray, Vec::new()), |(arr, mut bools), idx| {
3007                            let (arr, b) = builder
3008                                .add_is_borrowed(int_ty.clone(), size, arr, *idx)
3009                                .unwrap();
3010                            bools.push(b);
3011                            (arr, bools)
3012                        });
3013                let [b0, b1] = b0_bools.try_into().unwrap();
3014
3015                let b0 = builder.add_not(b0).unwrap(); // flip b0 to true
3016                let and01 = builder.add_and(b0, b1).unwrap();
3017                // convert bool to i1
3018                let i1 = builder
3019                    .add_dataflow_op(ConvertOpDef::ifrombool.without_log_width(), [and01])
3020                    .unwrap()
3021                    .out_wire(0);
3022                // widen i1 to i64
3023                let i_64 = builder
3024                    .add_dataflow_op(IntOpDef::iwiden_u.with_two_log_widths(0, 6), [i1])
3025                    .unwrap()
3026                    .out_wire(0);
3027                builder
3028                    .add_borrow_array_discard(int_ty.clone(), size, arr)
3029                    .unwrap();
3030                builder.finish_hugr_with_outputs([i_64]).unwrap()
3031            });
3032
3033        exec_ctx.add_extensions(|cge| {
3034            cge.add_default_prelude_extensions()
3035                .add_logic_extensions()
3036                .add_conversion_extensions()
3037                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
3038                .add_default_int_extensions()
3039        });
3040        assert_eq!(1, exec_ctx.exec_hugr_u64(hugr, "main"));
3041    }
3042
3043    #[rstest]
3044    fn exec_discard_part_borrowed(mut exec_ctx: TestContext) {
3045        use hugr_passes::replace_types::{DelegatingLinearizer, Linearizer};
3046        // Builds a HUGR that:
3047        // - Creates a borrow array [1,2,3]
3048        // - Borrows index 1
3049        // - Discards the borrow array using the ReplaceTypes linearizer
3050        // And then runs this, i.e. to check that it does not panic.
3051        let inn_arr_ty = borrow_array_type(2, usize_t());
3052        let arr_ty = borrow_array_type(3, inn_arr_ty.clone());
3053        let hugr = SimpleHugrConfig::new()
3054            .with_outs(int_type(6))
3055            .with_extensions(exec_registry())
3056            .finish(|mut builder| {
3057                let inner_arrays = [1, 3, 5].map(|i| {
3058                    let elems = [i, i + 1].map(|v| builder.add_load_value(ConstUsize::new(v)));
3059                    builder.add_new_borrow_array(usize_t(), elems).unwrap()
3060                });
3061                let outer = builder
3062                    .add_new_borrow_array(inn_arr_ty.clone(), inner_arrays)
3063                    .unwrap();
3064                let idx = builder.add_load_value(ConstUsize::new(0));
3065                let (outer, inner) = builder
3066                    .add_borrow_array_borrow(inn_arr_ty, 3, outer, idx)
3067                    .unwrap();
3068                builder
3069                    .add_borrow_array_discard(usize_t(), 2, inner)
3070                    .unwrap();
3071                let dl = DelegatingLinearizer::default();
3072                let nt = dl.copy_discard_op(&arr_ty, 0).unwrap();
3073                nt.add(&mut builder, [outer]).unwrap();
3074                let res = builder.add_load_value(ConstInt::new_u(6, 17).unwrap());
3075                builder.finish_hugr_with_outputs([res]).unwrap()
3076            });
3077        exec_ctx.add_extensions(|cge| {
3078            cge.add_default_prelude_extensions()
3079                .add_logic_extensions()
3080                .add_conversion_extensions()
3081                .add_default_borrow_array_extensions(DefaultPreludeCodegen)
3082                .add_default_int_extensions()
3083        });
3084        assert_eq!(17, exec_ctx.exec_hugr_u64(hugr, "main"));
3085    }
3086}