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

Improved nullable '=='/'!=' analysis #53198

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented May 6, 2021

Closes #41107

Test plan: #51463

Corresponding definite assignment PR: #52425

Specification (for definite assignment, but many aspects have an analogous fit in nullable analysis): https://github.com/dotnet/csharplang/blob/main/proposals/improved-definite-assignment.md#-relational-equality-operator-expressions

@RikkiGibson RikkiGibson changed the base branch from main to features/improved-definite-assignment May 6, 2021 00:00
@@ -3805,6 +3893,37 @@ private void AfterLeftChildHasBeenVisited(BoundExpression leftOperand, Conversio
var inferredResult = InferResultNullability(binary.OperatorKind, method, binary.Type, leftType, rightType);
SetResult(binary, inferredResult, inferredResult.ToTypeWithAnnotations(compilation));

TypeSymbol? getTypeIfContainingType(TypeSymbol baseType, TypeSymbol? derivedType)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just a move.

VisitRvalue(rightOperand);

var rightType = ResultType;
ReinferBinaryOperatorAndSetResult(leftOperand, leftConversion, leftType, rightOperand, rightConversion, rightType, binary);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I extracted the middle "chunk" of this method and left in the parts where we visit the right operand and learn from null tests in the operator.

@RikkiGibson RikkiGibson marked this pull request as ready for review May 11, 2021 00:45
@RikkiGibson RikkiGibson requested a review from a team as a code owner May 11, 2021 00:45
@jcouv jcouv self-assigned this May 11, 2021
@RikkiGibson
Copy link
Contributor Author

@333fred @jcouv Please take a look when you have the chance.

@RikkiGibson
Copy link
Contributor Author

@333fred @jcouv Please review

@jcouv
Copy link
Member

jcouv commented May 17, 2021

Looking

@jcouv
Copy link
Member

jcouv commented May 18, 2021

Short meeting notes (5/18/2021): Aleksey, Fred, Rikki and I thoroughly explored options, including an approach tracking changed slots. We're thinking that the double-visit approach is still likely the best trade-off (usability/complexity/performance), and we'll do some measurement to validate performance behavior.

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.

Done with review pass (iteration 1)

@jcouv jcouv modified the milestones: 17.0, C# 10 May 18, 2021
@RikkiGibson
Copy link
Contributor Author

RikkiGibson commented May 19, 2021

Have briefly investigated the performance with various complex right-hand sides.

Benchmark source
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class ComplexCondAccessEquals
{
    private MetadataReference[] coreRefs = null!;

    [GlobalSetup]
    public void Setup()
    {
        coreRefs = new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) };
    }

    [Params(10, 12, 15, 18)]
    public int Depth { get; set; }

    [Params("enable", "disable")]
    public string NullableContext { get; set; } = null!;

    [Benchmark]
    public void CondAccess_ComplexRightSide()
    {
        var source1 = @"
#nullable " + NullableContext + @"
object? x = null;
C? c = null;
if (
";
        var source2 = @"
)
{
}

class C
{
public bool M(object? obj) => false;
}
";
        var sourceBuilder = new StringBuilder();
        sourceBuilder.Append(source1);
        for (var i = 0; i < Depth; i++)
        {
            sourceBuilder.AppendLine($"    c?.M(x = {i}) == (");
        }
        sourceBuilder.AppendLine("    c!.M(x)");

        sourceBuilder.Append("    ");
        for (var i = 0; i < Depth; i++)
        {
            sourceBuilder.Append(")");
        }

        sourceBuilder.Append(source2);

        var tree = SyntaxFactory.ParseSyntaxTree(sourceBuilder.ToString(), path: "bench.cs");
        var comp = CSharpCompilation.Create(
            "Benchmark",
            new[] { tree },
            coreRefs);
        var diags = comp.GetDiagnostics();
    }
}

The scenario looks like:

#nullable enable
object? x = null;
C? c = null;
if (
    c?.M(x = 0) == (
    c?.M(x = 1) == (
    c?.M(x = 2) == (
    c?.M(x = 3) == (
    c?.M(x = 4) == (
    c?.M(x = 5) == (
    c?.M(x = 6) == (
    c?.M(x = 7) == (
    c?.M(x = 8) == (
    c?.M(x = 9) == (
    c!.M(x)
    ))))))))))
    )
{
}

class C
{
    public bool M(object? obj) => false;
}

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET Core SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
.NET Core 5.0 : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT

Job=.NET Core 5.0 Runtime=.NET Core 5.0

Method Depth NullableContext Mean Error StdDev
CondAccess_ComplexRightSide 10 disable 2.043 ms 0.0787 ms 0.2245 ms
CondAccess_ComplexRightSide 10 enable 21.165 ms 0.3698 ms 0.3459 ms
CondAccess_ComplexRightSide 12 disable 2.149 ms 0.0811 ms 0.2340 ms
CondAccess_ComplexRightSide 12 enable 73.526 ms 1.4463 ms 2.6080 ms
CondAccess_ComplexRightSide 15 disable 2.249 ms 0.1053 ms 0.3039 ms
CondAccess_ComplexRightSide 15 enable 538.476 ms 2.2169 ms 2.0737 ms
CondAccess_ComplexRightSide 18 disable 2.437 ms 0.1794 ms 0.5205 ms
CondAccess_ComplexRightSide 18 enable 4,286.360 ms 19.3094 ms 17.1173 ms

It seems to me like the level of performance we're seeing here is adequate. It certainly starts to get bad as the depth increases, but that's approaching the point where we expect only machine-generated code to reach, and machine-generated code like this should probably disable the nullable context regardless.

/cc @jcouv @AlekseyTs @333fred

@RikkiGibson RikkiGibson requested a review from a team as a code owner May 19, 2021 21:18
@RikkiGibson
Copy link
Contributor Author

@jcouv @333fred I believe I have addressed all the comments. Please take another look when you get the chance.

{
A a2;
B? b2 = null;
var c2 = b2 ?? a2;
Copy link
Member

Choose a reason for hiding this comment

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

Consider making this just an assignment, rather than relying on the rules of ?? to probably do the thing you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ended up replacing this with a test in FlowTests which attempts more directly to get into a situation where CanPropagateStateWhenNotNull could be called with a user-defined conversion with an unexpected parameter count. The method is shared between definite assignment and nullable so I feel like the level of testing introduced in the latest commit is adequate.

@333fred
Copy link
Member

333fred commented May 19, 2021

We have an existing benchmarks project at dotnet/performance. I don't think we should add this.


Refers to: src/Tools/CompilerBenchmarks/CompilerBenchmarks.csproj:1 in 4718b68. [](commit_id = 4718b68, deletion_comment = False)

@333fred
Copy link
Member

333fred commented May 19, 2021

Done review pass (commit 4)

@RikkiGibson
Copy link
Contributor Author

We have an existing benchmarks project at dotnet/performance. I don't think we should add this.

Perhaps the right thing is just to remove the new project and let the test case live on in OverloadResolutionPerfTests.cs.

@RikkiGibson
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@RikkiGibson
Copy link
Contributor Author

@333fred @jcouv I believe I've addressed all your comments. Please let me know if you have any more.

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.

LGTM (commit 7)

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 7)

@RikkiGibson RikkiGibson merged commit d5eed34 into dotnet:features/improved-definite-assignment May 21, 2021
@RikkiGibson RikkiGibson deleted the ida-nullable-eq branch May 21, 2021 00:01
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.

3 participants