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
Generate branchless IL for (b ? 1 : 0)
#67191
Changes from all commits
26b944a
e2c3f4d
01f7f64
333d1aa
1fe26ef
87a5313
bb482c5
513097c
62cc9d9
50d7ae7
f5bda52
270c816
6344f0c
97e88fa
3f19b46
9bee6a7
3270493
853b7bf
49e600c
435381b
9c45b18
08e5bf0
3a4b3c4
4e09301
ff8b135
65e3db4
857b0c4
1f0cc3a
e9aca83
f2c8d13
be1432c
d202e2b
c3e6928
72b4890
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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) && | ||||||||||||||||||||||||||||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, here: roslyn/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenConditionalOperatorTests.cs Lines 125 to 137 in e9aca83
|
||||||||||||||||||||||||||||
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) | ||||||||||||||||||||||||||||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
/// <summary> | ||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
} | ||
|
||
/// <summary> | ||
/// 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., <c>condition == sense</c>. | ||
/// </summary> | ||
private bool TryEmitComparison(BoundExpression condition, bool sense) | ||
{ | ||
RemoveNegation(ref condition, ref sense); | ||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Debug.Assert(condition.Type.SpecialType == SpecialType.System_Boolean); | ||
|
||
if (condition.ConstantValueOpt is { } constantValue) | ||
{ | ||
Debug.Assert(constantValue.Discriminator == ConstantValueTypeDiscriminator.Boolean); | ||
_builder.EmitBoolConstant(constantValue.BooleanValue == sense); | ||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
|
||
// Convert to 1 or 0. | ||
_builder.EmitOpCode(ILOpCode.Ldnull); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't follow why we're pushing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Never mind, I found out. |
||
_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); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mentioned
DebugFriendlyRelease
earlier. Do we have a test for that emit style? #ResolvedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.