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

Do not copy nullable value type receiver of a constrained call. #66311

Merged
merged 1 commit into from
Jan 10, 2023

Conversation

AlekseyTs
Copy link
Contributor

The check that we used to detect value types at runtime didn’t quite work for nullable value types due to special boxing behavior for them. Switching to typeof(T).IsValueType check instead.

Fixes #66162.
Related to #63221.

The check that we used to detect value types at runtime didn’t quite work for
nullable value types due to special boxing behavior for them.
Switching to `typeof(T).IsValueType` check instead.

Fixes dotnet#66162.
Related to dotnet#63221.
@AlekseyTs AlekseyTs requested a review from a team as a code owner January 8, 2023 00:14
@AlekseyTs
Copy link
Contributor Author

@dotnet/roslyn-compiler Please review

@AlekseyTs
Copy link
Contributor Author

@333fred Please review, you should know the context since you reviewed #65642

@jcouv
Copy link
Member

jcouv commented Jan 9, 2023

FWIW, I took a look (trying to understand the problem in the bug and how it was resolved), but couldn't figure it out after 20 minutes so I'm leaving it for someone else or if some more context could be provided (what's wrong with the IL from Call1 or Call2 in example, how does the fix address the problem?)


In reply to: 1376375491

@AlekseyTs
Copy link
Contributor Author

@jcouv

I took a look (trying to understand the problem in the bug and how it was resolved), but couldn't figure it out after 20 minutes so I'm leaving it for someone else or if some more context could be provided (what's wrong with the IL from Call1 or Call2 in example, how does the fix address the problem?)

If you look at the IL diff for affected tests, you'll notice that the change replaces sequences like

  IL_0002:  ldloca.s   V_0
  IL_0004:  initobj    ""T""
  IL_000a:  ldloc.0
  IL_000b:  box        ""T""
  IL_0010:  brtrue.s   IL_001a

with sequences like:

  IL_0002:  ldtoken    ""T""
  IL_0007:  call       ""System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)""
  IL_000c:  call       ""bool System.Type.IsValueType.get""
  IL_0011:  brtrue.s   IL_001b

These IL sequences produce the same result for all types, but for nullable value types. There is a special boxing behavior for nullable value types, boxing of a default value of a nullable value type yields null. Therefore, the old IL is not going to jump for a nullable value type, but instead going to fall through to the code that is supposed to be executed only for a class. That is wrong because a nullable value type is a value type.

@@ -4579,6 +4579,21 @@ internal enum AddressKind
stackLocalsOpt));
goto case BoundKind.ConditionalReceiver;

case BoundKind.ComplexReceiver:
Copy link
Member

@jcouv jcouv Jan 9, 2023

Choose a reason for hiding this comment

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

Can we reach this? I thought this node would only be introduced in a later phase (but not during initial binding). #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we reach this? I thought this node would only be introduced in a later phase (but not during initial binding).

The same goes for BoundKind.ComplexConditionalReceiver. Just follow the pattern.

Copy link
Member

Choose a reason for hiding this comment

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

Why not throw unreachable exception since this is unreachable? I'd do that for ComplexConditionalReceiver too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why not throw unreachable exception since this is unreachable? I'd do that for ComplexConditionalReceiver too

I am comfortable going as is


private void EmitComplexReceiver(BoundComplexReceiver expression, bool used)
{
Debug.Assert(!used);
Copy link
Member

@jcouv jcouv Jan 10, 2023

Choose a reason for hiding this comment

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

nit: What's the benefit of only asserting? Throwing seems better.
We can still keep the conditional AdjustStack below for the day we could reach with here with used==true. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's the benefit of only asserting? Throwing seems better.

The implementation is doing the right thing. I do not see any point in throwing. The assert will help us identify the test scenario.

@jcouv
Copy link
Member

jcouv commented Jan 10, 2023

            );

We have a note in the compiler test plan: "If you add a new expression form that requires spilling, test it in the catch filter". Consider adding a test for this.


In reply to: 1376544884


Refers to: src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenCallTests.cs:30721 in 208dc2a. [](commit_id = 208dc2a, deletion_comment = False)

Copy link
Member

@jcouv jcouv 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 (iteration 1) with test suggestion to consider

@jcouv jcouv self-assigned this Jan 10, 2023
@@ -2940,6 +2940,20 @@ public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditiona
return null;
}

public override BoundNode VisitComplexReceiver(BoundComplexReceiver node)
Copy link
Member

@333fred 333fred Jan 10, 2023

Choose a reason for hiding this comment

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

Are any flow passes actually going to see one of these? It looks like they're only created during local lowering, so I wouldn't expected them to be seen. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are any flow passes actually going to see one of these? It looks like they're only created during local lowering, so I wouldn't expected them to be seen.

This code path is reachable. Flow analysis is done during async rewrite, etc.

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Done review pass (commit 1)

@AlekseyTs
Copy link
Contributor Author

            );

We have a note in the compiler test plan: "If you add a new expression form that requires spilling, test it in the catch filter". Consider adding a test for this.

I think that refers to language constructs that themselves trigger spilling, like async or patterns, and is not talking about new bound nodes. I am not adding anything like that.


In reply to: 1376544884


Refers to: src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenCallTests.cs:30721 in 208dc2a. [](commit_id = 208dc2a, deletion_comment = False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Nullable value type receiver of a constrained call is unexpectedly copied
4 participants