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 @@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public void NullableTests([ValueSource("defaultOptions")] CompilerOptions options)
{
RunCS(options: options);
RunCS(options: options, forceRoslynRecompile: true);
}
[Test]
@ -301,7 +301,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -301,7 +301,7 @@ namespace ICSharpCode.Decompiler.Tests
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 testOutputFileName = testName + Tester.GetSuffix(options) + ".exe";
@ -311,7 +311,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -311,7 +311,7 @@ namespace ICSharpCode.Decompiler.Tests
outputFile = Tester.CompileCSharp(Path.Combine(TestCasePath, testFileName), options,
outputFileName: Path.Combine(TestCasePath, testOutputFileName));
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.
// 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.

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

@ -27,6 +27,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -27,6 +27,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
AvoidLifting();
BitNot();
FieldAccessOrderOfEvaluation(null);
ArrayAccessOrderOfEvaluation();
}
static void AvoidLifting()
@ -85,10 +86,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -85,10 +86,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
}
static int GetInt()
static T GetValue<T>()
{
Console.WriteLine("got int");
return 42;
Console.WriteLine("GetValue");
return default(T);
}
int intField;
@ -97,7 +98,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -97,7 +98,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
Console.WriteLine("GetInt, then NRE:");
try {
c.intField = GetInt();
c.intField = GetValue<int>();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
@ -105,11 +106,51 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -105,11 +106,51 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
try {
#if CS60
ref int i = ref c.intField;
i = GetInt();
i = GetValue<int>();
#endif
} catch (Exception ex) {
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 @@ -4117,7 +4117,6 @@ namespace ICSharpCode.Decompiler.IL
base.CheckInvariant(phase);
Debug.Assert(target.ResultType == StackType.Ref || target.ResultType == StackType.I);
Debug.Assert(value.ResultType == type.GetStackType());
Debug.Assert(IsValidTarget(target));
}
}
}

3
ICSharpCode.Decompiler/IL/Instructions.tt

@ -253,8 +253,7 @@ @@ -253,8 +253,7 @@
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())",
CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()"),
CustomInvariant("Debug.Assert(IsValidTarget(target));")),
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")),
new OpCode("box", "Boxes a value.",
Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("O")),

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

@ -52,13 +52,18 @@ namespace ICSharpCode.Decompiler.IL @@ -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#
/// without changing the program semantics. See https://github.com/icsharpcode/ILSpy/issues/2050
/// </summary>
/// <remarks>This is part of the StObj invariant.</remarks>
public static bool IsValidTarget(ILInstruction inst)
public bool CanInlineIntoTargetSlot(ILInstruction inst)
{
switch (inst.OpCode) {
case OpCode.LdElema:
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:
return true;
}

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

@ -581,7 +581,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -581,7 +581,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return FindResult.Stop;
if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) {
// 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
return FindResult.Stop;
}

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

@ -45,6 +45,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -45,6 +45,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <remarks>
/// 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.
///
/// 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>
void Run(Block block, int pos, StatementTransformContext context);
}
@ -122,6 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -122,6 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ctx.rerunPosition = null;
}
foreach (var transform in children) {
Debug.Assert(block.HasFlag(InstructionFlags.EndPointUnreachable));
transform.Run(block, pos, ctx);
#if DEBUG
block.Instructions[pos].CheckInvariant(ILPhase.Normal);

2
ILSpy/TextView/ILAsm-Mode.xshd

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

Loading…
Cancel
Save