Skip to content
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

feat!: used_extensions calls for both ops and signatures #1739

Merged
merged 24 commits into from
Dec 10, 2024

Conversation

aborgna-q
Copy link
Collaborator

@aborgna-q aborgna-q commented Dec 3, 2024

Adds methods

Moves the code from #1735 into resolution::types_mut, and adds a (quite-similar) non-mutable version in ::types that only collects the extensions without modifying the OpType.

Fixes the resolution not exploring types inside a CustomType type arguments.

drive-by: Implement Display, Serialize, and ::new for ExtensionRegistry.
drive-by: ExtensionSet should take ids by value when inserting.
drive-by: Fix Hugr::resolve_extension_defs not scanning all the ops.

These changes were extracted from #1738.

BREAKING CHANGE: Removed ExtensionRegistry::try_new. Use new instead, and call ExtensionRegistry::validate to validate.
BREAKING CHANGE: ExtensionSet::insert and singleton take extension ids by value instead of cloning internally.

Copy link

codecov bot commented Dec 3, 2024

Codecov Report

Attention: Patch coverage is 82.62712% with 82 lines in your changes missing coverage. Please review.

Project coverage is 86.29%. Comparing base (4c1c6ee) to head (2c8ad5e).
Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
hugr-core/src/extension/resolution/types_mut.rs 75.00% 4 Missing and 31 partials ⚠️
hugr-core/src/extension.rs 71.01% 20 Missing ⚠️
hugr-core/src/extension/resolution/types.rs 92.85% 7 Missing ⚠️
hugr-core/src/extension/resolution/ops.rs 68.75% 5 Missing ⚠️
hugr-core/src/ops/custom.rs 72.72% 3 Missing ⚠️
hugr-core/src/extension/resolution/test.rs 92.00% 1 Missing and 1 partial ⚠️
hugr-core/src/hugr/rewrite/inline_dfg.rs 66.66% 0 Missing and 2 partials ⚠️
hugr-core/src/hugr/rewrite/replace.rs 0.00% 2 Missing ⚠️
hugr-core/src/hugr/validate/test.rs 77.77% 0 Missing and 2 partials ⚠️
hugr-core/src/extension/op_def.rs 75.00% 0 Missing and 1 partial ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1739      +/-   ##
==========================================
+ Coverage   86.28%   86.29%   +0.01%     
==========================================
  Files         177      179       +2     
  Lines       32087    32662     +575     
  Branches    28999    29574     +575     
==========================================
+ Hits        27687    28187     +500     
- Misses       2775     2797      +22     
- Partials     1625     1678      +53     
Flag Coverage Δ
python 92.42% <ø> (ø)
rust 85.65% <82.62%> (+0.02%) ⬆️

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.

@aborgna-q
Copy link
Collaborator Author

aborgna-q commented Dec 4, 2024

Coverage is quite bad. I'll add some extra tests today.

Done

hugr-core/src/extension.rs Show resolved Hide resolved
hugr-core/src/extension.rs Show resolved Hide resolved
hugr-core/src/extension/resolution.rs Outdated Show resolved Hide resolved
hugr-core/src/extension/resolution/types.rs Outdated Show resolved Hide resolved
update_type_row_exts(node, &mut db.other_outputs, extensions, used_extensions)?;
for row in db.sum_rows.iter_mut() {
update_type_row_exts(node, row, extensions, used_extensions)?;
collect_type_row_exts(&db.inputs, &mut used, &mut missing);
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't the signature of a DataflowBlock include all the elements of the inputs, other_outputs and sum_rows? So can't you just collect_signature_exts on the block's signature, and the same for e.g. TailLoop, Conditional, and so on, like OpaqueOp and most other blocks?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, but that requires allocating a Signature, cloning all row elements, computing the concrete input/output rows, and immediately destructuring them to run this same logic.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this true even with Cow of signature? I mean, we're gonna be computing that signature a good few times anyway...

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmmm, even when we have Cow<Signature>? I mean, we are gonna be constructing that thing a few times, clearly it's gonna help to cache it in the op. OK don't add that for this PR, but if you're optimizing that anyway and could use that here...

/// signature. The extension deltas added via [`Self::with_extension_delta`]
/// refer to _runtime_ extensions, which may not be in all places that
/// manipulate a HUGR.
pub fn used_extensions(&self) -> Result<ExtensionRegistry, ExtensionCollectionError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

So the other way to do this - is to put the collect_blah_exts methods as impls of a single method in a trait, and then you can add a trait-default method (or a method taking an impl ExtensionCollectionTrait) that does this...that'd get you the two used_extensions methods that you have plus a load more with only having to write this once. (I wasn't going to suggest the trait approach until I saw this...)

Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't work if you change OpType::used_extensions to take the extra Option<Node> parameter I suggested elsewhere, though. So feel free to drop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I opted for keeping all the logic in a single module, as it's short and quite similar between all the ops.

hugr-core/src/extension/resolution/types.rs Outdated Show resolved Hide resolved
let mut collected_exts = ExtensionRegistry::default();
for node in hugr.nodes() {
let op = hugr.get_optype(node);
collected_exts.extend(collect_op_extensions(Some(node), op).unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

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

If you changed OpType::used_extensions to take the Option<Node> param as extra, which seems reasonable, then you could call that here and remove all direct calls to collect_op_(type_)extensions. Then hide those (or inline into the single caller OpType::used_extensions).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was calling the resolve methods explicitly, but it's exactly the same as op.used_exts.
I just changed it to that.

/// - `node`: The node where the operation is located, if available.
/// This is used to provide context in the error message.
/// - `op`: The operation to collect the extensions from.
pub fn collect_op_types_extensions(
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 pub? The nomenclature is a bit confusing here - it feels equivalent to Signature::used_extensions but actually it's only one part of OpType::used_extensions. However the other collect things take two &muts. Should one really use this directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's hidden inside the private types module. But I'll add the (crate) for clarity.

//! Resolve weak links inside `CustomType`s in an optype's signature, while
//! collecting all used extensions.
//!
//! For a non-mutating option see [`super::collect_op_types_extensions`].
Copy link
Contributor

Choose a reason for hiding this comment

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

I reckon the pub function here should be OpType::used_extensions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's a private module. (Otherwise the docs check would complain)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah ok, yes I missed that. Ok I'll hold some fire there then ;)

@aborgna-q aborgna-q changed the title feat: used_extensions calls for both ops and signatures feat!: used_extensions calls for both ops and signatures Dec 9, 2024
@hugrbot
Copy link
Collaborator

hugrbot commented Dec 9, 2024

This PR contains breaking changes to the public Rust API.

cargo-semver-checks summary

--- failure inherent_method_missing: pub method removed or renamed ---

Description:
A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.37.0/src/lints/inherent_method_missing.ron

Failed in:
ExtensionRegistry::try_new, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/extension.rs:58

--- failure pub_module_level_const_missing: pub module-level const is missing ---

Description:
A public const is missing, renamed, or changed from const to static.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.37.0/src/lints/pub_module_level_const_missing.ron

Failed in:
EMPTY_REG in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/extension.rs:209

@aborgna-q aborgna-q requested a review from acl-cqc December 9, 2024 12:05
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.

Generally - great :). I think all of these comments are now small and local, so looks good and thanks @aborgna-q , let's get it in :)

I think there is a bit of an issue that ideally one would like Hugr::validate to also validate the registry (if not cached in that AtomicBool), but that requires passing an &mut ExtensionRegistry which is gonna require changing everywhere (and annoyingly). Otherwise you could add methods to validate immutably (i.e. without updating the cached bool), I guess....

}

/// Delete an extension from the registry and return it if it was present.
pub fn remove_extension(&mut self, name: &ExtensionId) -> Option<Arc<Extension>> {
self.0.remove(name)
// Clear the valid flag so that the registry is re-validated.
Copy link
Contributor

Choose a reason for hiding this comment

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

Most nitty super-nit ever: these two lines after the removal, for consistency with the other operations :-) :-D

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

remove returns the Option<Arc<Ext>>, so moving it requires declaring local vars..

/// A flag indicating whether the current set of extensions has been
/// validated.
///
/// This is used to avoid re-validating the extensions every time they are
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// This is used to avoid re-validating the extensions every time they are
/// This is used to avoid re-validating the extensions every time the registry is

(I think - if you grab an extension out of the registry, you can use it without validating the registry; it's only when the registry itself is passed to validate a Hugr, say. Of course this is a subtlety of how we interpret "use" of an extension vs "use" of the regstry, so feel free to be more explicit - "every time a Hugr is validated against the registry", perhaps, although that's a bit more of a mouthful)

hugr-core/src/extension.rs Show resolved Hide resolved
hugr-core/src/extension.rs Show resolved Hide resolved
/// An Extension Registry containing no extensions.
pub const EMPTY_REG: ExtensionRegistry = ExtensionRegistry(BTreeMap::new());
pub static EMPTY_REG: ExtensionRegistry = ExtensionRegistry {
Copy link
Contributor

Choose a reason for hiding this comment

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

random driveby, feel free to ignore - could this could be a const fn, to which you could add Extensions yourself? (and then static EMPTY_REG: ExtensionRegistry = ExtensionRegistry::new_empty(); or similar)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ExtensionRegistry now has internal mutability, so it should not be used as a const.
(it does compile, but using it just raises linting errors everywhere, with good reason)

hugr-core/src/extension/resolution/types.rs Show resolved Hide resolved
hugr-core/src/extension/resolution/ops.rs Outdated Show resolved Hide resolved
update_type_row_exts(node, &mut db.other_outputs, extensions, used_extensions)?;
for row in db.sum_rows.iter_mut() {
update_type_row_exts(node, row, extensions, used_extensions)?;
collect_type_row_exts(&db.inputs, &mut used, &mut missing);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmmm, even when we have Cow<Signature>? I mean, we are gonna be constructing that thing a few times, clearly it's gonna help to cache it in the op. OK don't add that for this PR, but if you're optimizing that anyway and could use that here...

@@ -213,7 +213,7 @@ impl Hugr {
//
// This is not something we want to expose it the API, so we manually
// iterate instead of writing it as a method.
for n in 0..self.node_count() {
for n in 0..self.graph.node_capacity() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes please do! The comment above does not explain (to me) why we are using node_capacity

])
.unwrap()
]);
reg.validate()
Copy link
Contributor

Choose a reason for hiding this comment

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

Many examples here and elsewhere, I wonder if it's worth adding to ExtensionRegistry

   fn validated(mut self) -> Result<Self, ....> {
      self.validate()?;
      self
   }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We'll call ExtRegistry::validate on each Hugr::validate as soon as #1738 gets merged, so this shouldn't be needed.

@aborgna-q aborgna-q enabled auto-merge December 10, 2024 12:17
@aborgna-q aborgna-q added this pull request to the merge queue Dec 10, 2024
Merged via the queue into main with commit 1443284 Dec 10, 2024
24 checks passed
@aborgna-q aborgna-q deleted the ab/used-extensions branch December 10, 2024 12:27
@hugrbot hugrbot mentioned this pull request Dec 10, 2024
github-merge-queue bot pushed a commit that referenced this pull request Dec 10, 2024
…1738)

Closes #1613. Depends on #1739.

Hugrs now keep an `Extensions` registry that is automatically computed
when using the builder or deserializing from json (using the new
`Hugr::load_json`).
This set can contain unneeded extensions, but it should always be
sufficient to validate the HUGR definition. Note that this is **not**
runtime extensions (see #426, #1734).

A big chunk of the diff is removing the extension registry when
finishing building a hugr. The extension tracking is now done
automatically while adding operations.

drive-by: Remove unneeded `set_num_ports` call in
`insert_hugr_internal`.

BREAKING CHANGE: Removed `update_validate`. The hugr extensions should
be resolved at load time, so we can use `validate` instead.
BREAKING CHANGE: The builder `finish_hugr` function family no longer
takes a registry as parameter, and the `_prelude` variants have been
removed.
This was referenced Dec 11, 2024
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.

3 participants