/// <summary> /// For an if statement with an unreachable end point and no else block, /// inverts to match IL order of the first statement of each branch /// </summary> private void ImproveILOrdering(Block block, IfInstruction ifInst, Block continueTarget) { if (!block.HasFlag(InstructionFlags.EndPointUnreachable) || !ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable) || !ifInst.FalseInst.MatchNop()) { return; } Debug.Assert(ifInst != block.Instructions.Last()); var trueRangeStart = ConditionDetection.GetStartILOffset(ifInst.TrueInst, out bool trueRangeIsEmpty); var falseRangeStart = ConditionDetection.GetStartILOffset(block.Instructions[block.Instructions.IndexOf(ifInst) + 1], out bool falseRangeIsEmpty); if (trueRangeIsEmpty || falseRangeIsEmpty || falseRangeStart >= trueRangeStart) { return; } if (block.Instructions.Last() is Leave leave && !leave.IsLeavingFunction && leave.TargetContainer.Kind == ContainerKind.Normal) { // non-keyword leave. Can't move out of the last position in the block (fall-through) without introducing goto, unless it can be replaced with a keyword (return/continue) if (!CanDuplicateExit(block.Instructions.Last(), continueTarget, out var keywordExit)) { return; } context.Step("Replace leave with keyword exit", ifInst.TrueInst); block.Instructions.Last().ReplaceWith(keywordExit.Clone()); } ConditionDetection.InvertIf(block, ifInst, context); }
/// <summary> /// Reduce Nesting in if/else statements by duplicating an exit instruction. /// Does not affect IL order /// </summary> private bool ReduceNesting(Block block, IfInstruction ifInst, ILInstruction exitInst) { // start tallying stats for heuristics from then and else-if blocks int maxStatements = 0, maxDepth = 0; UpdateStats(ifInst.TrueInst, ref maxStatements, ref maxDepth); // if (cond) { ... } exit; if (ifInst.FalseInst.MatchNop()) { // a separate heuristic tp ShouldReduceNesting as there is visual balancing to be performed based on number of statments if (maxDepth < 2) { return(false); } // -> // if (!cond) exit; // ...; exit; EnsureEndPointUnreachable(ifInst.TrueInst, exitInst); EnsureEndPointUnreachable(block, exitInst); ConditionDetection.InvertIf(block, ifInst, context); return(true); } // else-if trees are considered as a single group from the root IfInstruction if (GetElseIfParent(ifInst) != null) { return(false); } // find the else block and tally stats for each else-if block while (Block.Unwrap(ifInst.FalseInst) is IfInstruction elseIfInst) { UpdateStats(elseIfInst.TrueInst, ref maxStatements, ref maxDepth); ifInst = elseIfInst; } if (!ShouldReduceNesting(ifInst.FalseInst, maxStatements, maxDepth)) { return(false); } // extract the else block and insert exit points all the way up the else-if tree do { var elseIfInst = GetElseIfParent(ifInst); // if (cond) { ... } else { ... } exit; // -> // if (cond) { ...; exit; } // ...; exit; EnsureEndPointUnreachable(ifInst.TrueInst, exitInst); ExtractElseBlock(ifInst); ifInst = elseIfInst; } while (ifInst != null); return(true); }
/// <summary> /// For an if statement with an unreachable end point and no else block, /// inverts to match IL order of the first statement of each branch /// </summary> private void ImproveILOrdering(Block block, IfInstruction ifInst) { if (!block.HasFlag(InstructionFlags.EndPointUnreachable) || !ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable) || !ifInst.FalseInst.MatchNop()) { return; } Debug.Assert(ifInst != block.Instructions.Last()); var trueRangeStart = ConditionDetection.GetStartILOffset(ifInst.TrueInst, out bool trueRangeIsEmpty); var falseRangeStart = ConditionDetection.GetStartILOffset(block.Instructions[block.Instructions.IndexOf(ifInst) + 1], out bool falseRangeIsEmpty); if (!trueRangeIsEmpty && !falseRangeIsEmpty && falseRangeStart < trueRangeStart) { ConditionDetection.InvertIf(block, ifInst, context); } }
bool MatchWhileLoop(BlockContainer loop, out IfInstruction condition, out Block loopBody) { // ConditionDetection favours leave inside if and branch at end of block // while-loop: // if (!loop-condition) leave loop-container // ... condition = null; loopBody = loop.EntryPoint; if (!(loopBody.Instructions[0] is IfInstruction ifInstruction)) { return(false); } if (!ifInstruction.FalseInst.MatchNop()) { return(false); } if (UsesVariableCapturedInLoop(loop, ifInstruction.Condition)) { return(false); } condition = ifInstruction; if (!ifInstruction.TrueInst.MatchLeave(loop)) { // sometimes the loop-body is nested within the if // if (loop-condition) { loop-body } // leave loop-container if (loopBody.Instructions.Count != 2 || !loop.EntryPoint.Instructions.Last().MatchLeave(loop)) { return(false); } if (!ifInstruction.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable)) { ((Block)ifInstruction.TrueInst).Instructions.Add(new Leave(loop)); } ConditionDetection.InvertIf(loopBody, ifInstruction, context); } context.Step("Transform to while (condition) loop", loop); loop.Kind = ContainerKind.While; //invert comparison ifInstruction.Condition = Comp.LogicNot(ifInstruction.Condition); ifInstruction.FalseInst = ifInstruction.TrueInst; //move the rest of the body into a new block loopBody = ConditionDetection.ExtractBlock(loop.EntryPoint, 1, loop.EntryPoint.Instructions.Count); loop.Blocks.Insert(1, loopBody); if (!loopBody.HasFlag(InstructionFlags.EndPointUnreachable)) { loopBody.Instructions.Add(new Leave(loop)); } ifInstruction.TrueInst = new Branch(loopBody); ExpressionTransforms.RunOnSingleStatement(ifInstruction, context); // Analyze conditions and decide whether to move some of them out of the condition block: /*var conditions = new List<ILInstruction>(); * SplitConditions(condition.Condition, conditions); * // Break apart conditions that could be a MoveNext call followed by a Current accessor call: * if (MightBeHeaderOfForEach(loop, conditions)) { * ifInstruction.Condition = conditions[0]; * foreach (var cond in conditions.Skip(1).Reverse()) { * IfInstruction inst; * loopBody.Instructions.Insert(0, inst = new IfInstruction(Comp.LogicNot(cond), new Leave(loop))); * ExpressionTransforms.RunOnSingleStatment(inst, context); * } * }*/ return(true); }
/// <summary> /// Reduce Nesting in if/else statements by duplicating an exit instruction. /// Does not affect IL order /// </summary> private bool ReduceNesting(Block block, IfInstruction ifInst, ILInstruction exitInst) { // start tallying stats for heuristics from then and else-if blocks int maxStatements = 0, maxDepth = 0; UpdateStats(ifInst.TrueInst, ref maxStatements, ref maxDepth); // if (cond) { ... } exit; if (ifInst.FalseInst.MatchNop()) { // a separate heuristic tp ShouldReduceNesting as there is visual balancing to be performed based on number of statments if (maxDepth < 2) { return(false); } // -> // if (!cond) exit; // ...; exit; EnsureEndPointUnreachable(ifInst.TrueInst, exitInst); EnsureEndPointUnreachable(block, exitInst); ConditionDetection.InvertIf(block, ifInst, context); return(true); } // else-if trees are considered as a single group from the root IfInstruction if (GetElseIfParent(ifInst) != null) { return(false); } // find the else block and tally stats for each else-if block while (Block.Unwrap(ifInst.FalseInst) is IfInstruction elseIfInst) { UpdateStats(elseIfInst.TrueInst, ref maxStatements, ref maxDepth); ifInst = elseIfInst; } if (!ShouldReduceNesting(ifInst.FalseInst, maxStatements, maxDepth)) { return(false); } // extract the else block and insert exit points all the way up the else-if tree do { var elseIfInst = GetElseIfParent(ifInst); // if (cond) { ... } else { ... } exit; // -> // if (cond) { ...; exit; } // ...; exit; EnsureEndPointUnreachable(ifInst.TrueInst, exitInst); if (ifInst.FalseInst.HasFlag(InstructionFlags.EndPointUnreachable)) { Debug.Assert(ifInst.HasFlag(InstructionFlags.EndPointUnreachable)); Debug.Assert(ifInst.Parent == block); int removeAfter = ifInst.ChildIndex + 1; if (removeAfter < block.Instructions.Count) { // Remove all instructions that ended up dead // (this should just be exitInst itself) Debug.Assert(block.Instructions.SecondToLastOrDefault() == ifInst); Debug.Assert(block.Instructions.Last() == exitInst); block.Instructions.RemoveRange(removeAfter, block.Instructions.Count - removeAfter); } } ExtractElseBlock(ifInst); ifInst = elseIfInst; } while (ifInst != null); return(true); }