|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.Binder;
namespace Microsoft.CodeAnalysis.CSharp.CodeGen
{
internal partial class CodeGenerator
{
private void EmitStatement(BoundStatement statement)
{
switch (statement.Kind)
{
case BoundKind.Block:
EmitBlock((BoundBlock)statement);
break;
case BoundKind.Scope:
EmitScope((BoundScope)statement);
break;
case BoundKind.SequencePoint:
this.EmitSequencePointStatement((BoundSequencePoint)statement);
break;
case BoundKind.SequencePointWithSpan:
this.EmitSequencePointStatement((BoundSequencePointWithSpan)statement);
break;
case BoundKind.SavePreviousSequencePoint:
this.EmitSavePreviousSequencePoint((BoundSavePreviousSequencePoint)statement);
break;
case BoundKind.RestorePreviousSequencePoint:
this.EmitRestorePreviousSequencePoint((BoundRestorePreviousSequencePoint)statement);
break;
case BoundKind.StepThroughSequencePoint:
this.EmitStepThroughSequencePoint((BoundStepThroughSequencePoint)statement);
break;
case BoundKind.ExpressionStatement:
EmitExpression(((BoundExpressionStatement)statement).Expression, false);
break;
case BoundKind.StatementList:
EmitStatementList((BoundStatementList)statement);
break;
case BoundKind.ReturnStatement:
EmitReturnStatement((BoundReturnStatement)statement);
break;
case BoundKind.GotoStatement:
EmitGotoStatement((BoundGotoStatement)statement);
break;
case BoundKind.LabelStatement:
EmitLabelStatement((BoundLabelStatement)statement);
break;
case BoundKind.ConditionalGoto:
EmitConditionalGoto((BoundConditionalGoto)statement);
break;
case BoundKind.ThrowStatement:
EmitThrowStatement((BoundThrowStatement)statement);
break;
case BoundKind.TryStatement:
EmitTryStatement((BoundTryStatement)statement);
break;
case BoundKind.SwitchDispatch:
EmitSwitchDispatch((BoundSwitchDispatch)statement);
break;
case BoundKind.StateMachineScope:
EmitStateMachineScope((BoundStateMachineScope)statement);
break;
case BoundKind.NoOpStatement:
EmitNoOpStatement((BoundNoOpStatement)statement);
break;
default:
// Code gen should not be invoked if there are errors.
throw ExceptionUtilities.UnexpectedValue(statement.Kind);
}
#if DEBUG
if (_stackLocals == null || _stackLocals.Count == 0)
{
_builder.AssertStackEmpty();
}
#endif
ReleaseExpressionTemps();
}
private int EmitStatementAndCountInstructions(BoundStatement statement)
{
int n = _builder.InstructionsEmitted;
this.EmitStatement(statement);
return _builder.InstructionsEmitted - n;
}
private void EmitStatementList(BoundStatementList list)
{
for (int i = 0, n = list.Statements.Length; i < n; i++)
{
EmitStatement(list.Statements[i]);
}
}
private void EmitNoOpStatement(BoundNoOpStatement statement)
{
switch (statement.Flavor)
{
case NoOpStatementFlavor.Default:
if (_ilEmitStyle == ILEmitStyle.Debug)
{
_builder.EmitOpCode(ILOpCode.Nop);
}
break;
case NoOpStatementFlavor.AwaitYieldPoint:
Debug.Assert((_asyncYieldPoints == null) == (_asyncResumePoints == null));
if (_asyncYieldPoints == null)
{
_asyncYieldPoints = ArrayBuilder<int>.GetInstance();
_asyncResumePoints = ArrayBuilder<int>.GetInstance();
}
Debug.Assert(_asyncYieldPoints.Count == _asyncResumePoints.Count);
_asyncYieldPoints.Add(_builder.AllocateILMarker());
break;
case NoOpStatementFlavor.AwaitResumePoint:
Debug.Assert(_asyncYieldPoints != null);
Debug.Assert(_asyncYieldPoints != null);
_asyncResumePoints.Add(_builder.AllocateILMarker());
Debug.Assert(_asyncYieldPoints.Count == _asyncResumePoints.Count);
break;
default:
throw ExceptionUtilities.UnexpectedValue(statement.Flavor);
}
}
private void EmitThrowStatement(BoundThrowStatement node)
{
EmitThrow(node.ExpressionOpt);
}
private void EmitThrow(BoundExpression thrown)
{
if (thrown != null)
{
this.EmitExpression(thrown, true);
var exprType = thrown.Type;
// Expression type will be null for "throw null;".
if (exprType?.TypeKind == TypeKind.TypeParameter)
{
this.EmitBox(exprType, thrown.Syntax);
}
}
_builder.EmitThrow(isRethrow: thrown == null);
}
private void EmitConditionalGoto(BoundConditionalGoto boundConditionalGoto)
{
object label = boundConditionalGoto.Label;
Debug.Assert(label != null);
EmitCondBranch(boundConditionalGoto.Condition, ref label, boundConditionalGoto.JumpIfTrue);
}
// 3.17 The brfalse instruction transfers control to target if value (of type int32, int64, object reference, managed
//pointer, unmanaged pointer or native int) is zero (false). If value is non-zero (true), execution continues at
//the next instruction.
private static bool CanPassToBrfalse(TypeSymbol ts)
{
if (ts.IsEnumType())
{
// valid enums are all primitives
return true;
}
var tc = ts.PrimitiveTypeCode;
switch (tc)
{
case Microsoft.Cci.PrimitiveTypeCode.Float32:
case Microsoft.Cci.PrimitiveTypeCode.Float64:
return false;
case Microsoft.Cci.PrimitiveTypeCode.NotPrimitive:
// if this is a generic type param, verifier will want us to box
// EmitCondBranch knows that
return ts.IsReferenceType;
default:
Debug.Assert(tc != Microsoft.Cci.PrimitiveTypeCode.Invalid);
Debug.Assert(tc != Microsoft.Cci.PrimitiveTypeCode.Void);
return true;
}
}
private static BoundExpression TryReduce(BoundBinaryOperator condition, ref bool sense)
{
var opKind = condition.OperatorKind.Operator();
Debug.Assert(opKind == BinaryOperatorKind.Equal ||
opKind == BinaryOperatorKind.NotEqual);
BoundExpression nonConstOp;
BoundExpression constOp = (condition.Left.ConstantValueOpt != null) ? condition.Left : null;
if (constOp != null)
{
nonConstOp = condition.Right;
}
else
{
constOp = (condition.Right.ConstantValueOpt != null) ? condition.Right : null;
if (constOp == null)
{
return null;
}
nonConstOp = condition.Left;
}
var nonConstType = nonConstOp.Type;
if (!CanPassToBrfalse(nonConstType))
{
return null;
}
bool isBool = nonConstType.PrimitiveTypeCode == Microsoft.Cci.PrimitiveTypeCode.Boolean;
bool isZero = constOp.ConstantValueOpt.IsDefaultValue;
// bool is special, only it can be compared to true and false...
if (!isBool && !isZero)
{
return null;
}
// if comparing to zero, flip the sense
if (isZero)
{
sense = !sense;
}
// if comparing != flip the sense
if (opKind == BinaryOperatorKind.NotEqual)
{
sense = !sense;
}
return nonConstOp;
}
private const int IL_OP_CODE_ROW_LENGTH = 4;
private static readonly ILOpCode[] s_condJumpOpCodes = new ILOpCode[]
{
// < <= > >=
ILOpCode.Blt, ILOpCode.Ble, ILOpCode.Bgt, ILOpCode.Bge, // Signed
ILOpCode.Bge, ILOpCode.Bgt, ILOpCode.Ble, ILOpCode.Blt, // Signed Invert
ILOpCode.Blt_un, ILOpCode.Ble_un, ILOpCode.Bgt_un, ILOpCode.Bge_un, // Unsigned
ILOpCode.Bge_un, ILOpCode.Bgt_un, ILOpCode.Ble_un, ILOpCode.Blt_un, // Unsigned Invert
ILOpCode.Blt, ILOpCode.Ble, ILOpCode.Bgt, ILOpCode.Bge, // Float
ILOpCode.Bge_un, ILOpCode.Bgt_un, ILOpCode.Ble_un, ILOpCode.Blt_un, // Float Invert
};
/// <summary>
/// Produces opcode for a jump that corresponds to given operation and sense.
/// Also produces a reverse opcode - opcode for the same condition with inverted sense.
/// </summary>
private static ILOpCode CodeForJump(BoundBinaryOperator op, bool sense, out ILOpCode revOpCode)
{
int opIdx;
switch (op.OperatorKind.Operator())
{
case BinaryOperatorKind.Equal:
revOpCode = !sense ? ILOpCode.Beq : ILOpCode.Bne_un;
return sense ? ILOpCode.Beq : ILOpCode.Bne_un;
case BinaryOperatorKind.NotEqual:
revOpCode = !sense ? ILOpCode.Bne_un : ILOpCode.Beq;
return sense ? ILOpCode.Bne_un : ILOpCode.Beq;
case BinaryOperatorKind.LessThan:
opIdx = 0;
break;
case BinaryOperatorKind.LessThanOrEqual:
opIdx = 1;
break;
case BinaryOperatorKind.GreaterThan:
opIdx = 2;
break;
case BinaryOperatorKind.GreaterThanOrEqual:
opIdx = 3;
break;
default:
throw ExceptionUtilities.UnexpectedValue(op.OperatorKind.Operator());
}
if (IsUnsignedBinaryOperator(op))
{
opIdx += 2 * IL_OP_CODE_ROW_LENGTH; //unsigned
}
else if (IsFloat(op.OperatorKind))
{
opIdx += 4 * IL_OP_CODE_ROW_LENGTH; //float
}
int revOpIdx = opIdx;
if (!sense)
{
opIdx += IL_OP_CODE_ROW_LENGTH; //invert op
}
else
{
revOpIdx += IL_OP_CODE_ROW_LENGTH; //invert rev
}
revOpCode = s_condJumpOpCodes[revOpIdx];
return s_condJumpOpCodes[opIdx];
}
// generate a jump to dest if (condition == sense) is true
private void EmitCondBranch(BoundExpression condition, ref object dest, bool sense)
{
_recursionDepth++;
if (_recursionDepth > 1)
{
StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
EmitCondBranchCore(condition, ref dest, sense);
}
else
{
EmitCondBranchCoreWithStackGuard(condition, ref dest, sense);
}
_recursionDepth--;
}
private void EmitCondBranchCoreWithStackGuard(BoundExpression condition, ref object dest, bool sense)
{
Debug.Assert(_recursionDepth == 1);
try
{
EmitCondBranchCore(condition, ref dest, sense);
Debug.Assert(_recursionDepth == 1);
}
catch (InsufficientExecutionStackException)
{
_diagnostics.Add(ErrorCode.ERR_InsufficientStack,
BoundTreeVisitor.CancelledByStackGuardException.GetTooLongOrComplexExpressionErrorLocation(condition));
throw new EmitCancelledException();
}
}
private void EmitCondBranchCore(BoundExpression condition, ref object dest, bool sense)
{
oneMoreTime:
ILOpCode ilcode;
if (condition.ConstantValueOpt != null)
{
bool taken = condition.ConstantValueOpt.IsDefaultValue != sense;
if (taken)
{
dest = dest ?? new object();
_builder.EmitBranch(ILOpCode.Br, dest);
}
else
{
// otherwise this branch will never be taken, so just fall through...
}
return;
}
switch (condition.Kind)
{
case BoundKind.BinaryOperator:
var binOp = (BoundBinaryOperator)condition;
Debug.Assert(binOp.ConstantValueOpt is null);
#nullable enable
if (binOp.OperatorKind.OperatorWithLogical() is BinaryOperatorKind.LogicalOr or BinaryOperatorKind.LogicalAnd)
{
var stack = ArrayBuilder<(BoundExpression? condition, StrongBox<object?> destBox, bool sense)>.GetInstance();
var destBox = new StrongBox<object?>(dest);
stack.Push((binOp, destBox, sense));
do
{
(BoundExpression? condition, StrongBox<object?> destBox, bool sense) top = stack.Pop();
if (top.condition is null)
{
// This is a special entry to indicate that it is time to append the block
object? fallThrough = top.destBox.Value;
if (fallThrough != null)
{
_builder.MarkLabel(fallThrough);
}
}
else if (top.condition.ConstantValueOpt is null &&
top.condition is BoundBinaryOperator binary &&
binary.OperatorKind.OperatorWithLogical() is BinaryOperatorKind.LogicalOr or BinaryOperatorKind.LogicalAnd)
{
if (binary.OperatorKind.OperatorWithLogical() is BinaryOperatorKind.LogicalOr ? !top.sense : top.sense)
{
// gotoif(a != sense) fallThrough
// gotoif(b == sense) dest
// fallThrough:
var fallThrough = new StrongBox<object?>();
// Note, operations are pushed to the stack in opposite order
stack.Push((null, fallThrough, true)); // This is a special entry to indicate that it is time to append the fallThrough block
stack.Push((binary.Right, top.destBox, top.sense));
stack.Push((binary.Left, fallThrough, !top.sense));
}
else
{
// gotoif(a == sense) labDest
// gotoif(b == sense) labDest
// Note, operations are pushed to the stack in opposite order
stack.Push((binary.Right, top.destBox, top.sense));
stack.Push((binary.Left, top.destBox, top.sense));
}
}
else if (stack.Count == 0 && ReferenceEquals(destBox, top.destBox))
{
// Instead of recursion we can restart from the top with new condition
condition = top.condition;
sense = top.sense;
dest = destBox.Value;
stack.Free();
goto oneMoreTime;
}
else
{
EmitCondBranch(top.condition, ref top.destBox.Value, top.sense);
}
}
while (stack.Count != 0);
dest = destBox.Value;
stack.Free();
return;
}
#nullable disable
switch (binOp.OperatorKind.OperatorWithLogical())
{
case BinaryOperatorKind.LogicalOr:
case BinaryOperatorKind.LogicalAnd:
throw ExceptionUtilities.Unreachable();
case BinaryOperatorKind.Equal:
case BinaryOperatorKind.NotEqual:
var reduced = TryReduce(binOp, ref sense);
if (reduced != null)
{
condition = reduced;
goto oneMoreTime;
}
// Fall through
goto case BinaryOperatorKind.LessThan;
case BinaryOperatorKind.LessThan:
case BinaryOperatorKind.LessThanOrEqual:
case BinaryOperatorKind.GreaterThan:
case BinaryOperatorKind.GreaterThanOrEqual:
EmitExpression(binOp.Left, true);
EmitExpression(binOp.Right, true);
ILOpCode revOpCode;
ilcode = CodeForJump(binOp, sense, out revOpCode);
dest = dest ?? new object();
_builder.EmitBranch(ilcode, dest, revOpCode);
return;
}
// none of above.
// then it is regular binary expression - Or, And, Xor ...
goto default;
case BoundKind.LoweredConditionalAccess:
{
var ca = (BoundLoweredConditionalAccess)condition;
var receiver = ca.Receiver;
var receiverType = receiver.Type;
// we need a copy if we deal with nonlocal value (to capture the value)
// or if we deal with stack local (reads are destructive)
var complexCase = !receiverType.IsReferenceType ||
LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) ||
(receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) ||
(ca.WhenNullOpt?.IsDefaultValue() == false);
if (complexCase)
{
goto default;
}
if (sense)
{
// gotoif(receiver != null) fallThrough
// gotoif(receiver.Access) dest
// fallThrough:
object fallThrough = null;
EmitCondBranch(receiver, ref fallThrough, sense: false);
// receiver is a reference type, and we only intend to read it
EmitReceiverRef(receiver, AddressKind.ReadOnly);
EmitCondBranch(ca.WhenNotNull, ref dest, sense: true);
if (fallThrough != null)
{
_builder.MarkLabel(fallThrough);
}
}
else
{
// gotoif(receiver == null) labDest
// gotoif(!receiver.Access) labDest
EmitCondBranch(receiver, ref dest, sense: false);
// receiver is a reference type, and we only intend to read it
EmitReceiverRef(receiver, AddressKind.ReadOnly);
condition = ca.WhenNotNull;
goto oneMoreTime;
}
}
return;
case BoundKind.UnaryOperator:
var unOp = (BoundUnaryOperator)condition;
if (unOp.OperatorKind == UnaryOperatorKind.BoolLogicalNegation)
{
sense = !sense;
condition = unOp.Operand;
goto oneMoreTime;
}
goto default;
case BoundKind.IsOperator:
var isOp = (BoundIsOperator)condition;
var operand = isOp.Operand;
EmitExpression(operand, true);
Debug.Assert((object)operand.Type != null);
if (!operand.Type.IsVerifierReference())
{
// box the operand for isinst if it is not a verifier reference
EmitBox(operand.Type, operand.Syntax);
}
_builder.EmitOpCode(ILOpCode.Isinst);
EmitSymbolToken(isOp.TargetType.Type, isOp.TargetType.Syntax);
ilcode = sense ? ILOpCode.Brtrue : ILOpCode.Brfalse;
dest = dest ?? new object();
_builder.EmitBranch(ilcode, dest);
return;
case BoundKind.Sequence:
var seq = (BoundSequence)condition;
EmitSequenceCondBranch(seq, ref dest, sense);
return;
default:
EmitExpression(condition, true);
var conditionType = condition.Type;
if (conditionType.IsReferenceType && !conditionType.IsVerifierReference())
{
EmitBox(conditionType, condition.Syntax);
}
ilcode = sense ? ILOpCode.Brtrue : ILOpCode.Brfalse;
dest = dest ?? new object();
_builder.EmitBranch(ilcode, dest);
return;
}
}
private void EmitSequenceCondBranch(BoundSequence sequence, ref object dest, bool sense)
{
DefineLocals(sequence);
EmitSideEffects(sequence);
EmitCondBranch(sequence.Value, ref dest, sense);
// sequence is used as a value, can release all locals
FreeLocals(sequence);
}
private void EmitLabelStatement(BoundLabelStatement boundLabelStatement)
{
_builder.MarkLabel(boundLabelStatement.Label);
}
private void EmitGotoStatement(BoundGotoStatement boundGotoStatement)
{
_builder.EmitBranch(ILOpCode.Br, boundGotoStatement.Label);
}
// used by HandleReturn method which tries to inject
// indirect ret sequence as a last statement in the block
// that is the last statement of the current method
// NOTE: it is important that there is no code after this "ret"
// it is desirable, for debug purposes, that this ret is emitted inside top level { }
private bool IsLastBlockInMethod(BoundBlock block)
{
if (_boundBody == block)
{
return true;
}
//sometimes top level node is a statement list containing
//epilogue and then a block. If we are having that block, it will do.
var list = _boundBody as BoundStatementList;
if (list != null && list.Statements.LastOrDefault() == block)
{
return true;
}
return false;
}
private void EmitBlock(BoundBlock block)
{
if (block.Instrumentation is not null)
{
EmitInstrumentedBlock(block.Instrumentation, block);
}
else
{
EmitUninstrumentedBlock(block);
}
}
private void EmitInstrumentedBlock(BoundBlockInstrumentation instrumentation, BoundBlock block)
{
if (!instrumentation.Locals.IsEmpty)
{
_builder.OpenLocalScope();
foreach (var local in instrumentation.Locals)
{
DefineLocal(local, block.Syntax);
}
}
if (instrumentation.Prologue != null)
{
if (_emitPdbSequencePoints)
{
EmitHiddenSequencePoint();
}
EmitStatement(instrumentation.Prologue);
}
_builder.AssertStackEmpty();
if (instrumentation.Epilogue != null)
{
_builder.OpenLocalScope(ScopeType.TryCatchFinally);
_builder.OpenLocalScope(ScopeType.Try);
EmitUninstrumentedBlock(block);
_builder.CloseLocalScope(); // try
_builder.OpenLocalScope(ScopeType.Finally);
if (_emitPdbSequencePoints)
{
EmitHiddenSequencePoint();
}
EmitStatement(instrumentation.Epilogue);
_builder.CloseLocalScope(); // finally
_builder.CloseLocalScope(); // try-finally
}
else
{
EmitUninstrumentedBlock(block);
}
if (!instrumentation.Locals.IsEmpty)
{
foreach (var local in instrumentation.Locals)
{
FreeLocal(local);
}
_builder.CloseLocalScope();
}
}
private void EmitUninstrumentedBlock(BoundBlock block)
{
var hasLocals = !block.Locals.IsEmpty;
if (hasLocals)
{
_builder.OpenLocalScope();
foreach (var local in block.Locals)
{
Debug.Assert(local.RefKind == RefKind.None || local.SynthesizedKind.IsLongLived(),
"A ref local ended up in a block and claims it is shortlived. That is dangerous. Are we sure it is short lived?");
var declaringReferences = local.DeclaringSyntaxReferences;
DefineLocal(local, !declaringReferences.IsEmpty ? (CSharpSyntaxNode)declaringReferences[0].GetSyntax() : block.Syntax);
}
}
EmitStatements(block.Statements);
if (_indirectReturnState == IndirectReturnState.Needed &&
IsLastBlockInMethod(block))
{
if (block.Instrumentation != null)
{
// jump out of try-finally
_builder.EmitBranch(ILOpCode.Br, s_returnLabel);
}
else
{
HandleReturn();
}
}
if (hasLocals)
{
foreach (var local in block.Locals)
{
FreeLocal(local);
}
_builder.CloseLocalScope();
}
}
private void EmitStatements(ImmutableArray<BoundStatement> statements)
{
foreach (var statement in statements)
{
EmitStatement(statement);
}
}
private void EmitScope(BoundScope block)
{
Debug.Assert(!block.Locals.IsEmpty);
_builder.OpenLocalScope();
foreach (var local in block.Locals)
{
Debug.Assert(local.Name != null);
Debug.Assert(local.SynthesizedKind == SynthesizedLocalKind.UserDefined &&
(local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchSection || local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchExpressionArm));
if (!local.IsConst && !IsStackLocal(local))
{
_builder.AddLocalToScope(_builder.LocalSlotManager.GetLocal(local));
}
}
EmitStatements(block.Statements);
_builder.CloseLocalScope();
}
private void EmitStateMachineScope(BoundStateMachineScope scope)
{
_builder.OpenLocalScope(ScopeType.StateMachineVariable);
foreach (var field in scope.Fields)
{
if (field.SlotIndex >= 0)
{
_builder.DefineUserDefinedStateMachineHoistedLocal(field.SlotIndex);
}
}
EmitStatement(scope.Statement);
_builder.CloseLocalScope();
}
// There are two ways a value can be returned from a function:
// - Using ret opcode
// - Store return value if any to a predefined temp and jump to the epilogue block
// Sometimes ret is not an option (try/catch etc.). We also do this when emitting
// debuggable code. This function is a stub for the logic that decides that.
private bool ShouldUseIndirectReturn()
{
// If the method/lambda body is a block we define a sequence point for the closing brace of the body
// and associate it with the ret instruction. If there is a return statement we need to store the value
// to a long-lived synthesized local since a sequence point requires an empty evaluation stack.
//
// The emitted pattern is:
// <evaluate return statement expression>
// stloc $ReturnValue
// ldloc $ReturnValue // sequence point
// ret
//
// Do not emit this pattern if the method doesn't include user code or doesn't have a block body.
return _ilEmitStyle == ILEmitStyle.Debug && _method.GenerateDebugInfo && _methodBodySyntaxOpt?.IsKind(SyntaxKind.Block) == true ||
_builder.InExceptionHandler;
}
// Compiler generated return mapped to a block is very likely the synthetic return
// that was added at the end of the last block of a void method by analysis.
// This is likely to be the last return in the method, so if we have not yet
// emitted return sequence, it is convenient to do it right here (if we can).
private bool CanHandleReturnLabel(BoundReturnStatement boundReturnStatement)
{
return boundReturnStatement.WasCompilerGenerated &&
(boundReturnStatement.Syntax.IsKind(SyntaxKind.Block) || _method?.IsImplicitConstructor == true) &&
!_builder.InExceptionHandler;
}
private void EmitReturnStatement(BoundReturnStatement boundReturnStatement)
{
var expressionOpt = boundReturnStatement.ExpressionOpt;
if (boundReturnStatement.RefKind == RefKind.None)
{
this.EmitExpression(expressionOpt, true);
}
else
{
// NOTE: passing "ReadOnlyStrict" here.
// we should never return an address of a copy
var unexpectedTemp = this.EmitAddress(expressionOpt, this._method.RefKind == RefKind.RefReadOnly ? AddressKind.ReadOnlyStrict : AddressKind.Writeable);
Debug.Assert(unexpectedTemp == null, "ref-returning a temp?");
}
if (ShouldUseIndirectReturn())
{
if (expressionOpt != null)
{
_builder.EmitLocalStore(LazyReturnTemp);
}
if (_indirectReturnState != IndirectReturnState.Emitted && CanHandleReturnLabel(boundReturnStatement))
{
HandleReturn();
}
else
{
_builder.EmitBranch(ILOpCode.Br, s_returnLabel);
if (_indirectReturnState == IndirectReturnState.NotNeeded)
{
_indirectReturnState = IndirectReturnState.Needed;
}
}
}
else
{
if (_indirectReturnState == IndirectReturnState.Needed && CanHandleReturnLabel(boundReturnStatement))
{
if (expressionOpt != null)
{
_builder.EmitLocalStore(LazyReturnTemp);
}
HandleReturn();
}
else
{
if (expressionOpt != null)
{
// Ensure the return type has been translated. (Necessary
// for cases of untranslated anonymous types.)
_module.Translate(expressionOpt.Type, boundReturnStatement.Syntax, _diagnostics.DiagnosticBag);
}
_builder.EmitRet(expressionOpt == null);
}
}
}
private void EmitTryStatement(BoundTryStatement statement, bool emitCatchesOnly = false)
{
Debug.Assert(!statement.CatchBlocks.IsDefault);
// Stack must be empty at beginning of try block.
_builder.AssertStackEmpty();
// IL requires catches and finally block to be distinct try
// blocks so if the source contained both a catch and
// a finally, nested scopes are emitted.
bool emitNestedScopes = (!emitCatchesOnly &&
(statement.CatchBlocks.Length > 0) &&
(statement.FinallyBlockOpt != null));
_builder.OpenLocalScope(ScopeType.TryCatchFinally);
_builder.OpenLocalScope(ScopeType.Try);
// IL requires catches and finally block to be distinct try
// blocks so if the source contained both a catch and
// a finally, nested scopes are emitted.
_tryNestingLevel++;
if (emitNestedScopes)
{
EmitTryStatement(statement, emitCatchesOnly: true);
}
else
{
EmitBlock(statement.TryBlock);
}
_tryNestingLevel--;
// Close the Try scope
_builder.CloseLocalScope();
if (!emitNestedScopes)
{
foreach (var catchBlock in statement.CatchBlocks)
{
EmitCatchBlock(catchBlock);
}
}
if (!emitCatchesOnly && (statement.FinallyBlockOpt != null))
{
_builder.OpenLocalScope(statement.PreferFaultHandler ? ScopeType.Fault : ScopeType.Finally);
EmitBlock(statement.FinallyBlockOpt);
// close Finally scope
_builder.CloseLocalScope();
// close the whole try statement scope
_builder.CloseLocalScope();
// in a case where we emit surrogate Finally using Fault, we emit code like this
//
// try{
// . . .
// } fault {
// finallyBlock;
// }
// finallyBlock;
//
// This is where the second copy of finallyBlock is emitted.
if (statement.PreferFaultHandler)
{
var finallyClone = FinallyCloner.MakeFinallyClone(statement);
EmitBlock(finallyClone);
}
}
else
{
// close the whole try statement scope
_builder.CloseLocalScope();
}
}
/// <remarks>
/// The interesting part in the following method is the support for exception filters.
/// === Example:
///
/// try
/// {
/// TryBlock
/// }
/// catch (ExceptionType ex) when (Condition)
/// {
/// Handler
/// }
///
/// gets emitted as something like ===>
///
/// Try
/// TryBlock
/// Filter
/// var tmp = Pop() as {ExceptionType}
/// if (tmp == null)
/// {
/// Push 0
/// }
/// else
/// {
/// ex = tmp
/// Push Condition ? 1 : 0
/// }
/// End Filter // leaves 1 or 0 on the stack
/// Catch // gets called after finalization of nested exception frames if condition above produced 1
/// Pop // CLR pushes the exception object again
/// variable ex can be used here
/// Handler
/// EndCatch
///
/// When evaluating `Condition` requires additional statements be executed first, those
/// statements are stored in `catchBlock.ExceptionFilterPrologueOpt` and emitted before the condition.
/// </remarks>
private void EmitCatchBlock(BoundCatchBlock catchBlock)
{
object typeCheckFailedLabel = null;
_builder.AdjustStack(1); // Account for exception on the stack.
// Open appropriate exception handler scope. (Catch or Filter)
// if it is a Filter, emit prologue that checks if the type on the stack
// converts to what we want.
if (catchBlock.ExceptionFilterOpt == null)
{
var exceptionType = ((object)catchBlock.ExceptionTypeOpt != null) ?
_module.Translate(catchBlock.ExceptionTypeOpt, catchBlock.Syntax, _diagnostics.DiagnosticBag) :
_module.GetSpecialType(SpecialType.System_Object, catchBlock.Syntax, _diagnostics.DiagnosticBag);
_builder.OpenLocalScope(ScopeType.Catch, exceptionType);
RecordAsyncCatchHandlerOffset(catchBlock);
// Dev12 inserts the sequence point on catch clause without a filter, just before
// the exception object is assigned to the variable.
//
// Also in Dev12 the exception variable scope span starts right after the stloc instruction and
// ends right before leave instruction. So when stopped at the sequence point Dev12 inserts,
// the exception variable is not visible.
if (_emitPdbSequencePoints)
{
var syntax = catchBlock.Syntax as CatchClauseSyntax;
if (syntax != null)
{
TextSpan spSpan;
var declaration = syntax.Declaration;
if (declaration == null)
{
spSpan = syntax.CatchKeyword.Span;
}
else
{
spSpan = TextSpan.FromBounds(syntax.SpanStart, syntax.Declaration.Span.End);
}
this.EmitSequencePoint(catchBlock.SyntaxTree, spSpan);
}
}
}
else
{
_builder.OpenLocalScope(ScopeType.Filter);
RecordAsyncCatchHandlerOffset(catchBlock);
// Filtering starts with simulating regular catch through a
// type check. If this is not our type then we are done.
var typeCheckPassedLabel = new object();
typeCheckFailedLabel = new object();
if ((object)catchBlock.ExceptionTypeOpt != null)
{
var exceptionType = _module.Translate(catchBlock.ExceptionTypeOpt, catchBlock.Syntax, _diagnostics.DiagnosticBag);
_builder.EmitOpCode(ILOpCode.Isinst);
_builder.EmitToken(exceptionType, catchBlock.Syntax, _diagnostics.DiagnosticBag);
_builder.EmitOpCode(ILOpCode.Dup);
_builder.EmitBranch(ILOpCode.Brtrue, typeCheckPassedLabel);
_builder.EmitOpCode(ILOpCode.Pop);
_builder.EmitIntConstant(0);
_builder.EmitBranch(ILOpCode.Br, typeCheckFailedLabel);
}
else
{
// no formal exception type means we always pass the check
}
_builder.MarkLabel(typeCheckPassedLabel);
}
foreach (var local in catchBlock.Locals)
{
var declaringReferences = local.DeclaringSyntaxReferences;
var localSyntax = !declaringReferences.IsEmpty ? (CSharpSyntaxNode)declaringReferences[0].GetSyntax() : catchBlock.Syntax;
DefineLocal(local, localSyntax);
}
var exceptionSourceOpt = catchBlock.ExceptionSourceOpt;
if (exceptionSourceOpt != null)
{
// here we have our exception on the stack in a form of a reference type (O)
// it means that we have to "unbox" it before storing to the local
// if exception's type is a generic type parameter.
if (!exceptionSourceOpt.Type.IsVerifierReference())
{
Debug.Assert(exceptionSourceOpt.Type.IsTypeParameter()); // only expecting type parameters
_builder.EmitOpCode(ILOpCode.Unbox_any);
EmitSymbolToken(exceptionSourceOpt.Type, exceptionSourceOpt.Syntax);
}
BoundExpression exceptionSource = exceptionSourceOpt;
while (exceptionSource.Kind == BoundKind.Sequence)
{
var seq = (BoundSequence)exceptionSource;
Debug.Assert(seq.Locals.IsDefaultOrEmpty);
EmitSideEffects(seq);
exceptionSource = seq.Value;
}
switch (exceptionSource.Kind)
{
case BoundKind.Local:
var exceptionSourceLocal = (BoundLocal)exceptionSource;
Debug.Assert(exceptionSourceLocal.LocalSymbol.RefKind == RefKind.None);
if (!IsStackLocal(exceptionSourceLocal.LocalSymbol))
{
_builder.EmitLocalStore(GetLocal(exceptionSourceLocal));
}
break;
case BoundKind.FieldAccess:
var left = (BoundFieldAccess)exceptionSource;
Debug.Assert(!left.FieldSymbol.IsStatic, "Not supported");
Debug.Assert(!left.ReceiverOpt.Type.IsTypeParameter());
Debug.Assert(left.FieldSymbol.RefKind == RefKind.None);
var stateMachineField = left.FieldSymbol as StateMachineFieldSymbol;
if (((object)stateMachineField != null) && (stateMachineField.SlotIndex >= 0))
{
_builder.DefineUserDefinedStateMachineHoistedLocal(stateMachineField.SlotIndex);
}
// When assigning to a field
// we need to push param address below the exception
var temp = AllocateTemp(exceptionSource.Type, exceptionSource.Syntax);
_builder.EmitLocalStore(temp);
var receiverTemp = EmitReceiverRef(left.ReceiverOpt, AddressKind.Writeable);
Debug.Assert(receiverTemp == null);
_builder.EmitLocalLoad(temp);
FreeTemp(temp);
EmitFieldStore(left, refAssign: false);
break;
default:
throw ExceptionUtilities.UnexpectedValue(exceptionSource.Kind);
}
}
else
{
_builder.EmitOpCode(ILOpCode.Pop);
}
if (catchBlock.ExceptionFilterPrologueOpt != null)
{
Debug.Assert(_builder.IsStackEmpty);
EmitStatements(catchBlock.ExceptionFilterPrologueOpt.Statements);
}
// Emit the actual filter expression, if we have one, and normalize
// results.
if (catchBlock.ExceptionFilterOpt != null)
{
EmitCondExpr(catchBlock.ExceptionFilterOpt, true);
// Normalize the return value because values other than 0 or 1
// produce unspecified results.
_builder.EmitIntConstant(0);
_builder.EmitOpCode(ILOpCode.Cgt_un);
_builder.MarkLabel(typeCheckFailedLabel);
// Now we are starting the actual handler
_builder.MarkFilterConditionEnd();
// Pop the exception; it should have already been stored to the
// variable by the filter.
_builder.EmitOpCode(ILOpCode.Pop);
}
EmitBlock(catchBlock.Body);
_builder.CloseLocalScope();
}
private void RecordAsyncCatchHandlerOffset(BoundCatchBlock catchBlock)
{
if (catchBlock.IsSynthesizedAsyncCatchAll)
{
Debug.Assert(_asyncCatchHandlerOffset < 0); // only one expected
_asyncCatchHandlerOffset = _builder.AllocateILMarker();
}
}
private void EmitSwitchDispatch(BoundSwitchDispatch dispatch)
{
// Switch expression must have a valid switch governing type
Debug.Assert((object)dispatch.Expression.Type != null);
Debug.Assert(dispatch.Expression.Type.IsValidV6SwitchGoverningType() || dispatch.Expression.Type.IsSpanOrReadOnlySpanChar());
// We must have rewritten nullable switch expression into non-nullable constructs.
Debug.Assert(!dispatch.Expression.Type.IsNullableType());
// This must be used only for nontrivial dispatches.
Debug.Assert(dispatch.Cases.Any());
EmitSwitchHeader(
dispatch.Expression,
dispatch.Cases.Select(p => new KeyValuePair<ConstantValue, object>(p.value, p.label)).ToArray(),
dispatch.DefaultLabel,
dispatch.LengthBasedStringSwitchDataOpt);
}
private void EmitSwitchHeader(
BoundExpression expression,
KeyValuePair<ConstantValue, object>[] switchCaseLabels,
LabelSymbol fallThroughLabel,
LengthBasedStringSwitchData lengthBasedSwitchStringJumpTableOpt)
{
Debug.Assert(expression.ConstantValueOpt == null);
Debug.Assert((object)expression.Type != null &&
(expression.Type.IsValidV6SwitchGoverningType() || expression.Type.IsSpanOrReadOnlySpanChar()));
Debug.Assert(switchCaseLabels.Length > 0);
Debug.Assert(switchCaseLabels != null || lengthBasedSwitchStringJumpTableOpt != null);
LocalDefinition temp = null;
LocalOrParameter key;
BoundSequence sequence = null;
if (expression.Kind == BoundKind.Sequence)
{
sequence = (BoundSequence)expression;
DefineLocals(sequence);
EmitSideEffects(sequence);
expression = sequence.Value;
}
if (expression.Kind == BoundKind.SequencePointExpression)
{
var sequencePointExpression = (BoundSequencePointExpression)expression;
EmitSequencePoint(sequencePointExpression);
expression = sequencePointExpression.Expression;
}
switch (expression.Kind)
{
case BoundKind.Local:
var local = ((BoundLocal)expression).LocalSymbol;
if (local.RefKind == RefKind.None && !IsStackLocal(local))
{
key = this.GetLocal(local);
break;
}
goto default;
case BoundKind.Parameter:
var parameter = (BoundParameter)expression;
if (parameter.ParameterSymbol.RefKind == RefKind.None)
{
key = ParameterSlot(parameter);
break;
}
goto default;
default:
EmitExpression(expression, true);
temp = AllocateTemp(expression.Type, expression.Syntax);
_builder.EmitLocalStore(temp);
key = temp;
break;
}
Debug.Assert(lengthBasedSwitchStringJumpTableOpt is null ||
expression.Type.SpecialType == SpecialType.System_String || expression.Type.IsSpanOrReadOnlySpanChar());
// Emit switch jump table
if (expression.Type.SpecialType == SpecialType.System_String || expression.Type.IsSpanOrReadOnlySpanChar())
{
if (lengthBasedSwitchStringJumpTableOpt is null)
{
this.EmitStringSwitchJumpTable(switchCaseLabels, fallThroughLabel, key, expression.Syntax, expression.Type);
}
else
{
this.EmitLengthBasedStringSwitchJumpTable(lengthBasedSwitchStringJumpTableOpt, fallThroughLabel, key, expression.Syntax, expression.Type);
}
}
else
{
_builder.EmitIntegerSwitchJumpTable(switchCaseLabels, fallThroughLabel, key, expression.Type.EnumUnderlyingTypeOrSelf().PrimitiveTypeCode);
}
if (temp != null)
{
FreeTemp(temp);
}
if (sequence != null)
{
// sequence was used as a value, can release all its locals.
FreeLocals(sequence);
}
}
#nullable enable
private void EmitLengthBasedStringSwitchJumpTable(
LengthBasedStringSwitchData lengthBasedSwitchData,
LabelSymbol fallThroughLabel,
LocalOrParameter keyTemp,
SyntaxNode syntaxNode,
TypeSymbol keyType)
{
// For the LengthJumpTable, emit:
// if (keyTemp is null)
// goto nullCaseLabel; OR goto fallThroughLabel;
//
// var lengthTmp = keyTemp.Length;
// switch dispatch on lengthTemp using fallThroughLabel and cases:
// lengthConstant -> corresponding label (may be the label to a CharJumpTable, or to a StringJumpTable in 1-length scenario, or in 0-length scenario, a final case label)
//
// var charTemp;
//
// For each CharJumpTable, emit:
// label for CharJumpTable:
// charTemp = keyTemp[selectedCharPosition];
// switch dispatch on charTemp using fallThroughLabel and cases:
// charConstant -> corresponding label (may be the label for a StringJumpTable or, in 1-length scenario, a final case label)
//
// For each StringJumpTable label, emit:
// label for StringJumpTable:
// switch dispatch on keyTemp using fallThroughLabel and cases:
// stringConstant -> corresponding label
bool isSpan = keyType.IsSpanChar();
bool isReadOnlySpan = keyType.IsReadOnlySpanChar();
bool isSpanOrReadOnlySpan = isSpan || isReadOnlySpan;
var indexerRef = GetIndexerRef(syntaxNode, keyType, isReadOnlySpan, isSpanOrReadOnlySpan);
var lengthMethodRef = GetLengthMethodRef(syntaxNode, keyType, isReadOnlySpan, isSpanOrReadOnlySpan);
Debug.Assert(indexerRef is not null);
Debug.Assert(lengthMethodRef is not null);
emitLengthDispatch(lengthBasedSwitchData, keyTemp, fallThroughLabel, syntaxNode);
emitCharDispatches(lengthBasedSwitchData, keyTemp, fallThroughLabel, syntaxNode);
emitFinalDispatches(lengthBasedSwitchData, keyTemp, keyType, fallThroughLabel, syntaxNode);
return;
void emitLengthDispatch(LengthBasedStringSwitchData lengthBasedSwitchInfo, LocalOrParameter keyTemp, LabelSymbol fallThroughLabel, SyntaxNode syntaxNode)
{
if (!isSpanOrReadOnlySpan)
{
// if (keyTemp is null)
// goto nullCaseLabel; OR goto fallThroughLabel;
_builder.EmitLoad(keyTemp);
_builder.EmitBranch(ILOpCode.Brfalse, lengthBasedSwitchInfo.LengthBasedJumpTable.NullCaseLabel ?? fallThroughLabel, ILOpCode.Brtrue);
}
// var stringLength = keyTemp.Length;
var int32Type = Binder.GetSpecialType(_module.Compilation, SpecialType.System_Int32, syntaxNode, _diagnostics);
var stringLength = AllocateTemp(int32Type, syntaxNode);
if (isSpanOrReadOnlySpan)
{
_builder.EmitLoadAddress(keyTemp);
}
else
{
_builder.EmitLoad(keyTemp);
}
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
emitMethodRef(lengthMethodRef);
_builder.EmitLocalStore(stringLength);
// switch dispatch on lengthTemp using fallThroughLabel and cases:
// lengthConstant -> corresponding label
_builder.EmitIntegerSwitchJumpTable(
lengthBasedSwitchInfo.LengthBasedJumpTable.LengthCaseLabels.Select(p => new KeyValuePair<ConstantValue, object>(ConstantValue.Create(p.value), p.label)).ToArray(),
fallThroughLabel, stringLength, int32Type.PrimitiveTypeCode);
FreeTemp(stringLength);
}
void emitCharDispatches(LengthBasedStringSwitchData lengthBasedSwitchInfo, LocalOrParameter keyTemp, LabelSymbol fallThroughLabel, SyntaxNode syntaxNode)
{
var charType = Binder.GetSpecialType(_module.Compilation, SpecialType.System_Char, syntaxNode, _diagnostics);
var charTemp = AllocateTemp(charType, syntaxNode);
foreach (var charJumpTable in lengthBasedSwitchInfo.CharBasedJumpTables)
{
// label for CharJumpTable:
_builder.MarkLabel(charJumpTable.Label);
// charTemp = keyTemp[selectedCharPosition];
if (isSpanOrReadOnlySpan)
{
_builder.EmitLoadAddress(keyTemp);
}
else
{
_builder.EmitLoad(keyTemp);
}
_builder.EmitIntConstant(charJumpTable.SelectedCharPosition);
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
emitMethodRef(indexerRef);
if (isSpanOrReadOnlySpan)
{
_builder.EmitOpCode(ILOpCode.Ldind_u2);
}
_builder.EmitLocalStore(charTemp);
// switch dispatch on charTemp using fallThroughLabel and cases:
// charConstant -> corresponding label
_builder.EmitIntegerSwitchJumpTable(
charJumpTable.CharCaseLabels.Select(p => new KeyValuePair<ConstantValue, object>(ConstantValue.Create(p.value), p.label)).ToArray(),
fallThroughLabel, charTemp, charType.PrimitiveTypeCode);
}
FreeTemp(charTemp);
}
void emitFinalDispatches(LengthBasedStringSwitchData lengthBasedSwitchInfo, LocalOrParameter keyTemp, TypeSymbol keyType, LabelSymbol fallThroughLabel, SyntaxNode syntaxNode)
{
foreach (var stringJumpTable in lengthBasedSwitchInfo.StringBasedJumpTables)
{
// label for StringJumpTable:
_builder.MarkLabel(stringJumpTable.Label);
// switch dispatch on keyTemp using fallThroughLabel and cases:
// stringConstant -> corresponding label
EmitStringSwitchJumpTable(
stringJumpTable.StringCaseLabels.Select(p => new KeyValuePair<ConstantValue, object>(ConstantValue.Create(p.value), p.label)).ToArray(),
fallThroughLabel, keyTemp, syntaxNode, keyType);
}
}
void emitMethodRef(Microsoft.Cci.IMethodReference lengthMethodRef)
{
var diag = DiagnosticBag.GetInstance();
_builder.EmitToken(lengthMethodRef, syntaxNode: null, diag);
Debug.Assert(diag.IsEmptyWithoutResolution);
diag.Free();
}
}
#nullable disable
private void EmitStringSwitchJumpTable(
KeyValuePair<ConstantValue, object>[] switchCaseLabels,
LabelSymbol fallThroughLabel,
LocalOrParameter key,
SyntaxNode syntaxNode,
TypeSymbol keyType)
{
var isSpan = keyType.IsSpanChar();
var isReadOnlySpan = keyType.IsReadOnlySpanChar();
var isSpanOrReadOnlySpan = isSpan || isReadOnlySpan;
LocalDefinition keyHash = null;
// Condition is necessary, but not sufficient (e.g. might be missing a special or well-known member).
if (SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch(switchCaseLabels.Length))
{
var privateImplClass = _module.GetPrivateImplClass(syntaxNode, _diagnostics.DiagnosticBag).PrivateImplementationDetails;
Cci.IReference stringHashMethodRef = privateImplClass.GetMethod(
isSpanOrReadOnlySpan
? isReadOnlySpan
? PrivateImplementationDetails.SynthesizedReadOnlySpanHashFunctionName
: PrivateImplementationDetails.SynthesizedSpanHashFunctionName
: PrivateImplementationDetails.SynthesizedStringHashFunctionName);
// Heuristics and well-known member availability determine the existence
// of this helper. Rather than reproduce that (language-specific) logic here,
// we simply check for the information we really want - whether the helper is
// available.
if (stringHashMethodRef != null)
{
// static uint ComputeStringHash(string s)
// pop 1 (s)
// push 1 (uint return value)
// stackAdjustment = (pushCount - popCount) = 0
_builder.EmitLoad(key);
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
_builder.EmitToken(stringHashMethodRef, syntaxNode, _diagnostics.DiagnosticBag);
var UInt32Type = Binder.GetSpecialType(_module.Compilation, SpecialType.System_UInt32, syntaxNode, _diagnostics);
keyHash = AllocateTemp(UInt32Type, syntaxNode);
_builder.EmitLocalStore(keyHash);
}
}
Cci.IMethodReference stringEqualityMethodRef = null;
Cci.IMethodReference sequenceEqualsMethodRef = null;
Cci.IMethodReference asSpanMethodRef = null;
if (isSpanOrReadOnlySpan)
{
// Binder.ConvertPatternExpression() has checked for these well-known members.
var sequenceEqualsTMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation,
(isReadOnlySpan
? WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T
: WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T),
_diagnostics, syntax: syntaxNode);
Debug.Assert(sequenceEqualsTMethod != null && !sequenceEqualsTMethod.HasUseSiteError);
var sequenceEqualsCharMethod = sequenceEqualsTMethod.Construct(Binder.GetSpecialType(_module.Compilation, SpecialType.System_Char, syntaxNode, _diagnostics));
sequenceEqualsMethodRef = _module.Translate(sequenceEqualsCharMethod, null, _diagnostics.DiagnosticBag);
var asSpanMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation, WellKnownMember.System_MemoryExtensions__AsSpan_String, _diagnostics, syntax: syntaxNode);
Debug.Assert(asSpanMethod != null && !asSpanMethod.HasUseSiteError);
asSpanMethodRef = _module.Translate(asSpanMethod, null, _diagnostics.DiagnosticBag);
}
else
{
var stringEqualityMethod = _module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Equality) as MethodSymbol;
Debug.Assert(stringEqualityMethod != null && !stringEqualityMethod.HasUseSiteError);
stringEqualityMethodRef = _module.Translate(stringEqualityMethod, syntaxNode, _diagnostics.DiagnosticBag);
}
Microsoft.Cci.IMethodReference lengthMethodRef = GetLengthMethodRef(syntaxNode, keyType, isReadOnlySpan, isSpanOrReadOnlySpan);
SwitchStringJumpTableEmitter.EmitStringCompareAndBranch emitStringCondBranchDelegate =
(keyArg, stringConstant, targetLabel) =>
{
if (stringConstant == ConstantValue.Null)
{
Debug.Assert(!isSpanOrReadOnlySpan);
// if (key == null)
// goto targetLabel
_builder.EmitLoad(keyArg);
_builder.EmitBranch(ILOpCode.Brfalse, targetLabel, ILOpCode.Brtrue);
}
else if (stringConstant.StringValue.Length == 0 && lengthMethodRef != null)
{
// if (key != null && key.Length == 0)
// goto targetLabel
object skipToNext = new object();
if (isSpanOrReadOnlySpan)
{
// The caller ensures that the key is not byref, and is not a stack local
_builder.EmitLoadAddress(keyArg);
}
else
{
_builder.EmitLoad(keyArg);
_builder.EmitBranch(ILOpCode.Brfalse, skipToNext, ILOpCode.Brtrue);
_builder.EmitLoad(keyArg);
}
// Stack: key --> length
_builder.EmitOpCode(ILOpCode.Call, 0);
var diag = DiagnosticBag.GetInstance();
_builder.EmitToken(lengthMethodRef, null, diag);
Debug.Assert(diag.IsEmptyWithoutResolution);
diag.Free();
_builder.EmitBranch(ILOpCode.Brfalse, targetLabel, ILOpCode.Brtrue);
_builder.MarkLabel(skipToNext);
}
else
{
if (isSpanOrReadOnlySpan)
{
this.EmitCharCompareAndBranch(key, syntaxNode, stringConstant, targetLabel, sequenceEqualsMethodRef, asSpanMethodRef);
}
else
{
this.EmitStringCompareAndBranch(key, syntaxNode, stringConstant, targetLabel, stringEqualityMethodRef);
}
}
};
_builder.EmitStringSwitchJumpTable(
caseLabels: switchCaseLabels,
fallThroughLabel: fallThroughLabel,
key: key,
keyHash: keyHash,
emitStringCondBranchDelegate: emitStringCondBranchDelegate,
computeStringHashcodeDelegate: SynthesizedStringSwitchHashMethod.ComputeStringHash);
if (keyHash != null)
{
FreeTemp(keyHash);
}
}
#nullable enable
private Cci.IMethodReference? GetLengthMethodRef(SyntaxNode syntaxNode, TypeSymbol keyType, bool isReadOnlySpan, bool isSpanOrReadOnlySpan)
{
if (isSpanOrReadOnlySpan)
{
var spanTLengthMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation,
(isReadOnlySpan ? WellKnownMember.System_ReadOnlySpan_T__get_Length : WellKnownMember.System_Span_T__get_Length),
_diagnostics, syntax: syntaxNode);
Debug.Assert(spanTLengthMethod != null && !spanTLengthMethod.HasUseSiteError);
var spanCharLengthMethod = spanTLengthMethod.AsMember((NamedTypeSymbol)keyType);
return _module.Translate(spanCharLengthMethod, syntaxNode, _diagnostics.DiagnosticBag);
}
else
{
var stringLengthMethod = _module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__Length) as MethodSymbol;
if (stringLengthMethod != null && !stringLengthMethod.HasUseSiteError)
{
return _module.Translate(stringLengthMethod, syntaxNode, _diagnostics.DiagnosticBag);
}
}
return null;
}
private Microsoft.Cci.IMethodReference? GetIndexerRef(SyntaxNode syntaxNode, TypeSymbol keyType, bool isReadOnlySpan, bool isSpanOrReadOnlySpan)
{
if (isSpanOrReadOnlySpan)
{
var spanTIndexerMethod = (MethodSymbol)Binder.GetWellKnownTypeMember(_module.Compilation,
(isReadOnlySpan ? WellKnownMember.System_ReadOnlySpan_T__get_Item : WellKnownMember.System_Span_T__get_Item),
_diagnostics, syntax: syntaxNode);
if (spanTIndexerMethod != null && !spanTIndexerMethod.HasUseSiteError)
{
var spanCharLengthMethod = spanTIndexerMethod.AsMember((NamedTypeSymbol)keyType);
return _module.Translate(spanCharLengthMethod, null, _diagnostics.DiagnosticBag);
}
}
else
{
var stringCharsIndexer = _module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__Chars) as MethodSymbol;
if (stringCharsIndexer != null && !stringCharsIndexer.HasUseSiteError)
{
return _module.Translate(stringCharsIndexer, syntaxNode, _diagnostics.DiagnosticBag);
}
}
return null;
}
#nullable disable
/// <summary>
/// Delegate to emit string compare call and conditional branch based on the compare result.
/// </summary>
/// <param name="key">Key to compare</param>
/// <param name="syntaxNode">Node for diagnostics.</param>
/// <param name="stringConstant">Case constant to compare the key against</param>
/// <param name="targetLabel">Target label to branch to if key = stringConstant</param>
/// <param name="stringEqualityMethodRef">String equality method</param>
private void EmitStringCompareAndBranch(LocalOrParameter key, SyntaxNode syntaxNode, ConstantValue stringConstant, object targetLabel, Microsoft.Cci.IReference stringEqualityMethodRef)
{
// Emit compare and branch:
// if (key == stringConstant)
// goto targetLabel;
Debug.Assert(stringEqualityMethodRef != null);
#if DEBUG
var assertDiagnostics = DiagnosticBag.GetInstance();
Debug.Assert(stringEqualityMethodRef == _module.Translate((MethodSymbol)_module.Compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Equality), (CSharpSyntaxNode)syntaxNode, assertDiagnostics));
assertDiagnostics.Free();
#endif
// static bool String.Equals(string a, string b)
// pop 2 (a, b)
// push 1 (bool return value)
// stackAdjustment = (pushCount - popCount) = -1
_builder.EmitLoad(key);
_builder.EmitConstantValue(stringConstant);
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
_builder.EmitToken(stringEqualityMethodRef, syntaxNode, _diagnostics.DiagnosticBag);
// Branch to targetLabel if String.Equals returned true.
_builder.EmitBranch(ILOpCode.Brtrue, targetLabel, ILOpCode.Brfalse);
}
/// <summary>
/// Delegate to emit ReadOnlySpanChar compare with string and conditional branch based on the compare result.
/// </summary>
/// <param name="key">Key to compare</param>
/// <param name="syntaxNode">Node for diagnostics.</param>
/// <param name="stringConstant">Case constant to compare the key against</param>
/// <param name="targetLabel">Target label to branch to if key = stringConstant</param>
/// <param name="sequenceEqualsRef">String equality method</param>
private void EmitCharCompareAndBranch(LocalOrParameter key, SyntaxNode syntaxNode, ConstantValue stringConstant, object targetLabel, Cci.IReference sequenceEqualsRef, Cci.IReference asSpanRef)
{
// Emit compare and branch:
// if (key.SequenceEqual(stringConstant.AsSpan()))
// goto targetLabel;
Debug.Assert(sequenceEqualsRef != null);
Debug.Assert(asSpanRef != null);
_builder.EmitLoad(key);
_builder.EmitConstantValue(stringConstant);
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0);
_builder.EmitToken(asSpanRef, syntaxNode, _diagnostics.DiagnosticBag);
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment: -1);
_builder.EmitToken(sequenceEqualsRef, syntaxNode, _diagnostics.DiagnosticBag);
// Branch to targetLabel if SequenceEquals returned true.
_builder.EmitBranch(ILOpCode.Brtrue, targetLabel, ILOpCode.Brfalse);
}
/// <summary>
/// Gets already declared and initialized local.
/// </summary>
private LocalDefinition GetLocal(BoundLocal localExpression)
{
var symbol = localExpression.LocalSymbol;
return GetLocal(symbol);
}
private LocalDefinition GetLocal(LocalSymbol symbol)
{
return _builder.LocalSlotManager.GetLocal(symbol);
}
private LocalDefinition DefineLocal(LocalSymbol local, SyntaxNode syntaxNode)
{
var dynamicTransformFlags = !local.IsCompilerGenerated && local.Type.ContainsDynamic() ?
CSharpCompilation.DynamicTransformsEncoder.Encode(local.Type, RefKind.None, 0) :
ImmutableArray<bool>.Empty;
var tupleElementNames = !local.IsCompilerGenerated && local.Type.ContainsTupleNames() ?
CSharpCompilation.TupleNamesEncoder.Encode(local.Type) :
ImmutableArray<string>.Empty;
if (local.IsConst)
{
Debug.Assert(local.HasConstantValue);
MetadataConstant compileTimeValue = _module.CreateConstant(local.Type, local.ConstantValue, syntaxNode, _diagnostics.DiagnosticBag);
LocalConstantDefinition localConstantDef = new LocalConstantDefinition(
local.Name,
local.GetFirstLocationOrNone(),
compileTimeValue,
dynamicTransformFlags: dynamicTransformFlags,
tupleElementNames: tupleElementNames);
_builder.AddLocalConstantToScope(localConstantDef);
return null;
}
if (IsStackLocal(local))
{
return null;
}
LocalSlotConstraints constraints;
Cci.ITypeReference translatedType;
if (local.DeclarationKind == LocalDeclarationKind.FixedVariable && local.IsPinned) // Excludes pointer local and string local in fixed string case.
{
Debug.Assert(local.RefKind == RefKind.None);
Debug.Assert(local.TypeWithAnnotations.Type.IsPointerType());
constraints = LocalSlotConstraints.ByRef | LocalSlotConstraints.Pinned;
PointerTypeSymbol pointerType = (PointerTypeSymbol)local.Type;
TypeSymbol pointedAtType = pointerType.PointedAtType;
// We can't declare a reference to void, so if the pointed-at type is void, use native int
// (represented here by IntPtr) instead.
translatedType = pointedAtType.IsVoidType()
? _module.GetSpecialType(SpecialType.System_IntPtr, syntaxNode, _diagnostics.DiagnosticBag)
: _module.Translate(pointedAtType, syntaxNode, _diagnostics.DiagnosticBag);
}
else
{
constraints = (local.IsPinned ? LocalSlotConstraints.Pinned : LocalSlotConstraints.None) |
(local.RefKind != RefKind.None ? LocalSlotConstraints.ByRef : LocalSlotConstraints.None);
translatedType = _module.Translate(local.Type, syntaxNode, _diagnostics.DiagnosticBag);
}
// Even though we don't need the token immediately, we will need it later when signature for the local is emitted.
// Also, requesting the token has side-effect of registering types used, which is critical for embedded types (NoPia, VBCore, etc).
_module.GetFakeSymbolTokenForIL(translatedType, syntaxNode, _diagnostics.DiagnosticBag);
LocalDebugId localId;
var name = GetLocalDebugName(local, out localId);
var localDef = _builder.LocalSlotManager.DeclareLocal(
type: translatedType,
symbol: local,
name: name,
kind: local.SynthesizedKind,
id: localId,
pdbAttributes: local.SynthesizedKind.PdbAttributes(),
constraints: constraints,
dynamicTransformFlags: dynamicTransformFlags,
tupleElementNames: tupleElementNames,
isSlotReusable: local.SynthesizedKind.IsSlotReusable(_ilEmitStyle != ILEmitStyle.Release));
// If named, add it to the local debug scope.
if (localDef.Name != null &&
!(local.SynthesizedKind == SynthesizedLocalKind.UserDefined &&
// Visibility scope of such locals is represented by BoundScope node.
(local.ScopeDesignatorOpt?.Kind() is SyntaxKind.SwitchSection or SyntaxKind.SwitchExpressionArm)))
{
_builder.AddLocalToScope(localDef);
}
return localDef;
}
/// <summary>
/// Gets the name and id of the local that are going to be generated into the debug metadata.
/// </summary>
private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId localId)
{
localId = LocalDebugId.None;
if (local.IsImportedFromMetadata)
{
return local.Name;
}
var localKind = local.SynthesizedKind;
// only user-defined locals should be named during lowering:
Debug.Assert((local.Name == null) == (localKind != SynthesizedLocalKind.UserDefined));
// Generating debug names for instrumentation payloads should be allowed, as described in https://github.com/dotnet/roslyn/issues/11024.
// For now, skip naming locals generated by instrumentation as they might not have a local syntax offset.
// Locals generated by instrumentation might exist in methods which do not contain a body (auto property initializers).
if (!localKind.IsLongLived() || localKind == SynthesizedLocalKind.InstrumentationPayload)
{
return null;
}
if (_ilEmitStyle == ILEmitStyle.Debug)
{
var syntax = local.GetDeclaratorSyntax();
int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(syntax), syntax.SyntaxTree);
int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset);
// user-defined locals should have 0 ordinal:
Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined);
localId = new LocalDebugId(syntaxOffset, ordinal);
}
return local.Name ?? GeneratedNames.MakeSynthesizedLocalName(localKind, ref _uniqueNameId);
}
private bool IsSlotReusable(LocalSymbol local)
{
return local.SynthesizedKind.IsSlotReusable(_ilEmitStyle != ILEmitStyle.Release);
}
/// <summary>
/// Releases a local.
/// </summary>
private void FreeLocal(LocalSymbol local)
{
// TODO: releasing named locals is NYI.
if (local.Name == null && IsSlotReusable(local) && !IsStackLocal(local))
{
_builder.LocalSlotManager.FreeLocal(local);
}
}
/// <summary>
/// Allocates a temp without identity.
/// </summary>
private LocalDefinition AllocateTemp(TypeSymbol type, SyntaxNode syntaxNode, LocalSlotConstraints slotConstraints = LocalSlotConstraints.None)
{
return _builder.LocalSlotManager.AllocateSlot(
_module.Translate(type, syntaxNode, _diagnostics.DiagnosticBag),
slotConstraints);
}
/// <summary>
/// Frees a temp.
/// </summary>
private void FreeTemp(LocalDefinition temp)
{
_builder.LocalSlotManager.FreeSlot(temp);
}
/// <summary>
/// Frees an optional temp.
/// </summary>
private void FreeOptTemp(LocalDefinition temp)
{
if (temp != null)
{
FreeTemp(temp);
}
}
/// <summary>
/// Clones all labels used in a finally block.
/// This allows creating an emittable clone of finally.
/// It is safe to do because no branches can go in or out of the finally handler.
/// </summary>
private class FinallyCloner : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
{
private Dictionary<LabelSymbol, GeneratedLabelSymbol> _labelClones;
private FinallyCloner() { }
/// <summary>
/// The argument is BoundTryStatement (and not a BoundBlock) specifically
/// to support only Finally blocks where it is guaranteed to not have incoming or leaving branches.
/// </summary>
public static BoundBlock MakeFinallyClone(BoundTryStatement node)
{
var cloner = new FinallyCloner();
return (BoundBlock)cloner.Visit(node.FinallyBlockOpt);
}
public override BoundNode VisitLabelStatement(BoundLabelStatement node)
{
return node.Update(GetLabelClone(node.Label));
}
public override BoundNode VisitGotoStatement(BoundGotoStatement node)
{
var labelClone = GetLabelClone(node.Label);
// expressions do not contain labels or branches
BoundExpression caseExpressionOpt = node.CaseExpressionOpt;
// expressions do not contain labels or branches
BoundLabel labelExpressionOpt = node.LabelExpressionOpt;
return node.Update(labelClone, caseExpressionOpt, labelExpressionOpt);
}
public override BoundNode VisitConditionalGoto(BoundConditionalGoto node)
{
var labelClone = GetLabelClone(node.Label);
// expressions do not contain labels or branches
BoundExpression condition = node.Condition;
return node.Update(condition, node.JumpIfTrue, labelClone);
}
public override BoundNode VisitSwitchDispatch(BoundSwitchDispatch node)
{
// expressions do not contain labels or branches
BoundExpression expression = node.Expression;
var defaultClone = GetLabelClone(node.DefaultLabel);
var casesBuilder = ArrayBuilder<(ConstantValue, LabelSymbol)>.GetInstance();
foreach (var (value, label) in node.Cases)
{
casesBuilder.Add((value, GetLabelClone(label)));
}
var lengthBasedSwitchData = node.LengthBasedStringSwitchDataOpt;
if (lengthBasedSwitchData is not null)
{
// We don't currently produce switch dispatches inside `fault` handler
throw ExceptionUtilities.Unreachable();
}
return node.Update(expression, casesBuilder.ToImmutableAndFree(), defaultClone, lengthBasedSwitchData);
}
public override BoundNode VisitExpressionStatement(BoundExpressionStatement node)
{
// expressions do not contain labels or branches
return node;
}
private GeneratedLabelSymbol GetLabelClone(LabelSymbol label)
{
var labelClones = _labelClones;
if (labelClones == null)
{
_labelClones = labelClones = new Dictionary<LabelSymbol, GeneratedLabelSymbol>();
}
GeneratedLabelSymbol clone;
if (!labelClones.TryGetValue(label, out clone))
{
clone = new GeneratedLabelSymbol("cloned_" + label.Name);
labelClones.Add(label, clone);
}
return clone;
}
}
}
}
|