Skip to content

Conversation

@rwy7
Copy link
Contributor

@rwy7 rwy7 commented Oct 14, 2025

Align and flush out the functionality for port insertion and erasure on instance and instance-choice op.

Overview of new API for Port Insertions:

  • cloneWithInsertedPorts: create a clone of the op with additional inserted ports. Returns the clone. This API must clone, even if no ports are inserted.
  • cloneWithInsertedPortsAndReplaceUses: replace the uses of this op with a clone created via the above API. This API must clone, even if no ports are inserted.
  • insertPorts: the "do what I mean" API: clone with port insertions, replace uses, and then erase the old instance, but only if there are any ports being inserted.

Then for port erasure, we have a parallel set of functions:

  • cloneWithErasedPorts
  • cloneWithErasedPortsAndReplaceUses
  • erasePorts

All six four functions are implemented for both the InstanceOp and InstanceChoiceOp.

@rwy7 rwy7 force-pushed the instance-op-insert-and-erase-ports branch from 4eb4dde to f6962ea Compare October 14, 2025 19:06
Copy link
Member

@seldridge seldridge left a comment

Choose a reason for hiding this comment

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

This looks great. Nits throughout.

Comment on lines 142 to 144
/// Clone this instance with inserted ports.
[[nodiscard]] InstanceOp cloneWithInsertedPorts(
ArrayRef<std::pair<unsigned, PortInfo>> insertions);
Copy link
Member

Choose a reason for hiding this comment

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

The [[nodiscard]] is a great idea!

Copy link
Member

Choose a reason for hiding this comment

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

After thinking more about this, I'm now questioning if this makes sense. None of the upstream operations like this, e.g., clone are restricted like this. Also, the couple of times that (void) casts to avoid the unused shows up indicates this may be an anti-pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really only wanted to put nodiscard on the functions insertPorts/erasePorts, because it is an easy mistake to think that these functions are updating the op in-place. I don't really think it's necessary on cloneWithInsertedPorts or cloneWithInsertedPortsAndReplaceUses, so I can remove nodiscard from these functions. I'm partial to keeping nodiscard on insertPorts/erasePorts... but now I wonder if we should just remove these two functions instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK dropped the nodiscard, and dropped insertPorts/erasePorts.

Comment on lines 150 to 152
/// Clone and replace with inserted ports, then drop this instance.
[[nodiscard]] InstanceOp insertPorts(
ArrayRef<std::pair<unsigned, PortInfo>> insertions);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: it seems slightly inconsistent that as the methods do more, their names get longer, except for insertPorts and erasePorts. It's also weird that the shortest methods do the most work. It may be clearer to either use a long name or to drop the variants that also erase (given that this is easy enough to do in a second function call).

This is just commentary as I'm not sure what makes the most sense here. Also, am I reading this correctly in that erasePorts will now delete the op whereas previously it wouldn't? It seems better to just drop ::erasePorts entirely as it should be fully replaced with cloneWithInsertedPortsAndReplaceUses.

Comment on lines 245 to 264
/// Clone this instance with inserted ports.
[[nodiscard]] InstanceChoiceOp cloneWithInsertedPorts(
ArrayRef<std::pair<unsigned, PortInfo>> insertions);

/// Clones with inserted ports, then replaces uses.
[[nodiscard]] InstanceChoiceOp cloneWithInsertedPortsAndReplaceUses(
ArrayRef<std::pair<unsigned, PortInfo>> insertions);

/// Clone and replace with inserted ports, then drop this instance.
[[nodiscard]] InstanceChoiceOp
insertPorts(ArrayRef<std::pair<unsigned, PortInfo>> insertions);

/// Clone this instance with erased ports.
[[nodiscard]] InstanceChoiceOp
cloneWithErasedPorts(const llvm::BitVector &erasures);

/// Clone this instance with erased ports, then replaces uses.
[[nodiscard]] InstanceChoiceOp
cloneWithErasedPortsAndReplaceUses(const llvm::BitVector &erasures);

/// Clone and replace with erased ports, then drop this instance.
[[nodiscard]] InstanceChoiceOp erasePorts(const llvm::BitVector &erasures);
Copy link
Member

Choose a reason for hiding this comment

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

Great! Would these make sense to raise into the FInstanceLike op interface (which would then necessitate them being implemented for ObjectOp as well)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like a good idea, but not something I want to do in this PR.

Comment on lines 382 to 387
if (disabledPorts.any()) {
OpBuilder builder(instance);
auto newInstance = instance.erasePorts(builder, disabledPorts);
instance->erase();
instance = newInstance;
}
Copy link
Member

Choose a reason for hiding this comment

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

Nice that this avoids the conditional now.

Comment on lines +2633 to +2631
size_t erased = 0;
for (size_t i = 0, e = op1->getNumResults(); i < e; ++i) {
Copy link
Member

Choose a reason for hiding this comment

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

Same nit as above: consider moving erased into the initialization of the for loop as it has no users outside the loop.

for (size_t i = 0, e = op1->getNumResults(); i < e; ++i) {
auto r1 = op1->getResult(i);
if (erasures[i]) {
assert(r1.use_empty() && "removed instance port has uses");
Copy link
Member

Choose a reason for hiding this comment

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

This assertion feels slightly misplaced as it creates an unexpected precondition that isn't related to the correct operation of this method. Probably remove and then reorder the auto r1 to be closer to its (now) sole use or inline the single use if still readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The check was present before, so I decided to preserve it. I agree it is misplaced but I think I would rather have it than not.

const llvm::BitVector &portIndices) {
assert(portIndices.size() >= getNumResults() &&
"portIndices is not at least as large as getNumResults()");
static void replaceUsesRespectingInsertedPorts(
Copy link
Member

Choose a reason for hiding this comment

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

This and the subsequent method are fantastic to have in static functions. 💯

Comment on lines +2733 to +2744
for (size_t i = 0; i < oldPortCount; ++i) {
while (inserted < numInsertions) {
auto &[index, info] = insertions[inserted];
if (index > i)
break;

auto domains = fixDomainInfoInsertions(
context, info.domains ? info.domains : empty, indexMap);
newPortDirections.push_back(info.direction);
newPortNames.push_back(info.name);
newPortTypes.push_back(info.type);
newPortAnnos.push_back(info.annotations.getArrayAttr());
newDomainInfo.push_back(domains);
++inserted;
}

newPortDirections.push_back(getPortDirection(i));
newPortNames.push_back(getPortName(i));
newPortTypes.push_back(getType(i));
newPortAnnos.push_back(getPortAnnotation(i));
newDomainInfo.push_back(getDomainInfo()[i]);
indexMap[i] = i + inserted;
}
Copy link
Member

Choose a reason for hiding this comment

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

Is there a way to share this code with that in the instance choice version?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so, but maybe we can do that in a follow up. I've got all the ducks in a row now so it should be an easy PR.

@rwy7 rwy7 force-pushed the instance-op-insert-and-erase-ports branch 3 times, most recently from 2980dc3 to b17b122 Compare October 15, 2025 16:56
Add missing helpers to the instance choice op, and make some simplifications to
the helpers already present on the instance op, to make insertion/erasure of
ports more symmetric and similar between instance and instance-choice ops.
@rwy7 rwy7 force-pushed the instance-op-insert-and-erase-ports branch from b17b122 to 6000d91 Compare October 15, 2025 17:11
@rwy7 rwy7 merged commit 284092f into llvm:main Oct 15, 2025
7 checks passed
@rwy7 rwy7 deleted the instance-op-insert-and-erase-ports branch October 15, 2025 18:26
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.

2 participants