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

[FIRRTL] Add RefDefineOp, disallow connecting references otherwise. #4812

Merged
merged 25 commits into from
Mar 30, 2023

Conversation

dtzSiFive
Copy link
Contributor

@dtzSiFive dtzSiFive commented Mar 13, 2023

Add "define" statement from References Proposal, move all reference connections to use this.

Fixes #4334 .
Fixes #3715 .
Fixes #3713 .

@dtzSiFive
Copy link
Contributor Author

re:RefConnect / Strict-only variant:
The plan was previously to allow non-sized operands to allow inferring through references, starting with "strict"-like (same source/dest type) since that's what we currently require anyway for reference "connections".
(see #4800 for predicate used to define what references may be connected).
This may instead be handled by operation in #4804, perhaps inserting as needed when generating these in the parser + code (emitConnect?).

@dtzSiFive dtzSiFive marked this pull request as ready for review March 13, 2023 16:41
// For now, refs can't be in bundles so this is sufficient.
// In the future need to ensure no other define's to same "fieldSource".
// (When aggregates can have references, we can define a reference within,
// but this must be unique. Checking this here may be expensive,
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 wondered if a number of the more expensive "must be true" but "should be made true and then never changed" things should move to an optional verif pass. Similar to "connect doesn't exist anymore" and maybe "only strict registers exist now". Essentially phase-dependent verification.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We've discussed doing this via a different dialect, or moving a bunch of stuff to CHIRRTL, so on-- but maybe we just lean into "phases" and throw an attribute on the circuit indicating which level/phase it's claiming to be at? Easy to verify when consuming/printing, and between pipeline "phases" (optional, as you say), and we don't need to duplicate things across a bunch of dialects (or sort out what the ideal mid-level dialect should be, yet)?

@darthscsi
Copy link
Contributor

This may instead be handled by operation in #4804, perhaps inserting as needed when generating these in the parser + code (emitConnect?).

My goal is that strictconnect guarantees identical types and doesn't hide special types (analog, ref, external). I thought I would need to legalize connects, but likely everything can be handled in emitConnect (parsing time). But, that isn't making a statement on how special types should behave w.r.t. inference.

Copy link
Contributor

@prithayan prithayan left a comment

Choose a reason for hiding this comment

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

Overall this looks good.
Bikeshed: ref.connect instead of ref.define ?

firrtl.module private @OutChild(in %x: !firrtl.uint, out %y: !firrtl.uint, out %p: !firrtl.ref<uint>) {
%0 = firrtl.ref.send %x : !firrtl.uint
firrtl.ref.define %p, %0 : !firrtl.ref<uint>
%1 = firrtl.ref.resolve %p : !firrtl.ref<uint>
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we need to make sure LowerXMR can handle this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. It makes it through firtool, but directly running LowerXMR fails / crashes in a cast looks like, missed that. The issue fixed is specifically about this being rejected by verifier as invalid entirely, which is what this test checks.

Or is there a change/action you're suggesting here?

Something for the TODO re:LowerXMR crashing on this? LowerXMR will need reworking for handling ref.sub and u-turns (#4692) as well, (and aggregates-with-refs eventually), so think this goes on that list.

Comment on lines 461 to 475
auto getRefDefine = [](Value result) -> RefDefineOp {
for (auto *user : result.getUsers()) {
if (auto rd = dyn_cast<RefDefineOp>(user);
rd && rd.getDest() == result)
return rd;
}
return {};
};
auto rd = getRefDefine(result);
assert(rd && "input ref port to instance is alive, but no driver?");
assert(isKnownAlive(rd.getSrc()));
result.replaceAllUsesWith(rd.getSrc());
++numErasedOps;
rd.erase();
continue;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it is not assured that rd.getSrc() dominates all users of results?

Copy link
Contributor Author

@dtzSiFive dtzSiFive Mar 15, 2023

Choose a reason for hiding this comment

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

Good call, thanks!

For this and other reasons, considering an op to bounce refs through that can be used in these situations -- replacing def of a ref that of course dominates its uses but the uses may not dominate each other (or indeed not all visible at any point). Something like %out, %in = firrtl.(ref.)pipe : !firrtl.ref<T>, or just a wire/wire-like, to plumb dataflow through. Such an op that guarantees (requires) a single driver is likely useful beyond references.

An op like that would hopefully avoid the need for the special casing done here and in ModuleInliner, or at least reduce it to producing the right sort of bounce declaration.

Looks like IMDCE presently assumes single-block, so one approach would be hoisting backwards_slice(driver), but that doesn't seem like a great approach or something to be bothering about in this pass.

Inliner has similar issues/concerns, and looks like it is intended to work with "when's" -- in some situations if there's input ref (same as the situation here w/IMDCE, for an example of this see [1]) it can produce invalid IR similarly re:dominance. Both are unreachable presently as we don't generate input reference ports.

One of a few candidate solutions...

[1] Example input (also crashes this IMDCE for reasons discussed): https://github.com/dtzSiFive/reftype-inputs/blob/ef6294d2c21063f5a26618e7e7b3d1a9c91896c5/inliner-refs.fir .

Copy link
Contributor

Choose a reason for hiding this comment

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

LLVM_DEBUG wrapped dominance check for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

report fatal error if not already in same block and ordered legally. Shouldn't happen unless we enable input references prematurely or run IMDCE before ExpandWhen's. I did not want to introduce additional fragility to our passes re:hidden assumptions, but at least this has a guard. Handling it at all is only needed if there are input references that are used locally but dead through the instance -- which may be good to make illegal anyway.

@dtzSiFive
Copy link
Contributor Author

Bikeshed: ref.connect instead of ref.define ?

I named it after the statement it's modelling that was added in FIRRTL 2.0, which seemed important at the time but seems less so now 👍. If it can be a FConnectLike it sure can be ref.connect (it's presently refconnect which has benefits but unless we rename the ref.* ops it seems like it should be also firrtl.ref.connect but 🤷). Don't feel strongly about this internally to CIRCT (vs really wanted to separate define from connect in the spec), thanks for the suggestion!

Copy link
Contributor

@darthscsi darthscsi left a comment

Choose a reason for hiding this comment

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

LGTM.

@@ -340,6 +340,15 @@ def FConnectLike : OpInterface<"FConnectLike"> {
"Value", "getDest", (ins)>,
InterfaceMethod<"Return a source of connection.",
"Value", "getSrc", (ins)>,
StaticInterfaceMethod<"Return connection behavior kind.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want a function on an interface or a trait? Might not matter.

Comment on lines 461 to 475
auto getRefDefine = [](Value result) -> RefDefineOp {
for (auto *user : result.getUsers()) {
if (auto rd = dyn_cast<RefDefineOp>(user);
rd && rd.getDest() == result)
return rd;
}
return {};
};
auto rd = getRefDefine(result);
assert(rd && "input ref port to instance is alive, but no driver?");
assert(isKnownAlive(rd.getSrc()));
result.replaceAllUsesWith(rd.getSrc());
++numErasedOps;
rd.erase();
continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

LLVM_DEBUG wrapped dominance check for now?

@dtzSiFive dtzSiFive force-pushed the feature/ref-define branch from 79bcc7e to d51f613 Compare March 28, 2023 22:17
@dtzSiFive dtzSiFive marked this pull request as draft March 28, 2023 22:17
@dtzSiFive
Copy link
Contributor Author

Draft until dominance situation resolved (guard or appropriately modeled in IR). Thanks folks.

@dtzSiFive dtzSiFive force-pushed the feature/ref-define branch from d51f613 to ad88a39 Compare March 29, 2023 20:40
@dtzSiFive dtzSiFive marked this pull request as ready for review March 29, 2023 21:21
@dtzSiFive dtzSiFive merged commit da6cb20 into llvm:main Mar 30, 2023
@dtzSiFive dtzSiFive deleted the feature/ref-define branch March 30, 2023 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants