Browse Source

Fix #2215: Use `Unsafe` intrinsics for `ldobj`/`stobj` instructions.

Use `Unsafe.As` to adjust the type of a managed reference if necessary (without converting to a pointer type).
This also adds support for the `ReadUnaligned`/`WriteUnaligned` intrinsics.
pull/2218/head
Daniel Grunwald 5 years ago
parent
commit
782e4ae88c
  1. 65
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs
  2. 103
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il
  3. 111
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 2
      ICSharpCode.Decompiler/IL/Instructions.cs
  5. 3
      ICSharpCode.Decompiler/IL/Instructions.tt

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

@ -98,6 +98,59 @@ internal sealed class ExtraUnsafeTests
int num = *ptr; int num = *ptr;
} }
} }
private static ref float AddressTypeMismatch(ref int A_0)
{
return ref Unsafe.As<int, float>(ref A_0);
}
private unsafe static ref float AddressTypeMismatch(int* A_0)
{
return ref *(float*)A_0;
}
private static float LoadWithTypeMismatch(ref int A_0)
{
return Unsafe.As<int, float>(ref A_0);
}
private unsafe static float LoadWithTypeMismatch(int* A_0)
{
return *(float*)A_0;
}
private static void StoreWithTypeMismatch(ref int A_0)
{
Unsafe.As<int, float>(ref A_0) = 1f;
}
private unsafe static void StoreWithTypeMismatch(int* A_0)
{
*(float*)A_0 = 1f;
}
private static ref float AddressOfFieldTypeMismatch(ref int A_0)
{
return ref Unsafe.As<int, SomeStruct>(ref A_0).float_field;
}
private unsafe static ref float AddressOfFieldTypeMismatch(int* A_0)
{
return ref ((SomeStruct*)A_0)->float_field;
}
private static float LoadOfFieldTypeMismatch(ref int A_0)
{
return Unsafe.As<int, SomeStruct>(ref A_0).float_field;
}
private unsafe static float LoadOfFieldTypeMismatch(int* A_0)
{
return ((SomeStruct*)A_0)->float_field;
}
private static void StoreOfFieldTypeMismatch(ref int A_0)
{
Unsafe.As<int, SomeStruct>(ref A_0).float_field = 1f;
}
private unsafe static void StoreOfFieldTypeMismatch(int* A_0)
{
((SomeStruct*)A_0)->float_field = 1f;
}
}
internal struct SomeStruct
{
public int int_field;
public float float_field;
} }
namespace System.Runtime.CompilerServices namespace System.Runtime.CompilerServices
@ -113,13 +166,13 @@ namespace System.Runtime.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static T ReadUnaligned<T>(void* source) public unsafe static T ReadUnaligned<T>(void* source)
{ {
return *(T*)source; return Unsafe.ReadUnaligned<T>(source);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static T ReadUnaligned<T>(ref byte source) public static T ReadUnaligned<T>(ref byte source)
{ {
return *(T*)(&source); return Unsafe.ReadUnaligned<T>(ref source);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -131,13 +184,13 @@ namespace System.Runtime.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static void WriteUnaligned<T>(void* destination, T value) public unsafe static void WriteUnaligned<T>(void* destination, T value)
{ {
*(T*)destination = value; Unsafe.WriteUnaligned(destination, value);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static void WriteUnaligned<T>(ref byte destination, T value) public static void WriteUnaligned<T>(ref byte destination, T value)
{ {
*(T*)(&destination) = value; Unsafe.WriteUnaligned(ref destination, value);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

103
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il

@ -681,4 +681,105 @@ lbl:
} // end of method Issue2189 } // end of method Issue2189
}
.method private hidebysig static
float32& AddressTypeMismatch (int32&) cil managed
{
ldarg.0
ret
}
.method private hidebysig static
float32& AddressTypeMismatch (int32*) cil managed
{
ldarg.0
ret
}
.method private hidebysig static
float32 LoadWithTypeMismatch (int32&) cil managed
{
ldarg.0
ldind.r4
ret
}
.method private hidebysig static
float32 LoadWithTypeMismatch (int32*) cil managed
{
ldarg.0
ldind.r4
ret
}
.method private hidebysig static
void StoreWithTypeMismatch (int32&) cil managed
{
ldarg.0
ldc.r4 1
stind.r4
ret
}
.method private hidebysig static
void StoreWithTypeMismatch (int32*) cil managed
{
ldarg.0
ldc.r4 1
stind.r4
ret
}
.method private hidebysig static
float32& AddressOfFieldTypeMismatch (int32&) cil managed
{
ldarg.0
ldflda float32 SomeStruct::float_field
ret
}
.method private hidebysig static
float32& AddressOfFieldTypeMismatch (int32*) cil managed
{
ldarg.0
ldflda float32 SomeStruct::float_field
ret
}
.method private hidebysig static
float32 LoadOfFieldTypeMismatch (int32&) cil managed
{
ldarg.0
ldfld float32 SomeStruct::float_field
ret
}
.method private hidebysig static
float32 LoadOfFieldTypeMismatch (int32*) cil managed
{
ldarg.0
ldfld float32 SomeStruct::float_field
ret
}
.method private hidebysig static
void StoreOfFieldTypeMismatch (int32&) cil managed
{
ldarg.0
ldc.r4 1
stfld float32 SomeStruct::float_field
ret
}
.method private hidebysig static
void StoreOfFieldTypeMismatch (int32*) cil managed
{
ldarg.0
ldc.r4 1
stfld float32 SomeStruct::float_field
ret
}
} // class ExtraUnsafeTests
.class private sequential ansi sealed beforefieldinit SomeStruct
extends [mscorlib]System.ValueType
{
// Fields
.field public int32 int_field
.field public float32 float_field
} // end of class SomeStruct

111
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2541,6 +2541,27 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitLdObj(LdObj inst, TranslationContext context) protected internal override TranslatedExpression VisitLdObj(LdObj inst, TranslationContext context)
{ {
if (inst.UnalignedPrefix != 0)
{
// Use one of: Unsafe.ReadUnaligned<T>(void*)
// or: Unsafe.ReadUnaligned<T>(ref byte)
var pointer = Translate(inst.Target);
if (pointer.Expression is DirectionExpression)
{
pointer = pointer.ConvertTo(new ByReferenceType(compilation.FindType(KnownTypeCode.Byte)), this);
}
else
{
pointer = pointer.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), this, allowImplicitConversion: true);
}
return CallUnsafeIntrinsic(
name: "ReadUnaligned",
arguments: new Expression[] { pointer },
returnType: inst.Type,
inst: inst,
typeArguments: new IType[] { inst.Type }
);
}
var result = LdObj(inst.Target, inst.Type); var result = LdObj(inst.Target, inst.Type);
//if (target.Type.IsSmallIntegerType() && loadType.IsSmallIntegerType() && target.Type.GetSign() != loadType.GetSign()) //if (target.Type.IsSmallIntegerType() && loadType.IsSmallIntegerType() && target.Type.GetSign() != loadType.GetSign())
// return result.ConvertTo(loadType, this); // return result.ConvertTo(loadType, this);
@ -2589,37 +2610,66 @@ namespace ICSharpCode.Decompiler.CSharp
else else
{ {
// We need to cast the pointer type: // We need to cast the pointer type:
target = target.ConvertTo(new PointerType(loadType), this); if (target.Expression is DirectionExpression)
return new UnaryOperatorExpression(UnaryOperatorType.Dereference, target.Expression) {
.WithRR(new ResolveResult(loadType)); target = target.ConvertTo(new ByReferenceType(loadType), this);
}
else
{
target = target.ConvertTo(new PointerType(loadType), this);
}
if (target.Expression is DirectionExpression dirExpr)
{
return target.UnwrapChild(dirExpr.Expression);
}
else
{
return new UnaryOperatorExpression(UnaryOperatorType.Dereference, target.Expression)
.WithRR(new ResolveResult(loadType));
}
} }
} }
protected internal override TranslatedExpression VisitStObj(StObj inst, TranslationContext context) protected internal override TranslatedExpression VisitStObj(StObj inst, TranslationContext context)
{ {
if (inst.UnalignedPrefix != 0)
{
return UnalignedStObj(inst);
}
var pointer = Translate(inst.Target); var pointer = Translate(inst.Target);
TranslatedExpression target; TranslatedExpression target;
TranslatedExpression value = default; TranslatedExpression value = default;
if (pointer.Expression is DirectionExpression && TypeUtils.IsCompatiblePointerTypeForMemoryAccess(pointer.Type, inst.Type)) // Cast pointer type if necessary:
if (!TypeUtils.IsCompatiblePointerTypeForMemoryAccess(pointer.Type, inst.Type))
{
value = Translate(inst.Value, typeHint: inst.Type);
IType castTargetType;
if (TypeUtils.IsCompatibleTypeForMemoryAccess(value.Type, inst.Type))
{
castTargetType = value.Type;
}
else
{
castTargetType = inst.Type;
}
if (pointer.Expression is DirectionExpression)
{
pointer = pointer.ConvertTo(new ByReferenceType(castTargetType), this);
}
else
{
pointer = pointer.ConvertTo(new PointerType(castTargetType), this);
}
}
if (pointer.Expression is DirectionExpression)
{ {
// we can deference the managed reference by stripping away the 'ref' // we can deference the managed reference by stripping away the 'ref'
target = pointer.UnwrapChild(((DirectionExpression)pointer.Expression).Expression); target = pointer.UnwrapChild(((DirectionExpression)pointer.Expression).Expression);
} }
else else
{ {
// Cast pointer type if necessary:
if (!TypeUtils.IsCompatiblePointerTypeForMemoryAccess(pointer.Type, inst.Type))
{
value = Translate(inst.Value, typeHint: inst.Type);
if (TypeUtils.IsCompatibleTypeForMemoryAccess(value.Type, inst.Type))
{
pointer = pointer.ConvertTo(new PointerType(value.Type), this);
}
else
{
pointer = pointer.ConvertTo(new PointerType(inst.Type), this);
}
}
if (pointer.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.AddressOf) if (pointer.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.AddressOf)
{ {
// *&ptr -> ptr // *&ptr -> ptr
@ -2639,6 +2689,33 @@ namespace ICSharpCode.Decompiler.CSharp
return Assignment(target, value).WithILInstruction(inst); return Assignment(target, value).WithILInstruction(inst);
} }
private TranslatedExpression UnalignedStObj(StObj inst)
{
// "unaligned.1; stobj" -> decompile to a call of
// Unsafe.WriteUnaligned<T>(void*, T)
// or Unsafe.WriteUnaligned<T>(ref byte, T)
var pointer = Translate(inst.Target);
var value = Translate(inst.Value, typeHint: inst.Type);
if (pointer.Expression is DirectionExpression)
{
pointer = pointer.ConvertTo(new ByReferenceType(compilation.FindType(KnownTypeCode.Byte)), this);
}
else
{
pointer = pointer.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), this, allowImplicitConversion: true);
}
if (!TypeUtils.IsCompatibleTypeForMemoryAccess(value.Type, inst.Type))
{
value = value.ConvertTo(inst.Type, this);
}
return CallUnsafeIntrinsic(
name: "WriteUnaligned",
arguments: new Expression[] { pointer, value },
returnType: compilation.FindType(KnownTypeCode.Void),
inst: inst
);
}
protected internal override TranslatedExpression VisitLdLen(LdLen inst, TranslationContext context) protected internal override TranslatedExpression VisitLdLen(LdLen inst, TranslationContext context)
{ {
IType arrayType = compilation.FindType(KnownTypeCode.Array); IType arrayType = compilation.FindType(KnownTypeCode.Array);

2
ICSharpCode.Decompiler/IL/Instructions.cs

@ -4143,7 +4143,7 @@ namespace ICSharpCode.Decompiler.IL
public bool IsVolatile { get; set; } public bool IsVolatile { get; set; }
/// <summary>Returns the alignment specified by the 'unaligned' prefix; or 0 if there was no 'unaligned' prefix.</summary> /// <summary>Returns the alignment specified by the 'unaligned' prefix; or 0 if there was no 'unaligned' prefix.</summary>
public byte UnalignedPrefix { get; set; } public byte UnalignedPrefix { get; set; }
public override StackType ResultType { get { return type.GetStackType(); } } public override StackType ResultType { get { return UnalignedPrefix == 0 ? type.GetStackType() : StackType.Void; } }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {
return target.Flags | value.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; return target.Flags | value.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow;

3
ICSharpCode.Decompiler/IL/Instructions.tt

@ -254,7 +254,8 @@
new OpCode("stobj", "Indirect store (store to ref/pointer)." + Environment.NewLine new OpCode("stobj", "Indirect store (store to ref/pointer)." + Environment.NewLine
+ "Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())", + "Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())",
CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal, CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()"), SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow,
ResultType("UnalignedPrefix == 0 ? type.GetStackType() : StackType.Void"),
CustomInvariant("CheckTargetSlot();")), CustomInvariant("CheckTargetSlot();")),
new OpCode("box", "Boxes a value.", new OpCode("box", "Boxes a value.",

Loading…
Cancel
Save