You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
782 lines
29 KiB
782 lines
29 KiB
// 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 System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
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 : IStatementTransform
|
|
{
|
|
/// <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 indexMethods = new IndexMethods(context.TypeSystem);
|
|
if (!indexMethods.IsValid)
|
|
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] = MakeIndex(IndexKind.FromEnd, bni.Right, indexMethods);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
class IndexMethods
|
|
{
|
|
public readonly IMethod IndexCtor;
|
|
public readonly IMethod IndexImplicitConv;
|
|
public readonly IMethod RangeCtor;
|
|
public IType IndexType => IndexCtor?.DeclaringType;
|
|
public IType RangeType => RangeCtor?.DeclaringType;
|
|
public bool IsValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null;
|
|
|
|
public readonly IMethod RangeStartAt;
|
|
public readonly IMethod RangeEndAt;
|
|
public readonly IMethod RangeGetAll;
|
|
|
|
public IndexMethods(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))
|
|
{
|
|
this.IndexCtor = ctor;
|
|
}
|
|
}
|
|
foreach (var op in indexType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit"))
|
|
{
|
|
if (op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
|
|
{
|
|
this.IndexImplicitConv = op;
|
|
}
|
|
}
|
|
var rangeType = compilation.FindType(KnownTypeCode.Range);
|
|
foreach (var ctor in rangeType.GetConstructors(m => m.Parameters.Count == 2))
|
|
{
|
|
if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Index)
|
|
&& ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Index))
|
|
{
|
|
this.RangeCtor = ctor;
|
|
}
|
|
}
|
|
foreach (var m in rangeType.GetMethods(m => m.Parameters.Count == 1))
|
|
{
|
|
if (m.Parameters.Count == 1 && m.Parameters[0].Type.IsKnownType(KnownTypeCode.Index))
|
|
{
|
|
if (m.Name == "StartAt")
|
|
this.RangeStartAt = m;
|
|
else if (m.Name == "EndAt")
|
|
this.RangeEndAt = m;
|
|
}
|
|
}
|
|
foreach (var p in rangeType.GetProperties(p => p.IsStatic && p.Name == "All"))
|
|
{
|
|
this.RangeGetAll = p.Getter;
|
|
}
|
|
}
|
|
|
|
public static bool IsRangeCtor(IMethod method)
|
|
{
|
|
return method.SymbolKind == SymbolKind.Constructor
|
|
&& method.Parameters.Count == 2
|
|
&& method.DeclaringType.IsKnownType(KnownTypeCode.Range)
|
|
&& method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Index));
|
|
}
|
|
}
|
|
|
|
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
|
|
{
|
|
if (!context.Settings.Ranges)
|
|
return;
|
|
int startPos = pos;
|
|
ILVariable containerVar = null;
|
|
// The container length access may be a separate instruction, or it may be inline with the variable's use
|
|
if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, ref containerVar))
|
|
{
|
|
// stloc containerLengthVar(call get_Length/get_Count(ldloc container))
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
// Reset if MatchContainerLengthStore only had a partial match. MatchGetOffset() will then set `containerVar`.
|
|
containerLengthVar = null;
|
|
containerVar = null;
|
|
}
|
|
if (block.Instructions[pos].MatchStLoc(out var rangeVar, out var rangeVarInit) && rangeVar.Type.IsKnownType(KnownTypeCode.Range))
|
|
{
|
|
// stloc rangeVar(rangeVarInit)
|
|
pos++;
|
|
}
|
|
else
|
|
{
|
|
rangeVar = null;
|
|
rangeVarInit = null;
|
|
}
|
|
|
|
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
|
|
if (!(block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit)
|
|
&& startOffsetVar.IsSingleDefinition && startOffsetVar.StackType == StackType.I4))
|
|
{
|
|
// Not our primary indexing/slicing pattern.
|
|
// However, we might be dealing with a partially-transformed pattern that needs to be extended.
|
|
ExtendSlicing();
|
|
return;
|
|
}
|
|
var startIndexKind = MatchGetOffset(startOffsetVarInit, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar);
|
|
pos++;
|
|
if (startOffsetVar.LoadCount == 1)
|
|
{
|
|
TransformIndexing();
|
|
}
|
|
else if (startOffsetVar.LoadCount == 2)
|
|
{
|
|
// might be slicing: startOffset is used once for the slice length calculation, and once for the Slice() method call
|
|
TransformSlicing();
|
|
}
|
|
|
|
void TransformIndexing()
|
|
{
|
|
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
|
|
|
|
if (rangeVar != null)
|
|
return;
|
|
if (!(startOffsetVar.LoadInstructions.Single().Parent is CallInstruction call))
|
|
return;
|
|
if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter && call.Arguments.Count == 2)
|
|
{
|
|
if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
|
|
return;
|
|
if (call.Method.Parameters.Count != 1)
|
|
return;
|
|
}
|
|
else if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Setter && call.Arguments.Count == 3)
|
|
{
|
|
if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
|
|
return;
|
|
if (call.Method.Parameters.Count != 2)
|
|
return;
|
|
}
|
|
else if (IsSlicingMethod(call.Method))
|
|
{
|
|
TransformSlicing(sliceLengthWasMisdetectedAsStartOffset: true);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
if (startIndexKind == IndexKind.FromStart)
|
|
{
|
|
// FromStart is only relevant for slicing; indexing from the start does not involve System.Index at all.
|
|
return;
|
|
}
|
|
if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind))
|
|
{
|
|
return;
|
|
}
|
|
if (!call.IsDescendantOf(block.Instructions[pos]))
|
|
return;
|
|
// startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point:
|
|
for (int i = startPos; i < pos; i++)
|
|
{
|
|
if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i]))
|
|
return;
|
|
}
|
|
if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
|
|
return;
|
|
if (!MatchContainerVar(call.Arguments[0], ref containerVar))
|
|
return;
|
|
if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
|
|
return;
|
|
var specialMethods = new IndexMethods(context.TypeSystem);
|
|
if (!specialMethods.IsValid)
|
|
return;
|
|
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false))
|
|
return;
|
|
|
|
context.Step($"{call.Method.Name} indexed with {startIndexKind}", call);
|
|
var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.IndexType, slicing: false);
|
|
var newCall = CallInstruction.Create(call.OpCode, newMethod);
|
|
newCall.ConstrainedTo = call.ConstrainedTo;
|
|
newCall.ILStackWasEmpty = call.ILStackWasEmpty;
|
|
newCall.Arguments.Add(call.Arguments[0]);
|
|
newCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
|
|
newCall.Arguments.AddRange(call.Arguments.Skip(2));
|
|
newCall.AddILRange(call);
|
|
for (int i = startPos; i < pos; i++)
|
|
{
|
|
newCall.AddILRange(block.Instructions[i]);
|
|
}
|
|
call.ReplaceWith(newCall);
|
|
block.Instructions.RemoveRange(startPos, pos - startPos);
|
|
}
|
|
|
|
void TransformSlicing(bool sliceLengthWasMisdetectedAsStartOffset = false)
|
|
{
|
|
ILVariable sliceLengthVar;
|
|
ILInstruction sliceLengthVarInit;
|
|
if (sliceLengthWasMisdetectedAsStartOffset)
|
|
{
|
|
// Special case: when slicing without a start point, the slice length calculation is mis-detected as the start offset,
|
|
// and since it only has a single use, we end in TransformIndexing(), which then calls TransformSlicing
|
|
// on this code path.
|
|
sliceLengthVar = startOffsetVar;
|
|
sliceLengthVarInit = ((StLoc)sliceLengthVar.StoreInstructions.Single()).Value;
|
|
startOffsetVar = null;
|
|
startIndexLoad = new LdcI4(0);
|
|
startIndexKind = IndexKind.TheStart;
|
|
}
|
|
else
|
|
{
|
|
// stloc containerLengthVar(call get_Length(ldloc containerVar))
|
|
// stloc startOffset(call GetOffset(startIndexLoad, ldloc length))
|
|
// -- we are here --
|
|
// stloc sliceLengthVar(binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
|
|
// complex_expr(call Slice(ldloc containerVar, ldloc startOffset, ldloc sliceLength))
|
|
|
|
if (!block.Instructions[pos].MatchStLoc(out sliceLengthVar, out sliceLengthVarInit))
|
|
return;
|
|
pos++;
|
|
}
|
|
|
|
if (!(sliceLengthVar.IsSingleDefinition && sliceLengthVar.LoadCount == 1))
|
|
return;
|
|
if (!MatchSliceLength(sliceLengthVarInit, out IndexKind endIndexKind, out ILInstruction endIndexLoad, containerLengthVar, ref containerVar, startOffsetVar))
|
|
return;
|
|
if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind))
|
|
{
|
|
return;
|
|
}
|
|
if (rangeVar != null)
|
|
{
|
|
return; // this should only ever happen in the second step (ExtendSlicing)
|
|
}
|
|
if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call))
|
|
return;
|
|
if (!call.IsDescendantOf(block.Instructions[pos]))
|
|
return;
|
|
if (!IsSlicingMethod(call.Method))
|
|
return;
|
|
if (call.Arguments.Count != 3)
|
|
return;
|
|
if (!MatchContainerVar(call.Arguments[0], ref containerVar))
|
|
return;
|
|
if (startOffsetVar == null)
|
|
{
|
|
Debug.Assert(startIndexKind == IndexKind.TheStart);
|
|
if (!call.Arguments[1].MatchLdcI4(0))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
|
|
return;
|
|
if (!ILInlining.CanMoveInto(startOffsetVarInit, block.Instructions[pos], call.Arguments[1]))
|
|
return;
|
|
}
|
|
if (!call.Arguments[2].MatchLdLoc(sliceLengthVar))
|
|
return;
|
|
if (!ILInlining.CanMoveInto(sliceLengthVarInit, block.Instructions[pos], call.Arguments[2]))
|
|
return;
|
|
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: true))
|
|
return;
|
|
var specialMethods = new IndexMethods(context.TypeSystem);
|
|
if (!specialMethods.IsValid)
|
|
return;
|
|
|
|
context.Step($"{call.Method.Name} sliced with {startIndexKind}..{endIndexKind}", call);
|
|
var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.RangeType, slicing: true);
|
|
var newCall = CallInstruction.Create(call.OpCode, newMethod);
|
|
newCall.ConstrainedTo = call.ConstrainedTo;
|
|
newCall.ILStackWasEmpty = call.ILStackWasEmpty;
|
|
newCall.Arguments.Add(call.Arguments[0]);
|
|
newCall.Arguments.Add(MakeRange(startIndexKind, startIndexLoad, endIndexKind, endIndexLoad, specialMethods));
|
|
newCall.AddILRange(call);
|
|
for (int i = startPos; i < pos; i++)
|
|
{
|
|
newCall.AddILRange(block.Instructions[i]);
|
|
}
|
|
call.ReplaceWith(newCall);
|
|
block.Instructions.RemoveRange(startPos, pos - startPos);
|
|
}
|
|
|
|
ILInstruction MakeRange(IndexKind startIndexKind, ILInstruction startIndexLoad, IndexKind endIndexKind, ILInstruction endIndexLoad, IndexMethods specialMethods)
|
|
{
|
|
if (rangeVar != null)
|
|
{
|
|
return rangeVarInit;
|
|
}
|
|
else if (startIndexKind == IndexKind.TheStart && endIndexKind == IndexKind.TheEnd && specialMethods.RangeGetAll != null)
|
|
{
|
|
return new Call(specialMethods.RangeGetAll);
|
|
}
|
|
else if (startIndexKind == IndexKind.TheStart && specialMethods.RangeEndAt != null)
|
|
{
|
|
var rangeCtorCall = new Call(specialMethods.RangeEndAt);
|
|
rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
|
|
return rangeCtorCall;
|
|
}
|
|
else if (endIndexKind == IndexKind.TheEnd && specialMethods.RangeStartAt != null)
|
|
{
|
|
var rangeCtorCall = new Call(specialMethods.RangeStartAt);
|
|
rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
|
|
return rangeCtorCall;
|
|
}
|
|
else
|
|
{
|
|
var rangeCtorCall = new NewObj(specialMethods.RangeCtor);
|
|
rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
|
|
rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
|
|
return rangeCtorCall;
|
|
}
|
|
}
|
|
|
|
void ExtendSlicing()
|
|
{
|
|
// We might be dealing with a situation where we executed TransformSlicing() in a previous run of this transform
|
|
// that only looked at a part of the instructions making up the slicing pattern.
|
|
// The first run would have mis-detected slicing from end as slicing from start.
|
|
// This results in code like:
|
|
// int length = span.Length;
|
|
// Console.WriteLine(span[GetIndex(1).GetOffset(length)..GetIndex(2).GetOffset(length)].ToString());
|
|
// or:
|
|
// int length = span.Length;
|
|
// Range range = GetRange();
|
|
// Console.WriteLine(span[range.Start.GetOffset(length)..range.End.GetOffset(length)].ToString());
|
|
if (containerLengthVar == null)
|
|
{
|
|
return; // need a container length to extend with
|
|
}
|
|
Debug.Assert(containerLengthVar.IsSingleDefinition);
|
|
Debug.Assert(containerLengthVar.LoadCount == 1 || containerLengthVar.LoadCount == 2);
|
|
NewObj rangeCtorCall = null;
|
|
foreach (var inst in containerLengthVar.LoadInstructions[0].Ancestors)
|
|
{
|
|
if (inst is NewObj newobj && IndexMethods.IsRangeCtor(newobj.Method))
|
|
{
|
|
rangeCtorCall = newobj;
|
|
break;
|
|
}
|
|
if (inst == block)
|
|
break;
|
|
}
|
|
if (rangeCtorCall == null)
|
|
return;
|
|
// Now match the pattern that TransformSlicing() generated in the IndexKind.FromStart case
|
|
if (!(rangeCtorCall.Parent is CallInstruction { Method: SyntheticRangeIndexAccessor _ } slicingCall))
|
|
return;
|
|
if (!MatchContainerVar(slicingCall.Arguments[0], ref containerVar))
|
|
return;
|
|
if (!slicingCall.IsDescendantOf(block.Instructions[pos]))
|
|
return;
|
|
Debug.Assert(rangeCtorCall.Arguments.Count == 2);
|
|
if (!MatchIndexImplicitConv(rangeCtorCall.Arguments[0], out var startOffsetInst))
|
|
return;
|
|
if (!MatchIndexImplicitConv(rangeCtorCall.Arguments[1], out var endOffsetInst))
|
|
return;
|
|
var startIndexKind = MatchGetOffset(startOffsetInst, out var startIndexLoad, containerLengthVar, ref containerVar);
|
|
var endIndexKind = MatchGetOffset(endOffsetInst, out var endIndexLoad, containerLengthVar, ref containerVar);
|
|
if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind))
|
|
{
|
|
return;
|
|
}
|
|
// holds because we've used containerLengthVar at least once
|
|
Debug.Assert(startIndexKind != IndexKind.FromStart || endIndexKind != IndexKind.FromStart);
|
|
if (rangeVar != null)
|
|
{
|
|
if (!ILInlining.CanMoveInto(rangeVarInit, block.Instructions[pos], startIndexLoad))
|
|
return;
|
|
if (!MatchIndexFromRange(startIndexKind, startIndexLoad, rangeVar, "get_Start"))
|
|
return;
|
|
if (!MatchIndexFromRange(endIndexKind, endIndexLoad, rangeVar, "get_End"))
|
|
return;
|
|
}
|
|
var specialMethods = new IndexMethods(context.TypeSystem);
|
|
if (!specialMethods.IsValid)
|
|
return;
|
|
context.Step("Merge containerLengthVar into slicing", slicingCall);
|
|
rangeCtorCall.ReplaceWith(MakeRange(startIndexKind, startIndexLoad, endIndexKind, endIndexLoad, specialMethods));
|
|
for (int i = startPos; i < pos; i++)
|
|
{
|
|
slicingCall.AddILRange(block.Instructions[i]);
|
|
}
|
|
block.Instructions.RemoveRange(startPos, pos - startPos);
|
|
}
|
|
}
|
|
|
|
private bool MatchIndexImplicitConv(ILInstruction inst, out ILInstruction offsetInst)
|
|
{
|
|
offsetInst = null;
|
|
if (!(inst is CallInstruction call))
|
|
return false;
|
|
if (!(call.Method.IsOperator && call.Method.Name == "op_Implicit"))
|
|
return false;
|
|
var op = call.Method;
|
|
if (!(op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)))
|
|
return false;
|
|
if (!op.DeclaringType.IsKnownType(KnownTypeCode.Index))
|
|
return false;
|
|
offsetInst = call.Arguments.Single();
|
|
return true;
|
|
}
|
|
|
|
static bool IsSlicingMethod(IMethod method)
|
|
{
|
|
if (method.IsExtensionMethod)
|
|
return false;
|
|
if (method.Parameters.Count != 2)
|
|
return false;
|
|
if (!method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32)))
|
|
return false;
|
|
return method.Name == "Slice"
|
|
|| (method.Name == "Substring" && method.DeclaringType.IsKnownType(KnownTypeCode.String));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check that the number of uses of the containerLengthVar variable matches those expected in the pattern.
|
|
/// </summary>
|
|
private bool CheckContainerLengthVariableUseCount(ILVariable containerLengthVar, IndexKind startIndexKind, IndexKind endIndexKind = IndexKind.FromStart)
|
|
{
|
|
int expectedUses = 0;
|
|
if (startIndexKind != IndexKind.FromStart && startIndexKind != IndexKind.TheStart)
|
|
expectedUses += 1;
|
|
if (endIndexKind != IndexKind.FromStart && endIndexKind != IndexKind.TheStart)
|
|
expectedUses += 1;
|
|
if (containerLengthVar != null)
|
|
{
|
|
return containerLengthVar.LoadCount == expectedUses;
|
|
}
|
|
else
|
|
{
|
|
return expectedUses <= 1; // can have one inline use
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Matches 'addressof System.Index(call get_Start/get_End(ldloca rangeVar))'
|
|
/// </summary>
|
|
static bool MatchIndexFromRange(IndexKind indexKind, ILInstruction indexLoad, ILVariable rangeVar, string accessorName)
|
|
{
|
|
if (indexKind != IndexKind.RefSystemIndex)
|
|
return false;
|
|
if (!(indexLoad is AddressOf addressOf))
|
|
return false;
|
|
if (!addressOf.Type.IsKnownType(KnownTypeCode.Index))
|
|
return false;
|
|
if (!(addressOf.Value is Call call))
|
|
return false;
|
|
if (call.Method.Name != accessorName)
|
|
return false;
|
|
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Range))
|
|
return false;
|
|
if (call.Arguments.Count != 1)
|
|
return false;
|
|
return call.Arguments[0].MatchLdLoca(rangeVar);
|
|
}
|
|
|
|
static ILInstruction MakeIndex(IndexKind indexKind, ILInstruction indexLoad, IndexMethods specialMethods)
|
|
{
|
|
if (indexKind == IndexKind.RefSystemIndex)
|
|
{
|
|
// stloc containerLengthVar(call get_Length/get_Count(ldloc container))
|
|
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
|
|
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
|
|
// -->
|
|
// complex_expr(call get_Item(ldloc container, ldobj startIndexLoad))
|
|
return new LdObj(indexLoad, specialMethods.IndexType);
|
|
}
|
|
else if (indexKind == IndexKind.FromEnd || indexKind == IndexKind.TheEnd)
|
|
{
|
|
// stloc offsetVar(binary.sub.i4(call get_Length/get_Count(ldloc container), startIndexLoad))
|
|
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
|
|
// -->
|
|
// complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true)))
|
|
return new NewObj(specialMethods.IndexCtor) { Arguments = { indexLoad, new LdcI4(1) } };
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(indexKind == IndexKind.FromStart || indexKind == IndexKind.TheStart);
|
|
return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
|
|
/// </summary>
|
|
private bool CSharpWillGenerateIndexer(IType declaringType, bool slicing)
|
|
{
|
|
bool foundInt32Overload = false;
|
|
bool foundIndexOverload = false;
|
|
bool foundRangeOverload = false;
|
|
bool foundCountProperty = false;
|
|
foreach (var prop in declaringType.GetProperties(p => p.IsIndexer || (p.Name == "Length" || p.Name == "Count")))
|
|
{
|
|
if (prop.IsIndexer && prop.Parameters.Count == 1)
|
|
{
|
|
var p = prop.Parameters[0];
|
|
if (p.Type.IsKnownType(KnownTypeCode.Int32))
|
|
{
|
|
foundInt32Overload = true;
|
|
}
|
|
else if (p.Type.IsKnownType(KnownTypeCode.Index))
|
|
{
|
|
foundIndexOverload = true;
|
|
}
|
|
else if (p.Type.IsKnownType(KnownTypeCode.Range))
|
|
{
|
|
foundRangeOverload = true;
|
|
}
|
|
}
|
|
else if (prop.Name == "Length" || prop.Name == "Count")
|
|
{
|
|
foundCountProperty = true;
|
|
}
|
|
}
|
|
if (slicing)
|
|
{
|
|
return /* foundSlicingMethod && */ foundCountProperty && !foundRangeOverload;
|
|
}
|
|
else
|
|
{
|
|
return foundInt32Overload && foundCountProperty && !foundIndexOverload;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Matches the instruction:
|
|
/// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
|
|
/// </summary>
|
|
static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, ref ILVariable containerVar)
|
|
{
|
|
if (!inst.MatchStLoc(out lengthVar, out var init))
|
|
return false;
|
|
if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
|
|
return false;
|
|
if (lengthVar.LoadCount == 0 || lengthVar.LoadCount > 2)
|
|
return false;
|
|
return MatchContainerLength(init, null, ref containerVar);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If lengthVar is non-null, matches 'ldloc lengthVar'.
|
|
///
|
|
/// Otherwise, matches the instruction:
|
|
/// call get_Length/get_Count(ldloc containerVar)
|
|
/// </summary>
|
|
static bool MatchContainerLength(ILInstruction init, ILVariable lengthVar, ref ILVariable containerVar)
|
|
{
|
|
if (lengthVar != null)
|
|
{
|
|
Debug.Assert(containerVar != null);
|
|
return init.MatchLdLoc(lengthVar);
|
|
}
|
|
if (!(init is CallInstruction call))
|
|
return false;
|
|
if (call.ResultType != StackType.I4)
|
|
return false;
|
|
if (!(call.Method.IsAccessor && call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter))
|
|
return false;
|
|
if (!(call.Method.AccessorOwner is IProperty lengthProp))
|
|
return false;
|
|
if (lengthProp.Name == "Length")
|
|
{
|
|
// OK, Length is preferred
|
|
}
|
|
else if (lengthProp.Name == "Count")
|
|
{
|
|
// Also works, but only if the type doesn't have "Length"
|
|
if (lengthProp.DeclaringType.GetProperties(p => p.Name == "Length").Any())
|
|
return false;
|
|
}
|
|
if (!lengthProp.ReturnType.IsKnownType(KnownTypeCode.Int32))
|
|
return false;
|
|
if (lengthProp.IsVirtual && call.OpCode != OpCode.CallVirt)
|
|
return false;
|
|
if (call.Arguments.Count != 1)
|
|
return false;
|
|
return MatchContainerVar(call.Arguments[0], ref containerVar);
|
|
}
|
|
|
|
static bool MatchContainerVar(ILInstruction inst, ref ILVariable containerVar)
|
|
{
|
|
if (containerVar != null)
|
|
{
|
|
return inst.MatchLdLoc(containerVar) || inst.MatchLdLoca(containerVar);
|
|
}
|
|
else
|
|
{
|
|
return inst.MatchLdLoc(out containerVar) || inst.MatchLdLoca(out containerVar);
|
|
}
|
|
}
|
|
|
|
enum IndexKind
|
|
{
|
|
/// <summary>
|
|
/// indexLoad is an integer, from the start of the container
|
|
/// </summary>
|
|
FromStart,
|
|
/// <summary>
|
|
/// indexLoad is loading the address of a System.Index struct
|
|
/// </summary>
|
|
RefSystemIndex,
|
|
/// <summary>
|
|
/// indexLoad is an integer, from the end of the container
|
|
/// </summary>
|
|
FromEnd,
|
|
/// <summary>
|
|
/// Always equivalent to `0`, used for the start-index when slicing without a startpoint `a[..end]`
|
|
/// </summary>
|
|
TheStart,
|
|
/// <summary>
|
|
/// Always equivalent to `^0`, used for the end-index when slicing without an endpoint `a[start..]`
|
|
/// </summary>
|
|
TheEnd,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Matches an instruction computing an offset:
|
|
/// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
|
|
/// or
|
|
/// binary.sub.i4(ldloc containerLengthVar, indexLoad)
|
|
///
|
|
/// Anything else not matching these patterns is interpreted as an `int` expression from the start of the container.
|
|
/// </summary>
|
|
static IndexKind MatchGetOffset(ILInstruction inst, out ILInstruction indexLoad,
|
|
ILVariable containerLengthVar, ref ILVariable containerVar)
|
|
{
|
|
indexLoad = inst;
|
|
if (MatchContainerLength(inst, containerLengthVar, ref containerVar))
|
|
{
|
|
indexLoad = new LdcI4(0);
|
|
return IndexKind.TheEnd;
|
|
}
|
|
else if (inst is CallInstruction call)
|
|
{
|
|
// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
|
|
if (call.Method.Name != "GetOffset")
|
|
return IndexKind.FromStart;
|
|
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
|
|
return IndexKind.FromStart;
|
|
if (call.Arguments.Count != 2)
|
|
return IndexKind.FromStart;
|
|
if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar))
|
|
return IndexKind.FromStart;
|
|
indexLoad = call.Arguments[0];
|
|
return IndexKind.RefSystemIndex;
|
|
}
|
|
else if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub)
|
|
{
|
|
if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
|
|
return IndexKind.FromStart;
|
|
// binary.sub.i4(ldloc containerLengthVar, indexLoad)
|
|
if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar))
|
|
return IndexKind.FromStart;
|
|
indexLoad = bni.Right;
|
|
return IndexKind.FromEnd;
|
|
}
|
|
else
|
|
{
|
|
return IndexKind.FromStart;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Matches an instruction computing a slice length:
|
|
/// binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
|
|
/// </summary>
|
|
static bool MatchSliceLength(ILInstruction inst, out IndexKind endIndexKind, out ILInstruction endIndexLoad, ILVariable containerLengthVar, ref ILVariable containerVar, ILVariable startOffsetVar)
|
|
{
|
|
endIndexKind = default;
|
|
endIndexLoad = default;
|
|
if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub)
|
|
{
|
|
if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
|
|
return false;
|
|
if (startOffsetVar == null)
|
|
{
|
|
// When slicing without explicit start point: `a[..endIndex]`
|
|
if (!bni.Right.MatchLdcI4(0))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!bni.Right.MatchLdLoc(startOffsetVar))
|
|
return false;
|
|
}
|
|
endIndexKind = MatchGetOffset(bni.Left, out endIndexLoad, containerLengthVar, ref containerVar);
|
|
return true;
|
|
}
|
|
else if (startOffsetVar == null)
|
|
{
|
|
// When slicing without explicit start point: `a[..endIndex]`, the compiler doesn't always emit the "- 0".
|
|
endIndexKind = MatchGetOffset(inst, out endIndexLoad, containerLengthVar, ref containerVar);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|