Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/Compilers/CSharp/Portable/CodeGen/CodeGenerator_RefSafety.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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;
using Microsoft.CodeAnalysis.CSharp.Symbols;

namespace Microsoft.CodeAnalysis.CSharp.CodeGen;

internal partial class CodeGenerator
{
private static bool MightEscapeTemporaryRefs(BoundCall node, bool used, AddressKind? receiverAddressKind)
{
return MightEscapeTemporaryRefs(
used: used,
returnType: node.Type,
returnRefKind: node.Method.RefKind,
receiverType: !node.Method.RequiresInstanceReceiver ? null : node.ReceiverOpt?.Type,
receiverScope: node.Method.TryGetThisParameter(out var thisParameter) ? thisParameter?.EffectiveScope : null,
receiverAddressKind: receiverAddressKind,
isReceiverReadOnly: node.Method.IsEffectivelyReadOnly,
parameters: node.Method.Parameters);
}

private static bool MightEscapeTemporaryRefs(BoundObjectCreationExpression node, bool used)
{
return MightEscapeTemporaryRefs(
used: used,
returnType: node.Type,
returnRefKind: RefKind.None,
receiverType: null,
receiverScope: null,
receiverAddressKind: null,
isReceiverReadOnly: false,
parameters: node.Constructor.Parameters);
}

private static bool MightEscapeTemporaryRefs(BoundFunctionPointerInvocation node, bool used)
{
FunctionPointerMethodSymbol method = node.FunctionPointer.Signature;
return MightEscapeTemporaryRefs(
used: used,
returnType: node.Type,
returnRefKind: method.RefKind,
receiverType: null,
receiverScope: null,
receiverAddressKind: null,
isReceiverReadOnly: false,
parameters: method.Parameters);
}

private static bool MightEscapeTemporaryRefs(
bool used,
TypeSymbol returnType,
RefKind returnRefKind,
TypeSymbol? receiverType,
ScopedKind? receiverScope,
AddressKind? receiverAddressKind,
bool isReceiverReadOnly,
ImmutableArray<ParameterSymbol> parameters)
{
Debug.Assert(receiverAddressKind is null || receiverType is not null);

// We check the signature of the method, counting potential `ref` sources and destinations
// to determine whether a `ref` can be captured by the method.
// The emit layer then uses this information to avoid reusing temporaries that are passed by ref to such methods.

// whether we have any outputs that can capture `ref`s
bool anyRefTargets = false;
// whether we have any inputs that can contain `ref`s
bool anyRefSources = false;
// NOTE: If there is at least one output and at least one input, a `ref` can be captured.

if (used && (returnRefKind != RefKind.None || returnType.IsRefLikeOrAllowsRefLikeType()))
{
// If returning by ref or returning a ref struct, the result might capture `ref`s.
anyRefTargets = true;
}

if (receiverType is not null)
{
Debug.Assert(receiverScope != null);
if (receiverType.IsRefLikeOrAllowsRefLikeType() && receiverScope != ScopedKind.ScopedValue)
{
anyRefSources = true;
if (!isReceiverReadOnly && !receiverType.IsReadOnly)
{
anyRefTargets = true;
}
}
else if (receiverAddressKind != null && receiverScope == ScopedKind.None)
{
anyRefSources = true;
}
}

if (anyRefTargets && anyRefSources)
{
return true;
}

foreach (var parameter in parameters)
{
if (parameter.Type.IsRefLikeOrAllowsRefLikeType() && parameter.EffectiveScope != ScopedKind.ScopedValue)
{
anyRefSources = true;
if (!parameter.Type.IsReadOnly && parameter.RefKind.IsWritableReference())
{
anyRefTargets = true;
}
}
else if (parameter.RefKind != RefKind.None && parameter.EffectiveScope == ScopedKind.None)
{
anyRefSources = true;
}

if (anyRefTargets && anyRefSources)
{
return true;
}
}

return false;
}
}
42 changes: 42 additions & 0 deletions src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1660,7 +1660,13 @@ private void EmitStaticCallExpression(BoundCall call, UseKind useKind)

Debug.Assert(method.IsStatic);

var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals();

EmitArguments(arguments, method.Parameters, call.ArgumentRefKindsOpt);

_builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore,
MightEscapeTemporaryRefs(call, used: useKind != UseKind.Unused, receiverAddressKind: null));

int stackBehavior = GetCallStackBehavior(method, arguments);

if (method.IsAbstract || method.IsVirtual)
Expand Down Expand Up @@ -1690,6 +1696,8 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind)
bool box;
LocalDefinition tempOpt;

var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals();

if (receiverIsInstanceCall(call, out BoundCall nested))
{
var calls = ArrayBuilder<BoundCall>.GetInstance();
Expand Down Expand Up @@ -1755,6 +1763,14 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind)
}

emitArgumentsAndCallEpilogue(call, callKind, receiverUseKind);

_builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, MightEscapeTemporaryRefs(
call,
used: true,
receiverAddressKind: receiverUseKind != UseKind.UsedAsAddress ? null : addressKind));

countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals();

FreeOptTemp(tempOpt);
tempOpt = null;

Expand Down Expand Up @@ -1815,6 +1831,12 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind)
}

emitArgumentsAndCallEpilogue(call, callKind, useKind);

_builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, MightEscapeTemporaryRefs(
call,
used: useKind != UseKind.Unused,
receiverAddressKind: useKind != UseKind.UsedAsAddress ? null : addressKind));

FreeOptTemp(tempOpt);

return;
Expand Down Expand Up @@ -2446,8 +2468,14 @@ private void EmitObjectCreationExpression(BoundObjectCreationExpression expressi
}

// none of the above cases, so just create an instance

var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals();

EmitArguments(expression.Arguments, constructor.Parameters, expression.ArgumentRefKindsOpt);

_builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore,
MightEscapeTemporaryRefs(expression, used));

var stackAdjustment = GetObjCreationStackBehavior(expression);
_builder.EmitOpCode(ILOpCode.Newobj, stackAdjustment);

Expand Down Expand Up @@ -2704,7 +2732,14 @@ private bool TryInPlaceCtorCall(BoundExpression target, BoundObjectCreationExpre
Debug.Assert(temp == null, "in-place ctor target should not create temps");

var constructor = objCreation.Constructor;

var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals();

EmitArguments(objCreation.Arguments, constructor.Parameters, objCreation.ArgumentRefKindsOpt);

_builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore,
MightEscapeTemporaryRefs(objCreation, used));

// -2 to adjust for consumed target address and not produced value.
var stackAdjustment = GetObjCreationStackBehavior(objCreation) - 2;
_builder.EmitOpCode(ILOpCode.Call, stackAdjustment);
Expand Down Expand Up @@ -4027,7 +4062,14 @@ private void EmitCalli(BoundFunctionPointerInvocation ptrInvocation, UseKind use
}

FunctionPointerMethodSymbol method = ptrInvocation.FunctionPointer.Signature;

var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals();

EmitArguments(ptrInvocation.Arguments, method.Parameters, ptrInvocation.ArgumentRefKindsOpt);

_builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore,
MightEscapeTemporaryRefs(ptrInvocation, used: useKind != UseKind.Unused));

var stackBehavior = GetCallStackBehavior(ptrInvocation.FunctionPointer.Signature, ptrInvocation.Arguments);

if (temp is object)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,20 @@ public override ImmutableArray<ParameterSymbol> Parameters
}
}

internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter)
{
if (UnderlyingMethod.TryGetThisParameter(out ParameterSymbol? underlyingThisParameter))
{
thisParameter = underlyingThisParameter != null
? new ThisParameterSymbol(this, _container)
: null;
return true;
}

thisParameter = null;
return false;
}

public override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations => ImmutableArray<MethodSymbol>.Empty;

public override ImmutableArray<CustomModifier> RefCustomModifiers => UnderlyingMethod.RefCustomModifiers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,8 @@ static ref readonly int M(in int x)
// Code size 72 (0x48)
.maxstack 3
.locals init (int V_0,
int V_1)
int V_1,
int V_2)
IL_0000: ldc.i4.s 42
IL_0002: stloc.0
IL_0003: ldloca.s V_0
Expand All @@ -870,22 +871,22 @@ .locals init (int V_0,
IL_000b: call ""void System.Console.WriteLine(int)""
IL_0010: newobj ""Program..ctor()""
IL_0015: ldc.i4.5
IL_0016: stloc.0
IL_0017: ldloca.s V_0
IL_0016: stloc.1
IL_0017: ldloca.s V_1
IL_0019: ldc.i4.6
IL_001a: stloc.1
IL_001b: ldloca.s V_1
IL_001a: stloc.2
IL_001b: ldloca.s V_2
IL_001d: call ""int Program.this[in int, in int].get""
IL_0022: call ""void System.Console.WriteLine(int)""
IL_0027: ldc.i4.s 42
IL_0029: stloc.0
IL_002a: ldloca.s V_0
IL_0029: stloc.1
IL_002a: ldloca.s V_1
IL_002c: call ""ref readonly int Program.M(in int)""
IL_0031: ldind.i4
IL_0032: call ""void System.Console.WriteLine(int)""
IL_0037: ldc.i4.s 42
IL_0039: stloc.0
IL_003a: ldloca.s V_0
IL_0039: stloc.2
IL_003a: ldloca.s V_2
IL_003c: call ""ref readonly int Program.M(in int)""
IL_0041: ldind.i4
IL_0042: call ""void System.Console.WriteLine(int)""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1628,7 +1628,8 @@ public static void F(S p)
.maxstack 4
.locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0,
S V_1, //x
S V_2)
S V_2,
S V_3)
// sequence point: <hidden>
IL_0000: ldtoken ""void S.F(S)""
IL_0005: call ""Microsoft.CodeAnalysis.Runtime.LocalStoreTracker Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogMethodEntry(int)""
Expand Down Expand Up @@ -1659,8 +1660,8 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0,
IL_0043: ldarg.0
IL_0044: dup
IL_0045: stloc.1
IL_0046: stloc.2
IL_0047: ldloca.s V_2
IL_0046: stloc.3
IL_0047: ldloca.s V_3
IL_0049: constrained. ""S""
IL_004f: callvirt ""string object.ToString()""
IL_0054: ldc.i4.1
Expand Down
Loading
Loading