switches :: Opcode -> ([Label] -> BranchMap -> (Label -> InstrM Int) -> InstrM ())
         -> BranchMap -> Maybe Instr -> Instr
switches opc f branchMap deflt = Instr $ do
  baseOffset <- getOffset
  lb <- saveLastBranch
  unInstr $ op opc
  modifyStack' $ CF.pop jint
  InstrState { isOffset = offset
             , isCtrlFlow = cf
             , isLabelTable = lt } <- get
  -- Align to 4-byte boundary
  let padding = (4 - (offset `mod` 4)) `mod` 4
      numBranches = IntMap.size branchMap
      branchList = map snd $ toInt32AscList branchMap
  writeBytes . BS.pack . replicate (fromIntegral padding) $ 0
  ls@(defaultLabel:breakLabel:labels) <- mkSystemLabels (1 + 1 + numBranches)
  let relOffsetToLabel = offsetToLabel' (Offset baseOffset)
  relOffsetToLabel defaultLabel >>= writeI32
  f ls branchMap relOffsetToLabel
  let branches = (defaultLabel, fromMaybe mempty deflt)
               : zip labels branchList
  cfsAndLtsAndLbs <- forM branches $ \(l, i) ->
    withCFState cf lt $ unInstr $
      putLabel l <> i <> condGoto Special breakLabel
  let (cfs, lts, mlbs) = unzip3 cfsAndLtsAndLbs
  putCtrlFlow' $ CF.merge cf cfs
  mergeLabels lts
  unInstr $ putLabel breakLabel
  resetLastBranch $ fromMaybe lb (selectLatestLBs mlbs)
switches :: Opcode -> ([Label] -> BranchMap -> (Label -> InstrM Int) -> InstrM ())
         -> BranchMap -> Maybe Instr -> Instr
switches opc f branchMap deflt = Instr $ do
  baseOffset <- getOffset
  lb <- saveLastBranch
  unInstr $ op opc
  modifyStack' $ CF.pop jint
  InstrState { isOffset = offset
             , isCtrlFlow = cf
             , isLabelTable = lt } <- get
  -- Align to 4-byte boundary
  let padding = (4 - (offset `mod` 4)) `mod` 4
      numBranches = IntMap.size branchMap
      branchList = map snd $ toInt32AscList branchMap
  writeBytes . BS.pack . replicate (fromIntegral padding) $ 0
  ls@(defaultLabel:breakLabel:labels) <- mkSystemLabels (1 + 1 + numBranches)
  let relOffsetToLabel = offsetToLabel' (Offset baseOffset)
  relOffsetToLabel defaultLabel >>= writeI32
  f ls branchMap relOffsetToLabel
  let branches = (defaultLabel, fromMaybe mempty deflt)
               : zip labels branchList
  cfsAndLtsAndLbs <- forM branches $ \(l, i) ->
    withCFState cf lt $ unInstr $
      putLabel l <> i <> condGoto Special breakLabel
  let (cfs, lts, mlbs) = unzip3 cfsAndLtsAndLbs
  putCtrlFlow' $ CF.merge cf cfs
  mergeLabels lts
  unInstr $ putLabel breakLabel
  resetLastBranch $ fromMaybe lb (selectLatestLBs mlbs)
switches opc f branchMap deflt = Instr $ do
  baseOffset <- getOffset
  lb <- saveLastBranch
  unInstr $ op opc
  modifyStack' $ CF.pop jint
  InstrState { isOffset = offset
             , isCtrlFlow = cf
             , isLabelTable = lt } <- get
  -- Align to 4-byte boundary
  let padding = (4 - (offset `mod` 4)) `mod` 4
      numBranches = IntMap.size branchMap
      branchList = map snd $ toInt32AscList branchMap
  writeBytes . BS.pack . replicate (fromIntegral padding) $ 0
  ls@(defaultLabel:breakLabel:labels) <- mkSystemLabels (1 + 1 + numBranches)
  let relOffsetToLabel = offsetToLabel' (Offset baseOffset)
  relOffsetToLabel defaultLabel >>= writeI32
  f ls branchMap relOffsetToLabel
  let branches = (defaultLabel, fromMaybe mempty deflt)
               : zip labels branchList
  cfsAndLtsAndLbs <- forM branches $ \(l, i) ->
    withCFState cf lt $ unInstr $
      putLabel l <> i <> condGoto Special breakLabel
  let (cfs, lts, mlbs) = unzip3 cfsAndLtsAndLbs
  putCtrlFlow' $ CF.merge cf cfs
  mergeLabels lts
  unInstr $ putLabel breakLabel
  resetLastBranch $ fromMaybe lb (selectLatestLBs mlbs)