diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index b34e3fa65ab71..a1ed8fb58df5d 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -202,7 +202,7 @@ private void EmitExpressionCore(BoundExpression expression, bool used) break; case BoundKind.IsOperator: - EmitIsExpression((BoundIsOperator)expression, used); + EmitIsExpression((BoundIsOperator)expression, used, omitBooleanConversion: false); break; case BoundKind.AsOperator: @@ -3158,7 +3158,7 @@ private void EmitPopIfUnused(bool used) } } - private void EmitIsExpression(BoundIsOperator isOp, bool used) + private void EmitIsExpression(BoundIsOperator isOp, bool used, bool omitBooleanConversion) { var operand = isOp.Operand; EmitExpression(operand, used); @@ -3172,8 +3172,12 @@ private void EmitIsExpression(BoundIsOperator isOp, bool used) } _builder.EmitOpCode(ILOpCode.Isinst); EmitSymbolToken(isOp.TargetType.Type, isOp.Syntax); - _builder.EmitOpCode(ILOpCode.Ldnull); - _builder.EmitOpCode(ILOpCode.Cgt_un); + + if (!omitBooleanConversion) + { + _builder.EmitOpCode(ILOpCode.Ldnull); + _builder.EmitOpCode(ILOpCode.Cgt_un); + } } } @@ -3483,6 +3487,22 @@ private void EmitConditionalOperator(BoundConditionalOperator expr, bool used) { Debug.Assert(expr.ConstantValueOpt == null, "Constant value should have been emitted directly"); + // Generate branchless IL for (b ? 1 : 0). + if (used && _ilEmitStyle != ILEmitStyle.Debug && + (IsNumeric(expr.Type) || expr.Type.PrimitiveTypeCode == Cci.PrimitiveTypeCode.Boolean) && + hasIntegralValueZeroOrOne(expr.Consequence, out var isConsequenceOne) && + hasIntegralValueZeroOrOne(expr.Alternative, out var isAlternativeOne) && + isConsequenceOne != isAlternativeOne && + TryEmitComparison(expr.Condition, sense: isConsequenceOne)) + { + var toType = expr.Type.PrimitiveTypeCode; + if (toType != Cci.PrimitiveTypeCode.Boolean) + { + _builder.EmitNumericConversion(Cci.PrimitiveTypeCode.Int32, toType, @checked: false); + } + return; + } + object consequenceLabel = new object(); object doneLabel = new object(); @@ -3547,6 +3567,33 @@ private void EmitConditionalOperator(BoundConditionalOperator expr, bool used) } _builder.MarkLabel(doneLabel); + + static bool hasIntegralValueZeroOrOne(BoundExpression expr, out bool isOne) + { + if (expr.ConstantValueOpt is { } constantValue) + { + if (constantValue is { IsIntegral: true, UInt64Value: (1 or 0) and var i }) + { + isOne = i == 1; + return true; + } + + if (constantValue is { IsBoolean: true, BooleanValue: var b }) + { + isOne = b; + return true; + } + + if (constantValue is { IsChar: true, CharValue: ((char)1 or (char)0) and var c }) + { + isOne = c == 1; + return true; + } + } + + isOne = false; + return false; + } } /// diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitOperators.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitOperators.cs index f9f22aa26413d..80ca057d83076 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitOperators.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitOperators.cs @@ -471,13 +471,7 @@ private void EmitBinaryCondOperatorHelper(ILOpCode opCode, BoundExpression left, // this will leave a value on the stack which conforms to sense, ie:(condition == sense) private void EmitCondExpr(BoundExpression condition, bool sense) { - while (condition.Kind == BoundKind.UnaryOperator) - { - var unOp = (BoundUnaryOperator)condition; - Debug.Assert(unOp.OperatorKind == UnaryOperatorKind.BoolLogicalNegation); - condition = unOp.Operand; - sense = !sense; - } + RemoveNegation(ref condition, ref sense); Debug.Assert(condition.Type.SpecialType == SpecialType.System_Boolean); @@ -506,6 +500,64 @@ private void EmitCondExpr(BoundExpression condition, bool sense) return; } + /// + /// Emits boolean expression without branching if possible (i.e., no logical operators, only comparisons). + /// Leaves a boolean (int32, 0 or 1) value on the stack which conforms to sense, i.e., condition == sense. + /// + private bool TryEmitComparison(BoundExpression condition, bool sense) + { + RemoveNegation(ref condition, ref sense); + + Debug.Assert(condition.Type.SpecialType == SpecialType.System_Boolean); + + if (condition.ConstantValueOpt is { } constantValue) + { + Debug.Assert(constantValue.Discriminator == ConstantValueTypeDiscriminator.Boolean); + _builder.EmitBoolConstant(constantValue.BooleanValue == sense); + return true; + } + + if (condition is BoundBinaryOperator binOp) + { + // Intentionally don't optimize logical operators, they need branches to short-circuit. + if (binOp.OperatorKind.IsComparison()) + { + EmitBinaryCondOperator(binOp, sense: sense); + return true; + } + } + else if (condition is BoundIsOperator isOp) + { + EmitIsExpression(isOp, used: true, omitBooleanConversion: true); + + // Convert to 1 or 0. + _builder.EmitOpCode(ILOpCode.Ldnull); + _builder.EmitOpCode(sense ? ILOpCode.Cgt_un : ILOpCode.Ceq); + return true; + } + else + { + EmitExpression(condition, used: true); + + // Convert to 1 or 0 (although `condition` is of type `bool`, it can contain any integer). + _builder.EmitOpCode(ILOpCode.Ldc_i4_0); + _builder.EmitOpCode(sense ? ILOpCode.Cgt_un : ILOpCode.Ceq); + return true; + } + + return false; + } + + private static void RemoveNegation(ref BoundExpression condition, ref bool sense) + { + while (condition is BoundUnaryOperator unOp) + { + Debug.Assert(unOp.OperatorKind == UnaryOperatorKind.BoolLogicalNegation); + condition = unOp.Operand; + sense = !sense; + } + } + private void EmitUnaryCheckedOperatorExpression(BoundUnaryOperator expression, bool used) { Debug.Assert(expression.OperatorKind.Operator() == UnaryOperatorKind.UnaryMinus); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenConditionalOperatorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenConditionalOperatorTests.cs index 074ccda8e2bd0..d3fcb31920f89 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenConditionalOperatorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenConditionalOperatorTests.cs @@ -13,6 +13,588 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { public class CodeGenConditionalOperatorTests : CSharpTestBase { + [Fact, WorkItem(61483, "https://github.com/dotnet/roslyn/issues/61483")] + public void Branchless_Compare() + { + var source = """ + using static System.Console; + WriteLine(C.Compare(1, 2)); + WriteLine(C.Compare(3, 3)); + WriteLine(C.Compare(5, 4)); + + class C + { + public static int Compare(int x, int y) + { + int tmp1 = (x > y) ? 1 : 0; + int tmp2 = (x < y) ? 1 : 0; + return tmp1 - tmp2; + } + } + """; + var expectedOutput = """ + -1 + 0 + 1 + """; + + var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expectedOutput); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("C.Compare", """ + { + // Code size 12 (0xc) + .maxstack 3 + .locals init (int V_0) //tmp2 + // sequence point: int tmp1 = (x > y) ? 1 : 0; + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: cgt + // sequence point: int tmp2 = (x < y) ? 1 : 0; + IL_0004: ldarg.0 + IL_0005: ldarg.1 + IL_0006: clt + IL_0008: stloc.0 + // sequence point: return tmp1 - tmp2; + IL_0009: ldloc.0 + IL_000a: sub + IL_000b: ret + } + """); + + verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe.WithDebugPlusMode(true), expectedOutput: expectedOutput); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("C.Compare", """ + { + // Code size 14 (0xe) + .maxstack 2 + .locals init (int V_0, //tmp1 + int V_1) //tmp2 + // sequence point: int tmp1 = (x > y) ? 1 : 0; + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: cgt + IL_0004: stloc.0 + // sequence point: int tmp2 = (x < y) ? 1 : 0; + IL_0005: ldarg.0 + IL_0006: ldarg.1 + IL_0007: clt + IL_0009: stloc.1 + // sequence point: return tmp1 - tmp2; + IL_000a: ldloc.0 + IL_000b: ldloc.1 + IL_000c: sub + IL_000d: ret + } + """); + + verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: expectedOutput); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("C.Compare", """ + { + // Code size 27 (0x1b) + .maxstack 2 + .locals init (int V_0, //tmp1 + int V_1, //tmp2 + int V_2) + // sequence point: { + IL_0000: nop + // sequence point: int tmp1 = (x > y) ? 1 : 0; + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: bgt.s IL_0008 + IL_0005: ldc.i4.0 + IL_0006: br.s IL_0009 + IL_0008: ldc.i4.1 + IL_0009: stloc.0 + // sequence point: int tmp2 = (x < y) ? 1 : 0; + IL_000a: ldarg.0 + IL_000b: ldarg.1 + IL_000c: blt.s IL_0011 + IL_000e: ldc.i4.0 + IL_000f: br.s IL_0012 + IL_0011: ldc.i4.1 + IL_0012: stloc.1 + // sequence point: return tmp1 - tmp2; + IL_0013: ldloc.0 + IL_0014: ldloc.1 + IL_0015: sub + IL_0016: stloc.2 + IL_0017: br.s IL_0019 + // sequence point: } + IL_0019: ldloc.2 + IL_001a: ret + } + """); + } + + [Fact, WorkItem(61483, "https://github.com/dotnet/roslyn/issues/61483")] + public void Branchless_Operations() + { + var source = """ + using static System.Console; + + class C + { + static void Main() => M(1, 0, null!, true); + static void M(int x, int y, object a, bool b) + { + Write(x == y ? 1 : 0); + Write(x == y ? 0 : 1); + Write(x < y ? 1 : 0); + Write(x < y ? 0 : 1); + Write(x > y ? 1 : 0); + Write(x > y ? 0 : 1); + Write(a is string ? 0 : 1); + Write(a is not string ? 0 : 1); + Write(b ? 0 : 1); + Write(!b ? 0 : 1); + Write(x <= y ? true : false); + Write(x <= y ? false : true); + Write(x != y ? (byte)1 : (byte)0); + Write(x != y ? 1 : (sbyte)0); + Write(x != y ? (short)1 : (short)0); + Write(x != y ? (ushort)1 : 0); + Write(x != y ? 1U : 0); + Write(x != y ? 1L : 0); + Write(x != y ? 1UL : 0); + Write(x != y ? (nint)1 : 0); + Write(x != y ? 1 : (nuint)0); + Write(x < y ? (char)0 : (char)1); + Write(x < y ? '\x1' : '\x0'); + Write(true ? 1 : 0); + Write(false ? 0 : 1); + const bool B = true; + Write(B ? 1 : 0); + } + } + """; + var verifier = CompileAndVerify(source, + options: TestOptions.ReleaseExe, + expectedOutput: "0101101001FalseTrue111111111" + (char)1 + (char)0 + "111"); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("C.M", """ + { + // Code size 288 (0x120) + .maxstack 2 + // sequence point: Write(x == y ? 1 : 0); + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ceq + IL_0004: call "void System.Console.Write(int)" + // sequence point: Write(x == y ? 0 : 1); + IL_0009: ldarg.0 + IL_000a: ldarg.1 + IL_000b: ceq + IL_000d: ldc.i4.0 + IL_000e: ceq + IL_0010: call "void System.Console.Write(int)" + // sequence point: Write(x < y ? 1 : 0); + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: clt + IL_0019: call "void System.Console.Write(int)" + // sequence point: Write(x < y ? 0 : 1); + IL_001e: ldarg.0 + IL_001f: ldarg.1 + IL_0020: clt + IL_0022: ldc.i4.0 + IL_0023: ceq + IL_0025: call "void System.Console.Write(int)" + // sequence point: Write(x > y ? 1 : 0); + IL_002a: ldarg.0 + IL_002b: ldarg.1 + IL_002c: cgt + IL_002e: call "void System.Console.Write(int)" + // sequence point: Write(x > y ? 0 : 1); + IL_0033: ldarg.0 + IL_0034: ldarg.1 + IL_0035: cgt + IL_0037: ldc.i4.0 + IL_0038: ceq + IL_003a: call "void System.Console.Write(int)" + // sequence point: Write(a is string ? 0 : 1); + IL_003f: ldarg.2 + IL_0040: isinst "string" + IL_0045: ldnull + IL_0046: ceq + IL_0048: call "void System.Console.Write(int)" + // sequence point: Write(a is not string ? 0 : 1); + IL_004d: ldarg.2 + IL_004e: isinst "string" + IL_0053: ldnull + IL_0054: cgt.un + IL_0056: call "void System.Console.Write(int)" + // sequence point: Write(b ? 0 : 1); + IL_005b: ldarg.3 + IL_005c: ldc.i4.0 + IL_005d: ceq + IL_005f: call "void System.Console.Write(int)" + // sequence point: Write(!b ? 0 : 1); + IL_0064: ldarg.3 + IL_0065: ldc.i4.0 + IL_0066: cgt.un + IL_0068: call "void System.Console.Write(int)" + // sequence point: Write(x <= y ? true : false); + IL_006d: ldarg.0 + IL_006e: ldarg.1 + IL_006f: cgt + IL_0071: ldc.i4.0 + IL_0072: ceq + IL_0074: call "void System.Console.Write(bool)" + // sequence point: Write(x <= y ? false : true); + IL_0079: ldarg.0 + IL_007a: ldarg.1 + IL_007b: cgt + IL_007d: call "void System.Console.Write(bool)" + // sequence point: Write(x != y ? (byte)1 : (byte)0); + IL_0082: ldarg.0 + IL_0083: ldarg.1 + IL_0084: ceq + IL_0086: ldc.i4.0 + IL_0087: ceq + IL_0089: conv.u1 + IL_008a: call "void System.Console.Write(int)" + // sequence point: Write(x != y ? 1 : (sbyte)0); + IL_008f: ldarg.0 + IL_0090: ldarg.1 + IL_0091: ceq + IL_0093: ldc.i4.0 + IL_0094: ceq + IL_0096: call "void System.Console.Write(int)" + // sequence point: Write(x != y ? (short)1 : (short)0); + IL_009b: ldarg.0 + IL_009c: ldarg.1 + IL_009d: ceq + IL_009f: ldc.i4.0 + IL_00a0: ceq + IL_00a2: conv.i2 + IL_00a3: call "void System.Console.Write(int)" + // sequence point: Write(x != y ? (ushort)1 : 0); + IL_00a8: ldarg.0 + IL_00a9: ldarg.1 + IL_00aa: ceq + IL_00ac: ldc.i4.0 + IL_00ad: ceq + IL_00af: call "void System.Console.Write(int)" + // sequence point: Write(x != y ? 1U : 0); + IL_00b4: ldarg.0 + IL_00b5: ldarg.1 + IL_00b6: ceq + IL_00b8: ldc.i4.0 + IL_00b9: ceq + IL_00bb: call "void System.Console.Write(uint)" + // sequence point: Write(x != y ? 1L : 0); + IL_00c0: ldarg.0 + IL_00c1: ldarg.1 + IL_00c2: ceq + IL_00c4: ldc.i4.0 + IL_00c5: ceq + IL_00c7: conv.i8 + IL_00c8: call "void System.Console.Write(long)" + // sequence point: Write(x != y ? 1UL : 0); + IL_00cd: ldarg.0 + IL_00ce: ldarg.1 + IL_00cf: ceq + IL_00d1: ldc.i4.0 + IL_00d2: ceq + IL_00d4: conv.i8 + IL_00d5: call "void System.Console.Write(ulong)" + // sequence point: Write(x != y ? (nint)1 : 0); + IL_00da: ldarg.0 + IL_00db: ldarg.1 + IL_00dc: ceq + IL_00de: ldc.i4.0 + IL_00df: ceq + IL_00e1: conv.i + IL_00e2: conv.i8 + IL_00e3: call "void System.Console.Write(long)" + // sequence point: Write(x != y ? 1 : (nuint)0); + IL_00e8: ldarg.0 + IL_00e9: ldarg.1 + IL_00ea: ceq + IL_00ec: ldc.i4.0 + IL_00ed: ceq + IL_00ef: conv.i + IL_00f0: conv.u8 + IL_00f1: call "void System.Console.Write(ulong)" + // sequence point: Write(x < y ? (char)0 : (char)1); + IL_00f6: ldarg.0 + IL_00f7: ldarg.1 + IL_00f8: clt + IL_00fa: ldc.i4.0 + IL_00fb: ceq + IL_00fd: conv.u2 + IL_00fe: call "void System.Console.Write(char)" + // sequence point: Write(x < y ? '\x1' : '\x0'); + IL_0103: ldarg.0 + IL_0104: ldarg.1 + IL_0105: clt + IL_0107: conv.u2 + IL_0108: call "void System.Console.Write(char)" + // sequence point: Write(true ? 1 : 0); + IL_010d: ldc.i4.1 + IL_010e: call "void System.Console.Write(int)" + // sequence point: Write(false ? 0 : 1); + IL_0113: ldc.i4.1 + IL_0114: call "void System.Console.Write(int)" + // sequence point: Write(B ? 1 : 0); + IL_0119: ldc.i4.1 + IL_011a: call "void System.Console.Write(int)" + // sequence point: } + IL_011f: ret + } + """); + } + + [Fact, WorkItem(61483, "https://github.com/dotnet/roslyn/issues/61483")] + public void Branchless_Negations() + { + var source = """ + using static System.Console; + + class C + { + static void Main() => M(1, 0, true); + static void M(int x, int y, bool b) + { + Write(!(x < y) ? 0 : 1); + Write(!!(x < y) ? 0 : 1); + Write(!!!(x < y) ? 0 : 1); + Write(!(x == y) ? 0 : 1); + Write(!b ? 0 : 1); + Write(!!b ? 0 : 1); + Write(!!!b ? 0 : 1); + Write(!false ? 0 : 1); + Write(!!false ? 0 : 1); + Write(!!!false ? 0 : 1); + } + } + """; + var verifier = CompileAndVerify(source, + options: TestOptions.ReleaseExe, + expectedOutput: "0100101010"); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("C.M", """ + { + // Code size 85 (0x55) + .maxstack 2 + // sequence point: Write(!(x < y) ? 0 : 1); + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: clt + IL_0004: call "void System.Console.Write(int)" + // sequence point: Write(!!(x < y) ? 0 : 1); + IL_0009: ldarg.0 + IL_000a: ldarg.1 + IL_000b: clt + IL_000d: ldc.i4.0 + IL_000e: ceq + IL_0010: call "void System.Console.Write(int)" + // sequence point: Write(!!!(x < y) ? 0 : 1); + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: clt + IL_0019: call "void System.Console.Write(int)" + // sequence point: Write(!(x == y) ? 0 : 1); + IL_001e: ldarg.0 + IL_001f: ldarg.1 + IL_0020: ceq + IL_0022: call "void System.Console.Write(int)" + // sequence point: Write(!b ? 0 : 1); + IL_0027: ldarg.2 + IL_0028: ldc.i4.0 + IL_0029: cgt.un + IL_002b: call "void System.Console.Write(int)" + // sequence point: Write(!!b ? 0 : 1); + IL_0030: ldarg.2 + IL_0031: ldc.i4.0 + IL_0032: ceq + IL_0034: call "void System.Console.Write(int)" + // sequence point: Write(!!!b ? 0 : 1); + IL_0039: ldarg.2 + IL_003a: ldc.i4.0 + IL_003b: cgt.un + IL_003d: call "void System.Console.Write(int)" + // sequence point: Write(!false ? 0 : 1); + IL_0042: ldc.i4.0 + IL_0043: call "void System.Console.Write(int)" + // sequence point: Write(!!false ? 0 : 1); + IL_0048: ldc.i4.1 + IL_0049: call "void System.Console.Write(int)" + // sequence point: Write(!!!false ? 0 : 1); + IL_004e: ldc.i4.0 + IL_004f: call "void System.Console.Write(int)" + // sequence point: } + IL_0054: ret + } + """); + } + + [Fact, WorkItem(61483, "https://github.com/dotnet/roslyn/issues/61483")] + public void Branchless_NonBinaryArms() + { + var source = """ + using static System.Console; + + class C + { + static void Main() => M(1, 0); + static void M(int x, int y) + { + Write(x == y ? 1 : 1); + Write(x != y ? 0 : 0); + Write(x <= y ? 0 : 2); + Write(x >= y ? 2 : 1); + Write(x < y ? 0 : -1); + Write(x < y ? 0d : 1d); + Write(x < y ? 0f : 1f); + Write(x < y ? 0m : 1m); + Write(x < y ? '\x0' : 'a'); + } + } + """; + var verifier = CompileAndVerify(source, + options: TestOptions.ReleaseExe, + expectedOutput: "1022-1111a"); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("C.M", """ + { + // Code size 151 (0x97) + .maxstack 2 + // sequence point: Write(x == y ? 1 : 1); + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: beq.s IL_0007 + IL_0004: ldc.i4.1 + IL_0005: br.s IL_0008 + IL_0007: ldc.i4.1 + IL_0008: call "void System.Console.Write(int)" + // sequence point: Write(x != y ? 0 : 0); + IL_000d: ldarg.0 + IL_000e: ldarg.1 + IL_000f: bne.un.s IL_0014 + IL_0011: ldc.i4.0 + IL_0012: br.s IL_0015 + IL_0014: ldc.i4.0 + IL_0015: call "void System.Console.Write(int)" + // sequence point: Write(x <= y ? 0 : 2); + IL_001a: ldarg.0 + IL_001b: ldarg.1 + IL_001c: ble.s IL_0021 + IL_001e: ldc.i4.2 + IL_001f: br.s IL_0022 + IL_0021: ldc.i4.0 + IL_0022: call "void System.Console.Write(int)" + // sequence point: Write(x >= y ? 2 : 1); + IL_0027: ldarg.0 + IL_0028: ldarg.1 + IL_0029: bge.s IL_002e + IL_002b: ldc.i4.1 + IL_002c: br.s IL_002f + IL_002e: ldc.i4.2 + IL_002f: call "void System.Console.Write(int)" + // sequence point: Write(x < y ? 0 : -1); + IL_0034: ldarg.0 + IL_0035: ldarg.1 + IL_0036: blt.s IL_003b + IL_0038: ldc.i4.m1 + IL_0039: br.s IL_003c + IL_003b: ldc.i4.0 + IL_003c: call "void System.Console.Write(int)" + // sequence point: Write(x < y ? 0d : 1d); + IL_0041: ldarg.0 + IL_0042: ldarg.1 + IL_0043: blt.s IL_0050 + IL_0045: ldc.r8 1 + IL_004e: br.s IL_0059 + IL_0050: ldc.r8 0 + IL_0059: call "void System.Console.Write(double)" + // sequence point: Write(x < y ? 0f : 1f); + IL_005e: ldarg.0 + IL_005f: ldarg.1 + IL_0060: blt.s IL_0069 + IL_0062: ldc.r4 1 + IL_0067: br.s IL_006e + IL_0069: ldc.r4 0 + IL_006e: call "void System.Console.Write(float)" + // sequence point: Write(x < y ? 0m : 1m); + IL_0073: ldarg.0 + IL_0074: ldarg.1 + IL_0075: blt.s IL_007e + IL_0077: ldsfld "decimal decimal.One" + IL_007c: br.s IL_0083 + IL_007e: ldsfld "decimal decimal.Zero" + IL_0083: call "void System.Console.Write(decimal)" + // sequence point: Write(x < y ? '\x0' : 'a'); + IL_0088: ldarg.0 + IL_0089: ldarg.1 + IL_008a: blt.s IL_0090 + IL_008c: ldc.i4.s 97 + IL_008e: br.s IL_0091 + IL_0090: ldc.i4.0 + IL_0091: call "void System.Console.Write(char)" + // sequence point: } + IL_0096: ret + } + """); + } + + [Fact, WorkItem(61483, "https://github.com/dotnet/roslyn/issues/61483")] + public void Branchless_NonBinaryCondition() + { + // public static class C { public static bool M() => -1; } + var source1 = """ + .class public auto ansi abstract sealed beforefieldinit C + extends System.Object + { + .method public hidebysig static bool M () cil managed + { + .maxstack 8 + ldc.i4.m1 + ret + } + } + """; + var source2 = """ + System.Console.WriteLine(D.M1()); + System.Console.WriteLine(D.M2()); + + class D + { + public static int M1() => C.M() ? 1 : 0; + public static int M2() => C.M() ? 0 : 1; + } + """; + var comp = CreateCompilationWithIL(source2, source1, options: TestOptions.ReleaseExe); + var verifier = CompileAndVerify(comp, expectedOutput: """ + 1 + 0 + """); + verifier.VerifyDiagnostics(); + verifier.VerifyMethodBody("D.M1", """ + { + // Code size 9 (0x9) + .maxstack 2 + // sequence point: C.M() ? 1 : 0 + IL_0000: call "bool C.M()" + IL_0005: ldc.i4.0 + IL_0006: cgt.un + IL_0008: ret + } + """); + verifier.VerifyMethodBody("D.M2", """ + { + // Code size 9 (0x9) + .maxstack 2 + // sequence point: C.M() ? 0 : 1 + IL_0000: call "bool C.M()" + IL_0005: ldc.i4.0 + IL_0006: ceq + IL_0008: ret + } + """); + } + [Fact, WorkItem(638289, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/638289")] public void ConditionalDelegateInterfaceUnification1() { @@ -1152,21 +1734,17 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @" { - // Code size 25 (0x19) - .maxstack 2 + // Code size 19 (0x13) + .maxstack 3 IL_0000: ldc.i4.1 IL_0001: dup - IL_0002: brtrue.s IL_0007 - IL_0004: ldc.i4.1 - IL_0005: br.s IL_0008 - IL_0007: ldc.i4.0 - IL_0008: call ""void System.Console.Write(int)"" - IL_000d: brtrue.s IL_0012 - IL_000f: ldc.i4.1 - IL_0010: br.s IL_0013 - IL_0012: ldc.i4.0 - IL_0013: call ""void System.Console.Write(uint)"" - IL_0018: ret + IL_0002: ldc.i4.0 + IL_0003: ceq + IL_0005: call ""void System.Console.Write(int)"" + IL_000a: ldc.i4.0 + IL_000b: ceq + IL_000d: call ""void System.Console.Write(uint)"" + IL_0012: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs index eab91bd63605e..caad6cb2b227c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/PatternTests.cs @@ -5949,7 +5949,7 @@ .maxstack 2 compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); compVerifier.VerifyIL("C.M1", @" { - // Code size 35 (0x23) + // Code size 33 (0x21) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 @@ -5971,11 +5971,9 @@ .locals init (bool V_0) IL_001a: ldc.i4.0 IL_001b: stloc.0 IL_001c: ldloc.0 - IL_001d: brtrue.s IL_0021 - IL_001f: ldc.i4.0 + IL_001d: ldc.i4.0 + IL_001e: cgt.un IL_0020: ret - IL_0021: ldc.i4.1 - IL_0022: ret } "); compVerifier.VerifyIL("C.M2", @" diff --git a/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb b/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb index 82126ab729acf..b9e790df25848 100644 --- a/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb +++ b/src/Compilers/VisualBasic/Portable/CodeGen/EmitExpression.vb @@ -1250,6 +1250,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen Private Sub EmitTernaryConditionalExpression(expr As BoundTernaryConditionalExpression, used As Boolean) Debug.Assert(expr.ConstantValueOpt Is Nothing, "Constant value should have been emitted directly") + ' Generate branchless IL for If(b, 1, 0). + Dim isOneWhenTrue = False + Dim isOneWhenFalse = False + If used AndAlso _ilEmitStyle <> ILEmitStyle.Debug AndAlso + (IsSimpleType(expr.Type.PrimitiveTypeCode) OrElse expr.Type.PrimitiveTypeCode = Cci.PrimitiveTypeCode.Char) AndAlso + HasIntegralValueZeroOrOne(expr.WhenTrue, isOneWhenTrue) AndAlso + HasIntegralValueZeroOrOne(expr.WhenFalse, isOneWhenFalse) AndAlso + isOneWhenTrue <> isOneWhenFalse AndAlso + TryEmitComparison(expr.Condition, sense:=isOneWhenTrue) Then + + Dim toType = expr.Type.PrimitiveTypeCode + If toType <> Cci.PrimitiveTypeCode.Boolean Then + _builder.EmitNumericConversion(Cci.PrimitiveTypeCode.Int32, toType, checked:=False) + End If + + Return + End If + Dim consequenceLabel = New Object() Dim doneLabel = New Object() @@ -1306,6 +1324,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen _builder.MarkLabel(doneLabel) End Sub + Private Function HasIntegralValueZeroOrOne(expr As BoundExpression, ByRef isOne As Boolean) As Boolean + If expr.IsConstant Then + Dim constantValue = expr.ConstantValueOpt + + If constantValue.IsIntegral AndAlso constantValue.UInt64Value <= 1 Then + isOne = (constantValue.UInt64Value = 1) + Return True + End If + + If constantValue.IsBoolean Then + isOne = constantValue.BooleanValue + Return True + End If + + If constantValue.IsChar AndAlso AscW(constantValue.CharValue) <= 1 Then + isOne = (AscW(constantValue.CharValue) = 1) + Return True + End If + End If + + Return False + End Function + ''' ''' Emit code for a null-coalescing operator. ''' diff --git a/src/Compilers/VisualBasic/Portable/CodeGen/EmitOperators.vb b/src/Compilers/VisualBasic/Portable/CodeGen/EmitOperators.vb index 1bdc3a1820acd..39fcbc0699b31 100644 --- a/src/Compilers/VisualBasic/Portable/CodeGen/EmitOperators.vb +++ b/src/Compilers/VisualBasic/Portable/CodeGen/EmitOperators.vb @@ -133,6 +133,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen End Select End Function + Private Function IsComparisonOperator(operationKind As BinaryOperatorKind) As Boolean + Select Case operationKind And BinaryOperatorKind.OpMask + Case BinaryOperatorKind.Equals, + BinaryOperatorKind.NotEquals, + BinaryOperatorKind.LessThanOrEqual, + BinaryOperatorKind.GreaterThanOrEqual, + BinaryOperatorKind.LessThan, + BinaryOperatorKind.GreaterThan, + BinaryOperatorKind.Is, + BinaryOperatorKind.IsNot + Return True + + Case Else + Return False + End Select + End Function + Private Sub EmitBinaryOperator(expression As BoundBinaryOperator) ' Do not blow the stack due to a deep recursion on the left. @@ -591,12 +608,7 @@ BinaryOperatorKindEqual: ' generate a conditional (ie, boolean) expression... ' this will leave a value on the stack which conforms to sense, ie:(condition == sense) Private Function EmitCondExpr(condition As BoundExpression, sense As Boolean) As ConstResKind - While condition.Kind = BoundKind.UnaryOperator - Dim unOp = DirectCast(condition, BoundUnaryOperator) - Debug.Assert(unOp.OperatorKind = UnaryOperatorKind.Not AndAlso unOp.Type.IsBooleanType()) - condition = unOp.Operand - sense = Not sense - End While + RemoveNegation(condition, sense) Debug.Assert(condition.Type.SpecialType = SpecialType.System_Boolean) @@ -621,6 +633,65 @@ BinaryOperatorKindEqual: Return ConstResKind.NotAConst End Function + ''' + ''' Emits boolean expression without branching if possible (i.e., no logical operators, only comparisons). + ''' Leaves a boolean (int32, 0 or 1) value on the stack which conforms to sense, i.e., condition = sense. + ''' + Private Function TryEmitComparison(condition As BoundExpression, sense As Boolean) As Boolean + RemoveNegation(condition, sense) + + Debug.Assert(condition.Type.SpecialType = SpecialType.System_Boolean) + + If condition.IsConstant Then + Dim constValue = condition.ConstantValueOpt + Debug.Assert(constValue.IsBoolean) + Dim constant = constValue.BooleanValue + + _builder.EmitBoolConstant(constant = sense) + Return True + End If + + If condition.Kind = BoundKind.BinaryOperator Then + Dim binOp = DirectCast(condition, BoundBinaryOperator) + ' Intentionally don't optimize logical operators, they need branches to short-circuit. + If IsComparisonOperator(binOp.OperatorKind) Then + EmitBinaryCondOperator(binOp, sense) + Return True + End If + ElseIf condition.Kind = BoundKind.TypeOf Then + Dim typeOfExpression = DirectCast(condition, BoundTypeOf) + + EmitTypeOfExpression(typeOfExpression, used:=True, optimize:=True) + + If typeOfExpression.IsTypeOfIsNotExpression Then + sense = Not sense + End If + + ' Convert to 1 or 0. + _builder.EmitOpCode(ILOpCode.Ldnull) + _builder.EmitOpCode(If(sense, ILOpCode.Cgt_un, ILOpCode.Ceq)) + Return True + Else + EmitExpression(condition, used:=True) + + ' Convert to 1 or 0 (although `condition` is of type `Boolean`, it can contain any integer). + _builder.EmitOpCode(ILOpCode.Ldc_i4_0) + _builder.EmitOpCode(If(sense, ILOpCode.Cgt_un, ILOpCode.Ceq)) + Return True + End If + + Return False + End Function + + Private Sub RemoveNegation(ByRef condition As BoundExpression, ByRef sense As Boolean) + While condition.Kind = BoundKind.UnaryOperator + Dim unOp = DirectCast(condition, BoundUnaryOperator) + Debug.Assert(unOp.OperatorKind = UnaryOperatorKind.Not AndAlso unOp.Type.IsBooleanType()) + condition = unOp.Operand + sense = Not sense + End While + End Sub + ' emits IsTrue/IsFalse according to the sense ' IsTrue actually does nothing Private Sub EmitIsSense(sense As Boolean) diff --git a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenIfOperator.vb b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenIfOperator.vb index d3e3118bf44ff..f36aaad845d07 100644 --- a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenIfOperator.vb +++ b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenIfOperator.vb @@ -15,6 +15,647 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Public Class CodeGenIfOperator Inherits BasicTestBase + + Public Sub Branchless_Compare() + Dim source = + + y, 1, 0) + Dim tmp2 As Integer = If(x < y, 1, 0) + Return tmp1 - tmp2 + End Function + End Module + ]]> + + Dim expectedOutput = + Dim verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.DebugExe) + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.Comp", y, 1, 0) + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: bgt.s IL_0008 + IL_0005: ldc.i4.0 + IL_0006: br.s IL_0009 + IL_0008: ldc.i4.1 + IL_0009: stloc.1 + // sequence point: tmp2 As Integer = If(x < y, 1, 0) + IL_000a: ldarg.0 + IL_000b: ldarg.1 + IL_000c: blt.s IL_0011 + IL_000e: ldc.i4.0 + IL_000f: br.s IL_0012 + IL_0011: ldc.i4.1 + IL_0012: stloc.2 + // sequence point: Return tmp1 - tmp2 + IL_0013: ldloc.1 + IL_0014: ldloc.2 + IL_0015: sub.ovf + IL_0016: stloc.0 + IL_0017: br.s IL_0019 + // sequence point: End Function + IL_0019: ldloc.0 + IL_001a: ret +} +]]>.Value) + verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.ReleaseExe) + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.Comp", y, 1, 0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: cgt + // sequence point: tmp2 As Integer = If(x < y, 1, 0) + IL_0004: ldarg.0 + IL_0005: ldarg.1 + IL_0006: clt + IL_0008: stloc.0 + // sequence point: Return tmp1 - tmp2 + IL_0009: ldloc.0 + IL_000a: sub.ovf + // sequence point: End Function + IL_000b: ret +} +]]>.Value) + verifier = CompileAndVerify(source, expectedOutput, options:=TestOptions.ReleaseExe.WithDebugPlusMode(True)) + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.Comp", y, 1, 0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: cgt + IL_0004: stloc.1 + // sequence point: tmp2 As Integer = If(x < y, 1, 0) + IL_0005: ldarg.0 + IL_0006: ldarg.1 + IL_0007: clt + IL_0009: stloc.2 + // sequence point: Return tmp1 - tmp2 + IL_000a: ldloc.1 + IL_000b: ldloc.2 + IL_000c: sub.ovf + IL_000d: stloc.0 + // sequence point: End Function + IL_000e: ldloc.0 + IL_000f: ret +} +]]>.Value) + End Sub + + + Public Sub Branchless_Operations() + Dim verifier = CompileAndVerify( + + y, 1, 0)) + Write(If(x > y, 0, 1)) + Write(If(a Is a, 0, 1)) + Write(If(a IsNot a, 0, 1)) + Write(If(TypeOf a Is Decimal, 0, 1)) + Write(If(TypeOf a IsNot Decimal, 0, 1)) + Write(If(b, 0, 1)) + Write(If(Not b, 0, 1)) + Write(If(x <= y, True, False)) + Write(If(x <= y, False, True)) + Write(If(x <> y, CByte(1), CByte(0))) + Write(If(x <> y, CSByte(1), CSByte(0))) + Write(If(x <> y, 1S, 0S)) + Write(If(x <> y, 1US, 0US)) + Write(If(x <> y, 1UI, 0UI)) + Write(If(x <> y, 1L, 0L)) + Write(If(x <> y, 1UL, 0UL)) + Write(If(x < y, ChrW(0), ChrW(1))) + Write(If(x < y, ChrW(1), vbNullChar)) + Write(If(True, 1, 0)) + Write(If(False, 0, 1)) + Const B2 As Boolean = True + Write(If(B2, 1, 0)) + End Sub + End Module + ]]> +, options:=TestOptions.ReleaseExe, expectedOutput:="010110011001FalseTrue1111111" & ChrW(1) & ChrW(0) & "111") + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.M", y, 1, 0)) + IL_002a: ldarg.0 + IL_002b: ldarg.1 + IL_002c: cgt + IL_002e: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x > y, 0, 1)) + IL_0033: ldarg.0 + IL_0034: ldarg.1 + IL_0035: cgt + IL_0037: ldc.i4.0 + IL_0038: ceq + IL_003a: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(a Is a, 0, 1)) + IL_003f: ldarg.2 + IL_0040: ldarg.2 + IL_0041: ceq + IL_0043: ldc.i4.0 + IL_0044: ceq + IL_0046: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(a IsNot a, 0, 1)) + IL_004b: ldarg.2 + IL_004c: ldarg.2 + IL_004d: ceq + IL_004f: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(TypeOf a Is Decimal, 0, 1)) + IL_0054: ldarg.2 + IL_0055: isinst "Decimal" + IL_005a: ldnull + IL_005b: ceq + IL_005d: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(TypeOf a IsNot Decimal, 0, 1)) + IL_0062: ldarg.2 + IL_0063: isinst "Decimal" + IL_0068: ldnull + IL_0069: cgt.un + IL_006b: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(b, 0, 1)) + IL_0070: ldarg.3 + IL_0071: ldc.i4.0 + IL_0072: ceq + IL_0074: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(Not b, 0, 1)) + IL_0079: ldarg.3 + IL_007a: ldc.i4.0 + IL_007b: cgt.un + IL_007d: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x <= y, True, False)) + IL_0082: ldarg.0 + IL_0083: ldarg.1 + IL_0084: cgt + IL_0086: ldc.i4.0 + IL_0087: ceq + IL_0089: call "Sub System.Console.Write(Boolean)" + // sequence point: Write(If(x <= y, False, True)) + IL_008e: ldarg.0 + IL_008f: ldarg.1 + IL_0090: cgt + IL_0092: call "Sub System.Console.Write(Boolean)" + // sequence point: Write(If(x <> y, CByte(1), CByte(0))) + IL_0097: ldarg.0 + IL_0098: ldarg.1 + IL_0099: ceq + IL_009b: ldc.i4.0 + IL_009c: ceq + IL_009e: conv.u1 + IL_009f: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x <> y, CSByte(1), CSByte(0))) + IL_00a4: ldarg.0 + IL_00a5: ldarg.1 + IL_00a6: ceq + IL_00a8: ldc.i4.0 + IL_00a9: ceq + IL_00ab: conv.i1 + IL_00ac: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x <> y, 1S, 0S)) + IL_00b1: ldarg.0 + IL_00b2: ldarg.1 + IL_00b3: ceq + IL_00b5: ldc.i4.0 + IL_00b6: ceq + IL_00b8: conv.i2 + IL_00b9: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x <> y, 1US, 0US)) + IL_00be: ldarg.0 + IL_00bf: ldarg.1 + IL_00c0: ceq + IL_00c2: ldc.i4.0 + IL_00c3: ceq + IL_00c5: conv.u2 + IL_00c6: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x <> y, 1UI, 0UI)) + IL_00cb: ldarg.0 + IL_00cc: ldarg.1 + IL_00cd: ceq + IL_00cf: ldc.i4.0 + IL_00d0: ceq + IL_00d2: call "Sub System.Console.Write(UInteger)" + // sequence point: Write(If(x <> y, 1L, 0L)) + IL_00d7: ldarg.0 + IL_00d8: ldarg.1 + IL_00d9: ceq + IL_00db: ldc.i4.0 + IL_00dc: ceq + IL_00de: conv.i8 + IL_00df: call "Sub System.Console.Write(Long)" + // sequence point: Write(If(x <> y, 1UL, 0UL)) + IL_00e4: ldarg.0 + IL_00e5: ldarg.1 + IL_00e6: ceq + IL_00e8: ldc.i4.0 + IL_00e9: ceq + IL_00eb: conv.i8 + IL_00ec: call "Sub System.Console.Write(ULong)" + // sequence point: Write(If(x < y, ChrW(0), ChrW(1))) + IL_00f1: ldarg.0 + IL_00f2: ldarg.1 + IL_00f3: clt + IL_00f5: ldc.i4.0 + IL_00f6: ceq + IL_00f8: conv.u2 + IL_00f9: call "Sub System.Console.Write(Char)" + // sequence point: Write(If(x < y, ChrW(1), vbNullChar)) + IL_00fe: ldarg.0 + IL_00ff: ldarg.1 + IL_0100: blt.s IL_0109 + IL_0102: ldstr "]]>.Value & ChrW(0) & .Value & ChrW(1) & .Value) + End Sub + + + Public Sub Branchless_Negations() + Dim verifier = CompileAndVerify( + + +, options:=TestOptions.ReleaseExe, expectedOutput:="0100101010") + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.M", .Value) + End Sub + + + Public Sub Branchless_NonBinaryArms() + Dim verifier = CompileAndVerify( + + y, 0, 0)) + Write(If(x <= y, 0, 2)) + Write(If(x >= y, 2, 1)) + Write(If(x < y, 0, -1)) + Write(If(x < y, 0R, 1R)) + Write(If(x < y, 0F, 1F)) + Write(If(x < y, 0D, 1D)) + Write(If(x < y, vbNullChar, "a"c)) + End Sub + End Module + ]]> +, options:=TestOptions.ReleaseExe, expectedOutput:="1022-1111a") + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.M", y, 0, 0)) + IL_000d: ldarg.0 + IL_000e: ldarg.1 + IL_000f: bne.un.s IL_0014 + IL_0011: ldc.i4.0 + IL_0012: br.s IL_0015 + IL_0014: ldc.i4.0 + IL_0015: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x <= y, 0, 2)) + IL_001a: ldarg.0 + IL_001b: ldarg.1 + IL_001c: ble.s IL_0021 + IL_001e: ldc.i4.2 + IL_001f: br.s IL_0022 + IL_0021: ldc.i4.0 + IL_0022: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x >= y, 2, 1)) + IL_0027: ldarg.0 + IL_0028: ldarg.1 + IL_0029: bge.s IL_002e + IL_002b: ldc.i4.1 + IL_002c: br.s IL_002f + IL_002e: ldc.i4.2 + IL_002f: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x < y, 0, -1)) + IL_0034: ldarg.0 + IL_0035: ldarg.1 + IL_0036: blt.s IL_003b + IL_0038: ldc.i4.m1 + IL_0039: br.s IL_003c + IL_003b: ldc.i4.0 + IL_003c: call "Sub System.Console.Write(Integer)" + // sequence point: Write(If(x < y, 0R, 1R)) + IL_0041: ldarg.0 + IL_0042: ldarg.1 + IL_0043: blt.s IL_0050 + IL_0045: ldc.r8 1 + IL_004e: br.s IL_0059 + IL_0050: ldc.r8 0 + IL_0059: call "Sub System.Console.Write(Double)" + // sequence point: Write(If(x < y, 0F, 1F)) + IL_005e: ldarg.0 + IL_005f: ldarg.1 + IL_0060: blt.s IL_0069 + IL_0062: ldc.r4 1 + IL_0067: br.s IL_006e + IL_0069: ldc.r4 0 + IL_006e: call "Sub System.Console.Write(Single)" + // sequence point: Write(If(x < y, 0D, 1D)) + IL_0073: ldarg.0 + IL_0074: ldarg.1 + IL_0075: blt.s IL_007e + IL_0077: ldsfld "Decimal.One As Decimal" + IL_007c: br.s IL_0083 + IL_007e: ldsfld "Decimal.Zero As Decimal" + IL_0083: call "Sub System.Console.Write(Decimal)" + // sequence point: Write(If(x < y, vbNullChar, "a"c)) + IL_0088: ldarg.0 + IL_0089: ldarg.1 + IL_008a: blt.s IL_0093 + IL_008c: ldstr "a" + IL_0091: br.s IL_0098 + IL_0093: ldstr "]]>.Value & ChrW(0) & .Value) + End Sub + + + Public Sub Branchless_NonBinaryCondition() + Dim comp = CreateCompilationWithCustomILSource( + + +, + -1; } + .class public auto ansi abstract sealed beforefieldinit C + extends System.Object + { + .method public hidebysig static bool M () cil managed + { + .maxstack 8 + ldc.i4.m1 + ret + } + } +]]>.Value, TestOptions.ReleaseExe) + Dim verifier = CompileAndVerify(comp, expectedOutput:="10") + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("D.M1", .Value) + verifier.VerifyMethodBody("D.M2", .Value) + End Sub + + + Public Sub Branchless_IntPtr() + Dim verifier = CompileAndVerify( + + y, IntPtr.Zero, CType(1, IntPtr))) + End Sub + End Module + ]]> +, options:=TestOptions.ReleaseExe, expectedOutput:="10") + verifier.VerifyDiagnostics() + verifier.VerifyMethodBody("C.M", y, IntPtr.Zero, CType(1, IntPtr))) + IL_001c: ldarg.0 + IL_001d: ldarg.1 + IL_001e: bne.un.s IL_0028 + IL_0020: ldc.i4.1 + IL_0021: call "Function System.IntPtr.op_Explicit(Integer) As System.IntPtr" + IL_0026: br.s IL_002d + IL_0028: ldsfld "System.IntPtr.Zero As System.IntPtr" + IL_002d: box "System.IntPtr" + IL_0032: call "Sub System.Console.Write(Object)" + // sequence point: End Sub + IL_0037: ret +} +]]>.Value) + End Sub + ' Conditional operator as parameter Public Sub ConditionalOperatorAsParameter() diff --git a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenSelectCase.vb b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenSelectCase.vb index 27edf9f595c9b..8afc93c16eb5b 100644 --- a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenSelectCase.vb +++ b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenSelectCase.vb @@ -2529,7 +2529,7 @@ End Module ]]> , expectedOutput:="Success").VerifyIL("M1.Test", ) VerifySynthesizedStringHashMethod(compVerifier, expected:=False) @@ -2918,7 +2877,7 @@ End Module ]]> , expectedOutput:="Success").VerifyIL("M1.Test", ) VerifySynthesizedStringHashMethod(compVerifier, expected:=False) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/TypeOfTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/TypeOfTests.vb index a076579bc071e..cdc9f392a5331 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/TypeOfTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/TypeOfTests.vb @@ -958,7 +958,7 @@ End Module CompileAndVerify(source).VerifyIL("Program.Main", ).VerifyIL("Program.M",