Browse Source

Add support for indexing arrays using System.Index

pull/1986/head
Daniel Grunwald 6 years ago
parent
commit
dc6e094a30
  1. 52
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
  2. 17
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 5
      ICSharpCode.Decompiler/IL/Instructions.cs
  5. 12
      ICSharpCode.Decompiler/IL/Instructions.tt
  6. 2
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  7. 5
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  8. 89
      ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs

52
ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs

@ -29,20 +29,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -29,20 +29,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
internal class IndexRangeTest
{
public static string[] GetArray()
{
public static int[] GetArray()
{
throw null;
}
public static List<string> GetList()
{
}
public static List<int> GetList()
{
throw null;
}
public static Span<int> GetSpan()
{
{
throw null;
}
public static string GetString()
{
{
throw null;
}
public static Index GetIndex(int i = 0)
@ -60,8 +60,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -60,8 +60,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static void UseIndex()
{
#if TODO
Console.WriteLine(GetArray()[GetIndex()]);
#if TODO
Console.WriteLine(GetList()[GetIndex()]);
Console.WriteLine(GetSpan()[GetIndex()]);
Console.WriteLine(GetString()[GetIndex()]);
@ -69,6 +69,38 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -69,6 +69,38 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
#endif
Console.WriteLine(new CustomList2()[GetIndex()]);
}
public static void UseIndexFromEnd()
{
Console.WriteLine(GetArray()[^GetInt()]);
#if TODO
Console.WriteLine(GetList()[^GetInt()]);
Console.WriteLine(GetSpan()[^GetInt()]);
Console.WriteLine(GetString()[^GetInt()]);
Console.WriteLine(new CustomList()[^GetInt()]);
#endif
Console.WriteLine(new CustomList2()[^GetInt()]);
}
public static void UseIndexForWrite()
{
GetArray()[GetIndex()] = GetInt();
#if TODO
GetList()[GetIndex()] = GetInt();
GetSpan()[GetIndex()] = GetInt();
#endif
}
private static void UseRef(ref int i)
{
}
public static void UseIndexForRef()
{
UseRef(ref GetArray()[GetIndex()]);
UseRef(ref GetArray()[^GetInt()]);
}
public static void UseRange()
{
#if TODO
@ -173,7 +205,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -173,7 +205,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
public static void UseIndexForIntIndexerWhenIndexIndexerIsAvailable()
{
{
// Same code as the compiler emits for CustomList,
// but here we can't translate it back to `customList[GetIndex()]`
// because that would call a different overload.
@ -184,7 +216,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -184,7 +216,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
public static void UseSliceWhenRangeIndexerIsAvailable()
{
{
// Same code as the compiler emits for CustomList,
// but here we can't translate it back to `customList[GetIndex()]`
// because that would call a different overload.

17
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2238,14 +2238,23 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2238,14 +2238,23 @@ namespace ICSharpCode.Decompiler.CSharp
arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count);
arrayExpr = arrayExpr.ConvertTo(arrayType, this);
}
TranslatedExpression expr = new IndexerExpression(
arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
).WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
IndexerExpression indexerExpr;
if (inst.WithSystemIndex) {
var systemIndex = compilation.FindType(KnownTypeCode.Index);
indexerExpr = new IndexerExpression(
arrayExpr, inst.Indices.Select(i => Translate(i, typeHint: systemIndex).ConvertTo(systemIndex, this).Expression)
);
} else {
indexerExpr = new IndexerExpression(
arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
);
}
TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
return new DirectionExpression(FieldDirection.Ref, expr)
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref));
}
TranslatedExpression TranslateArrayIndex(ILInstruction i)
TranslatedExpression TranslateArrayIndex(ILInstruction i, bool expectSystemIndex = false)
{
var input = Translate(i);
KnownTypeCode targetType;

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -64,6 +64,7 @@ @@ -64,6 +64,7 @@
<Compile Include="CSharp\OutputVisitor\GenericGrammarAmbiguityVisitor.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="IL\Transforms\IndexRangeTransform.cs" />
<Compile Include="CSharp\TranslatedStatement.cs" />
<Compile Include="DebugInfo\KnownGuids.cs" />
<Compile Include="Disassembler\DisassemblerSignatureTypeProvider.cs" />

5
ICSharpCode.Decompiler/IL/Instructions.cs

@ -4713,6 +4713,7 @@ namespace ICSharpCode.Decompiler.IL @@ -4713,6 +4713,7 @@ namespace ICSharpCode.Decompiler.IL
clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone()));
return clone;
}
public bool WithSystemIndex;
public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced
public override StackType ResultType { get { return StackType.Ref; } }
/// <summary>Gets whether the 'readonly' prefix was applied to this instruction.</summary>
@ -4729,6 +4730,8 @@ namespace ICSharpCode.Decompiler.IL @@ -4729,6 +4730,8 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
if (WithSystemIndex)
output.Write("withsystemindex.");
if (DelayExceptions)
output.Write("delayex.");
if (IsReadOnly)
@ -4759,7 +4762,7 @@ namespace ICSharpCode.Decompiler.IL @@ -4759,7 +4762,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as LdElema;
return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && DelayExceptions == o.DelayExceptions && IsReadOnly == o.IsReadOnly;
return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && this.WithSystemIndex == o.WithSystemIndex && DelayExceptions == o.DelayExceptions && IsReadOnly == o.IsReadOnly;
}
}
}

12
ICSharpCode.Decompiler/IL/Instructions.tt

@ -278,6 +278,7 @@ @@ -278,6 +278,7 @@
CustomClassName("LdLen"), CustomArguments(("array", new[] { "O" })), CustomConstructor, CustomWriteTo, MayThrow),
new OpCode("ldelema", "Load address of array element.",
CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
BoolFlag("WithSystemIndex"),
MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("get.pinnable.reference", "Retrieves a pinnable reference for the input object." + Environment.NewLine
+ "The input must be an object reference (O)." + Environment.NewLine
@ -1105,6 +1106,17 @@ protected override void Disconnected() @@ -1105,6 +1106,17 @@ protected override void Disconnected()
opCode.WriteOperand.Add("member.WriteTo(output);");
};
// Adds a member of type bool to the instruction.
static Action<OpCode> BoolFlag(string flagName)
{
return opCode => {
opCode.PerformMatchConditions.Add($"this.{flagName} == o.{flagName}");
opCode.Members.Add($"public bool {flagName};");
opCode.GenerateWriteTo = true;
opCode.WriteOpCodePrefix.Add($"if ({flagName}){Environment.NewLine}\toutput.Write(\"{flagName.ToLowerInvariant()}.\");");
};
}
// LoadConstant trait: the instruction loads a compile-time constant. Implies NoArguments.
static Action<OpCode> LoadConstant(string operandType)
{

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

@ -206,6 +206,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -206,6 +206,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
base.VisitLdElema(inst);
CleanUpArrayIndices(inst.Indices);
if (IndexRangeTransform.HandleLdElema(inst, context))
return;
}
protected internal override void VisitNewArr(NewArr inst)

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

@ -470,6 +470,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -470,6 +470,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
break;
case OpCode.LdElema:
if (((LdElema)parent).WithSystemIndex) {
return true;
}
break;
}
// decide based on the top-level target instruction into which we are inlining:
switch (next.OpCode) {

89
ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
// Copyright (c) 2020 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Transform for the C# 8 System.Index / System.Range feature
/// </summary>
class IndexRangeTransform
{
/// <summary>
/// Called by expression transforms.
/// Handles the `array[System.Index]` cases.
/// </summary>
public static bool HandleLdElema(LdElema ldelema, ILTransformContext context)
{
if (!context.Settings.Ranges)
return false;
if (!ldelema.Array.MatchLdLoc(out ILVariable array))
return false;
if (ldelema.Indices.Count != 1)
return false; // the index/range feature doesn't support multi-dimensional arrays
var index = ldelema.Indices[0];
if (index is CallInstruction call && call.Method.Name == "GetOffset" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) {
// ldelema T(ldloc array, call GetOffset(..., ldlen.i4(ldloc array)))
// -> withsystemindex.ldelema T(ldloc array, ...)
if (call.Arguments.Count != 2)
return false;
if (!(call.Arguments[1].MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
return false;
context.Step("ldelema with System.Index", ldelema);
foreach (var node in call.Arguments[1].Descendants)
ldelema.AddILRange(node);
ldelema.AddILRange(call);
ldelema.WithSystemIndex = true;
// The method call had a `ref System.Index` argument for the this pointer, but we want a `System.Index` by-value.
ldelema.Indices[0] = new LdObj(call.Arguments[0], call.Method.DeclaringType);
return true;
} else if (index is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.IsLifted && !bni.CheckForOverflow) {
// ldelema T(ldloc array, binary.sub.i4(ldlen.i4(ldloc array), ...))
// -> withsystemindex.ldelema T(ldloc array, newobj System.Index(..., fromEnd: true))
if (!(bni.Left.MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
return false;
var indexCtor = FindIndexConstructor(context.TypeSystem);
if (indexCtor == null)
return false; // don't use System.Index if not supported by the target framework
context.Step("ldelema indexed from end", ldelema);
foreach (var node in bni.Left.Descendants)
ldelema.AddILRange(node);
ldelema.AddILRange(bni);
ldelema.WithSystemIndex = true;
ldelema.Indices[0] = new NewObj(indexCtor) { Arguments = { bni.Right, new LdcI4(1) } };
return true;
}
return false;
}
static IMethod FindIndexConstructor(ICompilation compilation)
{
var indexType = compilation.FindType(KnownTypeCode.Index);
foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2)) {
if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)
&& ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean)) {
return ctor;
}
}
return null;
}
}
}
Loading…
Cancel
Save