mirror of https://github.com/icsharpcode/ILSpy.git
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; |
|
} |
|
} |
|
} |
|
}
|
|
|