Reimplement the logic for remove-unnecessary-cast.#56932
Reimplement the logic for remove-unnecessary-cast.#56932CyrusNajmabadi merged 131 commits intodotnet:mainfrom
Conversation
ce515c4 to
b10a2a3
Compare
88f3510 to
67ca12e
Compare
…passing, 81 failing tests.
…reference type. 244 passing, 73 failing tests.
…xing. 253 passing, 70 failing tests.
… 274 passing, 52 failing tests.
9aaff7c to
74e44bf
Compare
…g, 78 failing tests.
…g, 72 failing tests.
…78 passing, 68 failing tests.
…s not sealed. 381 passing, 65 failing tests.
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Outdated
Show resolved
Hide resolved
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Outdated
Show resolved
Hide resolved
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Outdated
Show resolved
Hide resolved
| if (rewrittenOperation.Parent is not IDelegateCreationOperation rewrittenDelegateCreationOperation) | ||
| return false; | ||
|
|
||
| RoslynDebug.Assert(operation.Type is not null); | ||
| if (!operation.Type.SpecialType.IsIntegralType()) | ||
| return null; | ||
| if (rewrittenDelegateCreationOperation.Type?.TypeKind != TypeKind.Delegate) | ||
| return false; |
There was a problem hiding this comment.
❔ Can these be combined? Not sure if pattern IDelegateCreationOperation { Type.TypeKind: TypeKind.Delegate } accounts for the possibility that Type is null.
There was a problem hiding this comment.
accounts for the possibility that Type is null.
Yup. It does. That's inherent in dotted property patterns.
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Outdated
Show resolved
Hide resolved
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Show resolved
Hide resolved
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Show resolved
Hide resolved
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Outdated
Show resolved
Hide resolved
...es/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/Simplifiers/CastSimplifier.cs
Show resolved
Hide resolved
| if (!conversion.IsIdentity) | ||
| return false; | ||
|
|
||
| var castType = semanticModel.GetTypeInfo(castNode, cancellationToken).Type; | ||
| var castedExpressionType = semanticModel.GetTypeInfo(castedExpressionNode, cancellationToken).Type; | ||
|
|
||
| // Floating point casts can have subtle runtime behavior, even between the same fp types. For example, a | ||
| // cast from float-to-float can still change behavior because it may take a higher precision computation and | ||
| // truncate it to 32bits. | ||
| // | ||
| // Because of this we keep floating point conversions unless we can prove that it's safe. The only safe | ||
| // times are when we're loading or storing into a location we know has the same size as the cast size | ||
| // (i.e. reading/writing into a field). |
There was a problem hiding this comment.
Should boxing be included?
float value = 0.0f;
object boxed = (float)value;There was a problem hiding this comment.
hrmm... this is a good case. @tannergooding ignoring the value 0.0f, is the above guaranteed to always produce the exact same results as:
float value = ...;
object boxed = value;I'm guess so as boxing the value means we'll have to narrow to 32bits, so the explicit cast to force a potentially widened float to 32bits happens either way.
(and i presume the same happens for double as well)?
There was a problem hiding this comment.
Since value is already a float, I believe there is no need to insert an additional cast here.
-- The runtime spec dictates that the type on the evaluation stack is F, not float32 and not float64. This means that:
- loading a
float32orfloat64onto the stack "converts" it to typeF, in the strictest sense. - operations, such as
x + y(where x and y are bothfloating-pointtypes) results in a value of typeF. - if you don't explicitly downcast and consume the value, you "can" see differing behavior on some platforms, which is why C# says the cast in
(float)(x + y) + zis required.- In practice, on modern .NET, this largely only matters for certain scenarios on 32-bit; 64-bit exclusively uses the SSE/SSE2 instructions and directly operate on
float32orfloat64 - However, some runtime is free to implement these as say 80-bit operations; such as was done on the x87 FPU stack
- In practice, on modern .NET, this largely only matters for certain scenarios on 32-bit; 64-bit exclusively uses the SSE/SSE2 instructions and directly operate on
However, box doesn't interpret the value based on the evaluation stack; instead it takes an explicit typeTok that specifies how the value should be interpreted and C# correctly tracks x and x + y and x + y + z as float, so the type token emitted here is System.Single
…mplification/Simplifiers/CastSimplifier.cs Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>
…mplification/Simplifiers/CastSimplifier.cs Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>
…mabadi/roslyn into removeUnnecessaryCastV2
…mplification/Simplifiers/CastSimplifier.cs Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>
…mabadi/roslyn into removeUnnecessaryCastV2
Fixes #52524
Fixes #53698
Fixes #55621
Fixes #56207
Should be reviewed one commit at a time.
This PR replaces the entire existing implementation of 'RemoveUnnecessaryCast' with a new one built on top of simpler logic based on first principles around the semantic operations the compiler forms.
As part of this much of the test cases were beefed up. This is the majority of the PR size here. In terms of hte simplifier itself, we went down from a whopping 3500 lines to just 800 lines. Around 25% the size/complexity of what it used to be.
The most general rule that this follows is the question: does the expression without a cast get converted to the same type that the original expression converted to? If so, this is a candidate for removal. In almost all cases, this is safe too. However, there are some unfortunately complex corner cases in the language that need to be handled (including cases where the change is legal, but the compiler issues warnings that code may be confusing).
The changes also follow the rule that the emitted code should effectively be the same as without the cast. However, following that rule regresses a a few cases we used to support. There are no known false positives. However, there are a few corner cases where things we used to support are no longer supported. These cases violated the above rule that the emitted code would be the same without the cast (though at runtime this would not be observable). #56938 tracks the cases we don't support anymore to bring back in the future.
On another positive note, this change fixes several outstanding reported bugs that would have been difficult to fix before. It also fixes several bits of broken behavior observed in many features where casts were either removed inappropriately, or not removed where they should have been.