def peep_hole_fuse_dict_add_updates(func_ir):
    """
    This rewrite removes d1._update_from_bytecode(d2)
    calls that are between two dictionaries, d1 and d2,
    in the same basic block. This pattern can appear as a
    result of Python 3.10 bytecode emission changes, which
    prevent large constant literal dictionaries
    (> 15 elements) from being constant. If both dictionaries
    are constant dictionaries defined in the same block and
    neither is used between the update call, then we replace d1
    with a new definition that combines the two dictionaries. At
    the bytecode translation stage we convert DICT_UPDATE into
    _update_from_bytecode, so we know that _update_from_bytecode
    always comes from the bytecode change and not user code.

    Python 3.10 may also rewrite the individual dictionaries
    as an empty build_map + many map_add. Here we again look
    for an _update_from_bytecode, and if so we replace these
    with a single constant dictionary.

    When running this algorithm we can always safely remove d2.

    This is the relevant section of the CPython 3.10 that causes
    this bytecode change:
    https://github.com/python/cpython/blob/3.10/Python/compile.c#L4048
    """

    # This algorithm fuses build_map expressions into the largest
    # possible build map before use. For example, if we have an
    # IR that looks like this:
    #
    #   $d1 = build_map([])
    #   $key = const("a")
    #   $value = const(2)
    #   $setitem_func = getattr($d1, "__setitem__")
    #   $unused1 = call (setitem_func, ($key, $value))
    #   $key2 = const("b")
    #   $value2 = const(3)
    #   $d2 = build_map([($key2, $value2)])
    #   $update_func = getattr($d1, "_update_from_bytecode")
    #   $unused2 = call ($update_func, ($d2,))
    #   $othervar = None
    #   $retvar = cast($othervar)
    #   return $retvar
    #
    # Then the IR is rewritten such that any __setitem__ and
    # _update_from_bytecode operations are fused into the original buildmap.
    # The new buildmap is then added to the
    # last location where it had previously had encountered a __setitem__,
    # _update_from_bytecode, or build_map before any other uses.
    # The new IR would look like:
    #
    #   $key = const("a")
    #   $value = const(2)
    #   $key2 = const("b")
    #   $value2 = const(3)
    #   $d1 = build_map([($key, $value), ($key2, $value2)])
    #   $othervar = None
    #   $retvar = cast($othervar)
    #   return $retvar
    #
    # Note that we don't push $d1 to the bottom of the block. This is because
    # some values may be found below this block (e.g pop_block) that are pattern
    # matched in other locations, such as objmode handling. It should be safe to
    # move a map to the last location at which there was _update_from_bytecode.

    errmsg = textwrap.dedent("""
        A DICT_UPDATE op-code was encountered that could not be replaced.
        If you have created a large constant dictionary, this may
        be an an indication that you are using inlined control
        flow. You can resolve this issue by moving the control flow out of
        the dicitonary constructor. For example, if you have

            d = {a: 1 if flag else 0, ...)

        Replace that with:

            a_val = 1 if flag else 0
            d = {a: a_val, ...)""")

    already_deleted_defs = collections.defaultdict(set)
    for blk in func_ir.blocks.values():
        new_body = []
        # literal map var name -> block idx of the original build_map
        lit_map_def_idx = {}
        # literal map var name -> list(map_uses)
        # This is the index of every build_map or __setitem__
        # in the IR that will need to be removed if the map
        # is updated.
        lit_map_use_idx = collections.defaultdict(list)
        # literal map var name -> list of key/value items for build map
        map_updates = {}
        blk_changed = False

        for i, stmt in enumerate(blk.body):
            # What instruction should we append
            new_inst = stmt
            # Name that should be skipped when tracking used
            # vars in statement. This is always the lhs with
            # a build_map.
            stmt_build_map_out = None
            if isinstance(stmt, ir.Assign) and isinstance(stmt.value, ir.Expr):
                if stmt.value.op == "build_map":
                    # Skip the output build_map when looking for used vars.
                    stmt_build_map_out = stmt.target.name
                    # If we encounter a build map add it to the
                    # tracked maps.
                    lit_map_def_idx[stmt.target.name] = i
                    lit_map_use_idx[stmt.target.name].append(i)
                    map_updates[stmt.target.name] = stmt.value.items.copy()
                elif stmt.value.op == "call" and i > 0:
                    # If we encounter a call we may need to replace
                    # the body
                    func_name = stmt.value.func.name
                    # If we have an update or a setitem
                    # it will be the previous expression.
                    getattr_stmt = blk.body[i - 1]
                    args = stmt.value.args
                    if (
                        isinstance(getattr_stmt, ir.Assign)
                        and getattr_stmt.target.name == func_name
                        and isinstance(getattr_stmt.value, ir.Expr)
                        and getattr_stmt.value.op == "getattr"
                        and getattr_stmt.value.attr in (
                            "__setitem__", "_update_from_bytecode"
                        )
                    ):
                        update_map_name = getattr_stmt.value.value.name
                        attr = getattr_stmt.value.attr
                        if (attr == "__setitem__"
                           and update_map_name in lit_map_use_idx):
                            # If we have a setitem, update the lists
                            map_updates[update_map_name].append(args)
                            # Update the list of instructions that would
                            # need to be removed to include the setitem
                            # and the the getattr
                            lit_map_use_idx[update_map_name].extend([i - 1, i])
                        elif attr == "_update_from_bytecode":
                            d2_map_name = args[0].name
                            if (update_map_name in lit_map_use_idx
                               and d2_map_name in lit_map_use_idx):
                                # If we have an update and the arg is also
                                # a literal dictionary, fuse the lists.
                                map_updates[update_map_name].extend(
                                    map_updates[d2_map_name]
                                )
                                # Delete the old IR for d1 and d2
                                lit_map_use_idx[update_map_name].extend(
                                    lit_map_use_idx[d2_map_name]
                                )
                                lit_map_use_idx[update_map_name].append(i - 1)
                                for linenum in lit_map_use_idx[update_map_name]:
                                    # Drop the existing definition.
                                    _remove_assignment_definition(
                                        blk.body,
                                        linenum,
                                        func_ir,
                                        already_deleted_defs,
                                    )
                                    # Delete it from the new block
                                    new_body[linenum] = None
                                # Delete the maps from dicts
                                del lit_map_def_idx[d2_map_name]
                                del lit_map_use_idx[d2_map_name]
                                del map_updates[d2_map_name]
                                # Add d1 as the new instruction, removing the
                                # old definition.
                                _remove_assignment_definition(
                                    blk.body, i, func_ir, already_deleted_defs
                                )
                                new_inst = _build_new_build_map(
                                    func_ir,
                                    update_map_name,
                                    blk.body,
                                    lit_map_def_idx[update_map_name],
                                    map_updates[update_map_name],
                                )
                                # Update d1 in lit_map_use_idx to just the new
                                # definition and clear the previous list.
                                lit_map_use_idx[update_map_name].clear()
                                lit_map_use_idx[update_map_name].append(i)
                                # Mark that this block has been modified
                                blk_changed = True
                            else:
                                # If we cannot remove _update_from_bytecode
                                # Then raise an error for the user.
                                raise UnsupportedBytecodeError(errmsg)

            # Check if we need to drop any maps from being tracked.
            # Skip the setitem/_update_from_bytecode getattr that
            # will be removed when handling their call in the next
            # iteration.
            if not (
                isinstance(stmt, ir.Assign)
                and isinstance(stmt.value, ir.Expr)
                and stmt.value.op == "getattr"
                and stmt.value.value.name in lit_map_use_idx
                and stmt.value.attr in ("__setitem__", "_update_from_bytecode")
            ):
                for var in stmt.list_vars():
                    # If a map is used it cannot be fused later in
                    # the block. As a result we delete it from
                    # the dicitonaries
                    if (
                        var.name in lit_map_use_idx
                        and var.name != stmt_build_map_out
                    ):
                        del lit_map_def_idx[var.name]
                        del lit_map_use_idx[var.name]
                        del map_updates[var.name]

            # Append the instruction to the new block
            new_body.append(new_inst)

        if blk_changed:
            # If the block is changed replace the block body.
            blk.body.clear()
            blk.body.extend([x for x in new_body if x is not None])

    return func_ir

    This rewrite removes d1._update_from_bytecode(d2)
    calls that are between two dictionaries, d1 and d2,
    in the same basic block. This pattern can appear as a
    result of Python 3.10 bytecode emission changes, which
    prevent large constant literal dictionaries
    (> 15 elements) from being constant. If both dictionaries
    are constant dictionaries defined in the same block and
    neither is used between the update call, then we replace d1
    with a new definition that combines the two dictionaries. At
    the bytecode translation stage we convert DICT_UPDATE into
    _update_from_bytecode, so we know that _update_from_bytecode
    always comes from the bytecode change and not user code.

    Python 3.10 may also rewrite the individual dictionaries
    as an empty build_map + many map_add. Here we again look
    for an _update_from_bytecode, and if so we replace these
    with a single constant dictionary.

    When running this algorithm we can always safely remove d2.

    This is the relevant section of the CPython 3.10 that causes
    this bytecode change:
    https://github.com/python/cpython/blob/3.10/Python/compile.c#L4048
    
numba/core/interpreter.py