This is an archive of the discontinued LLVM Phabricator instance.

Propagate the volatile qualifier of exp to store /load operations .
ClosedPublic

Authored by umesh.kalappa0 on Aug 14 2023, 8:42 AM.

Details

Summary

This changes to address the PR : 55207

We update the volatility on the LValue by looking at the LHS cast operation qualifier and propagate the RValue volatile-ness from the CGF data structure .

Diff Detail

Event Timeline

umesh.kalappa0 created this revision.Aug 14 2023, 8:42 AM
Herald added a project: Restricted Project. · View Herald TranscriptAug 14 2023, 8:42 AM
umesh.kalappa0 requested review of this revision.Aug 14 2023, 8:42 AM

Adding some more codegen reviewers; the changes should come with a re

clang/lib/CodeGen/CGExprScalar.cpp
2236–2240
clang/lib/CodeGen/CodeGenFunction.h
548–549 ↗(On Diff #549964)

I'm not quite sure how to repair the second sentence; were you going for "doesn't have to have the volatile qualifier"?

Using a function-wide "isVolatileCastStmt" will almost certainly break in more complex cases (consider, for example, the case where the address of a load itself contains loads).

The fix to EmitCastLValue looks correct and sufficient. All of this business with isVolatileCastStmt should be removed.

Slight correction: I guess a NoOp cast can *remove* qualifiers, too. We should just unconditionally use the volatile qualifier from the result type of the cast instead of only *adding* it.

umesh.kalappa0 added a comment.EditedAug 14 2023, 11:57 PM

Using a function-wide "isVolatileCastStmt" will almost certainly break in more complex cases (consider, for example, the case where the address of a load itself contains loads).

@efriedma ,thank you for the pointers and we thought of propagating this info from VisitCast ,but that needs various api change and how about the reset "isVolatileCastStmt " to false after use in "VisitCast" ,that resolve this complex address load issue .

or any other suggestions you have in mind ?

The fix to EmitCastLValue looks correct and sufficient. All of this business with isVolatileCastStmt should be removed.

@rjmccall ,we need "isVolatileCastStmt" for the RHS that has the cast like stated in the testcase and "EmitCastLValue" not handle the RValue .

Slight correction: I guess a NoOp cast can *remove* qualifiers, too. We should just unconditionally use the volatile qualifier from the result type of the cast instead of only *adding* it.

@rjmccall ,thank you and you right here but adding unconditionally can assert the case where either cast doesn't have the volatile qualifier nor result type doesn't have the same and if we remove the volatile-ness from LValue ,then it doesn't make sense to add right ?

umesh.kalappa0 retitled this revision from Propagate the volatile qualifier of exp type to store /load operations . to Propagate the volatile qualifier of exp to store /load operations ..Aug 15 2023, 12:06 AM
umesh.kalappa0 marked 2 inline comments as done.Aug 15 2023, 7:30 AM

The fix to EmitCastLValue looks correct and sufficient. All of this business with isVolatileCastStmt should be removed.

@rjmccall ,we need "isVolatileCastStmt" for the RHS that has the cast like stated in the testcase and "EmitCastLValue" not handle the RValue .

Oh, I think I understand the underlying issue with the RHS here, and it was something I was overlooking it before. Clang's r-value emitters aggressively recurse through LValueToRValue conversions, essentially sinking the load into the emission of the wrapped l-value expression. This is good to do because there are some common situations (especially in C++) where expressions are formally l-values but we can emit them more efficiently if we know there's going to be an immediate load. When we're sinking loads that way, it's usually okay to also recurse through a NoOp conversion. But we can't do that if there's a change in qualification that'll affect how the load is emitted, which mostly means a change in volatile.

Using a "global" flag on the CGF object is still the wrong way to solve this. The r-value emitters need to detect that a cast materially changes qualification (I think this just means volatile) and, if so, emit the expression as an l-value and then load from it rather than aggressively recursing through the cast.

You'll need to do this same logic in all of the r-value emitters: scalar, complex, and (probably) aggregate. You should be able to write a common helper function that says whether a CastExpr has a material qualification difference.

The fix to EmitCastLValue looks correct and sufficient. All of this business with isVolatileCastStmt should be removed.

@rjmccall ,we need "isVolatileCastStmt" for the RHS that has the cast like stated in the testcase and "EmitCastLValue" not handle the RValue .

Oh, I think I understand the underlying issue with the RHS here, and it was something I was overlooking it before. Clang's r-value emitters aggressively recurse through LValueToRValue conversions, essentially sinking the load into the emission of the wrapped l-value expression. This is good to do because there are some common situations (especially in C++) where expressions are formally l-values but we can emit them more efficiently if we know there's going to be an immediate load. When we're sinking loads that way, it's usually okay to also recurse through a NoOp conversion. But we can't do that if there's a change in qualification that'll affect how the load is emitted, which mostly means a change in volatile.

Using a "global" flag on the CGF object is still the wrong way to solve this. The r-value emitters need to detect that a cast materially changes qualification (I think this just means volatile) and, if so, emit the expression as an l-value and then load from it rather than aggressively recursing through the cast.

You'll need to do this same logic in all of the r-value emitters: scalar, complex, and (probably) aggregate. You should be able to write a common helper function that says whether a CastExpr has a material qualification difference.

Agree global flag is not the right way and refactor the changes for scalar/aggregate and complex and

whether a CastExpr has a material qualification difference.

tried to differentiate by QualType of declnode and expression with no success ,hence we go-ahead if the exp has volatile then we choose the other path .

The fix to EmitCastLValue looks correct and sufficient. All of this business with isVolatileCastStmt should be removed.

@rjmccall ,we need "isVolatileCastStmt" for the RHS that has the cast like stated in the testcase and "EmitCastLValue" not handle the RValue .

Oh, I think I understand the underlying issue with the RHS here, and it was something I was overlooking it before. Clang's r-value emitters aggressively recurse through LValueToRValue conversions, essentially sinking the load into the emission of the wrapped l-value expression. This is good to do because there are some common situations (especially in C++) where expressions are formally l-values but we can emit them more efficiently if we know there's going to be an immediate load. When we're sinking loads that way, it's usually okay to also recurse through a NoOp conversion. But we can't do that if there's a change in qualification that'll affect how the load is emitted, which mostly means a change in volatile.

Using a "global" flag on the CGF object is still the wrong way to solve this. The r-value emitters need to detect that a cast materially changes qualification (I think this just means volatile) and, if so, emit the expression as an l-value and then load from it rather than aggressively recursing through the cast.

You'll need to do this same logic in all of the r-value emitters: scalar, complex, and (probably) aggregate. You should be able to write a common helper function that says whether a CastExpr has a material qualification difference.

Agree global flag is not the right way and refactor the changes for scalar/aggregate and complex and

whether a CastExpr has a material qualification difference.

tried to differentiate by QualType of declnode and expression with no success ,hence we go-ahead if the exp has volatile then we choose the other path .

CE->isGLValue() && CE->getType().isVolatileQualified() != CE->getSubExpr()->getType().isVolatileQualified()

umesh.kalappa0 updated this revision to Diff 551004.EditedAug 16 2023, 11:01 PM

The fix to EmitCastLValue looks correct and sufficient. All of this business with isVolatileCastStmt should be removed.

@rjmccall ,we need "isVolatileCastStmt" for the RHS that has the cast like stated in the testcase and "EmitCastLValue" not handle the RValue .

Oh, I think I understand the underlying issue with the RHS here, and it was something I was overlooking it before. Clang's r-value emitters aggressively recurse through LValueToRValue conversions, essentially sinking the load into the emission of the wrapped l-value expression. This is good to do because there are some common situations (especially in C++) where expressions are formally l-values but we can emit them more efficiently if we know there's going to be an immediate load. When we're sinking loads that way, it's usually okay to also recurse through a NoOp conversion. But we can't do that if there's a change in qualification that'll affect how the load is emitted, which mostly means a change in volatile.

Using a "global" flag on the CGF object is still the wrong way to solve this. The r-value emitters need to detect that a cast materially changes qualification (I think this just means volatile) and, if so, emit the expression as an l-value and then load from it rather than aggressively recursing through the cast.

You'll need to do this same logic in all of the r-value emitters: scalar, complex, and (probably) aggregate. You should be able to write a common helper function that says whether a CastExpr has a material qualification difference.

Agree global flag is not the right way and refactor the changes for scalar/aggregate and complex and

whether a CastExpr has a material qualification difference.

tried to differentiate by QualType of declnode and expression with no success ,hence we go-ahead if the exp has volatile then we choose the other path .

CE->isGLValue() && CE->getType().isVolatileQualified() != CE->getSubExpr()->getType().isVolatileQualified()

Thank you @rjmccall for the pointers and same doesn't work with the case like

static_cast<volatile _Complex float &>(cf) = static_cast<volatile _Complex float&>(cf) + 1;

CXXStaticCastExpr 0x55556c405410 'volatile _Complex float' lvalue static_cast<volatile _Complex float &> <NoOp>
`-ImplicitCastExpr 0x55556c4053f8 'volatile _Complex float' lvalue <NoOp> part_of_explicit_cast
  `-DeclRefExpr 0x55556c4053c8 '_Complex float' lvalue Var 0x55556c404fb8 'cf' '_Complex float'

@rjmccall ,is that changes looks good to land on master or do you have any other comments ?

rjmccall added inline comments.Aug 18 2023, 10:07 AM
clang/include/clang/AST/Expr.h
3557

This is not a good name for this. Maybe changesVolatileQualification?

Also, please move this down after all the methods that are just getting and setting stored properties of the CastExpr — right before getTargetFieldForToUnionCast seems like the right place.

We should probably consider giving qualification conversions their own CastKind instead of folding them into NoOp. (You don't need to do that in this patch, I'm just saying that this seems like a poor choice of representation.)

clang/lib/CodeGen/CGExpr.cpp
4810

As discussed, you need to set the qualifiers to the values in E->getType(), not merge them. If someone casts away volatile, we should emit a non-volatile access. (And you should test this.)

umesh.kalappa0 marked an inline comment as done.
umesh.kalappa0 added inline comments.Aug 21 2023, 8:34 AM
clang/include/clang/AST/Expr.h
3557

We should probably consider giving qualification conversions their own CastKind instead of folding them into NoOp. (You don't need to do that in this patch, I'm just saying that this seems like a poor choice of representation.)

@rjmccall agree on this

rjmccall added inline comments.Aug 21 2023, 12:29 PM
clang/include/clang/AST/Expr.h
3639

Please don't use this-> when you don't have to and it isn't saying anything important (like contrasting with some other object of the same type).

clang/lib/CodeGen/CGExpr.cpp
4810
if (E->changesVolatileQualification())
  LV.setVolatile(E->getType().isVolatileQualified());

and please add a test case to check that this is emitted with a non-volatile load:

volatile int vx;
int x = const_cast<int&>(vx);
clang/lib/CodeGen/CGExprScalar.cpp
2230

Consistent indentation, please.

umesh.kalappa0 marked an inline comment as done.Aug 21 2023, 8:01 PM
umesh.kalappa0 added inline comments.
clang/include/clang/AST/Expr.h
3639

Agree

clang/lib/CodeGen/CGExpr.cpp
4810

Thank you @rjmccall for the suggestion and was wondering the exist check is taking care of volatile cast away and where it can break ?

clang/test/CodeGen/volatile.cpp
41

@rjmccall ,this line check should take care of non-volatile case.

umesh.kalappa0 marked an inline comment as done.Aug 21 2023, 8:03 PM
umesh.kalappa0 added inline comments.Aug 21 2023, 9:11 PM
clang/lib/CodeGen/CGExpr.cpp
4810

@rjmccall ,i think the reason we used addVolatile() on LV was that the setVolatile() mem or any other volatile setter member functions doesn't exit on the LV or we are missing something ? .

rjmccall added inline comments.Aug 22 2023, 11:01 AM
clang/include/clang/AST/Expr.h
3639

Just getSubExpr(), not getSubExprAsWritten().

clang/lib/CodeGen/CGExpr.cpp
4810

Ah, sorry, I was looking at the wrong type.

Looks like you can just do LV.getQuals() = E->getType().getQualifiers().

clang/include/clang/AST/Expr.h
3639

@rjmccall ,the getSubExpr() doesn't work for the case like

CXXStaticCastExpr 0x55556c4e7330 'volatile _Complex float' lvalue static_cast<volatile _Complex float &> <NoOp>
`-ImplicitCastExpr 0x55556c4e7318 'volatile _Complex float' lvalue <NoOp> part_of_explicit_cast

`-DeclRefExpr 0x55556c4e72e8 '_Complex float' lvalue Var 0x55556c4e6ed8 'cf' '_Complex float'

where getSubExpr() ="ImplicitCastExpr 'volatile _Complex float' lvalue <NoOp> part_of_explicit_cast"
and isVolatileQualified() is true .

rjmccall added inline comments.Aug 23 2023, 10:02 AM
clang/include/clang/AST/Expr.h
3639

That's fine. You'll recurse one step and then catch when looking at the ICE.

rjmccall added inline comments.Aug 24 2023, 10:33 AM
clang/include/clang/AST/Expr.h
3639

No, I mean that I don't think you need to do anything to handle this explicitly, because I would expect that IRGen will just recurse one step and then see the volatile change on the inner CE. That is:

  • First, ScalarExprEmitter will look at the outermost cast and see that it's a NoOp that doesn't change volatile qualification, so it'll recurse to the sub-expression
  • Next, ScalarExprEmitter will look at the middle cast and see that it's a NoOp that does change volatile qualification, so it'll do an EmitLoadOfLValue and do a volatile load.

Is that not what you're seeing?

umesh.kalappa0 marked an inline comment as done.Aug 24 2023, 11:36 PM
umesh.kalappa0 added inline comments.
clang/include/clang/AST/Expr.h
3639

Is that not what you're seeing?

@rjmccall ,thank you for inputs and yes that work for ScalarExprEmitter and not for ComplexExpr and we made changes accordingly now ,please let us know your thoughts on the same .

rjmccall added inline comments.Aug 25 2023, 11:05 AM
clang/include/clang/AST/Expr.h
3636

Please make this a const method so that you don't need these unnecessary const_casts wherever you call it.

This comment is over-precise; please use something more like:

Return true if this conversion changes the volatile-ness of a gl-value.
Qualification conversions on gl-values currently use CK_NoOp, but
it's important to recognize volatile-changing conversions in clients like
code generation that normally eagerly peephole loads. Note that the
query is answering for this specific node; Sema may produce multiple
cast nodes for any particular conversion sequence.

umesh.kalappa0 marked an inline comment as done.
umesh.kalappa0 marked an inline comment as done.
umesh.kalappa0 marked an inline comment as done.Aug 25 2023, 11:34 PM
umesh.kalappa0 added inline comments.
clang/include/clang/AST/Expr.h
3636

@rjmccall ,updated with changes .

umesh.kalappa0 marked an inline comment as done.
rjmccall added inline comments.Aug 26 2023, 9:27 AM
clang/test/CodeGen/volatile.cpp
41

Please test the case where a cast removes a volatile qualifier. You're not testing that here because the underlying l-value is not volatile in the first place — I could tell this even without looking at the declaration of vol because a static_cast is not allowed to remove qualifiers.

umesh.kalappa0 marked an inline comment as done.
umesh.kalappa0 added inline comments.
clang/test/CodeGen/volatile.cpp
41

@rjmccall ,updated accordingly ,thanks

rjmccall accepted this revision.Aug 28 2023, 10:56 AM

LGTM, thanks

This revision is now accepted and ready to land.Aug 28 2023, 10:56 AM
This revision was landed with ongoing or failed builds.Sep 23 2023, 7:10 AM
This revision was automatically updated to reflect the committed changes.