-
Notifications
You must be signed in to change notification settings - Fork 318
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
Conversation
re:RefConnect / Strict-only variant: |
// 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, |
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 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.
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'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)?
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. |
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.
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> |
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.
Maybe we need to make sure LowerXMR
can handle 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.
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.
91d4c23
to
22ef929
Compare
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; |
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.
Maybe it is not assured that rd.getSrc()
dominates all users of results?
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.
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 .
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.
LLVM_DEBUG wrapped dominance check for now?
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.
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.
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 |
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.
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.", |
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.
Do we want a function on an interface or a trait? Might not matter.
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; |
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.
LLVM_DEBUG wrapped dominance check for now?
79bcc7e
to
d51f613
Compare
Draft until dominance situation resolved (guard or appropriately modeled in IR). Thanks folks. |
This reverts commit 8c93feb.
Restoring change previously introduced w/RefConnectOp PR, here RefDefineOp is the only op allowed to connect references (don't use connect when uninferred size).
If refs were supported in strictconnect, 'flip' should matter for them just the same.
Good to check the output as well as not crashing.
Doesn't matter, mixed widths easier to debug.
d51f613
to
ad88a39
Compare
Add "define" statement from References Proposal, move all reference connections to use this.
Fixes #4334 .
Fixes #3715 .
Fixes #3713 .