Browse Source

Emit a call to `ref Unsafe.As<TFrom, TTo>(ref TFrom)` when the IL code contains a mismatch of managed reference types.

The previously emitted `ref *(TTo*)(&source)` only compiles when `source` is a local variable; otherwise C# complains about the memory not being pinned.
Note that we special-case local variables to keep the previous behavior around; this avoids pulling in `System.Runtime.CompilerServices.Unsafe.dll` when it's unnecessary.
pull/2045/head
Daniel Grunwald 5 years ago
parent
commit
a9d643b208
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs
  2. 8
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 39
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

4
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs

@ -151,9 +151,9 @@ namespace System.Runtime.CompilerServices @@ -151,9 +151,9 @@ namespace System.Runtime.CompilerServices
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static ref TTo As<TFrom, TTo>(ref TFrom source)
public static ref TTo As<TFrom, TTo>(ref TFrom source)
{
return ref *(TTo*)(&source);
return ref Unsafe.As<TFrom, TTo>(ref source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

8
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1138,13 +1138,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1138,13 +1138,17 @@ namespace ICSharpCode.Decompiler.CSharp
return null;
}
internal TranslatedExpression CallUnsafeIntrinsic(string name, Expression[] arguments, IType returnType, ILInstruction inst)
internal TranslatedExpression CallUnsafeIntrinsic(string name, Expression[] arguments, IType returnType, ILInstruction inst = null, IEnumerable<IType> typeArguments = null)
{
var target = new MemberReferenceExpression {
Target = new TypeReferenceExpression(astBuilder.ConvertType(compilation.FindType(KnownTypeCode.Unsafe))),
MemberName = name
};
var invocation = new InvocationExpression(target, arguments).WithILInstruction(inst);
if (typeArguments != null) {
target.TypeArguments.AddRange(typeArguments.Select(astBuilder.ConvertType));
}
var invocationExpr = new InvocationExpression(target, arguments);
var invocation = inst != null ? invocationExpr.WithILInstruction(inst) : invocationExpr.WithoutILInstruction();
if (returnType is ByReferenceType brt) {
return WrapInRef(invocation.WithRR(new ResolveResult(brt.ElementType)), brt);
} else {

39
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -388,6 +388,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -388,6 +388,9 @@ namespace ICSharpCode.Decompiler.CSharp
return pointerExpr.ConvertTo(targetType, expressionBuilder);
}
if (targetType.Kind == TypeKind.ByReference) {
if (NormalizeTypeVisitor.TypeErasure.EquivalentTypes(targetType, this.Type)) {
return this;
}
var elementType = ((ByReferenceType)targetType).ElementType;
if (this.Expression is DirectionExpression thisDir && this.ILInstructions.Any(i => i.OpCode == OpCode.AddressOf)
&& thisDir.Expression.GetResolveResult()?.Type.GetStackType() == elementType.GetStackType()) {
@ -398,6 +401,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -398,6 +401,14 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(this.ILInstructions)
.WithRR(new ByReferenceResolveResult(convertedTemp.ResolveResult, ReferenceKind.Ref));
}
if (this.Type.Kind == TypeKind.ByReference && !IsFixedVariable()) {
// Convert between managed reference types.
// We can't do this by going through a pointer type because that would temporarily stop GC tracking.
// Instead, emit `ref Unsafe.As<T>(ref expr)`
return expressionBuilder.CallUnsafeIntrinsic("As", new[] { this.Expression },
typeArguments: new IType[] { ((ByReferenceType)this.Type).ElementType, elementType },
returnType: targetType);
}
// Convert from integer/pointer to reference.
// First, convert to the corresponding pointer type:
var arg = this.ConvertTo(new PointerType(elementType), expressionBuilder, checkForOverflow);
@ -472,7 +483,33 @@ namespace ICSharpCode.Decompiler.CSharp @@ -472,7 +483,33 @@ namespace ICSharpCode.Decompiler.CSharp
}
return castExpr.WithoutILInstruction().WithRR(rr);
}
bool IsFixedVariable()
{
if (this.Expression is DirectionExpression dirExpr) {
var inst = dirExpr.Expression.Annotation<ILInstruction>();
return inst != null && IsFixedVariable(inst);
} else {
return false;
}
}
/// <summary>
/// Returns true if <c>inst</c> computes the address of a fixed variable; false if it computes the address of a moveable variable.
/// (see "Fixed and moveable variables" in the C# specification)
/// </summary>
static bool IsFixedVariable(ILInstruction inst)
{
switch (inst) {
case LdLoca ldloca:
return ldloca.Variable.CaptureScope == null; // locals are fixed if uncaptured
case LdFlda ldflda:
return IsFixedVariable(ldflda.Target);
default:
return inst.ResultType == StackType.I;
}
}
/// <summary>
/// Gets whether an implicit conversion from 'inputType' to 'newTargetType'
/// would have the same semantics as the existing cast from 'inputType' to 'oldTargetType'.

Loading…
Cancel
Save