Skip to content

Optimize is null #18657

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 30, 2017
Merged

Optimize is null #18657

merged 5 commits into from
May 30, 2017

Conversation

TyOverby
Copy link
Contributor

@TyOverby TyOverby commented Apr 12, 2017

closes #13247

optimizes obj is null patterns.


Customer scenario

When the customer gets faster obj is null patterns, resulting in prettier code where before they had to write (object) obj == null or obj.HasValue for similar performance.

Bugs this fixes:

#13247

Workarounds, if any

The old behavior of explicitly writing out method calls still works.

Risk

Low

Performance impact

Perf in the compiler is unaffected but programs compiled with this change will potentially get faster.

Is this a regression from a previous update?

No

Root cause analysis:

This optimization was not added earlier due to time constraints.

How was the bug found?

This was a known "want to have" feature from earlier in the development cycle.

@TyOverby
Copy link
Contributor Author

@gafter

@TyOverby
Copy link
Contributor Author

@dotnet/roslyn-compiler

@TyOverby
Copy link
Contributor Author

I'll add a few "switch/case" pattern unit tests tomorrow.

@TyOverby
Copy link
Contributor Author

Also, this is likely to fail existing tests since I never checked to see that they would work.

Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

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

Really hope we can take this change. But unsure if there is some subtle reason why we have to emit code using Equals instead of direct null comparison. @gafter will be able to clear up if this is a legal optimization or not.

_factory.Convert(_factory.SpecialType(SpecialType.System_Object), boundConstant),
_factory.Convert(_factory.SpecialType(SpecialType.System_Object), input)
);
var systemObject = _factory.SpecialType(SpecialType.System_Object);
Copy link
Member

Choose a reason for hiding this comment

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

Meta feedback: it's really unclear where in the compiler we can, or can't, depend on special types being non-null in the compiler. Depending on it in the local rewriter seems sane, but I've been surprised in the past. Can we take a follow up item to get good guidance on this point?

Copy link
Member

Choose a reason for hiding this comment

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

The pattern-matching is operator is very restricted in the kinds of conversions it can apply to the left operand. There is no target type for the left operand. It is essentially converted to object (by spec) only to bind to the second operand of static bool object.Equals(object, object). Thus only a boxing conversion could change its representation, and a boxing conversion never produces null (except from a nullable type).

Copy link
Member

@gafter gafter Apr 13, 2017

Choose a reason for hiding this comment

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

Looking again, perhaps because the pattern is lowered that the constant is always a literal. If that is the case, you shouldn't have to test for it.

_factory.Convert(_factory.SpecialType(SpecialType.System_Object), input)
);
var systemObject = _factory.SpecialType(SpecialType.System_Object);
if (boundConstant is BoundLiteral bl && bl.ConstantValue.IsNull)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure we can do this in every single case the RHS is null. One case that cames to mind is when the LHS is an unconstrained type parameter. Those can be compared to null using ==. I'm unsure if that's allowed with is though.

Copy link
Member

Choose a reason for hiding this comment

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

Two problems with this code. First, you should not assume that a literal has a non-null constant value. Second, you shouldn't require that the form of the constant value is a literal. I suggest if (boundConstant.ConstantValue?.IsNull == true)

Copy link
Member

Choose a reason for hiding this comment

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

Looking again, perhaps because the pattern is lowered that the constant is always a literal. If that is the case, you shouldn't have to test for it.

@gafter
Copy link
Member

gafter commented Apr 13, 2017

Fixes #13247.

_factory.Convert(_factory.SpecialType(SpecialType.System_Object), boundConstant),
_factory.Convert(_factory.SpecialType(SpecialType.System_Object), input)
);
var systemObject = _factory.SpecialType(SpecialType.System_Object);
Copy link
Member

Choose a reason for hiding this comment

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

The pattern-matching is operator is very restricted in the kinds of conversions it can apply to the left operand. There is no target type for the left operand. It is essentially converted to object (by spec) only to bind to the second operand of static bool object.Equals(object, object). Thus only a boxing conversion could change its representation, and a boxing conversion never produces null (except from a nullable type).

_factory.Convert(_factory.SpecialType(SpecialType.System_Object), input)
);
var systemObject = _factory.SpecialType(SpecialType.System_Object);
if (boundConstant is BoundLiteral bl && bl.ConstantValue.IsNull)
Copy link
Member

Choose a reason for hiding this comment

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

Two problems with this code. First, you should not assume that a literal has a non-null constant value. Second, you shouldn't require that the form of the constant value is a literal. I suggest if (boundConstant.ConstantValue?.IsNull == true)

return _factory.Binary(
kind: BinaryOperatorKind.Equal,
type: systemBoolean,
left: _factory.Convert(systemObject, input),
Copy link
Member

Choose a reason for hiding this comment

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

Probably not the best way to test for null when the value is of a value type or a nullable value type. In that case this code still boxes unnecessarily. You may be able to invoke the helper RewriteNullableNullEquality to get the desired effect. If you elect not to fix that in this PR, please note in #18657 that the case still remains unoptimized.

Copy link
Member

Choose a reason for hiding this comment

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

I think this should call MakeBinaryOperator, which should handle this kind of optimization and deal with lifted compares.
Just be careful with what is passed as operatorKind and method.

Copy link
Member

Choose a reason for hiding this comment

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

I think you can have here:
BinaryOperatorKind.NullableNullEqual
BinaryOperatorKind.ObjectEquals
BinaryOperatorKind.OperatorMethodEquals (with Object.Equals as method)

if (boundConstant is BoundLiteral bl && bl.ConstantValue.IsNull)
{
var systemBoolean = _factory.SpecialType(SpecialType.System_Boolean);
return _factory.Binary(
Copy link
Member

Choose a reason for hiding this comment

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

_factory.ObjectEqual(_factory.Convert(systemObject, input), right)

@gafter
Copy link
Member

gafter commented Apr 24, 2017

@TyOverby What is your status on this PR?

@TyOverby
Copy link
Contributor Author

@gafter it's on my todo list, but not high enough for anything progress to be made while async-main hasn't landed.

@gafter
Copy link
Member

gafter commented Apr 25, 2017

@TyOverby Makes sense to me, thanks.

@jaredpar jaredpar added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 1, 2017
@TyOverby TyOverby force-pushed the optimize-is-null branch 2 times, most recently from 39a848a to 5e02454 Compare May 22, 2017 21:32
@TyOverby TyOverby force-pushed the optimize-is-null branch from 5e02454 to 090844e Compare May 22, 2017 21:32
@TyOverby TyOverby removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 22, 2017
@TyOverby
Copy link
Contributor Author

@gafter: I made the changes that you recommended

@dotnet/roslyn-compiler Out for review.

IL_0010: nop
IL_0011: ret
}");
}
Copy link
Member

Choose a reason for hiding this comment

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

Blank line after closing }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -13,6 +13,50 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
public class CodeGenOperatorTests : CSharpTestBase
{

[Fact]
public void TestIsNullPattern()
Copy link
Member

Choose a reason for hiding this comment

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

Other tests that we should add:

  • Value of unconstrained type parameter testing is null
  • Value of type parameter constrained to class against is null
  • A constant, which is null, testing against is null

Copy link
Member

Choose a reason for hiding this comment

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

Also add couple tests were "is" is used as a condition - like:

if (c is null) . . .

In reply to: 118344562 [](ancestors = 118344562)

loweredRight: boundConstant,
returnType: systemBoolean

);
Copy link
Member

Choose a reason for hiding this comment

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

extra space.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@TyOverby
Copy link
Contributor Author

@jaredpar @VSadov: I've added the recommended tests.

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

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

Pending @gafter sign off.

@TyOverby
Copy link
Contributor Author

@dotnet-bot test windows_release_unit64_prtest please

@TyOverby
Copy link
Contributor Author

@gafter Any more recommendations?

_factory.Convert(_factory.SpecialType(SpecialType.System_Object), input)
);
var systemObject = _factory.SpecialType(SpecialType.System_Object);
if (boundConstant is BoundLiteral bl && bl.ConstantValue?.IsNull == true)
Copy link
Member

Choose a reason for hiding this comment

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

The BoundLiteral test is incorrect. This should only depend on whether the constant value is null (whether or not it is a literal). For example, the right-hand-side could be a named constant. And we should have a test for that scenario (with type string).

@TyOverby
Copy link
Contributor Author

@gafter Done. Also added a test like you suggested.

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

LGTM

@TyOverby
Copy link
Contributor Author

@gafter It occurs to me that this code doesn't handle Nullable<T>. I'm going to try to add this functionality and add tests.

@TyOverby
Copy link
Contributor Author

TyOverby commented May 30, 2017

@gafter @VSadov: I've added similar optimizations for objects that are of type Nullable<T>. Please re-review this commit at your convenience.

@TyOverby
Copy link
Contributor Author

reverted the most recent change due to signoff issues. I'll make a new PR for that functionality.

@TyOverby
Copy link
Contributor Author

Ping @MeiChin-Tsai for approval.

@MeiChin-Tsai
Copy link

Sorry to miss this earlier. Since the risk is low, we will take it. This is really consider "a small feature", correct?

@TyOverby
Copy link
Contributor Author

@MeiChin-Tsai: yeah, risk is low. I wouldn't even consider this a feature, more like a "perf fix"

@MeiChin-Tsai
Copy link

thx for clarification, @TyOverby. Approved.

@TyOverby TyOverby merged commit cb9ee8c into dotnet:master May 30, 2017
AdamSpeight2008 pushed a commit to AdamSpeight2008/roslyn-AdamSpeight2008 that referenced this pull request Jun 4, 2017
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.

Suboptimal code for e is null
7 participants