10000 [FIRRTL] Const handling in cast ops by trilorez · Pull Request #5151 · llvm/circt · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[FIRRTL] Const handling in cast ops #5151

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

Merged
merged 5 commits into from
May 17, 2023
Merged

[FIRRTL] Const handling in cast ops #5151

merged 5 commits into from
May 17, 2023

Conversation

trilorez
Copy link
Contributor
@trilorez trilorez commented May 8, 2023

This commit adds a ConstCastOp for casting away constness from types. This op and const types will be dropped early in the firrtl pipeline in a followup commit.

Verification has been added to other casting ops to ensure that type constness is taken into account.

@trilorez trilorez requested review from darthscsi and dtzSiFive May 8, 2023 20:54
@trilorez trilorez mentioned this pull request May 8, 2023
@trilorez trilorez force-pushed the dev/trilorez/const-cast branch 2 times, most recently from 4941449 to 54c0555 Compare May 9, 2023 02:22
Copy link
Contributor
@dtzSiFive dtzSiFive left a comment

Choose a reason for hiding this comment

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

Generally LGTM, added some comments/questions.

Does const work w/as{UInt,SInt,Clock,...} (just curious: how)? At a glance don't see it mentioned in other PR's, but they do feel like "cast" operations.

Will look at other PR's to put this in context, thanks I think that helps a lot.

if (*inTypeBits == *resTypeBits)
if (*inTypeBits == *resTypeBits) {
// non-'const' cannot be casted to 'const'
if (containsConst(getType()) && !isConst(getOperand().getType()))
Copy link
Contributor

Choose a reason for hiding this comment

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

The rule here is that bitcasts where the destination type has const anywhere /must/ originate from a fully-const input (even if they're the same type), is that intended/right? Is there a way to go from a bundle<x: const.uint<1>> to bundle<x: const.sint<1>> (or const.uint<1> to const.sint<1> for that matter?).

Checking if the bitcast portions are const-compatible sounds like a pain so not really suggesting that, but consider adding a test for the behavior or otherwise make that clear.

As an aside, the difference between a "const" type and a type that has const isn't always clear (which seems context-dependent), if there is phrasing that helps here that'd be great, especially for user-facing diagnostics.

An aggregate with all elements "const" being different than a const aggregate feels a little strange (as does const.bundle<x : const.uint<1>>) but I know that whole deal went through iterations and not sure how that could best be changed so there are canonical representations (e.g., outer const implies inner const).

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure how important this is (might be but might very much not be), doesn't seem like we use bitcast a lot?

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 idea is mainly to disallow non-const to const as a basic sanity-check without doing the full const-compatible check, since a const-cast would be more appropriate.

But on the other hand, bitcasts seem to be purely internal as opposed to an actual firrtl op and aren't introduced until the lower-types pass, which is after where the drop-const pass will be, so maybe I don't need to do anything for this op?

.Default(false);
}

LogicalResult ConstCastOp::verify() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding the verifier!

auto destElement = destElements[i];
auto srcElement = srcElements[i];
if (destElement.name != srcElement.name ||
destElement.isFlip != srcElement.isFlip ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a big deal but was curious: apparently can cast const.bundle<a: T, const b: U> to const.bundle<const a: T, b: U> which seems harmless since AFAIK the two are basically equivalent anyway (?).

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's weird but I consider it to be ok.

@trilorez
Copy link
Contributor Author

Thanks for the review!

Does const work w/as{UInt,SInt,Clock,...} (just curious: how)? At a glance don't see it mentioned in other PR's, but they do feel like "cast" operations.

The "as..." ops are handled in #5153 since they are prim ops. There, the ops preserve constness. See example.

@trilorez trilorez force-pushed the dev/trilorez/const-cast branch from 084edfe to 029a445 Compare May 15, 2023 17:48
@trilorez trilorez requested a review from youngar May 16, 2023 20:29
Comment on lines +871 to +943 < 8000 /div>
bool srcIsConst = srcType.isConst() || srcOuterTypeIsConst;

// Cannot cast non-'const' src to 'const' dest
if (destType.isConst() && !srcIsConst)
return false;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe move all the ground type handling down to the bottom like you did in the areTypesEquivalent PR.

@youngar
Copy link
Member
youngar commented May 17, 2023

I am trying to think of some weird cases where flip matters, I think I have one but it might not even matter. I guess a big question I have is, when are we going to end up using this operation? Is this for early lowering of connects to strict connects?

Here is the rough example:

firrtl.module(in %in : !firrtl.bundle<a flip: uint<1>>, out %out : !firrtl.bundle<a flip: const.uint<1>>) {
  // Option 0: don't use strict connect:
  firrtl.connect %out, %in : !firrtl.bundle<a flip: const.uint<1>>, !firrtl.bundle<a flip: uint<1>>
  
  // Option 1:  const-cast has the same "flow" as its input, meaning that it creates an alias to the input value.
  // This allows us to drive directly to the result of the cast:
  %0 = firrtl.constCast %out : (!firrtl.bundle<a flip: const.uint<1>>) -> !firrtl.bundle<a flip: uint<1>>
  firrtl.strictconnect %0, %in :  !firrtl.bundle<a flip: uint<1>>
  
  // Option 2: const-cast can add "const" to anything with a flip:
  %0 = firrtl.constCast %in : (!firrtl.bundle<a flip: uint<1>>) -> !firrtl.bundle<a flip: const.uint<1>>
  firrtl.connect %out, %0 : !firrtl.bundle<a flip: const.uint<1>>
}

edit: it appears to be important over here: https://github.com/llvm/circt/pull/5153/files#diff-c8689caabdf068960b794c6990c63337929f28b354cf839c2a43abb2973c23a6R126-R130

So it seems like we might fail to const-cast the two, and then hit the assert below?

: PredOpTrait<"operand constness must match",
CPred<"isConst($" # a # ".getType()) == isConst($" # b # ".getType())">>;

def UninferredResetCastOp : FIRRTLOp<"resetCast", [HasCustomSSAName, Pure, SameGroundTypeOperandConstness<"input", "result">]> {
Copy link
Member

Choose a reason for hiding this comment

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

We do try to keep the tablegen files to 80 (I think) columns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

@@ -938,6 +943,17 @@ def UninferredWidthCastOp : FIRRTLOp<"widthCast", [HasCustomSSAName, Pure]> {
"$input attr-dict `:` functional-type($input, $result)";
}

def ConstCastOp : FIRRTLOp<"constCast", [HasCustomSSAName, Pure]> {
let description = [{
Cast from a 'const' to a non-'const' type.
Copy link
Member

Choose a reason for hiding this comment

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

I like to add an example just demonstrating the op syntax. It makes the website documentation a little bit more useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Example added.

Comment on lines 58 to 60
/// Returns true if the type is or contains a 'const' type.
bool containsConst(Type type);

Copy link
Member
@youngar youngar May 17, 2023

Choose a reason for hiding this comment

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

It seems like it should go in FIRRTLTypes.h or FIRRTLUtils.h.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, not sure why I put it here in the first place. Moved along with isConst to FIRRTLTypes.h. Also updated the documentation to better reflect the spec.

Copy link
Member
@youngar youngar left a comment

Choose a reason for hiding this comment

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

A couple nits

@trilorez
Copy link
Contributor Author

So it seems like we might fail to const-cast the two, and then hit the assert below?

I think emitConnect handles this by swapping flipped elements. It looks like in that parsing branch, I can add a const-castable check here though to avoid doing unnecessary element-wise connects.

Relatedly, I'm thinking I should enforce that only passive types are const-castable to keep the logic simpler. Based on the emitConnect, that should handle the use cases for the op.

@trilorez trilorez force-pushed the dev/trilorez/const-cast branch from 029a445 to 236befe Compare May 17, 2023 17:24
@trilorez
Copy link
Contributor Author

Updated to require passive types for the cast op.

@youngar
Copy link
Member
youngar commented May 17, 2023

Relatedly, I'm thinking I should enforce that only passive types are const-castable to keep the logic simpler. Based on the emitConnect, that should handle the use cases for the op.

Sure, the more powerful the cast op is, the less connect operations we will need, its just a trade-off. Since we are breaking down connects eventually anyways, you might as well only allow ground types to be const-cast :P. But it looks like enforcing a passive operand will make the current code work with no changes.

Comment on lines 925 to 974
return llvm::all_of_zip(destElements, srcElements,
[&](const BundleType::BundleElement &destElement,
const BundleType::BundleElement &srcElement) {
return destElement.name == srcElement.name &&
destElement.isFlip == srcElement.isFlip &&
areTypesConstCastable(destElement.type,
srcElement.type,
srcIsConst);
});
Copy link
Member
@youngar youngar May 17, 2023

Choose a reason for hiding this comment

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

Should this check !destElement.isFlip && !srcElement.isFlip, i.e., that the bundle is passive?

If this is only called by the ConstCastOp verifier (I believe the plan is that it is not), then this will have already been checked.

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 point. I added a passive check above so every element doesn't need to check flip.

trilorez added 4 commits May 17, 2023 12:49
This commit adds a ConstCastOp for casting away constness from types.
This op and const types will be dropped early in the firrtl pipeline in a
followup commit.

Verification has been added to other casting ops to ensure that type
constness is taken into account.
@trilorez trilorez force-pushed the dev/trilorez/const-cast branch from 313216c to 3e54136 Compare May 17, 2023 18:53
Copy link
Member
@youngar youngar left a comment

Choose a reason for hiding this comment

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

LGTM thanks!

Comment on lines 961 to 974
auto destElements = destBundleType.getElements();
auto srcElements = srcBundleType.getElements();
size_t numDestElements = destElements.size();
if (numDestElements != srcElements.size())
return false;

return llvm::all_of_zip(destElements, srcElements,
[&](const BundleType::BundleElement &destElement,
const BundleType::BundleElement &srcElement) {
return destElement.name == srcElement.name &&
areTypesConstCastable(destElement.type,
srcElement.type,
srcIsConst);
DFF1 });
Copy link
Member

Choose a reason for hiding this comment

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

Might try inlining the locals or const auto & for arguments and see if the auto-formatter picks a different layout. I really don't love when it indents the body of the lambda so far. (If you like it like this its totally fine).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems slightly better with const auto &

@trilorez trilorez merged commit 2423baa into main May 17, 2023
@trilorez trilorez deleted the dev/trilorez/const-cast branch May 17, 2023 19:41
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
0