private BasicBlock BasicBlockFromIndex(int start, Context context) { if (start >= context.Block.Count) throw new InvalidOperationException("run off of end of instructions"); var offset = context.Block[start].Offset; var i = start; Func<int, Instructions> extractInclusive = j => context.Block.Peephole(start, j - start + 1, tracer); Func<int, Instructions> extractExclusive = j => context.Block.Peephole(start, j - start, tracer); while (i < context.Block.Count) { var instruction = context.Block[i]; if (i > start && targets.Contains(instruction.Offset)) { var jbb = new JumpBasicBlock(nextBlockId++, extractExclusive(i)); context.OffsetToBlock.Add(offset, jbb); jbb.Target = BasicBlockFromLocalTarget(instruction.Offset, context); jbb.Target.Sources.Add(jbb); return jbb; } else { switch (instruction.Flavor) { case InstructionFlavor.Misc: { var misci = (MiscInstruction)instruction; switch (misci.Op) { case MiscOp.Throw: { var nrbb = new NonReturningBasicBlock(nextBlockId++, extractInclusive(i)); context.OffsetToBlock.Add(offset, nrbb); return nrbb; } case MiscOp.Rethrow: { var handlerContext = context as HandlerContext; if (handlerContext == null || !(handlerContext.ILHandler is CatchTryInstructionHandler)) throw new InvalidOperationException("rethrow not within catch"); var nrbb = new NonReturningBasicBlock(nextBlockId++, extractInclusive(i)); context.OffsetToBlock.Add(offset, nrbb); return nrbb; } case MiscOp.Ret: case MiscOp.RetVal: { if (context is TryContext || context is HandlerContext) throw new InvalidOperationException("return within a try or handler block"); var nrbb = new NonReturningBasicBlock(nextBlockId++, extractInclusive(i)); context.OffsetToBlock.Add(offset, nrbb); return nrbb; } case MiscOp.Endfinally: { var handlerContext = context as HandlerContext; if (handlerContext == null) throw new InvalidOperationException ("endfinally not within fault/finally block"); switch (handlerContext.ILHandler.Flavor) { case HandlerFlavor.Catch: case HandlerFlavor.Filter: throw new InvalidOperationException ("endfinally not within fault/finally block"); case HandlerFlavor.Fault: { var efbb = new EndFaultBasicBlock (nextBlockId++, extractExclusive(i), (TBBFaultHandler)handlerContext.TBBHandler); context.OffsetToBlock.Add(offset, efbb); return efbb; } case HandlerFlavor.Finally: { var efbb = new EndFinallyBasicBlock (nextBlockId++, extractExclusive(i), (TBBFinallyHandler)handlerContext.TBBHandler, misci.BeforeState.Depth); context.OffsetToBlock.Add(offset, efbb); return efbb; } default: throw new ArgumentOutOfRangeException(); } } default: break; } break; } case InstructionFlavor.Branch: { var bri = (BranchInstruction)instruction; switch (bri.Op) { case BranchOp.Br: { var jbb = new JumpBasicBlock(nextBlockId++, extractExclusive(i)); context.OffsetToBlock.Add(offset, jbb); jbb.Target = BasicBlockFromLocalTarget(bri.Target, context); jbb.Target.Sources.Add(jbb); return jbb; } case BranchOp.Brtrue: case BranchOp.Brfalse: case BranchOp.Breq: case BranchOp.Brne: case BranchOp.BrLt: case BranchOp.BrLe: case BranchOp.BrGt: case BranchOp.BrGe: { if (i + 1 >= context.Block.Count) throw new InvalidOperationException("run off end of instructions"); var bbb = new BranchBasicBlock (nextBlockId++, extractExclusive(i), Test.FromBranchOp(bri.Op, bri.IsUnsigned, bri.Type)); context.OffsetToBlock.Add(offset, bbb); bbb.Target = BasicBlockFromLocalTarget(bri.Target, context); bbb.Target.Sources.Add(bbb); bbb.Fallthrough = BasicBlockFromLocalTarget(context.Block[i + 1].Offset, context); if (!bbb.Fallthrough.Equals(bbb.Target)) bbb.Fallthrough.Sources.Add(bbb); return bbb; } case BranchOp.Leave: { var handlerPopCount = default(int); var stackPopCount = bri.BeforeState.Depth; var leftContext = default(bool); var bb = BasicBlockFromTarget (bri.Target, context, out leftContext, out handlerPopCount); if (!leftContext) { // Not leaving try or handler block, so just empty stack and branch var lbb = new LeaveBasicBlock (nextBlockId++, extractExclusive(i), stackPopCount); context.OffsetToBlock.Add(offset, lbb); lbb.Target = bb; lbb.Target.Sources.Add(lbb); return lbb; } else { var tryContext = context as TryContext; if (tryContext != null) { // Poping at least one exception handler and branching var ltbb = new LeaveTryBasicBlock (nextBlockId++, extractExclusive(i), tryContext.TryBasicBlock, handlerPopCount, stackPopCount); context.OffsetToBlock.Add(offset, ltbb); ltbb.Target = bb; ltbb.Target.Sources.Add(ltbb); return ltbb; } else { var handlerContext = context as HandlerContext; if (handlerContext != null) { switch (handlerContext.ILHandler.Flavor) { case HandlerFlavor.Catch: { // Poping zero or more exception handlers and branching var lcbb = new LeaveCatchBasicBlock (nextBlockId++, extractExclusive(i), (TBBCatchHandler)handlerContext.TBBHandler, handlerPopCount, stackPopCount); lcbb.Target = bb; lcbb.Target.Sources.Add(lcbb); return lcbb; } case HandlerFlavor.Filter: throw new NotSupportedException("filter"); case HandlerFlavor.Fault: throw new InvalidOperationException("leaving fault block"); case HandlerFlavor.Finally: throw new InvalidOperationException("leaving finally block"); default: throw new ArgumentOutOfRangeException(); } } else throw new InvalidOperationException ("no try or handler context to leave"); } } } default: throw new ArgumentOutOfRangeException(); } } case InstructionFlavor.Switch: { if (i + 1 >= context.Block.Count) throw new InvalidOperationException("run off end of instructions"); var switchi = (SwitchInstruction)instruction; var sbb = new SwitchBasicBlock(nextBlockId++, extractExclusive(i)); context.OffsetToBlock.Add(offset, sbb); var seen = new Set<BasicBlock>(); foreach (var t in switchi.CaseTargets) { var target = BasicBlockFromLocalTarget(t, context); sbb.CaseTargets.Add(target); if (!seen.Contains(target)) { target.Sources.Add(sbb); seen.Add(target); } } sbb.Fallthrough = BasicBlockFromLocalTarget(context.Block[i + 1].Offset, context); if (!seen.Contains(sbb.Fallthrough)) sbb.Fallthrough.Sources.Add(sbb); return sbb; } case InstructionFlavor.Try: { // Try is known to be a target, thus i == start and extract(i) would yield empty var tryi = (TryInstruction)instruction; var parent = default(TryBasicBlock); var tryContext = context as TryContext; if (tryContext != null) parent = tryContext.TryBasicBlock; else { var handlerContext = context as HandlerContext; if (handlerContext != null) parent = handlerContext.TryContext.TryBasicBlock; } var tbb = new TryBasicBlock(nextBlockId++, parent, instruction.BeforeState); context.OffsetToBlock.Add(offset, tbb); var subTryContext = new TryContext(tryi.Body, context, tbb); tbb.Body = BasicBlockFromInstructions(subTryContext); tbb.Body.Sources.Add(tbb); foreach (var ilHandler in tryi.Handlers) { var tbbHandler = default(TBBHandler); switch (ilHandler.Flavor) { case HandlerFlavor.Catch: { var catchh = (CatchTryInstructionHandler)ilHandler; tbbHandler = new TBBCatchHandler(tbb, catchh.Type); break; } case HandlerFlavor.Filter: throw new NotSupportedException("filter blocks"); case HandlerFlavor.Fault: { tbbHandler = new TBBFaultHandler(tbb); break; } case HandlerFlavor.Finally: { tbbHandler = new TBBFinallyHandler(tbb); break; } default: throw new ArgumentOutOfRangeException(); } var subHandlerContext = new HandlerContext (ilHandler.Body, ilHandler, subTryContext, tbbHandler); tbbHandler.Body = BasicBlockFromInstructions(subHandlerContext); tbbHandler.Body.Sources.Add(tbb); tbb.Handlers.Add(tbbHandler); } return tbb; } default: break; } i++; } } throw new InvalidOperationException("ran off of end of instructions"); }
private string TryReduceSwitch(BasicBlock root, SwitchBasicBlock switchbb) { // Build a map from basic blocks to the cases which enter it. // Also collect some simple stats. var caseMap = new Map<BasicBlock, Set<int>> { { switchbb.Fallthrough, new Set<int> { -1 } } }; var allNonReturning = switchbb.Fallthrough is NonReturningBasicBlock; var allHaveOneSource = switchbb.Fallthrough.Sources.Count == 1; var jumpTargets = new Set<BasicBlock>(); var jumpbb = switchbb.Fallthrough as JumpBasicBlock; if (jumpbb != null) jumpTargets.Add(jumpbb.Target); for (var i = 0; i < switchbb.CaseTargets.Count; i++) { var t = switchbb.CaseTargets[i]; if (!(t is NonReturningBasicBlock)) allNonReturning = false; if (t.Sources.Count != 1) allHaveOneSource = false; jumpbb = t as JumpBasicBlock; if (jumpbb != null) jumpTargets.Add(jumpbb.Target); var values = default(Set<int>); if (caseMap.TryGetValue(t, out values)) // Block is shared amongst switch cases values.Add(i); else caseMap.Add(t, new Set<int> { i }); } if (caseMap.Count == 1) { // CASE 1: all switch cases go to the same block, so replace the switch with a pop and jump var instructions = switchbb.Block.CopyContents(); instructions.Add (new MiscInstruction(NextInstructionId--, MiscOp.Pop) { BeforeState = switchbb.Block.BeforeState, AfterState = switchbb.Fallthrough.Block.BeforeState }); var newbb = new JumpBasicBlock (nextBlockId++, Peephole(switchbb.Block.BeforeState, instructions), switchbb.Fallthrough); root.Coalesce(switchbb, new Set<BasicBlock> { switchbb }, newbb); return string.Format("removed switch at B{0}", switchbb.Id); } if (allNonReturning && allHaveOneSource) { // CASE 2: all switch cases are non-returning and have one source, so move them all into // a non-returing block with switch var group = new Set<BasicBlock> { switchbb }; var cases = new Seq<StructuralCase>(); foreach (var kv in caseMap) { cases.Add(new StructuralCase(kv.Value, kv.Key.Block)); group.Add(kv.Key); } var ssi = new StructuralSwitchInstruction(NextInstructionId--, switchbb.Block, cases) { BeforeState = switchbb.Block.BeforeState, AfterState = switchbb.Fallthrough.Block.AfterState }; var newbb = new NonReturningBasicBlock(nextBlockId++, new Instructions(ssi)); root.Coalesce(switchbb, group, newbb); return String.Format("non-returning structural switch from B{0}", switchbb.Id); } if (jumpTargets.Count == 1) { var theJumpTarget = jumpTargets[0]; var allIntermediateAreNonReturningOrJumpToTarget = true; foreach (var kv in caseMap) { if (!kv.Key.Equals(theJumpTarget)) { if (kv.Key.Sources.Count > 1 || !(kv.Key is NonReturningBasicBlock || kv.Key is JumpBasicBlock)) allIntermediateAreNonReturningOrJumpToTarget = false; } } if (allIntermediateAreNonReturningOrJumpToTarget) { // CASE 3: all switch cases either jump directly to the switch successor or go via jump block, // or don't return. Inline all the cases and place the switch in a jump to target block var group = new Set<BasicBlock> { switchbb }; var cases = new Seq<StructuralCase>(); var afterState = default(MachineState); foreach (var kv in caseMap) { var block = default(Instructions); if (kv.Key.Equals(theJumpTarget)) { var bci = new BreakContinueInstruction(NextInstructionId--, BreakContinueOp.Break, null) { BeforeState = theJumpTarget.Block.BeforeState, AfterState = theJumpTarget.Block.BeforeState }; // Fallthrough to final jump target block = new Instructions(bci); } else { // Inline case block, and if not non-returning then fallthrough to final jump target var instructions = kv.Key.Block.CopyContents(); if (!kv.Key.Block.NeverReturns) { var bci = new BreakContinueInstruction (NextInstructionId--, BreakContinueOp.Break, null) { BeforeState = kv.Key.Block.AfterState, AfterState = kv.Key.Block.AfterState }; instructions.Add(bci); } group.Add(kv.Key); block = new Instructions(kv.Key.Block.BeforeState, instructions); } if (afterState == null) afterState = block.AfterState; cases.Add(new StructuralCase(kv.Value, block)); } var ssi = new StructuralSwitchInstruction(NextInstructionId--, switchbb.Block, cases) { BeforeState = switchbb.Block.BeforeState, AfterState = afterState }; var newbb = new JumpBasicBlock(nextBlockId++, new Instructions(ssi), theJumpTarget); root.Coalesce(switchbb, group, newbb); return String.Format("structural switch from B{0}", switchbb.Id); } } return null; }