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;
        }