-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Don't create cast for tail call return value. #7908
Conversation
Asm diffs? |
if (!bIntrinsicImported) | ||
// Don't create cast for a variable on the stack if it is intrinsic or tail call. | ||
// We assume that the value will not be physically represented there. | ||
if (!bIntrinsicImported && !(tailCall && canTailCall)) |
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 am not sure this is the right fix for the following reason:
During importation, impImportCall(), some tail call checks are performed and remaining checks during fgMorphCall(). After the checks in fgMorphCall(), if JIT is committed to honor tail call, it will set a flag (GTF_CALL_M_TAILCALL) on GT_CALL node. Till then there is no guarantee a tail call will be honored.
Say we don't insert the cast during importation even if the tail call is returning a small type. What if the tail call was not honored and it was dispatched as a normal call? Is there anything to ensure that the cast is added back again?
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.
Yes, if we set checkSmallType = true and forbid tail calls in morph, we will get movzx before the commit and mov after this change. It is wrong.
To fix it we can:
- Move checkForSmallType logic from importer to morph;
- Create the cast during importation and delete it in morph;
What is better?
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 am inclined to believe that var=cast(call) is a new tail call pattern that is possible which is not currently handled by morph logic. But what stumps me is that so far we have not encountered such a tail call pattern to expose this issue. Do we have a test case that repro's this issue in non-R2R context? We need it anyways for adding as a unit test case.
I think option #2 is the way to go after we have convinced ourselves that this can happen in non-R2R tail call scenarios by constructing a repro case.
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.
It can't happen if checkForSmallType == false (checkForSmallType = opts.IsJit64Compat() || opts.IsReadyToRun();). So you can see it only in R2R or in JIT64Compat mode. However, in ngen we don't use tail calls and JIT64Compat is outdated.
It is possible to set this flag to true, in this case you will see the assert error in coreclr.
I think it is impossible to reproduce this failure on coreclr without changing source code.
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.
@sandreenko - as discussed offline, please see if we can repro this on desktop side. I am fine even if we can add an IL test case that exposes the issue when R2R compiling the test case binary. As I said earlier, handling this new tail call pattern in morph seems to be the way to go.
@sandreenko - Here is an IL repro that hits the same assert in morph.cpp on desktop
|
58b8812
to
e4ded66
Compare
I added a case for the new pattern "asg -> cast -> call". The pattern with cast already have existed as "return -> cast -> call". We don't need to delete cast because when we do the tailcall, we replace whole statement tree with it. |
@dotnet-bot test Linux ARM Emulator Cross Debug Build |
noway_assert( | ||
(stmtExpr->gtOper == GT_CALL && stmtExpr == call) || | ||
(stmtExpr->gtOper == GT_RETURN && | ||
(stmtExpr->gtOp.gtOp1 == call || stmtExpr->gtOp.gtOp1->gtOp.gtOp1 == call)) || |
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.
Since we are editing this assert, please tighten the assert that stmtExpr->gtOp.gtOp1 is a GT_CAST and stmtExpr->gtOp.gtOp1->gtOp.gtOp1 == call
(stmtExpr->gtOper == GT_CALL && stmtExpr == call) || | ||
(stmtExpr->gtOper == GT_RETURN && | ||
(stmtExpr->gtOp.gtOp1 == call || stmtExpr->gtOp.gtOp1->gtOp.gtOp1 == call)) || | ||
(stmtExpr->gtOper == GT_ASG && (stmtExpr->gtOp.gtOp2 == call || stmtExpr->gtOp.gtOp2->gtOp.gtOp1 == call))); |
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.
Here too please tighten the assert by adding the check that stmtExpr->gtOp.gtOp2 is a GT_CAST.
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.
In both of the above places we also need to assert that it is not only GT_CAST but a non-overflow checking one. Otherwise, it is not correct to remove GT_CAST.
(stmtExpr->gtOp.gtOp1 == call || stmtExpr->gtOp.gtOp1->gtOp.gtOp1 == call)) || | ||
(stmtExpr->gtOper == GT_ASG && stmtExpr->gtOp.gtOp2 == call)); | ||
// GT_RETURN(GT_CALL(..)) or GT_RETURN(GT_CAST(GT_CALL)) | ||
// var = GT_CALL or var = (GT_CAST(GT_CALL) |
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.
Nit: closing ")" missing in the comment.
} // end of method Test::Main | ||
|
||
|
||
} |
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.
Nit: Some special character?
e4ded66
to
6807ef6
Compare
sequense localVariable = cast.int(call.small_type()); return localVaribale is correct tail call pattern.
6807ef6
to
fb33095
Compare
@dotnet-bot test Linux ARM Emulator Cross Debug Build |
|
Looks good. |
Don't create cast from small type to bigger stack type when it is tail call tree.
Return value is not presented on the stack and does not require cast.
Fix dotnet/corert#2073.