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!: Allow static inputs to extension operations #1628

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

ss2165
Copy link
Member

@ss2165 ss2165 commented Nov 4, 2024

Extension operations can now have some number of static inputs that come between their dataflow input ports and the state order input port.

This can be used for compile-time-known inputs to operations.
I have attempted to keep the serialization backwards compatible.

OpDefs can either declare their static inputs as a row, or it can be calculated like the rest of the signature.

Closes #1594

BREAKING CHANGE: OpType, OpTrait, DataflowOpTrait methods which assumed one static input now support many. E.g. static_input_port -> static_input_ports. Returning a Vec rather than an Option. Operation definitions support static input type declarations, so a new type OpDefSignature has been introduced for this to hold the PolyFuncTypeRV and the static inputs. The cached signature of an ExtensionOp is also a new type ExtOpSignature that holds the dataflow Signature and the static inputs. OpTag::StaticInput removed because it doesn't actually narrow things down any more.

@ss2165 ss2165 requested a review from a team as a code owner November 4, 2024 14:46
@ss2165 ss2165 requested a review from aborgna-q November 4, 2024 14:46
Copy link

codecov bot commented Nov 4, 2024

Codecov Report

Attention: Patch coverage is 90.93484% with 32 lines in your changes missing coverage. Please review.

Project coverage is 85.82%. Comparing base (9962f97) to head (6b3085d).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
hugr-core/src/ops/custom.rs 79.41% 14 Missing ⚠️
hugr-core/src/extension/op_def.rs 86.74% 7 Missing and 4 partials ⚠️
...e/src/extension/op_def/serialize_signature_func.rs 92.85% 1 Missing and 1 partial ⚠️
hugr-core/src/types/poly_func.rs 95.34% 2 Missing ⚠️
hugr-core/src/builder/build_traits.rs 93.33% 0 Missing and 1 partial ⚠️
hugr-core/src/export.rs 50.00% 0 Missing and 1 partial ⚠️
hugr-core/src/ops.rs 96.96% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1628      +/-   ##
==========================================
+ Coverage   85.78%   85.82%   +0.04%     
==========================================
  Files         136      136              
  Lines       25135    25403     +268     
  Branches    22061    22320     +259     
==========================================
+ Hits        21561    21803     +242     
- Misses       2425     2446      +21     
- Partials     1149     1154       +5     
Flag Coverage Δ
python 92.47% <100.00%> (+0.02%) ⬆️
rust 84.91% <90.53%> (+0.05%) ⬆️

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.

@hugrbot
Copy link
Collaborator

hugrbot commented Nov 4, 2024

This PR contains breaking changes to the public Rust API.

cargo-semver-checks summary

--- failure enum_no_repr_variant_discriminant_changed: enum variant had its discriminant change value ---

Description:
The enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`.
      ref: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.36.0/src/lints/enum_no_repr_variant_discriminant_changed.ron

Failed in:
variant OpTag::StaticOutput 16 -> 15 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:50
variant OpTag::FnCall 17 -> 16 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:52
variant OpTag::LoadConst 18 -> 17 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:54
variant OpTag::LoadFunc 19 -> 18 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:56
variant OpTag::ScopedDefn 20 -> 19 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:58
variant OpTag::TailLoop 21 -> 20 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:60
variant OpTag::Conditional 22 -> 21 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:62
variant OpTag::Case 23 -> 22 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:64
variant OpTag::Leaf 24 -> 23 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:66
variant OpTag::DataflowBlock 25 -> 24 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:69
variant OpTag::BasicBlockExit 26 -> 25 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:71
variant OpTag::StaticOutput 16 -> 15 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:50
variant OpTag::FnCall 17 -> 16 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:52
variant OpTag::LoadConst 18 -> 17 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:54
variant OpTag::LoadFunc 19 -> 18 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:56
variant OpTag::ScopedDefn 20 -> 19 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:58
variant OpTag::TailLoop 21 -> 20 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:60
variant OpTag::Conditional 22 -> 21 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:62
variant OpTag::Case 23 -> 22 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:64
variant OpTag::Leaf 24 -> 23 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:66
variant OpTag::DataflowBlock 25 -> 24 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:69
variant OpTag::BasicBlockExit 26 -> 25 in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/tag.rs:71

--- failure enum_variant_missing: pub enum variant removed or renamed ---

Description:
A publicly-visible enum has at least one variant that 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.36.0/src/lints/enum_variant_missing.ron

Failed in:
variant OpTag::StaticInput, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops/tag.rs:50
variant OpTag::StaticInput, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops/tag.rs:50

--- 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.36.0/src/lints/inherent_method_missing.ron

Failed in:
OpType::static_port, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops.rs:232
OpType::static_input_port, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops.rs:240
OpType::static_port, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops.rs:232
OpType::static_input_port, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops.rs:240

--- failure method_parameter_count_changed: pub method parameter count changed ---

Description:
A publicly-visible method now takes a different number of parameters.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.36.0/src/lints/method_parameter_count_changed.ron

Failed in:
hugr_core::ops::custom::OpaqueOp::new now takes 6 parameters instead of 5, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/custom.rs:223
hugr_core::ops::OpaqueOp::new now takes 6 parameters instead of 5, in /home/runner/work/hugr/hugr/PR_BRANCH/hugr-core/src/ops/custom.rs:223

--- failure trait_method_missing: pub trait method removed or renamed ---

Description:
A trait method is no longer callable, and may have been renamed or removed entirely.
      ref: https://doc.rust-lang.org/cargo/reference/semver.html#major-any-change-to-trait-item-signatures
     impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.36.0/src/lints/trait_method_missing.ron

Failed in:
method static_input of trait DataflowOpTrait, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops/dataflow.rs:49
method static_input of trait DataflowOpTrait, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops/dataflow.rs:49
method static_source of trait HugrView, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/views.rs:420
method static_source of trait HugrView, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/views.rs:420
method static_source of trait HugrView, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/hugr/views.rs:420
method static_input of trait OpTrait, previously in file /home/runner/work/hugr/hugr/BASELINE_BRANCH/hugr-core/src/ops.rs:387

Copy link
Collaborator

@aborgna-q aborgna-q left a comment

Choose a reason for hiding this comment

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

Long review, but the PR looks nice.

You mentioned that some Vec return types cannot be the rewritten as impl Iterators, but some of them surely can?

Comment on lines +206 to +214
let op: OpType = nodetype.into();
let static_in_ports = op.static_input_ports();
let handle = self.add_dataflow_op(op, input_wires)?;

for (src, in_port) in static_wires.into_iter().zip(static_in_ports) {
self.hugr_mut()
.connect(src.node(), src.source(), handle.node(), in_port);
}
Ok(handle)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use self.add_node_with_wires with chained input_ and static_ directly?

Comment on lines +284 to +292
/// Returns the function type of the signature.
pub fn func_type(&self) -> &Signature {
&self.func_type
}

/// Returns the static inputs of the signature.
pub fn static_inputs(&self) -> &TypeRow {
&self.static_inputs
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we have these already we could make the attributes private.
Or do we need mut access?

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(Arbitrary), proptest(params = "RecursionDepth"))]
pub struct ExtOpSignature {
// #[serde(flatten)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// #[serde(flatten)]

?

@@ -47,9 +54,93 @@ pub type PolyFuncType = PolyFuncTypeBase<NoRV>;
/// The polymorphic type of an [OpDef], whose number of input and outputs
/// may vary according to how [RowVariable]s therein are instantiated.
///
/// It may also have a row of static inputs.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we state anywhere the order between static and runtime inputs in a node?

pub type PolyFuncTypeRV = PolyFuncTypeBase<RowVariable>;
#[derive(Clone, PartialEq, Debug, Default, Eq, Hash, derive_more::Display)]
#[cfg_attr(test, derive(Arbitrary), proptest(params = "RecursionDepth"))]
#[display("{}{}{}", self.display_params(), self.static_inputs, self.body())]
Copy link
Collaborator

Choose a reason for hiding this comment

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

ExtOpSignature displays the static_inputs between <>, if non-emtpy.
Should we do the same here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if OpDefSignature should now move into hugr_core::extension

Comment on lines +251 to +256
if let [p] = self.static_ports(Direction::Outgoing).as_slice() {
Some(p.as_outgoing().unwrap())
} else {
None
}
// .map(|p| p.as_outgoing().unwrap()).collect()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if let [p] = self.static_ports(Direction::Outgoing).as_slice() {
Some(p.as_outgoing().unwrap())
} else {
None
}
// .map(|p| p.as_outgoing().unwrap()).collect()
match self.static_ports(Direction::Outgoing).as_slice() {
[p] => Some(p.as_outgoing().unwrap()),
_ => None,
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Many of the OpType methods look like they could return an impl Iterator instead of collecting a vec.
Are there any lifetime problems with that?

}
}

/// Returns the [`ExtOpSignature`] of this [`ExtensionOp`].
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: We can already see the types.

Suggested change
/// Returns the [`ExtOpSignature`] of this [`ExtensionOp`].
/// Return the instantiated signature of this [`ExtensionOp`].

Comment on lines +198 to +204
def add(
self,
com: ops.Command,
*,
static_in: Iterable[Wire] | None = None,
metadata: dict[str, Any] | None = None,
) -> Node:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add the static_in argument to add_op too (and maybe to _wire_up), and use it here.

self,
com: Command,
*,
static_in: Iterable[Wire] | None = None,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The parameter is not used?

@zrho
Copy link
Contributor

zrho commented Nov 5, 2024

I'm wondering about the purpose of static inputs. So these are statically known runtime values, which are statically known because they are produced by a const node? We already have a mechanism to provide statically known information to an operation via TypeArgs; this isn't a hack but appears to be the most natural way to do this anyway. In that light I'd advocate for removing static inputs entirely instead of allowing multiple of them. Am I missing something?

@doug-q
Copy link
Collaborator

doug-q commented Nov 5, 2024

I'm wondering about the purpose of static inputs. So these are statically known runtime values, which are statically known because they are produced by a const node? We already have a mechanism to provide statically known information to an operation via TypeArgs; this isn't a hack but appears to be the most natural way to do this anyway. In that light I'd advocate for removing static inputs entirely instead of allowing multiple of them. Am I missing something?

We do not allow hugr::ops::constant::Value inside TypeArgs because Value does not have ==. Allowing Value inside TypeArg would mean that Type would not have ==, which is untenable.

Value cannot be given == because:

  • It might be a Value::Function, i.e. an embedded HUGR. A Hugr can't have == because Value can't, and because equality of programs is problematic.
  • It might be a CustomConst which does not support equality.

CustomConst cannot be given == because:

  • It might be a float, where equality does not exactly make sense(NAN).
  • It might be a user CustomConst where not enough information is available to determine equality. e.g. two symbol names that are not the same, but might be linked together later on.

@acl-cqc
Copy link
Contributor

acl-cqc commented Nov 5, 2024

....and specifically, TypeArg does not allow to contain static functions.

Now, IIUC there are proposals to allow a TypeArg:Node, which if it referenced a funcdefn, would allow a "loadconst"-like op to produce a function value (the TypeArg replacing the Const, or indeed, you could say it was a "loadfunction"-like op) - and then removing the ability to have Hugr's nested inside consts (==> equivalent to saying static function values can only be produced by LoadFunction). I haven't thought through monomorphic-vs-polymorphic, typeapply, etc. yet tho

@acl-cqc
Copy link
Contributor

acl-cqc commented Nov 5, 2024

In that light I'd advocate for removing static inputs entirely instead of allowing multiple of them

In some sense, this could well be where we are headed - but we certainly wouldn't want to remove them until we have finalized+implemented #1425! So in the meantime, aren't "static inputs" just the hugr-core way of representing (with nodes and edges) the noderef-like "terms" proposed in that discussion?

@zrho
Copy link
Contributor

zrho commented Nov 5, 2024

So in the meantime, aren't "static inputs" just the hugr-core way of representing (with nodes and edges) the noderef-like "terms" proposed in that discussion?

I don't think so. This PR introduces an additional parameter list to operations, so that operations can have three different types of inputs:

  • static values (TypeArg in core, Term in model)
  • runtime values, but statically known
  • runtime values

And this does not appear to be an implementation detail, but part of the specification of an operation.

@acl-cqc
Copy link
Contributor

acl-cqc commented Nov 5, 2024

operations can have three different types of inputs:

  • static values (TypeArg in core, Term in model)
  • runtime values, but statically known
  • runtime values

I thought the idea of #1425 was that the first two of these would be Terms in the model, which would include all "constant values" (probably excluding nested Hugrs, instead allowing references to function nodes). That some of these Terms would (at least initially) be turned into nodes with edges and others would be left as TypeArgs is an implementation detail of hugr-core and not part of the model.

@zrho
Copy link
Contributor

zrho commented Nov 5, 2024

That some of these Terms would (at least initially) be turned into nodes with edges and others would be left as TypeArgs is an implementation detail of hugr-core and not part of the model.

Yes, in the standup where we gave the thumbs up for this I understood it in that way. But that appears not compatible with how this PR does it since it makes static inputs into their own part of the signature. It therefore can't be a hugr-core implementation detail.

@acl-cqc
Copy link
Contributor

acl-cqc commented Nov 5, 2024

That some of these Terms would (at least initially) be turned into nodes with edges and others would be left as TypeArgs is an implementation detail of hugr-core and not part of the model.

But that appears not compatible with how this PR does it since it makes static inputs into their own part of the signature. It therefore can't be a hugr-core implementation detail.

I'm not sure I understand here - PR leaves the signature of a node unchanged, as a pub type Signature = FuncTypeBase<NoRV>. An OpDef now has a type_row of static inputs as well, i.e. an OpDefSignature rather than a PolyFuncTypeBase<RowVariable> - is that the problem, i.e. in the serialization==model-representation of extension definitions ?

@ss2165
Copy link
Member Author

ss2165 commented Nov 5, 2024

PR leaves the signature of a node unchanged,

Don't think this is true it may not be stored in the dataflow signature part but nodes still have to report their static signature, like using ExtOpSignature for extension ops.

@acl-cqc
Copy link
Contributor

acl-cqc commented Nov 5, 2024

Don't think this is true it may not be stored in the dataflow signature part but nodes still have to report their static signature, like using ExtOpSignature for extension ops.

Ah I see. Yes if that gets written out in the model then that doesn't sound like what we want. That info will eventually go in the model in the Term (equiv to TypeArg) part, IIUC.

}
}

/// Instantiated [OpDef] signature.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest this goes in hugr_core::ops::custom

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.

Allow custom ops with static constant input edges
6 participants