|
// 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.
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Operations;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.CSharp.CodeGen;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LocalRewriter
{
public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node)
{
return VisitDynamicInvocation(node, resultDiscarded: false);
}
public BoundExpression VisitDynamicInvocation(BoundDynamicInvocation node, bool resultDiscarded)
{
// Dynamic can't have created handler conversions because we don't know target types.
AssertNoImplicitInterpolatedStringHandlerConversions(node.Arguments);
var loweredArguments = VisitList(node.Arguments);
bool hasImplicitReceiver;
BoundExpression loweredReceiver;
ImmutableArray<TypeWithAnnotations> typeArguments;
string name;
switch (node.Expression.Kind)
{
case BoundKind.MethodGroup:
// method invocation
BoundMethodGroup methodGroup = (BoundMethodGroup)node.Expression;
typeArguments = methodGroup.TypeArgumentsOpt;
name = methodGroup.Name;
hasImplicitReceiver = (methodGroup.Flags & BoundMethodGroupFlags.HasImplicitReceiver) != 0;
// Should have been eliminated during binding of dynamic invocation:
Debug.Assert(methodGroup.ReceiverOpt == null || methodGroup.ReceiverOpt.Kind != BoundKind.TypeOrValueExpression);
if (methodGroup.ReceiverOpt == null)
{
// Calling a static method defined on an outer class via its simple name.
NamedTypeSymbol firstContainer = node.ApplicableMethods.First().ContainingType;
Debug.Assert(node.ApplicableMethods.All(m => !m.RequiresInstanceReceiver && TypeSymbol.Equals(m.ContainingType, firstContainer, TypeCompareKind.ConsiderEverything2)));
loweredReceiver = new BoundTypeExpression(node.Syntax, null, firstContainer);
}
else if (hasImplicitReceiver && _factory.TopLevelMethod is { RequiresInstanceReceiver: false })
{
// Calling a static method defined on the current class via its simple name.
Debug.Assert(_factory.CurrentType is { });
loweredReceiver = new BoundTypeExpression(node.Syntax, null, _factory.CurrentType);
}
else
{
loweredReceiver = VisitExpression(methodGroup.ReceiverOpt);
}
// If we are calling a method on a NoPIA type, we need to embed all methods/properties
// with the matching name of this dynamic invocation.
EmbedIfNeedTo(loweredReceiver, methodGroup.Methods, node.Syntax);
break;
case BoundKind.DynamicMemberAccess:
// method invocation
var memberAccess = (BoundDynamicMemberAccess)node.Expression;
name = memberAccess.Name;
typeArguments = memberAccess.TypeArgumentsOpt;
loweredReceiver = VisitExpression(memberAccess.Receiver);
hasImplicitReceiver = false;
break;
default:
// delegate invocation
var loweredExpression = VisitExpression(node.Expression);
return _dynamicFactory.MakeDynamicInvocation(loweredExpression, loweredArguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, resultDiscarded).ToExpression();
}
Debug.Assert(loweredReceiver != null);
return _dynamicFactory.MakeDynamicMemberInvocation(
name,
loweredReceiver,
typeArguments,
loweredArguments,
node.ArgumentNamesOpt,
node.ArgumentRefKindsOpt,
hasImplicitReceiver,
resultDiscarded).ToExpression();
}
private void EmbedIfNeedTo(BoundExpression receiver, ImmutableArray<MethodSymbol> methods, SyntaxNode syntaxNode)
{
// If we are calling a method on a NoPIA type, we need to embed all methods/properties
// with the matching name of this dynamic invocation.
var module = this.EmitModule;
if (module != null && receiver != null && receiver.Type is { })
{
var assembly = receiver.Type.ContainingAssembly;
if ((object)assembly != null && assembly.IsLinked)
{
foreach (var m in methods)
{
module.EmbeddedTypesManagerOpt.EmbedMethodIfNeedTo(m.OriginalDefinition.GetCciAdapter(), syntaxNode, _diagnostics.DiagnosticBag);
}
}
}
}
private void EmbedIfNeedTo(BoundExpression receiver, ImmutableArray<PropertySymbol> properties, SyntaxNode syntaxNode)
{
// If we are calling a method on a NoPIA type, we need to embed all methods/properties
// with the matching name of this dynamic invocation.
var module = this.EmitModule;
if (module != null && receiver is { Type: { } })
{
var assembly = receiver.Type.ContainingAssembly;
if ((object)assembly != null && assembly.IsLinked)
{
foreach (var p in properties)
{
module.EmbeddedTypesManagerOpt.EmbedPropertyIfNeedTo(p.OriginalDefinition.GetCciAdapter(), syntaxNode, _diagnostics.DiagnosticBag);
}
}
}
}
public override BoundNode VisitCall(BoundCall node)
{
Debug.Assert(node != null);
// Rewrite the receiver
BoundExpression? rewrittenReceiver = VisitExpression(node.ReceiverOpt);
var argRefKindsOpt = node.ArgumentRefKindsOpt;
ArrayBuilder<LocalSymbol>? temps = null;
var rewrittenArguments = VisitArgumentsAndCaptureReceiverIfNeeded(
ref rewrittenReceiver,
captureReceiverMode: ReceiverCaptureMode.Default,
node.Arguments,
node.Method,
node.ArgsToParamsOpt,
argRefKindsOpt,
storesOpt: null,
ref temps);
var rewrittenCall = MakeArgumentsAndCall(
syntax: node.Syntax,
rewrittenReceiver: rewrittenReceiver,
method: node.Method,
arguments: rewrittenArguments,
argumentRefKindsOpt: argRefKindsOpt,
expanded: node.Expanded,
invokedAsExtensionMethod: node.InvokedAsExtensionMethod,
argsToParamsOpt: node.ArgsToParamsOpt,
resultKind: node.ResultKind,
type: node.Type,
temps,
nodeOpt: node);
if (Instrument)
{
rewrittenCall = Instrumenter.InstrumentCall(node, rewrittenCall);
}
return rewrittenCall;
}
private BoundExpression MakeArgumentsAndCall(
SyntaxNode syntax,
BoundExpression? rewrittenReceiver,
MethodSymbol method,
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> argumentRefKindsOpt,
bool expanded,
bool invokedAsExtensionMethod,
ImmutableArray<int> argsToParamsOpt,
LookupResultKind resultKind,
TypeSymbol type,
ArrayBuilder<LocalSymbol>? temps,
BoundCall? nodeOpt = null)
{
arguments = MakeArguments(
syntax,
arguments,
method,
expanded,
argsToParamsOpt,
ref argumentRefKindsOpt,
ref temps,
invokedAsExtensionMethod);
return MakeCall(nodeOpt, syntax, rewrittenReceiver, method, arguments, argumentRefKindsOpt, invokedAsExtensionMethod, resultKind, type, temps.ToImmutableAndFree());
}
private BoundExpression MakeCall(
BoundCall? node,
SyntaxNode syntax,
BoundExpression? rewrittenReceiver,
MethodSymbol method,
ImmutableArray<BoundExpression> rewrittenArguments,
ImmutableArray<RefKind> argumentRefKinds,
bool invokedAsExtensionMethod,
LookupResultKind resultKind,
TypeSymbol type,
ImmutableArray<LocalSymbol> temps)
{
BoundExpression rewrittenBoundCall;
if (method.IsStatic &&
method.ContainingType.IsObjectType() &&
!_inExpressionLambda &&
(object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_Object__ReferenceEquals))
{
Debug.Assert(rewrittenArguments.Length == 2);
// ECMA - 335
// I.8.2.5.1 Identity
// ...
// Identity is implemented on System.Object via the ReferenceEquals method.
rewrittenBoundCall = new BoundBinaryOperator(
syntax,
BinaryOperatorKind.ObjectEqual,
null,
methodOpt: null,
constrainedToTypeOpt: null,
resultKind,
rewrittenArguments[0],
rewrittenArguments[1],
type);
}
else if (node == null)
{
rewrittenBoundCall = new BoundCall(
syntax,
rewrittenReceiver,
method,
rewrittenArguments,
default(ImmutableArray<string>),
argumentRefKinds,
isDelegateCall: false,
expanded: false,
invokedAsExtensionMethod: invokedAsExtensionMethod,
argsToParamsOpt: default(ImmutableArray<int>),
defaultArguments: default(BitVector),
resultKind: resultKind,
type: type);
}
else
{
rewrittenBoundCall = node.Update(
rewrittenReceiver,
method,
rewrittenArguments,
default(ImmutableArray<string>),
argumentRefKinds,
node.IsDelegateCall,
false,
node.InvokedAsExtensionMethod,
default(ImmutableArray<int>),
default(BitVector),
node.ResultKind,
node.Type);
}
if (!temps.IsDefaultOrEmpty)
{
return new BoundSequence(
syntax,
locals: temps,
sideEffects: ImmutableArray<BoundExpression>.Empty,
value: rewrittenBoundCall,
type: type);
}
return rewrittenBoundCall;
}
private BoundExpression MakeCall(SyntaxNode syntax, BoundExpression? rewrittenReceiver, MethodSymbol method, ImmutableArray<BoundExpression> rewrittenArguments, TypeSymbol type)
{
return MakeCall(
node: null,
syntax: syntax,
rewrittenReceiver: rewrittenReceiver,
method: method,
rewrittenArguments: rewrittenArguments,
argumentRefKinds: default(ImmutableArray<RefKind>),
invokedAsExtensionMethod: false,
resultKind: LookupResultKind.Viable,
type: type,
temps: default);
}
private static bool IsSafeForReordering(BoundExpression expression, RefKind kind)
{
// To be safe for reordering an expression must not cause any observable side effect *or
// observe any side effect*. Accessing a local by value, for example, is possibly not
// safe for reordering because reading a local can give a different result if reordered
// with respect to a write elsewhere.
var current = expression;
while (true)
{
if (current.ConstantValueOpt != null)
{
return true;
}
switch (current.Kind)
{
default:
return false;
case BoundKind.Parameter:
Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression));
goto case BoundKind.Local;
case BoundKind.Local:
// A ref to a local variable or formal parameter is safe to reorder; it
// never has a side effect or consumes one.
return kind != RefKind.None;
case BoundKind.PassByCopy:
return IsSafeForReordering(((BoundPassByCopy)current).Expression, kind);
case BoundKind.Conversion:
{
BoundConversion conv = (BoundConversion)current;
switch (conv.ConversionKind)
{
case ConversionKind.AnonymousFunction:
case ConversionKind.ImplicitConstant:
case ConversionKind.MethodGroup:
case ConversionKind.NullLiteral:
case ConversionKind.DefaultLiteral:
return true;
case ConversionKind.Boxing:
case ConversionKind.ImplicitDynamic:
case ConversionKind.ExplicitDynamic:
case ConversionKind.ExplicitEnumeration:
case ConversionKind.ExplicitNullable:
case ConversionKind.ExplicitNumeric:
case ConversionKind.ExplicitReference:
case ConversionKind.Identity:
case ConversionKind.ImplicitEnumeration:
case ConversionKind.ImplicitNullable:
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitReference:
case ConversionKind.Unboxing:
case ConversionKind.ExplicitPointerToInteger:
case ConversionKind.ExplicitPointerToPointer:
case ConversionKind.ImplicitPointerToVoid:
case ConversionKind.ImplicitNullToPointer:
case ConversionKind.ExplicitIntegerToPointer:
current = conv.Operand;
break;
case ConversionKind.ExplicitUserDefined:
case ConversionKind.ImplicitUserDefined:
// expression trees rewrite this later.
// it is a kind of user defined conversions on IntPtr and in some cases can fail
case ConversionKind.IntPtr:
case ConversionKind.ImplicitThrow:
return false;
default:
// when this assert is hit, examine whether such conversion kind is
// 1) actually expected to get this far
// 2) figure if it is possibly not producing or consuming any sideeffects (rare case)
// 3) add a case for it
Debug.Assert(false, "Unexpected conversion kind" + conv.ConversionKind);
// it is safe to assume that conversion is not reorderable
return false;
}
break;
}
}
}
}
internal static bool IsCapturedPrimaryConstructorParameter(BoundExpression expression)
{
return expression is BoundParameter { ParameterSymbol: { ContainingSymbol: SynthesizedPrimaryConstructor primaryCtor } parameter } &&
primaryCtor.GetCapturedParameters().ContainsKey(parameter);
}
private enum ReceiverCaptureMode
{
/// <summary>
/// No special capture of the receiver, unless arguments need to refer to it.
/// For example, in case of a string interpolation handler.
/// </summary>
Default = 0,
/// <summary>
/// Used for a regular indexer compound assignment rewrite.
/// Everything is going to be in a single setter call with a getter call inside its value argument.
/// Only receiver and the indexes can be evaluated prior to evaluating the setter call.
/// </summary>
CompoundAssignment,
/// <summary>
/// Used for situations when additional arbitrary side-effects are possibly involved.
/// Think about deconstruction, etc.
/// </summary>
UseTwiceComplex
}
/// <summary>
/// Visits all arguments of a method, doing any necessary rewriting for interpolated string handler conversions that
/// might be present in the arguments and creating temps for any discard parameters.
/// </summary>
private ImmutableArray<BoundExpression> VisitArgumentsAndCaptureReceiverIfNeeded(
[NotNullIfNotNull(nameof(rewrittenReceiver))] ref BoundExpression? rewrittenReceiver,
ReceiverCaptureMode captureReceiverMode,
ImmutableArray<BoundExpression> arguments,
Symbol methodOrIndexer,
ImmutableArray<int> argsToParamsOpt,
ImmutableArray<RefKind> argumentRefKindsOpt,
ArrayBuilder<BoundExpression>? storesOpt,
ref ArrayBuilder<LocalSymbol>? tempsOpt)
{
Debug.Assert(argumentRefKindsOpt.IsDefault || argumentRefKindsOpt.Length == arguments.Length);
var requiresInstanceReceiver = methodOrIndexer.RequiresInstanceReceiver() && methodOrIndexer is not MethodSymbol { MethodKind: MethodKind.Constructor } and not FunctionPointerMethodSymbol;
Debug.Assert(!requiresInstanceReceiver || rewrittenReceiver != null || _inExpressionLambda);
Debug.Assert(captureReceiverMode == ReceiverCaptureMode.Default || (requiresInstanceReceiver && rewrittenReceiver != null && storesOpt is object));
BoundLocal? receiverTemp = null;
BoundAssignmentOperator? assignmentToTemp = null;
if (captureReceiverMode != ReceiverCaptureMode.Default ||
(requiresInstanceReceiver && arguments.Any(a => usesReceiver(a))))
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(rewrittenReceiver is object);
Debug.Assert(rewrittenReceiver.Type is { });
RefKind refKind;
if (captureReceiverMode != ReceiverCaptureMode.Default)
{
// SPEC VIOLATION: It is not very clear when receiver of constrained callvirt is dereferenced - when pushed (in lexical order),
// SPEC VIOLATION: or when actual call is executed. The actual behavior seems to be implementation specific in different JITs.
// SPEC VIOLATION: To not depend on that, the right thing to do here is to store the value of the variable
// SPEC VIOLATION: when variable has reference type (regular temp), and store variable's location when it has a value type. (ref temp)
// SPEC VIOLATION: in a case of unconstrained generic type parameter a runtime test (default(T) == null) would be needed
// SPEC VIOLATION: However, for compatibility with Dev12 we will continue treating all generic type parameters, constrained or not,
// SPEC VIOLATION: as value types.
refKind = rewrittenReceiver.Type.IsValueType || rewrittenReceiver.Type.Kind == SymbolKind.TypeParameter ? RefKind.Ref : RefKind.None;
}
else
{
refKind = rewrittenReceiver.GetRefKind();
if (refKind == RefKind.None &&
!rewrittenReceiver.Type.IsReferenceType &&
Binder.HasHome(rewrittenReceiver,
Binder.AddressKind.Constrained,
_factory.CurrentFunction,
peVerifyCompatEnabled: false,
stackLocalsOpt: null))
{
refKind = RefKind.Ref;
}
}
receiverTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, refKind);
tempsOpt ??= ArrayBuilder<LocalSymbol>.GetInstance();
tempsOpt.Add(receiverTemp.LocalSymbol);
}
ImmutableArray<BoundExpression> rewrittenArguments;
if (arguments.IsEmpty)
{
rewrittenArguments = arguments;
}
else
{
var argumentsAssignedToTemp = BitVector.Null;
var visitedArgumentsBuilder = ArrayBuilder<BoundExpression>.GetInstance(arguments.Length);
var parameters = methodOrIndexer.GetParameters();
#if DEBUG
var saveTempsOpt = tempsOpt;
#endif
for (int i = 0; i < arguments.Length; i++)
{
var argument = arguments[i];
if (argument is BoundDiscardExpression discard)
{
ensureTempTrackingSetup(ref tempsOpt, ref argumentsAssignedToTemp);
visitedArgumentsBuilder.Add(_factory.MakeTempForDiscard(discard, tempsOpt));
argumentsAssignedToTemp[i] = true;
continue;
}
ImmutableArray<BoundInterpolatedStringArgumentPlaceholder> argumentPlaceholders = addInterpolationPlaceholderReplacements(
parameters,
visitedArgumentsBuilder,
i,
receiverTemp,
ref tempsOpt,
ref argumentsAssignedToTemp);
visitedArgumentsBuilder.Add(VisitExpression(argument));
foreach (var placeholder in argumentPlaceholders)
{
// We didn't set this one up, so we can't remove it.
if (placeholder.ArgumentIndex == BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter)
{
continue;
}
RemovePlaceholderReplacement(placeholder);
}
}
#if DEBUG
Debug.Assert(saveTempsOpt is object || tempsOpt?.Count is null or > 0);
#endif
rewrittenArguments = visitedArgumentsBuilder.ToImmutableAndFree();
}
if (receiverTemp is object)
{
Debug.Assert(assignmentToTemp is object);
Debug.Assert(tempsOpt is object);
BoundAssignmentOperator? extraRefInitialization = null;
if (receiverTemp.LocalSymbol.IsRef &&
CodeGenerator.IsPossibleReferenceTypeReceiverOfConstrainedCall(receiverTemp) &&
!CodeGenerator.ReceiverIsKnownToReferToTempIfReferenceType(receiverTemp) &&
(captureReceiverMode == ReceiverCaptureMode.UseTwiceComplex ||
!CodeGenerator.IsSafeToDereferenceReceiverRefAfterEvaluatingArguments(rewrittenArguments)))
{
ReferToTempIfReferenceTypeReceiver(receiverTemp, ref assignmentToTemp, out extraRefInitialization, tempsOpt);
}
if (storesOpt is object)
{
if (extraRefInitialization is object)
{
storesOpt.Add(extraRefInitialization);
}
storesOpt.Add(assignmentToTemp);
rewrittenReceiver = receiverTemp;
}
else
{
rewrittenReceiver = _factory.Sequence(
ImmutableArray<LocalSymbol>.Empty,
extraRefInitialization is object ? ImmutableArray.Create<BoundExpression>(extraRefInitialization, assignmentToTemp) : ImmutableArray.Create<BoundExpression>(assignmentToTemp),
receiverTemp);
}
}
return rewrittenArguments;
void ensureTempTrackingSetup([NotNull] ref ArrayBuilder<LocalSymbol>? tempsOpt, ref BitVector positionsAssignedToTemp)
{
tempsOpt ??= ArrayBuilder<LocalSymbol>.GetInstance();
if (positionsAssignedToTemp.IsNull)
{
positionsAssignedToTemp = BitVector.Create(arguments.Length);
}
}
ImmutableArray<BoundInterpolatedStringArgumentPlaceholder> addInterpolationPlaceholderReplacements(
ImmutableArray<ParameterSymbol> parameters,
ArrayBuilder<BoundExpression> visitedArgumentsBuilder,
int argumentIndex,
BoundLocal? receiverTemp,
ref ArrayBuilder<LocalSymbol>? tempsOpt,
ref BitVector argumentsAssignedToTemp)
{
var argument = arguments[argumentIndex];
if (argument is BoundConversion { ConversionKind: ConversionKind.InterpolatedStringHandler, Operand: BoundInterpolatedString or BoundBinaryOperator } conversion)
{
// Handler conversions are not supported in expression lambdas.
Debug.Assert(!_inExpressionLambda);
var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData();
if (interpolationData.ArgumentPlaceholders.Length > (interpolationData.HasTrailingHandlerValidityParameter ? 1 : 0))
{
Debug.Assert(!((BoundConversion)argument).ExplicitCastInCode);
// We have an interpolated string handler conversion that needs context from the surrounding arguments. We need to store
// all arguments up to and including the last argument needed by this interpolated string conversion into temps, in order
// to ensure we're keeping lexical ordering of side effects.
ensureTempTrackingSetup(ref tempsOpt, ref argumentsAssignedToTemp);
Debug.Assert(!argumentsAssignedToTemp.IsNull);
foreach (var placeholder in interpolationData.ArgumentPlaceholders)
{
// Replace each needed placeholder with a sequence of store and evaluate the temp.
var argIndex = placeholder.ArgumentIndex;
Debug.Assert(argIndex < argumentIndex);
BoundLocal local;
switch (argIndex)
{
case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter:
Debug.Assert(usesReceiver(argument));
Debug.Assert(requiresInstanceReceiver);
Debug.Assert(receiverTemp is object);
local = receiverTemp;
break;
case >= 0 when argumentsAssignedToTemp[argIndex]:
local = visitedArgumentsBuilder[argIndex] switch
{
BoundSequence { Value: BoundLocal l } => l,
BoundLocal l => l, // Can happen for discard arguments
var u => throw ExceptionUtilities.UnexpectedValue(u.Kind)
};
break;
case >= 0:
Debug.Assert(visitedArgumentsBuilder[argIndex] != null);
var paramIndex = argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex];
RefKind argRefKind = argumentRefKindsOpt.RefKinds(argIndex);
RefKind paramRefKind = parameters[paramIndex].RefKind;
var visitedArgument = visitedArgumentsBuilder[argIndex];
local = _factory.StoreToTemp(visitedArgument, out var store, refKind: paramRefKind == RefKind.In ? RefKind.In : argRefKind);
tempsOpt.Add(local.LocalSymbol);
visitedArgumentsBuilder[argIndex] = _factory.Sequence(ImmutableArray<LocalSymbol>.Empty, ImmutableArray.Create<BoundExpression>(store), local);
argumentsAssignedToTemp[argIndex] = true;
break;
case BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter:
// Visiting the interpolated string itself will allocate the temp for this one.
continue;
default:
throw ExceptionUtilities.UnexpectedValue(argIndex);
}
AddPlaceholderReplacement(placeholder, local);
}
return interpolationData.ArgumentPlaceholders;
}
}
return ImmutableArray<BoundInterpolatedStringArgumentPlaceholder>.Empty;
}
static bool usesReceiver(BoundExpression argument)
{
if (argument is BoundConversion { ConversionKind: ConversionKind.InterpolatedStringHandler, Operand: BoundInterpolatedString or BoundBinaryOperator } conversion)
{
var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData();
if (interpolationData.ArgumentPlaceholders.Length > (interpolationData.HasTrailingHandlerValidityParameter ? 1 : 0))
{
Debug.Assert(!((BoundConversion)argument).ExplicitCastInCode);
foreach (var placeholder in interpolationData.ArgumentPlaceholders)
{
if (placeholder.ArgumentIndex == BoundInterpolatedStringArgumentPlaceholder.InstanceParameter)
{
return true;
}
}
}
}
return false;
}
}
private void ReferToTempIfReferenceTypeReceiver(BoundLocal receiverTemp, ref BoundAssignmentOperator assignmentToTemp, out BoundAssignmentOperator? extraRefInitialization, ArrayBuilder<LocalSymbol> temps)
{
Debug.Assert(assignmentToTemp.IsRef);
var receiverType = receiverTemp.Type;
Debug.Assert(receiverType is object);
// A case where T is actually a class must be handled specially.
// Taking a reference to a class instance is fragile because the value behind the
// reference might change while arguments are evaluated. However, the call should be
// performed on the instance that is behind reference at the time we push the
// reference to the stack. So, for a class we need to emit a reference to a temporary
// location, rather than to the original location
BoundLocal cache = _factory.Local(_factory.SynthesizedLocal(receiverType));
temps.Add(cache.LocalSymbol);
if (!receiverType.IsReferenceType)
{
// Store receiver ref to a different ref local - intermediate ref
var intermediateRef = _factory.Local(_factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref));
temps.Add(intermediateRef.LocalSymbol);
extraRefInitialization = assignmentToTemp.Update(intermediateRef, assignmentToTemp.Right, assignmentToTemp.IsRef, assignmentToTemp.Type);
// `receiverTemp` initialization is adjusted as follows:
// If we are dealing with a value type, use value of the intermediate ref.
// Otherwise, use an address of a temp where we store the underlying reference type instance.
assignmentToTemp =
assignmentToTemp.Update(
assignmentToTemp.Left,
#pragma warning disable format
new BoundComplexConditionalReceiver(receiverTemp.Syntax,
intermediateRef,
_factory.Sequence(new BoundExpression[] { _factory.AssignmentExpression(cache, intermediateRef) }, cache),
receiverType) { WasCompilerGenerated = true },
#pragma warning restore format
assignmentToTemp.IsRef,
assignmentToTemp.Type);
// SpillSequenceSpiller should be able to recognize this node in order to handle its spilling.
Debug.Assert(SpillSequenceSpiller.IsComplexConditionalInitializationOfReceiverRef(assignmentToTemp, out _, out _, out _, out _));
}
else
{
extraRefInitialization = null;
// We are dealing with a reference type. We can simply copy the instance into a temp and
// use its address instead.
assignmentToTemp =
assignmentToTemp.Update(
assignmentToTemp.Left,
_factory.Sequence(new BoundExpression[] { _factory.AssignmentExpression(cache, assignmentToTemp.Right) }, cache),
assignmentToTemp.IsRef,
assignmentToTemp.Type);
}
((SynthesizedLocal)receiverTemp.LocalSymbol).SetIsKnownToReferToTempIfReferenceType();
Debug.Assert(CodeGenerator.ReceiverIsKnownToReferToTempIfReferenceType(receiverTemp));
}
/// <summary>
/// Rewrites arguments of an invocation according to the receiving method or indexer.
/// It is assumed that each argument has already been lowered, but we may need
/// additional rewriting for the arguments, such as generating a params array, re-ordering
/// arguments based on <paramref name="argsToParamsOpt"/> map, inserting arguments for optional parameters, etc.
/// </summary>
private ImmutableArray<BoundExpression> MakeArguments(
SyntaxNode syntax,
ImmutableArray<BoundExpression> rewrittenArguments,
Symbol methodOrIndexer,
bool expanded,
ImmutableArray<int> argsToParamsOpt,
ref ImmutableArray<RefKind> argumentRefKindsOpt,
[NotNull] ref ArrayBuilder<LocalSymbol>? temps,
bool invokedAsExtensionMethod = false)
{
// We need to do a fancy rewrite under the following circumstances:
// (1) a params array is being used; we need to generate the array.
// (2) there were named arguments that reordered the arguments; we might
// have to generate temporaries to ensure that the arguments are
// evaluated in source code order, not the actual call order.
//
// If none of those are the case then we can just take an early out.
Debug.Assert(rewrittenArguments.All(arg => arg is not BoundDiscardExpression), "Discards should have been substituted by VisitArguments");
temps ??= ArrayBuilder<LocalSymbol>.GetInstance();
ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters();
if (CanSkipRewriting(rewrittenArguments, methodOrIndexer, expanded, argsToParamsOpt, invokedAsExtensionMethod, false, out var isComReceiver))
{
argumentRefKindsOpt = GetEffectiveArgumentRefKinds(argumentRefKindsOpt, parameters);
return rewrittenArguments;
}
// We have:
// * a list of arguments, already converted to their proper types,
// in source code order. Some optional arguments might be missing.
// * a map showing which parameter each argument corresponds to. If
// this is null, then the argument to parameter mapping is one-to-one.
// * the ref kind of each argument, in source code order. That is, whether
// the argument was marked as ref, out, or value (neither).
// * a method symbol.
// * whether the call is expanded or normal form.
// We rewrite the call so that:
// * if in its expanded form, we create the params array.
// * if the call requires reordering of arguments because of named arguments, temporaries are generated as needed
// Doing this transformation can move around refness in interesting ways. For example, consider
//
// A().M(y : ref B()[C()], x : out D());
//
// This will be created as a call with receiver A(), symbol M, argument list ( B()[C()], D() ),
// name list ( y, x ) and ref list ( ref, out ). We can rewrite this into temporaries:
//
// A().M(
// seq ( ref int temp_y = ref B()[C()], out D() ),
// temp_y );
//
// Now we have a call with receiver A(), symbol M, argument list as shown, no name list,
// and ref list ( out, value ). We do not want to pass a *ref* to temp_y; the temporary
// storage is not the thing being ref'd! We want to pass the *value* of temp_y, which
// *contains* a reference.
// We attempt to minimize the number of temporaries required. Arguments which neither
// produce nor observe a side effect can be placed into their proper position without
// recourse to a temporary. For example:
//
// Where(predicate: x=>x.Length!=0, sequence: S())
//
// can be rewritten without any temporaries because the conversion from lambda to
// delegate does not produce any side effect that could be observed by S().
//
// By contrast:
//
// Goo(z: this.p, y: this.Q(), x: (object)10)
//
// The boxing of 10 can be reordered, but the fetch of this.p has to happen before the
// call to this.Q() because the call could change the value of this.p.
//
// We start by binding everything that is not obviously reorderable as a temporary, and
// then run an optimizer to remove unnecessary temporaries.
BoundExpression[] actualArguments = new BoundExpression[parameters.Length]; // The actual arguments that will be passed; one actual argument per formal parameter.
ArrayBuilder<BoundAssignmentOperator> storesToTemps = ArrayBuilder<BoundAssignmentOperator>.GetInstance(rewrittenArguments.Length);
ArrayBuilder<RefKind> refKinds = ArrayBuilder<RefKind>.GetInstance(parameters.Length, RefKind.None);
// Step one: Store everything that is non-trivial into a temporary; record the
// stores in storesToTemps and make the actual argument a reference to the temp.
// Do not yet attempt to deal with params arrays or optional arguments.
BuildStoresToTemps(
expanded,
argsToParamsOpt,
parameters,
argumentRefKindsOpt,
rewrittenArguments,
forceLambdaSpilling: false, // lambda conversions can be re-ordered in calls without side affects
actualArguments,
refKinds,
storesToTemps);
// all the formal arguments, except missing optionals, are now in place.
// Optimize away unnecessary temporaries.
// Necessary temporaries have their store instructions merged into the appropriate
// argument expression.
OptimizeTemporaries(actualArguments, storesToTemps, temps);
storesToTemps.Free();
// Step two: If we have a params array, build the array and fill in the argument.
if (expanded)
{
actualArguments[actualArguments.Length - 1] = BuildParamsArray(syntax, argsToParamsOpt, rewrittenArguments, parameters, actualArguments[actualArguments.Length - 1]);
}
if (isComReceiver)
{
RewriteArgumentsForComCall(parameters, actualArguments, refKinds, temps);
}
// * The refkind map is now filled out to match the arguments.
// * The list of parameter names is now null because the arguments have been reordered.
// * The args-to-params map is now null because every argument exactly matches its parameter.
// * The call is no longer in its expanded form.
argumentRefKindsOpt = GetRefKindsOrNull(refKinds);
refKinds.Free();
Debug.Assert(actualArguments.All(static arg => arg is not null));
return actualArguments.AsImmutableOrNull();
}
/// <summary>
/// Patch refKinds for arguments that match 'In' or 'Ref' parameters to have effective RefKind.
/// For the purpose of further analysis we will mark the arguments as -
/// - In if was originally passed as None
/// - StrictIn if was originally passed as In
/// - Ref if the argument is an interpolated string literal subject to an interpolated string handler conversion. No other types
/// are patched here.
/// Here and in the layers after the lowering we only care about None/notNone differences for the arguments
/// Except for async stack spilling which needs to know whether arguments were originally passed as "In" and must obey "no copying" rule.
/// </summary>
private static ImmutableArray<RefKind> GetEffectiveArgumentRefKinds(ImmutableArray<RefKind> argumentRefKindsOpt, ImmutableArray<ParameterSymbol> parameters)
{
ArrayBuilder<RefKind>? refKindsBuilder = null;
for (int i = 0; i < parameters.Length; i++)
{
var paramRefKind = parameters[i].RefKind;
if (paramRefKind == RefKind.In)
{
var argRefKind = argumentRefKindsOpt.IsDefault ? RefKind.None : argumentRefKindsOpt[i];
fillRefKindsBuilder(argumentRefKindsOpt, parameters, ref refKindsBuilder);
refKindsBuilder[i] = argRefKind == RefKind.None ? paramRefKind : RefKindExtensions.StrictIn;
}
else if (paramRefKind == RefKind.Ref)
{
var argRefKind = argumentRefKindsOpt.IsDefault ? RefKind.None : argumentRefKindsOpt[i];
if (argRefKind == RefKind.None)
{
// Interpolated strings used as interpolated string handlers are allowed to match ref parameters without `ref`
Debug.Assert(parameters[i].Type is NamedTypeSymbol { IsInterpolatedStringHandlerType: true, IsValueType: true });
fillRefKindsBuilder(argumentRefKindsOpt, parameters, ref refKindsBuilder);
refKindsBuilder[i] = RefKind.Ref;
}
}
}
if (refKindsBuilder != null)
{
argumentRefKindsOpt = refKindsBuilder.ToImmutableAndFree();
}
// NOTE: we may have more arguments than parameters in a case of arglist. That is ok.
Debug.Assert(argumentRefKindsOpt.IsDefault || argumentRefKindsOpt.Length >= parameters.Length);
return argumentRefKindsOpt;
static void fillRefKindsBuilder(ImmutableArray<RefKind> argumentRefKindsOpt, ImmutableArray<ParameterSymbol> parameters, [NotNull] ref ArrayBuilder<RefKind>? refKindsBuilder)
{
if (refKindsBuilder == null)
{
if (!argumentRefKindsOpt.IsDefault)
{
Debug.Assert(!argumentRefKindsOpt.IsEmpty);
refKindsBuilder = ArrayBuilder<RefKind>.GetInstance(parameters.Length);
refKindsBuilder.AddRange(argumentRefKindsOpt);
}
else
{
refKindsBuilder = ArrayBuilder<RefKind>.GetInstance(parameters.Length, fillWithValue: RefKind.None);
}
}
}
}
internal static ImmutableArray<IArgumentOperation> MakeArgumentsInEvaluationOrder(
CSharpOperationFactory operationFactory,
CSharpCompilation compilation,
SyntaxNode syntax,
ImmutableArray<BoundExpression> arguments,
Symbol methodOrIndexer,
bool expanded,
ImmutableArray<int> argsToParamsOpt,
BitVector defaultArguments,
bool invokedAsExtensionMethod)
{
// We need to do a fancy rewrite under the following circumstances:
// (1) a params array is being used; we need to generate the array.
// (2) named arguments were provided out-of-order of the parameters.
//
// If neither of those are the case then we can just take an early out.
if (CanSkipRewriting(arguments, methodOrIndexer, expanded, argsToParamsOpt, invokedAsExtensionMethod, true, out _))
{
// In this case, the invocation is not in expanded form and there's no named argument provided.
// So we just return list of arguments as is.
ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters();
ArrayBuilder<IArgumentOperation> argumentsBuilder = ArrayBuilder<IArgumentOperation>.GetInstance(arguments.Length);
int i = 0;
for (; i < parameters.Length; ++i)
{
var argumentKind = defaultArguments[i] ? ArgumentKind.DefaultValue : ArgumentKind.Explicit;
argumentsBuilder.Add(operationFactory.CreateArgumentOperation(argumentKind, parameters[i].GetPublicSymbol(), arguments[i]));
}
// TODO: In case of __arglist, we will have more arguments than parameters,
// set the parameter to null for __arglist argument for now.
// https://github.com/dotnet/roslyn/issues/19673
for (; i < arguments.Length; ++i)
{
var argumentKind = defaultArguments[i] ? ArgumentKind.DefaultValue : ArgumentKind.Explicit;
argumentsBuilder.Add(operationFactory.CreateArgumentOperation(argumentKind, null, arguments[i]));
}
Debug.Assert(methodOrIndexer.GetIsVararg() ^ parameters.Length == arguments.Length);
return argumentsBuilder.ToImmutableAndFree();
}
return BuildArgumentsInEvaluationOrder(
operationFactory,
syntax,
methodOrIndexer,
expanded,
argsToParamsOpt,
defaultArguments,
arguments,
compilation);
}
// temporariesBuilder will be null when factory is null.
private static bool CanSkipRewriting(
ImmutableArray<BoundExpression> rewrittenArguments,
Symbol methodOrIndexer,
bool expanded,
ImmutableArray<int> argsToParamsOpt,
bool invokedAsExtensionMethod,
bool ignoreComReceiver,
out bool isComReceiver)
{
isComReceiver = false;
// An applicable "vararg" method could not possibly be applicable in its expanded
// form, and cannot possibly have named arguments or used optional parameters,
// because the __arglist() argument has to be positional and in the last position.
if (methodOrIndexer.GetIsVararg())
{
Debug.Assert(rewrittenArguments.Length == methodOrIndexer.GetParameterCount() + 1);
Debug.Assert(argsToParamsOpt.IsDefault);
Debug.Assert(!expanded);
return true;
}
if (!ignoreComReceiver)
{
var receiverNamedType = invokedAsExtensionMethod ?
((MethodSymbol)methodOrIndexer).Parameters[0].Type as NamedTypeSymbol :
methodOrIndexer.ContainingType;
isComReceiver = receiverNamedType is { IsComImport: true };
}
return rewrittenArguments.Length == methodOrIndexer.GetParameterCount() &&
argsToParamsOpt.IsDefault &&
!expanded &&
!isComReceiver;
}
private static ImmutableArray<RefKind> GetRefKindsOrNull(ArrayBuilder<RefKind> refKinds)
{
foreach (var refKind in refKinds)
{
if (refKind != RefKind.None)
{
return refKinds.ToImmutable();
}
}
return default(ImmutableArray<RefKind>);
}
// This fills in the arguments, refKinds and storesToTemps arrays.
private void BuildStoresToTemps(
bool expanded,
ImmutableArray<int> argsToParamsOpt,
ImmutableArray<ParameterSymbol> parameters,
ImmutableArray<RefKind> argumentRefKinds,
ImmutableArray<BoundExpression> rewrittenArguments,
bool forceLambdaSpilling,
/* out */ BoundExpression[] arguments,
/* out */ ArrayBuilder<RefKind> refKinds,
/* out */ ArrayBuilder<BoundAssignmentOperator> storesToTemps)
{
Debug.Assert(refKinds.Count == arguments.Length);
Debug.Assert(storesToTemps.Count == 0);
for (int a = 0; a < rewrittenArguments.Length; ++a)
{
BoundExpression argument = rewrittenArguments[a];
int p = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[a] : a;
RefKind argRefKind = argumentRefKinds.RefKinds(a);
RefKind paramRefKind = parameters[p].RefKind;
Debug.Assert(arguments[p] == null);
// Unfortunately, we violate the specification and allow:
// M(int q, params int[] x) ... M(x : X(), q : Q());
// which means that we cannot bail out just because
// an argument of an expanded-form call corresponds to
// the parameter array. We need to make sure that the
// side effects of X() and Q() continue to happen in the right
// order here.
//
// Fortunately, we do disallow M(x : 123, x : 345, x : 456).
//
// Here's what we'll do. If all the remaining arguments
// correspond to elements in the parameter array then
// we can bail out here without creating any temporaries.
// The next step in the call rewriter will deal with gathering
// up the elements.
//
// However, if there are other elements after this one
// that do not correspond to elements in the parameter array
// then we need to create a temporary as usual. The step that
// produces the parameter array will need to deal with that
// eventuality.
if (IsBeginningOfParamArray(p, a, expanded, arguments.Length, rewrittenArguments, argsToParamsOpt, out int paramArrayArgumentCount)
&& a + paramArrayArgumentCount == rewrittenArguments.Length)
{
return;
}
if ((!forceLambdaSpilling || !isLambdaConversion(argument)) &&
IsSafeForReordering(argument, argRefKind))
{
arguments[p] = argument;
}
else
{
var temp = _factory.StoreToTemp(
argument,
out BoundAssignmentOperator assignment,
refKind: paramRefKind == RefKind.In ? RefKind.In : argRefKind);
storesToTemps.Add(assignment);
arguments[p] = temp;
}
// Patch refKinds for arguments that match 'In' parameters to have effective RefKind
// For the purpose of further analysis we will mark the arguments as -
// - In if was originally passed as None
// - StrictIn if was originally passed as In
// Here and in the layers after the lowering we only care about None/notNone differences for the arguments
// Except for async stack spilling which needs to know whether arguments were originally passed as "In" and must obey "no copying" rule.
if (paramRefKind == RefKind.In)
{
Debug.Assert(argRefKind == RefKind.None || argRefKind == RefKind.In);
argRefKind = argRefKind == RefKind.None ? RefKind.In : RefKindExtensions.StrictIn;
}
refKinds[p] = argRefKind;
}
return;
bool isLambdaConversion(BoundExpression expr)
=> expr is BoundConversion conv && conv.ConversionKind == ConversionKind.AnonymousFunction;
}
// This fills in the arguments and parameters arrays in evaluation order.
private static ImmutableArray<IArgumentOperation> BuildArgumentsInEvaluationOrder(
CSharpOperationFactory operationFactory,
SyntaxNode syntax,
Symbol methodOrIndexer,
bool expanded,
ImmutableArray<int> argsToParamsOpt,
BitVector defaultArguments,
ImmutableArray<BoundExpression> arguments,
CSharpCompilation compilation)
{
ImmutableArray<ParameterSymbol> parameters = methodOrIndexer.GetParameters();
ArrayBuilder<IArgumentOperation> argumentsInEvaluationBuilder = ArrayBuilder<IArgumentOperation>.GetInstance(parameters.Length);
bool visitedLastParam = false;
// First, fill in all the explicitly provided arguments.
for (int a = 0; a < arguments.Length; ++a)
{
BoundExpression argument = arguments[a];
int p = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[a] : a;
var parameter = parameters[p];
if (!visitedLastParam)
{
visitedLastParam = p == parameters.Length - 1;
}
ArgumentKind kind = defaultArguments[a] ? ArgumentKind.DefaultValue : ArgumentKind.Explicit;
if (IsBeginningOfParamArray(p, a, expanded, parameters.Length, arguments, argsToParamsOpt, out int paramArrayArgumentCount))
{
int firstNonParamArrayArgumentIndex = a + paramArrayArgumentCount;
Debug.Assert(firstNonParamArrayArgumentIndex <= arguments.Length);
kind = ArgumentKind.ParamArray;
ArrayBuilder<BoundExpression> paramArray = ArrayBuilder<BoundExpression>.GetInstance(paramArrayArgumentCount);
for (int i = a; i < firstNonParamArrayArgumentIndex; ++i)
{
paramArray.Add(arguments[i]);
}
// Set loop variable so the value for next iteration will be the index of the first non param-array argument after param-array argument(s).
a = firstNonParamArrayArgumentIndex - 1;
argument = CreateParamArrayArgument(syntax, parameter.Type, paramArray.ToImmutableAndFree(), compilation, localRewriter: null);
}
argumentsInEvaluationBuilder.Add(operationFactory.CreateArgumentOperation(kind, parameter.GetPublicSymbol(), argument));
}
// Finally, append the missing empty params array if necessary.
var lastParam = !parameters.IsEmpty ? parameters[^1] : null;
if (expanded && lastParam is object && !visitedLastParam)
{
Debug.Assert(lastParam.IsParams);
// Create an empty array for omitted param array argument.
BoundExpression argument = CreateParamArrayArgument(syntax, lastParam.Type, ImmutableArray<BoundExpression>.Empty, compilation, localRewriter: null);
ArgumentKind kind = ArgumentKind.ParamArray;
argumentsInEvaluationBuilder.Add(operationFactory.CreateArgumentOperation(kind, lastParam.GetPublicSymbol(), argument));
}
Debug.Assert(argumentsInEvaluationBuilder.All(static arg => arg is not null));
return argumentsInEvaluationBuilder.ToImmutableAndFree();
}
/// <summary>
/// Returns true if the given argument is the beginning of a list of param array arguments (could be empty), otherwise returns false.
/// When returns true, numberOfParamArrayArguments is set to the number of param array arguments.
/// </summary>
private static bool IsBeginningOfParamArray(
int parameterIndex,
int argumentIndex,
bool expanded,
int parameterCount,
ImmutableArray<BoundExpression> arguments,
ImmutableArray<int> argsToParamsOpt,
out int numberOfParamArrayArguments)
{
numberOfParamArrayArguments = 0;
if (expanded && parameterIndex == parameterCount - 1)
{
int remainingArgument = argumentIndex + 1;
for (; remainingArgument < arguments.Length; ++remainingArgument)
{
int remainingParameter = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[remainingArgument] : remainingArgument;
if (remainingParameter != parameterCount - 1)
{
break;
}
}
numberOfParamArrayArguments = remainingArgument - argumentIndex;
return true;
}
return false;
}
private BoundExpression BuildParamsArray(
SyntaxNode syntax,
ImmutableArray<int> argsToParamsOpt,
ImmutableArray<BoundExpression> rewrittenArguments,
ImmutableArray<ParameterSymbol> parameters,
BoundExpression tempStoreArgument)
{
ArrayBuilder<BoundExpression> paramArray = ArrayBuilder<BoundExpression>.GetInstance();
int paramsParam = parameters.Length - 1;
if (tempStoreArgument != null)
{
paramArray.Add(tempStoreArgument);
// Special case: see comment in BuildStoresToTemps above; if there
// is an argument already in the slot then it is the only element in
// the params array.
}
else
{
for (int a = 0; a < rewrittenArguments.Length; ++a)
{
BoundExpression argument = rewrittenArguments[a];
int p = (!argsToParamsOpt.IsDefault) ? argsToParamsOpt[a] : a;
if (p == paramsParam)
{
paramArray.Add(argument);
}
}
}
var paramArrayType = parameters[paramsParam].Type;
var arrayArgs = paramArray.ToImmutableAndFree();
// If this is a zero-length array, rather than using "new T[0]", optimize with "Array.Empty<T>()"
// if it's available. However, we also disable the optimization if we're in an expression lambda, the
// point of which is just to represent the semantics of an operation, and we don't know that all consumers
// of expression lambdas will appropriately understand Array.Empty<T>().
// We disable it for pointer types as well, since they cannot be used as Type Arguments.
if (arrayArgs.Length == 0
&& !_inExpressionLambda
&& paramArrayType is ArrayTypeSymbol ats // could be false if there's a semantic error, e.g. the params parameter type isn't an array
&& !ats.ElementType.IsPointerOrFunctionPointer())
{
MethodSymbol? arrayEmpty = _compilation.GetWellKnownTypeMember(WellKnownMember.System_Array__Empty) as MethodSymbol;
if (arrayEmpty != null) // will be null if Array.Empty<T> doesn't exist in reference assemblies
{
_diagnostics.ReportUseSite(arrayEmpty, syntax);
// return an invocation of "Array.Empty<T>()"
arrayEmpty = arrayEmpty.Construct(ImmutableArray.Create(ats.ElementType));
return new BoundCall(
syntax,
null,
arrayEmpty,
ImmutableArray<BoundExpression>.Empty,
default(ImmutableArray<string>),
default(ImmutableArray<RefKind>),
isDelegateCall: false,
expanded: false,
invokedAsExtensionMethod: false,
argsToParamsOpt: default(ImmutableArray<int>),
defaultArguments: default(BitVector),
resultKind: LookupResultKind.Viable,
type: arrayEmpty.ReturnType);
}
}
return CreateParamArrayArgument(syntax, paramArrayType, arrayArgs, _compilation, this);
}
private static BoundExpression CreateParamArrayArgument(SyntaxNode syntax,
TypeSymbol paramArrayType,
ImmutableArray<BoundExpression> arrayArgs,
CSharpCompilation compilation,
LocalRewriter? localRewriter)
{
TypeSymbol int32Type = compilation.GetSpecialType(SpecialType.System_Int32);
BoundExpression arraySize = MakeLiteral(syntax, ConstantValue.Create(arrayArgs.Length), int32Type, localRewriter);
return new BoundArrayCreation(
syntax,
ImmutableArray.Create(arraySize),
new BoundArrayInitialization(syntax, isInferred: false, arrayArgs) { WasCompilerGenerated = true },
paramArrayType)
{ WasCompilerGenerated = true };
}
/// <summary>
/// To create literal expression for IOperation, set localRewriter to null.
/// </summary>
private static BoundExpression MakeLiteral(SyntaxNode syntax, ConstantValue constantValue, TypeSymbol type, LocalRewriter? localRewriter)
{
if (localRewriter != null)
{
return localRewriter.MakeLiteral(syntax, constantValue, type);
}
else
{
return new BoundLiteral(syntax, constantValue, type, constantValue.IsBad) { WasCompilerGenerated = true };
}
}
private static void OptimizeTemporaries(
BoundExpression[] arguments,
ArrayBuilder<BoundAssignmentOperator> storesToTemps,
ArrayBuilder<LocalSymbol> temporariesBuilder)
{
Debug.Assert(arguments != null);
Debug.Assert(storesToTemps != null);
Debug.Assert(temporariesBuilder != null);
if (storesToTemps.Count > 0)
{
int tempsNeeded = MergeArgumentsAndSideEffects(arguments, storesToTemps);
if (tempsNeeded > 0)
{
foreach (BoundAssignmentOperator s in storesToTemps)
{
if (s != null)
{
temporariesBuilder.Add(((BoundLocal)s.Left).LocalSymbol);
}
}
}
}
}
/// <summary>
/// Process tempStores and add them as side-effects to arguments where needed. The return
/// value tells how many temps are actually needed. For unnecessary temps the corresponding
/// temp store will be cleared.
/// </summary>
private static int MergeArgumentsAndSideEffects(
BoundExpression[] arguments,
ArrayBuilder<BoundAssignmentOperator> tempStores)
{
Debug.Assert(arguments != null);
Debug.Assert(tempStores != null);
int tempsRemainedInUse = tempStores.Count;
// Suppose we've got temporaries: t0 = A(), t1 = B(), t2 = C(), t4 = D(), t5 = E()
// and arguments: t0, t2, t1, t4, 10, t5
//
// We wish to produce arguments list: A(), SEQ(t1=B(), C()), t1, D(), 10, E()
//
// Our algorithm essentially finds temp stores that must happen before given argument
// load, and if there are any they become side effects of the given load.
// Stores immediately followed by loads of the same thing can be eliminated.
//
// Constraints:
// Stores must happen before corresponding loads.
// Stores cannot move relative to other stores. If arg was movable it would not need a temp.
int firstUnclaimedStore = 0;
for (int a = 0; a < arguments.Length; ++a)
{
var argument = arguments[a];
// if argument is a load, search for corresponding store. if store is found, extract
// the actual expression we were storing and add it as an argument - this one does
// not need a temp. if there are any unclaimed stores before the found one, add them
// as side effects that precede this arg, they cannot happen later.
// NOTE: missing optional parameters are not filled yet and therefore nulls - no need to do anything for them
if (argument?.Kind == BoundKind.Local)
{
var correspondingStore = -1;
for (int i = firstUnclaimedStore; i < tempStores.Count; i++)
{
if (tempStores[i].Left == argument)
{
correspondingStore = i;
break;
}
}
// store found?
if (correspondingStore != -1)
{
var value = tempStores[correspondingStore].Right;
Debug.Assert(value.Type is { });
// the matched store will not need to go into side-effects, only ones before it will
// remove the store to signal that we are not using its temp.
tempStores[correspondingStore] = null!;
tempsRemainedInUse--;
// no need for side-effects?
// just combine store and load
if (correspondingStore == firstUnclaimedStore)
{
arguments[a] = value;
}
else
{
var sideeffects = new BoundExpression[correspondingStore - firstUnclaimedStore];
for (int s = 0; s < sideeffects.Length; s++)
{
sideeffects[s] = tempStores[firstUnclaimedStore + s];
}
arguments[a] = new BoundSequence(
value.Syntax,
// this sequence does not own locals. Note that temps that
// we use for the rewrite are stored in one arg and loaded
// in another so they must live in a scope above.
ImmutableArray<LocalSymbol>.Empty,
sideeffects.AsImmutableOrNull(),
value,
value.Type);
}
firstUnclaimedStore = correspondingStore + 1;
}
}
}
Debug.Assert(firstUnclaimedStore == tempStores.Count, "not all side-effects were claimed");
return tempsRemainedInUse;
}
// Omit ref feature for COM interop: We can pass arguments by value for ref parameters if we are calling a method/property on an instance of a COM imported type.
// We should have ignored the 'ref' on the parameter during overload resolution for the given method call.
// If we had any ref omitted argument for the given call, we create a temporary local and
// replace the argument with the following BoundSequence: { side-effects: { temp = argument }, value = { ref temp } }
// NOTE: The temporary local must be scoped to live across the entire BoundCall node,
// otherwise the codegen optimizer might re-use the same temporary for multiple ref-omitted arguments for this call.
private void RewriteArgumentsForComCall(
ImmutableArray<ParameterSymbol> parameters,
BoundExpression[] actualArguments, //already re-ordered to match parameters
ArrayBuilder<RefKind> argsRefKindsBuilder,
ArrayBuilder<LocalSymbol> temporariesBuilder)
{
Debug.Assert(actualArguments != null);
Debug.Assert(actualArguments.Length == parameters.Length);
Debug.Assert(argsRefKindsBuilder != null);
Debug.Assert(argsRefKindsBuilder.Count == parameters.Length);
var argsCount = actualArguments.Length;
for (int argIndex = 0; argIndex < argsCount; ++argIndex)
{
RefKind paramRefKind = parameters[argIndex].RefKind;
RefKind argRefKind = argsRefKindsBuilder[argIndex];
// Rewrite only if the argument was passed with no ref/out and the
// parameter was declared ref.
if (argRefKind != RefKind.None || paramRefKind != RefKind.Ref)
{
continue;
}
var argument = actualArguments[argIndex];
if (argument.Kind == BoundKind.Local)
{
var localRefKind = ((BoundLocal)argument).LocalSymbol.RefKind;
if (localRefKind == RefKind.Ref)
{
// Already passing an address from the ref local.
continue;
}
Debug.Assert(localRefKind == RefKind.None);
}
BoundAssignmentOperator boundAssignmentToTemp;
BoundLocal boundTemp = _factory.StoreToTemp(argument, out boundAssignmentToTemp);
actualArguments[argIndex] = new BoundSequence(
argument.Syntax,
locals: ImmutableArray<LocalSymbol>.Empty,
sideEffects: ImmutableArray.Create<BoundExpression>(boundAssignmentToTemp),
value: boundTemp,
type: boundTemp.Type);
argsRefKindsBuilder[argIndex] = RefKind.Ref;
temporariesBuilder.Add(boundTemp.LocalSymbol);
}
}
public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node)
{
// InvokeMember operation:
if (node.Invoked)
{
return node;
}
// GetMember operation:
Debug.Assert(node.TypeArgumentsOpt.IsDefault);
var loweredReceiver = VisitExpression(node.Receiver);
return _dynamicFactory.MakeDynamicGetMember(loweredReceiver, node.Name, node.Indexed).ToExpression();
}
}
}
|