Skip to content

Conversation

mark-koch
Copy link
Contributor

Closes #2551

The first two commits just copy the existing lowering. The changes to the lowering lgic are implemented in 888e457.

@mark-koch mark-koch requested a review from tatiana-s September 22, 2025 16:45
@mark-koch mark-koch requested a review from a team as a code owner September 22, 2025 16:45
@mark-koch mark-koch requested review from acl-cqc and removed request for a team and acl-cqc September 22, 2025 16:45
Copy link

codecov bot commented Sep 22, 2025

Codecov Report

❌ Patch coverage is 91.66667% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.32%. Comparing base (69a126d) to head (6cf427b).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
hugr-llvm/src/emit/func.rs 91.48% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2574      +/-   ##
==========================================
+ Coverage   83.07%   83.32%   +0.25%     
==========================================
  Files         255      256       +1     
  Lines       48436    50488    +2052     
  Branches    43946    46011    +2065     
==========================================
+ Hits        40240    42071    +1831     
+ Misses       6103     6064      -39     
- Partials     2093     2353     +260     
Flag Coverage Δ
python 91.53% <ø> (-0.03%) ⬇️
rust 82.53% <91.66%> (+0.31%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

let func = ctx
.get_current_module()
.get_function(func_name)
.map(Ok::<_, anyhow::Error>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Option of Result, etc., is a bit hard to figure out. How about let func = match ctx.get_current_module().get_function(func_name) { Some(f) => f, None => { .... } } which moves your closure into the body and removes the nested ?.

Or perhaps consider let func = if let Some(f) = ctx.blah() {f} else {....}

}

impl<'a, H: HugrView<Node = Node> + 'a> CodegenExtsBuilder<'a, H> {
/// Add a [`BorrowArrayCodegenExtension`] to the given [`CodegenExtsBuilder`] using `ccg`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: comments should be the other way round?

Copy link
Contributor

@acl-cqc acl-cqc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I thought I'd take a look at this, but +4,000 lines and I'm stopping a fraction of the way through.... the PR we are thinking this replaces is a third as long as this, right? How many opportunities are there for bugs here? (ReplaceTypes BorrowArray has the benefit that we get type-checking of the output Hugr by the hugr type system, too)

/// The first time this helper is called with a given function name, a function is built
/// using the provided closure. Future invocations with the same name will just emit calls
/// to this function.
pub fn outline_into_function<'c, H: HugrView<Node = Node>, const N: usize>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps get_or_make_function

//! to at least `n * sizeof(T)` bytes. The extra `usize` is an offset pointing to the
//! first element, i.e. the first element is at address `ptr + offset * sizeof(T)`.
//!
//! The rational behind the additional offset is the `pop_left` operation which bumps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this explanation, but is there no push_left / unpop ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, rational -> rationale

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there no push_left / unpop ?

No, as this would require reallocating the array in general

//! the offset instead of mutating the pointer. This way, we can still free the original
//! pointer when the array is discarded after a pop.
//!
//! We provide utility functions [`array_fat_pointer_ty`], [`build_barray_fat_pointer`], and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are some of these barray and others just array ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I forgot to rename those after copy pasting 😅

@tatiana-s
Copy link
Contributor

Well I thought I'd take a look at this, but +4,000 lines and I'm stopping a fraction of the way through.... the PR we are thinking this replaces is a third as long as this, right? How many opportunities are there for bugs here? (ReplaceTypes BorrowArray has the benefit that we get type-checking of the output Hugr by the hugr type system, too)

But most of this PR just copies the existing array lowering (including many snapshots) so it is only the bit mask code that is new which is a lot less than the type replacement

@mark-koch mark-koch force-pushed the feat/barray-llvm branch 4 times, most recently from d82bb30 to ba4df3d Compare September 29, 2025 14:05
Base automatically changed from test/panic to main September 29, 2025 15:46
@ss2165
Copy link
Member

ss2165 commented Oct 6, 2025

Please include #2609 before merging done

Copy link
Contributor

@acl-cqc acl-cqc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nearly there....done a bunch of mods, hope that's ok, a few questions here

Some(func) => func,
None => {
let arg_tys = args.iter().map(|v| v.get_type().into()).collect_vec();
let sig = match ret_type {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bizarre here that BasicTypeEnum and VoidType define fn_type as two completely unrelated methods! But yeah I don't see a better way :(

pub trait BorrowArrayCodegen: Clone {
/// Emit instructions to halt execution with the error `err`.
///
/// This should be consistent with the panic implementation from the [PreludeCodegen].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what you end up doing is parametrizing the struct that implements BorrowArrayCodegen by something that implements PreludeCodegen for this.

I guess the alternative is to define a PanicCodegen super-trait to both PreludeCodegen and BorrowArrayCodegen. The default impl in PreludeCodegen would then go there. Did you consider this? I don't really understand well enough how/where this is all used to know the advantages/disadvantages, but I could do/try that if you think it'd be better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider this?

No, I just went with this approach since we also used it for e.g. IntCodegenExtension. But I wouldn't mind changing to the super trait approach

}

/// Emit a [`hugr_core::std_extensions::collections::array::ArrayOp`].
/// Emit a [`hugr_core::std_extensions::collections::borrow_array::BArrayOp`].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called emit_barray_op? and similarly ...._unsafe_op and emit_barray_{clone, discard, repeat} etc. below?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep them as array that would ease recombining via trait BorrowArrayCodegen: ArrayCodegen although I'm not sure whether the change in parametrization would prevent that - I think you might need trait GenericArrayCodegen<AK : ArrayKind> which then makes default-impls hard

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this should all be barray, silly copy paste mistakes sorry 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did the top-levels, kept the trait. Figuring out how to recombine some of this code would be good (but left for later) - I'm not sure how to atm!

&ctx.typing_session(),
ptr.get_type().get_element_type().try_into().unwrap(),
);
let array_v = array_ty.get_poison();
let array_v = ctx
.builder()
.build_insert_value(array_v, ptr.as_basic_value_enum(), 0, "")?;
let array_v =
ctx.builder()
.build_insert_value(array_v, mask_ptr.as_basic_value_enum(), 1, "")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "" for name seems standard throughout the repo, but would strings like "elem_ptr", "bitmask_ptr" and "offset" help with debugging or cause problems? (There are more things to have to look up here...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use strings for build_extract_value after all!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this would probably be a good thing to do

)
.unwrap();
let val = if value {
ctx.iw_context().i8_type().const_all_ones()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've documented that this argument is always an i8t after breaking everything by trying usize_ty(&ctx.typing_session()) here instead ;)

let block_size = usize_t.const_int(usize_t.get_bit_width() as u64, false);
let builder = ctx.builder();
let block_idx = builder.build_int_unsigned_div(idx, block_size, "")?;
let block_ptr = unsafe { builder.build_in_bounds_gep(mask_ptr, &[block_idx], "")? };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust code that builds unsafe binary code is itself marked unsafe....!!! Lol, ok

let bit = builder.build_int_truncate(block_shifted, ctx.iw_context().bool_type(), "")?;

let borrowed_bb =
ctx.build_positioned_new_block("elem_borrowed", None, |ctx, borrowed_bb| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do wonder if these closures are, and this much generality is, a bit OTT...I switched out the if...get_terminator check to put the build_return(None) in the caller, that saved a bit. But passing two Option<&str> panic messages would probably save a bunch more, or one panic message and a bool 'expected_result' (so it's check_bitmask_eq) which would allow a for loop to build two basic blocks...any objections to the latter approach?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also thinking that if we kept a closure (for the success block) we could put the mask-flipping into the same block, which would avoid redoing all the block-index-calculation stuff in build_mask_flip (both at runtime, and duplicating the Rust code here)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any objections to the latter approach?

No, that sounds good 👍

we could put the mask-flipping into the same block

Also happy with this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I'm almost sad there are only execution tests because now we don't see how much smaller the code is ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with an enum MaskCheck which specifies which value to panic on and whether to flip

})?;

let head_block = ctx.build_positioned_new_block("", Some(body_block), |ctx, bb| {
let (body_start_block, body_end_block, val) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra values here are to allow the closure to add new blocks/branches/controlflow/etc. rather than only being able to emit instructions inside one block, right??

So does this build_loop generalize the old one (and we can remove the old from from array.rs?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should be able to remove the old one (and maybe move the new one somewhere else)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a third in stack_array.rs and moreover, these are all internal, so could be done later. Hence left for now.

@acl-cqc
Copy link
Contributor

acl-cqc commented Oct 8, 2025

Ok I guess this is now a joint PR ;-), hopefully this link shows just the changes I've made to Mark's original: https://github.com/CQCL/hugr/pull/2574/files/439f09c27e6591c125088000e03d8556b88fef2b..6cf427bee12c8f65fa35e3374cf4b47c5c9dd9ed

@acl-cqc acl-cqc requested a review from ss2165 October 8, 2025 11:01
Copy link
Member

@ss2165 ss2165 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor docstring/refactor suggestions

go(ctx, block)
})
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MaskCheck {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstrings on these would be good


/// Emits instructions to flip the borrowed mask flag for a given index.
fn build_mask_flip<'c, H: HugrView<Node = Node>>(
fn check_mask_elem<'c, H: HugrView<Node = Node>>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would seem to be a natural method on MaskCheck

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

hugr-llvm: Add direct lowering for borrow arrays
4 participants