Browse Source

#2050: Allow inlining into the StObj target slot when this is possible without changing the program semantics.

pull/2069/head
Daniel Grunwald 5 years ago
parent
commit
62fcab8d99
  1. 6
      ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
  2. 51
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs
  3. 1
      ICSharpCode.Decompiler/IL/Instructions.cs
  4. 3
      ICSharpCode.Decompiler/IL/Instructions.tt
  5. 11
      ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs
  6. 2
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  7. 5
      ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs
  8. 2
      ILSpy/TextView/ILAsm-Mode.xshd

6
ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs

@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public void NullableTests([ValueSource("defaultOptions")] CompilerOptions options) public void NullableTests([ValueSource("defaultOptions")] CompilerOptions options)
{ {
RunCS(options: options); RunCS(options: options, forceRoslynRecompile: true);
} }
[Test] [Test]
@ -301,7 +301,7 @@ namespace ICSharpCode.Decompiler.Tests
RunCS(options: options); RunCS(options: options);
} }
void RunCS([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug) void RunCS([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, bool forceRoslynRecompile = false)
{ {
string testFileName = testName + ".cs"; string testFileName = testName + ".cs";
string testOutputFileName = testName + Tester.GetSuffix(options) + ".exe"; string testOutputFileName = testName + Tester.GetSuffix(options) + ".exe";
@ -311,7 +311,7 @@ namespace ICSharpCode.Decompiler.Tests
outputFile = Tester.CompileCSharp(Path.Combine(TestCasePath, testFileName), options, outputFile = Tester.CompileCSharp(Path.Combine(TestCasePath, testFileName), options,
outputFileName: Path.Combine(TestCasePath, testOutputFileName)); outputFileName: Path.Combine(TestCasePath, testOutputFileName));
string decompiledCodeFile = Tester.DecompileCSharp(outputFile.PathToAssembly, Tester.GetSettings(options)); string decompiledCodeFile = Tester.DecompileCSharp(outputFile.PathToAssembly, Tester.GetSettings(options));
if (options.HasFlag(CompilerOptions.UseMcs)) { if (forceRoslynRecompile || options.HasFlag(CompilerOptions.UseMcs)) {
// For second pass, use roslyn instead of mcs. // For second pass, use roslyn instead of mcs.
// mcs has some compiler bugs that cause it to not accept ILSpy-generated code, // mcs has some compiler bugs that cause it to not accept ILSpy-generated code,
// for example when there's unreachable code due to other compiler bugs in the first mcs run. // for example when there's unreachable code due to other compiler bugs in the first mcs run.

51
ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs

@ -27,6 +27,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
AvoidLifting(); AvoidLifting();
BitNot(); BitNot();
FieldAccessOrderOfEvaluation(null); FieldAccessOrderOfEvaluation(null);
ArrayAccessOrderOfEvaluation();
} }
static void AvoidLifting() static void AvoidLifting()
@ -85,10 +86,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
} }
static int GetInt() static T GetValue<T>()
{ {
Console.WriteLine("got int"); Console.WriteLine("GetValue");
return 42; return default(T);
} }
int intField; int intField;
@ -97,7 +98,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{ {
Console.WriteLine("GetInt, then NRE:"); Console.WriteLine("GetInt, then NRE:");
try { try {
c.intField = GetInt(); c.intField = GetValue<int>();
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
} }
@ -105,11 +106,51 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
try { try {
#if CS60 #if CS60
ref int i = ref c.intField; ref int i = ref c.intField;
i = GetInt(); i = GetValue<int>();
#endif #endif
} catch (Exception ex) { } catch (Exception ex) {
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
} }
} }
static T[] GetArray<T>()
{
Console.WriteLine("GetArray");
return null;
}
static int GetIndex()
{
Console.WriteLine("GetIndex");
return 0;
}
static void ArrayAccessOrderOfEvaluation()
{
Console.WriteLine("GetArray direct:");
try {
GetArray<int>()[GetIndex()] = GetValue<int>();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Console.WriteLine("GetArray with ref:");
try {
#if CS60
ref int elem = ref GetArray<int>()[GetIndex()];
elem = GetValue<int>();
#endif
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Console.WriteLine("GetArray direct with value-type:");
try {
// This line is mis-compiled by legacy csc:
// with the legacy compiler the NRE is thrown before the GetValue call;
// with Roslyn the NRE is thrown after the GetValue call.
GetArray<TimeSpan>()[GetIndex()] = GetValue<TimeSpan>();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}
} }
} }

1
ICSharpCode.Decompiler/IL/Instructions.cs

@ -4117,7 +4117,6 @@ namespace ICSharpCode.Decompiler.IL
base.CheckInvariant(phase); base.CheckInvariant(phase);
Debug.Assert(target.ResultType == StackType.Ref || target.ResultType == StackType.I); Debug.Assert(target.ResultType == StackType.Ref || target.ResultType == StackType.I);
Debug.Assert(value.ResultType == type.GetStackType()); Debug.Assert(value.ResultType == type.GetStackType());
Debug.Assert(IsValidTarget(target));
} }
} }
} }

3
ICSharpCode.Decompiler/IL/Instructions.tt

@ -253,8 +253,7 @@
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("type.GetStackType()")),
CustomInvariant("Debug.Assert(IsValidTarget(target));")),
new OpCode("box", "Boxes a value.", new OpCode("box", "Boxes a value.",
Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("O")), Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("O")),

11
ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs

@ -52,13 +52,18 @@ namespace ICSharpCode.Decompiler.IL
/// This means a LdFlda/LdElema used as target for StObj must have DelayExceptions==true to allow a translation to C# /// This means a LdFlda/LdElema used as target for StObj must have DelayExceptions==true to allow a translation to C#
/// without changing the program semantics. See https://github.com/icsharpcode/ILSpy/issues/2050 /// without changing the program semantics. See https://github.com/icsharpcode/ILSpy/issues/2050
/// </summary> /// </summary>
/// <remarks>This is part of the StObj invariant.</remarks> public bool CanInlineIntoTargetSlot(ILInstruction inst)
public static bool IsValidTarget(ILInstruction inst)
{ {
switch (inst.OpCode) { switch (inst.OpCode) {
case OpCode.LdElema: case OpCode.LdElema:
case OpCode.LdFlda: case OpCode.LdFlda:
return !inst.HasDirectFlag(InstructionFlags.MayThrow); Debug.Assert(inst.HasDirectFlag(InstructionFlags.MayThrow));
// If the ldelema/ldflda may throw a non-delayed exception, inlining will cause it
// to turn into a delayed exception after the translation to C#.
// This is only valid if the value computation doesn't involve any side effects.
return SemanticHelper.IsPure(this.Value.Flags);
// Note that after inlining such a ldelema/ldflda, the normal inlining rules will
// prevent us from inlining an effectful instruction into the value slot.
default: default:
return true; return true;
} }

2
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -581,7 +581,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return FindResult.Stop; return FindResult.Stop;
if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) {
// Match found, we can inline // Match found, we can inline
if (expr.SlotInfo == StObj.TargetSlot && !StObj.IsValidTarget(expressionBeingMoved)) { if (expr.SlotInfo == StObj.TargetSlot && !((StObj)expr.Parent).CanInlineIntoTargetSlot(expressionBeingMoved)) {
// special case: the StObj.TargetSlot does not accept some kinds of expressions // special case: the StObj.TargetSlot does not accept some kinds of expressions
return FindResult.Stop; return FindResult.Stop;
} }

5
ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs

@ -45,6 +45,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <remarks> /// <remarks>
/// Instructions prior to block.Instructions[pos] must not be modified. /// Instructions prior to block.Instructions[pos] must not be modified.
/// It is valid to read such instructions, but not recommended as those have not been transformed yet. /// It is valid to read such instructions, but not recommended as those have not been transformed yet.
///
/// This function is only called on control-flow blocks with unreachable end-point.
/// Thus, the last instruction in the block always must have the EndPointUnreachable flag.
/// ==> Instructions with reachable end can't be last. Some transforms use this to save some bounds checks.
/// </remarks> /// </remarks>
void Run(Block block, int pos, StatementTransformContext context); void Run(Block block, int pos, StatementTransformContext context);
} }
@ -122,6 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ctx.rerunPosition = null; ctx.rerunPosition = null;
} }
foreach (var transform in children) { foreach (var transform in children) {
Debug.Assert(block.HasFlag(InstructionFlags.EndPointUnreachable));
transform.Run(block, pos, ctx); transform.Run(block, pos, ctx);
#if DEBUG #if DEBUG
block.Instructions[pos].CheckInvariant(ILPhase.Normal); block.Instructions[pos].CheckInvariant(ILPhase.Normal);

2
ILSpy/TextView/ILAsm-Mode.xshd

@ -149,6 +149,7 @@
<Word>newarr</Word> <Word>newarr</Word>
<Word>ldlen</Word> <Word>ldlen</Word>
<Word>ldelema</Word> <Word>ldelema</Word>
<Word>ldelem</Word>
<Word>ldelem.i1</Word> <Word>ldelem.i1</Word>
<Word>ldelem.u1</Word> <Word>ldelem.u1</Word>
<Word>ldelem.i2</Word> <Word>ldelem.i2</Word>
@ -160,6 +161,7 @@
<Word>ldelem.r4</Word> <Word>ldelem.r4</Word>
<Word>ldelem.r8</Word> <Word>ldelem.r8</Word>
<Word>ldelem.ref</Word> <Word>ldelem.ref</Word>
<Word>stelem</Word>
<Word>stelem.i</Word> <Word>stelem.i</Word>
<Word>stelem.i1</Word> <Word>stelem.i1</Word>
<Word>stelem.i2</Word> <Word>stelem.i2</Word>

Loading…
Cancel
Save