def inject_loop_body(self, switch_ir, loop_ir, caller_max_label,
                         dont_replace, switch_data):
        """
        Injects the "loop body" held in `loop_ir` into `switch_ir` where ever
        there is a statement of the form `SENTINEL.<int> = RHS`. It also:
        * Finds and then deliberately does not relabel non-local jumps so as to
          make the switch table suitable for injection into the IR from which
          the loop body was derived.
        * Looks for `typed_getitem` and wires them up to loop body version
          specific variables or, if possible, directly writes in their constant
          value at their use site.

        Args:
        - switch_ir, the switch table with SENTINELS as generated by
          self.gen_switch
        - loop_ir, the IR of the loop blocks (derived from the original func_ir)
        - caller_max_label, the maximum label in the func_ir caller
        - dont_replace, variables that should not be renamed (to handle
          references to variables that are incoming at the loop head/escaping at
          the loop exit.
        - switch_data, the switch table data used to generated the switch_ir,
          can be generated by self.analyse_tuple.

        Returns:
        - A type specific switch table with each case containing a versioned
          loop body suitable for injection as a replacement for the loop_ir.
        """

        # Switch IR came from code gen, immediately relabel to prevent
        # collisions with IR derived from the user code (caller)
        switch_ir.blocks = self.add_offset_to_labels_w_ignore(
            switch_ir.blocks, caller_max_label + 1)

        # Find the sentinels and validate the form
        sentinel_exits = set()
        sentinel_blocks = []
        for lbl, blk in switch_ir.blocks.items():
            for i, stmt in enumerate(blk.body):
                if isinstance(stmt, ir.Assign):
                    if "SENTINEL" in stmt.target.name:
                        sentinel_blocks.append(lbl)
                        sentinel_exits.add(blk.body[-1].target)
                        break

        assert len(sentinel_exits) == 1  # should only be 1 exit
        switch_ir.blocks.pop(sentinel_exits.pop())  # kill the exit, it's dead

        # find jumps that are non-local, we won't relabel these
        ignore_set = set()
        local_lbl = [x for x in loop_ir.blocks.keys()]
        for lbl, blk in loop_ir.blocks.items():
            for i, stmt in enumerate(blk.body):
                if isinstance(stmt, ir.Jump):
                    if stmt.target not in local_lbl:
                        ignore_set.add(stmt.target)
                if isinstance(stmt, ir.Branch):
                    if stmt.truebr not in local_lbl:
                        ignore_set.add(stmt.truebr)
                    if stmt.falsebr not in local_lbl:
                        ignore_set.add(stmt.falsebr)

        # make sure the generated switch table matches the switch data
        assert len(sentinel_blocks) == len(switch_data)

        # replace the sentinel_blocks with the loop body
        for lbl, branch_ty in zip(sentinel_blocks, switch_data.keys()):
            loop_blocks = deepcopy(loop_ir.blocks)
            # relabel blocks WRT switch table, each block replacement will shift
            # the maximum label
            max_label = max(switch_ir.blocks.keys())
            loop_blocks = self.add_offset_to_labels_w_ignore(
                loop_blocks, max_label + 1, ignore_set)

            # start label
            loop_start_lbl = min(loop_blocks.keys())

            # fix the typed_getitem locations in the loop blocks
            for blk in loop_blocks.values():
                new_body = []
                for stmt in blk.body:
                    if isinstance(stmt, ir.Assign):
                        if (isinstance(stmt.value, ir.Expr) and
                                stmt.value.op == "typed_getitem"):
                            if isinstance(branch_ty, types.Literal):
                                scope = switch_ir.blocks[lbl].scope
                                new_const_name = scope.redefine(
                                    "branch_const", stmt.loc).name
                                new_const_var = ir.Var(
                                    blk.scope, new_const_name, stmt.loc)
                                new_const_val = ir.Const(
                                    branch_ty.literal_value, stmt.loc)
                                const_assign = ir.Assign(
                                    new_const_val, new_const_var, stmt.loc)
                                new_assign = ir.Assign(
                                    new_const_var, stmt.target, stmt.loc)
                                new_body.append(const_assign)
                                new_body.append(new_assign)
                                dont_replace.append(new_const_name)
                            else:
                                orig = stmt.value
                                new_typed_getitem = ir.Expr.typed_getitem(
                                    value=orig.value, dtype=branch_ty,
                                    index=orig.index, loc=orig.loc)
                                new_assign = ir.Assign(
                                    new_typed_getitem, stmt.target, stmt.loc)
                                new_body.append(new_assign)
                        else:
                            new_body.append(stmt)
                    else:
                        new_body.append(stmt)
                blk.body = new_body

            # rename
            var_table = get_name_var_table(loop_blocks)
            drop_keys = []
            for k, v in var_table.items():
                if v.name in dont_replace:
                    drop_keys.append(k)
            for k in drop_keys:
                var_table.pop(k)

            new_var_dict = {}
            for name, var in var_table.items():
                scope = switch_ir.blocks[lbl].scope
                try:
                    scope.get_exact(name)
                except errors.NotDefinedError:
                    # In case the scope doesn't have the variable, we need to
                    # define it prior creating new copies of it! This is
                    # because the scope of the function and the scope of the
                    # loop are different and the variable needs to be redefined
                    # within the scope of the loop.
                    scope.define(name, var.loc)
                new_var_dict[name] = scope.redefine(name, var.loc).name
            replace_var_names(loop_blocks, new_var_dict)

            # clobber the sentinel body and then stuff in the rest
            switch_ir.blocks[lbl] = deepcopy(loop_blocks[loop_start_lbl])
            remaining_keys = [y for y in loop_blocks.keys()]
            remaining_keys.remove(loop_start_lbl)
            for k in remaining_keys:
                switch_ir.blocks[k] = deepcopy(loop_blocks[k])

        if self._DEBUG:
            print("-" * 80 + "EXIT STUFFER")
            switch_ir.dump()
            print("-" * 80)

        return switch_ir

        Injects the "loop body" held in `loop_ir` into `switch_ir` where ever
        there is a statement of the form `SENTINEL.<int> = RHS`. It also:
        * Finds and then deliberately does not relabel non-local jumps so as to
          make the switch table suitable for injection into the IR from which
          the loop body was derived.
        * Looks for `typed_getitem` and wires them up to loop body version
          specific variables or, if possible, directly writes in their constant
          value at their use site.

        Args:
        - switch_ir, the switch table with SENTINELS as generated by
          self.gen_switch
        - loop_ir, the IR of the loop blocks (derived from the original func_ir)
        - caller_max_label, the maximum label in the func_ir caller
        - dont_replace, variables that should not be renamed (to handle
          references to variables that are incoming at the loop head/escaping at
          the loop exit.
        - switch_data, the switch table data used to generated the switch_ir,
          can be generated by self.analyse_tuple.

        Returns:
        - A type specific switch table with each case containing a versioned
          loop body suitable for injection as a replacement for the loop_ir.
        
numba/core/untyped_passes.py