def get_reduce_nodes(reduction_node, nodes, func_ir):
    """
    Get nodes that combine the reduction variable with a sentinel variable.
    Recognizes the first node that combines the reduction variable with another
    variable.
    """
    reduce_nodes = None
    defs = {}

    def cyclic_lookup(var, varonly=True, start=None):
        """Lookup definition of ``var``.
        Returns ``None`` if variable definition forms a cycle.
        """
        lookedup_var = defs.get(var.name, None)
        if isinstance(lookedup_var, ir.Var):
            if start is None:
                start = lookedup_var
            elif start == lookedup_var:
                # cycle detected
                return None
            return cyclic_lookup(lookedup_var, start=start)
        else:
            return var if (varonly or lookedup_var is None) else lookedup_var

    def noncyclic_lookup(*args, **kwargs):
        """Similar to cyclic_lookup but raise AssertionError if a cycle is
        detected.
        """
        res = cyclic_lookup(*args, **kwargs)
        if res is None:
            raise AssertionError("unexpected cycle in lookup()")
        return res

    name = reduction_node.name
    unversioned_name = reduction_node.unversioned_name
    for i, stmt in enumerate(nodes):
        lhs = stmt.target
        rhs = stmt.value
        defs[lhs.name] = rhs
        if isinstance(rhs, ir.Var) and rhs.name in defs:
            rhs = cyclic_lookup(rhs)
        if isinstance(rhs, ir.Expr):
            in_vars = set(noncyclic_lookup(v, True).name
                          for v in rhs.list_vars())
            if name in in_vars:
                # reductions like sum have an assignment afterwards
                # e.g. $2 = a + $1; a = $2
                # reductions that are functions calls like max() don't have an
                # extra assignment afterwards

                # This code was created when Numba had an IR generation strategy
                # where a binop for a reduction would be followed by an
                # assignment as follows:
                #$c.4.15 = inplace_binop(fn=<iadd>, ...>, lhs=c.3, rhs=$const20)
                #c.4 = $c.4.15

                # With Python 3.12 changes, Numba may separate that assignment
                # to a new basic block.  The code below looks and sees if an
                # assignment to the reduction var follows the reduction operator
                # and if not it searches the rest of the reduction nodes to find
                # the assignment that should follow the reduction operator
                # and then reorders the reduction nodes so that assignment
                # follows the reduction operator.
                if (i + 1 < len(nodes) and
                    ((not isinstance(nodes[i + 1], ir.Assign)) or
                     nodes[i + 1].target.unversioned_name != unversioned_name)):
                    foundj = None
                    # Iterate through the rest of the reduction nodes.
                    for j, jstmt in enumerate(nodes[i + 1:]):
                        # If this stmt is an assignment where the right-hand
                        # side of the assignment is the output of the reduction
                        # operator.
                        if isinstance(jstmt, ir.Assign) and jstmt.value == lhs:
                            # Remember the index of this node.  Because of
                            # nodes[i+1] above, we have to add i + 1 to j below
                            # to get the index in the original nodes list.
                            foundj = i + j + 1
                            break
                    if foundj is not None:
                        # If we found the correct assignment then move it to
                        # after the reduction operator.
                        nodes = (nodes[:i + 1] +   # nodes up to operator
                                 nodes[foundj:foundj + 1] + # assignment node
                                 nodes[i + 1:foundj] + # between op and assign
                                 nodes[foundj + 1:]) # after assignment node

                if (not (i+1 < len(nodes) and isinstance(nodes[i+1], ir.Assign)
                        and nodes[i+1].target.unversioned_name == unversioned_name)
                        and lhs.unversioned_name != unversioned_name):
                    raise ValueError(
                        f"Use of reduction variable {unversioned_name!r} other "
                        "than in a supported reduction function is not "
                        "permitted."
                    )

                if not supported_reduction(rhs, func_ir):
                    raise ValueError(("Use of reduction variable " + unversioned_name +
                                      " in an unsupported reduction function."))
                args = [(x.name, noncyclic_lookup(x, True))
                        for x in get_expr_args(rhs) ]
                non_red_args = [ x for (x, y) in args if y.name != name ]
                assert len(non_red_args) == 1
                args = [ (x, y) for (x, y) in args if x != y.name ]
                replace_dict = dict(args)
                replace_dict[non_red_args[0]] = ir.Var(lhs.scope, name+"#init", lhs.loc)
                replace_vars_inner(rhs, replace_dict)
                reduce_nodes = nodes[i:]
                break
    return reduce_nodes

    Get nodes that combine the reduction variable with a sentinel variable.
    Recognizes the first node that combines the reduction variable with another
    variable.
    
numba/parfors/parfor.py