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

Fix long tuples in PatternLocalRewriter #47893

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -488,30 +488,47 @@ private BoundDecisionDag RewriteTupleInput(
bool canShareInputs,
out BoundExpression savedInputExpression)
{
int count = loweredInput.Arguments.Length;
var tupleElements = loweredInput.Type.TupleElements;

// first evaluate the inputs (in order) into temps
var originalInput = BoundDagTemp.ForOriginalInput(loweredInput.Syntax, loweredInput.Type);
var newArguments = ArrayBuilder<BoundExpression>.GetInstance(loweredInput.Arguments.Length);
for (int i = 0; i < count; i++)
for (int i = 0; i < tupleElements.Length; i++)
{
var field = loweredInput.Type.TupleElements[i].CorrespondingTupleField;
var field = tupleElements[i].CorrespondingTupleField;
Debug.Assert(field != null);
var expr = loweredInput.Arguments[i];
var expr = getExpression(i, loweredInput);
Copy link
Member Author

Choose a reason for hiding this comment

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

Here, we can't just access Arguments[i] for two reasons:

  1. If i == 8, the result expression will be a tuple, this is not intended.
  2. If i > 8, this will throw an exception due to the other fix I made.

var fieldFetchEvaluation = new BoundDagFieldEvaluation(expr.Syntax, field, originalInput);
var temp = new BoundDagTemp(expr.Syntax, expr.Type, fieldFetchEvaluation);
storeToTemp(temp, expr);
newArguments.Add(_tempAllocator.GetTemp(temp));
}

var rewrittenDag = decisionDag.Rewrite(makeReplacement);
savedInputExpression = loweredInput.Update(
loweredInput.Constructor, arguments: newArguments.ToImmutableAndFree(), loweredInput.ArgumentNamesOpt, loweredInput.ArgumentRefKindsOpt,
loweredInput.Constructor, arguments: loweredInput.Arguments, loweredInput.ArgumentNamesOpt, loweredInput.ArgumentRefKindsOpt,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused by the change to get rid of the newArguments array. I thought that would have undesired effects, particularly related to reuse of user temps or side effects.

Copy link
Member Author

Choose a reason for hiding this comment

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

@RikkiGibson I was expecting to see some test failures related to this too, but I don't know what scenarios exactly that should fail. Do you know any scenarios that should fail to add tests for them?

Copy link
Contributor

Choose a reason for hiding this comment

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

I tried the following scenario to induce a failure but it continued to have the expected side-effects in your branch:

        [Fact, WorkItem(47878, "https://github.com/dotnet/roslyn/issues/47878")]
        public void VerifyIL_8ElementsTuple_SideEffects()
        {
            var text = @"
using System;
public class C
{
    public static void Main()
    {
        int x = (M(1), M(2), M(3), M(4), M(5), M(6), M(7), M(8)) switch
        {
            (1, 2, 3, 4, 5, 6, 7, 7) => 1,
            (1, 2, 3, 4, 5, 6, 7, 8) => 1,
            _ => -1,
        };

        Console.WriteLine(x);
    }

    static int M(int x)
    {
        Console.Write(x);
        return x;
    }
}
";
            CompileAndVerify(text, expectedOutput: "123456781");
        }

I think the next thing I would try would be to make many of the tuple components local variables and see if the lowering code reuses the local variables properly instead of introducing new ones.

I'll try to debug in more detail soon.

Copy link
Contributor

Choose a reason for hiding this comment

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

Here's the scenario where we have unexpected side effects:

        [Fact, WorkItem(47878, "https://github.com/dotnet/roslyn/issues/47878")]
        public void VerifyIL_8ElementsTuple_SideEffects_02()
        {
            var text = @"
using System;
using System.Runtime.CompilerServices;
public class C
{
    public static void Main()
    {
        try
        {
            int x = (M(1), M(2), M(3), M(4), M(5), M(6), M(7), M(8)) switch
            {
                (1, 2, 3, 4, 5, 6, 7, 7) => 1,
            };

            Console.WriteLine(x);
        }
        catch (SwitchExpressionException ex)
        {
            Console.Write(""💥"");
        }
    }

    static int M(int x)
    {
        Console.Write(x);
        return x;
    }
}
namespace System.Runtime.CompilerServices
{
    public class SwitchExpressionException : InvalidOperationException
    {
        public SwitchExpressionException() {}
        public SwitchExpressionException(object unmatchedValue) => UnmatchedValue = unmatchedValue;
        public object UnmatchedValue { get; }
    }
}
";
            CompileAndVerify(text, expectedOutput: "12345678💥");
        }

With your change, the actual output is 1234567812345678💥. Let's revert that part of the change.

Copy link
Member Author

Choose a reason for hiding this comment

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

@RikkiGibson That will get us to the assertion failure too. I'm still not sure what is the appropriate fix. I'll add this test and give it a try again, but not sure if I would be able to get the correct fix.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the problem is that when we "unwrap" the tuple elements as is done in this PR, the length of the arguments no longer matches the length of the arguments ot the top-level object creation node. i.e. loweredInput.Arguments.Length should never be more than 8, and tuples of size 8 or larger must nest their arguments in additional object creation nodes.

I can try and look a bit to see how to do this--the fix isn't coming to mind instantly. But to get the behavior of the scenario fixed in 16.8 we may want to take #48028 and then optimize it later.

loweredInput.Expanded, loweredInput.ArgsToParamsOpt, loweredInput.ConstantValueOpt,
loweredInput.InitializerExpressionOpt, loweredInput.BinderOpt, loweredInput.Type);

return rewrittenDag;

static BoundExpression getExpression(int index, BoundObjectCreationExpression expression)
jcouv marked this conversation as resolved.
Show resolved Hide resolved
{
while (index >= NamedTypeSymbol.ValueTupleRestIndex)
{
if (expression.Arguments[NamedTypeSymbol.ValueTupleRestIndex] is BoundObjectCreationExpression restExpression)
{
expression = restExpression;
index -= NamedTypeSymbol.ValueTupleRestIndex;
}
else
{
// ValueTupleRestIndex should always be another tuple. Hence, it should always have BoundObjectCreationExpression.
throw ExceptionUtilities.Unreachable;
}
}

return expression.Arguments[index];
}

void storeToTemp(BoundDagTemp temp, BoundExpression expr)
{
if (canShareInputs && (expr.Kind == BoundKind.Parameter || expr.Kind == BoundKind.Local) && _tempAllocator.TrySetTemp(temp, expr))
Expand Down
228 changes: 228 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/SwitchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2949,6 +2949,234 @@ public static void Main()
Diagnostic(ErrorCode.WRN_UnreachableCode, "break").WithLocation(14, 17));
}

[Fact, WorkItem(47878, "https://github.com/dotnet/roslyn/issues/47878")]
public void Bug47878()
{
var text = @"
using System;
public class C
{
public static void Main()
{
int x1 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) switch
{
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) => 1,
_ => -1,
};
int x2 = (1, 2, 3, 4, 5, 6, 7, 8) switch
{
(1, 2, 3, 4, 5, 6, 7, 8) => 1,
_ => -1,
};
int x3 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) switch
{
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) => 1,
_ => -1,
};
int x4 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) switch
{
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) => 1,
_ => -1,
};
Console.WriteLine($""{x1} {x2} {x3} {x4}"");
}
}
";
var comp = CompileAndVerify(text, expectedOutput: "1 1 1 1");
comp.VerifyDiagnostics();
}

[Fact, WorkItem(47878, "https://github.com/dotnet/roslyn/issues/47878")]
public void VerifyIL_8ElementsTuple()
Copy link
Member Author

Choose a reason for hiding this comment

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

Is the IL for this test and the next test correct?

{
var text = @"
using System;
public class C
{
public static void Main()
{
int x = (1, 2, 3, 4, 5, 6, 7, 8) switch
{
(1, 2, 3, 4, 5, 6, 7, 8) => 1,
_ => -1,
};

Console.WriteLine(x);
}
}
";
CompileAndVerify(text).VerifyIL("C.Main", @"
{
// Code size 67 (0x43)
.maxstack 2
.locals init (int V_0,
int V_1,
int V_2,
int V_3,
int V_4,
int V_5,
int V_6,
int V_7)
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: stloc.1
IL_0003: ldc.i4.3
IL_0004: stloc.2
IL_0005: ldc.i4.4
IL_0006: stloc.3
IL_0007: ldc.i4.5
IL_0008: stloc.s V_4
IL_000a: ldc.i4.6
IL_000b: stloc.s V_5
IL_000d: ldc.i4.7
IL_000e: stloc.s V_6
IL_0010: ldc.i4.8
IL_0011: stloc.s V_7
IL_0013: ldc.i4.1
IL_0014: bne.un.s IL_003a
IL_0016: ldloc.1
IL_0017: ldc.i4.2
IL_0018: bne.un.s IL_003a
IL_001a: ldloc.2
IL_001b: ldc.i4.3
IL_001c: bne.un.s IL_003a
IL_001e: ldloc.3
IL_001f: ldc.i4.4
IL_0020: bne.un.s IL_003a
IL_0022: ldloc.s V_4
IL_0024: ldc.i4.5
IL_0025: bne.un.s IL_003a
IL_0027: ldloc.s V_5
IL_0029: ldc.i4.6
IL_002a: bne.un.s IL_003a
IL_002c: ldloc.s V_6
IL_002e: ldc.i4.7
IL_002f: bne.un.s IL_003a
IL_0031: ldloc.s V_7
IL_0033: ldc.i4.8
IL_0034: bne.un.s IL_003a
IL_0036: ldc.i4.1
IL_0037: stloc.0
IL_0038: br.s IL_003c
IL_003a: ldc.i4.m1
IL_003b: stloc.0
IL_003c: ldloc.0
IL_003d: call ""void System.Console.WriteLine(int)""
IL_0042: ret
}
");
}

[Fact, WorkItem(47878, "https://github.com/dotnet/roslyn/issues/47878")]
public void VerifyIL_GreatherThan8ElementsTuple()
{
var text = @"
using System;
public class C
{
public static void Main()
{
int x = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) switch
{
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) => 1,
_ => -1,
};

Console.WriteLine(x);
}
}
";
CompileAndVerify(text).VerifyIL("C.Main", @"
{
// Code size 117 (0x75)
.maxstack 2
.locals init (int V_0,
int V_1,
int V_2,
int V_3,
int V_4,
int V_5,
int V_6,
int V_7,
int V_8,
int V_9,
int V_10,
int V_11,
int V_12)
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: stloc.1
IL_0003: ldc.i4.3
IL_0004: stloc.2
IL_0005: ldc.i4.4
IL_0006: stloc.3
IL_0007: ldc.i4.5
IL_0008: stloc.s V_4
IL_000a: ldc.i4.6
IL_000b: stloc.s V_5
IL_000d: ldc.i4.7
IL_000e: stloc.s V_6
IL_0010: ldc.i4.8
IL_0011: stloc.s V_7
IL_0013: ldc.i4.s 9
IL_0015: stloc.s V_8
IL_0017: ldc.i4.s 10
IL_0019: stloc.s V_9
IL_001b: ldc.i4.s 11
IL_001d: stloc.s V_10
IL_001f: ldc.i4.s 12
IL_0021: stloc.s V_11
IL_0023: ldc.i4.s 13
IL_0025: stloc.s V_12
IL_0027: ldc.i4.1
IL_0028: bne.un.s IL_006c
IL_002a: ldloc.1
IL_002b: ldc.i4.2
IL_002c: bne.un.s IL_006c
IL_002e: ldloc.2
IL_002f: ldc.i4.3
IL_0030: bne.un.s IL_006c
IL_0032: ldloc.3
IL_0033: ldc.i4.4
IL_0034: bne.un.s IL_006c
IL_0036: ldloc.s V_4
IL_0038: ldc.i4.5
IL_0039: bne.un.s IL_006c
IL_003b: ldloc.s V_5
IL_003d: ldc.i4.6
IL_003e: bne.un.s IL_006c
IL_0040: ldloc.s V_6
IL_0042: ldc.i4.7
IL_0043: bne.un.s IL_006c
IL_0045: ldloc.s V_7
IL_0047: ldc.i4.8
IL_0048: bne.un.s IL_006c
IL_004a: ldloc.s V_8
IL_004c: ldc.i4.s 9
IL_004e: bne.un.s IL_006c
IL_0050: ldloc.s V_9
IL_0052: ldc.i4.s 10
IL_0054: bne.un.s IL_006c
IL_0056: ldloc.s V_10
IL_0058: ldc.i4.s 11
IL_005a: bne.un.s IL_006c
IL_005c: ldloc.s V_11
IL_005e: ldc.i4.s 12
IL_0060: bne.un.s IL_006c
IL_0062: ldloc.s V_12
IL_0064: ldc.i4.s 13
IL_0066: bne.un.s IL_006c
IL_0068: ldc.i4.1
IL_0069: stloc.0
IL_006a: br.s IL_006e
IL_006c: ldc.i4.m1
IL_006d: stloc.0
IL_006e: ldloc.0
IL_006f: call ""void System.Console.WriteLine(int)""
IL_0074: ret
}
");
}
#endregion
}
}