@ -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 DetectPinned Regions : 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