-
Notifications
You must be signed in to change notification settings - Fork 14
feat: LLVM lowering for borrow arrays using bitmasks #2574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
hugr-llvm/src/emit/func.rs
Outdated
let func = ctx | ||
.get_current_module() | ||
.get_function(func_name) | ||
.map(Ok::<_, anyhow::Error>) |
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
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?
There was a problem hiding this 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)
hugr-llvm/src/emit/func.rs
Outdated
/// 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>( |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, rational -> rationale
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 😅
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 |
d82bb30
to
ba4df3d
Compare
|
91f42b2
to
439f09c
Compare
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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]. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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`]. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😅
There was a problem hiding this comment.
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, "")?; |
There was a problem hiding this comment.
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...)
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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], "")? }; |
There was a problem hiding this comment.
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| { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ;-)
There was a problem hiding this comment.
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) = |
There was a problem hiding this comment.
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
?)
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
2d069c9
to
95b354a
Compare
95b354a
to
5be9249
Compare
5be9249
to
d5d5999
Compare
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 |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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>>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docstring 🙏
There was a problem hiding this comment.
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
Closes #2551
The first two commits just copy the existing lowering. The changes to the lowering lgic are implemented in 888e457.