def _hard_fuse(self):
        """Fuse consecutive loops over different iteration sets that do not
        present RAW, WAR or WAW dependencies. For examples, two loops like: ::

            par_loop(kernel_1, it_space_1,
                     dat_1_1(INC, ...),
                     dat_1_2(READ, ...),
                     ...)

            par_loop(kernel_2, it_space_2,
                     dat_2_1(INC, ...),
                     dat_2_2(READ, ...),
                     ...)

        where ``dat_1_1 == dat_2_1`` and, possibly (but not necessarily),
        ``it_space_1 != it_space_2``, can be hard fused. Note, in fact, that
        the presence of ``INC`` does not imply a real WAR dependency, because
        increments are associative."""

        def has_raw_or_war(loop1, loop2):
            # Note that INC after WRITE is a special case of RAW dependency since
            # INC cannot take place before WRITE.
            return loop2.reads & loop1.writes or loop2.writes & loop1.reads or \
                loop1.incs & (loop2.writes - loop2.incs) or \
                loop2.incs & (loop1.writes - loop1.incs)

        def has_iai(loop1, loop2):
            return loop1.incs & loop2.incs

        def fuse(base_loop, loop_chain, fused):
            """Try to fuse one of the loops in ``loop_chain`` with ``base_loop``."""
            for loop in loop_chain:
                if has_raw_or_war(loop, base_loop):
                    # Can't fuse across loops preseting RAW or WAR dependencies
                    return
                if loop.it_space == base_loop.it_space:
                    warning("Ignoring unexpected sequence of loops in loop fusion")
                    continue
                # Is there an overlap in any incremented regions? If that is
                # the case, then fusion can really be useful, by allowing to
                # save on the number of indirect increments or matrix insertions
                common_inc_data = has_iai(base_loop, loop)
                if not common_inc_data:
                    continue
                common_incs = [a for a in base_loop.args + loop.args
                               if a.data in common_inc_data]
                # Hard fusion potentially doable provided that we own a map between
                # the iteration spaces involved
                maps = list(set(flatten([a.map for a in common_incs])))
                maps += [m.factors for m in maps if hasattr(m, 'factors')]
                maps = list(flatten(maps))
                set1, set2 = base_loop.it_space.iterset, loop.it_space.iterset
                fused_map = [m for m in maps if set1 == m.iterset and set2 == m.toset]
                if fused_map:
                    fused.append((base_loop, loop, fused_map[0], common_incs[1]))
                    return
                fused_map = [m for m in maps if set1 == m.toset and set2 == m.iterset]
                if fused_map:
                    fused.append((loop, base_loop, fused_map[0], common_incs[0]))
                    return

        # First, find fusible kernels
        fused = []
        for i, l in enumerate(self._loop_chain, 1):
            fuse(l, self._loop_chain[i:], fused)
        if not fused:
            return

        # Then, create a suitable hard-fusion kernel
        # The hard fused kernel will have the following structure:
        #
        # wrapper (args: Union(kernel1, kernel2, extra):
        #   staging of pointers
        #   ...
        #   fusion (staged pointers, ..., extra)
        #   insertion (...)
        #
        # Where /extra/ represents additional arguments, like the map from
        # kernel1's iteration space to kernel2's iteration space. The /fusion/
        # function looks like:
        #
        # fusion (...):
        #   kernel1 (buffer, ...)
        #   for i = 0 to arity:
        #     if not already_executed[i]:
        #       kernel2 (buffer[..], ...)
        #
        # Where /arity/ is the number of kernel2's iterations incident to
        # kernel1's iterations.
        _fused = []
        for base_loop, fuse_loop, fused_map, fused_inc_arg in fused:
            # Start with analyzing the kernels' ASTs. Note: fusion occurs on fresh
            # copies of the /base/ and /fuse/ ASTs. This is because the optimization
            # of the /fused/ AST should be independent of that of individual ASTs,
            # and subsequent cache hits for non-fused ParLoops should always retrive
            # the original, unmodified ASTs. This is important not just for the
            # sake of performance, but also for correctness of padding, since hard
            # fusion changes the signature of /fuse/ (in particular, the buffers that
            # are provided for computation on iteration spaces)
            base, fuse = base_loop.kernel, fuse_loop.kernel
            base_ast = dcopy(base._original_ast) if base._code else dcopy(base._ast)
            retval = FindInstances.default_retval()
            base_info = FindInstances((ast.FunDecl, ast.PreprocessNode)).visit(base_ast, ret=retval)
            base_headers = base_info[ast.PreprocessNode]
            base_fundecl = base_info[ast.FunDecl]
            fuse_ast = dcopy(fuse._original_ast) if fuse._code else dcopy(fuse._ast)
            retval = FindInstances.default_retval()
            fuse_info = FindInstances((ast.FunDecl, ast.PreprocessNode)).visit(fuse_ast, ret=retval)
            fuse_headers = fuse_info[ast.PreprocessNode]
            fuse_fundecl = fuse_info[ast.FunDecl]
            retval = SymbolReferences.default_retval()
            fuse_symbol_refs = SymbolReferences().visit(fuse_ast, ret=retval)
            if len(base_fundecl) != 1 or len(fuse_fundecl) != 1:
                raise RuntimeError("Fusing kernels, but found unexpected AST")
            base_fundecl = base_fundecl[0]
            fuse_fundecl = fuse_fundecl[0]

            # Craft the /fusion/ kernel #

            # 1A) Create /fusion/ arguments and signature
            body = ast.Block([])
            fusion_name = '%s_%s' % (base_fundecl.name, fuse_fundecl.name)
            fusion_args = base_fundecl.args + fuse_fundecl.args
            fusion_fundecl = ast.FunDecl(base_fundecl.ret, fusion_name,
                                         fusion_args, body)

            # 1B) Filter out duplicate arguments, and append extra arguments to
            # the function declaration
            binding = self._filter_kernel_args([base_loop, fuse_loop], fusion_fundecl)
            fusion_fundecl.args += [ast.Decl('int**', ast.Symbol('executed'))]

            # 1C) Create /fusion/ body
            base_funcall_syms = [ast.Symbol(d.sym.symbol)
                                 for d in base_fundecl.args]
            base_funcall = ast.FunCall(base_fundecl.name, *base_funcall_syms)
            fuse_funcall_syms = [ast.Symbol(binding[arg].sym.symbol)
                                 for arg in fuse_loop.args]
            fuse_funcall = ast.FunCall(fuse_fundecl.name, *fuse_funcall_syms)
            if_cond = ast.Not(ast.Symbol('executed', ('i', 0)))
            if_update = ast.Assign(ast.Symbol('executed', ('i', 0)), ast.Symbol('1'))
            if_exec = ast.If(if_cond, [ast.Block([fuse_funcall, if_update],
                                                 open_scope=True)])
            fuse_body = ast.Block([if_exec], open_scope=True)
            fuse_for = ast.c_for('i', fused_map.arity, fuse_body, pragma=None)
            body.children.extend([base_funcall, fuse_for.children[0]])

            # Modify the /fuse/ kernel #
            # This is to take into account that many arguments are shared with
            # /base/, so they will only staged once for /base/. This requires
            # tweaking the way the arguments are declared and accessed in /fuse/
            # kernel. For example, the shared incremented array (called /buffer/
            # in the pseudocode in the comment above) now needs to take offsets
            # to be sure the locations that /base/ is supposed to increment are
            # actually accessed. The same concept apply to indirect arguments.
            ofs_syms, ofs_decls, ofs_vals = [], [], []
            init = lambda v: '{%s}' % ', '.join([str(j) for j in v])
            for i, fuse_args in enumerate(zip(fuse_loop.args, fuse_fundecl.args)):
                fuse_loop_arg, fuse_kernel_arg = fuse_args
                sym_id = fuse_kernel_arg.sym.symbol
                if fuse_loop_arg == fused_inc_arg:
                    # 2A) The shared incremented argument. A 'buffer' of statically
                    #     known size is expected by the kernel, so the offset is used
                    #     to index into it
                    # Note: the /fused_map/ is a factor of the /base/ iteration
                    # set map, so the order the /fuse/ loop's iterations are
                    # executed (in the /for i=0 to arity/ loop) reflects the order
                    # of the entries in /fused_map/
                    fuse_inc_refs = fuse_symbol_refs[sym_id]
                    fuse_inc_refs = [sym for sym, parent in fuse_inc_refs
                                     if not isinstance(parent, ast.Decl)]
                    # Handle the declaration
                    fuse_kernel_arg.sym.rank = binding[fused_inc_arg].sym.rank
                    for b in fused_inc_arg._block_shape:
                        for rc in b:
                            _ofs_vals = [[0] for j in range(len(rc))]
                            for j, ofs in enumerate(rc):
                                ofs_sym_id = 'm_ofs_%d_%d' % (i, j)
                                ofs_syms.append(ofs_sym_id)
                                ofs_decls.append(ast.Decl('int', ast.Symbol(ofs_sym_id)))
                                _ofs_vals[j].append(ofs)
                            for s in fuse_inc_refs:
                                s.offset = tuple((1, o) for o in ofs_syms)
                            ofs_vals.extend([init(o) for o in _ofs_vals])
                    # Tell COFFEE that the argument is not an empty buffer anymore,
                    # so any write to it must actually be an increment
                    fuse_kernel_arg.pragma = set([ast.INC])
                elif fuse_loop_arg._is_indirect:
                    # 2B) All indirect arguments. At the C level, these arguments
                    #     are of pointer type, so simple pointer arithmetic is used
                    #     to ensure the kernel accesses are to the correct locations
                    fuse_arity = fuse_loop_arg.map.arity
                    base_arity = fuse_arity*fused_map.arity
                    cdim = fuse_loop_arg.data.dataset.cdim
                    size = fuse_arity*cdim
                    if fuse_loop_arg._flatten and cdim > 1:
                        # Set the proper storage layout before invoking /fuse/
                        ofs_tmp = fuse_kernel_arg.sym.symbol % '_%s'
                        ofs_tmp_sym = ast.Symbol(ofs_tmp, (size,))
                        ofs_tmp_decl = ast.Decl('%s*' % fuse_loop_arg.ctype, ofs_tmp_sym)
                        _ofs_vals = [[base_arity*j + k for k in range(fuse_arity)]
                                     for j in range(cdim)]
                        _ofs_vals = [[fuse_arity*j + k for k in flatten(_ofs_vals)]
                                     for j in range(fused_map.arity)]
                        _ofs_vals = list(flatten(_ofs_vals))
                        ofs_idx_sym = 'v_i_ofs_%d' % i
                        body.children.insert(0, ast.Decl(
                            'int', ast.Symbol(ofs_idx_sym, (len(_ofs_vals),)),
                            ast.ArrayInit(init(_ofs_vals)), ['static', 'const']))
                        ofs_idx_syms = [ast.Symbol(ofs_idx_sym, ('i',), ((size, j),))
                                        for j in range(size)]
                        ofs_assigns = [ofs_tmp_decl]
                        ofs_assigns += [ast.Assign(ast.Symbol(ofs_tmp, (j,)),
                                                   ast.Symbol(sym_id, (k,)))
                                        for j, k in enumerate(ofs_idx_syms)]
                        # Need to reflect this onto the invocation of /fuse/
                        fuse_funcall.children[fuse_loop.args.index(fuse_loop_arg)] = \
                            ast.Symbol(ofs_tmp)
                    else:
                        # In this case, can just use offsets since it's not a
                        # multi-dimensional Dat
                        ofs_sym = ast.Symbol('ofs', (len(ofs_vals), 'i'))
                        ofs_assigns = [ast.Assign(sym_id, ast.Sum(sym_id, ofs_sym))]
                        ofs_vals.append(init([j*size for j in range(fused_map.arity)]))
                    if_exec.children[0].children[0:0] = ofs_assigns
            # Now change the /fusion/ kernel body accordingly
            body.children.insert(0, ast.Decl(
                'int', ast.Symbol('ofs', (len(ofs_vals), fused_map.arity)),
                ast.ArrayInit(init(ofs_vals)), ['static', 'const']))
            if_exec.children[0].children[0:0] = \
                [ast.Decl('int', ast.Symbol(s), ast.Symbol('ofs', (i, 'i')))
                 for i, s in enumerate(ofs_syms)]

            # 2C) Change /fuse/ kernel invocation, declaration, and body
            fuse_funcall.children.extend([ast.Symbol(s) for s in ofs_syms])
            fuse_fundecl.args.extend(ofs_decls)

            # 2D) Hard fusion breaks any padding applied to the /fuse/ kernel, so
            # this transformation pass needs to be re-performed;

            # Create a /fusion.Kernel/ object to be used to update the schedule
            fused_headers = set([str(h) for h in base_headers + fuse_headers])
            fused_ast = ast.Root([ast.PreprocessNode(h) for h in fused_headers] +
                                 [base_fundecl, fuse_fundecl, fusion_fundecl])
            kernels = [base, fuse]
            loop_chain_index = (self._loop_chain.index(base_loop),
                                self._loop_chain.index(fuse_loop))
            _fused.append((Kernel(kernels, fused_ast, loop_chain_index), fused_map))

        # Finally, generate a new schedule
        self._schedule = HardFusionSchedule(self._schedule, _fused)
        self._loop_chain = self._schedule(self._loop_chain, only_hard=True)
dataset/ETHPy150Open OP2/PyOP2/pyop2/fusion.py/Inspector._hard_fuse/swapped operands of `%`