Browse Source

Introduce array.to.pointer instruction.

This is necessary to construct pinned regions in all cases.
pull/728/head
Daniel Grunwald 9 years ago
parent
commit
7e50076671
  1. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 2
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  3. 2
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  4. 136
      ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs
  5. 2
      ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs
  6. 102
      ICSharpCode.Decompiler/IL/Instructions.cs
  7. 4
      ICSharpCode.Decompiler/IL/Instructions.tt
  8. 5
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  9. 3
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  10. 5
      ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs
  11. 36
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  12. 2
      ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -50,7 +50,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -50,7 +50,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ControlFlowSimplification(),
new ILInlining(),
new DetectPinnedRegions(),
new DetectPinRegions(),
new LoopDetection(),
new IntroduceExitPoints(),
new ConditionDetection(),

2
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -75,7 +75,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -75,7 +75,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Previous-to-last instruction might have conditional control flow,
// usually an IfInstruction with a branch:
IfInstruction ifInst = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 2) as IfInstruction;
IfInstruction ifInst = block.Instructions.SecondToLastOrDefault() as IfInstruction;
if (ifInst != null && ifInst.FalseInst.OpCode == OpCode.Nop) {
if (IsBranchToLaterTarget(ifInst.TrueInst, exitInst)) {
// "if (c) goto lateBlock; goto earlierBlock;"

2
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -45,7 +45,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
InlineReturnBlock(block);
// due to our of of basic blocks at this point,
// switch instructions can only appear as second-to-last insturction
SimplifySwitchInstruction(block.Instructions.ElementAtOrDefault(block.Instructions.Count - 2) as SwitchInstruction);
SimplifySwitchInstruction(block.Instructions.SecondToLastOrDefault() as SwitchInstruction);
}
SimplifyBranchChains(function);
CleanUpEmptyBlocks(function);

136
ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs

@ -50,18 +50,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -50,18 +50,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// This means this transform must run before LoopDetection.
/// To make our detection job easier, we must run after variable inlining.
/// </summary>
public class DetectPinnedRegions : IILTransform
public class DetectPinRegions : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
foreach (var container in function.Descendants.OfType<BlockContainer>()) {
SplitBlocksAtWritesToPinnedLocals(container);
DetectNullSafeArrayToPointer(container);
foreach (var block in container.Blocks)
Run(block);
container.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks
}
}
/// <summary>
/// Ensures that every write to a pinned local is followed by a branch instruction.
/// This ensures the 'pinning region' does not involve any half blocks, which makes it easier to extract.
/// </summary>
void SplitBlocksAtWritesToPinnedLocals(BlockContainer container)
{
for (int i = 0; i < container.Blocks.Count; i++) {
@ -84,11 +89,136 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -84,11 +89,136 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
#region null-safe array to pointer
void DetectNullSafeArrayToPointer(BlockContainer container)
{
// Detect the following pattern:
// ...
// stloc V(ldloc S)
// if (comp(ldloc S == ldnull)) br B_null_or_empty
// br B_not_null
// }
// Block B_not_null {
// if (conv i->i4 (ldlen(ldloc V))) br B_not_null_and_not_empty
// br B_null_or_empty
// }
// Block B_not_null_and_not_empty {
// stloc P(ldelema(ldloc V, ldc.i4 0, ...))
// br B_target
// }
// Block B_null_or_empty {
// stloc P(conv i4->u(ldc.i4 0))
// br B_target
// }
// And convert the whole thing into:
// ...
// stloc P(array.to.pointer(V))
// br B_target
bool modified = false;
for (int i = 0; i < container.Blocks.Count; i++) {
var block = container.Blocks[i];
ILVariable v, p;
Block targetBlock;
if (IsNullSafeArrayToPointerPattern(block, out v, out p, out targetBlock)) {
block.Instructions[block.Instructions.Count - 2] = new StLoc(p, new ArrayToPointer(new LdLoc(v)));
((Branch)block.Instructions.Last()).TargetBlock = targetBlock;
modified = true;
}
}
if (modified) {
container.Blocks.RemoveAll(b => b.IncomingEdgeCount == 0); // remove blocks made unreachable
}
}
bool IsNullSafeArrayToPointerPattern(Block block, out ILVariable v, out ILVariable p, out Block targetBlock)
{
v = null;
p = null;
targetBlock = null;
// ...
// if (comp(ldloc V == ldnull)) br B_null_or_empty
// br B_not_null
var ifInst = block.Instructions.SecondToLastOrDefault() as IfInstruction;
if (ifInst == null)
return false;
var condition = ifInst.Condition as Comp;
if (!(condition != null && condition.Kind == ComparisonKind.Equality && condition.Left.MatchLdLoc(out v) && condition.Right.MatchLdNull()))
return false;
if (v.Kind == VariableKind.StackSlot) {
// If the variable is a stack slot, that might be due to an inline assignment,
// so check the previous instruction:
var previous = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc;
if (previous.Value.MatchLdLoc(v)) {
// stloc V(ldloc S)
// if (comp(ldloc S == ldnull)) ...
v = previous.Variable;
}
}
Block nullOrEmptyBlock, notNullBlock;
return ifInst.TrueInst.MatchBranch(out nullOrEmptyBlock)
&& ifInst.FalseInst.MatchNop()
&& nullOrEmptyBlock.Parent == block.Parent
&& IsNullSafeArrayToPointerNullOrEmptyBlock(nullOrEmptyBlock, out p, out targetBlock)
&& block.Instructions.Last().MatchBranch(out notNullBlock)
&& notNullBlock.Parent == block.Parent
&& IsNullSafeArrayToPointerNotNullBlock(notNullBlock, v, p, nullOrEmptyBlock, targetBlock);
}
bool IsNullSafeArrayToPointerNotNullBlock(Block block, ILVariable v, ILVariable p, Block nullOrEmptyBlock, Block targetBlock)
{
// Block B_not_null {
// if (conv i->i4 (ldlen(ldloc V))) br B_not_null_and_not_empty
// br B_null_or_empty
// }
ILInstruction condition, trueInst, array;
Block notNullAndNotEmptyBlock;
return block.Instructions.Count == 2
&& block.Instructions[0].MatchIfInstruction(out condition, out trueInst)
&& condition.UnwrapConv().MatchLdLen(StackType.I, out array)
&& array.MatchLdLoc(v)
&& trueInst.MatchBranch(out notNullAndNotEmptyBlock)
&& notNullAndNotEmptyBlock.Parent == block.Parent
&& IsNullSafeArrayToPointerNotNullAndNotEmptyBlock(notNullAndNotEmptyBlock, v, p, targetBlock)
&& block.Instructions[1].MatchBranch(nullOrEmptyBlock);
}
bool IsNullSafeArrayToPointerNotNullAndNotEmptyBlock(Block block, ILVariable v, ILVariable p, Block targetBlock)
{
// Block B_not_null_and_not_empty {
// stloc P(ldelema(ldloc V, ldc.i4 0, ...))
// br B_target
// }
ILInstruction value;
return block.Instructions.Count == 2
&& block.Instructions[0].MatchStLoc(p, out value)
&& value.OpCode == OpCode.LdElema
&& ((LdElema)value).Array.MatchLdLoc(v)
&& ((LdElema)value).Indices.All(i => i.MatchLdcI4(0))
&& block.Instructions[1].MatchBranch(targetBlock);
}
bool IsNullSafeArrayToPointerNullOrEmptyBlock(Block block, out ILVariable p, out Block targetBlock)
{
p = null;
targetBlock = null;
// Block B_null_or_empty {
// stloc P(conv i4->u(ldc.i4 0))
// br B_target
// }
ILInstruction value;
return block.Instructions.Count == 2
&& block.Instructions[0].MatchStLoc(out p, out value)
&& p.Kind == VariableKind.PinnedLocal
&& IsNullOrZero(value)
&& block.Instructions[1].MatchBranch(out targetBlock);
}
#endregion
void Run(Block block)
{
// After SplitBlocksAtWritesToPinnedLocals(), only the second-to-last instruction in each block
// can be a write to a pinned local.
var stLoc = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 2) as StLoc;
var stLoc = block.Instructions.SecondToLastOrDefault() as StLoc;
if (stLoc == null || stLoc.Variable.Kind != VariableKind.PinnedLocal)
return;
// stLoc is a store to a pinned local.
@ -106,7 +236,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -106,7 +236,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
workList.Enqueue(entryBlock);
while (workList.Count > 0) {
Block workItem = workList.Dequeue();
StLoc workStLoc = workItem.Instructions.ElementAtOrDefault(workItem.Instructions.Count - 2) as StLoc;
StLoc workStLoc = workItem.Instructions.SecondToLastOrDefault() as StLoc;
int instructionCount;
if (workStLoc != null && workStLoc.Variable == stLoc.Variable && IsNullOrZero(workStLoc.Value)) {
// found unpin instruction: only consider branches prior to that instruction

2
ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs

@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -77,7 +77,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (slot == Block.InstructionSlot) {
Block block = (Block)inst.Parent;
return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1);
} else if (slot == TryInstruction.TryBlockSlot || slot == TryCatchHandler.BodySlot || slot == TryCatch.HandlerSlot) {
} else if (slot == TryInstruction.TryBlockSlot || slot == TryCatchHandler.BodySlot || slot == TryCatch.HandlerSlot || slot == PinnedRegion.BodySlot) {
return GetExit(inst.Parent);
} else if (slot == ILFunction.BodySlot) {
return ReturnExit;

102
ICSharpCode.Decompiler/IL/Instructions.cs

@ -171,6 +171,8 @@ namespace ICSharpCode.Decompiler.IL @@ -171,6 +171,8 @@ namespace ICSharpCode.Decompiler.IL
LdLen,
/// <summary>Load address of array element.</summary>
LdElema,
/// <summary>Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty.</summary>
ArrayToPointer,
/// <summary>Push a typed reference of type class onto the stack.</summary>
MakeRefAny,
/// <summary>Push the type token stored in a typed reference.</summary>
@ -3104,6 +3106,87 @@ namespace ICSharpCode.Decompiler.IL @@ -3104,6 +3106,87 @@ namespace ICSharpCode.Decompiler.IL
}
}
/// <summary>Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty.</summary>
public sealed partial class ArrayToPointer : ILInstruction
{
public ArrayToPointer(ILInstruction array) : base(OpCode.ArrayToPointer)
{
this.Array = array;
}
public static readonly SlotInfo ArraySlot = new SlotInfo("Array", canInlineInto: true);
ILInstruction array;
public ILInstruction Array {
get { return this.array; }
set {
ValidateChild(value);
SetChildInstruction(ref this.array, value, 0);
}
}
protected sealed override int GetChildCount()
{
return 1;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
case 0:
return this.array;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
case 0:
this.Array = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ArraySlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (ArrayToPointer)ShallowClone();
clone.Array = this.array.Clone();
return clone;
}
public override StackType ResultType { get { return StackType.Ref; } }
protected override InstructionFlags ComputeFlags()
{
return array.Flags;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.None;
}
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
output.Write('(');
this.array.WriteTo(output);
output.Write(')');
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitArrayToPointer(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitArrayToPointer(this);
}
}
/// <summary>Push a typed reference of type class onto the stack.</summary>
public sealed partial class MakeRefAny : UnaryInstruction
{
@ -3483,6 +3566,10 @@ namespace ICSharpCode.Decompiler.IL @@ -3483,6 +3566,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitArrayToPointer(ArrayToPointer inst)
{
Default(inst);
}
protected internal virtual void VisitMakeRefAny(MakeRefAny inst)
{
Default(inst);
@ -3789,6 +3876,10 @@ namespace ICSharpCode.Decompiler.IL @@ -3789,6 +3876,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitArrayToPointer(ArrayToPointer inst)
{
return Default(inst);
}
protected internal virtual T VisitMakeRefAny(MakeRefAny inst)
{
return Default(inst);
@ -3919,6 +4010,7 @@ namespace ICSharpCode.Decompiler.IL @@ -3919,6 +4010,7 @@ namespace ICSharpCode.Decompiler.IL
"sizeof",
"ldlen",
"ldelema",
"array.to.pointer",
"mkrefany",
"refanytype",
"refanyval",
@ -4503,6 +4595,16 @@ namespace ICSharpCode.Decompiler.IL @@ -4503,6 +4595,16 @@ namespace ICSharpCode.Decompiler.IL
array = default(ILInstruction);
return false;
}
public bool MatchArrayToPointer(out ILInstruction array)
{
var inst = this as ArrayToPointer;
if (inst != null) {
array = inst.Array;
return true;
}
array = default(ILInstruction);
return false;
}
public bool MatchMakeRefAny(out ILInstruction argument, out IType type)
{
var inst = this as MakeRefAny;

4
ICSharpCode.Decompiler/IL/Instructions.tt

@ -199,7 +199,9 @@ @@ -199,7 +199,9 @@
new OpCode("ldelema", "Load address of array element.",
CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
MayThrow, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("array.to.pointer", "Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty.",
CustomArguments("array"), ResultType("Ref")),
new OpCode("mkrefany", "Push a typed reference of type class onto the stack.",
CustomClassName("MakeRefAny"), Unary, HasTypeOperand, ResultType("O")),
new OpCode("refanytype", "Push the type token stored in a typed reference.",

5
ICSharpCode.Decompiler/IL/Instructions/Conv.cs

@ -239,5 +239,10 @@ namespace ICSharpCode.Decompiler.IL @@ -239,5 +239,10 @@ namespace ICSharpCode.Decompiler.IL
flags |= InstructionFlags.MayThrow;
return flags;
}
public override ILInstruction UnwrapConv()
{
return Argument.UnwrapConv();
}
}
}

3
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -195,6 +195,9 @@ namespace ICSharpCode.Decompiler.IL @@ -195,6 +195,9 @@ namespace ICSharpCode.Decompiler.IL
{
var output = new PlainTextOutput();
WriteTo(output);
if (!ILRange.IsEmpty) {
output.Write(" at IL_" + ILRange.Start.ToString("x4"));
}
return output.ToString();
}

5
ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs

@ -325,6 +325,11 @@ namespace ICSharpCode.Decompiler.IL @@ -325,6 +325,11 @@ namespace ICSharpCode.Decompiler.IL
return list.Count > 0 ? list[list.Count - 1] : null;
}
public T SecondToLastOrDefault()
{
return list.Count > 1 ? list[list.Count - 2] : null;
}
public T ElementAtOrDefault(int index)
{
if (index >= 0 && index < list.Count)

36
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -112,6 +112,37 @@ namespace ICSharpCode.Decompiler.IL @@ -112,6 +112,37 @@ namespace ICSharpCode.Decompiler.IL
return inst != null && inst.TargetContainer == targetContainer;
}
public bool MatchIfInstruction(out ILInstruction condition, out ILInstruction trueInst, out ILInstruction falseInst)
{
var inst = this as IfInstruction;
if (inst != null) {
condition = inst.Condition;
trueInst = inst.TrueInst;
falseInst = inst.FalseInst;
return true;
}
condition = null;
trueInst = null;
falseInst = null;
return false;
}
/// <summary>
/// Matches an if instruction where the false instruction is a nop.
/// </summary>
public bool MatchIfInstruction(out ILInstruction condition, out ILInstruction trueInst)
{
var inst = this as IfInstruction;
if (inst != null && inst.FalseInst.MatchNop()) {
condition = inst.Condition;
trueInst = inst.TrueInst;
return true;
}
condition = null;
trueInst = null;
return false;
}
public bool MatchTryCatchHandler(out ILVariable variable)
{
var inst = this as TryCatchHandler;
@ -122,5 +153,10 @@ namespace ICSharpCode.Decompiler.IL @@ -122,5 +153,10 @@ namespace ICSharpCode.Decompiler.IL
variable = null;
return false;
}
public virtual ILInstruction UnwrapConv()
{
return this;
}
}
}

2
ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

@ -133,7 +133,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -133,7 +133,7 @@ namespace ICSharpCode.Decompiler.Tests
info.RedirectStandardOutput = true;
Console.WriteLine($"\"{info.FileName}\" {info.Arguments}");
using (var p = Process.Start(info)) {
Regex errorRegex = new Regex(@"^[\w\d.\\]+\(\d+,\d+\):");
Regex errorRegex = new Regex(@"^[\w\d.\\-]+\(\d+,\d+\):");
string suffix = $" [{projectFile}]";
string line;
while ((line = p.StandardOutput.ReadLine()) != null) {

Loading…
Cancel
Save