Browse Source

Avoid using inline assignments if they truncate the input value.

pull/960/head
Daniel Grunwald 8 years ago
parent
commit
c7e60a9b3c
  1. 427
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
  2. 4
      ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs
  3. 63
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs
  4. 3
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

427
ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il

@ -1,12 +1,12 @@
.assembly extern mscorlib .assembly extern mscorlib
{ {
.publickeytoken = ( b7 7a 5c 56 19 34 e0 89 ) .publickeytoken = ( b7 7a 5c 56 19 34 e0 89 )
.ver 4:0:0:0 .ver 4:0:0:0
} }
.assembly 'StackTypes' .assembly 'StackTypes'
{ {
.ver 0:0:0:0 .ver 0:0:0:0
} }
.module StackTypes.exe .module StackTypes.exe
@ -15,204 +15,227 @@
.class private auto ansi abstract sealed beforefieldinit Program .class private auto ansi abstract sealed beforefieldinit Program
extends [mscorlib]System.Object extends [mscorlib]System.Object
{ {
.method public hidebysig static void Main (string[] args) cil managed .method public hidebysig static void Main (string[] args) cil managed
{ {
.maxstack 8 .maxstack 8
.entrypoint .entrypoint
//call void Program::Int32OrNativeTests() call void Program::InlineAssignByte()
//call void Program::Int32OrNativeTests()
ret
} // end of method Main ret
} // end of method Main
/*
.method public static void Int32OrNativeTests() .method public static void InlineAssignByte()
{ {
ldstr "Int32OrNative(0x7fffffff, false) = {0}" .locals init (
ldc.i4 0x7fffffff int8 local
ldc.i4 0 )
call native int Program::Int32OrNative(int32, bool) ldstr "InlineAssignByte: WriteLine(local = {0})"
box native int ldc.i4 300
call void [mscorlib]System.Console::WriteLine(string, object) dup
br pointless // this pointless branch is a workaround for https://github.com/dotnet/coreclr/issues/14784
ldstr "Int32OrNative(0x7fffffff, true) = {0}" // it doesn't have any effect on ILSpy as TransformAssignment runs after control-flow reconstruction
ldc.i4 0x7fffffff pointless:
ldc.i4 1 // This assignment cannot be turned into a C# inline assignment, because doing so would truncate to 8 bits.
call native int Program::Int32OrNative(int32, bool) stloc.0
box native int box int32
call void [mscorlib]System.Console::WriteLine(string, object) call void [mscorlib]System.Console::WriteLine(string, object)
ldstr "InlineAssignByte: local is {0}"
ldstr "Int32OrNative(-1, false) = {0}" ldloc.0
ldc.i4.m1 box int32
ldc.i4 0 call void [mscorlib]System.Console::WriteLine(string, object)
call native int Program::Int32OrNative(int32, bool) ret
box native int }
call void [mscorlib]System.Console::WriteLine(string, object)
/*
ldstr "Int32OrNative(-1, true) = {0}" .method public static void Int32OrNativeTests()
ldc.i4.m1 {
ldc.i4 1 ldstr "Int32OrNative(0x7fffffff, false) = {0}"
call native int Program::Int32OrNative(int32, bool) ldc.i4 0x7fffffff
box native int ldc.i4 0
call void [mscorlib]System.Console::WriteLine(string, object) call native int Program::Int32OrNative(int32, bool)
box native int
ldstr "Int32OrNativeLoopStyle(0x7fffffff):" call void [mscorlib]System.Console::WriteLine(string, object)
call void [mscorlib]System.Console::WriteLine(string)
ldc.i4 0x7fffffff ldstr "Int32OrNative(0x7fffffff, true) = {0}"
call void Program::Int32OrNativeLoopStyle(int32) ldc.i4 0x7fffffff
ldc.i4 1
ldstr "Int32OrNativeLoopStyle(-1):" call native int Program::Int32OrNative(int32, bool)
call void [mscorlib]System.Console::WriteLine(string) box native int
ldc.i4.m1 call void [mscorlib]System.Console::WriteLine(string, object)
call void Program::Int32OrNativeLoopStyle(int32)
ldstr "Int32OrNative(-1, false) = {0}"
ldstr "Int32OrNativeDeadCode(0x7fffffff) = {0}" ldc.i4.m1
ldc.i4 0x7fffffff ldc.i4 0
call native int Program::Int32OrNativeDeadCode(int32) call native int Program::Int32OrNative(int32, bool)
box native int box native int
call void [mscorlib]System.Console::WriteLine(string, object) call void [mscorlib]System.Console::WriteLine(string, object)
ldstr "Int32OrNativeDeadCode(-1) = {0}" ldstr "Int32OrNative(-1, true) = {0}"
ldc.i4.m1 ldc.i4.m1
call native int Program::Int32OrNativeDeadCode(int32) ldc.i4 1
box native int call native int Program::Int32OrNative(int32, bool)
call void [mscorlib]System.Console::WriteLine(string, object) box native int
call void [mscorlib]System.Console::WriteLine(string, object)
ldc.i4 0x7fffffff
call void Program::RunInt32OrNativeMultiUse(int32) ldstr "Int32OrNativeLoopStyle(0x7fffffff):"
ldc.i4.m1 call void [mscorlib]System.Console::WriteLine(string)
call void Program::RunInt32OrNativeMultiUse(int32) ldc.i4 0x7fffffff
call void Program::Int32OrNativeLoopStyle(int32)
ret
} ldstr "Int32OrNativeLoopStyle(-1):"
.method public static native int Int32OrNative(int32 val, bool use_native) call void [mscorlib]System.Console::WriteLine(string)
{ ldc.i4.m1
ldarg.1 call void Program::Int32OrNativeLoopStyle(int32)
brtrue use_native_int
use_i4: ldstr "Int32OrNativeDeadCode(0x7fffffff) = {0}"
ldarg.0 ldc.i4 0x7fffffff
br after_if call native int Program::Int32OrNativeDeadCode(int32)
after_if: box native int
ldc.i4.1 call void [mscorlib]System.Console::WriteLine(string, object)
add
ret ldstr "Int32OrNativeDeadCode(-1) = {0}"
use_native_int: ldc.i4.m1
ldarg.0 call native int Program::Int32OrNativeDeadCode(int32)
conv.u box native int
br after_if call void [mscorlib]System.Console::WriteLine(string, object)
}
ldc.i4 0x7fffffff
.method public static void Int32OrNativeLoopStyle(int32 val) call void Program::RunInt32OrNativeMultiUse(int32)
{ ldc.i4.m1
.locals init ( call void Program::RunInt32OrNativeMultiUse(int32)
int32 i
) ret
ldarg.0 }
loop: .method public static native int Int32OrNative(int32 val, bool use_native)
ldc.i4.1 {
add ldarg.1
call void Program::Print(native int) brtrue use_native_int
ldloc.0 use_i4:
brtrue end ldarg.0
br after_if
ldc.i4.1 after_if:
stloc.0 ldc.i4.1
ldarg.0 add
conv.u ret
br loop use_native_int:
end: ldarg.0
ret conv.u
} br after_if
}
.method public static native int Int32OrNativeDeadCode(int32 val)
{ .method public static void Int32OrNativeLoopStyle(int32 val)
use_i4: {
ldarg.0 .locals init (
br after_if int32 i
after_if: )
ldc.i4.1 ldarg.0
add loop:
ret ldc.i4.1
use_native_int: // dead code add
ldarg.0 call void Program::Print(native int)
conv.u ldloc.0
br after_if brtrue end
}
ldc.i4.1
.method public static void RunInt32OrNativeMultiUse(int32 val) stloc.0
{ ldarg.0
ldstr "RunInt32OrNativeMultiUse({0}, push_i: false, use2: false) = {1}" conv.u
ldarg val br loop
box int32 end:
ldarg val ret
ldc.i4 0 // push_i }
ldc.i4 0 // use2
call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2) .method public static native int Int32OrNativeDeadCode(int32 val)
box native int {
call void [mscorlib]System.Console::WriteLine(string, object, object) use_i4:
ldarg.0
ldstr "RunInt32OrNativeMultiUse({0}, push_i: false, use2: true) = {1}" br after_if
ldarg val after_if:
box int32 ldc.i4.1
ldarg val add
ldc.i4 0 // push_i ret
ldc.i4 1 // use2 use_native_int: // dead code
call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2) ldarg.0
box native int conv.u
call void [mscorlib]System.Console::WriteLine(string, object, object) br after_if
}
ldstr "RunInt32OrNativeMultiUse({0}, push_i: true, use2: false) = {1}"
ldarg val .method public static void RunInt32OrNativeMultiUse(int32 val)
box int32 {
ldarg val ldstr "RunInt32OrNativeMultiUse({0}, push_i: false, use2: false) = {1}"
ldc.i4 1 // push_i ldarg val
ldc.i4 0 // use2 box int32
call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2) ldarg val
box native int ldc.i4 0 // push_i
call void [mscorlib]System.Console::WriteLine(string, object, object) ldc.i4 0 // use2
call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2)
ldstr "RunInt32OrNativeMultiUse({0}, push_i: true, use2: true) = {1}" box native int
ldarg val call void [mscorlib]System.Console::WriteLine(string, object, object)
box int32
ldarg val ldstr "RunInt32OrNativeMultiUse({0}, push_i: false, use2: true) = {1}"
ldc.i4 1 // push_i ldarg val
ldc.i4 1 // use2 box int32
call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2) ldarg val
box native int ldc.i4 0 // push_i
call void [mscorlib]System.Console::WriteLine(string, object, object) ldc.i4 1 // use2
ret call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2)
} box native int
call void [mscorlib]System.Console::WriteLine(string, object, object)
.method public static native int Int32OrNativeMultiUse(int32 val, bool push_i, bool use2)
{ ldstr "RunInt32OrNativeMultiUse({0}, push_i: true, use2: false) = {1}"
ldarg.1 ldarg val
brtrue push_i box int32
br push_i4 ldarg val
push_i4: ldc.i4 1 // push_i
ldarg.0 ldc.i4 0 // use2
ldarg.2 call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2)
brtrue use2 box native int
br use1 call void [mscorlib]System.Console::WriteLine(string, object, object)
push_i:
ldarg.0 ldstr "RunInt32OrNativeMultiUse({0}, push_i: true, use2: true) = {1}"
conv.u ldarg val
br use1 box int32
use1: ldarg val
ldc.i4.1 ldc.i4 1 // push_i
add ldc.i4 1 // use2
ret call native int Program::Int32OrNativeMultiUse(int32 val, bool push_i, bool use2)
use2: box native int
ldc.i4.2 call void [mscorlib]System.Console::WriteLine(string, object, object)
add ret
ret }
}
*/ .method public static native int Int32OrNativeMultiUse(int32 val, bool push_i, bool use2)
{
ldarg.1
brtrue push_i
br push_i4
push_i4:
ldarg.0
ldarg.2
brtrue use2
br use1
push_i:
ldarg.0
conv.u
br use1
use1:
ldc.i4.1
add
ret
use2:
ldc.i4.2
add
ret
}
*/
.method public static void Print(native int val) .method public static void Print(native int val)
{ {
ldarg.0 ldarg.0
box native int box native int
call void [mscorlib]System.Console::WriteLine(object) call void [mscorlib]System.Console::WriteLine(object)
ret ret
} }
} }

4
ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs

@ -140,6 +140,10 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks> /// </remarks>
public int IndexOf(T item) public int IndexOf(T item)
{ {
if (item == null) {
// InstructionCollection can't contain nulls
return -1;
}
// If this collection is the item's primary position, we can use ChildIndex: // If this collection is the item's primary position, we can use ChildIndex:
int index = item.ChildIndex - firstChildIndex; int index = item.ChildIndex - firstChildIndex;
if (index >= 0 && index < list.Count && list[index] == item) if (index >= 0 && index < list.Count && list[index] == item)

63
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
@ -70,45 +71,43 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// stloc s(stobj (..., value)) /// stloc s(stobj (..., value))
/// </code> /// </code>
/// e.g. used for inline assignment to static field /// e.g. used for inline assignment to static field
bool TransformInlineAssignmentStObj(Block block, int i) bool TransformInlineAssignmentStObj(Block block, int pos)
{ {
var inst = block.Instructions[i] as StLoc; var inst = block.Instructions[pos] as StLoc;
// in some cases it can be a compiler-generated local // in some cases it can be a compiler-generated local
if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local)) if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local))
return false; return false;
var nextInst = block.Instructions.ElementAtOrDefault(i + 1); if (IsImplicitTruncation(inst.Value, inst.Variable.Type)) {
ILInstruction replacement; // 'stloc s' is implicitly truncating the value
StObj fieldStore; return false;
}
ILVariable local; ILVariable local;
if (nextInst is StLoc localStore) { // with extra local int nextPos;
if (block.Instructions[pos + 1] is StLoc localStore) { // with extra local
if (localStore.Variable.Kind != VariableKind.Local || !localStore.Value.MatchLdLoc(inst.Variable)) if (localStore.Variable.Kind != VariableKind.Local || !localStore.Value.MatchLdLoc(inst.Variable))
return false; return false;
if (!(inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 2)) if (!(inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 2))
return false; return false;
var memberStore = block.Instructions.ElementAtOrDefault(i + 2);
if (memberStore is StObj) {
fieldStore = memberStore as StObj;
if (!fieldStore.Value.MatchLdLoc(inst.Variable) || localStore.Variable.IsUsedWithin(fieldStore.Target))
return false;
replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type);
} else {
return false;
}
context.Step("Inline assignment stobj (with extra local)", fieldStore);
local = localStore.Variable; local = localStore.Variable;
block.Instructions.RemoveAt(i + 1); nextPos = pos + 2;
} else if (nextInst is StObj) { // without extra local
fieldStore = (StObj)nextInst;
if (!fieldStore.Value.MatchLdLoc(inst.Variable) || inst.Variable.IsUsedWithin(fieldStore.Target))
return false;
context.Step("Inline assignment stobj", fieldStore);
local = inst.Variable;
replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type);
} else { } else {
local = inst.Variable;
localStore = null;
nextPos = pos + 1;
}
if (!(block.Instructions[nextPos] is StObj stobj))
return false;
if (!stobj.Value.MatchLdLoc(inst.Variable) || inst.Variable.IsUsedWithin(stobj.Target))
return false;
if (IsImplicitTruncation(inst.Value, stobj.Type)) {
// 'stloc s' is implicitly truncating the value
return false; return false;
} }
block.Instructions.RemoveAt(i + 1); context.Step("Inline assignment stobj", stobj);
inst.ReplaceWith(new StLoc(local, replacement)); block.Instructions.Remove(localStore);
block.Instructions.Remove(stobj);
stobj.Value = inst.Value;
inst.ReplaceWith(new StLoc(local, stobj));
return true; return true;
} }
@ -253,8 +252,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (inst.Variable.Kind != VariableKind.StackSlot) if (inst.Variable.Kind != VariableKind.StackSlot)
return false; return false;
Debug.Assert(!inst.Variable.Type.IsSmallIntegerType());
if (nextInst.Variable.Kind != VariableKind.Local || !nextInst.Value.MatchLdLoc(inst.Variable)) if (nextInst.Variable.Kind != VariableKind.Local || !nextInst.Value.MatchLdLoc(inst.Variable))
return false; return false;
if (IsImplicitTruncation(inst.Value, nextInst.Variable.Type)) {
// 'stloc l' is implicitly truncating the stack value
return false;
}
context.Step("Inline assignment to local variable", inst); context.Step("Inline assignment to local variable", inst);
var value = inst.Value; var value = inst.Value;
var var = nextInst.Variable; var var = nextInst.Variable;
@ -263,7 +267,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value))); nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value)));
return true; return true;
} }
bool IsImplicitTruncation(ILInstruction value, IType type)
{
return type.IsSmallIntegerType();
}
/// <code> /// <code>
/// stloc s(ldloc l) /// stloc s(ldloc l)
/// stloc l(binary.op(ldloc s, ldc.i4 1)) /// stloc l(binary.op(ldloc s, ldc.i4 1))

3
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -106,7 +106,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary> /// </summary>
public static bool IsSmallIntegerType(this IType type) public static bool IsSmallIntegerType(this IType type)
{ {
return GetSize(type) < 4; int size = GetSize(type);
return size > 0 && size < 4;
} }
/// <summary> /// <summary>

Loading…
Cancel
Save